实战 Java 16 值类型 Record - 2. Record 的基本用法

干货满满张哈希 2021-04-16 12:40:09
java 实战 类型 record


在上一篇文章实战 Java 16 值类型 Record - 1. Record 的默认方法使用以及基于预编译生成相关字节码的底层实现中,我们详细分析了 Record 自带的属性以及方法和底层字节码与实现。这一篇我们来详细说明 Record 类的用法。

声明一个 Record

Record 可以单独作为一个文件的顶级类,即:
User.java 文件:

public record User(long id, String name, int age) {}

也可以作为一个成员类,即:

public class RecordTest {
public record User(long id, String name, int age) {}
}

也可以作为一个本地类,即:

public class RecordTest {
public void test() {
record Mail (long id, String content){}
Mail mail = new Mail(10, "content");
}
}

不能用 abstract 修饰 Record 类,会有编译错误。
可以用 final 修饰 Record 类,但是这其实是没有必要的,因为 Record 类本身就是 final 的

成员 Record 类,还有本地 Record 类,本身就是 static 的,也可以用 static 修饰,但是没有必要。

和普通类一样,Record 类可以被 public, protected, private 修饰,也可以不带这些修饰,这样就是 package-private 的。

和一般类不同的是,Record 类的直接父类不是 java.lang.Object 而是 java.lang.Record。但是,Record 类不能使用 extends,因为 Record 类不能继承任何类。

Record 类的属性

一般,在 Record 类声明头部指定这个 Record 类有哪些属性

public record User(long id, String name, int age) {}

同时,可以在头部的属性列表中运用注解:

@Target({ ElementType.RECORD_COMPONENT})
@Retention(RetentionPolicy.RUNTIME)
public @interface A {}
@Target({ ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface B {}
public record User(@A @B long id, String name, int age) {}

但是,需要注意一点,这里通过反射获取 id 的注解的时候,需要通过对应的方式进行获取,否则获取不到,即 ElementType.FIELD 通过 Field 获取,ElementType.RECORD_COMPONENT 通过 RecordComponent 获取:

Field[] fields = User.class.getDeclaredFields();
Annotation[] annotations = fields[0].getAnnotations(); // 获取到注解 @B
RecordComponent[] recordComponents = User.class.getRecordComponents();
annotations = recordComponents[0].getAnnotations(); // 获取到注解 @A

Record 类体

Record 类属性必须在头部声明,在 Record 类体只能声明静态属性

public record User(long id, String name, int age) {
static long anotherId;
}

Record 类体可以声明成员方法和静态方法,和一般类一样。但是不能声明 abstract 或者 native 方法

public record User(long id, String name, int age) {
public void test(){}
public static void test2(){}
}

Record 类体也不能包含实例初始化块,例如:

public record User(@A @B long id, String name, int age) {
{
System.out.println(); //编译异常
}
}

Record 成员

Record 的所有成员属性,都是 public final 非 static 的,对于每一个属性,都有一个对应的无参数返回类型为属性类型方法名称为属性名称的方法,即这个属性的 accessor。前面说这个方法是 getter 方法其实不太准确,因为方法名称中并没有 get 或者 is 而是只是纯属性名称作为方法名。

这个方法如果我们自己指定了,就不会自动生成:

public record User(long id) {
@Override
public long id() {
return id;
}
}

如果没有自己指定,则会自动生成这样一个方法:

  1. 方法名就是属性名称
  2. 返回类型就是对应的属性类型
  3. 是一个 public 方法,并且没有声明抛出任何异常
  4. 方法体就是返回对应属性
  5. 如果属性上面有任何注解,那么这个注解如果能加到方法上那么也会自动加到这个方法上。例如:
public record User(@A @B long id, String name, int age) {}
@Target({
ElementType.RECORD_COMPONENT,
ElementType.METHOD,
})
@Retention(RetentionPolicy.RUNTIME)
public @interface A {}
@Target({ ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface B {}

下面获取 id() 这个方法的注解则会获取到注解 @A

Method id = User.class.getDeclaredMethod("id");
Annotation[] idAnnotations = id.getAnnotations(); //@A

由于会自动生成这些方法,所以 Record 成员的名称不能和 Object 的某些不符合上述条件(即上面提到的 6 条)的方法的名称一样,例如:

public record User(
int wait, //编译错误
int hashcode, //这个不会有错误,因为 hashcode() 方法符合自动生成的 accessor 的限制条件。
int toString, //编译错误
int finalize //编译错误) {
}

Record 类如果没有指定,则默认会生成实现 java.lang.Record 的抽象方法,即hashcode(), equals(), toStrng() 这三个方法。这三个方法的实现方式,在第一节已经详细分析过,这里简单回顾下要点:

  1. hashcode() 在编译的时候自动生成字节码实现,核心逻辑基于 ObjectMethodsmakeHashCode 方法,里面的逻辑是对于每一个属性的哈希值移位组合起来。注意这里的所有调用(包括对于 ObjectMethods 的方法调用以及获取每个属性)都是利用 MethodHandle 实现的近似于直接调用的方式调用的。
  2. equals() 在编译的时候自动生成字节码实现,核心逻辑基于 ObjectMethodsmakeEquals 方法,里面的逻辑是对于两个 Record 对象每一个属性判断是否相等(对于引用类型用Objects.equals(),原始类型使用 ==),注意这里的所有调用(包括对于 ObjectMethods 的方法调用以及获取每个属性)都是利用 MethodHandle 实现的近似于直接调用的方式调用的。
  3. toString() 在编译的时候自动生成字节码实现,核心逻辑基于 ObjectMethodsmakeToString 方法,里面的逻辑是对于每一个属性的值组合起来构成字符串。注意这里的所有调用(包括对于 ObjectMethods 的方法调用以及获取每个属性)都是利用 MethodHandle 实现的近似于直接调用的方式调用的。

Record 构造器

如果没有指定构造器,Record 类会自动生成一个以所有属性为参数并且给每个属性赋值的构造器。我们可以通过两种方式自己声明构造器:

第一种是明确声明以所有属性为参数并且给每个属性赋值的构造器,这个构造器需要满足:

  1. 构造器参数需要包括所有属性(同名同类型),并按照 Record 类头的声明顺序。
  2. 不能声明抛出任何异常(不能使用 throws)
  3. 不能调用其他构造器(即不能使用 this(xxx))
  4. 构造器需要对于每个属性进行赋值。

对于其他构造器,需要明确调用这个包含所有属性的构造器

public record User(long id, String name, int age) {
public User(int age, long id) {
this(id, "name", age);
}
public User(long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
}

第二种是简略方式声明,例如:

public record User(long id, String name, int age) {
public User {
System.out.println("initialized");
id = 1000 + id;
name = "prefix_" + name;
age = 1 + age;
//在这之后,对每个属性赋值
}
}

这种方式相当于省略了参数以及对于每个属性赋值,相当于对这种构造器的开头插入代码。

微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer

image

版权声明
本文为[干货满满张哈希]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/zhxdick/p/14666222.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云