初识Java 17

编程入门 行业动态 更新时间:2024-10-25 08:16:43

初识<a href=https://www.elefans.com/category/jswz/34/1770091.html style=Java 17"/>

初识Java 17

本笔记参考自: 《On Java 中文版》


接口和类型信息

        interface关键字的一个重要目标就是允许程序员隔离组件,减少耦合。但我们可以通过类型信息来绕过接口的隔离,这使得接口不一定能够保证解耦。

        为了演示这一实现,我们需要先创建一个接口:

package reflection.interfacea;public interface A {void f();
}

        接下来的例子展示了如何绕过接口,偷偷访问实际的实现类型:

package reflection;import reflection.interfacea.A;class B implements A {@Overridepublic void f() {}public void g() {}
}public class InterfaceViolation {public static void main(String[] args) {A a = new B();a.f();// a.g(); // 此时还不能访问方法g()System.out.println(a.getClass().getName());if (a instanceof B) {B b = (B) a;b.g();}}
}

        通过反射,我们将a强制转换成了B类,以此来调用A中不存在的方法。

        很显然,这种实现是合理的。但当一些客户程序员使用这些代码时,他们也可能会通过这种方式的调用,使得其代码与我们的代码之间的耦合程度超出我们的预期。换言之,instanceof并不能够保护我们的代码。

    Windows系统就存在类似的问题……

        这时有两种解决方案:①直接声明,让客户程序员自己承当使用额外代码带来的后果。②而另一种方法,就是对代码的访问权限加以控制:

【例子:通过包访问权限隔绝包外的访问】

package reflection.packageaccess;import reflection.interfacea.A;class C implements A {@Overridepublic 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()");}private void w() {System.out.println("private C.w()");}
}public class HiddenC {public static A makeA() {return new C();}
}

        我们创建了一个接口A的实现:C类,并将其放到一个单独的包中。代码中只有HiddenC类存在一个与外界通信的接口makeA()。

        遗憾的是,我们依旧有办法绕过包的隐藏:

【例子:绕过包隐藏】

package reflection;import reflection.packageaccess.HiddenC;
import reflection.interfacea.A;import java.lang.reflect.Method;public class HiddenImplementation {public static void main(String[] args)throws Exception {A a = HiddenC.makeA();a.f();// 通过反射可以得到隐藏的类名System.out.println(a.getClass().getName());// 编译错误,无法找到"C":/* if (a instanceof C) {C c = (C) a;c.g();} */// 但依旧可以通过反射调用被隐藏起来的方法:callHiddenMethod(a, "g");// 以及访问权限更小的方法:callHiddenMethod(a, "u");callHiddenMethod(a, "v");callHiddenMethod(a, "w");}static void callHiddenMethod(Object a, String methodName)throws Exception {Method g =a.getClass().getDeclaredMethod(methodName);g.setAccessible(true);g.invoke(a); // 将获得的方法g()重定位到a}
}

        程序执行的结果是:

        即使访问权限限制了使用者对类的直接访问,反射依旧提供了足以调用所有方法的能力,只需要我们知道类中方法的名字即可。

        并且,即使我们只发布代码的已编译版本,JDK自带的反编译器依旧可以展示出文件中所有的成员。JDK的反编译命令如下:

javap -private C

        执行上述命令,可得到如下结果:

通过这种方式,任何人都可以看到被隐藏的方法或签名(并调用它们)。

        即使内部私有类也不能例外:

【例子:利用反射访问私有内部类】

package reflection;import reflection.interfacea.A;class InnerA {private static class C implements A {@Overridepublic 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()");}private void w() {System.out.println("private C.w()");}}public static A makeA() {return new C();}
}public class InnerImplementation {public static void main(String[] args)throws Exception {A a = InnerA.makeA();a.f();System.out.println(a.getClass().getName());// 通过反射访问私有类内部:HiddenImplementation.callHiddenMethod(a, "g");HiddenImplementation.callHiddenMethod(a, "u");HiddenImplementation.callHiddenMethod(a, "v");HiddenImplementation.callHiddenMethod(a, "w");}
}

        程序执行的结果是:

        匿名类也是如此(代码结构与上面的大致相同):

【例子:通过反射访问匿名类】

package reflection;import reflection.interfacea.A;class AnnoymousA {public static A makeA() {return new A() {@Overridepublic 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()");}private void w() {System.out.println("private C.w()");}};}
}public class AnnoymousImplementation {public static void main(String[] args)throws Exception {A a = AnnoymousA.makeA();a.f();System.out.println(a.getClass().getName());// 通过反射访问匿名类内部:HiddenImplementation.callHiddenMethod(a, "g");HiddenImplementation.callHiddenMethod(a, "u");HiddenImplementation.callHiddenMethod(a, "v");HiddenImplementation.callHiddenMethod(a, "w");}
}

        程序执行的结果是:

        除此之外,也可以通过反射访问字段:

【例子:通过反射访问字段】

package reflection;import java.lang.reflect.Field;class WithPrivateFinalField {private int i = 1;private final String s = "这条语句是private final的";private String s2 = "这条语句是private的";@Overridepublic String toString() {return "该类拥有的private字段如下:\n\t" +"i = " + i + "\n\t" +"s = " + s + "\n\t" +"s2 = " + s2;}
}public class ModifyingPrivateFields {public static void main(String[] args)throws Exception {WithPrivateFinalField pf =new WithPrivateFinalField();System.out.println(pf);System.out.println("\n通过反射访问字段:");// Field类可用于反射字段Field field = pf.getClass().getDeclaredField("i");field.setAccessible(true); // 允许访问System.out.println("f.getInt(pf):"+ field.getInt(pf));field.setInt(pf, 47);System.out.println("使用setInt()改变i的值," + pf);System.out.println("=====");field = pf.getClass().getDeclaredField("s");field.setAccessible(true); // 允许访问System.out.println("f.get(pf):"+ field.get(pf));field.set(pf, "尝试改变s");System.out.println("使用set()无法改变s的值," + pf);System.out.println("=====");field = pf.getClass().getDeclaredField("s2");field.setAccessible(true); // 允许访问System.out.println("f.get(s2):"+ field.get(pf));field.set(pf, "不安全");System.out.println("使用set()改变s的值," + pf);}
}

        程序执行的结果是:

        不过final字段还是安全的,不会因为反射而反射变化。

        一般而言,反射带来的麻烦不会有想象中的那么大,因为如果有人使用了反射,那么他们也应该承受代码改变带来的风险。并且,Java提供这样一个后门来访问类,确实可以解决一些问题。

    注意:面向对象编程语言要求,在任何可能的地方使用多态,而只在必要的地方使用反射(若一定要使用,可以将反射放到一个特定的类中进行使用。但我们也可能找到一个更好的替代方案)。

更多推荐

初识Java 17

本文发布于:2023-11-15 18:29:30,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1604187.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:Java

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!