Java新手村第二站:泛型、集合与IO流初探

发布于:2025-04-14 ⋅ 阅读:(26) ⋅ 点赞:(0)

Java新手村第二站:泛型、集合与IO流初探

泛型

泛型的概念与作用

  • 核心目的:在编译期提供类型安全检查,避免运行时的 ClassCastException

    • ClassCastException 是一种运行时异常(属于 RuntimeException),表示试图将一个对象强制转换为不兼容的类类型时发生的错误。

    • 示例

      Object obj = new Integer(100);
      String str = (String) obj; // 抛出 ClassCastException
      
  • 主要功能

    • 类型参数化:允许类、接口、方法操作的数据类型被参数化。
    • 代码复用:通过类型抽象,编写更通用的代码。
    • 类型安全:编译时检查类型一致性,减少强制类型转换。
  • 示例

    // 无泛型
    List list = new ArrayList();
    list.add("Hello");
    String s = (String) list.get(0); // 需显式强制转换
        
    // 有泛型
    List<String> list = new ArrayList<>();
    list.add("Hello");
    String s = list.get(0); // 自动类型安全
    

泛型类与泛型接口

  • 定义语法:在类/接口名后添加 <T>T 为类型参数(可自定义名称如 E, K, V)。

  • 类型参数:可以是任意非基本类型(需用包装类如 Integer)。

  • 示例

    // 泛型类
    public class Box<T> {
        private T content;
        public void setContent(T content) { this.content = content; }
        public T getContent() { return content; }
    }
        
    Box<String> stringBox = new Box<>();
    stringBox.setContent("Java");
    String value = stringBox.getContent(); // 无需强制转换
        
    // 泛型接口
    public interface Comparator<T> {
        int compare(T o1, T o2);
    }
    

泛型方法

  • 独立于类泛型:泛型方法的类型参数独立于类的泛型参数。

  • 语法:在返回类型前声明 <T>

  • 示例

    public class Utils {
        public static <T> T getFirstElement(List<T> list) {
            return list.isEmpty() ? null : list.get(0);
        }
    }
        
    List<Integer> numbers = Arrays.asList(1, 2, 3);
    Integer first = Utils.getFirstElement(numbers); // 自动类型推断
    

通配符

通配符(Wildcard) 是一个让程序员在不破坏类型安全的前提下,灵活处理未知类型的特殊符号。它的核心作用是放宽泛型类型约束,让泛型容器或方法能兼容更多类型,同时避免出现 ClassCastException

  • 无界通配符 <?>

    • 用途:表示“未知类型”,用于接受任意泛型类型的对象。

    • 限制:不能添加元素(除 null),只能读取为 Object

    • 示例

      public void printList(List<?> list) {
          for (Object elem : list) {
              System.out.println(elem);
          }
      }
      
  • 上界通配符 <? extends T>

    • 用途:接受 T 或其子类的泛型类型。

    • 限制:只能读取为 T,不能添加元素(除 null)。

    • 示例

      // 接受Number及其子类(如Integer、Double)
      public double sum(List<? extends Number> list) {
          double sum = 0;
          for (Number num : list) {
              sum += num.doubleValue();
          }
          return sum;
      }
      
  • 下界通配符 <? super T>

    • 用途:接受 T 或其父类的泛型类型。

    • 限制:可以添加 T 及其子类对象,但读取时需强制转换。

    • 示例

      // 向集合中添加Integer及其子类
      public void addNumbers(List<? super Integer> list) {
          for (int i = 1; i <= 5; i++) {
              list.add(i);
          }
      }
      

类型擦除

  • 核心机制:泛型仅在编译期存在,运行时类型参数被擦除为原始类型(Raw Type)。

  • 规则

    • 无界类型参数(如 <T>)→ 替换为 Object
    • 有界类型参数(如 <T extends Number>)→ 替换为边界类型(Number)。
    • 泛型方法的类型参数被擦除后可能生成桥方法(Bridge Methods)。
  • 示例

    // 编译前
    public class Box<T> {
        private T content;
        public void setContent(T content) { /* ... */ }
    }
    
    // 编译后(类型擦除)
    public class Box {
        private Object content;
        public void setContent(Object content) { /* ... */ }
    }
    
  • 注意事项

    • 无法在运行时获取泛型类型信息(如 new T()T.class)。
    • 泛型数组的创建受限(如 new List<String>[10] 非法)。

