JavaSE第四十六讲:迭代器、TreeSet及Comparator深度剖析

1. 理解上一讲所讲内容,hashCode与equals()的关系,hashCode()方法一般是在元素放入集合中才调用的,因为集合很经常使用,很多相同的元素都会放在集合中进行统一的管理。

结合上一讲内容

	public static void main(String[] args) {
		HashSet set = new HashSet();
		set.add(new People("zhangsan"));
		set.add(new People("lisi"));
		set.add(new People("zhangsan"));
		System.out.println(set);
		}

【说明】:如上程序,我们只是将其Set中的这些对象的引用打印出来,set会调用toString()方法进行打印,但是在实际开发中,一般很少这样输出引用的内容,我们更多的是需要打印出往Set添加的内容打印出来。

那这种情况我们应该如何处理呢,查看JDK Doc文档中的HashSet方法,它没有像ArrayList和LinkedList中提供get()方法,要如何取呢?

这时就引用到了iterator()方法。iterator[迭代器],这里iterator涉及到一种设计模式叫做迭代器模式。

查看JDK中的HashSet下的iterator()

iterator
public Iterator<E> iterator()
Returns an iterator over the elements in this set. The elements are returned in no particular order.

java.util 
Interface Iterator<E>

[iterator()方法是Interator接口的类型,但并不是说它是返回一个接口,只是返回实现这个接口的类的实例,但是具体是那一个类我们无从得知,我们只知道可以通过这个接口的方法去操纵返回回来的对象,这就是多态]

查看这个接口的方法,如图46-1所示


                          图46-1:Iterator接口的方法

这三个方法我们经常使用,请查看这个方法的详细介绍

首先调用hasNext()方法判断是否有下一个元素,如果有下一个元素,则返回真,并且调用next()方法将这个元素取出,同时指针转移到下一个元素的首端进行再判断,如此重复迭代,直到元素取完为止。这两个方法一般搭配使用,具体过程如下图46-2所示:


    图46-2:迭代器的工作原理

这是一个循环的过程,当没有下一个元素时,hasnext()方法判断没有下一个元素,返回false,则就不会调用next()方法去取元素了,循环完成。

    在通过迭代函数访问类集之前,必须得到一个迭代函数。每一个Collection类都提供一个iterator()函数,该函数返回一个对类集头的迭代函数。通过使用这个迭代函数对象,可以访问类集中的每一个元素,一次一个元素。通常,使用迭代函数循环通过类集的内容,步骤如下

1. 通过调用类集的iterator()方法获得对类集头的迭代函数。
2. 建立一个调用hasNext()方法的循环,只要hasNext()返回true,就进行循环迭代。
3. 在循环内部,通过调用next()方法来得到每一个元素

写一个迭代器程序如下:

package com.ahuier2;

import java.util.HashSet;
import java.util.Iterator;

public class IteratorTest {
	public static void main(String[] args) {
		HashSet set = new HashSet();
		set.add("a");
		set.add("b");
		set.add("c");
		set.add("d");
		set.add("e");
		
		Iterator iter = set.iterator();
		while(iter.hasNext()){
			String value = (String)iter.next(); //next()函数调用后返回的是Object类型,要向下类型转化为String类型
			System.out.println(value);	
		}
	}
}
编译执行结果:

d
e
b
c
a

【说】:根据JDK Doc文档中的iterator说明,取出来的元素不一定要按特定序列排序的。这边除了可以用while循环之外,也可以用for循环来设计,具体代码段如下:

/*		Iterator iter = set.iterator();
		while(iter.hasNext()){
			String value = (String)iter.next(); //next()函数调用后返回的是Object类型,要向下类型转化为String类型
			System.out.println(value);*/
		
		//注意这边for循环条件最后一句不写,但是分号别落下
		for(Iterator iter = set.iterator(); iter.hasNext(); ){
			String value = (String)iter.next(); 
			System.out.println(value);
		}
【说明】:注意for循环里面条件的写法


2. 再前面几讲内容中,我们已经对collection中的List和Set都做了剖析,现在开始剖析Set中的SortedSet[带排序的Set],主要这些都是类集框架的接口,接口是没法生成实例的,我们需要用的是通过他们的实现类来操作。

查看JDK Doc中的SortedSet接口,及相应说明

public interface SortedSet<E>
  
  
   
   extends Set<E>
  
  
SortedSet 定义的排序规则有两种:

1). natural ordering 自然排序

2). Comparator ordering 通过特定定义的规则来排序,通常这种定义的规则是由自己来定义的。

查看JDK Doc中的SortedSet接口的实现类及相应的方法,一般用的最多的是TreeSet这个实现类,这个类实现了SortedSet接口,所以它也是带排序的,另外由于SortedSet继承了Set,而TreeSet又实现了SortedSet,所以Set中定义的方法,TreeSet都会有。

package com.ahuier2;

