文章目录
4 类和接口
第13条 使类和成员的可访问性最小化
当访问级别从default变成protected的时候,会大大增加可访问性。protected的成员是类导出的API的一部分,必须永远得到支持。导出的类的protected成员也代表了该类对于某个实现细节的公开承诺。受保护的成员应该尽量少用。
**实例域决不能是公有的。**如果域是非final的,或者是一个指向可变对象的final引用,那么一旦使这个域成为共有的,就放弃了对存储在这个域中的值进行限制的能力;这意味着,你也放弃了强制这个域不可变的能力。同时,当这个域被修改的时候,你失去了对它采取任何行动的能力。因此,包含共有可变域的类并不是线程安全的。 即使域是final的,并且引用不可变的对象,当把这个域变成公有的时候,也就放弃了“切换到一种新的内部数据表示法”的灵活性。
长度非零的数组总是可变的。所以,类具有public的static的final的数组域,或者返回这种域的访问方法,这几乎总是错误的。
// 这是有潜在的安全漏洞的。
public static final Ting[] values = {}
许多ide会产生返回指向私有数组的引用的访问方法,这回导致上述问题产生。此问题有两种解决办法:
(1)使数组私有,并增加一个公有的不可变列表。
private static final Ting[] PRIVATE_VALUES = {}
public static final List<Thing> VALUES = Collections.unmodifidableList(Arrays.asList(PRIVATE_VALUES ))
(2)使数组私有,并添加一个公有方法,它返回私有数组的一个备份。
private static final Ting[] PRIVATE_VALUES = {}
public static final Thing[] values() {
return PRIVATE_VALUES.clone()
}
除了public static final常量域的特殊情形外,共有类都不应该包含公有域。并且要确保public static final常量域所引用的对象都是不可变的。
第14条 在公有类中使用访问方法而非公有域
第15条 使可变性最小化
(1)不要提供任何会修改对象状态的方法(也成为mutator)。
(2)保证类不会被扩展。一般做法是使这个类成为final的。
(3)使所有的域都是final的。
(4)使所有的域都成为私有的。虽然从技术上讲,允许不可变的类具有公有的final域,只要这些域包含基本类型的值或者是指向不可变对象的引用;但是不建议这样做。因为这样会使得在以后的版本中无法再改变内部的表示法。
(5)确保对于任何可变组件的互斥访问。如果类有指向可变对象的引用域,则必须确保这些引用域是非public的。永远不要用客户端提供的对象来初始化这样的域,也不要从任何访问方法中返回这样的域。在构造器、访问方法和readObject方法中使用保护性拷贝(defensive copy)技术。
// 不可变类的一个例子
package book.effective.rule15;
public final class Complex {
private final double re;
private final double im;
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
public double realPart() {
return re;
}
public double imaginaryPart() {
return im;
}
public Complex add(Complex c) {
return new Complex(re + c.re, im + c.im);
}
public Complex subtract(Complex c) {
return new Complex(re - c.re, im - c.im);
}
public Complex multiply(Complex c) {
return new Complex(re * c.re - im * c.im, re * c.im + im * c.re);
}
public Complex divide(Complex c) {
double temp = c.re * c.re + c.im * c.im;
return new Complex((re * c.re + im * c.im) / temp,
(im * c.re - re * c.im) / temp);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Complex)) {
return false;
}
Complex c = (Complex) obj;
return Double.compare(re, c.re) == 0
&& Double.compare(im, c.im) == 0;
}
@Override
public int hashCode() {
int result = 17 + hashDouble(re);
result = 31 * result + hashDouble(im);
return result;
}
private int hashDouble(double val) {
long longBits = Double.doubleToLongBits(re);
return (int) (longBits ^ (longBits >>> 32));
}
@Override
public String toString() {
return "(" + re + " + " + im + "i)";
}
}
不可变的对象本质上是线程安全的,它们不要求同步。不可变对象可以被自由地共享。 不可变的类可以提供一些静态工厂方法,他们把频繁被请求的实例缓存起来。
不仅可以共享不可变对象,甚至也可以共享他们的内部信息。 BigInteger内部使用了符号数值表示法。符号用一个int值来表示,数值则用int数组来表示。negate方法会产生一个相反符号的新的BigInteger。新的BigInteger并不需要拷贝数组,指向了原始实例中的同一个内部数组。
/**
* Returns a BigInteger whose value is {@code (-this)}.
*
* @return {@code -this}
*/
public BigInteger negate() {
return new BigInteger(this.mag, -this.signum);
}
不可变对象为其他对象提供了大量的构建(building blocks)。
不可变对象真正的唯一缺点是:对于每一个不同的值都需要一个单独的对象。 性能问题有两种解决办法:(1)将经常用到的多步骤操作作为基本类型提供。例如,BigInteger有一个包级私有的可变配套类(Companing class),加速模指数这类的运算。(2)提供公有的可变配套类。例如,StringBuilder。
类不可被继承的两种方法:
(1)使用final进行修饰
(2)构造器声明为private或者protected,然后提供静态工厂方法
静态工厂方法比较灵活,很容易扩展。
public static Complex valueOf(double re, double im) {
return new Complex(re, im);
}
/**
* 使用极坐标的方式来创建Complex
*
* @param r 极径
* @param theta 角度
* @return
*/
public static Complex valueOfPolar(double r, double theta) {
return new Complex(r * Math.cos(theta),
r * Math.sin(theta));
}
第16条:复合优先于继承
继承打破了封装性。
继承是有风险的:子类不得不随着父类的演进而跟随演进。
package book.effective.rule16;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
public class InstrumentedHashSet<E> extends HashSet<E> {
/**
* 记录添加的元素的个数
*/
private int addCount;
public InstrumentedHashSet() {
}
public InstrumentedHashSet(int initCapacity, float loadFactor) {
super(initCapacity, loadFactor);
}
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
public static void main(String[] args) {
InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.addAll(Arrays.asList("A", "B", "C"));
/**
* 返回值是6:因为hashset实现addAll的时候是调用了自身的add方法的
*/
System.out.println(s.getAddCount());
}
}
复合的实现方式:
package book.effective.rule16;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
public class InstrumentedHashSet<E> extends ForwardingSet<E> {
/**
* 记录添加的元素的个数
*/
private int addCount;
public InstrumentedHashSet(Set<E> s) {
super(s);
}
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
public static void main(String[] args) {
InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.addAll(Arrays.asList("A", "B", "C"));
/**
* 返回值是6:因为hashset实现addAll的时候是调用了自身的add方法的
*/
System.out.println(s.getAddCount());
}
}
package book.effective.rule16;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set s) {
this.s = s;
}
@Override
public int size() {
return s.size();
}
@Override
public boolean isEmpty() {
return s.isEmpty();
}
@Override
public boolean contains(Object o) {
return s.contains(o);
}
@Override
public Iterator<E> iterator() {
return s.iterator();
}
@Override
public Object[] toArray() {
return s.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return s.toArray(a);
}
@Override
public boolean add(E e) {
return s.add(e);
}
@Override
public boolean remove(Object o) {
return s.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return s.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends E> c) {
return s.addAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
return s.retainAll(c);
}
@Override
public boolean removeAll(Collection<?> c) {
return s.removeAll(c);
}
@Override
public void clear() {
s.clear();
}
}
第17条:要么为继承而设计,并提供说明文档,要么就禁止继承
可以被继承的类必须有文档说明它可覆盖的方法的自用性。
例如:java.util.AbstractCollection的规范文档。remove方法的文档中清楚地说明了覆盖iterator方法将会影响remove方法的行为,并且描述了将会怎样影响remove方法的行为。
/**
* {@inheritDoc}
*
* <p>This implementation iterates over the collection looking for the
* specified element. If it finds the element, it removes the element
* from the collection using the iterator's remove method.
*
* <p>Note that this implementation throws an
* <tt>UnsupportedOperationException</tt> if the iterator returned by this
* collection's iterator method does not implement the <tt>remove</tt>
* method and this collection contains the specified object.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public boolean remove(Object o) {
可被继承的类必须通过某种形式提供适当的钩子,以便能够进入到它的内部工作流程中,这种形式可以是protected方法,也可以是protected域(这个相对比较少见)。
例如:java.util.AbstractList中的removeRange方法
/**
* Removes from this list all of the elements whose index is between
* {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.
* Shifts any succeeding elements to the left (reduces their index).
* This call shortens the list by {@code (toIndex - fromIndex)} elements.
* (If {@code toIndex==fromIndex}, this operation has no effect.)
*
* <p>This method is called by the {@code clear} operation on this list
* and its subLists. Overriding this method to take advantage of
* the internals of the list implementation can <i>substantially</i>
* improve the performance of the {@code clear} operation on this list
* and its subLists.
*
* <p>This implementation gets a list iterator positioned before
* {@code fromIndex}, and repeatedly calls {@code ListIterator.next}
* followed by {@code ListIterator.remove} until the entire range has
* been removed. <b>Note: if {@code ListIterator.remove} requires linear
* time, this implementation requires quadratic time.</b>
*
* @param fromIndex index of first element to be removed
* @param toIndex index after last element to be removed
*/
protected void removeRange(int fromIndex, int toIndex) {
ListIterator<E> it = listIterator(fromIndex);
for (int i=0, n=toIndex-fromIndex; i<n; i++) {
it.next();
it.remove();
}
}
这个方法对于List实现的最终用户没有意义。提供该方法的唯一目的在于,使子类更易于提供针对子列表的快速clear方法。如果没有removeRange方法,当在子List上调用clear方法时,子类不得不用平方级的时间来完成它的工作。否则,就得重新编写整个subList机制——这个就不太现实了。
对于为了继承而设计的类,唯一的测试方法就是编写子类。并且要在发布之前先编写子类来对超类进行测试。
构造器决不能调用可被覆盖的方法,无论是直接调用还是间接调用。举个例子:
package book.effective.rule17;
import java.util.Date;
public final class Sub extends Super {
private final Date date;
Sub() {
date = new Date();
}
@Override
public void overrideMe() {
System.out.println(date);
}
public static void main(String[] args) {
Sub sub = new Sub();
sub.overrideMe();
}
}
运行结果:
null
Mon May 16 20:55:10 CST 2022
无论是clone还是readObject,都不可以调用可覆盖的方法,无论是直接还是间接的方式。 如果你决定在一个为了继承而设计的类中实现Serializeble,并且该类中有一个readResolve或者writeReplace方法,就必须使这两个方法声明为protected,而不是private。如果这两个方法是private的,那么子类会忽略掉折叠两个方法。
确保可被继承的类永远不会调用它的可被覆盖的方法。换言之,消除类中的可自用性。
第18条:接口优于抽象类
接口的优势:
(1)现有的类很容易被修改以实现新的接口
(2)接口是定义mixin(混合类型)的理想选择
Java中类不可能有一个以上的父亲,类层次结构中也没有适当的地方插入mixin
(3)接口允许我们构造非层次结构的类型框架
虽然接口不允许包含方法的实现,但是, 使用接口来定义类型并不妨碍你为程序员提供实现上的帮助。通过对你导出的每个重要接口都提供一个抽象的骨架实现(skeletal implementation)类,把接口和抽象类的优点结合起来。 接口的作用仍然是定义类型,骨架实现类接管了所有与接口实现相关的工作。
一般来说,骨架实现被称为AbstractInterface,例如AbstractCollection、AbstractList、AbstractSet和AbstractMap。将它们称为SkeletalCollection、SkeletalList、SkeletalSet和SkeletalMap也是有道理的,但是Abstract的用法已经深入人心。
第19条:接口只用于定义类型
当类实现接口时,接口就充当可以引用这个类的实例的类型(type)。因此,类实现了接口,就表明客户端可以对这个类的实例实施某些动作。为了其他目的而定义的接口都是不恰当的。
常量接口是对接口的不良使用。
public interface PhysicalConstants {
static final int CONSTANT_1;
static final int CONSTANT_2;
static final int CONSTANT_3;
......
}
定义常量的比较好的几种方案:
(1)如果常量与类紧密相关,直接定义在类中。例如, Integer.MIN_VALUE
(2)枚举
(3)直接定义不可实例化的工具类来导出常量
第20条:类层次优于标签类
标签类的缺点:
过于冗长、容易出错并且效率低下
(1)充斥着样板代码:包括枚举声明、标签域以及条件语句
(2)类中充斥多种实现,破坏了可读性
(3)内存占用增加,因为实例承担着属于其他风格的不相关的域。
package book.effective.rule20;
public class Figure {
enum Shape {RECTANGLE, CIRCLE};
// Tag域
final Shape shape;
// 矩形才使用的域
double length;
double width;
// 圆才使用的域
double radius;
// 圆的构造函数
Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
Figure(double length, double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
double area() {
switch (shape) {
case CIRCLE:
return Math.PI * (radius * radius);
case RECTANGLE:
return length * width;
default:
throw new AssertionError();
}
}
}
进行类的层次化改造:
package book.effective.rule20;
public abstract class Figure {
abstract double area();
}
package book.effective.rule20;
public class Circle extends Figure {
// 圆才使用的域
final double radius;
// 圆的构造函数
Circle(double radius) {
this.radius = radius;
}
double area() {
return Math.PI * (radius * radius);
}
}
package book.effective.rule20;
public class Rectangle {
// 矩形才使用的域
double length;
double width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double area() {
return length * width;
}
}
package book.effective.rule20;
public class Square extends Rectangle {
Square(double side) {
super(side, side);
}
}
第21条:用函数对象表示策略
例如Comparator函数。通过传递不同的比较器函数,可以获得各种不同的排列顺序。这也是策略模式的一个案例。比较器函数代表一种为元素排序的一种策略。
函数指针的主要用途就是实现策略模式。**Java,中声明一个接口来表示策略,并且每个具体的策略实现了一个类。当一个具体的策略只被使用一次的时候,通常使用匿名类来声明和实例化这个具体策略。当一个具体的策略是设计用来重复使用的时候,它的类通常就被实现为私有的静态成员类,并通过公有的静态final域被导出,其类型为该策略接口。 **
java的String类就是通过这种方式导出了一个不区分大小写的字符串比较器。
/**
* A Comparator that orders {@code String} objects as by
* {@code compareToIgnoreCase}. This comparator is serializable.
* <p>
* Note that this Comparator does <em>not</em> take locale into account,
* and will result in an unsatisfactory ordering for certain locales.
* The java.text package provides <em>Collators</em> to allow
* locale-sensitive ordering.
*
* @see java.text.Collator#compare(String, String)
* @since 1.2
*/
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
/** Replaces the de-serialized object. */
private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}
第22条:优先考虑静态成员类
嵌套类有四种:静态成员类(static member class)、非静态成员类(nonstatic member class)、匿名类(anonymous class)和局部类(local class)。后三种就被成为内部类(inner class)。
非静态成员类的一种常见用法就是定义一个Adaper,它允许外部类的实例被看作是另一个不相关的类的实例。例如Map接口的实现往往使用非静态成员类来实现它们的集合视图(collection view),这些集合视图是由Map的keySet、entrySet和values方法返回的。 同样地,诸如Set和List这种集合接口的实现往往也使用费静态成员类来实现它们的迭代器(iterator)。
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new KeySet();
keySet = ks;
}
return ks;
}
final class KeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<K> iterator() { return new KeyIterator(); }
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
public final boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Node<K,V> candidate = getNode(hash(key), key);
return candidate != null && candidate.equals(e);
}
public final boolean remove(Object o) {
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
public final Spliterator<Map.Entry<K,V>> spliterator() {
return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
public Collection<V> values() {
Collection<V> vs = values;
if (vs == null) {
vs = new Values();
values = vs;
}
return vs;
}
final class Values extends AbstractCollection<V> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<V> iterator() { return new ValueIterator(); }
public final boolean contains(Object o) { return containsValue(o); }
public final Spliterator<V> spliterator() {
return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
package book.effective.rule22;
import java.util.AbstractSet;
import java.util.Iterator;
/**
* 非静态内部类的典型使用
*/
public class MySet extends AbstractSet {
@Override
public Iterator iterator() {
return new MyIterator();
}
private class MyIterator<E> implements Iterator<E> {
@Override
public boolean hasNext() {
return false;
}
@Override
public E next() {
return null;
}
}
@Override
public int size() {
return 0;
}
}
如果声明成员类不需要访问外围实例,就要始终把static修饰符放在它的声明中,使其成为静态成员类。非静态成员类实例都会包含一个额外指向外围对象的引用,保存这份引用需要额外的空间和时间,并且会导致外围实例在符合gc条件时仍被保留。如果在没有外围实例的情况下,也需要分配外围实例。
Map内部的Entry对象。虽然每一个entry都与一个Map关联,但是Entry上面的方法并不需要访问该Map,此时,私有静态成员是最佳的选择。如果去掉static修饰符,Map仍然可以工作,但是每个entry都包含一个指向map的引用,这就浪费了时间和空间。
/**
* HashMap里面的Node类就是Entry
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
*/
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
当且仅当匿名类出现在非静态的环境中,它才有外围实例。但是即使它们出现再静态的环境中,也不可能拥有任何静态成员。 匿名类的一种常见用法是动态地创建函数对象,例如sort函数里面的Comparator;另一种常见用法是创建过程对象(process Object),例如Runnable、Thread或者TimerTask实例;第三种常见的就是在静态工厂方法的内部。
局部类用的最少。在任何可以声明局部变量的地方都可以声明局部类,局部类遵守同样的作用域规则。
如果一个嵌套类需要在单个方法外仍然是可见的, 或者它太长了,不适合放在方法内部,就应该使用成员类。如果成员类的每一个实例都需要一个指向其外围实例的引用,就要把成员类做成非静态的,否则就要做成静态的。假设这个嵌套类属于一个方法的内部,如果你只需要在一个地方创建实例,并且已经有了一个预置的类型可以说明这个类的特征,就要把它做成匿名类,否则,就要做成局部类。