Effective java 总结7- 方法

Effective java 总结7 - 方法

第49条 检查参数的有效性

Objects.requireNonNull

// 灵活且方便,不必再手工进行null检查
this.strategy = Objects.requireNonNull(strategy, "strategy");

assertion断言

断言如果失败,抛出AssertionError, 没有起到作用也没有成本开销

// 非公有的方法通常使用断言检查参数
private static void test(long a[], int offset, int length){
    assert a != null;
    assert offset >= 0 && offset <= a.length;
    ...
}

构造器参数检查非常重要, 避免构造的对象违反类的约束条件

总结

编写方法or构造器时应该考虑参数的限制,且写入文档注释,通过显示的检查来实施限制,且设计方法时,尽量考虑其通用性,符合实际需要


第50条 必要时进行保护性拷贝

// 约束 结束时间 > 开始时间
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...;
        this.start = start;
        this.end = end;
    }
    
    public Date start(){
        return start;
        // return new Date(start.getTime()); //保护性clone
    }
    
    public Date end(){
        return end;
        // return new Date(end.getTime()); //保护性clone
    }
}

// Date 是可变的,因此Period创建实例后可能被修改
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(8);

// 对构造器的每个 可变参数 进行保护性拷贝  
    public Period(Date start, Date end){
        if(start.compareTo(end) > 0) throw new...;
        this.start = new Date(start.getTime());
        this.end = new Date(end.getTime());
    }
// 其次可以使用 LocalDateTime (Date 已经过时)

保护性拷贝在检查参数有效性之前拷贝,且有效性检查针对的是拷贝后的对象,并非原始对象

对于参数类型可以被不信任方子类化的参数,不要使用clone方法进行保护性拷贝

简而言之,如果一个类包含有从客户端得到或返回到客户端的可变组件,这个类就必须保护性的拷贝这些组件


第51条 谨慎设计方法签名

良好的方法签名

  • 遵循标准命名习惯
  • 名称风格一致
  • 易于理解且大众认可
  • 参考 Java类库API

避免过长的参数列表

  • 将一个方法分成多个方法
  • 创建辅助类(多个常用参数合并成一个类),一般为静态成员类
  • 从对象构建到方法调用使用Builder模式(建造者模式)

参数类型

  • 优先使用接口而不是类,通用性更强
  • 对于Boolean参数,优先使用两个元素的枚举类型

第52条 慎用重载

重载

public class CollectionClassifer{
    public static String classify(Set<?> s) {return "Set";}
    public static String classify(List<?> l) {return "List";}
    public static String classify(Collection<?> c) {return "Collection";}
	
    public static void main(String args[]){
        Collection<?>[] collections = {
            new HashSet<String>(),
            new ArrayList<BigInteger>(), 
            new HashMap<Stirng, String>()
        }
        for(Collection<?> c : collections){
            sout(classify(c)); 
        }
    }
}
// 打印 collection collection collection 
// 原因:classify重载,决定调用哪个方法是编译时决定的

覆盖

class A{
    String name() {return "A";}
}
class B extends A{
    @Override String name() {return "B";}
}
class C extends C{
    @Override String name() {return "C";}
}
public class Demo{
    public static void main(String args[]){
        List<A> lists = List.of(new A(), new B(), new C());
        for(A a : lists){
            sout(a.name());
        }
    }
}
// 打印 A B C

对于重载方法的选择是静态的(编译期),对于覆盖的方法选择是动态的(运行时决定)

避免乱用重载,安全保守的策略是永远不要导出两个具有相同参数目的的重载方法,始终可以给方法起不同的名字,而不用重载机制:writeBoolean(Boolean), writeInt(Int), writeLong(long)…

两个重载方法在同样的参数上被调用时,只要是执行的相同的功能,就不会有危害,确保这种行为的做法是:让更具体化的重载方法把调用转发给更一般化的重载方法

public boolean contentEquals(StringBuffer sb){
    return contentEquals((CharSequence) sb);
}

第53条 慎用可变参数

