Effective Java笔记
使用Build模式
- 不是直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器,得到一个builder对象
- 然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数
- 最后,客户端调用无参的build方法来生成不可变的对象
// 在这当中 FF是不可变的,所有的默认参数都单独放到一个地方
public class FF {
private final int a;
private final int b;
private final int c;
// Build 模式模拟了含有具体名字的可选参数
public static class Builder{
private final int a;
private final int b;
private int c;
public Builder(int a, int b) { //两个参数
this.a = a;
this.b = b;
System.out.println(a);
System.out.println(b);
}
public Builder c(int val){ //单独一个参数
c = val;
System.out.println(c);
return this;
}
public FF build(){
return new FF(this);
}
}
private FF(Builder builder){
a = builder.a;
b = builder.b;
c = builder.c;
}
public static void main(String []args){
FF f = new Builder(1,2).c(100).build();
}
}
- build模式十分灵活,可以利用耽搁builder构建多个对象
- builder的参数可以在创建对象期间进行调整,也可随着不同的对象而改变
优先使用基本类型而不是装箱基本类型
测试
public static void main(String []args){
long startTime = System.currentTimeMillis(); //获取开始时间
long sum = 0L;
for(long i = 0; i < Integer.MAX_VALUE; i++){
sum += i;
}
System.out.println(sum);
long endTime = System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间:" + (endTime - startTime) + "ms"); //输出程序运行时间
}
- 只因为打错一个字符,变量sum被声明成Long而不是long,运行时间9254ms
- 将
sum
改为long
类型,运行时间为964ms
重写equals必须重写hashcode
反例
public class PhoneNum {
private final short code;
private final short prefix;
public PhoneNum(int code, int prefix) {
this.code = (short) code;
this.prefix = (short) prefix;
rangeCheck(code, 999, "code");
rangeCheck(prefix, 9999, "prefix");
}
private static void rangeCheck(int arg,int max,String name){
if(arg < 0 || arg > max){
throw new IllegalArgumentException();
}
}
@Override
public boolean equals(Object o){
if (o == this){
return true;
}
if(!(o instanceof PhoneNum)){
return false;
}
PhoneNum pn = (PhoneNum)o;
return pn.prefix == prefix
&& pn.code == code;
}
public static void main(String []args){
Map<PhoneNum,String> map = new HashMap<>();
map.put(new PhoneNum(1,2),"jenny");
System.out.println(map.get(new PhoneNum(1,2)));
}
}
- 这时候输出是
null
,这里涉及两个PhoneNum的实例; - 第一个被用于插入HashMap中,第二个实例与第一个实例相等,被用于获取;
- 由于PhoneNum类没有覆盖hashcode方法,从而导致两个相等的实例具有不想等的散列码,违反了hashCode规约
- 因此,put方法把电话号码对象存放在一个散列桶中
- get方法却在另一个散列桶中查找这个电话号码,及时这两个实例正好被放到同一个散列桶中
- get方法也必定会返回
null
,因为HashMap
有一项优化,可将与每个项相关的散列码缓存起来 - 如果散列码不匹配,也不必检验对象的等同性
类层次由于标签类
反例:下面类能表示圆形或者矩形
// 图形类
public class Figure {
enum Shape{RECTANGLE, CIRCLE};
final Shape shape;
// 矩形
double length;
double width;
// 圆形
double radius;
Figure(double radius){ //圆
shape = Shape.CIRCLE;
this.radius = radius;
}
Figure(double length,double width){ // 矩形
shape = Shape.CIRCLE;
this.length = length;
this.width = width;
}
//计算面积
double area(){
switch (shape){
case CIRCLE:
return Math.PI * (radius * radius);
case RECTANGLE:
return length * width;
default:
throw new AssertionError();
}
}
}
- 这种标签类有着许多缺点;充斥着样板代码,包括枚举声明、标签域;
- 在Figure类中,只有一个这样的方法:
area
,这个抽象类是类层次的根
接下来进行分解上面的反例
// 抽象类
abstract class Figure {
abstract double area();
}
// 圆
class Circle extends Figure{
final double radius;
Circle(double radius) { this.radius = radius; }
double area() { return Math.PI * (radius * radius); }
}
// 矩形
class Rectangle extends Figure{
final double lenght;
final double width;
Rectangle(double lenght, double width) {
this.lenght = lenght;
this.width = width;
}
double area() { return lenght * width; }
}
由于时间有限,写的不好请见谅,理解万岁(: