当前位置: 首页 > news >正文

Effective Java 第三版 第二三章总结

第二章 创建和销毁对象

1.可以考虑用静态工厂方法替代构造方法。

优点:

  • 静态工厂方法可以有名字,取一个合适的名称可以见名之意
  • 静态工厂方法可以不是每次执行都创建对象
  • 静态工厂方法可以不是返回本类型,可以返回当前类的子类
  • 由于静态工厂方法可以返回当前类的子类,所以可以返回当前不存在的类,这样可以组成服务提供者框架。多个实现,然后灵活的进行替换。

2.构造方法参数过多,有些必要有些不必要,可以考虑使用构建器

构造方法参数过多,有些是必须的,有些是非必须的,常见的处理方式是采用构造方法重载,缺点是容易混淆,不好区分,不够灵活;除了重载外还有一种方式是先调用默认无参的构造方法,在通过多次set方法进行设置初始值,这种方式虽然比较直观和清晰,但是缺点是不易校验入参。采用构建者模式能较灵活的选择需要的参数进行构造,并且会有较为清晰的代码结构。

构建者模式如何编码?

在类中声明一个静态内部类,这个静态内部类可以有构造方法,然后是一些静态方法,这些静态方法的名称要和设置的属性名称类似,通过这些静态方法去设置外部类的成员遍历,最后通过内部类的builder方法新建一个外部类对象返回。

举例说明:

//营养成分类
public class NutritionFacts {//分量private final int servingSize;//private final int servings;private final int calories;private final int fat;private final int sodium;private final int carbohydrate;public static class Builder {// Required parametersprivate final int servingSize;private final int servings;// Optional parameters - initialized to default valuesprivate int calories      = 0;private int fat           = 0;private int sodium        = 0;private int carbohydrate  = 0;public Builder(int servingSize, int servings) {this.servingSize = servingSize;this.servings    = servings;}public Builder calories(int val){ calories = val;      return this; }public Builder fat(int val){ fat = val;           return this; }public Builder sodium(int val){ sodium = val;        return this; }public Builder carbohydrate(int val){ carbohydrate = val;  return this; }public NutritionFacts build() {return new NutritionFacts(this);}}private NutritionFacts(Builder builder) {servingSize  = builder.servingSize;servings     = builder.servings;calories     = builder.calories;fat          = builder.fat;sodium       = builder.sodium;carbohydrate = builder.carbohydrate;}public static void main(String[] args) {NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();}
}

3.用私有构造器或枚举类型强化Singleton属性

实现单例:都需要先私有化构造函数。然后通过不同方式实现单例

饿汉式:一加载类就会创建对象

  • 使用公有的final的静态成员变量创建对象
  • 使用私有的final静态成员变量,初始化创建一次对象。通过公有的静态工厂成员方法返回对象。

懒汉式:

  • 私有静态成员变量为空,公有的静态工厂方法判断是成员变量是否为空,是则新建,不是则返回成员变量。

注意点:线程非安全,如需保证线程安全,需要双重判定锁。实现方式:成员变量加volatile保证可见性,防止指令重排。

public class Singleton {private static volatile Singleton instance; // volatile防止指令重排private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {//这里再判断的原因是防止,一个线程等待另一个线程创建,创建好后自己获得锁//再去直接创建,就会导致错误。因为在它之前获得锁的线程已经创建好了,而自己还以为//对象为空,所以需要再检查一次。if (instance == null) {instance = new Singleton();}}}return instance;}
}
  • 单元素的枚举通常是实现单例的最佳方式

4.通过私有化构造函数来强调类的不可实例化属性

有的类只需要静态方法或静态字段,则可以通过私有化默认构造函数来使它不可被实例化。

5.优先考虑依赖注入来引入资源

许多类会依赖于一个或多个底层资源,优先考虑在创建实例时将该资源通过构造方法传入实例。

这种方式还有一种变种,就是将资源工厂传给构造器,这样创建实例时还可以通过资源工厂获取资源的子类资源。

  • 避免使用静态工具类和单例类来引入资源,静态工具类和单例类不适用于需要引用底层资源的类。

6.避免创建不必要的对象

一般来说,最好能重用单个对象,而不是每次需要的时候就创建一个相同功能的新对象。如果对象是不可变的,它就始终可以被重用。

  • 大量的计算,要优先使用基本类型而不是装箱基本类型,下面的例子会导致创建大量的对象
private static long sum(){Long sum = 0L;for(int i =0;i<Integer.MAX_VALUE;i++){sum+=i;}return sum;
}

7.消除过期的对象引用

java支持垃圾回收,在支持垃圾回收的语言中,内存泄漏是很隐蔽的。如果一个对象引用被无意识的保留起来了,那么该对象引用的许多对象都不会被垃圾收集器回收。

  • 清除过期引用最好的方式是让包含该引用的变量结束其生命周期。如果你是在最紧凑的作用范围内定义每一个变量,它会自然发送。方法执行完,存放在栈中的变量被回收,对象的引用为空。
  • 清空对象引用是一种例外,而不是规范行为
  • 只有类是自己管理内存,我们就应该警惕内存泄漏问题。

8.避免使用终结方法和回调方法

终结方法(finalizer)和清除方法的缺点在于不能保证被及时执行。

