一篇吃透Java集合

集合

1.Collection接口

来看一张Collectiong的结构图:
[外链图片转存失败(img-qgQLaxne-1563270831936)(C:\Users\Laptop\AppData\Local\Temp\1563270794413.png)]

public interface Collection<E> extends Iterable<E>

在JDK1.5之前Iterable接口中的iterator()方法是直接在Collection接口中定义的

collection接口中定义了大量的方法:(摘自API)

    • booleanadd(E e) 确保此集合包含指定的元素(可选操作)。
      booleanaddAll(Collection<? extends E> c) 将指定集合中的所有元素添加到此集合(可选操作)。
      voidclear() 从此集合中删除所有元素(可选操作)。
      booleancontains(Object o) 如果此集合包含指定的元素,则返回 true
      booleancontainsAll(Collection<?> c) 如果此集合包含指定 集合中的所有元素,则返回true。
      booleanequals(Object o) 将指定的对象与此集合进行比较以获得相等性。
      inthashCode() 返回此集合的哈希码值。
      booleanisEmpty() 如果此集合不包含元素,则返回 true
      Iterator<E>iterator() 返回此集合中的元素的迭代器。
      default Stream<E>parallelStream() 返回可能并行的 Stream与此集合作为其来源。
      booleanremove(Object o) 从该集合中删除指定元素的单个实例(如果存在)(可选操作)。
      booleanremoveAll(Collection<?> c) 删除指定集合中包含的所有此集合的元素(可选操作)。
      default booleanremoveIf(Predicate<? super E> filter) 删除满足给定谓词的此集合的所有元素。
      booleanretainAll(Collection<?> c) 仅保留此集合中包含在指定集合中的元素(可选操作)。
      intsize() 返回此集合中的元素数。
      default Spliterator<E>spliterator() 创建一个Spliterator在这个集合中的元素。
      default Stream<E>stream() 返回以此集合作为源的顺序 Stream
      Object[]toArray() 返回一个包含此集合中所有元素的数组。
      <T> T[]toArray(T[] a) 返回包含此集合中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型。

1.Collecttion接口的子接口-------List接口(允许保存重复数据)(⭐)

List接口在Collection接口中扩充了两个重要方法(⭐)

E get(int index);
E set(int index, E element);
1 .List接口的子类-------------ArrayList

这些子类所使用的方法基本都是父接口的,所以我们更加关心它们的父接口所实现的方法;

ArrayList是一个针对于List接口的数组实现。 看下面简单实例:

        //面向接口编程⭐
        //ArrayList有三个构造方法,第一个无参,初始化容量为:10⭐
        // 第二个有参  int类型,表示初始化容量
        //第三个有参,为一个Collection
        List<Integer> list = new ArrayList<>();   //面向接口编程的写法
        list.add(2);
        list.add(3);
        list.add(1,10);
        List<Integer> list1 = new ArrayList<>();
        list1.add(9);
        list.addAll(list1);
        System.out.println(list);
        System.out.println("是否为空:"+list.isEmpty());
        System.out.println(list.toArray());   //[Ljava.lang.Object;@154617c
        System.out.println("获取下标为一的元素:"+list.get(1));  //10,下标是从0开始的哟
        System.out.println("修改下标为2的元素:"+list.set(2,40));  //返回的是被改前的值

