理解Java面向对象特性:封装、继承、多态

一、多态的理解

多态可以理解为不同的对象执行同一操作,表现不同的行为。针对于不同对象,是通过运行时判断的。运行时多态(动态绑定)有三个必要条件:
(1)存在类继承或者接口实现关系
(2)子类重写或实现父类方法
(3)用父类引用指向子类对象(向上转型)。

二、方法重写和重载

重载,是指在同一个类中方法名相同,而参数列表不同的情况。
重写,是指Java子类中方法覆盖父类中原方法实现,两个方法具有相同的方法签名。

关注点1:静态绑定和动态绑定

1、重载是一个编译期概念、重写是一个运行期间概念。
2、重载遵循所谓“编译期(静态)绑定”,即在编译时根据参数列表判断应该调用哪个方法。
3、重写遵循所谓“运行期(动态)绑定”,即在运行的时候,根据引用变量所指向的实际对象的类型来调用方法。
4、因为在编译期已经确定调用哪个方法,所以重载并不是多态。而重写是多态。重载只是一种语言特性,是一种语法规则,与多态无关,与面向对象也无关。
重载是编译时多态,属于静态多态,Java中多态是指动态多态。

关注点2:子类成员变量重复定义,称之为隐藏,而不是重写。

Java手册中提到:在一个类中,子类成员变量与分类的成员变量同名会隐藏父类成员变量,即使两个变量类型不同。在子类中父类的成员变量不能被简单地用引用来访问,而是必须通过父类引用来获取。一般来说,我们不推荐隐藏成员变量,因为这样会导致代码可读性差。

关注点3:访问权限不能变小

重写方法时,子类方法不能低于父类方法的可见性。父类方法为public时,子类方法必然为public。

关注点4:异常

子类重写父类方法时,子类方法中声明的已检查异常不能比父类方法中的异常更加通用。
规则解读:
(1)子类方法中可以抛出同一异常,或者子异常,或者不抛出异常。
(2)如果父类方法没有抛出已检查异常,则子类也不能抛出已检查异常。针对于这一点,在子类重写方法中,如果调用了其他可以抛出已检查异常的方法,只能在子类重写方法中try…catch处理掉,无法抛出。
(3)子类重写方法可以抛出任意运行时异常。

关注点5:向上转型

重写时,要求返回类型能够向上转型成为父类的返回类型;异常也要能向上转型成父类的异常。返回类型和异常是存在继承关系的!!

三、接口和抽象类的区别与选择

接口和抽象类的区别分为语法层面区别和设计层面区别。

(一)语法层面区别

区别1:方法
接口和抽象类,明显的区别就是接口只是定义了一些抽象方法而已,在不考虑Java8中default方法和静态方法情况下,接口中是没有代码实现。
Java9中新增了私有方法和私有静态方法,私有方法不对外暴露,也不能被实现该接口的类调用或重写。也正因为不对外暴露,私有方法必须定义方法实现才有意义。私有方法和私有静态方法是对Java8默认方法和静态方法的补充。

抽象类中的抽象方法可以有public、protected和default这些修饰符,而接口中默认修饰符是public。不可以使用其它修饰符。
区别2:域
接口中是不能定义成员变量的,接口的成员域仍然只能是常量(public static final)。而抽象类可以是各种类型。
区别3:构造
相较于接口,抽象类可以有构造函数,但是两者都不支持实例化。
区别4:单继承多实现
一个类只能继承一个抽象类,而一个类却可以实现多个接口。此处值得一提的是接口可以支持多继承。

(二)设计层面区别

抽象类是对整个类整体的抽象,包括属性、行为,而接口是对类局部行为进行抽象。
由于抽象类可以作为多个子类的父类,可以作为模板进行设计,参考模板方法设计模式。接口是一种行为规范,可以作为策略组合使用,参见策略模式(《Head First设计模式》第书中讲的 第一个设计模式)。

关注点1:继承实现关系

(1)具体类可以单继承多实现。
(2)接口可以继承,并且可以多继承。
(3)抽象类可以实现接口。
(4)抽象类可以继承具体类,毕竟Object就是具体类。

四、this和super关键字

在子类中,可以通过super.getter()方法调用父类方法,访问父类属性。
特别注意的是,super与this引用本质是不同的,不能将supper赋给另一个对象变量,它只是一个指示编译器调用父类方法的特殊关键字。

this与super

this的用途有,一是引用隐式参数,二是调用当前类中其他构造器,三是表示当前对象的引用。
super的用途有,一是调用父类方法,二是调用父类的构造器。
在调用构造器语句中,this和supper都只能作为构造器的第一条语句。

五、构造器与默认构造器

构造器,是一种特殊的方法,用于在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。 类中可以有多个构造器,通过重载来实现。

构造器跟一般的实例方法十分相似;但是与其它方法不同,构造器没有返回类型,不会被继承,且可以有范围修饰符。构造器的函数名称必须和它所属的类的名称相同。 它承担着初始化对象数据成员的任务。

类中没有显式定义构造器时,编译器会自动创建一个无参构造器。默认构造器一般会把成员变量的值初始化为默认值,数值类型设为0,布尔类型设为false,对象引用设为null。

关注点1:构造方法是否可以重写?

构造方法可以被重载,实现对象数据不同的初始化; 构造方法不能被重写,子类不能继承父类的构造方法,只能在子类的构造方法中调用父类的构造方法(自动调用父类默认构造方法,或调用父类指定的构造方法),保证父类对象也进行初始化(子类继承父类对象数据得到初始化)。

PS:构造器this逃逸:this在构造方法还没完成就被交出去被其他线程可见,此时的构造器需要同步。因此构造器不具备同步特性。

六、类变量、成员变量和局部变量

Java中共有三种变量,分别是类变量、成员变量和局部变量。他们分别存放在JVM的方法区、堆内存和栈内存中。
类变量和成员变量是共享变量,局部变量和形参是非共享变量。所以如果遇到多线程场景,对于变量a和b的操作是需要考虑线程安全的,而对于线程c和d的操作是不需要考虑线程安全的。

关注点1:public,protected,private以及不写之间的区别

public :表明该成员变量或者方法是对所有类或者对象都是可见的。
private:表明该成员变量或者方法是私有的,只有当前类对其具有访问权限。
protected:表明成员变量或者方法对类自身,与同在一个包中的其他类可见。
default:表明该成员变量或者方法只有自己和其位于同一个包的内可见。

关注点2:protected

protected强调了继承的理念,即使在不同包,子类依然能从通过自身实例调用继承自父类的protected方法,但是不能通过父类实例调用父类的protected非静态方法。

package com.lzp.base;
public class Test {
    protected void method() {
    }
}
package com.lzp.base.sub;
import com.lzp.base.Test;
public class SubTest extends Test {
    public static void main(String[] args) {
        new Test().method(); // 不能调用
        new SubTest().method();
    }
}

通过父类调用的话,没有用到protected继承权限,protected是同包可见,在不同包没有访问权限。

关注点3:类访问权限

对于类,只有public和包访问权限。一个内部类可以是private或protected的。

发布了17 篇原创文章 · 获赞 41 · 访问量 9995

猜你喜欢

转载自blog.csdn.net/awecoder/article/details/99246640