《Effective Java》读书笔记(1-2章)

第一章 创建和销毁对象

1. 考虑用静态代替构造方法

想要获取一个类的实例,一种传统的方式是通过共有的构造器,当然还可以使用另一种技术:提供共有的静态工厂方法。
什么是静态工厂?

public static Boolean valueOf(boolean b) {return (b ? TRUE : FALSE);
}

为什么要用静态工厂替换构造方法?有什么优点

  1. 静态工厂相比构造器来讲,有名字并且通俗易懂,构造器的名字必须和类名一致
  2. 静态工厂每次调用不必新建对象。所以适用于不可变的类,单例,初始化就缓存好,避免重复创建。
  3. 静态工厂方法能够返回原先返回类型的任意子类型的对象,更加灵活的选择返回对象。例如Collection有32个实现类在Collections中可以返回。
  4. 静态工厂可以根据调用传入的不同参数返回不同的对象。

静态工厂的不足之处?

  1. 静态工厂没有public和protected的方法,因此不能被子类化。
    一般静态工厂方法名字的含义
fromValue(value) //这种通过传入单个参数返回相应类型的实例对象
of(v1,v2,v3) // 传入多个参数,返回报站这些参数的实例。
// valueOf是from of更详细的替代方案
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
Object newArray = Array.newInstance(classObject, arrayLen);//每次返回的对象都是新的实例

2. 当遇到多个构造器使用构建者

构造方法和静态工厂共有的限制:不能很好的扩展很多可选参数的场景 。因此对于多个可选参数,考虑使用构建者模式。
其实对于等多个可选参数可以使用新建JavaBean 使用set方法创建实例,这样更通俗易懂但是会很冗长。
builder结合了构造方法的安全性和JavaBean 模式的可读性。

// Builder Pattern
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;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 NutritionFacts build() {return new NutritionFacts(this);}}private NutritionFacts(Builder builder) {servingSize  = builder.servingSize;servings     = builder.servings;calories     = builder.calories;fat          = builder.fat;}
}

为了确保多个参数的不变性不受攻击,可以在builder复制参数后对对象属性进行检查。检查失败抛出非法参数异常(IllegalArgumentException)。
单个builder可以重复使用构建多个不同的对象,对象的参数可以灵活调整,适用多个可选参数。
协变返回类型:一个子类的方法被声明为返回在父类中声明的返回类型的子类型,称为协变返回类型(covariant return typing)。 它允许客户端使用这些 builder,而不需要强制转换。(这个比较有意思)

3. 使用私有构造器或者枚举实现单例

单例对象通常表示无状态,不可变对象。
实现单例的几种方式,其中枚举方式最佳,无偿提供了序列化机制,防止多个实例化。可以阻止反射创建实例。

// 单例模式的实现方式一 Singleton with public final field
public class Elvis {// 公共静态成员变量public static final Elvis INSTANCE = new Elvis();// 私有化构造函数private Elvis() { ... }public void leaveTheBuilding() { ... }
}// 单例模式的实现方式二 Singleton with static factory
public class Elvis {private static final Elvis INSTANCE = new Elvis();private Elvis() { ... }// 每次调用该方法否返回同一个对象引用public static Elvis getInstance() { return INSTANCE; }public void leaveTheBuilding() { ... }
}// 单例模式的实现方式三 Enum singleton - the preferred approach(最佳方式)
public enum Elvis {INSTANCE;public void leaveTheBuilding() { ... }
}
// readResolve method to preserve singleton property
private Object readResolve() {// Return the one true Elvis and let the garbage collector// take care of the Elvis impersonator.return INSTANCE;
}

在实例化过程中为了保证单例不被破坏,生命所有的字段为transient,并提供readResolve方法。否则当序列化实例被反序列化时,就会创建一个新的实例。

4. 使用私有构造器实现非实例化

试图通过创建抽象类来实现非实例化是行不通的,因为该类的子类可以被实例化,并且他还可能会误导用户该类是为了继承而设计的。因此使用简单的方式——私有化构造函数实现类的非实例化。

5. 依赖注入优于硬链接资源

当多个类依赖于同一个或者多个底层资源时,静态工具和单例模式对于这种场景是不适用的。因为这两种方式再并发场景中变得不可用,更容易出错。
其实每个实例在使用客户端的资源时,可以在创建时将资源的参数传入构造函数中,这就是依赖注入的一种形式(构造方法注入)。这种方式保证了资源的不可变性,依赖注入不仅适用于构造器,也同样适用于静态工厂和Builder。Supplier这个接口就可以很好的标识这些工厂,客户端传入一个工厂,工厂负责创建指定类型的实例。

