Java基础:集合学习笔记

集合

集合概述

集合是Java中提供的一种容器,可以用来存储多个数据,集合与数组的区别是:

  1. 数组的长度是固定的,集合的长度是可变的。
  2. 数组中存储的是同一类型的元素,可以存储基本数据类型,也可以是对象。集合只能存储对象,但是对象的类型可以不一致。

集合框架

集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大内容:对外的接口、接口的实现和对集合运算的算法。

  • 接口:是代表集合的抽象数据类型。例如 Collection、List、Set、Map、SortseMap 等。之所以定义多个接口,是为了以不同的方式操作集合对象
  • 实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,例如:ArrayList、LinkedList、HashSet、HashMap。
  • 算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。

集合框架

上图中点线框是接口,虚线框是抽象类,实现框是实现类。Java集合框架主要包括两种类型的容器,一种是集合(Collection),采用线性列表的存储方式,长度可动态改变;另一种是图(Map),采用键/值对的存储方式,长度也可以动态改变。尽管 Map 不是集合,但是它们完全整合在集合框架中。ComparableComparator是比较器接口,比较器是集合的常用工具。Collections是给集合提供排序等现成算法的工具类,往往配合比较器使用。Arrays是给数组提供算法的工具类,放在这里是因为它有一个asList()方法可以把数组转换成List接口的实现类对象。

Iterator迭代器

  不同的集合实现类的底层数据结构不同,例如ArrayList的底层是数组,LinkedList底层是链表,它们存取元素的方式也不同。我们要取出这些集合中的元素,可以通过一种通用的获取方式来完成。
  Collection集合元素的通用获取方式:在取出元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,再继续判断,如果还有就再取出来,一直到把集合中的所有元素全部取出为止。这种获取方式专业术语称为迭代。

  JDK为我们提供一个迭代器接口java.util.Iterator定义了统一的判断元素和取出元素的方法。

Iterator的常用方法 说明
public E next() 取出集合中的下一个元素。
public boolean hasNext() 判断集合中是否有下一个元素,如果有则返回true。

  Collection接口中有一个方法public Iterator iterator()Collection的实现类对象调用这个方法就可以返回一个Iterator的实现类对象,再通过这个迭代器的实现类对象调用迭代器的方法就可以实现对该集合实现类对象的迭代。使用迭代器迭代的过程中操作的对象是迭代器的实现类对象,只能获取集合元素,不能对集合元素调用集合方法。

public class IteratorDemo1 {
    public static void main(String[] args) {
        //创建一个集合对象(多态)
        Collection<Character> number = new ArrayList<>();
        //添加元素
        number.add('一');
        number.add('二');
        number.add('三');
        //创建一个迭代器的实现类对象(多态)
        Iterator<Character> it = number.iterator();
        //使用Iterator接口的hasNext方法判断集合中是否有下一个元素并打印结果
        System.out.println(it.hasNext());//true
        //使用Iterator接口的next方法获取集合中的下一个元素并打印结果
        System.out.println(it.next());//一
        //循环取出集合中的所有元素
        while (it.hasNext()){
            System.out.println(it.next());
        }
        //for循环写法
        for (Iterator<Character> it1 = number.iterator();it1.hasNext();){
            System.out.println(it1.next());
        }
        System.out.println(it.hasNext());//此时集合已经没有元素了,返回false
        System.out.println(it.next());//没有元素时会抛出NoSuchElementException
        /*增强for循环也称for each循环,是JDK1.5以后新增的一个高级for循环,专门用来遍历数组和集合。
        它的内部原理其实是个Iterator迭代器,所以在遍历的过程中也无法对集合中的元素调用集合方法。*/
        for (Character character : number) {
            System.out.println(character);
        }
    }
}

泛型

  所有集合类都位于java.util包中,集合中只能保存对象的引用。集合类把它所含有的元素看成是Object的实例,这样虽然方便但是也有隐患,即多个类型不同的元素被放入一个集合中,会增加集合访问时类型转换的困难,甚至会产生错误。

