首页 > 其他分享 >05-面向对象_封装

05-面向对象_封装

时间:2022-11-27 11:26:17浏览次数:43  
标签:封装 name 构造方法 05 对象 age Person 面向对象 public

typora-root-url: images

一、编程思想

1.1、什么是思想

​ 何谓编程思想?首先解释一下“思想”。

​ 先问你个问题:你想做个怎样的人?

​ 可能你会回答:我想做个好人,孝敬父母,尊重长辈,关爱亲朋…

​ 你看,这就是思想。这是你做人的思想,或者说,是你做人的原则。做人有做人的原则,编程也有编程的原则。这些编程的原则呢,就是编程思想。

1.2、分类

  • 面向对象:Object Oriented Programming
  • 面向过程:Procedure Oriented Programming

面向过程与面向对象都是我们编程中,编写程序的一种思维方式。

1.3、面向过程与面向对象对比

  • 面向过程

    ​ 面向过程的程序设计方式,是遇到一件事时,思考“我该怎么做”,然后一步步实现的过程。

    ​ 例如:公司打扫卫生(擦玻璃、扫地、拖地、倒垃圾等),按照面向过程的程序设计方式会思考“打扫卫生我该怎么做,然后一件件的完成”,最后把公司卫生打扫干净了。

  • 面向对象

    ​ 面向对象的程序设计方式,是遇到一件事时,思考“我该让谁来做”,然后那个“谁”就是对象,他要怎么做这件事是他自己的事,反正最后一群对象合力能把事就好就行了。

    ​ 例如,公司打扫卫生(擦玻璃、扫地、拖地、倒垃圾等),按照面向对象的程序设计方式会思考“我该让谁来做,如小明擦玻璃、让小丽扫地、让小郭拖地、让小强倒垃圾等”,这里的“小明、小丽、小郭、小强”就是对象,他们要打扫卫生,怎么打扫是他们自己的事,反正最后一群对象合力把公司卫生打扫干净了。

1.4、面向对象举例

​ 需求:买电脑(组装机)

1.4.1、面向过程解决

​ 假如我们需要买组装电脑,这时首先会在网上查询具体每一个硬件的参数和报价。然后会去电脑城进行多家询价,接着询价结束后回家根据具体的结果分析出自己比较满意的哪家报价,接着会到这家店里进行组装,组装时还需要进行现场监督,组装完成安装相应的系统,然后电脑抱回家。

​ 分析上述整个过程大体分一下几步:上网查询参数和报价、电脑城询价、现场安装和监督、抱电脑回家。在整个过程中我们参与了每一个细节,并且会感觉相当累。

1.4.2、面向对象解决

​ 假如我们需要买组装机,这时应该找一个懂电脑硬件的人,让他帮我们查看参数和报价,并进行询价和杀价,以及现场组装监督。而我们自己并不需要亲历亲为具体怎么做,只要告诉这个人我们想要的具体需求即可。

​ 分析上述整个过程,发现瞬间变的十分轻松,只要找到懂电脑硬件的这个人,我们的问题都可以解决。并且在这个过程中我们不用那么辛苦。

1.5、面向对象思维方式的好处

​ 通过生活中的真实场景使用面向对象分析完之后,我们开始分析面向过程和面向对象的差异做出总结:

  • 面向对象思维方式是一种更符合人们思考习惯的思想
  • 面向过程思维方式中更多的体现的是执行者(自己做事情),面向对象中更多的体现是指挥者(指挥对象做事情)。
  • 面向对象思维方式将复杂的问题简单化。
  • 程序员从面向过程的执行者转化成了面向对象的指挥者。

1.6、总结

​ 洗衣服:

​ 面向过程:把衣服脱下来-->找盆-->放洗衣粉-->加点水-->浸泡10分钟-->揉一揉-->清洗衣服-->拧干-->晾起来

​ 面向对象:把衣服脱下来-->打开全自动洗衣机-->扔衣服-->按钮-->晾起来

​ 区别:

  • 面向过程:强调步骤。
  • 面向对象:强调对象,这里的对象就是洗衣机。

二、类和对象

2.1、对象在需求中的使用

​ 对面向对象有了了解之后,我们来说说在具体问题中如何使用面向对象去分析问题,和如何使用面向对象。

​ 我们把大象装冰箱为例进行分析。

​ 在针对具体的需求,可以使用名词提炼的办法进行分析,寻找具体的对象。

​ 需求:把大象装冰箱里

​ 对象:大象、冰箱

​ 分三步:

​ 1、打开冰箱门

​ 2、将大象装进去

​ 3、关闭冰箱门

​ 分析发现打开、装、关闭都是冰箱的功能。即冰箱对象具备如下功能:

​ 冰箱打开