[2, 10, 3, 3, 9]
是否为空:false
[Ljava.lang.Object;@154617c
获取下标为一的元素:10
修改下标为2的元素:3
[2, 10, 40, 3, 9]

2. List接口的子类-------------Vector (老版)

Vector在JDK1就提出来了,而ArrayList在JDK2提出来的;

其实Vector和ArrayList并没有本质上的差别,只是在线程安全和性能方面有差距,现在基本都优先使用ArrayList;

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

Enumeration。

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

3.集合与简单Java类
class Person {
    private String name;
    private int age;
    public Person() {

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {

        return Objects.hash(name, age);
    }
}
public class TestArrayList {
  
    public static void main(String[] args) {
        List<Person> list = new ArrayList<>();
        Person per = new Person();
        per.setAge(10);
        list.add(per);
        list.add(new Person("yy",18));
        list.add(new Person("yyg",20));
        list.add(new Person("ygz",25));
        System.out.println(list.contains(per));  //true
        //未在Person类中覆写equals和hashCode方法时,下面输出false
        System.out.println(list.contains(new Person("yy",18)));
    }
}

从上面代码可以看到,自己写的类型要覆写equals和hashcode方法,使用contain方法才能行
Integer啥的JDK自带的类型都已经覆写了,不需要考虑;

总结:集合操作简单java类时,对于remove()、contains()方法需要equals()方法支持。

4.List接口的子类-------------LinkedList (面试题:ArrayList、LinkedList区别)

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

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

2.Collecttion接口的子接口-------Set集合接口(不允许保存重复数据)(⭐)

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

1.Set接口的子类-------------HashSet(无序存储)
        Set<String> set = new HashSet<>();
        set.add("C++");
        set.add("Java");
        set.add("PHP");
        set.add("Java");  //不会报错,但size还是三
        System.out.println(set);
        System.out.println(set.size());   //  3
        System.out.println("是否存在Java:"+set.contains("Java"));   //true

输出:
[Java, C++, PHP]
3
是否存在Java:true

2.Set接口的子类-------------TreeSet(有序存储)

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

class Person implements Comparable{
    private String name;
    private int age;
    public Person() {

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {

        return Objects.hash(name, age);
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class TestArrayList {

    public static void main(String[] args) {
        Set<Person> set = new HashSet<>();
        set.add(new Person("A",10));
        set.add(new Person("B",5));
        set.add(new Person("D",20));
        System.out.println(set);

    }
}

输出:
[Person{name=‘D’, age=20}, Person{name=‘B’, age=5}, Person{name=‘A’, age=10}]

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

3.Comparable接口和Comparator接口(实现对象的比较)

对于比较大小的概念,我们之前都是比较的一些已有的数据类型,比如Integer数据类型、String数据类型等等…
那么有没有思考它们是如何比较的呢,遇到我们自定义的对象又该如何进行比较呢?

我们可以查看Integer的源码:

public final class Integer extends Number implements Comparable<Integer>

可以看到这里实现了Comparable类,这是一个内部排序接口,实现这个结构的类必须覆写compareTo这个方法,这个方法用来定义你要比较的规则,String等等其他类也都实现了Comparable接口,所以对于那些常用的基本数据类型,都可以用 .compareTo方法来直接进行比较;

1.Comparable接口(内部排序接口)是一个排序接口

源码:

public interface Comparable<T> {
    public int compareTo(T o);
}

关于返回值:
可以看出compareTo方法返回一个int值,该值有三种返回值:

  1. 返回负数:表示当前对象小于比较对象
  2. 返回0:表示当前对象等于目标对象
  3. 返回正数:表示当前对象大于目标对象

若一个类实现了Comparable接口,就意味着“该类支持排序”。 即然实现Comparable接口的类支持排序,假设现在存在“实现Comparable接口的类的对象的List列表(或数组)”,则该List列表(或数组)可以通过Collections.sort(或
Arrays.sort)进行排序。此外,“实现Comparable接口的类的对象”可以用作“有序映射(如TreeMap)”中的键或“有序集合(TreeSet)”中的元素,而不需要指定比较器。

实现自己的内部比较器:

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

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

    public int getAge() {
        return age;
    }

    @Override
    public int compareTo(Person o) {
        return this.getAge() - o.getAge();
    }
}
public class CompareDemo {
    public static void main(String[] args) {
        Person per1 = new Person("a",12);
        Person per2 = new Person("b", 23);
        System.out.println(per1.compareTo(per2));   //返回一个小于0的数
    }
}
2.Comparator接口(外部排序接口)是一个比较器接口

实现两个对象的比较还有一种方法,那就是实现外部比较

一般情况下,实现外部比较更为常用 ,因为实现内部比较只能实现一种方式的比较,对于上面所举的例子,假如你又要以姓名为标准来评定比较规则,那么你得修改comparable里面的代码,这样修改之后之前的比较结果就出现不同了;

实例:(外部比较可以实现多个比较标准)

class Person {
    private String name;
    private Integer age;

    public Person(String name, int 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;
    }

}
//用年龄来判定大小
class PersonCompareAge implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        return o1.getAge().compareTo(o2.getAge());   //Integer类型本来就实现了内部排序,这里也可以写成:o1.getAge - o2.getAge();
    }
}
//用姓名来比较大小
class PersonCompareName implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        return    o1.getName().compareTo(o2.getName());

    }
}
public class CompareDemo {
    public static void main(String[] args) {
        Person per1 = new Person("Alice",12);
        Person per2 = new Person("Bob", 23);
        PersonCompareAge personCompareAge = new PersonCompareAge();
        PersonCompareName personCompareName = new PersonCompareName();
        System.out.println(personCompareAge.compare(per1, per2));  //-1
        System.out.println(personCompareName.compare(per1, per2));   //-1
    }
}

2.集合输出(优先使用Iterator)

注意:

在集合遍历的时候,不要修改元素 (⭐),比如在你用Iterator遍历元素的时候,用remove方法删除元素时,会产生ConcurrentModificationException异常(并发修改异常)

分析:
逻辑上讲,迭代时可以添加元素,但是一旦开放这个功能,很有可能造成很多意想不到的情况。 比如你在迭代一个ArrayList,迭代器的工作方式是依次返回给你第0个元素,第1个元素,等等,假设当你迭代到第5个元素的时候,你突然在ArrayList的头部插入了一个元素,使得你所有的元素都往后移动,于是你当前访问的第5个元素就会被重复访问。 java认为在迭代过程中,容器应当保持不变。因此,java容器中通常保留了一个域称为modCount,每次你对容器修改,这个值就会加1。当你调用iterator方法时,返回的迭代器会记住当前的modCount,随后迭代过程中会检查这个值,一旦发现这个值发生变化,就说明你对容器做了修改,就会抛异常。

1.迭代输出:Iterator接口 (⭐)

在JDK1.5之前,在Collection接口中就定义有iterator()方法,通过此方法可以取得Iterator接口的实例化对象;而在JDK1.5之后,将此方法提升为Iterable接口中的方法 。无论你如何提升,只要Collection有这个方法,那么List、Set也一定有此方法。
对于Iterator接口最初的设计里面实际有三个抽象方法:

  1. 判断是否有下一个元素: public boolean hasNext();
  2. 取得当前元素: public E next();
  3. 删除元素: public default void remove(); 此方法从JDK1.8开始变为default完整方法。
        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("C++");
        list.add("PHP");
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()) {
            String str = iterator.next();
            System.out.println(str);
        }

2.双向迭代输出:ListIterator接口

Iterator接口只能由前往后遍历,而ListIterator可以双向遍历
注意:要从后往前遍历,必须先从前往后遍历⭐

listIterator是Iterator的子接口 ,此接口定义的方法有:

  1. 判断是否有上一个元素:public boolean hasPrevious();
  2. 取得上一个元素:public E previous();

Iterator接口对象是由Collection接口支持的,但是ListIterator是由List接口支持的,List接口提供有如下方法:
取得ListIterator接口对象:public ListIterator listIterator();

用法:

        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("C++");
        list.add("PHP");
        list.add("JS");
        ListIterator<String> listIterator = list.listIterator();
        System.out.println("从前往后:");
        while(listIterator.hasNext()) {
            System.out.println(listIterator.next());
        }
        //从后往前的前提是必须先从前往后遍历一遍⭐
        System.out.println("从后往前:");
        while(listIterator.hasPrevious()) {
            System.out.println(listIterator.previous());
        }

3.foreach输出

这里就不再多说了,jdk1.5后foreach就可以输出数组和集合;

        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("C++");
        list.add("PHP");
        list.add("JS");
        ListIterator<String> listIterator = list.listIterator();
        for(String s:list) {
            System.out.println(s);
        }

3.Map接口

先看一下这个接口提供的比较重要的方法:

序号方法类型描述
1V put(K key,V value)普通向Map中添加数据
2V get(Object key)普通根据key获取对应的value,如果没有返回null
3Set KeySet()普通取得所有key信息,key不能重复
4Collection values()普通取得所有value信息,可以重复
5Set<Map.Entry<K, V>> entrySet()普通将Map集合变为Set集合

Map本身是一个接口,要使用Map需要通过子类进行对象实例化。Map接口的常用子类有如下四个: HashMap、
Hashtable、TreeMap、ConcurrentHashMap。

1.Map的最常用子类-----------HashMap(⭐)

例码:

        Map<Integer,String> map = new HashMap<>();
        map.put(1,"C++");
        map.put(2,"Java");
        map.put(3,"PHP");
        map.put(2,"JS");
        //由于key不能重复,所以size还是为3
        System.out.println("当前size为:"+map.size());   // 3
        System.out.println("key为2的元素值为:"+map.get(2));   //JS,以后面添加的为准

        /**
         * 取得Map中的所有key信息 ⭐⭐
         */
        Set<Integer> set = map.keySet();
        Iterator iterator = set.iterator();
        while(iterator.hasNext()) {
            System.out.println(iterator.next());
        }