public class GenericityDemo1 {
    public static void main(String[] args) {
        //创建一个集合对象,不使用泛型约束,默认的类型就是Object类型,可多态兼容所有类型的对象
        Collection number = new ArrayList<>();
        //添加Character、Integer、String类型元素
        number.add('一');
        number.add(2);
        number.add("叁");
        //使用迭代器遍历集合
        Iterator it = number.iterator();
        while (it.hasNext()){
            //取出的元素默认是Object类型
            Object obj = it.next();
            //打印:一 2 叁
            System.out.print(obj+" ");
            //如果想要使用String类特有的方法length(),需要先把元素强制类型转换成String类型
            String str = (String) it.next();
            System.out.println(str.length());
            //'一'可以转换成"一",但Integer类型的2不能转换成String,会抛出ClassCastException
        }
    }
}

​ 泛型就是把确定类型的工作推迟到创建对象或调用方法的时候才去确定的特殊类型,泛型使用一个尖括号表示:<>,里 面指定一个或者多个类型参数的名字,多个类型参数之间使用逗号分隔:<T> <T,E>。泛型把数据类型当作参数一样传递,T,E只是代指一个未确定的引用数据类型,可以用任意字符或字符串命名表示。使用泛型来限制集合里元素的类型,并让集合记住元素的类型。这样可以允许编译器检查加入集合的元素类型,避免值类型不一致的错误。

泛型类的定义和使用

  定义格式:

修饰符 class 类名<E>{
    ......
}
//例如API中的ArrayList集合
 class ArrayList<E>{
     public boolean add(E e){...}
     public E get(int index){...}
     ......
 }

  定义完类型参数后,就可以在该类几乎任意地方(静态块、静态属性、静态方法除外)使用类型参数。注意,父类定义的类型参数不能被子类继承。

public class GenericClass<E> {
    private E name;

    public E getName() {
        return name;
    }

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

    public static void main(String[] args) {
        //创建GenericClass对象,泛型使用String类型。
        GenericClass<String> gc = new GenericClass<>();
        gc.setName("一二三");
        String str = gc.getName();
        System.out.println(str);
        //创建GenericClass对象,泛型使用Integer类型。
        GenericClass<Integer> gc1 = new GenericClass<>();
        gc1.setName(123);
        Integer int1 = gc1.getName();
        System.out.println(int1);
    }
}

泛型方法的定义和使用

  方法也可以泛型化,不管定义他们的类是不是泛型化。

  为什么要使用泛型方法而不是将类型T添加到类定义中呢?

  • 当泛型方法是静态时,不能使用类的类型参数。
  • 当T上的类型约束对于方法是局部时,这意味着没有在类的另一个方法签名中使用相同类型的T的约束。泛型方法的类型参数是局部的,可以简化封闭类型的签名。
public class GenericMethod {
    /*泛型方法的格式:
        修饰符 <T> 返回值类型 方法名(参数列表){
        ......
        }*/
    //非静态泛型方法
    private <T> void printGeneric(T t){
        System.out.print(t+" ");
    }
    //静态泛型方法
    private static <S> void printlnGeneric1(S s){
        System.out.println(s);
    }
    public static void main(String[] args) {
        GenericMethod gm = new GenericMethod();
        //调用泛型方法,传递什么类型的数据,泛型就是什么类型
        gm.printGeneric(1);
        gm.printGeneric('二');
        gm.printGeneric("叁");//打印:1 二 叁
        printlnGeneric1("四");//打印:四
    }
}

  泛型类是在实例化类时指明泛型的具体类型,泛型方法是在调用方法时指明泛型的具体类型。

泛型接口的定义和使用

  定义格式:

修饰符 interface 接口名<E>{
    ......
}
//例如API中的Interator接口
 public interface Interator<E>{
     public E next(){...}
 }

  泛型接口有两种使用方式:

  1. 实现类明确泛型接口的类型参数变量。
  2. 实现类不明确泛型接口的类型参数变量。
//泛型接口
public interface GenericInterface<I> {
    public abstract void printMethod(I i);
}
//实现类明确泛型接口的类型参数变量。
public class GenericInterfaceImpl1<I> implements GenericInterface<String> {
    @Override
    public void printMethod(String s) {
        System.out.println(s);
    }

