Java基本功——初始化与清理

1、搞懂this关键字的前因后果

先上一段代码:java

class Banana {
	void peel(int i) {
		/** .... **/
	}
}

public class EG {
	public static void main(String[] args) {
		Banana a = new Banana();
		Banana b = new Banana();
		a.peel(1);
		b.peel(2);

	}
}

咱们能够看到 Banana的两个对象a 和 b 都在调用Banana的方法peel(),可是编译器是如何区分到底是a调用了peel呢?仍是b调用的peel呢?app

由此,编译器在幕后作了一些工做,把“操做对象的引用”暗自传递给peel()方法,来区别是谁调用了peel()。因此事实上是这样调用的:jvm

Banana.peel(a, 1);函数

Banana.peel(b, 2);this

若是你想在方法的内部得到调用这个方法的引用,由于是编译器偷偷传入的,因此无法表示这个引用。为此,有个专门的关键字thisspa

此处有段代码为证:code

public class thisTest {
	public void instanceMethod(Object args) {
		System.out.println(this);
	}
	
	public static void main(String[] args) {
		thisTest t = new thisTest();
		System.out.println(t);
		t.instanceMethod("");
	}
}

能够看到两次输出都是同一个t对象的信息:orm

init.thisTest@15db9742
init.thisTest@15db9742对象

所以,this只能在方法内部使用,表示对“调用方法的那个对象”的引用继承

可是也不必到处使用this,将this放在不必的地方只会让你的代码变得复杂难懂。以下:
 

public class Apricot {

	void pick() {
		/** ... **/
	}

	void pit() {
		pick();
		/** ... **/
	}
}

在pit()中,咱们彻底能够写 this.pick()但没有必要,编译器能帮你自动添加。

1.1 屡次调用

只有当须要明确指出对当前对象的引用的时候,才须要使用this关键字。例如当须要返回对当前对象的引用时:

/**
 * Simple user of the "this" keyword
 * 
 * @author kfh
 *
 */

public class Leaf {
	int i = 0;

	Leaf increment() {
		i++;
		return this;
	}

	void print() {
		System.out.println("i = " + i);
	}

	public static void main(String[] args) {
		Leaf l = new Leaf();
		// 我不由感叹 这调用真tm精髓~
		l.increment().increment().increment().print();
	}
}

方法increment 用this关键字返回了调用它的对象,这使得在一条语句中屡次调用该对象的方法变得很简单。  

1.2 本身调本身

再看下面的这个例子,意味深长:

class Person {
	public void eat(Apple apple) {
		Apple peeled = apple.getPeeled();
		System.out.println("Yummy!");
	}
}

class Apple {
	Apple getPeeled() {
		// 此处调用getPeeled()方法,必须用this将调用getPeeled方法的apple传进去
		// 至关于这句话 apple.getPeeled(); 是将apple本身传到了getPeeled里面,把本身削了皮
		return Peeler.peel(this);
	}
}

class Peeler {
	static Apple peel(Apple apple) {
		System.err.println("remove apple peel...");
		return apple;// Peeled
	}
}

public class PassingThis {
	public static void main(String[] args) {
		new Person().eat(new Apple());
	}
}

1.3 调用构造器

有时候一个类会有多个构造方法,当在一个构造方法中调用另外一个构造方法时,就用this。一般写“this”的时候,表示这个对象或者当前对象,它自己则表示对当前对象的引用。若是this后面跟上了参数列表,则表示对某个构造函数明确调用

看下面的例子:

public class Flower {
	int petalCount = 0;
	String s = "initial value";

	Flower(int petals) {
		petalCount = petals;
		print("Constructor w/ int arg only, petalCount = " + petalCount);
	}

	Flower(String ss) {
		print("Constructor w/ int arg only, s = " + ss);
	}

	Flower(String s, int petals) {
		this(petals);
		this.s = s; // 因为参数s的名称和数据成员s的名称相同,因此用this.s 表明数据成员
		print("String & int args");
	}

	Flower() {
		this("hi", 47);
		print("default constuctor (no args)");
	}

	void printPetalCount() {
		print("petalCount = " + petalCount + " s = " + s);
	}
	
	public static void main(String[] args) {
		Flower x = new Flower();
		x.printPetalCount();
	}

