Java 基础学习(十九)网络编程、反射

1 Socket编程

1.1 Socket编程概述

1.1.1 Socket简介

在网络编程中,Socket(套接字)是一种抽象概念,它用于在不同计算机之间进行通信。Socket可以看作是一种通信的端点,可以通过Socket与其他计算机上的程序进行数据传输。

1.1.2 Java中的套接字编程

在Java中,Socket是一个类,可以用于创建客户端和服务器端的网络连接,并进行数据传输。

在Java网络编程中,Socket类与IO类常常结合使用。通过Socket类建立网络连接后,可以使用它提供的输入输出流对象来进行数据的读取和写入。

可以通过桥和路的关系来理解Socket与IO的关系。假设我们要实现两岸的交通,首先需要搭建一座桥梁连通两岸,这是通过Socket完成的。有了桥之后,我们可以通过桥上的路实现双向的车辆流动,这是通过IO实现的。

1.1.3 Socket和TCP/UDP的关系

Socket是实现网络编程的工具,而TCP/UDP是网络传输协议。

Socket编程常用于实现基于TCP或UDP的网络通信。TCP和UDP是在传输层上的协议,而Socket是在应用层与传输层之间的一个接口。通过Socket,应用程序可以使用TCP或UDP协议与其他计算机进行通信。

在Java中,可以使用Socket类来创建TCP连接,也可以使用DatagramSocket类来创建UDP连接。通过Socket类和DatagramSocket类,Java程序员可以方便地实现TCP和UDP通信,并进行数据传输。

1.2 聊天室案例

1.2.1 聊天室案例概述

使用Socket网络编程逐步实现一个聊天室应用。此案例需要按照版本进行迭代,如图所示:

每个版本可以存放在一个单独的package中,开发下一个版本时,将上一个版本的代码复制到新的package中,再进行后续的开发。

1.2.2 Socket编程

在Java网络编程中,Socket 和 ServerSocket是最常见的2个类,均位于java.net包中:

  • Socket:用于建立网络连接;在连接成功时,应用程序两端都会产生一个Socket实例
  • ServerSocket:用于服务端

TCP套接字的通信模型如下图所示:

1.2.3 ServerSocket概述

运行在服务端的ServerSocket主要完成两个工作:

1、向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立连接。

2、监听端口,一旦一个客户端建立连接,会立即返回一个Socket。通过这个Socket就可以和该客户端交互了。

代码示意如下:

//创建ServerSocket并申请服务端口8088
ServerSocket server = new ServerSocket(8088);
/*方法会产生阻塞,直到某个Socket连接并返回请求连接的Socket*/
Socket socket = server.accept();

我们可以把ServerSocket想象成某客服的"总机":用户打电话到总机,总机分配一个电话使得服务端与你沟通;我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克风(输出流),通过它们就可以与对方交流了。交互模型如下图所示:

1.2.4 【案例】聊天室案例V01

本案例需要实现的需求:

  • 分别开发客户端类和服务端类
  • 实现客户端与服务端的连接

首先开发Client端功能:

1、定义Client类:构造器中创建Socket并定义连接

2、定义start方法:用于封装后续业务功能

3、定义main方法

代码示意如下:

import java.io.IOException;
import java.net.Socket;
/*** 聊天室客户端*/
public class Client {// 客户端使用的Socketprivate Socket socket;/*** 构造方法,用来初始化客户端*/public Client(){try {System.out.println("正在连接服务端...");/*实例化Socket时要传入两个参数参数1:服务端的地址信息可以是IP地址,如果连接本机可以写"localhost"参数2:服务端开启的服务端口*/socket = new Socket("localhost",8088);System.out.println("与服务端建立连接!");} catch (IOException e) {e.printStackTrace();}}/*** 客户端开始工作的方法*/public void start(){}public static void main(String[] args) {Client client = new Client();client.start();}
}

开发Server端功能:

1、定义Server类:构造器中创建ServerSocket

2、定义start方法:用accept方法,侦听连接

3、定义main方法

代码示意如下:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/*** 聊天室服务端*/
public class Server {// 服务端使用的SocketServerprivate ServerSocket serverSocket;/*** 服务端构造方法,用来初始化*/public Server(){try {System.out.println("正在启动服务端...");/*实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他应用程序占用的端口相同,否则会抛出异常:java.net.BindException:address already in use端口是一个数字,取值范围:0-65535之间。6000之前的端口不要使用,密集绑定系统应用和流行应用程序。*/serverSocket = new ServerSocket(8088);System.out.println("服务端启动完毕!");} catch (IOException e) {e.printStackTrace();}}/*** 服务端开始工作的方法*/public void start(){try {System.out.println("等待客户端连接...");/*ServerSocket提供了接受客户端连接的方法:Socket accept()这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端的连接,直到一个客户端连接,此时该方法会立即返回一个Socket实例通过这个Socket就可以与客户端进行交互了。可以理解为此操作是接电话,电话没响时就一直等。*/Socket socket = serverSocket.accept();System.out.println("一个客户端连接了!");} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {Server server = new Server();server.start();}
}

1.2.5 获取网络输入流和网络输出流

使用 Socket 通讯时,可以通过 Socket 获取输入流与输出流,从而实现数据信息的交互。

  • InputStream getInputStream():该方法用于返回此套接字的输入流
  • OutputStream getOutputStream():该方法用于返回此套接字的输出流

代码示意如下:

public void testSocket()throws Exception {Socket socket = new Socket("localhost", 8088);InputStream in = socket.getInputStream();OutputStream out = socket.getOutputStream();
}

需要通过流操作来实现信息的发送和读取。操作过程如下图所示:

1.2.6 【案例】聊天室案例V02

本案例实现的需求:

  • 客户端向服务器发送一条消息
  • 服务端收到消息后输出到控制台

Client端实现信息的发送:start方法中添加代码,实现信息的发送。

代码示意如下:

import java.io.*;
import java.net.Socket;
/*** 聊天室客户端*/
public class Client {// 客户端使用的Socketprivate Socket socket;/*** 构造方法,用来初始化客户端*/public Client() {try {System.out.println("正在连接服务端...");socket = new Socket("localhost", 8088);System.out.println("与服务端建立连接!");} catch (IOException e) {e.printStackTrace();}}/*** 客户端开始工作的方法*/public void start() {try {/*Socket提供了一个方法:OutputStream getOutputStream()该方法获取的字节输出流写出的字节会通过网络发送给对方计算机。*///低级流,将字节通过网络发送给对方OutputStream out = socket.getOutputStream();//高级流,负责衔接字节流与字符流,并将写出的字符按指定字符集转字节OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");//高级流,负责块写文本数据加速BufferedWriter bw = new BufferedWriter(osw);//高级流,负责按行写出字符串,自动行刷新PrintWriter pw = new PrintWriter(bw, true);pw.println("你好服务端!");} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {Client client = new Client();client.start();}
}

Server端实现信息的读取:start方法中添加代码,实现信息的读取及展示。

代码示意如下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/*** 聊天室服务端*/
public class Server {// 服务端使用的SocketServerprivate ServerSocket serverSocket;/*** 服务端构造方法,用来初始化*/public Server(){try {System.out.println("正在启动服务端...");serverSocket = new ServerSocket(8088);System.out.println("服务端启动完毕!");} catch (IOException e) {e.printStackTrace();}}/*** 服务端开始工作的方法*/public void start(){try {System.out.println("等待客户端连接...");Socket socket = serverSocket.accept();System.out.println("一个客户端连接了!");/*Socket提供的方法:InputStream getInputStream()获取的字节输入流读取的是对方计算机发送过来的字节*/InputStream in = socket.getInputStream();InputStreamReader isr = new InputStreamReader(in,"UTF-8");BufferedReader br = new BufferedReader(isr);String message = br.readLine();System.out.println("客户端说:"+message);} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {Server server = new Server();server.start();}
}

1.2.7 close方法

当使用 Socket 进行通讯完毕后,需要调用 close方法关闭 Socket 以释放系统资源。关闭了套接字,也会同时关闭由此获取的输入流与输出流。完整流程如下所示:

1.2.8 【案例】聊天室案例V03

本案例需要实现客户端循环发送消息给服务端。

Client端代码示意如下:

import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/*** 聊天室客户端*/
public class Client {// 客户端使用的Socketprivate Socket socket;/*** 构造方法,用来初始化客户端*/public Client() {try {System.out.println("正在连接服务端...");socket = new Socket("localhost", 8088);System.out.println("与服务端建立连接!");} catch (IOException e) {e.printStackTrace();}}/*** 客户端开始工作的方法*/public void start() {try {/*Socket提供了一个方法:OutputStream getOutputStream()该方法获取的字节输出流写出的字节会通过网络发送给对方计算机。*///低级流,将字节通过网络发送给对方OutputStream out = socket.getOutputStream();//高级流,负责衔接字节流与字符流,并将写出的字符按指定字符集转字节OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");//高级流,负责块写文本数据加速BufferedWriter bw = new BufferedWriter(osw);//高级流,负责按行写出字符串,自动行刷新PrintWriter pw = new PrintWriter(bw, true);Scanner scanner = new Scanner(System.in);while(true) {String line = scanner.nextLine();if("exit".equalsIgnoreCase(line)){break;}pw.println(line);}} catch (IOException e) {e.printStackTrace();}finally {try {/*通讯完毕后调用socket的close方法。该方法会给对方发送断开信号。*/socket.close();} catch (IOException e) {e.printStackTrace();}}}public static void main(String[] args) {Client client = new Client();client.start();}
}