    public static void main(String[] args) {
        GenericInterfaceImpl1 gi1 = new GenericInterfaceImpl1();
        gi1.printMethod("一二三");//打印: 一二三
    }
}
//实现类不明确泛型接口的类型参数变量。
public class GenericInterfaceImpl2<I> implements GenericInterface<I> {
    @Override
    public void printMethod(I i) {
        System.out.print(i);
    }

    public static void main(String[] args) {
        GenericInterfaceImpl2<Character> gi1 = new GenericInterfaceImpl2();
        gi1.printMethod('一');
        GenericInterfaceImpl2<String> gi2 = new GenericInterfaceImpl2<>();
        gi2.printMethod("二三");
        //打印结果:一二三
    }
}

泛型通配符

  当使用泛型类或接口时,如果不确定泛型的类型,可使用通配符<?>表示。但是一旦使用泛型的通配符以后,就只能使用Object类中的共性方法,集合中元素自身的方法将无法使用。

注意:使用泛型通配符时只能接受数据,而不能往该集合中存储数据。

public class Demo1 {
    public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        list1.add(1);
        list1.add(2);
        ArrayList<String> list2 = new ArrayList<>();
        list2.add("a");
        list2.add("b");
        printArray(list1);
        printArray(list2);
    }
    private static void printArray(ArrayList<?> list){
        for (Object o : list) {
            System.out.print(o+" ");
        }//打印结果:1 2 a b
    }
}

泛型受限

  有时我们需要限制类型参数的范围。

泛型受限 格式 意义
泛型的上限 类型名称 <? extends 类> 对象名称 只能接收该类型及其子类型
泛型的下限 类型名称 <? super 类> 对象名称 只能接收该类型及其父类型
public class GenericRestriction{
    //该方法的参数只能接收Number类及其子类对象
    private static void getElement1(Collection<? extends Number> coll){}
    //该方法的参数只能接收Number类及其父类对象
    private static void getElement2(Collection<? super Number> coll){}

    public static void main(String[] args) {
        Collection<Object> list1 = new ArrayList<Object>();
        Collection<Integer> list2 = new ArrayList<Integer>();
        Collection<String> list3 = new ArrayList<String>();
        Collection<Number> list4 = new ArrayList<Number>();
        
        getElement1(list1);//泛型受限,报错
        getElement1(list2);
        getElement1(list3);//泛型受限,报错
        getElement1(list4);
        
        getElement2(list1);
        getElement2(list2);//泛型受限,报错
        getElement2(list3);//泛型受限,报错
        getElement2(list4);
    }
}

类型擦除(Type Erasure)

  泛型是在编译器这个层次实现的,在生成的字节代码中是不包含泛型的类型信息的。

  使用泛型时加上的类型参数,会被编译器在编译时去掉,这个过程称为类型擦除。也就是说,由泛型附加的类型信息对JVM来说都是不可见的。

  编译器在编译时尽可能地发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。

  类型擦除的过程:

  1. 找到用来替换类型参数的具体类。如果指定了类型参数的上界的话,则使用上界。
  2. 把代码中的类型参数都替换成具体类型,同时去掉出现的类型声明,即<>的内容;

 理解了类型擦除,就会明白编译器承担了全部的类型检查工作。编译器禁止某些泛型的使用方式,是为了确保类型的安全性。

  很多泛型的特性都与类型擦除有关,如

  • 静态变量是被泛型类的所有实例共享的,即泛型不能用于静态变量
  • 泛型的类型参数不能用在异常处理的catch语句中,因为异常处理是由JVM在运行时刻进行的。由于类型擦除,JVM已经无法区分源于同一泛型类型的两个不同类型的异常。

泛型的最佳使用原则

  • 在代码中避免泛型类和原始类型混用,如List和List不应共同使用。
  • 使用带通配符(?)的泛型类时,需要明确通配符所代表的一组类型的概念。
  • 不能实例化泛型类型变量,然后利用反射的newInstance来创建实例;同样,不能创建一个泛型的数组。
  • 尽量不要忽视编译器给出的警告。

Collection

Collection 接口是 Java 集合框架里的一个根接口。它也是 List、Set 和 Queue 接口的父接口。Collection 接口中定义了可用于操作 List、Set 和 Queue 的方法——增删改查。

