重写 重载

测试案例

重写(Override)

存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。

为了满足里式替换原则,重写有以下三个限制:

  • 子类方法的访问权限必须大于等于父类方法;
  • 子类方法的返回类型必须是父类方法返回类型或为其子类型。
  • 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。

使用 @Override 注解,可以让编译器帮忙检查是否满足上面的三个限制条件。

下面的示例中,SubClass 为 SuperClass 的子类,SubClass 重写了 SuperClass 的 func() 方法。其中:

  • 子类方法访问权限为 public,大于父类的 protected。
  • 子类的返回类型为 ArrayList,是父类返回类型 List 的子类。
  • 子类抛出的异常类型为 Exception,是父类抛出异常 Throwable 的子类。
  • 子类重写方法使用 @Override 注解,从而让编译器自动检查是否满足限制条件。
class SuperClass {
    protected List<Integer> func() throws Throwable {
        return new ArrayList<>();
    }
}

class SubClass extends SuperClass {
    @Override
    public ArrayList<Integer> func() throws Exception {
        return new ArrayList<>();
    }
}Copy to clipboardErrorCopied

在调用一个方法时,先从本类中查找看是否有对应的方法,如果没有查找到再到父类中查看,看是否有继承来的方法。否则就要对参数进行转型,转成父类之后看是否有对应的方法。总的来说,方法调用的优先级为:

  • this.func(this)
  • super.func(this)
  • this.func(super)
  • super.func(super)
/*
    A
    |
    B
    |
    C
    |
    D
 */


class A {

    public void show(A obj) {
        System.out.println("A.show(A)");
    }

    public void show(C obj) {
        System.out.println("A.show(C)");
    }
}

class B extends A {

    @Override
    public void show(A obj) {
        System.out.println("B.show(A)");
    }
}

class C extends B {
}

class D extends C {
}Copy to clipboardErrorCopied
public static void main(String[] args) {

    A a = new A();
    B b = new B();
    C c = new C();
    D d = new D();

    // 在 A 中存在 show(A obj),直接调用
    a.show(a); // A.show(A)
    // 在 A 中不存在 show(B obj),将 B 转型成其父类 A
    a.show(b); // A.show(A)
    // 在 B 中存在从 A 继承来的 show(C obj),直接调用
    b.show(c); // A.show(C)
    // 在 B 中不存在 show(D obj),但是存在从 A 继承来的 show(C obj),将 D 转型成其父类 C
    b.show(d); // A.show(C)

    // 引用的还是 B 对象,所以 ba 和 b 的调用结果一样
    A ba = new B();
    ba.show(c); // A.show(C)
    ba.show(d); // A.show(C)
}

2. 重载(Overload)

存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。

应该注意的是,返回值不同,其它都相同不算是重载。

深度解析:

1. 解析方式

分为动态解析和静态解析。静态解析是有些符号引用,在类加载阶段或者第一次使用时,转化为直接引用;
另外一些符号引用则是在每次运行期间转化为直接引用,这种引用叫做动态解析,体现了java的多态性。

静态解析的四种情形:(invokestatic,invokespecial)
 1.静态方法
 2.父类方法
 3.构造方法
 4.私有方法(不可以被重写)
 他们是在类加载阶段,就将符号应用转换为直接引用的

2.重载

重载对与类的解析方式则是属于静态解析,是在类加载的过程中将符号引用,转变为具体的直接引用,指向具体
方法。重载是个类中含有多个同名方法,参数个数、类型、顺序不同的方法,invokevirtual在重载方法中的作用,
则是根据是根据参数的静态类型去调用重载方法。

查看代码的执行结果:

遮挡住执行结果. method()方法是重载方法,参数不同,可以传递3中类型的参数,在主函数中调用方法,参数分别是Grandpa g1 = new Father(); Grandpa g2 = new Son();执行结果,可以看到,定位的方法都是method(Grandpa g);

方法的静态分配
Grandpa g1 = new Father();

以上代码,g1的静态类型是Grandpa,而实际类型(真正指向的类型)是Father。
结论:
	执行结果都是执行的Grandpa,根据上面的解释,这属于方法的静态分配,重载方法的分配,
