权限和反射
- 概述
- 访问权限控制
- 类访问
- 属性或方法访问权限
- 可变参数
- 基于嵌套的访问控制
- 反射和注解
- 反射基本原理
- 创建反射数据缓存
- 获得class的方式
- 运行时获取类的完整结构
- 获取方式
- 反射创建对象
- 调用newInstance()
- 通过构造器调用newInstance()
- 反射获取泛型
- 类上带有泛型
- 父类带有泛型参数
- 方法参数有泛型
- 方法返回泛型参数
- 获取类上的注解
- 获取属性上的注解
- 接口
- 注解Anotation
- Annotation解析
- 运行时Annotation解析
- 编译时Annotation解析
- @Repeatable
- lamda表达式
- @FunctionalInterface
- JEP
- EdDSA
- Sealed Classes(密封类)
- 虚拟机层面支持
- Records
- 声明一个Records
- 声明内部元素
概述
java中访问权限、注解。你自以为自己都滚瓜烂熟了,但你可能认为它们都是小儿科,记住了简单用法就行。真到了你写框架的时候,什么时候用public,什么时候用private,什么时候又用protect,就不是很清晰,随便用一个,框架也是照常运行。但你的框架很容易被攻击,或者缺少功能使用。因为不该暴露的暴露,该暴露的又没有暴露。注解又是如何被读取,怎么使用其带的值。
访问权限控制
访问权限,首先应该明白在哪访问(是否同包),通过谁访问(当前类对象、子孙类对象)、访问什么(方法、属性)。在哪访问应该优先考虑。
类访问
类修饰符public、abstract、final、缺省(没有修饰符)。
- public修饰:允许任何地方,创建该类的实例。至于属性方法的访问,要看它门头上的修饰符。
- abstract抽象类:允许该类有未实现的方法。不涉及访问权限。
- 默认:包外不可见,本包内,其他类可以创建它的实例,本包内继承。
- final类:包外不可见,本包内,其他类可以创建它的实例。不能被继承。
属性或方法访问权限
- public公开的,在任何地点,任何访问者都可以访问
- protected ,本包下,都可以访问。
- 默认,本包下,自己的对象才能访问。
- private ,本类中,自己对象才能访问。
作用域 | 当前类对象 | 同一个包(packege) | 子孙类对象 | 其他包 |
public | √ | √ | √ | √ |
protected | √ | √ | √ | X |
默认 | √ | √ | X | X |
private | √ | X | X | X |
列表中每次应该取对象(本类、子孙类对象)状态和包(本包、外包)状态做与运算。
可变参数
Object... args
必须放在方法参数最后,如果放前面,后面的参数也会被认为是可变参数。调用Spring的getBean一定要分清,你调用的四个方法中的哪一种,因为有可变长度的构造函数参数,一步小心就传成了就会出错。
factory.getBean("firstBean",null,"s",null)
本次传参混淆了doGetBean
的四参数,这样实际调用的是getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args)
,null和s被认为是可变长度参数。这样构造器覆盖构造参数时,由于传入的和构造器本身的参数长度不一致,会报错Error creating bean with name 'firstBean' defined in class path resource [beans.xml]: Could not resolve matching constructor (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)
。Idea默认集合中null不显示,怎么都发现不了为什么参数长度会是2。
基于嵌套的访问控制
Nest-Based Access Control是JDK11新引入的访问控制规则,在java11之前,classfile用InnerClasses和EnclosingMethod两种属性来帮助编译器确认源码的嵌套关系,每一个嵌套的类型会编译到自己的class文件中,在使用上述属性来连接其他class文件。这些属性对于jvm确定嵌套关系上已经足够了,但是它们不直接适用于访问控制,并且和java语言绑定的太紧了。为了提供一种更大的,更广泛的,不仅仅是java语言的嵌套类型,并且补足访问控制检测的不足,引入了两个新的class文件属性。一个是嵌套宿主NestHost
(也叫top-level class),将嵌套的宿主记录到当前类或接口声明中。另一个是嵌套成员NestMembers
,记录了被授权可以声明在嵌套宿主类或接着口中的类或接口。class文件结构的属性表不能同时包含NestMembers
属性和NestHost
属性。这个规则避免了一个嵌套宿主在不同的嵌套中声明成员。
当且仅当以下条件为真时,一个私有成员字段或方法R是可以被类或接口D访问的:
…
R是声明在另一个类或接口C中,同时 C 和 D 是嵌套伙伴。
由于C和D是嵌套同伴,那么他们一定有同一个嵌套宿主类,在这里是Test。如果C在自己的嵌套宿主的属性中可以列举出D,那么C类就会被D 断言为自己的嵌套成员。如果D也可以在自己的NestMembers 属性中列举出C,那么这个嵌套同伴关系就是有效的。D是自己的隐性嵌套成员。
public class NestHost {
class NestMemberOne {
private String R = "get到我了";
};
class NestMemberAnother {
private void privateMethod(){
System.out.println( (new NestMemberOne()).R);
}
};
public Object Ap(){
class LocalInnerClass {}
return new LocalInnerClass();
}
public static void main(String[] args) {
/**1.非宿主类必须通过宿主类对象创建宿主类的非静态内部类对象(
new NestHost().new NestMemberOne())。创建静态内部类对
象:NestHost.NestMemberOne mo =new NestHost.NestMemberOne()
2.宿主类可以提供非静态方法创建内部类对象(new NestMemberOne())。如
果是提供的是静态方法,创建非静态内部类对象,必须使用宿主类的对象来创
建内部类对象。如下:
*/
NestMemberOne mo = new NestHost().new NestMemberOne();
for(Class nb:mo.getClass().getNestMembers()) System.out.println(nb);
/**
NestMemberOne和NestMemberAnother是嵌套伙伴关系,所有嵌套成员属性
一致
class com.bean.anotation.ownanotation.NestHost
class com.bean.anotation.ownanotation.NestHost$NestMemberAnother
class com.bean.anotation.ownanotation.NestHost$NestMemberOne
*/
Class cl = (new NestHost()).Ap().getClass();
System.out.print("Enclosing Method :");
System.out.println(cl.getEnclosingMethod());
/**
当一个类为局部内部类或匿名嵌套类时才有EnclosingMethod属性,返回它的
外围方法。
Enclosing Method :public java.lang.Object
com.bean.anotation.ownanotation.NestHost.Ap()
*/
}
}
反射和注解
反射基本原理
一个Class对象可以同时被多个线程反射,线程安全怎么解决?多个线程同时反射会不会出现性能下降问题?提高性能当然用缓存。反射获取属性、方法都是通过getXX,它们内部调用privateGetXX方法,而他们第三行都是ReflectionData<T> rd = reflectionData()
rd是个缓存对象,缓存的内容实际上是反射需要获取的成员变量、成员方法、构造方法、类接口等可以由每个线程获取到的变量。
private static class ReflectionData<T> {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor<T>[] declaredConstructors;
volatile Constructor<T>[] publicConstructors;
// Intermediate results for getFields and getMethods
volatile Field[] declaredPublicFields;
volatile Method[] declaredPublicMethods;
volatile Class<?>[] interfaces;
// Value of classRedefinedCount when we created this ReflectionData instance
final int redefinedCount;
ReflectionData(int redefinedCount) {
this.redefinedCount = redefinedCount;
}
}
创建反射数据缓存
提高性能用缓存
//volatile内存可见的变量,并且是软引用,当JVM将要发生OOM时,回收缓存
private volatile transient SoftReference<ReflectionData<T>> reflectionData;
// Incremented by the VM on each call to JVM TI RedefineClasses()
// that redefines this class or a superclass.
/**每次调用JVM TI的RedefineClasses(),虚拟机自动递增计数。
* 什么时候调用?重新定义类或者它的父类时。获取ClassDefinition里的class
* 对象和需要修改的字节码,然后调用TI RedefineClasses()
*/
private volatile transient int classRedefinedCount = 0;
// Lazily create and cache ReflectionData
private ReflectionData<T> reflectionData() {
//1.获取反射缓存对象
SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
int classRedefinedCount = this.classRedefinedCount;
ReflectionData<T> rd;
//2.缓存存在,直接从缓存获取
if (useCaches &&
reflectionData != null &&
(rd = reflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
// else no SoftReference or cleared SoftReference or stale ReflectionData
// -> create and replace new instance
//3.没有缓存创建新的缓存实列
return newReflectionData(reflectionData, classRedefinedCount);
}
线程安全用自旋+CAS创建反射数据缓存对象
private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData,
int classRedefinedCount) {
if (!useCaches) return null;
while (true) {
ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);
// try to CAS it...
if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {
return rd;
}
// else retry
oldReflectionData = this.reflectionData;
classRedefinedCount = this.classRedefinedCount;
if (oldReflectionData != null &&
(rd = oldReflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
}
}
JvmtiEnv::RedefineClasses(jint class_count, const jvmtiClassDefinition* class_definitions) {
//TODO: add locking
//class_count就是classRedefinedCount
VM_RedefineClasses op(class_count, class_definitions, jvmti_class_load_kind_redefine);
VMThread::execute(&op);
return (op.check_error());
} /* end RedefineClasses */
获得class的方式
- Class.forName(类全名:包路径+类名)
- 类.class
- 对象.getClass()
- loadClass(类全名),一般在SPI中常用
//Class类的方法返回一个类的全类名
public String getName() {
String name = this.name;
if (name == null)
this.name = name = getName0();
return name;
}
运行时获取类的完整结构
- 获得类名
- clazz.getName()获得类全名
- clazz.getSimpleName()获得类简单名
获得属性 - clazz.getFields()只获取public属性
- clazz.getDeclareFileds()获取所有属性,包括私有
获得指定属性 - clazz.getFiled(“xxx”)获取指定名称的public属性
- clazz.getDeclareFiled(“xx”)获取指定名称的属性,包括私有
获得方法 - clazz.getMethods()获取所有public方法,包括父类的
- clazz.getDeclareMethods()获得本类的所有方法,包括私有的
获得指定方法 - clazz.getMethod(方法名,args参数)获得本类及父类指定名称public方法
- clazz.getDeclareMethod(方法名,args参数)获得本类指定名方法,包括私有
获取构造器 - clazz.getConstructors()获取本类public构造器
- clazz.getDeclareConstructors()获取本类所有构造器,包括私有
获取指定构造器 - clazz.getConstructor(参数类型)获取指定public构造器
- clazz.getDeclareConstructor(参数类型)获取指定构造器,包括私有
获取修饰符 - filed.getModifiers()获取属性修饰符
- method.getModifiers()获得方法属性
获取属性类型 - filed.getGenericType().getTypeName()
- method.getGenericType().getTypeName()
修饰对应的数字,多个修饰符,数字相加
PUBLIC: 1 (二进制 0000 0001)
PRIVATE: 2 (二进制 0000 0010)
PROTECTED: 4 (二进制 0000 0100)
STATIC: 8 (二进制 0000 1000)
FINAL: 16 (二进制 0001 0000)
SYNCHRONIZED: 32 (二进制 0010 0000)
VOLATILE: 64 (二进制 0100 0000)
TRANSIENT: 128 (二进制 1000 0000)
NATIVE: 256 (二进制 0001 0000 0000)
INTERFACE: 512 (二进制 0010 0000 0000)
ABSTRACT: 1024 (二进制 0100 0000 0000)
STRICT: 2048 (二进制 1000 0000 0000)
获取方式
以获取Method为例:先从缓存中获取,获取不到,从虚拟机中获取
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
checkInitted();
Method[] res;
ReflectionData<T> rd = reflectionData();
//先从缓存中获取
if (rd != null) {
res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
if (res != null) return res;
}
// 没有缓存的条件下从虚拟机中获取
res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
if (rd != null) {
if (publicOnly) {
rd.declaredPublicMethods = res;
} else {
rd.declaredMethods = res;
}
}
return res;
}
反射创建对象
调用newInstance()
Class clazz = Class.forName("全类名");
//调用的是无参构造器,所以类必须有无参构造器,否则出错
clazz.newInstance();
通过构造器调用newInstance()
Class clazz = Class.forName("全类名");
Constructor cs =clazz.getDeclaredConstructor(args);
cs.newInstance(args);
反射获取泛型
类上带有泛型
public class DenPendencybean <Security>
//获取类上所带泛型参数
TypeVariable [] t = clazz.getTypeParameters();
//打印Security全类名
System.out.println(t[0].getGenericDeclaration());
父类带有泛型参数
方式一,先获取父类,再获取参数。泛型在父类上(public class FatherPer<Security>
)这么用。
//先获取父类,再获取参数
TypeVariable [] t =clazz.getSuperclass().getTypeParameters();
//获取全类名
System.out.println(t[0].getGenericDeclaration());
方式二,直接getGenericSuperclass获得继承时父类传入的泛型参数(extends PerFather<Security>
)
/**父类有泛型(extends PerFather<Security>)
com.bean.dependency.PerFather<java.security.Security>
*/
/**这时type是ParameterizedTypeImpl,继承时出现了泛型参数,它就会取实现
ParameterizedTypeImpl。
*/
Type type = clazz.getGenericSuperclass();
//参数化类型
if(type instanceof ParameterizedType) {
//继承父类时有泛型参数,否则转型异常,所以必须判断一些
ParameterizedType p = (ParameterizedType)type ;
)
//获取真正的参数类型遍历打印
for(Type tp :p.getActualTypeArguments()){
System.out.println(tp);
}
方法参数有泛型
//获取方法泛型参数列表,遍历
Type[] type = clazz.getDeclaredMethod("setName", Map.class).getGenericParameterTypes();
for (Type t : type) {
if (t instanceof ParameterizedType) {
//向下转型
ParameterizedType pt = (ParameterizedType) t;
//获取实际参数
for(Type te:pt.getActualTypeArguments()){
System.out.println(te);
}
}
方法返回泛型参数
//获取返回泛型参数
Type type = clazz.getDeclaredMethod("setName", null).getGenericReturnType();
if (type instanceof ParameterizedType) {
//向下强转
ParameterizedType pt = (ParameterizedType)type;
for(Type te:pt.getActualTypeArguments()){
System.out.println(te);
}
获取类上的注解
//获取类上所有的注解,包括自己声明的以及继承的
Annotation [] at = clazz.getAnnotations();
//获取类上自己声明的注解
Annotation [] as = clazz.getDeclaredAnnotations();
//获取类声明的或继承的指定的注解
Table table = (Table)clazz.getAnnotation(Table.class);
获取属性上的注解
Field field = clazz.getDeclaredField("studentNumber");
MyField mf = field.getAnnotation(MyField.class);
System.out.println(mf.columnName());
Table table = (Table)clazz.getAnnotation(Table.class);
System.out.println(table.value());
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyField {
String columnName();
String type();
int length();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String value();
}
@Table("student_table")
public class Student {
@MyField(columnName = "db_number",type = "int",length = 10)
private Integer studentNumber;
@MyField(columnName = "db_name",type = "varchar",length = 10)
private String name;
@MyField(columnName = "db_age",type = "int",length = 10)
private Integer age;
public Integer getStudentNumber() {
return studentNumber;
}
public void setStudentNumber(Integer studentNumber) {
this.studentNumber = studentNumber;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
public class AnotationTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class clazz = Class.forName("com.bean.anotation.ownanotation.Student");
//获取Student上所有注解
Annotation [] at = clazz.getAnnotations();
Field field = clazz.getDeclaredField("studentNumber");
MyField mf = field.getAnnotation(MyField.class);
System.out.println(mf.columnName());
Table table = (Table)clazz.getAnnotation(Table.class);
System.out.println(table.value());
}
}
接口
接口中可以有final属性变量,默认方法,静态方法。
public interface IntefaceMmethod {
/**会默认final类型,它调用了静态方法来赋值。
* IntefaceMmethod在JVM初始化调用方法给fianl的HPQ赋值。外界通过接口
* 调用test(String...args)。所以测试类启动的时候会走两次。不要惊讶。
* 这样做的好处,我需要一个fianl常量,它有生成规则,而且这个规则也会给
* 变量使用。
*/
IntefaceMmethod HPQ = test("dalas");
static IntefaceMmethod test(String...args){
System.out.println(args);
return null;
}
}
注解Anotation
这是注解接口,但我们没见过它被extends,我们自定义的接口又是怎么继承它的。其中的@interface是一个关键字,这个关键字声明隐含了一个信息:这个接口必须继承java.lang.annotation.Annotation
接口,也就是说注解的本质还是接口。JDK动态代理对注解进行代理,这样注解就有了具体实现类。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String value();
}
//注解的class文件反编译之后
public interface Table extends java.lang.annotation.Annotation {
//注解元素成为抽象方法
public abstract java.lang.String value();
}
Annotation解析
运行时Annotation解析
运行时 Annotation 指 @Retention 为,RetentionPolicy.RUNTIME
的 Annotation,可调用Class的常用 API 解析。其实就是通过反射
getAnnotation(XXXAnnotation.class);
getAnnotations();
isAnnotationPresent(XXXAnnotation.class);
编译时Annotation解析
编译时Annotation指@Retention为RetentionPolicy.CLASS
的Annotation,由apt(Annotation Processing Tool) 自动解析。
- 重写其中的 process 方法
实际是 apt(Annotation Processing Tool) 在编译时自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法去处理
public class TableProcessor extends AbstractProcessor {
@Override
public boolean process(Set<?extends TypeElement> annotations, RoundEnvironment env) {
HashMap<String, String> map = new HashMap<String, String>();
for (TypeElement te : annotations) {
for (Element element : env.getElementsAnnotatedWith(te)) {
Table table = element.getAnnotation(Table.class);
map.put(element.getEnclosingElement().toString(), table.value());
}
}
return false;
}
}
有篇博文对APT的运用很好的实践
@Repeatable
java8新增元注解,就是一语法糖。让一个注解可以在一个地方重复使用多次。JDK1.8以前:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FileType {
String fileSuffix() default ".java";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FileTypes {
FileType[] value();
}
@FileTypes({@FileType(".java"),@FileType(".css"),@FileType(".html"),@FileType(".js")})
public void readFile(){
try {
FileType[] fileTypes= this.getClass().getMethod("readFile").getAnnotationsByType(FileType.class);
System.out.println("将从如下后缀名的文件中查找文件内容");
for (FileType fileType : fileTypes) {
System.out.println(fileType.value());
}
} catch (NoSuchMethodException | SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
这样解决了需求,但代码不宜读。用@Repeatable
```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(FileTypes.class)
public @interface FileType {
String fileSuffix() default ".java";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FileTypes {
FileType[] value();
}
@FileType(".java")
@FileType(".css")
@FileType(".html")
@FileType(".js")
public void readFile(){
try {
FileType[] fileTypes= this.getClass().getMethod("readFile").getAnnotationsByType(FileType.class);
System.out.println("将从如下后缀名的文件中查找文件内容");
for (FileType fileType : fileTypes) {
System.out.println(fileType.value());
}
} catch (NoSuchMethodException | SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
lamda表达式
@FunctionalInterface
有了这个注解注释的接口就可以完成lamda功能。例如执行注解value属性的策略API
@FunctionalInterface
interface ValueExtractor {
/**
* Extract the annotation attribute represented by the supplied {@link Method}
* from the supplied source {@link Object}.
*/
@Nullable
Object extract(Method attribute, @Nullable Object object);
}
ReflectionUtils::invokeMethod
由extract就知道该调用哪个invokeMethod
方法
JEP
这里的JEP是JDK增强计划书(JDK增强功能索引中涉及到编码层面的功能),详细内容查看官方JEP。
EdDSA
JEP 339:Edwards-Curve Digital Signature Algorithm (EdDSA) 爱德华兹曲线数字签名算法(EdDSA),和Schnorr,secp256k1, sm2 最大的区别在于没有使用随机数,这样产生的签名结果是确定性的,即每次对同一消息签名结果相同。一般说来随机数是安全措施中重要的一种方法,但是随机数的产生也是安全隐患,索尼的PS3 密钥泄露事件,就是随机数产生的问题导致的。签名过程中出现了这个相同的随机数 r,那么私钥将很容易被计算出来,造成泄露,当前很多兴起的区块链项目都在使用。
Sealed Classes(密封类)
JEP 360: Sealed Classes (Preview) 在 Java 语言中,代码的重用是通过类的继承实现的:多个子类可以继承同一个超类(并重用超类的方法)。但是重用代码并不是类层次结构的唯一目的,有时类层次结构仅仅是对某个领域的建模。以这种方式使用类层次结构时,限制子类集合可以简化建模。
比如在图形库中,Shape 类的开发者可能只希望有限个数的类扩展 Shape 类,开发者并不想为未知子类编写防御代码。以前的 Java 并不会限制 Shape 类的扩展属性,Shape 类可以拥有任意数量的子类。在封闭类(Sealed Classes)中,类层次结构是封闭的,但是代码仍然可以在有限范围内重用。
public sealed class Shape permits Circle, Rectangle, Square {
//类中内容
}
final class Circle extends Shape {}
//在子类数量很少时,在密封类的源文件中声明子类会很方便。密封类可以省略 permits子句
public sealed class Shape
{}
final class Circle extends Shape{
}
这个封闭接口的声明表明,只有 permits 关键字后面的类,包括 Circle, Rectangle, Square,能够实现这个接口。permits
关键字限定了封闭类或者封闭接口的可扩展范围。类修饰符 sealed 和 permits 关键字一起,使得封闭类和封闭接口只能在一个限定的、封闭的范围内,获得扩展性。
- 许可类可以声明为终极类(final),从而关闭扩展性;
- 许可类可以声明为封闭类(sealed),从而延续受限制的扩展性;
- 许可类可以声明为解封类(non-sealed), 从而支持不受限制的扩展性。
虚拟机层面支持
//InstanceKlass有成员变量
Array<jushort>* _permitted_subclasses;
尽管sealed关键字是类修饰符,但是ClassFile
中并没有 ACC_SEALED
标志。密封类的Class文件有PermittedSubclasses
属性(JVM规范中JDK15、16预览),该属性隐式指示密封修饰符,并显式指定允许的子类。
PermittedSubclasses_attribute {
//指向常量池中代表PermittedSubclasses字符串的索引
u2 attribute_name_index;
//属性长度,不包括最初的六个字节,也就是记录的是number_of_classes和classes长度
u4 attribute_length;
//封闭类允许子类个数,图中是2。和下一个属性classes中类的个数一致
u2 number_of_classes;
u2 classes[number_of_classes];
}
Records
JEP 359:Records (Preview) 数据载体Records
是Java的一种新的类型。同枚举一样,records也是对类的一种限制。records放弃了类通常享有的特性:将API和表示解耦。但是作为回报,records使数据类变得非常简洁。不能用abstract
修饰Record
类,可以用final
修饰Record
类,但是这没有必要,因为Record
类本身就是final
的。成员Record
类,还有本地Record
类,本身就是static
的,也可以用static
修饰,但是没有必要。
声明一个Records
- 声明顶级类:单独作为一个java文件
public record Student(long id, String name, int age) {}
- 作为一个成员内部类
public class ClassMates{
public record Student(long id, String name, int age) {}
}
- 作为局部内部类
public class ClassMates{
public void callTheRoll() {
record Student(long id, String name, int age) {}
}
声明内部元素
-
Record
类属性必须在头部声明,在Record
类体只能声明静态属性:
public record Student(long id, String name, int age) {
/**不是传参的,只能是字段,不包含在此类的Record属性中。作为参数传递进来
* 的会出现在Record属性,并且是final类型字段
*/
static long alaisId;
}
-
Record
类体可以声明方法(成员方法和静态方法都可以),和一般类一样。但是不能声明abstract
或者native
方法
public record Student(long id, String name, int age) {
public void test(){}
public static void test2(){}
}
- 类体也不能包含实例初始化块
public record Student(long id, String name, int age) {
//不能声明实列初始化块,但可以是静态块(初始化出来的实例都是它的成员变量,成员变量应该是static的,所以不能是非静态初始化块或者实例)
{
...
}
}
类体中只能声明静态字段,静态块。可以安全的发布对象,因为这些静态属性在类加载的准备阶段就已经初始化。如果非静态需要等到类初始化的时候才能被初始化。可能会影响对象的发布和使用。同时也是严格区分了Record
类和普通类。否则没有这些约束,和我们声明POJO
对象没有任何区别。静态字段可以作为一个Record
通用的属性,比如状态控制等。
protected List<JCTree> classOrInterfaceOrRecordBodyDeclaration(Name className, boolean isInterface, boolean isRecord) {
...
//标记
(token.kind == LBRACE &&
(mods.flags & Flags.StandardFlags & ~Flags.STATIC) == 0 &&
mods.annotations.isEmpty()) {
if (isInterface) {
log.error(DiagnosticFlag.SYNTAX, token.pos, Errors.InitializerNotAllowed);
} else if (isRecord && (mods.flags & Flags.STATIC) == 0) {
log.error(DiagnosticFlag.SYNTAX, token.pos, Errors.InstanceInitializerNotAllowedInRecords);
}
}