及时的执行终结方法和清除方法是垃圾回收算法的一个主要功能,不同的jvm中垃圾回收算法执行终结方法和清楚方法的时间差距很大。如果你的程序依赖于这些方法的执行时间,就会带来错误。因为它们本就不会保证被执行的时间。

9.try-with-resources 优先于try-finally

第三章:对于所有对象都通用的方法

        Object类是一个具体类,但设计它主要是为了扩展。它所有的非final方法(equals,hashcode,toString,clone和finalize)都有明确的通用约定,因为它们被设计成是需要被覆盖(override)的。任何一个类,重新这些方法时,如果不遵守这些约定,它们就无法和其他依赖于这些约定的类(HashMap,HashSet)一起正常工作。

10.覆盖equals时请遵守通用约定

重写equals方法看似简单,但许多重写方式容易导致严重错误。较为容易避免错误的方式是不重写equals方法。如满足以下条件,则不需要重写equals方法。

1.每个实例本质上都是唯一的。

2.该类的对象没有必要提供逻辑相等的逻辑

3.超类已经提供了equals方法,且对本类适用。如大多数的set,list集合的实现类。

4.类是私有的,或者是包级私有的,可以确定它的equals方法永远不会被调用。

重写equal方法必须遵守以下通用约定。

1.自反性。x.equals(x)

2.对称性。x.equals(y) 且 y.equals(x)

3.传递性。x.equals(y) 且 y.equals(z) 则 x.equals(z)

4.一致性。x.equals(y) 如果信息x和y没有被改变,则多次执行会返回相同结果

5.非空性。对于任意非空的x,x.equals(null)必须返回false。比如不能抛出空指针异常。所以需要先用instanceof 判断入参是否和x相同,instanceof如果入参为空,则会返回false。如果漏掉类型检测,则可能抛出空指针异常。

  • 我们无法在扩展可实例化的类的同时,既添加新的值组件,同时又保留equals约定。

告诫:

  • 覆盖equals方法时总要覆盖hashcode
  • 不要企图让equals方法过于智能
  • 不要将equals声明中的Object对象替换为其他的类型。

重写equals和hashcode,可以使用谷歌开源的Auto Value框架,或者ide自带的重写方法。

11.覆盖equals时总要覆盖hashcode

如果不这样做,就违法了hashcode的通用约定。从而导致该类无法和所有基于散列的集合一起运作。包括hashmap,和hashset.hashcode的约定如下:

  • 在程序执行期间,如果equals约定的比较信息没有改变,则它的hashcode,多次调用,也应该是相同的值。
  • 如果两个对象的根据equals比较是相同的,那么他们的hashcode应该返回相同的值。
  • equals比较是不相同的两个对象,他们的hashcode值不是非得不相同。但是我们应该知道让不相等的对象产生不同的hashcode,会提高散列表的性能。

如果一个类是不可变的,并计算hashcode的开销较大,我们可以将hashcode进行缓存。

12.始终要覆盖toString

无论是否决定指定格式,都应该在文档中进行说明。如果要指定格式,就应该严格这样去做。因为有人使用你的类,可能会去解析你toString返回的信息,你的格式变了,就会导致解析你格式的程序错误。所以我们需要:

  • 无论是否指定格式,都为toString返回值中包含的所有信息提供一种可以通过编程访问之的途径。

13.谨慎地覆盖clone

  • 不可变的类,永远都不要提供clone方法。
  • clone方法就是另一个中构造器;必须确保它不会伤害到原始的对象,并确保正确的创建被克隆对象中的约束条件。

14.考虑实现Comparable接口

compareTo方法的通用约定与equals方法的约定相似:自反性,传递性,对称性。

与equals方法不同的是,compareTo不能跨越不同类型的对象进行比较:在不同类型的对象时,compareTo可以抛出ClassCastException异常。

相关文章:

  • 【JVM】Java虚拟机(三)——类加载与类加载器
  • [创业之路-410]:经济学 - 国富论的核心思想和观点,以及对创业者的启发
  • Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
  • 2025-06-02-IP 地址规划及案例分析
  • OD 算法题 B卷【反转每对括号间的子串】
  • Secs/Gem第八讲(基于secs4net项目的ChatGpt介绍)
  • 剑指offer19_链表中倒数第k个节点
  • Netty集群搭建
  • python打卡day48
  • PandasAI使用
  • 深入解析 Pandas 核心数据结构:Series 与 DataFrame
  • 重读《人件》Peopleware -(15)Ⅱ 办公环境 Ⅷ 撑伞之步:构建理想办公环境(下)
  • 【C++系列】智能指针自定义析构
  • 6.5 自学测试 数据库基础 Day5
  • C++11 右值引用
  • 第十八章 归档与备份
  • python打卡训练营打卡记录day48
  • JS的数据类型分类
  • 自动交换两个文件的文件名 VSB脚本技巧 电脑技巧
  • java面试:JAVA并发篇
  • wordpress双语站/如何做宣传推广效果最好
  • wordpress 主题 移动端/seo官网优化
  • 商业网站开发实训心得体会范文/chrome官网
  • 北京网站制作公司哪家好/快手刷评论推广网站
  • 个人网站建设流程/seo优化的内容有哪些
  • 用仿网站做优化有效果吗/网站平台如何推广