方法 返回值 说明
add(E e) boolean 向 collection 的尾部追加指定的元素(可选操作)
addAll(Collection<? extend E> c) boolean 将指定 collection 中的所有元素都添加到此 collection 中(可选操作)
clear() void 移除此 collection 中的所有元素(可选操作)
contains(Object o) boolean 如果此 collection 包含指定的元素,则返回 true
containsAll(Collection<?> c) boolean 如果此 collection 包含指定 collection 的所有元素,则返回 true
equals(Object o) boolean 比较此 collection 与指定对象是否相等
hashCode() int 返回此 collection 的哈希码值
isEmpty() boolean 如果此 collection 不包含元素,则返回 true
iterator() Iterator 返回在此 collection 的元素上进行迭代的迭代器
remove(Object o) boolean 移除此 collection 中出现的首个指定元素(可选操作)
removeAll(Collection<?> c) boolean 移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)
retainAll(Collection<?> c) boolean 仅保留此 collection 中那些也包含在指定 collection 的元素(可选操作)
size() int 返回此 collection 中的元素数
toArray() Object[] 返回包含此 collection 中所有元素的数组
toArray(T[] a) T[] 返回包含此 collection 中所有元素的数组;返回数组的运行时类型与指定数组的运行时类型相同

List

List 是一个接口,不能实例化,需要一个具体类来实现实例化。

List 集合中的对象按照一定的顺序排放,里面的内容可以重复。 List 接口实现的类有:ArrayList(实现动态数组),Vector(实现动态数组),LinkedList(实现链表),Stack(实现堆栈)。

List 在 Collection 基础上增加的方法:

方法 返回值 说明
add(int index, E element) void 在列表的指定位置插入指定元素(可选操作)
addAll(int index, Collection<? extends E> c) boolean 将指定 collection 中的所有元素都插入到列表中的指定位置(可选操作)
get(int index) E 返回列表中指定位置的元素
indexOf(Object o) int 返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1
lastIndexOf(Object o) int 返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1
listIterator() ListIterator 返回此列表元素的列表迭代器(按适当顺序)
listIterator(int index) ListIterator 返回此列表元素的列表迭代器(按适当顺序),从列表的指定位置开始
remove(int index) E 移除列表中指定位置的元素(可选操作)
set(int index, E element) E 用指定元素替换列表中指定位置的元素(可选操作)
subList(int fromIndex, int toIndex) List 返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图

ArrayList

ArrayList  类实现一个可增长的动态数组,位于 java.util.ArrayList。实现了 List 接口,它可以存储不同类型的对象(包括 null 在内),而数组则只能存放特定数据类型的值。

ArrayList 编程实例

学校的教务系统会对学生进行统一的管理,每一个学生都会有一个学号和学生姓名,我们在维护整个系统的时候,大多数操作是对学生的添加、插入、删除、修改等操作。

先创建一个学生类 Student.java

/**
 * 学生类
 */
