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

文章目录

  • Java新手村第二站:泛型、集合与IO流初探
    • 泛型
    • 包装类
    • 集合
    • IO流
    • 函数式接口和Lambda表达式

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 为类型参数(可自定义名称如 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);
    }
    

泛型方法

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

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

  • 示例

    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);
          }
      }
      
  • 上界通配符

    • 用途:接受 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;
      }
      
  • 下界通配符

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

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

    • 示例

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

类型擦除

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

  • 规则

    • 无界类型参数(如 )→ 替换为 Object
    • 有界类型参数(如 )→ 替换为边界类型(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[10] 非法)。

泛型与继承

  • 泛型类不可协变List 不是 List 的子类。
  • 通配符实现协变/逆变
    • List 是协变的(接受 List)。
    • List 是逆变的(接受 List)。
  • 注意事项

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

      // 错误
      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 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));
    

    你可能感兴趣的:(java,学习)