一、类集
java中类集实际上就是一种动态对象数组,在实际开发中,数组使用的几率并不高,因为数组本身有一个最大的缺陷:长度固定。从JDK1.2开始,Java为了解决这种数组长度问题,提供了动态的对象数组实现框架——Java类集框架。Java集合类框架实际上就是java针对于数据结构的一种实现。在java类集框架中,主要有以下几个接口:Collection接口、List接口、Set接口、Comparator接口。
1.Collection接口
Collection接口是单个对象保存的最顶层父接口,即Collection接口以及其子接口在每次进行数据操作时只能够对单个对象进行处理。
public interface Collection<E> extends Iterable<E>
其中Iterable<E>是迭代器接口(就是为了遍历集合),Iterable接口中只有一个抽象方法iterator()(取得集合的迭代器,JDK1.5之前直接写在Collection接口中)
Collection接口中提供的核心方法
1.add(T t):向类集中添加元素
2.iterator():取得类集的迭代器
Collection接口只定义了存储数据的标准,但是无法区分存储类型.因此在实际中我们往往使用两个子接口List(允许数据重复)和Set(不允许数据重复)一般不直接使用Collection接口。
2.List接口(使用率80%)
在进行单个集合处理时,优先考虑List接口,List接口允许数据重复
在List接口中,拓展了两个重要方法(List接口独有)
public E get(int index) : 根据索引下标取得数据
public E set(int index, E element) : 根据索引下标更新数据,返回修改前的数据
List接口有三个重要子类 :ArrayList(90%)、Vector 、LinkedList。这三个子类使用上没有任何区别。
class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
System.out.println(list);
System.out.println(list.get(1));
list.set(1,"X");
System.out.println(list);
}
}
输出
当然List接口也可以保存自定义类的对象,在List接口中还有contains(),remove() 等方法。但是使用这两个方法时会有一个问题,看下面的代码:
class Person{
private String name;
private Integer age;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class Test {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("A",10));
list.add(new Person("B",20));
list.add(new Person("C",30));
list.remove(new Person("A",10));
System.out.println(list);
}
}
当我想把姓名为A,年龄是20的对象移除,上面的代码是无法实现的,因为remove中又new了一个新的对象,jvm认为这是两个对象,那么如果我想移除姓名为A,年龄是20的对象应该怎么做呢?我们可以看一下remove方法的源码,看看是如何实现的。
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
当对象不为空时,remove方法通过equals比较了两个对象,在ArrayList中是没有重新equals方法的,那么equals方法是使用的Object类提供的equals方法。Object类提供的equals方法直接通过比较两个对象的地址进行比较。如果我们想实现上述操作需要在自定义类中重写equals方法。
@Override
public boolean equals(Object obj) {
//如果obj为空,直接返回false
if(obj == null){
return false;
}else if(!(obj instanceof Person)){
//如果obj不能向下转型为Person对象,返回空
return false;
}else {
//将obj转型为Person对象,如果姓名和年龄都相同返回true
Person p = (Person)obj;
return p.name.equals(this.name)&&p.age==this.age;
}
}
这样jvm就会认为姓名和年龄都相同的对象就是一个对象。上面代码的通过equals比较两个姓名p.name.equals(this.name),实际上String类中也重写了equals方法,感兴趣的可以去看一下。这和我们比较两个字符串相等需要用equals而不用==是同样的原理。
3.Set接口
set接口不允许数据重复,也没有set和get方法
Set接口的常用子类:
1)HashSet(无序存储)
1.底层使用哈希表+红黑树
2.允许存放空值,无序存储
2)TreeSet(有序存储)
1.底层使用红黑树
2.不允许出现空值,有序存储
3.自定义类要想保存到TreeSet中,要么实现Comparable接口,要么向TreeSet传入比较器(Comparator接口)
Comparable接口(内部比较器)---排序接口:
若一个类实现了Comparable接口,就意味着该类支持排序.
存放该类的Collection或数组,就可以直接通过Collection.sort()或Arrays.sort进行排序
实现了Comparable接口的类可以直接存放在TreeSet或TreeMap中
需要重写 compareTo方法:
public int compareTo(T o);
返回正数:表示当前对象大于目标对象
返回0:表示当前对象等于目标对象
返回负数:表示当前对象小于目标对象
Comparator接口(外部排序接口)
若要控制某个自定义类的顺序,而该类本身不支持排序(类本身没有实现Comparable接口)。,我们可以建立一个该类的"比较器"来进行排序。比较器实现Comparator接口即可,需要重写compare方法。
int compare(T o1,T o2);
返回值与compareTo一样
返回正数:o1 > o2
返回小数:o1 < o2
返回0 : o1 = o2
实现了Comparator接口进行第三方排序是一种设计模式——策略模式。此方法更加灵活,可以轻松改变策略进行第三方的排序算法.
Comparator接口与Comparable接口的关系
Comparable是排序接口,若一个类实现了Comparable接口,意味着该类支持排序,是一个内部比较器(自己去和别人比)
Comparator接口是比较器接口,类本身不支持排序,专门有若干个第三方的比较器(实现了Comparator接口的类)来进行排序,是一个外部比较器(策略模式)
HashSet使用方法比较简单,也是通过add方法来添加数据,但是无序有序是什么意思,我们通过TreeSet来看一下
class Test{
public static void main(String[] args) {
Set<String> set = new TreeSet<>();
set.add("C");
set.add("B");
set.add("A");
System.out.println(set);
}
}
输出
我们在添加时的顺序是C、B、A但是存放的顺序是ABC,原因在于String类实现了Comparable接口。
当我们想把自定义类的对象存放在TreeSet中而不实现Comparable接口或者没传入比较器时,会出现类型转换异常
简单点理解就是TreeSet中存放的对象是有序的,但是你给他一个Person对象他不知道如何排序。如果想把Person对象放到TreeSet中,那么你需要给他一个排序的依据,有两种方法:
1.实现Comparable接口,重写compareTo方法:
@Override
public int compareTo(@NotNull Object o) {
Person p = (Person) o;
if(p.age > this.age){
return -1;
}else if(p.age < this.age){
return 1;
}
return p.name.compareTo(this.name);
}
2.传入一个比较器
首先先自定义一个比较器,实现Comparator接口并重写compare方法
class comparePerson implements Comparator{
@Override
public int compare(Object o1, Object o2) {
Person p1 = (Person) o1;
Person p2 = (Person) o2;
if(p1.getAge()>p2.getAge()){
return 1;
}else if(p1.getAge()<p2.getAge()){
return -1;
}
return p1.getName().compareTo(p2.getName());
}
}
在new TreeSet<>()中传入这个比较器,即Set<Person> set = new TreeSet<>(new comparePerson());
class Person{
private String name;
private Integer age;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
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;
}
@Override
public boolean equals(Object obj) {
if(obj == null){
return false;
}else if(!(obj instanceof Person)){
return false;
}else {
Person p = (Person)obj;
return p.name.equals(this.name)&&p.age==this.age;
}
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class comparePerson implements Comparator{
@Override
public int compare(Object o1, Object o2) {
Person p1 = (Person) o1;
Person p2 = (Person) o2;
if(p1.getAge()>p2.getAge()){
return 1;
}else if(p1.getAge()<p2.getAge()){
return -1;
}
return p1.getName().compareTo(p2.getName());
}
}
class Test{
public static void main(String[] args) {
Set<Person> set = new TreeSet<>(new comparePerson());
set.add(new Person("C",10));
set.add(new Person("B",20));
set.add(new Person("A",30));
System.out.println(set);
}
}