理解Java面对对象的三大特性--封装、继承、多态


Java 面对对象的三大特征是:封装、继承、多态
继承是为多态的实现做准备

封装

定义

隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别

封装的目的

增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,一特定的访问权限来使用类的成员

封装的好处

良好的封装能够减少耦合
类内部的结构可以自由修改
可以对成员进行更精确的控制
隐藏信息,实现细节

继承

概念

继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率

使用继承的场景

当两个类具有相同的特征(属性)和行为(方法)时,可以将相同的部分抽取出来放到一个类中作为父类,其它两个类继承这个父类

使用继承的优点

子类拥有父类非 private 的属性和方法,但是不能继承构造器,构造器只能被调用 super();(注意 super(); 必须是构造方法中的第一条语句)
eg.

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
//A.java
public class A {

A(){
System.out.println("star!!");
}
}

//B.java
public class B extends A{

B(){
super();//必须在第一行
System.out.println("aaa");
}
}

//Test.java
public class Test {
public static void main(String[] args) {
B b = new B();
}
}

//输出结果
star!!
aaa

子类可以拥有自己属性和方法,即子类可以对父类进行扩展
子类可以用自己的方式实现父类的方法
实现代码共享,避免重复外,还可以使得修改扩展继承而来的实现比较简单

多态

概念

定义

多态,是面向对象的程序设计语言最核心的特征。多态,意味着一个对象有着多重特征,可以在特定的情况下,表现不同的状态,从而对应着不同的属性和方法
即同一个事件发生在不同的对象上会产生不同的结果(同样是 Animal 的 voice(),但是 Cat/Dog 不同对象产生的 voice() 结果是不一样的)

实现技术

动态绑定,是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法

作用

消除类型之间的耦合关系
程序的可扩展性及可维护性增强

多态的例子

举一个通俗易懂的:比如有动物(Animal)之类别(class),由动物继承出类别鸟(Bird)、类别兔子(Ribbit)和猫(Cat),它们对同一源自类别动物(父类别)之一信息有不同的响应,如类别动物有“移动()”这个动作,而类别鸟会“飞()”,类别兔子会“跳()”,类别猫则会“跑()”,这就是多态,即相同的消息给予不同的对象会引发不同的动作

多态的实现

Java 中有两种形式可以实现多态:基于继承实现多态和基于接口实现多态

多态存在的三个必要条件

1 要有继承关系 (继承)
2 子类要重写父类的方法 (重写)
3 父类引用指向子类对象 (向上转型)
只要满足了上述三个条件,我们才能够在同一继承结构中使用统一的逻辑实现代码处理不同的对象,达到执行不同的行为,即多态

基于继承实现多态

基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为

eg.

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
//定义超类 Car
public class Car {
void start(){
System.out.println("汽车发动机启动");
}
}

//定义子类 XiaoHuangYa
public class XiaoHuangYa extends Car{
void start(){
System.out.println("小黄鸭的 Audi-RS5 发动机启动");
}
}

//定义子类 TuoHai
public class TuoHai extends Car{
void start(){
System.out.println("拓海的 丰田AE86 发动机启动");
}
}

//测试类 Test
public class Test {
public static void main(String[] args) {

Car c1 = new XiaoHuangYa();
Car c2 = new TuoHai();

c1.start(); //小黄鸭的 Audi-RS5 发动机启动
c2.start(); //拓海的 丰田AE86 发动机启动
}
}

//运行结果:
小黄鸭的 Audi-RS5 发动机启动
拓海的 丰田AE86 发动机启动

上述程序遵循的原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,在子类被覆盖的方法;当子类重写父类的方法被调用时,只有对象继承链中的最末端的方法才会被调用
若超类是一个抽象类的时候,抽象类的子类必须覆盖实现超类中的所有的抽象方法,否则子类必须被 abstract 修饰符修饰,但是被 abstract 修饰之后不能被实例化

基于接口实现多态

接口的灵活性就在于“规定一个类必须做什么,而不管你如何做”。我们可以定义一个接口类型的引用变量来引用实现接口的类的实例,当这个引用调用方法时,它会根据实际引用的类的实例来判断具体调用哪个方法,这和上述的超类对象引用访问子类对象的机制相似。
继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性

eg.

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
//定义一个接口
public interface CustomerService {

//定义一个退出系统的方法
void logout();
}

//定义一个类实现该接口
public class CustomerServiceImpl implements CustomerService{

//对接口中的抽象方法进行实现
public void logout() {

System.out.println("成功退出系统!");

}
}

