Java的三大特性之封装、继承和多态

面向对象编程有三大特性:封装、继承、多态。

【1】封装

封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据。对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法。

对于封装而言,一个对象它所封装的是自己的属性和方法,所以它是不需要依赖其他对象就可以完成自己的操作。

使用封装有几大好处:

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

【2】继承

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

继承是为了重用父类代码,两个类若存在IS-A的关系就可以使用继承。,同时继承也为实现多态做了铺垫。

如果有两个对象A和B,若可以描述为“A是B”,则可以表示A继承B,其中B是被继承者称之为父类或者超类,A是继承者称之为子类或者派生类。

使用继承时需要注意的几点:

  • 子类拥有父类非private的属性和方法。
  • 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
  • 子类可以用自己的方式实现父类的方法(方法的重写)。
  • 父类构造器只能够被调用,而不能被继承

那么子类能不能为父类private属性赋值并获取呢?答案是可以的!通过父类public setXxx方法赋值,通过public getXxx方法获取值。

那么什么是多态呢?多态的实现机制又是什么?

【3】多态

① 多态定义

所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定。即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

因为在程序运行时才确定具体的类,这样不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变。即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

② 编译时多态和运行时多态

对于面向对象而已,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载。它是根据参数列表的不同来区分不同的函数,通过编译之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们通常所说的多态性。

故而编译时多态又称静态多态,运行时多态又称动态多态。


② 多态的实现条件

Java实现多态有三个必要条件:继承、重写、向上转型。

  • 继承:在多态中必须存在有继承关系的子类和父类。
  • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
  • 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。

重载Overloading是一个类中多态性的一种表现,是编译时多态的一个例子,编译时多态在编译时就已经确定,运行时运行的时候调用的是确定的方法。

重写Overriding是父类与子类之间多态性的一种表现。


③ 多态的实现形式

多态通常有两种实现方法:

  • 子类继承父类(extends)
  • 类实现接口(implements)

要使用多态,在声明对象时就应该遵循一条法则:声明的总是父类类型或接口类型,创建的是实际(子类)类型

List list =new ArrayList();

在定义方法参数时也通常总是应该优先使用父类类型或接口类型:

public void doSomething(List list);

【4】多态的几个例子

① 父类引用不能调用子类自定义方法或重载方法

父类如下:

public class Wine {
    public void fun1(){
        System.out.println("Wine 的Fun.....");
        fun2();
    }

    public void fun2(){
        System.out.println("Wine 的Fun2...");
    }
    public void fun3(){
        System.out.println("Wine 的Fun3...");
    }
}

子类如下:

public class JNC extends Wine{
   
    public void fun1(){
        System.out.println("JNC 的 Fun1...");
        fun2();
    }

    public void fun1(String a){
        System.out.println("JNC 的 Fun1..."+a);
        fun2();
    }

    public void fun2(){
        System.out.println("JNC 的Fun2...");
    }

    public void fun4(){
        System.out.println("JNC 的Fun4...");
    }

    public static void main(String[] args) {
        Wine a = new JNC();
        a.fun1();
        a.fun2();
        a.fun3();
        a.fun4();
    }
}

由于父类未定义fun4()方法,故而直接报错:
在这里插入图片描述
父类引用能调用的只有父类定义的fun1(),fun2()和fun3()。不能调用子类重载方法fun1(String a)。

实际运行时,调用的是子类重写的fun1,fun2()和父类的fun3()–子类没有重写fun3():
在这里插入图片描述

可以从概念上这样理解,子类是继承父类的,那么父类所有非private方法子类都可以调用。调用哪个方法是由实际实例对象决定的(这里是子类JNC),那么这里就调用了子类重写的父类fun1,fun2()方法。但是子类自定义方法,父类引用是获取不到的。

也就是分两步:先达到方法—>再判断调用哪个实例的方法。JVM具体实现是方法表和指针。


② 子类是可以调用自身所有方法和从父类继承的方法(除了private方法)

在这里插入图片描述


【5】一个经典例子

代码示例如下:

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

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

}

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

public class C extends B{

}

public class D extends B{

}

public class Test {
    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--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D

分析第4 5

引用为A a2,尽快实际对象为new B。但是a2是达不到B的重载方法show(B obj)的。在调用show方法时,如何符合B的重写方法show(A obj)就会首先调用该方法。

故而 4 5 均为B and A


分析第6
B继承A,A中有方法show(D obj),故而直接使用,不用再向上转型使用show(A obj)。

故而,第6为 A and D

至于7 8 和1 2 3 一样,引用变量类型和实例对象一致,当然先从自身方法寻找,自身没有的从父类中寻找。至于第9 和第6一样,既然有show(D obj),那么就不用向上转型使用B的show(B obj)方法。

总结如下:

继承链中对象方法的调用存在一个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

即,首先是本类且参数不用转型的,其次是父类但是参数不用转型的,然后是本类但是参数需要转型,最后才是父类且参数需要转型的。

总之,记住一句话,首先是方法引用可达,然后参数不转型优先!

参考博文:

理解java的三大特性之多态

猜你喜欢

转载自blog.csdn.net/J080624/article/details/86497871