集合与泛型

集合框架

概述

集合是JAVA中提供的一种容器,用来存储多个数据。

前面学习过数组的知识,数组也是一种数据的容器,但是数组使用起来非常不方便,区别:

  • 数组特点:类型固定,长度固定
  • 集合特点:类型不固定,长度也不固定,随意存放任何数据

JAVA中提供了很多不同的集合,在不同的场景下选择合适的集合,这些众多的集合称为集合框架。

集合框架中大概分为两大类,分别是单列集合java.util.Collection和双列集合java.util.Map,集合框架都位于java.util包中

使用比较多的有接口有List、Set、Map接口,List,Set都是继承自Collection接口,Map则不是。

  • List接口:元素有放入顺序,元素可重复 。和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。
  • Set接口:元素无放入顺序,元素不可重复,重复元素会覆盖掉。检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
  • Map接口:Map用于保存具有映射关系的数据,Map里保存着两组数据:key和value,它们都可以使任何引用类型的数据,但key不能重复。所以通过指定的key就可以取出对应的value。Map 没有继承 Collection 接口。

ArrayList类

概述

        我们先学习单列集合中的List接口跟实现类ArrayList,这个是开发过程使用得最多最频繁的一个集合。ArrayList的底层结构是一个数组实现,并提供了非常丰富使用的API供使用。

        ArrayList实现了长度可变的数组,所有的元素是以一种线性方式进行存储的,在内存中分配连续的空间,遍历元素和随机访问元素的效率比较高。

常用API

  • public boolean add(E e): 把给定的对象添加到当前集合中 。
  • public E get(int index): 返回此列表中指定位置上的元素。
  • public void clear() : 清空集合中所有的元素。
  • public boolean remove(E e): 把给定的对象在当前集合中删除。
  • public boolean contains(E e): 判断当前集合中是否包含给定的对象。
  • public boolean isEmpty(): 判断当前集合是否为空。
  • public int size(): 返回集合中元素的个数。
  • public Object[] toArray(): 把集合中的元素,存储到数组中。

代码演示

public static void main(String[] args) {
//        创建集合容器
        List<String> list = new ArrayList<>();
        list.add("关羽");// 添加元素关羽,索引为0
        list.add("张飞");// 添加元素张飞,索引为1
        list.add("刘备");// 添加元素刘备,索引为2
        list.add("赵云");// 添加元素赵云,索引为3
        list.add("诸葛亮");// 添加元素诸葛亮,索引为4
​
        System.out.println(list);// 打印集合信息
        
        System.out.println(list.get(2));// 根据索引查找对应的元素
​
        System.out.println(list.size());// 查看集合中一共有多少个元素
​
        System.out.println(list.contains("赵云")); // 判断赵云是否存在集合中
​
        list.remove("诸葛亮");// 删除诸葛亮
​
        //Object[] toArray()转换成一个Object数组
        Object[] objects = list.toArray();
        // 遍历数组
        for (int i = 0; i < objects.length; i++) {
            System.out.println(objects[i]);
        }
​
        list.clear();// 清空集合
        System.out.println(list.isEmpty());// boolean  isEmpty()  判断是否为空
​
    }

tips: java.util.Vector集合用法同java.util.ArrayList,区别在于

java.util.Vector是线程安全的,而java.util.ArrayList是线程非安全的。

遍历

我们经常需要遍历集合,那么如果遍历ArrayList中的所有元素呢?

方式1

public static void main(String[] args) {
//        创建集合容器
        List<String> list = new ArrayList<>();
        list.add("关羽");// 添加元素关羽,索引为0
        list.add("张飞");// 添加元素张飞,索引为1
        list.add("刘备");// 添加元素刘备,索引为2
        list.add("赵云");// 添加元素赵云,索引为3
        list.add("诸葛亮");// 添加元素诸葛亮,索引为4
​
//        遍历方式1
        for (int i=0;i<list.size();i++){
            System.out.println(list.get(i));
        } 
​
    }

方式2

