Java SE(六)网络编程 反射 Lambda 注解
网络编程基本概念
网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的
网络编程的三要素
- IP地址:
要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号.也就是设备的标识.计算机的身份ID
- 端口号:
网络的通信,本质上是两个应用程序的通信.每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了.也就是应用程序的标识.
- 协议(规则):
通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样.在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换.常见的协议有UDP协议和TCP协议
InetAddress类
InetAddress类,来自于java.net包,表示计算机的主机名+ip地址的类型
InetAddress中没有提供出公共的构造方法,因此不能直接创建对象,可以通过类中的静态方法,创建出InetAddress对象出来
静态方法: 获取到InetAddress类对象
getLocalHost() : //表示返回当前计算设备的主机名+ip地址这样的一个InetAddress类对象
getByName(String host) : //通过主机名host或者ip地址获取到对应的InetAddress类对象
常用非静态方法:
getHostName(): :获取主机名,返回值类型String类型
getHostAddress() : 获取到IP地址,返回值类型String类型
toString() : 将一个InetAddress对象结果转换成字符串,返回值类型字符串
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Demo01_InetAddress {
public static void main(String[] args) throws UnknownHostException {
// 1.获取到本机ip地址
InetAddress ia = InetAddress.getLocalHost();
System.out.println(ia);
// 2.获取到任意的计算机对应的ip地址对象
// 参数可以是一个主机名, 也可以是一个指定IP地址, 都是以字符串的形式进行参数传递
InetAddress ia1 = InetAddress.getByName("10.10.10.242");
System.out.println(ia1);
InetAddress ia2 = InetAddress.getByName("LAPTOP-GBN4PA9J");
System.out.println(ia2);
//1.getHostName(): :获取主机名,返回值类型String类型
//2.getHostAddress() : 获取到IP地址,返回值类型String类型
//3.toString() : 将一个InetAddress对象结果转换成字符串,返回值类型字符串
System.out.println(ia1.getHostName());// LAPTOP-GBN4PA9J
System.out.println(ia1.getHostAddress());// 10.10.10.242
System.out.println(ia1.toString());// LAPTOP-GBN4PA9J/10.10.10.242
}
}
端口的详细解释
-
概述: 设备上应用程序的唯一标识.
-
端口号: 用两个字节表示的整数,它的取值范围是065535.其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号.如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败.
-
例如: 常用应用的端口号
QQ 4000
mysql 3306
tomcat 8080
web 80
协议的详细解释
- 计算机网络中,连接和通信的规则被称为网络通信协议(进行数据的通讯的时候,需要准守的规则)
规则包括 : 数据的封装和数据的解析,以及数据在传输过程中精确性
好比 : 交通法规,所有人准守的法规是一致的,因此开车在路上才是安全的
通讯协议中 : 目前使用最多的TCP/IP协议
-
UDP协议: 面向无连接
- 用户数据报协议(User Datagram Protocol)
- UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接.简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据.
- 由于使用UDP协议消耗系统资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输.
- 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响.但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议.
-
TCP协议: 面向有连接, 分为客户端和服务器端
- 传输控制协议 (Transmission Control Protocol)
- TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输.在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”.
- 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
第一次握手,客户端向服务器端发出连接请求,等待服务器确认
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
第三次握手,客户端再次向服务器端发送确认信息,确认连接
-
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了.由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛.例如上传文件、下载文件、浏览网页等
UDP编程
-
UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念.
-
Java提供了DatagramSocket类作为基于UDP协议的Socket
DatagramPacket: 来自于java.net包,表示数据报包,就是进行数据的封装和解析
数据封装 : 发送端发送数据时,需要将数据打包(封装)
数据解析 : 接收端在接收到数据后,将数据获取到(解析)
就像快递包裹
DatagramSocket: 来自于java.net,表示数据的发送和接收
数据发送 : 发送端将数据发送到接收端
数据接收 : 接收端获取数据
就像快递车
UDP的发送端
UDP发送端的实现步骤:
1.使用DatagramPacket封装需要发送的数据:
使用DatagramPacket构造方法:
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
1)buf参数是一个字节数组,表示需要发送的数据
2)length,表示将数组的多少字节进行发送
3)address,表示要将数据发送到哪里(对方的IP)
4)port ,表示目标计算机的端口号
2.使用DatagramSocket进行数据的发送
1)构造方法使用空参数构造即可
2)数据发送,使用send(DatagramPacket p) : 表示将数据p发送到指定的ip和端口上
3.关闭资源,使用close(); 方法
import java.io.IOException;
import java.net.*;
public class UDPSend {
public static void main(String[] args) throws IOException {
// 1. 准备需要发送数据 : 数据需要封装在DatagramPacket数报包中
// 因为DatagramPacket只能封装byte类型数组, 因此需要发送的数据,需要以
// byte类型数组的方式存在
byte[] b = "你好,UDP接收端".getBytes();
// 2. 将数据进行打包
// DatagramPacket(byte[] buf, int length, InetAddress address, int port)
// 1) byte[] buf : 表示需要进行发送数据
// 2) int length : 需要发送的数据的长度, 从buf数组中发送多少字节
// 3) InetAddress address: 接收端的IP地址
// 4) int port : 接收端的端口号
DatagramPacket dp = new DatagramPacket(b,b.length,
InetAddress.getByName("127.0.0.1"),8888);
// 3. 创建出一个DatagramSocket类型对象,就像一个快递车,将包裹发送到指定的目的地
// 发送到按指定的ip地址,指定的端口号上
// 创建出一个空参数构造方法即可
DatagramSocket ds = new DatagramSocket();
// 4. 调用DatagramSocket中send(DatagramPacket dp)
ds.send(dp);
// 5. 关闭资源
ds.close();
}
}
UDP的接收端
UDP接收端的实现步骤:
1.需要将接收数据的端口号,先进行监听(打开,使用)
使用DatagramSocket的构造方法
DatagramSocket(int port): //表示开启port对应的端口号,准备进行数据的通讯
2.准备DatagramPacket,用于接收发送端的数据
DatagramPacket(byte[] buf, int length) : //将发送端的数据,接收到buf数组中
3.接收数据
DatagramSocket中的receive(DatagramPacket p)://将发送端的数据接收到,然后将接收到的数据传递到DatagramPacket 中
4.从DatagramPacket 中解析数据
getLength() : //返回接收或者是发送时的数据长度
getPort() : //获取到发送发的端口号,了解即可
getAddress() : //获取到发送方的IP地址
5.关闭资源 close()
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
// UDP接收端: UDP服务, 先运行接收端, 再运行发送端
public class UDPReceive {
public static void main(String[] args) throws IOException {
// 1. 先将预期接收数据的端口进行监听
DatagramSocket ds = new DatagramSocket(8888);
// 2. 准备一个数据报包: 一个承接发送端数据包裹
byte[] b = new byte[1024];
DatagramPacket dp = new DatagramPacket(b,b.length);
// 3. 接收从发送端来的数据
// 调用DatagramSocket: 中receive(DatagramSocket dp)
// receive() : 接收方法, 具有阻塞功能, 等待,等到有了发送端的数据,才会继续运行
System.out.println("等待接收数据");
ds.receive(dp);
System.out.println("已收到");
// 4. 解析收到的数据内容
// 1)getLength() : 返回接收或者是发送时的数据字节个数
// 2)getPort() : 获取到发送发的端口号,了解即可
// 3)getAddress() : 获取到发送方的IP地址
int length = dp.getLength();
InetAddress ia = dp.getAddress();
int port = dp.getPort();--
System.out.println(ia + ":" + port + "--" + new String(b,0,length));
// 5. 关闭资源
ds.close();
}
}
永不停歇的聊天工具
要求 : 利用UDP协议的发送和接收,模拟聊天小程序,设计出一个可以一直聊天的工具,发送端从键盘录入,接收端进行数据的接收
思路 : 将发送端和接收端设计在死循环中
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class UDPSendForever {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
DatagramSocket ds = new DatagramSocket();
while(true){
System.out.println("请输入数据:");
String s = sc.next();
byte[] b = s.getBytes();
DatagramPacket dp = new DatagramPacket(b,b.length,
InetAddress.getByName("127.0.0.1"),8888);
ds.send(dp);
}
// ds.close();
}
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPReceiveForever {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(8888);
byte[] b = new byte[1024];
while(true){
DatagramPacket dp = new DatagramPacket(b,b.length);
ds.receive(dp);
int length = dp.getLength();
InetAddress ia = dp.getAddress();
int port = dp.getPort();
System.out.println(ia + ":" + port + "--" + new String(b,0,length));
}
// 5. 关闭资源
// ds.close();
}
}
TCP编程
TCP编程,面向有连接(客户端向服务器端发送请求,服务器端必须存在可以通讯,连接才能成立),分为:
客户端 : Socket
服务器端 : ServerSocket
举例: 游戏,英雄联盟,本地计算中的都是下载的客户端,远程的服务器端
TCP中,一般情况下,都是客户端主动的连接服务器端
TCP的客户端
主要是用Socket类
Socket类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
TCP客户端的实现步骤:
- 创建一个客户端的Socket对象,
使用构造方法 : Socket(String address , int port) : //表示连接address的ip地址
注意 : Socket构造放法就已经在连接服务器端,如果ip地址不存在或者端口号没有开启监听, 算作服务器端无连接, 那么直接报错
-
使用流资源进行与服务器的数据传输
- 向服务器发送数据,需要写,需要输出,需要输出流
- Socket中的方法 : getOutputStream() : 获取到一个输出流,返回值类型OutputStream字节输出流,使用write向服务器写内容
- Socket中的方法 : getInputStream() : 获取到一个输入流,返回值类型InputStream字节输入流,用于读取服务器返回的响应,使用read方法读
-
close() : 关闭Socket套接字的资源
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
// TCP的客户端
public class TCPSocket {
public static void main(String[] args) throws IOException {
// 1. 创建出一个客户端Socket对象
// Socket(String addressIp, int port) : 表示直接进行服务器端连接
Socket s = new Socket("127.0.0.1",9999);
// 2. getOutputStream() : 获取到一个输出流,向服务器写入数据
OutputStream os = s.getOutputStream();
// 3. 写数据
os.write("服务器,在吗?".getBytes());
// 4. 获取到一个输入流: 读取从服务器端返回的数据响应
InputStream is = s.getInputStream();
byte[] b = new byte[1024];
int len = is.read(b);
System.out.println(new String(b,0,len));
// 5. 关闭Socket套接字资源
s.close();
}
}
TCP的服务器端
主要使用ServerSocket类
课上现场画的图:
TCP的服务器端的实现步骤:
- 需要先将服务器的端口监听
ServerSocket的构造方法: ServerSocket(int port) : 表示将服务器的指定端口打开
-
接收客户端发送的请求数据
- 先通过ServerSocket中的accept() 方法获取到客户端你的套接字对象
- 使用客户端的套接字获取到对应的输入流
-
向客户端发送响应
使用使用客户端的套接字获取到对应的输出流,向客户端写入数据
- 关闭资源
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.SQLOutput;
// TCP服务器端: 先启动服务器端,可连接,客户端发送请求
public class TCPServerSocket {
public static void main(String[] args) throws IOException {
// 1. 创建出一个服务器端对象, 监听客户端目标要访问的端口号
ServerSocket ss = new ServerSocket(9999);
// 2. 获取到客户端Socket对象
// 注意 : accept()具有阻塞功能, 只有接收到了客户端的请求,才会执行accpet
System.out.println("等待客户端请求");
Socket s = ss.accept();
System.out.println("已经可以通讯,接收到请求");
// 3. 获取输入流,读取从客户端发送来数据
InputStream is = s.getInputStream();
byte[] b = new byte[1024];
int len = is.read(b);
System.out.println(new String(b,0,len));
// 4. 获取到输出流, 给客户端进行数据响应
OutputStream os = s.getOutputStream();
os.write("在,可以连接,ok".getBytes());
// 5. 关闭资源
ss.close();
}
}
UDP协议中广播与组播
广播
1.当前主机与网络中的所有主机通信.
2.广播地址必须为255.255.255.255
发送端代码:
import java.io.IOException;
import java.net.*;
public class UDP广播发送端 {
public static void main(String[] args) throws IOException {
// 1. 准备需要发送的数据
byte[] b = "小广播课堂开了啦!!!".getBytes();
// 2. 将数据进行封装DatagramPacket
DatagramPacket dp = new DatagramPacket(b,b.length, InetAddress.getByName("255.255.255.255"),9999);
// 3. 创建出一个DatagramSocket对象,发送数据
DatagramSocket ds = new DatagramSocket();
ds.send(dp);
// 4. 关闭资源
ds.close();
}
}
接收端代码:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UDP广播接收端 {
public static void main(String[] args) throws IOException {
// 1. 先将预计接收数据的端口号进行监听
DatagramSocket ds = new DatagramSocket(9999);
// 2. 准备一个包裹,接收发送端的数据
byte[] b = new byte[1024];
DatagramPacket dp = new DatagramPacket(b,b.length);
// 3. 接收数据 : receive具有阻塞功能, 等待发送端发来数据,才会接收和继续向下运行
ds.receive(dp);
// 4. 解析数据
int len = dp.getLength();
System.out.println(new String(b,0,len));
// 5. 关闭资源
ds.close();
}
}
组播
-
组播是指把信息同时传递给一组目的地址.组播地址的范围是224.0.0.0 ~ 239.255.255.255,如果超出这个范围就会抛出异常.
-
参与组播的所有主机的socket必须是MulticastSocket类的对象,它是DatagramSocket的子类,因此组播socket也是UDP协议的一种.
-
MulticastSocket构造方法:
MulticastSocket(): //创建组播套接字
MulticastSocket(int port): //创建组播套接字并将其绑定到特定端口
加入组播方法:
joinGroup(InetAddress mcastaddr): //加入组播
发送端代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
public class UDP组播发送端 {
public static void main(String[] args) throws IOException {
// 1. 准备要发送的数据
byte[] b = "组播开始发送消息,进群听取".getBytes();
// 2. 将预计发送的数据进行打包
DatagramPacket dp = new DatagramPacket(b,b.length, InetAddress.getByName("239.0.0.23"),8888);
// 3. 创建出MulticastSocket对象,用于进行组播的数据发送
MulticastSocket ms = new MulticastSocket();
// 4. 发送数据
ms.send(dp);
// 5. 关闭资源
ms.close();
}
}
接收端代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class UDP组播接收端 {
public static void main(String[] args) throws IOException {
// 1. 监听8888端口
MulticastSocket ms = new MulticastSocket(8888);
// 2. 准备接收数据
byte[] b = new byte[1024];
DatagramPacket dp = new DatagramPacket(b,b.length);
// 在接收数据之前, 先加入组播中,加入到组播指定的ip地址
InetAddress ia = InetAddress.getByName("239.0.0.23");
// joinGroup方法加入组播
ms.joinGroup(ia);
// 3.接收数据
ms.receive(dp);
// 4. 解析数据
int len = dp.getLength();
System.out.println(new String(b,0,len));
// 5. 关闭资源
ms.close();
}
}
TCP多线程文件上传
将客户端的图片上传到服务器
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TCPSocketPicture {
public static void main(String[] args) throws IOException {
// 1. 创建出一个客户端套接字对象: 连接服务器端
Socket s = new Socket("127.0.0.1",9999);
// 2. 获取字节输出流,向客户端写入图片数据
OutputStream os = s.getOutputStream();
// 3. 创建出一个字节输入流,用于读取指定一张图片中字节数据
FileInputStream fis = new FileInputStream("D:\\sheep.jpg");
// 4. 循环读取图片中字节数据
byte[] b = new byte[1024];
int len;
/*
客户端 : 从磁盘某文件中读取数据,如果读取到-1,证明图片读完了
*/
while((len = fis.read(b)) != -1){
// 5. 将图片数据写入到服务器端: 相当于上传
os.write(b,0,len);
}
// 当客户端向服务器端写入数据完毕,告诉服务器daunt,结束
// 相当于向服务器端写入一个-1的效果
s.shutdownOutput();
fis.close();
// 6. 获取自己输入流: 读取从服务器端返回的响应
InputStream is = s.getInputStream();
byte[] b1 = new byte[1024];
int len1 = is.read(b1);
System.out.println(new String(b1,0,len1));
// 7. 关闭资源
s.close();
}
}
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;
public class TCPServerSocketPicture {
public static void main(String[] args) throws IOException {
// 1. 将指定9999端口,进行监听
ServerSocket ss = new ServerSocket(9999);
Random ran = new Random();
// 2. 获取到客户端的套接字对象: 具有阻塞功能, 等待客户端发送请求,才能继续执行
Socket s = ss.accept();
// 3. 获取到字节输入流,读取从客户端发送来的请求数据
InputStream is = s.getInputStream();
// 循环读取从客户端上传的数据
byte[] b = new byte[1024];
int len;
// 4. 向服务器的指定路径下写入图片数据
int number = ran.nextInt(Integer.MAX_VALUE);
FileOutputStream fos = new FileOutputStream("D:\\copy" + "\\" + number + ".jpg");
/*
服务器端读取客户端的数据,图片,没有读取到-1的场景,因此导致了服务器端
的读取客户端数据,一直都没有结束
*/
while((len = is.read(b)) != -1){
fos.write(b,0,len);
}
// 5. 给客户端一个响应
// 获取到字节输出流
OutputStream os = s.getOutputStream();
os.write("上传成功".getBytes());
fos.close();
ss.close();
}
}
模拟多线程下文件上传到服务器端
-
客户端: 数据来自于磁盘某图片上传到服务器端, 后接受服务器端的反馈
-
服务器端: 接受来自客户端的图片数据, 给客户端反馈, 代码用线程进行封装, 为每一个客户开启一个线程
分析 :
-
一个服务器端可以对应多个客户端的使用, 因此将服务器端设计成一个永不结束的死循环, 客户端随时随地都可以请求服务器端
-
在服务器端设计出多线程代码, 为了让每一个线程中,都可以服务一个客户端的程序
多线程客户端代码:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TCPThreadSocketPicture implements Runnable{
public void run() {
// 1. 创建出一个客户端套接字对象: 连接服务器端
Socket s = null;
try {
s = new Socket("127.0.0.1",9999);
// 2. 获取字节输出流,向客户端写入图片数据
OutputStream os = s.getOutputStream();
// 3. 创建出一个字节输入流,用于读取指定一张图片中字节数据
FileInputStream fis = new FileInputStream("D:\\sheep.jpg");
// 4. 循环读取图片中字节数据
byte[] b = new byte[1024];
int len;
/*
客户端: 从磁盘某文件中读取数据,如果读取到-1,证明图片读完了
*/
while((len = fis.read(b)) != -1){
// 5. 将图片数据写入到服务器端: 相当于上传
os.write(b,0,len);
}
// 当客户端向服务器端写入数据完毕,告诉服务器daunt,结束
// 相当于向服务器端写入一个-1的效果
s.shutdownOutput();
fis.close();
// 6. 获取自己输入流: 读取从服务器端返回的响应
InputStream is = s.getInputStream();
byte[] b1 = new byte[1024];
int len1 = is.read(b1);
System.out.println(new String(b1,0,len1));
// 7. 关闭资源
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
多线程服务器端代码
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;
public class TCPThreadServerSocketPicture {
public static void main(String[] args) throws IOException {
// 1. 将指定9999端口,进行监听
ServerSocket ss = new ServerSocket(9999);
Random ran = new Random();
while(true){
// 2. 获取到客户端的套接字对象: 具有阻塞功能, 等待客户端发送请求,才能继续执行
Socket s = ss.accept();
// 注意: 在服务器端添加多线程代码,每一次客户端有请求,独立的线程通道
new Thread(){
@Override
public void run(){
InputStream is = null;
try {
is = s.getInputStream();
// 循环读取从客户端上传的数据
byte[] b = new byte[1024];
int len;
// 4. 向服务器的指定路径下写入图片数据
int number = ran.nextInt(Integer.MAX_VALUE);
FileOutputStream fos = new FileOutputStream("D:\\copy" + "\\" + number + ".jpg");
while((len = is.read(b)) != -1){
fos.write(b,0,len);
}
// 5. 给客户端一个响应
// 获取到字节输出流
OutputStream os = s.getOutputStream();
os.write("上传成功".getBytes());
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
// ss.close();
}
}
客户端测试代码
package com.ujiuye.net;
public class TestSocket {
public static void main(String[] args) {
TCPThreadSocketPicture tp = new TCPThreadSocketPicture();
Thread t1 = new Thread(tp);
Thread t2 = new Thread(tp);
Thread t3 = new Thread(tp);
t1.start();
t2.start();
t3.start();
}
}
反射
虚拟机类加载机制
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制.
类加载过程
当程序要使用某个类时,如果该类还未被加载到内存中,系统会通过加载,连接,初始化三步来实现对这个类的加载.
(1)加载
《1》就是指将class文件读入内存,并为之创建一个Class对象.
《2》任何类被使用时系统都会建立一个Class对象
(2)连接
《1》验证是否有正确的内部结构,并和其他类协调一致
《2》准备负责为类的静态成员分配内存,并设置默认初始化值
《3》解析将类的二进制数据中的符号引用替换为直接引用
(3)初始化
《1》主要对类变量进行初始化
a: 类还未被加载, 程序先加载并连接该类
b: 如该类还有直接父类, 则先初始化其直接父类
c: 有初始化语句,按顺序执行
类的初始化时机
总结 : 当使用这个类型时, JVM虚拟机,就会开始对这个类型进行加载和初始化
1.创建类的实例
2.类的静态成员使用
3.类的静态方法调用
4.使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
5.初始化某个类的子类
6.直接使用java.exe命令来运行某个主类
类加载器
类加载器是负责加载类的对象,将class文件(硬盘)加载到内存中,并为之生成对应的java.lang.Class对象.
类加载器的分类
- Bootstrap ClassLoader 引导类加载器,通常表示为null
也被称为根类加载器,负责Java核心类的加载,比如System,String等.
- Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下ext目录.
- Application ClassLoader 系统类加载器
负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径.
- 自定义类加载器
开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求.
类加载器之间的继承关系
-Bootstrap ClassLoader
-Extension ClassLoader
-Application ClassLoader
双亲委派机制
1.双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器.每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载.
2.双亲委派模型工作过程:
1)当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成.
2)当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成.
3)如果Bootstrap ClassLoader加载失败,就会让Extension ClassLoader尝试加载.
4)如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载.
5)如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载.
6)如果均加载失败,就会抛出ClassNotFoundException异常.
3.例子:
当一个Hello.class这样的文件要被加载时.不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了.如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法.父类中同理会先检查自己是否已经加载过,如果没有再往上.注意这个过程,直到到达Bootstrap classLoader之前,都是没有哪个加载器自己选择加载的.如果父加载器无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException.
CLassLoader类
1.ClassLoader 叫做类加载器.虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流” 也就是.class字节码文件,这个动作放到java虚拟机外部去实现,以便让应用程序自己决定去如何获取所需要的类,实现这个动作的代模块称之为“类加载器”.
2.ClassLoader的方法:
static ClassLoader getSystemClassLoader()
返回用于委派的系统类加载器
ClassLoader getParent()
返回父类加载器进行委派
public class Test {
public static void main(String[] args) {
ClassLoader c = ClassLoader.getSystemClassLoader();
System.out.println(c);// sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader c2 = c.getParent();
System.out.println(c2);// sun.misc.Launcher$ExtClassLoader@1b6d3586
ClassLoader c3 = c2.getParent();
System.out.println(c3);// null
}
}
反射应用
反射机制的概述
反射是指在运行时去获取一个类的变量和方法信息.然后通过获取到的信息来创建对象,调用方法的一种机制.由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍然可以扩展.
举例说明 :
-
例如项目工程中, 有两个类,Student, Teacher, 需要使用这两个类
-
因为要使用上述两类, 这个两个类就会被类加载器从磁盘路劲上加载进入到内存中
进入内存中是类对应的.class字节码文件
-
一个类型一旦进入到内存,证明需要使用,证明代码需要运行, 这种状态就是动态的效果(正在内存中运行)
-
类加载器为这个正在运行的.class字节码文件, 创建出一个对应的Class对象, 对象中包含了class文件中所有代码内容
-
Class对象中,可以包含类中所有成员变量, 构造方法,方法...
-
使用Class对象, 获取出类型中所有的需要的成员, 这种使用方式称为反射机制
反射机制的理解:
获取Class类对象三种方式
1.Class类: Class类型的实例表示正在运行的java应用程序的类或者接口.
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类
2.Class类的对象: 想获取和操作类中的内容,首先要获取类的字节码对象(Class类对象),每一个正在运行的类,都有对应的字节码对象,获取了类的字节码对象,就可以使用这个对象的所有方法,这些方法都定义在Class类型中.
3.三种获取Class类对象的方式:
类名.class属性
对象名.getClass()方法
Class.forName(全类名)方法
public class Demo01_GetClass {
public static void main(String[] args) throws ClassNotFoundException {
// 获取到Student类型对应的Class对象
// 1) 类名.class属性
Class c = Student.class;
System.out.println(c);// class com.ujiuye.reflect.Student
// 2) Class.forName(String className): 通过参数中给出的类名,获取到
// 这个类型对应的Class对象
// 注意 : 参数需要的是带有完整包名的类名
Class c1 = Class.forName("com.ujiuye.reflect.Student");
System.out.println(c1);// class com.ujiuye.reflect.Student
// 3) 创建出一个类对象,使用对象名.getClass() :
// getClass方法来自于父类Object中继承所得
Student s = new Student();
Class c2 = s.getClass();
System.out.println(c2);// class com.ujiuye.reflect.Student
Student s1 = new Student();
Class c3 = s1.getClass();
System.out.println(c3);// class com.ujiuye.reflect.Student
// 一个类型的.class字节码文件只进内存一次即可
// 字节码文件对应的Class对象在内存中只有一个
System.out.println(c == c1);// true
System.out.println(c == c2);// true
System.out.println(c == c3);// true
}
}
反射获取构造方法并使用
1.Class类获取构造方法对象:
方法分类:
Constructor<?>[] getConstructors()
//返回所有公共public构造方法对象的数组
Constructor<?>[] getDeclaredConstructors()
//返回所有构造方法对象的数组
Constructor getConstructor(Class<?>... parameterTypes)
//返回单个公共构造方法对象
Constructor getDeclaredConstructor(Class<?>...parameterTypes)
//返回单个构造方法对象
- 注意:
getConstructor(Class<?>... parameterTypes)
getDeclaredConstructor(Class<?>...parameterTypes)
两方法的参数列表为可变参数,可变参数即参数的个数可以是任意个,0个,1个或者多个均可
任意的数据类型都有对应Class对象, 连基本数据数据类型也不例外 : int.class
- Constructor类型:
1)表示构造方法类型,这个类的每个对象,都是一个确定的,具体的构造方法
2)构造方法对象应该具有的功能: 获取构造方法各种信息(构造方法修饰符、构造方法名称、构造方法的参数列表、构造方法的注解),最基本的一个功能就是,创建对象.
- Constructor类用于创建对象的方法:
T newInstance(Object...initargs) 根据指定的构造方法创建对象,参数为所运行构造方法需要的实际参数.
Person类代码
public class Person {
private String name;
public int age;
String address;
// 1) 公共构造
public Person(){}
// 2) 默认修饰的构造方法: 在一个类中,如果方法, 成员变量, 没有任何权限修饰
// 那么就是默认的修饰, 默认修饰不写出来的, 写出来直接报错
Person(String name){
this.name = name;
}
// 3) 私有修饰构造方法
private Person(int age){
this.age = age;
}
// 4) 公共有参数构造
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
}
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectConstructor {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1. 获取到Person类型对应的字节码文件对象Class对象
Class c = Person.class;
// 2. 通过c对象,获取出Person中构造方法
// 构造方法 : 又名 构造函数,构造器
// 1) Constructor<?>[] getConstructors(): 返回所有公共public构造方法对象的数组
Constructor[] conArr = c.getConstructors();
for(Constructor con : conArr){
System.out.println(con);
}
System.out.println("-------------------");
// 2) Constructor<?>[] getDeclaredConstructors(): 返回所有构造方法对象的数组
Constructor[] conAll =c.getDeclaredConstructors();
for(Constructor con : conAll){
System.out.println("--"+con);
}
System.out.println("+++++++++++++++++++++=");
// 3) Constructor getConstructor(Class<?>... parameterTypes):返回单个公共构造方法对象, 可变参数 : 参数个数可以0-n
Constructor con1 = c.getConstructor();
System.out.println(con1);
// 4)Constructor getDeclaredConstructor(Class<?>...parameterTypes):返回单个构造方法对象, 获取出指定构造方法: 私有构造
Constructor con2 = c.getDeclaredConstructor(int.class);
System.out.println(con2);
// 5) 获取到构造方法 : 运行构造
// 运行构造目的: 1)创建出一个对象 2) 运行构造时,需要传递实际参数,相当于给对象成员变量赋值
// T newInstance(Object...params) : 运行构造方法
Person per = (Person)con1.newInstance();
System.out.println(per.age);// 0
// 扩展: 获取有参数公共构造方法
// 获取构造
Constructor con3 =c.getConstructor(String.class,int.class,String.class);
// 运行构造
Person p1 = (Person)con3.newInstance("张三",20,"新加坡");
System.out.println(p1.age);// 20
System.out.println(p1.address);// 新加坡
// 试图运行私有构造方法: 报错, 非法的修饰符访问
/*Person p2 = (Person)con2.newInstance(35);
System.out.println(p2.age);*/
}
}
反射获取成员变量并使用
1.Class类获取成员变量对象:
方法分类:
Field[] getFields()
//返回所有公共成员变量对象的数组
Field[] getDeclaredFields()
//返回所有成员变量对象的数组
Field getField(String name)
//返回单个公共成员变量对象,参数name表示成员变量的名字
Field getDeclaredField(String name)
//返回单个成员变量对象,参数name表示成员变量的名字
2.Field类型: 表示一个成员变量类型,每个对象都是一个具体的成员变量
作用: 获取成员变量的各种信息(修饰符、注解、名称),做各种数据类型的转换.
3.Field类用于给成员变量赋值的方法:
set(Object obj, Object value): //用于给obj对象的,该成员变量,赋value值
4.Field类获取成员变量值的方法:
get(Object obj): //用于获取obj对象的指定成员变量值
public class ReflectField {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
// field variable : 都有表示变量的含义
// 1. 获取出Person类型对应的Class字节码文件对象
Class c = Person.class;
// 2. 获取出Person类中所有的公共修饰的成员变量
Field[] fieldArr = c.getFields();
for(Field f : fieldArr){
System.out.println(f);
}
System.out.println("--------------");
// 3. 获取出Person类中所有成员变量,不论任何修饰
Field[] fieldAll = c.getDeclaredFields();
for(Field f : fieldAll){
System.out.println(f);
}
System.out.println("+++++++++++++++++");
// 4. 获取到指定的公共修饰的一个成员变量
// Field getField(String name):返回单个公共成员变量对象,参数name表示成员变量的名字
Field fAge = c.getField("age");
System.out.println(fAge);// public int com.ujiuye.reflect.Person.age
// 5. 获取到任意的一个成员变量,不论修饰符
// Field getDeclaredField(String name): 返回单个成员变量对象
Field fName = c.getDeclaredField("name");
System.out.println(fName);// private java.lang.String com.ujiuye.reflect.Person.name
// 6. 给成员变量进行赋值
// Field类中 : set(Object obj, Object value): 用于给obj对象的,该成员变量,赋value值
// 扩展: 反射可以通过便捷方式进行对象的创建
// 1) 不便捷方式: 获取出构造, newInstance(Object...param): 运行构造,从而创建对象
// 2) 便捷方式: 如果类中有公共修饰的, 空参数构造, 可以直接通过Class对象中的newInstance()
// 直接创建出指定对象
Person per = (Person)c.newInstance();
// 给per对象中的age成员变量进行赋值
fAge.set(per,20);
// 获取出per对象中的成员变量的值
// get(Object obj): 用于获取obj对象的指定成员变量值
Integer i = (Integer)fAge.get(per);
System.out.println(i);// 20
}
}
获取类中的成员方法并执行
1.Class类获取成员方法对象:
方法分类
Method[] getMethods()
//返回所有公共成员方法对象的数组,包括继承的
Method[] getDeclaredMethods()
//返回所有成员方法对象的数组,不包括继承的
Method getMethod(String methodName, Class<?>...parameterTypes)
//返回单个公共成员方法对象
Method getDeclaredMethod(String methodName, Class<?>...parameterTypes)
//返回单个成员方法对象
2.Method类型:
1)表示成员方法的类型,该类型的每个对象,都是一个具体的成员方法
2)成员方法对象具有的功能: 获取成员方法信息,运行方法.
3.Method类用于执行方法的功能:
invoke(Object obj, Object...values)://调用obj对象的成员方法,参数是values,返回值是Object类型.
public class Person {
private String name;
public int age;
String address;
// 1) 公共构造
public Person(){}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("我setName执行了");
}
void show(){
System.out.println(name + "--" + age + "--" + address);
}
private String print(){
return name + "--" + address;
}
}
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectMethod {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
// 1. 获取到Person类型对应的字节码文件对象
Class c = Person.class;
// 2. 获取出Person类中所有公共修饰的方法功能
// Method[] getMethods(): 返回所有公共成员方法对象的数组,包括继承的
Method[] mArr = c.getMethods();
for(Method m : mArr){
System.out.println(m);
}
System.out.println("------------------");
// 3. Method[] getDeclaredMethods():返回所有成员方法对象的数组,不包括继承的
Method[] mAll = c.getDeclaredMethods();
for(Method m : mAll){
System.out.println(m);
}
System.out.println("+++++++++++++++++++=");
// 4. Method getMethod(String methodName, Class<?>...parameterTypes):返回单个公共成员方法对象
// 1) methodName : 表示获取的方法名字
// 2) Class<?>...parameterTypes : 方法的参数列表
// 注意 : 获取方法必须给出该方法的参数列表, 因为方法可以重载, 如果没有参数列表的区分
// 重载方法获取时就会发生冲突
Method m = c.getMethod("setName",String.class);
System.out.println(m);
// 5. Method getDeclaredMethod(String methodName, Class<?>...parameterTypes): 返回单个成员方法对象
Method m1 = c.getDeclaredMethod("show");
System.out.println(m1);
// 6. Method类中 : Object invoke(Object obj, Object...values):调用obj对象的成员方法,参数是values,返回值是Object类型.
// 1) obj : 表示一个当前类型的对象
// 2) Object...values : 表示方法的实际参数列表
// 利用Class类型对象中快速创建对象方式 : newInstance()
Person p = (Person)c.newInstance();
// 注意 : invoke方法返回值结果Object类型,表示方法的运行结果,方法的返回值
// 如果运行的方法有返回值类型, 可以接值
// 如果运行的方法没有返回值, 直接调用
m.invoke(p,"李四");// 我setName执行了
System.out.println(p.getName());// 李四
}
}
暴力反射
1.通过Class类中:
getDeclaredXXX方法: 可以获取类中的所有声明的成员(属性、方法、构造),私有的成员也可以获取到.但是私有成员进行访问使用时,会因为权限问题导致失败,因此就需要暴力反射解决访问私有的问题
2.修改该对象的访问权限:
AccessibleObject类是Field,Method和Constructor对象的基类. 它提供了将反射对象标记为在使用它时抑制默认Java语言访问控制检查的功能.
setAccessible(boolean flag): //true的值表示反射对象应该在使用时抑制Java语言访问检查,false的值表示反射对象应该强制执行Java语言访问检查
3.一旦设定当前对象可以访问,私有的成员也可以被访问,被修改.
import java.lang.reflect.Field;
public class 暴力反射 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
// 1. 获取到Person类型对应的字节码文件对象
Class c = Person.class;
// 2. 获取到Person类型中的私有成员变量
Field fName = c.getDeclaredField("name");
// 3. 快速生成一个Person类型实例化对象
Person p = (Person)c.newInstance();
// 4. 对于私有成员,如果想直接使用, 需要设置一个访问权限取消
// setAccessible(boolean flag) :
// 1) 因为Constructor, Field, Method都是AccessibleObject类的子类
// 2) 因此直接继承使用setAccessible(boolean flag)方法功能
// 3) 方法功能 : 参数设置为true,那么表示取消使用时权限修饰验证
// 4) 注意 : 因为private私有权限修饰, 就是为了提高代码的安全性, 而暴力反射将安全性瓦解了
// 因此, 尽量保证代码的安全,这一点很重要, 暴力反射尽量少进行使用
fName.setAccessible(true);
fName.set(p,"张三");
System.out.println(fName.get(p));
}
}
JDK新特性
- 概述:
jdk8之前接口是规则的集合体,方法只有抽象方法。
jdk8版本开始不光有抽象方法同时增加了实体方法。
- 增加内容:
jdk8:default默认方法, static静态方法
jdk9: private私有方法
为什么要在接口中添加默认方法, 静态方法
原因 : 因为接口的维护难度很大, 因为如果接口中只能定义抽象方法, 那么一旦在一个接口中添加一个抽象方法, 那么这个接口的所有实现类, 都需要重写这个抽象方法, 否则实现类就要报错,影响使用;
因此在JDK8版本当下, 可以在接口中添加一些默认方法, 静态方法, 方法可以不再抽象, 这些方法在接口中的添加,不会影响实现类的使用, 因此相对提高了接口的维护度
默认方法
-
被关键字 default 修饰的方法就是默认方法,是在jdk8版本才出现的方法,独属于接口所有。
-
语法格式:
修饰符 default 返回值类型 方法名 (参数列表){方法体}
- 使用规则:
-
加上default的,实现类可以不重写,直接调用
-
特殊情况1:实现类实现了两个接口,如果有相同的的抽象方法声明,则强制要求在实现类中,重写这个方法,以指定确定的实现内容
-
特殊情况2:在特殊情况1中,如果在实现类重写这个方法的过程中,希望指定其中第一个接口的默认实现,则可以调用”父接口名.super.默认方法名称(实际参数)”
-
特殊情况3:实现类实现了继承了一个父类,并且实现了一个接口,并且在父类中和接口中,有相同的方法声明,则“类优先”。即使用类的实现,即使继承的是一个抽象类,也是使用父类的实现(即强制重写)。
- 产生影响
-
接口中如果也可以定义非抽象方法,那么和抽象类的差别就越来越小
-
java中一个类只能继承一个抽象类,但是可以同时实现多个接口
-
所以有了默认方法,就会让大量的抽象类变成接口,即弱化了抽象类
public interface MyInterface {
// I是静态成员常量 : public static final 修饰
int I = 10;
// 接口中抽象方法
public abstract void show();
// JDK8版本可以在接口中添加默认方法 : default关键字
public default int getSum(int x, int y){
return x + y;
}
// JDK8版本可以在接口中添加默认方法 : default关键字
public default boolean equal(double x, double y){
return x == y;
}
}
public interface MyInterface2 {
// JDK8版本可以在接口中添加默认方法 : default关键字
public default boolean equal(double x, double y){
return x != y;
}
}
public class MyInterfaceImpl implements MyInterface{
@Override
public void show() {
System.out.println("我是重写的父接口中的show");
}
// 1. 实现类中可以重写从父接口继承到的默认方法功能
// 注意 : 如果重写默认方法功能, 权限修饰只能使用public
@Override
public boolean equal(double x, double y){
System.out.println("我重写了父接口中的默认方法");
return x == y;
}
}
public class MyInterAllImpl extends Fu implements MyInterface,MyInterface2{
// 接口中抽象方法
@Override
public void show(){
System.out.println("重写了父接口MyInterface中show方法");
}
// 2. 如果一个类同时实现多个接口,而多个接口中包含了相同的default默认方法声明
// 实现类必须将冲突的默认方法进行重写
// 不重写不行,代码报错, 因为现在出现了默认方法继承冲突问题
/*@Override
public boolean equal(double x, double y){
System.out.println("我重写了两个父接口中的冲突默认方法.实现类重写,才能解决冲突调用问题");
// 2. 如果现在实现类中重写的默认方法的一部分逻辑,恰巧与某一个父接口中继承到的默认方法逻辑一致
// 父接口名.super.默认方法名(实际参数);
return MyInterface2.super.equal(x,y);
// return x == y;
}*/
// 3. 一个类子继承一个父类的情况下,同时实现多个接口,那么"父优先"
// 从而,从Fu中继承到的equal就解决了,两个父接口中的默认方法冲突问题
}
public class TestInterface {
public static void main(String[] args) {
MyInterfaceImpl my = new MyInterfaceImpl();
System.out.println(MyInterfaceImpl.I);// 10
my.show();// 实现类重写方法
int sum = my.getSum(3,5);// 1. 直接从父接口中继承来的默认方法getSum
System.out.println(sum);
// 2. 测试实现类调用了重写的父接口中的默认方法
System.out.println(my.equal(3.14,3.35));// false
// 3. 测试实现类重写了多个父接口中的冲突默认方法
MyInterAllImpl myAll = new MyInterAllImpl();
System.out.println(myAll.equal(3.33,3.33));// true
// 4. 测试接口中静态方法, 使用接口名直接调用
MyInterStatic.fun();
// 5. 测试接口的实现类中,不能继承使用static方法功能
// MyInterStaticImpl.fun();
}
}
静态方法
1、接口的静态方法可以定义方法体内容
2、static不能和abstract共存
3、外界只能通过接口名称.静态方法来访问接口静态方法,实现类中不会继承接口中的静态方法,原因:如果一个类同时实现了两个接口,具有相同的静态方法签名,继承之后不知道应该以哪个为准
public interface MyInterStatic {
// 1. 接口在JDK8新特性下, 可以直接定义出带有方法体的静态方法
public static void fun(){
System.out.println("我是MyInterStatic中静态方法,有方法体");
}
}
public class MyInterStaticImpl implements MyInterStatic{
/*
static : 静态属于类,因此如果实现类或者子类中定义出一个静态方法,这个方法只属于子类本身
而不是从父类继承重写
1. 在子父类继承关系中,子类可以继承使用父类中static静态方法, 但是子类不能重写, 因为静态只属于类本身
2. 在类与接口实现关系中, 实现类不能继承使用父接口中的static方法, 类与接口多实现
interface A{
static void fun(){
shuchu: A
}
}
interface B{
static void fun(){
shuchu:B
}
}
class Zi implements A,B{
Zi继承到的两个静态方法冲突, 但是静态方法由不能重写,解决不了冲突情况
因此结论 : 在类与接口实现关系中, 实现类不能继承使用父接口中的static方法
}
*/
/* @Override
public static void fun(){
System.out.println("我是MyInterStatic中静态方法,有方法体");
}*/
}
私有方法
私有方法是jdk9版本增加的一个实体方法,主要是用来进一步封装代码,提升相关代码安全性的手段。私有化之后方法不能被实现类直接调用使用或重写修改,只能提供给接口的静态方法和默认方法使用。
普通私有方法:只能提供给默认方法调用使用
静态私有方法:默认方法和静态方法都可以调用
私有代码
// JDK9版本向下兼容JDK8中所有功能使用
public interface MyInterfacePrivate {
public default void fun(){
System.out.println("我是默认方法");
show();
show2();
}
public static void dun2(){
System.out.println("我是静态方法");
// 静态方法中不能直接使用非静态
// show();
show2();
}
// 1. 可以在接口中定义出私有方法 : JDK9以及之后才能在接口中定义
// 私有方法作用就是将接口中代码封装提升, 安全性提升
private void show(){
System.out.println("我是私有");
}
// 2. 接口中私有方法可以使用private static 同时修饰, 不会冲突
private static void show2(){
System.out.println("私有+静态方法,在接口中");
}
}
public class MyInterPrivateImpl implements MyInterfacePrivate{
}
public class TestImterface {
public static void main(String[] args) {
MyInterPrivateImpl my = new MyInterPrivateImpl();
my.fun();
// my.show();
MyInterfacePrivate.dun2();
}
}
Lambda表达式
-
本质:
表示一个对象,指定接口的实现类对象
-
前提:
函数式接口:只有一个抽象方法的接口
可以在接口声明之上, 使用注解: @FunctionalInterface
注解作用 : 验证接口是否只含有一个抽象方法的函数式接口
-
好处:
对匿名内部类对象的格式简化,大幅提升开发效率
格式详解
-
格式:
(参数列表) ->
-
详细说明:
1、(参数列表)
表示要实现的接口中,抽象方法的参数
2、->
箭头运算符,或者称为Lambda运算符,用于分隔前后两部分
3、{方法体}
也称为Lambda体,表示重写抽象方法的具体实现
public class Demo01_Lambda {
public static void main(String[] args) {
// 1. 使用匿名内部类对象实现MyOnlyOneMehtodInterface
new MyOnlyOneMehtodInterface(){
@Override
public void fun1() {
System.out.println("匿名内部类实现函数式接口");
}
}.fun1();
// 2. 使用Lambda表达式实现函数式接口
// (参数列表)->{方法体}; Lambda表达式就是一个函数式接口的实现类对象
// MyOnlyOneMehtodInterface my = (参数列表)->{方法体};
MyOnlyOneMehtodInterface my = ()->{
System.out.println("Lambda实现函数式接口");
};
my.fun1();
}
}
@FunctionalInterface
interface MyOnlyOneMehtodInterface{
public abstract void fun1();
}
特殊情况
1:有且只有一个参数,可以省略小括号
x -> {int result = x * x; System.out.println(x + “的平方为:” + result);}
2:Lambda体只有一句,且没有返回值,可以省略大括号
x -> System.out.println(x * x);
3:Lambda体只有一句,且有返回值,则return和大括号可以一起省略
(x, y) -> {return x + y;} 等价于 (x, y) -> x + y
注意:要么一起省略,要么都不省略
public class Demo02_LambdaUse {
public static void main(String[] args) {
// 1. Lambda表达式实现多个参数方法
// 注意 : Lambda表达式中的方法参数列表,不需要写数据类型, 直接给出变量名即可
// w--->int x
// t--->int y
MyInterface my = (w,t)->{
System.out.println(w + t);
};
my.getSum(3,5);// 8
// 2.Lambda表达式实现1个参数方法
MyInterface1 my1 = (str)->{
System.out.println(str + "end");
};
my1.print("abc");// abcend
// 特殊情况1: 如果方法只有一个参数,那么lambda表达式小括号可以省略
MyInterface1 my11 = str->{System.out.println(str + "hello");};
my11.print("world");// worldhello
// 特殊情况2: 如果lambda表达式没有返回值结果,方法的实现语句只有1句逻辑,大括号也可以省略
MyInterface1 my22 = s-> System.out.println(s);
my22.print("finally");// finally
// 3.Lambda表达式实现有返回值的方法
MyInterface2 myInter = (d,d1)->{
System.out.println(d + d1);
return d == d1;
};
boolean boo = myInter.equal(3.1,3.1);
System.out.println(boo);// 6.2 true
// 特殊情况跟3 : 如果方法有返回值类型,并且语句逻辑只有一句,这一句就是方法的返回值结果,那么大括号和return都可以同时省略
MyInterface2 myInter2 = (x,y)->{return x == y;};
MyInterface2 myInter3 = (x,y)->x==y;
System.out.println(myInter3.equal(6.4,6.7));// false
}
}
@FunctionalInterface
interface MyInterface{
public abstract void getSum(int x, int y);
}
@FunctionalInterface
interface MyInterface1{
public abstract void print(String s);
}
@FunctionalInterface
interface MyInterface2{
public abstract boolean equal(double d1, double d2);
}
方法引用
1、写一个函数式接口时,方法的实现(lambda体),已经被某个其他的对象实现了,就不需要在Lambda体中,再次调用这个实现,而可以直接使用那个已经定义好的方法。
2、格式:
函数式接口 名称 = 对象名 :: 方法名称;
函数式接口 名称 = 类名 :: 静态方法名;
3、作用:
把已经实现的方法,作为一个数据,作为实现类对象,赋值给某个函数式接口的引用
可以把这个引用当做方法的返回值,也可以作为方法的实际参数进行传递
4、本质:
可以把任意一个方法,作为函数式接口的一个实现类对象
public class Demo03_方法引用 {
public static void main(String[] args) {
// 1. Lambda表达式实现函数式接口Inter
Inter i = s-> System.out.println(s);
i.fun("hello");
// 2. 使用方法引用实现函数式接口
// System.out: 就是一个PrintStream输出流对象
// println(String s) : println是PrintStream中的一个方法
/*函数式接口 名称 = 对象名 :: 方法名称
函数式接口 名称 = 类名 :: 静态方法名*/
Inter i1 = System.out :: println;
i1.fun("world");
Inter i2 = new InterImpl() :: fun;
i2.fun("12");// 17
Inter i3 = InterImpl2 :: fun;
i3.fun("120");// 130
}
}
@FunctionalInterface
interface Inter{
public abstract void fun(String s);
}
class InterImpl{
public void fun(String s){
System.out.println(Integer.parseInt(s) + 5);
}
}
class InterImpl2{
public static void fun(String s){
System.out.println(Integer.parseInt(s) + 10);
}
}
函数式接口
概述
1、Lambda表达式使用的前提,就是接口必须是一个函数式接口
2、定义:
如果在接口中,只有一个抽象方法,那么这个接口就是函数式接口
3、格式说明:
使用注解来检查当前接口是否是一个函数式接口
@FunctionalInterface
如果不是函数式接口,则编译报错
4、理解:
1、函数:想表达的是一个方法的内容,由于方法不在任何类中,所以称为函数
2、函数式接口:其实想表达的就是一个函数的声明
5、作用:
使用函数式接口表达函数的声明;使用函数式接口的实现类对象表达函数的实现
6、使用原因:
Java中不支持将函数作为一个数据,也就不能将这个函数进行各种传递,也就不能作为对象的成员变量存在
只能在方法外加一层接口的声明,将来可以传递方法所在接口的实现类对象,来间接的传递方法内容
总结: 方法在Java中不能作为一种数据类型存在, 只是功能, 因此将方法称为函数; 有些场景下,需要将方法作为参数传递的, 所以将这个方法封装在一个接口中, 而这个接口的存在就是为了方法穿上一件外衣, 需要方法场景下, 可以直接传递这个函数式接口, 相当于传递其中唯一的抽象方法
举例 : 客户,要求定义出一个方法功能, 对两个整数进行任意操作
-
两数求和
-
两数求差
-
两数乘积 * 2
-
... 无数需求, 都需要在同一个方法中完成
public int useTwoInt(int x, int y, 对于x和y两数的操作思想,操作方式){
}
常用内置函数式接口
1、说明:
Java8中提供了一些常用的函数式接口,在使用类似功能的时候,不需要额外定义接口,直接使用jdk中提供的即可
2、罗列:
Consumer<T>:消费型接口
void accept(T t);
Supplier<T>:供给型接口
T get();
Function<T, R>:函数型接口
R apply(T t);
Predicate<T>:断言型接口
boolean test(T t);
消费型接口
1、Consumer
2、名称:消费型接口
3、
抽象方法:void accept(T t);
4、作用:
1、当某个函数可以接收一个数据,并且处理这个数据,处理完成之后,不需要返回任何数据,这个函数需要当做数据来进行传递,就使用消费型接口
2、以前只能传递要处理的数据,现在也可以传递处理数据的方式
案例 : 定义出一个方法功能, 客户预计消费500元现金, 每一个客户对于500元的消费都不同, 将客户的消费方式实现出来
-
客户1 : 花了500元, 买了一把大宝剑
-
客户2 : 花了500元, 买了一堆化妆品
-
客户3 : 花了500元, 买了一双球鞋
-
.... 还有无限的客户有不同种消费方式
import java.util.function.Consumer;
public class TestConsumer {
public static void main(String[] args) {
// 函数式接口的实现类对象可以使用Lambda表达式实现
// 1. 实现客户1的需求
Consumer<Double> con1 = x-> System.out.println("花了" + x + "元,买了一把大宝剑");
useConsumer(500,con1);
// 2. 实现客户2的需求
Consumer<Double> con2 = x->{
if(x >= 500){
System.out.println("没钱,赞一下钱再去买");
}else{
System.out.println("花了" + x + "元买了一堆化妆品");
}
};
useConsumer(589,con2);
}
public static void useConsumer(double money, Consumer<Double> con){
// System.out.println("花了" + money + "元,买了一把大宝剑");
// 将Consumer<Double> con作为方法参数进行传递, 目的: 传递唯一的抽象方法accept(T t)
// accept方法表示对于一个double类型数据的使用
con.accept(money);
}
}
供给型接口
1、Supplier
2、名称:供给型接口
3、
抽象方法:T get()
4、作用:
1、如果需要定义函数,可以生产一个需要的数据,这个函数需要当做数据来进行传递,那么就可以使用供给型接口。
2、以前我们只能传递数据,现在可以传递生产数据的方式
案例 : 定义出一个方法功能, 能给客户返回出一个ArrayList
-
客户1 : 5个数据, 都是30-80之间的随机数
-
客户2 : 8个数据, 1-100之间的随机偶数
-
...
import java.util.ArrayList;
import java.util.Random;
import java.util.function.Supplier;
public class TestSupplier {
public static void main(String[] args) {
//1)客户1 : 5个数据, 都是30-80之间的随机数
Supplier<Integer> sup1 = ()->new Random().nextInt(51) + 30;
ArrayList<Integer> list = useSupplier(5,sup1);
System.out.println(list);
//2)客户2 : 8个数据, 1-100之间的随机偶数
Supplier<Integer> sup2 = ()->{
Random ran = new Random();
while(true){
// number表示1-100的任意一个随机整数
int number = ran.nextInt(100)+1;
// 验证,number为偶数,才能作为返回
if(number % 2 == 0){
return number;
}
}
};
System.out.println(useSupplier(8,sup2));
}
public static ArrayList<Integer> useSupplier(int count, Supplier<Integer> sup){
ArrayList<Integer> list = new ArrayList<>();
// 设计出一个count次数的循环,每次循环,向list集合中存储一个整数数据
for(int i = 1; i <= count; i++){
// 需要一个Integer类型的整数数据
list.add(sup.get());
}
return list;
}
}
函数型接口
1、Function<T, R>
2、名称:函数型接口
3、
抽象方法:R apply(T t)
4、作用:
1、如果需要定义一个函数,接收一个数据,将数据进行处理,完成之后,还能返回一个结果,就可以使用函数型接口
2、以前我们只能传递处理好之后的数据,或者将原始数据传入方法,现在可以传入处理方式
5、提供功能:
Function andThen(Function f)://在调用者处理方式之后,再进行参数的处理方式处理
案例1 : 定义一个方法功能, 根据整数x,计算出对应整数y, x数据由客户给出, y数据的计算方式根据客户要求决定
-
客户1 : y为x的2倍
-
客户2 : y与x相等
-
客户3 : y为x的平方
-
...
案例2 : 定义一个方法功能, 根据字符串x,计算出对应整数y, x数据由客户给出, y数据的计算方式根据客户要求决定
-
客户4 : x为”6” , 计算出x转换成整数后, 2倍结果
-
客户5 : x为”-2”, 计算出x转换成整数后, +1的结果
...
import java.util.function.Function;
public class TestFunction {
public static void main(String[] args) {
// 1)客户1 : y为x的2倍
Function<Integer,Integer> fun1 = x->x*2;
int y = useFunction(5,fun1);
System.out.println(y);// 10
// 2)客户2 : y与x相等
Function<Integer,Integer> fun2 = x->x;
System.out.println(useFunction(6,fun2));// 6
// 3) 客户3 : y为x的平方
Function<Integer,Integer> fun3 = x->x*x;
System.out.println(useFunction(-9,fun3));// 81
System.out.println("----------------");
// 1)客户4 : x为”6” , 计算出x转换成整数后, 2倍结果
Function<String,Integer> f1 = s->Integer.parseInt(s);
// Function<Integer,Integer> f2 = x->2*x;
System.out.println(useFun2("6",f1,fun1));// 12
// 2)客户5 : x为”-2”, 计算出x转换成整数后, +1的结果
Function<Integer,Integer> f3 = x->x+1;
System.out.println(useFun2("-2",f1,f3));// -1
}
/* 案例1 : 定义一个方法功能, 根据整数x,计算出对应整数y,
, y数据的计算方式根据客户要求决定
1)客户1 : y为x的2倍
2)客户2 : y与x相等
3)客户3 : y为x的平方
4)...*/
public static int useFunction(int x, Function<Integer,Integer> fun) {
return fun.apply(x);
}
/* 案例2 : 定义一个方法功能, 根据字符串x,计算出对应整数y, x数据由客户给出
, y数据的计算方式根据客户要求决定
1)客户4 : x为”6” , 计算出x转换成整数后, 2倍结果
2)客户5 : x为”-2”, 计算出x转换成整数后, +1的结果
...
分析:
1) 第一个参数 : 字符串类型数字
2) 第二个参数: 将字符串转换成整数类型 Function<String,Integer> fun
3) 第三个参数: 将2)中整数,运算得出最终的结果y
*/
public static int useFun2(String x,Function<String,Integer> fun,
Function<Integer,Integer> fun1){
/* Integer x1 = fun.apply(x);
return fun1.apply(x1);*/
return fun.andThen(fun1).apply(x);
}
}
断言型接口
1、Predicate
2、名称:断言型接口
3、抽象方法:boolean test(T t)
4、作用:
1、如果需要定义一个函数,接收一个数据,判断数据是否合法,返回一个boolean结果,就可以使用断言型接口
2、以前我们只能传递过滤好的数据,而现在既可以传递原始数据,也可以传递过滤的条件
5、提供的功能:
Predicate and(Predicate pre)://在调用者条件判断之后,再由参数条件判断,返回两个条件的都满足的判断对象
Predicate or(Predicate pre)://返回两个条件任意一个满足的判断对象
案例1 :
定义出一个方法, 需要客户提供一个容器ArrayList
-
客户1 : 要求容器中的所有数, 都能被2整除
-
客户2 : 要求所有的数据都不大于100
-
客户3 : 要求所有数据都小于100, 并且都是奇数
-
客户4 : 要求所有数据或者小于100, 或者是偶数
-
...
import java.util.ArrayList;
import java.util.function.Predicate;
public class TestPredicate {
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(12);
list1.add(11);
list1.add(120);
list1.add(111);
list1.add(67);
list1.add(88);
// 1)客户1 : 要求容器中的所有数, 都能被2整除
Predicate<Integer> pre = x->x%2==0;
System.out.println(usePredicate(list1,pre));// [12, 120, 88]
// 2)客户2 : 要求所有的数据都不大于100
Predicate<Integer> pre1 = x->x < 100;
System.out.println(usePredicate(list1,pre1));// [12, 11, 67, 88]
// 3)客户3 : 要求所有数据都小于100, 并且都是奇数
Predicate<Integer> pre2 = x->x < 100 && x % 2 != 0;
System.out.println(usePredicate(list1,pre2));// [11, 67]
Predicate<Integer> pre3 = x->x % 2 != 0;
// and 方法相当于将两个断言型接口的判断结果做与运算 : &&
System.out.println(usePredicate(list1,pre1.and(pre3)));// [11, 67]
// 4) 客户4 : 要求所有数据或者小于100, 或者是偶数
Predicate<Integer> pre4 = x->x < 100 || x % 2 == 0;
System.out.println(usePredicate(list1,pre4));// [12, 11, 120, 67, 88]
// or : 方法相当于将两个断言型接口的判断结果做或运算 : ||
System.out.println(usePredicate(list1,pre1.or(pre)));// [12, 11, 120, 67, 88]
}
public static ArrayList<Integer> usePredicate(ArrayList<Integer> list1,
Predicate<Integer> pre){
ArrayList<Integer> li = new ArrayList<>();
for(Integer i : list1){
if(pre.test(i)){
li.add(i);
}
}
return li;
}
}
StreamingAPI
Stream是jdk8增加的一个接口,该接口提供了一些对容器数据进行操作的规则,有了这些规则就可以不通过遍历容器就可以以完成对相关数据的操作处理。
注意: Stream虽然可以操作数据但是本身不能够存储任何数据
Stream类型数据的获取
1、Collection的获取:
调用**stream()**方法即可,返回Stream接口类型的实现类对象
因为stream是default默认方法, 因此可以被单列集合所有类型继承使用: List,Set
2、Map的获取:不能直接获取Stream类型
keySet().stream()
values().stream()
values() : //获取到Map集合中的所有的value值, 放置在Collection容器中
entrySet().stream()
3、数组的获取
Stream中的静态方法of方法,获取到数组上流资源使用Stream.of(数组)
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
import java.util.stream.Stream;
public class Demo01_StreamGet {
public static void main(String[] args) {
// 1. 单列集合: Collection 中stream()
ArrayList<String> list = new ArrayList<>();
// 2. Stream<T t> : Stream流资源操作的容器中数据类型, 作为Stream的泛型使用
Stream<String> s = list.stream();
// 3. Set集合获取stream流资源对象
HashMap<Integer,String> map = new HashMap<>();
Set<Integer> set = map.keySet();
Stream<Integer> str = set.stream();
// 4. 数组获取到stream流资源对象
String[] arr = {"张三","李四"};
Stream<String> str2 = Stream.of(arr);
}
}
Stream中的常用方法
1.Stream filter(Predicate p):
//按照指定的条件对stream中数据进行过滤【满足保留】
2.Stream limit(int num):
//只获取Stream中前num个元素
3.Stream skip(int num):
//跳过前num个数据获取之后的数据
4.Stream map(Function f):
//映射功能【把Stream中的数据映射为另一种数据】
5.Stream concat(Stream s1,Stream s2):这是一个静态方法
//拼接两个stream流为一个stream流
6.toArray():
//把stream流中的数据收集到数组中
7.collect(Collector c):
//把stream流中的数据收集到指定的集合中
Collector :参数的类型 是一个接口获取可以通过工具类Collectors的方法获取
常用:
获取List集合:Collectors.toList()
获取Set集合:Collectors.toSet()
8.forEach(Consumer c):
//用来使用stream流中的数据的
9.int count():
//返回stream流中元素的个数
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Demo01_StreamMethod {
public static void main(String[] args) {
// 1. 定义出一个字符串数组
String[] arr = {"蓉蓉","露露","百合","红玫瑰","牡丹","百合","牡丹绿","露露"};
// 2. 获取到该数组的Stream流资源对象
Stream<String> str1 = Stream.of(arr);
// 3. 过滤筛选出长度为2的字符串
/*
Stream filter(Predicate p):按照指定的条件对stream中数据进行过滤【满足保留】
Predicate p-->唯一抽象方法boolean test(T t): 用于实现每一个元素的验证规则
*/
Stream<String> str2 = str1.filter(x->x.length() == 2);
/*
4. 遍历筛选出的长度为2字符串结果
void forEach(Consumer c): 表示遍历Stream流资源中的每一个元素
参数列表 : Consumer c--->唯一抽象方法void accept(T t), 表示遍历过程
中的每一个元素要进行的使用操作
*/
// 蓉蓉 露露 百合 牡丹 百合 露露
// str2.forEach(x-> System.out.println(x));
// str1.filter(x->x.length() == 2).forEach(System.out :: println);
// 5. 将str2流资源中的前4个元素保留下来
// Stream limit(int num):
// Stream<String> str3 = str2.limit(4); // 蓉蓉 露露 百合 牡丹
// str3.forEach(x-> System.out.println(x));
// 6.Stream skip(int num):跳过前num个数据获取之后的数据
// Stream<String> str4 = str2.skip(3);// 牡丹 百合 露露
// str4.forEach(x-> System.out.println(x));
// 7. Stream map(Function f):映射功能【把Stream中的数据映射为另一种数据】
/*
map方法参数列表: 函数式接口Function<T,R>--->唯一抽象方法R apply(T t)
map方法功能 : 将Stream流资源中的每一个元素T,转换成R类型数据(R类型是任意引用数据类型)
str4流资源中的每一个字符串转换成字符数组
注意 : print和println方法,打印字符数组,不是打印数组地址,而是打印数组中字符内容
在所有的数组打印中, 只有字符数组是特殊的,其他所有数组打印全部都是地址
*/
// str4.map(x->x.toCharArray()).forEach(x-> System.out.print(x));
/*Stream<byte[]> str5 = str4.map(x->x.getBytes());
str5.forEach(x-> System.out.println(Arrays.toString(x)));*/
/* String[] arr1 = {"蓉蓉"};
// 8. concat(str1,str2): 将str1和str2两个流资源合并成一个流资源
Stream<String> str2 = Stream.of(arr1);
Stream<String> strAll = Stream.concat(str1,str2);
// 蓉蓉 露露 百合 牡丹 百合 露露 牡丹 百合 露露
strAll.forEach(x-> System.out.print(x + " "));*/
// 9.toArray():把stream流中的数据收集到数组中 Object[]
/*Object[] objArr = str2.toArray();
System.out.println(Arrays.toString(objArr));*/
// 10. collect(Collector c):
// 把stream流中的数据收集到指定的集合中
// Collector :参数的类型 是一个接口获取可以通过工具类Collectors的方法获取
// 常用:
// 获取List集合:Collectors.toList()
// 获取Set集合: Collectors.toSet()
/*List<String> list = str2.collect(Collectors.toList());
System.out.println(list);// [蓉蓉, 露露, 百合, 牡丹, 百合, 露露]*/
/*Set<String> set = str2.collect(Collectors.toSet());
System.out.println(set);// [蓉蓉, 露露, 百合, 牡丹]*/
// 11.int count():返回stream流中元素的个数
System.out.println(str2.count());// 6
}
}
注解
-
概述: 注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
-
注解的作用:
- 编写文档:通过代码里标识的注解生成文档【例如,生成文档javadoc文档】
@author @version @param @return
- 代码分析:通过代码里标识的注解对代码进行分析【例如,注解的反射】
注解的底层实现原理 : 就是通过反射机制完成的
- 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【例如,Override,FunctionalInterface】
- 常见注解:
-
@author:用来标识作者名
-
@version:用于标识对象的版本号,适用范围:文件、类、方法。
-
@Override:
用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。
注解和注释是完全不同的
-
注解会影响代码的运行, 注释不会影响代码的运行;
-
注释是给程序员看的, 注解不是给人看的, 是对于程序的编译和运行起到一定作用的
自定义注解
定义格式:
元注解
public @interface 注解名称{
属性列表;
}
注解本质上就是一个接口,该接口默认继承Annotation接口
相当于:
public interface MyAnnotation extends java.lang.annotation.Annotation {}
举例 :
public @interface Person{}
注解不需要写任何的逻辑, 因为注解式标注
元注解
-
元注解:用来描述注解的注解
-
分类:
@Target:描述注解能够作用的位置(指定使用的地方)
value值:是一个ElementType[]
常用取值:
ElementType.TYPE : //针对类和接口使用
ElementType.METHOD : //针对方法使用
ElementType.FILED : //针对变量使用
@Retention:注解被保留的阶段
value值:是一个RetentionPolicy枚举RetentionPolicy(翻译: 保留策略)
取值:
1、RetentionPolicy.SOURCE---【在源代码中不会到字节码文件中】
//编译器运行时, 进行识别(编译时期识别), 例如@Override
2、RetentionPolicy.CLASS---【编译到字节码文件中但是运行时jvm不会读取】
//注解信息保留到类对应的class文件中
3、RetentionPolicy.RUNTIME--【编译到字节码文件中运行时jvm会读取,运行时使用】
//运行时期生效的注解一般都是筛选的作用 例如: @FunctionalInterface
例如: 程序运行时起作用的,@WebServlet, 代码运行时,检测哪些类型是Servlet
@Documented :描述注解是否被抽取到API文档中
@Inherited:注解是否被子类继承
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InitMethod {
}
注解通过反射机制解析运行
public class UseInitMethod {
@InitMethod
public void init(){
System.out.println("类UseInitMethod一旦进入内存,init方法马上运行");
}
@InitMethod
public void test(){
System.out.println("类UseInitMethod一旦进入内存,test方法马上运行");
}
public void fun(){
System.out.println("只有创建对象调用才运行");
}
}
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
// 测试带有自定义的InitMethod注解的方法,通过反射机制运行
public class TestInitMethod {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException {
// 1. 获取到带有注解的类型在内存中对应的Class字节码文件对象
Class c = UseInitMethod.class;
// 2. 通过Class类型对象, 获取到所有方法
Method[] m = c.getDeclaredMethods();
// 3. 遍历m数组,获取到每一个方法
for(Method me : m){
// 注意 : Method类型的父类Accessiable中继承给Method的方法
// isAnnotationPresent(Class 注解): 验证方法上是否带有参数的注解
// 如果带有返回true, 如果没有返回false
boolean boo = me.isAnnotationPresent(InitMethod.class);
if(boo){// 方法上带有InitMethod注解,马上运行
me.invoke(c.newInstance());
}
}
}
}
@Override注解模拟解析
public class Zi extends FU{
/*
剖析Override注解解析方式:
1) 当类进入到编译阶段,马上检测当前类型中,有哪些方法使用了Override注解
2) 将所有的带有Override方法获取到
3) 遍历获取每一个方法,到父类类型中,寻找,是否有与重写的方法一样的方法声明(包括修饰符的检查)
a : 父类有, Override成立,不报错
b : 父类没有, 或者权限不对,报错
*/
@Override
public void show(){
System.out.println("我是子类重写");
}
/*@Override
public void fun(){
System.out.println("我是子类重写");
}*/
}
注解的属性
-
属性的作用: 可以让用户在使用注解时传递参数,让注解的功能更加强大。
-
属性的格式:
public @interface 注解名称{
属性列表;
}
格式1:数据类型 属性名();
格式2:数据类型 属性名() default 默认值;
-
属性适用的数据类型
- 八种基本数据类型(int,float,boolean,byte,double,char,long,short)
- String类型,Class类型,枚举类型,注解类型
- 以上所有类型的一维数组
-
注意事项:
- 如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。
- 如果属性没有默认值,那么在使用注解时一定要给属性赋值。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Person {
// 向Person注解中添加属性
String name();
int age() default 18;
String sex() default "男";
}
注解的解析
注解只是标注, 注解在加载时, 通过反射机制将注解需要完成的功能进行实现
在程序中使用(解析)注解的步骤(获取注解中定义的属性值):
-
获取注解定义的位置的对象 (Class,Method,Field)
-
获取指定的注解 Class类中方法: getAnnotation(Class)
-
调用注解中的抽象方法获取配置的属性值
使用格式:
@注解名(属性名=属性值,属性名=属性值,属性名=属性值...)
@Person(name="金莲",age=16)
public class TestPerson {
public static void main(String[] args) {
// 1. 获取到使用了注解类型对应的Class字节码文件对象
Class<TestPerson> c = TestPerson.class;
// 2. 获取到类TestPerson上对应的注解
// Class 类型中: 方法功能getAnnotation(Class 注解),表示直接将类上注解获取到
Person per = c.getAnnotation(Person.class);
// 3. 获取Person注解中属性值
String name = per.name();
int age = per.age();
String sex = per.sex();
System.out.println(name + "--" + age + "--" + sex);// 金莲--16--男
}
}