Effective.Java第1-11条
1. 考虑使用静态工厂方法替代构造方法
一个类可以提供一个公共静态工厂方法,它只是返回类实例的静态方法。例如JDK的Boolean的valueOf方法:
public final class Boolean implements java.io.Serializable,
Comparable<Boolean>
{
public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false); public Boolean(boolean value) {
this.value = value;
} public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
...
}
静态工厂方法与设计模式中的工厂方法模式不同。
优点:
(1)静态工厂方法不像构造方法,它们有名字,语义清晰
(2)静态工厂方法不需要每次调用时都创建对象。例如返回静态对象,避免不必要的创建对象。
(3)静态工厂可以返回其返回类型的任何子类型的对象。
(4)其返回类型可以根据参数的不同而不同,比如EnumSet类的下面方法:
public static <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c) {
if (c instanceof EnumSet) {
return ((EnumSet<E>)c).clone();
} else {
if (c.isEmpty())
throw new IllegalArgumentException("Collection is empty");
Iterator<E> i = c.iterator();
E first = i.next();
EnumSet<E> result = EnumSet.of(first);
while (i.hasNext())
result.add(i.next());
return result;
}
}
(5)在编写包含该方法的类时,返回的对象的类不需要存在。这种灵活的静态工厂方法构成了服务提供者框架的基础。比如Java数据库连接JDBC。由对应的厂商提供具体的实现类。
缺点:
(1)如果类不含public或protected的构造方法,将不能被继承。
(2)与其它普通静态方法没有区别,没有明确的标识一个静态方法用于实例化类。也就是API不是非常全。所以,一般一个静态工厂方法需要有详细的注释,遵守标准的命名。如下:
from——A 类型转换方法,它接受单个参数并返回此类型的相应实例,例如:Date d = Date.from(instant);
of——一个聚合方法,接受多个参数并返回该类型的实例,并把他们合并在一起,例如:Set faceCards = EnumSet.of(JACK, QUEEN, KING);
valueOf——from 和 to 更为详细的替代 方式,例如:BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
instance 或 getinstance——返回一个由其参数 (如果有的话) 描述的实例,但不能说它具有相同的值,例如:StackWalker luke = StackWalker.getInstance(options);
create 或 newInstance——与 instance 或 getInstance 类似,除了该方法保证每个调用返回一个新的实例,例如:Object newArray = Array.newInstance(classObject, arrayLen);
getType——与 getInstance 类似,但是如果在工厂方法中不同的类中使用。Type 是工厂方法返回的对象类型,例如:FileStore fs = Files.getFileStore(path);
newType——与 newInstance 类似,但是如果在工厂方法中不同的类中使用。Type 是工厂方法返回的对象类型,例如:BuweredReader br = Files.newBuweredReader(path);
type—— getType 和 newType 简洁的替代方式,例如:List litany = Collections.list(legacyLitany);
2. 当构造方法参数过多时考虑构造模式
比如一个User,有好多属性,但是只有ID是必须有的,其他属性可有可无。如下:
package cn.qlq.builder; public class User { // 必须字段
private int id; private String name;
private String sex;
private String job;
private String health;
private String BMI;
private int height;
private int weight; public User() {
super();
} public User(int id, String name, String sex, String job, String health, String bMI, int height, int weight) {
super();
this.id = id;
this.name = name;
this.sex = sex;
this.job = job;
this.health = health;
BMI = bMI;
this.height = height;
this.weight = weight;
} public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getSex() {
return sex;
} public void setSex(String sex) {
this.sex = sex;
} public String getJob() {
return job;
} public void setJob(String job) {
this.job = job;
} public String getHealth() {
return health;
} public void setHealth(String health) {
this.health = health;
} public String getBMI() {
return BMI;
} public void setBMI(String bMI) {
BMI = bMI;
} public int getHeight() {
return height;
} public void setHeight(int height) {
this.height = height;
} public int getWeight() {
return weight;
} public void setWeight(int weight) {
this.weight = weight;
} @Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", sex=" + sex + ", job=" + job + ", health=" + health + ", BMI="
+ BMI + ", height=" + height + ", weight=" + weight + "]";
} }
当我们设置几个属性的时候可以通过构造方法进行创建,但是比如我们只想设置一些属性,其他属性没用,我们可能会写成空值,这样的代码阅读起来很难懂,属性更多的时候更加难以阅读:
User user = new User(1, "张三", "", "", "", "", 0, 0);
也有可能通过setter进行设值,如下:(属性更多的时候需要更多的setter)
User user = new User();
user.setId(1);
user.setName("xxx");
user.setBMI("XXX");
...
解决办法:采用建造模式 + 流式写法
由于是用Builder模式来创建某个对象,因此就没有必要再定义一个Builder接口,直接提供一个具体的建造者类就可以了。
对于创建一个复杂的对象,可能会有很多种不同的选择和步骤,干脆去掉“导演者”,把导演者的功能和Client的功能合并起来,也就是说,Client这个时候就相当于导演者,它来指导构建器类去构建需要的复杂对象。
package cn.qlq.builder; public class UserBuilder { private User user = new User(); /**
* 构造方法确保ID必有
*
* @param id
*/
public UserBuilder(int id) {
user.setId(id);
} UserBuilder name(String name) {
user.setName(name);
return this;
} UserBuilder sex(String sex) {
user.setSex(sex);
return this;
} UserBuilder job(String job) {
user.setJob(job);
return this;
} UserBuilder health(String health) {
user.setHealth(health);
return this;
} UserBuilder BMI(String BMI) {
user.setBMI(BMI);
return this;
} UserBuilder height(int height) {
user.setHeight(height);
return this;
} UserBuilder weight(int weight) {
user.setWeight(weight);
return this;
} public User build() {
if (user.getId() == 0) {
throw new RuntimeException("id必须设置");
} return user;
} }
客户端代码:
package cn.qlq.builder; public class MainClass { public static void main(String[] args) {
UserBuilder userBuilder = new UserBuilder(2);
User user = userBuilder.name("张三").BMI("xxx").health("健康").build();
System.out.println(user);
} }
这样的代码读起来也舒服,语义也更好理解。
3.使用私有构造方法或枚举类实现Singleton属性
(1)使用静态工厂方法实现
public class Runtime {
private static Runtime currentRuntime = new Runtime(); public static Runtime getRuntime() {
return currentRuntime;
} private Runtime() {}
。。。
}
如果需要防御通过反射创建对象,可以在构造方法中判断,当currentRuntime != null 的时候禁止创建(抛出一个RuntimeException).
(2)枚举类实现
package cn.qlq.thread.fifteen; /**
* 枚举实现单例模式
*
* @author Administrator
*
*/
public enum Singleton_6 { instance; private Singleton_6() {
System.out.println("调用构造方法");
} public Singleton_6 getInstance() {
return instance;
} public static void main(String[] args) {
System.out.println(Singleton_6.instance.getInstance());
}
}
4.使用私有构造方法执行非实例化
我们经常会使用一些只包含静态方法和静态属性的类。这样的类获得了不好的名声,因为有些人滥用这些类而避免以面向对象的方式思考,但是它们确实有着特殊的用途。比如说工具类或者说常量工具类。
JDK8开始,接口也允许有默认的静态方法和default方法。
这样的类不允许被实例化:一个实例是没有意义的。然而,在没有显示构造方法的情况下,编译器提供了一个公共的、无参的默认构造方法。因为当类不包含显示构造方法的时候,才会生成一个默认的构造方法,因此可以通过包含一个私有构造方法来实现类的非实例化:
public class DocUtils { // 静止实例化
private DocUtils() {
throw new RuntimeException();
} }
5. 依赖注入优于硬链接资源
许多类依赖一个或者多个底层资源。如:拼写检查类依赖于一个字典类.
常见一:将此类实现为静态实用工具类
public class SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker() {} // Noninstantiable
public static boolean isValid(String word) { ... }
public static List suggestions(String typo) { ... }
} }
常见二:实现为单例
public class SpellChecker {
private final Lexicon dictionary = ...; private SpellChecker(...) {}
public static INSTANCE = new SpellChecker(...); public boolean isValid(String word) { ... }
public List<String> suggestions(String typo) { ... }
}
上面两种做法都令人不满意,因为它们假设只有一本字典。
最高效的做法是使 dictionary 属性设置为非final,并且通过一个方法改变此属性,以实现支持多个字典。
依赖项注入(dependency injection)的一种形式:字典是拼写检查器的一个依赖项,当它创建时被注入到拼写检查器中。
// Dependency injection provides flexibility and testability
public class SpellChecker {
private final Lexicon dictionary; public SpellChecker(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
} public boolean isValid(String word) { ... }
public List<String> suggestions(String typo) { ... }
}
6. 避免创建不必要的对象
每次需要时重用一个对象而不是创建一个新的功能相同的对象。常用的如静态工厂方法模式,如:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
例如:
String s = new String("str");
每次执行都会创建一个String对象,而这些对象并不是必须的,建议改为:
String s = "str";
优先使用基本类型而不是装箱的基本类型,也要注意无意识地装箱。
7. 消除过期的对象引用
简单的理解为:一旦对象引用过期,将其设为null。一个好处就是如果之后被错误的引用会抛出NPE。
8. 避免使用Finalizer和Cleaner机制
Java9中用Cleaner代替了Finalizer,但是Finalizer仍被保留使用。
注意不能把Java中的Finalizer和Cleaner当成c++的析构函数。在c++中,析构函数是正确的回收对象相关资源方式,是与构造方法对应的。在Java中,当一个对象不可达时,垃圾收集器回收与对象相关联的存储空间,不需要开发人员做额外工作。C++析构函数也被用来回收其他内存资源。Java中,try-finally或者try-with-resources用于此目的。
Finalizer和Cleaner的一个缺点是不能保证他们能够及时地执行。也就是说当对象引用不可达时,这两个方法的执行时间是不固定的,所以不应该做任何业务相关代码。
不能相信System.gc() 和 System.runFinalization() 等方法,他们也只是简单的通知进行GC,并不会马上GC。
9. 使用 try-with-resources语句替代 try-finally 语句
关闭流的方式有很多种方式,一般都是手动关闭,比如 InputStream、OutputStream和Java.sql.Connection。
package cn.qlq; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; public class MainClass { public static void main(String[] args) {
File file = new File("/usr/test.txt");
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = new FileInputStream(file);
outputStream = new FileOutputStream(file);
// 使用流
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} } }
再次升级变为如下方式,有点类似于IOUtils的方法:
package cn.qlq; import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; public class MainClass { public static void main(String[] args) {
File file = new File("/usr/test.txt");
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = new FileInputStream(file);
outputStream = new FileOutputStream(file);
// 使用流
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
closeQuietly(outputStream);
closeQuietly(inputStream);
} } private static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
// ignored
}
}
} }
上面代码比较冗余,而且不易阅读,Java7引入了 try-with-resources。代码阅读比较简单,而且提供了更好的诊断。如下:(建议这种写法,当然catch代码块可以不写,一般用于记录一些信息)
public static String readValue(String filePath, String defaultValue) {
File file = new File("/usr/test.txt");
try (InputStream inputStream = new FileInputStream(file);
OutputStream outputStream = new FileOutputStream(file)) { // 读取文件返回值
return "";
} catch (Throwable e) {
// 记录日志
return defaultValue;
}
}
10. 重写equals()方法时遵守通用约定
按照约定,equals方法要满足以下规则。
自反性: 对于任何非空引用x,x.equals(x) 一定是true
对称性: 对于非空引用x和y, x.equals(y) 和 y.equals(x)结果一致
传递性: a 和 b equals , b 和 c equals,那么 a 和 c也一定equals。
一致性: 对于任何非空引用x和y,如果在equals比较中使用的信息没有被修改,则x.equals(y) 的多次调用必须始终返回true或者false。
非空性: 对于任何非空引用x, x.equals(null) 一定是false
同时,也有一些提醒,比如:
(1)重写了euqals方法的对象必须同时重写hashCode()方法。
(2)在equals方法中,不要将参数的Object类型换成其他类型。
编写高质量equals()方法的建议:
(1)使用==运算符检查参数是否为该对象的引用。如果是,返回ttrue。这只是一种性能优化。
(2)使用 instanceof 来检查参数是否是正确的类型,如果不是返回false。
(3)参数转换为正确的类型。
(4)对于每个类的重要属性,在equals中进行比较。
对于类型为非float或double的基本类型,使用==运算符比较;对于对象引用属性,递归地调用equals方法;对于float基本类型的属性,使用Float.compare(float, float)方法;对于double基本类型的属性,使用Double.compare(double, double)。
在很多情况下,不要重写equals方法,从Object继承的完全是想要的。如果确实重写了equals()方法,那么一定要比较这个类的所有属性,并且遵守上面五条规则。
在比较两个对象是否是同一个对象的时候用equals,不用==。
11. 重写了euqals方法的对象必须同时重写hashCode()方法。
这个方法返回对象的散列码,返回值是int类型的散列码。对象的散列码是为了更好的支持基于哈希机制的Java集合类,例如 Hashtable, HashMap, HashSet 等。
等价的(调用equals返回true)对象必须产生相同的散列码。不等价的对象,不要求产生的散列码不相同。
假设只重写equals而不重写hashcode,那么类的hashcode方法就是Object默认的hashcode方法,由于默认的hashcode方法是根据对象的内存地址经哈希算法得来的,所以会导致equals相等的两个对象的hasCode()不一定相等,违反了上面的约定。
例如:使用commons-lang包自带的工具类编写equals和hasCode方法:
package cn.qlq; import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder; public class User implements Cloneable { private int age;
private String name, sex; @Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
} if (obj == this) {
return true;
} if (!(obj instanceof User)) {
return false;
} final User tmpUser = (User) obj;
return new EqualsBuilder().appendSuper(super.equals(obj)).append(name, tmpUser.getName()).isEquals();
} @Override
public int hashCode() {
return new HashCodeBuilder().append(name).toHashCode();
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getSex() {
return sex;
} public void setSex(String sex) {
this.sex = sex;
} @Override
public String toString() {
return "User [age=" + age + ", name=" + name + ", sex=" + sex + "]";
} }
Effective.Java第1-11条的更多相关文章
- <<Effective Java>> 第四十三条
<<Effective Java>> 第四十三条:返回零长度的数组或者集合,而不是null 如果一个方法的返回值类型是集合或者数组 ,如果在方法内部需要返回的集合或者数组是零长 ...
- 《Effective Java》第11章 序列化
"将一个对象编码成一个字节流",称作将该对象序列化(serializing); 相反的处理过程被称作反序列化(deserializing),一旦对象被序列化后,它的编码就可以从一台 ...
- [Effective JavaScript 笔记] 第11条:熟练掌握闭包
理解闭包三个基本的事实 第一个事实:js允许你引用在当前函数以外定义的变量. function makeSandwich(){ var magicIngredient=”peanut butter”; ...
- Effective java读书札记第一条之 考虑用静态工厂方法取代构造器
对于类而言,为了让client获取它自身的一个实例,最经常使用的方法就是提供一个共同拥有的构造器. 另一种放你发,也应该子每一个程序猿的工具箱中占有一席之地.类能够提供一个共同拥有的静态 工厂方法.它 ...
- Android 性能优化(19)*代码优化11条技巧:Performance Tips
Performance Tips 1.In this document Avoid Creating Unnecessary Objects 避免多余的对象 Prefer Static Over Vi ...
- <<Effective Java>>之善用组合而不是继承
使用JAVA这门OO语言,第一要义就是,如果类不是专门设计来用于被继承的就尽量不要使用继承而应该使用组合 从上图2看,我们的类B复写了类A的add喝addALL方法,目的是每次调用的时候,我们就能统计 ...
- [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...
- Effective Java 第三版——11. 重写equals方法时同时也要重写hashcode方法
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- 阅读《Effective Java》每条tips的理解和总结(1)
<Effective Java>这本书的结构是90来条tips,有长有短,每条tip都值的学习.这里根据对书中每条tip的理解做简短的总结,方便日后回顾.持续更新~ 1. 考虑用静态方法代 ...
- Java异常(二) 《Effective Java》中关于异常处理的几条建议
概要 本章是从<Effective Java>摘录整理出来的关于异常处理的几条建议.内容包括:第1条: 只针对不正常的情况才使用异常第2条: 对于可恢复的条件使用被检查的异常,对于程序错误 ...
随机推荐
- 斐波那契查找(Fibonacci Search)
斐波那契查找 斐波那契查找就是在二分查找的基础上根据斐波那契数列进行分割的. 在斐波那契数列找一个等于略大于查找表中元素个数的数F[n],将原查找表扩展为长度为F[n](如果要补充元素,则补充重复 ...
- loadrunner安装和应用
问题:1.负载测试流程 2.为什么实现性能测试自动化 3.设置场景 (场景定义) 4.事物响应时间,吞吐量和吞吐率,正在运行vuser,windows资源,每秒点击次数,每秒http响应数. 5.i ...
- 记一次删除ocr与dbfile的恢复记录
自己造成的一个案例: 场景:ocr磁盘组被我dd掉了,dbfile磁盘组也被我dd掉了.Rac起不来.之前ocr的DATA磁盘组被替换到了ABC磁盘.所幸的是有备份. 重新加载OCR磁盘 [root@ ...
- TCP 通信时序及状态变迁
TCP 通信时序及状态变迁 参考链接: https://www.cnblogs.com/boxker/p/11214886.html https://blog.csdn.net/miss_ruoche ...
- 跳过__wakeup()魔法函数
__wakeup():将在序列化之后立即被调用. 漏洞原理:当反序列化字符串中,表示属性个数的值大于其真实值,则跳过__wakeup()执行. 参考题目:xctf-unserialize3 h ...
- 【JavaScript】图片加载由模糊变清晰 —— 图片优化
开发过程中,一些图片的展示时,加载很慢很久,后来把图片缩放压成缩略图吧,速度是快了但是模糊不清,如何处理这样问题,下面就和大家分享一下自己的处理方法. 先让客户端加载像素小的缩略图: <img ...
- vue - 过滤器-钩子函数路由
一.关于路由 1.使用vue router 本质上是声明一种可以通过路径进行 挂子,用子 找到对应的 template 进行页面渲染 <!DOCTYPE html> <html la ...
- JS高阶---数据、变量、内存
[一]基础 (1)什么是数据? 存储在内存里 代表特定信息 本质为0101,二进制数据 (2)什么是内存? 内存条通电后产生的可存储数据的空间(临时的) 拓展: 1.2种数据 2.内存分类--栈和堆 ...
- 织梦dedecms后台文件media_add.php任意上传漏洞解决办法
织梦在安装到阿里云服务器后阿里云后台会提示media_add.php后台文件任意上传漏洞,引起的文件是后台管理目录下的media_add.php文件,下面跟大家分享一下这个漏洞的修复方法: 首先找到并 ...
- NOIP 2013 火柴排队
洛谷 P1966 火柴排队 洛谷传送门 JDOJ 2227: [NOIP2013]火柴排队 D1 T2 JDOJ传送门 Description 涵涵有两盒火柴,每盒装有 n 根火柴,每根火柴都有一个高 ...