public static void main(String[] args) {
//        创建集合容器
        List<String> list = new ArrayList<>();
        list.add("关羽");// 添加元素关羽,索引为0
        list.add("张飞");// 添加元素张飞,索引为1
        list.add("刘备");// 添加元素刘备,索引为2
        list.add("赵云");// 添加元素赵云,索引为3
        list.add("诸葛亮");// 添加元素诸葛亮,索引为4
 
//        遍历方式2
        for (String name : list){
            System.out.println(name);
        }
​
    }

方式3

此外,我们还可以通过迭代器的方式来遍历,后面专门章节来讲解迭代器的用法。

LinkedList类

概述

        LinkedList也是List接口的实现类,所以List接口中定义的方法在LinkedList中也可以使用。

LinkedList底层的存储结构是一个链表,在元素的前后分别有一个前置结点和后置结点,用于连接集合中的上一个元素和下一个元素,依次“手拉手”,构成一条链式数据的集合。

        LinkedList集合的元素在内存中并不连续,从图中我们可以发现LinkedList对于添加、删除元素效率比较高,对于查找跟修改效率比较低。

常用API

  • public void addFirst(E e):将指定元素插入此列表的开头。
  • public void addLast(E e):将指定元素添加到此列表的结尾。
  • public E getFirst():返回此列表的第一个元素。
  • public E getLast():返回此列表的最后一个元素。
  • public E removeFirst():移除并返回此列表的第一个元素。
  • public E removeLast():移除并返回此列表的最后一个元素。
  • public E pop():从此列表所表示的堆栈处弹出一个元素。此方法等效于 removeFirst()。
  • public void push(E e):将元素推入此列表所表示的堆栈。此方法等效于 addFirst(E)。
  • public boolean isEmpty():如果列表不包含元素,则返回true。

代码演示

public static void main(String[] args) {
        LinkedList<String> link = new LinkedList<String>();
        //添加元素
        link.addFirst("关羽");
        link.addFirst("张飞");
        link.addFirst("刘备");
        System.out.println(link);
​
        System.out.println(link.getFirst()); // 获取第1个元素
        System.out.println(link.getLast()); // 获取最后1个元素
​
        System.out.println(link.removeFirst());// 删除第1个元素
        System.out.println(link.removeLast());// 删除最后1个元素
​
        while (!link.isEmpty()) { //判断集合是否为空
            System.out.println(link.pop()); //弹出集合中的栈顶元素
        }
​
        System.out.println(link);
    }

遍历

方式1

public static void main(String[] args) {
    LinkedList<String> link = new LinkedList<String>();
    //添加元素
    link.addFirst("关羽");
    link.addFirst("张飞");
    link.addFirst("刘备");
​
    for (int i=0; i<link.size(); i++) {
        System.out.println(link.get(i));
    }
}

方式2

public static void main(String[] args) {
        LinkedList<String> link = new LinkedList<String>();
        //添加元素
        link.addFirst("关羽");
        link.addFirst("张飞");
        link.addFirst("刘备");
​
        for (String s : link) {
            System.out.println(s);
        }
}

方式3

此外,我们还可以通过迭代器的方式来遍历,后面专门章节来讲解迭代器的用法。

HashSet类

概述

        java.util.Set接口和java.util.List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。

        Set集合有多个子类,这里我们介绍其中的java.util.HashSet、java.util.TreeSet这两个集合。

        java.util.HashSet是Set接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。java.util.HashSet底层的实现其实是一个java.util.HashMap。

常用API

  • public boolean add(E e): 如果此 set 中尚未包含指定元素,则添加指定元素 。
  • public boolean contains(Object o): 如果此 set 包含指定元素,则返回 true。
  • public void clear(): 从此 set 中移除所有元素。此调用返回后,该 set 将为空。
  • public boolean remove(Object o): 如果指定元素存在于此 set 中,则将其移除。
  • public boolean isEmpty(): 如果此 set 不包含任何元素,则返回 true。
  • public int size(): 返回此 set 中的元素的数量(set 的容量)。
  • public Iterator iterator(): 返回对此 set 中元素进行迭代的迭代器。返回元素的顺序并不是特定的。

代码演示

