Java学习(七)—— 构造器与垃圾收集器

系列文章目录

一:上手
二:类和对象
三:primitive主数据类型和引用
四:方法操作实例变量
五:编写程序
六:继承、接口与多态
七:构造器与垃圾收集器



前言

行百里者半九十


对象有生有死,因此要为对象的生命循环周期负责,决定着对象何时创建、如何创建,何时销毁对象。

1、栈与堆:生存空间

在我们能够弄清楚java创建对象的真正发生过程之前,我们需要退回一步,先对生存在java中的事务更加了解,这也就表示我们要对栈与堆有着更多的认识。

在java中,我们会更加注重内存中的两种区域,一个是对象的生存空间堆(heap),一个是方法调用及变量的生成空间栈(stack)。

当java虚拟机启动时,它会从底层的操作系统中取得一块内存,并以此区域来执行java程序。而且我们要知道,所有的对象都存活与可垃圾回收的堆上。但是我们却没有绝对的定义过变量存货的区域。那是因为变量分为实例变量和局部变量,实例变量存在于所属的对象中,也就是说它存活于堆上,而局部变量的和方法的参数都是声明在方法中,是暂时的,所以他们生命周期只限于这个方法的执行时间,所以这种区域变量,又被称为栈变量,所以它存活于栈中。

实例变量
实例变量是被声明在类而不是方法中的,他们代表每个独立对象的“字段”,每个实例都能有不同的值,实例变量存在于所属的对象中:

public class Duck {
	int size;//每个Duck都会有独立的size
}

局部变量
局部变量和方法的参数都是被声明在方法中的,他们是暂时的,且生命周期只限于方法被放在栈上的这段期间,也就是方法调用至执行完毕为止。

public void foo(int x) {
	int i = x + 3;
	boolean b = true;
}

方法会被堆在一起
当你调用一个方法时,这个方法会被放在调用栈的栈顶,如果这个方法继续调用别的方法的话,那么被调用的新的方法就会被放在这个栈的栈顶,压着刚才那个方法,也就形成了一个栈块,而栈块的执行顺序就是由上到下。被放上栈的其实是堆栈块,它带有方法的状态,包括执行到哪一行程序以及所有的局部变量的值。

2、栈上的对象引用

非primitive的变量只是保存对象的引用,而不是对象本身,对象本身只会存在于堆上。


public class StackRef{
    public void foof(){
        barf();
    }
    public void barf(){
        Duck d=new Duck(24);
    }
}

比如上面程序中先声明了foof,将他置在栈上,然后调用了braf方法,至于栈顶,构建了Duck对象的引用变量d,此时的d就在堆上,不管对象是在哪里声明的,他总是运行在堆上。
在这里插入图片描述
如果局部变量生存在栈上,那么实例变量呢?

实例变量就是存在于对象所属的堆空间上。

记住对象的实例变量的值,是存在对象中。如果实例变量都是primitive主数据类型的,则java会根据类型大小分配空间,int需要32,但不论int的值多大,它都是32位。所以java并不在乎类型的值,只在乎类型。
举个例子,现在有一个类,里面有一个实例变量,实例变量的类型是另一个类的类型,那么我们分析一下它们在堆上的联系。


public class CellPhone(){
    private Antenna ant;   //声明引用对象,没有赋值
    priavte Antenna antt=new Antenna();  //声明并赋值
}

如果有声明变量但没有给它赋值,则只会留下变量的空间
private Antenna ant;
直到引用变量赋值一个新的Antenna对象才会在堆上占有空间:
private Antenna ant = new Antenna();
在这里插入图片描述

3、构造函数

回顾一下创建对象的步骤:声明、创建、赋值Duck myDuck = new Duck();。这里的Duck()看起来很像是在调用Duck()方法,但其实并不是。我们是在调用Duck的构造函数

构造函数看起来很像方法,但他并不是方法,它带有new的时候会执行的代码程序,换句话说,这段程序代码会在你初始一个对象的时候执行。就算你没有自己写构造函数,编译器也会帮你写一个。

