1.类和对象
  • 什么是
    • 把一类事物的静态属性和动态操作组合在一起就形成了类;
    • 类是抽象的,用来模拟一类事物;
    • 类只是对象的设计模板,并不占用内存,在创建对象的时候根据类的设计分配内存;
  • 什么是对象
    • 对象是类的一个实体
    • 对象是具体的,实际存在的;
    • 对象具有生命周期;

比如,可以将类比作是一辆汽车的设计图纸,一辆辆汽车就是根据这个类生产出的对象:
Java基础_04 | 正式进入Java的世界(类和对象、类和对象的内存分配详解、构造函数)_JavaSE基础

2.类的定义和对象的创建

2.1.如何定义类?

  • 定义类使用class关键字;
  • 类中定义的变量称为这个类的属性
  • 类中定义的函数称为这个类的方法

比如定义一个Car类:

class Car
{
	//Car类的属性
	int number;		//车辆编号
	
	//Car类的方法
	void go()
	{
		//类中的方法访问类的属性
		System.out.printf("My number is %d, go go go!", number);
	}
}

2.2.如何创建一个对象?

在主方法中创建一个Car类的对象并访问其成员和方法:

class CarTest
{
	public static void main(String[] args)
	{
		//创建一个动态对象
		Car testCar = new Car();
		
		//访问对象的属性并修改
		testCar.number = 1;

		//调用对象的方法
		testCar.go();
	}
}

编译运行:

javac CarTest
java  CarTest

运行结果:
Java基础_04 | 正式进入Java的世界(类和对象、类和对象的内存分配详解、构造函数)_自定义_02

3.类和对象内存分配详解(重点)

!!!
这里说的内存指的是程序运行时占用的内存,可以理解为RAM空间。
!!!

  • 编写好程序源代码后,执行javac CarTest,生成的CarTest.class可执行程序保存在硬盘上
  1. 当我们执行java CarTest命令后,操作系统将整个程序加载到内存中,准备运行;
  2. 程序从main入口函数开始执行
  3. 程序在执行过程中进行内存分配,接下来我们开始逐步解析:

Java基础_04 | 正式进入Java的世界(类和对象、类和对象的内存分配详解、构造函数)_JavaSE基础_03

3.1.类的内存空间如何分配?

类只是一个概念,是抽象的,用来模拟一个事物,这里用来模拟一个车辆,所以类不会占用任何内存空间

3.2.对象的内存如何分配?

Car testCar = new Car();
  1. Car()函数称为Car类的构造函数,虽然我们并没有定义,但是这个函数是默认存在的,在创建一个该类对象时自动调用,在第4节具体讲述;
  2. new关键字表明这个对象创建时内存空间要动态分配,所以一个new出来的对象其内存空间在堆中new关键字的作用和C语言中malloc函数的作用相同,只不过更为简单,总结:系统按照类中的定义在堆中分配内存,然后将首地址返回
  3. 这个对象的首地址返回后,保存在testCar变量中,所以按照C语言来说是testCar指向堆中的Car对象,按照Java对象来说是testCar变量引用了堆中Car对象的地址
  4. testCar是一个Car类型的变量,并且在主函数中定义,所以是一个局部变量,其内存分配在栈中

Java基础_04 | 正式进入Java的世界(类和对象、类和对象的内存分配详解、构造函数)_JavaSE基础_04

3.3.堆中对象的属性和方法如何访问?

如上图所示,如果我们想要访问堆中Car对象的number属性和go方法,该如何访问呢?

显然,我们不可能直接去访问,车到山前必有路,testCar变量中保存着Car对象的首地址,所以,我们有了testCar变量后无所不能!

如上面的演示程序,直接通过testCar.nunber就可以访问number属性,通过testCar.go()就可以访问go方法

4.构造函数

4.1.构造函数的作用

在创建一个对象时,系统首先会为该对象分配内存,然后自动调用该类的构造函数

4.2.默认无参构造函数

在创建对象时,我们调用了Car()函数,但是这个函数在我们的类定义中并没有,那么这个函数是如何调用的呢?

Car testCar = new Car();

这里的Car()就是Car类的构造函数,在我们定义了Car类后,系统会自动生成默认构造函数Car()

4.3.自定义构造函数

自定义构造函数的时候,需要注意构造函数有以下特点:

  • 函数名和类名相同
  • 参数可以没有,也可以有多个
  • 无返回值
  • 构造函数必须是public或者默认修饰符
  • 可以有多个构造函数
  • 一旦自定义构造函数,系统不会再自动生成默认无参构造函数

比如,这里我自定义一个无参构造函数和带参构造函数:

class Car
{
	//Car类的属性
	int number;		//车辆编号

	//无参构造函数
	public Car()
	{
		System.out.printf("无参构造函数被调用.\n");
	}
	//有参构造函数
	public Car(int i)
	{
		number = i;
		System.out.printf("有参构造函数被调用.\n");
	}
	
	//Car类的方法
	void go()
	{
		//类中的方法访问类的属性
		System.out.printf("My number is %d, go go go!\n", number);
	}
}

使用无参构造函数和有参构造函数分别创建对象:

class CarTest
{
	public static void main(String[] args)
	{
		//使用无参构造函数创建一个动态对象
		Car testCar1 = new Car();
		
		//访问对象的属性并修改
		testCar1.number = 1;

		//调用对象的方法
		testCar1.go();

		//使用有参构造函数创建一个动态对象
		Car testCar2 = new Car(2);

		//调用对象的方法
		testCar2.go();
	}
}

运行结果如下:
Java基础_04 | 正式进入Java的世界(类和对象、类和对象的内存分配详解、构造函数)_自定义_05
为了验证“当我们自定义构造函数,系统是否会再自动生成默认构造函数?“”,我们将无参构造函数去掉,再次运行,系统报错,提示默认无参构造函数没有定义:
Java基础_04 | 正式进入Java的世界(类和对象、类和对象的内存分配详解、构造函数)_自定义_06
所以我们可以得出:

一旦自定义构造函数,系统不会再自动生成默认构造函数

4.4.构造函数中数据成员的赋值问题

对于类中的数据成员(类的属性),系统先执行定义时赋的初值,然后再执行构造函数中赋的初值

比如,我在定义时将number属性赋值为1,构造函数中赋值为2,最后的结果为2:

class Car
{
	//Car类的属性
	int number = 1;		//车辆编号

	//无参构造函数
	public Car()
	{
		System.out.printf("无参构造函数被调用.\n");
	}
	//有参构造函数
	public Car(int i)
	{
		number = i;
		System.out.printf("有参构造函数被调用.\n");
	}
	
	//Car类的方法
	void go()
	{
		//类中的方法访问类的属性
		System.out.printf("My number is %d, go go go!\n", number);
	}
}
class CarTest
{
	public static void main(String[] args)
	{
		//使用有参构造函数创建一个动态对象
		Car testCar = new Car(2);

		//调用对象的方法
		testCar.go();
	}
}

运行结果:
Java基础_04 | 正式进入Java的世界(类和对象、类和对象的内存分配详解、构造函数)_无参构造函数_07
接收更多精彩文章及资源推送,欢迎订阅我的微信公众号:『mculover666』

Java基础_04 | 正式进入Java的世界(类和对象、类和对象的内存分配详解、构造函数)_无参构造函数_08