public class Student {
    public String id;
    public String name;
    public Student(String id, String name){
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

再在创建一个 ListTest.java,其中包含了一个学生列表,通过操作学生列表来管理学生

import java.util.*;


public class ListTest {

    //集合后面的<>代表泛型的意思
    //泛型是规定了集合元素的类型
    /**
     * 用于存放学生的List
     */
    public List<Student> students;


    public ListTest() {
        this.students = new ArrayList<Student>();
    }

    /**
     * 用于往students中添加学生
     */
    public void testAdd() {
        // 创建一个学生对象,并通过调用add方法,添加到学生管理List中
        Student st1 = new Student("1", "张三");
        students.add(st1);

        // 取出 List中的Student对象 索引为0 也就是第一个
        Student temp = students.get(0);
        System.out.println("添加了学生:" + temp.id + ":" + temp.name);

        Student st2 = new Student("2", "李四");
        //添加到list中,插入到索引为0的位置,也就是第一个
        students.add(0, st2);
        Student temp2 = students.get(0);
        System.out.println("添加了学生:" + temp2.id + ":" + temp2.name);

        // 对象数组的形式添加
        Student[] student = {new Student("3", "王五"), new Student("4", "马六")};

        // Arrays类包含用来操作数组(比如排序和搜索)的各种方法,asList() 方法用来返回一个受指定数组支持的固定大小的列表
        students.addAll(Arrays.asList(student));
        Student temp3 = students.get(2);
        Student temp4 = students.get(3);
        System.out.println("添加了学生:" + temp3.id + ":" + temp3.name);
        System.out.println("添加了学生:" + temp4.id + ":" + temp4.name);
        Student[] student2 = {new Student("5", "周七"), new Student("6", "赵八")};
        students.addAll(2, Arrays.asList(student2));
        Student temp5 = students.get(2);
        Student temp6 = students.get(3);
        System.out.println("添加了学生:" + temp5.id + ":" + temp5.name);
        System.out.println("添加了学生:" + temp6.id + ":" + temp6.name);
    }


    /**
     * 取得List中的元素的方法
     */
    public void testGet() {
        int size = students.size();
        for (int i = 0; i < size; i++) {
            Student st = students.get(i);
            System.out.println("学生:" + st.id + ":" + st.name);

        }
    }


    /**
     * 通过迭代器来遍历
     * 迭代器的工作是遍历并选择序列中的对象,Java 中 Iterator 只能单向移动
     */
    public void testIterator() {
        // 通过集合的iterator方法,取得迭代器实例
        Iterator<Student> it = students.iterator();
        System.out.println("有如下学生(通过迭代器访问):");
        while (it.hasNext()) {

            Student st = it.next();
            System.out.println("学生" + st.id + ":" + st.name);
        }
    }

    /**
     * 通过for each 方法访问集合元素
     *
     */
    public void testForEach() {
        System.out.println("有如下学生(通过for each):");
        for (Student obj : students) {
            Student st = obj;
            System.out.println("学生:" + st.id + ":" + st.name);
        }
        //使用java8 Steam将学生排序后输出
        students.stream()//创建Stream
                //通过学生id排序
                .sorted(Comparator.comparing(x -> x.id))
                //输出
                .forEach(System.out::println);
    }

    /**
     * 修改List中的元素
     *
     */
    public void testModify() {
        students.set(4, new Student("3", "吴酒"));
    }

    /**
     * 删除List中的元素
     *
     */
    public void testRemove() {
        Student st = students.get(4);
        System.out.println("我是学生:" + st.id + ":" + st.name + ",我即将被删除");
        students.remove(st);
        System.out.println("成功删除学生!");
        testForEach();
    }


    public static void main(String[] args) {
        ListTest lt = new ListTest();
        lt.testAdd();
        lt.testGet();
        lt.testIterator();
        lt.testModify();
        lt.testForEach();
        lt.testRemove();

    }
}

打印结果:

添加了学生:1:张三
添加了学生:2:李四
添加了学生:3:王五
添加了学生:4:马六
添加了学生:5:周七
添加了学生:6:赵八
学生:2:李四
学生:1:张三
学生:5:周七
学生:6:赵八
学生:3:王五
学生:4:马六
有如下学生(通过迭代器访问):
学生2:李四
学生1:张三
学生5:周七
学生6:赵八
学生3:王五
学生4:马六
有如下学生(通过for each):
学生:2:李四
学生:1:张三
学生:5:周七
学生:6:赵八
学生:3:吴酒
学生:4:马六
Student{id='1', name='张三'}
Student{id='2', name='李四'}
Student{id='3', name='吴酒'}
Student{id='4', name='马六'}
Student{id='5', name='周七'}
Student{id='6', name='赵八'}
我是学生:3:吴酒,我即将被删除
成功删除学生!
有如下学生(通过for each):
学生:2:李四
学生:1:张三
学生:5:周七
学生:6:赵八
学生:4:马六
Student{id='1', name='张三'}
Student{id='2', name='李四'}
Student{id='4', name='马六'}
Student{id='5', name='周七'}
Student{id='6', name='赵八'}

在上面的代码中,用到了 Arrays 类, Arrays 包含用来操作数组(比如排序和搜索)的各种方法,asList() 方法用来返回一个受指定数组支持的固定大小的列表。

Map

Map 接口也是一个非常重要的集合接口,用于存储键 / 值对。Map 中的元素都是成对出现的,键值对就像数组的索引与数组的内容的关系一样,将一个键映射到一个值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。我们可以通过键去找到相应的值。

Map 映射

value 可以存储任意类型的对象,我们可以根据 key 键快速查找 value。Map 中的键 / 值对以 Entry 类型的对象实例形式存在。

看一看 Map 中的方法吧:

方法 返回值 说明
clear() void 从此映射中移除所用映射关系(可选操作)
containsKey(Object key) boolean 如果此映射包含指定键的映射关系,则返回 true
containsValue(Object value) boolean 如果此映射将一个或多个键映射到指定值,则返回 true
entrySet() Set<Map.Entry<K,V>> 返回此映射中包含的映射关系的 Set 视图
equals(Object o) boolean 比较指定的对象与此映射是否相等
get(Object key) V 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
hashCode() int 返回此映射的哈希码值
isEmpty() boolean 如果此映射未包含键 - 值映射关系,则返回 true
keySet() Set 返回此映射中包含的键的 Set 视图
put(K key, V value) V 将指定的值与此映射中的指定键关联(可选操作)
putAll(Map<? extends K, ? extends V> m) void 从指定映射中将所有映射关系复制到此映射中(可选操作)
remove(Object key) V 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)
size int 返回此映射中的键 - 值映射关系数
values() Collection 返回此映射中包含的值的 Collection 视图

HashMap

HashMap 是基于哈希表的 Map 接口的一个重要实现类。HashMap 中的 Entry 对象是 无序 排列的,Key 值和 value 值都可以为 null,但是一个 HashMap 只能有一个 key 值为 null 的映射(key 值不可重复)。

下面通过代码来学习 Map 中的方法。同学们都有过选课经历吧,我们就用 Map 来管理课程吧。

创建一个 Course 类:

// Course.java
public class Course {
    public String id;
    public String name;
    public Course(String id, String name){
        this.id = id;
        this.name = name;
    }
}

创建一个 MapTest 类:

// MapTest.java
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;

public class MapTest {

