【JAVA CORE_API】Day21 Map接口、在线聊天室v3.0、Java的反射机制(P1)

Map接口

Map接口

  • Map是Java中用于存储键值对(key-value pairs)的接口,每个键(key)对应一个值(value)。它不允许重复的键,但允许不同的键映射相同的值。

  • 关键特点:

    • 键值对存储:每个key对应一个值,使用key来获取对应的值;

    • 不允许重复key:一个key只能存在一次,重复key会覆盖旧值;

    • 常用实现类:HashMapTreeMapLinkedHashMap等。

  • 常用方法:

    • put(K key, V value):插入键值对;

    • get(Object key):根据key获取对应的值;

    • remove(Object key):根据key删除对应的键值对;

    • containsKey(Object key):检查是否包含指定key;

    • keySet():返回所有key的集合。

  • Map是一个非常灵活的接口,广泛用于需要通过唯一key快速访问数据的场景。

 package Day21;
 
 import java.util.HashMap;
 import java.util.Map;
 
 /**
  * java.util.Map 查找表
  */
 public class MapDemo1 {
     public static void main(String[] args) {
         // Map创建时要分别指定key和value的类型
         Map<String, Integer> map = new HashMap<>();
         /*
             put(K k, V v):返回一个value值
             向Map中保存一组键值对
             由于Map要求key不允许重复,如果使用重复的key存入新的value,那么则是替换value操作,
             如果此Map中不包含该key的映射关系,则返回null。
             此时put方法会把被替换下来的value返回。否则返回值为null。
          */
         Integer value = map.put("语文", 100);
         System.out.println(value);
 
         map.put("数学", 100);
         map.put("外语", 100);
         map.put("物理", 100);
         map.put("化学", 100);
         System.out.println(map);
 
         value = map.put("语文", 66);   // 会将“语文”原本的99替换掉
         System.out.println(value);   // 99
         System.out.println(map);
 
 //        int valuePoint = map.put("语文", 100);
 //        System.out.println(valuePoint);  // 空指针异常
 
         System.out.println("");
 
         /**
          * get(Object key) 返回value类型
          * 根据给定的key提取对应的value
          * 如果给定的key在Map中不存在则返回值为null
          */
         value = map.get("语文");
         System.out.println(value);
         value = map.get("体育");
         System.out.println(value);
 
         System.out.println("");
 
         /**
          * size(): 返回int类型
          * 返回Map中键值对的个数
          */
         int size = map.size();
         System.out.println("size=" + size);
 
         System.out.println("");
 
         /**
          * remove() 返回value类型
          * 根据给定的key删除对应的键值对,并且会将该key对应的value返回
          */
         value = map.remove("数学");
         System.out.println(value);  // 数学对应的value值:98
         System.out.println(map);
 
         System.out.println("");
 
         /**
          * isEmpty() 返回boolean类型
          * 检查是否为空Map(Map中不含有任何键值对)
          */
         boolean isEmpty = map.isEmpty();
         System.out.println("是否为空Map" + isEmpty);
 
         System.out.println("");
 
         /**
          * containsKey(key) 返回boolean类型
          * containsValue(Value) 返回boolean类型
          * 判断Map中是否包含指定的key或value
          * 底层还是利用equals方法比较
          */
         boolean containsKey = map.containsKey("语文");
         System.out.println("是否包含key:" + containsKey);
         boolean containsValue = map.containsValue(100);
         System.out.println("是否包含value:" + containsValue);
 
         System.out.println("");
 
         /**
          * clear()
          * 清空Map
          */
         map.clear();
         System.out.println(map);
     }
 }
 

Map的遍历

