Java集合类之Collection集合接口

1. Java类集简介

1.1、Java类集的引出
类集实际上就属于动态对象数组,在实际开发之中,数组的使用出现的几率并不高,因为数组本身有一个最大的缺陷:数组长度是固定的。由于此问题的存在,从JDK1.2开始,Java为了解决这种数组长度问题,提供了动态的对象数组实现框架–Java类集框架。Java集合类框架实际上就是java针对于数据结构的一种实现。而在数据结构之中,最为基础的就是链表。

1.2、Collection集合接口
在Java的类集里面(java.util包)提供了两个最为核心的接口:Collection、Map接口。其中Collection接口的操作形式与之前编写链表的操作形式类似,每一次进行数据操作的时候只能够对单个对象进行处理。

Collection是单个集合保存的最大父接口。
Collection接口的定义如下:

public interface Collection<E> extends Iterable<E>

从JDK1.5开始发现Collection接口上追加有泛型应用,这样的直接好处就是可以避免ClassCastException,里面的所有数据的保存类型应该是相同的。在JDK1.5之前Iterable接口中的iterator()方法是直接在Collection接口中定义的。此接口的常用方法有如下几个:
这里写图片描述
在开发之中如果按照使用频率来讲:add()、iterator()方法用到的最多。需要说明的一点是,我们很少会直接使用Collection接口,Collection接口只是一个存储数据的标准,并不能区分存储类型。例如:要存放的数据需要区分重复与不重复。在实际开发之中,往往会考虑使用Collection接口的子接口:List(允许数据重复)、Set(不允许数据重复)。
以上接口的继承、使用关系如下:
这里写图片描述
Collection接口中有两个重要方法:add()、iterator()。子接口都有这两个方法。

2. List接口

2.1、List接口概述
在实际开发之中,List接口的使用频率可以达到Collection系列的80%。在进行集合处理的时候,优先考虑List接口。
首先来观察List接口中提供的方法,在这个接口中有两个重要的扩充方法 :
这里写图片描述
List子接口与Collection接口相比最大的特点在于其有一个get()方法,可以根据索引取得内容。由于List本身还是接口,要想取得接口的实例化对象,就必须有子类,在List接口下有三个常用子类:ArrayList、Vector、LinkedList。
这里写图片描述
最终的操作还是以接口为主,所以所有的方法只参考接口中定义的方法即可。

2.1、ArrayList子类(优先考虑)
ArrayList是一个针对于List接口的数组实现。下面首先利用ArrayList进行List的基本操作。
范例:观察List基本处理:

import java.util.List;
import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
        // 此时集合中只保存String类型
        List<String> list = new ArrayList<>();
        list.add("Hello");
        // 重复数据
        list.add("Hello");
        list.add("World");
        System.out.println(list);
    }
}

通过上述代码我们可以发现,List允许保存重复数据。
观察其他操作:

import java.util.List;
import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
        // 此时集合中只保存String类型
        List<String> list = new ArrayList<>();
        System.out.println(list.size()+"、 " + list.isEmpty());
        list.add("Hello");
        // 重复数据
        list.add("Hello");
        list.add("World");
        System.out.println(list.size()+"、 " + list.isEmpty());
        System.out.println(list);
        System.out.println(list.remove("Hello"));
        // 尝试删除不存在的数据
        System.out.println(list.remove("AAA"));
        System.out.println(list.remove("World"));
        System.out.println(list);
    }
}

List本身有一个好的支持:存在get()方法,可以利用get()方法结合索引取得数据。
范例:List的get()操作:

import java.util.List;
import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
        // 此时集合中只保存String类型
        List<String> list = new ArrayList<>();
        list.add("Hello");
        // 重复数据
        list.add("Hello");
        list.add("World");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

get()方法是List子接口提供的。如果现在操作的是Collection接口,那么对于此时的数据取出只能够将集合变为对象数组操作。
范例:通过Collection进行输出处理

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