​ 冰箱存储

​ 冰箱关闭

​ 用伪代码描述,上述需求中有两个具体的事物 大象 和 冰箱

​ 描述大象:

​ class 大象

​ {

​ }

​ 描述冰箱:

​ class冰箱

​ {

​ void 打开(){}

​ void 存储(大象){}

​ void 关闭(){}

​ }

​ 当把具体的事物描述清楚之后,需要使用这些具体的事物,Java使用具体的事物,需要通过new关键字来创建这个事物的具体实例。

​ 使用对象:

​ 1、创建冰箱的对象

​ 冰箱 bx = new 冰箱();

​ 2、调用冰箱的功能

​ 对象.功能();

​ bx.打开();

​ bx.存储(new 大象());

​ bx.关闭();

​ 总结:

  • 先按照名词提炼问题领域中的对象
  • 对对象进行描述,其实就是在明确对象中应该具备的属性和功能
  • 通过new的方式就可以创建该事物的具体对象
  • 通过该对象调用它以后的功能。

2.2、对象在代码中的体现

​ 在分析现实生活中的事物时发现,这些事物都有其具体的特点和功能,这些特点和功能就组成了这个特殊的事物。

​ 描述小汽车。

​ 分析:

​ 事物的特点(属性):

​ 颜色。

​ 轮胎个数。

​ 事物的(功能):

​ 运行。

		发现:事物其实就是由特点(属性)和行为(功能)组成的。

​ 可以简单理解:属性就是数值,其实就是变量;行为就是功能,就是方法。

​ 小汽车 {

​ 颜色;

​ 轮胎个数;

​ 运行() { }

​ }

​ 通过计算机语言Java来描述这个事物:

​ 定义类的格式:

public class 类名 {
	//可编写0至n个属性
    数据类型 变量名1;
    数据类型 变量名2;
	
	//可编写0至n个方法
	修饰符 返回值类型 方法名(参数){
		执行语句;
	}
}

​ 汽车类:

public class Car {
	String color;
	int number;

	public void run() {
		System.out.println(color + ":" + number);
	}
}

​ 通过代码的描述,知道类的真正意义就是在描述事物。属性和功能统称为事物中的成员。

  • 事物的成员分为两种:成员属性和成员功能。
  • 成员属性在代码中的体现就是成员变量
  • 成员功能在代码中的体现就是成员方法

2.3、使用定义好的类

2.3.1、创建对象

# 类名 对象名 = new 类名();

2.3.2、测试

public class CarDemo {
	public static void main(String[] args) { 
		// 1,创建Car的对象。给对象起个名字。
		Car c = new Car();// c是类类型的变量。c指向了一个具体的Car类型的对象。
		// 2,通过已有的对象调用该对象的功能。格式:对象.对象成员;
		// 3,可以该对象的属性赋值。
		c.color = "red";
		c.number = 4;
		c.run();
	}
}

2.4、对象的内存图解

2.4.1、说明

​ 经过上面对小汽车的描述,和Java代码测试,我们虽然可以将生活中的事物使用Java代码描述出来,但是这些代码在内存中是如何执行的,接下来我们需要研究下对象在内存的图解。

​ 接下来就是分析对象在内存中的分配情况。这里需要画图一步一步演示,严格按照画图流程讲解内存对象创建过程。

2.4.2、图示

0066

2.5、类和对象的区别

​ 面向对象的编程思想力图在程序中对事物的描述与该事物在现实中的形态保持一致。为了做到这一点,面向对象的思想中提出两个概念,即类和对象。其中,类是对某一类事物的抽象描述,而对象用于表示现实中该类事物的个体。接下来通过一个图例来抽象描述类与对象的关系,如下图所示。
0067

​ 在上图中,可以将玩具模型看作是一个类,将一个个玩具看作对象,从玩具模型和玩具之间的关系便可以看出类与对象之间的关系。类用于描述多个对象的共同特征,它是对象的模板。对象用于描述现实中的个体,它是类的实例。从上图中可以明显看出对象是根据类创建的,并且一个类可以对应多个对象。

​ 经过前面几个知识点的学习,基本上掌握了类是用于描述事物的,类中可以定义事物的属性和行为。而对象是通过描述的这个类,使用new关键字创建出来,通过对象就可以调用该对象具体的属性和功能了。

2.6、局部变量和成员变量区别

​ 理解清楚了类和对象之后,发现在描述类的属性和前面学习定义变量差别不大,唯一区别就是位置发生了改变,那么类中定义的变量,和在方法定义的变量有啥差别呢?

