javacore - chapter5- 02 -多态与ArrayStoreException、方法调用理解、抽象 demo4

多态:
有一个用来判断是否应该设计为继承关系的简单规则,就是“is-a”规则,它表明子类的每个对象也是超类的对象。例如,每个经理都是雇员,因此将manager类设计为employee类的子类是显而易见的,反之,并不是每个雇员都是经理。
“is-a"的另一种表述法是“替换原则”,它表明程序中任何出现超类对象的地方都可以替换成为employee对象,也可以引用employee类的任意一个子类的对象。
📢注意:
在java中,子类数组的引用可以转化成为超类数组的引用,这是完全合法的,不需要强制类型转换,毕竟一个manager的数组代表它一定是一个employee数组。但是,反过来就会出问题,因此为了确保不发生错误,所有数组都要牢记创建它们的元素类型,并监督负责仅将类型兼容的引用存储到数组中。例如,使用new manager[10]创建的数组是一个经理数组。
如果视图存储一个employee类型的引用就会引发ArrayStoreException

方法调用过程
假设调用x.f(args),隐式参数x声明为类c的一个对象,调用过程为:

  1. 编译器查看对象的声明类型和方法名,假设调用x.f(param),且隐式参数x声明为c类的对象,需要注意的是:有可能存在多个方法f,但是参数类型不一样的方法。例如,可能存在方法f(int)和方法f(string)。编译器将会意义列举类c中名为f的方法和其超类中访问属性为public且名为f的方法(超类的私有方法不可以访问)。至此,编译器已经获得了所有可能被调用的候选方法。
  2. 接下来,编译器将查看调用方法时候提供的参数类型,如果在所有名为f的方法中存在一个与提供的参数类型完全匹配,就选择这个方法,这个过程被称为“重载解析”。例如,对于调用x.f(“hello”)来说,编译器将会挑选f(string),而不是f(int),由于允许类型转化,int可以换成double,manager可以换成employee等等,所以这个过程可能会比较复杂。如果编译器没有找到与参数匹配的方法,或者发现经过类型转化后多个方法与之匹配,就会报告一个错误。至此,编译器已经获得了需要调用的方法的方法名和参数类型。
  3. 如果是private方法、static方法、final方法或者构造器,那么编译器可以准确知道将要调用的是那一个方法,我们将这种调用方法称为静态绑定,与此对应的是,调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定。在列举的示例中,编译器采用动态绑定的方式生成一条调用f(string)的指令。
  4. 当程序运行,并且采用动态绑定调用方法时候,虚拟机一定调用与x所引用对象的实际类型最为是和的那个类的方法。假设x的实际类型是D,它是C的子类。如果D类定义了f(string)方法,就直接用子类D中的定义方法,如果子类没有定义这个f(string)方法,程序会在超类中寻找f(string),以此类推
    每次调用方法都要进行搜索,时间开销相当大。因此,虚拟机预先为每个类创造了一个方法表(method table),其中列出了所有方法签名和实际调用的方法,这样一来,在调用方法的时候,虚拟机仅仅只需要查找这个表就可以了。在前面的例子中,虚拟机搜索D类型的方法,以便于能够匹配调用f(string)相匹配的方法。这个方法既有可能是D.f(string)也有可能是X.f(string),这里的X类是D类的超类,这里需要提醒一点,如果调用的是super.f(param),编译器将会对隐式参数超类的方法表进行搜索。
  5. 动态绑定有一个非常重要的特性:无需对现存的代码进行修改,就可以对程序进行扩展。假设增加一个新类Executive,并且变量e有可能引用这个类的对象,我们不需要对包含调用e.getsalary()的的代码进行重新编译。如果e恰好引用一个Executive类的对象,就会自动调用Executive.getSalary的方法。
    📢注意:
    在覆盖一个方法的时候**,子类方法不能低于超类方法的可见性。特别是,如果超类方法是public,子类方法一定要声明为public。**经常会发生这类错误:在声明子类方法的时候,遗漏了public修饰符。此时,编译器将会把它解释为视图提供更加严格的访问权限。

阻止继承:final类和方法

有时候,人们希望利用某个类定义子类,不允许扩展的类被称为final类。如果在定义类的时候就使用了final修饰符就表明这个类是一个final类。例如,假设希望阻止人们定义Executive类的子类,就可以在定义这个类的时候,使用final修饰符声明,声明格式如下:

public final class Excutive extends Manager{
				...
}

