Java泛型全方位剖析:从入门到精通的完整指南(上篇)

Java泛型全方位剖析:从入门到精通的完整指南

第一部分:引言与基础概念

Java泛型是Java 5引入的重要特性,它允许类、接口和方法在定义时使用类型参数,提高了代码的类型安全性和可读性。本文将全面解析Java泛型的概念、使用方法及高级应用,帮助开发者深入理解这一强大功能。

1. 什么是泛型?

泛型允许我们在定义类、接口和方法时使用类型参数,使得同一份代码可以适用于多种数据类型,同时保持类型安全。

上图展示了Java泛型的核心概念:通过类型参数(如图中的T),我们可以创建一个通用容器类,然后针对不同类型(String、Integer、自定义类型如User等)创建对应的具体容器实例,实现代码复用的同时保证类型安全。

2. 为什么需要泛型?

在泛型出现之前,Java集合类只能存储Object类型的对象,这带来了两个主要问题:

1. 类型安全问题:可以向集合中添加任何类型的对象,容易导致类型错误。

2. 类型转换繁琐:从集合中取出对象时必须进行显式类型转换。

上图对比了Java泛型出现前后的代码编写方式和特性。在泛型出现前,集合可以存储任意类型的对象,但取出时需要显式类型转换,且容易出现运行时类型转换异常。而泛型的引入使编译器能够在编译期捕获类型错误,提高了代码的类型安全性和可读性。

3. 泛型基本语法

Java泛型的基本语法包括以下几种形式:

o 泛型类class ClassName { ... }

o 泛型接口interface InterfaceName { ... }

o 泛型方法 returnType methodName(T parameter) { ... }

下面是一个泛型类的简单示例:

泛型类示例代码

/**
 * 一个简单的泛型类示例
 * @param  数据类型参数
 */
public class Box {
    // 用于存储任意类型的数据
    private T data;
    
    // 构造方法
    public Box(T data) {
        this.data = data;
    }
    
    // 获取数据
    public T getData() {
        return data;
    }
    
    // 设置数据
    public void setData(T data) {
        this.data = data;
    }
    
    // 测试程序
    public static void main(String[] args) {
        // 创建存储String的Box
        Box stringBox = new Box<>("Hello Generics");
        System.out.println("字符串盒子: " + stringBox.getData());
        
        // 创建存储Integer的Box
        Box integerBox = new Box<>(100);
        System.out.println("整数盒子: " + integerBox.getData());
        
        // 创建存储Double的Box
        Box doubleBox = new Box<>(3.14159);
        System.out.println("浮点数盒子: " + doubleBox.getData());
        
        // 尝试错误类型赋值(此行会导致编译错误)
        // stringBox.setData(100); // 编译错误:不兼容的类型
    }
}

/* 输出结果:
字符串盒子: Hello Generics
整数盒子: 100
浮点数盒子: 3.14159
*/

上面的代码展示了一个简单的泛型类Box,它可以存储任何类型的单个数据。通过类型参数T,我们可以创建存储不同类型(如String、Integer、Double等)的Box实例。编译器会确保类型安全,例如不允许向Box中存入Integer

第二部分:泛型的类型参数与命名约定

1. 类型参数命名约定

在Java泛型中,通常使用单个大写字母表示类型参数。这些字母有特定的含义约定:

o T - Type,表示一般的任何类型

o E - Element,表示集合中的元素类型

o K - Key,表示键的类型

o V - Value,表示值的类型

o N - Number,表示数值类型

o S, U, V 等 - 表示多个类型参数时的第2个、第3个、第4个类型

上图展示了Java泛型中常用的类型参数命名约定。这些命名约定虽然不是强制性的,但在实际开发中遵循这些约定可以提高代码的可读性和可维护性。例如,在集合类中通常使用E表示元素类型,在Map接口中使用KV分别表示键和值的类型。

2. 泛型类和泛型接口

