Effective.Java第12-22条
12. 始终重写toString()方法
如果不重写toString()方法,打印的时候是 类名+@+哈希码的无符号十六进制。我们查看 Object的toString()方法如下:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
阿里规约也有一条:
POJO类必须重写toString方法。如果继承了另一个类,在前面加super.toString()。这么做的好处是在方法抛出异常时,可以直接调用toString()方法打印其属性,便于排查。
13. 谨慎地重写clone方法
所有的JavaBean都继承自Java.lang.Object,而Object类提供一个clone方法,可以将一个JavaBean对象复制一份。但是这个JavaBean必须实现一个表示接口Cloneable,表明这个JavaBean支持复制。如果一个对象没有实现这个接口而调用clone()方法,Java编译器会抛出CloneNotSupportedException异常。
阿里规约也有一条:
慎用Object的clone方法来拷贝对象。对象的clone方法默认是浅拷贝,若想实现深拷贝需要重写clone方法实现属性对象的拷贝。
14. 考虑实现Comparable接口
看到tree集合,可以按顺序进行排列,就要想到两个接口。Comparable(集合中元素实现这个接口,元素自身具备可比性),Comparator(比较器,传入容器构造方法中,容器具备可比性)。
对于Arrays.sort和Collections.sort有两种用法,第一个是只传递一个arrays或list参数,第二个是传递一个加 Comparator 比较器的参数。
比如我们想比较人的时候按年龄倒序排列:
public class Person implements Comparable<Person> { private int age; private String name; 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; } @Override public int compareTo(Person comparedUser) { // 返回负数表示this对象排在comparedUser前面 if (this.age > comparedUser.getAge()) { return -1; } // 返回正数表示this对象排在comparedUser后面 if (this.age < comparedUser.getAge()) { return 1; } return 0; } public Person(int age, String name) { super(); this.age = age; this.name = name; } @Override public String toString() { return "Person [age=" + age + ", name=" + name + "]"; } }
测试代码如下:
public static void main(String[] args) { List<Person> list = new ArrayList<>(); list.add(new Person(20, "张三")); list.add(new Person(22, "李四")); list.add(new Person(19, "王五")); list.add(new Person(19, "张是")); list.add(new Person(17, "开发")); list.add(new Person(29, "看")); Collections.sort(list); for (Person p : list) { System.out.println(p); } }
结果:
Person [age=29, name=看]
Person [age=22, name=李四]
Person [age=20, name=张三]
Person [age=19, name=王五]
Person [age=19, name=张是]
Person [age=17, name=开发]
当然了,我们可以编写一个比较器,也就是实现Comparator接口:(逆序比较器)
import java.util.Comparator; public class PersonComparator implements Comparator<Person> { /** * 第一个参数可以理解为基准,第二个是与之比较的元素 。返回负数表示排在其前面,返回正数表示排在其后面 */ @Override public int compare(Person o1, Person o2) { // 返回负数表示o1排在o2前面 if (o1.getAge() > o2.getAge()) { return -1; } // 返回正数表示o1排在o2后面 if (o1.getAge() < o2.getAge()) { return 1; } return 0; } }
如果想变成正序比较器使用如下方法:
Comparator<Person> reverseOrder = Collections.reverseOrder(new PersonComparator());
测试代码:(使用比较器进行排序)
public static void main(String[] args) { List<Person> list = new ArrayList<>(); list.add(new Person(20, "张三")); list.add(new Person(22, "李四")); list.add(new Person(19, "王五")); list.add(new Person(19, "张是")); list.add(new Person(17, "开发")); list.add(new Person(29, "看")); Collections.sort(list, new PersonComparator()); for (Person p : list) { System.out.println(p); } }
结果:
Person [age=29, name=看]
Person [age=22, name=李四]
Person [age=20, name=张三]
Person [age=19, name=王五]
Person [age=19, name=张是]
Person [age=17, name=开发]
15. 使类和成员的可访问性最小化
让类或成员尽可能地不被访问。
1、private修饰词,表示成员是私有的,只有自身可以访问;
2、protected,表示受保护权限,体现在继承,即子类可以访问父类受保护成员,同时相同包内的其他类也可以访问protected成员。
3、无修饰词(默认),表示包访问权限(friendly, java语言中是没有friendly这个修饰符的,这样称呼应该是来源于c++ ),同一个包内可以访问,访问权限是包级访问权限;
4、public修饰词,表示成员是公开的,所有其他类都可以访问;
阿里规约也有详细的描述: 类成员与方法访问控制从严
1) 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。
2) 工具类不允许有 public 或 default 构造方法。
3) 类非 static 成员变量并且与子类共享,必须是 protected。
4) 类非 static 成员变量并且仅在本类使用,必须是 private。
5) 类 static 成员变量如果仅在本类使用,必须是 private。
6) 若是 static 成员变量,必须考虑是否为 final。
7) 类成员方法只供类内部调用,必须是 private。
8) 类成员方法只对继承类公开,那么限制为 protected。
说明:任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。 思考:如果是一个 private 的方法,想删除就删除,可是一个 public 的 service 方法,或者 一个 public 的成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的 视线内,变量作用域太大,无限制的到处跑,那么你会担心的。
16. 在公共类中使用访问方法而不是公共属性
如下x属性和y属性切勿写成public直接访问。
public class Point { private double x; private double y; public double getX() { return x; } public void setX(double x) { this.x = x; } public double getY() { return y; } public void setY(double y) { this.y = y; } }
17. 最小可变性
不可变类简单来说就是它的实例不能被修改的类。Java中有String、基本数据类型的包装类型以及BigInteger和BigDecimal。
要使一个类不可变,需要遵循下面五条规则:
1.不要提供修改对象状态的方法
2.确保这个类不能被继承
3.把所有属性设置为final
4.把所有属性设置为private
5.确保对任何可变组件的互斥访问
不可变对象本质上是线程安全的,它们不需要同步。
不可变对象提供了免费的原子失败机制。它们的状态永远不会改变,所以不可能出现临时的不一致。
不可变类的主要缺点是对于每个不同的值都需要一个单独的对象。
一条最基本的原则是:不共享可变的数据,要么压根不共享。换句话说,将可变数据限制在单个线程中。
18. 组合优于继承
继承是实现代码重用的有效方式,但并不是最好的工具。
在包中使用继承是安全的,其中子类和父类都在同一个程序员的控制之下。对应专门为了继承而设计的,并且有文档说明的类来说,使用继承也是安全的。然后,从普通的具体类跨越包边界继承是危险的。
与方法调用不同,继承打破了封装。换句话说,一个子类依赖于其父类的实现细节来保证其正确的功能。父类的实现可能会从发布版本不断变化,如果是这样,子类可能被破坏。因此,子类必须与其父类一起更新和变化。
解决办法:不要继承一个现有的类,而是给新类增加一个私有属性,该属性是现有类的实例引用,这种设计被称为组合,因为现有的类成为新类的组成部分(有点类似于对象型适配器模式)。新类中的每个实例方法调用现有类的包含实例上的相应方法并返回结果,这种被称为转发,而新类中的方法被称为转发方法。有时组合和转发的结合被不精确地称为委托。
如下:
目标接口:
package cn.qlq.adapter; public interface Target { void option(); void option2(); }
现有的类:
package cn.qlq.adapter; public class Adaptee { public void option2() { System.out.println("option2"); } }
新类:(组合模式将option2()方法委托给Adaptee)
package cn.qlq.adapter; public class Adapter implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { super(); this.adaptee = adaptee; } @Override public void option() { System.out.println("option"); } @Override public void option2() { adaptee.option2(); } }
19. 要么设计继承并提供文档说明,要么禁用继承
设计继承的时候必须文档说明所有的自用模式,必须承诺他们为整个生命周期。如果你不这样做,子类可能会依赖于父类的实现细节,并且如果父类的实现发生改变,子类可能会损坏。为了允许其他人编写高效的子类,肯能还需要导出一个或多个受保护的方法。除非你知道有一个真正的子类需要,否则最好声明你的类为final静止继承,或者确保没有可访问的构造方法(也就是声明一个private的构造方法)。
补充:
我们知道当我们不写构造方法的时候编译器会自动加一个无参的public修饰的构造方法,但是当我们用private声明一个构造方法之后编译器不会再添加构造方法。子类的对象也是父类的对象,所以如果父类没有无参的构造方法,在子类的构造方法必须显示的调用父类的带参数的构造方法。
20. 接口优于抽象类
Java8中引入了接口的默认方法(default)方法,抽象类本身可以具有具体的方法。因为Java只允许单一继承,所以对抽象类的这种限制严格限定了它们作为类型定义的使用。
21. 为后代设计接口
22. 接口仅用来定义类型
尽量不在接口中使用变量,接口只能用于定义类型。更不能将接口用于工具类。