引文

搜了很多篇文章,都说protected的本质是

1.基类的protected成员是包内可见的,并且对子类可见;

2.若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法。

其中我特别疑惑的是第二种情况,什么叫做”访问从基类继承而来的protected方法“,什么叫做”访问基类实例的protected方法“,

特别是相对详细的这篇文章:Java 访问权限控制:你真的了解 protected 关键字吗?里提到了特别多的例子来解释,似乎关键在于是否重写,但是实际上第一个例子我就没理解明白,所以有了这篇文章。

首先抛出最终我得出的观点,再说明推导过程,如有错误希望能得到指正:

  • 实例对象.f()是属于“通过父类的实例对象访问protected成员”
  • super.f()是属于“通过从父类继承的protected成员进行访问”

影响结果的始终是“到底是通过父类的实例变量访问,还是直接访问从父类继承的protected成员”

重写带来了不同的结果是因为,当父类重写了祖先类的时候,子类调用的就是父类的f()方法,而不是祖先类的f()方法,这样带来的结果就是,包的可见性的变化,从而导致编译结果不一样

情况假设

Father和Son有两种可能性:

  1. 同包(由于我只是对第二种情况比较疑惑,所以同包可以不考虑了)
  2. 不同包

Clone方法的存在方式:

  1. 父子都不重写
  2. 父重写
  3. 子重写
  4. 父子都重写

Test:

  1. 单独,和Father同包;
  2. 单独,和Son同包;
  3. 单独,都不同包
  4. 为Father;
  5. 为Son;

总共有2×4×5种可能的结果,由于我们只考虑父子不同包,而且由于Test的五种情况可以一起测试,所以一共只有4种情况

  1. 父子都不重写

测试代码为:

public static void main(String[] args) {
        Father father = new Father();
        father.clone();//(1)

        Son son = new Son();
        son.clone();//(2)
    }

测试结论为:

在这里插入图片描述
其中,除了Father的(1)和Son的(2),其他都是编译不通过的

public  class Father(){
    father.clone();//(1)
}
public  class Son(){
    son.clone();//(2)
}
  1. 父重写

测试代码不变,测试结论为:

在这里插入图片描述

和第一种情况一样,只是FTest的father.clone也可以通过了,即:

public class FTest {
    father.clone();//(1)
}
  1. 子重写

测试代码不变,测试结论为:

在这里插入图片描述

和第一种情况一样,只是STest的son.clone也可以通过了,即:

public class STest {
    son.clone();
}
  1. 父子都重写

    结论是3和4的综合

初步猜测(其实是错误的)

我对1~4出现的情况进行以下猜测:

在"2.父重写"中,此时clone方法的来源是Father,由于FTest和Father是同一个包,因此可以访问father.clone();

public class FTest {
    father.clone();//(1)
}

而在"1.父子都不重写"中,clone方法的来源是Object,FTest既不是Object的直接子类,和Object也不同包,因此不能访问

但是这么猜测很明显有个问题,Object是任何对象的超类,FTest既然没有继承别的类,那么根据Java单继承的特性,FTest应该直接继承于Object,那这样在1和2中都FTest应该都可以直接访问father.clone才对

引入GrandPa类模拟Object类

我考虑到可能是Object这个类比较特殊,所以我又创建了一个祖先类Grand进行测试,用来模拟Object

package grandp;

public class GrandPa {

    protected void f() {
        System.out.println("I'm GrandPa's protected method");
    }

}

在这里插入图片描述

然后测试类改成:

public static void main(String[] args) {
    Father father = new Father();
    father.clone();
    father.f();

    Son son = new Son();
    son.clone();
    son.f();
}

结果我发现f()的测试结果和clone()是一致的,也就是特殊性不在于Object类,而是我理解出了问题

最终结论

不过这样对比就能理解到底是哪里出了问题了,实际上是,FTest确实是继承于GrandPa,但是依然不能通过father.f()访问f()方法,不过可以在方法重写中使用super.f()访问GrandPa的f()方法

public class FTest extends GrandPa{

    @Override
    protected void f() {
        // TODO Auto-generated method stub
        super.f();//编译通过,从父类继承而来的f
    }

    public static void main(String[] args) {
        father.f();//编译不通过,父类实例的f
    }
}

这样结论就出来了

father.f()是属于“通过父类的实例对象访问protected成员”
super.f()是属于“通过从父类继承的protected成员进行访问”

影响结果的始终是“到底是通过父类的实例变量访问protected成员,还是直接访问从父类继承的protected成员”

关于重写

重写带来了不同的结果是因为,当父类重写了祖先类的时候,子类调用的就是父类的f()方法,而不是祖先类的f()方法,这样带来的结果就是,包的可见性的变化
比如说,在“1.父子都不重写”中,FTest调用的father.clone()方法来自Object,因此可见包为Java.lang,由于FTest不在这个包中,所以编译不通过
而在“2.父重写”中,FTest调用的father.clone()方法来自Father,因此可见包为fatherp,由于Father和FTest在同一个包下,因此在2中father.clone()编译通过

使用GrandPa类模拟Object类验证这个结论:

新建一个GrandTest类,与GrandPa放在同一个包里

图片说明

  1. 当父子类都不重写的时候

    public class GrandTest {
        public static void main(String[] args) {
            Father father = new Father();
            father.f();//编译通过
    
            Son son = new Son();
            son.f();//编译通过
        }
    }
    
  2. 当父类重写的时候

    public class GrandTest {
        public static void main(String[] args) {
            Father father = new Father();
            father.f();//编译不通过
    
            Son son = new Son();
            son.f();//编译通过
        }
    }

    此时由于father.f()重写了,所以father.f()是来自Father类,GrandTest与Father类不同包,因此编译不通过;

    而son.f()由于没有重写,因此son.f()方法还是来自GrandPa类,GrandTest与GrandPa类同包,因此编译通过;