java 面向对象三大特性之封装 万字详解(超详细)

举报
Cyan_RA9 发表于 2023/04/03 16:55:40 2023/04/03
【摘要】 Java 面向对象三大特性之封装,详细讲解了private关键字,this关键字,以及构造器,一文讲透!

目录

前言 : 

一、为什么需要封装 :

        前言 : 

        1.封装的好处 :

        2.java中的常见封装体:

二、封装简介 : 

        1.封装的概念 : 

        2.封装的关键 :

三、private关键字(重要) : 

        1.封装的必要利器—private介绍:

                Δ不用private的代码演示 : 

        2.基本概念:

        3.特点 :

        4.用法 : 

        5.private关键字代码演示 : 

        6.setter,getter方法展示(重要) : 

                ①介绍:

                ②如何定义setter,getter方法 : 

                ③代码演示 及 问题延申 :

        7.关于public关键字 : 

四、this关键字(重要) : 

        1.基本概念 : 

        2.特点 : 

        3.用法 : 

        4.作用 : 

        5.代码演示 : 

                ①private关键字演示中Phone类setter方法的强龙地头蛇布局定式冲突的解决 : 

                ②this关键字调用成员变量(属性)的演示 : 

                ③this关键字调用成员方法(行为)的演示 : 

        6.深入理解this关键字 : 

                图解  : 

                代码演示 : 

        7.hashCode相关(了解即可) : 

                ①介绍 : 

                ②hashCode的常规协定 : 

五、构造器详解(重要) : 

        1.什么是构造器 : 

        2.那么,谁来创建对象呢?

        PS: Java创建对象的内存图解 : 

        3.构造器的定义格式 : 

        4.构造器需要满足的要求:

        5.注意事项(重要):

        6.代码演示 : 

                ①我们先不定义构造器,测试一下是否能成功创建对象。     

                ②下一步,我们定义一个自己的无参构造,

                ③第三步,我们建立一个有参构造,

                ④在仅含一个带参构造的前提下,测试空参构造:

                ⑤同时定义了无参构造和有参构造:

        7.关于javap命令(了解即可) : 

                ①介绍 : 

                ②位置 : 

                ③图示 : 

                ④使用格式 : 

                ⑤常用指令 : 

                ⑥代码演示,javap命令演示 : 

        8.构造器的改进(🐂🖊) : 

六、标准代码JavaBean!(编程之美)

        1.Java语言编写类的标准规范 :

        2.标准JavaBean类代码演示 :

七、总结 : 


前言 : 

        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("🐔你太美~,🐔你实在是太美~");
    }
}

image.gif

                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();
    }
}

image.gif

                输出结果如下图 : 

image.png

                注意看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("🐔你太美~,🐔你实在是太美~");
    }
}

image.gif

                这时候你会发现,这**刚加上private关键字就报错了,如下图所示 : 

image.png          

  IDEA提示我们有两条相关问题。什么是相关问题呢?意思就是问题本身不是在当前类,但是出了问题的类呢,又与当前类有关系。显然,只能是TestPhone类出了问题!如下图:            image.png               

明明白白,它提示我们:这些个变量都有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("🐔你太美~,🐔你实在是太美~");
    }
}

image.gif

                有了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();
    }
}

image.gif

                 输出结果如下 : 

image.png     

😁效果不错。               

                这时候,就要有p小将(personable小将,指风度翩翩的人!)要说理了:诶诶诶,up你难道没发现吗,你的setter方法体中形参的定义不符合见名知意的效果!我把其中的一个setter方法拿出来给大家看看

public void setName(String n) {
    name = n;
}

image.gif

                我趣,还真是这样,不愧是p小将😭,6。没错,所谓见名知意,就是指看到变量名就可以基本确定这个变量是干嘛的,但是你看我们写得setter方法,传入的形参变量定义成了n,要知道n开头的英语单词可是多的很捏,谁知道你表示名字呢?因此这么定义形参名无法满足“见名知意”的效果,降低了代码的可读性和逻辑性

                好,改!下面我们把形参名统一改成成员变量名,如下 : 

image.png              就让我们来看看p小将的方法能不能行得通。        

                再次运行TestPhone类结果如下 : 

