泛型数组列表ArrayList

  |  

摘要: 本文是《Java核心技术 10th》中泛型数组列表 ArrayList 及其周边知识点的要点总结

【对算法,数学,计算机感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:算法题刷刷
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings


本文是《Java核心技术1》第10版 【Chap5 继承】中关于泛型数组列表 ArrayList 的要点总结。由于此前在 C++ 中已经接触过面向对象编程,这里主要关注 Java 与 C++ 有区别的地方。

主要内容如下:

  • 泛型数组列表:ArrayList<> 主要解决运行时更改数组大小的问题,与 C++ vector 很像。
  • 对象包装器:ArrayList<> 的类型参数是不能写基本类型的,但是可以写对象包装器
  • 自动装箱:对象包装器在实际使用时涉及到自动装箱的问题。
  • 可变参数数量的方法中,如果用了 Object[] 则很有可能会涉及到自动装箱的问题。
  • 枚举类:枚举类实际上也是一个泛型类。

泛型数组列表

C++ 中,必须编译时确定数组大小。而 Java 是可以运行时确定数组大小的。

1
2
int size = (int)1e5;
Employee[] staffs = new Employee[size];

虽然可以运行时确定大小,但是没有解决运行时更改数组大小的问题。

解法是用 ArrayList,一个采用类型参数的泛型类,添加或删除时,有自动调节数组容量的功能。

1
ArrayList<Empolyee> staff = new ArrayList<Empolyee>();

C++ vector 与 Java ArrayList 的区别

C++ vector 为了访问方便重载了 [],由于 Java 没有运算符重载,必须调用显式方法。

C++ vector 中 vec2 = vec1 会构造一个新 vector,而 Java 中是 vec1 和 vec2 引用同一个数组列表。

构造数组列表的基本 API

API 含义
ArrayList<E>() 构造一个空数组列表。
ArrayList<E>(int initialCapacity) 用指定容量构造一个空数组列表。参数:initalCapacity 数组列表的最初容量
boolean add(E obj) 在数组列表的尾端添加一个元素。永远返回true。参数:obj 添加的元素
int size() 返回存储在数组列表中的当前元素数量。(这个值将小于或等于数组列表的容量。)
void ensureCapacity(int capacity) 确保数组列表在不重新分配存储空间的情况下就能够保存给定数量的元素。参数:capacity 需要的存储容量
void trimToSize() 将数组列表的存储容量削减到当前尺寸。

访问数组列表元素

由 get 和 set 方法访问或改变数组元素。

1
2
staff.set(i, harry);
Employee e = staff.get(i, harry);

下面的代码创建数组列表,添加所有元素,然后使用 toArray 方法将数组元素拷贝到一个数组中。

1
2
3
4
5
6
7
8
ArrayList<Employee> list = new ArrayList<>();
while(...) {
x = ...;
list.add(...);
}

Employee[] a = new Employee[list.size()];
list.toArray(a);

对象包装器与自动装箱

所有基本类型都有一个对应的类,称为包装器,例如 Integer 对应 int。

1
2
3
4
5
6
7
8
9
Integer
Long
Float
Double
Short
Byte
Charater
Void
Boolean

前六个类派生于公共超类 Number。

对象包装器是不可变的,创建对象后,不能更改其中的值;对象包装器都是 final 的,不能定义子类。

ArrayList<> 的类型参数是不能写基本类型的,但是可以写对象包装器。

1
ArrayList<Integer> list = new ArrayList<>();

由于每个值分别包装在对象中,所以 ArrayList<Integer> 的效率远远低于 int[] 数组。

对象包装器的比较:应该用 equals。== 也可以用于对象包装器对象,只是比较的是对象是否指向同一存储区域,但是同一个值在同一个存储区域这件事是不一定的,不建议。

自动装箱:对于前面的 list,list.add(3) 会自动变成 list.add(Integer.valueOf(3))

自动拆箱:将 Integer 对象赋给 int 值时,会自动拆箱。int n = list.get(i) 会自动变成 int n = list.get(i).intValue()