重点:HashMap源码分析

先看看HashMap的定义:

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

这里有我写的单独的一篇博客来分析HashMap源码:
https://blog.youkuaiyun.com/eternal_yangyun/article/details/89278871

2.HashTable

JDK1.0提供有三大主要类:Vector、Enumeration、Hashtable。Hashtable是最早实现这种二元偶对象数据结构,后期的设计也让其与Vector一样多实现了Map接口而已

HashTable用法与HashMap差异不大,所以这里不再讲解,这里着重看一下他们的区别;

1.HashMap与HashTable的区别(⭐)
NO区别HashMapHashTable
1版本jdk1.2jdk1.0
2性能异步处理,性能高同步处理,性能低
3安全性非线程安全线程安全
4null操作允许存放null,但只能有一个key与value都不允许为空,否则抛出空指针异常

3.HashMap的输出

HashMap不直接支持迭代器Iterator的输出,但可以将HashMap转化为Set集合再调用迭代器输出;

        Map<Integer,String> map = new HashMap<>();
        map.put(1,"java");
        map.put(2,"c++");
        map.put(3,"PHP");
        System.out.println("Map的数据:"+map);
        System.out.println(map.get(1));

        System.out.println("-------------------");
        System.out.println("利用foreach输出:");
        //1.key -->value
        //2.values(无法获取到key)
        //3.entryset
        for(Integer k:map.keySet()) {
            System.out.printf("%d = %s \n",k,map.get(k));
        }

        System.out.println("-------------------");
        System.out.println("利用迭代器输出:");
        Set<Map.Entry<Integer,String>> set = map.entrySet();
        Iterator<Map.Entry<Integer,String>> iterator = set.iterator();
        while(iterator.hasNext()) {
            Map.Entry<Integer,String> entry = iterator.next();
            Integer key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key+" = "+value);

            //下面这将会错误输出,因为包含了两个next调用了
//            Integer key = iterator.next().getKey();
//            String value = iterator.next().getValue();
//            System.out.println(key+"="+value);
        }

4.ConcurrentHashMap 子类

前面已经了解过HashTable和HashMap这两个子类,他们最大的区别就是线程安全与否,需要线程安全就用HashTable,否则就使用HashMap,那么我们为什么还要实现ConcurrentHashMap这个类呢?
前面也提到,HashTable实现线程安全的办法是直接在需要同步的地方加上synchronized关键字来实现同步 ,而我们学过多线程的知道,synchronized同步的实现,在一个线程进行访问的时候,其他线程只能够等待这个线程,所以效率十分低下, 而且我们只需要在修改值得时候同步,在取值的时候并不需要同步,HashTable却是在各种方法都加上了synchronized关键字;

5.自定义对象作为Map集合的key

其实这个跟在List集合中用自定义对象作为元素的道理是一样的,这里就不再多多叙述,总之就一句话,自定义的key需要覆写equals方法和hashcode方法

4.栈和队列

1.栈----Stack

栈是一种常见的数据结构,它最大的特点就是先进后出,在Java中,实现了这样一种集合来实现这一数据结构

先来看它的定义:(Stack类在jdk1就有了)

public
class Stack<E> extends Vector<E>

可以看到Stack类继承了Vector类,但值得我们注意的是,对栈的操作(push、pop、peek等)都是在Stack类里直接实现的方法,而不是用的Vector里面的方法,Vector只是用来作为栈的存储结构,因为栈其实本来就是个数组;

简单应用:

        Stack<Integer> stack = new Stack<>();
        stack.push(3);
        stack.push(4);
        System.out.println(stack.peek());  //4
        System.out.println(stack.pop());    //4

2.队列----Queue

队列的最大特点就是先进先出,他在我们实际开发中经常用到

先来看它的定义:

public interface Queue<E> extends Collection<E>

可以看到,队列Queue是一个接口,上面的Stack是一个类,Queue接口继承了Collection接口;
继承Queue接口的有这样一个接口Deque,而LinkedList类又实现了Deque这个接口,所以LinkedList间接实现了Queue ,所以实现一个队列需要LinkedList来实现;

简单应用:

        Queue<Integer> queue = new LinkedList<>();
        queue.add(3);
        queue.add(4);
        queue.add(5);
        System.out.println(queue.poll());  //3
        System.out.println(queue.poll());  //4
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值