	void print(Object o) {
		System.out.println(o);
	}

}

说明一下,这里Flower x = new Flower(); 首先调用了无参的构造

到这:

Flower() {
        this("hi", 47);
        print("default constuctor (no args)");
    }

而后this("hi", 47) 则调用的是俩参 (String, int)的构造因此到了这里:

Flower(String s, int petals) {
        this(petals);
        this.s = s; // 因为参数s的名称和数据成员s的名称相同,因此用this.s 表明数据成员
        print("String & int args");
    }

而后this(petals) 调的是Flower(int) 的构造 到了这里:

Flower(int petals) {
        petalCount = petals;
        print("Constructor w/ int arg only, petalCount = " + petalCount);
    }

因此输出结果是:

Constructor w/ int arg only, petalCount = 47
String & int args
default constuctor (no args)
petalCount = 47 s = hi

因此先输出int构造,而后是俩参构造,最后是无参构造。

可是须要注意的是:1.只能在构造方法中用this调用构造方法,不能在非构造方法中用this调构造方法。2. 用this调构造必须写在构造方法的第一行

1.4  static代码中无this?

什么是static方法?static方法就是没有this的方法。为何呢?  你们回忆一下关于this的用法,忘记的能够向前翻一下。
是在方法的内部得到对当前对象的引用,这个引用是编译器在调用方法的时候“偷偷”做为第一个参数传入的,因此没有变量表示它,用this表示。
由于static方法是在没有建立对象的前提下,仅仅经过类自己来调用static方法,这样编译器没有将对象的引用传入方法中,因此无法用this来得到当前对象的引用。

 

1.5 有关finalize()

java中的清理工做都是gc来完成的,其中咱们能够看到的则是这个继承自Object类的私有方法finalize();

 

什么状况下会调用finalize()呢?有以下三种状况

1.全部对象被Garbage Collection时自动调用,好比运行System.gc()的时候.

2.程序退出时为每一个对象调用一次finalize方法。

3.显式的调用finalize方法

除此之外,正常状况下,当某个对象被系统收集为无用信息的时候,finalize()将被自动调用,可是jvm不保证finalize()必定被调用,也就是说,finalize()的调用是不肯定的,这也就是为何sun不提倡使用finalize()的缘由. 

且看下面的例子:

/**
 * Create a class with a finalize() method that prints a message. In main(),
 * create an object of your class. Explain the behavior of your program.
 * 
 * @author kfh
 *
 */
class Bank {
	boolean loggedIn;

	Bank(boolean loggedStatus) {
		this.loggedIn = loggedStatus;
	}

	void logIn() {
		loggedIn = true;
	}

	void logOut() {
		loggedIn = false;
	}

	// finalize()方法是属于一个类的,在gc清理以前买足有引用须要清理的时候gc自动调用
	protected void finalize() {
		if (loggedIn) {
			System.out.println("ERROR: still logging in!");
			// Normally, you'll also do this:
			// super.finalize(); // Call the base-class version
		}
	}
}

public class Ex10 {

	public static void main(String[] args) {
		Bank b1 = new Bank(true);// 第一个bank登录
		b1.logOut();
		Bank b2 = new Bank(true);// 第二个bank登录
		new Bank(false); // 只有当存在这句话的时候,类中的finalize()方法才会被触发
		/**
		 * new Bank(true) => 输出:ERROR: still logging in!
		 */
		System.gc();
	}
}

2. 初始化

有关初始化我要说的是: 类的成员是会在调用该类的任何非静态方法(包括构造器)被调用以前的到初始化的。

注:其实类的成员初始化是在调用该类的构造器以前完成的,上面那句话是由于调用类的别的方法以前必然已经调用了该类的构造器了,可是调用static的方法就不行了哦

若是类的成员是基本类型的,会初始成一个默认的值。若是是一个对象引用时,会初始成null。

而初始化的顺序是根据变量定义的顺序决定的,且看下面的这个例子:

/**
 * Demonstrates initialization order <br>
 * 在类的内部,变量定义的顺序决定了初始化的顺序。即便变量定义散布于方法定义<br>
 * 之间,他们仍旧会在任何方法(包括构造器)被调用以前获得初始化。
 * 
 * @author kfh
 *
 */
