Think in java 读书笔记
pikzas
2019.03.06
第五章 初始化与清理
Think in Java 中该章节的内容只是初步介绍了一些语法层面的内容,具体的细节需要参考之后的另一本书周志明的《深入理解Java虚拟机》
知识点
方法重载 方法名相同 但是参数列表不同
- 参数的个数
- 参数的类型
- 参数的顺序
基本数据类型参数的重载
基本参数类型可以从一个较小的类型转为一个较大的类型,但是有些要注意
可以看到char是特例 char类型会直接提升到int类型。
反之 如果实参是较大的数据类型,而形参是较小的数据类型。则在调用的时候需要显示强转。表明你已经认识到可能会有丢失数据精度的可能。
一个认知错误的地方就是,不可能依据返回值的不同来重载方法。因为返回值不是方法签名中的一员。
一旦手动改写了构造函数,则编译器就不会为我们隠式添加默认的构造函数。
this关键字指的是调用当前类中某个方法的对象是谁,所以this关键字只可能存在于方法之内。
this的一个应用就是用来返回自身对象
class Demo{
int i = 0;
Demo increment(){
i++;
return this;
}
void print(){
System.out.println("i = "+i);
}
public static void main(String[] args){
Demo demo = new Demo();
demo.increment().increment().increment().print();
}
}
//output
//i = 3
构造器中可以调用别的构造器,但是某个构造器中只能调用最多一次其他构造器,并且调用的代码必须写在该构造器的第一行,同时方法中不能调用构造器。
你的构造器都没执行完,对象都没有创建成功,就不要想搞别的东西。
static关键字的含义
static表示,我是类固有的属性或者方法,不需要依托于该类对象,就能够存在。所以static声明的属性或者方法,在编译器加载.class文件的时候,就被放到内存当中了。所以无论你new出了多少对象,我还是只有一份。
java 中垃圾回收器是如何工作的
- JVM 如果不是在内存即将耗尽的前提下,垃圾回收器是不会去执行垃圾回收的。
- Java 垃圾回收器是 自适应 分代的 停止-复制(stop-copy) 标记-清扫(mark-sweep)式的
自适应 系统处于不同运行阶段,使用不同的垃圾回收策略
分代的 处于稳定期的系统,会对内存分块,同时标记上所处的代数,新生代、老生代、伊甸区。。。
停止-复制 刚启动的系统,加载的新对象较多,需要频繁的分配内存、整理内存。在通过对堆栈和静态存储区中对象标记,找出不在使用的死对象之后,将其内存回收,同时整理空间,此时程序会停止。
标记-清扫 对于较早版本的jvm,如果系统处于稳定期,会同样对内存进行标记,清扫只有内存不够用的时候才会发生。一旦发生,系统也会停止一会。
成员初始化
- 类中的字段(全局变量),如果未给定值,基本数据类型会默认给0、0.0或者空,引用数据类型会给null
- 方法中的字段(局部变量),不给定初始值,编译器就会报错。
全局变量的初始化方式-指定初始化
- 可以直接给特定的值
class Demo{
int i = 123;
}
- 调用方法来赋值
class Demo{
int i = fun();
int j = fun2(i);
int fun2(int in){
return 2*in;
}
int fun(){
return 11;
}
}
构造器初始化
构造器初始化之前,默认的成员初始化一定会发生,该类被分配到内存上的时候,就给所有的全局变量指定了默认值。
think in java 中文第四版(94页)
class Counter{
int i;
Counter(){
i = 7;
}
}
我们无法阻止自动初始化的进行,它将在构造器被调用之前发生。就是 i首先会被置0,然后变成7。对于所有基本类型和引用类型,包括在定义的时候已经指定初始值的变量,这种情况都是成立的。
所以我们在并发的时候要考虑到,对象只进行了自动初始化,构造器还尚未调用的情况,这时候的产生的对象是不完整的对象。
初始化顺序
1 静态对象先初始化(如果之前该类已经实例化过,静态对象就不会再次加载了)
2 然后是非静态对象
3 最后是构造器
初始化实际的执行流程
1 查找*.class文件
2 载入class文件 创建一个Class对象 然后所有静态对象初始化
3 new 对象的时候,首先在堆上分配足够的内存 (静态属性都加载完了)
4 将这块内存区域空间置0 (自动初始化的结果,所有数据先开始是0或者空)
5 执行类中定义的赋值操作 (所有非静态属性有了设定的值)
6 指定构造器 (构造器被调用,可能导致对象的属性发生变化,还可能会导致父类被调用。。。)
静态代码块
class Cup{
static Cpu cup1;
static Cpu cup2;
static {
cup1 = new Cup(); // 只有该对象第一次创建的时候才会执行
cup2 = new Cup();
}
}
非静态代码块
class Cup{
Cpu cup1;
Cpu cup2;
{
cup1 = new Cup(); // 每new一个该对象的时候都会执行一次
cup2 = new Cup();
}
}
数组的初始化
- int[] arrays;
- int arrays[]; 两种定义方式都可以。
数组默认有一个属性length 表明数组内对象的的个数,且数组的下标是从0开始的,所以最大值为length-1
可变参数列表
class Demo{
void fun(Object... objs){
//objs 就是一个数组
}
}
所以在调用fun()的时候,实参可以直接是个数组对象,如fun(new Object[]{1,2,3})。也可以是一串对象,如fun(1,2,3),这时候会自动转换为一个数组作为参数。也可以什么都不传入。
可变参数列表与方法重载之间的冲突 由于可以接受可变参数的方法,也可以什么入参都没有,如果同时又两个可变参数列表的方法,都不传入入参,此时就会爆出编译器错误。
class Demo{
static void f(Character... cs){print("first");}
static void f(Integer... ins){print("second");}
static void f(Long... ls){print("third");}
public static void main(String[] args){
//f(); 这时候会提示编译错误,因为方法f被重载了三次,而不传入参数都可以调用,编译器判定不了,所以报错。
}
}
枚举 也是一个类,只是java默认添加了很多方法,也可以通过接口来实现。但是较为复杂。而且枚举的实现是线程安全的
public enum Smile{
HAHA,XIXI,HEHE
}
自带的一些方法
- ordinal() 按照定义顺序,从0开始计数
- toString() 转为字面量(定义顺序未被写入toString方法)
- values() 获得由字面量组成的数组
class Demo{
public static void main(String[] args){
for(Smile s: Smile.values()){
System.out.println(s + ". ordinal " + s.ordinal());
}
}
}
//output
// HAHA. ordinal 0
// XIXI. ordinal 1
// HEHE. ordinal 2
枚举和switch case并用
switch(Smile){
case HAHA: sout(1111) break;
case XIXI: sout(2222) break;
case HEHE: sout(3333) break;
default: sout(4444);
}