哈喽,我是兔哥呀,今天就让我们继续这个JavaSE成神之路!
这一节啊,咱们要学习的内容是Java的面向对象。
首先我们回顾一下,之前的章节我们学到了哪些东西。
我们学会了写一个类,然后里面弄一个main方法,在main方法里面写一个输出语句。
我们学会了循环结构和分支结构。
我们学会了8种基本数据类型。
我们学会了怎么定义一个普通方法,然后在main方法中调用普通方法。
我们学会了Java的作用域,常量和变量。
这些知识,可以让我们来解决一些简单的问题,但是如果涉及到复杂的业务逻辑,就不够用了。
可以这么理解,之前我们学到的东西,可以让你搭一间茅草屋,但不足以你用来建造商品房。
要建造商品房,需要我们掌握工程化的东西,而在Java中,这个就是面向对象。
1. 类和对象
面向对象,就是用程序来模拟我们在现实世界处理问题的方式。
首先解释类和对象,看一个具体的场景。
你想找一个女朋友,这个时候你不知道女朋友到底叫什么名字,但是你总有一套自己的标准吧。
比如你要找一个长发,戴眼镜,穿jk超短裙,肤白貌美的女朋友。这些标准就是在描述一个类,或者说是一个模板。
体现在程序中,就是一个class,这个class有头发、是否戴眼镜、裙子种类,肤色等静态特征,即属性。
你还要求,这个女朋友对你好,每天可以亲亲抱抱举高高,这个体现在class中就是动态行为,也就是方法。
以上就是类的概念,那对象是什么呢?
对象就是哪天你真的找到了这样一个女朋友,这时候女朋友是真实存在的,可盐可甜的,比如有一个具体的名字叫王菊花。
所以,类是一个抽象的概念和模板,而对象是根据这个模板新建的一个真实的个体。
2.怎么创建一个类
根据上面的女朋友类,我们可以编写这样的class:
public class GirlFriend{
//类的属性:
private String name;
private String hair;
private boolean glasses;
private String skirt;
private String color;
//类的构造方法
public GirlFriend(String name,String hair,boolean glasses,String skirt,String color){
this.name = name;
this.hair = hair;
this.glasses = glasses;
this.skirt = skirt;
this.color = color;
}
//类的其他方法
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void setHair(String hair){
this.hair = hair;
}
public String getHair(){
return this.hair;
}
public void setGlasses(boolean glasses){
this.glasses = glasses;
}
public boolean getGlasses(){
return this.glasses;
}
public void setSkirt(String skirt){
this.skirt = skirt;
}
public String getSkirt(){
return this.skirt;
}
public void setColor(String color){
this.color = color;
}
public String getColor(){
return this.color;
}
}
类的属性,也叫成员变量,我们一般在属性上添加修饰符private
,代表私有的,这个体现了封装。
然后呢,类的方法,上面写的都是get/set
方法。
Java的get/set
方法是用来获取或设置对象属性的方法,它们在Java中是一种编程习惯,满足封装性的要求。get/set方法提供了一种简便的方式来访问对象的属性,同时也提供了一种安全性的控制,可以在获取和设置属性的时候进行一些简单的检查。
可能你已经注意到了,上面有一个特殊的方法:
//类的构造方法
public GirlFriend(String name,String hair,boolean glasses,String skirt,String color){
this.name = name;
this.hair = hair;
this.glasses = glasses;
this.skirt = skirt;
this.color = color;
}
类的构造方法
是在创建一个类的对象时调用的一种特殊方法,它可以帮助我们对新创建的对象进行初始化。
比如假如我们有一个Person类,每个Person对象都有name和age属性,那么构造方法可以是这样的:
public Person(String name, int age){
this.name = name;
this.age = age;
}
这样,当我们需要新建一个Person对象时,我们可以指定name和age参数,它们将会被赋值给新创建对象的name和age属性。
GirlFriend构造方法也是一样的道理,构造方法默认是缺省的。
3.怎么实例化对象
实例化是指根据类的定义来创建一个具体的对象的过程,它是类的实体化操作,也就是说,通过实例化,程序可以为类的抽象定义创建具体的对象,从而使程序具有可操作性。
实例化GirlFriend类就是使用new关键字来创建一个GirlFriend类的具体实例,例如:
GirlFriend gf = new GirlFriend();
通过实例化,我们可以引用gf来访问GirlFriend类的方法和属性,从而使程序更具有可操作性。
但是,上面的代码会报错:
这是因为,在GirlFriend类中,我们显示定义了一个构造方法,却没有显示定义空构造方法。
如果你不写
public GirlFriend(String name,String hair,boolean glasses,String skirt,String color){
this.name = name;
this.hair = hair;
this.glasses = glasses;
this.skirt = skirt;
this.color = color;
}
反而不会报错,因为每个class会默认拥有一个空构造方法。
这边我们需要加上这个空构造。
public GirlFriend() {
}
就不报错了。
public class TestGirlFriend {
public static void main(String[] args) {
GirlFriend gf = new GirlFriend();
}
}
4.单一职责原则
不知道你有没有发现,我们为了测试,是单独写了一个测试类的。
为什么我们不把main方法直接写在GirlFriend类中呢?
这是因为,我们要满足单一职责原则。
单一职责原则是指一个类应该只负责一项职责,这意味着一个类应该只有一个引起它变化的原因。具体来说,单一职责原则要求我们将类划分成若干个只负责一件事情的小类,而不是让一个类承担多个职责。这样做的好处在于,当一个类只负责一件事情时,它就变得非常简单,并且它的变化也会变得很小。
例如,如果一个类同时负责存储数据
和打印数据
,那么当我们需要更改数据存储方式时,这个类就必须发生变化,而如果按照单一职责原则将这两个功能分开,那么就可以使变化的影响降到最低。
所以,对于GirlFriend类,我们就只是保存一些属性而已,那是你找对象的标准,而不应该把测试的方法也写在同一个类中。
一个类的职责不能太多,这样你的类才能够被复用。
单一职责原则告诉我们:一个类不能太“累”!在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的 可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职 责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多 个职责总是同时发生改变则可将它们封装在同一类中。
之所以会出现单一职责原则就是因为在软件设计时会出现以下类似场景:T负责两个不同的职责:职责 P1,职责 P2。当由 于职责 P1 需求发生改变而需要修改类 T 时,有可能会导致原本运行正常的职责P2 功能发生故障。也就是说职责 P1 和 P2 被耦合在了一起。 解决办法:遵守单一职责原则,将不同的职责封装到不同的类或模块中。分别建立两个类 T1、T2,使 T1 完成职责 P1 功 能,T2 完成职责 P2 功能。这样,当修改类 T1 时,不会使职责 P2 发生故障风险;同理,当修改 T2 时,也不会使职责 P1 发生故障风险。
单一职责原则,是整个面向对象的基础。
5.new关键字和内存分配
GirlFriend gf = new GirlFriend();
我们用new关键字可以创建对象,上面的代码其实是两个步骤。
GirlFriend gf;
gf = new GirlFriend();
第一步只是创建了一个局部变量gf,gf是存储在栈
中的。 第二步是用new关键字结合GirlFriend空构造方法,在堆
中开辟了一块内存空间,并把内存地址赋值给栈
中的gf变量。
关于堆栈,其实就是两块不同的内存。
栈一般存储的是基本数据类型,以及局部变量。堆一般存储的是动态的对象。
在 Java 中,堆和栈是两个不同的内存区域,用于存储不同类型的数据。
堆(heap)是一个运行时内存区域,用于存储对象实例。所有对象实例都在堆上分配内存。堆内存是动态分配的,也就是说,程序在运行时可以随时申请或释放内存。
栈(stack)是一个高速缓存区域,用于存储线程的局部变量、方法调用信息以及操作数。栈内存是静态分配的,也就是说,在程序运行之前就已经分配好了。
在 Java 中,所有的基本数据类型(如 int、double 等)和对象的引用(即指向对象实例的指针)都是在栈上分配内存的。而对象实例本身则存储在堆上。
举个例子,假设我们有一个 Dog 类,并在 main 方法中创建了一个 Dog 对象:
Dog dog = new Dog();
在这里,dog 是一个引用,它存储在栈上。而 new Dog() 创建的 Dog 对象实例则存储在堆上。
总之,堆用于存储对象实例,而栈用于存储基本数据类型和对象的引用。
6. 可以有多个引用
刚才的例子:
GirlFriend gf;
gf = new GirlFriend();
gf是变量,但是因为赋值的是一个实例对象,没错哈,new出来的对象叫做实例对象。
那么变量gf又有了一个新的名字,叫做引用。(其实本质还是一个变量哈)
gf就好比一把钥匙,实例对象好比是一间屋子,你有了钥匙就可以去实例对象中溜达溜达,塞进去一些东西,也可以拿出来一些东西。
比如,你可以调用里面的方法。
public class TestGirlFriend {
public static void main(String[] args) {
GirlFriend gf;
gf = new GirlFriend();
gf.setHair("长发");
gf.setColor("肤白貌美");
gf.setGlasses(true);
gf.setName("小美");
gf.setSkirt("jk");
}
}
房子是一个,引用可以是多个,你只需要用“=”进行赋值操作即可。
GirlFriend gf2 = gf;
System.out.println(gf == gf2);
答案是true,因为gf保存的是GirlFriend对象在堆中的地址,那么你做的只不过是把这个地址传给了gf2,现在就相当于有两把钥匙,都指向了同一个对象。
7.课后作业
设计一个宠物类Pet,构造方法用于接收宠物的名字、类型及性别,实例化宠物后,可以用实例化的宠物对象来访问宠物的名字、类型及性别,并实现宠物的叫声功能,要求叫声不同类型的宠物不同,实现叫声时可以显示出宠物的名字和类型。
有任何不懂的地方,可以点击下方的“发消息”给我留言。
PS:从这一节开始,欢迎你使用任何你喜欢的IDE完成作业,比如eclipse,IDEA,如果不会使用工具,可以去B站搜索相关的视频进行学习哈。