public static void main(String[] args) {
//        创建集合容器
        Set<String> set = new HashSet<>();
        set.add("关羽");
        set.add("张飞");
        set.add("刘备");
        set.add("赵云");
        set.add("诸葛亮");
​
        System.out.println(set);// 打印集合信息
​
        System.out.println(set.size());// 查看集合中一共有多少个元素
​
        System.out.println(set.contains("赵云")); // 判断赵云是否存在集合中
​
        set.remove("诸葛亮");// 删除诸葛亮
​
​
        set.clear();// 清空集合
        System.out.println(set.isEmpty());// boolean  isEmpty()  判断是否为空
​
    }

遍历

方式1

public static void main(String[] args) {
        LinkedList<String> link = new LinkedList<String>();
        //添加元素
        link.addFirst("关羽");
        link.addFirst("张飞");
        link.addFirst("刘备");
​
        for (String s : link) {
            System.out.println(s);
        }
}

方式2

此外,我们还可以通过迭代器的方式来遍历,后面专门章节来讲解迭代器的用法。

TreeSet类

        TreeSet同样也是Set接口的一个实现类TreeSet是一个有序集合,底层结构为红黑树。会根据自然排序排列或比较器进行排序,且没有重复元素,也没有下标。通过TreeMap实现。

        TreeSet由于底层结构是红黑树,红黑树满足2叉查找树的特性,故在TreeSet中的元素需要满足具有可比性的特性,可比性需要内部比较器或者外部比较器来实现,否则会抛出java.lang.ClassCastException。

代码演示

        创建TreeSet容器,里面元素使用自定义Person类,拥有name、age属性,按照age的大小进行比较。

import java.util.TreeSet;
​
public class Demo05 {
​
    public static void main(String[] args) {
        TreeSet<Person> treeSet = new TreeSet<>();
        treeSet.add(new Person("刘备",33));
        treeSet.add(new Person("关羽",23));
        treeSet.add(new Person("张飞",27));
    }
​
}
​
class Person implements  Comparable<Person>{
​
    private String name ;
    private int 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 int getAge() {
        return age;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
​
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
​
    @Override
    public int compareTo(Person person) {
        return this.age - person.age;
    }
}

迭代器

Iterator接口

        在开发过程中,经常需要遍历集合中的所有元素。JDK专门提供了一个接口java.util.Iterator。它与Collection、Map接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。

想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:

  • public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的。
  • 迭代:简而言之,就是使用循环从头开始遍历所有元素。迭代的方法为在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。

Iterator接口的常用方法如下:

  • public E next():返回迭代的下一个元素。
  • public boolean hasNext():如果仍有元素可以迭代,则返回 true。

 代码演示

public static void main(String[] args) {
    //        准备集合跟元素
    List<String> list = new ArrayList<>();
    list.add("关羽");
    list.add("张飞");
    list.add("刘备");
    list.add("赵云");
    list.add("诸葛亮");
​
    //        获取集合对应的迭代器
    Iterator<String> iterator = list.iterator();
    //        每次判断是否有下一个元素,如果有返回true,否则返回false
    while (iterator.hasNext()){
        String hero = iterator.next();
        System.out.println(hero);
    }
​
}

实现原理

        我们在之前案例已经完成了Iterator遍历集合的整个过程。当遍历集合时,首先通过调用t集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。

        Iterator是使用索引指针完成遍历过程的。在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。

        Iterator是快速失败的,意思就是在获取迭代器后,遍历结束之前,不能修改集合的数据结构。否则会出现ConcurrentModificationException异常。如果在迭代过程中需要删除元素的话,只能通过Iterator的void remove()方法实现,而不能通过集合的remove方法。

代码演示

public static void main(String[] args) {
    //        准备集合跟元素
    List<String> list = new ArrayList<>();
    list.add("关羽");
    list.add("张飞");
    list.add("刘备");
    list.add("赵云");
    list.add("诸葛亮");
​
    //        获取集合对应的迭代器
    Iterator<String> iterator = list.iterator();
    //      获取迭代器之后修改了集合结构,会异常
    //        list.add("黄忠");
​
    //        每次判断是否有下一个元素,如果有返回true,否则返回false
    while (iterator.hasNext()){
        String hero = iterator.next();
        System.out.println(hero);
        //            遍历过程中修改了集合的结构,会异常
        //            list.remove(hero);
    }
​
}

tips:增强for循环的底层就是通过迭代器实现的,所以使用这种方式的时候,也同样是快速失败的。

总结

集合

问题: 数组长度固定,在很多场景不方便使用。因此有了集合。

特点: 数组:数据类型固定,长度固定

集合:数据类型不固定,长度不固定

集合不是某一个类,而是一大的体系,通常称为集合框架。

Collection(单列集合)