类中特定方法也可以被声明为final。如果这样做,子类就不能覆盖这个方法(final类中所有的方法都会自动的成为final方法)。域亦可以被声明为final。对于final域来说,构造对象之后就不允许改变它们的值了。不过,如果将一个类声明为final,只有其中的方法自动第成为final,而不包含域。
将方法或者类声明为final主要目的是:确保它们不会在子类中改变语义。例如Calendar类中的getTime和setTime方法都被声明哼了final。这表明Calendar类的设计者负责实现Date类与日历状态之间的转化,而不允许子类处理这些问题。同样地,String类也是final类,它意味着不允许任何人定义String的子类。换言之,如果有一个String的引用,它引用的一定是一个String对象,而不可能是其他类的对象。

强制类型转化
将一个类型强制转化成为另一个林伟的过程成为类型转化,有时候需要将某个类的对象引用转化成为另一个类的对象的引用。对象引用的转化语法与数组表达式的类型转化类似,仅需要用一对圆括号将目标类名括起来,并放置在需要转化的对象引用之前就可以了。

进行类型转化的唯一原因是:在暂时忽视对象的实际类型后,使用对象的全部功能,在java中,每个对象变量都属于一个类型,类型描述了这个变量所引用的以及能够引用的对象类型,将一个值存入变量时候,编译器将会检查是否允许该操作,将一个子类的引用赋给一个超类变量,编译器是允许的。但是,将一个超类的引用赋给一个子类变量,必须进行类型转化,这样才能够通过运行时的检查。如果继承链向下转型过程失败,程序就会曝出一个ClassCastException,这个时候就建议在进行类型转化之前先进行类型检查if A instance of B,先检查一个是否能够成功地转换,最后,如果这个类型不可能陈宫,编译器就不会进行转化。

综上小结:

  1. 只能在继承层次类进行类型转化
  2. 在将超类转化成为子类之前,应该使用instance of进行检查
  3. 只要没有捕获ClassCastException异常,程序就会停止运行。在一般情况下,应该减少类型转化和instanceof运算符的使用

抽象

为了提高程序的清晰度,包含一个或者多个的抽象方法的类本省必须被声明成抽象的。抽象方法充当着占位的角色,它们的具体实现在子类中。抽象扩展类可以有两种选择,一种是在抽象类中定义不封抽象方法或者不定义抽象类方法,这样就必须将子类也标记为抽象类;另一种是实现父类定义的所有抽象方法,这样,子类就不是抽象类了。
例如,通过扩展Person类,并且实现getDescription方法来定义Student类,由于Student类中不再含有抽象方法,所以就不必将这个类声明为抽象的。类即使不含有抽象方法,也可以被声明为抽象类。抽象类不能被实例化,也就是说,如果一个类声明为abstract,就不能创建这个类的对象,但是可以创建一个具体子类的对象。

看一下关于抽象的demo1:

创建抽象类 Person:

package corejava.chapter5.abstractPackage;

/**
 * @Auther WangYu
 * @Date 2022/3/6
 * 创建抽象类Person
 */
public abstract class Person {
    public abstract String getDescription();

    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

第二步:创建实现类Employee继承Person

package corejava.chapter5.abstractPackage;

import java.time.LocalDate;

/**
 * @Auther WangYu
 * @Date 2022/3/6
 */
public class Employee extends Person {
    private double salary;
    private LocalDate hireDay;

    public Employee(String name, double salary, int year, int month, int day) {
        super(name);
        this.salary = salary;
        hireDay = LocalDate.of(year, month, day);
    }

    public double getSalary() {
        return salary;
    }

    public LocalDate getHireDay() {
        return hireDay;
    }

    @Override
    public String getDescription() {
        return String.format("an employee with a salary of $%.2f", salary);
    }

    public void raiseSalary(double percent) {
        double raise = salary * percent / 100;
        salary = raise + salary;
    }
}

第三步:创建Student类继承Person

package corejava.chapter5.abstractPackage;

/**
 * @Auther WangYu
 * @Date 2022/3/6
 */
public class Student extends Person {

    private String major;

    public Student(String name, String major) {
        super(name);
        this.major = major;
    }

    @Override
    public String getDescription() {
        return "a student major in :" + major;
    }
}

第四步:调用方法

package corejava.chapter5.abstractPackage;

/**
 * @Auther WangYu
 * @Date 2022/3/6
 * 抽象类person的测试方法
 */
public class PersonTest {
    public static void main(String[] args) {
        Person[] people = new Person[2];

        people[0]  = new Employee("刘小呆", 20000, 2003, 3, 3 );
        people[1]  = new Student("王晓璐", "computer science" );

        for (Person person : people){
            System.out.println(person.getName() + person.getDescription());
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值