第六章 面向对象基础---中(封装,继承,多态)

1.封装概述

1.1 封装的概述

1.1.1 为什么要封装?

        面向对象编程语言是对客观世界的模拟,客观世界里每一个事物的内部信息都是隐藏在对象内部的,外界无法直接操作和修改,只能通过指定的方式进行访问和修改。封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。

        随着我们系统越来越复杂,类会越来越多,那么类之间的访问边界必须把握好,面向对象的开发原则要遵循“高内聚、低耦合”,而“高内聚,低耦合”的体现之一:

  • 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;

  • 低耦合:仅对外暴露少量的方法用于使用

        隐藏对象内部的复杂性,只对外公开简单和可控的访问方式,从而提高系统的可扩展性、可维护性。通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。

1.1.2 如何实现封装呢?

        实现封装就是指控制类或成员的可见性范围?这就需要依赖访问控制修饰符,也称为权限修饰符来控制。

        权限修饰符:public,protected,缺省,private

修饰符本类本包其他包子类其他包非子类
private×××
缺省××
protected×
public

外部类:public和缺省

成员变量、成员方法、构造器、成员内部类:public,protected,缺省,private

1.2 成员变量/属性私有化问题

        成员变量(field)私有化之后,提供标准的get/set方法,我们把这种成员变量也称为属性(property)。

或者可以说只要能通过get/set操作的就是事物的属性,哪怕它没有对应的成员变量。

1.2.1 成员变量封装的目的

  • 隐藏类的实现细节

  • 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。

  • 便于修改,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问方式不变的话,外部根本感觉不到它的修改。例如:Java8->Java9,String从char[]转为byte[]内部实现,而对外的方法不变,我们使用者根本感觉不到它内部的修改。

1.2.2 实现步骤

  1. 使用 private 修饰成员变量

private 数据类型 变量名;

代码如下:

public class Person {
    private String name;
  	private int age;


    // 提供 set/get 方法
    public void setName(String n) {
		name = n;
    }

    public String getName() {
        return name;
	}

    public void setAge(int a) {
        age = a;
    }

    public int getAge() {
        return age;
    }
}

// 测试
public class TestPerson {
    public static void main(String[] args) {
        Person p = new Person();

        //实例变量私有化,跨类是无法直接使用的
        /* p.name = "张三";
        p.age = 23;
        p.marry = true;*/

        p.setName("张三");
        System.out.println("p.name = " + p.getName());

        p.setAge(23);
        System.out.println("p.age = " + p.getAge());
}

1.3 IDEA 自动生成 get/set 模板

1.如何解决局部变量与实例变量同名问题

        当局部变量与实例变量(非静态成员变量)同名时,在实例变量前面必须加“this“

2.IDEA 自动生成 get/set 方法模板

        1.大部分键盘模式按Alt + Insert键。

        2.部分键盘模式需要按Alt + Insert + Fn键。

2.继承

2.1 继承的概述

        继承有延续(下一代延续上一代的基因、财富)、扩展(下一代和上一代又有所不同)的意思。

Java中的继承

        多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。如图所示:

        其中,多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类超类(superclass)或者基类

        继承描述的是事物之间的所属关系,这种关系是:is-a 的关系。例如,图中猫属于动物,狗也属于动物。可见,父类更通用或更一般,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。

继承的好处

  • 提高代码的复用性

  • 提高代码的扩展性

  • 表示类与类之间的is-a关系

2.2 继承的语法格式

        通过extends关键字,可以声明一个子类继承另外一个父类,定义格式如下:

【修饰符】 class 父类 {
	...
}

【修饰符】 class 子类 extends 父类 {
	...
}

1.父类:

/*
 * 定义动物类Animal,做为父类
 */
public class Animal {
    // 定义name属性
    String name;
    // 定义age属性
    int age;

    // 定义动物的吃东西方法
    public void eat() {
        System.out.println(age + "岁的" + name + "在吃东西");
    }
}

2.子类:

/*
 * 定义猫类Cat 继承 动物类Animal
 */
public class Cat extends Animal {
    int count;//记录每只猫抓的老鼠数量