        List:有序可重复的集合

                ArrayList:线程不安全的,底层也是基于数组,特点是:查询、修改快,添加、删除相对LinkedList来说要慢。

                        扩容机制:原长度的1.5倍,如果待添加的数据比扩容长度要大,就使用待添加数据长度,如果超过int类型最大长度-8,则使用int类型最大长度

                LinkedList:线程不安全,底层是双向链表,特点是:添加、删除快,查询和修改相对ArrayList来说要慢。

                Vector:线程安全的,底层也是基于数组,相对于ArrayList和LinkedList都要慢。

        Set:无序不可重复集合

                HashSet:底层使用是HashMap。(无序不可重复)

                TreeSet:底层使用是TreeMap。(有序不可重复)

                判定对象是否重复是依据对象的hashcode和equals方法

Map(双列集合)

HashMap

ArrayList和LinkedList的区别

  1. ArrayList基于数组,LinkedList基于链表
  2. ArrayList查询快,修改快(不改变结构)
  3. LinkedList添加快,删除快

hashcode

规则1:同一对象在程序同一执行时间内无论调用多少次,都返回相同的hashcode值

规则2:两对象equals方法相等,hashcode必须也相同。

规则3:两对象equals方法不相等,hashcode也可能相等。

哈希冲突:两不同对象的hashcode值相同即为哈希冲突,一般在使用hashmap时会出现hash冲突问题。

1、重写equals方法,需要重写hashcode方法吗?

需要

2、重写hashcode方法,需要重写equals方法吗?

不需要

Map接口

概述

        前面学习的List跟Set主要用来存储单个实体的元素,但在生活中,很多时候我们会遇到这种集合:老公跟老婆、个人与身份证号等成对的数据组合,显然用List跟Set就显得力不从心了。对于这种数据就可以使用Map来完成。

        Map中的集合,元素是成对存在的。每个元素由键与值两部分组成,通过键可以找对所对应的值。成为双列集合。其中键(Key)不能重复,值(Value)可以重复,每个键对应一个值。简称键值(K-V)对。

        Map常用子类有HashMap、TreeMap。

常见API

Map接口中定义了很多方法,常用的如下:

  • public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。
  • public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
  • public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
  • boolean containsKey(Object key)判断集合中是否包含指定的键。
  • public Set keySet(): 获取Map集合中所有的键,存储到Set集合中。
  • public Set> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。

HashMap类

API的使用

        HashMap是使用最频繁的一个Map接口的子类。底层结构在JDK1.8主要采用数组+链表+红黑树实现。

我们看下HashMap的基本用法:

//三国时期,刘备派关羽守樊城,张飞守新野,赵子龙守徐州,他自己坐镇荆州。
public static void main(String[] args) { 
        //创建 map对象
        HashMap<String, String> map = new HashMap<String, String>();
​
        //添加元素到集合
        map.put("关羽", "樊城");
        map.put("张飞", "新野");
        map.put("赵子龙", "徐州");
        map.put("刘备", "荆州");
        System.out.println(map);
​
//        删除赵子龙
        System.out.println(map.remove("赵子龙"));
        System.out.println(map);
​
        // 想要查看刘备守哪座城?
        System.out.println(map.get("刘备"));
    }

那如果存储的键改为自定义类呢?

代码演示

三国时期,刘备派关羽守樊城,张飞守新野,赵子龙守徐州,他自己坐镇荆州。使用HashMap来实现,并打印输出,张飞守的是那座城?

public class Demo08HashMap {
​
    public static void main(String[] args) {
        //创建 map对象
        HashMap<Hero, String> map = new HashMap<Hero, String>();
​
        //添加元素到集合
        map.put(new Hero("关羽"), "樊城");
        map.put(new Hero("张飞"), "新野");
        map.put(new Hero("赵子龙"), "徐州");
        map.put(new Hero("刘备"), "荆州");
        map.put(new Hero("刘备"), "荆州");
​
        System.out.println(map);
// {Hero{name='张飞'}=新野, Hero{name='刘备'}=荆州, Hero{name='刘备'}=荆州, Hero{name='关羽'}=樊城, Hero{name='赵子龙'}=徐州}
    }
}
​
class Hero{
​
    private String name ;
​
    public Hero(String name) {
        this.name = name;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    @Override
    public String toString() {
        return "Hero{" +
                "name='" + name + '\'' +
                '}';
    }
}

这里通过Map的输出可以看到刘备的Hero重复显示了,但是前面我们说了Map有个特性,Key不能重复。那这里为什么重复了呢?主要原因是对于自定义类型是如何判断这个Key是否是重复的呢?对于HashMap而言,主要是通过对象的hashcode跟equals方法来决定的。