// 例如生成马赛克的工厂
Mosaic create(Supplier<? extends Tile> tileFactory) { ... }

依赖注入可以大幅度提升类的灵活性,可测试性,复用性。

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

如果一个对象是不可变的,那么他总能被复用,复用相比于新建更快速。
当不可变类同时提供了构造器和静态工厂方法时,优先使用静态方法来避免创建不必要对象。
自动装箱可能会创建不必要的对象,他模糊了基本类型和装箱类型之间的区别,但是没有消除这种区别,有可能会导致一些性能问题,因此优先使用基本类型而不是装箱类型。

private static long sum() {Long sum = 0L;for (long i = 0; i <= Integer.MAX_VALUE; i++)// Long => longsum += i;return sum; 
}

注意相比于创建新对象,复用的代价更高,如果没能暴增拷贝安全,将会导致潜在的bug和安全漏洞。

7. 消除过时的对象引用

内存泄漏:指程序再申请内存后,无法释放已申请的内存空间,内存泄漏堆积后就会发生内存溢出。
内存溢出:报错OOM,没有足够的内存供申请者使用。
一般来讲当一个类自己管理自己的内存时,程序员就要注意内存内存泄露问题了,只要一个元素被释放了,那这个元素包含的所有对象应用都应该被清空。

public Object pop() {if (size == 0)throw new EmptyStackException();Object result = elements[--size];elements[size] = null; // Eliminate obsolete reference help gcreturn result;
}

消除过期引用的最佳方式是将每个变量定义在最小的作用域中。
缓存是内存泄漏的另一个来源。当将一个对象放到缓存中取,时间长了很容易忘记他还在那,剞劂方法可以使用WeakHashMap来充当缓存,只要key过期后就会被自动清除。

8. 避免使用终结方法和清理方法

在Java9中,finalizers方法已经过时了,替代的是清理方法,清理方法比终结方法危险性更低,但仍然是不可预测的,性能比较低,并且也是非必要不使用。
不使用的原因:
● finalizers方法和清理方法的缺点在于不能保证被及时执行或会被执行,当一个对象变得不可达,到执行终结方法或清理方法时,这个时间段是任意长的。因此当有对时间要求的任务不应该调用终结或清理方法完成,不应该依赖终结方法或者清理方法来更新重要的持久态。
● finalizers方法的另一个问题是在终结过程中会忽略掉抛出的异常,并且不会打印线程终止的堆栈信息。如果另一个线程企图使用这种未捕获异常的对象可能会发生不确定的行为。
● finalizers方法和清理方法会严重影响性能。
清理方法和终结方法的两种用途:

  1. 当对象的持有者忘记调用终止方法的情况下充当安全网。如 FileInputStream、FileOutputStream、ThreadPoolExecutor、和 java.sql.Connection具有充当安全网终结方法。
  2. 本地对灯体是普通对象通过本机方法委托的非Java对象,因为本地对等体不是普通Java对象,因此垃圾收集器不会识别它,当性能可接受且本地对等体没有关键的资源,则可以用清理或者终结方法回收。

9. 使用try-with-resources代替try-finally

Java中有许多必须通过调用close方法手动关闭的资源,比如InputStream,OutputStream.
在之前,即使是程序抛出异常或者返回的情况下,try-finally是保证资源正确关闭的最佳方式。但是当处理多个资源关闭时,情况就会变糟。

// try-finally is ugly when used with more than one resource!
static void copy(String src, String dst) throws IOException {InputStream in = new FileInputStream(src);try {OutputStream out = new FileOutputStream(dst);try {byte[] buf = new byte[BUFFER_SIZE];int n;while ((n = in.read(buf)) >= 0)out.write(buf, 0, n);} finally {out.close();}} finally {in.close();}
}// try-with-resources on multiple resources - short and sweet
static void copy(String src, String dst) throws IOException {try (InputStream   in = new FileInputStream(src);OutputStream out = new FileOutputStream(dst)) {byte[] buf = new byte[BUFFER_SIZE];int n;while ((n = in.read(buf)) >= 0)out.write(buf, 0, n);}
}

这个时候,当最里层的finally的close方法关闭失败,外层的的异常就会覆盖掉里层的异常,导致调试过程会很困难。但使用try-with-resourses就可以避免这个问题,并且try-with-resourses代码更加简洁易读。

