文章目录
第13章、Java类集框架
我们想要自己实现一个良好的数据结构是过于麻烦的,同时在实际的开发中几乎所有项目都会使用到数据结构(核心意义在于解决数组的长度限制问题),所以为了开发者的使用方便,JDK 1.0提供了Vector、Hashtable、Enumeration等常见的数据结构实现类。而从 JDK 1.2开始,Java又进一步完善了自己的可用数据结构操作,提出了完整的类集框架的概念。
13.1 类集框架简介
- 在实际的开发中,一定会存在着需要保存多个对象的操作,而如果使用对象数组来保存,则会有一个问题:数组长度是不可变的。所以为了动态实现多个对象的保存,可以使用链表来实现,但链表这一数据结构的编写会有以下三个问题:
- 由于需要处理大量的引用关系,如果要开发链表工具类,对初学者而言难度较高;
- 为了保证链表在实际的开发中可用,在编写链表实现时必须更多地考虑到性能问题;
- 链表为了可以保存任意对象类型,统一使用了Object类型进行保存。那么所有要保存的对象必须发生向上转型,而在进行对象信息取出时又必须强制性地向下转型操作。如果在一个链表中所保存的数据不是某种类型,这样的操作会带来安全隐患。
- 为解决以上问题,在JDK1.0中,提供了一个与链表类似的工具类——Vector(向量类)。但随着时间推移,这个类并不能很好的描述所需的数结构,所以在JDK1.2又提供了一个专门实现数据结构的开发框架——类集框架(所有的程序接口与类都保存在java.util包中)。在JDK1.5之后,泛型的引入,又解决了所有操作类型都用Object所带来的安全隐患。而JDK1.8又针对类集的大数据的操作环境退出了数据流的分析操作功能(MapReduce操作)。
- 类集在整个Java中最为核心的用处就是在其实现了动态数组的操作,并定义了大量的操作标准。而类集中最核心的接口为:Collection、List、Set、Map、Iterator、Enumeration。
13.2 单对象保存父接口:Collection
-
java.util.Collection是进行单对象保存的最大父接口,即每次利用Collection接口只能保存一个对象信息。
-
java.util.Collection接口定义:
public interface Collection<E> extends Iterable<E>
-
java.util.Collection接口的核心方法:
public boolean add(E e) // 普通,向集合里保存一个元素 public boolean addAll(Collection<? extends E> c) // 普通,追加一个集合 public void clear() // 普通,清空结合,根元素为null public boolean contains(Object o) // 普通,判断是否包含指定内容,需要equals()支持 public boolean isEmpty() // 普通,判断是否为空集合(不是null) public boolean remove(Object o) // 普通,删除对象,需要equals()支持 public int size() // 普通,取得集合中保存的元素个数 public Object[] toArray() // 普通,将集合变为对象数组保存 public Iterator<E> iterator() // 普通,为Iterator(迭代器)接口实例化 default Stream<E> Stream() // 普通,为java.util.stream.Stream接口进行实例化操作
注意contains()和remove()方法的使用需要保证对象的类中覆写了equals()方法。
-
虽然Collection是单对象集合操作的最大父接口,但其存在一个问题:无法区分保存的数据是否重复。所以在实际开发中会使用Collection的两个子接口:List子接口(数据允许重复)和Set子接口(数据不允许重复)。
-
Collection接口与List子接口、Set子接口的继承关系:
13.3 List子接口
-
java.util.List子接口定义:
public interface List<E> extends Collection<E> {}
-
java.util.List子接口的扩充方法:
public E get(int index) // 普通,取得指定索引编号的内容 public E set(int index, E element) // 普通,修改指定索引编号的内容 public ListIterator<E> listIterator // 普通,为ListIterator接口实例化(13.5中讲解)
在使用List接口时可以利用ArrayList或Vector两个子类来实例对象
新的子类:ArrayList
ArrayList子类是List接口中最为常用的一个子类。
-
使用ArrayList子类来实例化List接口对象:
package com.yootk.demo; import java.util.ArrayList; import java.util.List; public class TestDemo { public static void main(String[] args) { // 从JDK 1.5开始应用了泛型,从而保证集合中所有的数据类型都一致 List<String> all = new ArrayList<String>() ; // 实例化List集合 System.out.println("长度:" + all.size() + ",是否为空:" + all.isEmpty()); all.add("Hello"); // 保存数据 all.add("Hello"); // 保存重复元素 all.add("World"); // 保存数据 System.out.println("长度:" + all.size() + ",是否为空:" + all.isEmpty()); // Collection接口定义size()方法取得了集合长度,List子接口扩充get()方法根据索引取得了数据 for (int x = 0; x < all.size(); x++) { String str = all.get(x); // 取得索引数据 System.out.println(str); // 直接输出内容 } } }
本程序通过ArrayList子类实例化了List接口对象,这样就可以使用List接口中定义的方法(包括Collection接口定义的方法)
-
在集合中保存对象:
package com.yootk.demo; import java.util.ArrayList; import java.util.List; class Book { // 创建一个自定义类 private String title; private double price; public Book(String title, double price) { this.title = title; this.price = price; } @Override public boolean equals(Object obj) { // 必须覆写此方法,否则remove()、contains()无法使用 if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof Book)) { return false; } Book book = (Book) obj; if (this.title.equals(book.title) && this.price == book.price) { return true; } return false; } @Override public String toString() { return "书名:" + this.title + ",价格:" + this.price + "\n"; } } public class TestDemo { public static void main(String[] args) { List<Book> all = new ArrayList<Book>(); // List接口对象 all.add(new Book("Java开发实战经典", 79.8)); // 保存自定义类对象 all.add(new Book("Java Web开发实战经典", 69.8)); // 保存自定义类对象 all.add(new Book("Oracle开发实战经典", 89.8)); // 保存自定义类对象 all.remove(new Book("Oracle开发实战经典", 89.8)); // 需要使用equals()方法 System.out.println(all); } } /* 程序执行结果: [书名:"Java开发实战经典", 价格:79.8 书名:"Java Web开发实战经典", 价格:69.8] */
注意:在使用集合保存对象时,如果要使用contains()和remove()方法,需要保证对象的类中覆写了equals()方法
-
实际上在List接口的子类中还有一个LinkedList子类,而ArrayList子类与LinkedList子类的区别为:
- ArrayList中采用顺序式的结果进行数据的保存,并且可以自动生成相应的所以信息。(实现原理类似于C++STL中的vector容器 )
- LinkedList集合保存的是前后元素,也就是说,它的每个节点中保存的是两个元素对象,一个它对应的下一个节点,以及另一个对应的上一个节点,所以LinkedList要占有比ArrayList更多的内存空间。同时LinkedList比ArrayList多实现了一个Queue队列数据接口。(实现原理类似于C++STL中的list容器 )相对于ArrayList有更好的中间插入、删除性能,但随机访问能力不如ArrayList。
旧的子类:Vector
Vector类早在JDK1.0就有了,但在JDK1.2有了类集框架之后,保留了Vector类,并让他实现了List接口。
-
相对于ArrayList子类,Vector子类独有的一个方法:
public Enumeration<E> elements() // 普通,为Enumeration接口实例化
这个方法使得Vector子类可以进行Enumeration(枚举)输出
-
使用Vector子类实例化List接口:
package com.yootk.demo; import java.util.List; import java.util.Vector; public class TestDemo { public static void main(String[] args) { // 由于都是利用子类为父类实例化,所以不管使用哪个子类,List接口功能不变 List<String> all = new Vector<String>() ; // 实例化List集合 System.out.println("长度:" + all.size() + ",是否为空:" + all.isEmpty()); all.add("Hello"); // 保存数据 all.add("Hello"); // 保存重复元素 all.add("World"); // 保存数据 System.out.println("长度:" + all.size() + ",是否为空:" + all.isEmpty()); // Collection接口定义了size()方法取得集合长度,List子接口扩充了get()方法,根据索引取得数据 for (int x = 0; x < all.size(); x++) { String str = all.get(x); // 取得索引数据 System.out.println(str); // 直接输出内容 } } }
-
ArrayList子类与Vector子类的区别:
两个操作类最大的区别在Vector类中的部分方法使用了synchronized关键字声明,所以:
- Vector属于同步处理,而ArrayList为异步处理;
- Vector是线程安全的,而ArrayList为非线程安全的
- 输出:
- Vector:Iterator、ListIterator、foreach、Enumeration
- ArrayList:Iterator、ListIterator、foreach
在实际开发中,往往会优先考虑使用ArrayList子类。
13.4 Set子接口
在Collection接口下又有另外一个子接口——Set子接口,相对于List子接口,Set没有对Collection接口进行大量扩充,而是简单地继承了Collection接口。也就是说Set类没法使用get()、set()等方法操作数据。
HashSet子类与TreeSet子类
-
在Set接口下有两个常用的子类:HashSet、TreeSet
- HashSet子类:散列存放数据
- TreeSet子类:有序存放数据,所以在使用TreeSet子类时,要考虑比较器的使用。
所以如果对排序没有要求,则优先考虑HashSet子类
-
HashSet子类实例化Set接口:
package com.yootk.demo; import java.util.HashSet; import java.util.Set; public class TestDemo { public static void main(String[] args) { Set<String> all = new HashSet<String>(); // 实例化Set接口 all.add("jixianit"); // 保存数据 all.add("mldn"); // 保存数据 all.add("yootk"); // 保存数据 all.add("yootk"); // 重复数据 System.out.println(all); // 直接输出集合 } } // 程序执行结果: [mldn, yook, jixianit]
可以发现输出结果与数据添加顺序无关,是无序的。
-
关于“Hash”的说明:HashSet类中采用了Hash算法(一般称为散列、无序)。这种算法就是利用二进制的计算结果来设置保存的空间,根据数值的不同,最终保存空间的位置也不同,所以利用Hash算法保存的集合都是无序的,但是其查找速度较快。
-
TreeSet子类实例化Set接口:
package com.yootk.demo; import java.util.Set; import java.util.TreeSet; public class TestDemo { public static void main(String[] args) { Set<String> all = new TreeSet<String>(); // 实例化Set接口 all.add("jixianit"); // 保存数据 all.add("yootk"); // 保存数据 all.add("mldn"); // 保存数据 all.add("mldn"); // 重复数据 System.out.println(all); // 直接输出集合 } } // 程序执行结果: [jixianit, mldn, yootk]
TreeSet子类属于有排序的类集结构,对于String类对象的默认排序为按字母升序排列。
关于数据排序的说明
TreeSet子类可以按一定顺序保存内容,这一功能是需要依靠比较器的。所以如果需要使用TreeSet子类保存任意类的对象,则该对象所在的类必须要实现java.lang.Comparable接口。
-
注意:因为TreeSet还需要保证元素不能重复,且实现此功能也需要利用Comparable接口,所以对象所在类在覆写比较器compareTo()时需要比较所有元素。
-
利用TreeSet保存自定义类对象:
package com.yootk.demo; import java.util.Set; import java.util.TreeSet; class Book implements Comparable<Book> { // 需要实现Comparable接口 private String title; private double price; public Book(String title, double price) { this.title = title; this.price = price; } @Override public String toString() { return "书名:" + this.title + ",价格:" + this.price + "\n"; } @Override public int compareTo(Book o) { // 覆写比较器,比较所有属性 if (this.price > o.price) { return 1; } else if (this.price < o.price) { return -1; } else { return this.title.compareTo(o.title); // 调用String类的比较大小 } } } public class TestDemo { public static void main(String[] args) { Set<Book> all = new TreeSet<Book>(); // 实例化Set接口 all.add(new Book("Java开发实战经典", 79.8)); // 保存数据 all.add(new Book("Java开发实战经典", 79.8)); // 全部信息重复 all.add(new Book("JSP开发实战经典", 79.8)); // 价格信息重复 all.add(new Book("Android开发实战经典", 89.8)); // 都不重复 System.out.println(all); } }
关于重复元素的说明
-
TreeSet子类是利用Comparable接口实现重复元素的判断,而HashSet子类想消除重复元素,则必须依靠Object类中的两个方法:
- 取得哈希码:public int hashCode():先判断对象的哈希码是否相同,依靠哈希码取得一个对象的内容。
- 对象比较:public boolean equals(Object obj):再将对象的属性进行一次的比较。
即:HashSet保存的对象所在的类必须覆写hashCode()和equals()两方法
-
以上方法简单举例来说就是:如果要核查一个人的信息,肯定先要通过身份证编号查找到这个编号的信息(hashCode()方法负责编号),再利用此身份证信息与个人信息进行比较(equals()进行属性的比较)后才可以确定。
-
利用HashSet子类保存自定义类对象:
package com.yootk.demo; import java.util.Set; import java.util.HashSet; class Book { private String title; private double price; public Book(String title, double price) { this.title = title; this.price = price; } @Override public int hashCode() { //覆写hashCode() final int prime = 31; int result = 1; long temp; temp = Double.doubleToLongBits(price); result = prime * result + (int) (temp ^ (temp >>> 32)); result = prime * result + ((title == null) ? 0 : title.hashCode()); return result; } @Override public boolean equals(Object obj) { // 覆写equals() if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Book other = (Book) obj; if (Double.doubleToLongBits(price) != Double.doubleToLongBits(other.price)) return false; if (title == null) { if (other.title != null) return false; } else if (!title.equals(other.title)) return false; return true; } @Override public String toString() { // 覆写toString() return "书名:" + this.title + ",价格:" + this.price + "\n"; } } public class TestDemo { public static void main(String[] args) { Set<Book> all = new HashSet<Book>(); // 实例化Set接口 all.add(new Book("Java开发实战经典", 79.8)); // 保存数据 all.add(new Book("Java开发实战经典", 79.8)); // 全部信息重复 all.add(new Book("JSP开发实战经典", 79.8)); // 价格信息重复 all.add(new Book("Android开发实战经典", 89.8)); // 都不重复 System.out.println(all); } }
-
使用IDE中自动生成hashCode()和equals()方法:以IDEA为例,直接在类中输入hashCode和equals回车就会出现自动生成的hashCode()和equals()方法。注意在select all non-null fields 处不打勾。
13.5 集合输出
在Java中,集合的输出操作有4种形式:Iterator、ListIterator、foreach、Enumeration
迭代输出:Iterator
Iterator(迭代器)是集合输出操作中最常用的接口,且在Collection接口中也提供了直接为Iterator接口实例化方法:iterator(),所以在任何集合类型中都可以使用Iterator接口输出。
-
java.util.Iterator接口的定义:
public interface Iterator<E> {}
-
java.util.Iterator接口中的方法:
public boolean hasNext() // 普通,判断当前层是否有内容 public E next() // 普通,取出当前内容,并使当前指针指向下一层 public void remove() // 普通,删除该层的内容,JDK1.8之前 default void remove() // 普通,删除该层的内容,JDK1.8之后
指针默认指向集合顶端,使用hasNext()会先判断这一层是否存在内容,如果存在则返回true。而next()则是取出当前指针指向的内容,每取出一个,指针指向下一层。
-
实例化Iterator接口一般方式:
Iterator<E> iter = collection.iterator();
-
在11章中的Scanner类其实也是Iterator接口的子类,所以Scanner使用时才需要先利用hasNext()判断是否有数据,在利用next()取得数据。
-
使用Iterator输出集合:
package com.yootk.demo; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class TestDemo { public static void main(String[] args) { List<String> all = new ArrayList<String>() ; // 实例化List集合 all.add("Hello"); // 保存数据 all.add("Hello"); // 保存重复元素 all.add("World"); // 保存数据 Iterator<String> iter = all.iterator() ; // 实例化Iterator接口 while (iter.hasNext()) { // 判断是否有数据 String str = iter.next() ; // 取出当前数据 System.out.println(str); // 输出数据 } } }
-
之所以提供Iterator接口中的remove()方法,是因为在使用Iterator输出数据时,如果使用集合类(Collection、List、Set)提供的remove()方法会导致程序终端执行的结果,而非要进行集合元素的删除,只能使用Iterator接口提供的remove()方法才可以正常完成。
package com.yootk.demo; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class TestDemo { public static void main(String[] args) { List<String> all = new ArrayList<String>() ; // 实例化List集合 all.add("Hello"); // 保存重复元素 all.add("mldn"); // 保存数据 all.add("World"); // 保存数据 all.add("Yootk"); // 保存数据 Iterator<String> iter = all.iterator() ; // 实例化Iterator接口 while (iter.hasNext()) { // 判断是否有数据 String str = iter.next() ; // 取出当前数据 if ("mldn".equals(str)) { all.remove(str) ; // 此代码一执行,输出将中断 } System.out.println(str); // 输出数据 } } } /* 程序执行结果: Hello mldn Exception in thread "main" java.util. */
本程序没有正常完成输出,因为在迭代输出时进行了集合数据的错误删除操作。要避免此类问题,需要使用Iterator接口提供的remove()方法。
-
使用Iterator接口的remove()方法删除元素:
package com.yootk.demo; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class TestDemo { public static void main(String[] args) { List<String> all = new ArrayList<String>() ; // 实例化List集合 all.add("Hello"); // 保存重复元素 all.add("mldn"); // 保存数据 all.add("World"); // 保存数据 all.add("Yootk"); // 保存数据 Iterator<String> iter1 = all.iterator() ; // 实例化Iterator接口 while (iter1.hasNext()) { // 判断是否有数据 String str = iter1.next() ; // 取出当前数据 if ("mldn".equals(str)) { iter1.remove(); // 执行并不会中断程序,且会完整输出所有元素 } System.out.print(str + "、"); // 输出数据 } System.out.println(); Iterator<String> iter2 = all.iterator() ; // 再次实例化Iterator接口 while (iter2.hasNext()) { // 判断是否有数据 String str = iter2.next() ; // 取出当前数据 System.out.print(str + "、"); // 输出数据 } } } /* 程序执行结果: Hello、mldn、World、Yootk、 Hello、World、Yootk */
可以发现使用在进行迭代输出时使用Iterator接口的remove()方法删除元素不会使程序中断,且会完整输出所有元素,也就是说虽然有一个元素被删,但它在本轮的迭代输出中还是存在的。但再进行一次迭代输出就会发现该元素已被删除。
双向迭代:ListIterator
-
Iterator存在一个小问题,即:只能进行由前往后的输出。所以为了更加灵活的输出,在类集框架中就提供了一个ListIterator接口,利用该接口可以实现双向迭代。ListIterator属于Iterator的子接口。
-
java.util.ListIterator接口定义:
public interface ListIterator<E> extends Iterator<E> {}
-
java.util.ListIterator接口常用方法:
public boolean hasPrevious() // 普通,判断是否有前一个元素 public E previous() // 普通,取出前一个元素 public void add(E e) // 普通,向集合追加数据 public void set(E e) // 普通,修改集合数据
ListIterator接口除了可以继续使用Iterator接口中的hasNext()与next()方法来向后迭代,还能向前迭代操作(hasPrevious()与previous())。不仅如此还能增加和修改元素。
-
注意:ListIterator是专门为List接口定义的输出接口,所以ListIrterator接口的实例化可以依靠List接口提供的方法:
public ListIterator<E> listIterator() // 普通,为ListIterator接口实例化
-
完成双向迭代:
package com.yootk.demo; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; public class TestDemo { public static void main(String[] args) { List<String> all = new ArrayList<String>() ; // 实例化List接口对象 all.add("www.jixianit.com") ; // 向集合保存数据 all.add("www.yootk.com") ; // 向集合保存数据 all.add("www.mldn.cn") ; // 向集合保存数据 System.out.print("由前向后输出:"); // 向集合保存数据 ListIterator<String> iter = all.listIterator() ;// 实例化ListIterator接口 while (iter.hasNext()) { // 由前向后迭代 String str = iter.next() ; // 取出当前数据 System.out.print(str + "、"); // 输出数据 } System.out.print("\n由后向前输出:"); while (iter.hasPrevious()) { // 由后向前迭代 String str = iter.previous() ; // 取出当前数据 System.out.print(str + "、"); // 输出数据 } } } /* 程序执行结果: 由前向后输出:www.jixianit.com、www.yootk.com、www.mldn.cn 由后向前输出:www.mldn.cn、www.yootk.com、www.jixianit.com */
-
注意:ListIterator接口并不常用,在实际开发中大多数情况进行集合输出都是使用Iterator接口。
foreach输出
-
使用foreach也可实现集合的输出:
package com.yootk.demo; import java.util.ArrayList; import java.util.List; public class TestDemo { public static void main(String[] args) { List<String> all = new ArrayList<String>() ; // 实例化List接口对象 all.add("www.jixianit.com") ; // 向集合保存数据 all.add("www.yootk.com") ; // 向集合保存数据 all.add("www.mldn.cn") ; // 向集合保存数据 // 集合中包含的数据都是String型,所以需要使用String接收集合中的每一个数据 for (String str : all) { // for循环输出 System.out.println(str); } } }
Enumeration输出
-
Enumeration(枚举输出)是与Vector类一起在JDK1.0时推出的输出接口,即最早的Vector如果要输出数据,就需要使用Enumeration接口完成。
-
java.util.Enumeration接口定义:
public interface Enumeration<E> { public boolean hasMoreElements(); // 普通,判断是否有元素,等同于hasNext() public E nextElement(); // 普通,取出当前元素,并使指针指向下一个,等同于next() }
-
注意:Enumeration接口的实例化要依靠Vector类中的elements()方法完成:
public Enumeration<E> elements() // 普通,为Enumeration接口实例化
-
利用Enumeration接口输出数据:
package com.yootk.demo; import java.util.Enumeration; import java.util.Vector; public class TestDemo { public static void main(String[] args) { Vector<String> all = new Vector<String>() ; // 实例化Vector子类对象 all.add("www.jixianit.com") ; // 向集合保存数据 all.add("www.yootk.com") ; // 向集合保存数据 all.add("www.mldn.cn") ; // 向集合保存数据 Enumeration<String> enu = all.elements() ; // 取得Enumeration接口对象 while(enu.hasMoreElements()) { // 判断是否有数据 String str = enu.nextElement() ; // 取出当前数据 System.out.println(str); // 输出数据 } } }
本程序与Iterator接口输出实现的效果完全一样。
13.6 偶对象保存:Map接口
Collection每次只能保存一个对象,所以属于单值保存父接口。而类集中又提供了偶对象的集合:Map集合,利用Map集合可以保存一对关联数据(安照”key = value“的形式)。如图所示为Collection与Map保存的区别:
-
java.util.Map接口定义:
public interface Map<K,V> {}
-
java.util.Map接口常用方法:
public V put(K key, V value) // 普通,向集合中保存元素 public V get(Object key) // 普通,根据key查找对应的value数据,如果key不存在,则返回null public Set<Map.Entry<K,V>> entrySet() // 普通,将Map集合转换为Set集合 public Set<K> keySet() // 普通,取出全部的key,以Set集合形式返回
HashMap子类与Hashtable子类
在Map接口中存在两个常用的子类:HashMap子类与Hashtable子类
HashMap子类
-
观察HashMap子类的使用:
package com.yootk.demo; import java.util.HashMap; import java.util.Map; public class TestDemo { public static void main(String[] args) { Map<String, Integer> map = new HashMap<String, Integer>(); // 定义Map集合 map.put("壹", 1); // 保存数据 map.put("贰", 2); // 保存数据 map.put("叄", 3); // 保存数据 map.put("叄", 33); // key数据重复 map.put("空", null); // value为null map.put(null, 0) ; // key为null System.out.println(map); // 输出全部map集合 } } // 程序输出结果: {贰=2, null=0, 叄=33, 壹=1, 空=null}
使用HashMap子类实例化Map。
-
通过以上程序可以得出Map的一些特点:
- 使用HashMap定义的Map集合是无序存放的(与输入顺序无关)
- 如果发现重复的key会进行覆盖
- 使用HashMap子类保存数据时key或value可以保存为null
-
其实Map集合注意功能不是输出,而是使用get()方法通过key查找对应的value数据:
package com.yootk.demo; import java.util.HashMap; import java.util.Map; public class TestDemo { public static void main(String[] args) { Map<String, Integer> map = new HashMap<String, Integer>(); // 定义Map集合 map.put("壹", 1); // 保存数据 map.put("贰", 2); // 保存数据 map.put("叄", 3); // 保存数据 map.put("叄", 33); // key数据重复 map.put("空", null); // value为null map.put(null, 0) ; // key为null System.out.println(map.get("壹")); // key存在返回value System.out.println(map.get("陸")); // 如果key不存在,返回null System.out.println(map.get(null)); // key存在 } } /* 程序执行结果: 1 null 0 */
Hashtable子类
Hashtable子类是JDK1.0时提供的,在JDK1.2提供类集框架后,Hashtable子类实现了Map接口,从而得以保存下来。
- Hashtable子类与HashMap子类的区别:
- 性能:Hashtable子类采用同步处理,HashMap子类采用异步处理
- 数据安全:Hashtable子类线程安全,HashMap子类采用非线程安全
- 能否保持null:Hashtable子类保存的key和value都不允许为null,Hashtable子类保存的key和value都允许为null
- 在开发中优先考虑使用HashMap子类
利用Iterator输出Map集合
-
在一般的开发原则下,集合的输出要利用Iterator接口完成。但是Map接口与Collection接口不同,Map接口在定义时没有继承Iterator接口,也没有提供直接获取Ite瑞安桐人接口对象的方法。所以要想使用Iterator输出Map接口的数据,就必须清楚Collection接口与Map接口在数据保存形式上的区别:
-
通过上图可发现,Collection集合保存数据都是直接保存的,而Map集合保存数据时,会先把key和value自动包装成Map.Entry接口的对象。也就是说要使用Iterator接口输出Map数据,其实每次使用next()返回的是Map.Entry接口的对象。
-
java.util.Map.Entry接口的定义:
public static interface Map.Entry<K, V> {}
其实Map.Entry接口是定义在Map接口内部的一个static内部接口(相当于外部接口)。
-
java.util.Map.Entry接口的常用方法:
public K getKey() // 普通,取得数据中的key public V getValue() // 普通,取得数据中的value
public V setValue(V value) // 普通,修改数据中的value
~~~
-
实现Map接口输出的关键在Map接口中定义的一个方法:
public Set<Map.Entry<K, V>> entrySet() // 普通,将Map集合转换为Set集合
-
使用Iterator输出Map集合的操作过程:
- 利用entrySet()方法将Map接口数据转换为Set接口数据,此时Set接口中的数据类型为Map.Entry<K, V>
- 利用Set接口中的iterator()方法将Set集合转换为Iterator接口实例
- 利用Iterator接口进行迭代输出,每次迭代取得的数据都是Map.Entry接口的实例,再利用此接口的方法将key和value分离
-
利用Iterator实现Map接口的输出:
package com.yootk.demo; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import java.util.Set; public class TestDemo { public static void main(String[] args) { Map<String, Integer> map = new Hashtable<String, Integer>(); // 定义Map集合 map.put("壹", 1); // 保存数据 map.put("贰", 2); // 保存数据 map.put("叄", 3); // 保存数据 map.put("叄", 33); // key数据重复 // 将Map集合变为Set集合,目的是使用iterator()方法,注意泛型的统一 Set<Map.Entry<String,Integer>> set = map.entrySet() ; Iterator<Map.Entry<String,Integer>> iter = set.iterator() ; // 取得Iterator实例 while (iter.hasNext()) { // 迭代输出 Map.Entry<String, Integer> me = iter.next() ; // 取出Map.Entry System.out.println(me.getKey() + " = " + me.getValue()); // 输出数据 } } }
自定义Map集合的key类型
Map集合的key和value可以是任意类型,但当使用自定义类作为Map集合的key时,该自定义类必须覆写Object类中的hashCode()与equals()两个方法,因为只有靠这两个方法才能确保key不重复。
- 具体类的定义见“13.4 Set子接口-关于重复元素的说明”中的代码。
- 虽然Map集合中的key可以为任意类型,但在实际开发中,一般不建议使用自定义类作为key,其中使用String作为key是最为常见。
13.7 Stack子类
Stack(栈)是一种动态对象数组,采用的是一种先进后出的数据形式。
-
java.util.Stack类的定义:
public class Stack<E> extends Vector<E> {}
注意:虽然Stack类是Vector类的子类,但在进行Stack操作时一般不会使用Vector类定义的方法。
-
java.util.Stack类的常用方法:
public E push(E item) // 普通,数据入栈 public E pop() // 普通,数据出栈,如果没有数据在会抛出空栈异常(EmptyStackException)
-
观察栈的操作:
package com.yootk.demo; import java.util.Stack; public class TestDemo { public static void main(String[] args) { Stack<String> all = new Stack<String>(); all.push("www.jixianit.com") ; all.push("www.yootk.com") ; all.push("www.mldn.cn") ; System.out.println(all.pop()); System.out.println(all.pop()); System.out.println(all.pop()); System.out.println(all.pop()); // 抛出异常:EmptyStackException } }
13.8 Properties子类
在Map集合中,key和value可以设置为任意数据类型,虽然这样比较灵活,但在某些开发中并不适用。所以在类集框架中就提供了一个Properties子类,此子类只能保存字符串类型的数据。
-
java.util.Properties子类的定义:
public class Properties extends Hashtable<Object, Object>
注意:虽然Properties是Hashtable的子类,但由于Properties类都使用String数据类型进行操作,所以Properties类一般只使用本类所定义的方法。
-
java.util.Properties子类的常用方法:
public Properties() // 构造,实例化Properties对象 public Object setProperty(String key, String value) // 普通,设置属性 public String getProperty(String key) // 普通,取得属性,如果key不存在则返回null public String getProperty(String key, String defaultValue) // 普通,取得属性,取过key不存在则返回默认值defaultValue public void store(OutputStream out, String comments) throws IOException // 普通,通过输出流保存保存属性内容,输出的同时可以设置注释消息 public void load(InputStream inStream) throws IOException // 普通,通过输入流读取属性内容
-
属性的基本操作:
package com.yootk.demo; import java.util.Properties; public class TestDemo { public static void main(String[] args) { Properties pro = new Properties(); // 实例化类对象 pro.setProperty("BJ", "北京"); // 保存属性信息 pro.setProperty("TJ", "天津"); // 保存属性信息 System.out.println(pro.getProperty("BJ")); // 根据key取得属性信息 System.out.println(pro.getProperty("GZ")); // 根据key取得属性信息 System.out.println(pro.getProperty("GZ", "没有此记录")); // 没有key返回默认值 } } /* 程序输出结果: 北京 null 没有此记录 */
-
属性信息的IO操作:
package com.yootk.demo; import java.io.File; import java.io.FileOutputStream; import java.util.Properties; public class TestDemo { public static void main(String[] args) throws Exception { Properties pro1 = new Properties(); // 实例化类对象 pro1.setProperty("BJ", "北京"); // 保存属性信息 pro1.setProperty("TJ", "天津"); // 保存属性信息 // 一般而言后缀可以随意设置,但是标准来讲,既然是属性文件,后缀就必须是*.properties,这样做也是为了与国际化对应 // 在进行属性信息保存时如果属性内容为中文则会自动进行转码操作 File file1 = new File("E:" + File.separator + "area.properties"); OutputStream out = new FileOutputStream(file1); pro1.store(out, "Area Info"); Properties pro2 = new Properties(); File file2 = new File("E:" + File.separator + "area.properties"); InputStream input = new FileInputStream(file2); pro2.load(input); System.out.println(pro2.getProperty("BJ")); } } /* 文件内容: #Area Info #Fri Apr 16 20:45:57 CST 2021 BJ=\u5317\u4EAC TJ=\u5929\u6D25 程序执行结果: 北京 */
13.9 Collections工具类
为了方法用户使用,Java专门提供了一个集合的工具类——Collections类,这个工具类可以实现List、Set、Map、集合的操作。
-
java.util.Collections类的定义:
public class Collections {}
-
java.util.Collections类的常用方法:
public static <T> boolean addAll(Collection<? super T> c, T... elements) // 普通,实现集合数据追加,可一次追加多个数据 public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) // 普通,使用二分查找法查找集合数据 public static <T> void copy(List<? super T> dest, List<? extends T> src) // 普通,复制集合 public static void reverse(List<?> list) // 普通,集合反转 public static <T extends Comparable<? super T>> void sort(List<T> list) // 普通,集合排序
-
例:
package com.yootk.demo; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class TestDemo { public static void main(String[] args) throws Exception { List<String> all = new ArrayList<String>(); // 实例化集合对象 // 利用Collections类的方法向集合保存多个数据 Collections.addAll(all, "jixianit", "mldn", "yootk", "mldnjava", "lixinghua"); Collections.reverse(all); // 集合反转 System.out.println(all); // 直接输出集合对象 } } /* 程序执行结果: [lixinghua, mldnjava, yootk, mldn, jixianit] */
-
Collection与Collections的区别:
- Collection是进行单对象保存的最大父接口,包含List和Set两个子接口。
- Collections是集合操作的工具类,可以直接利用类中的方法操作List、Set、Map、等集合的数据。
13.10 数据流
从JDK1.8开始,Java开始更多的融合大数据的操作模式,其中最具代表性的就是提供了Stream接口,同时利用Stream接口可以实现MapReduce操作。
数据流基础操作
-
从JDK1.8开始会发现在整个类集中所提供的接口都出现了大量的default或static方法。在Iterable接口中(Collection的父接口)定义了一个方法以实现集合的输出:
default void forEach(consumer<? super T> action) // 普通,主要是设置输出的位置
本方法需要传递一个消费型的函数式接口,主要是设置输出的位置,例如,在屏幕上显示集合的内容,可以进行System.out.println()方法的引用。
-
利用forEach()方法输出:
package com.yootk.demo; import java.util.ArrayList; import java.util.List; public class TestDemo { public static void main(String[] args) throws Exception { List<String> all = new ArrayList<String>(); // 实例化List集合接口 all.add("www.JIXIANIT.com"); // 保存数据 all.add("www.yootk.com"); // 保存数据 all.add("www.mldn.cn"); // 保存数据 all.forEach(System.out::println); // 引用forEach输出 } } /* 程序执行结果: www.JIXIANIT.com www.yootk.com www.mldn.cn */
本程序并没有使用Iterator接口,但是由于使用消费型功能函数,所以只能实现输出操作,而不能针对每个数据进行处理。因此,forEach()方法对实际的开发并没有实际的用处。
-
使用 Iterator 接口输出数据的目的是在每次迭代操作时都可以针对集合的每一个数据进行处理。所以在JDK 1.8中为了简化集合数据处理的操作,专门提供了一个数据流操作接口:java.util.stream.Stream,这个类可以利用Collection接口提供的default型方法实现Stream接口的实例化操作:
default Steam<E> stream() // 普通,为java.util.stream.Stream接口进行实例化操作
-
java.util.stream.Stream接口定义:
public interface Stream<T> extends BaseStream<T, Stream<T>> {}
-
java.util.stream.Stream接口中的常用方法:
public long count() // 普通,返回元素个数 public Stream<T> distinct() // 普通,消除重复元素 public <R,A> R collect(Collector<? super T,A,R> collector) // 普通,利用收集器接收处理后的数据 public Stream<T> filter(Predicate<? super T> predicate) // 普通,数据过滤(设置断言型函数式接口) public <R> Stream<R> map(Function<? super T,?extends R> mapper) // 普通,数据处理操作(设置功能型函数式接口) public Stream<T> skip(long n) // 普通,设置跳过的数据行数 public Stream<T> limit(long maxSize) // 普通,设置取出的数据个数 public boolean allMatch(Predicate<? super T> predicate) // 普通,数据查询,要求全部匹配 public boolean anyMatch(Predicate<? super T> predicate) // 普通,数据查询,匹配任意一个 default Predicate<T> or(Predicate<? super T> other) // 普通,断言型函数式接口的或操作 default Predicate<T> and(Predicate<? super T> other) // 普通,断言型函数式接口的与操作
其中collect()方法主要进行数据的手机操作,同时收集完成数据可以通过Collectors类中方法设置返回的集合类型,例如:使用toList()方法返回List集合,使用toSet()方法可以返回Set集合。
-
取消集合中的重复数据,并指定输出:(distinct,skip,limit,collect函数)
package com.yootk.demo; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class TestDemo { public static void main(String[] args) throws Exception { List<String> all = new ArrayList<String>(); // 实例化List集合接口 all.add("www.JIXIANIT.com"); all.add("www.yootk.com"); all.add("www.yootk.com"); all.add("www.mldn.cn"); all.add("www.qq.com"); all.add("www.baidu.com"); Stream<String> stream = all.stream() ; // 取得Stream类的对象 // 去掉重复数据后形成新的List集合数据,里面不包含重复内容的集合 List<String> newAll = stream.distinct().collect(Collectors.toList()) ; newAll.forEach(System.out :: println); // 取得消除重复数据后的内容 } }
-
在调用filter()之前先调用map()对数据进行处理:
package com.yootk.demo; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class TestDemo { public static void main(String[] args) throws Exception { List<String> all = new ArrayList<String>(); // 实例化List集合接口 all.add("www.JIXIANIT.com"); all.add("www.yootk.com"); all.add("www.yootk.com"); all.add("www.mldn.cn"); all.add("www.mldn.cn"); Stream<String> stream = all.stream() ; // 取得Stream类的对象 // 去掉重复元素后执行数据过滤操作,在对集合中数据操作时将每条数据统一转为小写 // 在过滤中由于需要断言型函数式接口,所以引用contains()方法,此时只判断小写字母 // 将满足过滤条件的数据利用收集器保存在新的List集合中 List<String> newAll = stream.distinct().map((x) -> x.toLowerCase()). filter((x) -> x.contains("t")).collect(Collectors.toList()); newAll.forEach(System.out :: println); // 取得消除重复数据后的内容 } } /* 程序执行结果: www.jixianit.com www.yootk.com */
该程序处理流程图:
-
断言型函数式接口的与或操作:
package com.yootk.demo; import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; import java.util.stream.Stream; public class TestDemo { public static void main(String[] args) throws Exception { List<String> all = new ArrayList<String>(); // 实例化List集合接口 all.add("www.JIXIANIT.com"); all.add("www.yootk.com"); all.add("www.yootk.com"); all.add("www.mldn.cn"); all.add("www.mldn.cn"); Predicate<String> p1 = (x) -> x.contains("yootk") ; // 断言型接口方法引用 Predicate<String> p2 = (x) -> x.contains("mldn") ; // 断言型接口方法引用 Stream<String> stream = all.stream() ; // 取得Stream类的对象 if (stream.anyMatch(p1.or(p2))) { // 两个条件有一个满足即可 System.out.println("数据存在!"); } } }
MapReduce
-
MapReduce是一种进行大数据操作的开发模型,在Stream数据流中也提供了类似的实现。
-
java.util.stream.Stream接口实现与MapDuduce功能:
public <R> Stre<R> map(Function<? super T,? extends R> mapper) // 普通,数据处理方法 public Option<T> reduce(BinaryOperator<T> accumulator) // 普通,数据分析方法 public DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper) // 普通,按照Double进行数据处理 public IntStream mapToInt(ToIntFunction<? super T> mapper) // 普通,按照Int进行数据处理 public LongStream mapToInt(ToLongFunction<? super T> mapper) // 普通,按照Long进行数据处理
BinaryOperator是Bifunction的子接口,也属于功能型函数式接口。在这个接口中定义了一个apply()方法(public R apply(T t, U u))
-
下面模拟一个网站用户购买数据的记录分析,为了可以保存相关的数据记录,可以将购买的商品信息保存在一个Orders类中。
-
使用map和reduce实例:
package com.yootk.demo; import java.util.ArrayList; import java.util.List; class Orders { private String pname ; // 商品名称 private double price ; // 商品单价 private int amount ; // 购买数量 public Orders(String pname,double price,int amount) { this.pname = pname ; this.price = price ; this.amount = amount ; } public String getPname() { return pname; } public int getAmount() { return amount; } public double getPrice() { return price; } } public class TestDemo { public static void main(String[] args) throws Exception { List<Orders> all = new ArrayList<Orders>(); all.add(new Orders("Java开发实战经典", 79.8, 200)); // 添加购买记录 all.add(new Orders("JavaWeb开发实战经典", 69.8, 500)); // 添加购买记录 all.add(new Orders("Android开发实战经典", 89.8, 300)); // 添加购买记录 all.add(new Orders("Oracle开发实战经典", 99.0, 800)); // 添加购买记录 double allPrice = all.stream(). map((x) -> x.getAmount() * x.getPrice()). // 用于实现每件商品总价的计算 reduce((sum, m) -> sum + m).get(); // 输出每一个商品的总价 System.out.println("购买图书总价:" + allPrice); } } // 程序执行结果: 购买图书总价:157000.0
-
Stream、DoubleStream、IntStream、LongStream接口的继承关系:
-
DoubleStream、IntStream、LongStream接口中分别提供以下数据统计的操作方法:
- DoubleStream提供的数据统计方法:public DoubleSummaryStatistics summaryStatistics()
- IntStream提供的数据统计方法:public IntSummaryStatistics summaryStatistics()
- LongStream提供的数据统计方法:public LongSummaryStatistics summaryStatistics()
-
以DoubleStream接口中返回的DoubleSummaryStatistics类为例,在此类中提供了以下数据统计方法:
public final double getSum() // 普通,求和 public final double getAverage() // 普通,求平均值 public final long getCount() // 普通,求数量 public final double getMax() // 普通,求最大值 public final double getMin() // 普通,求最小值
-
以DoubleStream为例:
public class TestDemo { public static void main(String[] args) throws Exception { List<Orders> all = new ArrayList<Orders>(); all.add(new Orders("Java开发实战经典", 79.8, 200)); // 添加购买记录 all.add(new Orders("JavaWeb开发实战经典", 69.8, 500)); // 添加购买记录 all.add(new Orders("Android开发实战经典", 89.8, 300)); // 添加购买记录 all.add(new Orders("Oracle开发实战经典", 99.0, 800)); // 添加购买记录 DoubleSummaryStatistics dss = all.stream() .mapToDouble((sc) -> sc.getAmount() * sc.getPrice()) // 数据处理 .summaryStatistics(); // 进行数据统计 System.out.println("商品个数:" + dss.getCount()); System.out.println("总花费:" + dss.getSum()); System.out.println("平均花费:" + dss.getAverage()); System.out.println("最高花费:" + dss.getMax()); System.out.println("最低花费:" + dss.getMin()); } } /* 程序执行结果: 商品个数:4 总花费:15700.0 平均花费:39250.0 最高花费:79200.0 最低花费:15960.0 */
本章小结
- 类集的目的是创建动态的对象数组操作。
- Collection接口是类集中最大单值操作的父接口,但是一般开发中不会直接使用此接口,而常使用List或 Set接口。
- List接口扩展了Collection接口,里面的内容是允许重复的。
- List 接口的常用子类是 ArrayList和Vector。在开发中 ArrayList性能较高,属于异步处理;而Vector性能较低,属于同步处理。
- Set 接口与Collection接口的定义一致,里面的内容不允许重复,依靠 Object类中的equals()和hashCode()方法来区分是否是同一个对象。
- Set接口的常用子类是 HashSet和TreeSet,HashSet 是散列存放,没有顺序;TreeSet是顺序存放,使用Comparable进行排序操作。
- 集合的输出要使用Iterator接口完成,Iterator属于迭代输出接口。
- 在JDK 1.5之后集合也可以使用foreach的方式输出。
- Enumeration属于最早的迭代输出接口,现在基本上很少使用,在类集中 Vector类可以使用Enumeration接口进行内容的输出。
- List集合的操作可以使用ListIterator接口进行双向的输出操作。
- Map 接口可以存放一对内容,所有的内容以“key = value”的形式保存,每一对“key =value”都是一个Map.Entry对象的实例。
- Map中的常用子类是 HashMap、Hashtable。HashMap属于异步处理,性能较高;Hashtable属于同步处理,性能较低。
- 类集中提供了Collections工具类完成类集的相关操作。
- Stack类可以完成先进后出的操作。
- Properties类属于属性操作类,使用属性操作类可以直接操作属性文件。