注:本文是《Effective Java》学习的笔记。

本片叙述如何处理参数和返回值,如何设计方法签名,如何为方法编写文档。

49.检查参数的有效性

大多数方法和构造器对于传递给它们的参数值都会有某些限制。例如,索引值必须是非负数,对象引用不能为null,等等。

应该在文档中清楚的指明这些限制,并且在方法体的开头处检查参数,以强制施加这些限制。

如上是发生错误之后尽快检测出错误的原则。

在Java7中新增了Objects.requireNonNull方法比较灵活且方便,因此不必因手工进行null检查。也可以用它的重载方法,增加了异常抛出说明。

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

50.必要时进行保护性拷贝

假设类的客户端会尽其所能来破坏这个类的约束条件,因此你必须保护性的设计程序。

以日期类为例。date 是一个可变的类。 simpleDateFormat 这个类也是一个多线程存在不安全问题的类。

所以以上这一套日期操作,在java8完全可以被替换掉。使用LocalDateTime(当前时间,没划分时区)

Instant(有时区划分,默认格林尼治时间)  ZonedDateTime(有时区划分) 来替换date

使用 DateTimeFormatter 来代替 SimpleDateFormat 下面是一个小demo

EffectiveJava(7)之方法

Date已经过时了,不应该在新代码中使用。

下面是一个Date通过构造方法保护实例,防止被修改的Demo

@ToString
@Getter
@AllArgsConstructor
public final class DateTest {                    //111111
    private final Date start;
    private final Date end;
    public static void main(String[] args) {
       Date end = new Date(1990,1,1);
       DateTest dateTest = new DateTest(new Date(1970,1,1),end);
       end.setYear(1000);
       System.out.println(dateTest); 
      //DateTest(start=Tue Feb 01 00:00:00 CST 3870, end=Mon Feb 01 00:00:00 CST 2900)
    }
}
@Getter
public final class DateTest {                       //2222222
    private final Date start;
    private final Date end;
    public DateTest(Date start,Date end){
        this.start = new Date(start.getTime());    //内层重新创建实例。
        this.end = new Date(end.getTime());
    }
}

11111是没修改之前的类,可见end.setYear();就可以轻松破坏掉类的实例。 

2222是修改之后的类,内层重新创建实例可以保证外层修改end 当前实例不会被破坏

对于构造器的每个可变参数进行保护性拷贝是必要的。

!!注:保护性拷贝是在检查参数的有效性之前进行的,并且有效性检查是针对拷贝之后的对象,而不是针对原始的对象。

上述没有使用clone进行对象的克隆是因为date不是final的  使用克隆有被Date子类破坏掉的风险。

51.谨慎设计方法签名

谨慎的选择方法的名称,要符合命名规范。每个公司都有命名规范,默认遵循阿里的命名规范了。

不要过于追求提供便利的方法。

避免过长的参数列表。 相同类型的长参数序列格外有害。

对于参数类型,要优先使用接口而不是类。

对于boolean参数,要优先使用两个元素的枚举类型。这样方便后续修改。比如语义是大为true 小为false 

那么可以设计一个枚举   BIG,SMALL 这样后续需要修改时再在枚举中添加就好了。亲测好用。

52.慎用重载

要调用哪个重载方法是在编译时做出决定的。

对于重载方法的选择是静态的,而对于被重写的方法的选择则是动态的。

public  static  String  test(Set<?> s);

public  static  String  test(List<?> s);

public  static  String  test(Collection<?> s);  这三个重载的方法若是调用时泛型时Collection 则只会调用第三个而不会调用前两个,这算是一个小坑吧。

上述的重载可以用  instanceof代替。 

public static String test(Colletion<?> c){ return  c instanceof List ? "list" ...... }

安全而保守的策略是,永远不要导出两个具有相同参数数目的重载方法。

始终可以给方法起不同的名称,而不是使用重载机制。

综上,尽量不用、也没有太大好处。就是方法名一样而已。

53.慎用可变参数

