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

【1】封装

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

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

使用封装有几大好处:

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

对自己属性封装,就可以保证安全性。对自己方法进行封装提供默认实现,子类可以继承或重写该方法。父类封装的方法也可以是个模板方法,子类只需要实现该方法的某一步骤。

所以封装总结来讲就是安全性、隐蔽性、规范性(规范方法的行为,如模板方法)。

【2】继承

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

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

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

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

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

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

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

【3】多态

① 多态定义

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

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

封装可以隐藏实现细节,使得代码模块化。继承可以扩展已存在的代码模块(类)。它们的目的都是为了——代码重用。而多态除了代码的复用性外,还可以解决项目中紧偶合的问题,提高程序的可扩展性。​​派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性​​。

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

对于面向对象而已,多态分为​​编译时多态和运行时多态​​。

其中编译时多态是静态的,主要是指方法的重载。它是根据参数列表的不同来区分不同的函数,通过编译之后会变成两个不同的函数,在运行时谈不上多态。

而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们通常所说的多态性。

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


② 多态的实现条件

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

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

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

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

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

对于Java而言,它多态的实现机制遵循一个原则:当超类对象引用变量引用子类 对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。


③ 多态的实现形式

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

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

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

List list =new ArrayList();

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

public void doSomething(List list);

④ 多态的几个例子

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

父类如下:

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()方法,故而直接报错:
Java的三大特性之封装、继承和多态详解_子类
父类引用能调用的只有父类定义的fun1(),fun2()和fun3(),不能调用子类重载方法fun1(String a)。

实际运行时,调用的是子类重写的fun1,fun2()和父类的fun3()–子类没有重写fun3():
Java的三大特性之封装、继承和多态详解_父类_02

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

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


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

Java的三大特性之封装、继承和多态详解_子类_03


参考博文:

你真的清楚多态中究竟是哪个实例调用哪个方法吗?

理解java的三大特性之多态