​ 回忆以前学习时变量的定义方式和位置,以及现在定义类中属性的特点。总结下面几点异同:

  • 区别一:定义的位置不同

    • 定义在类中的变量是成员变量
    • 定义在方法中的变量是局部变量
  • 区别二:在内存中的位置不同

    • 成员变量存储在对内存的对象中
    • 局部变量存储在栈内存的方法中
  • 区别三:声明周期不同

    • 成员变量随着对象的出现而出现在堆中,随着对象的消失而从堆中消失
    • 局部变量随着方法的运行而出现在栈中,随着方法的弹栈而消失
  • 区别四:初始化不同

    • 成员变量因为在堆内存中,所有默认的初始化值
    • 局部变量没有默认的初始化值,必须手动的给其赋值才可以使用。

2.7、基本类型和引用类型作为参数传递

2.7.1、基本类型作为参数传递

0068

​ 基本类型作为参数传递时,其实就是将基本类型变量x空间中的值复制了一份传递给调用的方法show(),当在show()方法中x接受到了复制的值,再在show()方法中对x变量进行操作,这时只会影响到show中的x。当show方法执行完成,弹栈后,程序又回到main方法执行,main方法中的x值还是原来的值。

2.7.2、引用类型作为参数传递

0069

​ 当引用变量作为参数传递时,这时其实是将引用变量空间中的内存地址(引用)复制了一份传递给了show方法的d引用变量。这时会有两个引用同时指向堆中的同一个对象。当执行show方法中的d.x=6时,会根据d所持有的引用找到堆中的对象,并将其x属性的值改为6.show方法弹栈。

​ 由于是两个引用指向同一个对象,不管是哪一个引用改变了引用的所指向的对象的中的值,其他引用再次使用都是改变后的值。

2.8、总结

2.8.1、什么是类

:是一组相关属性行为的集合。可以看成是一类事物的模板,使用事物的属性特征和行为特征来描述该

类事物。

​ 现实中,描述一类事物:

  • 属性:就是该事物的状态信息。
  • 行为:就是该事物能够做什么。

​ 举例:小猫。

  • 属性:名字、体重、年龄、颜色。
  • 行为:走、跑、叫。

2.8.2、什么是对象

对象:是一类事物的具体体现。对象是类的一个实例,必然具备该类事物的属性和行为。

​ 现实中,一类事物的一个实例:一只小猫。

​ 举例:一只小猫。

  • 属性:tom、5kg、2 years、yellow。
  • 行为:溜墙根走、蹦跶的跑、喵喵叫。

2.8.3、类和对象的关系

  • 类:类是对现实生活中一类具有共同属性和行为的事物的抽象
  • 对象:是能够看得到摸的着的真实存在的实体
  • 简单理解:类是对事物的一种描述,对象则为具体存在的事物

三、类的定义及对象使用

3.1、类的定义

3.1.1、说明

​ 现实世界的一类事物: 属性:事物的状态信息。 行为:事物能够做什么。

​ Java中用class描述事物也是如此: 成员变量:对应事物的属性成员方法:对应事物的行为

​ 简单来说,定义一个类的步骤是:

​ ① 定义类

​ ② 编写类的成员变量

​ ③ 编写类的成员方法

3.1.2、定义类的格式

public class 类名{
    // 成员变量
    // 成员方法
}
public class Phone{
    String brand; 
    int price; 
    //成员方法 
    public void call() { 
        System.out.println("打电话"); 
    }
    public void sendMessage() { 
        System.out.println("发短信"); 
    }
}
  • 定义类:就是定义类的成员,包括成员变量成员方法
  • 成员变量:和以前定义变量几乎是一样的。只不过位置发生了改变。在类中,方法外
  • 成员方法:和以前定义方法几乎是一样的。

3.2、对象的使用

3.2.1、创建对象的格式

# 类名 对象名 = new 类名();

3.2.2、使用对象访问类中的成员

# 对象名.成员变量; 
# 对象名.成员方法();

3.2.3、示例

class Student { 
    //成员变量 
    String name; 
    int age; 
    //成员方法 
    public void study() { 
        System.out.println("好好学习,天天向上"); 
    }
    public void doHomework() { 
        System.out.println("键盘敲烂,月薪过万"); 
    } 
}

/* 学生测试类 */ 
public class StudentDemo { 
    public static void main(String[] args) { 
        //创建对象 
        Student s = new Student(); 
        //使用对象 
        System.out.println(s.name + "," + s.age);
        s.name = "林青霞"; 
        s.age = 30; 
        System.out.println(s.name + "," + s.age); 
        
        // 调用方法
        s.study(); 
        s.doHomework(); 
    } 
}