下面是一个结合泛型类和泛型接口的实际示例,我们创建一个简单的键值对存储系统:

泛型类和泛型接口示例

/**
 * 键值对操作接口
 * @param  键的类型
 * @param  值的类型
 */
interface KeyValueStore {
    // 存储键值对
    void put(K key, V value);
    
    // 根据键获取值
    V get(K key);
    
    // 检查是否包含指定键
    boolean containsKey(K key);
    
    // 移除指定键的条目
    V remove(K key);
    
    // 获取所有键的集合
    Iterable keys();
    
    // 获取条目数量
    int size();
}

/**
 * 简单的键值对存储实现
 * @param  键的类型
 * @param  值的类型
 */
class SimpleKeyValueStore implements KeyValueStore {
    // 内部使用一个简单的数组列表存储键值对
    private static class Entry {
        K key;
        V value;
        
        Entry(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }
    
    private java.util.ArrayList<Entry> entries;
    
    public SimpleKeyValueStore() {
        entries = new java.util.ArrayList<>();
    }
    
    @Override
    public void put(K key, V value) {
        // 如果键已存在,更新值
        for (int i = 0; i < entries.size(); i++) {
            if (entries.get(i).key.equals(key)) {
                entries.get(i).value = value;
                return;
            }
        }
        // 否则添加新条目
        entries.add(new Entry<>(key, value));
    }
    
    @Override
    public V get(K key) {
        for (Entry entry : entries) {
            if (entry.key.equals(key)) {
                return entry.value;
            }
        }
        return null; // 键不存在
    }
    
    @Override
    public boolean containsKey(K key) {
        for (Entry entry : entries) {
            if (entry.key.equals(key)) {
                return true;
            }
        }
        return false;
    }
    
    @Override
    public V remove(K key) {
        for (int i = 0; i < entries.size(); i++) {
            if (entries.get(i).key.equals(key)) {
                V value = entries.get(i).value;
                entries.remove(i);
                return value;
            }
        }
        return null; // 键不存在
    }
    
    @Override
    public Iterable keys() {
        java.util.ArrayList keys = new java.util.ArrayList<>();
        for (Entry entry : entries) {
            keys.add(entry.key);
        }
        return keys;
    }
    
    @Override
    public int size() {
        return entries.size();
    }
}

/**
 * 测试类
 */
public class GenericKeyValueExample {
    public static void main(String[] args) {
        // 创建一个存储String键和Integer值的存储
        KeyValueStore scores = new SimpleKeyValueStore<>();
        
        // 添加一些学生成绩
        scores.put("Alice", 95);
        scores.put("Bob", 87);
        scores.put("Charlie", 92);
        
        // 获取并打印成绩
        System.out.println("Alice的成绩: " + scores.get("Alice"));
        System.out.println("Bob的成绩: " + scores.get("Bob"));
        
        // 修改成绩
        scores.put("Alice", 97);
        System.out.println("Alice的更新成绩: " + scores.get("Alice"));
        
        // 检查键是否存在
        System.out.println("是否包含David: " + scores.containsKey("David"));
        
        // 移除一个条目
        Integer removedScore = scores.remove("Bob");
        System.out.println("已移除Bob的成绩: " + removedScore);
        System.out.println("移除后是否包含Bob: " + scores.containsKey("Bob"));
        
        // 打印所有键
        System.out.println("所有学生:");
        for (String student : scores.keys()) {
            System.out.println("- " + student + ": " + scores.get(student));
        }
        
        // 打印总条目数
        System.out.println("总学生数: " + scores.size());
        
        // 创建不同类型的键值存储
        KeyValueStore idToName = new SimpleKeyValueStore<>();
        idToName.put(1001, "Apple");
        idToName.put(1002, "Banana");
        idToName.put(1003, "Cherry");
        
        System.out.println("\\nID 1002对应的水果: " + idToName.get(1002));
    }
}

/* 输出结果:
Alice的成绩: 95
Bob的成绩: 87
Alice的更新成绩: 97
是否包含David: false
已移除Bob的成绩: 87
移除后是否包含Bob: false
所有学生:
- Alice: 97
- Charlie: 92
总学生数: 2

ID 1002对应的水果: Banana
*/

在上面的示例中,我们定义了一个泛型接口KeyValueStore和它的一个具体实现SimpleKeyValueStore。这个例子展示了:

1. 如何定义带有多个类型参数的泛型接口

2. 如何实现泛型接口

3. 如何在泛型类中定义内部泛型类

4. 如何使用不同类型组合创建具体的实例

通过泛型,我们能够用相同的代码处理不同类型的键值对,例如的学生成绩存储和的ID到名称映射。

3. 泛型方法

泛型方法是在方法级别引入类型参数的方法,它可以存在于泛型类中,也可以存在于普通类中。泛型方法的类型参数位于方法返回类型之前。

上图展示了Java泛型方法的语法结构。泛型方法的类型参数声明(如)位于返回类型之前,这与泛型类不同。泛型方法的类型参数作用域仅限于该方法内部,与类级别的类型参数无关。

下面是一个泛型方法的实际示例:

泛型方法示例代码

import java.util.Arrays;
import java.util.Comparator;

/**
 * 泛型方法示例类
 */
public class GenericMethodExample {
    
