一、數據類型
包裝類型
八個基本類型:
- boolean/1
- byte/8
- char/16
- short/16
- int/32
- float/32
- long/64
- double/64
基本類型都有對應的包裝類型,基本類型與其對應的包裝類型之間的賦值使用自動裝箱與拆箱完成。
1
2
|
Integer x =
2
;
// 裝箱
int
y = x;
// 拆箱
|
緩存池
new Integer(123) 與 Integer.valueOf(123) 的區別在於:
- new Integer(123) 每次都會新建一個對象
- Integer.valueOf(123) 會使用緩存池中的對象,多次調用會取得同一個對象的引用。
1
2
3
4
5
6
|
Integer x =
new
Integer(
123
);
Integer y =
new
Integer(
123
);
System.out.println(x == y);
// false
Integer z = Integer.valueOf(
123
);
Integer k = Integer.valueOf(
123
);
System.out.println(z == k);
// true
|
valueOf() 方法的實現比較簡單,就是先判斷值是否在緩存池中,如果在的話就直接返回緩存池的內容。
1
2
3
4
5
|
public
static
Integer valueOf(
int
i) {
if
(i >= IntegerCache.low && i <= IntegerCache.high)
return
IntegerCache.cache[i + (-IntegerCache.low)];
return
new
Integer(i);
}
|
在 Java 8 中,Integer 緩存池的大小默認為 -128~127。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
static
final
int
low = -
128
;
static
final
int
high;
static
final
Integer cache[];
static
{
// high value may be configured by property
int
h =
127
;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty(
"java.lang.Integer.IntegerCache.high"
);
if
(integerCacheHighPropValue !=
null
) {
try
{
int
i = parseInt(integerCacheHighPropValue);
i = Math.max(i,
127
);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -
1
);
}
catch
( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache =
new
Integer[(high - low) +
1
];
int
j = low;
for
(
int
k =
0
; k < cache.length; k++)
cache[k] =
new
Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert
IntegerCache.high >=
127
;
}
|
編譯器會在自動裝箱過程調用 valueOf() 方法,因此多個 Integer 實例使用自動裝箱來創建並且值相同,那么就會引用相同的對象。
1
2
3
|
Integer m =
123
;
Integer n =
123
;
System.out.println(m == n);
// true
|
基本類型對應的緩沖池如下:
- boolean values true and false
- all byte values
- short values between -128 and 127
- int values between -128 and 127
- char in the range \u0000 to \u007F
在使用這些基本類型對應的包裝類型時,就可以直接使用緩沖池中的對象。
StackOverflow : Differences between new Integer(123), Integer.valueOf(123) and just 123
二、String
概覽
String 被聲明為 final,因此它不可被繼承。
內部使用 char 數組存儲數據,該數組被聲明為 final,這意味着 value 數組初始化之后就不能再引用其它數組。並且 String 內部沒有改變 value 數組的方法,因此可以保證 String 不可變。
1
2
3
4
|
public
final
class
String
implements
java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private
final
char
value[];
|
不可變的好處
1. 可以緩存 hash 值
因為 String 的 hash 值經常被使用,例如 String 用做 HashMap 的 key。不可變的特性可以使得 hash 值也不可變,因此只需要進行一次計算。
2. String Pool 的需要
如果一個 String 對象已經被創建過了,那么就會從 String Pool 中取得引用。只有 String 是不可變的,才可能使用 String Pool。
3. 安全性
String 經常作為參數,String 不可變性可以保證參數不可變。例如在作為網絡連接參數的情況下如果 String 是可變的,那么在網絡連接過程中,String 被改變,改變 String 對象的那一方以為現在連接的是其它主機,而實際情況卻不一定是。
4. 線程安全
String 不可變性天生具備線程安全,可以在多個線程中安全地使用。
Program Creek : Why String is immutable in Java?
String, StringBuffer and StringBuilder
1. 可變性
- String 不可變
- StringBuffer 和 StringBuilder 可變
2. 線程安全
- String 不可變,因此是線程安全的
- StringBuilder 不是線程安全的
- StringBuffer 是線程安全的,內部使用 synchronized 進行同步
StackOverflow : String, StringBuffer, and StringBuilder
String.intern()
使用 String.intern() 可以保證相同內容的字符串變量引用同一的內存對象。
下面示例中,s1 和 s2 采用 new String() 的方式新建了兩個不同對象,而 s3 是通過 s1.intern() 方法取得一個對象引用。intern() 首先把 s1 引用的對象放到 String Pool(字符串常量池)中,然后返回這個對象引用。因此 s3 和 s1 引用的是同一個字符串常量池的對象。
1
2
3
4
5
|
String s1 =
new
String(
"aaa"
);
String s2 =
new
String(
"aaa"
);
System.out.println(s1 == s2);
// false
String s3 = s1.intern();
System.out.println(s1.intern() == s3);
// true
|
如果是采用 "bbb" 這種使用雙引號的形式創建字符串實例,會自動地將新建的對象放入 String Pool 中。
1
2
3
|
String s4 =
"bbb"
;
String s5 =
"bbb"
;
System.out.println(s4 == s5);
// true
|
在 Java 7 之前,字符串常量池被放在運行時常量池中,它屬於永久代。而在 Java 7,字符串常量池被移到 Native Method 中。這是因為永久代的空間有限,在大量使用字符串的場景下會導致 OutOfMemoryError 錯誤。
三、運算
參數傳遞
Java 的參數是以值傳遞的形式傳入方法中,而不是引用傳遞。
以下代碼中 Dog dog 的 dog 是一個指針,存儲的是對象的地址。在將一個參數傳入一個方法時,本質上是將對象的地址以值的方式傳遞到形參中。因此在方法中改變指針引用的對象,那么這兩個指針此時指向的是完全不同的對象,一方改變其所指向對象的內容對另一方沒有影響。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public
class
Dog {
String name;
Dog(String name) {
this
.name = name;
}
String getName() {
return
this
.name;
}
void
setName(String name) {
this
.name = name;
}
String getObjectAddress() {
return
super
.toString();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
class
PassByValueExample {
public
static
void
main(String[] args) {
Dog dog =
new
Dog(
"A"
);
System.out.println(dog.getObjectAddress());
// Dog@4554617c
func(dog);
System.out.println(dog.getObjectAddress());
// Dog@4554617c
System.out.println(dog.getName());
// A
}
private
static
void
func(Dog dog) {
System.out.println(dog.getObjectAddress());
// Dog@4554617c
dog =
new
Dog(
"B"
);
System.out.println(dog.getObjectAddress());
// Dog@74a14482
System.out.println(dog.getName());
// B
}
}
|
但是如果在方法中改變對象的字段值會改變原對象該字段值,因為改變的是同一個地址指向的內容。
1
2
3
4
5
6
7
8
9
10
11
|
class
PassByValueExample {
public
static
void
main(String[] args) {
Dog dog =
new
Dog(
"A"
);
func(dog);
System.out.println(dog.getName());
// B
}
private
static
void
func(Dog dog) {
dog.setName(
"B"
);
}
}
|
StackOverflow: Is Java “pass-by-reference” or “pass-by-value”?
float 與 double
1.1 字面量屬於 double 類型,不能直接將 1.1 直接賦值給 float 變量,因為這是向下轉型。Java 不能隱式執行向下轉型,因為這會使得精度降低。
1
|
// float f = 1.1;
|
1.1f 字面量才是 float 類型。
1
|
float
f =
1
.1f;
|
隱式類型轉換
因為字面量 1 是 int 類型,它比 short 類型精度要高,因此不能隱式地將 int 類型下轉型為 short 類型。
1
2
|
short
s1 =
1
;
// s1 = s1 + 1;
|
但是使用 += 運算符可以執行隱式類型轉換。
1
|
s1 +=
1
;
|
上面的語句相當於將 s1 + 1 的計算結果進行了向下轉型:
1
|
s1 = (
short
) (s1 +
1
);
|
StackOverflow : Why don't Java's +=, -=, *=, /= compound assignment operators require casting?
switch
從 Java 7 開始,可以在 switch 條件判斷語句中使用 String 對象。
1
2
3
4
5
6
7
8
9
|
String s =
"a"
;
switch
(s) {
case
"a"
:
System.out.println(
"aaa"
);
break
;
case
"b"
:
System.out.println(
"bbb"
);
break
;
}
|
switch 不支持 long,是因為 switch 的設計初衷是對那些只有少數的幾個值進行等值判斷,如果值過於復雜,那么還是用 if 比較合適。
1
2
3
4
5
6
7
8
9
|
// long x = 111;
// switch (x) { // Incompatible types. Found: 'long', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'
// case 111:
// System.out.println(111);
// break;
// case 222:
// System.out.println(222);
// break;
// }
|
StackOverflow : Why can't your switch statement data type be long, Java?
四、繼承
訪問權限
Java 中有三個訪問權限修飾符:private、protected 以及 public,如果不加訪問修飾符,表示包級可見。
可以對類或類中的成員(字段以及方法)加上訪問修飾符。
- 類可見表示其它類可以用這個類創建實例對象。
- 成員可見表示其它類可以用這個類的實例對象訪問到該成員;
protected 用於修飾成員,表示在繼承體系中成員對於子類可見,但是這個訪問修飾符對於類沒有意義。
設計良好的模塊會隱藏所有的實現細節,把它的 API 與它的實現清晰地隔離開來。模塊之間只通過它們的 API 進行通信,一個模塊不需要知道其他模塊的內部工作情況,這個概念被稱為信息隱藏或封裝。因此訪問權限應當盡可能地使每個類或者成員不被外界訪問。
如果子類的方法重寫了父類的方法,那么子類中該方法的訪問級別不允許低於父類的訪問級別。這是為了確保可以使用父類實例的地方都可以使用子類實例,也就是確保滿足里氏替換原則。
字段決不能是公有的,因為這么做的話就失去了對這個字段修改行為的控制,客戶端可以對其隨意修改。例如下面的例子中,AccessExample 擁有 id 共有字段,如果在某個時刻,我們想要使用 int 去存儲 id 字段,那么就需要去修改所有的客戶端代碼。
1
2
3
|
public
class
AccessExample {
public
String id;
}
|
可以使用公有的 getter 和 setter 方法來替換公有字段,這樣的話就可以控制對字段的修改行為。
1
2
3
4
5
6
7
8
9
10
11
12
|
public
class
AccessExample {
private
int
id;
public
String getId() {
return
id +
""
;
}
public
void
setId(String id) {
this
.id = Integer.valueOf(id);
}
}
|
但是也有例外,如果是包級私有的類或者私有的嵌套類,那么直接暴露成員不會有特別大的影響。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
class
AccessWithInnerClassExample {
private
class
InnerClass {
int
x;
}
private
InnerClass innerClass;
public
AccessWithInnerClassExample() {
innerClass =
new
InnerClass();
}
public
int
getValue() {
return
innerClass.x;
// 直接訪問
}
}
|
抽象類與接口
1. 抽象類
抽象類和抽象方法都使用 abstract 關鍵字進行聲明。抽象類一般會包含抽象方法,抽象方法一定位於抽象類中。
抽象類和普通類最大的區別是,抽象類不能被實例化,需要繼承抽象類才能實例化其子類。
1
2
3
4
5
6
7
8
9
10
11
|
public
abstract
class
AbstractClassExample {
protected
int
x;
private
int
y;
public
abstract
void
func1();
public
void
func2() {
System.out.println(
"func2"
);
}
}
|
1
2
3
4
5
6
|
public
class
AbstractExtendClassExample
extends
AbstractClassExample {
@Override
public
void
func1() {
System.out.println(
"func1"
);
}
}
|
1
2
3
|
// AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated
AbstractClassExample ac2 =
new
AbstractExtendClassExample();
ac2.func1();
|
2. 接口
接口是抽象類的延伸,在 Java 8 之前,它可以看成是一個完全抽象的類,也就是說它不能有任何的方法實現。
從 Java 8 開始,接口也可以擁有默認的方法實現,這是因為不支持默認方法的接口的維護成本太高了。在 Java 8 之前,如果一個接口想要添加新的方法,那么要修改所有實現了該接口的類。
接口的成員(字段 + 方法)默認都是 public 的,並且不允許定義為 private 或者 protected。
接口的字段默認都是 static 和 final 的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
interface
InterfaceExample {
void
func1();
default
void
func2(){
System.out.println(
"func2"
);
}
int
x =
123
;
// int y; // Variable 'y' might not have been initialized
public
int
z =
0
;
// Modifier 'public' is redundant for interface fields
// private int k = 0; // Modifier 'private' not allowed here
// protected int l = 0; // Modifier 'protected' not allowed here
// private void fun3(); // Modifier 'private' not allowed here
}
|
1
2
3
4
5
6
|
public
class
InterfaceImplementExample
implements
InterfaceExample {
@Override
public
void
func1() {
System.out.println(
"func1"
);
}
}
|
1
2
3
4
|
// InterfaceExample ie1 = new InterfaceExample(); // 'InterfaceExample' is abstract; cannot be instantiated
InterfaceExample ie2 =
new
InterfaceImplementExample();
ie2.func1();
System.out.println(InterfaceExample.x);
|
3. 比較
- 從設計層面上看,抽象類提供了一種 IS-A 關系,那么就必須滿足里式替換原則,即子類對象必須能夠替換掉所有父類對象。而接口更像是一種 LIKE-A 關系,它只是提供一種方法實現契約,並不要求接口和實現接口的類具有 IS-A 關系。
- 從使用上來看,一個類可以實現多個接口,但是不能繼承多個抽象類。
- 接口的字段只能是 static 和 final 類型的,而抽象類的字段沒有這種限制。
- 接口的成員只能是 public 的,而抽象類的成員可以有多種訪問權限。
4. 使用選擇
使用接口:
- 需要讓不相關的類都實現一個方法,例如不相關的類都可以實現 Compareable 接口中的 compareTo() 方法;
- 需要使用多重繼承。
使用抽象類:
- 需要在幾個相關的類中共享代碼。
- 需要能控制繼承來的成員的訪問權限,而不是都為 public。
- 需要繼承非靜態和非常量字段。
在很多情況下,接口優先於抽象類,因為接口沒有抽象類嚴格的類層次結構要求,可以靈活地為一個類添加行為。並且從 Java 8 開始,接口也可以有默認的方法實現,使得修改接口的成本也變的很低。
super
- 訪問父類的構造函數:可以使用 super() 函數訪問父類的構造函數,從而委托父類完成一些初始化的工作。
- 訪問父類的成員:如果子類重寫了父類的中某個方法的實現,可以通過使用 super 關鍵字來引用父類的方法實現。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class
SuperExample {
protected
int
x;
protected
int
y;
public
SuperExample(
int
x,
int
y) {
this
.x = x;
this
.y = y;
}
public
void
func() {
System.out.println(
"SuperExample.func()"
);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
class
SuperExtendExample
extends
SuperExample {
private
int
z;
public
SuperExtendExample(
int
x,
int
y,
int
z) {
super
(x, y);
this
.z = z;
}
@Override
public
void
func() {
super
.func();
System.out.println(
"SuperExtendExample.func()"
);
}
}
|
1
2
|
SuperExample e =
new
SuperExtendExample(
1
,
2
,
3
);
e.func();
|
1
2
|
SuperExample.func()
SuperExtendExample.func()
|
重寫與重載
1. 重寫(Override)
存在於繼承體系中,指子類實現了一個與父類在方法聲明上完全相同的一個方法。
為了滿足里式替換原則,重寫有有以下兩個限制:
- 子類方法的訪問權限必須大於等於父類方法;
- 子類方法的返回類型必須是父類方法返回類型或為其子類型。
使用 @Override 注解,可以讓編譯器幫忙檢查是否滿足上面的兩個限制條件。
2. 重載(Overload)
存在於同一個類中,指一個方法與已經存在的方法名稱上相同,但是參數類型、個數、順序至少有一個不同。
應該注意的是,返回值不同,其它都相同不算是重載。
五、Object 通用方法
概覽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public
final
native
Class<?> getClass()
public
native
int
hashCode()
public
boolean
equals(Object obj)
protected
native
Object clone()
throws
CloneNotSupportedException
public
String toString()
public
final
native
void
notify()
public
final
native
void
notifyAll()
public
final
native
void
wait(
long
timeout)
throws
InterruptedException
public
final
void
wait(
long
timeout,
int
nanos)
throws
InterruptedException
public
final
void
wait()
throws
InterruptedException
protected
void
finalize()
throws
Throwable {}
|
equals()
1. 等價關系
(一)自反性
1
|
x.equals(x);
// true
|
(二)對稱性
1
|
x.equals(y) == y.equals(x);
// true
|
(三)傳遞性
1
2
|
if
(x.equals(y) && y.equals(z))
x.equals(z);
// true;
|
(四)一致性
多次調用 equals() 方法結果不變
1
|
x.equals(y) == x.equals(y);
// true
|
(五)與 null 的比較
對任何不是 null 的對象 x 調用 x.equals(null) 結果都為 false
1
|
x.equals(
null
);
// false;
|
2. equals() 與 ==
- 對於基本類型,== 判斷兩個值是否相等,基本類型沒有 equals() 方法。
- 對於引用類型,== 判斷兩個變量是否引用同一個對象,而 equals() 判斷引用的對象是否等價。
1
2
3
4
|
Integer x =
new
Integer(
1
);
Integer y =
new
Integer(
1
);
System.out.println(x.equals(y));
// true
System.out.println(x == y);
// false
|
3. 實現
- 檢查是否為同一個對象的引用,如果是直接返回 true;
- 檢查是否是同一個類型,如果不是,直接返回 false;
- 將 Object 對象進行轉型;
- 判斷每個關鍵域是否相等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
class
EqualExample {
private
int
x;
private
int
y;
private
int
z;
public
EqualExample(
int
x,
int
y,
int
z) {
this
.x = x;
this
.y = y;
this
.z = z;
}
@Override
public
boolean
equals(Object o) {
if
(
this
== o)
return
true
;
if
(o ==
null
|| getClass() != o.getClass())
return
false
;
EqualExample that = (EqualExample) o;
if
(x != that.x)
return
false
;
if
(y != that.y)
return
false
;
return
z == that.z;
}
}
|
hashCode()
hasCode() 返回散列值,而 equals() 是用來判斷兩個對象是否等價。等價的兩個對象散列值一定相同,但是散列值相同的兩個對象不一定等價。
在覆蓋 equals() 方法時應當總是覆蓋 hashCode() 方法,保證等價的兩個對象散列值也相等。
下面的代碼中,新建了兩個等價的對象,並將它們添加到 HashSet 中。我們希望將這兩個對象當成一樣的,只在集合中添加一個對象,但是因為 EqualExample 沒有實現 hasCode() 方法,因此這兩個對象的散列值是不同的,最終導致集合添加了兩個等價的對象。
1
2
3
4
5
6
7
|
EqualExample e1 =
new
EqualExample(
1
,
1
,
1
);
EqualExample e2 =
new
EqualExample(
1
,
1
,
1
);
System.out.println(e1.equals(e2));
// true
HashSet<EqualExample> set =
new
HashSet<>();
set.add(e1);
set.add(e2);
System.out.println(set.size());
// 2
|
理想的散列函數應當具有均勻性,即不相等的對象應當均勻分布到所有可能的散列值上。這就要求了散列函數要把所有域的值都考慮進來,可以將每個域都當成 R 進制的某一位,然后組成一個 R 進制的整數。R 一般取 31,因為它是一個奇素數,如果是偶數的話,當出現乘法溢出,信息就會丟失,因為與 2 相乘相當於向左移一位。
一個數與 31 相乘可以轉換成移位和減法:31*x == (x<<5)-x
,編譯器會自動進行這個優化。
1
2
3
4
5
6
7
8
|
@Override
public
int
hashCode() {
int
result =
17
;
result =
31
* result + x;
result =
31
* result + y;
result =
31
* result + z;
return
result;
}
|
toString()
默認返回 ToStringExample@4554617c 這種形式,其中 @ 后面的數值為散列碼的無符號十六進制表示。
1
2
3
4
5
6
7
|
public
class
ToStringExample {
private
int
number;
public
ToStringExample(
int
number) {
this
.number = number;
}
}
|
1
2
|
ToStringExample example =
new
ToStringExample(
123
);
System.out.println(example.toString());
|
1
|
ToStringExample
@4554617c
|
clone()
1. cloneable
clone() 是 Object 的 protected 方法,它不是 public,一個類不顯式去重寫 clone(),其它類就不能直接去調用該類實例的 clone() 方法。
1
2
3
4
|
public
class
CloneExample {
private
int
a;
private
int
b;
}
|
1
2
|
CloneExample e1 =
new
CloneExample();
// CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object'
|
重寫 clone() 得到以下實現:
1
2
3
4
5
6
7
8
9
|
public
class
CloneExample {
private
int
a;
private
int
b;
@Override
protected
CloneExample clone()
throws
CloneNotSupportedException {
return
(CloneExample)
super
.clone();
}
}
|
1
2
3
4
5
6
|
CloneExample e1 =
new
CloneExample();
try
{
CloneExample e2 = e1.clone();
}
catch
(CloneNotSupportedException e) {
e.printStackTrace();
}
|
1
|
java.lang.CloneNotSupportedException: CloneExample
|
以上拋出了 CloneNotSupportedException,這是因為 CloneExample 沒有實現 Cloneable 接口。
應該注意的是,clone() 方法並不是 Cloneable 接口的方法,而是 Object 的一個 protected 方法。Cloneable 接口只是規定,如果一個類沒有實現 Cloneable 接口又調用了 clone() 方法,就會拋出 CloneNotSupportedException。
1
2
3
4
5
6
7
8
9
|
public
class
CloneExample
implements
Cloneable {
private
int
a;
private
int
b;
@Override
protected
Object clone()
throws
CloneNotSupportedException {
return
super
.clone();
}
}
|
2. 淺拷貝
拷貝對象和原始對象的引用類型引用同一個對象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
class
ShallowCloneExample
implements
Cloneable {
private
int
[] arr;
public
ShallowCloneExample() {
arr =
new
int
[
10
];
for
(
int
i =
0
; i < arr.length; i++) {
arr[i] = i;
}
}
public
void
set(
int
index,
int
value) {
arr[index] = value;
}
public
int
get(
int
index) {
return
arr[index];
}
@Override
protected
ShallowCloneExample clone()
throws
CloneNotSupportedException {
return
(ShallowCloneExample)
super
.clone();
}
}
|
1
2
3
4
5
6
7
8
9
|
ShallowCloneExample e1 =
new
ShallowCloneExample();
ShallowCloneExample e2 =
null
;
try
{
e2 = e1.clone();
}
catch
(CloneNotSupportedException e) {
e.printStackTrace();
}
e1.set(
2
,
222
);
System.out.println(e2.get(
2
));
// 222
|
3. 深拷貝
拷貝對象和原始對象的引用類型引用不同對象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public
class
DeepCloneExample
implements
Cloneable {
private
int
[] arr;
public
DeepCloneExample() {
arr =
new
int
[
10
];
for
(
int
i =
0
; i < arr.length; i++) {
arr[i] = i;
}
}
public
void
set(
int
index,
int
value) {
arr[index] = value;
}
public
int
get(
int
index) {
return
arr[index];
}
@Override
protected
DeepCloneExample clone()
throws
CloneNotSupportedException {
DeepCloneExample result = (DeepCloneExample)
super
.clone();
result.arr =
new
int
[arr.length];
for
(
int
i =
0
; i < arr.length; i++) {
result.arr[i] = arr[i];
}
return
result;
}
}
|
1
2
3
4
5
6
7
8
9
|
DeepCloneExample e1 =
new
DeepCloneExample();
DeepCloneExample e2 =
null
;
try
{
e2 = e1.clone();
}
catch
(CloneNotSupportedException e) {
e.printStackTrace();
}
e1.set(
2
,
222
);
System.out.println(e2.get(
2
));
// 2
|
4. clone() 的替代方案
使用 clone() 方法來拷貝一個對象即復雜又有風險,它會拋出異常,並且還需要類型轉換。Effective Java 書上講到,最好不要去使用 clone(),可以使用拷貝構造函數或者拷貝工廠來拷貝一個對象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public
class
CloneConstructorExample {
private
int
[] arr;
public
CloneConstructorExample() {
arr =
new
int
[
10
];
for
(
int
i =
0
; i < arr.length; i++) {
arr[i] = i;
}
}
public
CloneConstructorExample(CloneConstructorExample original) {
arr =
new
int
[original.arr.length];
for
(
int
i =
0
; i < original.arr.length; i++) {
arr[i] = original.arr[i];
}
}
public
void
set(
int
index,
int
value) {
arr[index] = value;
}
public
int
get(
int
index) {
return
arr[index];
}
}
|
1
2
3
4
|
CloneConstructorExample e1 =
new
CloneConstructorExample();
CloneConstructorExample e2 =
new
CloneConstructorExample(e1);
e1.set(
2
,
222
);
System.out.println(e2.get(
2
));
// 2
|
六、關鍵字
final
1. 數據
聲明數據為常量,可以是編譯時常量,也可以是在運行時被初始化后不能被改變的常量。
- 對於基本類型,final 使數值不變;
- 對於引用類型,final 使引用不變,也就不能引用其它對象,但是被引用的對象本身是可以修改的。
1
2
3
4
|
final
int
x =
1
;
// x = 2; // cannot assign value to final variable 'x'
final
A y =
new
A();
y.a =
1
;
|
2. 方法
聲明方法不能被子類重寫。
private 方法隱式地被指定為 final,如果在子類中定義的方法和基類中的一個 private 方法簽名相同,此時子類的方法不是重寫基類方法,而是在子類中定義了一個新的方法。
3. 類
聲明類不允許被繼承。
static
1. 靜態變量
- 靜態變量:又稱為類變量,也就是說這個變量屬於類的,類所有的實例都共享靜態變量,可以直接通過類名來訪問它;靜態變量在內存中只存在一份。
- 實例變量:每創建一個實例就會產生一個實例變量,它與該實例同生共死。
1
2
3
4
5
6
7
8
9
10
11
|
public
class
A {
private
int
x;
// 實例變量
private
static
int
y;
// 靜態變量
public
static
void
main(String[] args) {
// int x = A.x; // Non-static field 'x' cannot be referenced from a static context
A a =
new
A();
int
x = a.x;
int
y = A.y;
}
}
|
2. 靜態方法
靜態方法在類加載的時候就存在了,它不依賴於任何實例。所以靜態方法必須有實現,也就是說它不能是抽象方法(abstract)。
1
2
3
4
5
|
public
abstract
class
A {
public
static
void
func1(){
}
// public abstract static void func2(); // Illegal combination of modifiers: 'abstract' and 'static'
}
|
只能訪問所屬類的靜態字段和靜態方法,方法中不能有 this 和 super 關鍵字。
1
2
3
4
5
6
7
8
9
10
|
public
class
A {
private
static
int
x;
private
int
y;
public
static
void
func1(){
int
a = x;
// int b = y; // Non-static field 'y' cannot be referenced from a static context
// int b = this.y; // 'A.this' cannot be referenced from a static context
}
}
|
3. 靜態語句塊
靜態語句塊在類初始化時運行一次。
1
2
3
4
5
6
7
8
9
10
|
public
class
A {
static
{
System.out.println(
"123"
);
}
public
static
void
main(String[] args) {
A a1 =
new
A();
A a2 =
new
A();
}
}
|
1
|
123
|
4. 靜態內部類
非靜態內部類依賴於外部類的實例,而靜態內部類不需要。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
class
OuterClass {
class
InnerClass {
}
static
class
StaticInnerClass {
}
public
static
void
main(String[] args) {
// InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
OuterClass outerClass =
new
OuterClass();
InnerClass innerClass = outerClass.
new
InnerClass();
StaticInnerClass staticInnerClass =
new
StaticInnerClass();
}
}
|
靜態內部類不能訪問外部類的非靜態的變量和方法。
5. 靜態導包
在使用靜態變量和方法時不用再指明 ClassName,從而簡化代碼,但可讀性大大降低。
1
|
import
static
com.xxx.ClassName.*
|
6. 初始化順序
靜態變量和靜態語句塊優先於實例變量和普通語句塊,靜態變量和靜態語句塊的初始化順序取決於它們在代碼中的順序。
1
|
public
static
String staticField =
"靜態變量"
;
|
1
2
3
|
static
{
System.out.println(
"靜態語句塊"
);
}
|
1
|
public
String field =
"實例變量"
;
|
1
2
3
|
{
System.out.println(
"普通語句塊"
);
}
|
最后才是構造函數的初始化。
1
2
3
|
public
InitialOrderTest() {
System.out.println(
"構造函數"
);
}
|
存在繼承的情況下,初始化順序為:
- 父類(靜態變量、靜態語句塊)
- 子類(靜態變量、靜態語句塊)
- 父類(實例變量、普通語句塊)
- 父類(構造函數)
- 子類(實例變量、普通語句塊)
- 子類(構造函數)
七、反射
每個類都有一個 Class 對象,包含了與類有關的信息。當編譯一個新類時,會產生一個同名的 .class 文件,該文件內容保存着 Class 對象。
類加載相當於 Class 對象的加載。類在第一次使用時才動態加載到 JVM 中,可以使用 Class.forName("com.mysql.jdbc.Driver")
這種方式來控制類的加載,該方法會返回一個 Class 對象。
反射可以提供運行時的類信息,並且這個類可以在運行時才加載進來,甚至在編譯時期該類的 .class 不存在也可以加載進來。
Class 和 java.lang.reflect 一起對反射提供了支持,java.lang.reflect 類庫主要包含了以下三個類:
- Field :可以使用 get() 和 set() 方法讀取和修改 Field 對象關聯的字段;
- Method :可以使用 invoke() 方法調用與 Method 對象關聯的方法;
- Constructor :可以用 Constructor 創建新的對象。
Advantages of Using Reflection:
- Extensibility Features : An application may make use of external, user-defined classes by creating instances of extensibility objects using their fully-qualified names.
- Class Browsers and Visual Development Environments : A class browser needs to be able to enumerate the members of classes. Visual development environments can benefit from making use of type information available in reflection to aid the developer in writing correct code.
- Debuggers and Test Tools : Debuggers need to be able to examine private members on classes. Test harnesses can make use of reflection to systematically call a discoverable set APIs defined on a class, to insure a high level of code coverage in a test suite.
Drawbacks of Reflection:
Reflection is powerful, but should not be used indiscriminately. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it. The following concerns should be kept in mind when accessing code via reflection.
-
Performance Overhead : Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
-
Security Restrictions : Reflection requires a runtime permission which may not be present when running under a security manager. This is in an important consideration for code which has to run in a restricted security context, such as in an Applet.
-
Exposure of Internals :Since reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform.
八、異常
Throwable 可以用來表示任何可以作為異常拋出的類,分為兩種: Error 和 Exception。其中 Error 用來表示 JVM 無法處理的錯誤,Exception 分為兩種:
- 受檢異常 :需要用 try...catch... 語句捕獲並進行處理,並且可以從異常中恢復;
- 非受檢異常 :是程序運行時錯誤,例如除 0 會引發 Arithmetic Exception,此時程序崩潰並且無法恢復。
九、泛型
1
2
3
4
5
6
|
public
class
Box<T> {
// T stands for "Type"
private
T t;
public
void
set(T t) {
this
.t = t; }
public
T get() {
return
t; }
}
|
十、注解
Java 注解是附加在代碼中的一些元信息,用於一些工具在編譯、運行時進行解析和使用,起到說明、配置的功能。注解不會也不能影響代碼的實際邏輯,僅僅起到輔助性的作用。
十一、特性
Java 各版本的新特性
New highlights in Java SE 8
- Lambda Expressions
- Pipelines and Streams
- Date and Time API
- Default Methods
- Type Annotations
- Nashhorn JavaScript Engine
- Concurrent Accumulators
- Parallel operations
- PermGen Error Removed
New highlights in Java SE 7
- Strings in Switch Statement
- Type Inference for Generic Instance Creation
- Multiple Exception Handling
- Support for Dynamic Languages
- Try with Resources
- Java nio Package
- Binary Literals, Underscore in literals
- Diamond Syntax
Java 與 C++ 的區別
- Java 是純粹的面向對象語言,所有的對象都繼承自 java.lang.Object,C++ 為了兼容 C 即支持面向對象也支持面向過程。
- Java 通過虛擬機從而實現跨平台特性,但是 C++ 依賴於特定的平台。
- Java 沒有指針,它的引用可以理解為安全指針,而 C++ 具有和 C 一樣的指針。
- Java 支持自動垃圾回收,而 C++ 需要手動回收。
- Java 不支持多重繼承,只能通過實現多個接口來達到相同目的,而 C++ 支持多重繼承。
- Java 不支持操作符重載,雖然可以對兩個 String 對象支持加法運算,但是這是語言內置支持的操作,不屬於操作符重載,而 C++ 可以。
- Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto。
- Java 不支持條件編譯,C++ 通過 #ifdef #ifndef 等預處理命令從而實現條件編譯。
What are the main differences between Java and C++?
JRE or JDK
- JRE is the JVM program, Java application need to run on JRE.
- JDK is a superset of JRE, JRE + tools for developing java programs. e.g, it provides the compiler "javac"