image.png

                我趣?!这时候很多小伙伴儿要懵了,这咋还一问三不知了。 

                出现这个问题的原因也很好解释

                大家都知道一句话吧,叫“强龙不压地头蛇”。没错,仔细看我们定义的setter方法,可以明确地看出,setter方法的形参列表都定义了局部变量,都不为空! 而Java中变量的使用规则是“就近原则”。即局部位置-> 成员位置-> 父类-> 更高的父类-> ...... -> Object如果一直到Object还没找到这个变量,报错!(Object是所有类的顶层父类,先了解一下即可,之后的继承特性中我们会讲到)。那既然现在setter方法内(局部位置)定义了形参,方法内部当然是优先使用这个定义的形参喽!所以,如果仅仅这么更改的话,达到的效果想必你也猜到了,没错,就是形参变量自己赋值给了自己,而成员变量,也就是我们要更改的属性,却仍然是String类型的默认值null

                其实,大家只要把鼠标悬停在右边的变量上,智能的IDEA就会自动报出提示 : 该变量自己赋值给了自己。如下图 : 

image.png

                因此,我们需要想办法让左面的变量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("🐔你太美~,🐔你实在是太美~");
    }
}

image.gif

                我们再来运行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();
    }
}

image.gif

                运行结果

image.png

                可以看到三个String类型的属性都再次被成功赋值了,this 牛逼👍。其实,如果你使用快捷键生成了getter和setter方法,你会发现IDEA自动写出来的setter方法就是这么一回事儿:

public void setXxx(数据类型 形参名xxx) {
    this.形参名xxx = 形参名xxx;     //成员变量xxx == this.形参名xxx
}

image.gif

                这样的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);
    }
}

image.gif

                TestPhone类代码如下: 

package knowledge.define;
public class
TestPhone {
    public static void main(String[] args) {
//1.创建对象
        Phone p = new Phone();
//2.调用成员方法
        p.pintAge();
    }
}

image.gif

                输出结果

image.png

可以看到,第一个没有用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);
    }
}

image.gif

                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
    }
}

image.gif

                输出结果

image.png        

我们看到成员变量age的值不再是默认的0了,而是我们通过setAge() 更改后的11。

                ③this关键字调用成员方法(行为)的演示 : 

                我们在Phone类中定义一个私有方法,如下 : 

 

package knowledge.define;
public class Phone {
    //定义一个私有的方法(无实际意义,仅作为演示)
    private void icon() {
        System.out.println("我是练习时长两年半的个人练习生:KunKun");
    }
}

image.gif

                当我们想在TestPhone类中直接调用icon() 该方法的时候,IDEA会提示报错,如下图所示: 

image.png

                这种情况下,如果我们想继续调用该方法,就需要用到this关键字了。

                我们在Phone类中定义一个新的公共的方法,然后在这个新方法中通过this关键字来调用icon方法,如下 : 

 

package knowledge.define;
public class Phone {
    //定义一个私有的方法(无实际意义,仅作为演示)
    private void icon() {
        System.out.println("我是练习时常两年半的个人练习生:KunKun");
    }
    //通过this关键字解决私有函数无法直接调用的问题
    public void demo() {
        this.icon();
    }
}

image.gif

                然后我们在TestPhone类中调用demo() 方法,就可以成功间接调用 icon方法,TestPhone类代码如下: 

package knowledge.define;
public class TestPhone {
    public static void main(String[] args) {
//1.创建对象
        Phone p = new Phone();
//2.调用成员方法
        p.demo();
    }
}

image.gif

                输出结果

image.png

        6.深入理解this关键字 : 

                this关键字的本质到底是什么呢?

                其实,JVM在堆空间给对象分配空间时,每个对象都有一个隐藏的属性this,this指向该对象本身。即,如果用C语言来解释的话,this就是一个指向堆空间中对象本身的指针,只不过在Java中没有指针,叫做引用而已。this自己是对象的一部分,它也在堆空间,但是它又指向了它自己。

                光这么说多少有丶带抽象,来张内存图直观的表示一下,如下图:

                图解  : 

image.png

                一看图就明白了,就是这么回事儿。当然,我们还可以通过另一种直观的方法来理解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());
    }
}

image.gif

                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();
    }
}

image.gif

                输出结果: 

image.png      

                可以看到, 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;
    }
}

image.gif

                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("-------------------------------------------");
    }
}

image.gif

                输出结果

image.png

                首先,我们在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;
    }
}