常用实现类

  • 遍历<key,value>

    • 使用 entrySet() 方法,获取键值对的集合。

    • 示例:

       package Day21;
       
       import java.util.HashMap;
       import java.util.Map;
       import java.util.Set;
       
       public class MapDemo2 {
           public static void main(String[] args) {
               Map<String, Integer> map = new HashMap<>();
               map.put("语文", 100);
               map.put("数学", 90);
               map.put("英语", 80);
               map.put("化学", 100);
               map.put("物理", 100);
               System.out.println(map);
             
               /**
                * entrySet() 返回所有的键值对
                * 将当前Map中所有的键值对以若干的Entry实例表示,并将他们以一个Set集合的形式返回
                */
               Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
               for (Map.Entry<String, Integer> entry : entrySet) {
                   System.out.println("entry:" + entry);
               }
           }
       }
       
      
  • 遍历key

    • 使用 keySet() 方法,获取键的集合。

    • 示例:

       package Day21;
       
       import java.util.HashMap;
       import java.util.Map;
       import java.util.Set;
       
       public class MapDemo2 {
           public static void main(String[] args) {
               Map<String, Integer> map = new HashMap<>();
               map.put("语文", 100);
               map.put("数学", 90);
               map.put("英语", 80);
               map.put("化学", 100);
               map.put("物理", 100);
               System.out.println(map);
       
               /**
                * keySet() 返回所有的key
                * 将当前Map中所有的key以一个Set集合的形式返回
                */
               Set<String> keySet = map.keySet();
               for (String key : keySet) {
                   System.out.println("key:" + key);
               }
           }
       }
       
      
  • 遍历value

    • 使用 values() 方法,获取所有值的集合。

    • 示例:

       for (V value : map.values()) {
           // 处理值
       }
      

forEach方法

  • forEach 方法

    • forEach 是 Java 8 引入的一个方法,用于遍历集合或流中的每个元素,通常与 Lambda 表达式结合使用。
  • 用法示例

     List<String> list = Arrays.asList("a", "b", "c");
     
     // 使用 forEach 方法遍历列表
     list.forEach(element -> {
         System.out.println(element);
     });
    
  • 关键特点

    • 简洁:用 Lambda 表达式直接在遍历时定义要执行的操作。

    • 灵活:可以传递方法引用,如 list.forEach(System.out::println);

    • 适用范围:适用于所有实现了 Iterable 接口的集合,如 ListSet,以及 Stream 流。

    • forEach 方法简化了遍历过程,特别适合在函数式编程风格中使用。

小练习

 package Day21;
 
 import java.util.HashMap;
 import java.util.Map;
 
 /**
  * 编写程序,统计字符串中每个字符出现的次数
  */
 public class Test {
 
     public static void main(String[] args) {
         /*
             输出的结果,要求看到每个字符出现的次数。
             例如
             h:2
             e:2
             l:4
             xx:xx
 
             思路:
             1:创建一个Map用来保存统计结果
               key:出现的每种字符   value:该字符出现的次数
 
             2:
               2.1:遍历line中的每一个字符
                   String有一个方法:charAt(int index) 根据下标提取对应的元素
                   下标范围:0-字符串的length
               2.2:将该字符作为key存入到map中,这是会有两种情况
                   Map的containsKey()方法
                   2.2.1:如果这个字符作为key在map中不存在,将字符作为key,value存入1(第一次出现)
                   2.2.3:如果该字符作为key在map中存在,说明该字符之前出现过,此时将value+1
               2.3:遍历完毕相当于统计完毕,将map输出看结果即可
 
               注意:char类型对应的包装类是Character
          */
         String line = "Patiention! Is key in life! Just be patient!";
         Map<Character, Integer> map = new HashMap<>();
         for (int i = 0; i < line.length(); i++) {
             char c = line.charAt(i);
             if (map.containsKey(c)) {
                 map.put(c, map.get(c) + 1);
             } else {
                 map.put(c, 1);
             }
         }
         System.out.println(map);
     }
 }
 

聊天室v3.0

