java 面向对象三大特性之封装 万字详解(超详细)
目录
①private关键字演示中Phone类setter方法的强龙地头蛇布局定式冲突的解决 :
前言 :
Hey,guys.从这篇博文开始,我们就要正式进入java面向对象三大特性的学习了。这三大特性:封装、继承、多态构成了面向对象的灵魂,也是面向对象和面向过程最大的差别。所以,学好三个特性对于一个java人来说,非常重要!这篇博文的内容包括但不限于封装的介绍,private关键字详解,this关键字详解,构造器详解,JavaBean介绍等等。注意,代码中的注释往往包含了讲解,别错过。
一、为什么需要封装 :
前言 :
我们知道,类是属性和行为的集合,是一个抽象概念。而对象是该类事物的具体体现,是一种具体存在。而当我们需要对某个类中的一些数据进行保护,不想让外界随意修改它们时,或者当我们想调用某个类中的方法,但又不想将该方法的实现细节暴露出来时,就需要用到java这个听起来牛逼哄哄的特性了,“封装"。
1.封装的好处 :
①提高代码的安全性
②提高代码的复用性
③将复杂的事情变得简单化
2.java中的常见封装体:
类 :
安全性 : 调用者无法直接修改类中的数据(属性)。
复用性 : 同一个类可以被重复使用。
简单化 : 类的对象包含了更多的功能,使用也更方便。
方法:
安全性 : 调用者不知道方法的具体实现。
复用性 : 定义的方法可以被多次调用。
简单化 : 将繁多的代码以一个方法的形式呈现,仅通过调用方法就可以实现功能,代码维护也变得简单。
二、封装简介 :
1.封装的概念 :
将一系列相关事物的共同的属性和行为提取出来,放到一个类中,同时隐藏对象的属性和实现细节,仅对外提供公共的访问方式。
Δ注意,JavaBean类就可以看作是封装的完美案例。JavaBean我们后面会说到。
2.封装的关键 :
①封装使数据被保护在内部。
②绝对不可以让类中的方法直接访问其他类的数据(属性),程序仅通过对象的方法与对象的数据进行间接交互。
③封装可以使得我们对传入的数据进行校验和限制,加强了业务逻辑。
三、private关键字(重要) :
1.封装的必要利器—private介绍:
我们之前写类的时候,从来没有用过private(假设你才刚学,确实没用过🤗)。那不用private使用类时的效果是什么呢,我们可以在本包下的其他类随意的使用该类的属性和行为。平时我们自己玩玩儿啥的雀氏也没啥,但是将来开发怎么可能让你这么专横哈哈。所以,我们要学会去保护它们。So,如何保护呢?private上就完了。
现在我们先给大家看一下不用private是怎样的?假设(其实就是定义了)定义一个Phone类(手机类),在Phone中定义一些属性和方法,均不用private修饰,甚至方法我们也暂且不用static修饰(假设你还没学嘛,因为我还没讲捏🤗),然后再新建一个TestPhone类,测试其中的属性和方法。
Δ不用private的代码演示 :
Phone类代码如下:
package knowledge.define;
public class Phone {
// 成员变量:
String brand; //手机牌子
String model; //手机型号
String name; //手机持有者
// 成员方法:
public void call(String name) {
System.out.println("给我"+ name +"打个电话");
}
public void sendMessage() {
System.out.println("🐔 : 律师函告CSDN🔨");
}
public void listenMusic() {
System.out.println("🐔你太美~,🐔你实在是太美~");
}
}
TestPhone类代码如下 :
package knowledge.define;
/**
* 手机类的测试
*/
public class
TestPhone {
public static void main(String[] args) {
//1.创建对象
Phone p = new Phone();
//2.调用成员变量,并打印
//给成员变量赋值
p.brand = "Huawei";
p.model = "p40";
p.name = "Kyrie";
//打印成员变量的值
System.out.println(p.brand);
System.out.println(p.model);
System.out.println(p.name);
System.out.println("________________________");
//3.调用成员方法
p.call("KunKun");
p.sendMessage();
p.listenMusic();
}
}
输出结果如下图 :
注意看TestPhone类中的代码,我们是可以通过创建对象,以“对象.” 的形式来直接访问Phone类中的属性的,可能你一听也觉得没什么,但其实这是一件顶可怕的事儿😱。
2.基本概念:
private [ˈpraɪvət] ,私有的。private是Java四种访问权限修饰符中的一种,关于访问权限修饰符,大家先了解即可,之后讲到继承时,我们会详细地介绍四种访问权限修饰符。
3.特点 :
Δ被修饰的成员只能在本类中访问。其他类无法直接访问本类中被private修饰的成员。
4.用法 :
修饰成员变量 :
private 数据类型 变量名;
修饰成员方法 :
private 返回值类型 方法名 (形参列表) { //方法体 }
5.private关键字代码演示 :
以我们刚刚演示的Phone类和TestPhone类为栗,我们给Phone类中的成员变量加上private修饰符,TestPhone类保持不变。Phone类代码如下 :
package knowledge.define;
public class Phone {
// 成员变量:
private String brand; //手机品牌
private String model; //手机型号
private String name; //手机持有者
// 成员方法:
public void call(String name) {
System.out.println("给我"+ name +"打个电话");
}
public void sendMessage() {
System.out.println("🐔 : 律师函告CSDN🔨");
}
public void listenMusic() {
System.out.println("🐔你太美~,🐔你实在是太美~");
}
}
这时候你会发现,这**刚加上private关键字就报错了,如下图所示 :
IDEA提示我们有两条相关问题。什么是相关问题呢?意思就是问题本身不是在当前类,但是出了问题的类呢,又与当前类有关系。显然,只能是TestPhone类出了问题!如下图:
明明白白,它提示我们:这些个变量都有private修饰🌶!你不能直接使用!
这下很多小伙伴儿要蒙圈了!不加private嫌我不保护数据,加上又不让我用,这**不是耍👴吗?让👴怎么玩儿?
这位👴您先别生气!马上我们就要隆重介绍新的嘉宾出场 : setter,getter方法!
6.setter,getter方法展示(重要) :
①介绍:
setter[ˈsetər] ,制定者,安排者。
getter[ˈɡetər] ,获得者。
setter和getter其实指的分别是setXxx和getXxx两个函数,当然了,相信聪明的你看名字也能猜出来!setXxx可以修改对应属性的值,而getXxx则可以获取对应属性的值。比方说,假设有一个私有的brand(品牌)属性,我们可以通过函数setBrand来修改brand属性的值,而通过getBrand来获取对应属性的值。
②如何定义setter,getter方法 :
两种方法 : 手写。或者快捷键。
当然,对于初学者来说,当然是建议大家先手写,写熟练了你再用快捷键嘛,快捷键的方法up已经专门写过一篇博客,在此不再赘述,有兴趣可以看看。现在我们来讲一讲,如何去手写定义这两个函数:
首先,setXxx() 方法,肯定需要你去传入一个值,不然谁知道你要改成啥呀。然后这个函数还必须将接收到的值赋值给类中的对应属性(对应属性即指setXxx中的Xxx),否则对应属性肯定不会修改成功,你的目的就是修改对应属性的值嘛,假如你创建该属性时未进行显式初始化,那么该属性就是默认值。因此,setXxx() 方法的关键两步骤就是:传入值和赋值值,方法体中仅需要一句赋值语句,当然,函数的返回值类型就是void了,你只需要修改就好。
其次,getXxx() 方法,我们想达到的效果是:每次调用该函数都可以返回对应属性的当前值,所以,getXxx() 方法是一定有返回值的,而返回值类型取决于你后面的Xxx属性本身的数据类型,对应返回就好。而且,getXxx() 方法的方法体中,仅需要"return Xxx; "一句代码就可以,简洁高效。
③代码演示 及 问题延申 :
新的Phone类代码如下 :
package knowledge.define;
public class Phone {
//成员变量:
private String brand; //手机品牌
private String model; //手机型号
private String name; //手机持有人
//setter,getter方法
//brand的getter方法
public String getBrand() {
return brand;
}
//brand的setter方法
public void setBrand(String b) {
brand = b;
}
//model的getter方法
public String getModel() {
return model;
}
//model的setter方法
public void setModel(String m) {
model = m;
}
//name的getter方法
public String getName() {
return name;
}
//name的setter方法
public void setName(String n) {
name = n;
}
//成员方法:
public void call(String name) {
System.out.println("给我"+ name +"打个电话");
}
public void sendMessage() {
System.out.println("🐔 : 律师函告CSDN🔨");
}
public void listenMusic() {
System.out.println("🐔你太美~,🐔你实在是太美~");
}
}
有了setter,getter方法,我们就可以在TestPhone类中测试它们了,新的TestPhone类代码如下 :
package knowledge.define;
/**
* 手机类的测试
*/
public class
TestPhone {
public static void main(String[] args) {
//1.创建对象
Phone p = new Phone();
//2.调用成员变量,并打印
//给成员变量赋值
p.setBrand("Huawei");
p.setModel("Mate40");
p.setName("Cyan");
//打印成员变量的值
System.out.println("啥手机啊?" + p.getBrand());
System.out.println("废话,我不知道华为?我问你什么型号:" + p.getModel());
System.out.println("谁的手机啊?" + p.getName());
System.out.println("________________________");
//3.调用成员方法
p.call("KunKun");
p.sendMessage();
p.listenMusic();
}
}
输出结果如下 :
😁效果不错。
这时候,就要有p小将(personable小将,指风度翩翩的人!)要说理了:诶诶诶,up你难道没发现吗,你的setter方法体中形参的定义不符合见名知意的效果!我把其中的一个setter方法拿出来给大家看看 :
public void setName(String n) {
name = n;
}
我趣,还真是这样,不愧是p小将😭,6。没错,所谓见名知意,就是指看到变量名就可以基本确定这个变量是干嘛的,但是你看我们写得setter方法,传入的形参变量定义成了n,要知道n开头的英语单词可是多的很捏,谁知道你表示名字呢?因此这么定义形参名无法满足“见名知意”的效果,降低了代码的可读性和逻辑性。
好,改!下面我们把形参名统一改成成员变量名,如下 :
就让我们来看看p小将的方法能不能行得通。
再次运行TestPhone类结果如下 :
我趣?!这时候很多小伙伴儿要懵了,这咋还一问三不知了。
出现这个问题的原因也很好解释:
大家都知道一句话吧,叫“强龙不压地头蛇”。没错,仔细看我们定义的setter方法,可以明确地看出,setter方法的形参列表都定义了局部变量,都不为空! 而Java中变量的使用规则是“就近原则”。即局部位置-> 成员位置-> 父类-> 更高的父类-> ...... -> Object,如果一直到Object还没找到这个变量,报错!(Object是所有类的顶层父类,先了解一下即可,之后的继承特性中我们会讲到)。那既然现在setter方法内(局部位置)定义了形参,方法内部当然是优先使用这个定义的形参喽!所以,如果仅仅这么更改的话,达到的效果想必你也猜到了,没错,就是形参变量自己赋值给了自己,而成员变量,也就是我们要更改的属性,却仍然是String类型的默认值null。
其实,大家只要把鼠标悬停在右边的变量上,智能的IDEA就会自动报出提示 : 该变量自己赋值给了自己。如下图 :
因此,我们需要想办法让左面的变量name能够正确表示成员变量name,而让右边的变量name,依旧表示我们的局部变量name,即接收用户传入的参数。可是,我们怎样才能达到这样的愿景呢?
😋,问得好,这就需要用到我们真正牛逼哄哄的this关键字了。
7.关于public关键字 :
在讲this关键字之前,稍微穿插一下关于public关键字的那些事儿。
public[ˈpʌblɪk] 公众的,公有的,公开的。很明显,这是和private反着来的访问权限修饰符。public,是Java四种访问权限修饰符之另一个。
public可以用来修饰类,成员变量,成员方法等,被public修饰的内容可以在任意类中访问。因此,public是访问权限最高的修饰符。其实吧,现在说太多也没啥用,直接说一个结论吧,记住就行。
private 一般用来修饰成员变量(属性)。
public 一般用来修饰成员方法(行为)。
四、this关键字(重要) :
1.基本概念 :
this [ðɪs],这、这个,表示对本类对象的引用。
Java虚拟机会给创建的每个对象分配this,代表当前对象。
2.特点 :
每一个创建的对象都有一个this属性,指向该对象本身(类似于指针,但在Java中叫做引用),因此this可代表当前对象,把它当作当前对象来看待。
3.用法 :
①this.属性名; (可调用当前对象的属性,this.属性名就是当前对象的属性)
②this.方法名(参数); (可调用当前对象的方法)
③知识延申 :
PS : this(参数列表) 可以访问本类的构造器(构造器下面会讲到)。但要注意:
Δ此时this后不需要加"."。
Δ该途径只能在构造器中使用,且使用时必须置于构造器的首句。我们称之为“构造器的复用”。
4.作用 :
可以解决类似我们刚刚遇到了“强龙不压地头蛇”的冲突问题。即形参名与属性名的重名问题。
5.代码演示 :
①private关键字演示中Phone类setter方法中的强龙地头蛇布局定式冲突的解决 :
刚才p小将一针见血地指出了我们的setter方法中,形参名无法见名知意的问题,让我们难堪,这下我们可以回击p小将了。走起 :
Phone代码如下 :
package knowledge.define;
public class Phone {
//成员变量:
private String brand; //手机品牌
private String model; //手机型号
private String name; //手机持有人
//setter,getter方法 (在setter方法中使用了this关键字来解决强龙地头蛇的冲突问题)
//brand
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
//model
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
//name
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//成员方法:
public void call(String name) {
System.out.println("给我"+ name +"打个电话");
}
public void sendMessage() {
System.out.println("🐔 : 律师函告CSDN🔨");
}
public void listenMusic() {
System.out.println("🐔你太美~,🐔你实在是太美~");
}
}
我们再来运行TestPhone类,看一下属性有没有被成功赋值。TestPhone类代码如下:
package knowledge.define;
public class
TestPhone {
public static void main(String[] args) {
//1.创建对象
Phone p = new Phone();
//2.调用成员变量,并打印
//给成员变量赋值
p.setBrand("Huawei");
p.setModel("Mate40");
p.setName("Cyan");
//打印成员变量的值
System.out.println("啥手机啊?" + p.getBrand());
System.out.println("废话,我不知道华为?我问你什么型号:" + p.getModel());
System.out.println("谁的手机啊?" + p.getName());
System.out.println("________________________");
//3.调用成员方法
p.call("KunKun");
p.sendMessage();
p.listenMusic();
}
}
运行结果 :
可以看到三个String类型的属性都再次被成功赋值了,this 牛逼👍。其实,如果你使用快捷键生成了getter和setter方法,你会发现IDEA自动写出来的setter方法就是这么一回事儿:
public void setXxx(数据类型 形参名xxx) {
this.形参名xxx = 形参名xxx; //成员变量xxx == this.形参名xxx
}
这样的setter方法其实就是正确的,常规的,一写中的的写法!😀
②this关键字调用成员变量(属性)的演示 :
我们仍然使用 Phone类 与 TestPhone类 做演示,绝不是因为懒得建新的演示类的了(bushi),我们在Phone中定义一个age成员变量表示手机的累计使用年限,再定义一个printAge()方法,来打印age变量的值,特别的,我们在printAge() 方法内再定义一个局部变量age,并试图在该方法内同时打印出局部变量age和成员变量age。然后我们在TestPhone类中创建一个Phone类对象,并利用创建的Phone类对象来调用printAge() 方法。
Phone类代码如下 :
package knowledge.define;
public class Phone {
//成员变量:
private int age; //手机累计使用年限
//this 关键字对于属性的应用:
public void pintAge () {
int age = 10;
/*
此处在nameShow()方法内也定义了一个age的整型变量,与成员变量age同名,
形成了强龙地头蛇布局定式。
*/
//直接输出的age毫无疑问是地头蛇age。
System.out.println("根据就近原则,没有加this关键字的age变量肯定是局部变量10呀:" + age);
//若想利用此函数来输出强龙age,则需要用到this关键字
System.out.println("加上this关键字后,就可以在局部位置输出成员变量:" + this.age);
}
}
TestPhone类代码如下:
package knowledge.define;
public class
TestPhone {
public static void main(String[] args) {
//1.创建对象
Phone p = new Phone();
//2.调用成员方法
p.pintAge();
}
}
输出结果 :
可以看到,第一个没有用this关键字的age变量,其输出结果的确是为它赋的值10。但是,我们要注意,因为这次并没有用setAge修改age属性的值,因此输出属性age值为0(整型的默认值等于0)。
但是,这么看对比好像不强烈,我们用setter方法来更改一下age属性的值,并将打印age的函数增加一个形参age,在显式赋值语句之前先输出传入的形参。
Phone类代码如下:
package knowledge.define;
public class Phone {
//成员变量:
private int age; //手机累计使用年限
//setter,getter方法
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//this 关键字的使用:
public void pintAge (int age) {
/*
注意,当我们在形参列表中定义了age变量时,方法体中就不能重复定义age变量了,
只能对它进行赋值更改
*/
System.out.println("调用pringAge() 方法时,传入的实参是多少呀:" + age);
age = 10;
//直接输出的age毫无疑问是地头蛇age。
System.out.println("根据就近原则,没有加this关键字的age变量肯定是局部变量10呀:" + age);
//若想利用此函数来输出强龙age,则需要用到this关键字
System.out.println("加上this关键字后,就可以在局部位置输出成员变量:" + this.age);
}
}
TestPhone类代码如下 :
package knowledge.define;
public class TestPhone {
public static void main(String[] args) {
//1.创建对象
Phone p = new Phone();
//2.给成员变量赋值
p.setAge(11); //这个11是赋给了成员变量age
//3.调用成员方法
p.pintAge(5); //这个5是赋给了printAge的形参age
}
}
输出结果:
我们看到成员变量age的值不再是默认的0了,而是我们通过setAge() 更改后的11。
③this关键字调用成员方法(行为)的演示 :
我们在Phone类中定义一个私有方法,如下 :
package knowledge.define;
public class Phone {
//定义一个私有的方法(无实际意义,仅作为演示)
private void icon() {
System.out.println("我是练习时长两年半的个人练习生:KunKun");
}
}
当我们想在TestPhone类中直接调用icon() 该方法的时候,IDEA会提示报错,如下图所示:
这种情况下,如果我们想继续调用该方法,就需要用到this关键字了。
我们在Phone类中定义一个新的公共的方法,然后在这个新方法中通过this关键字来调用icon方法,如下 :
package knowledge.define;
public class Phone {
//定义一个私有的方法(无实际意义,仅作为演示)
private void icon() {
System.out.println("我是练习时常两年半的个人练习生:KunKun");
}
//通过this关键字解决私有函数无法直接调用的问题
public void demo() {
this.icon();
}
}
然后我们在TestPhone类中调用demo() 方法,就可以成功间接调用 icon方法,TestPhone类代码如下:
package knowledge.define;
public class TestPhone {
public static void main(String[] args) {
//1.创建对象
Phone p = new Phone();
//2.调用成员方法
p.demo();
}
}
输出结果 :
6.深入理解this关键字 :
this关键字的本质到底是什么呢?
其实,JVM在堆空间给对象分配空间时,每个对象都有一个隐藏的属性this,this指向该对象本身。即,如果用C语言来解释的话,this就是一个指向堆空间中对象本身的指针,只不过在Java中没有指针,叫做引用而已。this自己是对象的一部分,它也在堆空间,但是它又指向了它自己。
光这么说多少有丶带抽象,来张内存图直观的表示一下,如下图:
图解 :
一看图就明白了,就是这么回事儿。当然,我们还可以通过另一种直观的方法来理解this,我们可以分别输出创建的Phone类对象和this对象的哈希码值,并进行比较。进行该操作需要用到hasCode方法(之后我们会讲到hasCode)。
代码演示 :
Phone类代码如下:
package knowledge.define;
public class Phone {
//成员变量:
private String brand; //手机品牌
private String model; //手机型号
private String name; //手机持有人
//setter,getter方法
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//定义一个方法,用来输出当前this对象的哈希码值。
public void printThisHasCode() {
System.out.println(this.hashCode());
}
}
TestPhone类代码如下:
package knowledge.define;
/**
* 手机类的测试
*/
public class
TestPhone {
public static void main(String[] args) {
//1.创建对象
Phone phone1 = new Phone();
Phone phone2 = new Phone();
//2.调用成员变量,并打印
//给成员变量赋值
phone1.setBrand("Huawei");
phone1.setModel("Mate40");
phone1.setName("Cyan");
System.out.println("输出phone1对象的哈希码值 : " + phone1.hashCode());
System.out.print("输出this对象的哈希码值 : ");
phone1.printThisHasCode();
System.out.println("------------------------------------");
phone2.setBrand("Huawei");
phone2.setModel("p40");
phone2.setName("Five");
System.out.println("输出phone2对象的哈希码值 : " + phone2.hashCode());
System.out.print("输出this对象的哈希码值 : ");
phone2.printThisHasCode();
}
}
输出结果:
可以看到, this的哈希码值和当前对象保持一致,因此我们可以证明:谁调用本类属性或者行为,this就指向谁。
7.hashCode相关(了解即可) :
①介绍 :
public int hashCode() :
该方法可以根据地址值进行计算,然后返回该对象的哈希码值。支持此方法是为了提高哈希表的性能(例如java.util.Hashtable提供的哈希表)。
②hashCode的常规协定 :
Δ在java应用程序执行期间,在对同一对象多次调用hashCode方法时,必须一致地返回相同的整数,前提是将对象进行equals比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数元需要保持一致。
Δ如果根据equals(Object)方法,如果两个对象是相等的,那么对这两个对象中的每个对象调用hasCode方法都必须生成相同的整数结果。
Δ如果根据equals(java.lang.Object)方法比较后,两个对象不相等,那么对两个对象中的任一对象上调用hasCode方法不要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
五、构造器详解(重要) :
1.什么是构造器 :
构造器,也叫构造方法,指用来帮助创建对象的方法,但仅是帮助。它不是用来创建新对象的,而是完成对新对象的初始化。
2.那么,谁来创建对象呢?
new关键字。Java通过new关键字来创建对象,并在内存中开辟空间,然后使用构造器完成对象的初始化工作。
PS: Java创建对象的内存图解 :
up之前写过的一篇博文详细介绍了Java创建新对象,在内存中实际发生了什么,有助于初学者快速理解new关键字,有兴趣的小伙伴儿可以点击链接查看,链接如下 https://blog.csdn.net/TYRA9/article/details/128508466?spm=1001.2014.3001.5501
3.构造器的定义格式 :
访问权限修饰符 构造方法名(参数列表){
//
//方法体
//
}
Δ补充说明 :
①对于“(参数列表)” 而言,如果构造器在定义时参数列表为空,我们称它为空参构造(也叫无参构造),否则称之为带参构造(也叫有参构造)。
②方法体中的语句往往要么就是使用了this关键字的赋值语句(就像我们的setter方法一样),要么就是对传入参数的校验和筛选(这个我们会在构造器的改进中讲到)。至于构造器这里为什么要使用this关键字? 这个我们在讲setter和getter方法时已经给大家说过啦,这里不再赘述,有不理解的小伙伴儿可以再回去setter,getter方法那里重新仔细看看。
4.构造器需要满足的要求:
Δ构造方法名必须与类名相同!(包括大小写)
Δ构造方法没有返回值!(但是也可以在方法内部写一个return)
Δ构造方法没有返回值类型!因此定义构造器时千万不要写返回值类型,连void都不可以写!这一点容易和普通方法混淆!(连返回值都没有,哪儿来的返回值类型)
5.注意事项(重要):
①当类中没有定义任何构造器时,该类默认隐含一个无参构造。这也是为什么我们之前写过的类中没有定义构造器,却依然可以创建该类的对象,因为系统默认给出了无参构造,所以就会以默认的无参构造对新对象进行初始化。
Δ但要注意:若类中已经提供了任意的构造器,那么系统不会再提供任何无参构造。啥意思呢?就是说,如果我在某个类中自定义了一个无参构造,或者自定义了一个有参构造,或者我两个都定义了,那么初始化本类对象就只能用你定义的构造器,此时不再有系统默认的空参构造器了。这一点也很重要,比如,如果我们在某个类中仅仅定义了带参构造器,那么初始化本类对象时,是不可以用空参构造的。
②构造器可以重载,就和方法一样,同一个类中可以定义多个构造器。至于方法的重载我们之前已经讲过了,在此不再赘述,只需要记得重载是方法名相同参数列表不同就🆗了。
③构造器是在执行new关键字的时候,由系统来完成的,即在创建对象时,系统会自动匹配并调用该类的某个构造器完成对对象的初始化。PS:其实一个对象的初始化需要三个步骤,分别是默认初始化,显式初始化和构造器初始化,这一点我们在Java创建对象的内存图解那里已经讲过了,我再次把链接放一下 :
https://blog.csdn.net/TYRA9/article/details/128508466?spm=1001.2014.3001.5501
6.代码演示 :
我们以KunKun类作为要实例化的类,以TestKunKun类作为测试类,
①我们先不定义构造器,测试一下是否能成功创建对象。
KunKun类代码如下 :
package knowledge.constructor;
public class KunKun {
//成员变量(KunKun类的属性)
private String name; //不对name进行显式初始化
private int age = 11; //对age进行显式初始化
//这里是构造器,但是我们现在先暂时什么都不写。
//name和age的getter,setter方法 :
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
TestKunKun类代码如下 :
package knowledge.constructor;
public class TestKunKun {
public static void main(String[] args) {
//1.测试系统默认提供的空参构造
KunKun kun1 = new KunKun();
//使用空参构造构造初始化对象,除非属性值有显式初始化,否则均为默认值。
System.out.println("kun1的名字是:" + kun1.getName());
System.out.println("kun1的年龄是:" + kun1.getAge());
System.out.println("-------------------------------------------");
//使用setter方法修改kun1对象的属性,并重新打印
kun1.setName("坤哥");
kun1.setAge(18);
System.out.println("kun1的名字是:" + kun1.getName());
System.out.println("kun1的年龄是:" + kun1.getAge());
System.out.println("-------------------------------------------");
}
}
输出结果 :
首先,我们在TestKunKun类创建KunKun对象时,IDEA并没有报错,因此可以确定系统提供了无参构造。通过输出结果,我们发现,如果未对成员变量进行显式初始化,也没有通过setter方法修改成员变量的值,输出成员变量就是它的默认值,比如此处的name属性,一开始输出name就是默认值null,而age在定义时进行了显式初始化,因此一开始输出age的结果不是默认值0。当然,通过setter方法修改属性值后,name和age的值都发生了变化。
②下一步,我们定义一个自己的无参构造,
因为系统默认给的无参构造里面啥都没有,就是能让你初始化对象就完了😂,你也别嫌弃,就像是免费给你的东西,要什么🚲呀。但是总得让大家看看所谓自定义无参构造是怎么一回事儿😋。好滴,我们在无参构造中输出一句话,当无参构造被调用成功时给出我们提示。
KunKun类代码如下 :
package knowledge.constructor;
public class KunKun {
//成员变量(KunKun类的属性)
private String name;
private int age = 11;
//公有的空参构造 (重点)
public KunKun() {
System.out.println("这是空参构造,成功调用此构造时打印这句话");
}
//name和age的getter,setter方法 :
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
TestKunKun类代码如下:
package knowledge.constructor;
public class TestKunKun {
public static void main(String[] args) {
//1.测试我们自定义的的空参构造(注意这里变了哦)
KunKun kun1 = new KunKun();
//使用空参构造构造初始化对象,除非属性值有显式初始化,否则均为默认值。
System.out.println("kun1的名字是:" + kun1.getName());
System.out.println("kun1的年龄是:" + kun1.getAge());
System.out.println("-------------------------------------------");
//使用setter方法修改kun1对象的属性,并重新打印
kun1.setName("坤哥");
kun1.setAge(18);
System.out.println("kun1的名字是:" + kun1.getName());
System.out.println("kun1的年龄是:" + kun1.getAge());
System.out.println("-------------------------------------------");
}
}
输出结果 :
输出后我们发现, 我们定义的无参构造成功被调用,且提示语句也成功输出,👌。
③第三步,我们建立一个有参构造,
并且把刚刚建立的无参构造暂时注释掉,即,测试KunKun类仅含一个有参构造的情况。KunKun类代码如下 :
package knowledge.constructor;
public class KunKun {
//成员变量(KunKun类的属性)
private String name;
private int age = 11;
//公有的带参构造 (重点)
public KunKun(String name, int age) {
System.out.println("这句话打印出来,说明带参构造被成功调用");
this.name = name;
this.age = age;
}
//name和age的getter,setter方法 :
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
TestKunKun类代码如下 :
package knowledge.constructor;
public class TestKunKun {
public static void main(String[] args) {
//2.测试带参构造
KunKun kun2 = new KunKun("两年半", 18);
/*
使用带参构造可以使我们免去用setter方法赋值的步骤,
而是可以直接修改kun2对象中对应的属性值
*/
System.out.println("Kun2叫啥名儿 :" + kun2.getName());
System.out.println("Kun2几岁啦 :" + kun2.getName());
System.out.println("-------------------------------------------");
}
}
输出结果 :
可以看到带参构造成功被调用了捏🤗 。
④在仅含一个带参构造的前提下,测试空参构造:
但这时候,IDEA直接给我们报错了,如下图所示 :
IDEA提示我们不能使用空参构造来初始化KunKun类对象,这也很好解释,你**在KunKun类中就只定义了一个带参构造,根据上面的注意事项1,当我们自定义任何一个构造器后,系统就不会在提供默认的无参构造了。而你这里又想用无参构造,所以才报错。那我们怎么办呢?很简单,你有参和无参都定义一个⑧就⭐了!😎
⑤同时定义了无参构造和有参构造:
KunKun类代码如下 :
package knowledge.constructor;
public class KunKun {
//成员变量(KunKun类的属性)
private String name;
private int age = 11;
//公有的空参构造 (重点)
public KunKun() {
System.out.println("这是空参构造,成功调用此构造时打印这句话");
}
//公有的带参构造 (重点)
public KunKun(String name, int age) {
System.out.println("这句话打印出来,说明带参构造被成功调用");
this.name = name;
this.age = age;
}
//name和age的getter,setter方法 :
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//KunKun类中的成员方法(KunKun类的行为)
/*
复习一下我们的private关键字和this关键字,此处唱、跳、rap,篮球四个方法,均为私有,
但是我们可以通过新建一个public公共方法,然后通过this关键字来分别调用这几个私有方法。
*/
private void sing() { //唱方法
System.out.println("🐔你太美~~🐔你实在是太美~");
}
private void jump() { //跳方法
System.out.println("哎哟你干嘛~");
}
private void rap() { //rap方法
System.out.println("你Kun哥厉不厉害?");
}
private void basketball() { //篮球方法
System.out.println("🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀");
}
//通过this关键字来调用本类对象的私有方法
public void personalTrainee() {
System.out.println("我会唱跳rap篮球!");
this.sing();
this.jump();
this.rap();
this.basketball();
}
}
TestKunKun类代码如下 :
package knowledge.constructor;
public class TestKunKun {
public static void main(String[] args) {
//1.测试我们自定义的的空参构造(注意这里变了哦)
KunKun kun1 = new KunKun();
//使用空参构造构造初始化对象,除非属性值有显式初始化,否则均为默认值。
System.out.println("kun1的名字是:" + kun1.getName());
System.out.println("kun1的年龄是:" + kun1.getAge());
System.out.println("-------------------------------------------");
//使用setter方法修改kun1对象的属性,并重新打印
kun1.setName("坤哥");
kun1.setAge(18);
System.out.println("kun1的名字是:" + kun1.getName());
System.out.println("kun1的年龄是:" + kun1.getAge());
System.out.println("-------------------------------------------");
//2.测试带参构造
KunKun kun2 = new KunKun("两年半", 18);
/*
使用带参构造可以使我们免去用setter方法赋值的步骤,
而是可以直接修改kun2对象中对应的属性值
*/
System.out.println("Kun2叫啥名儿 :" + kun2.getName());
System.out.println("Kun2几岁啦 :" + kun2.getName());
System.out.println("-------------------------------------------");
//3.测试成员方法
kun1.personalTrainee();
}
}
输出结果 :
同时定义有参构造和无参构造,我们就可以想调用哪个调用哪个。
因此:系统默认有一个无参构造。 但如果类只定义了一个带参构造,则无法再使用默认的空参构造,除非在类中再显式地定义一个空参构造。 其实,我们后面的JavaBean类就会讲到,标准的JavaBean类往往就是需要空参构造和带参构造同时都要有的。
7.关于javap命令(了解即可) :
①介绍 :
1>我们知道,java程序的运行原理是 : 我们先写好___.java的源文件,经过javac.exe命令可以将___.java的源文件编译为___.class的字节码文件,然后再由java.exe命令运行字节码文件,打印出结果。如下图所示 :
2>而我们的javap命令可以理解为将“编译”这一过程给反过来了。 javap是JDK提供的一个命令行工具,javap能对给定的class文件的字节代码进行反编译。通过javap,我们可以对照源代码和字节码,从而了解许多编译器内部的工作,对更深入地理解如何提高程序执行的效率等问题有极大的帮助。
②位置 :
javap命令位于JDK安装目录的bin目录下。如下图:
③图示 :
④使用格式 :
java <options> <classes>
其中:
options表示操作符,不同的操作符可以实现不同的功能,也可以不写操作符.
classes表示类名,就以KunKun类为栗,你可以写javap KunKun.class, 也可以写成javap KunKun,意思就是字节码文件的后缀名“.class”可以不写。
⑤常用指令 :
javap -version 查看当前JDK的版本信息
javap -v 输出附加信息(一大堆,啥都有,你现在也看不懂,了解即可)
javap -l 输出行号和本地变量表(解释 : 同-v)
javap -public 仅显示公共类和成员
javap -protected 显示受保护的公共类和成员
javap -package 显示程序包受保护的公共类和成员(默认)
javap -private 显示所有类和成员
javap -c 对代码进行反汇编
javap -s 输出内部类型签名
javap -sysinfo 显示正在处理的类
⑥代码演示,javap命令演示 :
我们以KunKun类为栗,试试通过javap命令对KunKun类反编译会发生什么,
KunKun类代码如下 :
package knowledge.constructor;
public class KunKun {
//成员变量(KunKun类的属性)
private String name;
private int age = 11;
//公有的空参构造 (重点)
public KunKun() {
System.out.println("这是空参构造,成功调用此构造时打印这句话");
}
//公有的带参构造 (重点)
public KunKun(String name, int age) {
System.out.println("这句话打印出来,说明带参构造被成功调用");
this.name = name;
this.age = age;
}
//name和age的getter,setter方法 :
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//KunKun类中的成员方法(KunKun类的行为)
private void sing() { //唱方法
System.out.println("🐔你太美~~🐔你实在是太美~");
}
private void jump() { //跳方法
System.out.println("哎哟你干嘛~");
}
private void rap() { //rap方法
System.out.println("你Kun哥厉不厉害?");
}
private void basketball() { //篮球方法
System.out.println("🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀");
}
//通过this关键字来调用本类对象的私有方法
public void personalTrainee() {
System.out.println("我会唱跳rap篮球!");
this.sing();
this.jump();
this.rap();
this.basketball();
}
}
其实就是刚才测试构造器时KunKun类的最终形态。这时候,我们需要找到KunKun类的字节码文件所在的位置,字节码文件统一放在out目录下(src是存放源代码的目录), 如下图 :
然后我们进入cmd, 通过DOS命令进入该目录,或者直接先找到该目录,然后在地址栏输入cmd回车,如下gif图 :
🆗,接下来我们进行演示 : 如下GIF图所示 :
可以看到,KunKun类中的空参构造和带参构造,以及所有的public方法全都显示出来了,但没有私有方法,如果要查看私有方法,需要加命令操作符 -p或者-private,如下GIF:
8.构造器的改进(🐂🖊) :
说是构造器的改进,其实,就是针对带参构造😂。以往我们在带参构造中会写啥呢,无非就是诸如this.xxx = xxx;的语句。But,现在我们可以不写这些this关键字的语句了,肯定会有p小将(personable 小将,指风度翩翩的人)来问了,你**不用this关键字你怎么把传入的值赋值给成员变量呢?
p小将你先别急。仔细想想,除了带参构造,哪里经常见到this关键字呢?没错,setter方法里面!所以,我猜你已经猜到了,我们可以利用调用setter方法的语句来代替this语句!原理 : 其实,平时我们写的带参构造,无非就是把所有setter方法里面的this语句合在一起了,所以调用带参构造就可以省去调用setter方法的步骤。而现在我们要做的,就是返璞归真,在带参构造里面调用指定的setter方法,来定义指定的构造器。
可是,为什么我们要这么做呢?
别忘了我们的标题,啥?优化呀!说白了我们是要在setter方法里面做点手脚,让它在带参构造中发挥一些我们希望的作用效果,比如说对传入参数的校验和修正,从而实现带参构造的升级优化。
eg : 我们创建了一个Phone类(手机类),通过实例化Phone类来模拟我要买一个新手机。因为大部分人买手机多少都抱着自己的期待和要求,比如想购买某品牌的某款手机。就拿up来说,我想买的是Huawei品牌的手机,因此实例化Phone类时,传入的brand参数必须是Huawei,不是就发出提示;up不想买旧款,因此up想买的是发售年限小于等于一年的Huawei品牌的手机,如果传入的outYear参数大于1,就发出提示;up有钱,要买的手机不想低于10000块钱,因此,如果传入的price参数小于等于10000,就发出提示。
根据上面的要求,我们可以写代码了,
Phone类代码如下 :
package knowledge.constructor;
public class Phone {
private String name;
private int outYear;
private double price;
public Phone() {
}
public Phone(String name, int outYear, double price) {
// this.name = name;
// this.outYear = outYear;
// this.price = price;
setName(name);
setOutYear(outYear);
setPrice(price);
}
public String getName() {
return name;
}
public void setName(String name) {
if ("Huawei".equals(name)) {
this.name = name;
} else {
System.out.println("姐,我要买的是Huawei手机。");
}
}
public int getOutYear() {
return outYear;
}
public void setOutYear(int outYear) {
if (1 >= outYear) {
this.outYear = outYear;
} else {
System.out.println("姐,我想要的是新款。");
}
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
if (10000 < price) {
this.price = price;
} else {
System.out.println("👴刚发了奖金,不差钱!");
}
}
}
接着,我们再创建一个TestPhone类,用来测试Phone类,TestPhone类代码如下:
package knowledge.constructor;
public class TestPhone {
public static void main(String[] args) {
//1.我们先实例化一个不是Huawei品牌的手机
Phone phone = new Phone("Apple", 1, 19999);
System.out.println("--------------------------------------------");
//2.再来实例化一个发售年限超过1年的Huawei品牌的手机
Phone phone2 = new Phone("Huawei", 3, 12999);
System.out.println("--------------------------------------------");
//3.再来实例化一个价格低于10000的,发售年限1年内的Huawei品牌手机
Phone phone3 = new Phone("Huawei", 1, 3699);
System.out.println("--------------------------------------------");
//4.最后实例化一个我们想买的,符合我们条件的手机
Phone phoneEX = new Phone("Huawei", 1, 13999);
if ("Huawei".equals(phoneEX.getName()) && 1 >= phoneEX.getOutYear() && 10000 < phoneEX.getPrice()) {
System.out.println("是👴要买的手机😁");
}
}
}
输出结果 :
六、标准代码JavaBean!(编程之美)
1.Java语言编写类的标准规范 :
JavaBean是一种特殊的Java类,是Java语言编写类的标准规范,也是最美的Java类。
①符合JavaBean标准的类,必须是具体,公共的;
②并且不但具有空参构造,通常也需要写出它的带参构造;
③成员变量全部用private关键字修饰,并且要提供用来操作这些成员变量的setter和getter方法。
当JavaBean类写多了你会发现:代码越看越顺眼,美观大方,整洁优雅,反正up现在一看到符合JavaBean类的代码,都会很欣慰😂。
2.标准JavaBean类代码演示 :
以Student2类为演示类,Student2类代码如下:
package knowledge.define.student;
//这就是一个标准的javaBean类
public class Student2 {
//无参构造:
public Student2() { }
//带参构造:
public Student2(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
//成员变量
private String name;
private int age;
private String sex;
//成员变量的setter和getter方法。
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
/*
* Summary:
* 1.只要你写了一个任意形式的构造体,那么系统就不会给默认的无参构造了
* 2.所以一般定义类用到构造体时,无参和有参形式都需要手动写上。
* 3.注意setter函数返回值类型均为void,而getter函数的返回值类型取决于该getter函数对应的
* setter函数的形参类型。
* */
TestStudent类代码如下:
package knowledge.define.student;
public class TestStudent2 {
public static void main(String[] args) {
//需求一: 定义一个姓名为Cyan,年龄为19的学生
//格式一: 通过无参构造实现:
Student2 student2 = new Student2();
student2.setName("Cyan");
student2.setAge(19);
student2.setSex("male");
System.out.println(student2.getName());
System.out.println(student2.getAge());
System.out.println(student2.getSex());
System.out.println("-------------------");
//格式二: 通过构造方法快速初始化:
Student2 st2 = new Student2("Cyan", 19, "男");
System.out.println(st2.getName());
System.out.println(st2.getAge());
System.out.println(st2.getSex());
}
}
输出结果 :
七、总结 :
🆗,费了这么大功夫总算是把面向对象三个特性的第一关BOSS给干掉了😄。回顾一下,我们从封装的引入开始,到private关键字,this关键字,以及构造器,中间这三个都很详细得做了讲解,最后我们又说到了JavaBean。好滴,感谢阅读!
System.out.println("END------------------------------------------------------------");
- 点赞
- 收藏
- 关注作者
评论(0)