    /**
     * 泛型方法:在数组中查找最大元素
     * @param  元素类型
     * @param array 要搜索的数组
     * @param comp 用于比较元素的比较器
     * @return 数组中的最大元素,如果数组为空则返回null
     */
    public static  T findMax(T[] array, Comparator comp) {
        if (array == null || array.length == 0) {
            return null;
        }
        
        T max = array[0];
        for (int i = 1; i < array.length i if comp.comparearrayi max> 0) {
                max = array[i];
            }
        }
        return max;
    }
    
    /**
     * 泛型方法:交换数组中的两个元素
     * @param  元素类型
     * @param array 数组
     * @param i 第一个元素的索引
     * @param j 第二个元素的索引
     */
    public static  void swap(T[] array, int i, int j) {
        if (array == null || i < 0 || j < 0 i>= array.length || j >= array.length) {
            return;
        }
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
    
    /**
     * 泛型方法:打印数组的所有元素
     * @param  元素类型
     * @param array 要打印的数组
     */
    public static  void printArray(T[] array) {
        if (array == null) {
            System.out.println("null");
            return;
        }
        
        System.out.print("[");
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]);
            if (i < array.length - 1) {
                System.out.print(", ");
            }
        }
        System.out.println("]");
    }
    
    /**
     * 泛型方法:将一个元素添加到新数组末尾
     * @param  元素类型
     * @param array 原数组
     * @param element 要添加的元素
     * @return 包含原数组所有元素和新元素的新数组
     */
    @SuppressWarnings("unchecked")
    public static  T[] appendElement(T[] array, T element) {
        // 注意:在Java中不能直接创建泛型类型的数组,这里使用了强制类型转换
        // 这是Java泛型的限制之一,实际生产中应考虑使用List等集合类
        T[] newArray = (T[]) java.lang.reflect.Array.newInstance(
            array.getClass().getComponentType(), array.length + 1);
        
        System.arraycopy(array, 0, newArray, 0, array.length);
        newArray[array.length] = element;
        
        return newArray;
    }
    
    /**
     * 主方法:测试泛型方法
     */
    public static void main(String[] args) {
        // 测试整数数组
        Integer[] numbers = {5, 2, 8, 1, 9, 3};
        System.out.println("原始整数数组:");
        printArray(numbers);
        
        // 查找最大整数
        Integer maxNumber = findMax(numbers, Integer::compareTo);
        System.out.println("最大整数: " + maxNumber);
        
        // 交换元素
        swap(numbers, 0, numbers.length - 1);
        System.out.println("交换后的整数数组:");
        printArray(numbers);
        
        // 添加元素
        numbers = appendElement(numbers, 10);
        System.out.println("添加元素后的整数数组:");
        printArray(numbers);
        
        // 测试字符串数组
        String[] fruits = {"Apple", "Orange", "Banana", "Pineapple", "Grape"};
        System.out.println("\\n原始字符串数组:");
        printArray(fruits);
        
        // 基于字符串长度查找最长字符串
        String longestFruit = findMax(fruits, (s1, s2) -> s1.length() - s2.length());
        System.out.println("最长的水果名称: " + longestFruit);
        
        // 基于字母顺序查找最后一个水果
        String lastFruit = findMax(fruits, String::compareTo);
        System.out.println("按字母顺序排最后的水果: " + lastFruit);
        
        // 创建自定义对象数组
        Person[] people = {
            new Person("Alice", 25),
            new Person("Bob", 30),
            new Person("Charlie", 22),
            new Person("David", 28)
        };
        
        System.out.println("\\n人员数组:");
        printArray(people);
        
        // 查找年龄最大的人
        Person oldestPerson = findMax(people, Comparator.comparing(Person::getAge));
        System.out.println("年龄最大的人: " + oldestPerson);
        
        // 查找名字按字母顺序排最后的人
        Person lastPerson = findMax(people, Comparator.comparing(Person::getName));
        System.out.println("名字按字母顺序排最后的人: " + lastPerson);
    }
    
    /**
     * 用于测试的简单Person类
     */
    static class Person {
        private String name;
        private int age;
        
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        public String getName() {
            return name;
        }
        
        public int getAge() {
            return age;
        }
        
        @Override
        public String toString() {
            return name + "(" + age + ")";
        }
    }
}