Server端代码示意如下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/*** 聊天室服务端*/
public class Server {// 服务端使用的SocketServerprivate ServerSocket serverSocket;/*** 服务端构造方法,用来初始化*/public Server(){try {System.out.println("正在启动服务端...");serverSocket = new ServerSocket(8088);System.out.println("服务端启动完毕!");} catch (IOException e) {e.printStackTrace();}}/*** 服务端开始工作的方法*/public void start(){try {System.out.println("等待客户端连接...");Socket socket = serverSocket.accept();System.out.println("一个客户端连接了!");/*Socket提供的方法:InputStream getInputStream()获取的字节输入流读取的是对方计算机发送过来的字节*/InputStream in = socket.getInputStream();InputStreamReader isr = new InputStreamReader(in,"UTF-8");BufferedReader br = new BufferedReader(isr);String message = null;while((message = br.readLine())!=null) { // br.readLine()方法会处于阻塞状态,直到收到消息// br.readLine()返回null,说明客户端发送了断开连接的信息System.out.println("客户端说:" + message);}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {Server server = new Server();server.start();}
}

1.3 聊天室案例(多线程)

1.3.1 Server端多线程模型

若想使一个服务端可以支持多客户端连接,我们需要解决以下问题:

  • 循环调用 accept 方法侦听客户端的连接
  • 使用线程来处理单一客户端的数据交互

因为需要处理多客户端,所以服务端要周期性循环调用accept方法,但该方法会产生阻塞,所以与某个客户端的交互就需要使用线程来并发处理。

服务端的流程如下所示:

1.3.2 【案例】聊天室案例V04

本案例需要实现服务器端能够接受多个客户端的访问。

Server端代码如下所示:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/*** 聊天室服务端*/
public class Server {// 服务端使用的SocketServerprivate ServerSocket serverSocket;/*** 服务端构造方法,用来初始化*/public Server(){try {System.out.println("正在启动服务端...");serverSocket = new ServerSocket(8088);System.out.println("服务端启动完毕!");} catch (IOException e) {e.printStackTrace();}}/*** 服务端开始工作的方法*/public void start(){try {while(true) {System.out.println("等待客户端链接...");Socket socket = serverSocket.accept();System.out.println("一个客户端链接了!");//启动一个线程与该客户端交互ClientHandler clientHandler = new ClientHandler(socket);Thread t = new Thread(clientHandler);t.start();}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {Server server = new Server();server.start();}/*** 定义线程任务* 目的是让一个线程完成与特定客户端的交互工作*/private class ClientHandler implements Runnable{private Socket socket;public ClientHandler(Socket socket){this.socket = socket;}public void run(){try{InputStream in = socket.getInputStream();InputStreamReader isr = new InputStreamReader(in, "UTF-8");BufferedReader br = new BufferedReader(isr);String message = null;String name = Thread.currentThread().getName();while ((message = br.readLine()) != null) {System.out.println(name + ": 客户端说:" + message);}}catch(IOException e){e.printStackTrace();}}}
}

Client端代码不需要改变,通过IDEA设置启动多个客户端程序,思路如下:

 操作步骤如下:

1.3.3 获取本地地址和端口号

可以通过 Socket 获取本地的地址以及端口号:

  • int getLocalPort():用于获取本地使用的端口号
  • InetAddress getLocalAddress():用于获取套接字绑定的本地地址

使用 InetAddress 获取本地的地址:

  • String getCanonicalHostName():获取此 IP 地址的完全限定域名
  • String getHostAddress():返回 IP 地址字符串(以文本表现形式)

代码示意如下:

public void testSocket()throws Exception {Socket socket = new Socket("localhost",8088);InetAddress add = socket.getLocalAddress();System.out.println(add.getCanonicalHostName());System.out.println(add.getHostAddress());System.out.println(socket.getLocalPort());
}

1.3.4 获取远端地址和端口号

通过 Socket 获取远端的地址以及端口号

  • int getPort():用于获取远端使用的端口号
  • InetAddress getInetAddress():该方法用于获取套接字绑定的远端地址

代码示意如下:

public void testSocket()throws Exception {Socket socket = new Socket("localhost",8088);InetAddress inetAdd = socket.getInetAddress();System.out.println(inetAdd.getCanonicalHostName());System.out.println(inetAdd.getHostAddress());System.out.println(socket.getPort());
}

1.3.5 【案例】聊天室案例V05

本案例需求:服务端发送消息给客户端。

