《Effective Java中文版 第2版》学习笔记
作者:互联网
JAVA语言支持四种类型:接口(interface)、类(class)、数组(array)和基本类型(primitive)。
类实例和数组是对象(object),基本类型的值不是对象。
类的成员(member)由它的域(field)、方法(method)、成员类(member class)和成员接口(member interface)组成。
方法的签名(signature)由它的名称和所有参数类型组成,不包括它的返回类型。
类、接口、构造器、成员以及序列化形式被统称为API元素(API element)。
1、创建和销毁对象:
-------------------------------------------------
(1)考虑用静态工厂方法代替构造器;
(2)遇到多个构造器参数时考虑用构建器;
(3)用私有构造器或枚举类型强化Singleton属性;
(4)通过私有构造器强化不可实例化能力;
(5)避免创建不必要的对象;
(6)消除过期的对象引用;
(7)避免使用终结方法。
--------------------------------------------------
1)考虑用静态工厂方法代替构造器
考虑用静态工厂方法代替构造器,此处静态工厂方法不直接对应设置模式的工厂方法。
如Boolean里基本类型值转换:
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 static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); } public static Boolean valueOf(String s) { return parseBoolean(s) ? TRUE : FALSE; } }
静态工厂方法较构造器的优点:
①易于阅读:可以有实际意义的名称,而不只是参数类型、个数、顺序的不同;
②提高性能:不必在每次调用的时候都创建一个新对象;也就是说我们可以为重复的调用返回相同对象;
③更加灵活:可以返回原返回类型的任何子类型的对象或者接口。客户端通过接口来引用被返回的对象,而不是通过它的实现类来引用被返回的对象。
如java.util.EnumSet用静态工厂方法返回两种实现类之一
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E> implements Cloneable, java.io.Serializable { public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) { Enum<?>[] universe = getUniverse(elementType); if (universe == null) throw new ClassCastException(elementType + " not an enum"); if (universe.length <= 64) return new RegularEnumSet<>(elementType, universe); else return new JumboEnumSet<>(elementType, universe); } }
④代码更加简洁:在创建参数化类型实例的时候,它们使代码变得更加简洁。
在调用参数化类的构造器时,要接连两次提供类型参数,如:
Map<String, List<String> m = new HashMap<String, List<String>>();
假如HashMap提供了这样的静态方法:(目前发行版本还没有这样做,只是假设演示)
public static<K, V> HashMap<K, V> newInstance() { return new HashMap<K, V>(); }
我们可以这么写:
Map<String, List<String> m = HashMap.newInstance();
静态工厂方法的缺点:
①类如果不含有公有的或受保护的构造器,就不能被子类化。
②它们与其他的静态方法实际上没有任何区别,对于提供静态工厂方法而没有构造器的类,我们查明调用什么方法有点困难。但是,我们可以通过在类和接口注释上说明这静态工厂,以及遵循标准的命名习惯,就可以方便我们知道通过该静态工厂来实例化一个类,如valueOf,getInstance,newInstance,getType,newType。
静态工厂和构造器的共同局限性:都不能很好地扩展到大量的可选参数。
2)遇到多个构造器参数时考虑用构建器;
1、使用重叠构造器模式,这样难以阅读和控制,我们可能需要为每一种可能写一个构造器方法。
2、使用JavaBeans模式(常用):调用一个无参构造器创建对象,通过调用setter方法设置每个必要参数和可选参数,但是导致了在构造过程中JavaBean可能处于不一致的状态。
3、使用Builder模式,使用builder对象(构建类的静态成员类),通过builder对象的方法来设置参数后,最终通过无参的build()方法生成不可变的对象。
public class Facts { private Long id; private String no; private Integer size; private String name; public static class Builder { // 初始化必须参数 private Long id; private String no; // 初始化非必须参数 private Integer size; private String name; public Builder(Long id, String no) { this.id = id; this.no = no; } public Builder setSize(Integer size) { this.size = size; return this; } public Builder setName(String name) { this.name = name; return this; } public Facts build() { return new Facts(this); } } private Facts(Builder builder) { this.id = builder.id; this.no = builder.no; this.size = builder.size; this.name = builder.name; } @Override public String toString() { return "Facts [id=" + id + ", no=" + no + ", size=" + size + ", name=" + name + "]"; } public static void main(String[] args) { // 为了创建对象Facts,必须先创建它的构建器Builder,造成了一定的开销,不过在实践中不明显。 Facts facts = new Facts.Builder(1l, "001").setName("name").setSize(1).build(); System.out.println(facts); } }
结论:
如果类的构造器或静态工厂中具有多个参数,使用Builder模式会更好,特别是大多数参数可选时。
Builder模式的客户端代码,比重叠构造器模式更易于阅读和编写,比JavaBeans更加安全。
3)用私有构造器或枚举类型强化Singleton属性
Singleton指仅仅被实例化一次的类,实现Singleton方式:
①final域修饰的公有静态成员+私有的构造器,这样私有构造器只被调用一次;但通过反射机制调用私有构造器,就无法保证构造器只被调用一次了;这样我们只能通过在第二次创建时抛出异常来防止发生。
public class SingletonDemo { // final域修饰的公有静态成员 public static final SingletonDemo INSTANCE = new SingletonDemo(); // 私有的构造器 private SingletonDemo() {} }
②final域修饰的私有静态成员+私有的构造器+公有的静态工厂方法
public class SingletonDemo { // final域修饰的私有静态成员 private static final SingletonDemo INSTANCE = new SingletonDemo(); // 私有的构造器 private SingletonDemo() {} // 公有的静态工厂方法 public static SingletonDemo getInstance() { return INSTANCE; } }
这两种方法要实现可序列化,除了声明中加"implements Serializable",必须声明所有的实例域都是瞬时(transient)的,并提供readResolve方法。
public class SingletonDemo implements Serializable { // final域修饰的私有静态成员 private transient static final SingletonDemo INSTANCE = new SingletonDemo(); // 私有的构造器 private SingletonDemo() {} // 公有的静态工厂方法 public static SingletonDemo getInstance() { return INSTANCE; } private Object readResolve() { return INSTANCE; } }
③编写一个包含单个元素的枚举类型实现单例(未广泛使用):
public enum SingletonEnum { INSTANCE; }
4)通过私有构造器强化不可实例化能力:
public class UtilsClass { // 通过显示定义的构造方法私有化,使其不可实例化,同时子类也没了可访问的超类构造器 private UtilsClass() { // 抛出异常,可以防止被内部调用构造器 throw new AssertionError(); } }
2、对于所有对象都通用的方法
覆盖equals时请遵守通用约定:
equals方法实现了等价关系:
自反性:对于任何非null的引用值x,x.equals(x)必须返回true。
对称性:对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true.
传递性:对于对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。
一致性:对于任何非null的引用值x、y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)返回必须一致。
对于任何非null的引用值x,x.equals(null)必须返回false。
覆盖equals时总要覆盖hashCode。
不要企图让equals方法过于智能。
不要将equals声明中的Object对象替换为其他的类型
hashCode的通用约定:
只要对象的equals的比较操作所用的信息没有被修改,那么这个对象无论被调用多少次,hashCode方法一定返回同一整数。
如果两个对象根据equals(Object)方法比较是相等的,则它们一定有相等的散列码(hash code)。
如果两个对象的equals(Object)方法比较不相等,但调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果。但是一个好的散列函数通常倾向于为不相等的对象产生不相等的散列码。
谨慎地覆盖clone:
Cloneable接口的目前是作为对象的一个mixin接口,表明这样的对象允许克隆。
如何实现一个行为良好的clone方法,何时适合这样做:
如果一个类实现了Cloneable接口,Object的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportedException异常。
拷贝对象往往会导致创建它的类的一个新实例,同时也会要求拷贝内部的数据结构。
标签:Java,Effective,静态,中文版,equals,构造,private,SingletonDemo,public 来源: https://www.cnblogs.com/scorpio-cat/p/EffectiveJava.html