第五章 初始化与清理
文章目录
- 问题:
- 忘记初始化、不知道如何初始化
- 忘记清理已经不用的变量 ——> 资源用尽
- 解决:
- 构造器:一个在创建对象时被自动调用的特殊方法
- 垃圾回收器:对于不再使用的内存资源,垃圾回收器会自动将其释放。
5.1 用构造器确保初始化
Java会在用户有能力操作对象之前自动调用相应的构造器,从而保证初始化的进行。
-
构造器命名:
- 避免与成员命名冲突
- 编译器必须知道构造器名称,以便调用
构造器采用和类相同的名称
package com.alex2ice.chapter5;
import static com.alex2ice.util.Print.print;
/**
* @author Alex2ice
* @version v1.0.0
* @time 2023/2/20 15:56
* @description 用构造器确保初始化
*/
public class Demo01 {
// 如果没有写构造器,会默认生成一个无参构造器
Demo01() { // 无参构造器
print("Demo01");
}
// 构造器重载
Demo01(int id) { // 有参构造器
print("Demo01 " + id);
}
public static void main(String[] args) {
new Demo01();
new Demo01(512); // new表达式返回了对新对象的引用,但构造器不会返回任何东西。
}
}
5.2 方法重载
Java中,想要以不同的方式创建对象,就必须对构造器进行重载,同时也可以对成员方法进行重载
5.2.1 区分重载方法
每个重载的方法必须有一个独一无二的参数类型列表
参数数目不同、类型不同、顺序不同...都可以
5.2.2 涉及基本类型的重载
基本类型能自动从一个“较小”的类型提升至一个“较大”的类型
public class Demo02 {
static void f1(char x) {printnb("f1(char)");}
static void f1(byte x) {printnb("f1(byte)");}
static void f1(short x) {printnb("f1(short)");}
static void f1(int x) {printnb("f1(int)");}
static void f1(long x) {printnb("f1(long)");}
static void f1(float x) {printnb("f1(float)");}
static void f1(double x) {printnb("f1(double)");}
static void f2(byte x) {printnb("f2(byte)");}
static void f2(short x) {printnb("f2(short)");}
static void f2(int x) {printnb("f2(int)");}
static void f2(long x) {printnb("f2(long)");}
static void f2(float x) {printnb("f2(float)");}
static void f2(double x) {printnb("f2(double)");}
static void f3(short x) {printnb("f3(short)");}
static void f3(int x) {printnb("f3(int)");}
static void f3(long x) {printnb("f3(long)");}
static void f3(float x) {printnb("f3(float)");}
static void f3(double x) {printnb("f3(double)");}
static void f4(int x) {printnb("f4(int)");}
static void f4(long x) {printnb("f4(long)");}
static void f4(float x) {printnb("f4(float)");}
static void f4(double x) {printnb("f4(double)");}
static void f5(long x) {printnb("f5(long)");}
static void f5(float x) {printnb("f5(float)");}
static void f5(double x) {printnb("f5(double)");}
static void f6(float x) {printnb("f6(float)");}
static void f6(double x) {printnb("f6(double)");}
static void f7(double x) {printnb("f7(double)");}
public static void main(String[] args) {
printnb("const value: ");
f1(5);f2(5);f3(5);f4(5);f5(5);f6(5);f7(5);
print();
printnb("char value: ");
char x = 'x';
f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
print();
printnb("byte value: ");
byte b = 1;
f1(b);f2(b);f3(b);f4(b);f5(b);f6(b);f7(b);
print();
printnb("short value: ");
short s = 1;
f1(s);f2(s);f3(s);f4(s);f5(s);f6(s);f7(s);
print();
printnb("int value: ");
int i = 1;
f1(i);f2(i);f3(i);f4(i);f5(i);f6(i);f7(i);
print();
printnb("long value: ");
long l = 1;
f1(l);f2(l);f3(l);f4(l);f5(l);f6(l);f7(l);
print();
printnb("float value: ");
float f = 1;
f1(f);f2(f);f3(f);f4(f);f5(f);f6(f);f7(f);
print();
printnb("double value: ");
double d = 1;
f1(d);f2(d);f3(d);f4(d);f5(d);f6(d);f7(d);
/*
const value: f1(int)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double)
char value: f1(char)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double)
byte value: f1(byte)f2(byte)f3(short)f4(int)f5(long)f6(float)f7(double)
short value: f1(short)f2(short)f3(short)f4(int)f5(long)f6(float)f7(double)
int value: f1(int)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double)
long value: f1(long)f2(long)f3(long)f4(long)f5(long)f6(float)f7(double)
float value: f1(float)f2(float)f3(float)f4(float)f5(float)f6(float)f7(double)
double value: f1(double)f2(double)f3(double)f4(double)f5(double)f6(double)f7(double)
*/
}
}
- 结论
-
常数值5被当作 int 处理,所以如果有某个重载方法接受int型参数,它就会被调用。
-
其它类型,如果传入的数据类型小于方法中声明的形式参数类型,实际数据类型就会被提升。
-
char型,如果无法找到恰好接受char参数的方法,就会把char直接提升至 int 型
方法接受较小的基本类型作为参数。但如果传入的实际参数较大,就得通过类型转换来执行窄化转换。
-
public class Demo03 {
static void f1(char x) {printnb("f1(char)");}
static void f1(byte x) {printnb("f1(byte)");}
static void f1(short x) {printnb("f1(short)");}
static void f1(int x) {printnb("f1(int)");}
static void f1(long x) {printnb("f1(long)");}
static void f1(float x) {printnb("f1(float)");}
static void f1(double x) {printnb("f1(double)");}
static void f2(char x) {printnb("f2(char)");}
static void f2(byte x) {printnb("f2(byte)");}
static void f2(short x) {printnb("f2(short)");}
static void f2(int x) {printnb("f2(int)");}
static void f2(long x) {printnb("f2(long)");}
static void f2(float x) {printnb("f2(float)");}
static void f3(char x) {printnb("f3(char)");}
static void f3(byte x) {printnb("byte)");}
static void f3(short x) {printnb("f3(short)");}
static void f3(int x) {printnb("f3(int)");}
static void f3(long x) {printnb("f3(long)");}
static void f4(char x) {printnb("f4(char)");}
static void f4(byte x) {printnb("f4(byte)");}
static void f4(short x) {printnb("f4(short)");}
static void f4(int x) {printnb("f4(int)");}
static void f5(char x) {printnb("f5(char)");}
static void f5(byte x) {printnb("f5(byte)");}
static void f5(short x) {printnb("f5(short)");}
static void f6(char x) {printnb("f6(char)");}
static void f6(byte x) {printnb("f6(byte)");}
static void f7(char x) {printnb("f7(char)");}
public static void main(String[] args) {
printnb("double value: ");
double d = 1;
f1(d);f2((float) d);f3((long) d);f4((int) d);f5((short) d);f6((byte) d);f7((char) d);
/*
double value: f1(double)f2(float)f3(long)f4(int)f5(short)f6(byte)f7(char)
*/
}
}
5.2.3 以返回值区分重载方法
void f() {}
int f() {return 1;}
int i = f(); 确实可以根据此区分重载方法
f(); 调用函数而忽略返回值,就无法判断所调用函数是哪一个
所以,根据方法返回值区分重载函数是行不通的
5.3 默认构造器
默认构造器,又叫无参构造器,它的作用是创建一个“默认对象”。
在类没有构造器时,编译器会自动创建一个默认构造器。
但是,如果已经定义了一个构造器(无论是否有参数),编译器就不会自动帮你创建默认构造器。
5.4 this关键字
public class Demo05 {
// f()方法如何区分是那个对象调用的方法?
// java编译器会暗自把“所操作对象的引用”作为第一个参数传递给 f().
// Demo05.f(d1, 1);
// Demo05.f(d2, 2);
public void f(int i) {
System.out.println("f方法被调用了" + i);
}
public static void main(String[] args) {
Demo05 d1 = new Demo05(), d2 = new Demo05();
d1.f(1);
d2.f(2);
}
}
java编译器会暗自把“所操作对象的引用”作为第一个参数传递给 f().
this 关键字:在方法内部,表示“调用方法的那个对象”的引用。
this的使用
public class Demo05 {
int i = 0;
public Demo05 increment() {
i++;
// this作为参数
Test.test(this);
// this作为返回值
return this;
}
@Override
public String toString() {
return "" + i;
}
public static void main(String[] args) {
Demo05 d1 = new Demo05();
System.out.println(d1.increment().increment().increment().toString());
} // 3
}
class Test {
public static void test(Demo05 d) {
System.out.println(d);
}
}
5.4.1 在构造器中调用构造器
调用该类的其它构造器,可以通过 this(所调用构造器需要的参数); 调用
public class Demo05 {
int i;
public Demo05() {
this(512); // this必须写在首行
// 先调用有参构造器,再执行无参构造器后续代码
System.out.println("无参构造器被调用了");
}
public Demo05(int i) {
System.out.println("有参构造器被调用了");
this.i = i;
}
public Demo05 increment() {
// this(); // Call to 'this()' must be first statement in constructor body
i++;
return this;
}
public static void main(String[] args) {
Demo05 d1 = new Demo05(); // 有参构造器被调用了 无参构造器被调用了
}
}
5.4.2 static 的含义
static方法就是没有this的方法。
在static内部不能调用其它非静态方法,但反过来可以
static方法很像全局方法,,在类中置入static方法就可以访问其它static方法和static域
5.5 清理:终结处理和垃圾回收
Java有垃圾回收器负责回收无用对象占据的内存资源
但如果对象不是通过new来获得的,那么垃圾回收器就不知道该如何释放这块“特殊”内存。
Java允许在类中定义一个名为finalize()的方法。
原理:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。
System.gc()用于强制进行终结动作。
无论是垃圾回收还是终结都不保证一定会发生。如果JVM 未面临用尽的情况,它是不会浪费时间去执行垃圾回收以恢复内存的
5.5.1垃圾回收器如何工作
引用计数法(简单但速度很慢,在对象中记录引用该对象的引用数目),引用数目为0时,回收该对象内存。
垃圾回收器的思想:对任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。
- 停止-复制
我们把内存分成两部分,一段时间内只允许在其中一部分内存分配空间。
当这块内存满了之后,我们把所有存活对象全部复制到另一部分内存上,并对引用进行修正,当前内存则全部清空。 - 标记-清扫 对一般用途而言,这种方式很慢,但如果只有少量垃圾甚至没有产生垃圾时,速度就很快了。
思路:先对追溯到的存活对象进行标记,标记结束后,再清理释放掉没有标记的对象。
问题:剩下的可用空间会是不连续的,不便于堆指针后续使用,所以想要连续的空间的话,就需要重新整理剩下的对象。