唯一能够调用构造函数的办法就是新建一个类。

程序举例

下面我们构造一个Duck,让我们更好的介入new过程

class Duck {
	//构造函数在此
	public Duck() {
		System.out.println("gagaga");
	}
}

public class UseDuck {
	
	public static void main(String[] args) {
		Duck d = new Duck();//在这里会启动Duck的构造函数
	}
}

注意有什么不同,方法有返回类型,构造函数没有返回类型。
运行结果:
在这里插入图片描述

如果某种对象不应该在状态被初始化之前就使用,就别让任何人能在不初始化的情况下取得该对象!让用户先构造出Duck对象在设定状态(比如大小)很危险,如果用户不知道或者忘记执行setSize()怎么办?

最好的办法就是把初始化的程序放在构造函数中,然后把构造函数设置成需要参数的

class Duck {
	
	int size;
	//构造函数在此
	public Duck(int DuckSize) {
		System.out.println("gagaga");
		size = DuckSize;
		System.out.println("Size is " + size);
	}
}

public class UseDuck {
	
	public static void main(String[] args) {
		Duck d = new Duck(24);//在这里会启动Duck的构造函数
	}
}

但上面的程序不传入参数时会报错,因此一个更具有鲁棒性的写法就是

class Duck {
	
	int size;
	//构造函数在此
	
	public Duck() {
		size =27;//指定默认值
		System.out.println("gagaga");
		System.out.println("Size is " + size);
	}
	
	public Duck(int DuckSize) {
		System.out.println("gagaga");
		size = DuckSize;//使用参数设置
		System.out.println("Size is " + size);
	}
}

public class UseDuck {
	
	public static void main(String[] args) {
		//不知道大小时
		Duck d1 = new Duck();//在这里会启动Duck的构造函数
		//知道大小时
		Duck d2 = new Duck(48);
	}
}

执行结果
在这里插入图片描述
这里就是构造函数的重载

重载构造函数

重载构造函数的意思代表你有一个以上的构造函数且参数都不相同

public class TestDuck {
	
	public static void main(String[] args) {
		
		int weight = 8;
		float density = 2.3F;
		String name = "Donald";
		long[] feathers = {1,2,3,4,5,6};
		boolean canFly = true;
		int airspeed = 22;
		
		Duck[] d = new Duck[7];
		d[0] = new Duck();
		d[1] = new Duck(density, weight);
		d[2] = new Duck(name, feathers);
		d[3] = new Duck(canFly);
		d[4] = new Duck(3.3F, airspeed);
		d[5] = new Duck(false);
		d[6] = new Duck(airspeed, density);
	}
}

class Duck {
	
	int pounds = 6;
	float floatability = 2.1F;
	String name = "Generic";
	long[] feathers = {1,2,3,4,5,6,7};
	boolean canFly = true;
	int maxSpeed = 25;
	
	public Duck() {
		System.out.println("type 1 duck");
	}
	
	public Duck(boolean fly) {
		canFly = fly;
		System.out.println("type 2 duck");
	}
	
	public Duck(String n, long[] f) {
		name = n;
		feathers = f;
		System.out.println("type 3 duck");
	}
	
	public Duck(int w, float f) {
		pounds = w;
		floatability = f;
		System.out.println("type 4 duck");
	}
	
	public Duck(float density, int max) {
		floatability = density;
		maxSpeed = max;
		System.out.println("type 5 duck");
	}
}

如果你已经写了一个有参数的构造函数,并且需要一个没有参数的构造函数,则必须要自己写。此时的编译器无法帮你生成无参的构造函数。

父类的构造函数在对象的生命中所扮演的角色

在创建新对象时,所有继承下来的构造函数都会执行
这代表每个父类都有一个构造函数(因为每个类都至少有一个构造函数),且每个构造函数都会在子类对象创建时期执行。执行new的指令是个重大事件,他会启动构造函数的连锁反应。还有,就算是抽象的类也有构造函数,虽然不能对抽象的类执行new操作,但是抽象的类还是父类,因此它的构造函数会在具体的子类创建出实例时执行。

