Java的protected修饰符——什么叫“访问从基类继承而来的protected方法“以及”访问基类实例的protected方法”

引文

搜了很多篇文章,都说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)
	}

测试结论为:

Java的protected修饰符——什么叫“访问从基类继承而来的protected方法“以及”访问基类实例的protected方法”_第1张图片
其中,除了Father的(1)和Son的(2),其他都是编译不通过的

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

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

Java的protected修饰符——什么叫“访问从基类继承而来的protected方法“以及”访问基类实例的protected方法”_第2张图片

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

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

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

Java的protected修饰符——什么叫“访问从基类继承而来的protected方法“以及”访问基类实例的protected方法”_第3张图片

和第一种情况一样,只是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");
	}
	
}

Java的protected修饰符——什么叫“访问从基类继承而来的protected方法“以及”访问基类实例的protected方法”_第4张图片

然后测试类改成:

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()编译通过

你可能感兴趣的:(Java)