    // 定义一个猫抓老鼠的方法catchMouse
    public void catchMouse() {
        count++;
        System.out.println("抓老鼠,已经抓了"+ count + "只老鼠");
    }
}

3.测试类:

public class TestCat {
    public static void main(String[] args) {
        // 创建一个猫类对象
        Cat cat = new Cat();
        // 为该猫类对象的name属性进行赋值
        cat.name = "Tom";
        // 为该猫类对象的age属性进行赋值
        cat.age = 2;
        // 调用该猫继承来的eat()方法
        cat.eat();
        // 调用该猫的catchMouse()方法
        cat.catchMouse();
        cat.catchMouse();
        cat.catchMouse();
    }
}

2.3 继承的特点

1.子类会继承父类所有的实例变量和实例方法

        从类的定义来看,类是一类具有相同特性的事物的抽象描述。父类是所有子类共同特征的抽象描述。而实例变量和实例方法就是事物的特征,那么父类中声明的实例变量和实例方法代表子类事物也有这个特征。

  • 当子类对象被创建时,在堆中给对象申请内存时,就要看子类和父类都声明了什么实例变量,这些实例变量都要分配内存。

  • 当子类对象调用方法时,编译器会先在子类模板中看该类是否有这个方法,如果没找到,会看它的父类甚至父类的父类是否声明了这个方法,遵循从下往上找的顺序,找到了就停止,一直到根父类都没有找到,就会报编译错误。

        所以继承意味着子类的对象除了看子类的类模板还要看父类的类模板。

2.Java只支持单继承,不支持多重继承

public class A{}
class B extends A{}

//一个类只能有一个父类,不可以有多个直接父类。
class C extends B{} 	//true
class C extends A,B...	//error

3.Java支持多层继承(继承体系)

class A{}
class B extends A{}
class C extends B{}

顶层父类是Object类。所有的类默认继承Object,作为父类。

4.一个父类可以同时拥有多个子类

class A{}
class B extends A{}
class D extends A{}
class E extends A{}

2.4 IDEA中如何查看继承关系

1.子类和父类是一种相对的概念

例如:B类对于A来说是子类,但是对于C类来说是父类

2.查看继承关系快捷键

例如:选择A类名,按Ctrl + H就会显示A类的继承树。

:A类的父类和子类

:A类的父类

:A类的所有子类

例如:在类继承目录树中选中某个类,比如C类,按Ctrl+ Alt+U就会用图形化方式显示C类的继承祖宗

2.5 权限修饰符限制问题

权限修饰符:public,protected,缺省,private

修饰符本类本包其他包子类其他包非子类
private×××
缺省√(本包子类非子类都可见)××
protected√(本包子类非子类都可见)√(其他包仅限于子类中可见)×
public

外部类:public和缺省

成员变量、成员方法等:public,protected,缺省,private

1、外部类要跨包使用必须是public,否则仅限于本包使用

(1)外部类的权限修饰符如果缺省,本包使用没问题

(2)外部类的权限修饰符如果缺省,跨包使用有问题

2、成员的权限修饰符问题

(1)本包下使用:成员的权限修饰符可以是public、protected、缺省

(2)跨包下使用:要求严格

(3)跨包使用时,如果类的权限修饰符缺省,成员权限修饰符>类的权限修饰符也没有意义

3、父类成员变量私有化(private)

子类虽会继承父类私有(private)的成员变量,但子类不能对继承的私有成员变量直接进行访问,可通过继承的get/set方法进行访问。如图所示:

 n

父类代码:

public class Person {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getInfo(){
        return "姓名:" + name + ",年龄:" + age;
    }
}

子类代码:

public class Student extends Person {
    private int score;

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    public String getInfo(){
    // return "姓名:" + name + ",年龄:" + age;
    // 在子类中不能直接使用父类私有的name和age
    return "姓名:" + getName() + ",年龄:" + getAge();
    }
}

测试类代码:

public class TestStudent {
    public static void main(String[] args) {
        Student student = new Student();

        student.setName("张三");
        student.setAge(23);
        student.setScore(89);

        System.out.println(student.getInfo());
    }
}

IDEA在Debug模式下查看学生对象信息:

2.6 方法重写(Override)

        我们说父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于子类,该怎么办呢?我们可以进行方法重写 (Override)

1、方法重写

