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 {
    // 客户端使用的Socket
    private 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 {
    // 服务端使用的SocketServer
    private 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 {
    // 客户端使用的Socket
    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 {
            /*
              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 {
    // 服务端使用的SocketServer
    private 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 {
    // 客户端使用的Socket
    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 {
            /*
              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 {
    // 服务端使用的SocketServer
    private 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 {
    // 服务端使用的SocketServer
    private 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 {
    // 服务端使用的SocketServer
    private 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;
    // 教材中省略无参构造器,带参构造器,get/set方法和toString方法   
}
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!"。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue实现H5拖拽可视化编辑器

一款专注可视化平台工具&#xff0c;功能强大&#xff0c;高可扩展的HTML5可视化编辑器&#xff0c;致力于提供一套简单易用、高效创新、无限可能的解决方案。技术栈采用vue和typescript开发, 专注研发创新工具。 <template><div:style"style":class"…

数据分析之词云图绘制

试验任务概述&#xff1a;如下为所给CSDN博客信息表&#xff0c;分别汇总了ai, algo, big-data, blockchain, hardware, math, miniprog等7个标签的博客。对CSDN不同领域标签类别的博客内容进行词频统计&#xff0c;绘制词频统计图&#xff0c;并根据词频统计的结果绘制词云图。…

【产品经理】axure中继器的使用——表格增删改查分页实现

笔记为个人总结笔记&#xff0c;若有错误欢迎指出哟~ axure中继器的使用——表格增删改查分页实现 中继器介绍总体视图视频预览功能1.表头设计2.中继器3.添加功能实现4.删除功能实现5.修改功能实现6.查询功能实现7.批量删除 中继器介绍 在 Axure RP9 中&#xff0c;中继器&…

如何在Android Termux中使用SFTP实现远程传输文件

文章目录 1. 安装openSSH2. 安装cpolar3. 远程SFTP连接配置4. 远程SFTP访问5. 配置固定远程连接地址6、结语 SFTP&#xff08;SSH File Transfer Protocol&#xff09;是一种基于SSH&#xff08;Secure Shell&#xff09;安全协议的文件传输协议。与FTP协议相比&#xff0c;SFT…

用katalon解决接口/自动化测试拦路虎--参数化

不管是做接口测试还是做自动化测试&#xff0c;参数化肯定是一个绕不过去的坎。 因为我们要考虑到多个接口都使用相同参数的问题。所以&#xff0c;本文将讲述一下katalon是如何进行参数化的。 全局变量 右侧菜单栏中打开profile&#xff0c;点击default&#xff0c;打开之后…

c语言-打印某种图案练习题

目录 前言一、题目一二、题目二总结 前言 本篇文章叙述c语言中打印某种图案的练习题&#xff0c;以便加深对c语言的运用和理解。 一、题目一 题目分析&#xff1a; 行与行之间的关系&#xff1a;第一行不进行空格&#xff0c;从第二行开始空一个空格&#xff0c;后面的空格式逐…

FTP原理与配置

FTP是用来传送文件的协议。使用FTP实现远程文件传输的同时&#xff0c;还可以保证数据传输的可靠性和高效性。 FTP的应用 FTP 提供了一种在服务器和客户机之间上传和下载文件的有效方式。在企业网络中部署一台FTP服务器&#xff0c;将网络设备配置为FTP客户端&#xff0c;则可…

RabbitMQ核心概念记录

本文来记录下RabbitMQ核心概念 文章目录 什么叫消息队列为何用消息队列RabbitMQ简介RabbitMQ基本概念RabbitMQ 特点具体特点包括 Rabbitmq的工作过程RabbitMQ集群RabbitMQ 的集群节点包括Rabbit 模式大概分为以下三种单一模式普通模式镜像模式 本文小结 什么叫消息队列 消息&am…

手拉手后端Springboot整合JWT

环境介绍 技术栈 springbootmybatis-plusmysqljava-jwt 软件 版本 mysql 8 IDEA IntelliJ IDEA 2022.2.1 JDK 1.8 Spring Boot 2.7.13 mybatis-plus 3.5.3.2 Json Web令牌简称JWT Token是在服务端产生的一串字符串是客户端访问资源接口(AP)时所需要的资源凭证。…

【算法与数据结构】860、LeetCode柠檬水找零

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;本题的思路比较简单&#xff0c;首先要保存收到的零钱&#xff0c;其次计算找零&#xff0c;最后分解找…

B/S架构云端SaaS服务的医院云HIS系统源码,自主研发,支持电子病历4级

医院云HIS系统源码&#xff0c;自主研发&#xff0c;自主版权&#xff0c;电子病历病历4级 系统概述&#xff1a; 一款满足基层医院各类业务需要的云HIS系统。该系统能帮助基层医院完成日常各类业务&#xff0c;提供病患挂号支持、病患问诊、电子病历、开药发药、会员管理、统…

【头歌实训】PySpark Streaming 入门

文章目录 第1关&#xff1a;SparkStreaming 基础 与 套接字流任务描述相关知识Spark Streaming 简介Python 与 Spark StreamingPython Spark Streaming APISpark Streaming 初体验&#xff08;套接字流&#xff09; 编程要求测试说明答案代码 第2关&#xff1a;文件流任务描述相…

Pandas有了平替Polars

Polars是一个Python数据处理库&#xff0c;旨在提供高性能、易用且功能丰富的数据操作和分析工具。它的设计灵感来自于Pandas&#xff0c;但在性能上更加出色。 Polars具有以下主要特点&#xff1a; 强大的数据操作功能&#xff1a;Polars提供了类似于Pandas的数据操作接口&am…

Xshell连接ubuntu,从github克隆项目,用Xshell克隆项目

访问不了github&#xff1a;https://blog.csdn.net/liu834189447/article/details/135246914 短暂解决访问问题。 ping不通虚拟机/无法连接虚拟机&#xff1a;https://blog.csdn.net/liu834189447/article/details/135240276 ps: Xshell、ubuntu的粘贴快捷键为 Shift Insert …

23款奔驰GLC260L升级原厂540全景影像 高清环绕的视野

嗨 今天给大家介绍一台奔驰GLC260L升级原厂360全景影像 新款GLC升级原厂360全景影像 也只需要安装前面 左右三个摄像头 后面的那个还是正常用的&#xff0c;不过不一样的是 升级完成之后会有多了个功能 那就是新款透明底盘&#xff0c;星骏汇小许 Xjh15863 左右两边只需要更换…

XV7021BB陀螺仪传感器

XV7021BB具有优越的性能特性&#xff0c;特别是在偏置输出稳定性和低噪声&#xff0c;而消耗小于1 mA的电流。爱普生通过使用爱普生的原始石英传感器元件来实现这些性能。 该传感器具有数字输出接口(SPl和l&#xff1f;C)&#xff0c;兼容由独立于主电源电压&#xff08;VodM&…

Redis 分布式锁总结

在一个分布式系统中&#xff0c;由于涉及到多个实例同时对同一个资源加锁的问题&#xff0c;像传统的synchronized、ReentrantLock等单进程情况加锁的api就不再适用&#xff0c;需要使用分布式锁来保证多服务实例之间加锁的安全性。常见的分布式锁的实现方式有zookeeper和redis…

『Linux升级路』冯诺依曼体系结构与操作系统

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;Linux &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、冯诺依曼体系结构 &#x1f4d2;1.1为什么要有体系结构 &#x1f4d2;1.2…

前端文件在虚拟机,后端在本机,两个如何通信

前端文件在虚拟机&#xff0c;后端在本机&#xff0c;两个如何通信 如果前端的文件放在虚拟机里面&#xff0c;但是调用接口的后端在本地调试&#xff0c;如何做到在虚拟机中也能访问到本地的接口内容。 其实这个问题很简单&#xff0c;只要讲本地的IP和虚拟机中的IP结合就可…

[SWPUCTF 2021 新生赛]finalrce

[SWPUCTF 2021 新生赛]finalrce wp 注&#xff1a;本文参考了 NSSCTF Leaderchen 师傅的题解&#xff0c;并修补了其中些许不足。 此外&#xff0c;参考了 命令执行(RCE)面对各种过滤&#xff0c;骚姿势绕过总结 题目代码&#xff1a; <?php highlight_file(__FILE__); …