//测试类
public class Test {

public static void main(String[] args) {

//要执行 CustomerServiceImpl 中的 logout 方法
//以下程序面向接口去调用
CustomerService cs = new CustomerServiceImpl(); //多态

//调用
cs.logout();
}
}

输出结果:
成功退出系统!

Java 在利用接口变量调用其实现类的对象的方法时,该方法必须已经在接口中被声明,而且在接口的实现类中该实现方法的类型和参数必须与接口中所定义的精确匹配

继承链中的优先级

继承链中对象方法的调用存在一个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)
即先查 this 对象的父类,没有就重头再查参数的父类

eg. 优先级的体现

  1. 新建一个 A.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class A {
    public String show(D obj){
    return ("A adn D");
    }

    public String show(A obj){
    return ("A and A");
    }
    }
  2. 新建一个 B.java 继承 A.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class B extends A{
    public String show(B obj){
    return ("B and B");
    }

    public String show(A obj){
    return("B and A");
    }
    }
  3. 新建一个 C.java 继承 B.java

    1
    2
    3
    public class C extends B{

    }
  4. 新建一个 D.java 继承 B.java

    1
    2
    3
    public class D extends B{

    }
  5. 新建一个 Tesy.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class Tesy {

    public static void main(String[] args) {

    A a1 = new A();
    A a2 = new B();
    B b = new B();
    C c = new C();
    D d = new D();

    System.out.println("1--> " + a1.show(b));
    System.out.println("2--> " + a1.show(c));
    System.out.println("3--> " + a1.show(d));
    System.out.println("4--> " + a2.show(b));
    System.out.println("5--> " + a2.show(c));
    System.out.println("6--> " + a2.show(d));
    System.out.println("7--> " + b.show(b));
    System.out.println("8--> " + b.show(c));
    System.out.println("9--> " + b.show(d));


    }
    }

输出结果:

1
2
3
4
5
6
7
8
9
1--> A and A
2--> A and A
3--> A adn D
4--> B and A
5--> B and A
6--> A adn D
7--> B and B
8--> B and B
9--> A adn D

该多态经典例子来自:Csdn-例子

看完例子,加入一些自己的理解,通过例子,对继承链中的优先级进行分析

学习完多态对于 1 - 7 都是比较好理解的,继承链中的优先级较好地体现在 8 9

先对 8 进行分析:b.show(c),b 是 B 类型的引用变量,所以 this 就代表了 B,b.show(c),它在 B 类中发现没有找到,于是到 B 的超类中找 (super),由于 B 的超类是 A,所以到 A 找,super.show(0),它在 A 类中发现没找到,现在到第 3 级 –> this.show((super)0),C 的超类有 B、A ,所以 (super)0 为 B、A ,this 依然是 B,现在在 B 类中找到了 show(B obj),因此最终会调用 B 类的 show(B obj)方法,结果是 “B and B”

再对 9 进行分析:b.show(d), b 是 B 类型的引用变量,所以 this 就代表了 B,b.show(d),它在 B 类中发现没有找到,于是到 B 的超类中找 (super),由于 B 的超类是 A,所以到 A 找,super.show(0),现在在 A 类中找到了 show(D obj),因此最终会调用 A 类的 show(D obj)方法,结果是 “A and D”

通过修改 B.java ,删去 show(B obj) 方法,进一步说明继承链中的优先级

修改 8.java

1
2
3
4
5
6
7
public class B extends A{


public String show(A obj){
return("B and A");
}
}

再运行 Tsey.java 会发现 8 的结果变成 “B and A”
对 8 变化的结果进行分析:当第三步在 B 类中没有找到 this.show((super)0),再到第 4 级 –> super.show((super)O),B 的超类为 A ,所以 super 为 A ,现在在 A 类中找到了 show(A obj),由于 b 是 B 类型的引用,在 B 类中重写了 show(A obj),因此最终会调用 B 类的 show(A obj) 方法,结果是 “B and A”

修改 8.java 再把重写的 show(A obj) 方法删去

1
2
3
public class B extends A{

}

再运行 Tsey.java 会发现 8 的结果变成 “A and A”
对 8 变化的结果进行分析:因为在第 4 级 A 类中找到 show(A obj) 方法,虽然 b 是 B 类型的引用,但是 B 类继承 A 类之后并没有重写 show(A obj) 方法,因此最终会调用 A 类的 show(A obj) 方法,结果是 “A and A”


 Comments