類和接口

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,則每個實例都包含一個額外的指向外圍對象的引用,保存這份引用要消耗時間和空間

匿名類的一種用法是動態的創建函數對象,如根據一組字符串的長度進行排序,另一種用法是創建過程對象,如線程實例,第三種是在靜態工廠方法的內部

發佈了63 篇原創文章 · 獲贊 1 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章