    /**
     * 用来承装课程类型对象
     */
    public Map<String, Course> courses;

    /**
     * 在构造器中初始化 courses 属性
     * @param args
     */
    public MapTest() {
        this.courses = new HashMap<String, Course>();
    }

    /**
     * 测试添加:输入课程 ID,判断是否被占用
     * 若未被占用,输入课程名称,创建新课程对象
     * 并且添加到 courses 中
     * @param args
     */
    public void testPut() {
        //创建一个 Scanner 对象,用来获取输入的课程 ID 和名称
        Scanner console = new Scanner(System.in);

        for(int i = 0; i < 3; i++) {
            System.out.println("请输入课程 ID:");
            String ID = console.next();
            //判断该 ID 是否被占用
            Course cr = courses.get(ID);
            if(cr == null){
                //提示输入课程名称
                System.out.println("请输入课程名称:");
                String name = console.next();
                //创建新的课程对象
                Course newCourse = new Course(ID,name);
                //通过调用 courses 的 put 方法,添加 ID-课程映射
                courses.put(ID, newCourse);
                System.out.println("成功添加课程:" + courses.get(ID).name);
            }
            else {
                System.out.println("该课程 ID 已被占用");
                continue;
            }
        }
    }

    /**
     * 测试 Map 的 keySet 方法
     * @param args
     */

    public void testKeySet() {
        //通过 keySet 方法,返回 Map 中的所有键的 Set 集合
        Set<String> keySet = courses.keySet();
        //遍历 keySet,取得每一个键,在调用 get 方法取得每个键对应的 value
        for(String crID: keySet) {
            Course cr = courses.get(crID);
            if(cr != null){
                System.out.println("课程:" + cr.name);
            }
        }
    }

    /**
     * 测试删除 Map 中的映射
     * @param args
     */
    public void testRemove() {
        //获取从键盘输入的待删除课程 ID 字符串
        Scanner console = new Scanner(System.in);
        while(true){
            //提示输出待删除的课程 ID
            System.out.println("请输入要删除的课程 ID!");
            String ID = console.next();
            //判断该 ID 是否对应的课程对象
            Course cr = courses.get(ID);
            if(cr == null) {
                //提示输入的 ID 并不存在
                System.out.println("该 ID 不存在!");
                continue;
            }
            courses.remove(ID);
            System.out.println("成功删除课程" + cr.name);
            break;
        }
    }

