Java反射破坏了封装性?

这是本菜鸡面试时被问到的一个问题,觉得挺有意思的,遂打算写一篇文章简单的谈下自己的看法。

在讨论这个问题之前,让我们先来简单复习一下Java反射的知识。

Java 反射

一、类型信息

在回顾Java反射之前,我们先来看一下RTTI(Run-Time Type Identification),运行时类型识别,它能在运行时就能够自动识别每个编译时已知的类型。

理解RTTI在Java中的工作原理,首先需要知道类型信息在运行时是如何表示的,这是由Class对象来完成的,它包含了与类有关的信息。Class对象就是用来创建所有“常规”对象的,Java使用Class对象来执行RTTI,即使你正在执行的是类似类型转换这样的操作。

二、反射

如果不知道某个对象的确切类型,RTTI可以告诉你,但是有一个前提:这个类型在编译时必须已知,这样才能使用RTTI来识别它。并利用这些信息做一些有用的事情。换句话说,在编译的时候,编译器必须知道所有要通过RTTI来处理的类。

而反射则不然,它并不需要编译时获取类的信息!

Class类与java.lang.reflect类库一起对反射进行了支持,该类库包含Field、Method和Constructor类,这些类的对象由JVM在启动时创建,用以表示未知类里对应的成员。这样的话就可以使用Contructor创建新的对象,用get()和set()方法获取和修改类中与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getFields()、getMethods()和getConstructors()等许多便利的方法,以返回表示字段、方法、以及构造器对象的数组,这样,对象信息可以在运行时被完全确定下来,而在编译时不需要知道关于类的任何事情。

接下来,本文就Field简单的举个例,看看反射有多强大。

首先来简单的定义一个小白鼠类。。。

class Person{
	
	public Person() {
	}
	
	public String name = "小明";
	public int age = 15;
	
	private int height = 170;
	private double weight = 55.6;

	@Override
	public String toString() {
		return "我叫"+name+",我今年"+age+"岁了,我的身高为"+height+"cm,体重为"+weight+"kg";
	}
}

之后我们开始使用反射来实现一些骚操作~

public class Test {
	
	public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException {
		System.out.println("通过反射获取Person的class对象");
		Class clazz = Class.forName("com.util.xgb.Person");
		System.out.println("根据Person的class对象来创建一个Person对象");
		Object obj = clazz.getConstructor().newInstance();
		System.out.println("先来给大伙自我介绍一下。。。");
		System.out.println(obj);
		System.out.println("----------------------------------------------------");
		
		
		System.out.println("通过反射拿到person对象的所有成员变量!连私有的我也要知晓!");
		Field[] fields = clazz.getDeclaredFields();
		for(Field f : fields) {
			System.out.println(f);
		}
		
		System.out.println("----------------------------------------------------");
		System.out.println("现在见证反射的强大威力,穿透Person的private屏障!改变私有字段的值!");
		Field field1 = clazz.getDeclaredField("weight");
		Field field2 = clazz.getDeclaredField("height");
		
		System.out.println("暴力反射,解除私有限定,使得我们可以更改私有属性的值!");
		field1.setAccessible(true);
		field1.set(obj, 60.5);
		field2.setAccessible(true);
		field2.set(obj, 160);
		
		System.out.println("再来介绍一下自己吧。。。");
		System.out.println(obj);
	}

我们看一下打印输出结果。

通过反射获取Person的class对象
根据Person的class对象来创建一个Person对象
先来给大伙自我介绍一下。。。
我叫小明,我今年15岁了,我的身高为170cm,体重为55.6kg
----------------------------------------------------
通过反射拿到person对象的所有成员变量!连私有的我也要知晓!
public java.lang.String com.util.xgb.Person.name
public int com.util.xgb.Person.age
private int com.util.xgb.Person.height
private double com.util.xgb.Person.weight
----------------------------------------------------
现在见证反射的强大威力,穿透Person的private屏障!改变私有字段的值!
暴力反射,解除私有限定,使得我们可以更改私有属性的值!
再来介绍一下自己吧。。。
我叫小明,我今年15岁了,我的身高为160cm,体重为60.5kg

尽管举的例子很low,但是已经能够表达意思了,不管字段和方法被何种权限修饰符修饰,在反射面前,它们都是一丝不挂的。。。。

其实反射机制并没有什么神奇之处,当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类。因此,那个类的.class对于JVM来说必须是可获取的,要么在本地机器上,要么从网络获取。所以对于RTTI和反射之间的真正区别只在于:

