北川广海の梦

北川广海の梦

Java子类真的不能继承父类的Private成员吗?

731
2020-01-13

从我们学习面向对象程序编程开始,就被告知,子类继承父类,只能继承父类的public,proteced 成员,对于private成员,是不能被继承的。 那么事实真的是如此吗?

我们来验证一下,这里我们使用了Ehcache的SizeOf工具,可以得知对象的大小

import net.sf.ehcache.pool.sizeof.AgentSizeOf;
import net.sf.ehcache.pool.sizeof.SizeOf;

public class ClassA{

}

public class ClassB extends ClassA{
	public static void main(String[] args) {
        SizeOf size = new AgentSizeOf();
         ClassA a = new ClassA();
         ClassB b=new ClassB();
        System.out.println("ClassA size = " + size.sizeOf(a));
	System.out.println("ClassB size = " + size.SizeOf(b));
    }
}

这段程序运行的结果,classA是16字节,ClassB是16字节。 为什么ClassA和ClassB明明是空对象,却占用了16字节空间呢?

答案很简单,在64位平台上,如果开了指针压缩,则会占用12字节,否则是16字节。 而这个大小其实是对象头的大小。 对于类实例对象来说,

对象头包含两部分,分别有一个_mark和_metadata。

为什么会有对象头呢,对象头其实是继承于JVM内部的OopDesc对象, JVM内部采用了oop-klass这种二分模型,包含继承于oopDesc的对象头,用于描述对象信息,而klassKlass区域,则储存着实例数据。

Oop对象头 (_mark,_metadata)
实例区数据1
实例区数据2
实例区数据3
..........

_mark存储了JVM内部对象的哈希值,锁状态等信息。

_metadata则是一个指针,指向klassOopDesc类型实例。

所以我们签名写的ClassA和ClassB 明明是空对象,却占用16字节空间。 在开启了指针压缩之后,其实占用大小为12字节,但是JVM进行了内存对齐,会填补4个空白字节。所以大小也就成了16字节。

上面的例子只是描述了Java对象在JVM内部的存储模式。 那么Private成员到底会不会被继承呢? 下面再写一个例子

public class ClassA{
   private double value1=10;
   private double value2=12;
}

public class ClassB extends ClassA{
	public static void main(String[] args) {
        SizeOf size = new AgentSizeOf();
         ClassA a = new ClassA();
         ClassB b=new ClassB();
        System.out.println("ClassA size = " + size.sizeOf(a));
	System.out.println("ClassB size = " + size.SizeOf(b));
    }
}

这次打印的结果变成了,两个size都是32,说明无论是ClassA还是ClassB都是有这两个double类型的字段的。 这说明子类是可以继承父类的所有成员的,包括私有方法,构造方法,静态方法,或者是被final修饰的方法等等。 但是由于被private修饰,导致不能在子类访问这个成员。 private的限定其实也非常明确,就是这个类本身的范围,子类并不属于这个父类的范围,所以不能访问。 而java也完全可以通过反射来访问父类的私有成员 下面是例子。

public class ClassA{
   private double value1=10;
   private double value2=12;
}

public class ClassB extends ClassA{
	public static void main(String[] args) {
        Class b=ClassB.class;
        Class a=b.getSuperclass();
        try {
       Field[] fields= a.getDeclaredFields();
            AccessibleObject.setAccessible(fields,true);
        for(Field field :fields){
            field.setAccessible(true);
            System.out.println("ClassA value = " + field.get(new ClassB()).toString());
            System.out.println("ClassB value = " + field.get(new ClassA()).toString());
        }
        }catch (Exception e){
            System.out.println(e.getMessage());
        }

    }
}

最后的结果是: ClassA value = 10.0 ClassB value = 10.0 ClassA value = 12.0 ClassB value = 12.0

总结

Java在继承中,会继承所有的父类成员,即使这个成员被private修饰。在内存中,是可以通过大小明确验证成员的存在的。而想要访问父类的隐藏成员,可以通过反射这一机制实现。