泛型与继承

  • 泛型类不可协变List<String> 不是 List<Object> 的子类。
  • 通配符实现协变/逆变
    • List<? extends Number> 是协变的(接受 List<Integer>)。
    • List<? super Integer> 是逆变的(接受 List<Number>)。

注意事项

  • 基本类型不可用:泛型类型参数必须是引用类型。

    // 错误
    List<int> list = new ArrayList<>();
    // 正确
    List<Integer> list = new ArrayList<>();
    
  • 无法实例化类型参数

    public class Box<T> {
        private T obj = new T(); // 编译错误
    }
    
  • 静态上下文限制:静态变量或方法不能引用类的类型参数。

    public class Box<T> {
        private static T staticObj; // 编译错误
    }
    

包装类

Java通过包装类为基本数据类型提供了对象表示,使得基本类型可以用于面向对象的场景。

每个基本类型都有对应的包装类

  • byteByte
  • shortShort
  • intInteger
  • longLong
  • floatFloat
  • doubleDouble
  • charCharacter
  • booleanBoolean

作用

  • 泛型支持

    集合类(如 ListMap)只能存储对象,不能存储基本类型。例如:List<Integer> list = new ArrayList<>();

  • Null 值处理
    包装类可以表示 null,用于区分缺失值和默认值(如 0false)。

  • 实用方法
    提供类型转换、进制转换等方法,例如:

    • Integer.parseInt("123"):字符串转 int
    • Integer.toHexString(255):转十六进制字符串。

自动装箱与拆箱

  • 装箱:基本类型 → 包装类对象(编译器调用 valueOf())。

    Integer a = 100; // 自动装箱,等价于 Integer.valueOf(100)
    
  • 拆箱:包装类对象 → 基本类型(编译器调用 xxxValue())。

    int b = a; // 自动拆箱,等价于 a.intValue()
    
  • 注意事项

    • 频繁装箱可能影响性能(对象创建开销)。
    • 拆箱时若对象为 null,抛出 NullPointerException

缓存机制

部分包装类(如 IntegerByteShortLongCharacter)对特定范围的值缓存对象:

  • 默认范围-128127(可通过 JVM 参数调整 IntegerCache.high)。
  • 效果Integer.valueOf(127) == Integer.valueOf(127) 返回 true,但 new Integer(127) 始终创建新对象。

建议:优先使用 valueOf() 而非构造函数,以利用缓存。

不可变性

包装类对象一旦创建,值不可修改。所有修改操作(如加法)会生成新对象。

Integer x = 10;
x = x + 5; // 新对象赋值给 x,原对象未被修改。

常用方法

  • 类型转换
    • static int parseInt(String s):字符串 → 基本类型。
    • String toString():对象 → 字符串。
  • 比较
    • int compareTo(Integer another):比较大小。
    • boolean equals(Object obj):比较值是否相等(而非 == 比较引用)。

注意事项

  • 比较操作
    • == 比较对象引用,仅在缓存范围内有效。
    • 建议用 equals() 或拆箱后比较基本类型。
  • 性能敏感场景
    • 避免在循环中频繁装箱,优先使用基本类型数组(如 int[])。
  • Null 安全
    • 拆箱前需确保包装类对象非 null

示例代码

// 自动装箱与拆箱
Integer num1 = 200; // 装箱(若超出缓存范围,每次新建对象)
int num2 = num1;    // 拆箱

// 比较问题
Integer a = 100, b = 100;
System.out.println(a == b); // true(缓存范围内)
Integer c = 200, d = 200;
System.out.println(c == d); // false(超出缓存范围)

// 实用方法
String s = "123";
int n = Integer.parseInt(s); // 字符串 → int

集合

本周只是简单的了解一下常用集合的基本使用,后续会深入学习Java集合框架