import java.util.TreeSet;

public class TreeSetTest {
	public static void main(String[] args) {
		TreeSet set = new TreeSet();
		set.add("C");
		set.add("A");
		set.add("B");
		set.add("E");
		set.add("F");
		set.add("D");
		System.out.println(set);
	}
}
编译执行结果:

[A, B, C, D, E, F]

【说明】:这边可以看出排序是有序,定义的规则是TreeSet中的自然法则来排序的。


但是很多时候在实际开发中,我们不一样要按这种自然的排序规则来排序,比如按学生的分数升序排序,或者降序排序,在这种情况下,我们要如何来定义呢?比如如下程序

package com.ahuier2;

import java.util.TreeSet;

public class TreeSetTest2 {
	public static void main(String[] args) {
		TreeSet set = new TreeSet();
		Person p1 = new Person(10);
		Person p2 = new Person(20);
		Person p3 = new Person(30);
		Person p4 = new Person(40);
		
		set.add(p1);
		set.add(p2);
		set.add(p3);
		set.add(p4);
		System.out.println(set);	
	}
}
class Person{
	int score;
	public Person(int score){
		this.score = score;
	}
	//重写toString()方法是为了main方法中的打印set能打印出score,如果不重写这个方法,则直接调用set这个引用的toString()方法
	public String toString() {
		return String.valueOf(this.score); 
	}
}
【程序解读】:重写toString()方法,我们想要直接返回score分数,所以要用String的valueof()方法,将其转化为String,这个实际开发中很常见,而之所以要重写toString()方法请看程序注释。

编译执行结果:

Exception in thread "main" java.lang.ClassCastException: com.ahuier2.Person cannot be cast to java.lang.Comparable
at java.util.TreeMap.put(Unknown Source)
at java.util.TreeSet.add(Unknown Source)
at com.ahuier2.TreeSetTest2.main(TreeSetTest2.java:14)

【说明】:程序出现类型转换异常,Person类无法转换为java.lang.Comparable。

继续跟踪Comparable类,查看JDK Doc文档发现Comparable是一个接口,里面只有定义一个方法:

int compareTo(T o) 
          Compares this object with the specified object for order.

[是一个int类型的方法,传进来的Object类型对象与调用的这个对象进行比较]

程序错误原因是第一次往里面add(p1)之后,第二次调用add(p2)方法时候出现异常,出现这种情况的原因一定与TreeSet中的add()方法有关系。

查看JDK中的TreeSet类的add()方法:

Throws:
ClassCastException - if the specified object cannot be compared with the elements currently in this set
[类转换异常]- 如果Set中放的这个特定元素不能与Set中的当前的元素进行比较的话就会出现这个异常。
NullPointerException - if the specified element is null and this set uses natural ordering, or its comparator does not permit null elements

从这边可以知道上诉异常的原因是由这一部错误引起的,为什么会出现这种情况呢?

原因是因为TreeSet是一个带排序的类,既然是带排序的,再放入的过程中必然会有一个比较的过程,在往Set中第一次add()的时候是可以放进去的,因为此时没有元素与它进行比较,第二次add()的时候往Set中放的这个元素就会第一次放的元素进行比较,而此时的排序比较规则是以特定的规则来排序的,不是按之前程序中的自然规则排序,由于我们没有给它定义这种排序规则,系统就会出现这种异常。


所以现在的问题是我们必须给它指定一种排序规则,但是如何指定,在哪里指定呢?

首先我们可以在构造方法中指定这种规则,因为当我们生成这个对象的时候,我们就应该指定完成了这种规则,查看TreeSet中的构造方法:

TreeSet

public TreeSet(Comparator<? super E> comparator)
Constructs a new, empty tree set, sorted according to the specified comparator. All elements inserted into the set must bemutually comparable by the specified comparator:comparator.compare(e1, e2) must not throw aClassCastException for any elementse1 and e2 in the set. If the user attempts to add an element to the set that violates this constraint, theadd call will throw aClassCastException.
Parameters:
comparator - the comparator that will be used to order this set. Ifnull, thenatural ordering of the elements will be used.

       [构造一个新的,空的Set,这个Set的排序根据特定的规则(就是传递进来的参数)来排序]

继续查看Comparator接口中的方法:


查看compare()方法的使用规则:

int compare(T o1,T o2)

查看它的返回值:它的返回值返回负数,0,正数。当第一参数小于、等于、大于第二个参数的时候。

例如:compare(new Integer(1), new Integer(2)) ---> 返回负数

     compare(new Integer(2), new Integer(2)) ---> 返回0

     compare(new Integer(3), new Integer(2)) ---> 返回正数

显然对于TreeSet中的这个构造方法,必须要实现这个构造方法中的Comparator接口,同时还要时间它的compare方法,这里equals()可以不用实现,因为equals()方法继承了Object类。