        比如新的手机增加来电显示头像的功能,代码如下:

public class Phone {
    public void sendMessage(){
        System.out.println("发短信");
    }
    public void call(){
        System.out.println("打电话");
    }
    public void showNum(){
        System.out.println("来电显示号码");
    }
}
//smartphone:智能手机
public class Smartphone extends Phone{
    //重写父类的来电显示功能的方法
    public void showNum(){
        //来电显示姓名和图片功能
        System.out.println("显示来电姓名");
        System.out.println("显示头像");
    }
}
public class TestOverride {
    public static void main(String[] args) {
        // 创建子类对象
        Smartphone sp = new Smartphone();

        // 调用父类继承而来的方法
        sp.call();

        // 调用子类重写的方法
        sp.showNum();
    }
}

2、在子类中如何调用父类被重写的方法

//smartphone:智能手机
public class Smartphone extends Phone{
    //重写父类的来电显示功能的方法
    public void showNum(){
        //来电显示姓名和图片功能
        System.out.println("显示来电姓名");
        System.out.println("显示头像");

        //保留父类来电显示号码的功能
        super.showNum();//此处必须加super.,否则就是无限递归,那么就会栈内存溢出
    }
}

3、IDEA重写方法快捷键:Ctrl + O

//smartphone:智能手机
public class Smartphone extends Phone{
    //重写父类的来电显示功能的方法
    public void showNum(){
        //来电显示姓名和图片功能
        System.out.println("显示来电姓名");
        System.out.println("显示头像");

        //保留父类来电显示号码的功能
        super.showNum();//此处必须加super.,否则就是无限递归,那么就会栈内存溢出
    }

    @Override
    public void call() {
        super.call();
        System.out.println("视频通话");
    }
}

@Override:写在方法上面,用来检测是不是满足重写方法的要求。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留,这样编译器可以帮助我们检查格式,另外也可以让阅读源代码的程序员清晰的知道这是一个重写的方法。

4、重写方法的要求

1.必须保证父子类之间重写方法的名称相同。

2.必须保证父子类之间重写方法的参数列表也完全相同。

2.子类方法的返回值类型必须【小于等于】父类方法的返回值类型(小于其实就是是它的子类,例如:Student < Person)。

注意:如果返回值类型是基本数据类型和void,那么必须是相同

3.子类方法的权限必须【大于等于】父类方法的权限修饰符。

注意:public > protected > 缺省 > private

父类私有方法不能重写

跨包的父类缺省的方法也不能重写

5、方法的重载和方法的重写

方法的重载:方法名相同,形参列表不同。不看返回值类型。

方法的重写:见上面。

(1)同一个类中

public class TestOverload {
    public int max(int a, int b){
        return a > b ? a : b;
    }
    public double max(double a, double b){
        return a > b ? a : b;
    }
    public int max(int a, int b,int c){
        return max(max(a,b),c);
    }
}

(2)父子类中

public class TestOverloadOverride {
    public static void main(String[] args) {
        Son s = new Son();
        s.method(1);//只有一个形式的method方法

        Daughter d = new Daughter();
        d.method(1);
        d.method(1,2);//有两个形式的method方法
    }
}

class Father{
    public void method(int i){
        System.out.println("Father.method");
    }
}
class Son extends Father{
    public void method(int i){//重写
        System.out.println("Son.method");
    }
}
class Daughter extends Father{
    public void method(int i,int j){//重载
        System.out.println("Daughter.method");
    }
}

3.多态

3.1 多态解决什么样的问题

有的时候,我们在设计一个数组、或一个成员变量、或一个方法的形参、返回值类型时,无法确定它具体的类型,只能确定它是某个系列的类型。

案例:

(1)声明一个Dog类,包含public void eat()方法,输出“狗狗啃骨头”

(2)声明一个Cat类,包含public void eat()方法,输出“猫咪吃鱼仔”

(3)声明一个Person类,

  • 包含宠物属性

  • 包含领养宠物方法 public void adopt(宠物类型 pet)

