首页 > 编程语言 >《Effective Java》阅读笔记-第八章

《Effective Java》阅读笔记-第八章

时间:2024-02-21 16:26:23浏览次数:35  
标签:Java Effective int end 第八章 start Date new public

Effective Java 阅读笔记

第八章 方法

第 49 条 检查参数的有效性

基于“发生错误后应尽快检测出错误”这一通用原则,应对方法的参数进行检查。

Java 7 中增加了Objects.requireNonNull方法,可以很方便的对参数进行null检查并抛出异常:

public void someMethod(String args) {
    args = Objects.requireNonNull(args, "xxx 参数不可以为 null");
}

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

在设计类时,应考虑到使用者会破坏类的约束条件,因此应设置保护性措施。

比如要设计一个 Period 的类:

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

这个类看起来是不可变的,但是不要忘了Date类本身是可变的,因此很容易违反开始不能大于结束的约束。

解决方法就是使用 Java 8 加入的不可变类LocalDateTime,因为Date已经过时了。

或者在进行保护性拷贝:

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 = new Date(start.getTime());
        this.end = new Date(end.getTime());
    }

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

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

这样这个类就是真正不可变的了(这里不讨论反射)。

第 51 条 谨慎设计方法签名

  • 谨慎选择方法名称:方法名称首先要易于理解,并且风格一致。然后是优先大众认可的名称。
  • 不要过于追求提供便利的方法:方法太多会使学习、使用、文档化、测试、维护的难度增加,优先提供一个功能齐全的方法,只有当某一项操作经常用到的时候才考虑为它提供快捷方式,如果不确定,那就不要提供快捷方式。
  • 避免过长的参数列表:尽量在4个参数或者更少,参数过多不便使用。
  • 参数类型优先使用接口,而不是类:使用接口扩展性更好。
  • boolean 参数,可以使用两个元素的枚举:用枚举的话,不管是可读性还是后续扩展,都更好一些。

第 52 条 重载方法需谨慎

比如下面这个程序,根据一个集合是 Set 还是 List 还是其他类型,对其进行分类:

public class CollectionClassifier {
    public static String classify(Set<?> set) {
        return "Set";
    }
    public static String classify(List<?> list) {
        return "List";
    }
    public static String classify(Collection<?> coll) {
        return "Unknown Collection";
    }

    public static void main(String[] args) {
        List<Collection<?>> list = new ArrayList<>();
        list.add(new HashSet<String>());
        list.add(new ArrayList<Integer>());
        list.add(new HashMap<String, String>().values());

        for (Collection<?> collection : list) {
            System.out.println(CollectionClassifier.classify(collection));
        }
    }
}

本意是好的,但是实际程序打印了三次Unknown Collection,因为classify方法被重载(overloaded)了,要调用哪个重载方法是在编译时决定的,因此调用的全部是classify(Collection<?> coll)。正确的做法是使用instanceof进行运行时检测。

应避免胡乱使用重载,比较保守的策略是永远不要导出两个具有相同参数数量的方法,如果有需要可以给方法改名,而不是使用重载。

Java 5 之前,基本类型不同于所有引用类型,但是引入自动拆装箱之后,List接口中的方法重载就产生了混乱,比如下面这个例子:

Set<Integer> set = new TreeSet<>();
List<Integer> list = new ArrayList<>();

for (int i = -3; i < 3; i++) {
    set.add(i);
    list.add(i);
}

for (int i = 0; i < 3; i++) {
    set.remove(i);
    list.remove(i);
}

System.out.println("set: " + set);
System.out.println("list: " + list);

上面的例子中,Set 和 List 进行了相同的操作,大多数人的直觉来看,最终都打印[-3, -2, -1]
但是结果并不是这样,最后打印结果是:

set: [-3, -2, -1]
list: [-2, 0, 2]

原因就是 Collection 中有一个方法remove(Object o),Collection 的子接口 List 有一个同名的重载方法remove(int index),因此就很容易使用出错。

想要上述代码产生一直的结果需要这么改:

for (int i = 0; i < 3; i++) {
    set.remove(i);
    list.remove((Integer) i);
}

上面就是加入自动拆装箱之后破坏了 List 接口。

Java 8 引入 Lambda 之后加剧了重载造成的混乱:

// 可编译
new Thread(System.out::println).start();

// 不可编译
ExecutorService exec = Executors.newCachedThreadPool();
exec.submit(System.out::println);

上面的代码中,第一行可以编译,后面两行不可以编译。因为submit方法有一个Callable<T>的重载,而 Thread 构造函数没有。

并且加入println方法没有重载,那么调用就是合法的。归根结底就是因为System.out::println是一个不精确调用,即使所有的重载方法都是void的。

因此在重载方法时,应该保证:传递同样的参数时,所有重载方法的行为应该保持一致。

第 53 条 慎用可变参数

如果要检查可变参数的数量,比如至少为1个,这种情况是就应该直接在方法签名上修改为一个参数+一个可变参数,而不是直接用可变参数。

例子

比如有时候写一个必须要一个或多个参数的方法,如果只使用可变参数是这种情况:

public static int sum(int... args) {
    if (args.length == 0) throw new IllegalArgumentException("args too few");

    int sum = 0;
    for (int arg : args) {
        sum += arg;
    }
    return sum;
}

把问题从编译期推迟到了运行期,这种情况看就应该直接定义成下面这种方式:

public static int sum(int firstArg, int... remainingArgs) {
    int sum = firstArg;
    for (int arg : remainingArgs) {
        sum += arg;
    }
    return sum;
}

