契子:明年就要離開學校找工作了,時間過的真快,想一想這幾年,做了一些事,也有一些事並沒有做好,有很多收穫,也有不少遺憾。感性的話在此不宜多說,既然選擇了程序員這條道路,也要有把它到做事業的態度。在正式找工作前還有幾個月的時間,做東西,嘗試新的技術固然很爽,但是基礎也很重要,在這短短的幾個月的時間裏,我將把以前學過的一些知識,Java,數據結構,算法,網絡,OS&Linux,J2EE等等知識查缺補漏,好好梳理一遍,不光是爲了找工作,也是一種必須要堅持的態度。
對於Java知識的整理,基於《Effetive Java》2nd和《Java編程思想》4th輔以JVM和設計模式的相關知識,結合書本上的知識和我的理解進行整理。好了,開始我的一篇——創建和銷燬對象。
1. Java中的構造器:
構造器是一種特殊類型的方法,它和類同名,沒有返回類型,和new關鍵字結合可以返回對象實例的引用。TIJ中說它是一種靜態方法,但是通過字節碼我們可以看到其實並沒有static關鍵字,它的行爲也和其他靜態方法有異(可以訪問非靜態成員變量),因此這種說法並不完全準確,這裏不再深究。
1.1 定義構造器:
一個類可以有多個構造器,如果你沒有定義構造器,Java編譯器會在語義分析的階段,首先添加一個默認構造器。
多個構造器可以通過方法重載(overload)實現,注意只有同方法名和不同參數列表可以區別不同的重載版本,返回類型並不能區分。
尤其是使用基本類型參數重載時,要注意類型的自動轉換如(char—>int,小轉大)和窄化轉換(強制類型轉換,大轉小),當然會使用最匹配的類型。
1.2 this關鍵字:
通過this指針我們可以訪問類的實例變量和方法,但最好是在必要的時候(需要返回或使用該實例,內部類訪問外部類同名實例變量方法,構造器設置屬性等)使用它,否則你不必添加它,編譯同樣會幫你添加。
在存在多個重載版本的構造器時我們可以在構造器內使用this調用其他構造器,可以避免一些重複的代碼:
public ConstructorTest(int a) {
this.a = a;
}
public ConstructorTest(int a, String s) {
this(a);
this.s = s;
}
PS:在構造器存在很多參數情況下,重疊構造器是一種選擇,但是更好的做法是使用Builder模式,後面會講到。
1.3 static關鍵字:
static(靜態),static方法和static變量是類方法和類變量,它們不能使用this引用,都放在方法區中,供各個線程共享。static變量初始化和static初始化其,會在類加載(隱式加載或顯示加載)後執行一次。
2. 清理,終結對象(finalize)/垃圾回收(CG):
釋放資源,終結和垃圾回收有什麼關係:
Finalize方法到底有什麼用:
總的來說除此以外儘量不要使用finalize方法。
3. 初始化:
如果想真正弄清楚對象初始化,而不是僅僅記住一些像成員變量的初始值這樣的規則,我覺得應該瞭解一個類在第一個創建對象時是如何從字節碼編程的可用的對象的。
在第一次使用一個類的時候,無論是顯示加載一個類(Class.forName等)還是隱式加載一個類(A.staticVariable,new A())時,首先要有ClassLoader進行加載:
(1)ClassLoader首先通過類名定位到類文件的位置(通過classpath等),將字節碼加載到內存,通過準備、字節碼驗證和resolve等環節將等到一個個Class對象,放到方法區中;
(2)在此之後就是類初始化,這是類中的靜態變量和靜態初始化器將按照位置順序進行初始化工作,靜態變量同樣放在方法區中;
(3)如果你進行是實例創建的化,接下來的工作首先是在堆上分配內存了,具體的方法可能有指針碰撞和空閒列表;
(4)獲得了內存空間後,首先全部置零,這也就是爲什麼類的成員變量會還有初始值的原因,之後如果指定了初始化值,同樣這裏也是按順序進行的;
(5)最後將執行<init>也就是我們定義使用的構造器來進行我們自定義的初始化過程了,這裏就可以獲得我們想要的對象實例的引用了。
所以在類中,各個部分的初始化順序是:靜態變量,靜態初始化器(按位置順序)——>非靜態成員變量(按位置順序)——>構造器;
說完了基本過程,我們來看看在Java中一些具體的類型是怎樣進行初始化的。
3.1 數組初始化:
public class MStack {
private static final int DEFAULT_SIZE = 20;
private Object[] elements = new Object[DEFAULT_SIZE];
private int size = 0;
public MStack() {
elements = new Object[DEFAULT_SIZE];
}
public void push(Object element) {
ensureCapacity();
elements[size++] = element;
}
public Object pop() {
if(size == 0) {
throw new RuntimeException("empty stack cannot pop");
}
return elements[--size];
}
public void ensureCapacity() {
if(size == elements.length) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
這是《Effective Java》的一個例子,該例中的Stack在pop是並沒有將已經出棧的引用置爲null;這些引用是“過期引用”,這些引用雖然沒有被使用,但是它將隨着Arrays.copy一起被複制到更大的數組中,對於JVM來說它們同樣是存活的對象,但是對我們的應用程序來說這些是無用的。在一個需要長期運行的服務中如果出現這樣的問題很容易導致OOM。
3.2 可變參數列表:
public class VarArgsInit {
//overload with var argument
public static void f(Long...longs) {
System.out.println("f_long_varArgs");
}
public static void f(Character...characters) {
System.out.println("f_character_varArgs");
}
public static void f(float f, Character...characters) {
System.out.println("f_float_character_varArgs");
}
public static void g(float f, Character...characters) {
System.out.println("g_float_character_varArgs");
}
public static void g(char c, Character...characters) {
System.out.println("g_char_character_varArgs");
}
public static void main(String[] args) {
// f(); //Error:(19, 9) java: reference to f is ambiguous
// f();
f(1, 'a'); //OK
// f('a', 'b'); //Error:(19, 9) java: reference to f is ambiguous
g('a', 'b'); //OK
}
這個例子中,f('a','b')會引起編譯錯誤,因爲它會同時匹配第3個和第2個f()版本(因爲'a'可以轉換成float),解決方法,很簡單g方法的兩個版本就不會有這種衝突。
3.3 枚舉:
final enum hr.test.Color {
// 所有的枚舉值都是類靜態常量
public static final enum hr.test.Color RED;
public static final enum hr.test.Color BLUE;
public static final enum hr.test.Color BLACK;
public static final enum hr.test.Color YELLOW;
public static final enum hr.test.Color GREEN;
private static final synthetic hr.test.Color[] ENUM$VALUES;
// 初始化過程,對枚舉類的所有枚舉值對象進行第一次初始化
static {
0 new hr.test.Color [1]
3 dup
4 ldc <String "RED"> [16] //把枚舉值字符串"RED"壓入操作數棧
6 iconst_0 // 把整型值0壓入操作數棧
7 invokespecial hr.test.Color(java.lang.String, int) [17] //調用Color類的私有構造器創建Color對象RED
10 putstatic hr.test.Color.RED : hr.test.Color [21] //將枚舉對象賦給Color的靜態常量RED。
......... 枚舉對象BLUE等與上同
102 return
};
// 私有構造器,外部不可能動態創建一個枚舉類對象(也就是不可能動態創建一個枚舉值)。
private Color(java.lang.String arg0, int arg1){
// 調用父類Enum的受保護構造器創建一個枚舉對象
3 invokespecial java.lang.Enum(java.lang.String, int) [38]
};
public static hr.test.Color[] values();
public static hr.test.Color valueOf(java.lang.String arg0);
}
從字節碼解析中,首先可以看到:public enum MEnum {
E1;
static class A {
private A(){
}
}
public static void main(String[] args) throws Exception {
Class<A> a = A.class;
Constructor constructor = a.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
Class<?> ec = MEnum.class;
Constructor constructor1 = ec.getDeclaredConstructor(String.class, int.class);
constructor1.setAccessible(true);
constructor1.newInstance("YJH", 2);
}
}
結果:A類可以正常創建,而enum類型,java.lang.IllegalArgumentException: Cannot reflectively create enum objects,因爲在class.newInstance中有這樣的檢查: if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
(2)values靜態方法;4. 創建和銷燬對象的實踐:
4.1 使用靜態工廠方法代替構造器:
優點:4.2 多個構造器參數時使用Builder模式:
public final class HasBuilder {
private final int i1;
private final int i2;
private final String s1;
public HasBuilder(Builder builder) {
this.i1 = builder.i1;
this.i2 = builder.i2;
this.s1 = builder.s1;
}
public static class Builder {
private int i1;
private int i2;
private String s1;
public Builder i1(int i1) {
this.i1 = i1;
return this;
}
public Builder i2(int i2) {
this.i2 = i2;
return this;
}
public Builder s1(String s1) {
this.s1 = s1;
return this;
}
public HasBuilder build() {
return new HasBuilder(this);
}
}
}
你可以通過在build構建在具體的設值方法裏進行約束檢查。4.3 建立合適的單例:
public class SingletonWithInnerClass {
private SingletonWithInnerClass() {
System.out.println("initialized");
}
private static class SingletonHolder {
private static final SingletonWithInnerClass s = new SingletonWithInnerClass();
}
public static SingletonWithInnerClass getInstance() {
return SingletonHolder.s;
}
public static void main(String[] args) {
Class c = SingletonWithInnerClass.class; //這裏並沒有進行初始化
System.out.println("start initialization:");
SingletonWithInnerClass singletonWithInnerClass = SingletonWithInnerClass.getInstance();
}
}
這段代碼的輸出結果:initialized