  • 包含喂宠物吃东西的方法 public void feed(),实现为调用宠物对象.eat()方法

public class Dog {
    public void eat(){
        System.out.println("狗狗啃骨头");
    }
}
public class Cat {
    public void eat(){
        System.out.println("猫咪吃鱼仔");
    }
}
public class Person {
    private Dog dog;

    //adopt:领养
    public void adopt(Dog dog){
        this.dog = dog;
    }

    //feed:喂食
    public void feed(){
        if(dog != null){
            dog.eat();
        }
    }
    /*
    问题:
    1、从养狗切换到养猫怎么办?   
    	修改代码把Dog修改为养猫?
    2、或者有的人养狗,有的人养猫怎么办?  
    3、要是同时养多个狗,或猫怎么办?
    4、要是还有更多其他宠物类型怎么办?
    如果Java不支持多态,那么上面的问题将会非常麻烦,代码维护起来很难,扩展性很差。
    */
}

3.2 多态的形式和体现

1、多态引用

Java规定父类类型的变量可以接收子类类型的对象,这一点从逻辑上也是说得通的。

父类类型 变量名 = 子类对象;

父类类型:指子类继承的父类类型,或者实现的父接口类型。

所以说继承是多态的前提

2、多态引用的表现

表现:编译时类型与运行时类型不一致,编译时看“父类”,运行时看“子类”。

3、多态引用的好处和弊端

弊端:编译时,只能调用父类声明的方法,不能调用子类扩展的方法;

好处:运行时,看“子类”,如果子类重写了方法,一定是执行子类重写的方法体;变量引用的子类对象不同,执行的方法就不同,实现动态绑定。代码编写更灵活、功能更强大,可维护性和扩展性更好了。

4、多态演示

让Dog和Cat都继承Pet宠物类。

public class Pet {
    private String nickname;

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public void eat(){
        System.out.println(nickname + "吃东西");
    }
}
public class Cat extends Pet {
    //子类重写父类的方法
    @Override
    public void eat() {
        System.out.println("猫咪" + getNickname() + "吃鱼仔");
    }

    //子类扩展的方法
    public void catchMouse() {
        System.out.println("抓老鼠");
    }
}
public class Dog extends Pet {
    //子类重写父类的方法
    @Override
    public void eat() {
        System.out.println("狗狗" + getNickname() + "啃骨头");
    }

    //子类扩展的方法
    public void watchHouse() {
        System.out.println("看家");
    }
}
public class TestPet {
    public static void main(String[] args) {
        //多态引用
        Pet pet = new Dog();
        pet.setNickname("小白");

        //多态的表现形式
        /*
        编译时看父类:只能调用父类声明的方法,不能调用子类扩展的方法;
        运行时,看“子类”,如果子类重写了方法,一定是执行子类重写的方法体;
         */
        pet.eat();//运行时执行子类Dog重写的方法
//        pet.watchHouse();//不能调用Dog子类扩展的方法

        pet = new Cat();
        pet.setNickname("雪球");
        pet.eat();//运行时执行子类Cat重写的方法
    }
}

3.3 应用多态解决问题

1、声明变量是父类类型,变量赋值子类对象

  • 方法的形参是父类类型,调用方法的实参是子类对象

  • 实例变量声明父类类型,实际存储的是子类对象

public class OnePersonOnePet {
    private Pet pet;
    public void adopt(Pet pet) {//形参是父类类型,实参是子类对象
        this.pet = pet;
    }
    public void feed(){
        pet.eat();//pet实际引用的对象类型不同,执行的eat方法也不同
    }
}
public class TestOnePersonOnePet {
    public static void main(String[] args) {
        OnePersonOnePet person = new OnePersonOnePet();

        Dog dog = new Dog();
        dog.setNickname("小白");
        person.adopt(dog);//实参是dog子类对象,形参是父类Pet类型
        person.feed();

        Cat cat = new Cat();
        cat.setNickname("雪球");
        person.adopt(cat);//实参是cat子类对象,形参是父类Pet类型
        person.feed();
    }
}

2、数组元素是父类类型,元素对象是子类对象

public class OnePersonManyPets {
    private Pet[] pets;//数组元素类型是父类类型,元素存储的是子类对象

    public void adopt(Pet[] pets) {
        this.pets = pets;
    }

