Effective Java读书笔记三(Java Tips.Day.3)

TIP13.使类和成员的可访问性最小化(封装)


  • 实例域决不能是公有的
  • 不应当具有公有的静态final数组域,或者返回这种域的方法。作为替代,你可以使公有数组变成私有的,并增加一个公有的不可变列表:
private static final Thing[] VALUES[] = {...};
public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

或者,添加一个公有方法来返回该数组的一个备份

public static final Thing[] values(){
    return VALUES.clone();
}

TIP14.在共有类中使用访问方法而非公有域


  • 共有类永远不应该暴露可变的域
  • 如果类是包私有的,或者是私有的嵌套类,直接暴露它的数据域并没有本质错误

TIP15.使可变性最小化(不可变类)


  • 不可变类,即实例不能被修改的类。所有信息必须在创建实例时提供,并在对象的整个生命周期内固定不变。
  • 不要提供任何会修改对象状态的方法
  • 保证类不会被扩展
  • 使所有的域都是final的
  • 使所有的域都是私有的
  • 确保对任何可变组件的互斥访问
使用functional的方式来获取新状态的对象

考虑不可变类Point拥有两个域x和y,需要得到新的Point(x+deltaX,y+deltaY),则应该这样处理:

pulibc Point add(float deltaX,float deltaY){
    return new Point(this.x+deltaX,this.y+deltaY);
}
不可变类的一些特性
  • 它是线程安全的,因此可以被自由的共享。
  • 频繁重用:对一些经常用到的值,提供公有的static final常量,比如:
    public static final Point PointZero = new Point(0.0,0.0)

  • 不需要,也不应该为不可变类提供clone方法,或拷贝构造器。

  • 不可变对象为其他对象提供了大量的构件(building blocks)。
  • 唯一的缺点是,对于每个不同的值,都需要一个单独的对象,可能导致性能问题。
防止被扩展
  • 使用final修饰不可变类
  • 构造器私有,或包私有,然后用静态工厂方法来代替构造器
无法使用不可变类

有些类不能做出不可变的,仍然应该尽可能的限制他的可变性。
除非有足够的理由使域变成非final的,否则,每个域都应该是final。
不要在构造器和静态工厂之外,再额外的提供初始化方法,除非有令人信服的理由必须这么做。

TIP16.复合优先于继承


继承可能出现的问题

继承打破了封装性,子类依赖于父类中特定功能的实现细节。
考虑下面的InstrumentedHashSet类,扩展了HashSet,用addCount来记录已添加的元素数量。

import java.util.*;  

public class InstrumentedHashSet<E> extends HashSet<E> {  
    // The number of attempted element insertions  
    private int addCount = 0;  

    public InstrumentedHashSet() {  
    }  

    public InstrumentedHashSet(int initCap, float loadFactor) {  
        super(initCap, 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<String>();  
        s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));      
        System.out.println(s.getAddCount());  
    }  
} 

s中的确只添加了3个元素,但s.getAddCount()返回的值是6。
期望的调用过程是这样的:

s.addAll(e1,e2,e3) -> 
    [
        addCount+=3; 
        s.super.addAll(e1,e2,e3) -> 
            [
                s.super.add(e);
            ] * loop(3)
    ]

但因为子类覆盖了父类的add(e)方法,由于多态机制,会调用子类的实现。
于是实际的调用过程是这样的:

s.addAll(e1,e2,e3,....) -> 
    [
        addCount+=3; 
            [
                s.add(e1,e2,e3) -> 
                    [
                        addCount ++; 
                        s.super.add(e);
                    ]
            ] * loop(3)
    ]

如果子类不覆盖addAll方法,就可以修正这个类。
然而,这样的类虽然可以正常工作,但它的功能正确性却依赖于:父类的addAll是在它的add方法上实现的。
这种“自用性”是实现细节,不是承诺。父类不承诺这个实现永远不变,而一旦发生改变,子类仍然可能无法正常工作。
如果子类覆盖addAll方法来遍历参数集合,为每个元素调用一次(已覆盖的)add方法,可以得到正确的结果,不管父类的addAll是否依赖add实现。因为父类的addAll实现将不会再被调用到。这实际上相当于重新实现了超类的方法。而这样做并不可靠,因为无法访问父类的私有域,可能有些方法无法实现。

使用复合(composition)

转发(forwarding): 新类中的每个实例方法,都可以调用被包含的现有实例中对应的方法,并返回它的结果。这个过程称为转发。

下面重新实现上面的例子:

import java.util.*;  
//可重用的转发类
public class ForwardingSet<E> implements Set<E> {  
    private final Set<E> s;  
    public ForwardingSet(Set<E> s) { this.s = s; }  

    public void clear()               { s.clear();            }  
    public boolean contains(Object o) { return s.contains(o); }  
    public boolean isEmpty()          { return s.isEmpty();   }  
    public int size()                 { return s.size();      }  
    public Iterator<E> iterator()     { return s.iterator();  }  
    public boolean add(E e)           { return s.add(e);      }  
    public boolean remove(Object o)   { return s.remove(o);   }  
    public boolean containsAll(Collection<?> c)  
                                   { return s.containsAll(c); }  
    public boolean addAll(Collection<? extends E> c)  
                                   { return s.addAll(c);      }  
    public boolean removeAll(Collection<?> c)  
                                   { return s.removeAll(c);   }  
    public boolean retainAll(Collection<?> c)  
                                   { return s.retainAll(c);   }  
    public Object[] toArray()          { return s.toArray();  }  
    public <T> T[] toArray(T[] a)      { return s.toArray(a); }  
    @Override public boolean equals(Object o)  
                                       { return s.equals(o);  }  
    @Override public int hashCode()    { return s.hashCode(); }  
    @Override public String toString() { return s.toString(); }  
}  


import java.util.*;  
//实现类
public class InstrumentedSet<E> extends ForwardingSet<E> {  
    private int addCount = 0;  

    public InstrumentedSet(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) {  
        InstrumentedSet<String> s =  
            new InstrumentedSet<String>(new HashSet<String>());  
        s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));      
        System.out.println(s.getAddCount());  
    }  
}  

这正是装饰模式(Decorator)。

is-a还是has-a问题

如果不确定,一律考虑用复合的方式。
Java平台类库中,有一些违反这条原则的地方。比如stack并不是vector,所以Stack不应该扩展Vector。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值