    /**
     * 通过 entrySet 方法来遍历 Map
     * @param args
     */
    public void testEntrySet() {
        //通过 entrySet 方法,返回 Map 中的所有键值对
        Set<Entry<String,Course>> entrySet = courses.entrySet();
        for(Entry<String,Course> entry: entrySet) {
            System.out.println("取得键:" + entry.getKey());
            System.out.println("对应的值为:" + entry.getValue().name);
        }
    }

    /**
     * 利用 put 方法修改Map 中的已有映射
     * @param args
     */
    public void testModify(){
        //提示输入要修改的课程 ID
        System.out.println("请输入要修改的课程 ID:");
        //创建一个 Scanner 对象,去获取从键盘上输入的课程 ID 字符串
        Scanner console = new Scanner(System.in);
        while(true) {
            //取得从键盘输入的课程 ID
            String crID = console.next();
            //从 courses 中查找该课程 ID 对应的对象
            Course course = courses.get(crID);
            if(course == null) {
                System.out.println("该 ID 不存在!请重新输入!");
                continue;
            }
            //提示当前对应的课程对象的名称
            System.out.println("当前该课程 ID,所对应的课程为:" + course.name);
            //提示输入新的课程名称,来修改已有的映射
            System.out.println("请输入新的课程名称:");
            String name = console.next();
            Course newCourse = new Course(crID,name);
            courses.put(crID, newCourse);
            System.out.println("修改成功!");
            break;
        }
    }

    public static void main(String[] args) {
        MapTest mt = new MapTest();
        mt.testPut();
        mt.testKeySet();
        mt.testRemove();
        mt.testModify();
        mt.testEntrySet();

    }
}

打印结果:

请输入课程 ID:
1
请输入课程名称:
语文
成功添加课程:语文
请输入课程 ID:
1
该课程 ID 已被占用
请输入课程 ID:
2
请输入课程名称:
数学
成功添加课程:数学
课程:语文
课程:数学
请输入要删除的课程 ID!
1
成功删除课程语文
请输入要修改的课程 ID:
2
当前该课程 ID,所对应的课程为:数学
请输入新的课程名称:
英语
修改成功!
取得键:2
对应的值为:英语

Set 和 HashSet

Set 接口也是 Collection 接口的子接口,它有一个很重要也是很常用的实现类——HashSet,Set 是元素无序并且不包含重复元素的 collection(List 可以重复),被称为集。

HashSet 由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。

假设现在学生们要做项目,每个项目有一个组长,由组长来组织组员,我们便来实现项目组的管理吧。

因为项目组的组长由一个老师担任,首先创建一个 PD 类

// PD.java
import java.util.HashSet;
import java.util.Set;
/*
 * 项目组长类
 */
public class PD {