// when the construtor is called to create a
// window object, you'll see a message
class Window {
	Window(int marker) {
		print("Window(" + marker + ")");
	}
}

class House {
	Window w1 = new Window(1); // Before constructor

	House() {
		// Show that we're in the constructor;
		print("House()");
		new Window(33);

	}

	Window w2 = new Window(2);// After constructor

	void f() {
		print("f()");
	}

	Window w3 = new Window(3);// At end
}

public class InitOrder {
	public static void main(String[] args) {
		House house = new House();
		house.f();
	}
}

/**
 * 因此此处的输出是这样的:<br>
 * Window(1) <br>
 * Window(2) <br>
 * Window(3)<br>
 * House()<br>
 * Window(33)<br>
 * f()<br>
 * 
 */

 

2.1 有关static变量的初始化

请你们好好研读这段代码,将下面这个例子看明白了构造器和成员包括static的初始化就没问题了:

class Bowl {
	Bowl(int marker) {
		print("Bowl(" + marker + ")");
	}

	void f1(int marker) {
		print("f1(" + marker + ")");
	}
}

class Table {
	static Bowl bowl1 = new Bowl(1);

	Table() {
		print("Table()");
		bowl2.f1(1);
	}

	void f2(int marker) {
		print("f2(" + marker + ")");
	}

	static Bowl bowl2 = new Bowl(2);
}

class Cupboard {
	Bowl bowl3 = new Bowl(3);
	static Bowl bowl4 = new Bowl(4);

	Cupboard() {
		print("Cupboard()");
		bowl4.f1(2);
	}

	void f3(int marker) {
		print("f3(" + marker + ")");
	}

	static Bowl bowl5 = new Bowl(5);
}

public class StaticInitialization {
	public static void main(String[] args) {
		print("Creating new Cupboard() in main");
		new Cupboard();
		print("Creating new Cupboard() in main");
		new Cupboard();
		table.f2(1);
		cupboard.f3(1);
	}

	static Table table = new Table();
	static Cupboard cupboard = new Cupboard();
}

/**
 * output: <br>
 * Bowl(1) <br>
 * Bowl(2) <br>
 * Table() <br>
 * f(1) <br>
 * Bowl(4) <br>
 * Bowl(5) <br>
 * Bowl(3) <br>
 * Cupboard() <br>
 * f1(2) <br>
 * Creating new Cupboard() in main <br>
 * Cupboard() <br>
 * f1(2) <br>
 * Creating new Cupboard() in main <br>
 * Cupboard() <br>
 * f1(2) <br>
 * f2(1) <br>
 * f3(1) <br>
 * 
 */


静态成员的初始化只有在必要的时候才会发生,若是将上面这段代码改为这样:

public class StaticInitialization {
	public static void main(String[] args) {
		print("Creating new Cupboard() in main");
		new Cupboard();
		print("Creating new Cupboard() in main");
		new Cupboard();
		// table.f2(1);
		cupboard.f3(1);

		

	}

	// static Table table = new Table();
	static Cupboard cupboard = new Cupboard();

}

bowl1 和 bowl2将永远不会被初始化,只有在第一个Table对象被建立的时候,或者第一次访问静态数据的时候,它们才会被初始化。

至此咱们应该清楚 在调用一个类的构造方法以前,会发生一些事情,好比进行初始化。

咱们来总结一下建立对象的过程(其中夹杂了初始化)(很是重要)

1.即便没有显式的使用static关键字,构造器实际上也是静态方法。所以当首次建立类型为Dog的对象时(构造器能够当作静态方法),或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。
2.而后载入Dog.class(后面会看到,这将建立一个Class对象),有关静态初始化的全部动做都会执行。所以,静态初始化只在Class对象首次加载的时候进行一次。
3.当用 new Dog()建立对象的时候,首先将在堆上为Dog对象分配足够的存储空间

4. 这块存储空间会被清零,这就自动的将Dog对象中的全部基本类型数据都设置成了默认值,而引用则被设置成了null
5.执行全部出现于字段定义处的初始化动做(非静态的初始化)
6.执行构造器。正如将在后面看到将涉及不少继承的东西。(在执行构造器以前初始化就完成了

相关文章
相关标签/搜索