三、网络编程
1.网络编程概述
Java 是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序。
2.网络的基础
(1)计算机网络
把分布在不同地理区域的计算机与专门的外部设备用通讯线路互联成一个规模大,功能强的网络系统,从而使众多的计算机可以方便地互传信息,共享硬件,软件,数据信息等资源。
(2)网络编程的目的
直接或间接的通过网络协议与其他计算机实现数据互换,进行通讯。
3.网络通讯的要素概述
(1)通讯双方的地址:IP 地址,端口号
(2)一定的规则,即网络通讯的协议,有两套参考模型:
① OSI 参考模型:模型过于理想化,未能在因特网上进行广泛推广
② TCP/IP 参考模型(TCP/IP协议):事实上的国际标准
(3)网络通讯要素1:IP 地址和端口号
1)IP 地址:InetAddress
a. 唯一的标识 Internet 上的计算机(通讯实体)
b. 本地环回地址(127.0.0.1):127.0.0.1/localhost
c. IP地址的分类方式1:IPV4 和 IPV6
a) IPV4:4 个字节组成,4 个 0~255,大概 42 亿,30 亿都在北美,亚洲 4 亿,2011 年初已经用完,以点分十进制表示,比如:192.16.0.211
b) IPV6: 128位(16个字节),写成 8 个无符号整数,每个整数用四个十六进制位表示,数之间用冒号(:)分开,例如:3ffe:3201:1401:1280:c8ff:fe4d:db39:984
d. IP 地址分类方式 2:公网地址(万维网使用)和私有地址(局域网使用)。其中 192.168. 开头的就是私有地址,范围是 192.168.0.0~192.168.255.255,专门为组织机构内部使用。
2)端口号标识正在计算机上运行的进程(程序)
a. 不同的进程有不同的端口号,这样同一个操作系统中同时允许多个进程进行通讯。
b. 被规定为一个 16 位的整数:0~65535
c. 端口的分类
a) 公认端口:0~1023,被预先定义的服务通讯占用(如:HTTP 占用端口号 80,FTP 占用 21 端口,Telnet 占用23端口),程序员不能去占用这些端口,
b) 注册端口:1024~49151,分配给用户进程或应用程序(如 Tomcat 占用端口 8080,MySQL 占用端口3306,Oracle 占用端口 1521)
c) 动态/私有端口:49152~65535
d. 端口号和 IP 地址组合得到一个网络套接字:Socket
3)InetAddress 类:Java 中表示 IP 地址的类
a. Internet 上的主机又有两种表示方式:
a) 域名(hostName):www.baidu.com,每个域名都对应了一个或多个IP地址,通过域名服务器(DNS)将域名解析成 IP 地址的形成来通讯,域名比 IP 地址更容易记忆。
b) IP 地址(hostAddress):39.156.66.14
b. InetAddress 类主要表示 IP 地址,有两个子类:Inet4Address,Inet6Address。
c. InetAddress 类对象包含有一个 Internet 主机地址的域名或IP地址:www.baidu.com或39.156.66.14
d. 域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转换为IP地址,这样才能和主机建立连接。----域名解析
e. InetAddress 类没有提供公共的构造器,而是提供了如下几个静态方法来获取 InetAddress:
public static InetAddress getLocalHost(); //获取本机对应的 InetAddress
public static InetAddress getByName(String host); //通过主机名(域名)或IP地址获取InetAddress
f. InetAddress 常用的方法
public String getHostAddress():返回 IP 地址字符串(以文本表现形式)
public String getHostName():获取此 IP 地址的主机名
public boolean isReachable(int timeout):测试是否可以达到该地址
示例:
(4)网络通讯的要素2:网络协议
1)网络通讯协议
计算机网络中实现通讯必须要有一些约定,即通信协议,对速率,传输代码,代码结构,传输控制步骤,出错控制等指定标准。
2)问题:网络协议太复杂
计算机网络通讯涉及内部比较多,比如指定源地址和目的地地址,加密解密,压缩解压缩,差错控制,路由控制,如何实现如此复杂的网络协议呢
3)通信协议分层的思想
在指定协议时,把复杂成分分解成一些简单的成分,再将它们复合起来,最常用的复合方式就是层次方式,即同层间可以通讯,上一层可以调用下一层,而与再一层不发生关系,各层互不影响,利于系统的开发和扩展。
4)TCP/IP 协议簇
a. 传输层协议中有两个非常重要的协议:
传输层控制协议 TCP(Transmission Control Protocol)
用户数据报协议 UDP(User Datagram Protocol)
b. TCP/IP 以其两个主要协议:传输层控制协议(TCP)和网络互联协议(IP)而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议
c. IP(Internet Protocol)协议是网络层的主要协议,支持网间互联的数据通讯。
d. TCP/IP 协议模型从更实用的角度出发,形成了高效的四层体系结构,即物理链路层,IP层,传输层,应用层。
5)TCP 和 UDP
a. TCP 协议:
使用 TCP 协议前,需要建立 TCP 连接,形成传输数据通道,
传输前,采用“三次握手”方式,点对点通信,是可靠的通信
TCP 协议进行通信的两个应用程序:客户端和服务器
在连接中可进行大数据量的传输
传输完毕,需要释放已建立的连接,效率低。
b. UDP 协议
将数据,源地址,目的地封装成数据报,不需要建立连接
每个数据报的大小限制在 64K 内
发送不管对方是否准备好,接收方收到也不确认,所以是不可靠
可以广播发送
发送数据结束时无需释放资源,开销小,速度快。
(5)Socket 编程
1)利用套接字(Socket)开发网络应用程序已被广泛使用,以至于成为了事实上的标准
2)网络上具有唯一标识的 IP 地址和端口号组合在一起才能构成唯一能识别的标识符套接字
3)通信两端都要有 Socket,是两台机器通信的端点。
4)网络通信其实就是 Socket 通讯
5)Socket 允许程序把网络连接当成一个流,数据在两个 Socket 间通过 IO 传输
6)一般主动发送通信的应用程序是客户端,等待通信请求的是服务端。
7)Socket 分类
a. 流套接字(Stream Socket):使用 TCP 提供的字节流服务
b. 数据报套接字(Datagram Socket):使用 UDP 提供“尽力而为”的数据报服务
8)Socket 类的常用构造器:
public Socket(InetAddress address,int port):创建一个流套接字(TCP)并将其连接到指定IP地址和端口号。
public Socket(String host,int port):创建一个流套接字并将其连接到指定主机上的指定端口。
9)Socket类的常用方法:
public InputStream getInputStream():返回此套接字的输入流。可以用于接收网络消息。
public OutputStream getOutputStream():返回此套接字的输出流。可以用于发送网络消息
public InetAddress getInetAddress():此套接字连接到的远程IP地址;如果套接字是未连接的,则返回null。
public InetAddress getLocalAddress():获取套接字绑定的本地地址。即本地的IP地址
public int getPort():此套接字连接到的远程端口号;如果套接字尚未连接,返回0
public int getLocalPort():返回此套接字绑定到的本地端口。如果尚未绑定套接字,则返回-1.
public void close():关闭此套接字。套接字被关闭后,便不可在以后的网络中使用(即无法重新连接或重新绑定)。需要创建新的套接字对象。关闭此套接字也将会关闭该套接字的 InputStream 和 OutputStream
public void shutdownInput():如果在此套接字上调用该方法后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。即不能从此套接字的输入流中接收任何数据。
public void shutdownOutput():禁用此套接字的输出流。即不能通过此套接字的输出流发送任何数据。
(6)TCP 网络编程:基于 Socket 的 TCP 编程
示例:实现服务器端可以同时接收多个客户端上传文件
服务端:
package com.edu.net;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//文件上传多线程服务端
public class TCPServer {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
//1. 创建一个带缓冲的线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
//2. 创建服务端的 Socket,监听端口,等待客户端连接上来
ServerSocket serverSocket = new ServerSocket(6666);
//实现多个客户端连接服务器的操作
while(true) {
//3. 注意:此时返回的 socket 就是指当前连接上来的客户端 Socket
Socket socket = serverSocket.accept();//该方法会阻塞,直到有客户端连接上来为止
//4. 一旦有一个客户端连接上来,我们就创建一个线程来处理该客户端的请求
Runnable r = () -> {
try {
// 5. 显示一下当前是哪个客户端连接上来了
InetAddress ipAddr = socket.getInetAddress();//得到客户端的 IP 地址
String ip = ipAddr.getHostAddress();//获取 IP 地址字符串
System.out.println("客户端:" + ip + ",连接到服务器...");
//6. 获得 socket 的输入流
InputStream in = socket.getInputStream();
BufferedInputStream bis = new BufferedInputStream(in);
// 7. 创建输出流,指定上传文件的目录,比如我们需要在 F 盘中创建一个 upload 目录
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("f:/upload/" + ip + "(" +
System.currentTimeMillis() + ").jpg"));
byte[] buf = new byte[1024];
int len = -1;
while((len = bis.read(buf)) != -1) {
//写入文件
bos.write(buf, 0, len);
}
//8. 完成之后输出一些信息给客户端
OutputStream out = socket.getOutputStream();
out.write("图片上传成功".getBytes());
//9. 关闭资源
out.close();
bos.close();
bis.close();
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
//将上面的任务放到线程池中执行
threadPool.execute(r);
}
}
}
客户端:
package com.edu.net;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class TCPClient {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
//1. 创建客户端 Socket,指定服务器的 IP 地址和端口去连接服务器
Socket socket = new Socket("192.168.110.56", 6666);
//2. 获取 Socket 输出流,把图片文件写给服务器
OutputStream out = socket.getOutputStream();
//3. 创建输入流,读取本地图片文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("d:/img.jpg"));
//4. 写出图片数据给服务器
byte[] buf = new byte[1024];
int len = -1;
while((len = bis.read(buf)) != -1) {
out.write(buf, 0, len);
}
//5. 写完之后,关闭输出流
socket.shutdownOutput();
//6. 读取服务器反馈的信息
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int length = in.read(buffer);
System.out.println(new String(buffer, 0, length));
//7. 关闭资源
in.close();
bis.close();
out.close();
socket.close();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
(7)UDP网络编程:UDP网络通讯
1)类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议的网络程序
2)UDP 数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证 UDP 数据报能够安全的到达目的地,也不能确定什么时候能到达。
3)DatagramPacket 对象封装了 UDP 数据报,在数据报中包含了发送端 IP 地址和端口号以及接收端的 IP 地址和端口。
4)UDP 协议中每个数据报都给出完整的地址信息,因此无需建立发送方和接收方的连接,类似与发快递包裹一样。
5)DatagramSocket 类的常用方法
public DatagramSocket(int port):创建数据报套接字并将其绑定在本机指定端口。套接字将被绑定到通配符地址,IP 地址由内核来选择。
public DatagramSocket(int port, InetAddress Iaddr):创建数据报套接字,将其绑定到指定本地地址,本地端口必须在0到65535之间,如果 IP 地址是 0.0.0.0,套接字将被绑定到通配符地址,IP 地址由内核选择。
public void close(): 关闭此数据报套接字。
public void send(DatagramPacket p) : 从此套接字发送数据报报。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的IP地址和远程主机的端口号。
public void receive(DatagramPacket p):从此套接字接收数据报报。当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据。数据报报也包含发送方的IP地址和端口号。此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比报的长度长,该信息将被截断。
public InetAddress getLocalAddress():获取套接字绑定的本地地址。
public int getLocalPort():返回此套接字绑定的本地主机上的端口号。
public InetAddress getInetAddress():返回此套接字连接的地址。如果套接字未连接,则返回null。
public int getPort():返回此套接字的端口。如果套接字未连接,则返回-1.
public DatagramPacket(byte[] buf,int length):构造 DatagramPacket,用来接收长度为 length 的数据包。length 参数必须小于等于buf.length。
public DatagramPacket(byte[] buf,int length,InetAddress address,int port):构造数据报报,用来将长度为length 的包发送到指定主机上的指定端口号。length 参数必须小于等于 buf.length。
public InetAddress getAddress():返回某台机器的IP地址,此数据报将要发往该机器或者是从该机器接收到的。
public int getPort():返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。
public byte[] getData():返回数据缓冲区。接收到的或将要发送的数据从缓冲区的偏移量 offset 处开始,持续length 长度。
public int getLength():返回将要发送或接收到的数据的长度。
6)流程:
a. 构造 DatagramSocket 和 DatagramPacket
b. 建立发送端和接收端
c. 建立数据报
d. 调用 Socket 发送,接收
e. 关闭Socket
发送端和接收端是两个独立的程序
示例:
发送端:
package com.edu.net;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
public class Sender {
public static void main(String[] args) {
try {
//构建一个用户数据报套接字
DatagramSocket socket = new DatagramSocket();
//准备数据
String str = "UDP 数据报测试数据";
byte[] bytes = str.getBytes();
//这里不能用str.length(),因为字符串是2个字节表示一个字符,我们这里需要的是多少个byte字节
int length = str.getBytes().length;
//构建一个数据报 DataGramPacket,指定要发送的数据,长度,以及要去向的目的地的IP地址和端口
DatagramPacket packet = new DatagramPacket(bytes, length, InetAddress.getByName("192.168.110.565"), 8888);
//发送数据报
socket.send(packet);
//关闭
socket.close();
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
接收端:
package com.edu.net;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class Receiver {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
//创建DataGramSocket,监听 8888 端口
DatagramSocket socket = new DatagramSocket(8888);
//创建 buf 来接收数据
byte[] buf = new byte[1024];
int len = buf.length;
//构建数据报
DatagramPacket packet = new DatagramPacket(buf, len);
//接收数据报
socket.receive(packet);//该方法会阻塞直到收到发送方的数据报
System.out.println("接收到的数据:" + new String(buf, 0, len));
//关闭
socket.close();
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
结果:先启动接收端,再启动发送端:
(8)URL 类
1)URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一个资源的地址
2)它是一种具体的 URI ,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。
3)通过 URL 我们可以访问 Internet 上的各种网络资源,比如常见的 www,ftp 站点,浏览器通过解析给定的URL 可以在网络上查找相应的文件或其他资源。
4)URL 的基本结构由5部分组成:
<传输协议>://<主机名/域名/IP地址>:<端口号>/文件名#片段名?查询名列表
例如:
http://192.168.1.100:8080/helloworld/index.jsp#a?username=xiaoming&password=123
#片段名:即锚点,例如看小说,直接定位到章节
查询名列表/请求参数:参数名=参数值&参数名=参数值
5)URL 类构造器
a. 为了表示 URL,java.net 中实现了类 URL,我们可以通过下面的构造器来初始化 URL 对象:
public URL(String spec):通过一个表示 URL 地址的字符串来构造一个URL对象
例如:URL url = new URL(“http://www.163.com”);
public URL(URL conext,String spec):通过基URL和相对URL构造一个URL对象。
例如:URL url= new URL(url,”download.html”); // http://www.163.com/download.html
public URL(String protocol,String host,String file)
例如:URL url = new URL(“http”,”www.163.com”,”download.html”); // http://www.163.com/download.html
public URL(String protocol,String host,int port,String file)
例如:URL url = new URL(“http”,”www.163.com”,80,”download.html”)
b. URL 类的构造器都声明了抛出非运行时异常,必须要对这个异常进行处理,通常是加上try...catch语句进行捕获。
(9)针对于 HTTP 协议 URLConnection 类
1)URL 类的方法 openConnection():能够从网络上读取数据
2)若希望输出数据,例如向服务端(Tomcat 服务器(基于 HTTP 协议的服务器))发送一些数据,则必须先与URL建立连接,然后才能对其进行读写,此时需要使用 URLConnection.
3)URLConnection:表示到 URL 所引用的远程对象的连接,当与一个 URL 建立连接时,首先要在一个 URL 对象上通过 openConnection() 生成对应的 URLConnection 对象,如果连接失败,将产生IOException,例如:
URL url = new URL(“http://localhost:8080/index.jsp”);
URLConnection conn = url.openConnection();
4)通过 URLConnection 对象获取输入和输出流,就可以和 Tomcat 服务器进行交互,常见的方法有:
public Object getContent() throws IOException
public int getConentLength()
public String getContentType()
public long getDate()
public long getLastModified()
public InputStream getInputStream() throws IOException
public OutputStream getOutputStream() throws IOException
(10)URI
是 Uniform Resource Identifier,统一资源标识符,用来唯一的标识一个资源,而 URL 是Uniform Resource Locator,统一资源定位符,它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源,而URN,Uniform Resource Name,统一资源命名,是通过名字来标识资源,比如:mailto:xiaoming@126.com,也就是说,URI 是一种抽象的,高层次概念定义统一资源标识,而 URL 和 URN 则是具体的资源标识的方式,URL 和 URN 都是一种 URI。
四、JDK17 新特性
1. JDK8 和 JDK17共存:保持先前的 JDK8 的环境变量配置不变
安装 JDK17:保持默认的路径一路向前下一步。
(1) 在系统环境变量中新增 JDK8 和 JDK17 两个环境变量,分别引用 JDK8 和 JDK17 的安装目录
(2) 然后将之前配置的系统环境变量 JAVA_HOME 改成如下:比如我们改来使用 JDK17
以后我们要切换回JDK8,那么就将“变量值”改为“%JDK8%”
然后系统环境变量Path的值保持原来的配置,然后验证一下:
(3) Eclipse 中配置 JDK17
新建一个工程:
从 Springboot3.0 开始,已经不⽀持 JDK8 了。 从 3.0 开始,转变为 JDK17。
Java17 是长期支持版本(LTS),于 2021 年 9 月发布,是继 Java 11 之后的又一个重要的稳定版本。Java17 提供了许多语言特性和 API 改进,同时也移除了一些过时的功能,显著提高了语言表达能力和运行时性能。
2.下面我们来看一下Java17的新特性:
1. 密封类(Sealed Classes)
2.1 什么是密封类?
密封类是一种新特性,允许开发者严格控制子类的继承。通过定义一个密封类,可以限制哪些类能够继承它。
2.2 使用场景
更精确地控制继承关系。
更好的代码安全性和可读性。
避免了意外的类扩展。
3.3 示例
2.4 关键字说明
sealed:定义密封类。
permits:指定允许继承密封类的子类。
non-sealed:允许子类取消密封。
final:子类不能再被继承。
3. var关键字
从 Java10 开始,var 被引⼊
var name = "zhangsan";
var age = 10;
上述代码中,编译器会⾃动推断出 name 是⼀个 String 类型,age 是⼀个 int 类型。
为什么使⽤ var?
使⽤ var 可以使代码更简洁。有时候,类型名称可能会⾮常⻓,例如泛型。var 就像是⼀个简化器,让你不必反复写出繁琐的类型名。
使⽤注意事项:
1. 不能使⽤ var 来声明字段(类属性)
2. 不能使⽤ var 来声明⽅法参数
3. 不能使⽤ var 来声明⽅法返回类型
4. var 声明变量必须初始化,但是不能初始化为 null
例如:
4. 文本块
这个更新非常实用。在没有这个特性之前,编写长文本非常痛苦。虽然 IDEA 等集成开发工具可以自动处理,但最终效果仍然丑陋,充满拼接符号。现在,通过字符串块,我们可以轻松编写 JSON、HTML、SQL 等内容,效果更清爽。
示例:
5. NullPointerException 增强
空指针异常(NPE)一直是 Java 程序员的痛点,因为报错信息无法直观地指出哪个对象为空,只抛出一个 NullPointerException 和一堆堆栈信息,定位问题耗时且麻烦。尤其在遇到级联调用的代码时,逐行排查更是令人头疼。如果在测试环境中,可能还需通过远程调试查明空对象,费时费力。Java17 终于在这方面取得了突破,提供了更详细的空指针异常信息,帮助开发者迅速定位问题源头。
6. Records
在 Java 中,POJO(普通的Java对象,可以理解为就是 JavaBean)对象(如DO、PO、VO、DTO等)通常包含成员变量及相应的 Getter 和 Setter 方法。尽管可以通过工具或 IDE 生成这些代码,但修改和维护仍然麻烦。为此,Java 引入了标准解决方案:Records。它通过简洁的语法定义数据类,大大简化了 POJO 类的编写,如下所示。虽然 hashcode 和 equals 方法仍需手动编写,但 IDE 能够自动生成。这一特性有效解决了模板代码问题,提升了代码整洁度和可维护性。
示例:
7. 全新的switch表达式
在 Java12 的时候就引入了 switch 表达式,注意这里是表达式,而不是语句,原来的 switch 是语句。主要的差别就是就是表达式有返回值,而语句则没有。再配合模式匹配,以及 yield 和 “->” 符号的加入,全新的switch 用起来爽到飞起来。
示例:
package com.edu.jdk17;
public class SwitchDemo {
/**
* 在JDK8中获取 switch 返回值方式
*
* @param week
* @return
*/
public static int getByJDK8(Week week) {
int i = 0;
switch (week) {
case MONDAY, TUESDAY:
i = 1;
break;
case WEDNESDAY:
i = 3;
break;
case THURSDAY:
i = 4;
break;
case FRIDAY:
i = 5;
break;
case SATURDAY:
i = 6;
break;
case SUNDAY:
i = 7;
break;
default:
i = 0;
break;
}
return i;
}
/**
* 在JDK17中获取switch返回值
*
* @param week
* @return
*/
public static int getByJDK17(Week week) {
// 1, 现在的switch变成了表达式,可以返回值了,而且支持yield和->符号来返回值
// 2, 再也不用担心漏写了break,而导致出问题了
// 3, case后面支持写多个条件
return switch (week) {
case MONDAY -> 1;
case TUESDAY -> 2;
case WEDNESDAY -> 3;
case THURSDAY -> 4;
case FRIDAY -> 5;
case SATURDAY, SUNDAY -> 6;
default -> 0;
};
}
private enum Week {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
public static void main(String[] args) {
System.out.println(getByJDK17(Week.SATURDAY));
}
}
8. 私有接口方法
从 Java8 开始,允许在 interface 里面添加默认方法,其实当时就有些小困惑,如果一个 default 方法体很大怎么办,拆到另外的类去写吗?实在有些不太合理,所以在 Java17 里面,如果一个 default 方法体很大,那么可以通过新增接口私有方法来进行一个合理的拆分了。
9. 模式匹配
在 JDK17 中,模式匹配主要用于 instanceof 表达式。模式匹配增强了 instanceof 的语法和功能,使类型检查和类型转换更加简洁和高效。在传统的 Java 版本中,我们通常使用 instanceof 结合类型转换来判断对象类型并进行处理,这往往会导致冗长的代码。
10. 集合类的工厂方法
在 Java8 的年代,即便创建一个很小的集合,或者固定元素的集合都是比较麻烦的,为了简洁一些,有时我甚至会引入一些依赖。
11. JDK17 的其他特性
(1) 新的String方法
repeat:重复生成字符串
isBlank:不用在引入第三方库就可以实现字符串判空了
strip:去除字符串两边的空格,支持全角和半角,之前的trim只支持半角
lines:能根据一段字符串中的终止符提取出行为单位的流
indent:给字符串做缩进,接受一个int型的输入
transform:接受一个转换函数,实现字符串的转换
(2) Stream API的增强
增加 takeWhile, dropWhile, ofNullable, iterate 以及 toList 的API,越来越像一些函数式语言了。
用法举例:
(3) Jshell
在新的 JDK 版本中,支持直接在命令行下执行 java 程序,类似于 python 的交互式 REPL。简而言之,使用 JShell,你可以输入代码片段并马上看到运行结果,然后就可以根据需要作出调整,这样在验证一些简单的代码的时候,就可以通过 Jshell 得到快速地验证,非常方便。
(4) java 命令直接执行 java 文件
在现在可以直接通过执行java xxx.java,即可运行该 java 文件,无须先执行 javac,然后再执行 java,是不是又简单了一步。
(5) ZGC
在 ParallelOldGC、CMS 和 G1 之后,JDK11 引入了全新的 ZGC(Z Garbage Collector)。官方宣称 ZGC 的垃圾回收停顿时间不超过10ms,能支持高达 16TB 的堆空间,并且停顿时间不会随着堆的增大而增加。
那么,ZGC 到底解决了什么问题?Oracle 官方介绍它是一个可伸缩的低延迟垃圾回收器,旨在降低停顿时间,尽管这可能会导致吞吐量的降低。不过,通过横向扩展服务器可以解决吞吐量问题。官方已建议 ZGC 可用于生产环境,这无疑将成为未来的主流垃圾回收器。