集合类型 接口 实现类 特点 典型应用场景
动态数组 List ArrayList 随机访问快,增删慢 需要频繁按索引访问元素
链表 List LinkedList 增删快,随机访问慢 频繁插入/删除,栈/队列操作
哈希表 Set/Map HashSet/HashMap 无序,O(1) 时间查询 快速去重、键值对映射
有序树 Set/Map TreeSet/TreeMap 自动排序,O(log n) 时间操作 需要有序遍历或范围查询
双端队列 Deque ArrayDeque 高效头尾操作 栈、队列、滑动窗口

常用方法

  • ArrayList(动态数组)

    List<Integer> list = new ArrayList<>();
    
    // 增
    list.add(1);           // 末尾添加元素
    list.add(0, 10);       // 在索引0插入元素
    list.addAll(otherList); // 合并集合
    
    // 删
    list.remove(0);        // 删除索引0的元素
    list.remove(Integer.valueOf(3)); // 删除元素3
    list.clear();          // 清空
    
    // 查
    int num = list.get(0); // 获取索引0的元素
    int size = list.size(); // 长度
    boolean isEmpty = list.isEmpty();
    
    // 遍历
    for (int val : list) { /* ... */ }
    list.forEach(val -> System.out.println(val));
    
  • LinkedList(链表)

LinkedList<Integer> linkedList = new LinkedList<>();

// 可当作队列或栈使用
linkedList.addFirst(1);   // 头部插入
linkedList.addLast(2);    // 尾部插入
int first = linkedList.pollFirst(); // 移除头部
int last = linkedList.pollLast();   // 移除尾部
  • HashSet(哈希集合)

    Set<Integer> set = new HashSet<>();
    
    set.add(5);              // 添加元素
    set.remove(5);           // 删除元素
    boolean exists = set.contains(5); // 存在性检查
    set.size();              // 大小
    
    // 遍历
    for (int val : set) { /* ... */ }
    
  • HashMap(哈希表)

    Map<String, Integer> map = new HashMap<>();
    
    // 增/改
    map.put("apple", 1);      // 添加键值对
    map.put("apple", 2);      // 更新值
    map.putIfAbsent("apple", 3); // 仅当键不存在时插入
    
    // 删
    map.remove("apple");      // 删除键
    map.remove("apple", 2);   // 仅当值匹配时删除
    
    // 查
    int count = map.get("apple");        // 获取值(需处理null)
    boolean hasKey = map.containsKey("apple");
    boolean hasValue = map.containsValue(2);
    
    // 遍历
    for (Map.Entry<String, Integer> entry : map.entrySet()) {
        String key = entry.getKey();
        int value = entry.getValue();
    }
    
  • TreeMap(有序哈希表)

    TreeMap<Integer, String> treeMap = new TreeMap<>();
    
    treeMap.put(3, "three");
    treeMap.put(1, "one");
    treeMap.put(2, "two");
    
    // 获取第一个键(最小)
    int firstKey = treeMap.firstKey(); 
    
    // 获取小于等于4的最大键
    Integer floorKey = treeMap.floorKey(4);
    
    // 范围查询:键在[1, 3)的条目
    Map<Integer, String> subMap = treeMap.subMap(1, true, 3, false);
    
  • ArrayDeque(双端队列)

    Deque<Integer> deque = new ArrayDeque<>();
      
    // 可当作栈或队列使用
    deque.push(1);           // 栈顶压入元素(相当于addFirst)
    int top = deque.pop();   // 栈顶弹出(相当于removeFirst)
      
    deque.offer(2);          // 队尾添加(相当于addLast)
    int head = deque.poll(); // 队头弹出(相当于removeFirst)
    

使用技巧

  • 快速去重

    Set<Integer> unique = new HashSet<>(list); // 直接去重
    List<Integer> uniqueList = new ArrayList<>(unique);
    
  • 频率统计

    Map<Character, Integer> freq = new HashMap<>();
    for (char c : s.toCharArray()) {
        freq.put(c, freq.getOrDefault(c, 0) + 1);
    }
    
  • 排序

    List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 4));
    Collections.sort(list); // 升序
    Collections.sort(list, (a, b) -> b - a); // 降序
    
  • 优先队列(堆)

    // 最小堆(默认)
    PriorityQueue<Integer> minHeap = new PriorityQueue<>();
    // 最大堆
    PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
    
    minHeap.offer(3);
    minHeap.offer(1);
    minHeap.poll(); // 弹出1(最小值)
    
  • 字符串与集合互相转换

    // 字符串转字符集合
    char[] chars = s.toCharArray();
    List<Character> list = new ArrayList<>();
    for (char c : chars) list.add(c);
      
    // 集合转数组
    Integer[] arr = list.toArray(new Integer[0]);
    