/* 输出结果:
原始整数数组:
[5, 2, 8, 1, 9, 3]
最大整数: 9
交换后的整数数组:
[3, 2, 8, 1, 9, 5]
添加元素后的整数数组:
[3, 2, 8, 1, 9, 5, 10]

原始字符串数组:
[Apple, Orange, Banana, Pineapple, Grape]
最长的水果名称: Pineapple
按字母顺序排最后的水果: Pineapple

人员数组:
[Alice(25), Bob(30), Charlie(22), David(28)]
年龄最大的人: Bob(30)
名字按字母顺序排最后的人: David(28)
*/

上面的代码展示了四个泛型方法的定义和使用:

1. findMax:在数组中查找最大元素,使用比较器进行元素比较

2. swap:交换数组中的两个元素

3. printArray:打印数组的所有元素

4. appendElement:将元素添加到数组末尾,返回新数组

这些方法展示了泛型方法的强大之处:同一个方法可以处理各种不同类型的数组,如整数数组、字符串数组和自定义对象数组。泛型方法消除了为每种类型编写单独方法的需要,使代码更简洁、更可维护。

第三部分:泛型的类型边界

1. 类型边界概念

在Java泛型中,类型边界(Type Bounds)允许我们限制可用于类型参数的类型。通过类型边界,我们可以指定类型参数必须是某个类的子类或实现某个接口,从而在泛型代码中使用这些类或接口的方法。

类型边界分为上界(upper bound)和下界(lower bound):

o 上界:使用 extends 关键字,表示类型参数必须是指定类型或其子类型

o 下界:使用 super 关键字,表示类型参数必须是指定类型或其父类型

上图展示了Java泛型中的类型边界概念。通过使用上界(extends)和下界(super),我们可以限制类型参数的范围:

o 表示T必须是Number或其子类,如Integer、Double、Float等

o 表示T必须是Integer或其父类,如Integer、Number、Object等

使用类型边界的主要好处是能够在泛型代码中调用边界类型的方法。例如,通过声明<T extends Comparable>,我们可以在泛型方法中调用类型T的compareTo()方法。

2. 上界(Upper Bound)示例

