jax-rs jax-ws_如何以大数据的JAX-RS响应的形式将JPA结果流化/序列化

jax-rs jax-ws

有时,有必要通过JPA检索大型数据集(例如,超过1,000,000条记录),并将它们填充到java.util.List的单个实例中是有风险的(内存障碍)。 因此,这是一个快速的解决方案,它可以解决JAX-RS REST资源端点如何仍能通过“页面”对JPA实体进行流式处理或序列化而在不破坏内存约束的情况下及时提供响应。

示例数据库表和JPA实体

数据库表

为了演示如何实现大数据输出,这是我们可以使用的示例MySQL数据库表。

create database large_data_test_db;
use large_data_test_db;create table generated_uuids (record_no bigint not null auto_increment,uuid varchar(100) not null,datetime_generated datetime not null,primary key(record_no),unique(uuid)
);

JPA实体

接下来,定义代表上述表结构的JPA实体类。

GeneratedUuidEntity.java的代码

package com.developerscrappad;import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;@Entity
@Table( name = "generated_uuids" )
@NamedQueries( {@NamedQuery( name = "GeneratedUuidEntity.listAll", query = "SELECT u FROM GeneratedUuidEntity u" ),@NamedQuery( name = "GeneratedUuidEntity.queryRecordsSize", query = "SELECT count(u) FROM GeneratedUuidEntity u" )
} )
public class GeneratedUuidEntity implements Serializable {private static final long serialVersionUID = 12312312234234123L;@Id@GeneratedValue( strategy = GenerationType.IDENTITY )@Column( name = "record_no" )private Long recordNo;@Column( name = "uuid" )private String uuid;@Column( name = "datetime_generated" )@Temporal( TemporalType.TIMESTAMP )private Date datetimeGenerated;public GeneratedUuidEntity() {}public GeneratedUuidEntity( Long recordNo ) {this.recordNo = recordNo;}public GeneratedUuidEntity( Long recordNo, String uuid, Date datetimeGenerated ) {this.recordNo = recordNo;this.uuid = uuid;this.datetimeGenerated = datetimeGenerated;}public Long getRecordNo() {return recordNo;}public void setRecordNo( Long recordNo ) {this.recordNo = recordNo;}public String getUuid() {return uuid;}public void setUuid( String uuid ) {this.uuid = uuid;}public Date getDatetimeGenerated() {return datetimeGenerated;}public void setDatetimeGenerated( Date datetimeGenerated ) {this.datetimeGenerated = datetimeGenerated;}@Overridepublic int hashCode() {int hash = 0;hash += ( recordNo != null ? recordNo.hashCode() : 0 );return hash;}@Overridepublic boolean equals( Object object ) {// TODO: Warning - this method won't work in the case the id fields are not setif ( !( object instanceof GeneratedUuidEntity ) ) {return false;}GeneratedUuidEntity other = ( GeneratedUuidEntity ) object;if ( ( this.recordNo == null && other.recordNo != null ) || ( this.recordNo != null && !this.recordNo.equals( other.recordNo ) ) ) {return false;}return true;}@Overridepublic String toString() {return "com.developerscrappad.GeneratedUuidEntity[ recordNo=" + recordNo + " ]";}
}

GeneratedUuidEntity中定义了两个命名查询。 GeneratedUuidEntity.queryRecordsSize用于查询表的总记录数,而GeneratedUuidEntity.listAll用于检索表中的所有记录。

实施JAX-RS REST资源(Java EE方式)

让我们有一个名称为JPAStreamingRESTResource的JAX-RS REST资源类,其中有一个可用的JPA EntityManager(持久性名称: JPAStreamingPU )要注入,并通过受保护的方法getEntityManager()获得

@Path( "generated-uuids" )
@Stateless( name = "JPAStreamingRESTResource", mappedName = "ejb/JPAStreamingRESTResource" )
public class JPAStreamingRESTResource {@PersistenceContext( unitName = "JPAStreamingPU" )private EntityManager entityManager;protected EntityManager getEntityManager() {return entityManager;}/*** Say "NO" to response caching*/protected Response.ResponseBuilder getNoCacheResponseBuilder( Response.Status status ) {CacheControl cc = new CacheControl();cc.setNoCache( true );cc.setMaxAge( -1 );cc.setMustRevalidate( true );return Response.status( status ).cacheControl( cc );}
}

此外,我们有一个名为getNoCacheResponseBuilder()的方法,该方法将获得一个非缓存的javax.ws.rs.core.Response.ResponseBuilder ,这样以后就不会再得到奇怪的缓存结果了。

JPA调用方法

接下来,让我们在资源类中定义两个方法,即:

queryGeneratedUuidRecordsSize() –检索表中的记录总数

private int queryGeneratedUuidRecordsSize() {return getEntityManager().createNamedQuery( "GeneratedUuidEntity.queryRecordsSize", Long.class ).getSingleResult().intValue();
}

listAllGeneratedUuidEntities() –从表中检索所有数据,但具有某些限制条件,例如记录的开始位置(recordPosition)和每次往返数据库的最大记录数(recordsPerRoundTrip)。 目的是“分页”结果,以使结果列表不会过分膨胀。 我们稍后会看到它的作用。

private List<GeneratedUuidEntity> listAllGeneratedUuidEntities( int recordPosition, int recordsPerRoundTrip ) {return getEntityManager().createNamedQuery( "GeneratedUuidEntity.listAll" ).setFirstResult( recordPosition ).setMaxResults( recordsPerRoundTrip ).getResultList();
}

让流开始

现在,让我们实现资源端点方法,至少从理论上讲,该方法可以在不损害大小的情况下检索数据。 此方法将返回JSON响应,其数据格式为:

{"result": [{"record_no": 1,"uuid": "34d99089-3e36-4f00-ab93-846b61771eb3","datetime_generated": "2015-06-28 21:02:23"},…]
}
@GET@Path( "list-all" )@Produces( "application/json" )@TransactionAttribute( TransactionAttributeType.NEVER )public Response streamGeneratedUuids() {// Define the format of timestamp outputSimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );return getNoCacheResponseBuilder( Response.Status.OK ).entity( new StreamingOutput() {// Instruct how StreamingOutput's write method is to stream the data@Overridepublic void write( OutputStream os ) throws IOException, WebApplicationException {int recordsPerRoundTrip = 100;                      // Number of records for every round trip to the databaseint recordPosition = 0;                             // Initial record position indexint recordSize = queryGeneratedUuidRecordsSize();   // Total records found for the query// Start streaming the datatry ( PrintWriter writer = new PrintWriter( new BufferedWriter( new OutputStreamWriter( os ) ) ) ) {writer.print( "{\"result\": [" );while ( recordSize > 0 ) {// Get the paged data set from the DBList<GeneratedUuidEntity> generatedUuidEntities = listAllGeneratedUuidEntities( recordPosition, recordsPerRoundTrip );for ( GeneratedUuidEntity generatedUuidEntity : generatedUuidEntities ) {if ( recordPosition > 0 ) {writer.print( "," );}// Stream the data in Json object formatwriter.print( Json.createObjectBuilder().add( "record_no", generatedUuidEntity.getRecordNo() ).add( "uuid", generatedUuidEntity.getUuid() ).add( "datetime_generated", df.format( generatedUuidEntity.getDatetimeGenerated() ) ).build().toString() );// Increase the recordPosition for every record streamedrecordPosition++;}// update the recordSize (remaining no. of records)recordSize -= recordsPerRoundTrip;}// Done!writer.print( "]}" );}}} ).build();}

电源线说明:

实际上,这很简单。 诀窍是通过重写write()方法来定义匿名类StreamingOutput的表达式,该方法首先通过queryGeneratedUuidRecordsSize()查询总记录大小,然后通过listAllGeneratedUuidEntities()逐页检索记录。 此方法将多次访问数据库,具体取决于定义的recordsPerRoundTrip值。

JPAStreamingRESTResource.java的完整源代码:

package com.developerscrappad;import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.List;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.json.Json;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;@Path( "generated-uuids" )
@Stateless( name = "JPAStreamingRESTResource", mappedName = "ejb/JPAStreamingRESTResource" )
public class JPAStreamingRESTResource {@PersistenceContext( unitName = "JPAStreamingPU" )private EntityManager entityManager;private List<GeneratedUuidEntity> listAllGeneratedUuidEntities( int recordPosition, int recordsPerRoundTrip ) {return getEntityManager().createNamedQuery( "GeneratedUuidEntity.listAll" ).setFirstResult( recordPosition ).setMaxResults( recordsPerRoundTrip ).getResultList();}private int queryGeneratedUuidRecordsSize() {return getEntityManager().createNamedQuery( "GeneratedUuidEntity.queryRecordsSize", Long.class ).getSingleResult().intValue();}protected EntityManager getEntityManager() {return entityManager;}/*** Say "NO" to response caching*/protected Response.ResponseBuilder getNoCacheResponseBuilder( Response.Status status ) {CacheControl cc = new CacheControl();cc.setNoCache( true );cc.setMaxAge( -1 );cc.setMustRevalidate( true );return Response.status( status ).cacheControl( cc );}@GET@Path( "list-all" )@Produces( "application/json" )@TransactionAttribute( TransactionAttributeType.NEVER )public Response streamGeneratedUuids() {// Define the format of timestamp outputSimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );return getNoCacheResponseBuilder( Response.Status.OK ).entity( new StreamingOutput() {// Instruct how StreamingOutput's write method is to stream the data@Overridepublic void write( OutputStream os ) throws IOException, WebApplicationException {int recordsPerRoundTrip = 100;                      // Number of records for every round trip to the databaseint recordPosition = 0;                             // Initial record position indexint recordSize = queryGeneratedUuidRecordsSize();   // Total records found for the query// Start streaming the datatry ( PrintWriter writer = new PrintWriter( new BufferedWriter( new OutputStreamWriter( os ) ) ) ) {writer.print( "{\"result\": [" );while ( recordSize > 0 ) {// Get the paged data set from the DBList<GeneratedUuidEntity> generatedUuidEntities = listAllGeneratedUuidEntities( recordPosition, recordsPerRoundTrip );for ( GeneratedUuidEntity generatedUuidEntity : generatedUuidEntities ) {if ( recordPosition > 0 ) {writer.print( "," );}// Stream the data in Json object formatwriter.print( Json.createObjectBuilder().add( "record_no", generatedUuidEntity.getRecordNo() ).add( "uuid", generatedUuidEntity.getUuid() ).add( "datetime_generated", df.format( generatedUuidEntity.getDatetimeGenerated() ) ).build().toString() );// Increase the recordPosition for every record streamedrecordPosition++;}// update the recordSize (remaining no. of records)recordSize -= recordsPerRoundTrip;}// Done!writer.print( "]}" );}}} ).build();}
}

小心

请记住要调整应用程序服务器的响应连接超时值,以防止REST或Http Client抛出java.io.IOException过早的EOF异常。

测试它

要测试它是否有效,只需在表中加载567条记录即可。 然后,让单元测试调用端点URL,并使用以下单元测试代码将检索到的JSON数据保存到文件中(使用Apache HttpClient):

JPAStreamingUnitTest.java的代码:

package com.developerscrappad;import java.io.File;
import java.io.FileInputStream;
import static org.junit.Assert.*;import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.UUID;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;public class JPAStreamingUnitTest {private static final String dbDriverClassname = "com.mysql.jdbc.Driver";private static final String dbUrl = "jdbc:mysql://localhost:3306/large_data_test_db";private static final String username = "username";private static final String password = "password";private static final int numberOfRecords = 567;private static final String jsonResultOutputFilename = "testing123.json";@BeforeClasspublic static void setUpClass() {try {Class.forName( dbDriverClassname );try ( Connection conn = DriverManager.getConnection( dbUrl, username, password ) ) {String insertSQL = "insert into generated_uuids (uuid, datetime_generated) values (?, now())";try ( PreparedStatement stmt = conn.prepareStatement( insertSQL ) ) {for ( int i = 0; i < numberOfRecords; i++ ) {System.out.println( "Inserting row: " + i );stmt.setString( 1, UUID.randomUUID().toString() );stmt.executeUpdate();}}}} catch ( final Exception ex ) {ex.printStackTrace();fail( ex.getMessage() );}}@AfterClasspublic static void tearDownClass() {try {Class.forName( dbDriverClassname );try ( Connection conn = DriverManager.getConnection( dbUrl, username, password ) ) {String truncateSQL = "truncate generated_uuids";conn.createStatement().executeUpdate( truncateSQL );}new File( System.getProperty( "java.io.tmpdir" ), jsonResultOutputFilename ).delete();} catch ( final Exception ex ) {ex.printStackTrace();fail( ex.getMessage() );}}@Testpublic void testJPAStreaming() {String url = "http://localhost:8080/JPAStreaming/rest-api/generated-uuids/list-all/";try {CloseableHttpClient httpclient = HttpClients.createDefault();HttpGet httpGet = new HttpGet( url );try ( CloseableHttpResponse response1 = httpclient.execute( httpGet ) ) {System.out.println( response1.getStatusLine() );HttpEntity entity1 = response1.getEntity();Files.copy( entity1.getContent(), FileSystems.getDefault().getPath( System.getProperty( "java.io.tmpdir" ), jsonResultOutputFilename ) );}// Validatetry ( JsonReader jsonReader = Json.createReader( new FileInputStream( new File( System.getProperty( "java.io.tmpdir" ), jsonResultOutputFilename ) ) ) ) {JsonObject jsonObj = jsonReader.readObject();assertTrue( jsonObj.containsKey( "result" ) );JsonArray jsonArray = jsonObj.getJsonArray( "result" );assertEquals( numberOfRecords, jsonArray.size() );SimpleDateFormat validationDF = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );for ( int i = 0; i < jsonArray.size(); i++ ) {JsonObject generatedUuidJsonObj = jsonArray.getJsonObject( i );int recordNumber = generatedUuidJsonObj.getInt( "record_no" );assertTrue( recordNumber > 0 );try {UUID.fromString( generatedUuidJsonObj.getString( "uuid" ) );} catch ( IllegalArgumentException ex ) {fail( "Invalid UUID format at record number: " + recordNumber );}try {validationDF.parse( generatedUuidJsonObj.getString( "datetime_generated" ) );} catch ( final NullPointerException | ParseException ex ) {fail( "datetime_generated field must not be null and must be of format yyyy-MM-dd HH:mm:ss" );}}}} catch ( final Exception ex ) {ex.printStackTrace();fail( ex.getMessage() );}}
}

我们完成了。 感谢您的阅读,希望对您有所帮助。

翻译自: https://www.javacodegeeks.com/2015/07/how-to-streamserialize-jpa-result-as-jax-rs-response-for-large-data.html

jax-rs jax-ws

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337792.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php 字符串 字典序序排序,C++ 怎么实现字典序排序法,自然排序

C 如何实现字典序排序法,自然排序类似PHP的natcasesort函数,整了一天没有头绪.数组是vector排序前:[0] > IMG0.png[1] > IMG3.png[2] > img1.png[3] > img10.png[4] > img12.png[5] > img2.png排序后:[0] > IMG0.png[1] > img1.png[2] > img2.png[3…

如何优雅地利用C++编程从1乘到20?

点击蓝字关注我们知乎的一个问题&#xff1a;答主&#xff1a;小白白。数学家版本&#xff1a;&#xff08;简单利索&#xff0c;深藏功与名&#xff09;#include <iostream> #include <cmath> int main() {std::cout << std::tgamma(20 1) << std::e…

春天猫rtsy_春天:注入列表,地图,可选对象和getBeansOfType()陷阱

春天猫rtsy如果您使用Spring框架超过一个星期&#xff0c;那么您可能已经知道此功能。 假设您有多个bean实现了给定的接口。 尝试仅自动连接此类接口的一个bean注定会失败&#xff0c;因为Spring不知道您需要哪个特定实例。 您可以通过使用Primary批注来指定一个优先于其他实现…

matlab内维尔差值代码,计算方法上机练习数值积分(包括两次作业).PDF

计算方法上机练习 数值积分(包括两次的作业 )马骢问题&#xff1a; 《计算方法引论》pp.132–133 练习分析&#xff1a;在实际应中基本的数值积分&#xff0c;可 以分为以下种类 &#xff1a;• 牛顿型 &#xff1a;在给定有 限区 间上求等距节 点上 的函数值 。如牛顿-柯茨法则…

用这个C语言骰子代码做选择

点击蓝字关注我们我相信很多人都遇到选择的事情&#xff0c;比较正常的就是&#xff0c;我拿了两个offer要如何选择。用下面这段C代码挺好的&#xff0c;你可以自己加上自己喜欢的判断。#include<stdio.h> #include<stdlib.h> #include<time.h>int main() {i…

java ee的小程序_Java EE应用程序的单片到微服务重构

java ee的小程序您是否曾经想过将现有的Java EE单体应用程序重构为基于微服务的应用程序需要做什么&#xff1f; 该博客说明了一个简单的购物车示例如何转换为基于微服务的应用程序&#xff0c;以及围绕它的一些担忧。 整体和基于微服务的应用程序的完整代码库位于&#xff1a…

matlab工程计算及应用 课程名称,《 MATLAB 工程计算及应用》教学大纲课程名称MATLAB 工程计算及应用.pdf...

《MATLAB 工程计算及应用》教学大纲课程名称 MATLAB 工程计算及应用 课程编号 02T5071课程英文名称 Applications of MATLAB课程性质 选修 学时和学分 32(上机 10)/1 适用专业 工科各专业大纲执笔人 蔡哓君、郭炜 审核人 蔡晓君 先修要求 高等数学、线性代数一、课程基本目的&a…

C语言数据结构:什么是树?什么是二叉树?

点击蓝字关注我们前言在之前的数据结构学习中&#xff0c;我们学习了顺序表、链表、栈、队列这几种结构它们都是用链表或者数组的方式来实现的&#xff0c;主要考察我们对结构体的运用今天让我们来学习一个新的数据结构&#xff0c;也就是下面这副图里面的树啊不好意思&#xf…

git hok json_从战中反弹:将Git提交信息作为JSON返回

git hok json在某些情况下&#xff0c;我们必须知道部署到远程服务器的Web应用程序的确切版本。 例如&#xff0c;客户可能想知道我们是否已经在服务器X上部署了错误修复程序。 当然&#xff0c;我们可以尝试使用“传统”方法找到该问题的答案。 问题是&#xff1a; 没有人不…

oracle plsql异常,【Oracle篇】异常处理和PLSQL

一、所有的PL/SQL异常都具有以下几个基本特征&#xff1a; 错误类型&#xff1a;表示了错误是ORA错误还是PLS错误 错误代号&#xff1a;一串表示错误代号的数字 错误文本&#xff1a;错误消息文本&#xff0c;包括错误代号 二、PL/SQL会产生两种类型的错误 &#xff1a; ORA错误…

如何把CPP源程序改写成C语言?

点击蓝字关注我们曾经参与过公司的bpp项目&#xff0c;就是bluetooth print profile。由于使用了hpijs的开源包&#xff0c;但是是C&#xff0b;&#xff0b;的。由于C&#xff0b;&#xff0b;解释器比C语言解释器占用的存储空间要大500k左右。为了节省有限的存储空间&#xf…

php cdi_使用Fabric8在CDI管理的bean中注入Kubernetes Services

php cdi序幕 我在Kubernetes中最喜欢的是发现服务的方式。 为什么&#xff1f; 主要是因为用户代码不必处理注册&#xff0c;查找服务&#xff0c;也没有网络意外&#xff08;如果您曾经尝试过基于注册表的方法&#xff0c;那么您就会知道我在说什么&#xff09; 。 这篇文章…

oracle日志恢复启用命令,oracle 日志恢复数据详解

1&#xff1a;首先查找redo&#xff0c;如果redo有可供恢复的信息&#xff0c;就那redo中的信息进行恢复&#xff0c;此时一般在恢复时&#xff0c;类似如下:SQL> recover database;Media recovery complete.2&#xff1a;如果在redo中没有找到可供恢复的信息&#xff0c;or…

9 个很酷的 CMD 命令

点击蓝字关注我们ipconfig功能&#xff1a;查询本机IP地址操作方法&#xff1a;只要在在打开的cmd命令界面中输入“ipconfig”就可以了。msg功能&#xff1a;向对方电脑发送一条文本提示操作方法&#xff1a;首先你要知道对方的IP地址&#xff0c;接下来输入命令“msg /server:…

使用java进行婚礼庆祝_#102030:在30天内运行20 10K,庆祝Java 20年

使用java进行婚礼庆祝1995年5月23日是技术史上的重要时刻。 业界似乎并未意识到当天发布的语言会在未来几年内完全改变技术的格局。 Java将在今年的同一天庆祝20岁生日。 Java 20年&#xff0c;哇&#xff01; 回顾20年前的存储器时代&#xff0c;思考一下Java的发明时间/方式…

oracle 插入出错_使用sqlca打印错误原因,ORACLE-Proc:SQLCA

SQL 通信区是用下列语句描述的&#xff1a;EXEC SQL INCLUDE SQLCA&#xff1b;此部分提供了用户运行程序的成败记录和错误处理。SQLCA的组成SQLCA是一个结构类型的变量&#xff0c;它是ORACLE 和应用程序的一个接口。在执行 Pro*C程序时&#xff0c; ORACLE 把每一个嵌入SQL语…

这几行 C++ 代码,真的骚!

点击蓝字关注我们事情是这么一回事&#xff1a;国外有个大佬在StackExchange上发起了一个叫做 Tweetable Mathematical Art 的比赛。参赛者需要用C编写代表三原色的RD、GR、BL三个函数&#xff0c;每个函数都不能超过 140 个字符。每个函数都会接到 i 和 j 两个整型参数&#x…

sts集成jboss_JBoss BPM Travel Agency演示与现代BPM数据集成

sts集成jboss不久前&#xff0c;我们启动了一个规模较大的JBoss Travel Agency演示项目&#xff0c;以展示JBoss BPM Suite的一些更有趣的功能。 我们提供了一系列视频 &#xff0c;不仅向您展示了如何安装它&#xff0c;项目中各种规则和流程工件的含义&#xff0c;还向您介绍…

详解C语言的C#数组

点击蓝字关注我们数组是一种存储相同类型元素的固定大小顺序集合。数组用于存储数据集合&#xff0c;但一般会将数组视为存储在连续存储器位置的相同类型的变量的集合。如果要存储表示100名称学生的分数&#xff0c;需要独立地声明100整数变量。例如&#xff1a;number0.number…

jdk注解_我们正在下注:这个注解很快就会出现在JDK中

jdk注解Yahor最近提出的Stack Overflow问题引起了我的兴趣&#xff1a; 如何在Java 8编译时确保方法签名“实现”功能接口 。 这是一个很好的问题。 让我们假设以下名义类型&#xff1a; FunctionalInterface interface LongHasher {int hash(long x); }该类型强加了清晰的合同…