13.使類和成員的可通路性最小化
盡可能使每個類或者成員不被外界通路,頂層的類(非嵌套的)和接口,隻有倆種可能的通路級别,包級私有的(package-private)和共有的(public)
//公有的,不安全
public static final Thing[] VALUES = {};
//把公有數組變成私有的,增加一個公有的不可變數組
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();
}
14.在共有類中使用通路方法而非公有域
共有類中設定私有域,通過方法擷取,也可以強加限制條件
public final class Time {
private static final int HOURS_PER_DAY = 24;
private static final int MINUTES_PER_HOUR = 60;
public final int hour;
public final int minutes;
public Time(int hour, int minutes) {
if(hour <0||hour>=HOURS_PER_DAY){
throw new IllegalArgumentException("Hour:"+hour);
}
if(minutes <0||minutes>=MINUTES_PER_HOUR){
throw new IllegalArgumentException("minutes:"+minutes);
}
this.hour = hour;
this.minutes = minutes;
}
}
15.使可變性最小化
不可變類隻是它的執行個體不能被修改的類。
類不可變:
不提供任何會修改對象狀态的方法
保證類不會被拓展,防止類子類化,一般的做法是使這個類變成final
使所有的域都是final
使所有的域都是私有的
確定對于任何可變元件的互斥行為,如果類具有指向可變對象的域,則必須確定該類的用戶端無法獲得指向這些對象的引用
//大部分不可變類使用這種模式,函數的做法,隻傳回函數的結果
public final class Complex {
private final double re;
private final double im;
public Complex(double re, double im) {
super();
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.re+im*c.im);
}
public Complex drive(Complex c){
double tmp = c.re*c.re + c.im * c.im;
return new Complex((re*c.re+im*c.im)/tmp,(im*c.re-re*c.im)/tmp);
}
@Override
public boolean equals(Object o){
if(o == this){
return true;
}
if(!(o instanceof Complex)){
return false;
}
Complex c = (Complex) o;
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 +")";
}
}
不可變對象本質上是線程安全的,不需要同步,因為它不會被修改
盡可能的重用現有的執行個體
public static final Complex ZERO = new Complex(0, 0);
public static final Complex ONE = new Complex(1, 0);
public static final Complex I = new Complex(0, 1);
不可變類可以提供一些靜态工廠,把頻繁通路的資料緩存起來,詳見第一條
不可變類的缺點:對于每個不同的值都需要一個單獨的對象
如果執行一個多步驟操作,每個操作産生一個新對象,除了最後的,其他的都會被丢棄,性能問題就出來了。
解決方法:猜測會用到哪些多步驟的操作,将他們作為基本類型提供,不可變類就沒必要每個步驟單獨建立一個對象
無法預測,提供公有的可變配套類,如String類的可變配套類是StringBuilder,和基本上已經廢棄的StringBuffer,在特定環境下,相對于BigInteger而言,BigSet也是可變配套類
讓不可變的類變成final的另一種做法:
讓類的所有構造器都變成私有的或包級私有的,并添加公有的靜态工廠來替代公有的構造器(見第一條)
public class Complex1 {
private final double re;
private final double im;
private Complex1(double re, double im) {
this.re = re;
this.im = im;
}
public static Complex1 valueOf(double re,double im){
return new Complex1(re, im);
}
}
靜态工廠可以建立方法,工廠的名字表明功能
BigInteger和BigDecimal如果可變,就要進行保護性拷貝
public static BigInteger safeInstance(BigInteger val){
if(val.getClass() !=BigInteger.class)
return new BigInteger(val.toByteArray());
return val;
}
為了提高性能,不可變類的規則可以如下:
沒有一個方法可以對對象的狀态産生外部可見的變化
許多不可變類擁有一個或多個非final的域,第一次請求計算出結果緩存入這些域,再有相同計算,傳回緩存的值。
堅決不要為每個get方法編寫一個相應的set方法。不要在構造器或者靜态工廠之外再提供公有的初始化方法
16.複合優先于繼承
繼承會導緻子類脆弱。
有個方法可以不用拓展現有的類,在新類中增加一個私有域,引用現有類的一個執行個體,現有類變成新類的一個元件,
//把一個set轉變成了另一個set,并增加了計數功能,
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 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 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();
}
@Override
public boolean remove(Object o) {
return false;
}
}
基于繼承的方法隻适用于單個具體的類,并且對于超類中所支援的每個構造器都要求有一個單獨的構造器,這裡的包裝類可以被用來包裝任何Set實作,可以結合任何先前的構造器一起工作
Set<Date> set = new InstrumentedSet<>(new TreeSet<>(cmp));
Set<E> set2 = new InstrumentedSet<>(new HashSet<>(capacity));
//替換原本沒有計數特性的Set執行個體
static void walk(Set<Dog> dogs){
InstrumentedSet<Dog> iDogs = new InstrumentedSet<>(dogs);
}
繼承違背了封裝原則,複合和轉發機制代替繼承可以避免脆弱性
17.要麼為繼承而設計,并提供文檔說明,要麼就禁止繼承
為了允許繼承,構造器構造器決不能調用可被覆寫的方法
/**
* @author 冒雲龍
* @date 2017年7月13日 下午10:48:06
* @describe
*/
public class Super {
public Super(){
overrideMe();
}
public void overrideMe(){}
}
public class Sub extends Super{
private final Date date;
public Sub() {
date = new Date();
}
@Override
public void overrideMe(){
System.out.println(date);
}
public static void main(String[] args) {
//overrideMe方法被super構造器調用時,構造器sub還沒有初始化date域
Sub sub = new Sub();
sub.overrideMe();
}
}
18.接口優于抽象類
現有的類可以很容易被更新,以實作新的接口
接口是定義混合類型的理想選擇
接口允許我們構造非層次結構的類型架構
public interface Singer {
AudioClip sing(Song s);
}
public interface Songwritter {
Song compose(boolean hit);
}
public interface SingerSongWriter extends Singer,Songwritter{
AudioClip strum();
void actSensitive();
}
16條包裝類模式,接口使得安全地增強類的功能成為可能
通過對導出的每個重要接口都提供一個抽象的骨架實作類,把接口和抽象類的優點結合起來
static List<Integer> intArrayAsList(final int[]a){
if(a==null){
throw new NullPointerException();
}
return new AbstractList<Integer>() {
public Integer get(int i){
return a[i];
}
@Override
public Integer set(int i,Integer val){
int oldVal = a[i];
a[i]=val;
return oldVal;
}
public int size(){
return a.length;
}
};
}
public abstract class AbstractMapEntry <K,V> implements Map.Entry<K, V>{
public abstract K getKey();
public abstract V getValue();
public V setValue(V value){
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object o){
if(o == this){
return true;
}
if(!(o instanceof Map.Entry))
return false;
Map.Entry<?, ?> arg = (Map.Entry) o;
return equals(getKey(),arg.getKey()) && equals(getValue(),arg.getValue());
}
private static boolean equals(Object o1,Object o2){
return o1==null ?o2==null:o1.equals(o2);
}
@Override
public int hashCode(){
return hashCode(getKey())^hashCode(getValue());
}
public static int hashCode(Object obj){
return obj == null?0:obj.hashCode();
}
}
19.接口隻用于定義類型
常量接口,隻包含靜态的final域,每個域都導出一個常量,使用這些常量的類實作這個接口,以避免用類名來修飾常量名,反例,不要用,應該用枚舉類型(第30條)或者使用不可執行個體化的工具類,見地4條
public interface PhysicalConstants {
static final double AAA=1;
static final double BBB=2;
static final double CCC=3;
}
工具類通過類名修飾這些常量名,也可以用靜态導入
接口應該隻被用來定義類型而不是用來導出常量
20.類層次優于标簽頁
标簽類過于冗長,容易出錯,效率低下
//标簽類,遠遠低于類層次結構
class Figure {
enum Shap {RECTANGLE,CIRCLE};
//标記字段,圖的形狀
final Shap shap;
double length;
double width;
//半徑
double radius;
Figure(double radius) {
shap = Shap.CIRCLE;
this.radius = radius;
}
Figure(double length, double width) {
shap = Shap.RECTANGLE;
this.length = length;
this.width = width;
}
double area(){
switch (shap) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError();
}
}
}
子類型化,為标簽類中的每個方法都定義一個包含抽象方法的抽象類
abstract class Figure1 {
abstract double area();
}
class Circle extends Figure{
final double radius;
Circle(double radius) {
this.radius = radius;
}
double area(){
return Math.PI * (radius * radius);
}
}
class Rectangle extends Figure{
final double length;
final double width;
Rectangle(double length,double width) {
this.length = length;
this.width = width;
}
double area(){
return length * width;
}
}
标簽類重構到層次中去
21.用函數對象表示政策
22.優先考慮靜态成員類
嵌套類是指被定義在另一個類的内部的類,嵌套類存在的目的應該隻是為了它的外圍類服務
嵌套類有四種:靜态成員類,非靜态成員類,匿名類,局部類,後三種都成為内部類
靜态成員類最好看作是普通的類,隻是湊巧聲明在一個類的内部,可以通路外圍類的所有成員,包括聲明為私有的成員,它是外圍類的一個靜态成員,如果被聲明為私有的,就隻能在外圍類的内部才可以被通路。
如果聲明成員類不要求通路外部執行個體,就要始終把static放在聲明中,沒有static,則每個執行個體都包含一個額外的指向外圍對象的引用,儲存這份引用要消耗時間和空間
匿名類的一種用法是動态的建立函數對象,如根據一組字元串的長度進行排序,另一種用法是建立過程對象,如線程執行個體,第三種是在靜态工廠方法的内部