zoukankan      html  css  js  c++  java
  • 《Effective Java》读书笔记

    Chapter 4 Classes and Interfaces

    Item 13: Minimize the accessibility of classes and members

    一个好的模块设计会封装所有实现细节,模块之间只能通过他们公开的API进行交流。因为这些模块互不影响(loosely coupled),所以可以进行单独测试等等。所以为了封装,我们应该把每一个class或member弄得越inaccessible越好。顶层的class和interface只能是public或者default,如果它只是用作实现,那么就应该是default的,如果它只被一个类使用,可以考虑把它换成这个类的private nested class。BTW,private和default的field可能会通过Serializable泄露到公开API中。protected级别的members要尽量少。但是,Override基类方法的时候,不允许你指定更inaccessible的修饰符,这是因为要满足“基类的实例能用的地方,子类的实例都能用”。还有实现interface里面的方法就只能声明成public的。如果想便于测试,最多弄成default级别的,不能再高了。Instance fields绝不应该是public的,因为如果写成public的话,你就缺少了很多灵活(因为当你发布给用户后,然后你又想给这个field换一个数据类型,就没办法了,并且当这个field被修改的时候,你也没法验证什么的)。static fields也一样,唯有一个例外:static final fields可以是public的,但是一定要确保这些fields指向primitive或者immutable对象(绝不能是数组),如果想用数组,你可以用点技巧:

    private static final Thing[] PRIVATE_VALUES = { ... };
    public static final List<Thing> VALUES =         
        Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
    //或者:
    private static final Thing[] PRIVATE_VALUES = { ... };
    public static final Thing[] values() {
        return PRIVATE_VALUES.clone();
    }
    

    Item 14: In public classes, use accessor methods, not public fields

    如果你这么做了,这个public fields就相当于对外的API,如果你想换个数据类型,你不得不改API(which会破坏client代码),你的fields值可能会随时被别人改变,你的fields在被获取之前也不能做一些辅助工作。所以说,如果是public class,用getter和setter吧。
    但如果是default的或者private的内部类,这样做其实没啥坏处。如果是immutable的fields,直接public出去也许不会“那么的坏”,但是也是受到质疑的。

    Item 15: Minimize mutability

    想让一个class是immutable的,请遵守以下几点:
    一.不要提供任何可以修改其状态的方法。
    二.确保它不能被extend。
    因为如果可以被extend的话,它的子类可能会无意或有意地修改自己的状态,这就违背了immutable。有两种方法:一是把class声明成final的;二是让它只有private的constructor,然后用static factory methods。第二种方法更好,详情见item1。注意:BigInteger和BigDecimal不是final的,所以使用的话要注意安全。
    三.所有的fields都应该是final的。
    不是“那么的绝对”,比如可以定义一些“cache fileds”来提升性能,因为final的fields只能在类被创建的时候就被初始化,之后就再也不能被赋值,而这些cache fileds(比如可以cache哈希码)可以在第一次被请求计算的时候,被赋值。
    四.所有的fields都应该是private的。
    五.如果有指向mutable objects的field,确保clients无法获得这些objects的引用,在constructor,getter和readObject方法中使用defensive copies(Item 76)。

    functional approach就是每个方法都会返回一个新的实例(比如String),还有procedural or imperative approach书上一句带过,所以也不知何意。immutable对象的好处在于:
    一.被创建的时候它的状态就确定了并且会永远保持不变,然而mutable的对象可能有复杂的状态变化,如果没有可靠的文档,可能很难正确地使用。
    二.天生线程安全。所以可以毫无顾忌地在任何地方复用它的某个instance。可以用static factory methods来实现(item 1)。并且完全不需要clone方法和“copy constructor”(Java中的String虽然提供了,但千万别用)。
    三.有时候immutable对象的的内部东西也可以复用,比如BigInteger内部有个int数组表示绝对值,一个int表示符号,如果你想得到一个符号相反但是绝对值相等的BigInteger,只要返回一个新的BigInteger 它内部的int数组还是指向同一个数组,但是它内部的int变一下就行。
    四.Immutable objects可以很好地作为其他对象的组成部分。比如作为HashMap的key和HashSet的元素。

    唯一的缺点就是 每一个值都需要一个新的对象,导致性能受损。解决方法请参考String和Stringbuilder。

    有一个注意事项:如果你的immutable对象中有fields指向mutable对象,那么如果要实现Serializable,必须另加手段(比如显示写一个readObject),否则坏人可能通过某种手段把你的object变成可变的。

    总结一下,如果能让class是immutable的,尽量这样做,特别是对一些小的值类型(而java.util.Date却是mutable的,是个反面教材)。如果一个class不能是immutable,尽量让它尽可能多的fields是final的,让这个class的对象的状态越少越好。

    Item 16: Favor composition over inheritance

    继承其实会破坏封encapsulation。在同一个package内用inheritance是安全的,但是在不同的package间用inheritance就很惨了,注这里的inheritance只是指extends class而不包括implement(extends) interface。比如基类的实现者是类库作者,然后client继承了它,然后子类的行为可能是依赖于某些基类的方法的,而基类的方法可能随着一次次的release而改变,这样就会破坏client的子类了(除非基类的作者很好地写了文档,并说明如何继承该类)。下面举个反例,假设我们想有这么一个HashSet,再每次有元素被放进去的时候,就记录一下,统计一共有几次放入元素的操作:

    // Broken - Inappropriate use of inheritance!
    public class InstrumentedHashSet<E> extends HashSet<E> {
    	// The number of attempted element insertions
    	private int addCount = 0;
    	@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;
    	}
    }
    

    然后如果你加了三个元素进去:

    InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
    s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
    

    那么你会发现得到的addCount是6,显然与你设计的意图不符。这是因为在HashSet内部的addAll方法会调用add方法,所以相当于先加了3,然后对每一个元素又加了1。那我们不override addAll方法了行吗?虽然可以暂时“解决问题”,但是其实我们作了一个假设就是:在allAll方法内部会调用add。但是这是个实现细节,可能会随着新版本的发布而改变,所以你的子类还是不靠谱儿。
    再比如,如果你的子类不override任何方法,只是新增一些方法。那么你可能运气不好,以后的新版本的基类里刚好新增了一个签名和 你的新增方法 一模一样的方法,那默就吃瘪。
    总之就是如果基类新增或修改了它的方法,由于某些你的假定,可能会对子类造成影响。那么怎么办呢?其实你可以设置一个private的field,并指向一个“基类”的实例:

    public class InstrumentedSet<E> {
            private final Set<E> s;
        private int addCount = 0;
        @Override public boolean add(E e) {
        	addCount++;
        	return s.add(e);
        }
        @Override public boolean addAll(Collection<? extends E> c) {
        	addCount += c.size();
        	return s.addAll(c);
        }
        public int getAddCount() {
        	return addCount;
        }
    }
    

    现在由于没有了继承关系,所以不会产生多态,所以调用addAll不会调用add了。然后你可以把所有Set的方法都写一个对应版本的方法,然后只要“s.对应方法()”就行了。为了更好的代码复用,比如你又想实现一个别的类,它也拥有Set的这些行为,那么你又要再重新写一遍这些对应方法就很麻烦,所以你可以直接写一个通用基类:

    // Reusable forwarding class
    public class ForwardingSet<E> implements Set<E> {
    	private final Set<E> s;
    	public ForwardingSet(Set<E> s) {
    		this.s = s;
    	}
    	public boolean add(E e) {
    		return s.add(e);
    	}
    	...//其他方法
    }
    

    然后再把刚才那个InstrumentedSet<E>直接继承这个类,再稍微改动一点就好了,比如把s.add()改成super.add()。这种方法我们叫做forwarding,这个新类里面的方法我们叫做forwarding方法。想继承Set的话,直接继承它(上面这个ForwardingSet)就好了,但本质上是“composition ”。
    相比继承,这种方法更加灵活,比如继承的时候你只能选择是继承HashSet还是TreeSet,这里的话你可以随时改变你内部的那个“component”:

    Set<Date> s = new InstrumentedSet<Date>(new TreeSet<Date>());
    Set<E> s2 = new InstrumentedSet<E>(new HashSet<E>());
    

    这个InstrumentedSet我们叫它wrapper class,因为它wrapp了另一个Set instance。这也叫Decorator pattern,因为它给Set装饰了一个新的特性。有时候也被叫delegation,虽然严格定义上不正确(但是delegation的严格定义是什么不用纠结了)。
    这种方法的缺陷很少,比如不适合callback,虽然书上没具体说,但我觉得你可以想一下观察者模式,被wrap的类假设是个观察者,然后被创建的时候把自己注册给了某个Subject,那么当它收到通知的时候,是直接跳过它的wrapper的。
    那么什么时候用继承?你一定要先仔细想想是不是有“is-a”的关系,如果有那么一点感觉不是,就别用。Java库里就有很多这样的错误,比如Stack居然继承了Vector,Properties居然继承了HashTable,p.getProperty(key)和p.get(key)会得到不同结果,因为后者来自HashTable,而且Properties的设计者的本意是只能将String作为key,但是如果你access underlying的HashTable的话,是可以放别的类型的key进去的,这样的话某些Properties的API就不能用了。而且继承还有不好的地方就是会把有缺陷的基类的方法带到子类来,而composition可以隐藏掉这些缺陷。

    Item 17: Design and document for inheritance or else prohibit it

    一个类必须准确地把 “自己的public或者protected的方法 会调用哪些其他overridable的方法,以何种顺序,每一次调用的结果对后续处理的影响;以及何时可能会调用这些个overridable方法(比如来自后台线程或者static initializers的调用)”写入文档。比如上一个item中的HashSet的addAll就应该说明自己在内部会调用add。By convention,这个说明一般是在文档中的最后以“This implementation...”开头,意思就是接下来的描述是关乎这个方法的内部情况的,但不会随着release的新版本而改变(也就是你做了个承诺,你承诺这种self-use pattern(也就是调用同一个类中的其他方法,如addAll会调用all)永远不会改变)。举个例子,java.util.AbstractCollection的public boolean remove(Object o)的文档中的最后一段是:
    This implementation iterates over the collection looking for the specified element.然后说明了会用iterator的remove方法来移除一个元素。
    所以说这段说明告诉了我们:override iterator方法会影响remove这个方法。但是这种做法岂不是违背了“we should describe what a given method does and not how it does it”,确实如此,但是没办法,因为“inheritance violates encapsulation”。想要给一个 可以被安全地继承的class 写文档,就必须描述这些实现细节。
    另外,为了subclass的实现者考虑,你还要说明某个protected的方法(偶尔也可以是field),是怎么用的,可以怎么用,会被谁用。比如java.util.AbstractList的
    protected void removeRange(int fromIndex, int toIndex)
    的文档就说明了:这个方法会移除fromIndex到toIndex范围内的所有元素。并且,注意重点来了,这个方法会被这个list或其sublists的clear方法调用。所以你可以override这个方法,来提升clear的效率。虽然这个说明对 不需要继承这个class的end-user 来说没用,但写这本书的时候,还没有什么办法来把这种说明和普通api文档分开。
    当你写完一个class designed for inheritance后,一般需要写三个subclass来进行测试,并根据测试决定是否要把你的class的某些成员从private变为protected或反之。

    当允许继承的时候,constructors必须不能调用overridable的方法,因为基类的constructor在子类的constructor之前运行,所以说如果你这时候去调用一些子类中的overriding的方法,也许这些overriding的方法是基于某些子类中需要初始化的变量的,那么这时候这些变量还没有被初始化。我觉得很好想象了,具体例子就不写了。同理,clone和readObject(序列化里面的)方法也要遵循同样的原则。
    一个class designed for inheritance最好不要实现Cloneable和Serializable接口,否则会给subclass的实现者很大压力,参考item11和item 74。BTW,如果你的class designed for inheritance实现了Serializable,记得把readResolve和writeReplace(如果有的话)声明为protected(rather than private),否则会被subclass默默地忽略。

    如果一个普通的类,如果它is not designed and documented to be safely subclassed,那就别继承它(或者从类库实现者的角度就是,让它不能被继承)。顺便一提,如果你完全消除了一个类的self-use of overridable methods,那么它就是可以被安全地继承的,因为override一个方法绝不会影响到其他方法,比如你可以这么做:先把每一个overridable的方法中的代码移到一个对应的private的“helper method”里去,然后把这个overridable的方法改成直接调用对应的“helper method”,然后把每一个对overridable方法的self-use都替换成对 对应的“helper method” 的调用。

    Item 18: Prefer interfaces to abstract classes

    已经存在的class可以很轻松地被新增一个接口实现,但是却不能给一个已经继承了别的类的类新增一个abstract class(因为只能继承一个类)。
    接口可以很好地实现“nonhierarchical types”,比如定义两个接口Singer和Songwriter,他俩是没有hierarchical上的关系的。然后你就可以写一个类同时实现者两个接口。但是如果换成是两个抽象类就不行,因为你只能继承其中一个,如果你一定要让一个类同时拥有Singer和Songwriter的能力,那你只能再新定义一个类比如叫SingerAndSongwirter,就很白痴了。
    你可以用一种叫skeletal implementation的方法来结合interface的好处和抽象类的可以提供默认实现代码的功能。举个例子:AbstractCollection, AbstractSet, AbstractList, AbstractMap。这样的话,你就不需要自己实现interface中的所有方法了。
    但是用抽象类比接口的好的地方就在于,如果你想给接口新增一个方法,那么你本来已经实现这个接口的类就都编译不通过了,但是用抽象类的话你只要提供一个默认实现就行了,所以说在设计接口的时候一定要仔细。
    但是,Java 8支持接口中的默认方法了,就不存在以上问题了,我个人感觉连那个什么skeletal implementation都不需要了。

    Item 19: Use interfaces only to define types

    意思就是,接口是用来定义类型的,你可以用这个类型来refer to instances of the class(that implements the Interface),这应该是接口的唯一的用法和作用,而不应该把接口用作别的目的。
    关于接口的用法有个反例,就是这个接口只包含常量(放在interface里的field自动static+final),而没有任何方法,比如:

    // Constant interface antipattern - do not use!
    public interface PhysicalConstants {
        static final double AVOGADROS_NUMBER = 6.02214199e23;// Avogadro's number (1/mol)
        static final double ELECTRON_MASS = 9.10938188e-31;// Mass of the electron (kg)
    }
    

    这个接口的意义仅仅就是为了:如果实现这个接口,那么在类里面就可以直接用,比如AVOGADROS_NUMBER,而不需要用PhysicalConstants.AVOGADROS_NUMBER。作者说这是一种非常不好的做法,因为,“某个类需要这些常量”这件事是一个实现细节,不应该通过接口而暴露成API出来,这个类的client并不需要知道这个interface,相反这个interface还会让他们疑惑。而且你一旦发布,在以后的版本里如果想去掉 实现这个interface,都不行。接口的作用应该是规定了:client用这个接口类型的变量能做些什么。而不是为了“减少代码长度”。
    那么应该怎么做?如果这些常量只跟某个类有关,那么就应该直接放到那个类里面去,比如Integer就有MIN_VALUE和MAX_VALUE常量。或者如果有很多类都需要,那你应该放到一个noninstantiable的utility class里去,然后用static import就能“减少代码长度”了。

    Item 20: Prefer class hierarchies to tagged classes

    tagged class就是一种很傻的实现,比如说定义了一个类叫Figure(形状),然后初始化或者调用某些方法的时候根据某个变量进行switch case,比如:enum Shape { RECTANGLE, CIRCLE },然后如果是RECTANGLE,那么就初始化一些RECTANGLE独有的fields(比如边长),如果是CIRCLE就初始化半径什么的。反正这种做法的坏处一大堆。正确做法就是定义一个Figure类,再定义一个Rectangle类继承Figure,Circle同理。本来看完这条item我都不想写,因为觉得这是最基本的OO的多态,但后面想想自己可能确实无意中写出这样类似的“switch case代码“。

    Item 21: Use function objects to represent strategies

    一个只有一个方法的instance常常被叫做function object,也可以叫“一个concrete strategy”(strategy pattern),对应C#中的委托实例。这样的instance最好被弄成singleton,因为你只是需要一个方法而已。然后这个instance的class需要实现某个接口,这个接口里面就只有一个方法,对应C#委托中的委托类型。当你要传一个function object给一个方法时,比较老的方法是可以new一个匿名类,但是这样的缺点是如果你多次new,就会构造多个对象,是不必要的,所以也许你可以把它存起来,然后复用:

    class Host {
        private static class StrLenCmp implements Comparator<String>,{
            public int compare(String s1, String s2) {
                return s1.length() - s2.length();
            }
        }
        public static final Comparator<String> STRING_LENGTH_COMPARATOR = new StrLenCmp();
    }
    

    我用eclipse做了个测试,private的内部类即使有个public的constructor,在其Outer class之外的地方也不能new一个这个内部类,但是请看以上代码,compare方法由于是interface里面的,所以必须是public的,而在Host(上面代码中的类名)外部,想要用这个strategy的时候,只要是需要一个Comparator<String>的地方,就可以传一个Host.STRING_LENGTH_COMPARATOR进去,也就可以用它的compare方法了(内部类虽然是private的,但他实现的接口是public的)。
    Java中的String类就用这个方法,可以返回一个CASE_INSENSITIVE_ORDER。
    当然,Java8中的Lambda表达式不知道有没有上面说的缺陷。

    Item 22: Favor static member classes over nonstatic

    一个static member class不过就是个普通的class,只不过恰好被声明在另一个类里面,并且可以access这个类的所有成员。static member class的一种用法是定义一些 只跟自己的outer class有关系的 public helper class,比如在Calculator这个类中定义一个static public enum Operation{PLUS,MINUS},然后Calculator这个类的client就可以用,比如Calculator.Operation.PLUS了。
    而每一个nonstatic member class的实例都必须和一个instance of its outer class相关联。一般是这么用:在一个outer class的instance method中new一个nonstatic member class,这时候这种“关联”会自动建立,比如:

    // Typical use of a nonstatic member class
    public class MySet<E> extends AbstractSet<E> {
        public Iterator<E> iterator() {
            return new MyIterator();
        }
        private class MyIterator implements Iterator<E> {...}
    }
    

    如果你的member class不需要一个其outer class的instance引用的话,就把它定义为static的。比如上面的MyIterator内部需要access一些MySet中的instance field,所以只能定义成nonstatic的。但如果不需要outer class的一个实例,你却还是定义成nonstatic的,那么就会导致额外的不必要的开销,比如必须先new一个outer class才能new一个这个member class,再比如可能outer class的对象已经可以被GC了,但是你的member class实例还保存着对它的引用导致无法GC。
    private static member class经常被用作其外部类的“components”,比如Map中的Entry。

    Anonymous class有很多限制,比如它不能有static的成员。你也不能对他们用instanceof(因为没类名)或者定义constructor什么的。也不能让它实现多个接口。为了可读性,anonymous classes必须尽量简洁。
    Local classes是最少用的,它们的声明和作用域和local variables一样。

  • 相关阅读:
    *Delete Duplicate Emails
    Rising Temperature [MySQL]
    mysql链接表,connection string, federated engine
    谷歌在招什么样的人?
    用memcached的时候找key找不到,写了个命令来找找
    jna
    绕树三匝,无枝可依
    vm lxc
    linux proxy
    elisp
  • 原文地址:https://www.cnblogs.com/raytheweak/p/7189999.html
Copyright © 2011-2022 走看看