3.2.4、练习

  • 需求:首先定义一个手机类,然后定义一个手机测试类,在手机测试类中通过对象完成成员变量和成员方法的使用

  • 分析:

    • 成员变量:品牌,价格,颜色…
    • 成员方法:打电话,发短信…
  • 示例代码:

    public class Phone { 
        // 成员变量 
        String brand; //品牌 
        int price; //价格 
        String color; //颜色 
        // 成员方法 
        //打电话 
        public void call(String name) { 
            System.out.println("给"+name+"打电话"); 
        }
        //发短信 
        public void sendMessage() { 
            System.out.println("群发短信"); 
        } 
    }
    
    public class Test02Phone { 
        public static void main(String[] args) { 
            //创建对象 
            Phone p = new Phone(); 
            //输出成员变量值 
            System.out.println("品牌:"+p.brand);//null 
            System.out.println("价格:"+p.price);//0 
            System.out.println("颜色:"+p.color);//null 
            System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐");
            
            //给成员变量赋值 
            p.brand = "锤子"; 
            p.price = 2999; 
            p.color = "棕色"; 
            //再次输出成员变量值 
            System.out.println("品牌:"+p.brand);//锤子 
            System.out.println("价格:"+p.price);//2999 
            System.out.println("颜色:"+p.color);//棕色 
            System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐");
            
            //调用成员方法 
            p.call("紫霞"); 
            p.sendMessage(); 
        } 
    }
    

3.3、对象内存图分析

3.3.1、单个对象内存图

0070

​ 通过上图,我们可以理解,在栈内存中运行的方法,遵循"先进后出,后进先出"的原则。变量p指向堆内存中 的空间,寻找方法信息,去执行该方法。

​ 但是,这里依然有问题存在。创建多个对象时,如果每个对象内部都保存一份方法信息,这就非常浪费内存了,因为所有对象的方法信息都是一样的。那么如何解决这个问题呢?请看如下图解。

3.3.2、两个对象内存图

0071

​ 对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。这样哪怕是多个对象,方法信息只保存一份,节约内存空间。

3.3.3、引用作为参数传递到方法中内存图

0072

引用类型作为参数,传递的是地址值。

四、封装

4.1、概述

​ 提起封装,大家并不陌生。前面我们学习方法时,就提起过,将具体功能封装到方法中,学习对象时,也提过将方法封装在类中,其实这些都是封装。

​ 封装,它也是面向对象思想的特征之一。面向对象共有三个特征:封装,继承,多态。接下来我们具体学习封装。

  • 封装表现:

    • 1、方法就是一个最基本封装体。
    • 2、类其实也是一个封装体。
  • 从以上两点得出结论,封装的好处:

    • 1、提高了代码的复用性。
    • 2、隐藏了实现细节,还要对外提供可以访问的方式。便于调用者的使用。这是核心之一,也可以理解为就是封装的概念。
    • 3、提高了安全性。

4.2、举例

4.2.1、案例1

​ 一台电脑,它是由CPU、主板、显卡、内存、硬盘、电源等部件组成,其实我们将这些部件组装在一起就可以使用电脑了,但是发现这些部件都散落在外面,很容造成不安全因素,于是,使用机箱壳子,把这些部件都装在里面,并在机箱壳上留下一些插口等,若不留插口,大家想想会是什么情况。

​ 总结:机箱其实就是隐藏了各部件设备的细节,对外提供了插口以及开关等访问内部细节的方式。

4.2.2、案例2

​ 我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?

4.3、封装的作用

​ 我们程序设计追求“高内聚,低耦合”:

  • 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
  • 低耦合:仅对外暴露少量的方法用于使用。

​ 隐藏对象内部的复杂性,只对外公开简单的接口:

​ 便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。

4.4、封装的步骤

4.4.1、问题引入

​ 当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。但除此之外,没有其他制约条件。但是,实际问题中,我们往往需要给属性赋值加入额外限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行条件的添加。比如说,每个人都有年龄属性(age),如果直接赋值的话,有可能输入的是非法的值。所以,我们需要避免用户再使用“对象.属性”的方式对属性进行赋值。则需要将属性声明为私有的(private),如果属性是私有的,那么外部是不能直接访问的,那么此时,针对于属性就体现了封装性。当然我们还是需要对属性赋值的嘛,怎么办呢?就需要通过方法进行对该属性赋值,那么在方法中,就可以实现对用户赋予的值进行判断了,从而避免非法值赋予年龄。

4.4.2、步骤

  • 使用 private 关键字来修饰成员变量。
  • 对需要访问的成员变量,提供对应的一对 getXxx 方法 、 setXxx 方法

4.4.3、案例

​ 描述人。Person

​ 属性:年龄。

​ 行为:说话:说出自己的年龄。

class Person {
	int age;
	String name;

	public void show() {
		System.out.println("age=" + age + ",name" + name);
	}
}

