程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA編程入門知識 >> 和我一起學Effective Java之方法

和我一起學Effective Java之方法

編輯:JAVA編程入門知識

 

方法

第38條:檢查參數的有效性

  • 公有方法:要用Javadoc的@throws標簽在文檔中說明違反參數值限制時拋出的異常。
/**
     * Returns a BigInteger whose value is {@code (this mod m}).  This method
     * differs from {@code remainder} in that it always returns a
     * <i>non-negative</i> BigInteger.
     *
     * @param  m the modulus.
     * @return {@code this mod m}
     * @throws ArithmeticException {@code m} &le; 0
     * @see    #remainder
     */
    public BigInteger mod(BigInteger m) {
        if (m.signum <= 0)
            throw new ArithmeticException("BigInteger: modulus not positive");

        BigInteger result = this.remainder(m);
        return (result.signum >= 0 ? result : result.add(m));
    }
  • 非公有方法:使用斷言檢查參數
  private static void sort(long array[],int offset,int length){
        //assert 斷言
        assert array!=null;
        assert offset>=0&&offset<=array.length;
        assert length>=0&&length<= array.length-offset;
    }
    
    
    
    sort(null,0,0);
    //Exception in thread "main" java.lang.AssertionError

斷言默認是關閉的

  • Java編譯中啟用斷言:-enableassertions,簡寫為-ea

  • IDEA啟用斷言:Run-->Edit Configuration-->VM Options-->添加-ea

第39條:必要時進行保護性拷貝

保護性地設計程序(假設類的客戶端會盡其所能破壞類的約束條件)

//下面的類聲稱可以表示一段不可變的時間周期
public final class Period {
    private final Date start;
    private final Date end;

    public Period(Date start,Date end){
        if(start.compareTo(end)>0)
            throw new IllegalArgumentException(start+"after"+end);
        this.start = start;
        this.end = end;
    }
    public Date getStart(){
        return start;
    }

    public Date getEnd() {
        return end;
    }
}

上面那個Period類表面上看是不可變類,並且加了約束條件:周期的起始時間不能再結束時間之後。但由於Date類是可變的,很容易就違反這個約束條件。如下。

 public static void main(String[] args) {
        Calendar calendar = Calendar.getInstance();

        calendar.set(2008,Calendar.JULY,8);
        Date start = calendar.getTime();

        calendar.set(2016,Calendar.JULY,3);
        Date end = calendar.getTime();

        Period period = new Period(start,end);
        start.setYear(2017);//修改了Period類

        Date start1 = period.getStart();
        System.out.println(start1.getYear());//2017
    }

為避免類的實例的內部信息受到攻擊,可對構造器中的每個可變參數進行保護性拷貝

 public Period(Date start,Date end){
//        if(start.compareTo(end)>0)
//            throw new IllegalArgumentException(start+"after"+end);
//        this.start = start;
//        this.end = end;
        this.start = new Date(start.getTime());
        this.end = new Date(end.getTime());
        if(this.start.compareTo(this.end)>0)
            throw new IllegalArgumentException(start+"after"+end);
    }

雖然替換構造方法能避免上述的攻擊,但仍然可以改變Period實例。它的訪問方法提供了對其可變內部成員的訪問能力

period.getEnd().setYear(1998);//修改了Period實例

對於後一種攻擊,只需修改方法,使它返回可變內部域的保護性拷貝即可:

    public Date getStart(){
        return new Date(start.getTime());
    }

    public Date getEnd() {
        return new Date(end.getTime());
    }

參數的保護性拷貝:

  • 不可變類
  • 客戶提供的對象進入內部數據結構中(編寫方法或構造器時)

下面的例子就是使用可變對象作為Map的鍵,導致Map的約束條件被破壞。

        Calendar calendar = Calendar.getInstance();
        calendar.set(2008,Calendar.JULY,8);
        Date start = calendar.getTime();

        Map<Date,String> map = new HashMap<>();
        map.put(start,start.getYear()+"");
        start.setYear(1999);
        String s = map.get(start);
        System.out.println(s);
        //修改後,使用Date.getTime()返回的long值作為Map內部時間的表示方法
        Map<Long,String> newMap = new HashMap<>();
        newMap.put(start.getTime(),start.getYear()+"");

結論:

  • 最好使用不可變對象作為對象內部的組件。

  • 類具有從客戶端得到或返回客戶端的可變組件,類就必須保護性地拷貝這些組件。

  • 拷貝的成本受到限制,且類信任它的客戶端不會不恰當地修改組件,就可在文檔中指明客戶端的職責是不得修改受到影響的組件。

第40條:謹慎設計方法簽名

  • 謹慎選擇方法的名稱

可參看Google Java Style中的方法命名標准

  • 不要過於追求提供便利的方法
  • 避免過長的參數列表

目標是4個參數,或者更少。 相同類型的長參數序列格外有害。 容易弄錯順序,但程序仍可以編譯和運行。

縮短過長的參數列表的三種辦法:

  • 1.分解成多個方法
  • 2.創建輔助類
  • 3.Builder模式

對於參數類型,優先使用接口而不是類。

methodA(Map<K,V> map);

//methodA(HashMap<K,V> map);

對於boolean參數,優先使用兩個元素的枚舉類型。

public enum TemperatureScale{
  F,C
}

Thermometer.newInstance(TemperatureScale.C);

慎用重載

public class CollectionClassifier {

    public static String classify(Set<?> set){
        return "Set";
    }

    public static String classify(List<?> list){
        return "List";
    }

    public static String classify(Collection<?> collection){
        return "Unknown Collection";
    }

    public static void main(String[] args) {
        Collection<?> [] collections = {
                new HashSet<String>(),
                new ArrayList<BigInteger>(),
                new HashMap<String,Object>().values()
        };

        for(Collection collection:collections){
            System.out.println(classify(collection));
        }
    }
}

//
//Unknown Collection
//Unknown Collection
//Unknown Collection

classify方法被重載(overloaded)了,而調用哪個重載方法是在編譯時做出決定的。for循環中的三次循環,參數的編譯時類型都是相同的:Collection<?>。即使每次循環的運行時類型都是不同的。

重載方法(overloaded method)的選擇是靜態的,被覆蓋的方法(overridden method)的選擇是動態的。調用哪個被覆蓋的方法是在運行時做出決定的。

public class Overriding {
    public static void main(String[] args) {
        Wine [] wines = {
            new Wine(),
                new SparklingWine(),
                new Champagne()
        };
        for(Wine wine:wines){
            System.out.println(wine.name());
        }
    }
}
class Wine{
    String name(){
        return "wine";
    }
}
class SparklingWine extends Wine{
    @Override
    String name() {
        return "sparkling wine";
    }
}
class Champagne extends SparklingWine{
    @Override
    String name() {
        return "champagne";
    }
}

//output:
//wine
//sparkling wine
//champagne
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved