Java-08 泛型 、ArrayList
2020-06-18 本文已影响0人
哆啦_
泛型 (Generics)
- 从java 5开始,增加了泛型技术
什么是泛型?
- 将类型变为参数,提高代码的复用率
// T:类型参数
public class Student<T> {
private T score;
public Student(T score ) {
this.score = score;
}
}
Student<String> stu1 = new Student<String>("A");
Student<Double> stu2 = new Student<Double>(88.8);
// java 7之后 可以省略右边的类型
Student<Integer> stu3 = new Student<>(44);
泛型类型的类型参数只能用于实例方法中,不能用于静态方法。因为泛型类型的类型参数是跟实例相关联的
public class Student<T> {
private T score;
public Student(T score ) {
this.score = score;
}
// 报错
public static void print(T e) {
}
}
但是可以使用泛型方法
public static <T1, T2>void print(T1 e, T2 s) {
}
可以通过extends
对类型参数进行限制,比如<T extends A>
extends
后面可以跟上类名,接口名,代表T必须是A类型,或者继承/实现了A。 跟dart类似
public class Student<T extends Number> {
}
可以同时添加多个限制,比如<T extends A & B & C>
,代表T必须同时满足A、B、C
不过有一个需要注意的是,类名需要放在接口的前面,且最多只能有一个类名(因为java不支持多类型)
原始类型(Raw Type)
什么是原始类型?
- 没有传递具体的类型给泛型的类型参数
通配符
在泛型中,?
被称为通配符。通常用作变量类型,返回值类型的类型参数。不能用作泛型方法调用、泛型类型实例化、泛型类型定义的类型参数
通配符 - 上界
可以通过extends设置类型参数的上界
// 类型参数必须是Number类型或者其子类型
static void showBox(Box<? extends Number> box) {
}
public class Main {
public static void main(String[] args) {
Box<Integer> box1 = new Box<>();
Box<Double> box2 = new Box<>();
Box<Object> box3 = new Box<>();
Box<? extends Number> box4 = null;
box4 = box1;
box4 = box2;
// 报错 因为限制了泛型类型必须为Number或其子类型
box4 = box3;
showBox(box1);
showBox(box2);
// 报错
showBox(box3);
}
// 1. 使用泛型方法
public static <T extends Number> void showBox(Box<T> box) {
}
// 2. 使用通配符
// 类型参数必须是Number类型或者其子类型
static void showBox(Box<? extends Number> box) {
}
}
通配符- 下界
可以通过super
设置类型参数的下届
// 类型参数必须是Integer类型或者Integer的父类型
void testLower(Box<? super Integer> box) {
}
通配符 - 无限制
// 无限制 类型参数是什么类型都可以
void test(Box<?>box) {
}
ArrayList的常用方法
ArrayList list = new ArrayList();
list.add(11);
list.add(false);
list.add(null);
// 添加元素到指定的下标
list.add(0, "Jack");
System.out.println(list.size());
类似于OC中的可变数组
遍历
ArrayList<Integer> list = new ArrayList<>();
list.add(11);
list.add(22);
list.add(33);
// 遍历
// 经典
int size = list.size();
for (int i = 0; i < size; i++) {
System.out.println(list.get(i));
}
// 迭代器
Iterator<Integer> it = list.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
// for-each 本质也是使用迭代器 跟上面方法本质上是一样的
for (Integer integer : list) {
System.out.println(integer);
}
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer t) {
System.out.println(t);
}
});
// lambda表达式
list.forEach((i) -> {
System.out.println(i);
});
list.forEach((i) -> System.out.println(i));
// 方法引用简化
list.forEach(System.out::println);
for-each 格式
// for-each 格式
for (元素类型 变量名 : 数组\Iterable) {
}
实现了Iterable
接口的对象,都可以使用for-each
格式遍历元素, 比如List
,Set
Iterabel在使用for-each
格式遍历元素时,本质上使用了Iterator
对象
public class ClassRoom implements Iterable<String> {
private String[] students;
public ClassRoom(String... students) {
this.students = students;
}
public String[] getStudents() {
return students;
}
public void setStudents(String[] students) {
this.students = students;
}
@Override
public Iterator<String> iterator() {
// 返回一个迭代器
return new ClassRoomIterator();
}
// 自定义一个迭代器
private class ClassRoomIterator implements Iterator<String> {
private int cursor;
@Override
public boolean hasNext() {
// 是否有下一个元素 取决于游标指向的位置是否超过数组边界
return cursor < students.length;
}
@Override
public String next() {
// 取出下一个元素
// 1. 取出当前游标指向的元素
// 2. 将游标指向下一个位置
return students[cursor++];
}
}
}
public class Main {
public static void main(String[] args) {
ClassRoom room = new ClassRoom("Jack","Bob");
for (String name : room) {
System.out.println(name);
}
Iterator<String> it = room.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
循环删除元素
循环删除元素时 可以使用Iterator
的remove
方法
ArrayList<Integer> list = new ArrayList<>();
list.add(11);
list.add(22);
list.add(33);
list.add(44);
Iterator<Integer> it = list.iterator();
while(it.hasNext()) {
// 取出元素
it.next();
// 删除元素
// 使用迭代器的remove方法 而不是list的remove方法
it.remove();
}
System.out.println(list.toString());
ArrayList的扩容
ArrayList每次扩容都是上一次容量的1.5倍
// 计算新的容量
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 原来的容量右移一位 再加上原来的容量
// 加法与位移运算的效率是高于乘法/除法的
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
当添加的元素数量很多时,可能会多次扩容,为了优化性能,可以考虑一次扩容(前提是知道扩容的大小)
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(i);
}
// 提前扩容
list.ensureCapacity(list.size() + 10000);
for (int i = 0; i < 10000; i++) {
list.add(i);
}
当容量很大,但实际存储的元素很少时,可以考虑适当的缩容
ArrayList<Integer> list = new ArrayList();
for (int i = 0; i < 10000; i++) {
list.add(i);
}
list.clear();
for (int i = 0; i < 10; i++) {
list.add(i);
}
// 缩容
list.trimToSize();
上面的list的容量为10000, 但最后实际上只存放了10个元素,可以考虑缩容
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
trimToSize 把元素拷贝到更小的数组中(长度为size)
LinkedList
LinkedList是一个双向链表,跟ArrayList一样都实现了List
接口。API跟ArrayList类似
List<Integer> list = new LinkedList<>();
for (int i = 0; i < 5; i++) {
list.add(i);
}
LinkedList跟ArrayList的区别