Java初始化之二
this关键字
假设有同一类型的两个对象,分别是a与b.
class Banana{ void peel( int i ){ /*……*/} }
public class BananaPeel{
public static void main(String[] args){
Banana a=new Banana(),
b=new Banana();
a.peel(1);
b.peel(2);
}
}
如果只有一个peel()方法,它是如何知道是被a调用的还是被b调用的呢?
实际上编译器做了一些幕后工作。它暗自把所操作对象的引用作为第一个参数传递给了peel(),所以上述两个方法改写一下,可变为:
Banana.peel(a,1);
Banana.peel(b,2);
这只是内部表现形式,我们并不能这样书写,那这种写法可以帮助你了解实际发生的事。
假设你希望在方法内部获得当前对象的引用。由于这个引用是由编译器偷偷传入的,所以没有标识符可用。但是,为此有个专门的关键词:this。this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。但如果想在当前方法中调用同类中的其他方法就没必要使用this,直接调用即可。
public class Apricot{
void pick();
void pit(){ pick(); }
}
在pit()内部,你可以写this.pick();但没有必要。编译器能自动帮你添加。只有在明确指出当前对象的引用时才会使用this.
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 x=new Leaf();
x.increment().increment().increment().print();
}
}
输出
i = 3
由于increment()通过this关键字反悔了对当前对象的引用,所以很容易在一条与距离对同一个对象进行多次操作。
class Person{
public void eat(Apple apple){
Apple peeled = apple.getPeeled();
System.out.println(“Yummy”);
}
}
class Peeler{
static Apple peel(Apple apple){
return apple;
}
}
class Apple{
Apple getPeeled(){return Peeler.peel(this);}
}
public class PassingThis{
public static void main(String [] args){
new Person().eat(new Apple());
}
}
输出
Yummy
由这个例子可以看出来,this还可以将自身引用传递给外部方法。
在构造器中调用构造器
可能会为一个类编写多个构造器,有时候可能想在一个构造器中调用另一个构造器,以免重复代码,用this可以避免这一点。
通常写this的时候,都指的是“这个对象”或者“当前对象”,而且他本身表示对当前对象的引用。在构造器中,如果为this添加了参数列表,那么就有了不同的含义。这将产生对符合此参数里表的某个构造器的明确调用;这样,调用构造器就有了直接的途径:
public class Flower{
int petalCount=0;
String s=”initial value”;
Flower(int petals){
petalCount=petals;
System.out.println(“Constructor w/ int arg only, petalCount= ”+petalCount);
}
Flower(String ss){
System.out.println(“Constructor w/ String arg onlt, s =”+ss);
s=ss;
}
Flower(String s, int petals){
this(petals);
//! this(s);//不能两个一起调用
this.s=s;
System.out.println(“String & int args”);
}
Flower(){
this(“hi”,47);
System.out.println(“default constructor (on args)”);
}
void printPetalCount(){
//!this(11); //Not inside non-constructor!
System.out.println(“petalCount = ” + petalCount + “ s = “ + s);
}
public static void main(String [] args){
Flower x = new Flower();
x printPetalCount();
}
}
输出
Constructor w/ int arg only, petalCount = 47
String & int args
default constructor (no args)
petalCount = 47 s = hi
从上面的例子可以看出来,尽管可以用this调用一个构造器,但却不能调用两个,此外,必须将构造器调用至于最起始处,否则编译器会报错。
这个例子还展示了this的另一种用法,使用this.s来区分了由于参数和成员名称相同所带来的问题。
此外在printPetalCount()方法中可以看出,除了构造器之外,严谨在其他任何地方调用构造器。
成员初始化
Java会尽力保证所有变量在使用之前都能得到恰当的初始化,对于方法的局部变量,Java以编译是错误的形式来贯彻这种保证。
void f(){
int i;
i++;//error i未被初始化
}
实际上编译器可以为i赋一个默认值,但是局部未初始化的变量更有可能是程序员的疏忽,所以采用默认值反而会掩盖这种失误,因此强制程序员提供一个初始值。
但如果类的数据成员(字段)是基本类型,Java都会保证其会有一个初始值
public class InitialValues{
boolean t;
char c;
byte b;
short s;
int i;
long l;
float f;
double d;
InitialValues reference;
void printInitialValues(){
System.out.println(“Data type Initial value”);
System.out.println(“boolean “+t);
System.out.println(“char “+c);
System.out.println(“byte “+b);
System.out.println(“short “+s);
System.out.println(“int “+i);
System.out.println(“long “+l);
System.out.println(“float “+f);
System.out.println(“double “+d);
System.out.println(“reference ”+reference);
}
public static void main(String [] args){
InitialValues iv=new InitialValues();
iv.printInitialValues();
}
}
输出
Data type Initial value
boolean false
char [ ]
byte 0
short 0
int 0
long 0
float 0.0
double 0.0
reference null
char的值为0,所以显示为空白,尽管我们没有给予变量初始值,但他们确实被赋予了初始值,对于类里的对象引用,如果不将其初始化,此引用就会获得一个null.
指定初始化
如果想为类中某个字段赋值,直接在其被定义的地方赋值即可
public class InitialValues2{
boolean bool=true;
char c=’x’;
byte b=47;
short s=0xff;
int i=999;
long lng=1;
float f=3.14f;
double d=3.141582;
}
也可以用相同的方法来初始化非基本类型对象。
class Depth{}
public class Measurement{
Depth d=new Depth();
}
也可以通过方法返回值来初始化
public class MethodInit{
int i=f();
int f(){return 11;}
}
方法中也可带有参数,参数必须是已经初始化的变量
public class MethodInit2{
int i=f();
int j=g(i);
int f(){return 11;}
int g(int n){reutn n*10;}
}
但下面的写法就是错误的
public class MethodInit3{
//! int j=g(i); //Illegal forward reference
int i=f();
int f(){ return 11;}
int g(int n){ return n*10;}
}
从上面的例子可以看出正确的初始化取决于初始化顺序,而与编译方式无关。
构造器初始化
也可以使用构造器来进行初始化,在运行时刻,可以调用方法或执行某些动作来确定初始值。但要记住:无法阻止自动初始化的进行,例如。
public class Counter{
int i;
Counter(){i = 7;}
}
在这里i首先被赋值为0,而后再被赋值为7.
初始化顺序
在类的内部,变量定义的先后顺序据定了初始化的顺序。即使变量的定义散布于方法定义之间。它仍旧会在任何方法被调用之前得到初始化。
class Window{
Window(int marker){System.out.println(“Window(”+ marker + “)”);}
}
class House{
Window w1=new Window(1);
House(){
System.out.println(“House()”);
w3=new Window(33);
}
Window w2=new Window(2);
void f(){System.out.println(“f()”);}
Window w3=new Window(3);
}
public class OrderOfInitialization{
public static void main(String[] args){
House h=new House();
h.f();
}
}
输出
Window(1)
Window(2)
Window(3)
House()
Window(33)
f()
Window对象的3次实例化分别被安排在了House内部的各个角落,以足以证明它们全部在调用构造器或其他方法之前得到初始化。此外w3被引用2次。
由输出可见,w3这个引用会被初始化两次;一次在调用构造器前,一次在调用构造器后(第一次引用的对象将被丢弃,并作为垃圾回收)