  • equals()相等的两个对象他们的hashCode()肯定相等。
  • hashCode()相等的两个对象他们的equals()不一定相等。

修改后的代码演示

class Hero{
​
    private String name ;
​
    public Hero(String name) {
        this.name = name;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Hero)) return false;
        Hero hero = (Hero) o;
        return Objects.equals(name, hero.name);
    }
​
    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
​
    @Override
    public String toString() {
        return "Hero{" +
                "name='" + name + '\'' +
                '}';
    }
}
​
​
​
public class Demo08HashMap {
​
​
​
    public static void main(String[] args) {
        //创建 map对象
        HashMap<Hero, String> map = new HashMap<Hero, String>();
​
        //添加元素到集合
        map.put(new Hero("关羽"), "樊城");
        map.put(new Hero("张飞"), "新野");
        map.put(new Hero("赵子龙"), "徐州");
        map.put(new Hero("刘备"), "荆州");
        map.put(new Hero("刘备"), "荆州");
​
        System.out.println(map);
       // {Hero{name='张飞'}=新野, Hero{name='关羽'}=樊城, Hero{name='赵子龙'}=徐州, Hero{name='刘备'}=荆州}
​
    }
}

这里我们可以通过结果看到重复的刘备Hero没有重复显示了。

遍历

方式1

        通过map的keySet方法可以获取装所有的key的set,然后遍历再获取value。

代码演示

//创建 map对象
        HashMap<String, String> map = new HashMap<String, String>();
​
        //添加元素到集合
        map.put("关羽", "樊城");
        map.put("张飞", "新野");
        map.put("赵子龙", "徐州");
        map.put("刘备", "荆州"); 
​
        Set<String> keys = map.keySet();
        for (String name : keys) {
            System.out.println(name + " " + map.get(name));
        }

tips: 如果只想获取所有的值的话,可以通过map的values方法。

Collection<String> values = map.values();
for (String value : map.values()) {
    System.out.println("Value = " + value);
}

方式1使用比较简单,但是效率比较低。

方式2

        HashMap将所有的Key跟所有的Value重新包装成Map.Entry类了,并且通过entrySet方法返回所有数据对应的Map.Entry集合。

代码演示

public static void main(String[] args) {
        //创建 map对象
        HashMap<String, String> map = new HashMap<String, String>();
​
        //添加元素到集合
        map.put("关羽", "樊城");
        map.put("张飞", "新野");
        map.put("赵子龙", "徐州");
        map.put("刘备", "荆州");
 
​
//        方式2
     Iterator<Map.Entry<String, String>> entries = map.entrySet().iterator();
        while (entries.hasNext()) {
            Map.Entry<String, String> entry = entries.next();
            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
        }
​
 }

方式3

public static void main(String[] args) {
    //创建 map对象
    HashMap<String, String> map = new HashMap<String, String>();
​
    //添加元素到集合
    map.put("关羽", "樊城");
    map.put("张飞", "新野");
    map.put("赵子龙", "徐州");
    map.put("刘备", "荆州");
​
    //方式3 推荐
    for(Map.Entry<String, String> entry : map.entrySet()) {
        System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue());
    }