是根据变量的静态类型确定的,在编译期间那参数的静态类型去和方法中的参数类型进行匹配。
结论
一个类中含有多个同名方法,参数个数、类型、顺序不同的方法是方法的重载方法, 对于重载的方法调用,是一种
静态行为,是根据参数的静态类型去调用重载方法。`对于jvm,方法重载,是一种静态行为,是根据变量的类型去
确定方法,在加载期间,确定调用那个重载方法(是根据参数类型,调用那个重载方法,不是根据对象类型,去调
用对象的具体方法,这里是静态的。

可以参看代码

package byteCode;

/***
 * * <p>
 *  * 符号引用 直接引用
 *  * <p>
 *  * 有些符号引用在类加载阶段或是第一次使用时就会转换为直接引用,这种转换叫做静态解析,;
 *  * 另外一些符号引用则是在每次运行期间转换为直接引用,这种转换叫做动态解析,这体现了
 *  * java的多态性。
 *  *
 *  * 静态解析的四种情形:(invokestatic,invokespecial)
 *  * 1.静态方法
 *  * 2.父类方法
 *  * 3.构造方法
 *  * 4.私有方法(不可以被重写)
 *  * 他们是在类加载阶段,就将符号应用转换为直接引用的
 */

/**
 * 方法重载与invokeVirtual字节码指令的关系:
 * <p>
 * 方法的静态分配:
 * <p>
 * Grandpa g1 = new Father();
 * <p>
 * 以上代码,g1的静态类型是Grandpa,而实际类型(真正指向的类型)是Father。
 * 结论:
 * 变量的静态类型是不会发生变化的,而变量的实际类型会随着实际类型的而变化(多态的体现),
 * 实际类型是在方法运行期间方可确定的。
 * <p>
 * 一个类中含有多个同名方法,参数个数、类型、顺序不同的方法是方法的重载方法,
 * 对于重载的方法调用,是一种静态行为,是根据参数的静态类型去调用重载方法。
 * `对于jvm,方法重载,是一种静态行为,是根据变量的类型去确定方法,在加载期间,
 * 确定调用那个重载方法(是根据参数类型,调用那个重载方法,不是根据对象类型,去调用对象的具体方法,这里是静态的)`。
 */
public class Demo5 {

    public void method(Grandpa g) {
        System.out.println("Grandpa");
    }

    public void method(Father f) {
        System.out.println("Father");
    }

    public void Son(Son s) {
        System.out.println("Son");
    }

    /*
    method方法是重载的,创建两个对象,静态类型是Grandpa,实际类型是Father和Son
    调用method方法,执行结果?
     */

    /**
     * 执行结果都是执行的Grandpa,根据上面的解释,这属于方法的静态分配,重载方法的分配,是根据变量的静态类型确定的,
     * 在编译期间那参数的静态类型去和方法中的参数类型进行匹配。
     *
     * 》》》 字节码层次的分析是,这个invokevirtual指令,直接获取传入参数的静态类型,然后去确定调用的那个具体的重载方法,然后指令
     *         在根据确定的方法,进行从低向上的实际对象的多态流程查找。
     */
    public static void main(String[] args) {
        Grandpa g1 = new Father();
        Grandpa g2 = new Son();
        Demo5 d = new Demo5();
        d.method(g1);
        d.method(g2);
        /*
        Grandpa
        Grandpa
         */
    }
    /**字节码
     *  0 new #7 <byteCode/Father>
     *  3 dup
     *  4 invokespecial #8 <byteCode/Father.<init>>
     *  7 astore_1
     *
     *  8 new #9 <byteCode/Son>
     * 11 dup
     * 12 invokespecial #10 <byteCode/Son.<init>>
     * 15 astore_2
     *
     * 16 new #11 <byteCode/Demo5>
     * 19 dup
     * 20 invokespecial #12 <byteCode/Demo5.<init>>
     * 23 astore_3
     *
     * //invokevirtual: 调用虚方法,运行期间动态查找的过程,用静态类型去判定
     * 24 aload_3
     * 25 aload_1
     * 26 invokevirtual #13 <byteCode/Demo5.method>
     *
     * 29 aload_3
     * 30 aload_2
     * 31 invokevirtual #13 <byteCode/Demo5.method>
     * 34 return
     */
}

class Grandpa {

}

class Father extends Grandpa {

}

class Son extends Father {

}

3.重写

方法的动态分配:
方法的动态分派涉及到一个重要概念:方法的接收者。

invokevirtual字节码指令多态查找流程:
1.去操作数栈的栈顶,找到到栈顶的元素,然后获取栈顶元素的实际指向对象的实际类型
2.根据实际对象的类型,在这个类型中会去寻找invokevirtual参数中的方法(方法名称和描述符一样),如果权限通
  过,则直接返回这个对象的类型的方法的引用。
3.如果实际对象类型中找寻不到,则会根据当前实际对象继承体系去向上寻找,寻找父类中是否有方法,查找到,
则返回方法引用,否则抛出异常;
package byteCode;

/**
 * 方法重写与invokeVirtual字节码指令的关系:
 * <p>
 * 方法的动态分配:
 * <p>
 * 方法的动态分派涉及到一个重要概念:方法的接收者。
 * <p>
 * invokevirtual字节码指令多态查找流程:
 * 1.去操作数栈的栈顶,找到到栈顶的元素,然后获取栈顶元素的实际指向对象的实际类型
 * 2.根据实际对象的类型,在这个类型中会去寻找invokevirtual参数中的方法(方法名称和描述符一样),如果权限通过,则直接返回这个
 *  对象的类型的方法的引用。
 * 3.如果实际对象类型中找寻不到,则会根据当前实际对象继承体系去向上寻找,寻找父类中是否有方法,查找到,则返回方法
 *  引用,否则抛出异常;
 *
 *  可以看到下面字节码第17行和21行,他们有相同的符号引用,在运行期间,被解析到不同的直接引用上面了,
 *
 *
 *  重点:
 *  方法重载和方法重写的区别:
 *
 *   1.调用方:
 *      重载对方法调用是同一个对象纠结调用那一个方法,是同一个对象中,确定调用哪一个重载方法。
 *      重写是纠结哪一个对象去调用这个方法,是有一个方法,确定是那个对象来调用了。
 *   2.执行顺序:
 *      重载是在编译期间确定使用哪一个具体的方法(确定方法描述符),是先于重写的,他不需要运行就可以确定,是根据参数的静态类型。
 *      重写则是在运行期间确定这个方法具体由那个对象调用,是有对象本身还是有父类对象,会根据多态查找流程去确定调用对象。
 *   联系:
 *      方法的重载执行先于重写,先通过重载,去确定执行的方法的名称,描述符,访问权限等方法标识,然后通过这个确定的方法,去寻找
 *      具体的执行对象。
 *
 */
public class Demo6 {

    /**
     * 0 new #2 <byteCode/Apple>  //1.分配内存空间
     * 3 dup   //压栈
     * 4 invokespecial #3 <byteCode/Apple.<init>>  //2.调用构造方法
     * 7 astore_1  //3.存储对象的引用变量
     * <p>
     * 8 new #4 <byteCode/Bear>
     * 11 dup
     * 12 invokespecial #5 <byteCode/Bear.<init>>
     * 15 astore_2
     * <p>
     * 16 aload_1
     * 17 invokevirtual #6 <byteCode/Fruit.test>
     * 20 aload_2
     * 21 invokevirtual #6 <byteCode/Fruit.test>
     * 24 return
     */
    public static void main(String[] args) {
        Fruit f1 = new Apple();
        Fruit f2 = new Bear();
        f1.test();
        f2.test();
        /*
        Apple
        Bear
         */
    }
}

class Fruit {

    public void test() {
        System.out.println("Fruit");
    }
}

class Apple extends Fruit {

    public void test() {
        System.out.println("Apple");
    }
}

class Bear extends Fruit {

    public void test() {
        System.out.println("Bear");
    }
}
方法重载和方法重写的区别:
 *  1.调用方:
 *      重载对方法调用是同一个对象纠结调用那一个方法,是同一个对象中,确定调用哪一个重载方法。
 *      重写是纠结哪一个对象去调用这个方法,是有一个方法,确定是那个对象来调用了。
 *   2.执行顺序:
 *      重载是在编译期间确定使用哪一个具体的方法(确定方法描述符),是先于重写的,他不需要运行就可以确定,是根据参数的静态类型。
 *      重写则是在运行期间确定这个方法具体由那个对象调用,是有对象本身还是有父类对象,会根据多态查找流程去确定调用对象。
 *   联系:
 *      方法的重载执行先于重写,先通过重载,去确定执行的方法的名称,描述符,访问权限等方法标识,然后通过这个确定的方法,去寻找
 *      具体的执行对象。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值