001 Java 泛型与类型知识总结
Java 泛型与类型知识总结
本篇总结涵盖了 Java 中基本数据类型与引用数据类型的区别、泛型为何不能使用基本数据类型声明、泛型上下限的使用以及 Java 原生 API 中泛型的经典应用场景。每个知识点都配有详细的解释和代码示例,便于理解和复习。
一、Java 中基本数据类型和引用数据类型的区别
1. 存储位置
- 基本数据类型:存储在 栈内存 中,变量直接包含实际的值。
- 引用数据类型:存储在 堆内存 中,变量保存对象的引用(内存地址)。
2. 类型分类
- 基本数据类型(共 8 种):
- 整数类型:
byte、short、int、long - 浮点类型:
float、double - 字符类型:
char - 布尔类型:
boolean
- 整数类型:
- 引用数据类型:
- 类(Class)
- 接口(Interface)
- 数组(Array)
- 枚举(Enum)
- 注解(Annotation)
3. 内存大小
- 基本数据类型:有固定的内存大小,例如:
int:4 字节double:8 字节
- 引用数据类型:大小不固定,取决于对象的具体内容。
4. 默认值
- 基本数据类型:有默认值,例如:
int:0boolean:false
- 引用数据类型:默认值为
null,表示不引用任何对象。
5. 传值方式
- 基本数据类型:按值传递,方法调用时会复制一份值,不影响原值。
- 引用数据类型:按引用传递,传递对象的引用,可能影响原对象。
6. 操作方式
- 基本数据类型:可直接进行算术运算。
- 引用数据类型:需要通过方法调用操作对象。
7. 可空性
- 基本数据类型:不能为
null。 - 引用数据类型:可以为
null。
二、泛型为何不可用基本数据类型声明
1. 类型擦除机制
Java 的泛型是通过 类型擦除(Type Erasure)实现的,编译时会将泛型参数擦除为其上限类型(默认为 Object)。由于基本数据类型不是对象类型,不能被替换为 Object,因此泛型无法直接使用基本数据类型。
javaList<Integer> list = new ArrayList<>(); // 编译后类型擦除为: List list = new ArrayList<>();
2. 基本数据类型与对象类型的区别
基本数据类型不是对象,不能作为泛型的类型参数。泛型要求类型参数是一个对象类型(引用类型)。
3. 自动装箱与拆箱机制
Java 提供了 自动装箱(Autoboxing)和 自动拆箱(Unboxing)机制,将基本数据类型与其对应的包装类互相转换,使得基本数据类型可以间接用于泛型。
int↔Integerdouble↔Doublechar↔Characterboolean↔Boolean示例:
plaintextjava复制代码List<Integer> intList = new ArrayList<>(); intList.add(10); // 自动装箱,将 int 类型的 10 转换为 Integer 对象 int num = intList.get(0); // 自动拆箱,将 Integer 转换为 int
三、泛型上下限的使用
泛型上下限通过通配符 ? extends T(上限)和 ? super T(下限)来限定类型参数的范围,提高代码的灵活性和类型安全性。
1. 上限通配符 ? extends T
? extends T 表示类型是 T 或 T 的子类,主要用于 读取,不能 写入(除了 null)。
示例:
javaimport java.util.List; import java.util.ArrayList; public class ExtendsExample { public static void main(String[] args) { List<? extends Number> list = new ArrayList<Integer>(); // list.add(1); // 编译错误,不能添加元素 Number num = list.get(0); // 可以读取,类型为 Number } }
解释:
List<? extends Number>表示列表中的元素类型是Number或其子类(如Integer、Double)。- 由于无法确定列表的具体子类型,不能安全地添加元素。
- 可以安全地读取元素,读取的类型为上限
Number。
2. 下限通配符 ? super T
? super T 表示类型是 T 或 T 的父类,允许写入 T 及其子类的对象,适合 写入。
示例:
javaimport java.util.List; import java.util.ArrayList; public class SuperExample { public static void main(String[] args) { List<? super Integer> list = new ArrayList<Number>(); list.add(1); // 可以添加 Integer list.add(new Integer(2)); // 可以添加 Integer // list.add(new Object()); // 编译错误,不能添加非 Integer 子类 Object obj = list.get(0); // 读取时只能作为 Object 类型 } }
解释:
List<? super Integer>表示列表中的元素类型是Integer或其父类(如Number、Object)。- 可以安全地添加
Integer及其子类对象,因为它们都可以向上转型为Integer的父类。 - 读取时只能作为
Object类型,因为无法确定具体类型。 多态性解释: - Java 中的多态性允许子类对象赋值给父类引用。
- 因此,
Integer类型的对象可以存储到Number或Object类型的列表中。
3. 无界通配符 ?
? 表示任意类型,由于类型未知,既不能安全地进行 读取,也不能 写入(除了 null)。
示例:
javajava复制代码import java.util.List; import java.util.ArrayList; public class UnboundedExample { public static void main(String[] args) { List<?> list = new ArrayList<String>(); // list.add("hello"); // 编译错误,不能添加元素 Object obj = list.get(0); // 可以读取,类型为 Object } }
解释:
List<?>表示列表中的元素类型未知,可以是任何类型。- 由于无法确定类型,不能安全地添加元素,也不能确定读取元素的具体类型。
四、Java 原生 API 中泛型的经典应用
Java 的泛型在标准库中广泛应用,以下是一些经典的使用泛型的地方。
1. Java 集合框架(Java Collections Framework)
a. List 接口
List 接口及其实现类(如 ArrayList、LinkedList)使用泛型,确保列表中的元素类型一致。
javajava复制代码List<String> stringList = new ArrayList<>(); stringList.add("Hello"); // stringList.add(1); // 编译错误,不能添加非 String 类型
b. Map 接口
Map<K, V> 是键值对集合,使用泛型参数 K 和 V 表示键和值的类型。
javajava复制代码Map<Integer, String> map = new HashMap<>(); map.put(1, "One"); // map.put("Two", 2); // 编译错误,键必须是 Integer,值必须是 String
c. Set 接口
Set 接口及其实现类(如 HashSet、TreeSet)使用泛型,确保集合中的元素类型一致。
plaintextjava复制代码Set<Double> set = new HashSet<>(); set.add(3.14); // set.add("Pi"); // 编译错误,不能添加非 Double 类型
d. Queue 接口
Queue 接口及其实现类(如 LinkedList、PriorityQueue)使用泛型,指定队列中元素的类型。
plaintextjava复制代码Queue<Integer> queue = new LinkedList<>(); queue.add(1); // queue.add("String"); // 编译错误,不能添加非 Integer 类型
2. Comparator<T> 接口
Comparator 接口用于比较对象,泛型参数 T 确保类型一致。
plaintextjava复制代码Comparator<String> comparator = new Comparator<String>() { @Override public int compare(String s1, String s2) { return s1.length() - s2.length(); } }; List<String> strings = Arrays.asList("apple", "banana", "cherry"); Collections.sort(strings, comparator);
3. Optional<T> 类
Optional 是一个容器对象,用于表示可能存在或不存在的值,避免 NullPointerException。
plaintextjava复制代码Optional<String> optionalString = Optional.of("Hello"); optionalString.ifPresent(System.out::println); // 输出 "Hello" Optional<Integer> optionalInteger = Optional.ofNullable(null); Integer value = optionalInteger.orElse(0); // 如果为空,返回默认值 0
4. Stream<T> API
Stream API 使用泛型,支持对集合的函数式操作。
plaintextjava复制代码List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); List<String> filteredNames = names.stream() .filter(name -> name.startsWith("A")) .collect(Collectors.toList());
5. Future<T> 和 CompletableFuture<T>
在并发编程中,Future 和 CompletableFuture 使用泛型表示异步计算的结果类型。
plaintextjava复制代码ExecutorService executorService = Executors.newFixedThreadPool(2); Future<Integer> future = executorService.submit(() -> { // 执行耗时任务 return 42; }); CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello"); completableFuture.thenAccept(result -> System.out.println(result)); // 输出 "Hello"
6. Class<T> 类
Class<T> 使用泛型表示类的类型信息,常用于反射机制。
plaintextjava复制代码Class<String> stringClass = String.class; String str = stringClass.newInstance(); // 创建新的 String 实例
7. 函数式接口
Java 8 引入的函数式接口广泛使用泛型。
a. Supplier<T>
提供一个类型 T 的值。
plaintextjava复制代码Supplier<String> supplier = () -> "Hello"; String result = supplier.get(); // 返回 "Hello"
b. Consumer<T>
消费一个类型 T 的值。
plaintextjava复制代码Consumer<String> consumer = s -> System.out.println(s); consumer.accept("Hello"); // 输出 "Hello"
c. Function<T, R>
将类型 T 的值映射为类型 R 的值。
plaintextjava复制代码Function<String, Integer> function = s -> s.length(); Integer length = function.apply("Hello"); // 返回 5
五、总结
Java 中基本数据类型和引用数据类型在存储方式、默认值、操作方式等方面存在显著区别。由于泛型的类型擦除机制,泛型无法直接使用基本数据类型,但可以通过包装类和自动装箱/拆箱机制间接地在泛型中使用基本数据类型。 泛型上下限的使用可以提高代码的灵活性和类型安全性。在 Java 原生 API 中,泛型被广泛应用于集合框架、并发编程、函数式编程等方面,提高了代码的可读性和可维护性。
版权声明