public class Test {
    public static void main(String[] args) {
        // 此时集合中只保存String类型
        Collection<String> list = new ArrayList<>();
        list.add("Hello");
        // 重复数据
        list.add("Hello");
        list.add("World");
        // 操作以Object为主,有可能需要向下转型,就有可能产生ClassCastException
        Object[] result = list.toArray();
        System.out.println(Arrays.toString(result));
    }
}

2.2、集合与简单Java类
在以后的实际开发中,集合里面保存最多的数据类型,就是简单Java类。
范例:向集合中保存简单Java类对象

import java.util.*;

class Person {
    private String name;
    private Integer age;

    public Person(String name, Integer age) {
        super();
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) obj;
        if (age == null) {
            if (other.age != null)
                return false;
        } else if (!age.equals(other.age))
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    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 class Test {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<>();
        personList.add(new Person("张三", 18));
        personList.add(new Person("李四", 19));
        personList.add(new Person("王五", 20));
        // 集合类中contains()、remove()方法需要equals()支持
        personList.remove(new Person("李四", 19));
        System.out.println(personList.contains(new Person("王五", 20)));
        for (Person person : personList) {
            System.out.println(person);
        }
    }
}

集合操作简单java类时,对于remove()、contains()方法需要equals()方法支持。
2.3、旧的子类(Vector)
Vector是从JDK1.0提出的,而ArrayList是从JDK1.2提出的。
范例:使用Vector

import java.util.List;
import java.util.Vector;

public class Test {
    public static void main(String[] args) {
        List<String> list = new Vector<>();
        list.add("Hello");
        list.add("Hello");
        list.add("World");
        System.out.println(list);
        list.remove("Hello");
        System.out.println(list);
    }
}

面试题:请解释ArrayList与Vector区别

  1. 历史时间:ArrayList是从JDK1.2提供的,而Vector是从JDK1.0就提供了。
  2. 处理形式:ArrayList是异步处理,性能更高;Vector是同步处理,性能较低。
  3. 数据安全:ArrayList是非线程安全;Vector是性能安全。
  4. 输出形式:ArrayList支持Iterator、ListIterator、foreach;Vector支持Iterator、ListIterator、foreach、Enumeration。

在以后使用的时候优先考虑ArrayList,因为其性能更高,实际开发时很多时候也是每个线程拥有自己独立的集合资源。如果需要考虑同步也可以使用concurrent包提供的工具将ArrayList变为线程安全的集合(了解)。

2.4、linkedList子类
在List接口中还有一个LinkedList子类,这个子类如果向父接口转型的话,使用形式与之前没有任何区别。
范例:使用LinkedList

import java.util.LinkedList;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        List<String> list = new LinkedList<>();
        list.add("Hello");
        list.add("Hello");
        list.add("World");
        System.out.println(list);
        list.remove("Hello");
        System.out.println(list);
    }
}

面试题:请解释ArrayList与LinkedList区别

  1. 观察ArrayList源码,可以发现ArrayList里面存放的是一个数组,如果实例化此类对象时传入了数组大
    小,则里面保存的数组就会开辟一个定长的数组,但是后面再进行数据保存的时候发现数组个数不够了
    会进行数组动态扩充。 所以在实际开发之中,使用ArrayList最好的做法就是设置初始化大小。
  2. LinkedList:是一个纯粹的链表实现,与之前编写的链表程序的实现基本一样(人家性能高)。

总结:ArrayList封装的是数组;LinkedList封装的是链表。ArrayList时间复杂度为1,而LinkedList的复杂度为n。

3. Set集合接口

Set接口与List接口最大的不同在于Set接口中的内容是不允许重复的。同时需要注意的是,Set接口并没有对Collection接口进行扩充,而List对Collection进行了扩充。因此,在Set接口中没有get()方法。
在Set子接口中有两个常用子类:HashSet(无序存储)、TreeSet(有序存储)

3.1、 Set接口常用子类
这里写图片描述

范例:观察HashSet的使用

import java.util.HashSet;
import java.util.Set;

