1.new表达式与构造器
new表达式会返回对新建对象的引用,但这与构造器一点关系也没有,构造器本身并没有任何返回值。并且与返回值为空void明显不同,构造器不会返回任何东西。
2.默认构造器
- 当你什么构造器也没写时,系统会自动默认的给你一个无参构造器。
- 但当你写了一个有参构造器,并且当前类中仅有这一个构造器时,那你就只能用这个有参构造器来创建对象,而默认的无参构造器会自动消失,你若想要同时获得一个无参构造器,那就只能再重新写一个无参构造器出来,这样,在当前类中才会同时存在一个无参构造器和一个有参构造器。
3.在构造器中调用一个构造器
- this表示当前对象的引用,若当前类中存在多个构造器,如果想要在当前构造器中调用另一个构造器,就可以用到this关键字,这样做可以避免重复代码。
- 若用this在构造器内部调用其它构造器,只能将对应代码置于最开始的地方,否则编译器会报错。
- 使用this在一个构造器中仅能调用一个构造器。
- 除构造器之外,编译器禁止在其它任何方法中调用构造器。
public class A {
A(int num) {
this("NB", 26); //调用下面的那个A构造器
...
}
A(string str, int num) {
...
}
}
4.方法的局部变量必须强制初始化
对于方法中的局部变量,必须强制给它一个初始值,不然编译器将会报错,这与类中的字段不一样,类中的字段就算你不给予它们一个初始值,编译器也会默认给予一个初始值,但方法中的局部变量不会。所以要加倍小心这种情况的发生。
void f() {
int i; //方法的局部变量,没有初始值,报错。
}
class O {
int i; //类中的字段,没有初始值,没事,会自动给予一个初始值。
}
5.构造器初始化在自动初始化发生后进行
可以使用构造器来进行初始化,这是毋庸置疑的,但是必须在自动初始化之后进行。如:
public class counter {
int i;
int j = 26;
counter() {
i = 66;
j = 66;
}
}
/*
* 先执行自动初始化:
* 第一步: i 和 j 都先给予默认值0
* 第二步: j 被初始化为26
* 再执行构造器初始化:
* 第三步: 构造器中将 i 和 j 都初始化为66
*/
6.初始化操作一定会在构造器或其它方法调用之前进行
不管初始化的语句是在相对于方法的哪个位置,就算是定义在方法的下面,也都会先进行初始化的操作。如:
public class House {
int i = 26;
House() {
...
}
int j = 26;
public static void main() {
House house = new House();
}
}
/*
* 这里调用的顺序是先进行 i 和 j 的初始化,然后再调用House构造函数。
*/
7.new一个对象也是初始化操作
初始化不只是像int i = 26
, House house = new House()
, 类似new一个对象,也是初始化操作。
8.静态初始化的起因
当某一个类的静态方法/静态域被访问时(首次生成该类的一个对象或首次访问该类的静态数据成员时(即使从未生成过该类的对象)),Java解释器通过查找类路径,定位到这个类的.class
文件,然后将该文件载入(这将创建一个class对象),此时,有关静态初始化的所有动作都将被执行。
因此,静态初始化只在class对象首次加载的时候进行一次。
特别注意:由于构造器也是一个静态方法,所以通常在new一个对象,调用构造器的时候,就会定位到该对象所在的类,然后执行该类中的所有静态初始化操作。
特别注意:静态初始化只进行一次。也就是说,当第一次new一个类的对象或者首次访问一个类的静态数据成员时,该类的所有静态初始化操作将被执行,而之后不管再创建几个对象,访问几次该类的静态数据成员,该类的静态初始化操作将不会再进行。
8.1根据《Java编程思想》p146改进笔记:
- 每个类的编译代码都存在于它自己的独立的文件中,该文件只在初次使用时才会被加载。
- 初次使用通常是指:
-
- 创建类的第一个对象之时
- 访问
static
域或static
方法时
- 因为创建类的第一个对象时会调用构造方法,而构造方法也是
static
的,所以更准确的来说,初次使用是指:类是在其任何static
成员被访问时加载的。 - 初次使用之处也是
static
初始化发生之处:所有的static对象和static代码段都会在加载时依据在程序中的书写顺序而依次初始化。当然,static初始化只会发生一次。
9.无继承情况的对象创建过程
假设现在有一个Dog类:
- 在mian方法中new一个Dog对象,在堆中为Dog对象分配足够多的存储空间,然后将该存储空间清零(也就是默认初始化操作)
- Java解释器载入Dog.class文件,然后执行该类的所有静态初始化操作。
- 接着执行该类的字段初始化.
- 执行构造器,成功创建对象。
10.区别于有继承情况的对象创建过程:
就是静态初始化与字段初始化的方式不一样:
- 无继承情况下,因为只有一个类,所以先在一个类中执行静态初始化然后接着执行字段初始化,没毛病。
- 有继承情况下,因为有多个类,所以会先将所有类的静态初始化全部执行过后,再执行父类的字段初始化,然后执行子类的字段初始化。
- 注意:默认初始化已在静态初始化之前完成。
11.静态子句、静态块
public class Spoon {
static int i;
static {
i = 26;
}
}
/*
* 3、4、5行代码就是静态子句、静态块
* 和其它静态初始化动作一样,只执行一次
*/
有个注意的地方,就是静态块里操作的对象,应该也只能是static的。
12.在构造器之前执行的非静态实例初始化子句
{
Mug mug1 = new Mug();
Mug mug2 = new Mug();
}
/*
* 上面就是非静态实例初始化子句,只是用一对花括号括起来,它会执行在构造器之前被调用。
*/
13.初始化的真正全过程
- 执行默认初始化(将分配給对象的存储空间初始化成二进制的零)
- 执行静态初始化
- 执行父类的字段初始化
- 调用父类的构造器(如果此时在父类构造器里面调用父类的某个被子类重写了的方法的话,调用的是子类中重写之后的版本,这是构造器内部的多态方法行为)
- 执行子类的字段初始化
- 调用子类的构造器
注:参考《Java编程思想》p162 8.3.3 构造器内部的多态方法的行为
14.将数组赋值给另一个数组
在Java中,可以将一个数组赋值给另一个数组,但是真正做的只是复制了一个引用。如:
int a1[] = {1, 2, 3, 4, 5};
int a2[];
a2 = a1;
上面代码将a1对应的数组的引用复制给了a2,使a1和a2代表了同一个数组。
往后任何对该数组的修改,a1和a2都将受到影响。
15.Random.nextInt(int number)
该方法返回0到输入参数之间的一个值。
Random rand = new Random(47); //47是随机数种子,创建Random对象的时候一般要传入进去。
int number = rand.nextInt(20); //返回0到20之间的一个值
16.创建非基本数据类型的引用数组
当你创建了除基本数据类型之外的数组时,你将创建的是一个引用数组,必须将对象传入给它,否则,如果你试图使用数组中的空引用,就会在运行时产生异常。
Random rand = new Random(47);
Integer[] a = new Integer[rand.nextInt(20)];
for(int i = 0; i < a.length; i++) {
a[i] = rand.nextInt(500); //自动包装机制会将 Int 基本数据类型转成 Integer 类型,传给引用数组。
}
17.可用花括号括起来的列表初始化对象数组
Integer a[] = {
new Integer(1),
new Integer(2),
new Integer(3), //最后这个逗号可加可不加。
};
18.可变参数列表
用Object... args
表示可变的参数列表,这时候,你可以传入任意个参数。
void f(string... strs);
f("one");
f("one", "two");
f();
/*
* 注意,就算没有传入参数,也是可以的。
*/
19.枚举类型enum
可以把enum看成是一个类,只是它只有数据成员,并且都是常量值。
public enum Animal {
DOG, CAT, HORSE, LION
}
使用上面的枚举类型Animal:
Animal animal = Animal.CAT;
System.out.println(animal);
* Output:
* CAT