public class PersonDemo {
	public static void main(String[] args) {
		// 创建Person对象
		Person p = new Person();
		p.age = -20; // 给Person对象赋值
		p.name = "人妖";
		p.show(); // 调用Person的show方法
	}
}

​ 通过上述代码发现,虽然我们用Java代码把Person描述清楚了,但有个严重的问题,就是Person中的属性的行为可以任意访问和使用。这明显不符合实际需求。

​ 可是怎么才能不让访问呢?需要使用一个Java中的关键字也是一个修饰符 private(私有,权限修饰符)。只要将Person的属性和行为私有起来,这样就无法直接访问。

class Person {
	private int age;
	private String name;

	public void show() {
		System.out.println("age=" + age + ",name" + name);
	}
}

​ 年龄已被私有,错误的值无法赋值,可是正确的值也赋值不了,这样还是不行,那肿么办呢?按照之前所学习的封装的原理,隐藏后,还需要提供访问方式。只要对外提供可以访问的方法,让其他程序访问这些方法。同时在方法中可以对数据进行验证。

​ 一般对成员属性的访问动作:赋值(设置 set),取值(获取 get),因此对私有的变量访问的方式可以提供对应的 setXxx或者getXxx的方法。

class Person {
	// 私有成员变量
	private int age;
	private String name;

	// 对外提供设置成员变量的方法
	public void setAge(int a) {
		// 由于是设置成员变量的值,这里可以加入数据的验证
		if (a < 0 || a > 130) {
			System.out.println(a + "不符合年龄的数据范围");
			return;
		}
		age = a; 
	}

	// 对外提供访问成员变量的方法
	public void getAge() {
		return age;
	}
}

4.4.4、总结

​ 类中不需要对外提供的内容都私有化,包括属性和方法。

​ 以后再描述事物,属性都私有化,并提供setXxx getXxx方法对其进行访问。

注意:私有仅仅是封装的体现形式而已。

4.5、private关键字

4.5.1、说明

  • private是一个权限修饰符,代表最小权限。
  • 可以修饰成员变量和成员方法。
  • 被private修饰后的成员变量和成员方法,只在本类中才能访问。

修饰符:

0073

4.5.2、案例

public class Student { 
    private String name; 
    private int age; 
    
    public void setName(String n) { 
        name = n; 
    }
    public String getName() { 
        return name; 
    }
    public void setAge(int a) { 
        age = a; 
    }
    public int getAge() { 
        return age; 
    }
}

4.6、this关键字

4.6.1、问题引入

​ 我们发现 setXxx 方法中的形参名字并不符合见名知意的规定,那么如果修改与成员变量名一致,是否就见名知意了呢?

public class Student { 
    private String name; 
    private int age; 
    
    public void setName(String name) { 
		name = name; 
    }
    
    public void setAge(int age) { 
        age = age; 
    } 
}

​ 经过修改和测试,我们发现新的问题,成员变量赋值失败了。也就是说,在修改了 setXxx() 的形参变量名后,方法并没有给成员变量赋值!这是由于形参变量名与成员变量名重名,导致成员变量名被隐藏,方法中的变量名,无法访问到成员变量,从而赋值失败。所以,该怎么解决?

4.6.2、this

​ 当在方法中出现了局部变量和成员变量同名的时候,那么在方法中怎么区别局部变量成员变量呢?可以在成员变量名前面加上this.来区别成员变量和局部变量。

​ 注意:

  • this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)
    • 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
    • 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量

​ 事实上:

​ this代表所在类的当前对象的引用(地址值),即对象自己的引用。

记住 :方法被哪个对象调用,方法中的this就代表那个对象。即谁在调用,this就代表谁。

4.6.3、问题解决

​ 使用 this 修饰方法中的变量,解决成员变量被隐藏的问题。

public class Student { 
    private String name; 
    private int age; 
    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;
    }
}

方法中只有一个变量名时,默认也是使用this 修饰,可以省略不写。

4.6.4、this内存图解

  • 代码

    class Person {
    	private int age;
    	public int getAge() {
    		return this.age;
    	}
    	public void setAge(int age) {
    		this.age = age;
    	}
    }
    public class PersonDemo {
    	public static void main(String[] args) {
    		Person p = new Person();
    		p.setAge(30);
    		System.out.println("大家好,今年我" + p.getAge() + "岁");
    	}
    }
    
  • 图示

    0074

  • 解释

    • 程序执行流程说明:
      1. 先执行main方法(压栈),执行其中的 Person p = new Person();
      2. 在堆内存中开辟空间,并为其分配内存地址0x1234,紧接着成员变量默认初始化(age = 0);将内存地址0x1234赋值给栈内中的Person p 变量
      3. 继续执行p.setAge(30)语句,这时会调用setAge(int age)方法,将30赋值为setAge方法中的“age”变量;执行this.age = age语句,将age变量值30 赋值给成员变量this.age为30;
      4. setAge()方法执行完毕后(弹栈),回到main()方法,执行输出语句System.out.println(),控制台打印p对象中的age年龄值。

    • 注意:

      • this到底代表什么呢?this代表的是对象,具体代表哪个对象呢?哪个对象调用了this所在的方法,this就代表哪个对象。
      • 上述代码中的 p.setAge(30)语句中,setAge(int age)方法中的this代表的就是p对象。

