本文为《Java 编程思想》14.9节的读书笔记,文章内容仅限交流使用!
我们先看看使用接口时方法的访问权限(就这一个小标题,真没地方加小标题啊!)
使用interface关键字定义的接口主要是为了实现代码间的隔离,用以避免 使用接口的代码 与 实现类 之间的耦合。通常一个实现了某个接口的类中拥有自己的非来自于接口的方法,向上转型为接口的时候,就无法通过转型后的接口对象来调用子类自己另添加的方法。
这个是完全合理的,但是可以使用类型信息绕过这种限制,可以对实现类中的非接口方法进行调用。
首先定义一个接口A:
package com.henevalf.learn.typeinfo
public interface A {
void f();
}
然后用类B实现接口A,在后面的InterfaceViolation中我们可以看到如何绕过限制 :
package com.henvealf.learn.typeinfo;
class B implements A {
@Override
public void f() {
}
public void g(){
}
}
public class InterfaceViolation {
private A a;
public InterfaceViolation(A a) {
this.a = a;
}
public void invoke() {
a.f();
//a.g();
System.out.println(a.getClass().getName());
// 看见没!先检查一下类型,然后转型调用。。。。滋滋滋,真不要脸。
if(a instanceof B) {
B b = (B)a;
b.g();
}
}
public static void main(String[] args){
InterfaceViolation inv = new InterfaceViolation(new B());
}
}
嗯,没错,在invoke()方法里,就是强制类型转换,就可以使用在接口中未定义的方法g(),在本例中先用 instanceof 检测了一下类型是否可转。 想必也都使用过这种方式。这样并没有什么不妥,可以正常运行,但是他违背了我们当初使用接口的本意,类 InterfaceViolation 与类 B 无意之间就增加耦合。
如果你有难以克制的强迫症,就是不希望使用你的类的其他程序员这样做。那么有两种解决方法:
- 到他面前义正言辞的告诉他,不许你这样用。然而谁理你!!
- 自己在代码中进行控制。
怎么控制那?最简单的方式就是对实现类使用包访问权限。意思是将你的实现类放在一个包中,并设置实现类只能在包中才能被访问到,使用你类的程序员就找不到你的实现类的存在,就无法完成转型,看代码:
package com.henvealf.learn.typeinfo.packageaccess;
import com.henvealf.learn.typeinfo.A;
/**
* Created by Henvealf on 2016/9/10.
*/
class C implements A {
@Override
public void f() {
System.out.println("public C.f()");
}
public void g() {
System.out.println("public C.g()");
}
void u() {
System.out.println("package C.u()");
}
protected void v() {
System.out.println("protected C.v()");
}
public void w() {
System.out.println("private C.w()");
}
}
public class HiddenC {
public static A makeA(){
return new C();
}
}
注意包名,现在A的实现类C是在一个独立的包中,在这个包里面,唯一对外开放的public既是HiddenC,它有一个静态方法,返回C的一个对象,这样的话你就无法在包的外部调用A以外的任何方法了,因为你无法在包的外部找到C的类型信息,就无法进行转型:
package com.henvealf.learn.typeinfo;
import com.henvealf.learn.typeinfo.packageaccess.HiddenC;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
*
* Created by Henvealf on 2016/9/10.
*/
public class HiddenImplementation {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
A a = HiddenC.makeA();
a.f();
System.out.println(a.getClass().getName());
/*if(a instanceof C) { 编译错误,因为找不到C
.....
C c = (C)a;
c.g();
}*/
//我的天,反射竟然可以让你继续调用个g()
callHiddenMethod(a,"g");
// 甚至私有方法都可以
callHiddenMethod(a,"u");
callHiddenMethod(a,"v");
callHiddenMethod(a,"w");
}
static void callHiddenMethod(Object a, String methodName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 先获得类型信息(Class对象),然后获取其Method对象。
Method g = a.getClass().getDeclaredMethod(methodName);
// 就是这里,可以允许访问私有方法。
g.setAccessible(true);
//调用
g.invoke(a);
}
}
可以发现,在包外我们无法找到类型C,无法进行相应的转换。除此之外,可以看到竟然可以通过反射来调用对象C中的方法。甚至是私有方法,其原因就是在Method对象上调用了setAccessible(true),顾名思义就是设置访问权限。
你可能会想,要想使用这种方式,就必须要获得类C的方法列表,如果我们得到的只是类C编译后的字节码(.class文件),我们大可以使用javap来反编译字节码,以来的到方法列表。
反射除了能够突破包访问的权限,还能够访问到私有内部类,匿名类的所有方法。
当然除了方法,对于域(字段/属性),也同样如此,不过在域的访问中有一个特殊的,就是final字段,它只能被读取,不通过反射被再次赋值。
你可能会问,这样反射不就无法无天了吗!什么都能够访问到。而反射存在原因就是为了给程序员提供一个后门,能够让程序员解决一些难以解决的某些特定类型的问题(至于什么样的问题我也不清楚)。如果没有反射,这些问题将会难以或者不可能解决,所以说反射的好处是毋庸置疑的。
End...