    public void feed() {
        for (int i = 0; i < pets.length; i++) {
            pets[i].eat();//pets[i]实际引用的对象类型不同,执行的eat方法也不同
        }
    }
}
public class TestPets {
    public static void main(String[] args) {
        Pet[] pets = new Pet[2];
        pets[0] = new Dog();//多态引用
        pets[0].setNickname("小白");
        pets[1] = new Cat();//多态引用
        pets[1].setNickname("雪球");

        OnePersonManyPets person = new OnePersonManyPets();
        person.adopt(pets);
        person.feed();
    }
}

3、方法返回值类型声明为父类类型,实际返回的是子类对象

public class PetShop {
    //返回值类型是父类类型,实际返回的是子类对象
    public Pet sale(String type){
        switch (type){
            case "Dog":
                return new Dog();
            case "Cat":
                return new Cat();
        }
        return null;
    }
}
public class TestPetShop {
    public static void main(String[] args) {
        PetShop shop = new PetShop();

        Pet dog = shop.sale("Dog");
        dog.setNickname("小白");
        dog.eat();

        Pet cat = shop.sale("Cat");
        cat.setNickname("雪球");
        cat.eat();
    }
}

3.4 向上转型与向下转型

首先,一个对象在new的时候创建是哪个类型的对象,它从头至尾都不会变。即这个对象的运行时类型,本质的类型用于不会变。但是,把这个对象赋值给不同类型的变量时,这些变量的编译时类型却不同。

这个和基本数据类型的转换是不同的。基本数据类型是把数据值copy了一份,相当于有两种数据类型的值。而对象的赋值不会产生两个对象。

1、为什么要类型转换呢?

因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间,就会出现类型转换的现象。

但是,使用父类变量接收了子类对象之后,我们就不能调用子类拥有,而父类没有的方法了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做类型转换,使得编译通过

  • 向上转型:当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型

    • 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了

    • 但是,运行时,仍然是对象本身的类型,所以执行的方法是子类重写的方法体。

    • 此时,一定是安全的,而且也是自动完成的

  • 向下转型:当左边的变量的类型(子类)<右边对象/变量的编译时类型(父类),我们就称为向下转型

    • 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了

    • 但是,运行时,仍然是对象本身的类型

    • 不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过isInstanceof关键字进行判断

2、如何向上转型与向下转型

向上转型:自动完成

向下转型:(子类类型)父类变量

public class ClassCastTest {
    public static void main(String[] args) {
        //没有类型转换
        Dog dog = new Dog();//dog的编译时类型和运行时类型都是Dog

        //向上转型
        Pet pet = new Dog();//pet的编译时类型是Pet,运行时类型是Dog
        pet.setNickname("小白");
        pet.eat();//可以调用父类Pet有声明的方法eat,但执行的是子类重写的eat方法体
//        pet.watchHouse();//不能调用父类没有的方法watchHouse

        Dog d = (Dog) pet;
        System.out.println("d.nickname = " + d.getNickname());
        d.eat();//可以调用eat方法
        d.watchHouse();//可以调用子类扩展的方法watchHouse

        Cat c = (Cat) pet;//编译通过,因为从语法检查来说,pet的编译时类型是Pet,Cat是Pet的子类,所以向下转型语法正确
        //这句代码运行报错ClassCastException,因为pet变量的运行时类型是Dog,Dog和Cat之间是没有继承关系的
    }
}

3、instanceof关键字

        为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。

变量/匿名对象 instanceof 数据类型 

那么,哪些instanceof判断会返回true呢?

  • 变量/匿名对象的编译时类型 与 instanceof后面数据类型是直系亲属关系才可以比较

  • 变量/匿名对象的运行时类型<= instanceof后面数据类型,才为true

示例代码:

public class TestInstanceof {
    public static void main(String[] args) {
        Pet[] pets = new Pet[2];
        pets[0] = new Dog();//多态引用
        pets[0].setNickname("小白");
        pets[1] = new Cat();//多态引用
        pets[1].setNickname("雪球");

        for (int i = 0; i < pets.length; i++) {
            pets[i].eat();

            if(pets[i] instanceof Dog){
                Dog dog = (Dog) pets[i];
                dog.watchHouse();
            }else if(pets[i] instanceof Cat){
                Cat cat = (Cat) pets[i];
                cat.catchMouse();
            }
        }
    }
}

3.5 虚方法

