Java 基础避坑与原理:Scanner、泛型与 ArrayList

内容由AI生成

Java 基础避坑与原理:Scanner、泛型与 ArrayList(含源码验证)

1. Scanner 的“回车符”陷阱

现象:
在使用 Scanner 时,如果先调用 nextInt(),紧接着调用 nextLine(),第二个字符串输入会直接跳过(读到空字符串)。

int num = sc.nextInt();
String str = sc.nextLine(); // 还没输入就结束了?

原理:

  1. 缓冲区残留nextInt() 读取数字后,将结束符(回车 \n)留在了输入缓冲区中。
  2. 读取机制nextLine() 以换行符为结束标志。它刚启动就读取到了残留的 \n,认为一行结束,于是返回空字符串。
  3. 注意next() 会自动跳过空白符,所以没有这个问题。

解决方案:

  • 方案 A:在 nextInt() 后加一句 sc.nextLine() 手动吃掉回车。
  • 方案 B(推荐):统统使用 nextLine() 读取,再通过 Integer.parseInt() 转换,彻底避坑。

2. 泛型与基本类型(ArrayList<int>)

现象:
ArrayList<int> 编译报错,必须写成 ArrayList<Integer>

核心原理:

  1. 类型擦除:Java 泛型是伪泛型,编译后泛型类型会被擦除为 Object
  2. 不兼容:基本类型(int)不是对象,无法转换为 Object,因此泛型不支持基本类型。

面试高频点:

  • 自动装箱/拆箱:编译器自动在 intInteger 间调用 valueOf()intValue()
  • 空指针异常 (NPE)Integer 默认为 null,如果自动拆箱赋值给 int 会崩。
  • Integer 缓存池-128127 之间的数字,Integer.valueOf() 会复用缓存对象。
    • Integer a=100, b=100; a==b (true,走缓存)
    • Integer c=200, d=200; c==d (false,new 新对象)
    • 结论:包装类比较一定要用 .equals()

3. ArrayList 的初始化与扩容机制

疑问: new ArrayList<>() 初始大小是多少?会频繁扩容影响性能吗?

源码真相(JDK 1.8+):

  1. 懒加载机制
    • 执行 new 时,底层数组指向空数组,物理容量为 0
    • 执行第一次 add() 时,才初始化底层数组,容量直接变为 10
  2. 扩容策略
    • 触发时机:数组填满时。
    • 增长幅度1.5 倍。
    • 演变:0 -> 10 -> 15 -> 22 -> 33 ...

源码验证(IDEA实操):
想亲眼确认?按住 Ctrl (Mac 为 Cmd) 点击代码中的 ArrayList 进入源码:

  1. 验证懒加载

    • 搜索构造方法 public ArrayList()
    • 代码this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    • 解读:赋值的是一个空数组,证明初始并未分配内存。
  2. 验证 1.5 倍扩容

    • 搜索 grow(int minCapacity) 方法。
    • 代码int newCapacity = oldCapacity + (oldCapacity >> 1);
    • 解读>> 1 表示右移一位(即除以 2 取整)。
    • 计算示例 (15 -> 22)15 + (15 >> 1) = 15 + 7 = 22
    • (注:Java 整数运算自动舍弃小数部分,并非四舍五入)
JDK 17 / 21+(新版写法):
调用 ArraysSupport 工具类:
code
Java
int newCapacity = ArraysSupport.newLength(oldCapacity,
        minCapacity - oldCapacity, 
        oldCapacity >> 1); // <--- 看这里!依然是右移一位(0.5倍)

性能总结:

  • 由于是倍增策略,数据量越大扩容频率越低,一般场景下不会有严重性能问题。
  • 最佳实践:如果预知数据量(如 1000 条),建议直接指定容量 new ArrayList<>(1000),避免中间的数组拷贝开销。
posted @ 2025-12-06 10:15  Nickey103  阅读(9)  评论(0)    收藏  举报