美文网首页
7.Java集合

7.Java集合

作者: hainingwyx | 来源:发表于2017-05-04 22:20 被阅读109次

    以下是《疯狂Java讲义》中的一些知识,如有错误,烦请指正。


    集合概述

    Java集合可以分为Set、List、Map、Queue四种体系。Set是无序集合、List是有序集合、Queue是队列实现,Map保存映射关系。Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口。

    集合类:为了保存数量不确定的数据以及保存具有映射关系的数据。集合里只能保存对象,不能保存基本类型。集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。所有集合类都位于java.util包下。

    派生接口:Collection和Map

    Collection和Iterator接口

    Collection是List、Set、Queue接口的父接口。

    import java.util.*;
    public class CollectionTest
    {
        public static void main(String[] args)
        {
            Collection c = new ArrayList();
            // 添加元素
            c.add("孙悟空");
            // 虽然集合里不能放基本类型的值,但Java支持自动装箱
            c.add(6);
            System.out.println("c集合的元素个数为:" + c.size()); // 输出2
            // 删除指定元素
            c.remove(6);
            System.out.println("c集合的元素个数为:" + c.size()); // 输出1
            // 判断是否包含指定字符串
            System.out.println("c集合的是否包含\"孙悟空\"字符串:"
                + c.contains("孙悟空")); // 输出true
            c.add("轻量级Java EE企业应用实战");
            System.out.println("c集合的元素:" + c);
            Collection books = new HashSet();
            books.add("轻量级Java EE企业应用实战");
            books.add("疯狂Java讲义");
            System.out.println("c集合是否完全包含books集合?"
                + c.containsAll(books)); // 输出false
            // 用c集合减去books集合里的元素
            c.removeAll(books);
            System.out.println("c集合的元素:" + c);
            // 删除c集合里所有元素
            c.clear();
            System.out.println("c集合的元素:" + c);
            // 控制books集合里只剩下c集合里也包含的元素
            books.retainAll(c);
            System.out.println("books集合的元素:" + books);
        }
    }
    
    import java.util.*;
    public class ForeachTest
    {
        public static void main(String[] args)
        {
            // 创建集合、添加元素的代码与前一个程序相同
            Collection books = new HashSet();
            books.add(new String("轻量级Java EE企业应用实战"));
            books.add(new String("疯狂Java讲义"));
            books.add(new String("疯狂Android讲义"));
            for (Object obj : books)
            {
                // 此处的book变量也不是集合元素本身
                String book = (String)obj;
                System.out.println(book);
                if (book.equals("疯狂Android讲义"))
                {
                    // 下面代码会引发ConcurrentModificationException异常
                    books.remove(book);     //①
                }
            }
            System.out.println(books);
        }
    }
    

    Iterator也是Java集合框架的成员,主要用于遍历,Iterable接口是Collection接口的父接口。

    import java.util.*;
    public class CollectionEach
    {
        public static void main(String[] args)
        {
            // 创建一个集合
            Collection books = new HashSet();
            books.add("轻量级Java EE企业应用实战");
            books.add("疯狂Java讲义");
            books.add("疯狂Android讲义");
            // 调用forEach()方法遍历集合,Collection集合直接调用父接口的默认方法
            // foreach 方法所需参数类型是一个函数型接口
            books.forEach(obj -> System.out.println("迭代集合元素:" + obj));
        }
    }
    
    import java.util.*;
    public class IteratorTest
    {
        public static void main(String[] args)
        {
            // 创建集合、添加元素的代码与前一个程序相同
            Collection books = new HashSet();
            books.add("轻量级Java EE企业应用实战");
            books.add("疯狂Java讲义");
            books.add("疯狂Android讲义");
            // 获取books集合对应的迭代器
            Iterator it = books.iterator();
            while(it.hasNext())
            {
                // it.next()方法返回的数据类型是Object类型,因此需要强制类型转换
                String book = (String)it.next();
                System.out.println(book);
                if (book.equals("疯狂Java讲义"))
                {
                    // 从集合中删除上一次next方法返回的元素
                    it.remove();
                }
                // 对book变量赋值,不会改变集合元素本身
                book = "测试字符串";   
            }
            System.out.println(books);
        }
    }
    

    当使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iterator的remove方法删除上一次返回的集合元素才可以,否则引发java.util.ConcurrentModificationException异常

    import java.util.*;
    public class IteratorErrorTest
    {
        public static void main(String[] args)
        {
            // 创建集合、添加元素的代码与前一个程序相同
            Collection books = new HashSet();
            books.add("轻量级Java EE企业应用实战");
            books.add("疯狂Java讲义");
            books.add("疯狂Android讲义");
            // 获取books集合对应的迭代器
            Iterator it = books.iterator();
            while(it.hasNext())
            {
                String book = (String)it.next();
                System.out.println(book);
                if (book.equals("疯狂Android讲义"))
                {
                    // 使用Iterator迭代过程中,不可修改集合元素,下面代码引发异常
                    books.remove(book);
                }
            }
        }
    }
    

    迭代时检测到集合被修改将会引发异常,只有删除集合的某个特定元素才不会引发异常。

    lambda表达式遍历Iterator
    java8提供的forEachRemaining参数同样也是函数式接口。

    import java.util.*;
    public class IteratorEach
    {
        public static void main(String[] args)
        {
            // 创建集合、添加元素的代码与前一个程序相同
            Collection books = new HashSet();
            books.add("轻量级Java EE企业应用实战");
            books.add("疯狂Java讲义");
            books.add("疯狂Android讲义");
            // 获取books集合对应的迭代器
            Iterator it = books.iterator();
            // 使用Lambda表达式(目标类型是Comsumer)来遍历集合元素
            it.forEachRemaining(obj -> System.out.println("迭代集合元素:" + obj));
        }
    }
    

    使用foreach循环遍历集合元素
    同样当使用foreach迭代访问集合元素时,该集合不能被改变。

    import java.util.*;
    public class ForeachTest
    {
        public static void main(String[] args)
        {
            // 创建集合、添加元素的代码与前一个程序相同
            Collection books = new HashSet();
            books.add(new String("轻量级Java EE企业应用实战"));
            books.add(new String("疯狂Java讲义"));
            books.add(new String("疯狂Android讲义"));
            for (Object obj : books)
            {
                // 此处的book变量也不是集合元素本身
                String book = (String)obj;
                System.out.println(book);
                if (book.equals("疯狂Android讲义"))
                {
                    // 下面代码会引发ConcurrentModificationException异常
                    books.remove(book);
                }
            }
            System.out.println(books);
        }
    }
    

    使用Predicate操作集合
    Java8新增了一个removeIf(Predicate filter)方法,该方法将删除符合filter条件的所有元素。Predicate也是函数式接口,因此可以使用Lambda表达式作为参数。

    import java.util.*;
    import java.util.function.*;
    public class PredicateTest
    {
        public static void main(String[] args)
        {
            // 创建一个集合
            Collection books = new HashSet();
            books.add(new String("轻量级Java EE企业应用实战"));
            books.add(new String("疯狂Java讲义"));
            books.add(new String("疯狂iOS讲义"));
            books.add(new String("疯狂Ajax讲义"));
            books.add(new String("疯狂Android讲义"));
            // 使用Lambda表达式(目标类型是Predicate)过滤集合
            books.removeIf(ele -> ((String)ele).length() < 10);
            System.out.println(books);
        }
    }
    

    使用Predicate可以简化集合的运算。callAll方法只统计满足Predicate条件的图书。

    
    import java.util.*;
    import java.util.function.*;
    public class PredicateTest2
    {
        public static void main(String[] args)
        {
            // 创建books集合、为books集合添加元素的代码与前一个程序相同。
            Collection books = new HashSet();
            books.add(new String("轻量级Java EE企业应用实战"));
            books.add(new String("疯狂Java讲义"));
            books.add(new String("疯狂iOS讲义"));
            books.add(new String("疯狂Ajax讲义"));
            books.add(new String("疯狂Android讲义"));
            // 统计书名包含“疯狂”子串的图书数量
            System.out.println(calAll(books , ele->((String)ele).contains("疯狂")));
            // 统计书名包含“Java”子串的图书数量
            System.out.println(calAll(books , ele->((String)ele).contains("Java")));
            // 统计书名字符串长度大于10的图书数量
            System.out.println(calAll(books , ele->((String)ele).length() > 10));
        }
        public static int calAll(Collection books , Predicate p)
        {
            int total = 0;
            for (Object obj : books)
            {
                // 使用Predicate的test()方法判断该对象是否满足Predicate指定的条件
                if (p.test(obj))
                {
                    total ++;
                }
            }
            return total;
        }
    }
    

    Java 8新增的Stream操作
    Java 8还新增了Stream、IntStream、LongStream、DoubleStream等流式API。
    独立使用Stream的步骤如下:

    1. 使用Stream或XxxStream的builder()类方法创建该Stream对应Builder。
    2. 重复调用Builder的add()方法向该流中添加多个元素。
    3. 调用Builder的build()方法获取对应的Stream。
    4. 调用Stream的聚集方法。
    import java.util.stream.*;
    public class IntStreamTest
    {
        public static void main(String[] args)
        {
            IntStream is = IntStream.builder()
                .add(20)
                .add(13)
                .add(-2)
                .add(18)
                .build();
            // 下面调用聚集方法的代码每次只能执行一个
            System.out.println("is所有元素的最大值:" + is.max().getAsInt());
            System.out.println("is所有元素的最小值:" + is.min().getAsInt());
            System.out.println("is所有元素的总和:" + is.sum());
            System.out.println("is所有元素的总数:" + is.count());
            System.out.println("is所有元素的平均值:" + is.average());
            System.out.println("is所有元素的平方是否都大于20:"
                + is.allMatch(ele -> ele * ele > 20));
            System.out.println("is是否包含任何元素的平方大于20:"
                + is.anyMatch(ele -> ele * ele > 20));
            // 将is映射成一个新Stream,新Stream的每个元素是原Stream元素的2倍+1
            IntStream newIs = is.map(ele -> ele * 2 + 1);
            // 使用方法引用的方式来遍历集合元素
            newIs.forEach(System.out::println); // 输出41 27 -3 37
        }
    }
    

    Collection接口提供了一个stream()默认方法,该方法可返回该集合对应的流,接下来即可通过流API来操作集合元素。由于Stream可以对集合元素进行整体的聚集操作,因此Stream极大了丰富了集合的功能。

    import java.util.*;
    import java.util.function.*;
    public class CollectionStream
    {
        public static void main(String[] args)
        {
            // 创建books集合、为books集合添加元素的代码与8.2.5小节的程序相同。
            Collection books = new HashSet();
            books.add(new String("轻量级Java EE企业应用实战"));
            books.add(new String("疯狂Java讲义"));
            books.add(new String("疯狂iOS讲义"));
            books.add(new String("疯狂Ajax讲义"));
            books.add(new String("疯狂Android讲义"));
            // 统计书名包含“疯狂”子串的图书数量
            System.out.println(books.stream()
                .filter(ele->((String)ele).contains("疯狂"))
                .count()); // 输出4
            // 统计书名包含“Java”子串的图书数量
            System.out.println(books.stream()
                .filter(ele->((String)ele).contains("Java") )
                .count()); // 输出2
            // 统计书名字符串长度大于10的图书数量
            System.out.println(books.stream()
                .filter(ele->((String)ele).length() > 10)
                .count()); // 输出2
            // 先调用Collection对象的stream()方法将集合转换为Stream,
            // 再调用Stream的mapToInt()方法获取原有的Stream对应的IntStream
            books.stream().mapToInt(ele -> ((String)ele).length())
                // 调用forEach()方法遍历IntStream中每个元素
                .forEach(System.out::println);// 输出8  11  16  7  8
        }
    }
    

    Set集合

    Set集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set集合中,添加操作失败,add方法返回false,且新元素不会被加入。

    HashSet类是Set接口的典型实现。HashSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找性能。当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该HashCode值来决定该对象在HashSet中存储位置。如果有两个元素通过equals方法比较返回true,但它们的hashCode()方法返回值不相等,HashSet将会把它们存储在不同位置,也就可以添加成功。

    特点:无序、非同步、可以包含null。
    HashSet判断元素相等:1.equals方法比较相等;2.hashCode()方法返回值相等。所以如果重写equals方法,需要重写hashCode方法。

    import java.util.*;
    // 类A的equals方法总是返回true,但没有重写其hashCode()方法
    class A
    {
        public boolean equals(Object obj)
        {
            return true;
        }
    }
    // 类B的hashCode()方法总是返回1,但没有重写其equals()方法
    class B
    {
        public int hashCode()
        {
            return 1;
        }
    }
    // 类C的hashCode()方法总是返回2,且重写其equals()方法总是返回true
    class C
    {
        public int hashCode()
        {
            return 2;
        }
        public boolean equals(Object obj)
        {
            return true;
        }
    }
    public class HashSetTest
    {
        public static void main(String[] args)
        {
            HashSet books = new HashSet();
            // 分别向books集合中添加两个A对象,两个B对象,两个C对象
            books.add(new A());
            books.add(new A());
            books.add(new B());
            books.add(new B());
            books.add(new C());
            books.add(new C());
            System.out.println(books);//输出2A,2B,1C
        }
    }
    

    重写hashCode的基本规则:

    • 同一个对象多次调用hashCode方法应该返回相同的值
    • 两个对象equal方法返回true。则hashCode应返回相等的值
    • 用作equal方法比较标准的实例变量,都应该用于计算hashCode值。

    程序把可变对象添加到HashSet中之后,尽量不要修改参与运算的hashCode()、equals()的实例变量,否则会导致HashSet无法正确操作这些集合元素。因为对象的Hashcode改变了,实际存储的位置并没有变化,无法对其进行准确的索引。

    import java.util.*;
    class R
    {
        int count;
        public R(int count)
        {
            this.count = count;
        }
        public String toString()
        {
            return "R[count:" + count + "]";
        }
        public boolean equals(Object obj)
        {
            if(this == obj)
                return true;
            if (obj != null && obj.getClass() == R.class)
            {
                R r = (R)obj;
                return this.count == r.count;
            }
            return false;
        }
        public int hashCode()
        {
            return this.count;
        }
    }
    public class HashSetTest2
    {
        public static void main(String[] args)
        {
            HashSet hs = new HashSet();
            hs.add(new R(5));
            hs.add(new R(-3));
            hs.add(new R(9));
            hs.add(new R(-2));
            // 打印HashSet集合,集合元素没有重复
            System.out.println(hs);
            // 取出第一个元素
            Iterator it = hs.iterator();
            R first = (R)it.next();
            // 为第一个元素的count实例变量赋值
            first.count = -3;     
            // 再次输出HashSet集合,集合元素有重复元素
            System.out.println(hs);
            // 删除count为-3的R对象
            hs.remove(new R(-3));    
            // 可以看到被删除了一个R元素
            System.out.println(hs);
            System.out.println("hs是否包含count为-3的R对象?"
                + hs.contains(new R(-3))); // 输出false
            System.out.println("hs是否包含count为-2的R对象?"
                + hs.contains(new R(-2))); // 输出false
        }
    }
    

    LinkedHashSet
    LinkedHashSet根据元素的hashCode来决定元素的存储位置,但同时使用链表维护元素的次序。当遍历LinkedHashSet集合里的元素时,将会按照元素的添加顺序来访问集合里的元素。因为需要维护元素的插入顺序,性能略低于HashSet的性能,但在迭代访问Set里的全部元素时有很好的性能。

    import java.util.*;
    public class LinkedHashSetTest
    {
        public static void main(String[] args)
        {
            LinkedHashSet books = new LinkedHashSet();
            books.add("疯狂Java讲义");
            books.add("轻量级Java EE企业应用实战");
            System.out.println(books);//与添加顺序一致
            // 删除 疯狂Java讲义
            books.remove("疯狂Java讲义");
            // 重新添加 疯狂Java讲义
            books.add("疯狂Java讲义");
            System.out.println(books);
        }
    }
    

    TreeSet
    TreeSet是SortedSet接口的实现类,可以确保集合元素处于排序状态。

    import java.util.*;
    public class TreeSetTest
    {
        public static void main(String[] args)
        {
            TreeSet nums = new TreeSet();
            // 向TreeSet中添加四个Integer对象
            nums.add(5);
            nums.add(2);
            nums.add(10);
            nums.add(-9);
            // 输出集合元素,看到集合元素已经处于排序状态
            System.out.println(nums);
            // 输出集合里的第一个元素
            System.out.println(nums.first()); // 输出-9
            // 输出集合里的最后一个元素
            System.out.println(nums.last());  // 输出10
            // 返回小于4的子集,不包含4
            System.out.println(nums.headSet(4)); // 输出[-9, 2]
            // 返回大于5的子集,如果Set中包含5,子集中还包含5
            System.out.println(nums.tailSet(5)); // 输出 [5, 10]
            // 返回大于等于-3,小于4的子集。
            System.out.println(nums.subSet(-3 , 4)); // 输出[2]
        }
    }
    

    TreeSet采用红黑树的数据结构对元素进行排序。TreeSet支持两种排序方法:自然排序和定制排序。
    1. 自然排序
    TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按照升序排列。如果试图把一个对象添加到TreeSet时,则该对象必须实现Compareable接口的compareTo(Object obj)方法,否则抛出异常。

    import java.util.*;
    class Err{}
    public class TreeSetErrorTest
    {
        public static void main(String[] args)
        {
            TreeSet ts = new TreeSet();
            // 向TreeSet集合中添加两个Err对象
            ts.add(new Err());
            ts.add(new Err());  //Err没有实现compareTo方法,引发ClassCastException
        }
    }
    

    大部分类在实现CompareTo方法时,需要将被比较对象强制转换成相同类型。因此向TreeSet中添加的应该是同一个类的对象,否则引发ClassCastException。

    import java.util.*;
    public class TreeSetErrorTest2
    {
        public static void main(String[] args)
        {
            TreeSet ts = new TreeSet();
            // 向TreeSet集合中添加两个对象
            ts.add(new String("疯狂Java讲义"));
            ts.add(new Date());   // 会引发异常
        }
    }
    

    如果希望TreeSet能正常运作,TreeSet只能添加同一种类型的对象。
    TreeSet判断对象相等:通过compareTo方法比较是否返回0

    import java.util.*;
    class Z implements Comparable
    {
        int age;
        public Z(int age)
        {
            this.age = age;
        }
        // 重写equals()方法,总是返回true
        public boolean equals(Object obj)
        {
            return true;
        }
        // 重写了compareTo(Object obj)方法,总是返回1,总是认为不相等
        public int compareTo(Object obj)
        {
            return 1;
        }
    }
    public class TreeSetTest2
    {
        public static void main(String[] args)
        {
            TreeSet set = new TreeSet();
            Z z1 = new Z(6);
            set.add(z1);
            // 第二次添加同一个对象,输出true,表明添加成功
            System.out.println(set.add(z1));    //true
            // 下面输出set集合,将看到有两个元素
            System.out.println(set);
            // 修改set集合的第一个元素的age变量
             ((Z)(set.first())).age = 9;
            // 输出set集合的最后一个元素的age变量,将看到也变成了9
            System.out.println(((Z)(set.last())).age);
        }
    }
    

    注意equals方法应该和compareTo方法有一致的结果。

    一旦改变了TreeSet集合里可变元素的实例变量,导致它的与其他对象的大小顺序发生改变,但TreeSet不会调整他们的 顺序,可能导致compareTo返回0。当试图删除该对象时,删除会失败,可以删除没有被修改的实例变量、切不与被修改的实例变量重复的变量。执行一次成功删除之后TreeSet会对集合中的元素重新索引,接下来就可以删除TreeSet中的所有元素了。

    import java.util.*;
    class R implements Comparable
    {
        int count;
        public R(int count)
        {
            this.count = count;
        }
        public String toString()
        {
            return "R[count:" + count + "]";
        }
        // 重写equals方法,根据count来判断是否相等
        public boolean equals(Object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if(obj != null && obj.getClass() == R.class)
            {
                R r = (R)obj;
                return r.count == this.count;
            }
            return false;
        }
        // 重写compareTo方法,根据count来比较大小
        public int compareTo(Object obj)
        {
            R r = (R)obj;
            return count > r.count ? 1 :
                count < r.count ? -1 : 0;
        }
    }
    public class TreeSetTest3
    {
        public static void main(String[] args)
        {
            TreeSet ts = new TreeSet();
            ts.add(new R(5));
            ts.add(new R(-3));
            ts.add(new R(9));
            ts.add(new R(-2));
            // 打印TreeSet集合,集合元素是有序排列的
            System.out.println(ts);    // -3 -2 5 9
            // 取出第一个元素
            R first = (R)ts.first();
            // 对第一个元素的count赋值
            first.count = 20;
            // 取出最后一个元素
            R last = (R)ts.last();
            // 对最后一个元素的count赋值,与第二个元素的count相同
            last.count = -2;
            // 再次输出将看到TreeSet里的元素处于无序状态,且有重复元素
            System.out.println(ts);   // 20 -2 5 -2
            // 删除实例变量被改变的元素,删除失败
            System.out.println(ts.remove(new R(-2)));   // false
            System.out.println(ts);//20 -2 5 -2
            // 删除实例变量没有被改变的元素,删除成功
            System.out.println(ts.remove(new R(5)));    // true,之后会重新索引,可以删除所有元素了
            System.out.println(ts);// 20 -2 -2
        }
    }
    

    2. 定制排序
    如果需要实现定制排序,例如降序,需要在创建TreeSet集合对象时,提供一个Comparator对象与该TreeSet集合关联,由该Comparator对象负责集合元素的排序逻辑。可以用Lambda表达式代替Comparator对象,这时候无需实现Comparable接口。

    import java.util.*;
    class M
    {
        int age;
        public M(int age)
        {
            this.age = age;
        }
        public String toString()
        {
            return "M[age:" + age + "]";
        }
    }
    public class TreeSetTest4
    {
        public static void main(String[] args)
        {
            // 此处Lambda表达式的目标类型是Comparator
            TreeSet ts = new TreeSet((o1 , o2) ->
            {
                M m1 = (M)o1;
                M m2 = (M)o2;
                // 根据M对象的age属性来决定大小,age越大,M对象反而越小
                return m1.age > m2.age ? -1
                    : m1.age < m2.age ? 1 : 0;
            });
            ts.add(new M(5));
            ts.add(new M(-3));
            ts.add(new M(9));
            System.out.println(ts);
        }
    }
    

    List集合

    List集合代表一个元素有序可重复的集合,集合中每个元素都有其对应的顺序索引。List作为Collection接口的子接口,与Set集合相比多了根据索引插入、替换、删除集合元素的方法。

    import java.util.*;
    public class ListTest
    {
        public static void main(String[] args)
        {
            List books = new ArrayList();
            // 向books集合中添加三个元素
            books.add(new String("轻量级Java EE企业应用实战"));
            books.add(new String("疯狂Java讲义"));
            books.add(new String("疯狂Android讲义"));
            System.out.println(books);
            // 将新字符串对象插入在第二个位置
            books.add(1 , new String("疯狂Ajax讲义"));
            for (int i = 0 ; i < books.size() ; i++ )
            {
                System.out.println(books.get(i));
            }
            // 删除第三个元素
            books.remove(2);
            System.out.println(books);
            // 判断指定元素在List集合中位置:输出1,表明位于第二位
            System.out.println(books.indexOf(new String("疯狂Ajax讲义"))); //①
            //将第二个元素替换成新的字符串对象
            books.set(1, new String("疯狂Java讲义"));
            System.out.println(books);
            //将books集合的第二个元素(包括)
            //到第三个元素(不包括)截取成子集合
            System.out.println(books.subList(1 , 2));
        }
    }
    

    List判断对象相等:equals方法返回true即可。

    import java.util.*;
    class A
    {
        public boolean equals(Object obj)
        {
            return true;
        }
    }
    public class ListTest2
    {
        public static void main(String[] args)
        {
            List books = new ArrayList();
            books.add(new String("轻量级Java EE企业应用实战"));
            books.add(new String("疯狂Java讲义"));
            books.add(new String("疯狂Android讲义"));
            System.out.println(books);
            // 删除集合中A对象,将导致第一个元素被删除
            books.remove(new A());     // ①
            System.out.println(books);
            // 删除集合中A对象,再次删除集合中第一个元素
            books.remove(new A());     // ②
            System.out.println(books);
        }
    }
    

    List常用的默认方法sort()需要一个Comparator对象控制秩序,可使用Lambda表达式作为参数;默认方法replaceAll()需要一个函数式接口的UnaryOperator来替换所有集合元素,也可用Lambda表达式作为参数。

    import java.util.*;
    public class ListTest3
    {
        public static void main(String[] args)
        {
            List books = new ArrayList();
            // 向books集合中添加4个元素
            books.add(new String("轻量级Java EE企业应用实战"));
            books.add(new String("疯狂Java讲义"));
            books.add(new String("疯狂Android讲义"));
            books.add(new String("疯狂iOS讲义"));
            // 使用目标类型为Comparator的Lambda表达式对List集合排序
            books.sort((o1, o2)->((String)o1).length() - ((String)o2).length());
            System.out.println(books);
            // 使用目标类型为UnaryOperator的Lambda表达式来替换集合中所有元素
            // 该Lambda表达式控制使用每个字符串的长度作为新的集合元素
            books.replaceAll(ele->((String)ele).length());
            System.out.println(books); // 输出[7, 8, 11, 16]
    
        }
    }
    

    Set只提供一个iterator()方法,List提供了listIterator()方法,该方法返回一个ListIterator对象,ListIterator接口继承了Iterator接口。

    import java.util.*;
    public class ListIteratorTest
    {
        public static void main(String[] args)
        {
            String[] books = {
                "疯狂Java讲义", "疯狂iOS讲义",
                "轻量级Java EE企业应用实战"
            };
            List bookList = new ArrayList();
            for (int i = 0; i < books.length ; i++ )
            {
                bookList.add(books[i]);
            }
            ListIterator lit = bookList.listIterator();
            while (lit.hasNext())
            {
                System.out.println(lit.next());
                lit.add("-------分隔符-------");
            }
            System.out.println("=======下面开始反向迭代=======");
            while(lit.hasPrevious())
            {
                System.out.println(lit.previous());
            }
        }
    }
    
    

    ArrayList和Vector
    两个类都是基于数组实现的List类,所以都封装了动态的可以再分配的Object[]数组。ArrayList对象和Vector对象使用initialCapacity参数来设置数组的长度。添加元素超出数组长度时,initialCapacity会自动增加。
    Vector提供了一个子类Stack,这也是一个古老的集合,线程安全,性能较差,尽量少用。
    区别在于:ArrayList时线程不安全的,Vector是线程安全的。所以Vector性能比ArrayList性能要低。
    Vector提供了Stack子类,用于模拟后进先出(LIFO)的“栈”这种数据结构。同样线程安全、性能较差。如果需要实现“栈”,可以使用ArrayDeque。

    固定长度的List
    介绍数组时的Arrays类提供了asList(Object a)方法,该方法可以把一个数组或者指定个数对象转换成一个List集合,这个List集合既不是ArrayList实现类的实例,也不是Vector实现类的实例,而是Arrays的内部类ArrayList的实例。Arrays.ArrayList是一个固定长度的List集合,程序只能遍历访问该集合里的元素,不可增加、删除该集合里的元素。

    import java.util.*;
    public class FixedSizeList
    {
        public static void main(String[] args)
        {
            List fixedList = Arrays.asList("疯狂Java讲义"
                , "轻量级Java EE企业应用实战");
            // 获取fixedList的实现类,将输出Arrays$ArrayList
            System.out.println(fixedList.getClass());
            // 使用方法引用遍历集合元素
            fixedList.forEach(System.out::println);
            // 试图增加、删除元素都会引发UnsupportedOperationException异常
            fixedList.add("疯狂Android讲义");
            fixedList.remove("疯狂Java讲义");
        }
    }
    

    Queue集合

    Queue用于模拟“先进先出”(FIFO)队列这种数据结构,通常队列不允许随机访问队列中的元素。Queue接口有一个PriorityQueue实现类和一个Deque接口,后者既可以当成队列使用,也可以当栈使用。

    PriorityQueue实现类
    比较标准的队列实现类--保存队列元素的顺序按照队列元素的大小重新排列。这是违反队列的先进先出的原则的

    import java.util.*;
    public class PriorityQueueTest
    {
        public static void main(String[] args)
        {
            PriorityQueue pq = new PriorityQueue();
            // 下面代码依次向pq中加入四个元素
            pq.offer(6);
            pq.offer(-3);
            pq.offer(20);
            pq.offer(18);
            // 输出pq队列,并不是按元素的加入顺序排列
            System.out.println(pq); // 输出[-3, 6, 20, 18]
            // 访问队列第一个元素,其实就是队列中最小的元素:-3
            System.out.println(pq.poll());
        }
    }
    

    没有从小到大排序是因为受了toString方法返回值的影响。如果是使用poll()方法,则严格从小到大。
    PriorityQueue两种排序方式:自然排序、定制排序,详细参考TreeSet。

    Deque接口和ArrayDeque实现类
    Deque接口代表一个双端队列,还可以当成栈使用。Deque接口提供了典型的实现类ArrayDeque,这是基于数组实现的双端队列。
    ArrayDeque可作为“栈”,也可作为“队列”
    注意:ArrayList和ArrayDeque两个集合类的实现机制基本相似,底层都采用一个动态的、可重分配的Object[]数组来存储集合元素,当集合元素超出了数组的容量时,系统会在底层重新分配一个Object[]数组来存储元素。
    ArrayDeque作为栈:

    import java.util.*;
    public class ArrayDequeStack
    {
        public static void main(String[] args)
        {
            ArrayDeque stack = new ArrayDeque();
            // 依次将三个元素push入"栈"
            stack.push("疯狂Java讲义");
            stack.push("轻量级Java EE企业应用实战");
            stack.push("疯狂Android讲义");
            // 输出:[疯狂Android讲义, 轻量级Java EE企业应用实战, 疯狂Java讲义]
            System.out.println(stack);
            // 访问第一个元素,但并不将其pop出"栈",输出:疯狂Android讲义
            System.out.println(stack.peek());
            // 依然输出:[疯狂Android讲义, 疯狂Java讲义, 轻量级Java EE企业应用实战]
            System.out.println(stack);
            // pop出第一个元素,输出:疯狂Android讲义
            System.out.println(stack.pop());
            // 输出:[轻量级Java EE企业应用实战, 疯狂Java讲义]
            System.out.println(stack);
        }
    }
    

    ArrayDeque作为队列

    import java.util.*;
    public class ArrayDequeQueue
    {
        public static void main(String[] args)
        {
            ArrayDeque queue = new ArrayDeque();
            // 依次将三个元素加入队列
            queue.offer("疯狂Java讲义");
            queue.offer("轻量级Java EE企业应用实战");
            queue.offer("疯狂Android讲义");
            // 输出:[疯狂Java讲义, 轻量级Java EE企业应用实战, 疯狂Android讲义]
            System.out.println(queue);
            // 访问队列头部的元素,但并不将其poll出队列"栈",输出:疯狂Java讲义
            System.out.println(queue.peek());
            // 依然输出:[疯狂Java讲义, 轻量级Java EE企业应用实战, 疯狂Android讲义]
            System.out.println(queue);
            // poll出第一个元素,输出:疯狂Java讲义
            System.out.println(queue.poll());
            // 输出:[轻量级Java EE企业应用实战, 疯狂Android讲义]
            System.out.println(queue);
        }
    }
    

    LinkedList实现类
    这是一个List接口的实现类,所以可以根据索引来随机访问集合中的元素。除此之外还实现了Deque接口,可作为双端队列。双端队列栈的头部和尾部分别对应队列的头部和尾部。

    import java.util.*;
    public class LinkedListTest
    {
        public static void main(String[] args)
        {
            LinkedList books = new LinkedList();
            // 将字符串元素加入队列的尾部
            books.offer("book1");
            books.offer("boook2");
            // 将一个字符串元素加入栈的顶部
            books.push("book3");
            books.push("book4");
            // 将字符串元素添加到队列的头部(相当于栈的顶部)
            books.offerFirst("book5");
            books.offerFirst("book6");
            // 以List的方式(按索引访问的方式)来遍历集合元素
            for (int i = 0; i < books.size() ; i++ )
            {
                System.out.println("遍历中:" + books.get(i));
            }
            // 访问、并不删除栈顶的元素 book6
            System.out.println(books.peekFirst());
            // 访问、并不删除队列的最后一个元素 book2
            System.out.println(books.peekLast());
            // 将栈顶的元素弹出“栈”  book6
            System.out.println(books.pop());
            // 下面输出将看到队列中第一个元素被删除
            System.out.println(books);
            // 访问、并删除队列的最后一个元素book2
            System.out.println(books.pollLast());
            
            System.out.println(books);
        }
    }
    

    LinkedList与ArrayList、ArrayDeque的实现机制完全不同。后两者内部以数组形式来保存集合中的元素,因此随机访问集合元素时性能较好;LinkedList内部以链表的形式来保存集合中的元素,因此随机访问集合中的元素时性能较差,但在插入、删除元素时性能较好。Vector和后两者都是以数组形式存储元素,,而Vector实现了线程安全,所以性能较差。

    各种线性表的性能分析
    数组一块连续内存区来保存所有的数组元素,所以在随机访问时性能最好,所有的内部以数组作为底层实现的集合随机访问时性能都比较好。而内部以链表作为底层实现的集合在执行插入、删除操作时性能较好。
    List集合使用的几点建议

    • 需要遍历List集合元素时,对于ArrayList、Vector集合,应该使用随机访问方法来遍历集合元素;对于LinkedList应该采用迭代器来遍历。
    • 需要经常执行插入、删除来改变包含大量数据的List集合的大小,可考虑使用LinkedList集合。使用ArrayList需要重新分配内部数组的大小,效果比较差。
    • 如果有多个线程需要同时访问List集合中的元素,开发者可考虑使用Collection将集合包装成线性安全的集合。

    Map集合

    Map用于保存具有映射关系的数据,包含两组值,一组是key,一组是value。key不允许重复。Java源码中先实现了Map,然后通过包装一个所有value都为null的Map实现了Set集合。Map中包括一个内部类Entry,该类封装了一个key-value对。

    import java.util.*;
    public class MapTest
    {
        public static void main(String[] args)
        {
            Map map = new HashMap();
            // 成对放入多个key-value对
            map.put("疯狂Java讲义" , 109);
            map.put("疯狂iOS讲义" , 10);
            map.put("疯狂Ajax讲义" , 79);
            // 多次放入的key-value对中value可以重复
            map.put("轻量级Java EE企业应用实战" , 99);
            // 放入重复的key时,新的value会覆盖原有的value
            // 如果新的value覆盖了原有的value,该方法返回被覆盖的value
            System.out.println(map.put("疯狂iOS讲义" , 99)); // 输出10
            System.out.println(map); // 输出的Map集合包含4个key-value对
            // 判断是否包含指定key
            System.out.println("是否包含值为 疯狂iOS讲义 key:"
                + map.containsKey("疯狂iOS讲义")); // 输出true
            // 判断是否包含指定value
            System.out.println("是否包含值为 99 value:"
                + map.containsValue(99)); // 输出true
            // 获取Map集合的所有key组成的集合,通过遍历key来实现遍历所有key-value对
            for (Object key : map.keySet() )
            {
                // map.get(key)方法获取指定key对应的value
                System.out.println(key + "-->" + map.get(key));
            }
            map.remove("疯狂Ajax讲义"); // 根据key来删除key-value对。
            System.out.println(map); // 输出结果不再包含 疯狂Ajax讲义=79 的key-value对
        }
    }
    
    import java.util.*;
    public class MapTest2
    {
        public static void main(String[] args)
        {
            Map map = new HashMap();
            // 成对放入多个key-value对
            map.put("疯狂Java讲义" , 109);
            map.put("疯狂iOS讲义" , 99);
            map.put("疯狂Ajax讲义" , 79);
            // 尝试替换key为"疯狂XML讲义"的value,由于原Map中没有对应的key,
            // 因此对Map没有改变,不会添加新的key-value对
            map.replace("疯狂XML讲义" , 66);
            System.out.println(map);
            // 使用原value与参数计算出来的结果覆盖原有的value
            map.merge("疯狂iOS讲义" , 10 ,
                (oldVal , param) -> (Integer)oldVal + (Integer)param);
            System.out.println(map); // "疯狂iOS讲义"的value增大了10
            // 当key为"Java"对应的value为null(或不存在时),使用计算的结果作为新value
            map.computeIfAbsent("Java" , (key)->((String)key).length());
            System.out.println(map); // map中添加了 Java=4 这组key-value对
            // 当key为"Java"对应的value存在时,使用计算的结果作为新value
            map.computeIfPresent("Java",
                (key , value) -> (Integer)value * (Integer)value);
            System.out.println(map); // map中 Java=4 变成 Java=16
        }
    }
    

    HashMap和Hashtable实现类
    HashMap和Hashtable类似于ArrayList和Vector的关系,Hashtable是一个古老的Map类。类似Vector尽量少用Hashtable,可以通过Collections工具类把HashMap变成线程安全的。
    区别:

    • Hashtable线程安全,HashMap线程不安全。HashMap性能更高。多个线程访问同一个Map对象时使用Hashtable
    • Hashtable不允许使用null作为key或者value,HashMap可以使用null作为key或者value
    import java.util.*;
    public class NullInHashMap
    {
        public static void main(String[] args)
        {
            HashMap hm = new HashMap();
            // 试图将两个key为null的key-value对放入HashMap中
            hm.put(null , null);
            hm.put(null , null);    // 无法放入
            // 将一个value为null的key-value对放入HashMap中
            hm.put("a" , null);    // ②
            // 输出Map对象
            System.out.println(hm);
        }
    }
    

    HashMap、Hashtable判断key相等:两个key通过equal方法返回true;两个key的hashCode值相等。
    HashMap、Hashtable判断value相等:两个对象通过equal方法返回true。

    import java.util.*;
    class A
    {
        int count;
        public A(int count)
        {
            this.count = count;
        }
        // 根据count的值来判断两个对象是否相等。
        public boolean equals(Object obj)
        {
            if (obj == this)
                return true;
            if (obj != null && obj.getClass() == A.class)
            {
                A a = (A)obj;
                return this.count == a.count;
            }
            return false;
        }
        // 根据count来计算hashCode值。
        public int hashCode()
        {
            return this.count;
        }
    }
    class B
    {
        // 重写equals()方法,B对象与任何对象通过equals()方法比较都返回true
        public boolean equals(Object obj)
        {
            return true;
        }
    }
    public class HashtableTest
    {
        public static void main(String[] args)
        {
            Hashtable ht = new Hashtable();
            ht.put(new A(60000) , "疯狂Java讲义");
            ht.put(new A(87563) , "轻量级Java EE企业应用实战");
            ht.put(new A(1232) , new B());
            System.out.println(ht);
            // 只要两个对象通过equals比较返回true,
            // Hashtable就认为它们是相等的value。
            // 由于Hashtable中有一个B对象,
            // 它与任何对象通过equals比较都相等,所以下面输出true。
            System.out.println(ht.containsValue("测试字符串")); // 输出true
            // 只要两个A对象的count相等,它们通过equals比较返回true,且hashCode相等
            // Hashtable即认为它们是相同的key,所以下面输出true。
            System.out.println(ht.containsKey(new A(87563)));   // 输出true
            // 下面语句可以删除最后一个key-value对
            ht.remove(new A(1232));
            System.out.println(ht);
        }
    }
    

    尽量不要使用可变对象作为HashMap、Hashtable的key,如果确实需要,尽量不要在程序中修改作为key的可变对象。

    LinkedHashMap实现类
    LinkedHashMap也是用双向链表来维护key-value对的次序,该链表负责维护Map的迭代顺序,其余key-value对的插入顺序保持一致。因为需要维护元素的插入顺序,性能不如HashMap,但在迭代访问Map里的全部元素时将有较好的性能。

    import java.util.*;
    public class HashMapErrorTest
    {
        public static void main(String[] args)
        {
            HashMap ht = new HashMap();
            // 此处的A类与前一个程序的A类是同一个类
            ht.put(new A(60000) , "疯狂Java讲义");
            ht.put(new A(87563) , "轻量级Java EE企业应用实战");
            // 获得Hashtable的key Set集合对应的Iterator迭代器
            Iterator it = ht.keySet().iterator();
            // 取出Map中第一个key,并修改它的count值
            A first = (A)it.next();
            first.count = 87563;   
            // 输出{A@1560b=疯狂Java讲义, A@1560b=轻量级Java EE企业应用实战}
            System.out.println(ht);
            // 只能删除没有被修改过的key所对应的key-value对
            ht.remove(new A(87563));
            System.out.println(ht);
            // 无法获取剩下的value,下面两行代码都将输出null。
            System.out.println(ht.get(new A(87563)));   //输出null
            System.out.println(ht.get(new A(60000)));   //输出null
        }
    }
    

    LinkedHashMap实现类
    使用双向链表维护key-value对。因为需要维护元素的插入顺序,性能低于HaspMap。

    import java.util.*;
    public class LinkedHashMapTest
    {
        public static void main(String[] args)
        {
            LinkedHashMap scores = new LinkedHashMap();
            scores.put("语文" , 80);
            scores.put("英文" , 82);
            scores.put("数学" , 76);
            // 调用forEach方法遍历scores里的所有key-value对
            scores.forEach((key, value) -> System.out.println(key + "-->" + value));
        }
    }
    

    使用Properties读写属性文件
    Properties类是Hashtable类的子类,该对象在处理属性文件时特别方便。Properties类可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入属性文件,也可以把属性文件中的属性名=属性值加载到Map对象中。由于属性文件里的属性名、属性值只能是字符串类型,所以Properties里的key、value都是字符串类型

    import java.util.*;
    import java.io.*;
    public class PropertiesTest
    {
        public static void main(String[] args)
            throws Exception
        {
            Properties props = new Properties();
            // 向Properties中增加属性
            props.setProperty("username" , "yeeku");
            props.setProperty("password" , "123456");
            // 将Properties中的key-value对保存到a.ini文件中
            props.store(new FileOutputStream("a.ini")
                , "comment line");   //写入文件
            // 新建一个Properties对象
            Properties props2 = new Properties();
            // 向Properties中增加属性
            props2.setProperty("gender" , "male");
            // 将a.ini文件中的key-value对追加到props2中
            props2.load(new FileInputStream("a.ini") );   //读取
            System.out.println(props2);
        }
    }
    

    SortedMap和TreeMap实现类
    Set接口派生出SortedSet子接口,TreeSet接口也有一个TreeSet实现类。
    Map接口派生出SortedMap子接口,SortedMap接口也有一个TreeMap实现类。
    两种排序方式:自然排序、定制排序,同TreeSet。
    TreeMap判断key相等:两个key通过compareTo方法返回0。但是自定义类时需要equals方法和compareTo方法返回的结果一致。

    import java.util.*;
    class R implements Comparable
    {
        int count;
        public R(int count)
        {
            this.count = count;
        }
        public String toString()
        {
            return "R[count:" + count + "]";
        }
        // 根据count来判断两个对象是否相等。
        public boolean equals(Object obj)
        {
            if (this == obj)
                return true;
            if (obj != null && obj.getClass() == R.class)
            {
                R r = (R)obj;
                return r.count == this.count;
            }
            return false;
        }
        // 根据count属性值来判断两个对象的大小。
        public int compareTo(Object obj)
        {
            R r = (R)obj;
            return count > r.count ? 1 :
                count < r.count ? -1 : 0;
        }
    }
    public class TreeMapTest
    {
        public static void main(String[] args)
        {
            TreeMap tm = new TreeMap();
            tm.put(new R(3) , "轻量级Java EE企业应用实战");
            tm.put(new R(-5) , "疯狂Java讲义");
            tm.put(new R(9) , "疯狂Android讲义");
            System.out.println(tm);
            // 返回该TreeMap的第一个Entry对象
            System.out.println(tm.firstEntry());
            // 返回该TreeMap的最后一个key值
            System.out.println(tm.lastKey());
            // 返回该TreeMap的比new R(2)大的最小key值。
            System.out.println(tm.higherKey(new R(2)));
            // 返回该TreeMap的比new R(2)小的最大的key-value对。
            System.out.println(tm.lowerEntry(new R(2)));
            // 返回该TreeMap的子TreeMap 介于-1--4之间的key
            System.out.println(tm.subMap(new R(-1) , new R(4)));
        }
    }
    

    Map实现类的性能分析
    HashMap和Hashtable实现机制几乎一样,但是后者是线程安全的,HashMap通常比Hashtable快。
    TreeMap比HashMap和Hashtable慢,因为其底层采用红黑树管理key-value对。优势在于:key-value对总是处于有序状态,无须专门进行排序操作。
    以上应该多考虑使用HashMap。
    LinkedHashMap比HashMap慢一点,因为需要维护链表维持添加顺序 。

    操作集合的工具类

    Collections提供了大量方法对Set、List、Map进行排序、查询、修改操作。

    排序

    public class SortTest
    {
        public static void main(String[] args)
        {
            ArrayList nums = new ArrayList();
            nums.add(2);
            nums.add(-5);
            nums.add(3);
            nums.add(0);
            System.out.println(nums); // 输出:[2, -5, 3, 0]
            Collections.reverse(nums); // 将List集合元素的次序反转
            System.out.println(nums); // 输出:[0, 3, -5, 2]
            Collections.sort(nums); // 将List集合元素的按自然顺序排序
            System.out.println(nums); // 输出:[-5, 0, 2, 3]
            Collections.shuffle(nums); // 将List集合元素的按随机顺序排序
            System.out.println(nums); // 每次输出的次序不固定
        }
    }
    

    查找替换

    import java.util.*;
    public class SearchTest
    {
        public static void main(String[] args)
        {
            ArrayList nums = new ArrayList();
            nums.add(2);
            nums.add(-5);
            nums.add(3);
            nums.add(0);
            System.out.println(nums); // 输出:[2, -5, 3, 0]
            System.out.println(Collections.max(nums)); // 输出最大元素,将输出3
            System.out.println(Collections.min(nums)); // 输出最小元素,将输出-5
            Collections.replaceAll(nums , 0 , 1); // 将nums中的0使用1来代替
            System.out.println(nums); // 输出:[2, -5, 3, 1]
            // 判断-5在List集合中出现的次数,返回1
            System.out.println(Collections.frequency(nums , -5));
            Collections.sort(nums); // 对nums集合排序
            System.out.println(nums); // 输出:[-5, 1, 2, 3]
            //只有排序后的List集合才可用二分法查询,输出3
            System.out.println(Collections.binarySearch(nums , 3));
        }
    }
    

    同步控制
    HashSet、TreeSet、ArrayList、ArrayDeque、linkedlist、HashMap、TreeMap都是线程不安全的。Collections提供了许多类方法把他们包装成线程同步集合。

    import java.util.*;
    public class SynchronizedTest
    {
        public static void main(String[] args)
        {
            // 下面程序创建了四个线程安全的集合对象
            Collection c = Collections
                .synchronizedCollection(new ArrayList());
            List list = Collections.synchronizedList(new ArrayList());
            Set s = Collections.synchronizedSet(new HashSet());
            Map m = Collections.synchronizedMap(new HashMap());
        }
    }
    

    设置不可变集合

    import java.util.*;
    public class UnmodifiableTest
    {
        public static void main(String[] args)
        {
            // 创建一个空的、不可改变的List对象
            List unmodifiableList = Collections.emptyList();
            // 创建一个只有一个元素,且不可改变的Set对象
            Set unmodifiableSet = Collections.singleton("疯狂Java讲义");
            // 创建一个普通Map对象
            Map scores = new HashMap();
            scores.put("语文" , 80);
            scores.put("Java" , 82);
            // 返回普通Map对象对应的不可变版本
            Map unmodifiableMap = Collections.unmodifiableMap(scores);
            // 下面任意一行代码都将引发UnsupportedOperationException异常
            unmodifiableList.add("测试元素");   //异常
            unmodifiableSet.add("测试元素");    //异常
            unmodifiableMap.put("语文" , 90);   //异常
        }
    }
    
    

    相关文章

      网友评论

          本文标题:7.Java集合

          本文链接:https://www.haomeiwen.com/subject/agmdtxtx.html