【构造器】
① 当new出一个对象时,便调用了该类的构造函数
② 如果没有自己写构造函数,编译器会帮我们写一个(这样没有参数的构造函数):
public Dog(){
// 构造函数体为空
}
可以看出构造函数的特点是:构造函数没有返回类型,和类同名
③
public class Loli{
int age; // 实例变量
public age(){ // 自己写的不含参的构造函数(用于赋予默认值)
age = 12;
}
public age(int theAge){ // 带参数的构造函数,new对象时传入参数
age = theAge;
}
public age(int theAge, boolean isGothic){ // 构造函数同样可以重载
age = theAge;
System.out.println("Saikou!")
}
}
※ 注:
- 当你自己写了任何一个构造函数时,不管有参无参,编译器都不会再生成那个不带参、函数体为空的默认构造函数
- 构造函数可以是共有、私有、或者不指定的
- 抽象的类也有构造函数:虽然抽象类本身不能new出对象,但它的子类在构造时会调用到它的构造函数
③ 关于构造函数与父类继承的关系(即子类构造函数和父类构造函数的关系),
首先要明确的是:在新建对象时,整个构造函数链都会被执行
④ (★☆★)这个构造函数链的执行顺序是:
首先最末端的构造函数开始执行,而这个构造函数在执行的时候,第一件事是去执行它的父类的构造函数(这是通过编译器在每个构造函数的第一行自动加上的super()实现的),父类的构造函数执行时,又立即去执行它的父类的构造函数…如此向上,一路到Object。
下面的图生动清晰展现了这个在栈上进行的过程:
为什么是以这样的次序进行构造呢?这涉及到对象的本质结构;
对象也是有层次的,这个洋葱似的层次结构是它在构造时的次序的真正原因:
☀ 注:
- super()必须在构造函数的第一行;编译器默认添加的super()也是默认在第一行的
- 位于构造函数链末端的构造函数(自身的构造函数)最早被调用,但最后一个执行完毕
- 一个极其重要的思想:对象的父类必须先完备,子类才能创建
- 如果父类的构造函数带有参数,则子类在调用它时在super()中也传入参数就可以了:诸如:
super(age);
、super(age, size);
⑤ 使用this()从某个构造函数调用同一个类的另外一个构造函数
⑥ this()只能用在构造函数的第一行,因此this()与super()不能兼得
【垃圾收集器(Garbage Collector, GC)】
① 先弄清楚局部变量和实例变量的生存周期:
局部变量只会存活在声明该变量的方法中。当方法中调用另一个方法时,另一个方法压入栈并成为栈顶,原先的第一个方法的局部变量被暂存等待,并且此时栈顶的方法不在其范围中(也就是说,局部变量的范围仅仅在一层方法中);
这个方法运行结束后,弹出栈顶,原先等待的局部变量重新有效。这个方法也结束后,弹出栈顶,其内部的局部变量被释放;
实例变量的寿命与对象相同。只要对象活着,实例变量就是活的;
② 而对象的生命周期呢?完全依赖于指向这个对象的引用。当最后一个指向它的引用消失时,对象就会变成可回收的。
我们有三种方法“杀死”一个对象:
- 引用永久的离开它的范围(引用也是个变量,当它是在方法中声明的,它就是一个局部变量,其生命周期与方法一致)。在方法中new出一个对象,随着方法结束,从栈顶弹出,其局部变量(包括指向这个对象的引用)被释放,这就预示着这个对象的生命随着引用的释放而走到了终点(被标记为可回收);
- 引用被赋值到其他对象上。如果这个被移开的引用是指向这个对象的唯一引用,那么该对象变为可回收;
- 直接将引用设定为null。原理和上边两条一样,没了引用,对象无法以任何方法被触碰到,所以"死了"
【静态】
① 非静态是对象的,通过对象名称进行调用;静态是类的,通过类名直接调用。
② 静态方法不依赖于堆上的实例对象。
③ 静态变量是被同类的所有实例对象共享的
④ 实例变量:每个实例(对象)一个;
静态变量:每个类一个
⑤ 在静态方法中调用非静态的变量的方法是不合法的
☀ ☀ ☀ 机制解析 :
静态变量在类第一次被加载时完成初始化。JVM何时会加载某个类呢
?
有两种情况:一是第一次有人尝试创建该类的实例;二是使用该类的静态变量或方法;
因此,“类被加载”和“类被创建”的逻辑关系是:类被创建时会去加载类;但类被加载不能说明类正在被实例化,还有可能是调用了该类的静态变量或方法;
而且,就算是第一次在实例化类的过程中,静态变量的初始化也是要比实例变量的初始化要超前的;
明确一个基本原理,可以帮助记忆:
静态的变量和方法提前初始化了,因而你可以放心地在非静态方法中调用它们;
反之则不行:因为此时非静态变量可能还没被初始化
(静态方法不仅不能调用非静态变量,非静态方法也不行,即使该方法压根没有调用非静态变量)
⑥ 两个保证:
- 静态变量会在该类的任何对象创建之前就完成初始化
- 静态变量会在该类的任何静态方法执行之前就完成初始化
⑦ 静态变量有默认值(0, 0.0, flase, null)
⑧ 请再一次回答这个问题:为什么不能再静态方法中调用非静态的变量和方法?
Answer:类的加载发生在调用静态变量和方法,以及尝试new出对象时。 在第一种情况下,类被加载,静态初始化程序被触发;但是构造函数未被触发,非静态的变量和方法并未被初始化。在静态方法中可以调用静态变量,因为静态变量的初始化在静态方法之前;但是显然,在静态方法中不能调用还未初始化的非静态变量和方法。
【常数】
① 静态的final变量可以看作是常量:
static使它不依赖于对象就可使用;final防止它的值被修改
② 常量的命名惯例是全部大写
③ 什么是静态初始化程序?
静态初始化程序(static initializer)是一段在加载类是会执行的程序代码(加载类和new出对象是两回事)
④ final的静态变量没有默认值,必须在声明时或在静态初始化中被被赋值
class Foo {
final static int x; // 声明final的静态变量(常量特征);没有在声明时赋值在,则必须在下面的静态初始化程序中被赋值
static { // 静态初始化程序
x = 12;
}
}
⑤ 静态初始化程序会在构造函数之前执行 !
⑥ final的用法和效果:
class Foo {
final int size = 3; // final修饰实例变量
final int age; // final修饰实例变量
Foo() {
age = 12;
}
void func(final int x) { // final修饰方法的参数
// 方法体中不能改变 x 的值
// 如果参数是引用,则这个引用的指向不能被修改
final int y; // final修饰局部变量
}
}
// 另外:
// final修饰方法,则该方法无法被覆盖
// final修饰类,则该类无法被继承
【auto-boxing/unboxing机制】
① 8个primative主数据类型有对应的8个包装类:
Boolean, Character, Byte, Short, Integer, Long, Float, Double
②
int n = 12;
Integer wrap = new Integer(n); // 包装
int unWrap = wrap.intValue(); // 解包装
③ 由于自动包装-解包装机制(auto-boxing/unboxing)的存在,primitive主数据类型和其对应的包装类在一般情况下,完全可以混用
通常需要注意两个地方:
- 一是generic类型(泛型)的规则是只能指定类或接口,诸如
ArrayList<int>
无法通过编译; - 二是基本数据类型及其包装类的默认值是不同的,比如int默认值为0,Integer默认值为null
④ 包装类的几个静态方法十分常用 :
String s = "233";
int n = Integer.parseInt(s); // "2" 被解析成 2
String s = "12.345f";
double d = Double.parseDouble(s); // "12.345f" 被解析成 12.345
// 作用显而易见:将String参数解析成对应的包装类类型
// 不要自作聪明!参数只能是String!
另外,值得一提的是:
boolean b1 = new Boolean(true); // 传入boolean进行包装当然可以
boolean b2 = new Boolean("true"); // 传入String也会被包装成Boolean
↑ 均合法。这是因为Boolean的构造函数有个String参数的重载方法
【格式化】
① format是String的一个静态方法 :
System.out.println(String.format("%s and %d", "loli", 233));
也可以这样:
System.out.format("%s and %d", "loli", 233);
☀ 《Head First Java》Kathy Sierra & Bert Bates