1. 类集简介
我们都知道,数组本身有一个很大的缺陷:长度固定。所以从JDK1.2开始,Java为了解决数组长度的问题,提供了动态的对象数组实现框架–Java的类集框架。Java类集框架实际上也是java针对数据结构的一种实现。
2. Collection接口
Collection
是Java类集(java.util
包)里的核心接口中的一个,还有一个就是Map
接口,后面会说到。
其中Collection
接口的操作形式与编写链表的操作形式类似,即每一次进行数据操作的时候只能够对单个对象进行处理。Collection是单个集合保存的最大父接口。
Collection接口定义:
//实现了迭代器接口,表示可迭代,表示可以通过迭代器获取集合元素
public interface Collection<E> extends Iterable<E>
- 此接口的常用方法有:
① 向集合中添加数据:add(E e)
② 向集合中添加一组数据:addAll(Collection<?extends E> c)
③ 清空集合数据:clear()
④ 查找数据是否存在:contains(Object o)
⑤ 删除数据:remove(Object o)
⑥ 取得集合长度:size()
⑦ 将集合变为对象数组返回:toArray()
⑧ 取得Iterator
对象,用于集合输出:iterator()
我们很少会直接使用Collection
接口,因为Collection
接口只是一个存储数据的标准,并不能区分存储类型。而在实际开发之中,往往会考虑使用Collection
接口的子接口:List
(允许数据重复)、Set
(不允许数据重复)。
3. List接口
在实际开发之中,List
接口的使用频率可以达到Collection
系列的80%
。在进行集合处理的时候,优先考虑List
接口。
- 在这个接口中有两个重要的扩充方法:
① 根据索引取得数据:get(int index)
② 修改数据:set(int index, E element)
由于List
本身还是接口,所以要想取得List
的实例化对象,就必须要有子类,在List
接口下有三个常用子类:ArrayList
、Vector
、LinkedList
。
3.1 ArrayList子类
- 通过
List
进行输出处理:
package com.xxx;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
public class TestArrayList {
public static void main(String[] args) {
//顶层接口Collection,但是一般不会直接用它,而是用它的子接口List或者Set
List<String> list = new ArrayList<>();
//添加
list.add("Java");
list.add("C++");
list.add(0, "PHP");
list.add(2, "Python");
//重复添加
list.add("Java");
System.out.println("原list:"+list);
System.out.println("list是否为空:"+list.isEmpty());
System.out.println("list的长度:"+list.size());
System.out.println("list是否包含PHP:"+list.contains("PHP"));
//移除
System.out.println("list移除Java:"+list.remove("java"));
System.out.println("list移除index=0:"+list.remove(0));
System.out.println("当前的list:"+list);
//获取
System.out.println("获取index=1的元素:"+list.get(1));
//System.out.println("获取index=10的元素:"+list.get(10));//IndexOutOfBoundsException
//遍历
for(int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
- 通过
Collection
进行输出处理:
package com.xxx;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
public class TestArrayList {
public static void main(String[] args) {
//使用顶层接口Collection
Collection<String> collection = new ArrayList<>();
//添加
collection.add("Java");
collection.add("C++");
collection.add("PHP");
//遍历
Object[] values = collection.toArray();
for(Object value : values) {
System.out.println(value);
}
//Object[] -> String
System.out.println(Arrays.toString(values));
}
}
-
ArrayList
与简单Java
类: - 自定义了一个
Person
类:
package com.xxx;
import java.util.Objects;
public class Person {
private String name;
private Integer age;
private transient String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", password='" + password + '\'' +
'}';
}
}
package com.xxx;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
public class TestArrayList {
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
Person person1 = new Person();
person1.setName("张三");
person1.setAge(22);
person1.setPassword("111");
Person person2 = new Person();
person2.setName("李四");
person2.setAge(18);
person2.setPassword("123456");
//添加
personList.add(person1);
personList.add(person2);
System.out.println("List包含person1对象吗:"+personList.contains(person1));//true
//创建了一个跟person1属性值相同的person3对象
Person person3 = new Person();
person3.setName("张三");
person3.setAge(22);
person3.setPassword("111");
System.out.println("List包含person3对象吗:"+personList.contains(person3));//false
}
}
但是上述是不满足业务需求的,我们通常关心的并不是到底是不是一个对象,而是两个对象的值是否相等。
- 自定义Person类:
package com.xxx;
import java.util.Objects;
public class Person {
private String name;
private Integer age;
private transient String password;
public Person() {
}
public Person(String name, Integer age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
package com.xxx;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
public class TestArrayList {
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
Person person2 = new Person();
person2.setName("李四");
person2.setAge(18);
person2.setPassword("123456");
//添加
//匿名对象没办法判断是否包含
personList.add(new Person("张三", 22, "111"));
personList.add(person2);
}
}
所以需要覆写
equals
方法:
① 通过工具自动生成
② 参考已经覆写过的类
③ equals方法和hashCode方法要同时覆写,参与运算的属性保持一致
package com.xxx;
import java.util.Objects;
public class Person {
private String name;
private Integer age;
private transient String password;
public Person() {
}
public Person(String name, Integer age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", password='" + password + '\'' +
'}';
}
//判断内容是否相等
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return name.equals(person.name) &&
age.equals(person.age) &&
password.equals(person.password);
}
@Override
public int hashCode() {
return Objects.hash(name, age, password);
}
}
package com.xxx;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
public class TestArrayList {
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
Person person1 = new Person();
person1.setName("张三");
person1.setAge(22);
person1.setPassword("111");
Person person2 = new Person();
person2.setName("李四");
person2.setAge(18);
person2.setPassword("123456");
//包含判断是否是同一个对象
System.out.println("List包含person1对象吗:"+personList.contains(person1));//true
//创建了一个跟person1属性值相同的person3对象
Person person3 = new Person();
person3.setName("张三");
person3.setAge(22);
person3.setPassword("111");
//覆写equals之后,判断的是内容 true
System.out.println("List包含person3对象吗:"+personList.contains(person3));//true
}
}
3.2 Vector子类
使用方法和ArrayList
基本相同,实现不同,多了一个关键字synchronize
。
- 请解释
ArrayList
与Vector
区别:
① 历史时间:ArrayList是从JDK1.2提供的,而Vector是从JDK1.0就提供了。
② 处理形式:ArrayList是异步处理,性能更高;Vector是同步处理,性能较低。
③ 数据安全:ArrayList是非线程安全;Vector是性能安全。
④ 输出形式:ArrayList支持Iterator、ListIterator、foreach;Vector支持Iterator、ListIterator、foreach、Enumeration。
3.3 LinkedList子类
package com.xxx;
import java.util.LinkedList;
import java.util.List;
public class TestLinkedList {
public static void main(String[] args) {
//LinkedList双向链表
List<String> list = new LinkedList<>();
list.add("Java");
list.add("C++");
list.add(1, "PHP");
System.out.println("list的长度:"+list.size());
System.out.println("是否包含Java:"+list.contains("Java"));
System.out.println("删除C++:"+list.remove("C++"));
System.out.println(list);
}
}
- 请解释
ArrayList
与LinkedList
区别:
① 观察ArrayList源码,可以发现ArrayList里面存放的是一个数组,如果实例化此类对象时传入了数组大小,则里面保存的数组就会开辟一个定长的数组,但是后面再进行数据保存的时候发现数组个数不够了会进行数组动态扩充。 所以在实际开发之中,使用ArrayList最好的做法就是设置初始化大小。
② LinkedList是一个纯粹的链表实现,与之前编写的链表程序的实现基本一样。
③ 性能:ArrayList封装的是数组,访问的话按下标访问,时间复杂度为1;LinkedList封装的是链表,时间复杂度为n。
4. Set接口
与List
接口的区别就是元素不能重复,里面没有扩展get
、set
方法,而List有。
两个常用子类:HashSet
(无序存储)、TreeSet
(有序存储)。
4.1 HashSet子类
package com.xxx;
import java.util.HashSet;
import java.util.Set;
public class TestHashSet {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("Java");
set.add("C++");
set.add("JavaScript");
set.add("PHP");
//元素不能重复
//set.add("C++");
System.out.println("Set的长度:"+set.size());
System.out.println("Set是否包含Java:"+set.contains("Java"));
System.out.println("Set是否包含Python:"+set.contains("Python"));
System.out.println("Set移除C++:"+set.remove("C++"));
System.out.println(set);
//无序的
for(String item : set) {
System.out.println(item);
}
}
}
-
HashSet
与简单Java
类:
package com.xxx;
import java.util.HashSet;
import java.util.Set;
public class TestHashSet {
public static void main(String[] args) {
Set<Person> people = new HashSet<>();
people.add(new Person("张三", 22, "123"));
people.add(new Person("李四", 18, "abc"));
people.add(new Person("张三", 22, "123"));
System.out.println(people);
//Person类还是前面的Person类,覆写equals输出2个对象,不覆写输出3个
}
}
4.2 TreeSet子类
既然TreeSet
子类可以进行排序,所以我们可以利用TreeSet
实现数据的排列处理操作。此时要想进行排序实际上是针对于对象数组进行的排序处理,而如果要进行对象数组的排序:
① 内部比较器:元素必须能够排序,实例化元素的类型需要实现可比较(Comparable)的接口。
② 外部比较器:构造TreeSet时在构造方法参数处指定比较器(Comparator)的实例化对象(灵活)。
- 内部比较器:
package com.xxx;
import java.util.Objects;
public class Person implements Comparable<Person> {
private String name;
private Integer age;
private transient String password;
public Person() {
}
public Person(String name, Integer age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", password='" + password + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return name.equals(person.name) &&
age.equals(person.age) &&
password.equals(person.password);
}
@Override
public int hashCode() {
return Objects.hash(name, age, password);
}
@Override
public int compareTo(Person o) {
//this 当前对象
//o 进行比较的对象
//对Person集合进行排序,排序规则
//1. 按照年龄升序排列
//return this.age - o.getAge();
//2. 按照年龄降序排列
//return -1*(this.age - o.getAge())*(-1);
//3. 按照密码长度升序排序
return this.password.length() - o.getPassword().length();
//但是这种方式不太灵活,希望在用的时候再决定怎么排序
}
}
- 外部比较器:
package com.xxx;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class TestTreeSet {
public static void main(String[] args) {
Set<Integer> set = new TreeSet<>();
set.add(20);
set.add(18);
set.add(21);
set.add(15);
System.out.println(set);
Set<String> set1 = new TreeSet<>();
set1.add("Java");
set1.add("C++");
set1.add("PHP");
set1.add("Python");
//排序的第二种方式,lambda表达式
Set<Person> set2 = new TreeSet<>((o1, o2) -> -1*o1.getAge().compareTo(o2.getAge()));
//IDEA提示:non-comparable
set2.add(new Person("张三", 22, "123"));
set2.add(new Person("李四", 18, "abc"));
set2.add(new Person("王五", 20, "123456"));
}
}
建议使用构造方法;如果排序只是一种策略,就实现Comparable
接口
如果两个对象equals,那么它们的hashCode必然相等, 但是hashCode相等,equals不一定相等。
对象判断必须两个方法equals()、hashCode()返回值都相同才判断为相同
对象判断必须两个方法equals、hashCode返回值都相同才判断为相同。
5. 集合输出
5.1 迭代器Iterator
Iterator()
接口中的方法有:
① hasNext
:判断是否有下一个元素
② next
:取得下一个元素
③ remove
:删除元素
5.2 双向迭代接口
① hasPervious
:判断是否有上一个元素
② peroius
:取得上一个元素
package com.xxx;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class TestIterator {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("C++");
list.add("PHP");
list.add("Python");
list.add("JavaScript");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
if(element.startsWith("Java")) {
iterator.remove();
}
System.out.println(element);
}
/*在集合遍历的时候不能删除元素,可以选择使用迭代器删除
//ConcurrentModificationException 并发修改异常
for(String element : list) {
if(element.startsWith("Java")) {
list.remove(element);
}
}
System.out.println(list);
*/
System.out.println("原始的list:"+list);
ListIterator<String> listIterator = list.listIterator();
System.out.print("从前往后:");
while(listIterator.hasNext()) {
System.out.print(listIterator.next() + "->");
}
System.out.println();
System.out.print("从后往前:");
while(listIterator.hasPrevious()) {
System.out.print(listIterator.previous() + "<-");
}
}
}
5.3 Enumeration枚举输出
是Vector
里面的方法,建议不再使用,优先使用Iterator
。
① hasMoreElements
:判断是否有下一个元素
② nextElement
:取得下一个元素
package com.xxx;
import java.util.Enumeration;
import java.util.Vector;
public class TestEnumeration {
public static void main(String[] args) {
//属于Vector里面的方法,如果是List就用不了了
Vector<String> vector = new Vector<>();
vector.add("Java");
vector.add("C++");
vector.add("PHP");
vector.add("Python");
vector.add("JavaScript");
//建议不再使用,优先使用Iterator
Enumeration<String> enumeration = vector.elements();
while(enumeration.hasMoreElements()) {
System.out.println(enumeration.nextElement());
}
}
}