如何调用父类的构造函数?

调用父类构造函数的唯一方法是调用super()

abstract class Animal {
	private String name;
	
	public String getName() {//Hippo会继承这个getter方法
		return name;
	}
	
	public Animal(String theName) {//有参数的构造函数,用来设定name
		name = theName;
	}
}

class Hippo extends Animal {
	
	public Hippo(String name) {
		super(name);//传给Animal的构造函数
	}
}

public class MakeHippo {
	public static void main(String[] args) {
		Hippo h = new Hippo("Buffy");
		System.out.println(h.getName());
	}
}

执行结果
在这里插入图片描述

注意,父类的部分必须在子类创建完成之前就必须完整的成型,子类对象可能会动用到从父类继承下来的东西,所以父类的构造函数一定要再子类的构造函数之前完成。

从某个构造函数中调用重载版的另一个构造函数

  • 使用this()来从某个构造函数调用同一个类的另一个构造函数
  • this()只能用在构造函数中,且必须是第一行语句
  • super()与this()不能兼得

比如比较年龄大小这个示例程序

class Person{
	
	int id; 
	String name; 
	int age;  
	
	//有参构造函数
	public Person(int id,String name ,int age){
		this.id  = id;
		this.name = name;
		this.age = age;
	}
	//比较年龄的方法,这是本身就具备的,还有一个和谁比较是未知数
	public void compareAge(Person p2){
		if(this.age>p2.age){
			System.out.println(this.name+"大!");
		}else if(this.age<p2.age){
			System.out.println(p2.name+"大!");
		}else{
			System.out.println("同龄");
		}
	}
}
 
public class comparePerson{
	public static void main(String[] args) {
		Person p1 = new Person(110,"狗娃",17);
		Person p2 = new Person(119,"铁蛋",9);
		p1.compareAge(p2);
	}
}

4、对象的生命周期

  • 局部变量只会存活在声明该变量的方法中
  • 实例变量的寿命与对象相同,如果对象还活着,则实例变量也是活的
  • 除非有对对象的引用,否则该对象一点意义也没有

三种方法可以释放对象的引用

1、引用永久性离开它的范围

void go() {
	Life z = new Life();
}

z会在方法结束时消失

2、引用被赋值到其他的对象上

Life z = new Life();
z = new Life();

第一个对象会在z被赋值到别处时挂掉

3、直接将引用设定为null

Life z = new Life();
z = null;

第一个对象会在z被赋值为null时死掉。

null的理解
当你把引用设置为null时,你就等于抹除了遥控器的功能,换句话说,你拿了一个没有电视的遥控器。
你期待喵喵叫的这个动作,但是没有Cat可以执行!

5、总结

  • 我们关心栈与堆这两种内存空间
  • 实例变量声明在类中方法之外的地方
  • 局部变量声明在方法或者方法的参数上
  • 所有局部变量都存在与栈上相对应的堆栈块中
  • 对象引用变量与primitive主数据类型变量都是放在栈上
  • 不管是实例变量还是局部变量,对象本身都在堆上
  • 实例变量保存在所属的对象中,位于堆上
  • 如果实例变量是个对对象的引用,则引用与对象都在堆上
  • 构造函数是在新建对象时执行的程序代码
  • 构造函数必须与类名同名且没有返回类型
  • 你可以用构造函数来初始化被创建的对象的状态
  • 如果没写构造函数,编译器会帮你安排一个
  • 默认的构造函数是没有参数的
  • 最好能有无参数的构造函数让人选择使用默认参数
  • 重载的构造函数意思是有超过一个以上的构造函数,且有不同的参数
  • 两个构造函数的参数必须不同
  • 实例变量有默认值,原始的默认值是0、0.0、false,引用变量的默认值是null
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值