## 第4 章 方法、构造器与变量143
### 话题23 相差无几——main 方法很“特殊”吗? 143
public static void main(String[] args)
1.5 可以使用可变参数写法:
public static void main(String...args)
main方法Java虚拟机在程序启动的时候调用的
main方法也可以:
1.重载
2.被子类继承
3.子类可以隐藏父类的main方法
4.在其它地方也可以调用main方法
5.main方法也可以带类型参数
6.可以抛出异常
书中写着是可以,在生产环境中会提示出错
### 话题24 一词多义——方法重载的详细说明150
重载:方法名相同,参数列表类型和个数不一样。
重载方法的选择:选择最明确的
(1)基本数据类型
m(1);
下面的方法都合适,合适程度从高到低:
m(int i){}
m(long i){}
m(float i){}
m(double i){}
(2)引用类型
继承关系:
Square >Rectangle >Parallelogram >Object
m(Square);
下面的方法都合适,合适程度从高到低
m(Square i){}
m(Rectangle i){}
m(Parallelogram i){}
m(Object i){}
(3)自动装箱拆箱和可变参数
m(1);
下面的方法都合适,合适程度从高到低:
m(int i){}
m(Integer i){}
m(int... i){}
包装类型和可变形参不优先考虑是为了向1.5前兼容,可变参数方法最后考虑。
(4)泛型方法:将泛型方法的类型擦除,然后选择最明确的方法调用
m(Y)
public <T extends Y> void m(T t);
public <T> void m(T t);
擦除后:
public void m(Y t);
public void m(Object t);
最明确选择第一个方法
(5)不明确方法:编译出错
重载是在编译时已经确定需要调用的方法,而重写方法是运行时根据调用对象的引用类型确定。
重载方法的静态绑定:根据静态类型(编译时类型)决定调用哪个方法
m(String s){}
m(Object o){}
Object o = new String();
m(o);
虽然o引用指向的是String对象,但o静态类型是Object,所以选择的是m(Object o)
### 话题25 踵事增华——方法重写的真正条件166
重写的条件:
1.5 后放宽了返回类型可以是可替换类型
可替换类型条件:
两个方法都是静态方法是方法隐藏
方法签名指方法的名称与方法的参数类型
1.5 引入@Override 注解,判断子类是否重写父类方法。
1.6 使用@Override标注也可判断是否实现某个接口中抽象方法。
### 话题26 一叶障目——方法与成员变量的隐藏177
父类方法X,子类方法Y
方法隐藏条件:
(1)X,Y都是静态方法
(2)Y的签名是X的子签名
(3)Y返回类型是X的可替换类型
(4)Y访问权限不能比X的低
(5)Y不能抛出更多的异常
(6)子类继承了父类X方法
成员变量隐藏条件:
(1)具有相同名字
(2)子类继承父类的成员变量
重写是动态绑定,根据运行时引用所指向对象的实际类型决定调用相关类的成员。
隐藏是静态绑定的,根据编译时引用的静态类型决定调用相关类的成员。
即
重写效果:当父类引用指向子类对象时,父类引用调用的是子类重写的方法。
隐藏效果:父类引用调用的还是父类的方法。
### 话题27 发轫之始——执行初始化的构造器182
(1)构造器不是方法,也不是类成员。
(2)构造器是递归调用,默认无参构造函数(权限跟类的一致)会调用父类构造器,一直调用到Object的构造器,所有类都是Object的子类。
(3)使用new关键字先创建对象再由系统自动调用构造器,构造器用来初始化类的实例成员,构造器没有创建对象
(4)调用构造器需要在同类或子类的另外一个构造器中使用this或super调用,而且调用的语句必须是第一条语句。
(5)继承关系中,没有显式调用父类构造器,会隐式使用super()调用父类无参构造函数。如果父类定义了有参构造函数就不会自动创建无参构造函数。为了避免出错在子类显式调用父类的有参构造函数或在父类定义一个无参构造函数。
```java
class Tree{
// 定义无参构造函数,或在子类显式调用父类的有参构造函数
// Tree(){}
public Tree(int height){}
}
class AppleTree extends Tree{
// Implicit super constructor Tree() is undefined. Must explicitly invoke another constructor
AppleTree(){
//这里会隐式使用super()调用父类无参构造函数,但父类没有无参构造
//函数(如果父类定义了有参构造函数就不会自动创建无参构造函数),会报错
super(20);
}
}
```
(6)this的来源是在new对象的时候,会将新创建对象的引用作为第一个参数隐式传递到构造器中。当调用实例方法时,也会将该对象的引用作为第一个参数隐式传递。
### 话题28 殊途同归——成员变量不同的初始化方式193
(1)成员变量有默认值,局部变量没有默认值
(2)实例变量的初始化
a.在声明处
b.在实例初始化块中
c.在构造器中
(3)静态变量的初始化
a.在声明处
b.在静态初始化块中
(4)
父类的静态成员变量在所有的子类对象中共享
非静态成员变量在所有的子类对象中独立
(5)
子类继承父类的实例变量X,如果子类没有隐藏变量X,则同一个对象的this.X跟super.X是同一个变量。如果子类隐藏了变量X,则同一个对象的this.X跟super.X不是同一个变量。
子类继承父类的静态变量X,如果子类没有隐藏变量X,则X由父类跟其所有子类共享。如果子类隐藏了变量X,子类跟父类访问的就不是同一个变量X。
### 话题29 按部就班——初始化顺序与向前引用206
初始化的顺序是先静态后实例,先父类后子类
单个类初始化顺序
例如:
```java
public class Test{
Test(int x,int y){
this.x = x;
this.y = y;
}
{实例初始化块01}
private int x;
private int y = initY();
{
实例初始化块02
x = initX();
}
static{静态初始化块01}
private static int staticX;
private static int staticY = initStaticY();
static{
静态初始化块02
staticX = initStaticX();
}
public int initX(){ return 1;}
public int initY(){ return 1;}
public static int initStaticX(){ return 1;}
public static int initStaticY(){ return 1;}
public static void main(String[] args){
Test t1 = new Test(10,15);
Test t2 = new Test(20,15);
}
}
```
注意:无论是初始化静态变量还是实例变量,都按照初始化在类中声明的顺序初始化,不是变量的声明顺序。
上面的Test类初始化执行顺序:
(1) 静态初始化
运行Test类,当虚拟机发现main方法所在的类还没加载,先加载类,执行链接,在链接阶段为类的静态变量分配空间,并设置默认值。
静态初始化块01->staticY ->静态初始化块02->staticX
(2)main执行
类初始化完成后执行main方法,创建Test类对象,使用new在堆中为对象分配空间,所有实例变量都置为默认值。然后执行实例初始化。
main->实例初始化块01->实例变量y初始化->实例初始化块02->实例变量x初始化
(3)执行Test构造器
构造器中对实例变量的初始化会覆盖之前在实例变量声明处和实例初始化块中初始化的值。
(4)再次创建Test实例t2,静态初始化已经完成,直接执行实例初始化。
继承关系的初始化顺序
假设入口main方法在子类中,执行顺序为:
加载链接子类->加载链接父类->父类静态初始化->子类静态初始化->main->父类实例初始化->父类构造->子类实例初始化->子类构造
引用其它类的初始化顺序
在引用的点上执行引用类的初始化,顺序跟上面的一样。
父类构造器不可调用可被子类重写的方法,调用final 和private才是安全的
final修饰的在声明处初始化且值为编译期常量的变量最先被初始化,变量的值会直接写入到字节码(class)文件中,不会生成变量的符号引用,在程序中观察不到默认值。