​
​
}

tips: 这种方式简单好用又高效,推荐使用。

Collections类

        JDK提供了一个工具类java.utils.Collections,可以用来对集合进行操作。

常用API

  • public static boolean addAll(Collection c, T... elements):往集合中添加一些元素。
@Test
public  void testAddAll() {
    ArrayList<String> list = new ArrayList<String>();
    //      往集合中添加元素
    Collections.addAll(list,"关羽","张飞","赵子龙","刘备");
    System.out.println(list);
}
  • public static void shuffle(List list) 打乱顺序:打乱集合顺序。
@Test
public  void testShuffle() {
    ArrayList<String> list = new ArrayList<String>();
    //      往集合中添加元素
    Collections.addAll(list,"关羽","张飞","赵子龙","刘备");
    System.out.println(list);
​
    //     随机打乱这个集合中的元素
    Collections.shuffle(list);
    System.out.println(list);
}
  • public static void sort(List list):将集合中元素按照默认规则排序。
  • public static void sort(List list,Comparator ):将集合中元素按照指定规则排序。
@Test
    public  void testSort() {
        ArrayList<Integer> list = new ArrayList<Integer>();
​
//      往集合中添加元素
        Collections.addAll(list,3,9,6,4,1,5);
        System.out.println(list);
​
//      用默认的比较器对集合中的元素排序
        Collections.sort(list);
        System.out.println(list);
​
        Collections.sort(list, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });
        System.out.println(list);
    }
  • public static void reverse(List list) : 反转指定列表中元素的顺序。
@Test
    public  void testReverse() {
        ArrayList<String> list = new ArrayList<String>();
        Collections.addAll(list,"关羽","张飞","赵子龙","刘备");
//      往集合中所有元素反转
        Collections.reverse(list);
        System.out.println(list);
    }
  • public static > T min(Collection coll):根据其元素的自然顺序返回给定集合的最小元素。
  • public static > T max(Collection coll):根据其元素的自然顺序返回给定集合的最大元素。
@Test
    public  void testMinAndMax() {
        ArrayList<Integer> list = new ArrayList<Integer>();
        Collections.addAll(list,3,9,6,4,1,5);
        System.out.println(Collections.min(list));
        System.out.println(Collections.max(list));
    }

public static void swap(List list, int i, int j) : 交换指定列表中指定位置的元素。

@Test
    public  void testSwap() {
        ArrayList<String> list = new ArrayList<String>();
        Collections.addAll(list,"关羽","张飞","赵子龙","刘备");
//      往集合中的元素进行互换
        Collections.swap(list,0,2);
        System.out.println(list);
    }
  • public static int binarySearch(List> list,T key):使用二叉搜索算法搜索指定对象的指定列表。必须具有有序性
@Test
public  void testBinarySearc() {
    ArrayList<Integer> list = new ArrayList<Integer>();
    Collections.addAll(list,1,2,3,4,5,6,7,8,9);
    System.out.println(Collections.binarySearch(list, 4));
}
  • public static boolean replaceAll(List list,T oldVal,T newVal): 将列表中一个指定值的所有出现替换为另一个。
@Test
public  void testReplaceAll() {
    ArrayList<String> list = new ArrayList<String>();
    Collections.addAll(list,"关羽","张飞","赵子龙","刘备");
    Collections.replaceAll(list,"刘备","马超");
    System.out.println(list);
}

泛型

概述

        在集合存放数据时,我们可以存入任意类型,但通常为了严谨,我们更希望集合中的所有数据具有相同类型,比如书柜里面全是书,酒柜里面全是酒,衣柜里面全是衣服……

        此时我们可以通过泛型来规范集合的所有元素的类型,能够使API更简洁,也能得到编译期间的语法检查,将运行期的类型不一致的问题调整到编译期的编译错误。

        泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。

代码演示

public static void main(String[] args) {
//        指定了泛型,决定了这个集合中的元素只能放入String类型
    List<String> list = new ArrayList<>();
    list.add("唐僧");
    list.add("孙悟空");
    list.add("猪八戒");
    list.add("沙僧");
//        list.add(1);// 编译错误,因为不是String类型
}

泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。

泛型类

  • 定义

修饰符 class 类名<泛型> {

...

 查看ArrayList的API

class ArrayList<E>{ 
    
    public boolean add(E e){ }
​
    public E get(int index){ }
    
    ....
}

在创建对象的时候ArrayList list = new ArrayList();就自动将泛型确定下来,用定义的String替换定义中的E。

class ArrayList<String>{ 
     public boolean add(String e){ }
​
     public String get(int index){  }
     ...
}
  • 自定义泛型类
// 仿照ArrayList自定义泛型类
class MyGeneric<E> {
    private  E e ;
​
    public void set(E e){
        this.e = e ;
    }
​
    public E get(){
        return e ;
    }
​
}
​
​
public class Demo12 {
​
    public static void main(String[] args) {
        // 泛型类确定为String
        MyGeneric<String> g1 = new MyGeneric<String>();
        g1.set("blb");
        System.out.println(g1.get());
       // 泛型类确定为Integer
        MyGeneric<Integer> g2 = new MyGeneric<Integer>();
        g2.set(9);
        System.out.println(g2.get());
​
    }
}

泛型方法

  • 定义

 修饰符 <泛型> 返回值类型 方法名(参数){ }

 代码演示

class GenericMethod  {
​
    public <E>  void show(E e){
        System.out.println(e);
    }
​
}
​
public class Demo13 {
​
    public static void main(String[] args) {
        GenericMethod gm = new GenericMethod();
//        泛型方法的类型确定为字符串
        gm.show("blb");
//        泛型方法的类型确定为Integer
        gm.show(99);
    }
}

tips:泛型方法中的泛型是在方法被调用的时候被确定下来。

泛型接口

  • 定义

 修饰符 interface接口名<泛型> { }

interface GenericInterface<E>{
    public abstract void set(E e);
    public abstract E get();
}

实现泛型接口并使用

//此时泛型接口中的泛型确定为String
class GenericImpl implements GenericInterface<String>{
    private String data ;
​
    @Override
    public void set(String s) {
        this.data = s ;
    }
​
    @Override
    public String get() {
        return data;
    }
}
​
public class Demo14 {
​
    public static void main(String[] args) {
        GenericInterface gi = new GenericImpl() ;
        gi.set("blb");
        System.out.println(gi.get());
    }
}

tips: 泛型接口中的泛型是在创建实现类的时候确定的。

通配符泛型

        当使用泛型类跟泛型接口传递参数的时候,泛型类型不确定的时候可以使用通配符来表示任意泛型类。

        一旦使用泛型的通配符后,带泛型参数的API就不能使用了。

代码演示

public static void main(String[] args) {
//        定义泛型为String
        List<String> list1 = new ArrayList<String>();
        show(list1);
//        定义泛型为Integer
        List<Integer> list2 = new ArrayList<Integer>();
        show(list2);
​
​
    }
​
    public static void show(List<?> list){
//        list.add(); //不能调用add方法
        for(int i=0;i<list.size();i++){
            System.out.println(list.get(i));
        }
    }
  • 受限泛型

刚刚可以使用来传递任意泛型,我们还可以指定一个泛型的上限下限

泛型的上限:

  • 格式: 类型名称<? extends 类> 对象名称
  • 意义: 只能接收该类型及其子类

泛型的下限:

  • 格式: 类型名称<? super 类> 对象名称
  • 意义: 只能接收该类型及其父类型

代码演示

现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类。

public class Demo16Generic {
​
    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<Integer>();
        List<String> list2 = new ArrayList<String>();
        List<Number> list3 = new ArrayList<Number>();
        List<Object> list4 = new ArrayList<Object>();
​
        show1(list1);
//        show1(list2);//报错
        show1(list3);
//        show1(list4);//报错
​
//        show2(list1);//报错
//        show2(list2);//报错
        show2(list3);
        show2(list4);
    }
​
    public static void show1(List<? extends Number> list){
    }
​
    public static void show2(List<? super Number> list){
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力学习Java的鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值