码迷,mamicode.com
首页 > 编程语言 > 详细

和我一起学Effective Java之方法

时间:2016-04-08 19:59:57      阅读:219      评论:0      收藏:0      [点我收藏+]

标签:

 

方法

第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

和我一起学Effective Java之方法

标签:

原文地址:http://www.cnblogs.com/JohnTsai/p/5369225.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!