这条建议,我几乎没有使用过可变参数。没觉得带来很多好处。参数不固定时确实是一个可行的使用场景。

54.返回零长度的数组或者集合,而不是null

有的人任务null返回值 比零长度集合或者数组更好,因为避免额分配零长度的容器所带来的开销。我之前就是这个想法。

这个观点是站不住脚的。原因有两点。第一:在这个级别上担心性能问题是不明智的,除非分析表明这个方法正是造成性能问题的真正源头。第二:不需要分配零长度的集合或者数组,也可以返回它们。

public List<String> get(){ return new ArrayList<>(someList); }    也可以像下面这儿样的返回空list  map啥的、也可以像数组那样,事先定义好一个空集合供返回。

public static List<String> get(){
    return Collections.emptyList();
}

关于数组也是这么玩的。返回一个零长度的数组而不是null

永远都不要返回null,而不返回一个零长度的数组或者集合。如果返回null,那样会使API更难以使用,也更容易出错,而且没有任何性能优势。

55.谨慎返回Optinal 

Java8之前,要编写一个在特定环境下无法返回任何值的方法是,有两种方法:要么抛出异常,要么返回null.

上述两种异常的代价很高,而返回null就像埋下了一个地雷。客户端编码缺少校验很容易NPE

第三种编写不能返回值的方法是 使用  Optional 

理论上能返回T的方法,实践中也可能无法返回,因此在某些特定的条件下,可以改为声明返回Optional

private static Optional<?> getArr(String arr){
    return arr == null? Optional.empty():Optional.of(arr);
}
System.out.println(getArr("asd").isPresent());    //true
System.out.println(getArr("asd").get());          //asd

永远不要通过返回Optional的方法返回null   Optional本质上与受检异常类似。

Stream有很多终止操作就是返回的Optional

如果方法返回Optional ,客户端必须做出选择:如果该方法不能返回值时应该采取什么动作。可以指定一个缺省值。

Stream.of("a","b","c").max((o1,o2)->o2.length()-o1.length()).orElse("other  words");

max().get()就获取到了这个Stream的结果。 

Optional有一个 isPresent 方法可以判断是否存在值。Boolean类型返回结果。

当使用Stream编程时,经常会遇到Stream<Optional<T>> ,为了推动进程还需要一个包含了非空optional中所有元素的Stream<T>. 可以通过过滤器 Optional::isPresent 来解决。

容器类型包括集合、映射、Stream、数组和Optional,都不应该被包装在Optional中。

Optional<T>的使用场景如下。

如果无法返回结果并且当没有返回结果时客户端必须执行特殊的处理,那么就应该声明该方法返回Optional<T>

optional不适应于注重性能的情况。   永远不应该返回基本类型的optional (int long double)

几乎永远都不适合用optional作为键、值,或者集合或数组中的元素。而是只作为一个返回结果。

总之:如果发现自己在编写的方法始终无法返回值,并且相信该方法的用户每次在调用它时都要考虑到这种可能性,那么或许就应该返回一个optional 。但是,应当注意到与返回optional相关的真实的性能影响;对于注重性能的方法,最好是返回一个null,或者抛出异常。最后,不要讲optional用作返回值以外的其他用途。

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

为了正确的编写API文档,必须在每个被导出的类、接口、构造器、方法和域声明之间增加一个文档注释。

方法的文档注释应该简洁的描述出它和客户端之间的约定。

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

当为枚举类型编写文档时,要确保在文档中说明常量。

为注解类型编写文档时,要确保在文档中说明说要成员。

类或者静态方法是否线程安全,应该在文档中对它的线程安全级别进行说明。

 

 

 

 

 

 

 

相关文章:

  • 2021-10-18
  • 2022-01-22
  • 2022-12-23
  • 2022-12-23
  • 2021-07-16
  • 2022-02-12
  • 2022-12-23
  • 2021-07-18
猜你喜欢
  • 2021-12-10
  • 2021-04-01
  • 2021-04-17
  • 2021-07-04
  • 2021-07-27
  • 2022-12-23
  • 2021-11-07
相关资源
相似解决方案