所以这边要先定义一个规则,然后将这个类的的对象作为参数传递给 TreeSet 中的publicTreeSet(Comparator<? super E> comparator)这个构造函数。


现在定义一个类,定义自己的规则,实现自己的Comparator。

在实现上诉需求前,先写一个简单的定义规则的程序。需求,往Set中放置英文字母,根据英文字母倒序方式将其输出。

package com.ahuier2;

import java.util.Iterator;
import java.util.TreeSet;

public class IteratorTest2 {
	public static void main(String[] args) {
		TreeSet set = new TreeSet();
		set.add("C");
		set.add("A");
		set.add("B");
		set.add("E");
		set.add("F");
		set.add("D");
		
		for(Iterator ite = set.iterator(); ite.hasNext();){
			String value = (String)ite.next();
			System.out.println(value);
			
		}
	}
}
编译执行结果:

A
B
C
D
E
F

【说明】:在没有定义规则之前,程序排序是按自然规则排序,打印的。现在利用Iterator接口的实现类定义自己的排序规则[将英文元素倒序排序]

定义排序规则步骤:[参考JDK Doc文档]

1. 选择TreeSet中的构造方法:public TreeSet(Comparator comparator)这个构造方法。

2. 定义一个类实现Comparator这个接口

3. 实现这个接口中的 compare(T o1, T o2)方法。[这个方法就是体现自定义的规则,这个规则在这个方法中体现]

package com.ahuier2;

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

public class IteratorTest2 {
	public static void main(String[] args) {
		//使用实现Comparator接口的实现类的实例作为参数的构造函数
		TreeSet set = new TreeSet(new MyComparator());
		set.add("C");
		set.add("A");
		set.add("B");
		set.add("E");
		set.add("F");
		set.add("D");
		
		for(Iterator ite = set.iterator(); ite.hasNext();){
			String value = (String)ite.next();
			System.out.println(value);
			
		}
	}
}
//定义一个类实现Comparator接口
class MyComparator implements Comparator{
	//实现Comparator接口中的compare方法,可以不实现它的equals()方法
	public int compare(Object o1, Object o2) {
		//由于compare方法传递的参数的类型是Object类型,所以要向下类型转换为String类型,因为我们传递的都是String类型的英文字符串
		String s1 = (String)o1;
		String s2 = (String)o2;
	    //定义排序规则
		return s2.compareTo(s1);
	}
}
编译执行结果:

F
E
D
C
B
A

【说明】:再定义规则中,使用String的compareTo方法[JDK Doc中String类的方法],这个方法是比较调用的字符与参数字符这两个字符按字典排序,返回值为负数,0,正数分别对于字典中靠前,一样,靠后的结果。例如s1.compareTo(s2)则是按字典排序,现在反过来s2.compare(s1)则变成倒序排列。或者倒序也可以写成:-s1.compareTo(s2);


注意这边大写英文字母与小写英文字母的ASII码,A 为65,a 为97 a = A + 32。在上诉程序代码段插入小写a,倒序后输出a则排在首位。

		//使用实现Comparator接口的实现类的实例作为参数的构造函数
		TreeSet set = new TreeSet(new MyComparator());
		set.add("C");
		set.add("A");
		set.add("B");
		set.add("a");
		set.add("E");
		set.add("F");
		set.add("D");
编译执行结果:

a
F
E
D
C
B
A

现在反回来继续看前面定义学生分数的例子,定义规则按学生分数的升序排列规则。

package com.ahuier2;

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

public class TreeSetTest2 {
	public static void main(String[] args) {
		TreeSet set = new TreeSet(new PerComparator());
		Person p1 = new Person(10);
		Person p2 = new Person(20);
		Person p3 = new Person(30);
		Person p4 = new Person(40);
		
		set.add(p1);
		set.add(p2);
		set.add(p3);
		set.add(p4);
//		System.out.println(set);
		Iterator ite = set.iterator();
		while(ite.hasNext()){
			Person value = (Person)ite.next();
			System.out.println(value.score);	
		}
	}
}
class Person{
	int score;
	public Person(int score){
		this.score = score;
	}
/*	
	//重写toString()方法是为了main方法中的打印set能打印出score,如果不重写这个方法,则直接调用set这个引用的toString()方法
	public String toString() {
		return String.valueOf(this.score); 
	}*/
}
class PerComparator implements Comparator{
	//这边这边compare方法的参数表示的是传递进来的实际要比较的值,所以这些实参是什么类型的,就向下类型转换为什么类型。
	public int compare(Object o1, Object o2) {
		Person per1 = (Person)o1;
		Person per2 = (Person)o2;
		return per1.score - per2.score;
	}
}
编译执行结果:

10
20
30
40

【说明】:return per1.score - per2.score; 表示如果返回负数,则前面元素比我面元素小,所以是升序排列,如果是降序则可以写为:return per2.score - per1.score;或者-(per1.score - per2.score)。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值