五、构造方法

5.1、问题引入

​ 我们对封装已经有了基本的了解,接下来我们来看一个新的问题,依然以Person为例,由于Person中的属性都被private了,外界无法直接访问属性,必须对外提供相应的set和get方法。

​ 但是我们当创建人对象的时候,人对象一创建就要明确其姓名和年龄,那该怎么做呢?

5.2、构造方法概述

​ 在开发中经常需要在创建对象的同时明确对象的属性值,比如员工入职公司就要明确他的姓名、年龄等属性信息。

​ 那么,创建对象就要明确属性值,那怎么解决呢?也就是在创建对象的时候就要做的事情,当使用new关键字创建对象时,怎么给对象的属性初始化值呢?这就要学习Java另外一门小技术,构造方法。

​ 那什么是构造方法呢?从字面上理解即为构建创造对象时用的方法,即就是对象创建时要执行的方法。既然是对象创建时要执行的方法,那么只要在new对象时,知道其执行的构造方法是什么,就可以在执行这个方法的时候给对象进行属性赋值。

5.3、构造方法的使用

5.3.1、语法格式

修饰符 构造方法名(参数列表){ 
    // 方法体 
}

5.3.2、特点

  • 构造方法没有返回值类型。也不需要写返回值。因为它是为构建对象的,对象创建完,方法就执行结束。
  • 构造方法名称必须和类名保持一致。
  • 构造方法没有具体的返回值,甚至不需要void。

5.3.3、示例

class Person {
	// Person的成员属性age和name
	private int age;
    private String name;

	// Person的构造方法,拥有参数列表
	Person(int age, String name) {
		// 接受到创建对象时传递进来的值,将值赋给成员属性
		this.age = age;
		this.name = name;
	}
}

5.3.4、构造函数的作用

构造方法是一种特殊的方法,主要是创建对象,并且完成对象数据的初始化。

5.4、其他说明

  • 如果没有显示的定义类的构造方法的话,则系统默认提供一个空参的构造方法。
  • 一个类中定义的多个构造器,彼此构成重载,既可以定义参数,也可以不定义参数。
  • 一旦显示的定义了类的构造器之后,系统不再提供默认的空参构造器。
  • 一个类中,至少会有一个构造器。
  • 如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法。
  • 无论是否使用,都手工书写无参数构造方法,这个是推荐的做法。

5.5、案例

// 学生类
class Student { 
    private String name; 
    private int age; 
    
    public Student() {
        
    } 
    
    public Student(String name) { 
        this.name = name; 
    }
    
    public Student(int age) { 
        this.age = age; 
    }
    
    public Student(String name,int age) { 
        this.name = name; 
        this.age = age; 
    }
    
    public void show() { 
        System.out.println(name + "," + age); 
    } 
}

// 测试类
public class StudentDemo { 
    public static void main(String[] args) { 
        //创建对象 
        Student s1 = new Student(); 
        s1.show(); 
        
        //public Student(String name) 
        Student s2 = new Student("林青霞");
        s2.show(); 
        
        //public Student(int age) 
        Student s3 = new Student(30); 
        s3.show(); 
        
        //public Student(String name,int age) 
        Student s4 = new Student("林青霞",30); 
        s4.show(); 
    } 
}

5.6、构造方法调用和内存图解

5.6.1、代码

class Person {
	// Person的成员属性age和name
	private int age;
	private String name;

	// Person的构造方法,拥有参数列表
	Person(int a, String nm) {
		// 接受到创建对象时传递进来的值,将值赋给成员属性
		age = a;
		name = nm;
	}

	public void speak() {
		System.out.println("name=" + name + ",age=" + age);
	}
}

class PersonDemo {
	public static void main(String[] args) {
		// 创建Person对象,并明确对象的年龄和姓名
		Person p2 = new Person(23, "张三");
		p2.speak();
	}
}

​ 即在创建对象时,会调用与参数列表对应的构造方法。

5.6.2、图示

0075