image.gif

                 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("-------------------------------------------");
    }
}

image.gif

                输出结果

image.png

                输出后我们发现, 我们定义的无参构造成功被调用,且提示语句也成功输出,👌。

                ③第三步,我们建立一个有参构造,

                并且把刚刚建立的无参构造暂时注释掉,即,测试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;
    }
}

image.gif

                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("-------------------------------------------");
    }
}

image.gif

                输出结果

image.png     

                可以看到带参构造成功被调用了捏🤗 。

                ④在仅含一个带参构造的前提下,测试空参构造:

                但这时候,IDEA直接给我们报错了,如下图所示 : 

image.png

                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();
    }
}

image.gif

                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();
    }
}

image.gif

                输出结果

image.png

                同时定义有参构造和无参构造,我们就可以想调用哪个调用哪个。

                因此:系统默认有一个无参构造。 但如果类只定义了一个带参构造,则无法再使用默认的空参构造,除非在类中再显式地定义一个空参构造。 其实,我们后面的JavaBean类就会讲到,标准的JavaBean类往往就是需要空参构造和带参构造同时都要有的。

        7.关于javap命令(了解即可) : 

                ①介绍 : 

                        1>我们知道,java程序的运行原理是 : 我们先写好___.java的源文件,经过javac.exe命令可以将___.java的源文件编译为___.class的字节码文件,然后再由java.exe命令运行字节码文件,打印出结果。如下图所示 : 

  image.png

                        2>而我们的javap命令可以理解为将“编译”这一过程给反过来了。 javap是JDK提供的一个命令行工具,javap能对给定的class文件的字节代码进行反编译通过javap,我们可以对照源代码和字节码,从而了解许多编译器内部的工作,对更深入地理解如何提高程序执行的效率等问题有极大的帮助。

                ②位置 : 

                        javap命令位于JDK安装目录的bin目录下。如下图: 

image.png

                ③图示 : 

image.png

                ④使用格式 : 

                        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();
    }
}

image.gif

                其实就是刚才测试构造器时KunKun类的最终形态。这时候,我们需要找到KunKun类的字节码文件所在的位置,字节码文件统一放在out目录下(src是存放源代码的目录), 如下图 : 

image.png

                然后我们进入cmd, 通过DOS命令进入该目录,或者直接先找到该目录,然后在地址栏输入cmd回车,如下gif图

image.png

                🆗,接下来我们进行演示 : 如下GIF图所示 : 

image.png

                可以看到,KunKun类中的空参构造和带参构造,以及所有的public方法全都显示出来了,但没有私有方法,如果要查看私有方法,需要加命令操作符 -p或者-private,如下GIF: 

image.png

        8.构造器的改进(🐂🖊) : 

                说是构造器的改进,其实,就是针对带参构造😂。以往我们在带参构造中会写啥呢,无非就是诸如this.xxx = xxx;的语句。But,现在我们可以不写这些this关键字的语句了,肯定会有p小将(personable 小将,指风度翩翩的人来问了,你**不用this关键字你怎么把传入的值赋值给成员变量呢

                p小将你先别急。仔细想想,除了带参构造,哪里经常见到this关键字呢?没错,setter方法里面!所以,我猜你已经猜到了,我们可以利用调用setter方法的语句来代替this语句原理 : 其实,平时我们写的带参构造,无非就是把所有setter方法里面的this语句合在一起了,所以调用带参构造就可以省去调用setter方法的步骤。而现在我们要做的,就是返璞归真,在带参构造里面调用指定的setter方法,来定义指定的构造器。

                可是,为什么我们要这么做呢?

image.png

                 别忘了我们的标题,啥?优化呀!说白了我们是要在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("👴刚发了奖金,不差钱!");
        }
    }
}

image.gif

                接着,我们再创建一个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("是👴要买的手机😁");
        }
    }
}

image.gif

                输出结果

image.png

六、标准代码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函数的形参类型。
* */

image.gif

                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());
    }
}

image.gif

                输出结果

image.png

七、总结 : 

                🆗,费了这么大功夫总算是把面向对象三个特性的第一关BOSS给干掉了😄。回顾一下,我们从封装的引入开始,到private关键字this关键字,以及构造器,中间这三个都很详细得做了讲解,最后我们又说到了JavaBean。好滴,感谢阅读!

System.out.println("END------------------------------------------------------------");          

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。