下面是一个使用上界的泛型方法示例,该方法可以计算任何数字集合的总和:

上界(Upper Bound)示例代码

import java.util.List;
import java.util.ArrayList;

/**
 * 演示泛型上界的示例类
 */
public class UpperBoundExample {
    
    /**
     * 计算数字列表的总和
     * @param  必须是Number或其子类的类型
     * @param list 数字列表
     * @return 列表中所有数字的总和
     */
    public static  double sum(List list) {
        double sum = 0.0;
        for (T number : list) {
            // 由于T extends Number,我们可以调用Number类的doubleValue()方法
            sum += number.doubleValue();
        }
        return sum;
    }
    
    /**
     * 寻找列表中的最大元素
     * @param  必须是实现了Comparable接口的类型
     * @param list 元素列表
     * @return 列表中的最大元素,如果列表为空则返回null
     */
    public static <T extends Comparable> T findMax(List list) {
        if (list == null || list.isEmpty()) {
            return null;
        }
        
        T max = list.get(0);
        for (int i = 1; i < list.size(); i++) {
            // 由于T extends Comparable,我们可以调用compareTo方法
            if (list.get(i).compareTo(max) > 0) {
                max = list.get(i);
            }
        }
        return max;
    }
    
    /**
     * 检查列表中所有元素是否都是相等的
     * @param  必须是实现了Comparable接口的类型
     * @param list 元素列表
     * @return 如果所有元素都相等则返回true,否则返回false
     */
    public static <T extends Comparable> boolean areAllEqual(List list) {
        if (list == null || list.size() <= 1) {
            return true;
        }
        
        T first = list.get(0);
        for (int i = 1; i < list.size(); i++) {
            // 使用compareTo检查相等性
            if (list.get(i).compareTo(first) != 0) {
                return false;
            }
        }
        return true;
    }
    
    /**
     * 打印可打印对象的列表
     * @param  必须是实现了Printable接口的类型
     * @param list 可打印对象的列表
     */
    public static  void printAll(List list) {
        for (T item : list) {
            // 由于T extends Printable,我们可以调用print方法
            item.print();
        }
    }
    
    /**
     * 测试上界的主方法
     */
    public static void main(String[] args) {
        // 测试sum方法
        List integers = new ArrayList<>();
        integers.add(1);
        integers.add(2);
        integers.add(3);
        integers.add(4);
        integers.add(5);
        
        List doubles = new ArrayList<>();
        doubles.add(1.1);
        doubles.add(2.2);
        doubles.add(3.3);
        
        System.out.println("整数列表总和: " + sum(integers));
        System.out.println("浮点数列表总和: " + sum(doubles));
        
        // 测试findMax方法
        List strings = new ArrayList<>();
        strings.add("apple");
        strings.add("orange");
        strings.add("banana");
        strings.add("pineapple");
        
        System.out.println("整数列表最大值: " + findMax(integers));
        System.out.println("字符串列表最大值: " + findMax(strings));
        
        // 测试areAllEqual方法
        List sameIntegers = new ArrayList<>();
        sameIntegers.add(5);
        sameIntegers.add(5);
        sameIntegers.add(5);
        
        System.out.println("integers列表元素都相等? " + areAllEqual(integers));
        System.out.println("sameIntegers列表元素都相等? " + areAllEqual(sameIntegers));
        
        // 测试printAll方法
        List printableStrings = new ArrayList<>();
        printableStrings.add(new PrintableString("Hello"));
        printableStrings.add(new PrintableString("Generics"));
        printableStrings.add(new PrintableString("World"));
        
        System.out.println("\\n打印所有可打印字符串:");
        printAll(printableStrings);
    }
    
    /**
     * 用于演示的简单Printable接口
     */
    interface Printable {
        void print();
    }
    
    /**
     * 实现Printable接口的字符串包装类
     */
    static class PrintableString implements Printable {
        private String text;
        
        public PrintableString(String text) {
            this.text = text;
        }
        