5.6.3、解释

  • 首先会将main方法压入栈中,执行main方法中的 new Person(23,"张三");
  • 在堆内存中分配一片区域,用来存放创建的Person对象,这片内存区域会有属于自己的内存地址(0x88)。然后给成员变量进行默认初始化(name=null,age=0)。
  • 执行构造方法中的代码(age = a ; name = nm;),将变量a对应的23赋值给age,将变量nm对应的”张三赋值给name,这段代码执行结束后,成员变量age和name的值已经改变。执行结束之后构造方法弹栈,Person对象创建完成。将Person对象的内存地址0x88赋值给p2 。

5.7、默认构造方法和细节

5.7.1、问题引入

​ 在没有学习构造方法之前,我们也可以通过new关键字创建对象,并调用相应的方法,同时在描述事物时也没有写构造方法。这是为什么呢?

​ 在之前学习的过程中,描述事物时,并没有显示指定构造方法,当在编译Java文件时,编译器会自动给class文件中添加默认的构造方法。如果在描述类时,我们显示指定了构造方法,那么,当在编译Java源文件时,编译器就不会再给class文件中添加默认构造方法。

class  Person {
	//如果没有显示指定构造方法,编译会在编译时自动添加默认的构造方法
	//Person(){}  //空参数的默认构造方法
}

5.7.2、说明

​ 当在描述事物时,要不要在类中写构造方法呢?这时要根据描述事物的特点来确定,当描述的事物在创建其对象时就要明确属性的值,这时就需要在定义类的时候书写带参数的构造方法。若创建对象时不需要明确具体的数据,这时可以不用书写构造方法(不书写也有默认的构造方法)。

  • 构造方法的细节:
    • 一个类中可以有多个构造方法,多个构造方法是以重载的形式存在的
    • 构造方法是可以被private修饰的,作用:其他程序无法创建该类的对象

5.7.3、示例

class Person {
	private int age;
	private String name;

	// 私有无参数的构造方法,即外界不能通过new Person();语句创建本类对象
	private Person() {
	}

	// 多个构造方法是以重载的形式存在
	Person(int a) {
		age = a;
	}

	Person(String nm, int a) {
		name = nm;
		age = a;
	}
}

5.8、构造方法和一般方法区别

​ 到目前为止,学习两种方法,分别为构造方法和一般方法,那么他们之间有什么异同呢?

  • 构造方法在对象创建时就执行了,而且只执行一次。
  • 一般方法是在对象创建后,需要使用时才被对象调用,并可以被多次调用。

​ 问题:

​ 有了构造方法之后可以对对象的属性进行初始化,那么还需要对应的set和get方法吗?

​ 答案:需要相应的set和get方法,因为对象在创建之后需要修改和访问相应的属性值时,在这时只能通过set或者get方法来操作。

六、JavaBean

6.1、说明

​ JavaBean 是 Java语言编写类的一种标准规范。符合 JavaBean 的类,要求类必须是具体的和公共的,并且具有无参数的构造方法,提供用来操作成员变量的 set 和 get 方法。

6.2、总结

​ 所谓 javaBean,是指符合如下标准的 Java 类:

  • 类是公共的
  • 有一个无参的公共的构造方法
  • 有属性,是私有的,且有对应的 get、set 方法

6.3、案例

public class Student { 
    //成员变量 
    private String name; 
    private int age; 
    
    //构造方法 
    public Student() {
        
    } 
    
    public Student(String name,int age) { 
        this.name = name; 
        this.age = age; 
    }
    
    //成员方法 
    public void setName(String name) { 
        this.name = name; 
    }
    
    public String getName() { 
        return name; 
    }
    
    public void setAge(int age) { 
        this.age = age; 
    }
    
    publicint getAge() { 
        return age; 
    } 
}
public class TestStudent { 
    public static void main(String[] args) { 
        //无参构造使用 
        Student s= new Student(); 
        s.setName("柳岩"); 
        s.setAge(18); 
        System.out.println(s.getName()+"‐‐‐"+s.getAge());
        
        //带参构造使用 
        Student s2= new Student("赵丽颖",18);
        System.out.println(s2.getName()+"‐‐‐"+s2.getAge());
    } 
}

七、构造函数中的this使用

7.1、说明

​ 在之前学习方法时,我们知道方法之间是可以相互调用的,那么构造方法之间能不能相互调用呢?若可以,怎么调用呢?

​ 在之前学习方法之间调用时,可以通过方法名进行调用。可是针对构造方法,是无法通过构造方法名来相互调用。

7.2、this调用构造方法

7.2.1、解决

​ 构造方法之间的调用,可以通过this关键字来完成。

7.2.2、格式

this(参数列表);

7.2.3、构造方法的调用

class Person {
	// Person的成员属性
	private int age;
	private String name;

	// 无参数的构造方法
	Person() {
	}

	// 给姓名初始化的构造方法
	Person(String name) {
		this.name = name;
	}

