java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java 包装类与泛型

Java 包装类与泛型深度拆解:从基础到源码核心

作者:2501_93786094

在Java中,包装类(Wrapper Classes)和泛型(Generics)是Java语言的核心特性,它们提供了类型安全和操作的便利性,下面我们将从基础概念讲到源码层面的实现,帮助你更好地理解这两个概念

在Java开发中,包装类是基本类型与对象世界的桥梁,而泛型则是实现代码复用、保证类型安全的核心语法。二者相辅相成,是阅读Java集合源码、写出高质量代码的必备知识。今天就从基础概念、核心用法到底层原理,系统拆解包装类与泛型的核心要点。

一、包装类:基本类型的“对象化外衣”

Java是面向对象语言,但8种基本数据类型(byte、short、int、long、float、double、char、boolean)并非继承自Object,无法直接参与对象相关操作(如泛型、集合存储)。为此,Java为每种基本类型设计了对应的包装类,实现基本类型与对象的转换。

1.1 基本类型与包装类对照表

IntegerCharacter外,其余包装类均为基本类型首字母大写:

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

1.2 装箱与拆箱

装箱:把基本类型转换为对应包装类对象;拆箱:把包装类对象转换为基本类型。

手动装箱与拆箱

int i = 10;
// 手动装箱(两种方式)
Integer ii = Integer.valueOf(i);
Integer ij = new Integer(i);
// 手动拆箱
int j = ii.intValue();

自动装箱与自动拆箱(JDK1.5+)

为简化代码,Java提供自动转换机制,编译器自动完成装箱/拆箱:

int i = 10;
Integer ii = i; // 自动装箱
int j = ii;     // 自动拆箱

可通过javap -c查看字节码,能清晰看到编译器自动调用valueOf()(装箱)和intValue()(拆箱)。

1.3 高频面试题:Integer缓存池

public static void main(String[] args) {
    Integer a = 127;
    Integer b = 127;
    Integer c = 128;
    Integer d = 128;
    System.out.println(a == b); // true
    System.out.println(c == d); // false
}

答案与原因

二、泛型:类型参数化,告别类型不安全

2.1 泛型是什么?

泛型是JDK1.5引入的语法,核心是类型参数化——把类型当作参数传递,让类、方法支持多种类型,同时在编译期做类型检查,避免类型转换异常。

简单说:泛型就是“适用于多种类型”,让编译器帮我们管控类型安全

2.2 为什么需要泛型?——从Object的痛点说起

如果不用泛型,想让容器存储任意类型,只能用Object

class MyArray {
    public Object[] array = new Object[10];
    public Object getPos(int pos) { return array[pos]; }
    public void setVal(int pos, Object val) { array[pos] = val; }
}
// 使用
MyArray myArray = new MyArray();
myArray.setVal(0, 10);
myArray.setVal(1, "hello");
String ret = (String) myArray.getPos(1); // 必须强转,编译不报错、运行可能异常

痛点

  1. 编译期无类型检查,可随意存任意类型;
  2. 取值必须强制类型转换,易引发ClassCastException

泛型的核心价值指定容器只能存一种类型,编译期自动检查类型,无需手动强转

2.3 泛型类:语法与使用

1. 泛型类定义语法

class 类名<类型形参> {
    // 可直接使用类型形参
}

规范:类型形参用单个大写字母,常用:

2. 泛型类改造MyArray

class MyArray<T> { // T为类型形参
    public Object[] array = new Object[10];
    public T getPos(int pos) { return (T) array[pos]; }
    public void setVal(int pos, T val) { array[pos] = val; }
}
// 使用
MyArray<Integer> myArray = new MyArray<>(); // 指定类型为Integer
myArray.setVal(0, 10);
int ret = myArray.getPos(0); // 无需强转,编译自动转换
myArray.setVal(1, "hello"); // 编译报错!类型不匹配

3. 类型推导(JDK1.7+)

实例化时可省略右侧类型,编译器自动推导:

MyArray<Integer> list = new MyArray<>(); // 等价于new MyArray<Integer>()

4. 裸类型(了解即可)

泛型类不指定类型实参即为裸类型,仅用于兼容旧代码,开发中禁止使用

MyArray list = new MyArray(); // 裸类型,等价于MyArray<Object>

2.4 泛型的底层:类型擦除