Server端代码如下所示:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/*** 聊天室服务端*/
public class Server {// 服务端使用的SocketServerprivate ServerSocket serverSocket;/*** 服务端构造方法,用来初始化*/public Server(){try {System.out.println("正在启动服务端...");serverSocket = new ServerSocket(8088);System.out.println("服务端启动完毕!");} catch (IOException e) {e.printStackTrace();}}/*** 服务端开始工作的方法*/public void start(){try {while(true) {System.out.println("等待客户端链接...");Socket socket = serverSocket.accept();System.out.println("一个客户端链接了!");//启动一个线程与该客户端交互ClientHandler clientHandler = new ClientHandler(socket);Thread t = new Thread(clientHandler);t.start();}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {Server server = new Server();server.start();}/*** 定义线程任务* 目的是让一个线程完成与特定客户端的交互工作*/private class ClientHandler implements Runnable{private Socket socket;private String host; // 记录客户端的IP地址信息public ClientHandler(Socket socket){this.socket = socket;// 通过socket获取远端计算机地址信息this.host = socket.getInetAddress().getHostAddress();}public void run(){try{InputStream in = socket.getInputStream();InputStreamReader isr = new InputStreamReader(in, "UTF-8");BufferedReader br = new BufferedReader(isr);OutputStream out = socket.getOutputStream();OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");BufferedWriter bw = new BufferedWriter(osw);PrintWriter pw = new PrintWriter(bw,true);String message = null;String name = Thread.currentThread().getName();while ((message = br.readLine()) != null) {System.out.println(name + ": 客户端说:" + message);// 将消息回复给客户端pw.println(this.host +"说:"+message);}}catch(IOException e){e.printStackTrace();}}}
}

Client端代码如下所示:

import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/*** 聊天室客户端*/
public class Client {private Socket socket;public Client() {try {System.out.println("正在连接服务端...");socket = new Socket("localhost", 8088);System.out.println("与服务端建立连接!");} catch (IOException e) {e.printStackTrace();}}/*** 客户端开始工作的方法*/public void start() {try {OutputStream out = socket.getOutputStream();OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");BufferedWriter bw = new BufferedWriter(osw);PrintWriter pw = new PrintWriter(bw, true);//通过socket获取输入流读取服务端发送过来的消息InputStream in = socket.getInputStream();InputStreamReader isr = new InputStreamReader(in,"UTF-8");BufferedReader br = new BufferedReader(isr);Scanner scanner = new Scanner(System.in);while(true) {String line = scanner.nextLine();if("exit".equalsIgnoreCase(line)){break;}pw.println(line);// 读取服务器发来的消息并输出到控制台line = br.readLine();System.out.println(line);}} catch (IOException e) {e.printStackTrace();}finally {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}public static void main(String[] args) {Client client = new Client();client.start();}
}

1.3.6 【案例】聊天室案例V06

本案例需要实现服务端向所有的客户端推送消息,如下所示:、

 本案例仅需要更新服务端,代码示意如下:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/*** 聊天室服务端*/
public class Server {private ServerSocket serverSocket;// 存放所有客户端输出流,用于广播消息private List<PrintWriter> allOut = new ArrayList();public Server(){try {System.out.println("正在启动服务端...");serverSocket = new ServerSocket(8088);System.out.println("服务端启动完毕!");} catch (IOException e) {e.printStackTrace();}}/*** 服务端开始工作的方法*/public void start(){try {while(true) {System.out.println("等待客户端链接...");Socket socket = serverSocket.accept();System.out.println("一个客户端链接了!");//启动一个线程与该客户端交互ClientHandler clientHandler = new ClientHandler(socket);Thread t = new Thread(clientHandler);t.start();}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {Server server = new Server();server.start();}/*** 定义线程任务* 目的是让一个线程完成与特定客户端的交互工作*/private class ClientHandler implements Runnable{private Socket socket;private String host; // 记录客户端的IP地址信息public ClientHandler(Socket socket){this.socket = socket;// 通过socket获取远端计算机地址信息this.host = socket.getInetAddress().getHostAddress();}public void run(){try{InputStream in = socket.getInputStream();InputStreamReader isr = new InputStreamReader(in, "UTF-8");BufferedReader br = new BufferedReader(isr);OutputStream out = socket.getOutputStream();OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");BufferedWriter bw = new BufferedWriter(osw);PrintWriter pw = new PrintWriter(bw,true);// 将该输出流存入allOut中allOut.add(pw);String message = null;String name = Thread.currentThread().getName();while ((message = br.readLine()) != null) {System.out.println(name + ": 客户端说:" + message);// 将消息推送给所有客户端for(PrintWriter o: allOut){o.println(this.host +"说:"+message);}}}catch(IOException e){e.printStackTrace();}}}
}

1.3.7 【案例】聊天室案例V07

在上一个版本中,客户端需要先发消息,才能接收到一条推送的消息,这与聊天室的需求不符。本案例继续改进:实现客户端收发消息的分离,不论是否发送消息,都可以接收到完整的推送消息。逻辑如下所示:

 本案例仅需要更新客户端,代码示意如下:

import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/*** 聊天室客户端*/
public class Client {private Socket socket;public Client() {try {System.out.println("正在连接服务端...");socket = new Socket("localhost", 8088);System.out.println("与服务端建立连接!");} catch (IOException e) {e.printStackTrace();}}/*** 客户端开始工作的方法*/public void start() {try {//启动读取服务端发送过来消息的线程ServerHandler handler = new ServerHandler();Thread t = new Thread(handler);t.setDaemon(true);t.start();OutputStream out = socket.getOutputStream();OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");BufferedWriter bw = new BufferedWriter(osw);PrintWriter pw = new PrintWriter(bw, true);Scanner scanner = new Scanner(System.in);while(true) {String line = scanner.nextLine();if("exit".equalsIgnoreCase(line)){break;}pw.println(line);}} catch (IOException e) {e.printStackTrace();}finally {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}public static void main(String[] args) {Client client = new Client();client.start();}/*** 该线程负责接收服务端发送过来的消息*/private class ServerHandler implements Runnable{public void run(){//通过socket获取输入流读取服务端发送过来的消息try {InputStream in = socket.getInputStream();InputStreamReader isr = new InputStreamReader(in,"UTF-8");BufferedReader br = new BufferedReader(isr);String line;//循环读取服务端发送过来的每一行字符串while((line = br.readLine())!=null){System.out.println(line);}} catch (IOException e) {e.printStackTrace();}}}
}

2 反射

2.1 反射概述

2.1.1 什么是反射

反射机制(Reflection)是程序在运行时能够获取自身的信息。在Java中,只要给定类的名字,就可以通过反射机制来获取类的所有属性和方法。

反射的作用:

  • 可以动态查看一个类或对象的所有属性和方法,包括使用private修饰的属性和方法
  • 可以动态加载类
  • 可以动态的创建一个类的实例
  • 可以动态调用一个对象的方法

2.1.2 反射的优缺点

反射的优点:

  • 反射允许我们在程序运行期间获得类的信息并操作一个类中的方法,因此可以提高代码的灵活性和扩展性
  • 反射是Java中很多高级特性的基础,比如后面会介绍的注解、动态代理等特性
  • 在很多框架中,对反射技术的使用也非常多,比如大名鼎鼎的Spring框架、各类ORM框架、RPC框架等

反射的缺点:

  • 反射的代码的可读性和可维护性都比较低
  • 反射的代码执行的性能低

开发者应该在业务代码中尽量避免使用反射。但是,作为一个合格的Java开发者,需要具备读懂中间件和框架中反射代码的能力和使用反射解决特定问题的能力。

2.1.3 反射API

Java提供了反射相关的API,核心是java.lang.Class类,用于加载类和获取类的相关信息。

Java 反射 API 位于 java.lang.reflect 包中,主要包括:

  • Constructor类:用来描述一个类的构造方法
  • Field类:用来描述一个类的成员变量
  • Method类:用来描述一个类的方法
  • Modifier类:用来描述类内各元素的修饰符
  • Array:用来对数组进行操作

2.2 Class类

2.2.1 Class类概述

java.lang.Class类是Java反射机制的基础。从面向对象编程的角度,Class类是对Java程序中的“类”进行的抽象,每个Class的对象代表一个被JVM加载到内存中的“类”。

在程序运行时,JVM首先检查要加载的类对应的Class对象是否已经创建。如果没有创建,JVM会根据类名查找.class文件,将其加载到内存中,并创建相应的Class对象。如下图所示:

 需要注意,Class类的构造器被设计为私有的,也就是说开发者不能主动创建Class类的对象。Class类的对象仅能由JVM创建。

2.2.2 类加载器

类加载器(Class Loader)是JVM的一个子系统,负责将class文件加载到内存中,然后在堆中创建一个代表这个类的Class对象,作为方法区中类数据的访问入口。

2.2.3 获取Class对象

开发者可以通过4种方式获取一个Java类的Class对象:

  • 调用对象的getClass()方法获取Class对象
  • 根据类名.class获取Class对象
  • 根据Class中的静态方法Class.forName()获取Class对象
  • 通过类加载器ClassLoader加载类并获取Class对象

2.2.4 【案例】动态加载类示例

编写代码,实现动态加载类。代码示意如下:

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
public class ReflectDemo1 {public static void main(String[] args) throws Exception {String str = "abc";// 调用对象的getClass()方法获取Class对象Class clazz1 = str.getClass();System.out.println(clazz1.getName());// 根据类名.class获取Class对象Class clazz2 = String.class;System.out.println(clazz2.getName());// 根据Class中的静态方法Class.forName()获取Class对象Class clazz3 = Class.forName("java.lang.String");System.out.println(clazz3.getName());// 通过类加载器ClassLoader加载类对象ClassLoader classLoader = ReflectDemo1.class.getClassLoader();Class clazz4 = classLoader.loadClass("java.util.ArrayList");System.out.println(clazz4.getName());}
} 

2.2.5 动态创建对象

Class 提供了动态创建对象的方法:

Object newInstance()

newInstance方法将调用类信息中的无参数构造器创建对象。由于该方法在异常处理上存在缺陷,故在Java 9版本开始被标记为过期方法。

在Java 9及后续版本中,可以通过Constructor的API来动态创建对象。

clazz.getDeclaredConstructor().newInstance()

2.2.6 【案例】动态创建对象示例

编写代码,调用无参构造器动态创建实例。代码示意如下:

public class Student {private String name;private Integer age; 
}
public class ReflectDemo2 {public static void main(String[] args) throws Exception {//1加载类对象ClassLoader classLoader = ReflectDemo2.class.getClassLoader();Class cls = classLoader.loadClass("jaf.day07.cases.reflect.Student");//2通过类对象实例化Object o1 = cls.newInstance();//调用无参构造器,已过期System.out.println(o1);Object o2 = cls.getDeclaredConstructor().newInstance();System.out.println(o2);// 3通过带参构造器实例化Object o3 = cls.getDeclaredConstructor(String.class, Integer.class).newInstance("Tom", 18);System.out.println(o3);}
}

2.2.7 动态调用方法

Class 可以动态获取方法:

Method getMethod() 

其中,返回的Method 代表方法信息,可以利用Method API获取方法对详细信息,如:方法名,返回值类型,参数类型列表等。

Method 还可以动态执行一个方法:

Object invoke(Object obj, Object… args)
  • 参数1:obj 代表一个对象,该对象上一定包含当前方法,否则将出现调用异常;如果obj为null则抛出空指针异常
  • 参数2:args 代表调用方法时候传递的实际参数,如果没有参数可以不用或者传递null,但是要注意参数的个数和类型必须和要调用的方法匹配,否则将出现参数错误异常
  • 返回值:表示方法执行的结果,因为可能是任何类型,则其类型为Object,调用没有返回值的方法则返回值为null

当被调用方法执行出现异常时候抛出InvocationTargetException。

2.2.8 【案例】动态调用方法示例

编写代码,动态调用无参方法。代码示意如下:

import java.lang.reflect.Method;
import java.util.Scanner;
public class ReflectDemo3 {public static void main(String[] args) throws Exception {//实例化Class cls = Student.class;Object o = cls.getDeclaredConstructor().newInstance();//调用方法//1 通过类对象获取要调用的方法Method method = cls.getMethod("toString");//2 通过方法对象执行该方法Object result = method.invoke(o);System.out.println(result);//3 调用带参方法Method method1 = cls.getMethod("setName", String.class);method1.invoke(o, "Jerry");Method method2 = cls.getMethod("setAge", Integer.class);method2.invoke(o, 18);System.out.println(o);}
}

2.3 注解

2.3.1 注解概述

Java注解(Annotation)是一种元数据(metadata)机制,可以将各种信息(例如类、方法、变量等)与程序元素相关联。Java注解可以用于许多目的,例如:

  • 为代码提供元数据信息,例如文档、版本号等
  • 为编译器提供指示,例如抑制警告、生成代码等
  • 为运行时提供指示,例如启用事务、配置参数等

Java注解以@符号开头,后跟注解名称和注解元素。注解元素可以是单个值或一组值,类似于方法的参数。

Java提供了一些内置的注解,例如@Override、@Deprecated和@SupressWarnings等,也可以创建自定义的注解。

2.3.2 使用Java注解

使用Java注解的一般步骤如下:

1、定义注解

使用 @interface 关键字定义注解类型及其成员变量。

代码如下所示:

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 MyAnnotation {String value();
}

@Target(ElementType.METHOD) 是Java注解中的一个元注解(meta-annotation),用于标记自定义注解的适用范围,指定了注解可以应用在哪些程序元素上。

  • 本例中,@Target(ElementType.METHOD) 表示该注解只能应用于方法(Method)上。
  • 换句话说,这个自定义注解只能标记在方法上,不能标记在其他程序元素(如类、字段、变量等)上。

@Retention(RetentionPolicy.RUNTIME) 是Java注解中的一个元注解,用于指定注解的生命周期。