第二章 所有对象都通用的方法

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

因为Object主要是为继承设计的,它的所有非final方法都有清晰地约定,任何类要重写这些方法时,都有义务去遵守这些约定否则其他依赖这些约定的类就不会正常工作。
什么时候不需要覆盖equals方法?
● 每个类的实例都是固有唯一的。如Thread
● 类不需要提供逻辑相等的功能。
● 父类已经重写过equals方法,父类的行为完全适合子类。
● 类是私有的,并且equals方法永远不会被调用。
什么时候需要重写equals方法?
如果一个类需要一个逻辑相等的概念,并且父类没有重写过这个方法,需要在该类中重写equals方法。通常这种类是值类。如Integer,String。
重写equals方法必须遵守的约定。

  1. 自反性:对任何费控引用x,x.equals(x)必须返回true。
  2. 对称性:对于任何非空引用x和y,如果y.equals(x)=true,则x.equals(y)=true
  3. 传递性: x,y,z都不为null,如果x.equals(y)=true,y.equals(z)=true,则z.equals(x)=true。
  4. 一致性:如果x,y非空,并且equals比较中的信息没有修改,多次调用x.equals(y)都要始终返回true或false。
  5. 对任何非空引用x,x.equals(null)必须返回false。
    重写equals方法时,要重写hashCode方法,不要让equals方法干太多事,不要将Object参数类型替换成其他类型。

11. 重写equals方法时总要重写hashCode

重写equals方法必须重写hashCode方法,如果没有重写,在使用HasMap或HashSet时无法正常运作。
重写的equals方法必须遵守 Object 中指定的规定。
● 在应用程序的执行期间,只要对象的 equals 方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode 方法都必须始终如一地返回相同的值。
● 如果两个对象调用 equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的 hashCode 方法都必须产生相同的整数结果。
● 如果两个对象根据 equals(Object)方法比较是不相等的,那么调用这两个对象中任意一个对象的 hashCode 方法,则不一定要产生不同的结果。无论如何,开发者应该知道,不相等的对象产生截然不同的结果,有可能提高散列表(hash tables)的性能。

12. 始终覆盖toString

默认的Object的toString方法返回的是:类名+@+无符号16进制散列码。
toString 方法应该返回对象中包含的所有值得关注的信息。
在实现toString时,需要判断是否处理返回值的格式。
● 指定字符串的格式的好处:更易读。
● 指定字符串的格式的坏处:一旦被广泛使用,必须始终坚持这种格式。如果改变格式,将会破坏代码和数据。
无论是否指定格式,都为 toString 返回值中包含的所有信息,提供一种编程式的访问路径。否则不得不自己去解析,解析过程可能会出错。

13. 谨慎覆盖clone

假设我们需要为一个类实现Cloneable接口,这个类的父类提供了一个良好的clone方法。我们从super.clone中得到的对象将会是原始对象的一个完整克隆。类中声明的任一属性的值将会和原始类对应的属性的值相等。如果每个属性包含了基本类型值或者不过变对象的引用,那么返回的对象可能正是我们要的,在这种情况下,不需要进一步的处理。
如果一个类它的所有父类获取clone的对象是通过调用super.clone,那么

x.clone().getClass() == x.getClass()

对于不可变的类不应该提供clone方法,因为这会造成无意义的拷贝。对于final修饰的属性,克隆不会成功因为禁止向final修饰的属性二次赋值。
实际上,clone方法就是另一个构造器,我们必须保证它不会破坏原始对象而且能恰当创建被克隆对象的约束条件。

14. 考虑是否实现comparable

