读书笔记:Effective Java-第8章 方法Methods

11 篇文章 0 订阅
11 篇文章 0 订阅

目录

Item 49: Check parameters for validity

Item 50: Make defensive copies when needed

Item 51: Design method signatures carefully

Item 52: Use overloading judiciously

Item 53: Use varargs judiciously

Item 54: Return empty collections or arrays, not nulls

Item 55: Return optionals judiciously

Item 56: Write doc comments for all exposed API elements


Item 49: Check parameters for validity

检查参数的有效性

术语:

  • failure atomicity:失败原子性。

应在方法体的开头处对入参进行有效性检查,并在文档中清楚地指明参数的所有限制。

例外情况:

  1. 有效性检查消耗很大或不实际。
  2. 在代码处理过程中已经隐含了这种检查,如Collections.sort(List)隐含了对未继承Comparable接口的对象抛出ClassCastException。

Item 50: Make defensive copies when needed

必要时进行保护性拷贝

术语:

defensive copies:保护性拷贝,指通过深度拷贝等措施来防止类的成员变量(field)面临被篡改的危险。

防止类成员变量被调用代码篡改的方法:

(1)保护性拷贝:

  • 在给类的成员变量赋值的位置,如构造函数、set函数;
  • 供外部的访问类成员变量的函数,如get函数;

(2)使用不可变(immutalbe)变量作为类的成员变量,如用Instant / LocalDateTime / ZoneDateTime代替Date类型变量。

注意:

  • 长度非0的数组是可变的(mutable)。
  • 保护性拷贝应该在参数校验之前。
  • 如果保护性拷贝代价太大,而类信任调用它的客户端,则可以在文档中指明不得修改受影响的成员变量。

Item 51: Design method signatures carefully

谨慎设计方法签名

  • 谨慎选择方法名称:遵循标准命名习惯,采用大众熟悉的名称,要易于理解、风格统一。
  • 不要过于追求提供便利的方法(?不解啥意思)。
  • 避免过长的参数列表:不要超过4个。
  • 对于参数类型,优先使用接口而不是类:即用接口来代替实现该接口的类,如定义入参为HashMap的方法时,入参类型推荐使用Map,而非HashMap。
  • 对于boolean参数,优先使用两个元素的枚举类型,除非boolean的含义对于方法名是清晰的:枚举类型可读性强、易于扩展,示例:
// 温度计类Thermometer有如下一个静态工厂方法,后续它能很容易增加KELVIN这个新元素
public enum TemperatureScale { FAHRENHEIT, CELSIUS }

Item 52: Use overloading judiciously

慎用重载

术语:

  • overloading method:重载方法,指同名不同参数的函数。编译时决定调用哪个重载方法(静态)。
  • overriding method:覆盖方法,指子类中重写父类的方法。运行时决定调用哪个子类的重载方法(动态)。

调用哪个重载方法是在编译时做出决定的,看如下代码:

public class CollectionClassify {
    public static String classify(Set<?> s) {
        return "Set";
    }

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

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

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

        for (Collection<?> c : cs) {
            System.out.println(classify(c));  // 3次打印的都是 Unknown Collection
        }
    }
}

虽然运行时每次遍历的集合类型都不一样,但因为在编译时已经确定了哪个重载方法,所以调用的是同一个方法。如果需要运行时决定具体类型,可将上面函数改写成子类重载的方式,或者用instanceof显式判断:

public static String classify (Collection < ? > c){
    return c instanceof Set ? "Set" :
           c instanceof List ? "List" : "Unknown Collection";
}

安全保守的做法:

  • 不要对可变参数的函数进行重载。
  • 尽量不要对有相同参数个数的函数进行重载;如果不可避免,则至少存在一个彼此之间不能类型转换的参数,如int和List;如果还是不可避免,则应该保证传入相同参数时,各重载方法的结果是一致的。

一个常见的容易混淆的重载方法(java库中少数容易混淆的api):

List<Integer> list = new ArrayList<>();
list.add(0);  list.add(1);
list.remove(1);  // 表示删除index为1的元素
list.remove((Integer)1);  // 表示删除值为1的元素

Item 53: Use varargs judiciously

慎用可变参数

可变参数varags方法的正式名为variable arity methods(可匹配不同长度的变量的方法),可接受0或者多个指定类型的参数。

实现机制:首先创建一个数组(大小为调用位置所传递的参数数量),然后将参数值赋值给数组,最后将数组传递给方法。

在重视性能的场景下,要小心使用可变参数方法。每次调用都会导致一次数组的分配和初始化。如果即要性能又要可变参数,可将常用的参数个数的情况设计为重载方法,如:

// 实际调查发现,95%调用的参数个数少于或等于2,则可重载0-2个参数的方法,来提升性能
public void foo(){}
public void foo(int a1){}
public void foo(int a1, int a2){}
public void foo(int a1, int a2, int... rest){}

Item 54: Return empty collections or arrays, not nulls

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

List<String> list= ...;

// 返回空集合
return new ArrayList<>(list);  // 常用情况
return list.isEmpty() ? Collections.emptyList() : new ArrayList<>(list);  // 对性能有严格要求情况,Collections.emptyList()是不可变的immutable

// 返回空数组
return list.toArray(new String[0]);  // 常用情况,数组长度不为0也用此表达式
private static final String[] EMPTY_STRING_ARRAY = new String[0];
return list.toArray(new String[EMPTY_STRING_ARRAY ]);  // 对性能有严格要求情况,长度为0的数组是不可变的
// list转数组时,不推荐使用预分配内存方法(即使数组长度不为0),否则有损性能,即
// return list.toArray(new String[new String[list.size()]]);  
// 参考:https://m.imooc.com/wenda/detail/664892

