一、List的功能方法
正如你所看到的,基本的List很容易使用:大多数时候只是调用add()添加对象,使用get()一次取出一个元素,以及调用iterator()获取用于该序列的Iterator。
下面例子中的每个方法都涵盖了一组不同的动作:basicTest()中包含每个List都可以执行的操作:iterMotion()使用Iterator遍历元素;对应的iterManipulation()使用Iterator修改元素;testVisual()用以查看List的操作效果;还有一些LinkedList专用的操作。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import com.buba.util.Countries;
public class Lists {
private static boolean b;
private static String s;
private static int i;
private static Iterator<String> it;
private static ListIterator<String> lit;
public static void basicTest(List<String> a) {
a.add(1, "x");
a.add("x");
a.addAll(Countries.names(25));
a.addAll(3, Countries.names(25));
b = a.contains("1");
b = a.containsAll(Countries.names(25));
s = a.get(1);
i = a.indexOf("1");
b = a.isEmpty();
it = a.iterator();
lit = a.listIterator();
lit = a.listIterator(3);
i = a.lastIndexOf("1");
a.remove(1);
a.remove("3");
a.set(1, "y");
a.retainAll(Countries.names(25));
a.removeAll(Countries.names(25));
i = a.size();
a.clear();
}
public static void iterMotion(List<String> a) {
ListIterator<String> it = a.listIterator();
b = it.hasNext();
b = it.hasPrevious();
s = it.next();
i = it.nextIndex();
s = it.previous();
i = it.previousIndex();
}
public static void iterManipulation(List<String> a) {
ListIterator<String> it = a.listIterator();
it.add("47");
it.next();
it.remove();
it.next();
it.set("47");
}
public static void testVisual(List<String> a) {
System.out.println(a);
List<String> b = Countries.names(25);
System.out.println("b = " + b);
a.addAll(b);
a.addAll(b);
System.out.println(a);
ListIterator<String> x = a.listIterator(a.size() / 2);
x.add("one");
System.out.println(a);
System.out.println(x.next());
x.remove();
System.out.println(x.next());
x.set("47");
System.out.println(a);
x = a.listIterator(a.size());
while (x.hasPrevious()) {
System.out.print(x.previous() + " ");
}
System.out.println();
System.out.println("testVisual finished");
}
public static void testLinkedList() {
LinkedList<String> ll = new LinkedList<>();
ll.addAll(Countries.names(25));
System.out.println(ll);
ll.addFirst("one");
ll.addFirst("two");
System.out.println(ll);
System.out.println(ll.getFirst());
System.out.println(ll.removeFirst());
System.out.println(ll.removeFirst());
System.out.println(ll.removeLast());
System.out.println(ll);
}
public static void main(String[] args) {
basicTest(new LinkedList<String>(Countries.names(25)));
basicTest(new ArrayList<String>(Countries.names(25)));
iterMotion(new LinkedList<String>(Countries.names(25)));
iterMotion(new ArrayList<String>(Countries.names(25)));
iterManipulation(new LinkedList<String>(Countries.names(25)));
iterManipulation(new ArrayList<String>(Countries.names(25)));
testVisual(new LinkedList<String>(Countries.names(25)));
testLinkedList();
}
}
在basicTest()和iterMontion()方法中,调用只是为了演示正确的语法,虽然取得了返回值,却没有使用。某些情况下则根本没有捕获返回值。使用这些方法前,应该查询JDK帮助文档,以充分了解各种方法的用途。
二、Set和存储顺序
在持有对象中的Set示例对可以用基本的Set执行操作,提供了很好的介绍。那些示例很方便的使用了诸如Integer和String这样的java预定义的类型,这些类型被设计为可以在容器内部使用。当你创建自己的类型时,要意识到Set需要一种方式来维护存储顺序,而存储顺序如何维护,则是在Set的不同实现之间会有所变化。因此,不同的Set实现不仅具有不同的行为,而且它们对于可以在特定的Set中放置的元素的类型也有不同的要求:
Set(interface) | 存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序 |
HashSet* | 为快速查找而设计的Set。存入HashSet的元素必须定义HashCode() |
TreeSet | 保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。元素必须实现Comparable接口 |
LinkedHashSet | 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。元素也必须定义hashCode()方法。 |
在HashSet上打星号表示,如果没有其他的限制,这就应该是你默认的选择,因为它对速度进行了优化。
定义hashCode()的机制将在本章稍后进行介绍。你必须为散列存储和树型存储都创建一个equals()方法,但是hashCode()只有在这个类将会被置于HashSet(这是有可能的,因为它通常是你的Set实现的首选)或者LinkedHashSet中时才是必需的。但是,对于良好的编程风格而言,你应该在覆盖equals()方法时,总是同时覆盖hashCode()方法。
下面的示例演示了为了成功的使用特定的Set实现类型而必须定义的方法:
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
class SetType {
int i;
public SetType(int n) {
i = n;
}
public boolean equals(Object o) {
return o instanceof SetType && (i == ((SetType) o).i);
}
public String toString() {
return Integer.toString(i);
}
}
class HashType extends SetType {
public HashType(int n) {
super(n);
}
public int hashCode() {
return i;
}
}
class TreeType extends SetType implements Comparable<TreeType> {
public TreeType(int n) {
super(n);
}
@Override
public int compareTo(TreeType o) {
return o.i < i ? -1 : (o.i == i ? 0 : 1);
}
}
public class TypesForSets {
static <T> Set<T> fill(Set<T> set, Class<T> type) {
try {
for (int i = 0; i < 10; i++) {
set.add(type.getConstructor(int.class).newInstance(i));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return set;
}
static <T> void test(Set<T> set, Class<T> type) {
fill(set, type);
fill(set, type);
fill(set, type);
System.out.println(set);
}
public static void main(String[] args) {
test(new HashSet<HashType>(), HashType.class);
test(new LinkedHashSet<HashType>(), HashType.class);
test(new TreeSet<TreeType>(), TreeType.class);
test(new HashSet<SetType>(), SetType.class);
test(new HashSet<TreeType>(), TreeType.class);
test(new LinkedHashSet<SetType>(), SetType.class);
test(new LinkedHashSet<TreeType>(), TreeType.class);
try {
test(new TreeSet<SetType>(), SetType.class);
} catch (Exception e) {
System.out.println(e.getMessage());
}
try {
test(new TreeSet<HashType>(), HashType.class);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
为了证明哪些方法对于某种特定的Set是必需的,并且同时还要避免代码重复,我们创建了三个类。基类SetType只存储一个int,并且通过toString()方法产生它的值。因为所有在Set中存储的类都必须具有equals()方法,因此在基类中也有该方法,其等价性是基于这个int类型的i的值确定的。
HashType继承自SetType,并且添加了HashCode()方法,该方法对于放置到Set的散列实现中的对象来说是必需的。
TreeType实现了Comparable接口,如果一个对象被用于任何种类的排序容器中,例如,SortedSet(TreeMap是其唯一实现),那么它必须实现这个接口。注意,在compareTo()中,我没有使用“简洁明了”的形式return i-i2,因为这是一个常见的编程错误,它只有在i和i2都是无符号的int(如果java确实有unsigned关键字的话,但实际上没有)时才能正确工作。对于java的有符号int,它就会出错,因为int不够大,不足以表现两个有符号int的差。例如i是很大的正整数,而j是很大的负整数,i-j就会溢出并且返回负值,这就不正确了。
你通常会希望compareTo()方法可以产生与equals()方法一致的自然排序。如果equals()对于某个特定比较产生true,那么compareTo()对于该比较应该返回0,如果equals()对于某个比较产生false,那么compareTo()对于该比较应该返回非0值。
在TypesForSets中,fill()和test()方法都是用泛型定义的,这是为了避免代码重复。为了验证某个Set行为,test会在被测Set上调用fill()三次,尝试着在其中引入重复对象。fill()方法可以接受任何类型的Set,以及其相同类型Class对象,它使用Class对象来发现并接受int参数的构造器,然后调用该构造器将元素添加到Set中。
从输出中可以看到,HashSet以某种神秘的顺序保存所有的元素(这将在本章稍后进行解释),LinkedHashSet按照元素插入的顺序保存元素,而TreeSet按照排序顺序维护元素(按照compareTo()的实现方式,这里维护的是降序)。
如果我们尝试着将没有恰当地支持必需的操作的类型用于需要这些方法的Set,那么就会有大麻烦了。对于没有重新定义HashCode()方法的SetType和TreeType,如果将它们放置到任何散列实现中都会产生重复值,这样就违反了Set的基本契约。这相当烦人,因为这甚至不会有运行时错误。但是,默认的hashCode()是合法的,因此这是合法的行为,即便它不正确。确保这种程序的正确性的唯一可靠方法就是将单元测试合并到你的构建系统中。
如果我们尝试着在TreeSet中使用没有实现Comparable的类型,那么你将会得到更确定的结果:在TreeSet试图将对象当作Comparable使用时,将抛出一个异常。
三、SortedSet
SortedSet中的元素可以保证处于排序状态,这使得它可以通过在SortedSet接口中的下例方法提供附加功能:Comparator comparator()返回当前Set使用的Comparator;或者返回null,表示以自然方式排序。
- Object first()返回容器中的第一个元素。
- Object last()返回容器中的最末一个元素。
- SortedSet subSet( formElement, toElement)生成此Set的子集,范围从formElement(包含)到toElement(不包含)。
- SortedSet headSet(toElement)生成此Set的子集,由小于toElement的元素组成。
- SortedSet tailSet(formElement)生成此Set的子集,由大于或等于formElement的元素组成。
下面是一个简单的示例:
import java.util.Collections;
import java.util.Iterator;
import java.util.SortedSet;
import java.util.TreeSet;
public class SortedSetDemo {
public static void main(String[] args) {
SortedSet<String> sortedSet = new TreeSet<>();
Collections.addAll(sortedSet, "one two three four five six seven eight".split(" "));
System.out.println(sortedSet);
String low = sortedSet.first();
String high = sortedSet.last();
System.out.println(low);
System.out.println(high);
Iterator<String> it = sortedSet.iterator();
for (int i = 0; i <= 6; i++) {
if (i == 3)
low = it.next();
if (i == 6)
high = it.next();
else
it.next();
}
System.out.println(low);
System.out.println(high);
System.out.println(sortedSet.subSet(low, high));
System.out.println(sortedSet.headSet(high));
System.out.println(sortedSet.tailSet(low));
}
}
请注意,SortedSet意思是“按对象的比较函数对元素排序”,而不是指“元素插入的次序”。插入顺序可以用LinkedHashSet来保存。
如果本文对您有很大的帮助,还请点赞关注一下。