  • RTTI,编译器在编译时打开和检查.class文件
  • 反射,运行时打开和检查.class文件

那么Java反射破坏了封装性吗?

我们知道,封装、继承、多态是Java的三大特性!而我们通过上面简单的示例也看到了,通过反射,我们可以轻而易举的获取对私有成员的操作权利。那么这就是所谓的封装性遭受到破坏了吗?Java这样做岂不是搬起石头砸自己的脚吗?

我们不妨先来回想一下我们自己在使用Java撸代码的时候,声明私有方法时的出发点是什么?是不是大多数的情况下,这样做是为了支撑某一个我们对外提供的方法?并且,很多时候,我们为了代码的可读性会将一整个public方法拆分成很多个言简意赅、易读性更强的private方法和一个组合这些private方法的public方法。对于私有的成员变量,也是同理,使用private修饰变量,也是为了更好的服务于我们编写的某个对外提供的功能,而这些私有的成员变量和成员方法,仅在本类内有意义,如果对外开发的话,不仅毫无意义,甚至会严重影响到我们打算对外提供的功能。下面笔者拿ArrayList这个大家都很熟悉的类的源码举个例子:

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return true (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

比如大家最熟悉的ArrayList为我们提供的add方法,我们可以看到它仅有短短的三行代码,但是其中有一个非常重要的操作就是需要确保底层的数组空间足够,如果不够需要执行相应的扩容。那么我们可以清晰的看到这些涉及检查容量/扩容的方法几乎都是private的,它对使用ArrayList的用户完全透明,但是却举足轻重,都是为了使public的add方法更加完整更加安全。

笔者挑选的这几个简单的私有成员来进行举例,它们无不是为了服务于ArrayList对外为我们提供的那些功能而存在,同时,如果这些私有成员对外开放,它们中的任何一个单独拿出来都将不具备任何意义,并且如果放宽这些私有成员的存取权限,很可能会导致对ArrayList的操作结果出乎我们的意料,甚至很有可能会出错。

现在,我们再回过头来想一想,Java反射破坏了封装性吗?
通过上述的分析我们现在应该有了答案

  • 一、用户通过反射机制获取了所使用的类中私有成员的存取权限,这是毫无意义的,因为我们上面分析过了,大多数的这些成员是依附于其它public方法而存在的,基本上都是为了服务这些public成员的。
  • 二、紧接着上面的来说,就算用户拿到了private成员的存取权限,而且还“恶意”的修改类的私有成员,那么这么做的目的何在呢?这将大概率导致类的功能无法正常提供,你这不是自己搞自己呢么。。。

写在最后:

  1. 上面第一部分关于反射部分的介绍,主要摘自《Thinking in Java》第四版的第十四章:类型信息部分。另外所介绍的内容不够详细,仅仅是为了引出下面对于文章题目的探讨,反射机制远比本文所介绍的更为强大更为丰富,不太熟悉的读者可以参考后面这个链接去学习一下,写的非常好。Java基础之—反射(非常重要) .
  2. 对于本文标题所指出的问题的看法(第二部分叙述的内容),都是本菜鸡自己的想法,可能会有些片面甚至很不成熟,如果哪里理解的有误,欢迎各位朋友指正,大家一起探讨学习,一起进步!

你可能感兴趣的:(Java基础)