可变参数接受0或者多个指定类型的参数, 原理是先创建一个数组,大小为参数数量,将参数传递给数组,然后将数组传递给方法

  • 声明方法带有两个参数,一个指定类型的正常参数,一个是可变参数

    static int min(int firstArg, int... remainargs){
        int min = firstArg;
        for (int arg : remainargs){
            if(arg < min) min = arg;
        }
        return min;
    }
    
  • 每次调用可变参数方法都会导致数组的一次分配和初始化,优化方案为声明该方法的几个重载

    public void f() {}
    public void f(int a1) {}
    public void f(int a1, int a2) {}
    public void f(int a1, int a2, int a3) {}
    public void f(int a1, int a2, int a3, int... rest) {}
    

使用可变参数前,先包含所有必要的参数,并且关注可变参数带来的性能影响


第54条 返回零长度的数组、集合而不是null

返回null 会使代码判断变得更加复杂,且容易出错

可以通过返回同一个不可变的零长度集合、数组避免分配的重复执行,不可变对象可以自由共享

private static final Cheese[] EMPTY_CHEESE = new Cheese[0];

public Cheese[] getCheeses(){
    return cheeseInStock.toArray(EMPTY_CHEESE);
}

第55条 谨慎返回optional

特定环境下无法返回任何值的方法的一些缺点(没有optional之前)

1、null : 客户端必须处理返回值为null的情况

2、异常: 创建异常时会捕捉整个堆栈轨迹,抛出异常开销高

optional的作用是显示表达变量的状态,不是为了完全消灭NPE

// 第30条代码
public static <E extends Comparable<E>> E max(Collection<E> c){
    if(c.isEmpty())
        throw new Ill.....
    E result = null;
    for(E e: c){
        if(result == null || e.comparaTo(result) > 0){
            result = e;
        }
    }
    return result;
}
// optional 修改
public static <E extends Comparable<E>> Optional<E> max(Collection<E> c){
    if(c.isEmpty())  return Optional.empty();
    E result = null;
	...
    return Optional.of(result);
}
// 传入Optional的值可能为null时
Optional.ofNullable(result);

Stream的许多终止操作返回Optional

public static <E extends Comparable<E>> Optional<E> max(Collection<E> c){
    return c.stream().max(Comparator.naturalOrder());
}

方法返回Optional, 客户端的处理 (Optronal本质与受检异常类似)

// get() , 前提Optional对象不为空
// ifPresent(), 安全阀
Optional<ProcessHandle> pp = ph.parent();
sout("PID :" + (pp.ifPresent() ? String.valueof(pp.get().pid()) : "N/A"));
or
sout("PID :" + ph.parent().map(h -> String.valueof(h.pid())).orElse("N/A"));

// orElse()
String s = max(words).orElse("No words");

// orElseThrow()
Toy mytoy = max(toys).orElseThrow(TemperTantrumException::new);
  • 容器类型包括集合,映射,Stream, 数组和optional 都不应该被包装在Optional中

  • 不要返回基本包装类型的Optional (Boolean, Byte, Character,Short, Float 除外), 用OptionalInt, OptionalLong, OptionalDouble代替

  • 不适合用optional作为键、值或者集合数组中的元素

  • 不要将optional用作返回值以外的其他用途

声明方法接受一个Optional参数or将结果作为optional返回,让方法的使用者清楚知道它可以接受空值,或者返回一个空值,方便后续操作


第56条 为所有导出的API元素编写文档注释

javadoc 利用特殊文档注释生成html API文档

每个被导出的类、接口、构造器、方法、域声明都应该增加文档注释

  • 为泛型或者方法编写文档,确保在文档中说明所有的类型参数

  • 为枚举, 在文档中说明所有的参数

  • 为注解类型, 在文档中说明所有成员

  • 类或静态方法是否线程安全或是否可以序列化,应该明确说明

为API编写文档注释是最好的,注释内部出现的HTML标签都是可以的,但元字符要转义


l返回,让方法的使用者清楚知道它可以接受空值,或者返回一个空值,方便后续操作


第56条 为所有导出的API元素编写文档注释

javadoc 利用特殊文档注释生成html API文档

每个被导出的类、接口、构造器、方法、域声明都应该增加文档注释

  • 为泛型或者方法编写文档,确保在文档中说明所有的类型参数

  • 为枚举, 在文档中说明所有的参数

  • 为注解类型, 在文档中说明所有成员

  • 类或静态方法是否线程安全或是否可以序列化,应该明确说明

为API编写文档注释是最好的,注释内部出现的HTML标签都是可以的,但元字符要转义


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值