  • 本例中,@Retention(RetentionPolicy.RUNTIME) 表示该注解在运行时(Runtime)保留,可以通过反射机制获取注解的信息。

value() 是Java注解中的一个特殊方法,它是一种简化形式的注解成员变量,可以省略成员变量名,直接使用注解时赋值。

2、应用注解

在需要使用注解的地方,使用 @注解名 标记程序元素,并为注解成员变量赋值。代码如下所示:

public class MyClass {@MyAnnotation("Hello, world!")public void myMethod() {// do something}
}

在使用注解时,我们可以省略成员变量名,直接给 value 赋值,例如 @MyAnnotation("Hello, world!")。这等价于 @MyAnnotation(value = "Hello, world!")。

需要注意的是,如果注解中有多个成员变量,而我们只想设置其中的一个成员变量,那么在赋值时必须使用成员变量名,而不能省略成员变量名。

3、处理注解

使用反射机制获取程序元素上的注解,并根据注解的值进行处理。代码如下所示:

import java.lang.reflect.Method;
public class AnnotationDemo {public static void main(String[] args) throws Exception {//获取方法信息Method method = MyClass.class.getMethod("myMethod");//获取方法上的注解信息MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);//获取注解的值String value = annotation.value();System.out.println(value);//检查方法上是否标注了@MyAnnotation注解boolean hasAnnotation = method.isAnnotationPresent(MyAnnotation.class);System.out.println(hasAnnotation);}
}

method.getAnnotation(MyAnnotation.class) 是Java反射机制中的一个方法,用于获取指定方法(Method)上的指定注解(Annotation)。

上面的例子中,我们首先定义了一个自定义注解 MyAnnotation,它有一个成员变量 value。然后我们在 myMethod 方法上应用了 MyAnnotation 注解,并设置了 value 的值为 "Hello, world!"。

在main方法中,我们使用反射机制获取myMethod方法,并使用 method.getAnnotation(MyAnnotation.class) 方法获取该方法上的 MyAnnotation 注解。如果获取到了注解,我们就可以通过注解对象调用 value() 方法获取注解的值,即输出 "Hello, world!"。

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

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

相关文章

盛最多水的容器(力扣11题)

例题&#xff1a; 分析&#xff1a; 这道题给出了一个数组&#xff0c;数组里的元素可以看成每一个挡板&#xff0c;要找到哪两个挡板之间盛的水最多&#xff0c;返回盛水量的最大值。这其实是一个双指针问题。 我们可以先固定第一个挡板( i )和最后一个挡板( j )&#xff0c…

gitee创建仓库

描述 本文章记录了怎么在gitee上创建项目&#xff0c;以及使用vscode提代码到远程呢个仓库&#xff0c;如何创建一个新分支&#xff0c;并将新分支提交到远程仓库。 1、创建远程仓库 在创建远程仓库之前要先进行ssh密钥的设置 &#xff08;1&#xff09;打开黑窗口&#xff…

计算机丢失mfc110.dll的5种常用解决方法分享

丢失动态链接库文件&#xff08;DLL&#xff09;是比较常见的一种情况&#xff0c;其中之一就是“计算机丢失mfc110.dll”。这个问题通常是由于系统文件损坏或缺失引起的&#xff0c;给计算机的正常运行带来了困扰。为了解决这个问题&#xff0c;我总结了以下五种方法&#xff…

顶帽运算在OpenCv中的应用

项目背景 假如我们拍了一张自拍&#xff0c;想为自己的照片添加一个酷炫的火星飞舞的效果&#xff0c;素材库中正好有一张火焰的照片&#xff0c;如果想去除图中的火焰&#xff0c;只保留火星效果&#xff0c;可以使用顶帽子算法 图片中的火星部分正好属于比周围亮一些的斑块…

LabVIEW开发滚筒洗衣机动态监测系统

LabVIEW软件在滚筒洗衣机的动态监测和分析中扮演着关键角色。本案例展示了如何利用LabVIEW开发的系统来优化洗衣机的性能和可靠性。 首先&#xff0c;在建立洗衣机的动力学模型基础上&#xff0c;利用LabVIEW进行了关键零部件的动态优化设计。通过LabVIEW的高级计算和模拟功能…

【JavaFX】JDK11 基于Gson、hutool、Jackson持久化存储实体类数据的解决方案 (读取、追加、去重、写入json对象)

文章目录 开发环境效果前言一、Gson是什么?二、使用步骤1.引入依赖2.创建实体类创建 JsonFileService类创建JsonFileService的实现类 JsonFileServiceImpl三、实现效果开发环境 JDK11IDEA 2023.3Gson、hutool、JacksonJavaFX 11效果 前言 使用JDK1

112. 雷达设备(贪心/逆向思考)

题目&#xff1a; 112. 雷达设备 - AcWing题库 输入样例&#xff1a; 3 2 1 2 -3 1 2 1输出样例&#xff1a; 2 思路&#xff1a; 代码&#xff1a; #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include<…

BMS、AFE、菊花链技术

一、BMS的分布式架构和集中式架构 AFE在从板中&#xff0c;用来采集电池电压和温度&#xff0c;以及均衡管理 BMS通常以分布式架构为主&#xff0c;即分为主板和从板。原来主从板上都有微处控制器。从板采集单体电池电压和温度&#xff0c;通过CAN总线传给主板。 而现在的趋势…

Oracle-数据库迁移之后性能变慢问题分析

问题背景&#xff1a; ​一套Oracle11.2.0.4的RAC集群&#xff0c;通过Dataguard switchover方式迁移到新机器之后&#xff0c;运行第一天应用报障说应用性能慢&#xff0c;需要进行性能问题排查 问题分析&#xff1a; 首先&#xff0c;登陆到服务器&#xff0c;用TOP看一眼两个…

虾皮马来站点选品:在虾皮(Shopee)5个热门品类和市场特点

在虾皮&#xff08;Shopee&#xff09;马来西亚站点选择商品时&#xff0c;卖家应该考虑一些热门品类和市场特点&#xff0c;以确保他们的产品能够满足当地消费者的需求并取得良好的销售业绩。以下是在虾皮&#xff08;Shopee&#xff09;马来西亚站点销售商品时需要考虑的五个…

裂变新模式:分销市场的翘楚

在当今的商业世界&#xff0c;推荐机制已经成为一种重要的营销策略。通过用户推荐&#xff0c;企业不仅能够扩大品牌影响力&#xff0c;还能有效降低获客成本。然而&#xff0c;如何设计一个合理的推荐机制&#xff0c;使得用户有足够的动力去推荐新人&#xff0c;同时保持团队…

【Java】接口和抽象类有什么共同点和区别?

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 【Java】接口和抽象类有什么共同点和区别&…

SQL Server注入之攻防技战法

那天下着很大的雨&#xff0c;母亲从城里走回来的时候&#xff0c;浑身就是一个泥人&#xff0c;那一刻我就知道我没有别的选择了 1.Mssql报错注入 0.判断数据库类型 1.爆当前用户名 2.爆版本 3.爆服务器名 4.判断数据库个数 5.获取全部数据库 语句只适合>2005 爆当前数据…

DDD落地实践-架构师眼中的餐厅(转)

本文以餐厅场景为叙事主线&#xff0c;以领域驱动为核心思想&#xff0c;结合架构设计与功能设计方法论。是从领域分析到落地的全过程案例&#xff0c;内容偏重于落地&#xff0c;因此不乏一些探讨&#xff0c;欢迎指正。 文章较长、全程干货、耐心读完、必有收获。 本文不针…

【docker】如何编写dockerfile文件,构建docker镜像

如何编写dockerfile文件&#xff0c;构建docker镜像 一、docker 镜像与 dockerfile1.1 什么是Docker镜像1.2 Docker 镜像的结构 二、dockerfile 中常用的构建指令三、dockerfile 内容示例四、构建 docker 镜像 一、docker 镜像与 dockerfile 1.1 什么是Docker镜像 Docker镜像…

虾皮跨境电商物流:为卖家提供高效灵活的物流解决方案

虾皮&#xff08;Shopee&#xff09;作为一家知名的跨境电商平台&#xff0c;其物流服务是其成功的关键因素之一。虾皮跨境电商物流服务为卖家提供了一站式的物流解决方案&#xff0c;从订单处理到最终交付&#xff0c;为卖家提供高效、灵活、成本效益高的物流服务&#xff0c;…

【教学类-43-13】 20240103 (4宫格数独:错误版:768套) 不重复的基础模板数量:768套

作品展示&#xff1a;——4宫格 768套不重复模板&#xff08;64页*12套题&#xff09; 有错误&#xff0c;实际数量小于768套 背景需求&#xff1a; 测试4宫格数独基础模板有几种。 写个程序&#xff0c;验算是不是真的是乘阶法的288种。 代码展示&#xff1a; 768套4宫格题…

Python for与while循环的介绍和对应练习题的巩固

for循环 重复执行同一段代码就是循环 循环列表 for val in list_name: list_num [1,2,3,4,5,6,7,8,9] for i in list_num:print(i)代码执行顺序 从上往下依次执行 遍历 通过某种顺序把某种集合所有元素都访问一遍 list_food{"火锅","烧烤","张…

流媒体学习之路(WebRTC)——GCC分析(4)

流媒体学习之路(WebRTC)——GCC分析&#xff08;4&#xff09; —— 我正在的github给大家开发一个用于做实验的项目 —— github.com/qw225967/Bifrost目标&#xff1a;可以让大家熟悉各类Qos能力、带宽估计能力&#xff0c;提供每个环节关键参数调节接口并实现一个json全配置…

服务器硬件及RAID磁盘阵列详解

文章目录 一、服务器硬件服务器常见故障 二、RAID磁盘阵列详解1、RAID磁盘阵列概述2、RAID 0(条带化存储)3、RAID 1&#xff08;镜像存储&#xff09;4、RAID 55、RAID 66、RAID 10&#xff08;先做镜像&#xff0c;再做条带&#xff09;7、RAID 01&#xff08;先做条带&#x…