Java泛型是编译期语法糖,编译后会擦除泛型信息,回归到原始类型(通常为Object),保证兼容JDK1.5前的代码。

1. 擦除过程

2. 擦除示例

// 擦除前
class MyArray<T> {
    public Object[] array = new Object[10];
    public T getPos(int pos) { return (T) array[pos]; }
    public void setVal(int pos, T val) { array[pos] = val; }
}
// 擦除后
class MyArray {
    public Object[] array = new Object[10];
    public Object getPos(int pos) { return array[pos]; }
    public void setVal(int pos, Object val) { array[pos] = val; }
}

3. 桥接方法(保证多态)

子类继承泛型父类时,擦除后方法签名不一致,编译器生成桥接方法:

// 父类
class Node<T> {
    public void setData(T data) {}
}
// 子类
class StringNode extends Node<String> {
    @Override
    public void setData(String data) {}
}
// 擦除后,编译器自动生成桥接方法
class StringNode extends Node {
    public void setData(String data) {}
    // 桥接方法
    public void setData(Object data) {
        setData((String) data);
    }
}

2.5 泛型上界:限制类型范围

定义泛型时可通过extends限制类型形参的范围,仅允许指定类/接口的子类:

1. 语法

class 类名<T extends 上界类型> {}

2. 示例

// 仅允许Number及其子类(Integer、Double等)
class MyArray<E extends Number> {}
MyArray<Integer> l1; // 合法
MyArray<String> l2; // 编译报错,String不是Number子类

2.6 泛型方法:独立于类的泛型

泛型方法的泛型参数独立于类,可定义在普通类/静态方法中:

1. 语法

修饰符 <类型形参> 返回值 方法名(参数列表) {}

2. 示例(静态泛型方法)

class Util {
    // 交换数组元素
    public static <E> void swap(E[] array, int i, int j) {
        E temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}
// 使用
Integer[] arr = {1,2,3};
Util.swap(arr, 0, 1); // 自动推导类型
Util.<Integer>swap(arr, 0, 1); // 手动指定类型

2.7 通配符?:灵活接收多种泛型类型

通配符?用于泛型的使用阶段,解决“方法接收任意泛型类型参数”的问题,分为无界通配符、上界通配符、下界通配符。

1. 无界通配符?:接收任意类型

class Message<T> {
    private T msg;
    public T getMsg() { return msg; }
    public void setMsg(T msg) { this.msg = msg; }
}
public class Test {
    // 接收任意Message类型
    public static void fun(Message<?> temp) {
        System.out.println(temp.getMsg());
    }
    public static void main(String[] args) {
        Message<String> m1 = new Message<>();
        Message<Integer> m2 = new Message<>();
        fun(m1);
        fun(m2);
    }
}

2. 通配符上界? extends T:只读

接收T及其子类,只能读、不能写(无法确定具体子类):

class Fruit {}
class Apple extends Fruit {}
class Plate<T> {
    private T data;
    public T getData() { return data; }
    public void setData(T data) { this.data = data; }
}
public class Test {
    // 接收Fruit及其子类
    public static void fun(Plate<? extends Fruit> temp) {
        System.out.println(temp.getData()); // 可读
        temp.setData(new Apple()); // 编译报错,不可写
    }
}

3. 通配符下界? super T:只写

接收T及其父类,可写、读时需强制转换

public class Test {
    // 接收Fruit及其父类
    public static void fun(Plate<? super Fruit> temp) {
        temp.setData(new Apple()); // 可写(子类)
        System.out.println(temp.getData()); // 可读
        Fruit fruit = temp.getData(); // 编译报错,无法确定父类类型
    }
}

三、总结

  1. 包装类:实现基本类型对象化,自动装箱/拆箱简化开发,Integer缓存池需注意==比较;
  2. 泛型核心:类型参数化,编译期类型安全检查,避免强转异常;
  3. 类型擦除:泛型是语法糖,编译后擦除信息,桥接方法保证多态;
  4. 通配符?无界、? extends T只读、? super T只写,灵活适配泛型场景。

掌握包装类与泛型,是理解Java集合框架(如ListMap)源码的关键,也是写出类型安全、复用性高代码的基础。

到此这篇关于Java 包装类与泛型深度拆解:从基础到源码核心的文章就介绍到这了,更多相关Java 包装类与泛型内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文