# 面向对象三大特征

# 一、封装

# 1.1、目前存在的问题

public class MyTest {
    public static void main() {
        Person person = new Person();
        person.age = 9999;
    }
}
1
2
3
4
5
6

在对象的外部,为对象的属性赋值,可能存在非法数据的录入。

使用者对类内部定义的属性直接操作会导致数据的错误、混乱或安全性问题。

# 1.2、什么是封装

尽可能隐藏对象的内部实现细节,控制对象的修改及访问的权限。

隐藏该隐藏的,暴露该暴露的。

# 1.3、如何实现封装

将属性使用访问修饰符private进行修饰,被private修饰后,属性仅在本类可见。

提供公共的方法(被public修饰)getXXXsetXXX实现对该属性的操作。

在公共的访问方法内部,添加逻辑判断,进而过滤掉非法数据,以保证数据安全(可选的)。

public class Person {
    private String name;// 姓名
    private int age;// 年龄
    private String gender;// 性别
    private String id;// 身份证号
    private String tel;// 电话

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    /*
    	在公共的访问方法内部,添加逻辑判断,
    	进而过滤掉非法数据,以保证数据安全。
    */
    public void setAge(int age) {
        if(age > 0 && age < 130) {
            this.age = age;
        } else {
            this.age = 20;
        }
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTel() {
        return tel;
    }

    public void setTel(String tel) {
        this.tel = tel;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

测试

public class MyTest1 {
    public static void main(String[] args) {
        Person person = new Person();
        person.setAge(1000);
        System.out.println(person.getAge());
    }
}
1
2
3
4
5
6
7

总结:

  • 不可以直接访问属性,仅可访问公共方法;
  • get/set方法是外界访问对象私有属性的唯一通道,方法内部可对数据进行检验和过滤。

# 1.4、通讯录管理系统

实现对通讯录信息的增删改查操作。

要求,姓名是唯一的,姓名不能重复。

主界面

image-20210813165932001

添加

image-20210813170258529

添加同名信息

image-20210813170405960

查询所有

image-20210813170452898

根据姓名查询

image-20210813170540412

修改通讯录

image-20210813170738100

修改通讯里,修改的名字不存在

image-20210813170832716

修改之后的名字和其他项名字重复,不允许修改

image-20210813171033085

删除

image-20211215223443760

image-20211215223545337

# 二、继承

# 2.1、什么是继承

# 2.1.1、生活中的继承

生活中的继承是施方一种赠与,受方的一种获得。

将一方所拥有的东西给与另一方。

施方和受方往往二者具有继承关系(直系亲属、亲属)。

生活中的继承

# 2.1.2、程序中的继承

程序中的继承,是类与类之间特征和行为的一种赠与或获得

两个类之间的继承关系,必须满足is a的关系。

  • Dog is an Animal;
  • Cat is an Animal。

根据上面的关系我们可以设计三个类,Dog、Cat、Animal

  • Dog继承Animal;
  • Cat继承Animal。

被继承的类称为父类,上述案例中的Animal。

继承的类称为子类,上述案例中的Dog和Cat。

# 2.1.3、父类的选择

现实生活中,很多类别之间都存在着继承关系,都满足is a的关系。

狗是一种动物、狗是一种生物。

多个类别都可作为的父类,需要从中选择出最适合的父类。

//生物
属性:
	品种、年龄、性别
方法:
	呼吸
	
//动物
属性:
	品种、年龄、性别
方法:
	呼吸、吃、睡
1
2
3
4
5
6
7
8
9
10
11

功能越精细,重合点越多,越接近直接父类。

功能越粗略,重合点越少,越接近Object类。

在实际开发中,可根据程序需要使用到的多个具体类,进行共性提取,进而定义父类。

//狗
属性:
	品种、年龄、性别、毛色
方法:
	吃、睡、跑
	
//鱼
属性:
	品种、年龄、性别
方法:
	吃、睡、游

//鸟
属性:
	品种、年龄、性别、毛色
方法:
	吃、睡、飞
	
//蛇
属性:
	品种、年龄、性别
方法:
	吃、睡、爬
	
//动物
属性:
	品种、年龄、性别
方法:
	吃、睡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

在一组相同或类似的类中,抽取出共性的特征和行为,定义在父类中,实现重用。

# 2.2、如何实现继承

//继承语法
class 子类名 extends 父类 { //定义子类时,指定其父类
    //属性
    //构造方法
    //方法
}
1
2
3
4
5
6

产生继承关系之后,子类可以使用父类中的属性和方法,也可以定义子类独有的属性和方法。

//父类
public class Animal {
    private String variety;//品种
    private int age;//年龄
    private String gender;//性别
    
    //get和set

    //吃
    public void eat() {
        System.out.println("eat...");
    }

    //睡
    public void sleep() {
        System.out.println("sleep...");
    }
}

//子类继承父类
public class Dog extends Animal {
    private String color;//毛色
    
    //get和set

    //跑
    public void run() {
        System.out.println("run...");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

好处:提高了代码的复用性和可扩展性。

  • extends这个单词本身就具有扩展的意思,我们也可以将继承理解为扩展,父类的功能不能达到要求,使用子类实现对父类的扩展。

# 2.2.1、关于单继承

Java为单继承,一个类只能有一个直接父类,但可以多级继承,属性和方法逐级叠加。

class 生物 {
    属性:品种、年龄、性别
    方法:呼吸
}

class 动物 extends 生物 {
    方法:吃、睡    
}

classextends 动物 {
    属性:毛色
    方法:跑    
}

=============================================================================================
//生物
public class LivingThings {
    private String variety;//品种
    private int age;//年龄
    private String gender;//性别
    
    //get和set
    
    //呼吸
    public void breath() {
        System.out.println("breath...");
    }
}

//动物
public class Animal extends LivingThings {
    //吃
    public void eat() {
        System.out.println("eat...");
    }

    //睡
    public void sleep() {
        System.out.println("sleep...");
    }
}fangfa

//狗
public class Dog extends Animal {
    private String color;//毛色
    
    //get和set

    //跑
    public void run() {
        System.out.println("run...");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 2.3、访问控制修饰符

本类 同包 非同包子类 其他
private
default
protected
public

✔:表示可以访问;

✘:表示不可以访问;

从上到下,从严格到宽松。

关于不可继承

  • 类中的构造方法,只负责创建本类对象,不可继承;
  • private修饰的属性和方法,仅本类可见;
  • 父子类不在同一个package中时,default修饰的属性和方法。

关于defualtprotected验证

LivingThings

package com.qfedu.test;

public class LivingThings {
    String variety;//品种,defualt级别
    private int age;//年龄
    private String gender;//性别

    //呼吸
    public void breath() {
        System.out.println(this.variety +  " breath...");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

Animal类,和LivingThings类在同一个包下,可以访问同包下的default级别的属性。

package com.qfedu.test;

public class Animal extends LivingThings {
    //吃
    public void eat() {
        System.out.println(this.variety + "eat...");
    }

    //睡
    public void sleep() {
        System.out.println("sleep...");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

Plant类,和LivingThings类不在同一个包下,不可以访问同包下的default级别的属性。

package com.qfedu.test1;

import com.qfedu.test.LivingThings;

//表示植物的类
public class Plant extends LivingThings {
    public void photosynthesis() {
        //下面的一行会报错
        System.out.println(this.variety + " photosynthesis...");
    }
}
1
2
3
4
5
6
7
8
9
10
11

修改LivingThings

package com.qfedu.test;

public class LivingThings {
    protected String variety;//品种,protected级别
    private int age;//年龄
    private String gender;//性别

    //呼吸
    public void breath() {
        System.out.println(this.variety +  " breath...");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

Plant类,和LivingThings类不在同一个包下,可以访问同包下的protected级别的属性。

package com.qfedu.test1;

import com.qfedu.test.LivingThings;

//表示植物的类
public class Plant extends LivingThings {
    public void photosynthesis() {
        //下面的一行不会报错
        System.out.println(this.variety + " photosynthesis...");
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 2.4、方法的重写

当父类提供的方法无法满足子类需求时,可在子类中定义和父类相同的方法进行重写(Override)

方法重写的原则:

  • 方法名称、参数列表、返回值类型必须与父类相同
  • 访问修饰符可与父类相同或是比父类更宽泛

方法重写的执行:

  • 子类重写父类方法后,调用时优先执行子类重写后的方法。
public class Dog extends Animal {
    private String color;//毛色

    //跑
    public void run() {
        System.out.println("run...");
    }

    //子类重写父类中的方法,方法名称、参数列表、返回值类型必须与父类相同。
    @Override
    public void eat() {
        System.out.println("狗吃骨头...");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyTest1 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat(); //调用子类中重写的方法。
    }
}
1
2
3
4
5
6

# 2.5、super关键字

# 2.5.1、代表父类对象

在子类中,可直接访问从父类继承到的属性和方法,但如果父子类的属性或方法存在重名(属性遮蔽、方法重写)时,需要加以区分,才可访问到父类中的属性或方法。

public class Dog extends Animal {
    private String color;//毛色

    //跑
    public void run() {
        System.out.println("run...");
    }

    //子类重写父类中的方法,方法名称、参数列表、返回值类型必须与父类相同。
    @Override
    public void eat() {
        super.eat();
        System.out.println("狗吃骨头...");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

super关键字可在子类中访问父类的方法。

使用super.的形式访问父类的方法,进而完成在子类中的复用,叠加额外的功能代码,组成新的功能。

父子类的同名属性不存在重写关系,两块空间同时存在(子类遮蔽父类属性),需使用不同前缀进行访问。

public class A {
    int value = 10;
}

public class B extends A {
    int value = 20; //子类属性遮蔽父类属性

    public void print() {
        int value = 30;

        System.out.println(value); //访问局部变量
        System.out.println(this.value); //访问本类的属性
        System.out.println(super.value); //访问父类的属性
    }
}

public class MyTest2 {
    public static void main(String[] args) {
        B b = new B();
        b.print();//分别输出30、20、10
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 2.5.2、继承中对象创建

在具有继承关系的对象创建中,构建子类对象会先构建父类对象

由父类的共性内容,叠加子类的独有内容,组成完整的子类对象。

public class X {
    public X() {
        System.out.println("X的构造方法...");
    }
}

public class Y {
    public Y() {
        System.out.println("Y的构造方法...");
    }
}

public class A {
	private X x = new X();
    
    public A() {
        System.out.println("A的构造方法...");
    }
}

public class B extends A {
    private Y y = new Y();
    
    public B() {
        System.out.println("B的构造方法...");
    }
}

public class C extends B {
    public C() {
        System.out.println("C的构造方法...");
    }    
}

public class MyTest3 {
    public static void main(String[] args) {
        C c = new C();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

构建过程:

  1. A类
    • 默认构建父类对象Object
    • 初始化A的属性;
    • 执行A的构造方法代码。
  2. B类
    • 构建父类对象A;
    • 初始化B的属性;
    • 执行B的构造方法代码。
  3. C类
    • 构建父类对象B;
    • 初始化C的属性;
    • 执行C的构造方法代码。

输出如下:

X的构造方法...
A的构造方法...
Y的构造方法...
B的构造方法...
C的构造方法...
1
2
3
4
5

# 2.5.3、代表父类构造方法

super():表示调用父类无参构造方法,如果没有显式书写,隐式存在于子类构造方法的首行。

public class A {
	private X x = new X();
    
    public A() {
        System.out.println("A的构造方法...");
    }
}

public class B extends A {
    private Y y = new Y();
    
    public B() {
        //super(); //默认存在
        System.out.println("B的构造方法...");
    }
}

public class C extends B {
    public C() {
        //super(); //默认存在
        System.out.println("C的构造方法...");
    }    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

super(参数...):表示调用父类有参构造方法。

public class A {
	private X x = new X();
    
    public A() {
        System.out.println("A的构造方法...");
    }
    
    public A(int value) {
        System.out.println("A的构造方法..." + value);
    }
}

public class B extends A {
    private Y y = new Y();
    
    public B() {
        //super(); //默认存在
        System.out.println("B的构造方法...");
    }
    
    public B(int value) {
        super(value);
        System.out.println("B的构造方法..." + value);
    }
}

public class MyTest4 {
    public static void main(String[] args) {
        B b = new B(100);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

输出:

X的构造方法...
A的构造方法...100
Y的构造方法...
B的构造方法...100
1
2
3
4

thissuper使用在构造方法中时,都要求在首行。

当子类构造中使用了this()this(参数),就不可再同时书写super()super(参数),会由this()指向的构造方法完成super()的调用。

public class A {
    private X x = new X();

    public A() {
        System.out.println("A的无参构造...");
    }

    public A(int value) {
        System.out.println("A的有参构造..." + value);
    }
}

public class B extends A {
    private Y y = new Y();

    public B() {
        super();
        System.out.println("B的无参方法...");
    }

    public B(int value) {
        this();
        System.out.println("B的有参方法..." + value);
    }
}

public class MyTest5 {
    public static void main(String[] args) {
        B b = new B(100);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

输出:

X的构造方法...
A的无参构造...
Y的构造方法...
B的无参方法...
B的有参方法...100
1
2
3
4
5

# 2.5.4、关于super总结

两种用法:

  • 在子类方法中使用super.的形式访问父类的属性和方法;
  • 在子类的构造方法的首行,使用super()super(参数),调用父类构造方法。

注意:

  • 如果子类构造方法中,没有显式定义super()super(参数),则默认提供super();
  • 同一个子类构造方法中,super()this()不可同时存在。

# 三、多态

# 3.1、什么是多态

多态指的是多种形态,父类引用指向子类对象,从而产生多种形态。

/*
 * "="左侧,父类引用
 * "="右侧,子类对象
*/
Animal a = new Dog();
1
2
3
4
5

注意:

  • 父类引用仅可调用父类所声明的属性和方法,不可调用子类独有的属性和方法;
  • 子类对象赋值给父类引用,如果运行被重写的方法,实际运行的是子类中的方法。

# 3.2、多态的应用

  • 使用父类作为方法形参实现多态,使方法参数的类型更为宽泛;
  • 使用父类作为方法返回值实现多态,使方法可以返回不同的子类对象。
public class Master {
    public void feed(Dog dog) {
        dog.eat();
    }
    
    public void feed(Cat cat) {
        cat.eat();
    }
    
    public void feed(Fish fish) {
        fish.eat();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

方法重载可以解决接收不同对象参数的问题,但其缺点也比较明显,随着子类的增加,Master类需要提供大量的方法重载,多次修改并重新编译。

使用多态修改Master

public class Master {
    public void feed(Animal a) {
        a.eat();
    }
}
1
2
3
4
5

# 3.3、向上转型和向下转型

# 3.3.1、向上转型

子类引用的对象转换为父类类型称为向上转型

程序运行的状态

  • 编译,看左边,左侧是什么类型,就能调用什么样的方法;
  • 运行,看右边,右侧是什么类型,运行时就调用什么样的方法。
public class MyTest6 {
    public static void main(String[] args) {
        //子类对象的多态性---子类对象赋值给父类引用---父类引用指向子类对象
        Animal d = new Dog();
        //编译时看左侧,运行时看右侧
        d.eat();
    }
}
1
2
3
4
5
6
7
8

# 3.3.2、向下转型

父类引用的对象转换为子类类型称为向下转型

注意:只有转换回子类真实类型,才可调用子类独有的属性和方法。

向下转型时,如果父类引用中的子类对象类型和目标类型不匹配,则会发生类型转换异常(ClassCastException)。

通常向下转型前,应判断引用中的对象真实类型,保证类型转换的正确性。

  • 语法:父类引用 instanceof 类型
public class Master {
    public void feed(Animal a) {
        if(a instanceof Dog) {
            Dog d = (Dog)a;
             d.eat();
        } else if(a instanceof Cat) {
            Cat c = (Cat)a;
            c.eat();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
上次更新: 2024/4/13