权限和反射

  • 概述
  • 访问权限控制
  • 类访问
  • 属性或方法访问权限
  • 可变参数
  • 基于嵌套的访问控制
  • 反射和注解
  • 反射基本原理
  • 创建反射数据缓存
  • 获得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。

Java增加语句 java增加功能_spring

基于嵌套的访问控制

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动态代理对注解进行代理,这样注解就有了具体实现类。

Java增加语句 java增加功能_开发语言_02

@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 关键字一起,使得封闭类和封闭接口只能在一个限定的、封闭的范围内,获得扩展性。

  1. 许可类可以声明为终极类(final),从而关闭扩展性;
  2. 许可类可以声明为封闭类(sealed),从而延续受限制的扩展性;
  3. 许可类可以声明为解封类(non-sealed), 从而支持不受限制的扩展性。

虚拟机层面支持

//InstanceKlass有成员变量
Array<jushort>* _permitted_subclasses;

尽管sealed关键字是类修饰符,但是ClassFile中并没有 ACC_SEALED 标志。密封类的Class文件有PermittedSubclasses属性(JVM规范中JDK15、16预览),该属性隐式指示密封修饰符,并显式指定允许的子类。

Java增加语句 java增加功能_嵌套_03

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

  1. 声明顶级类:单独作为一个java文件
public record Student(long id, String name, int age) {}
  1. 作为一个成员内部类
public class ClassMates{
    public record Student(long id, String name, int age) {}
}
  1. 作为局部内部类
public class ClassMates{
	public void callTheRoll() {
    	record Student(long id, String name, int age) {}
}

声明内部元素

  1. Record类属性必须在头部声明,在Record类体只能声明静态属性:
public record Student(long id, String name, int age) {
	/**不是传参的,只能是字段,不包含在此类的Record属性中。作为参数传递进来
	* 的会出现在Record属性,并且是final类型字段
	*/
    static long alaisId;
}
  1. Record类体可以声明方法(成员方法和静态方法都可以),和一般类一样。但是不能声明abstract或者native方法
public record Student(long id, String name, int age) {
    public void test(){}
    public static void test2(){}
}
  1. 类体也不能包含实例初始化块
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);
                }
}