        @Override
        public void print() {
            System.out.println("PrintableString: " + text);
        }
    }
}

/* 输出结果:
整数列表总和: 15.0
浮点数列表总和: 6.6
整数列表最大值: 5
字符串列表最大值: pineapple
integers列表元素都相等? false
sameIntegers列表元素都相等? true

打印所有可打印字符串:
PrintableString: Hello
PrintableString: Generics
PrintableString: World
*/

在上面的代码中,我们展示了四个使用不同上界的泛型方法:

1. sum(List list):使用,限制T必须是Number或其子类,从而可以使用doubleValue()方法。

2. findMax(List list):使用<T extends Comparable>,确保T类型的元素可以相互比较。

3. areAllEqual(List list):同样使用<T extends Comparable>来比较元素。

4. printAll(List list):使用,确保T实现了自定义的Printable接口。

这些例子展示了上界的主要用途:允许我们在泛型代码中使用特定类型或接口的方法。

3. 下界(Lower Bound)示例

下界使用super关键字,指定类型参数必须是特定类型或其父类型。下界在Java中主要用于设计能够写入特定类型及其子类型的方法。

下界(Lower Bound)示例代码

import java.util.List;
import java.util.ArrayList;

/**
 * 演示泛型下界的示例类
 */
public class LowerBoundExample {
    
    /**
     * 将整数添加到数字列表中
     * 使用下界确保列表能接受Integer或其父类型
     * @param list 数字列表
     * @param n 要添加的整数
     */
    public static void addInteger(List list, Integer n) {
        list.add(n); // 安全的,因为list能存储Integer或其父类型
    }
    
    /**
     * 将元素添加到列表中
     * @param  元素类型
     * @param list 目标列表
     * @param element 要添加的元素
     * @param  列表元素类型,必须是T或T的父类
     */
    public static  void addElement(List list, T element) {
        // 这种方式在编译时会报错,因为S虽然是T的子类,但List不是List的子类
        // list.add(element); // 编译错误
        
        // 正确的方式是使用下界通配符
        addToList(list, element);
    }
    
    /**
     * 辅助方法:使用下界通配符添加元素到列表
     * @param  元素类型
     * @param list 目标列表
     * @param element 要添加的元素
     */
    private static  void addToList(List list, T element) {
        list.add(element); // 安全的,因为list能存储T或其父类型
    }
    
    /**
     * 测试下界的主方法
     */
    public static void main(String[] args) {
        // 测试addInteger方法
        List numbers = new ArrayList<>();
        List integers = new ArrayList<>();
        List<Object> objects = new ArrayList<>();
        
        // 添加整数到不同的列表
        addInteger(numbers, 10);  // List 可以接受 Integer
        addInteger(integers, 20); // List 可以接受 Integer
        addInteger(objects, 30);  // List<Object> 可以接受 Integer
        
        System.out.println("numbers列表: " + numbers);
        System.out.println("integers列表: " + integers);
        System.out.println("objects列表: " + objects);
        
        // 测试继承层次结构
        List animals = new ArrayList<>();
        List mammals = new ArrayList<>();
        List dogs = new ArrayList<>();
        
        // 创建实例
        Animal animal = new Animal("普通动物");
        Mammal mammal = new Mammal("普通哺乳动物");
        Dog dog = new Dog("小狗");
        
        // 使用addToList方法测试下界
        System.out.println("\\n测试addToList方法:");
        
        // 可以添加Dog到Dog列表或其父类列表
        addToList(dogs, dog);       // List 接受 Dog
        addToList(mammals, dog);    // List 接受 Dog
        addToList(animals, dog);    // List 接受 Dog
        
        // 可以添加Mammal到Mammal列表或其父类列表,但不能添加到Dog列表
        addToList(mammals, mammal); // List 接受 Mammal
        addToList(animals, mammal); // List 接受 Mammal
        // addToList(dogs, mammal);    // 编译错误
        
        // 可以添加Animal到Animal列表,但不能添加到其子类列表
        addToList(animals, animal); // List 接受 Animal
        // addToList(mammals, animal); // 编译错误
        // addToList(dogs, animal);    // 编译错误
        
        System.out.println("animals列表大小: " + animals.size());
        System.out.println("mammals列表大小: " + mammals.size());
        System.out.println("dogs列表大小: " + dogs.size());
        
        // 打印所有动物
        System.out.println("\\n所有动物:");
        for (Animal a : animals) {
            System.out.println(a.getName());
        }
        
        // 打印所有哺乳动物
        System.out.println("\\n所有哺乳动物:");
        for (Mammal m : mammals) {
            System.out.println(m.getName());
        }
        
        // 打印所有狗
        System.out.println("\\n所有狗:");
        for (Dog d : dogs) {
            System.out.println(d.getName());
        }
    }
    