当list和传入的数组大小一致时,toArray()直接向传入的数组进行赋值;当不一致时,toArray()会重新创建一个创建新内存空间存放数组并赋值。

探究list和toArray()和传入数组的内存地址关系:

final String[] ES = new String[0];
List<String> sl1 = Arrays.asList("a", "g");
List<String> sl2 = Arrays.asList("d", "t");
List<String> sl3 = new ArrayList<>();
List<String> sl4 = new ArrayList<>();

System.out.println(ES + " -->ES地址");
System.out.println(sl3.toArray(ES) + " -->sl3.toArray(ES)地址");
System.out.println(sl4.toArray(ES) + " -->sl4.toArray(ES)地址");
System.out.println(sl1.toArray(ES) + " -->sl1.toArray(ES)地址");
System.out.println(sl2.toArray(ES) + " -->sl2.toArray(ES)地址");
System.out.println(sl3.toArray(new String[0]) + " -->sl3.toArray(new String[0])地址");
System.out.println(sl4.toArray(new String[0]) + " -->sl4.toArray(new String[0])地址");

打印结果:
[Ljava.lang.String;@2d98a335 -->ES地址
[Ljava.lang.String;@2d98a335 -->sl3.toArray(ES)地址
[Ljava.lang.String;@2d98a335 -->sl4.toArray(ES)地址
[Ljava.lang.String;@16b98e56 -->sl1.toArray(ES)地址
[Ljava.lang.String;@7ef20235 -->sl2.toArray(ES)地址
[Ljava.lang.String;@27d6c5e0 -->sl3.toArray(new String[0])地址
[Ljava.lang.String;@4f3f5b24 -->sl4.toArray(new String[0])地址

Item 55: Return optionals judiciously

谨慎返回optional

当函数中存在无法返回值的情况时,一般有如下3种处理方式:

  1. 抛出异常,消耗大,因为要捕获整个堆栈轨迹。
  2. 返回null:调用位置要增加检查值是否为null的逻辑。
  3. (推荐)用Optional类型替换原来的返回类型:使用更灵活、不易出错。

永远不要对Optional返回类型的方法返回null(违背设计本意)。

optional不适用类型:自带空元素类型,如container types(含collection、map、stream、array、optional)。

不适用场合:

  • 注重性能的时候,因为optional需要额外开销;
  • map的key和value;
  • collection和array中的元素。

永远不应该使用int/long/double这3种基本包装类型的Optional,而应该使用基本类型的(开销更低),但使用形式有所不同:OptionalInt、OptionalLong、OptionalDouble。(其他基本类型和常规用法一样)

Item 56: Write doc comments for all exposed API elements

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

在How to Write Doc Comments [Javadoc-guild]网站有教程。

应在类、接口、方法、字段等声明的前面添加文档注释。

pulbic类不应使用默认构造器,因为无法添加文档注释。

为了便于维护,应该对未导出(或非public)的类、接口、方法、字段也提供注释。

方法的注释应简洁地描述出它和客户端之间的约定(约束):

  • 描述做什么(what)而非怎么做(how),用动词短语;
  • 描述方法的前置条件和后置条件,前置条件有@throw中的unchecked exception、@param中的参数校验;
  • 描述方法副作用,如启动了后台线程。

常用注释注解:

  • @param:对每个参数都要描述,用名词短语或算术表达式。
  • @throws:对每个受检和不受检异常,用if描述发生异常的条件。
  • @retrun:非void的返回值,如果和description部分一样,则可以忽略。
  • @implSpec:用于描述为了继承的父类中使用了自用模式(self-use)的方法
  • {@code}:用于包裹注释中的代码
  • {@litera}:用于包裹需要转义html元素符号的句子,如<、>、&等。
  • {@index}:添加为生成的html注释文档的搜索关键词。
  • {@inheritDoc}:表示从父类继承注释

这些注释后一般不用加英文句号,且句子出现英文句号后紧跟空格/换行符的,需要用{@litera}包裹,否则导致注释过早结束。

文档注释中可以添加html的tags元素(如<p><i>),javadoc工具会将注释转换为html文档。

javadoc有继承方法注释的能力,接口文档注释优于超类的注释。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MOOC(大规模开放式在线课程)是一种通过网络平台开设的在线教育课程,可以为广大学习者提供方便灵活的学习机会。人工智能实践:TensorFlow笔记,是由北京大学推出的一门针对人工智能领域的实践课程,旨在帮助学习者掌握使用TensorFlow框架进行深度学习的基本方法和技巧。 该课程的代码提供了一系列丰富的示例和实践项目,通过这些代码我们可以了解和掌握TensorFlow的使用方法。其中包括数据处理、模型构建、模型训练与评估等关键步骤。通过学习和实践,我们可以学会如何搭建神经网络模型,进行图像分类、文本生成等任务。 在这门课程中,北京大学的代码示例主要围绕深度学习的常用库TensorFlow展开,通过给出具体的代码实现,解释了每部分的原理和操作方法,帮助学习者理解基本概念和技术,熟悉TensorFlow框架和编程语言的使用。 此外,这门课程还涵盖了一些实践项目,例如基于TensorFlow的手写数字识别、图像分类与预测、文本生成等。通过完成这些实践项目,我们可以加深对TensorFlow的理解并提高实践能力。 总之,人工智能实践: TensorFlow笔记 - 北京大学代码是一门结合了理论与实践的在线课程,通过教授深度学习的基本概念和TensorFlow的应用方法,帮助学习者掌握人工智能领域的基本技能。通过这门课程,我们可以学习到TensorFlow的使用方法,掌握一定的实践能力,并将这些知识应用于实际项目当中。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值