Java讲义第八章学习笔记
chapter 8 Java集合
8.1 类的加载、连接和初始化
集合类主要负责保存、盛装其他数据,因此集合类也称为容器类。所有的集合类都位于java.util包下,后来为了处理多线程环境下的并发安全问题,Java 5还在java.util.concurrent包下提供了一些多线程支持的集合类。
Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。
8.2 Java 11增强的Collection和Iterator接口
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);
}
}
输出结果:
c集合的元素个数为:2 c集合的元素个数为:1 c集合是否包含"孙悟空"字符串:true c集合的元素:[孙悟空, 轻量级java ee企业应用实战] c集合是否包含books集合?false c集合的元素:[孙悟空] c集合里的元素:[] books 集合的元素:[]
--8.2.1 使用Lambda表达式遍历集合
Java 8为Iterable接口新增了一个forEach(Consumer action)默认方法,该方法所需参数的类型是一个函数式接口。正因为Consumer是函数式接口,因此可以使用Lambda表达式来遍历集合元素。
public class CollectionEach {
public static void main(String[] args) {
//创建一个集合
Collection books = new HashSet();
books.add("轻量级java ee企业应用实战");
books.add("疯狂Java讲义");
books.add("疯狂Android讲义");
//调用forEach()方法遍历集合
books.forEach(obj -> System.out.println("迭代集合元素:" + obj));
}
}
运行结果:
迭代集合元素:疯狂Android讲义 迭代集合元素:轻量级java ee企业应用实战 迭代集合元素:疯狂Java讲义
--8.2.2 使用Iterator遍历集合元素
public class IteratorTest {
public static void main(String[] args) {
//创建集合、添加元素的代码与钱一个程序相同
Collection books = new HashSet();
books.add("轻量级java ee企业应用实战");
books.add("疯狂Java讲义");
books.add("疯狂Android讲义");
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 = "测试字符串"; //1
}
System.out.println(books);
}
}
结果:
疯狂Android讲义 轻量级java ee企业应用实战 疯狂Java讲义 [疯狂Android讲义, 轻量级java ee企业应用实战]
从上面代码可以看出,Iterator 仅用于遍历集合,Iterator本身并不提供盛装对象的能力,如果需要创建Iterator对象,则必须有一个被迭代的集合。没有集合的Iterator仿佛无本之木,没有存在价值。
当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传递给了迭代变量,而是把集合元素的值传递给了迭代变量。
当使用Iterator 迭代访问 Collection 集合元素时,Collection 集合里的元素不能被改变,只能通过Iterator 的 remove()方法删除上一次 next()方法返回的集合元素才可以;否则将会引发java.util.ConcurrentModificationException异常。
public class IteratorErrorTest {
public static void main(String[] args) {
Collection books = new HashSet();
books.add("轻量级java ee企业应用实战");
books.add("疯狂Java讲义");
books.add("疯狂Android讲义");
Iterator it = books.iterator();
while (it.hasNext()) {
//it.next()方法返回的数据类型是Object类型,因此需要强制类型转换
String book = (String) it.next();
System.out.println(book);
if (book.equals("疯狂Android讲义")) {
//使用 Iterator 迭代过程中,不可修改集合元素,下面代码引发异常
books.remove(book);
}
}
}
}
运行结果:
疯狂Android讲义
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
at java.util.HashMap$KeyIterator.next(HashMap.java:1469)
at IteratorErrorTest.main(IteratorErrorTest.java:15)
--8.2.3 使用Lambda表达式遍历Iterator
Java 8为Iterator新增了一个forEachRemaining(Consumer action)方法,该方法所需的Consumer参数同样也是函数式接口。当程序调用Iterator的forEachRemaining(Consumeraction)遍历集合元素时,程序会依次将集合元素传给Consumer的accept(T t)方法(该接口中唯一的抽象方法)。
public class IteratorEach {
public static void main(String[] args) {
Collection books = new HashSet();
books.add("轻量级java ee企业应用实战");
books.add("疯狂Java讲义");
books.add("疯狂Android讲义");
Iterator it = books.iterator();
//使用Lambda表达式
it.forEachRemaining(obj -> System.out.println("迭代集合元素:" + obj));
}
}
结果:
迭代集合元素:疯狂Android讲义 迭代集合元素:轻量级java ee企业应用实战 迭代集合元素:疯狂Java讲义
--8.2.4 使用foreach循环遍历集合元素
Java 5提供的foreach循环迭代访问集合元素更加便捷。
public class ForeachTest {
public static void main(String[] args) {
Collection books = new HashSet();
books.add("轻量级java ee企业应用实战");
books.add("疯狂Java讲义");
books.add("疯狂Android讲义");
for (Object obj : books){
//此处的book变量也不是集合元素本身
String book = (String)obj;
System.out.println(book);
if(book.equals("疯狂Android讲义")){
//下面代码会引发ConcurrentModificationException异常
books.remove(book);
}
}
}
}
--8.2.5 使用Predicate操作集合
Java 8为Collection集合新增了一个removeIf(Predicate filter)方法,该方法将会批量删除符合filter条件的所有元素。该方法需要一个Predicate(谓词)对象作为参数,Predicate也是函数式接口,因此可使用Lambda表达式作为参数。
--8.2.6 使用Stream操作集合
public class InputStreamTest {
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);
}
}
public class CollectionStream {
public static void main(String[] args) {
//创建 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(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(System.out::print); //Lambda表达式,方法引用
}
}
输出结果:
4 2 2 8711168
8.3 Set集合
Set集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set集合中,则添加操作失败,add()方法返回false,且新元素不会被加入。
--8.3.1 HashSet类
//类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);
}
}
存储结果:
[B@1, B@1, C@2, A@7f31245a, A@14ae5a5]
上述结果看出 A 类存储两个, B类存储两个,C类只存储一个。
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; //1
//再次输出HashSet集合,集合元素有重复元素
System.out.println(hs);
//删除count为-3的R对象
hs.remove((new R(-3))); //2
//可以看到删除了一个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
}
}
运行结果:
[R[count:-2], R[count:-3], R[count:5], R[count:9]] [R[count:-3], R[count:-3], R[count:5], R[count:9]] [R[count:-3], R[count:5], R[count:9]] hs是否包含count 为-3的R对象?false hs是否包含count 为-2的R对象?false
上面程序提供了R类,R类重写了equals(Object obj)方法和hashCode()方法,这两个方法都是根据R对象的count 实例变量来判断的。上面程序 //1 处改变了Set集合中第一个R对象的count实例变量的值,这将导致该R对象与集合中的其他对象相同。
如运行结果,HashSet集合中第一个元素和第二个元素完全相同,这表明两个元素已经重复。此时HashSet会比较混乱:当试图删除count为 -3 的R对象时,HashSet会计算出该对象的HashCode值,从而找出该对象在集合中的保存位置,然后把此处的对象与count为-3的R对象通过equals()方法进行比较,如果相等则删除该对象 ——HashSet 只有第2个元素才满足该条件(第一个元素实际上保存在count = -2的R对象对应的位置),所以第二个元素被删除。至于第一个count为-3的R对象,他保存在count为-2的R对象对应的位置,但使用equals()方法拿他和count()为-2的R对象比较时有返回false —— 这将导致HashSet 不可能准确访问该元素。
--8.3.2 LinkedHashSet类
HashSet还有一个子类LinkedHashSet,LinkedHashSet集合也是根据元素的hashCode值来决定元素的存储位置,但它同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的。也就是说,当遍历LinkedHashSet集合里的元素时,LinkedHashSet将会按元素的添加顺序来访问集合里的元素。LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问Set里的全部元素时将有很好的性能,因为它以链表来维护内部顺序。
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);
}
}
输出结果:
[疯狂Java讲义, 轻量级java ee企业应用实战] [轻量级java ee企业应用实战, 疯狂Java讲义]
输出LinkedHashSet集合的元素时,元素的顺序总是与添加顺序一致。
--8.3.3 TreeSet类
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]
}
}
与HashSet集合采用hash算法来决定元素的存储位置不同,TreeSet采用红黑树的数据结构来存储集合元素。TreeSet支持两种排序方法:自然排序和定制排序。在默认情况下,TreeSet采用自然排序。
1.自然排序
TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列,这种方式就是自然排序。
Java提供了一个Comparable 接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现该接口的类必须实现该方法,实现了该接口的类的对象就可以比较大小。当一个对象调用该方法与另一个对象进行比较时,例如obj1.compareTo(obj2),如果该方法返回0,则表明这两个对象相等;如果该方法返回一个负整数,则表明obj1 小于 obj2。
如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序将会抛出异常。
public class Err {
public static void main(String[] args) {
TreeSet ts = new TreeSet();
//向TreeSet集合中添加两个Err对象
ts.add(new Err());
ts.add(new Err());
}
}
上面程序试图向TreeSet集合中添加两个Err对象,添加第一个对象时,TreeSet里没有任何元素,所以不会出现任何问题;当添加第二个Err对象时,TreeSet就会调用该对象的compareTo(Object obj)方法与集合中的其他元素进行比较——如果其对应的类没有实现Comparable接口,则会引发ClassCastException异常。
还有一点:大部分类在实现compareTo(Object obj)方法时,都需要将被比较对象obj强制类型转换成相同类型,因为只有相同类的两个实例才会比较大小。
如果向TreeSet中添加一个可变对象后,并且后面程序修改了该可变对象的实例变量,这将导致它与其他对象的大小顺序发生了改变,但TreeSet不会再次调整他们的顺序,甚至可能导致TreeSet 中保存的这两个对象通过compareTo(Object obj)方法比较返回0。
class R1 implements Comparable{
int count;
public R1(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() == R1.class){
R1 r = (R1)obj;
return r.count == this.count;
}
return false;
}
public int compareTo(Object obj) {
R1 r = (R1)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 R1(5));
ts.add(new R1(-3));
ts.add(new R1(9));
ts.add(new R1(-2));
//打印TreeSet 集合,集合元素是有序排列的
System.out.println(ts); //1
//取出第一个元素
R1 first = (R1)ts.first();
//对第一个元素的count赋值,与第二个元素的count相同
first.count = 20;
//取出最后一个元素
R1 last = (R1)ts.last();
//对最后一个元素的count赋值,与第二个元素的count相同
last.count = -2;
//再次输出将看到TreeSet里的元素处于无序状态,且有重复元素
System.out.println(ts); //2
//删除实例变量被改变的元素,删除失败
System.out.println(ts.remove(new R1(-2))); //3
System.out.println(ts);
//删除实例变量没有被改动的元素,删除成功
System.out.println(ts.remove(new R1(5))); //4
System.out.println(ts);
}
}
运行结果:
[R[count:-3], R[count:-2], R[count:5], R[count:9]] [R[count:20], R[count:-2], R[count:5], R[count:-2]] false [R[count:20], R[count:-2], R[count:5], R[count:-2]] true [R[count:20], R[count:-2], R[count:-2]]
通过上面程序得知,尽量不要修改TreeSet,HashSet中的实例变量。
2.定制排序
TreeSet 的自然排序是根据集合元素的大小,TreeSet将他们以升序排序。如果需要实现定制排序,例如以降序排序,则可以通过Comparator接口的帮助。该接口里包含一个 int compare(T o1,T o2)方法,该方法用于比较o1和o2的大小:如果该方法返回正整数,则表明o1等于o2;如果该方法返回负整数,则表明o1小于o2。
如果需要实现定制排序,则需要在创建TreeSet 集表达式来代替Comparator对象。
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);
}
}
运行结果:
[M[age:9], M[age:5], M[age:-3]]
--8.3.4 EnumSet类
enum Season
{
SPRING,SUMMER,FALL,WINTER
}
public class EnumSetTest {
public static void main(String[] args) {
//创建一个EnumSet集合,集合元素就是Season枚举类的全部枚举值
EnumSet es1 = EnumSet.allOf(Season.class);
System.out.println(es1); //输出[SPRING,SUMMER,FALL,WINTER]
//创建一个 EnumSet 空集合,指定其集合元素是Season类的枚举值
EnumSet es2 = EnumSet.noneOf(Season.class);
System.out.println(es2); //输出[]
//手动添加两个元素
es2.add(Season.WINTER);
es2.add(Season.SPRING);
System.out.println(es2); //输出[SPRING,WINTER]
//以指定枚举值创建EnumSet集合
EnumSet es3 = EnumSet.of(Season.SUMMER,Season.WINTER);
System.out.println(es3); //输出[SUMMER,WINTER]
EnumSet es4 = EnumSet.range(Season.SUMMER,Season.WINTER);
System.out.println(es4); //输出[SUMMER,FALL,WINTER]
//新创建的 EnumSet 集合元素和 es4 集合元素有相同的类型
//es5 集合元素 + es4 集合元素 = Season枚举类的全部枚举值 公有
EnumSet es5 = EnumSet.complementOf(es4);
System.out.println(es5); //输出[SPRING]
}
}
复制:
public class EnumSetTest2 {
public static void main(String[] args) {
Collection c = new HashSet();
c.clear();
c.add(Season.FALL);
c.add(Season.SPRING);
//复制Collection集合中的所有元素来创建EnumSet集合
EnumSet enumSet = EnumSet.copyOf(c); //1
System.out.println(enumSet); //输出[SPRING,FALL]
c.add("疯狂Java讲义");
c.add("轻量级 JAVA EE 企业应用实战");
//下面代码出现异常:因为c集合里的元素不是全部都为枚举值
enumSet = EnumSet.copyOf(c); //2
}
}
运行结果:
[SPRING, FALL]
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Enum
at java.util.RegularEnumSet.add(RegularEnumSet.java:36)
at java.util.EnumSet.copyOf(EnumSet.java:179)
at EnumSetTest2.main(EnumSetTest2.java:17)
--8.3.5 各Set实现类的性能分析
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讲义"))); //1
//将第二个元素替换成新的字符串对象
books.set(1,new String("疯狂Java讲义"));
System.out.println(books);
//将books集合的第二个元素(包括)
//到第三个元素(不包括)截取成子集合
System.out.println(books.subList(1,2));
}
}
运行结果:
[轻量级Java EE企业应用实战, 疯狂Java讲义, 疯狂Android讲义] 轻量级Java EE企业应用实战 疯狂Ajax讲义 疯狂Java讲义 疯狂Android讲义 [轻量级Java EE企业应用实战, 疯狂Ajax讲义, 疯狂Android讲义] 1 [轻量级Java EE企业应用实战, 疯狂Java讲义, 疯狂Android讲义] [疯狂Java讲义]
Java 8为List集合增加了sort()和replaceAll()两个常用的默认方法,其中sort()方法需要一个Comparator对象来控制元素排序,程序可使用Lambda表达式来作为参数;而replaceAll()方法则需要一个UnaryOperator来替换所有集合元素,UnaryOperator也是一个函数式接口,因此程序也可使用Lambda表达式作为参数。
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]
}
}
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());
}
}
}
运行结果:
疯狂Java讲义 疯狂IOS讲义 轻量级Java EE企业应用实战 ======下面开始反向迭代======= -------分隔符-------- 轻量级Java EE企业应用实战 -------分隔符-------- 疯狂IOS讲义 -------分隔符-------- 疯狂Java讲义
--8.4.2 ArrayList和Vector实现类
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讲义");
}
}
8.5 Queue集合
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());
}
}
运行结果:
[-3, 6, 20, 18] -3
从上面方法中可以看出,Deque不仅可以当成双端队列使用,而且可以被当成栈来使用,因为该类里还包含了pop(出栈)、push(入栈)两个方法。
Deque的方法与Queue的方法对照表如表8.2所示。
Deque的方法与Stack的方法对照表如表8.3所示。
public class ArrayDequeStack {
public static void main(String[] args) {
ArrayDeque stack = new ArrayDeque();
//依次将三个元素 push 入"栈"
stack.push("疯狂Java讲义");
stack.push("轻量级 Java EE 企业应用实战");
stack.push("疯狂Android讲义");
//输出:[疯狂Java讲义,轻量级 Java EE 企业应用实战,疯狂Android讲义]
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);
}
}
队列:
public class ArrayDequeQueue {
public static void main(String[] args) {
ArrayDeque queue = new ArrayDeque();
//依次将三个元素 push 入队列
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());
//依然输出:[疯狂Android讲义,疯狂Java讲义,轻量级 Java EE 企业应用实战]
System.out.println(queue);
//pop出第一个元素,输出:疯狂Java讲义
System.out.println(queue.poll());
//输出:[轻量级 Java EE 企业应用实战, 疯狂Android讲义]
System.out.println(queue);
}
}
public class LinkListTest {
public static void main(String[] args) {
LinkedList books = new LinkedList();
//将字符串元素加入队列的尾部
books.offer("疯狂Java讲义");
//将一个字符串元素加入到栈的顶部
books.push("轻量级 Java EE 企业应用实战");
//将字符串元素添加到队列头部(相当于栈的顶部)
books.offerFirst("疯狂Android讲义");
//以List的方式 (按索引访问的方式) 来遍历集合元素
for(int i = 0; i < books.size() ; i++){
System.out.println("遍历中:" + books.get(i));
}
//访问并不删除栈顶元素
System.out.println(books.peekFirst());
//访问不删除队列的最后一个元素
System.out.println(books.peekLast());
//将栈顶的元素弹出 "栈"
System.out.println(books.pop());
//下面输出将看到队列中第一个元素被删除
System.out.println(books);
//访问并删除队列的最后一个元素
System.out.println(books.pollLast());
//下面输出:[轻量级 Java EE 企业应用实战]
System.out.println(books);
}
}
输出结果:
遍历中:疯狂Android讲义 遍历中:轻量级 Java EE 企业应用实战 遍历中:疯狂Java讲义 疯狂Android讲义 疯狂Java讲义 疯狂Android讲义 [轻量级 Java EE 企业应用实战, 疯狂Java讲义] 疯狂Java讲义 [轻量级 Java EE 企业应用实战]
对比:
ArrayList、ArrayDeque内部以数组的形式来保存集合中的元素,因此随机访问集合元素时有较好的性能;而LinkedList内部以链表的形式来保存集合中的元素,因此随机访问集合元素时性能较差,但在插入、删除元素时性能比较出色(只需改变指针所指的地址即可)。需要指出的是,虽然Vector也是以数组的形式来存储集合元素的,但因为它实现了线程同步功能(而且实现机制也不好),所以各方面性能都比较差。
--8.5.4 各种线性表的性能分析
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对
}
}
运行结果:
10
{疯狂Ajax讲义=79, 轻量级 Java EE 企业应用实战=99, 疯狂IOS讲义=99, 疯狂Java讲义=109}
是否包含值为 疯狂IOS讲义 key: true
是否包含值为 99 value: true
疯狂Ajax讲义-->79
轻量级 Java EE 企业应用实战-->99
疯狂IOS讲义-->99
疯狂Java讲义-->109
{轻量级 Java EE 企业应用实战=99, 疯狂IOS讲义=99, 疯狂Java讲义=109}
--8.6.1 Java 8为Map新增的方法
- Object compute(Object key, BiFunction remappingFunction)
- Object computeIfAbsent(Object key, Function mappingFunction)
- Object computeIfPresent(Object key, BiFunction remappingFunction)
- void forEach(BiConsumer action)
- Object getOrDefault(Object key, V defaultValue)等。
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
}
}
运行结果:
{疯狂Ajax讲义=79, 疯狂IOS讲义=99, 疯狂Java讲义=109}
{疯狂Ajax讲义=79, 疯狂IOS讲义=109, 疯狂Java讲义=109}
{Java=4, 疯狂Ajax讲义=79, 疯狂IOS讲义=109, 疯狂Java讲义=109}
{Java=16, 疯狂Ajax讲义=79, 疯狂IOS讲义=109, 疯狂Java讲义=109}
--8.6.2 改进的HashMap和Hashtable实现类
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); //1 无法放入
//将一个value 为null值的key-value对放入HashMap中
hm.put("a",null); //2 可以放入
//输出Map对象
System.out.println(hm);
}
}
运行结果:
{null=null, a=null}
为了成功的在HashMap、Hashtable中存储、获取对象,用做key的对象必须实现hashCode()方法和equals()方法。
与HashSet集合不能保证元素的顺序一样,HashMap、Hashtable也不能保证其中key-value对的顺序。类似于HashSet,HashMap,Hashtable判断两个key相等的标准也是:两个key通过equals()方法比较返回true,两个key的hashCode值也相等。
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("测试字符串")); //1输出true
//只要两个A对象的count相等,它们通过equals()方法比较返回true,且hashCode值相等
//Hashtable即认为他们是相同的key,所以下面输出true
System.out.println(ht.containsValue(new A(87563))); //输出true
//下面语句可以删除最后一个 key-value对
ht.remove(new A(1232)); //3
System.out.println(ht);
}
}
运行结果:
{lfw.A@ea60=疯狂Java讲义, lfw.A@1560b=轻量级Java EE企业应用实战, lfw.A@4d0=lfw.B@14ae5a5}
true
true
{lfw.A@ea60=疯狂Java讲义, lfw.A@1560b=轻量级Java EE企业应用实战}
Hashtable判断value相等的标准是:value与另外一个对象通过equals()方法比较返回true即可。
--8.6.3 LinkedHashMap实现类
HashSet有一个LinkedHashSet子类,HashMap也有一个LinkedHashMap子类;LinkedHashMap也使用双向链表来维护key-value对的顺序(其实只需要考虑key的顺序),该链表负责维护Map的迭代顺序,迭代顺序与key-value对的插入顺序保持一致。
LinkedHashMap可以避免对HashMap、Hashtable里的key-value对进行排序(只要插入key-value对时保持顺序即可),同时又可避免使用TreeMap所增加的成本。
LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能;但因为它以链表来维护内部顺序,所以在迭代访问Map里的全部元素时将有较好的性能。
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));
}
}
运行结果:
语文--->80 英文--->82 数学--->76
LinkedHashMapTest可以记住Map添加顺序。
--8.6.4 使用Properties读写属性文件
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"); //1
//新建一个Properties对象
Properties prop2 = new Properties();
//添加属性
prop2.setProperty("gender","male");
//将a.ini文件中的key-value对追加到props2中
prop2.load(new FileInputStream("a.ini")); //2
System.out.println(prop2);
}
}
该程序会在当前目录下生成:{password=123456, gender=male, username=yeeku} a .ini文件
--8.6.5 SortedMap接口和TreeMap实现类
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
System.out.println(tm.subMap(new R(-1),new R(4)));
}
}
运行结果:
{R[count=-5]=疯狂Java讲义, R[count=3]=轻量级Java EE 企业应用实战, R[count=9]=疯狂Android讲义}
R[count=-5]=疯狂Java讲义
R[count=9]
R[count=3]
R[count=-5]=疯狂Java讲义
{R[count=3]=轻量级Java EE 企业应用实战}
--8.6.6 WeakHashMap实现类
WeakHashMap与HashMap的用法基本相似。与HashMap的区别在于,HashMap的key保留了对实际对象的强引用,这意味着只要该HashMap对象不被销毁,该HashMap的所有key所引用的对象就不会被垃圾回收,HashMap也不会自动删除这些key所对应的key-value对;但WeakHashMap的key只保留了对实际对象的弱引用,这意味着如果WeakHashMap对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被垃圾回收,WeakHashMap也可能自动删除这些key所对应的key-value对。
public class WeakHashMapTest {
public static void main(String[] args) {
WeakHashMap whm = new WeakHashMap();
//向WeakHashMap中添加三个 key-value对
//三个key都是匿名字符串对象(没有去其他引用)
whm.put(new String("语文"),new String("良好"));
whm.put(new String("数学"),new String("及格"));
whm.put(new String("英文"),new String("中等"));
//向WeakHashMap中添加一个key-value对
//该key 是一个系统缓存的字符串对象
whm.put("java",new String("中等")); //1
//输出whm对象,将看到4个key-value对
System.out.println(whm);
//通知系统立即进行垃圾回收
System.gc();
System.runFinalization();
//在通常情况下,将只看到一个key-value对
System.out.println(whm);
}
}
运行结果:
{英文=中等, java=中等, 数学=及格, 语文=良好}
{java=中等}
从上面运行结果可以看出,当系统进行垃圾回收时,删除了WeakHashMap对象的前三个key-value对。这是因为添加前三个 key-value对时,这三个key都是匿名的字符串对象,WeakHashMap只保留了对他们的弱引用,这样垃圾回收时会自动删除这三个key-value对。
WeakHashMap对象中第4个组key-value对的key是一个字符串直接量。(系统会自动保留对该字符串对象的强引用),所以垃圾回收时不会回收它。
--8.6.7 IdentityHashMap实现类
public class IdentityHashMapTest {
public static void main(String[] args) {
IdentityHashMap ihm = new IdentityHashMap();
//下面两行代码将会向IdentityHashMap对象中添加两个key-value对
ihm.put(new String("语文"),89);
ihm.put(new String("语文"),78);
//下面两行代码只会向IdentifyHashMap对象中添加一个key-value对
ihm.put("java",93);
ihm.put("java",98);
System.out.println(ihm);
}
}
运行结果:
{语文=89, java=98, 语文=78}
上面程序中前两个key-value是匿名字符串对象,通过==比较不相等。后面两个是常量池管理的字符串直接量,通过==比较返回true。
--8.6.8 EnumMap实现类
enum Season{
SPRING,SUMMER,FALL,WINTER
}
public class EnumMapTest {
public static void main(String[] args) {
//创建 EnumMap 对象,该EnumMap的所有key都是Season枚举类的枚举值
EnumMap enumMap = new EnumMap(Season.class);
enumMap.put(Season.SUMMER,"夏日炎炎");
enumMap.put(Season.SPRING,"春暖花开");
System.out.println(enumMap); //{SPRING=春暖花开, SUMMER=夏日炎炎}
}
}
--8.6.9 各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); //反转
System.out.println(nums);//输出:[0,3,-5,2]
Collections.sort(nums); //自然排序
System.out.println(nums);//输出:[-5,0,2,3]
Collections.shuffle(nums); //随机排序
System.out.println(nums); //每次输出的次序不固定
}
}
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); //排序
System.out.println(nums);//[-5,1,2,3]
//只有排序后的List集合才可用二分法查询,输出 3(索引)
System.out.println(Collections.binarySearch(nums,3));
}
}
public class SynchronizedTest {
public static void main(String[] args) {
//下面程序创建了4个线程安全的集合对象
Collection c = Collections.synchronizedCollection(new ArrayList<>());
List list = Collections.synchronizedList(new ArrayList<>());
Set s = Collections.synchronizedSet(new HashSet<>());
Map m = Collections.synchronizedMap(new HashMap<>());
}
}
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("测试技术");//1
unmodifiableSet.add("测试元素");//2
unmodifiableMap.put("语文",90); //3
}
}