这样在编译时就可以发现问题。

在重视性能的时候,使用可变参数应该小心,因为每一次调用都会创建并初始化一个数组,如果没办法承受这个成本,那么就可以吧常用的参数数量的方法定义出来:

就像这样
public void foo()
public void foo(int a1)
public void foo(int a1, int a2)
public void foo(int a1, int a2, int a3)
public void foo(int a1, int a2, int a3, int... rest)

第 54 条 返回 0 长度的数组或集合,而不是 null

返回 null 会使调用者需要额外的代码处理 null 值,而空集合不会。

返回空集合时不要使用 new 创建,可以使用Collections.emptyList()Collections.emptySet()Collections.emptyMap()等。

第 55 条 谨慎返回 Optional

在 Java 8 之前,在编写特定情况下没有值的时候,只能返回 null 或者抛出异常。

Java 8 引入了Optional,强制用户处理 null 情况。

  • 如果一个方法返回 Optional,那就不要直接返回 null,而是返回Optional.empty()
  • 容器类型包括集合、映射、Stream、数组和 Optional,都不应该被包装在 Optional 中,也就是不要返回Optional<List<T>>,而是返回一个空List<T>
  • 如果没办法返回结果,并且没有返回结果是调用者必须进行特殊处理时,就可以返回 Optional。
  • 不应该返回Optional<Integer>,使用OptionalIntOptionalLongOptionalDouble代替。
  • 基本任何时候都不适合用 Optional 作为键、值,或者集合和数组中的元素。

第 56 条 为所有导出的 API 编写文档

JavaDoc 是个好东西。

标签:Java,Effective,int,end,第八章,start,Date,new,public
From: https://www.cnblogs.com/code-blog/p/18025529

相关文章

  • 《Effective Java》阅读笔记-第十一章
    EffectiveJava阅读笔记第十一章并发第78条同步访问共享的可变数据多线程访问变量时,需要进行同步,否则就会产生并发问题。同步代码块、加锁等或者直接不共享变量,也就是将可变数据限制在单个线程内。ThreadLocal这种第79条避免过度同步为了避免活性失败和安全性失败......
  • 《Effective Java》阅读笔记-第十章
    EffectiveJava阅读笔记第十章异常第69条只针对异常的情况才使用异常说白了就是不要吧你的业务逻辑用异常来写。举个反例比如用抛出异常来遍历一个数组:try{inti=0;while(true){range[i++].doSomething();}}catch(ArrayIndexOutOfBoun......
  • 《Effective Java》阅读笔记-第十二章
    EffectiveJava阅读笔记第十二章序列化第85条其他方法优先于Java本身的序列化Java本身的序列化漏洞过多,很容易被攻击。避免被序列化攻击的最好方式就是不要反序列化任何字节流,并且新的系统中没有任何理由使用Java本身的序列化。JSON和Protobuf是两种优秀的序列化......
  • java类初始化及代码块加载顺序连根拔起
    说明相信很多人对于java中父子继承关系中,子类实例化调用过程中,代码块的执行顺序都容易忘记或搞混,尤其是java初级笔试题或面试题最容易出这类题目,让人恨得牙痒痒!!!本文就一次性将其连根铲除,看完你就不会有这个烦恼了,哈哈。先引用一下骨灰级大作《Java编程思想》的复用章节Java......
  • 一键脚本破解最新版 idea 步骤,开启学习java 之旅,好好好
    效果:步骤1、idea安装:直接在官网下载最新idea-2022.2.3.dwg(:官网地址,然后根据安装引导一步一步完成安装即可,完成后打开idea看到如下效果表示idea安装成功!如图发现idea需要注册!这里我们先不管,直接关闭idea准备激活!步骤2、下载最新的破解包https://pan.baidu.com/s/1iQby9......
  • Hutool - 简化Java编程的法宝,让工作更高效
    一、在项目的pom.xml的dependencies中加入以下内容:<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.0.7</version></dependency>非Maven项目的话就自己百度下载一下jar包,导入即可。二、StrUtil看这里名字也应该明白了,......
  • 承前启后,Java对象内存布局和对象头
    承前启后,Java对象内存布局和对象头大家好,我是小高先生。在我之前的一篇文章《并发编程防御装-锁(基础版)》中,我简要介绍了锁的基础知识,并解释了为什么Java中的任何对象都可以作为锁。在那里,我提到了对象头中有一个指向ObjectMonitor的指针,但没有深入探讨Java对象的内存结构。本文将......
  • Java的配置
    环境变量配置找到配置的位置右击此电脑-->属性-->高级系统设置-->环境变量-->系统变量配置Path环境变量(必须配置的)(目的:为了可以在任意目录下找到javac和java命令)方式1:直接在Path变量中添加jdk的bin目录的完整路径系统变量-->Path-->新建-->D:\soft\java\jdk\bin方式2:(推荐......
  • java练习2(四位数字进行加密)
    packagecom.shujia.zuoye;importjava.util.Scanner;/*某个公司采用公用电话传递数据,数据是四位的整数,在传递过程中是加密的,加密规则如下:每位数字都加上5,然后用和除以10的余数代替该数字,再将第一位和第四位交换,第二位和第三位交换。结果如图所示。*/publicclass加密......
  • java练习1(求圆的周长与面积)
    packagecom.shujia.zuoye;importjava.util.Scanner;/*输入圆形半径,求圆形的周长和圆形的面积,并将结果输出。/publicclass求圆的面积{publicstaticvoidmain(String[]args){Scannersc=newScanner(System.in);System.out.println("请输入圆的半径:");doubler=s......