自动装箱规范要求 boolean、byte、char<=127,介于 -128 ~ 127 之间的 short 和 int 被包装到固定的对象中。

对象包装器可能为 null,因此可能 NullPointException。

表达式混合使用 Integer 和 Double,Integer 会拆箱,提升为 double,在装箱为 Double。

自动拆箱和装箱是编译器认可的,不是虚拟机,只是编译器在生成的字节码时,插入必要的方法调用。

用是指对象包装器还有个好处,可以把某些基本方法放在包装器中,例如将数字字符串转换为数值:

1
int x = Integer.parseInt(s);

对象包装器的方法

方法 说明
int intValue() 以 int 的形式返回 Integer 对象的值(在 Number 类中覆盖了 intValue 方法)。
static String toString(int i)
static String toString(int i, int radix)
以一个新 String 对象的形式返回给定数值 i 的十进制表示。(第一种方法)
返回数值 i 的基于给定 radix 参数进制的表示。(第二种方法)
static int parseInt(String s)
static int parseInt(String s, int radix)
返回字符串 s 表示的整型数值,给定字符串表示的是十进制的整数(第一种方法)
或者是 radix 参数进制的整数(第二种方法)。
static Integer valueOf(String s)
Static Integer value Of(String s, int radix)
返回用 s 表示的整型数值进行初始化后的一个新 Integer 对象,给定字符串表示的是十进制的整数(第一种方法)
或者是 radix 参数进制的整数(第二种方法)。

参数数量可变的方法

变参方法:可以用可变的参数数量调用的方法。例如 printf

1
2
System.out.printf("%d", n);
System.out.printf("%d%s", n, "widgets");

printf 定义如下:

1
2
3
4
5
public class PrintStream {
public PrintStream printf(String fmt, Object... args) {
return format(fmt, args);
}
}

省略号 ... 表示可以接受任意数量的对象。第一个参数是格式字符串,第二个参数是 Object[] 数组。

编译器会对 printf 进行转换,将参数绑定到数组上,并在必要时候自动装箱。

自定义可变参数的方法,例子:计算若干个数值的最大值。

1
2
3
4
5
6
7
8
9
public static double max(double... values) {
double largest = Double.NEGATIVE_INFINITY;
for(double v: values) {
if(v > largest) {
largest = v;
}
}
return largest;
}

假设调用 double m = max(3.1, 30, -2),编译器会将 new double[] {3.1, 30, -2} 传递给 max。

可以将已经存在且最后一个参数是数组的方法重新定义为可变参数的方法,而不会破坏任何已经存在的代码。


枚举类

  • 变量取值只在有限集合内
  • 枚举类型的变量只能存储给定的某个枚举值,或 null 值
1
2
3
public enum Size {
SMALL, MEDIUM, LARGE, EXTRA_LARGE
};

这个声明定义的类型是一个类,它刚好有 4 个实例。因此在比较两个枚举类型时,不要调用 equals,直接用 == 就可以了。

可以自爱枚举类型中添加构造器,方法和域,如下。构造器只是在构造枚举常量时才调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
public enum Size {
SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");

private String abbreviation;

private Size(String abbreviation) {
this.abbreviation = abbreviation;
}

public String getDescription() {
return abbreviation;
}
}

枚举类型 Size 实际上应该是 Enum<Size>,只是为了简化神略了类型参数,这点与 Class 类差不多。

所有枚举类型都是 Enum 类的子类。可以从 Enum 这个类继承很多方法,例如:

  • toString 方法:返回枚举常量名
1
System.out.println(Size.SMALL.toString());
  • 静态方法 valueOf: 是 toString 的逆方法,从字符串返回枚举常量。
1
Size s = Enum.valueOf(Size.class, "SMALL");
  • 静态的 values 方法:返回包含全部枚举值的数组。
1
Size[] values = Size.values();
  • ordinal 方法: 返回 enum 声明中枚举常量的位置,位置从 0 开始计数。
1
int idx = Size.MEDIUM.ordinal();
  • compareTo 方法: 枚举常量的顺序的比较。

Share