Collection集合
集合:集合是java提供的容器,可以用来存储多个数据。
集合和数组的区别:
- 数组的长度固定,集合的长度不可变。
- 数组存储的都是同一类型的基本数据类型。而集合存储的都是对象,对象的类型可以不一致。
Collection:集合层次的顶层接口,没有直接的实现类,有不同的子接口继承Collection。而每一个子接口都有自己的特点。每一个子接口都有自己的实现类。
java.util.Collection
接口中常用的集合方法::
public boolean add(E e)
:把指定的参数元素,添加到集合中;返回添加动作是否成功。
public boolean remove(Object e)
:根据参数指定的元素内容,从集合当中删除元素;返回删除是否成功。
public boolean contains(Object e)
:判断集合是否包含指定的元素。
public boolean isEmpty()
:判断集合是否为空白(一个元素都没有)。
public void clear()
:清空集合当中所有的元素内容。
public int size()
:获取集合的长度尺寸。
public Object[] toArray()
:将集合转换成为数组形式。
注意事项:
对于remove方法
和contains方法
来说,参数是Object类型
。这里是如何得知两个对象是否“相同”的呢?
由equals和hashCode两个方法共同决定。
Collection<String> coll = new ArrayList<>();
coll.add("迪丽热巴");
coll.add("古力娜扎");
coll.add("玛尔扎哈");
System.out.println(coll); // [迪丽热巴, 古力娜扎, 玛尔扎哈]
boolean success = coll.remove("玛尔扎哈");
System.out.println("删除玛尔扎哈是否成功:" + success); // true
System.out.println(coll); // [迪丽热巴, 古力娜扎]
success = coll.remove("高圆圆");
System.out.println("删除高圆圆是否成功:" + success); // false
System.out.println(coll); // [迪丽热巴, 古力娜扎]
coll.add("鹿晗");
coll.add("马晗");
coll.add("牛晗");
coll.add("杨晗");
coll.add("于晗");
coll.add("朱晗");
coll.add("侯晗");
System.out.println(coll); // [迪丽热巴, 古力娜扎, 鹿晗, 马晗, 牛晗, 杨晗, 于晗, 朱晗, 侯晗]
System.out.println(coll.contains("马晗")); // true
System.out.println(coll.contains("狗晗")); // false
System.out.println("=============");
System.out.println("集合是否为空:" + coll.isEmpty()); // false
coll.clear();
System.out.println("集合是否为空:" + coll.isEmpty()); // true
System.out.println("集合的尺寸:" + coll.size()); // 0
coll.add("XXX");
coll.add("YYY");
coll.add("ZZZ");
System.out.println("集合的尺寸:" + coll.size()); // 3
System.out.println("=============");
Object[] array = coll.toArray();
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
Iterator迭代器
由于集合中并不是所有的集合都有索引值,所以遍历Collection集合,就要获取迭代器完成迭代操作。
迭代器的方法:
public Iterator iterator()
:获取对应集合的迭代器,用来遍历集合当中的元素。
概念:
对于一个Collection集合来说,集合元素的获取方式,在取元素的时候要先判断集合当中有没有元素,如果有,把这个元素取出来,继续判断,如果有就取出。一直到把集合当中的所有元素都取出来了。这种取出的方法称为迭代。
java.util.Iterator<T>
接口代表迭代器:
public boolean hasNext
:判断集合中有没有元素,有就返回true。
public E next()
:返回迭代的下一个元素。
注意:
在使用迭代器遍历集合的过程当中,一定要避免直接通过集合改变其中元素的个数。
如果不听话,不乖,那么将会发生ConcurrentModificationException并发修改异常。
Collection<String> coll = new ArrayList<>();
coll.add("迪丽热巴");
coll.add("古力娜扎");
coll.add("泷泽萝拉");
Iterator<String> iter = coll.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
// coll.add("阿里巴巴"); // ConcurrentModificationException
}
增强for循环:
增强for循环(昵称也叫做for-each循环)是JDK 1.5添加的特性之一。
for (数据类型 变量名称 : 数组) {
// ...
}
注意:其中的数据类型并不一定是int,不代表索引值。
含义:用左边的变量,分别得到右侧数组当中的每一个数据值。
备注:
这其实只是一个语法糖。对于数组来说,增强for循环底层其实就是一个普通的for循环。
double[] array = {1.5, 2.5, 3.5};
// 变量num将会分别得到数组array当中的每一个元素
for (double num : array) {
System.out.println(num);
}
增强for循环照样也可以支持集合。
for (数据类型 变量名称 : 集合) {
// ...
}
备注:
这其实也是一种语法糖,对于集合来说,底层其实就是迭代器,只是表面上增强for循环的写法简单而已。
Collection<String> coll = new ArrayList<>();
coll.add("迪丽热巴");
coll.add("古力娜扎");
coll.add("阿里巴巴");
// name将会分别获取集合coll当中的每一个元素
for (String name : coll) { // iter.next()
System.out.println(name);
}
使用增强for循环的时候,注意事项:
- 支持数组,其实是一个语法糖,底层就是普通的fori循环。
- 支持java.lang.Iterable接口对象,其中就包含了集合。因为这个接口规定了一项能力:
public Iterator<T> iterator()
:获取迭代器的方法。 - 支持集合,也是一个语法糖。底层就是在使用迭代器。
- 增强for循环当中没有索引值,所以就无法直接修改数组或集合中的内容。【重点】
// 外侧还定义了一个Person类。有name和age两个属性值。
// 增强for循环没有索引值,所以基本数据类型的数据值不能变,同时引用数据类型的地址值不能变。
Collection<Person> coll = new ArrayList<>();
coll.add(new Person("赵丽颖", 18));
coll.add(new Person("鹿晗", 73));
coll.add(new Person("王宝强", 84));
// [Person{name='赵丽颖', age=18}, Person{name='鹿晗', age=73}, Person{name='王宝强', age=84}]
System.out.println(coll);
// 换一个别的对象是做不到的。
// 但是通过对象名称地址值调用方法仍然是可以的,从而对象的成员变量内容发生了改变。
for (Person person : coll) { // Person person = iter.next();
person.setAge(40);
}
// [Person{name='赵丽颖', age=40}, Person{name='鹿晗', age=40}, Person{name='王宝强', age=40}]
System.out.println(coll);
泛型
在集合中可以存取任意类型的对象,在把对象存储到集合中后,他们都会被提升成Object类型。每取出一个对象,进行相关操作,就必须进行类型转换。
Collection<String> coll = new ArrayList<>();
coll.add("abc");
coll.add("Hello");
// coll.add(100); // 错误写法!
// for (Object o : coll) {
// String str = (String) o;
// System.out.println(str.length());
// }
for (String str : coll) {
System.out.println(str.toUpperCase()); // 转成大写
}
- 泛型:
tips:一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。
使用泛型的好处:
- 保证类型统一,确保类型安全。
- 将可能发生的类型安全问题,从运行期提前到编译期。(有问题尽量在javac的时候就暴露出来最好,别等到java运行时再暴露)
- 省去向下转型的麻烦。
- 让代码模板化。
自定义泛型的三种用法:
- 泛型类
- 泛型接口
- 泛型方法
如何自定义一个泛型类?
修饰符 class 类名称<泛型> {
// ...
}
泛型的名称用什么都可以,一般推荐使用大写字母。
泛型代表一种尚不确定的类型,所有本类范围之内都能用这个泛型当做不确定的类型进行使用。
什么时候才能确定这个泛型?
当创建泛型类对象的时候,就可以确定。
// 定义一个泛型类:
public class Factory<A> {
public A makePhone(A param) {
// 进行组装的若干动作
return param;
}
}
// 测试类:
Factory<IPhone> factory1 = new Factory<>();
IPhone result1 = factory1.makePhone(new IPhone());
Factory<Nokia> factory2 = new Factory<>();
Nokia result2 = factory2.makePhone(new Nokia());
如何定义一个泛型接口呢?
修饰符 interface 接口名称<泛型> {
// ...
}
含义和泛型类是完全相同的:本接口之内泛型通用。
什么时候才能确定接口的泛型?
- 实现类在实现接口的时候,直接指定具体泛型。
- 实现类仍然不指定具体泛型,那么实现类也必须是一个泛型类。
// 定义一个泛型接口
public interface MyInterface<T> {
void method(T param); // 抽象方法的参数类型,跟着泛型走。
}
// 定义一个泛型的实现接口
public class MyInterfaceImpl<T> implements MyInterface<T> {
@Override
public void method(T param) { /* 这个泛型仍然不确定,
本类的泛型是谁,它就是谁*/
}
}
// 测试类
MyInterfaceImpl<String> impl = new MyInterfaceImpl<>();
impl.method("abc");
// 也可以用普通的实现类实现泛型接口,需要指定泛型的类型
public class MyInterfaceImplA implements MyInterface<String> {
@Override
public void method(String param) { // 这个参数就是具体泛型String类型
}
}
对于泛型类/泛型接口来说,泛型是在本类/本接口当中全局通用。
泛型方法:
修饰符 <泛型> 返回值类型 方法名称(参数类型 参数名称) {
方法体
}
备注:
这个泛型定义在方法上,所以只有当前这个方法自己专用。别人不能用。
// 定义一个泛型的类,在类中定义个泛型的方法
public class MyClass<A> {
public void method1(A param) {
}
public void method2(A param) {
}
// 定义一个泛型的方法
public <B> B methodSpecial(B param) {
// ...
return param;
}
}
// 测试类:
MyClass<Integer> obj = new MyClass<>();
obj.method1(100);
obj.method2(200);
// obj.method1("abc"); // 错误
String str = obj.methodSpecial("abc");
Person person = obj.methodSpecial(new Person());
泛型通配符:
泛型的通配符其实就是一个问号:【?】
作用:用来被动匹配任何一种泛型。
注意事项:
- 一旦使用了泛型?通配符进行匹配接收,那么遍历的时候就只能当做Object,因为不确定泛型到底传进来的是谁。
- 这个通配符问号,只能在匹配接收的时候使用,不能在定义泛型的时候使用。
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
methodA(list1); // 正确
methodB(list2); // 正确
// methodA(list2); // 错误
// methodB(list1); // 错误
// methodObject(list1); // 错误
// methodObject(list2); // 错误
method(list1); // 正确
method(list2); // 正确
}
private static void method(ArrayList<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
泛型的上下限:
对于泛型的通配符问号,有两种特殊写法:上下限,上下界。
<?>:随便,谁都行。
<? extends 类>:可以是指定的类,或者其子类。(上限,最高不能超过这个类。)
<? super 类>:可以是指定的类,或者其父类。(下限,最低不能超过这个类。)
public static void main(String[] args) {
ArrayList<Animal> animalList = new ArrayList<>();
ArrayList<Dog> dogList = new ArrayList<>();
ArrayList<Cat> catList = new ArrayList<>();
ArrayList<String> strList = new ArrayList<>();
methodExtends(animalList); // OK
methodExtends(dogList); // OK
methodExtends(catList); // OK
// methodExtends(strList); // 错误!
System.out.println("==============");
methodSuper(dogList); // OK
methodSuper(animalList); // OK
// methodSuper(catList); // 错误!
// methodSuper(strList); // 更错!
}
// 这个泛型只能匹配Animal或者是Animal的子类
private static void methodExtends(ArrayList<? extends Animal> list) {
}
// 这个泛型只能匹配Dog或者是Dog的父类
private static void methodSuper(ArrayList<? super Dog> list) {
}