byte/8java
char/16程序員
short/16數組
int/32瀏覽器
float/32緩存
long/64安全
double/64bash
boolean/~網絡
booleanjvm
boolean只有兩個值:true、false,可使用 1 bit 來存儲,可是具體大小沒有明確規定。JVM 會在編譯時期將 boolean 類型的數據轉換爲 int,使用 1 來表示 true,0 表示 false。JVM 支持 boolean 數組,可是是經過讀寫 byteide
所佔位數->精度
float f = 1.1 編譯沒法經過。由於字面量 1.1 是 double 類型,它比 float 類型精度要高,所以不能隱式地將 double 類型下轉型爲 float 類型。
由於字面量 1 是 int 類型,它比 short 類型精度要高,所以不能隱式地將 int 類型下轉型爲 short 類型。
short s1 = 1;
// s1 = s1 + 1;
複製代碼
可是使用 += 或者 ++ 運算符能夠執行隱式類型轉換。
s1 += 1;
// s1++;
複製代碼
上面的語句至關於將 s1 + 1 的計算結果進行了向下轉型:
s1 = (short) (s1 + 1);
複製代碼
基本類型都有對應的包裝類型,基本類型與其對應的包裝類型之間的賦值使用自動裝箱與拆箱完成。
Integer x = 2; // 裝箱
int y = x; // 拆箱
複製代碼
new Integer(123) 與 Integer.valueOf(123) 的區別在於:
new Integer(123) 每次都會新建一個對象; Integer.valueOf(123) 會使用緩存池中的對象,屢次調用會取得同一個對象的引用。
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() 方法的實現比較簡單,就是先判斷值是否在緩存池中,若是在的話就直接返回緩存池的內容。
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(緩存池實際也是經過new Integer()去實例的)。
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++);//這裏說明也是經過new Integer去實例的
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
複製代碼
編譯器會在自動裝箱過程調用 valueOf() 方法,所以多個值相同且值在緩存池範圍內的 Integer 實例使用自動裝箱來建立,那麼就會引用相同的對象。
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
在使用這些基本類型對應的包裝類型時,若是該數值範圍在緩衝池範圍內,就能夠直接使用緩衝池中的對象。
在 jdk 1.8 全部的數值類緩衝池中,Integer 的緩衝池 IntegerCache 很特殊,這個緩衝池的下界是 - 128,上界默認是 127,可是這個上界是可調的,在啓動 jvm 的時候,經過 -XX:AutoBoxCacheMax= 來指定這個緩衝池的大小,該選項在 JVM 初始化的時候會設定一個名爲 java.lang.IntegerCache.high 系統屬性,而後 IntegerCache 初始化的時候就會讀取該系統屬性來決定上界。
String 被聲明爲 final,所以它不可被繼承。
Java 8 中,String 內部使用 char 數組存儲數據
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
複製代碼
在 Java 9 以後,String 類的實現改用 byte 數組存儲字符串,同時使用 coder 來標識使用了哪一種編碼。 這裏咱們只討論Java 8
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final byte[] value;
/** The identifier of the encoding used to encode the bytes in {@code value}. */
private final byte coder;
}
複製代碼
value 數組被聲明爲 final,這意味着 value 數組初始化以後就不能再引用其它數組。而且 String 內部沒有改變 value 數組的方法,所以能夠保證 String 不可變(此不可變非彼不可變)。
那麼不可變有什麼好處呢?
由於 String 的 hash 值常常被使用,例如 String 用作 HashMap 的 key。不可變的特性可使得 hash 值也不可變,所以只須要進行一次計算。
若是一個 String 對象已經被建立過了,那麼就會從 String Pool 中取得引用。只有 String 是不可變的,纔可能使用 String Pool。
3. 安全性String 常常做爲參數,String 不可變性能夠保證參數不可變。例如在做爲網絡鏈接參數的狀況下若是 String 是可變的,那麼在網絡鏈接過程當中,String 被改變,改變 String 對象的那一方覺得如今鏈接的是其它主機,而實際狀況卻不必定是。
String 不可變性天生具有線程安全,能夠在多個線程中安全地使用。
String 不可變 StringBuffer 和 StringBuilder 可變 2. 線程安全
String 不可變,所以是線程安全的 StringBuilder 不是線程安全的 StringBuffer 是線程安全的,內部使用 synchronized 進行同步
字符串常量池(String Pool)保存着全部字符串字面量(literal strings)
在 Java 7 以前,String Pool 被放在運行時常量池中,它屬於永久代。而在 Java 7,String Pool 被移到堆中。這是由於永久代的空間有限,在大量使用字符串的場景下會致使 OutOfMemoryError 錯誤。
自動地將字符串放入 String Pool 中,而且返回該對象
String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6); // true
複製代碼
使用這種方式一共會建立兩個字符串對象(前提是 String Pool 中尚未 "abc" 字符串對象)。
"abc" 屬於字符串字面量,所以編譯時期會在 String Pool 中建立一個字符串對象,指向這個 "abc" 字符串字面量;
而使用 new 的方式會在堆中建立一個字符串對象。
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2); // false
複製代碼
個字符串調用 intern() 方法時,若是 String Pool 中已經存在一個字符串和該字符串值相等(使用 equals() 方法進行肯定),那麼就會返回 String Pool 中字符串的引用;不然,就會在 String Pool 中添加一個新的字符串,並返回這個新字符串的引用
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4); // true
複製代碼
public boolean equals(Object obj)
public native int hashCode()
protected native Object clone() throws CloneNotSupportedException
public String toString()
public final native Class<?> getClass()
protected void finalize() throws Throwable {}
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
複製代碼
對於基本類型,== 判斷兩個值是否相等,基本類型沒有 equals() 方法。
對於引用類型,== 判斷兩個變量是否引用同一個對象,而 equals() 判斷引用的對象是否等價
檢查是否爲同一個對象的引用,若是是直接返回 true; 檢查是不是同一個類型,若是不是,直接返回 false; 將 Object 對象進行轉型; 判斷每一個關鍵域是否相等
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;
}
}
複製代碼
在覆蓋 equals() 方法時應當老是覆蓋 hashCode() 方法,保證等價的兩個對象散列值也相等
如何不重寫 會發生什麼事呢?
下面的代碼中,新建了兩個等價的對象,並將它們添加到 HashSet 中。咱們但願將這兩個對象當成同樣的,只在集合中添加一個對象,可是由於 EqualExample 沒有實現 hashCode() 方法,所以這兩個對象的散列值是不一樣的,最終致使集合添加了兩個等價的對象。
EqualExample e1 = new EqualExample(1, 1, 1);
EqualExample e2 = new EqualExample(1, 1, 1);
System.out.println(e1.equals(e2)); // true
//因爲許多基本類內部都經過hashCode()去判斷key的相等
HashSet<EqualExample> set = new HashSet<>();
set.add(e1);
set.add(e2);
System.out.println(set.size()); // 2
複製代碼
那麼咱們要如何重寫hashCode()呢
理想的散列函數應當具備均勻性,即不相等的對象應當均勻分佈到全部可能的散列值上。這就要求了散列函數要把全部域的值都考慮進來。能夠將每一個域都當成 R 進制的某一位,而後組成一個 R 進制的整數。R 通常取 31,由於它是一個奇素數,若是是偶數的話,當出現乘法溢出,信息就會丟失,由於與 2 相乘至關於向左移一位。
另外一個選擇31的緣由:
一個數與 31 相乘能夠轉換成移位和減法:31*x == (x<<5)-x,編譯器會自動進行這個優化。
@Override
public int hashCode() {
int result = 17;
result = 31 * result + x;//若是x是引用類型 能夠改爲31*result + x.hashCode();
result = 31 * result + y;
result = 31 * result + z;
return result;
}
複製代碼
默認返回 ToStringExample@4554617c 這種形式,其中 @ 後面的數值爲散列碼的無符號十六進制表示。
clone() 方法並非 Cloneable 接口的方法,而是 Object 的一個 protected 方法。Cloneable 接口只是規定,若是一個類沒有實現 Cloneable 接口又調用了 clone() 方法,就會拋出 CloneNotSupportedException。
public class CloneExample implements Cloneable {
private int a;
private int b;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
複製代碼
拷貝對象和原始對象的引用類型引用同一個對象。
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();
}
}
複製代碼
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
複製代碼
拷貝對象和原始對象的引用類型引用不一樣對象。
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;
}
}
複製代碼
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
複製代碼
使用 clone() 方法來拷貝一個對象即複雜又有風險,它會拋出異常,而且還須要類型轉換。Effective Java 書上講到,最好不要去使用 clone(),可使用拷貝構造函數或者拷貝工廠來拷貝一個對象。
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];
}
}
複製代碼
CloneConstructorExample e1 = new CloneConstructorExample();
CloneConstructorExample e2 = new CloneConstructorExample(e1);
e1.set(2, 222);
System.out.println(e2.get(2)); // 2
複製代碼
聲明數據爲常量,能夠是編譯時常量,也能夠是在運行時被初始化後不能被改變的常量。
對於基本類型,final 使數值不變;
對於引用類型,final 使引用不變,也就不能引用其它對象,可是被引用的對象自己是能夠修改的。
final int x = 1;
// x = 2; // cannot assign value to final variable 'x'
final A y = new A();
y.a = 1;
複製代碼
聲明方法不能被子類重寫。
private 方法隱式地被指定爲 final,若是在子類中定義的方法和基類中的一個 private 方法簽名相同,此時子類的方法不是重寫基類方法,而是在子類中定義了一個新的方法。
聲明類不容許被繼承。
靜態變量和靜態語句塊優先於實例變量和普通語句塊,靜態變量和靜態語句塊的初始化順序取決於它們在代碼中的順序。
父類>靜態>實例塊>構造函數
public static String staticField = "靜態變量";
static {
System.out.println("靜態語句塊");
}
public String field = "實例變量";
{
System.out.println("普通語句塊");
}
最後纔是構造函數的初始化。
public InitialOrderTest() {
System.out.println("構造函數");
}
存在繼承的狀況下,初始化順序爲:
父類(靜態變量、靜態語句塊)
子類(靜態變量、靜態語句塊)
父類(實例變量、普通語句塊)
父類(構造函數)
子類(實例變量、普通語句塊)
子類(構造函數)
複製代碼
可擴展性 :應用程序能夠利用全限定名建立可擴展對象的實例,來使用來自外部的用戶自定義類。 類瀏覽器和可視化開發環境 :一個類瀏覽器須要能夠枚舉類的成員。可視化開發環境(如 IDE)能夠從利用反射中可用的類型信息中受益,以幫助程序員編寫正確的代碼。 調試器和測試工具 : 調試器須要可以檢查一個類裏的私有成員。測試工具能夠利用反射來自動地調用類裏定義的可被發現的 API 定義,以確保一組測試中有較高的代碼覆蓋率。
儘管反射很是強大,但也不能濫用。若是一個功能能夠不用反射完成,那麼最好就不用。在咱們使用反射技術時,下面幾條內容應該牢記於心。
性能開銷 :反射涉及了動態類型的解析,因此 JVM 沒法對這些代碼進行優化。所以,反射操做的效率要比那些非反射操做低得多。咱們應該避免在常常被執行的代碼或對性能要求很高的程序中使用反射。
安全限制 :使用反射技術要求程序必須在一個沒有安全限制的環境中運行。若是一個程序必須在有安全限制的環境中運行,如 Applet,那麼這就是個問題了。
內部暴露 :因爲反射容許代碼執行一些在正常狀況下不被容許的操做(好比訪問私有的屬性和方法),因此使用反射可能會致使意料以外的反作用,這可能致使代碼功能失調並破壞可移植性。反射代碼破壞了抽象性,所以當平臺發生改變的時候,代碼的行爲就有可能也隨着變化。
Throwable 能夠用來表示任何能夠做爲異常拋出的類,分爲兩種: Error 和 Exception。其中 Error 用來表示 JVM 沒法處理的錯誤,Exception 分爲兩種:
checked異常 :須要用 try...catch... 語句捕獲並進行處理,而且能夠從異常中恢復;
unchecked異常 :是程序運行時錯誤,例如除 0 會引起 Arithmetic Exception,此時程序崩潰而且沒法恢復。