文章目录
第1条: 静态工厂方法代替构造器
优势1:它们有名称
更为清楚。
优势2:不必在每次调用它们的时候都创建一个新对象。
类总能严格的控制在某个时刻哪些实例应该存在。即实例受控的类(instance-controlled)。
优势3:它们可以返回原返回类型的任何子类对象。
api可以返回对象(子类对象),同时又不会使对象的类(子类)变成公有的,这种方式隐藏实现类会使得api变得非常简洁。
public class Base {
public static Base get(){
return new Son();
}
}
class Son extends Base{
}
这项技术也适用于基于接口的框架。因为在这种框架中,接口为静态工厂方法提供了自然返回类型。
- Java8之前,接口不能有静态方法,按照惯例,接口Type的静态工厂方法被放在一个名为Types的不可实例化的伴生类中。
例如Java Collections Framework的集合接口有45个工具实现,几乎这些所有的实现都通过静态工厂方法放在一个不可实例化的类(java.util.Collections)中。所有返回对象的类都是非公有(非公有并不一定是private)的。
- java8之后。似乎没有任何理由给接口提供一个不可实例化的伴生类。但是仍然有必要将这些静态方法背后的大部分实现代码放进一个包级别的类中。因为java8要求所有静态盛有都是公有的。java9允许有私有的静态方法,其他仍然必须是公有的。
优势4:所返回的对象的类可以随着每次调用而发生变化
只要是已声明的返回类型的子类型。
优势5:方法返回的对象所属的类,在编写包含该静态方法的类时可以不存在。
这种灵活的静态工厂方法构成了服务提供者框架(Service-Provider-Framework)的基础。例如JDBC的api
缺陷1:类如果不含公有或保护的构造器。就不能被子类化(继承)。
缺陷2:没有统一的标准。相对构造函数较难查明。
静态工厂惯用名称:
from-类型转换方法,单个参数,返回该类型的一个相对应的实例
Date d=Date.from(instant)
of-聚合方法,多个参数,返回该类型的一个实例,把它们合并起来
Set<Rank> s=EnumSet.of(JAVK,QUEEN,KING)
valueOf-比from和of更琐碎的一种替代方法
BigInteger bi=BigInteger.valueOf(Integer.MAX_VALUE)
instance或getInstance-返回的实例通过方法的参数(如有)来描述的,但并不能说与参数拥有一样的值
StackWalker sw=StackWalker.getInstance(options)
create或newInstance。类似instance或getInstance,但是create或者newInstance保证每次调用都返回一个新的实例。
Object o=Array.newInstance(classObject,arrayLen)
getXXX----像getInstance一样,但是在工厂方法处于不同的类时使用,XXX表示工厂方法所返回的对象类型
FileStore fs=Files.getFileStore(path)
newXXX----像newInstance一样,但是在工厂方法处于不同的类时使用,XXX表示工厂方法所返回的对象类型
BufferedReader br=Files.newBufferedReader(path)
type----getType或newType的简版
List<Complaint> litany=Collections.list(legacyLitany)
第7条:清除过期的对象引用
public Object pop(){
if(size == 0)
throw new EmptyStackException();
Object result = elements[--size];
//elements[size] = null;
return result
}
示例代码不添加注释行的话就没有清空过期引用从而导致内存泄漏。
第13条:谨慎的覆盖clone
需要覆盖clone时,步骤应该是先调用super.clone()方法,然后修正任何需要修正的域(意味着需要深拷贝)。
**但是建议使用拷贝构造器或者拷贝工厂(数组除外)**而不是clone方法。
//Copy constructor
public Test(Test test){ ... };
//Copy factory
public static Test newInstance(Test test){ ... };
第14条:考虑实现Comparable接口
实现一个对排序敏感的类时,都应该让这个类实现Comparable接口,比较域值时,使用基本类型的装箱类的静态compare方法,或者在Comparator接口中使用比较器构造方法。
//Comparable with comparator construcation methods
private static final Comparator<Test> COMPARATOR =
comparingInt((Test t) -> t.id)
.thenComparingInt(t -> t.age)
public int compareTo(Test t){
return COMPARATOR.compare(this,pn);
}
第17条:不可变对象本质上是线程安全的,他们不要求同步
不可变,所以安全。不可变对象唯一的缺点是对于每个不同的值都需要一个单独的对象。
第20条:接口优于抽象类
通过对接口提供一个抽象的骨架实现类,可以把接口和抽象类的实现结合起来。
接口负责定义类型,或许还包含一些缺省方法,而骨架实现类则负责实现除基本类型接口方法外,剩下的非基本类型接口方法。扩展骨架实现占了实现接口之外的大部分工作。这就是模板方法模式。
示例代码(AbstractList就是抽象骨架实现类):
static List<Integer> intArrayAsList(int[] arr){
Objects.requireNonNull(arr);
//钻石操作符(diamond operator)<>用于简化泛型,只能在java9及之后在内部类使用
//java9之前这里应该是 return new AbstractList<Integer>(){...}
return new AbstractList<>(){
@Override
public Integer set(int i,Integer val){
int oldVal = arr[i];
arr[i] = val;
return oldVal;
}
@Override
public int size() {
return arr.length;
}
@Override
public Integer get(int index) {
return arr[index];
}
};
}
这个例子演示了抽象骨架实现类的功能。而且这个例子是个适配器(Adapter),它的功能是将int数组转换成Integer实例的列表。
另外,实现了接口的类可以把对于接口方法的调用转发到一个内部私有类的实例上,这个内部私有类扩展了抽象骨架实现类。这种方法称作模拟多重继承。
但是记住,接口无法也不能为Object方法提供缺省方法。
(我理解的是,这也是抽象骨架类的主要工作或者存在的意义(之一?):提供Object方法的缺省方法。第89页)
Java7开始,字面量中下划线的使用已经合法了。
建议每三位一组
private static final double TEST_UNDERLINE = 1.278_448_425;
枚举
p133
关于枚举
- 枚举类经过编译后产生的是一个Class文件,所以枚举实际上是(生成了)一个类,该类继承Enum(所以枚举不能继承其他类)
- 枚举中的实例隐式的用static final修饰过。实例必须最先声明,实例之间用逗号分隔,最后一个实例后面加分号。
- 枚举的构造器默认就是私有的,且只能是私有的
- 枚举类型继承了Enum的一些方法。同时与一个单独的int值关联(Enum类的private final int ordinal)。
values()返回实例数组
toString()返回实例的声明名称
ordinal()返回ordinal的值(即声明的顺序)。不建议使用该方法,建议使用实例域代替序数。35条
valuesOf(String)将实例名(形参)转换成实例(返回值)
-
枚举通过公有的静态final域为每个枚举常量导出一个实例,枚举没有可访问的构造器,所以是实例受控的。
-
枚举允许添加任意的方法和域。枚举的父类Enum提供了所有Object方法的高级实现,实现了Comparable和Serializable接口,并针对枚举的可任意改变性设计了序列化方式。
-
每个枚举常量(实例)后面括号中的数值就是传递给构造器的参数
-
类似示例中的枚举实例
PLUS("+") { public double apply(double x, double y) { return x + y;}}
实际相当于
public static final Operation2 PLUS = new Operation2("+"){ public double apply(double x, double y) { return x + y;}}
即创建匿名内部类的一个对象,并由PLUS引用该对象
示例
初始版本,使用switch语句判断,不建议。
enum Operation1 {
PLUS("+"), MINUS("-"), TIMES("*"), DIVIDE("/");
private final String symbol;
Operation1(String symbol) {
this.symbol = symbol;
}
public double apply(double x, double y) {
switch (this) {
case PLUS:
return x + y;
case MINUS:
return x - y;
case TIMES:
return x * y;
case DIVIDE:
return x / y;
default:
throw new AssertionError("Unknown op:" + this);
}
}
@Override
public String toString() {
return symbol;
}
}
改良后
//接口增强扩展性,另一种写法是在枚举类中添加抽象方法
//public abstract double apply(double x,double y);
public interface Operation {
double apply(double x, double y);
}
enum Operation2 implements Operation {
PLUS("+") {
@Override
public double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
@Override
public double apply(double x, double y) {
return x - y;
}
},
TIMES("*") {
@Override
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
@Override
public double apply(double x, double y) {
return x / y;
}
};
private final String symbol;
Operation2(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
}
Stream与枚举
使用Stream实现将枚举实例名(toString)变回相应的枚举
private static final Map<String, Operation1> enumMap = Stream.of(Operation1.values()).collect(toMap(Objects::toString, e -> e));
private static Optional<Operation1> getOperation1(String string) {
return Optional.ofNullable(enumMap.get(string));
}
除编译时常量域之外。枚举构造器不可以访问枚举的静态域
上一条需求,当然也可以通过遍历实现,但是如果使用让每个枚举实例都从构造器将自己放入到Map中来实现是不起作用的。
因为除编译时常量域之外。枚举构造器不可以访问枚举的静态域。
编译时常量域:用final关键字修饰的基本类型或String类型并直接赋值(非复杂运算)的变量(无论是否用static修饰)。编译时,所有编译时常量都将被替换成字面量,编译期常量不依赖类,不会引起类的初始化;而运行时常量依赖类,会引起类的初始化。
原因
构造器运行时,即枚举实例初始化时,而枚举实例必须最先声明且也是静态的,所以其他静态成员在枚举实例后才初始化。所以构造器不能访问除编译时常量域外的静态成员。
示例
enum EnumConstructorTest {
TEST1;
private static int i = 1;
EnumConstructorTest() {
//报错
//System.out.println(i);
}
}
策略枚举
多个枚举常量同时共享相同的行为,则可以考虑策略枚举。p141
用实例域代替序数(ordinal)
p142
虽然枚举类型与一个单独的int值关联(Enum类的private final int ordinal),即序数。
但是永远不要根据枚举的序数导出与它关联的值,而是将他保存在一个实例域中。即最好完全避免使用ordinal方法
示例
enum OrdinalTest{
TEST1(1),TEST2(2);
private final int number;
OrdinalTest(int number) {
this.number = number;
}
}
用EnumSet代替位域
p143
用EnumMap代替序数索引
最好不要使用序数来索引枚举集合数组,而是使用EnumMap
p145