public class Test {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        set.add("Hello");
        // 重复元素
        set.add("Hello");
        set.add("World");
        System.out.println(set);
    }
}

范例:观察TreeSet的使用

import java.util.Set;
import java.util.TreeSet;

public class Test {
    public static void main(String[] args) {
        Set<String> set = new TreeSet<>();
        set.add("D");
        // 重复元素
        set.add("D");
        set.add("E");
        set.add("A");
        set.add("C");
        set.add("B");
        System.out.println(set);
    }
}

TreeSet使用的是升序排列的模式完成的。

3.2、 TreeSet排序分析
既然TreeSet子类可以进行排序,所以我们可以利用TreeSet实现数据的排列处理操作。此时要想进行排序实际上是针对于对象数组进行的排序处理,而如果要进行对象数组的排序,对象所在的类一定要实现Comparable接口并且覆写compareTo()方法,只有通过此方法才能知道大小关系。

需要提醒的是如果现在试用Comparable接口进行大小关系匹配,所有属性必须全部进行比较操作。

范例:使用TreeSet排列

import java.util.Set;
import java.util.TreeSet;

class Person implements Comparable<Person> {
    private String name;
    private Integer age;

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }

    @Override
    public int compareTo(Person o) {
        if (this.age > o.age) {
            return 1;
        } else if (this.age < o.age) {
            return -1;
        } else {
            return this.name.compareTo(o.name);
        }
    }

    public Person(String name, Integer age) {
        super();
        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;
    }
}

public class Test {
    public static void main(String[] args) {
        Set<Person> set = new TreeSet<>();
        set.add(new Person("张三", 19));
        // 重复元素
        set.add(new Person("张三", 19));
        set.add(new Person("李四", 20));
        set.add(new Person("王五", 18));
        System.out.println(set);
    }
}

在实际使用之中,使用TreeSet过于麻烦了。项目开发之中,简单java类是根据数据表设计得来的,如果一个类的属性很多,那么比较起来就很麻烦了。所以我们一般使用的是HashSet。

3.3、 重复元素判断
在使用TreeSet子类进行数据保存的时候,重复元素的判断依靠的ComParable接口完成的。但是这并不是全部Set接口判断重复元素的方式,因为如果使用的是HashSet子类,由于其跟Comparable没有任何关系,所以它判断重复元素的方式依靠的是Object类中的两个方法:

  1. hash码: public native int hashCode();
  2. 对象比较:public boolean equals(Object obj);

在Java中进行对象比较的操作有两步:第一步要通过一个对象的唯一编码找到一个对象的信息,当编码匹配之后再调用equals()方法进行内容的比较。

范例:覆写hashCode()与equals()方法消除重复

import java.util.HashSet;
import java.util.Set;
import java.util.Objects;

class Person implements Comparable<Person> {
    private String name;
    private Integer age;

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) obj;
        if (age == null) {
            if (other.age != null)
                return false;
        } else if (!age.equals(other.age))
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }


    @Override
    public int compareTo(Person o) {
        if (this.age > o.age) {
            return 1;
        } else if (this.age < o.age) {
            return -1;
        } else {
            return this.name.compareTo(o.name);
        }
    }

    public Person(String name, Integer age) {
        super();
        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;
    }
}

public class Test {
    public static void main(String[] args) {
        Set<Person> set = new HashSet<>();
        set.add(new Person("张三", 19));
        // 重复元素
        set.add(new Person("张三", 19));
        set.add(new Person("李四", 20));
        set.add(new Person("王五", 18));
        System.out.println(set);
    }
}

如果要想标识出对象的唯一性,一定需要equals()与hashCode()方法共同调用。
面试题:如果两个对象的hashCode()相同,equals()不同结果是什么?不能消除
面试题:如果两个对象的hashCode()不同,equals()相同结果是什么?不能消除
对象判断必须两个方法equals()、hashCode()返回值都相同才判断为相同。

猜你喜欢

转载自blog.csdn.net/yubujian_l/article/details/80459889