    public String id;
    public String name;
    //集合后面的<>代表泛型的意思
    //泛型是规定了集合元素的类型
    public Set<Student> students;
    public PD(String id, String name){
        this.id = id;
        this.name = name;
        this.students = new HashSet<Student>();
    }
}

创建一个学生类 Student.java

/**
 * 学生类
 */
 // Student.java
public class Student {
    public String id;
    public String name;
    public Student(String id, String name){
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

接下来我们便创建一个 SetTest 类,用来管理项目成员

// SetTest.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;

public class SetTest {

    public List<Student> students;

    public SetTest() {
        students = new ArrayList<Student>();
    }

    /*
     * 用于往students中添加学生
     */
    public void testAdd() {
        //创建一个学生对象,并通过调用add方法,添加到学生管理List中
        Student st1 = new Student("1", "张三");
        students.add(st1);

        //添加到List中的类型均为Object,所以取出时还需要强转

        Student st2 = new Student("2","李四");
        students.add(st2);

        Student[] student = {new Student("3", "王五"),new Student("4", "马六")};
        students.addAll(Arrays.asList(student));

        Student[] student2 = {new Student("5", "周七"),new Student("6", "赵八")};
        students.addAll(Arrays.asList(student2));

    }

    /**
     * 通过for each 方法访问集合元素
     * @param args
     */
    public void testForEach() {
        System.out.println("有如下学生(通过for each):");
        for(Object obj:students){
            Student st = (Student)obj;
            System.out.println("学生:" + st.id + ":" + st.name);
        }
    }

    public static void main(String[] args){
        SetTest st = new SetTest();
        st.testAdd();
        st.testForEach();
        PD pd = new PD("1","张老师");
        System.out.println("请:" + pd.name + "选择小组成员!");
        //创建一个 Scanner 对象,用来接收从键盘输入的学生 ID
        Scanner console = new Scanner(System.in);

        for(int i = 0;i < 3; i++){
            System.out.println("请输入学生 ID");
            String studentID = console.next();
            for(Student s:st.students){
                if(s.id.equals(studentID)){
                    pd.students.add(s);
                }
            }
        }
        st.testForEachForSer(pd);
        // 关闭 Scanner 对象
        console.close();
    }
    //打印输出,老师所选的学生!Set里遍历元素只能用foreach 和 iterator
    //不能使用 get() 方法,因为它是无序的,不能想 List 一样查询具体索引的元素
    public void testForEachForSer(PD pd){
        for(Student s: pd.students) {
        System.out.println("选择了学生:" + s.id + ":" + s.name);
        }
    }

}

打印结果:

有如下学生(通过for each):
学生:1:张三
学生:2:李四
学生:3:王五
学生:4:马六
学生:5:周七
学生:6:赵八
请:张老师选择小组成员!
请输入学生 ID
4
请输入学生 ID
5
请输入学生 ID
6
选择了学生:4:马六
选择了学生:5:周七
选择了学生:6:赵八

Collections

java.util.Collections 是一个工具类,他包含了大量对集合进行操作的静态方法。

常用方法

方法名 描述
void sort(List list) 按自然升序排序
void sort(List list, Comparator c) 自定义排序规则排序
void shuffle(List list) 随机排序,用于打乱顺序
void reverse(List list) 反转,将列表元素顺序反转
void swap(List list, int i , int j) 交换处于索引 i 和 j 位置的元素
int binarySearch(List list, Object key) 二分查找,列表必须有序,返回找到的元素索引位置
int max(Collection coll) 查找最大值
int min(Collection coll) 查找最小值
void fill(List list, Object obj) 使用 obj 填充 list 所有元素
boolean replaceAll(List list, Object oldVal, Object newVal) 使用用 newVal 替换所有的 oldVal。
<K,V> Map<K,V> synchronizedMap(Map<K,V> m) 将 m 包装为线程安全的 Map
List synchronizedList(List list) 将 list 包装为线程安全的 List

编程实例

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

public class CollectionsDemo {
    public static void main(String[] args) {
//        创建一个空List
        List<Integer> list = new ArrayList<Integer>();
        //赋值
        list.add(3);
        list.add(5);
        list.add(7);
        list.add(9);
        list.add(12);
        System.out.print("初始顺序:");
        list.forEach(v -> System.out.print(v + "\t"));


        //打乱顺序
        Collections.shuffle(list);
        System.out.print("\n打乱顺序:");
        list.forEach(v -> System.out.print(v + "\t"));

        //反转
        Collections.reverse(list);
        System.out.print("\n反转集合:");
        list.forEach(v -> System.out.print(v + "\t"));

        //第一个位和最后一位交换
        Collections.swap(list,0,list.size()-1);
        System.out.print("\n交换第一位和最后一位:");
        list.forEach(v -> System.out.print(v + "\t"));

        //按自然升序排序
        Collections.sort(list);
        System.out.print("\nSort排序后:");
        list.forEach(v -> System.out.print(v + "\t"));

        //二分查找 必须排序后
        System.out.print("\n二分查找数值7的位置:"+Collections.binarySearch(list, 7));

        //返回线程安全的list
        List<Integer> synchronizedList = Collections.synchronizedList(list);
    }
}

打印结果:

初始顺序:3    5    7    9    12
打乱顺序:5    7    3    12    9
反转集合:9    12    3    7    5
交换第一位和最后一位:5    12    3    7    9
Sort排序后:3    5    7    9    12
二分查找数值7的位置:2

posted @ 2020-08-02 22:37  矜君  阅读(227)  评论(0)    收藏  举报