工具类 CollectionsArrays

  • Collections 常用方法

    List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 2));
    
    Collections.reverse(list);        // 反转 [2, 1, 3]
    Collections.swap(list, 0, 2);     // 交换位置 [3, 1, 2]
    int max = Collections.max(list);  // 最大值3
    int freq = Collections.frequency(list, 2); // 元素2的出现次数
    
  • Arrays 常用方法

    int[] arr = {3, 1, 2};
    Arrays.sort(arr);                  // 排序 [1, 2, 3]
    int index = Arrays.binarySearch(arr, 2); // 二分查找索引1
      
    // 数组转列表(注意返回的是固定大小的列表)
    List<Integer> list = Arrays.asList(1, 2, 3);
    

选择技巧

  • 需要快速随机访问?ArrayList
  • 需要频繁插入/删除?LinkedList
  • 需要去重或快速存在性检查?HashSet/HashMap
  • 需要有序遍历?TreeSet/TreeMap
  • 实现栈或队列?ArrayDeque

IO流

什么是流(Stream)?

  • 流就像一根管道:数据通过这根管道从源头(如文件、网络)传输到程序,或从程序传输到目标。
  • 两种方向
    • 输入流:读取外部数据到程序(如从文件读内容)。
    • 输出流:将程序数据写入外部(如向文件写内容)。

流的分类

分类依据 类型 核心类
数据单位 字节流 InputStream / OutputStream
字符流 Reader / Writer
数据流向 输入流 FileReader / FileInputStream
输出流 FileWriter / FileOutputStream
功能 节点流(直接操作数据源) FileInputStreamFileReader
处理流(增强功能,包装节点流) BufferedInputStreamBufferedReader

File类的使用

  • 作用

    • 表示文件或目录的抽象路径,用于操作文件的元信息(创建、删除、重命名等)。
    • 注意:File 类不能直接读写文件内容,需配合 IO 流使用。
  • 常用方法

    File file = new File("test.txt");
    // 获取文件信息
    System.out.println(file.getName());     // 文件名
    System.out.println(file.getPath());     // 相对路径
    System.out.println(file.exists());      // 是否存在
    // 创建与删除
    file.createNewFile();                   // 创建文件
    file.delete();                          // 删除文件
    

字节流

