Java 15中的隐藏类
1.概述
Java 15引入了大量 特征。在本文中,我们将在下面讨论一个名为“隐藏类”的新特性JEP-371。此功能是作为 不安全API 的替代项而引入的,在 JDK 之外不推荐使用它.
隐藏类特性对于任何使用动态字节码或JVM语言的人都很有用。
2.什么是隐藏类?
动态生成的类为低延迟应用程序提供了效率和灵活性。它们只在有限的时间内需要。在静态生成类的生命周期内保留它们会增加内存占用。针对这种情况现有的解决方案,如按类加载程序,既麻烦又低效。
自Java15以来,隐藏类已成为生成动态类的标准方法。
隐藏类是字节码或其他类不能直接使用的类。 尽管它被称为类,但应该理解为是隐藏类或接口。它也可以定义为 访问控制嵌套 并且可以独立于其他类卸载。
3.隐藏类的属性
让我们来看看这些动态生成的类的属性:
- 不可发现-在字节码链接期间,JVM或显式使用类加载器的程序都无法发现隐藏类。反射方法 Class::forName, ClassLoader::findLoadedClass, and Lookup::findClass 都不会找到它们。
- 我们不能将隐藏类用作超类、字段类型、返回类型或参数类型。
- 隐藏类中的代码可以直接使用它,而不依赖于类对象。
- final 隐藏类中声明的字段不可修改,无论其可访问标志如何。
- 它使用不可发现的类扩展访问控制嵌套。
- 它可以被卸载,即使它的概念定义类加载器仍然可以访问。
- 默认情况下,堆栈跟踪不会显示隐藏类的方法或名称,但是,调整JVM选项可以显示它们。
4.创建隐藏类
隐藏类不是由任何类加载器创建的。 它具有与查找类相同的定义类加载器、运行时包和保护域。
首先,让我们创建一个 Lookup 对象:
MethodHandles.Lookup lookup = MethodHandles.lookup();
这个 Lookup::defineHiddenClass 方法创建隐藏类。此方法接受字节数组。
为了简单起见,我们将定义一个名为 HiddenClass(HiddenCass) 具有将给定字符串转换为大写的方法:
public class HiddenClass {
public String convertToUpperCase(String s) {
return s.toUpperCase();
}
}
让我们获取类的路径并将其加载到输入流中。之后,我们将使用 IOUItils.到ByteArray():
Class<?> clazz = HiddenClass.class;
String className = clazz.getName();
String classAsPath = className.replace('.', '/') + ".class";
InputStream stream = clazz.getClassLoader()
.getResourceAsStream(classAsPath);
byte[] bytes = IOUtils.toByteArray();
最后,我们将这些构造的字节传递到Lookup::defineHiddenClass:
Class<?> hiddenClass = lookup.defineHiddenClass(IOUtils.toByteArray(stream),
true, ClassOption.NESTMATE).lookupClass();
第二个 boolean参数true 初始化类. 第三个参数ClassOption.NESTMATE 指定创建的隐藏类将作为嵌套对象添加到查找类,以便它可以访问 私有的 同一嵌套中所有类和接口的成员。
假设我们想将隐藏类与其类加载器强绑定,ClassOption.STRONG。这意味着只有在无法访问其定义加载器时,才能卸载隐藏类。
5.使用隐藏类
隐藏类由在运行时生成类并通过反射间接使用它们的框架所使用。
在上一节中,我们讨论了创建一个隐藏类。在本节中,我们将了解如何使用它并创建实例。
指派去获取Lookup.defineHiddenClass 对于任何其他类对象都是不可能的,我们使用 Object 以存储隐藏的类实例。如果我们希望转换隐藏类,我们可以定义一个接口并创建一个实现该接口的隐藏类:
Object hiddenClassObject = hiddenClass.getConstructor().newInstance();
现在,让我们从隐藏类中获取方法。获取该方法后,我们将作为任何其他标准方法调用它:
Method method = hiddenClassObject.getClass() .getDeclaredMethod("convertToUpperCase", String.class);
Assertions.assertEquals("HELLO", method.invoke(hiddenClassObject, "Hello"));
现在,我们可以通过调用隐藏类的一些方法来验证它的一些属性:
方法isHidden()将返回 true 对于此类:
Assertions.assertEquals(true, hiddenClass.isHidden());
此外,由于隐藏类没有实际名称,因此其规范名称将为 无效的:
Assertions.assertEquals(null, hiddenClass.getCanonicalName());
隐藏类将具有与执行查找的类相同的定义加载器。由于查找发生在同一类中,以下断言将成功:
Assertions.assertEquals(this.getClass()
.getClassLoader(), hiddenClass.getClassLoader());
如果我们试图通过任何方法访问隐藏类,它们将抛出 ClassNotFoundException这是显而易见的,因为隐藏的类名非常不寻常,不符合条件,其他类无法看到。让我们检查几个断言,以证明隐藏类是不可发现的:
Assertions.assertThrows(ClassNotFoundException.class, () -> Class.forName(hiddenClass.getName()));
Assertions.assertThrows(ClassNotFoundException.class, () -> lookup.findClass(hiddenClass.getName()));
注意,其他类可以使用隐藏类的唯一方法是通过 Class 对象
6.匿名类与隐藏类
我们在前面的章节中创建了一个隐藏类,并使用了它的一些属性。现在,让我们详细说明匿名类(没有显式名称的内部类)和隐藏类之间的区别:
- 匿名类有一个动态生成的名称,名称之间有一个$,而一个隐藏类是从 com.baeldung.reflection.identclass.HiddenClass类 衍生为 com.baeldung.reflection.identclass.HiddenClass/1234。
- 匿名类是用Unsafe::defineAnonymousClass创建的实例,是已弃用的,而 Lookup::defineHiddenClass 是实例化隐藏类的.
- 隐藏类不支持的常量池补丁。它有助于定义匿名类,其常量池条目已解析为具体值。
- 与隐藏类不同,匿名类可以访问 protected 宿主类的成员,即使它位于不同的包而不是子类中。
- 匿名类可以封闭其他类以访问其成员,但隐藏类不能封闭其他类。
虽然隐藏类不能替代匿名类,它们正在取代JDK中匿名类的一些用法。 在Java15中,lambda表达式使用隐藏类.
7.结论
在本文中,我们详细讨论了一个名为隐藏类的新语言特性。一如既往,代码可用 在GitHub上.
- 本文标签: Java
- 本文链接: https://www.v8en.com/article/290
- 版权声明: 本文由SIMON原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权