interface 关键字相信大家都不会陌生,我们可以使用它来创建接口,从而实现隔离构件,降低代码耦合性的目的。
先来看一个例子,定义接口 A ,接口 A 的实现类 B。
public interface A {
void f();
}
public class B implements A {
@Override
public void f() {
System.out.println("A.f execute");
}
private void g() {
System.out.println("B.g execute");
}
}
在 InterfaceViolation 类中通过类型检查得知 a 是被当做 B 实现的,将 a 转换成 B 可调用接口 A 中不存在的方法。
public class InterfaceViolation {
public static void main(String[] args) {
A a = new B();
a.f();
System.out.println(a.getClass().getName());
if (a instanceof B){
B b = (B) a;
b.f();
}
}
}
A.f execute
mtn.baymax.charpter14.B
A.f execute
上述的操作虽然是合法的,但我们有时候会不希望客户端程序员使用接口以外的方法。最简单的方式对实现类使用包访问权限,再通过一个公共类返回实现类的引用。如下所示:
class B implements A {
@Override
public void f() {
System.out.println("A.f execute");
}
public void g() {
System.out.println("B.g execute");
}
}
public class HiddenB {
public static A makeA() {
return new B();
}
}
这样即使知道返回的类是 B 类型,也会因无法获取到B类的访问权限,无法实现强制转换。
但这样就能阻止别人调用 B 类中的方法了嘛?
答案是不可能的,通过反射仍然可以做到。
public class InterfaceViolation {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException,
IllegalAccessException {
A a = HiddenB.makeA();
Method g = a.getClass().getDeclaredMethod("g");
g.setAccessible(true);
g.invoke(a);
}
}
B.g execute
这里可能会有同学有疑问,这里必须知道 B 类中的方法名才能调用。我仅给别人提供编译过的 class 文件,是不是就没有这种问题了?
曾经的我也是如此天真。但现实是,通过JDK 自带的 javap 命令即可反编译突破限制。
不仅仅是获取非公共方法,即使是类的私有变量和私有方法也可以通过 javap -private 类进行查看。
public class C implements A {
private String name = "tom";
private int age = 24;
private final String address = "NO.1 Street";
@Override
public void f() {
System.out.println("A.f execute");
}
private void h() {
System.out.println("C.h execute");
}
@Override
public String toString() {
return "C{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
其次,即使是 private 域也无法阻止反射的访问,反射不仅可以调用私有方法,还可以获取私有变量,甚至可以修改私有变量的值。
public class HiddenC {
public static A makeA() {
return new C();
}
}
public class ModifyingPrivateClass {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException,
IllegalAccessException, NoSuchFieldException {
A a = HiddenC.makeA();
System.out.println("------调用私有方法------");
Method h = a.getClass().getDeclaredMethod("h");
h.setAccessible(true);
h.invoke(a);
System.out.println("------输出原对象------");
System.out.println(a);
System.out.println("------改变name属性------");
Field name = a.getClass().getDeclaredField("name");
name.setAccessible(true);
name.set(a, "Jerry");
System.out.println(a);
System.out.println("------改变age属性------");
Field age = a.getClass().getDeclaredField("age");
age.setAccessible(true);
age.set(a, 12);
System.out.println(a);
System.out.println("------改变address属性------");
Field address = a.getClass().getDeclaredField("address");
address.setAccessible(true);
address.set(a, "NO.12 Street");
System.out.println(a);
}
}
------调用私有方法------
C.h execute
------输出原对象------
C{
name='tom', age=24, address='NO.1 Street'}
------改变name属性------
C{
name='Jerry', age=24, address='NO.1 Street'}
------改变age属性------
C{
name='Jerry', age=12, address='NO.1 Street'}
------改变address属性------
C{
name='Jerry', age=12, address='NO.1 Street'}
不过,通过上述例子,我们发现被 final 修饰的变量 address 是无法被反射修改的,故它是安全的。
其实,不仅仅是 private 域,类中的私有内部类或者匿名类,在反射面前都没有任何隐私可言。
虽然通过反射违反访问权限的操作看似非常糟糕,但在某些特定时刻,说不定可以帮助你解决问题。当然,我们还是应当尽量避免此类操作,因调用服务提供类中的 private 中的方法会增加代码的耦合性,在后续的迭代升级中,可能会使得程序不兼容,服务无法正常运行。
本次分享至此结束,希望本文对你有所帮助,若能点亮下方的点赞按钮,在下感激不尽,谢谢您的【精神支持】。
若有任何疑问,也欢迎与我交流,若存在不足之处,也欢迎各位指正!