        在Java中虚方法是指在编译阶段和类加载阶段都不能确定方法的调用入口地址,在运行阶段才能确定的方法,即可能被重写的方法。

当我们通过“对象xx.方法”的形式调用一个虚方法时,要如何确定它具体执行哪个方法呢?

(1)静态分派:先看这个对象xx的编译时类型,在这个对象的编译时类型中找到能匹配的方法

匹配的原则:看实参的编译时类型与方法形参的类型的匹配程度
A:找最匹配    实参的编译时类型 = 方法形参的类型
B:找兼容      实参的编译时类型 < 方法形参的类型

(2)动态绑定:再看这个对象xx的运行时类型,如果这个对象xx的运行时类重写了刚刚找到的那个匹配的方法,那么执行重写的,否则仍然执行刚才编译时类型中的那个匹配的方法

class MyClass{
	public void method(Father f) {
		System.out.println("father");
	}
	public void method(Son s) {
		System.out.println("son");
	}
}
class MySub extends MyClass{
	public void method(Father d) {
		System.out.println("sub--father");
	}
    public void method(Daughter d) {
		System.out.println("daughter");
	}
}
class Father{
	
}
class Son extends Father{
	
}
class Daughter extends Father{
	
}
public class TestVirtualMethod {
    public static void main(String[] args) {
        MyClass my = new MySub();
        Father f = new Father();
        Son s = new Son();
        Daughter d = new Daughter();
        my.method(f);//sub--
            /*
            (1)静态分派:看my的编译时类型MyClass,在MyClass中找最匹配的
                匹配的原则:看实参的编译时类型与方法形参的类型的匹配程度
                 A:找最匹配    实参的编译时类型 = 方法形参的类型
                 B:找兼容      实参的编译时类型 < 方法形参的类型
                 实参f的编译时类型是Father,形参(Father f) 、(Son s)
                 最匹配的是public void method(Father f)
            (2)动态绑定:看my的运行时类型MySub,看在MySub中是否有对    public void method(Father f)进行重写
                发现有重写,如果有重写,就执行重写的
                    public void method(Father d) {
                        System.out.println("sub--");
                    }
             */
        my.method(s);//son
            /*
            (1)静态分派:看my的编译时类型MyClass,在MyClass中找最匹配的
                匹配的原则:看实参的编译时类型与方法形参的类型的匹配程度
                 A:找最匹配    实参的编译时类型 = 方法形参的类型
                 B:找兼容      实参的编译时类型 < 方法形参的类型
                 实参s的编译时类型是Son,形参(Father f) 、(Son s)
                 最匹配的是public void method(Son s)
            (2)动态绑定:看my的运行时类型MySub,看在MySub中是否有对 public void method(Son s)进行重写
                发现没有重写,如果没有重写,就执行刚刚父类中找到的方法
             */
        my.method(d);//sub--
             /*
            (1)静态分派:看my的编译时类型MyClass,在MyClass中找最匹配的
                匹配的原则:看实参的编译时类型与方法形参的类型的匹配程度
                 A:找最匹配    实参的编译时类型 = 方法形参的类型
                 B:找兼容      实参的编译时类型 < 方法形参的类型
                 实参d的编译时类型是Daughter,形参(Father f) 、(Son s)
                 最匹配的是public void method(Father f)
            (2)动态绑定:看my的运行时类型MySub,看在MySub中是否有对 public void method(Father f)进行重写
                发现有重写,如果有重写,就执行重写的
                    public void method(Father d) {
                        System.out.println("sub--");
                    }
             */
    }
}

3.6 成员变量没有多态一说

package com.haogu.polymorphism.grammar;

public class TestVariable {
    public static void main(String[] args) {
        Base b = new Sub();
        System.out.println(b.a);
        System.out.println(((Sub)b).a);

        Sub s = new Sub();
        System.out.println(s.a);
        System.out.println(((Base)s).a);
    }
}
class Base{
    int a = 1;
}
class Sub extends Base{
    int a = 2;
}

后续内容,正在更新...

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值