compareTo方法并没有在Object里被声明,而是在Comparable接口中声明的唯一方法。
假如一个类实现了Comparable接口,那就表明了这个类的各个实例之间是有顺序的。几乎所有的Java类库,包括枚举类型(条目34),都实现了Comparable接口。
在下面的表述中,符号sgn(expression)表示数学中的signum函数,返回值为-1,0,1。
● 实现类必须确保对于所有的x和y,sgn(x.compareTo(y)) == -sgn(y. compareTo(x))成立。(这意味着,当且仅当y.compareTo(x)抛出了异常,x.compareTo(y)也必须抛出异常。)(自反性)
● 实现类必须确保关系的传递性:若(x. compareTo(y) > 0 && y.compareTo(z) > 0),则x.compareTo(z)>0。
● 最后,实现类必须确保若x.compareTo(y) == 0,则对于所有的z,sgn(x.compareTo(z)) == sgn(y.compareTo(z))成立。(一致性)
● 强烈建议(x.compareTo(y) == 0) == (x.equals(y))成立,但这并不是必须的,通常来说,任何实现了Comparable接口的类如果违反了这个条件,那么应该做个说明。推荐的说法是“注意:该类具有自然排序,但是与equals方法不一致。”
当遇到不同类型的实例比较时,会抛出ClassCastException异常。
在compareTo方法里,我们对属性进行比较是为了得到一个顺序而不是看其是否相等。为了比较对象引用的属性,我们可以递归地调用compareTo方法。如果一个属性没有实现Comparable接口或者我们需要一个非标准的顺序,可以使用Comparator来替代。

private static final Comparator<PhoneNumber> COMPARATOR = comparingInt((PhoneNumber pn) -> pn.areaCode)
.thenComparingInt(pn -> pn.prefix)  
.thenComparingInt(pn -> pn.lineNum);
public int compareTo(PhoneNumber pn) { return COMPARATOR.compare(this, pn);
}

compareTo或compare方法依赖于两个值之间的差值,若第一个值小于第二个值,则为负,若两个值相等,则为0,若第一个值大于第二个值,则为正。

在实现compareTo方法时,避免使用 < 和 > 运算符来进行属性值的比较。相反,我们应该使用封箱基本类型的静态比较方法(例如Integer.compare),或者Comparator接口里的比较器构造方法。
(例如Comparator<Object> hashCodeOrder = Comparator.comparingInt(o -> o.hashCode());)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/124330.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ansible的安装和部署

目录 1.Ansible的安装 2.构建Ansible清单 直接书写受管主机名或ip 设定受管主机的组[组名称] 主机规格的范围化操作 指定其他清单文件 ansible命令指定清单的正则表达式 3.Ansible配置文件参数详解 配置文件的分类与优先级 常用配置参数 4.构建用户级Ansible操作环…

Spring面试题:(一)IoC,DI,AOP和BeanFactory,ApplicationContext

IoC&#xff0c;DI&#xff0c;AOP思想 IOC就是控制反转&#xff0c;是指创建对象的控制权的转移。以前创建对象的主动权和时机是由自己把控的&#xff0c;而现在这种权力转移到Spring容器中&#xff0c;并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系。对象与对…

Java的单元测试Testng和mock

目录 单元测试重要性 TestNG TestNG官网介绍 TestNG教程 TestNG注解: TestNG配置注解实例

结构体和联合体嵌套访问

在JSON项目中&#xff0c;使用了联合体和结构体之间的嵌套&#xff0c;但是在访问内部的联合体和结构体的时候出现了问题&#xff0c;这篇文章作为记录&#xff0c;也希望能帮助遇到相同问题的好伙伴。 struct lept_value {union {struct str{char *s;size_t len;};double n;}…

【系统架构】架构风格专题

目录 1、定义 2、通用架构风格分类 3、架构风格比较 4、示例&#xff1a;管道-过滤 VS 数据仓库&#xff09;比较因素分析 1、定义 架构风格&#xff1a;描述某一特定应用领域中系统组织方式的惯用模式&#xff0c;反映了领域中众多系统所共有的结构和语义特性&#xff0c…

c++设计模式三:工厂模式

本文通过一个例子简单介绍简单工厂模式、工厂模式和抽象工厂模式。 1.简单工厂&#xff08;静态&#xff09; 假如我想换个手机&#xff0c;换什么手机呢&#xff1f;可以考虑苹果或者华为手机&#xff0c;那我们用简单工厂模式来实现这个功能&#xff1a; 我们关注的产品是手…

Lambda表达式与“::“方法引用 判空包装类Optional

函数式接口 函数式接口&#xff08;Functional Interface&#xff09;就是有且仅有一个抽象方法&#xff0c;但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为Lambda表达式。 Lambda表达式与"::"方法引用 方法引用主要是对Lambda表达式的一种优化&…

Scrum团队中的人

人永远是第一位的。 Scrum团队里有一个Scrum master、一个Product owner和若干个Developer。人数大概在10人左右&#xff0c;甚至更少。千万别在Scrum团队再搞什么小组&#xff0c;scrum团队就是最原子的团队了。我见过有些不专业的12人的scrum团队里&#xff0c;还安插了几个…