字节流以 8 位字节(byte,1 字节) 为基本单位处理数据,适合处理所有类型的二进制数据(如图片、视频、音频等)或原始字节流。

  • 核心类

    • 基类InputStream(输入流)和 OutputStream(输出流)。
    • 常见实现类
      • FileInputStream / FileOutputStream:文件读写。
      • ByteArrayInputStream / ByteArrayOutputStream:内存字节数组读写。
      • BufferedInputStream / BufferedOutputStream:带缓冲的字节流,提升性能。
      • DataInputStream / DataOutputStream:读写基本数据类型(如 int, double)。
      • ObjectInputStream / ObjectOutputStream:对象的序列化与反序列化。
  • 示例

    // 使用字节流复制文件
    try (InputStream in = new FileInputStream("source.jpg");
         OutputStream out = new FileOutputStream("target.jpg")) {
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = in.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    

字符流

字符流以 16 位 Unicode 字符(char,2 字节) 为基本单位处理数据,适合处理文本数据(如 .txt, .csv 文件),会自动处理字符编码问题。

  • 核心类

    • 基类Reader(输入流)和 Writer(输出流)。
    • 常见实现类
      • FileReader / FileWriter:文件读写(默认使用平台编码,可能乱码)。
      • InputStreamReader / OutputStreamWriter:字节流与字符流的桥梁,可指定编码(如 UTF-8)。
      • BufferedReader / BufferedWriter:带缓冲的字符流,提升性能。
      • StringReader / StringWriter:字符串读写。
  • 示例

    // 使用字符流复制文本文件(指定编码为 UTF-8)
    try (Reader reader = new InputStreamReader(
            new FileInputStream("source.txt"), StandardCharsets.UTF_8);
         Writer writer = new OutputStreamWriter(
            new FileOutputStream("target.txt"), StandardCharsets.UTF_8)) {
        char[] buffer = new char[1024];
        int charsRead;
        while ((charsRead = reader.read(buffer)) != -1) {
            writer.write(buffer, 0, charsRead);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    
    // 使用 BufferedReader 逐行读取文本
    try (BufferedReader br = new BufferedReader(
            new FileReader("text.txt"))) {
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    

处理流

在 Java I/O 中,处理流(Processing Streams)(也称装饰流包装流)是建立在基础字节流或字符流之上的高级流,用于对底层流进行功能扩展或数据加工。它们通过装饰者模式动态增强流的能力,例如添加缓冲、数据转换、对象序列化等功能。

  • 缓冲流

    • 作用:通过内存缓冲区减少物理I/O操作次数,提升读写效率。

      类名 基类 功能 典型用途
      BufferedInputStream InputStream 为字节输入流添加缓冲区 读取文件、网络数据
      BufferedOutputStream OutputStream 为字节输出流添加缓冲区 写入文件、网络数据
      BufferedReader Reader 为字符输入流添加缓冲区 逐行读取文本文件
      BufferedWriter Writer 为字符输出流添加缓冲区 高效写入文本数据
    • 示例

      // 使用缓冲流复制文件(字节流)
      try (InputStream in = new FileInputStream("source.jpg");
           BufferedInputStream bis = new BufferedInputStream(in);
           OutputStream out = new FileOutputStream("target.jpg");
           BufferedOutputStream bos = new BufferedOutputStream(out)) {
          byte[] buffer = new byte[1024];
          int bytesRead;
          while ((bytesRead = bis.read(buffer)) != -1) {
              bos.write(buffer, 0, bytesRead);
          }
      } catch (IOException e) {
          e.printStackTrace();
      }
      
      // 使用缓冲流逐行读取文本(字符流)
      try (BufferedReader br = new BufferedReader(new FileReader("text.txt"))) {
          String line;
          while ((line = br.readLine()) != null) {
              System.out.println(line);
          }
      } catch (IOException e) {
          e.printStackTrace();
      }
      
  • 数据转换流

    • 作用:直接读写基本数据类型(如 int, double)或字符串。

      类名 基类 功能 典型用途
      DataInputStream InputStream 从字节流读取基本数据类型 读取二进制数据文件
      DataOutputStream OutputStream 向字节流写入基本数据类型 写入二进制数据文件
    • 示例

      // 写入基本数据类型到文件
      try (DataOutputStream dos = new DataOutputStream(
              new FileOutputStream("data.bin"))) {
          dos.writeInt(100);        // 写入 int
          dos.writeDouble(3.14);    // 写入 double
          dos.writeUTF("Hello");    // 写入 UTF-8 字符串
      } catch (IOException e) {
          e.printStackTrace();
      }
      
      // 从文件读取基本数据类型
      try (DataInputStream dis = new DataInputStream(
              new FileInputStream("data.bin"))) {
          int num = dis.readInt();
          double value = dis.readDouble();
          String text = dis.readUTF();
          System.out.println(num + ", " + value + ", " + text);
      } catch (IOException e) {
          e.printStackTrace();
      }
      
  • 序列化流

    • 作用:实现对象的序列化(将对象转为字节流)与反序列化(从字节流重建对象)。

      类名 基类 功能 典型用途
      ObjectInputStream InputStream 反序列化对象 读取序列化对象
      ObjectOutputStream OutputStream 序列化对象 写入序列化对象
    • 示例

      // 序列化对象到文件
      class Person implements Serializable {
          String name;
          int age;
          // 构造方法、getter/setter 省略
      }
      
      try (ObjectOutputStream oos = new ObjectOutputStream(
              new FileOutputStream("person.dat"))) {
          Person p = new Person("Alice", 30);
          oos.writeObject(p);
      } catch (IOException e) {
          e.printStackTrace();
      }
      
      // 从文件反序列化对象
      try (ObjectInputStream ois = new ObjectInputStream(
              new FileInputStream("person.dat"))) {
          Person p = (Person) ois.readObject();
          System.out.println(p.getName() + ", " + p.getAge());
      } catch (IOException | ClassNotFoundException e) {
          e.printStackTrace();
      }
      
  • 字符编码转换流

    • 作用:处理字节流与字符流之间的转换,并指定字符编码。

      类名 基类 功能 典型用途
      InputStreamReader Reader 将字节输入流转为字符输入流 按指定编码读取文本文件
      OutputStreamWriter Writer 将字符输出流转为字节输出流 按指定编码写入文本文件
    • 示例

      // 按 UTF-8 编码读取文本文件
      try (Reader reader = new InputStreamReader(
              new FileInputStream("text.txt"), StandardCharsets.UTF_8)) {
          char[] buffer = new char[1024];
          int charsRead;
          while ((charsRead = reader.read(buffer)) != -1) {
              System.out.println(new String(buffer, 0, charsRead));
          }
      } catch (IOException e) {
          e.printStackTrace();
      }
      
  • 打印流

    • 作用:提供格式化的输出功能(如 print(), println(), printf())。

      类名 基类 功能 典型用途
      PrintStream OutputStream 格式化输出字节流 控制台输出(System.out)
      PrintWriter Writer 格式化输出字符流 写入格式化的文本数据
    • 示例

      // 使用 PrintWriter 写入格式化的文本
      try (PrintWriter pw = new PrintWriter("output.txt")) {
          pw.println("Hello World");
          pw.printf("PI = %.2f", Math.PI);
      } catch (IOException e) {
          e.printStackTrace();
      }
      

函数式接口和Lambda表达式

函数式接口

定义

  • 仅含一个抽象方法:接口中必须且只能有一个未实现的抽象方法。
  • 允许其他方法:可以包含默认方法(default)、静态方法(static)或从Object类继承的方法(如toString()equals())。
  • 注解标记:建议使用@FunctionalInterface注解,编译器会强制校验接口是否符合规范。

示例

@FunctionalInterface
interface MyFunctionalInterface {
    void doWork(); // 唯一的抽象方法

    default void log(String msg) { // 默认方法(允许存在)
        System.out.println("Log: " + msg);
    }

    static void staticMethod() { // 静态方法(允许存在)
        System.out.println("Static method");
    }
}

为什么需要函数式接口?

  • 为Lambda表达式提供类型:Lambda表达式本质是函数式接口的实例。
  • 支持函数式编程范式:将行为(方法)作为参数传递,增强代码灵活性。
  • 简化代码:避免匿名内部类的冗余语法。

@FunctionalInterface注解的作用

  • 编译时校验:确保接口仅有一个抽象方法,不符合则报错。
  • 文档提示:明确该接口设计意图是作为Lambda的类型。

注意事项

  • 抽象方法唯一性:即使接口继承其他接口,所有父接口的抽象方法也计入总数。

    @FunctionalInterface
    interface Child extends MyFunctionalInterface {
        void anotherMethod(); // 编译错误:此时抽象方法数量为2
    }
    
  • Object类方法不计入:如果抽象方法是Object类的方法(如equals),不影响函数式接口的定义。

    @FunctionalInterface
    interface ValidInterface {
        boolean equals(Object obj); // 来自Object类,不计入抽象方法数量
        void execute();
    }
    

Lambda表达式

定义:Lambda表达式是Java8引入的语法糖,用于简化函数式接口的实现。可以使代码更简洁,避免匿名内部类的冗余语法,提升可读性。

语法

(参数列表) -> { 代码主体 }
  • 参数列表:可省略参数类型(编译器自动推断);单参数时可省略括号。
  • 箭头符号 ->:分隔参数和代码主体。
  • 代码主体:单行代码可省略大括号和return;多行需用大括号。

示例

// 匿名内部类
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
};

// Lambda表达式
Runnable r2 = () -> System.out.println("Hello");

//Lambda表达式用于集合遍历
List<String> names = Arrays.asList("Alice", "Bob");
names.forEach(name -> System.out.println(name));

网站公告

今日签到

点亮在社区的每一天
去签到