目录一、包装类让基本类型也能面向对象1.1 为什么需要包装类1.2 装箱与拆箱1.3 自动装箱与自动拆箱1.4 高频面试题Integer的比较陷阱二、泛型让代码更加通用和安全2.1 什么是泛型2.2 泛型的基本语法2.3 泛型的类型推导三、泛型背后的秘密类型擦除3.1 擦除机制3.2 桥接方法四、泛型的高级用法4.1 泛型的上界4.2 泛型方法4.3 通配符通配符上界 ? extends T通配符下界 ? super T一个实用口诀五、实战用泛型实现一个简易的ArrayList六、总结与建议6.1 核心要点回顾6.2 学习建议一、包装类让基本类型也能面向对象1.1 为什么需要包装类在Java中int、double、char这类基本类型并不是对象也不继承自Object。但Java的集合框架如ArrayList、HashMap只能存储对象类型。这就产生了一个矛盾——我们想用集合存整数怎么办为了解决这个问题Java为每个基本类型都提供了一个对应的包装类基本类型包装类byteByteshortShortintIntegerlongLongfloatFloatdoubleDoublecharCharacterbooleanBoolean记忆技巧除了Integer和Character其他包装类都是基本类型首字母大写。1.2 装箱与拆箱把基本类型变成包装类对象的过程叫装箱反过来叫拆箱。int i 10; // 手动装箱 Integer ii Integer.valueOf(i); Integer ij new Integer(i); // 不推荐已过时 // 手动拆箱 int j ii.intValue();1.3 自动装箱与自动拆箱手动装箱拆箱太麻烦Java贴心地提供了自动机制int i 10; Integer ii i; // 自动装箱 Integer ij (Integer) i; // 自动装箱 int j ii; // 自动拆箱 int k (int) ii; // 自动拆箱你可以用javap -c命令反编译字节码文件观察底层实际上调用了valueOf()和intValue()方法。1.4 高频面试题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 }为什么会这样这是因为Integer内部有一个缓存机制默认情况下-128到127之间的整数会被缓存起来。当你用自动装箱创建这个范围内的Integer对象时实际上返回的是同一个缓存对象。而128超出了缓存范围每次都会创建新对象所以c d比较的是两个不同对象的地址结果为false。正确做法比较包装类对象的值永远使用equals()方法而不是。二、泛型让代码更加通用和安全2.1 什么是泛型想象这样一个场景你想写一个通用的盒子类既可以装苹果也可以装书本还可以装手机。没有泛型之前你只能用Object来实现class Box { private Object item; public void setItem(Object item) { this.item item; } public Object getItem() { return item; } }但这样做有两个问题取出来时必须强制类型转换不小心放错了类型运行时才会发现泛型就是为了解决这些问题而生——它把类型也变成了一个可以传递的参数。2.2 泛型的基本语法// 定义一个泛型类 class BoxT { private T item; public void setItem(T item) { this.item item; } public T getItem() { return item; } } // 使用泛型类 BoxString stringBox new Box(); stringBox.setItem(Hello); String str stringBox.getItem(); // 无需强转 BoxInteger intBox new Box(); intBox.setItem(100); Integer num intBox.getItem(); // 无需强转关键点T中的T是类型占位符可以是任意字母常用的命名约定E(Element)、K(Key)、V(Value)、T(Type)泛型只能接受引用类型不能是基本类型要用包装类2.3 泛型的类型推导Java编译器很聪明它能根据上下文推断出类型参数// 完整写法 BoxString box new BoxString(); // 类型推导JDK 7 BoxString box new Box();右边的被称为菱形运算符编译器会自动推断出类型。三、泛型背后的秘密类型擦除3.1 擦除机制很多人以为泛型是在运行时起作用的其实不然。Java的泛型只在编译阶段有效编译完成后泛型信息会被擦除掉。看这个例子擦除前源代码class MyArrayT { public T[] array (T[]) new Object[10]; public T getPos(int pos) { return this.array[pos]; } public void setVal(int pos, T val) { this.array[pos] val; } }擦除后编译后的代码class MyArray { public Object[] array new Object[10]; public Object getPos(int pos) { return this.array[pos]; } public void setVal(int pos, Object val) { this.array[pos] val; } }可以看到T被替换成了Object并且在必要的地方插入了类型转换。3.2 桥接方法类型擦除可能会导致子类方法和父类方法签名不一致这时编译器会生成桥接方法来维护多态性。class NodeT { private T data; public void setData(T data) { this.data data; } } class StringNode extends NodeString { Override public void setData(String data) { System.out.println(设置字符串: data); super.setData(data); } }擦除后Node的setData参数变成了Object而StringNode的setData参数是String两者不再构成重写关系。编译器会自动生成一个桥接方法// 编译器生成的桥接方法 public void setData(Object data) { setData((String) data); }这样就保证了多态性的正常工作。四、泛型的高级用法4.1 泛型的上界有时候我们希望泛型只能接受特定类型的子类这时可以使用上界// 只接受Number及其子类 class CalculatorT extends Number { private T value; public double square() { return value.doubleValue() * value.doubleValue(); } } CalculatorInteger calc1 new Calculator(); // 没问题 CalculatorDouble calc2 new Calculator(); // 没问题 CalculatorString calc3 new Calculator(); // 编译错误更复杂的用法要求泛型必须实现某个接口// 要求T必须实现了Comparable接口 class MaxFinderT extends ComparableT { public T findMax(T[] array) { T max array[0]; for (T item : array) { if (item.compareTo(max) 0) { max item; } } return max; } }4.2 泛型方法不仅类可以有泛型方法也可以有自己的泛型参数public class ArrayUtils { // 静态泛型方法 public static E void swap(E[] array, int i, int j) { E temp array[i]; array[i] array[j]; array[j] temp; } } // 使用示例 Integer[] numbers {1, 2, 3, 4, 5}; ArrayUtils.swap(numbers, 0, 4); // 类型推导 ArrayUtils.Integerswap(numbers, 0, 4); // 显式指定类型4.3 通配符通配符?用于解决泛型在使用过程中的灵活性问题。场景写一个方法可以打印任何类型的Message对象的内容。class MessageT { private T content; public T getContent() { return content; } public void setContent(T content) { this.content content; } } // 使用通配符 public static void printMessage(Message? msg) { System.out.println(msg.getContent()); // msg.setContent(...); // 编译错误不能修改 }通配符上界? extends T表示可以接受T或其子类public static void processNumbers(List? extends Number list) { for (Number num : list) { System.out.println(num.doubleValue()); } // list.add(123); // 编译错误不能添加元素 }为什么不能添加​ 因为? extends Number可能是Integer、Double等编译器无法确定具体类型所以不允许写入。通配符下界? super T表示可以接受T或其父类public static void addNumbers(List? super Integer list) { list.add(123); // 可以添加Integer list.add(456); // 可以添加Integer // Integer num list.get(0); // 编译错误取出的可能是Object }为什么可以添加但不能确定取出类型​ 因为? super Integer可能是Integer、Number或Object存入Integer总是安全的但取出来的类型不确定。一个实用口诀生产者Producer用? extends T我只读数据不往里写消费者Consumer用? super T我只写数据不在乎读出来的是什么这其实就是著名的PECS原则Producer Extends, Consumer Super。五、实战用泛型实现一个简易的ArrayList让我们把学到的知识串起来手写一个简化版的ArrayListimport java.util.Arrays; public class SimpleArrayListE { private static final int DEFAULT_CAPACITY 10; private Object[] elements; private int size; public SimpleArrayList() { this.elements new Object[DEFAULT_CAPACITY]; this.size 0; } public void add(E element) { ensureCapacity(); elements[size] element; } SuppressWarnings(unchecked) public E get(int index) { if (index 0 || index size) { throw new IndexOutOfBoundsException(索引越界: index); } return (E) elements[index]; } public int size() { return size; } private void ensureCapacity() { if (size elements.length) { elements Arrays.copyOf(elements, elements.length * 2); } } } // 使用示例 SimpleArrayListString list new SimpleArrayList(); list.add(Java); list.add(泛型); list.add(真香); for (int i 0; i list.size(); i) { String str list.get(i); // 无需强制类型转换 System.out.println(str); }六、总结与建议6.1 核心要点回顾包装类让基本类型能够参与面向对象的编程注意Integer的缓存机制泛型实现了类型参数化让代码更加通用和安全类型擦除是Java泛型的实现机制理解它有助于避开一些坑通配符提供了更灵活的类型匹配方式遵循PECS原则6.2 学习建议多读源码打开IDE看看ArrayList、HashMap的源码你会发现到处都是泛型的身影多写代码尝试自己实现一个泛型类或泛型方法在实践中加深理解注意陷阱牢记Integer缓存、泛型不能是基本类型、通配符的读写限制等细节如果你觉得这篇文章对你有帮助欢迎点赞收藏。下期我们将深入Java集合框架从源码角度剖析ArrayList和LinkedList的性能差异敬请期待