ubuntu部署个人网盘nextCloud使用docker-compose方式

概述 当下各大网盘的容量都是有限制的&#xff0c;而且xx云不开会员网速就拉跨。 所以就想搭建一个自己的盘&#xff0c;并且可以控制用户的权限分组&#xff1b; nextCloud就很合适 我这边都是自己用偶尔给其他人使用下&#xff0c;所以直接docker部署了。 ubuntu版本&…

Stream流基础使用

目录 Stream出现时间: 作用: 什么是 Stream? 生成流 forEach map filter limit

【2023.10.30练习】C语言-循环右移字符

计算机能力挑战初赛2020.19题 题目描述&#xff1a; 现要对一个由字符a-z和A-Z组成的字符串进行解密&#xff0c;已知加密规则是&#xff1a; 字符串中所有字符分别在大写或小写的字母表中被循环左移5位(fGh-->aBc)&#xff0c; 输入&#xff1a;一个加密过的字符串&#…

3.6每日一题(线性方程求通解)

1、判断类型选择方法&#xff1a;发现以y为未知函数&#xff0c;以x为自变量&#xff0c;不符合我们学过的类型 2、此时有两种方法&#xff1a; &#xff08;1&#xff09;x 与 y 对调&#xff0c;此时 x 为未知函数&#xff0c;y 为自变量 &#xff08;2&#xff09;变量代换…

10.27~10.29数电第三次实验分析与问题

实验要求 分析 寄存器 D触发器有两个输出口&#xff0c;一个输入口&#xff0c;一个时钟信号&#xff0c;一个复位信号 同步异步就是说复位信号在不在always里 给它加一个load就成了一位寄存器&#xff0c; 寄存器堆 8个8位的寄存器堆&#xff0c;每个寄存器都有两读一写…

GBase8a SSL 配置

GBase8a SSL 配置 GBase8a MPP Cluster 支持 SSL 标准协议&#xff0c; SSL 协议是一种安全性更高的协议标准&#xff0c; 它加入了数字签名和数字证书来实现客户端和服务器的双向身份验证&#xff0c;保证了通信双方更加安全的数据传输。 配置客户端使用 SSL 安全连接的方式连…

计算机网络 期末复习方向

一&#xff0c;网络层 前三章我们解决了在具体物理网络&#xff0c;具体局域网上数据的传输过程&#xff0c;我们能实现在一个具体网络上把数据从A主机传到B主机&#xff0c;这件事情解决了。 引入问题&#xff1a;如果网络的局域网不考虑网络的扩展问题之后&#xff0c;我们…

【Leetcode】【简单】13. 罗马数字转整数

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能&#xff0c;轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/roman-to-integer/description/ …

在win10下,使用torchviz对深度学习网络模型进行可视化

目录 1. 安装 graphviz 和 torchviz 2.安装 graphviz.exe 3.实例测试 4.如果你的电脑还是无法画图&#xff0c;并且出现了下面的报错&#xff1a; 5.参考文章&#xff1a; 1. 安装 graphviz 和 torchviz 首先打开 Anaconda prompt 进入自己的 pytorch 环境(图中 pt 是我自…

servlet和SpringBoot两种方式分别获取Cookie和Session方式比较(带源码) —— 图文并茂 两种方式获取Header

目录 1、cookie和Session以及Header知识点 2、设置/获取 Cookie 3、设置/获取Session 4、设置/获取Header Cookie —— 客户端机制 Session —— 服务端机制 本篇博客主在用servlet和SpringBoot两种方式分别获取Session和Cookie&#xff0c;用来比较记忆与分析 1、cookie和…

MSQL系列(十二) Mysql实战-为什么索引要建立在被驱动表上

Mysql实战-left/right/inner join 使用详解 前面我们讲解了BTree的索引结构&#xff0c;也详细讲解下 left Join的底层驱动表 选择原理&#xff0c;那么今天我们来实战一下 left join&#xff0c;right join&#xff0c;inner join 等sql命令&#xff0c;看看到底如何用以及如…

Leetcode周赛369补题(3 / 3)

目录 1、找出数组的K-or值 - 位运算 模拟 2、数组的最小相等和 - 分情况讨论 3、使数组变美的最小增量运算数 - 动态规划dp 1、找出数组的K-or值 - 位运算 模拟 100111. 找出数组中的 K-or 值 思路&#xff1a; 根据范围&#xff0c;我们可以枚举0~30位&#xff0c;然后在…