    // 简单的动物类层次结构
    static class Animal {
        private String name;
        
        public Animal(String name) {
            this.name = name;
        }
        
        public String getName() {
            return name;
        }
    }
    
    static class Mammal extends Animal {
        public Mammal(String name) {
            super(name);
        }
    }
    
    static class Dog extends Mammal {
        public Dog(String name) {
            super(name);
        }
    }
}

/* 输出结果:
numbers列表: [10]
integers列表: [20]
objects列表: [30]

测试addToList方法:
animals列表大小: 3
mammals列表大小: 2
dogs列表大小: 1

所有动物:
小狗
普通哺乳动物
普通动物

所有哺乳动物:
小狗
普通哺乳动物

所有狗:
小狗
*/

上面的代码演示了泛型下界的使用:

1. addInteger(List list, Integer n)方法接受一个可以存储Integer或其父类型的列表,这允许该方法操作ListListList<Object>

2. addToList(List list, T element)方法使用下界泛型通配符来确保可以将类型T的元素添加到能够存储T或其父类型的列表中。

3. 通过AnimalMammalDog的继承关系,展示了下界通配符的实际应用:

o 可以将Dog对象添加到ListListList

o 可以将Mammal对象添加到ListList,但不能添加到List

o 可以将Animal对象添加到List,但不能添加到ListList

下界在实现生产者-消费者模式时特别有用,特别是当您需要向泛型容器中写入数据时。

更多文章一键直达:

深入解析MySQL索引高速查询的核心机制与原理

Spring Bean生命周期:从创建到销毁的全过程详解

Redis全栈应用实战:从缓存到分布式系统全场景解析

解密Java ThreadLocal:核心原理、最佳实践与常见陷阱全解析

Java实现Mybatis日志转MySQL可执行SQL的智能转换工具

HTTP协议详解:万维网背后的通信魔法

相关文章

Java 中的8种基本数据类型

Java 中有 8 种基本数据类型,分别为:6 种数字类型:4 种整数型:byte、short、int、long2 种浮点型:float、double1 种字符类型:char1 种布尔型:boolea...

java基本数据类型

前言前两节对java做了一个简单的介绍以及java环境的安装,那么本节就算是基本进入到java编程的正式学习,在进行学习java编程之前我首先要认识一下java里面有哪几种数据类型。四类八种Java的...

java float精度引发的问题

最近做地图相关的项目,因为使用float导致精度损失的问题回顾:根据百度地图api标注的经纬度返回类型为float,所以字段类型也定义为float// 百度地图api返回值 {"status":0,"...

JAVA中的浮点数与二进制

先来看一段简单的代码public static void main(String[] args) { System.out.println(0.1+0.2); }打印结果如下:0.30...

Java程序员,一周Python入门:数据类型、变量、字符串和字符编码

Java程序员,一周Python入门:数据类型、变量、字符串和字符编码对比学习Java 和 Python 在数据类型、变量管理、字符串处理等方面有很大的区别,下面进行详细对比。1. 数据类型和变量1....