【Java基礎】類和介面

cryAllen發表於2016-06-24

Num1:使類和成員的可訪問性最小化

要區別設計良好的模組與設計不好的模組,最重要的因素在於,這個模組對於外部的其他模組而言,是否隱藏其內部資料和其他實現細節。設計良好的模組會隱藏所有的實現細節,把它的API與它的實現清晰地隔離開來。也稱呼為封裝。

所以有這麼一句話:儘可能地使每個類或者成員不被外界訪問,可以包括實體宣告中所出現的訪問修飾符共同決定的,有四種訪問級別,如下:

  • 私有的,private
  • 包級私有的,default
  • 受保護的,protected
  • 公有的,public

Num2:在公有類中使用訪問方法而非公有域

如果類可以在它所在的包的外部進行訪問,就提供訪問方法,以保留將來改變該類的內部表示法的靈活性。

class Point {
    private double x;
    private double y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    public void setX(double x) {
        this.x = x;
    }

    public void setY(double y) {
        this.y = y;
    }
}

總之,公有類永遠都不應該暴露可變的域。

Num3:使可變性最小化

不可變類只是其例項不能被修改的類。每個例項中包含的所有資訊都必須在建立該例項的時候就提供了,並在物件的整個生命週期內固定不變。Java平臺類庫中包含許多不可變的類,其中有String、基本型別的包裝類、BigIntegerBigDecimal,存在不可變的類有許多理由:不可變的類比可變類更加易於設計、實現和使用,它們不容易出錯,且更加安全。

有五條規則,使得類成為不可變:

  1. 不要提供任何會修改物件狀態的方法
  2. 保證類不會被擴充套件。一般做法是使得這個類成為final
  3. 使所有域都是final的
  4. 使所有的域都成為私有的
  5. 確保對於任何可變元件的互斥訪問
public final class Complex {
    private final double re;
    private final double im;

    private Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }

    public static Complex valueOf(double re, double im) {
        return new Complex(re, im);
    }

    public static Complex valueOfPolar(double r, double theta) {
        return new Complex(r * Math.cos(theta), r * Math.sin(theta));
    }

    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);

    // Accessors with no corresponding mutators
    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 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;

        // See page 43 to find out why we use compare instead of ==
        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)";
    }
}

不可變物件本質上是執行緒安全的,它們不要求同步。

Num4:複合優先於繼承

與方法呼叫不同的是,繼承打破了封裝性,換句話說,子類依賴於其超類中特定功能的實現細節。不過有種設計叫做“複合”,在新的類中增加一個私有域,它引用現有類的一個例項,因為現有的類變成了新類的一個元件,新類中的每個例項方法都可以呼叫被包含的現有類例項中對應的方法,並返回它的結果。這樣得到的類將會非常穩固,它不依賴於現有類的實現細節,看段程式碼。

轉發類

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();
    }
}

繼承類

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());
    }
}

Set介面的存在使得IntrumentedSet類的設計成為可能,因為Set介面儲存了HashSet類的功能特性。除獲得了健壯性之外,這種設計也帶來了格外的靈活性。

包裝類幾乎沒有什麼缺點,需要注意一點的是,包裝類不適合用在回撥框架(CallBack Framework)中,在回撥框架中,物件需要把自身的引用傳遞給其他的物件,用於後續的呼叫。

只有當子類真正是超類的子型別的時,才適合用繼承,換句話說,對於兩個類A和B,只有當兩者之間確實存在"is-a"關係的時候,類B才應該擴充套件類A,如果你打算讓類B擴充套件類A,就應該問問自己:每個B確實也是A嗎?如果答案是否定的,通常情況下,B應該包含A的一個私有例項,並且暴露一個較小的,較簡單的API:A本質上不是B的一部分,只是它的實現細節而已。

Num5:介面優先於抽象類

Java程式設計中提供了兩種機制:介面和抽象類。這兩種機制之間最明顯的區別在於,抽象類執行包含某些方法的實現,但是介面則不允許。一個更為重要的區別在於,為了實現由抽象類定義的型別,類必須成為抽象類的一個子類。

有三個方式:

  • 現有的類可以很容易被更新,以實現新的介面
  • 介面是定義混合型別的理想選擇
  • 介面允許我們構造非層次結構的型別框架

雖然介面不允許包含方法的實現,但是,使用介面來定義型別並不妨礙你成為程式設計師提供實現上的幫助。通過你匯出的每個重要介面都提供一個抽象的骨架來實現類,把介面和抽象類的優點結合起來。介面的作用仍然是定義型別,但是骨架實現類接管了所有與介面實現相關的工作。

public class IntArrays {
    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]; // Autoboxing (Item 5)
            }

            @Override
            public Integer set(int i, Integer val) {
                int oldVal = a[i];
                a[i] = val; // Auto-unboxing
                return oldVal; // Autoboxing
            }

            public int size() {
                return a.length;
            }
        };
    }

    public static void main(String[] args) {
        int[] a = new int[10];
        for (int i = 0; i < a.length; i++)
            a[i] = i;
        List<Integer> list = intArrayAsList(a);

        Collections.shuffle(list);
        System.out.println(list);
    }
}

需要注意的是,介面一旦被公開發行,並且已被廣泛實現,再想改變這個介面幾乎是不可能的,你必須在初次設計介面的時候就應該保證介面是正確的。總之,介面通常是定義允許多個實現型別的最佳途徑。

相關文章