Collection 体系的三个核心约定
Collection 系列文章的总目录:
- Collection 体系的三个核心约定
- Sorted & Navigable
- Iterator & Iterable
- Java 中的数组
- ArrayList
- LinkedList
- HashMap
- LinkedHashMap
- TreeMap
- HashSet/LinkedHashSet/TreeSet
Collection 的大体类图:
注意:Map 不是 Collection 接口的实现
Collection 体系
对以下类要了如指掌:
/**
* @see Set
* @see List
* @see Map
* @see SortedSet
* @see SortedMap
* @see HashSet
* @see TreeSet
* @see ArrayList
* @see LinkedList
* @see Vector
* @see Collections
* @see Arrays
* @see AbstractCollection
*/
了解 Collection 的每个方法的含义:包括 Java8 之后的 default 方法
public interface Collection<E> extends Iterable<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
boolean retainAll(Collection<?> c);
void clear();
boolean equals(Object o);
int hashCode();
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
}
Arrays/Collections 工具方法
一般,若X是一个类,则Xs就是与之相关的工具方法:
- Array -> Arrays
- Collection -> Collections
- List -> Lists (guava)
- Set -> Sets (guava)
Collections的一些方法:
Collections.emptyList():
- 返回一个final的空List,这个List是一个内部类EmptyList的实例
Collections.synchronizedList(list):
- 装饰器模式,返回一个SynchronizedList的实例,这个类将list的每个方法都用synchronized包装起来了
Collections.unmodifiableList():
- 返回一个UnmodifiableList的实例,对这个实例的修改操作,都会抛出UnsupportedOperationException
Collections.sort(List<T> list):
Collections.sort(List<T> list, Comparator<? super T> c):
- 排序,实际上就是调用了list自己的sort,直接修改原list,没有返回值
还有一些checkXX和singletonXX等之类的方法
Collections 的一些内部类:
这类内部类基本上是用于上述方法的
AbstractCollection
AbstractCollection 为 Collection 接口提供骨架的实现,使用了模板方法模式
比如:
Collection定义了size()和isEmpty()这两个方法
在AbstractCollection中,isEmpty()的实现是:return size() == 0;
这样子,大部分AbstractCollection的子类就不需要自己再实现一遍isEmpty()方法了,
当然,子类也可以选择覆盖isEmpty()方法,以提供自己的实现。
以及:
contains(Object o) 方法,判断一个元素是否在这个集合中,调用了 iterator 来获取子类的迭代器,然后迭代集合内的元素进行比较。
toArray、remove、removeAll、retainAll、clear、toString等方法也调用了iterator
也存在模板方法相互调用的情况:containsAll -> contains
除了模板方法模式,AbstractCollection 本身提供一些默认实现:
add()方法的现实是:throw new UnsupportedOperationException();
AbstractList、AbstractSet类似
Q:看源码我们注意到,一些在 Collection 接口中定义的方法,AbstractCollection中会再次声明成abstract
的,这是为什么?
是为了体现这些方法是抽象的?还是为了防止接口给这些方法提供default实现?
A:TODO
其他:
接口与接口、类与类之间用extends
类与接口用implements
如果接口有default方法a()
抽象类又将a()声明成抽象的
那么实现类还是需要覆盖a()
Collection体系的核心约定
equals:
- 自反性:reflexive,x.equals(x) == true
- 对称性:symmetric,x.equals(y) == y.equals(x)
- 传递性:transitive,若 x.equals(y) == true,y.equals(z) == true,则x.equals(z) == true
- 一致性:consistent,无论调用多少次 x.equals(y) , 它的返回值都不该改变,前提是不修改equals方法中使用到的信息
- 对于任意非空对象 x,x.equals(null) == false
Object.equals():
- It is reflexive: for any non-null reference value x, x.equals(x) should return true.
- It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
- It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
- It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
- For any non-null reference value x, x.equals(null) should return false.
hashCode:
- 在同一个 JVM 周期,无论对同一对象调用多少次 hashCode,返回的 int 都应该一样,前提是不修改 equals 方法中使用到的信息。
- 若 x.equals(y) == true,则 x.hashCode() == y.hashCode()
- 若 x.equals(y) == false,则 x.hashCode() 与 y.hashCode() 没有强制要求不相等。
- 但程序员应该意识到,对于 unequals 的两个对象,hashCode 返回不同的值可以提高 hash table 的性能
Object.hashCode():
- Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
- If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
- It is not required that if two objects are unequal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
Comparable/Comparator:
- 对于每个实现
Comparable
接口的类,都被强制要求实现整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。- (即使用类的 compareTo 方法排序的就是自然排序,否则不是,比如使用额外的 comparator)
- 实现了
Comparable
接口的对象列表或数组,都可以被Collections.sort
(和Arrays.sort
) 自动地排序。实现这个接口的对象可以被用于SortedMap
的 key 或者SortedSet
的元素,而不需要指定 comparator。 - 对于类 C 的任意对象 e1、e2,有且仅当 e1.compareTo(e2) == 0 和 e1.equals(e2) 返回值一样,类 C 的自然顺序才能叫做和 equals 一致。
- 注意:null 不是任何类的实例, e.compareTo(null) 应该抛出
NullPointerException
,即使 e.equals(null) 返回 false。
- 注意:null 不是任何类的实例, e.compareTo(null) 应该抛出
- 强烈建议让自然排序和 equals 一致。这是因为那些使用自然排序与 equals 不一致的元素 (或 key)时,没有指定额外的
Comparator
的SortedSet
(和SortedMap
)的行为会变得“怪异”。特别的,这样的SortedSet
(或SortedMap
)违反了根据 equals 方法定义的 Set (或 Map)的常规约定。 - 例如:...(后面有例子)
- 实际上,所有实现
Comparable
的 Java 核心类都具有与 equals 一致的自然排序。java.math.BigDecimal
是个例外,它的自然排序将值相等但精确度不同的BigDecimal
对象(比如 4.0 和 4.00)视为相等。
Comparable:
This interface imposes a total ordering on the objects of each class that implements it. This ordering is referred to as the class's natural ordering, and the class's compareTo method is referred to as its natural comparison method.
Lists (and arrays) of objects that implement this interface can be sorted automatically by Collections.sort (and Arrays.sort). Objects that implement this interface can be used as keys in a sorted map or as elements in a sorted set, without the need to specify a comparator.
The natural ordering for a class C is said to be consistent with equals if and only if e1.compareTo(e2) == 0 has the same boolean value as e1.equals(e2) for every e1 and e2 of class C. Note that null is not an instance of any class, and e.compareTo(null) should throw a NullPointerException even though e.equals(null) returns false.
It is strongly recommended (though not required) that natural orderings be consistent with equals. This is so because sorted sets (and sorted maps) without explicit comparators behave "strangely" when they are used with elements (or keys) whose natural ordering is inconsistent with equals. In particular, such a sorted set (or sorted map) violates the general contract for set (or map), which is defined in terms of the equals method.
For example, if one adds two keys a and b such that (!a.equals(b) && a.compareTo(b) == 0) to a sorted set that does not use an explicit comparator, the second add operation returns false (and the size of the sorted set does not increase) because a and b are equivalent from the sorted set's perspective.
Virtually all Java core classes that implement Comparable have natural orderings that are consistent with equals. One exception is java.math.BigDecimal, whose natural ordering equates BigDecimal objects with equal values and different precisions (such as 4.0 and 4.00).
For the mathematically inclined, the relation that defines the natural ordering on a given class C is:
{(x, y) such that x.compareTo(y) <= 0}.
The quotient for this total order is:
{(x, y) such that x.compareTo(y) == 0}.
It follows immediately from the contract for compareTo that the quotient is an equivalence relation on C, and that the natural ordering is a total order on C. When we say that a class's natural ordering is consistent with equals, we mean that the quotient for the natural ordering is the equivalence relation defined by the class's equals(Object) method:
{(x, y) such that x.equals(y)}.
Comparable 的正确用法:
// 实现Comparable接口,重写compareTo方法。这里只比较age
public class User implements Comparable<User> {
int id;
int age;
@Override
public int compareTo(User that) {
// 小于就返回-1,大于就返回1,等于就返回0
if (age < that.age) {
return -1;
} else if (age > that.age) {
return 1;
} else {
return 0;
}
// 每种基本类型都会有现成的比较器,一般不需要自己写
// return Integer.compare(age, that.age);
}
}
取巧问题:
// compareTo只需要返回正数、负数或0,并不一定要返回1、-1,所以有的人就会使用相减来实现
@Override
public int compareTo(User that) {
return age - that.age;
}
// 这会有什么问题呢?
// 整数溢出问题,如果 age = Integer.MIN_VALUE,that.age = Integer.MAX_VALUE,
// 那么 age - that.age = 1
// 以及不要使用非/取反操作
// age = Integer.MIN_VALUE
// -age = -2147483648
所以: 不要使用相加、相减、取非来实现 compareTo,因为可能会导致数值溢出。
自然排序和 equals 不一致的例子:
public class User implements Comparable<User> {
int id;
int age;
public User(int id, int age) {
this.id = id;
this.age = age;
}
@Override
public int hashCode() {
return Objects.hash(id, age);
}
// equals比较了id和age
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null || getClass() != that.getClass()) {
return false;
}
User user = (User) that;
return id == user.id &&
age == user.age;
}
// compareTo只比较了age
@Override
public int compareTo(User that) {
return Integer.compare(age, that.age);
}
public static void main(String[] args) {
// 1号user与3号user unequals,即!user1.equals(user3)
// user1.compareTo(user3) == 0
// 而 SortedSet(TreeSet) 内部是使用compareTo进行比较的,那么user1与user3是相等的,所以add不进去
Set<User> set = new TreeSet<>();
set.add(new User(1, 20));
set.add(new User(2, 10));
set.add(new User(3, 20));
// [User{id=2, age=10}, User{id=1, age=20}]
System.out.println(set);
}
}
equals 和 hashCode 在 Collection 中哪里用到了
TODO