	// 给姓名和年龄初始化的构造方法
	Person(String name, int age) {
		// 由于已经存在给姓名进行初始化的构造方法 this.name = name;因此只需要调用即可
		// 调用其他构造方法,需要通过this关键字来调用
		this(name);
		// 给年龄初始化
		this.age = age;
	}
}

7.2.4、this的原理图解

​ 了解了构造方法之间是可以相互调用,那为什么他们之间通过this就可以调用呢?

​ 通过上面的学习,简单知道使用this可以实现构造方法之间的调用,但是为什么就会知道this调用哪一个构造方法呢?接下来需要图解完成。

class Person {
	private int age;
	private String name;

	Person() {
	}
	Person(String nm) {
		name = nm;
	}
	Person(String nm, int a) {
		this(nm);
		age = a;
	}
}

class PersonDemo {
	public static void main(String[] args) {
		Person p = new Person("张三", 23);
	}
}

​ 解释说明:

1、先执行main方法,main方法压栈,执行其中的new Person(“张三”,23);

2、堆内存中开辟空间,并为其分配内存地址0x33,紧接着成员变量默认初始化(name=null age = 0);

3、拥有两个参数的构造方法(Person(String nm , int a))压栈,在这个构造方法中有一个隐式的this,因为构造方法是给对象初始化的,那么哪个对象调用到这个构造方法,this就指向堆中的哪个对象。

4、由于Person(String nm , int a)构造方法中使用了this(nm);构造方法Person(String nm)就会压栈,并将“张三”传递给nm。在Person(String nm , int a)构造方法中同样也有隐式的this,this的值同样也为0x33,这时会执行其中name = nm,即把“张三”赋值给成员的name。当赋值结束后Person(String nm , int a)构造方法弹栈。

5、程序继续执行构造方法(Person(String nm , int a)中的age = a;这时会将23赋值给成员属性age。赋值结束构造方法(Person(String nm , int a)弹栈。

6、当构造方法(Person(String nm , int a)弹栈结束后,Person对象在内存中创建完成,并将0x33赋值给main方法中的p引用变量。

​ this到底代表什么呢?this代表的是对象,具体代表哪个对象呢?哪个对象调用了this所在的方法,this就代表哪个对象。

​ 调用其他构造方法的语句必须定义在构造方法的第一行,原因是初始化动作要最先执行。

7.2.5、成员变量和局部变量同名问题

​ 通过上面学习,基本明确了对象初始化过程中的细节,也知道了构造方法之间的调用是通过this关键字完成的。但this也有另外一个用途,接下来我们就学习下。

​ 当在方法中出现了局部变量和成员变量同名的时候,那么在方法中怎么区别局部变量成员变量呢?

可以在成员变量名前面加上this.来区别成员变量和局部变量

class Person {
	private int age;
	private String name;

	// 给姓名和年龄初始化的构造方法
	Person(String name, int age) {
		// 当需要访问成员变量是,只需要在成员变量前面加上this.即可
		this.name = name;
		this.age = age;
	}

	public void speak() {
		System.out.println("name=" + this.name + ",age=" + this.age);
	}
}

class PersonDemo {
	public static void main(String[] args) {
		Person p = new Person("张三", 23);
		p.speak();
	}
}

7.2.6、案例

​ 需求:在Person类中定义功能,判断两个人是否是同龄人。

class Person {
	private int age;
	private String name;

	// 给姓名和年龄初始化的构造方法
	Person(String name, int age) {
		// 当需要访问成员变量是,只需要在成员变量前面加上this.即可
		this.name = name;
		this.age = age;
	}

	public void speak() {
		System.out.println("name=" + this.name + ",age=" + this.age);
	}

	// 判断是否为同龄人
	public boolean equalsAge(Person p) {
		// 使用当前调用该equalsAge方法对象的age和传递进来p的age进行比较
		// 由于无法确定具体是哪一个对象调用equalsAge方法,这里就可以使用this来代替
		return this.age == p.age;
	}
}

7.2.7、总结

  • ① 我们可以在类的构造方法中,显式的使用"this(形参列表)"的方式,调用本类中重载的其他的构造方法!
  •  ② 构造方法中不能通过"this(形参列表)"的方式调用自己。
    
  •  ③ 如果一个类中声明了n个构造方法,则最多有n -1个构造方法中使用了"this(形参列表)"。
    
  •  ④ "this(形参列表)"必须声明在类的构造器的首行!
    
  •  ⑤ 在类的一个构造器中,最多只能声明一个"this(形参列表)"。

标签:封装,name,构造方法,05,对象,age,Person,面向对象,public
From: https://www.cnblogs.com/rito-blog/p/16929203.html

相关文章