上一期学习了变量、方法和修饰符,里面的代码涉部分及到本期内容,可能你还并不太理解它们。但没关系,第一节我们已经对类与对象有了初步的了解,本期继续深入学习并掌握它们的使用。
学习内容
- 理解对象的概念
- 学会将任意一个实物转换成Java对象
准备工作
你可以新建一个类或直接使用现有的类,本期使用的类文件名:ObjectAndClass.java。
在里面先创建好main函数以供调试。
类
类的解释
类的实质是#引用数据类型#,它与基本数据类型相似,可以被作为变量、函数、接口的返回值类型,但它 与基本数据类型的不同点在于它是一种复杂的数据类型。
类的实质是数据类型,所以它不在内存中也不能被直接使用,必须将其$实例化$成对象才可以操作。
类也可以是应用中具有共同特征的事物的抽象(例如人、动物、天体等)。例如在实际应用中遇到需要的数据类型有共同特征,就可以把它抽象成一个类,这样会让代码变得更好理解,也方便后续的修改与维护。
类的实例化
看完上面的概念你可能还是不明白类到底是什么,下面我们结合实例来看一下:
首先我在ObjectAndClass类里面声明了一个新的类名为Human。
这个新的类将在下面的教程中用来对人进行抽象,现在我们先看一下如何实例化一个类。
public class ObjectAndClass {
public static void main(String[] args) {
}
class Human {
}
}
如上所示,我已经在内声明了Human类,观察一下你会发现它没有任何访问修饰符,你还记得这种情况下,它的作用范围吗?(不记得可以再回顾一下上一期学过的访问修饰符)
然后我们来到main函数中,用声明变量的方式,将数据类型改成这个类的类名Human,变量名可以在符合命名规则的前提下随意起。
按照上面提示里面的格式来声明变量,发现它好像不太正确。这是因为:静态方法只能只用静态变量与静态方法(来自于上一期的非访问修饰符)
main函数被static修饰,因此它是一个静态方法,若我们要在里面实例化类,这个被实例化的类也必须是被static修饰成静态类才行,所以我们给它加上static修饰。
public class ObjectAndClass {
public static void main(String[] args) {
Human human = new Human();
}
static class Human {
}
}
像这样,我们就把Human这个类实例化了,并且把它赋值给human这个变量。
类的构造函数
构造函数的介绍
现在,你已经知道如何实例化一个类了,那么接下来我们来看一看什么是构造函数。
构造函数是一种特殊的方法,主要同于实例化类时初始化类,简而言之就是在一个类被new实例化之后调用的函数。
如同上面的 new Human()
我们发现 Human()
好像是对一个方法的调用,实际上也确实如此。当你在new实例化类时,就已经调用了这个被实例化的类的构造函数,但你并没有把它写出来,这一点下面继续讲。
一个类可以拥有多个具有不同参数构造函数(不同与参数的个数、类型),构造函数一般用于初始化一个类。
还记得实例变量吗?
上一期讲到实例变量实际上就是指在类下面的变量,它们可以在类的内部被访问,并且我们已经知道怎么给它们设定默认值。
如果实例变量的值不能确定呢?那我们也许要用下面的方法来对已经实例化的类内部的实例变量赋值:
public static void main(String[] args) {
Human human = new Human();
human.name = "小明";
human.age = 18;
human.blood_type = "O";
human.sex = 0;
}
这样我们就对一个已经实例化的类进行了初始化,也就是把内容填进去。
如果我们要让这个类在被实例化出来之后再进行一系列的动作(比如打招呼、移动到指定地点),就要在填完信息之后再去调用类的方法。
这样操作下来,我们每实例化一个新的Human就要写这么多行代码,这样的效率显然很低,况且还可能会遇到需要调用类的private方法/变量,这时外部类是不能访问它们的。
于是就有了构造函数。
构造函数的语法与声明
语法很简单,与方法的声明类似,也是[访问修饰符] [类名] ([参数类型 参数名]…) {}
现在我们来看一下构造函数的具体实现:
static class Human {
// 默认的空构造函数
Human() {
}
// 带有访问修饰符和参数的构造函数
public Human(int a) {
}
// 构造函数的参数类型、数量可以相同 但前提是顺序不同
private Human(String a, int ab) {
}
private Human(int ab, String a) {
}
}
看完上面三个例子,构造函数的声明就已经结束了。
调用构造函数
先看上面的默认的空构造函数,里面什么都没有,这就是刚才new Human()调用的函数,由于里面没有任何代码,因此它可以被省略。
但是你仍然可以声明这个构造函数,在里面写代码,这样使用new Human()实例化类时就会自动执行里面的代码了。
下面我们看一下带有参数的构造函数要怎么调用。
其实很简单,与方法的调用类似。
由于构造函数区别于参数的类型、数量,所以我们只需将()里面填入指定的参数即可。
现在我们来完善一下Human类,先给它添加几个实例变量。
static class Human {
public String name; // 名字
public int sex; // 性别
public String blood_type; // 血型
public int age; // 年龄
}
然后再添加一个构造函数,参数分别是String name, int sex, String blood_type, int age(这里构造函数参数的名字可以随意,顺序也可以随意,但实例化类时将参数填入构造函数的顺序也要发生改变)。
public Human(String name, int sex, String blood_type, int age) {
}
写好构造函数,接下来就是把构造函数接收到的参数赋值到实例的实例变量。
static class Human {
public String name; // 名字
public int sex; // 性别
public String blood_type; // 血型
public int age; // 年龄
public Human(String name, int sex, String blood_type, int age) {
this.name = name;
this.sex = sex;
this.blood_type = blood_type;
this.age = age;
}
}
现在我们就已经完善了这个构造函数,接下来我们试着调用一下。
回到main类,在刚才的new Human()的()内按顺序填入参数,例如:
public static void main(String[] args) {
Human human = new Human("小明",0,"O",18);
}
对比一下上面的代码,是不是简洁了许多,这样以后再想实例化Human类来表示其他人时,就可以直接调用构造函数了,从而代替重复的变量赋值语句。
类的三大特性
封装
继承
多态
这个多态可不是多肽哦。
对象
在上面类的学习中又对对象有了进一步的认识,其实上面的Human类就可以被当做一个对象了,也就是把人的共性抽象成了Human对象。
现在你可以发挥想象,试着把周围的事物抽象成一个类。
例如:对计算机的抽象,可以简单地抽象成下面的结构
这样看起来还是不够深入…还可以再抽象到具体属性:
这些新增的节点可以认为是它们父节点类的实例变量,我们知道变量的数据类型可以是类,因此这里的每一个节点都可以认为是一个类,而计算机就是最大的类。
这样看来,类确实是复杂的数据类型。
课后练习
1.定义一个新函数,要求返回一个任意(自己确定)的双精度浮点小数,参数可有可无。
2.声明一个新的类,在里面声明实例变量、静态变量(可常量),并且声明一个方法,含有三个不同类型(任意基本数据类型)的参数,在这个方法里面声明两个局部变量,将其中一个定义为常量。
3.在一个类里面声明一个类,在这个类里面声明两个不同的方法(其中一个方法需要带有两个不同类型的参数),在其中一个方法调用另一个。
4.声明一个含有实例变量的类,并且要求:可以通过构造函数初始化,也可以通过访问实例变量并赋值的方式初始化。
5.声明一个含有被public和private修饰的构造函数的类,并且在另一个java文件中的main函数中,调用这个类的构造函数,比较被public和private修饰的构造函数的区别。
6.试着把上面已经给出的计算机对象框架写成代码,并且试着实例化一个计算机类并初始化。
练习详解
第一题
首先定义一个新函数aNewMethod:
public class Practice3 {
public void aNewMethod() {
}
}
返回一个双精度浮点小数,也就是要返回一个double类型的值,因此我们要将函数的返回值void(无返回值)修改成double。
public class Practice3 {
public double aNewMethod() {
return 3.14d;
}
}
根据题意,利用return返回一个double值就行了,3.14后的d代表这个数是一个双精度浮点数。
第二题
先创建一个新的类Practice3Class,并且声明实例变量、静态变量:
public class Practice3Class {
// 实例变量
public int age;
// 静态常量 被final修饰不可被修改
public static final String STATIC_STR = "This is a static value";
}
再声明一个方法,需要有三个不同的基本数据类型的参数。
还是先把方法的模板摆上去:
public void paramsMethod() {
}
根据需要,在方法名后的括号内添加三个参数:
public void paramsMethod(int a, float b, double c) {
}
这里选择了int、float、double,当然也可以有其他组合。
再声明两个局部变量,并且其中一个是常量:
public void paramsMethod(int a, float b, double c) {
int tmp1 = 99;
final String STATIC_STR = "This is a static value in a method";
}
这样我们就声明了一个名为tmp1的变量和一个名为STATIC_STR的常量。
现在完整的代码如下:
public class Practice3Class {
// 实例变量
public int age;
// 静态常量 被final修饰不可被修改
public static final String STATIC_STR = "This is a static value";
public void paramsMethod(int a, float b, double c) {
int tmp1 = 99;
final String STATIC_STR = "This is a static value in a method";
}
}
第三题
先在一个类里面声明另一个类InnerClass:
public class Practice3 {
public class InnerClass {
}
}
再声明两个不同的方法:
public class Practice3 {
public class InnerClass {
public void methodA() {
}
public void methodB() {
}
}
}
根据需求,其中一个方法需要有两个不同数据类型的参数,不妨选择methodA:
public void methodA(int a, float b) {
}
最后,我们在另一个方法methodB中调用methodA:
public void methodB() {
methodA(1,3.14f);
}
由于是同一个类里面的方法,因此只需要直接用 方法名(参数)
调用即可,也可以使用 this.方法名(参数)
调用。
全部代码如下:
public class Practice3 {
public class InnerClass {
public void methodA(int a, float b) {
}
public void methodB() {
this.methodA(1,3.14f);
}
}
}
第四题
首先声明一个带有实例变量的类:
public class Practice4 {
public String name;
public int age;
public boolean isBoy;
}
然后声明两个不同的构造方法:
public class Practice4 {
public String name;
public int age;
public boolean isBoy;
public Practice4() {
}
public Practice4() {
}
}
写到这里,IDE已经在 public Practice4() {
这一行标出红色波浪线了,不用担心,接下来我们修正它。
我们需要用一个构造函数来初始化实例,因此只要把构造方法接收到的参数赋值给实例变量即可:
public Practice4(String name, int age, boolean isBoy) {
this.name = name;
this.age = age;
this.isBoy = isBoy;
}
另一个构造函数保持原样即可,这样我们就不仅可以用构造函数初始化实例,也可以通过空构造函数对这个类实例化了。
由于实例变量被public修饰,因此满足通过访问实例变量并赋值的方法进行实例初始化。
完整代码如下:
public class Practice4 {
public String name;
public int age;
public boolean isBoy;
public Practice4(String name, int age, boolean isBoy) {
this.name = name;
this.age = age;
this.isBoy = isBoy;
}
public Practice4() {
}
}
第五题
有了前几题的经验,下面我们就快速地将代码写出来:
这里我们创建了两个不同的类,Practice4_2和Practice4_3,它们是两个不同的java文件。
public class Practice4_2 {
public Practice4_2() {
System.out.println("This is a public method");
}
private Practice4_2(int a) {
System.out.println("This is a private method");
}
}
public class Practice4_3 {
public static void main(String[] args) {
}
}
现在我们来到Practice4_3的main函数调用Practice4_2的构造函数:
public static void main(String[] args) {
Practice4_2 anyInstanceName = new Practice4_2();
}
可以看到控制台输出了This is a public method,说明public构造函数被调用了。
现在我们给构造函数添加一个int类型的参数:
可以看到提示了错误,这是因为这个构造函数被private修饰,不能被其他类访问。
这里我们再一次复习了访问修饰符的作用范围。
第六题
// 计算机类
public class Computer {
public CPU cpu;
public CPU_Fan cpu_fan;
public Memory memory;
public Disk disk;
public Graphics graphics;
public Computer(CPU cpu, CPU_Fan cpu_fan, Memory memory, Disk disk, Graphics graphics) {
this.cpu = cpu;
this.cpu_fan = cpu_fan;
this.memory = memory;
this.disk = disk;
this.graphics = graphics;
}
public static void main(String[] args) {
CPU cpu = new CPU();
cpu.cpuName = "R5 3500x";
cpu.clockT = 3.59f;
cpu.cache1 = 384; // KB
cpu.cache2 = 3; // MB
cpu.cache3 = 32; // MB
CPU_Fan cpu_fan = new CPU_Fan();
cpu_fan.speed = 2500;
Memory memory = new Memory();
memory.type = "DDR4";
memory.clockT = 2666;
Disk disk = new Disk();
disk.type = "SATA";
disk.sizeGB = 1024;
Graphics graphics = new Graphics();
graphics.memorySize = 2048;
graphics.bitWidth = 128;
graphics.clockT = 1354;
Computer computer = new Computer(cpu,cpu_fan,memory,disk,graphics);
}
}
public class CPU {
public String cpuName;
public float clockT; // 时钟频率
public int cache1,cache2,cache3;
}
// CPU风扇类
public class CPU_Fan {
public int speed; // 转速
}
// 内存类
public class Memory {
public String type; //DDR?
public int clockT; // 时钟频率
}
// 硬盘类
public class Disk {
public String type; // 接口类型
public int sizeGB; // 容量
}
// 显卡类
public class Graphics {
public int memorySize; // 显存大小
public int clockT; // 显存频率
public int bitWidth; // 显存位宽
}