Client.java

 package Day21;
 import java.io.*;
 import java.net.Socket;
 import java.nio.charset.StandardCharsets;
 import java.util.Scanner;
 
 /**
  * 客户端
  */
 public class Client {
     /**
      * java.net.Socket:套接字
      * 它封装了TCP协议的通信细节,使用它可以和服务器建立连接并进行交互
      * 可以理解为是电话
      */
     private Socket socket;
     public Client() {
         // 实例化Socket对象,就是与远端计算机建立连接的过程
         // 需要传递两个对数:
         // 1.远端计算机的IP地址,用于找到网络上对方的计算机
         // 2.远端计算机的端口号,用于找到对方计算机中的应用程序
         try {
             System.out.println("正在连接服务端......");
             /**
              实例化Socket对象,就是与远端计算机建立连接的过程
              需要传入两个对数:
              1.服务端的IP地址,用于找到网络上对方的计算机
              2.服务端口,用于找到服务端计算机上的应用程序
 
              查看IP操作:
               Windows: win+r,输入cmd,回车,输入ipconfig,IPv4就是IP地址
               Mac:触控板上五指向中间抓,选择"终端"程序打开,输入/sbin/ifconfig查看IP地址
 
               创建Socket对象向服务端发送请求时,会进行三次握手
              */
             socket = new Socket("localhost", 8888);
             System.out.println("连接成功!");
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
 
     // 该方法用于启动客户端程序的执行
     public void start() {
         /*
              Socket提供了一个方法:
              OutputStream getOutputStream()
              通过socket获取输出流,将写出的信息发送给服务端
              */
         try {
             OutputStream outputStream = socket.getOutputStream();
             OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
             BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
             PrintWriter printWriter = new PrintWriter(bufferedWriter,true);  // 自动行刷新
 
             // 输入昵称
             Scanner scanner01 = new Scanner(System.in);
             while (true) {
                 System.out.println("请输入昵称:");
                 // 要求:昵称不能为空或者只有空格都不可以,如果发生则提示用户重新输入
                 String nickname = scanner01.nextLine();
                 if (nickname.trim().isEmpty()){
                     System.out.println("昵称不能为空,请重新输入!");
                 }else {
                     printWriter.println(nickname);
                     System.out.println("昵称设置成功!欢迎您:" + nickname);
                     break;
                 }
             }
 
             // 启动用于读取服务端消息的线程
             ServerHandler serverHandler = new ServerHandler();
             Thread thread = new Thread(serverHandler);
             // 如果我们输入exit想结束聊天,这个线程依然活着,所以我们想让他和主线程一起死,于是我们设计这个线程为守护线程
             thread.setDaemon(true);
             thread.start();
 
             Scanner scanner = new Scanner(System.in);
             while (true) {
                 String line = scanner.nextLine();
                 if ("exit".equalsIgnoreCase(line)) {
                     break;
                 }
                 printWriter.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 {
         @Override
         public void run() {
             try {
                 // 通过Socket提供的方法InputStream getInputStream(),获取输入流,将服务端发送的消息读取出来
                 InputStream inputStream = socket.getInputStream();
                 InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
                 BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
 
                 // 循环读取来自服务端发送过来的消息并输出到客户端控制台上
                 String message;
                 while ((message = bufferedReader.readLine()) != null) {
                     System.out.println(message);
                 }
 
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
     }
 }
 

server.java

 package Day21;
 
 import java.io.*;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 /**
  * 服务端
  */
 public class Server {
     /**
      * java.net.ServerSocket:
      * 运行在服务端,主要作用有两个:
      * 1.像系统申请服务端口,客户端通过该端口与服务端建立连接
      * 2.监听服务端口,一旦有客户链接了,就会立即创建一个Socket对象与客户端进行交互
      *      如果将Socket比喻为“电话”,那ServerSocket就相当于客户中心的“总机”。
      * 解决方法:
      * 1.更换端口号;
      * 2.杀死占用该端口的进行(通常由于服务端启动了两次导致)
      */
     // key:用户的昵称    value:该客户端的输出流
     private Map<String, PrintWriter> allOut = new HashMap<>();
 
     private ServerSocket serverSocket;
 //    private List<PrintWriter> allOut = new ArrayList<>();
     public Server(){
         try {
             System.out.println("正在启动服务端......");
             /**
              * 创建ServerSocket对象时,会申请一个端口
              * 如果该端口被其它程序占用,会抛出异常:
              *   java.net.BindException: Address already in use:
              * 解决办法:
              * 1.更换端口号
              * 2.杀死占用该端口的进行(通常由于服务端启动两次导致)
              */
             serverSocket = new ServerSocket(8888);
             System.out.println("服务端启动完成!");
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
 
     // 启动服务端方法:
     /**
      * accept():
      * 用于接收客户端连接,并返回一个Socket对象与所连接的客户端进行交互
      * 该方法是一个阻塞方法,调用后程序会“卡住”,直到一个客户端连接为止
      */
     public void start(){
         try {
             while (true){
                 System.out.println("服务端正在等待客户端连接......");
                 Socket socket = serverSocket.accept();
                 System.out.println("一个客户端连接了!");
 
                 // 启动单独的线程来处理与客户端的通信
                 Runnable clientHandler = new ClientHandler(socket);
                 Thread thread = new Thread(clientHandler);
                 thread.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地址
         private String nickname;
         public ClientHandler(Socket socket) {
             this.socket = socket;
             // 通过socket获取客户端ip地址
             host = socket.getInetAddress().getHostAddress();
         }
         @Override
         public void run() {
             PrintWriter printWriter = null;
             try {
                 //接收客户端发送过来的消息
                 /*
                  Socket提供了一个方法:
                  InputStream getInputStream()
                  通过socket获取输入流,用于读取客户端发送过来的数据
                  */
                 InputStream inputStream = socket.getInputStream();
                 InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
                 BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                 // 读取客户端发送过来的第一行字符串一定是客户端的昵称
                 nickname = bufferedReader.readLine();
 
                 // 通过Socket获取输出流,用于将消息发送给该客户端
                 OutputStream outputStream = socket.getOutputStream();
                 OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
                 BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
                 printWriter = new PrintWriter(bufferedWriter,true);
 
                 // 将对应该客户端的输出流存入到共享集合allOut中
                 synchronized (allOut) {
                     allOut.put(nickname, printWriter);
                 }
 
                 // 广播该客户端上线了
                 broadcast(nickname + "["+host+"]上线了!当前在线人数:" + allOut.size());
                 /*
                   当我们通过br.readLine()读取客户端发送过来的数据时,
                   客户端如果调用socket.close()时(进行四次挥手),正常与我们断开连接
                   那么readLine()会返回null
                   若客户端异常中断(强制杀死进程),此时readLine()会抛出异常
                   注意:这种情况咱们服务端是没有办法的
                  */
 
                 String message;
                 while ((message = bufferedReader.readLine()) != null) {    // 读取客户端发送过来的消息
                     if(message.startsWith("@")) {
                         // 如果消息是以@开头,说明是私聊消息
                         sendMessageToSomeone(message);
                     }else{
                         broadcast(nickname + "[" +host + "]说:" + message);
                     }
                 }
             } catch (IOException e) {
 
             } finally {
                 // 处理该客户端断开连接后的操作
                 // 将该客户端的输出流从共享集合allOut中删除
                 synchronized (allOut) {
 //                    allOut.remove(printWriter);
                     allOut.remove(nickname);
                 }
                 // 广播消息,告知所有客户端该用户下线了
                 broadcast(nickname + "[" + host + "]下线了!当前在线人数:" + allOut.size());
 
                 try {    // 进行四次挥手
                     serverSocket.close();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
         }
 
         /**
          * 将消息发送给所有客户端
          * @param message
          */
         private void broadcast(String message){
             System.out.println(message);
             synchronized (allOut) {
 //                for (PrintWriter writer : allOut) {
 //                    writer.println(message);
 //                }
                 for (PrintWriter writer : allOut.values()){
                     writer.println(message);
                 }
             }
         }
 
         /**
          * 私聊功能,将消息发送给某个人
          * @param message
          */
         public void sendMessageToSomeone(String message){
             /*
                 过程:
                 私聊消息的格式-->@对方昵称:聊天消息
                 1:截取出对方的昵称
                   substring()->截"@"到":"之间的内容
                 2:从allOut中使用对方的昵称作为key提取到对应的输出流
                   map.get(对方昵称)
                 3:将聊天消息通过该输出流发送给对方
                   聊天消息substring()->":"后面的内容
                   然后通过输出流发送给对象
              */
             // 首先验证私聊消息是否为正确的格式:@xxx:xxx
             if (message.matches("@.+:.+")) {
                 //1
                 String toNickName = message.substring(1, message.indexOf(":"));
                 if (allOut.containsKey(toNickName)) {
                     //2
                     PrintWriter printWriter = allOut.get(toNickName);
                     //3
                     String toMessage = message.substring(message.indexOf(":") + 1);
                     printWriter.println(nickname + "悄悄对你说:" + toMessage);
                 }else {
                     PrintWriter printWriter = allOut.get(nickname);
                     printWriter.println("私聊对象不存在!");
                 }
             } else {
                 // 如果格式不对,给当前用户提示私聊格式不正确
                 PrintWriter printWriter = allOut.get(nickname);
                 printWriter.println("私聊消息格式不正确,私聊的格式应当为:@对方昵称:私聊消息");
             }
         }
     }
 }
 
  • 接下来将Client.java客户端内全部代码发送给GPT,请求生成一个可视化界面,GPT语言:这是一个多人在线聊天室的客户端代码,请根据Client.java中的Client类的代码内容,生成图形化的界面,对输入昵称可以单独创建界面,聊天界面要求要在窗口的下方生成文本框,用于获取用户输入的聊天消息发送给服务端,包括上方存在一个文本框用来获取服务器发送来的聊天记录信息,下方具有发送按钮,发送可以使用enter键触发,字体也可以进行修改,界面内部使用中文布局,可以参考其他在线聊天室软件的界面布局,还有就是将字的大小改为24,聊天界面按钮别搞那么长,把聊天框放大一些,把界面做的更复杂一些

     package Day21;
     
     import javax.swing.*;
     import java.awt.*;
     import java.awt.event.ActionEvent;
     import java.awt.event.ActionListener;
     import java.awt.event.KeyAdapter;
     import java.awt.event.KeyEvent;
     import java.io.*;
     import java.net.Socket;
     import java.nio.charset.StandardCharsets;
     
     public class ClientGUI {
         private Socket socket;
         private String nickname;
         private JTextArea chatArea;
         private JTextField inputField;
     
         public ClientGUI() {
             connectToServer();
             createNicknameFrame();
         }
     
         // 连接服务器
         private void connectToServer() {
             try {
                 socket = new Socket("localhost", 8888);
                 System.out.println("连接成功!");
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
     
         // 昵称输入界面
         private void createNicknameFrame() {
             JFrame nicknameFrame = new JFrame("输入昵称");
             nicknameFrame.setSize(500, 250);
             nicknameFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
             nicknameFrame.setLayout(new BorderLayout());
     
             JPanel panel = new JPanel(new GridBagLayout());
             JLabel label = new JLabel("请输入昵称:");
             label.setFont(new Font("宋体", Font.PLAIN, 24));
             JTextField nicknameField = new JTextField(15);
             nicknameField.setFont(new Font("宋体", Font.PLAIN, 24));
             JButton confirmButton = new JButton("确认");
             confirmButton.setFont(new Font("宋体", Font.PLAIN, 24));
     
             confirmButton.addActionListener(e -> {
                 String inputNickname = nicknameField.getText().trim();
                 if (inputNickname.isEmpty()) {
                     JOptionPane.showMessageDialog(nicknameFrame, "昵称不能为空,请重新输入!");
                 } else {
                     nickname = inputNickname;
                     sendNicknameToServer();
                     nicknameFrame.dispose();
                     createChatFrame();
                 }
             });
     
             GridBagConstraints gbc = new GridBagConstraints();
             gbc.insets = new Insets(10, 10, 10, 10);
             gbc.gridx = 0;
             gbc.gridy = 0;
             panel.add(label, gbc);
     
             gbc.gridx = 1;
             panel.add(nicknameField, gbc);
     
             gbc.gridx = 0;
             gbc.gridy = 1;
             gbc.gridwidth = 2;
             panel.add(confirmButton, gbc);
     
             nicknameFrame.add(panel, BorderLayout.CENTER);
             nicknameFrame.setVisible(true);
         }
     
         // 发送昵称给服务器
         private void sendNicknameToServer() {
             try {
                 PrintWriter writer = new PrintWriter(new BufferedWriter(
                         new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8)), true);
                 writer.println(nickname);
                 System.out.println("昵称设置成功!欢迎您:" + nickname);
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
     
         // 主聊天界面
         private void createChatFrame() {
             JFrame chatFrame = new JFrame("聊天室 - " + nickname);
             chatFrame.setSize(800, 600);
             chatFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
             chatFrame.setLayout(new BorderLayout());
     
             chatArea = new JTextArea();
             chatArea.setEditable(false);
             chatArea.setFont(new Font("宋体", Font.PLAIN, 24));
             JScrollPane chatScrollPane = new JScrollPane(chatArea);
     
             inputField = new JTextField();
             inputField.setFont(new Font("宋体", Font.PLAIN, 24));
             inputField.setPreferredSize(new Dimension(600, 50));
     
             JButton sendButton = new JButton("发送");
             sendButton.setFont(new Font("宋体", Font.PLAIN, 24));
             sendButton.setPreferredSize(new Dimension(150, 50));
     
             // 按下发送按钮或回车键发送消息
             sendButton.addActionListener(e -> sendMessage());
             inputField.addKeyListener(new KeyAdapter() {
                 @Override
                 public void keyPressed(KeyEvent e) {
                     if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                         sendMessage();
                     }
                 }
             });
     
             JPanel inputPanel = new JPanel(new BorderLayout());
             inputPanel.add(inputField, BorderLayout.CENTER);
             inputPanel.add(sendButton, BorderLayout.EAST);
     
             chatFrame.add(chatScrollPane, BorderLayout.CENTER);
             chatFrame.add(inputPanel, BorderLayout.SOUTH);
     
             chatFrame.setVisible(true);
     
             // 启动接收服务器消息的线程
             new Thread(new ServerHandler()).start();
         }
     
         // 发送消息到服务器
         private void sendMessage() {
             String message = inputField.getText().trim();
             if (!message.isEmpty()) {
                 try {
                     PrintWriter writer = new PrintWriter(new BufferedWriter(
                             new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8)), true);
                     writer.println(message);
                     inputField.setText("");
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
         }
     
         // 处理服务器消息
         private class ServerHandler implements Runnable {
             @Override
             public void run() {
                 try {
                     BufferedReader reader = new BufferedReader(
                             new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
                     String message;
                     while ((message = reader.readLine()) != null) {
                         chatArea.append(message + "\n");
                     }
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
         }
     
         public static void main(String[] args) {
             new ClientGUI();
         }
     }
     
    

在这里插入图片描述

Java的反射机制

反射机制

什么是反射机制

  • Java的反射机制是一种让程序在运行时动态获取类、接口、方法、属性等信息的能力。通过反射,程序可以在不知道类的情况下创建对象、调用方法、或访问属性。这种机制允许程序在运行时检查和操作代码结构,而不需要在编译时知道具体的类型。

  • 简而言之,反射使得代码能够“自我检查”和“自我修改”。这对于编写更灵活、通用的代码非常有用,比如框架、工具库、或一些需要处理未知类型对象的程序。

  • Java的反射机制就像是你手中的一面“魔法镜子”(反射API)。当你面对一扇门(类)时,不用打开门就能通过这面魔法镜子看到门后面的所有东西(类的结构),比如房间里的家具(属性)、开关(方法)等等。你甚至可以通过镜子直接操作这些东西,比如打开开关(调用方法)或移动家具(修改属性),而不需要知道这个房间具体是什么样的。

反射API

什么是Class类?

  • Class类是Java反射机制的核心。它是一个用来表示类或接口的字节码文件在JVM中的对象。通过Class类,程序可以在运行时获取关于类的各种信息,比如类的名称、修饰符、构造方法、方法、字段等。

  • 每个Java类在加载时,JVM都会为其生成一个对应的Class对象,这个对象包含了该类的所有元数据信息。开发者可以通过这个Class对象使用反射机制来操作这个类。

获取类对象

  • Class.forName("类的全限定名")

    • 用法: 通过类的全限定名(即包名+类名)来获取Class对象。

    • 适用场景: 当你知道类的名称,并且希望在运行时动态加载类时,这种方法最为常用。

    • 示例:

       Class class1 = Class.forName("java.util.String");
      
  • 类名.class

    • 用法: 直接通过类的名字获取Class对象。

    • 适用场景: 当你在编译时就已经知道类的具体类型时,这种方法更简洁。

    • 示例:

       String name = class1.getName();
      
  • 对象.getClass()

    • 用法: 通过一个已实例化的对象来获取它的Class对象。

    • 适用场景: 当你有一个对象的实例,但不确定它的具体类型时,这种方法可以动态获取该对象的Class对象。

    • 示例:

       String string = new String();
       Class class1 = string.getClass();
      

获取类信息

  • 获取类的完全限定名

    • 方法: getName()

    • 示例:

       System.out.println(class1.getName());
      
  • 获取当前类名

    • 方法getSimpleName()

    • 示例

       System.out.println(class1.getSimpleName());
      
  • 获取构造方法

    • 方法: getConstructors(), getDeclaredConstructors()

    • 示例:

       Constructor[] constructors = class1.getConstructors();
       for (Constructor constructor : constructors) {
           System.out.println("Constructor: " + constructor);
       }
       
       // 获取所有声明的构造方法(包括私有构造方法)
       Constructor[] declaredConstructors = class1.getDeclaredConstructors();
       for (Constructor constructor : declaredConstructors) {
           System.out.println("Declared Constructor: " + constructor);
       }
      
  • 获取方法

    • 方法: getMethods(), getDeclaredMethods()

    • 示例:

       Method[] methods = class1.getMethods();
       for (Method method : methods) {
           System.out.println("Method: " + method);
       }
       
       // 获取所有声明的方法(包括私有方法)
       Method[] declaredMethods = class1.getDeclaredMethods();
       for (Method method : declaredMethods) {
           System.out.println("Declared Method: " + method);
       }
      
  • 获取字段

    • 方法: getFields(), getDeclaredFields()

    • 示例:

       Field[] fields = class1.getFields();
       for (Field field : fields) {
           System.out.println("Field: " + field);
       }
       
       // 获取所有声明的字段(包括私有字段)
       Field[] declaredFields = class1.getDeclaredFields();
       for (Field field : declaredFields) {
           System.out.println("Declared Field: " + field);
       }
      
  • 获取父类

    • 方法: getSuperclass()

    • 示例:

       Class superClass = class1.getSuperclass();
       System.out.println("Superclass: " + superClass);
      
  • 获取接口

    • 方法: getInterfaces()

    • 示例:

       Class[] interfaces = class1.getInterfaces();
       for (Class iface : interfaces) {
           System.out.println("Interface: " + iface);
       }
      
  • 获取类加载器

    • 方法: getClassLoader()

    • 示例:

       ClassLoader classLoader = class1.getClassLoader();
       System.out.println("ClassLoader: " + classLoader);
      

Package类

  • java.lang.Package,用于获取包内的数据,每一个实例都用于表示一个包的信息,通过类对象的getPackage()方法获取一个类的包信息。

     Class class1 = ArrayList.class;
     Package pack = class1.getPackage();
     // 通过包对象获取它表示的包的名字
     String packName = pack.getName();
     System.out.peintln(packName);  // 输出java.util
    

对象实例化

  • 我们提前创建一个Person类,顺便将其配置好:

     package day21;
     
     import java.util.Objects;
     
     public class Person {
         private String name;
         private int age;
     
         public Person() {
         }
     
         public Person(String name) {
             this.name = name;
         }
     
         public Person(String name, int age) {
             this.name = name;
             this.age = age;
         }
     
         public String getName() {
             return name;
         }
     
         public void setName(String name) {
             this.name = name;
         }
     
         @Override
         public boolean equals(Object object) {
             if (this == object) return true;
             if (object == null || getClass() != object.getClass()) return false;
             Person person = (Person) object;
             return age == person.age && Objects.equals(name, person.name);
         }
     
         @Override
         public int hashCode() {
             return Objects.hash(name, age);
         }
     
         public int getAge() {
             return age;
         }
     
         public void setAge(int age) {
             this.age = age;
         }
     
         @Override
         public String toString() {
             return "Person{" +
                     "name='" + name + '\'' +
                     ", age=" + age +
                     '}';
         }
     }
     
    
  • 使用 new 关键字

    • 这是最常见和直接的对象实例化方法,通过new关键字可以创建类的实例:
     // 程序写死,那么只能实例化ArrayList
     ArrayList arrayList = new ArrayList(); 
    
    • 这种方式在编译时需要知道类的具体类型,并且只能用于类的公共无参构造函数。
  • 使用 Class 类的 newInstance() 方法

    • Class类提供了newInstance()方法来创建对象实例。这种方法允许你通过反射在运行时动态创建类的实例:

    • 方法: Class.newInstance()

    • 用法: 调用Class对象的newInstance()方法,创建一个该类的实例。

    • 示例:

               // 1.获取要实例化的类的对象
               // 反射机制实例化对象
               Scanner scanner = new Scanner(System.in);  // 反射机制可以运行起来根据需要去实例化的对象
               System.out.println("请输入一个类的名字:");
               String className = scanner.next();
               Class cls = Class.forName(className);
               /*
                   2.类对象
                */
               Object obj = cls.newInstance();
               System.out.println(obj);
      
    • 注意: newInstance()方法已被标记为过时(deprecated),主要因为它不能处理构造函数的异常。建议使用Constructor类的方法来替代。

  • 使用 Constructor

    • Constructor类提供了更灵活的方式来创建对象实例。可以通过Constructor对象调用构造函数来创建实例:

    • 获取 Constructor 对象: 使用Class类的getConstructor()getDeclaredConstructor()方法获取构造函数。

    • 创建实例: 使用Constructor对象的newInstance()方法创建对象。

    • 示例:

     package day21;
     
     import java.lang.reflect.Constructor;
     import java.lang.reflect.InvocationTargetException;
     
     public class ReflectDemo3 {
         public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
             Person person = new Person("张禹垚", 21);
             System.out.println(person);
     
             // 获取类对象
             Class cls = Class.forName("day21.Person");
             // Person(String)
             Constructor constructor = cls.getConstructor(String.class);
             // new Person("刘备");
             Object obj = constructor.newInstance("刘备");
             System.out.println(obj);
     
             Class cls1 = Class.forName("day21.Person");
             Constructor constructor1 = cls1.getConstructor(String.class, int.class);
             Object obj1 = constructor1.newInstance("关羽", 33);
             System.out.println(obj1);
         }
     }
     
    
  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的 Spring Boot 实现 API 接口多版本支持的示例代码。 首先,我们需要在 `pom.xml` 文件中添加 `spring-boot-starter-parent` 依赖和 `spring-boot-starter-web` 依赖。然后,在 `application.properties` 文件中添加一些配置: ``` # API 版本 api.version=v1 # v1 版本 API 的 URL 前缀 api.v1.path=/api/v1 # v2 版本 API 的 URL 前缀 api.v2.path=/api/v2 ``` 然后,我们创建一个 `VersionHandlerInterceptor` 拦截器来处理 API 版本: ```java import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class VersionHandlerInterceptor implements HandlerInterceptor { private static final String API_VERSION_HEADER = "Api-Version"; private static final String DEFAULT_API_VERSION = "v1"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String apiVersion = request.getHeader(API_VERSION_HEADER); if (apiVersion == null) { apiVersion = DEFAULT_API_VERSION; } request.setAttribute("apiVersion", apiVersion); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } } ``` 该拦截器从请求头中获取 API 版本,如果没有指定则默认为 `v1`,并将其存储到请求属性中。 接下来,我们创建一个 `ApiVersionRequestMappingHandlerMapping` 请求映射处理器来支持多版本 API: ```java import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.StringUtils; import org.springframework.web.servlet.mvc.condition.RequestCondition; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.lang.reflect.Method; public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping { private static final String API_VERSION_ATTRIBUTE = "apiVersion"; @Override protected RequestCondition<ApiVersionRequestCondition> getCustomTypeCondition(Class<?> handlerType) { ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class); return createRequestCondition(apiVersion); } @Override protected RequestCondition<ApiVersionRequestCondition> getCustomMethodCondition(Method method) { ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class); return createRequestCondition(apiVersion); } private RequestCondition<ApiVersionRequestCondition> createRequestCondition(ApiVersion apiVersion) { return apiVersion == null ? null : new ApiVersionRequestCondition(apiVersion.value()); } private static class ApiVersionRequestCondition implements RequestCondition<ApiVersionRequestCondition> { private final String apiVersion; public ApiVersionRequestCondition(String apiVersion) { this.apiVersion = apiVersion; } @Override public ApiVersionRequestCondition combine(ApiVersionRequestCondition other) { return new ApiVersionRequestCondition(other.apiVersion); } @Override public ApiVersionRequestCondition getMatchingCondition(HttpServletRequest request) { String apiVersion = (String) request.getAttribute(API_VERSION_ATTRIBUTE); if (!StringUtils.hasText(apiVersion)) { apiVersion = DEFAULT_API_VERSION; } return new ApiVersionRequestCondition(apiVersion.equals(this.apiVersion) ? this.apiVersion : null); } @Override public int compareTo(ApiVersionRequestCondition other, HttpServletRequest request) { String apiVersion = (String) request.getAttribute(API_VERSION_ATTRIBUTE); if (!StringUtils.hasText(apiVersion)) { apiVersion = DEFAULT_API_VERSION; } return apiVersion.compareTo(other.apiVersion); } } } ``` 该请求映射处理器通过 `ApiVersion` 注解获取 API 版本,然后根据 API 版本创建一个自定义请求条件 `ApiVersionRequestCondition`。 最后,我们可以使用 `@ApiVersion` 注解来指定 API 版本: ```java import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController @ApiVersion("v1") public class UserController { @GetMapping("/users") public String getUsers() { return "v1 users"; } } ``` 根据上述示例代码,我们可以轻松实现 Spring Boot 的 API 接口多版本支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值