0%

1.创建和销毁对象

这个章节包含创建和销毁对象,什么时候和怎样创建,什么时候避免创建,如何确保对象在准确的时机销毁,如何管理与清理销毁的对象

Item1 考虑用静态工厂方法替代构造方法

通常来说获取一个类的实例是通过它的构造方法,但这有一种更科学的方式:

1
2
3
public static Boolean valueOf(boolean b){
return b ? Boolean.TRUE : Boolean.FALSE;
}

注意,静态工厂方法并不等价工厂设计模式

当然使用这种方式有利有弊
利:
1.静态工厂方法有名字,可以一目了然 eg:BigPerson.name

2.在被调用的时候只创建一个对象
3.封装了实现细节,可返回任意需要的对象
4.减少对象的创建参数
eg:java Map<String,List<String>> m = new HashMap<String,List<String>>(); Map<String,List<String>> m = MyMap.newInstance();

弊:
1.无法从它派生出子类
继承的特性使用不上
2.无法和其它静态方法明显做出区分
推荐常用的几个

1
2
3
4
newInstance()
newType()
getInstance()
valueOf()

Item2 考虑用建造者模式创建多个参数的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;

public static class Builder {
private final int servingSize;
private final int servings;
private final int calories = 0;
private final int fat= 0;
private final int sodium= 0;
private final int carbohydrate= 0;

public Builder(int ser,int servings){
this.servingSize = ser;
this.servings = servings;
}
public Builder calories(int val){calories = val;return this;}
public Builder fat(int val){fat= val;return this;}
public Builder sodium(int val){sodium= val;return this;}
public Builder carbohydrate(int val){carbohydrate= val;return this;}
}
public NutritionFacts build(){return new NutritionFacts(this);}

private NutritionFacts (Builder builder){
servingSize = builder.servingSize ;
servings = builder.servings ;
calories = builder.calories ;
fat= builder.fat;
sodium= builder.sodium;
carbohydrate= builder.carbohydrate;
}
}

用法

1
2
NutritionFacts obj = new NutritionFacts.Builder(240,8).calories(100)
.sodium(33).carbohydrate(22).build();

比如典型的Dialog就是用的建造者模式


Item3 (强制)单例属性使用私有的构造方法或者使用枚举
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis () {...}
public static Elvis getInstance() {return INSTANCE ;}

//枚举
private Object readRssolve() {
return INSTANCE ;
}
public enum Elvis {
INSTANCE ;
}
}
Item4 (强制)对于只有静态方法的工具类不允许使用私有构造方法
1
2
3
4
5
6
7
8
public calss UtilityClass{
private UtilityClass(){
//error
}
public static void doSomething(){
//...
}
}
Item5 避免创建不必要的对象

重复利用一个对象好过重新创建一个新对象
重复利用访问更快而且更时尚,理论上说一个对象不可变可一直重复利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//bad DON'T DO THIS!
public class Person{
private final Date birthDate;
public boolean isBabyBoomer(){
//分配了不必要的强度对象
Calendar gmtCal =
Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal .set(1946,Calendar.JANUARY,1,0,0,0);
Date boomStart = gmtCal .getTime();
gmtCal .set(1965,Calendar.JANUARY,1,0,0,0);
Date boomEnd = gmtCal .getTime();
return birthDate.compareTo(boomStart) >= 0
&& birthDate.compareTo(boomEnd) < 0;
}

}

isBabyBoomer每次调用都会创建Calendar,TimeZone,和两个Date实例,优化后如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//good
class Person{
private final Date birthDate;
private static final Date BOOM_START;
private static final Date BOOM_END:

static {
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946,Calendar.JANUARY,1,0,0,0);
BOOM_START = gmtCal.getTime();
gmtCal.set(1946,Calendar.JANUARY,1,0,0,0);
BOOM_END = gmtCal.getTime();
}

public boolean isBabyBoomer(){
return birthDate.compareTo(BOOM_START) >= 0
&& birthDate.compareTo(BOOM_END) < 0;
}
}

重复调用1000万次isBabyBoomer,使用bad-耗时32,000ms 使用good-耗时130ms,Wow~


Item6 清除过时的对象引用

Java有GC机制,并不意味着Java程序员不需要考虑内存了
你能找到如下代码内存泄漏之处吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_SIZE = 16;

public Stack(){
elements = new Object[DEFAULT_SIZE];
}

public void push(Object e){
ensureCapacity();
elements[size++] = e;
}

public Object pop(){
if(size == 0)
throw new EmptyStackException();
return elements[--size];
}

private void ensureCapacity(){
if(elements.length == size)
elements = Arrays.copyOf(elements,2 * size + 1);
}
}

乍一看没什么问题吧?你也可以用你想到方式测试它,它都能运行良好 但这有一个内存泄漏的问题哦
嘟嘟嘟~
so,问题出在哪里呢?
嘟嘟嘟~


问题在于当我们pop了一些对象,GC并不知道这些对象已经过时,它只知道elements[]还处于活跃状态,所以一直不会清理那部分过时的对象
优化如下

1
2
3
4
5
6
7
public Object pop(){
if(size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;//清理过时的对象
return result;
}

类似的情况还有在集合中保存了对象,常常会忘记这些对象的存在

对于监听的listener和callback也要及时注册和反注册,不然也会导致内存异常

Item7 避免使用finalizers

下一章:Object的方法

本章主要讨论语言的具体内容。它讨论了局部变量的处理、控制结构、库的使用、各种数据类型的使用,以及使用反射和本地方法。最后,讨论了优化和命名约定

Item 45:最小化局部变量作用域

作用域:一个花括号{}包裹起来的区域

此条例同Item13相似:最小化类和成员变量的访问权限
Java允许你在任何地方声明变量,但是最重要的是在首次使用的地方声明变量,并初始化
循环提供了一种实现此种方式的机制,而且for循环比while循环好,如

1
2
3
4
5
6
7
8
for (Element e : c) {
doSomething(e);
}

//before JDK1.5
for (Iterator i = c.iterator(); i.hasNext(); ) {
doSomething((Element) i.next());
}

为什么for比while好呢?

1
2
3
4
5
6
7
8
9
10
//bad
Iterator<Element> i = c.iterator();
while (i.hasNext()) {
doSomething(i.next());
}
...
Iterator<Element> i2 = c2.iterator();
while (i.hasNext()) { // BUG! 应该是i2
doSomethingElse(i2.next());
}

当我们写一个差不多的代码,从一个地方copy过来的时候,很有可能忘记修改某个变量值(如i2),它不会在编译期报错,我们很可能长时间遗留这个bug
使用for循环可以避免这个bug

1
2
3
4
5
6
7
8
for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
doSomething(i.next());
}
...
// Compile-time error - cannot find symbol i
for (Iterator<Element> i2 = c2.iterator(); i.hasNext(); ) {
doSomething(i2.next());
}

这就是最小化作用域的好处
#####Item 46: Prefer for-each loops to traditional for loops
对于不需要下标来做特殊操作的遍历,推荐使用增强for循环
你能发现下面的bug吗?

1
2
3
4
5
6
7
8
9
10
enum Suit { CLUB, DIAMOND, HEART, SPADE }
enum Rank { ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT,
NINE, TEN, JACK, QUEEN, KING }
...
Collection<Suit> suits = Arrays.asList(Suit.values());
Collection<Rank> ranks = Arrays.asList(Rank.values());
List<Card> deck = new ArrayList<Card>();
for (Iterator<Suit> i = suits.iterator(); i.hasNext(); )
for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); )
deck.add(new Card(i.next(), j.next()));







发现不了也不要难过,很多有经验的程序员也会犯这个错误
原因在于i.next()会被重复调用,导致结果异常
可以修复如下

1
2
3
4
5
6

for (Iterator<Suit> i = suits.iterator(); i.hasNext(); ){
Suit suit = i.next();
for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); )
deck.add(new Card(suit, j.next()));
}

虽然解决了问题,但是很丑,更简洁的写法如下

1
2
3
for (Suit suit : suits)
for (Rank rank : ranks)
deck.add(new Card(suit, rank));

尽可能的使用增强for循环
#####Item 47: Know and use the libraries
不要重复造轮子
如果需要一个常用的功能,去发现一个库并使用它,往往比自己写的要好
因为库会随着时间推移更新迭代越来越好,自己也节约了时间
这并不会评论一个人的能力
#####Item 48: Avoid float and double if exact answers are required
当需要一个精确的答案时,避免使用float或double类型的变量

float或double是为了科学和工程计算而设计的,特别不适用于货币计算

推荐使用int或long代替
#####Item 49: Prefer primitive types to boxed primitives

使用原始类型替代装箱类型

byte short char int long float double boolbean等是Java中的基本类型,也有对应的装箱类型,如 Integer, Double, and Boolean.应当谨慎对待这两者之间的差别

首先第一个差别,原始类型仅仅包含对应的值,装箱类型既包含对应的值也有对应的引用,第二个差别是装箱类型有可能为null,第三个差别是原始类型在时间和空间消耗中更高效
#####Item 50: Avoid strings where other types are more appropriate

如果有更合适的类型,避免使用String

string被设计成描述文本类型的数据,而且干得很好,本章主要讨论将string用于其它情况的错误用法

string不能替代值类型 如果我们正在等待键盘的输入,或者从网络获取某个值,我们很方便的使用string作为接收类型,但是如果我们输入的是数字或者真假值的话,对应的int或boolean能更好的标识输入 虽然这条规则很明显,但是经常被违反

string不能替代枚举Item30:枚举讨论的那样,对于静态常量使用枚举

string不能替代聚合字符

1
String compoundKey = className + "#" + i.next();//bad

我们经常写上述代码,这样的写法有很多缺点.如果我们想使用某一部分字段需要解析字符串,很耗时而且容易出错.String提供的equals,compareTo等方法也不能使用

比较好的方式是写一个静态内部类来表示聚合字符

1
2
3
4
5
static class CompoundKey{
private String className;
private String next;
...
}

#####Item 51: Beware the performance of string concatenation

考虑字符连接(+)的性能

使用(+)能很方便的拼接若干个字符串,但是我们也要考虑到开销

如下两个代码都是拼接字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//方式一
public String statement() {
String result = "";
for (int i = 0; i < numItems(); i++)
result += lineForItem(i); // String concatenation
return result;
}

//方式二
public String statement() {
StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH);
for (int i = 0; i < numItems(); i++)
b.append(lineForItem(i));
return b.toString();
}

当numItems()==100;lineForItem(i)返回80个长度的字符时,在作者的机器上方式二比方式一快85倍

如果我们需要拼接大量字符时,使用StringBuilder代替
#####Item 52: Refer to objects by their interfaces

使用接口代替对象引用

如果有一个合适的接口来描述当前类的时候使用这个接口来引用,如

1
2
3
4
5
// Good - uses interface as type
List<Subscriber> subscribers = new Vector<Subscriber>();

// Bad - uses class as type!
Vector<Subscriber> subscribers = new Vector<Subscriber>();

如果你养成了这个习惯,那么你的代码将会更灵活
有几种情况不能使用接口
1.没有合适的接口声明
2.接口中没有想要的某个方法
3.使用的类集成自framework,并且是一个抽象类
#####Item 53: Prefer interfaces to reflection
使用接口代替反射

反射核心类java.lang.reflect提供了对加载类信息的访问

例如,Method.Invoke允许在任何类的任何对象上调用任何方法(受通常的安全约束)。 即使编译后的类不存在,也允许一个类使用另一个类。然而,这种力量是有代价的。

  • 你不能在编译时检查出异常 如果你使用了对应的反射操作,在发生异常时,只有在运行时才能发现

  • 阅读性极差

  • 性能有影响 使用反射比普通调用要慢些,因为受很多因素的影响,慢多少很难说,在作者的机器上,速度差在两倍到五十倍不止

反射核心用在基于组件设计的应用,为了按需加载类,使用反射找到对应的类构造与否;普通应用尽量不要使用反射,找到代替的接口或者父类对象
#####Item 54: Use native methods judiciously
明智地使用本地方法

JNI允许Java调用C或C++写的本地方法

从历史上看,本地方法有三种主要用途。

1.它们提供了对特定于平台的设施的访问,例如注册表和文件锁。

2.他们提供了对旧代码库(Java想使用历史上C或C++写的库)

的访问,可以反过来提供对旧数据的访问。

3.使用本地方法用本地语言编写应用程序关键部分,以提高性能。

Java平台在不断发展,访问特定平台设施,使用Java提供的工具类就可做到,而且也不建议使用本地方法来提升程序性能

使用本地方法有严重的缺点

1.本地语言是不安全的,会受机器内存错误的影响

2.依赖于平台,不便于移植

3.本地代码很难调式

4.访问本地代码开销很大

5.本地代码不宜阅读

总之,要再三思考是否使用本地代码,如果需要使用以前的代码库,请尽可能减少本地代码片段并加强测试,很小很小的本地代码错误将破坏你的整个程序
#####Item 55: Optimize judiciously
明智的优化

有三个人人都应该知道的优化格言

More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reason—including blind stupidity.
—William A. Wulf [Wulf72]
更多的计算罪恶是以效率的名义犯下的(不一定要达到这一目的),而不是因为任何其他单一的原因-包括盲目的愚蠢

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
—Donald E. Knuth [Knuth74]
我们应该忽略小效率,大约百分之97的情况下,邪恶之源就是过早优化

We follow two rules in the matter of optimization:
Rule 1. Don’t do it.
Rule 2 (for experts only). Don’t do it yet—that is, not until you have a
perfectly clear and unoptimized solution.
—M. A. Jackson [Jackson75]
关于优化遵循如下两个原则:
原则1.别这样做
原则2.(仅对专家而言).还不要做,直到你找到一个清晰的解决方案或者一个未优化的解决办法

不要为了性能而损坏架构,致力于写出好的程序而不是快的程序,如果一个好的程序还不够快,它的架构会允许优化.

这并不意味着当你的程序完后不需要优化,你应该在设计阶段就考虑到性能

尽量避免影响性能的设计,已经实现好的组件很难在改变,尤其是API,数据结构,多方约定好的协议

考虑好API使用效果,设计一个可变的类可能会导致后续使用中出现过多的深拷贝,造成对象分配的额外开销

API的设计对性能有很真实的影响,如java.awt.Component中的getSize()方法,每调用一次就会返回一个新的 Dimension 实例(JDK1.2版本已经修复),虽然分配一个实例的开销很小,但是成百上千次调用也会对程序有严重影响

幸运的是,好的API设计自然带来了好的性能

总之,致力于写出好的程序,快随之而来 当做出一部分改变后就要测量代码的性能,对于Android来说内存,卡顿,anr等方面

参考DDMS性能调优



#####Item 56: Adhere to generally accepted naming conventions
遵循公共的命名规范,参考阿里巴巴发布的Android手册


上一章:方法
下一章:异常

3. 类和接口

类和接口是Java编程的核心
#####Item13 最小化类和成员变量的访问权限
信息隐藏与封装是程序设计的基本原则
通用经验是能让类或者变量不可访问就让它不可访问
四种访问权限:

  • private
    只在它声明的地方可用
  • package-private
    同一个包内可用
  • protected
    同一个包或子类可用
  • public
    任何地方可用

    公共类不应包含公共字段。确保static final引用的对象是不可变的。
    绝不可以用private static final来声明一个数组对象

Item14 用公共方法提供公共字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//bad
public class Point {
public int x;
public int y;
}

//good
class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() { return x; }
public double getY() { return y; }
public void setX(double x) { this.x = x; }
public void setY(double y) { this.y = y; }
}
Item15 最小化可变性

对象始终不变的类比可变的更安全和好用,如String
我们设计不可变对象时应遵循如下准则:

  • 不要提供任何方法来改变对象的状态
  • 确保类不可继承
  • 所有的字段使用private final修饰
  • 确保不要让类中的可变对象访问,使用深拷贝替代
    如果一个类确实可变,也要保证其它地方尽可能不变,TimerTask是很好的例子
    #####Item16 组合和继承,优先使用组合

    继承虽然有很多好处,但是违背了封装性,暴露了父类的实现细节
    #####Item17 专用于继承的设计,否则禁止继承
    未能理解,后续补充
    #####Item18 接口和抽象类之间倾向于接口
    Java提供了两种机制来提供多个类型定义的实现:接口和抽象类

最明显的区别在于抽象类允许某些方法的实现,更重要的区别在于一个类使用了抽象类的方式来定义,则这个类必须是抽象类的子类.因为Java只允许单一继承,因此对抽象类的这种限制严重影响了它们作为类型定义的使用

  • 已经存在的类可以很容易的实现一个新的接口
    比如实现Comparable接口
  • 接口是定义混合器的理想选择
    比如为一个主要类型的类添加比较方法,可通过实现Comparable接口实现实例之间的排序

有一个特例是抽象类比接口更加易用,如果注重程序的易用性而不是灵活性则可以考虑抽象类
#####Item19 仅使用接口定义类型
当一个类实现了一个接口,这个接口是为类的实例服务的而不是为了其他目的

所以有一种常量接口尽量不要使用

1
2
3
4
5
6
7
8
9
//bad
public interface PhysicalConstants {
// Avogadro's number (1/mol)
static final double AVOGADROS_NUMBER = 6.02214199e23;
// Boltzmann constant (J/K)
static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
// Mass of the electron (kg)
static final double ELECTRON_MASS = 9.10938188e-31;
}

如果有这种常量需要使用,可以把它定义在与之关联的类里面

Item20 用继承来替代复合类(原单词: tagged classes)

偶尔会遇到一个类的实例包含了多种不同的类别,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// bad Tagged class - vastly inferior to a class hierarchy!
class Figure {
enum Shape { RECTANGLE, CIRCLE };
// Tag field - the shape of this figure
final Shape shape;
// These fields are used only if shape is RECTANGLE
double length;
double width;
// This field is used only if shape is CIRCLE
double radius;
// Constructor for circle
Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
// Constructor for rectangle
Figure(double length, double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
double area() {
switch(shape) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError();
}
}
}

优化如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//Class hierarchy replacement for a tagged class
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 length;
final double width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double area() { return length * width; }
}
Item21 使用函数对象表示策略

直译过来有点不好理解,大意是对于多个类都要使用的策略(或用方法表示),采用接口来实现

如一个类需要比较方法,另一个类也需要,则抽象出一个Comparable接口来给各个类通用

Item22 嵌套类的使用

有四种嵌套类,分别是静态成员类、非静态成员类、匿名内部类和本地类
除了第一个都是内部类

  • 静态成员类
    可以访问外部类的私有属性
  • 非静态成员类
    同静态成员类,区别在于没有static修饰
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class MySet<E> extends AbstractSet<E> {
    public Iterator<E> iterator() {
    return new MyIterator();
    }
    //典型用法
    private class MyIterator implements Iterator<E> {
    ...
    }
    }
  • 匿名内部类
    不用声明,常用于监听器,随用随销
  • 本地类
    略 未能理解,后续补充,欢迎留言增益知识

    如果成员类需要外部类的引用,则使用非静态成员类,否则使用静态成员类


上一章:Object的方法
下一章:泛型

Java1.5中提供的两种新类型

Item30: 用枚举替代int型常量

枚举:一系列常量类型的集合
没有枚举前大量定义的常量如下

1
2
3
4
5
6
7
8
// The int enum pattern - severely deficient!
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;
public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;

首先调试不方便,我们只会打印出数字,然后回归代码
其次没有命名空间做区别,命名累赘
使用枚举后

1
2
3
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH }
public enum Orange { NAVEL, TEMPLE, BLOOD }

再来看一个星球的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public enum Planet {
MERCURY(3.302e+23, 2.439e6),
VENUS (4.869e+24, 6.052e6),
EARTH (5.975e+24, 6.378e6),
MARS (6.419e+23, 3.393e6),
JUPITER(1.899e+27, 7.149e7),
SATURN (5.685e+26, 6.027e7),
URANUS (8.683e+25, 2.556e7),
NEPTUNE(1.024e+26, 2.477e7);
private final double mass; // In kilograms
private final double radius; // In meters
private final double surfaceGravity; // In m / s^2
// Universal gravitational constant in m^3 / kg s^2
private static final double G = 6.67300E-11;
// Constructor
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
surfaceGravity = G * mass / (radius * radius);
}
public double mass() { return mass; }
public double radius() { return radius; }
public double surfaceGravity() { return surfaceGravity; }
public double surfaceWeight(double mass) {
return mass * surfaceGravity; // F = ma
}
}

再来看一个算数的例子

1
2
3
4
5
6
7
public enum Operation {
PLUS { double apply(double x, double y){return x + y;} },
MINUS { double apply(double x, double y){return x - y;} },
TIMES { double apply(double x, double y){return x * y;} },
DIVIDE { double apply(double x, double y){return x / y;} };
abstract double apply(double x, double y);
}

总结:枚举比int型常量更安全和具有可读性

Item31: Use instance fields instead of ordinals(使用实例字段代替序号)

所有的枚举都有
ordinal 方法(从0开始计数),如果我们想用序号的int值(从1开始计数),不要直接修改ordinal 方法

1
2
3
4
5
6
//bad
public enum Ensemble {
SOLO, DUET, TRIO, QUARTET, QUINTET,
SEXTET, SEPTET, OCTET, NONET, DECTET;
public int numberOfMusicians() { return ordinal() + 1; }
}

虽然上述代码能正常使用,但是却会导致一场噩梦
如果我们改变了枚举中的常量顺序,之前的序号就会一团糟
优化代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public enum Ensemble {
SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),
SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8),
NONET(9), DECTET(10), TRIPLE_QUARTET(12);

private final int numberOfMusicians;

Ensemble(int size) {
this.numberOfMusicians = size;
}

public int numberOfMusicians() {
return numberOfMusicians;
}
}
Item32: Use EnumSet instead of bit fields(用EnumSet替代位字段)

如我们有一段代码

1
2
3
4
5
6
7
8
9
10
11
// Bit field enumeration constants - OBSOLETE!
public class Text {
public static final int STYLE_BOLD = 1 << 0; // 1
public static final int STYLE_ITALIC = 1 << 1; // 2
public static final int STYLE_UNDERLINE = 1 << 2; // 4
public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8
// Parameter is bitwise OR of zero or more STYLE_ constants
public void applyStyles(int styles) { ... }
}
//使用
text.applyStyles(STYLE_BOLD | STYLE_ITALIC);

用EnumSet优化如下:

1
2
3
4
5
6
7
8
9
// EnumSet - a modern replacement for bit fields
public class Text {
public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }
// Any Set could be passed in, but EnumSet is clearly best
public void applyStyles(Set<Style> styles) { ... }
}

//使用
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
Item33: Use EnumMap instead of ordinal indexing(使用EnumMap代替顺序索引)

EnumSet/EnumMap详解

Item34: 使用接口提升枚举的扩展性

code say everything
普通操作 加减乘除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Emulated extensible enum using an interface
public interface Operation {
double apply(double x, double y);
}
public enum BasicOperation implements Operation {
PLUS("+") {
public double apply(double x, double y) { return x + y; }
},
MINUS("-") {
public double apply(double x, double y) { return x - y; }
},
TIMES("*") {
public double apply(double x, double y) { return x * y; }
},
DIVIDE("/") {
public double apply(double x, double y) { return x / y; }
};

private final String symbol;
BasicOperation(String symbol) {
this.symbol = symbol;
}
@Override public String toString() {
return symbol;
}
}

现需要扩展取余,异或等方法,不要再BasicOperation 中新加枚举,而是实现公共接口Operation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Emulated extension enum
public enum ExtendedOperation implements Operation {
EXP("^") {
public double apply(double x, double y) {
return Math.pow(x, y);
}
},
REMAINDER("%") {
public double apply(double x, double y) {
return x % y;
}
};

private final String symbol;
ExtendedOperation(String symbol) {
this.symbol = symbol;
}
@Override public String toString() {
return symbol;
}
}
Item36: 始终使用override注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 你能找到这个bug吗?
public class Bigram {
private final char first;
private final char second;
public Bigram(char first, char second) {
this.first = first;
this.second = second;
}
public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
public int hashCode() {
return 31 * first + second;
}

public static void main(String[] args) {
Set<Bigram> s = new HashSet<Bigram>();
for (int i = 0; i < 10; i++)
for (char ch = 'a'; ch <= 'z'; ch++)
s.add(new Bigram(ch, ch));
System.out.println(s.size());
}
}

理论上我们希望打印26,因为HashSet没有重复元素,但是得到的值是260
因为没有override equals&hashCode方法,所以默认是使用的”==”比较的地址,得到的都是不同的对象
修复如下

1
2
3
4
5
6
   @Override public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
@Override public int hashCode() {
return 31 * first + second;
}
Item37: 使用标记接口定义类型(待补充)
  • 标记接口:只为了标记某一种类型
    如 Serializable 接口,表明可将实例序列化
    优点:

1.标记接口定义了一种类型
2.可用于任何扩展的子类

  • 标记注解:
    优点:

1.描述信息更丰富,可在使用后添加方法
2.位于FrameWork层,各个应用使用同一套规则


上一章:泛型
下一章:方法

这一章介绍方法设计的几个方面:如何对待参数和返回值,如何设计方法签名,如何注释方法

Item38: 检查参数的合法性

大部分使用的方法参数都有一定的限制,如不为null,size>0等
通用的原则就是预防大于整改,提前发现错误可以更快的规避问题,而不是在程序运行中发生

对于公共方法,使用Javadoc@块标记,来记录在违反参数值限制时抛出的异常(Item62)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Returns a BigInteger whose value is (this mod m). This method
* differs from the remainder method in that it always returns a
* non-negative BigInteger.
*
* @param m the modulus, which must be positive
* @return this mod m
* @throws ArithmeticException if m is less than or equal to 0
*/
public BigInteger mod(BigInteger m) {
if (m.signum() <= 0)
throw new ArithmeticException("Modulus <= 0: " + m);
... // Do the computation
}

对于私有的方法则使用断言

1
2
3
4
5
6
7
// Private helper function for a recursive sort
private static void sort(long a[], int offset, int length) {
assert a != null;
assert offset >= 0 && offset <= a.length;
assert length >= 0 && length <= a.length - offset;
... // Do the computation
}

assert在实际项目中使用的很少,更多的还是使用的if判断

每次写一个方法或者构造函数的时候,在方法的开头考虑参数的合法性是必不可少的
#####Item39: 必要的时候使用拷贝对象
Java是一门安全的语言,即使在Java语言中,也要假设用户正想方设法的破坏你的程序.除了少部分人想破坏系统的安全性,大部分问题都是编程人员可以控制的
虽然没有对象的帮助,另一个类不太可能改变对象的内部状态,但偶尔也有疏忽的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//一个不可变的周期类
// Broken "immutable" time period class
public final class Period {
private final Date start;
private final Date end;
/**
* @param start the beginning of the period
* @param end the end of the period; must not precede start
* @throws IllegalArgumentException if start is after end
* @throws NullPointerException if start or end is null
*/
public Period(Date start, Date end) {
if (start.compareTo(end) > 0)
throw new IllegalArgumentException(
start + " after " + end);
this.start = start;
this.end = end;
}
public Date start() {
return start;
}

public Date end() {
return end;
}
... // Remainder omitted
}

其中Date对象是可变的

1
2
3
4
5
// bad
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // 修改了p的内部状态

为了保护对象的状态,我们需要在构造函数中对可变参数执行拷贝防御

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Repaired constructor - makes defensive copies of parameters
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (this.start.compareTo(this.end) > 0)
throw new IllegalArgumentException(start +" after "+ end);
}

// Repaired accessors - make defensive copies of internal fields
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}

这里没有使用clone方法,因为Date有其它不可信任的子类

经验上讲,在内部尽量不要使用可变的类,如Date,可用long型替代Date.getTime()

总结:如果一个类调用了或返回可变的对象,则需要用拷贝对象防御,如果很信任调用者不会修改类的内部状态,则需要有一份警告文档提示调用者不能修改类的状态
#####Item 40: 如何设计方法

  • 选择一个合适的方法名称
    你的主要目标是设计一个利于理解的方法名,次要目标是方法名称之间保持协调性,如向数据库中插入一条数据,有的使用addXX,有的使用setXX,有的使用insertXX,尽量保持统一
  • 不要过分的使用方法
    太多的方法容易使一个类难于维护和测试,只要当它需要经常调用的时候才考虑提出一个方法,否则就不管它
  • 避免参数长的方法
    尽量保持在四个参数或以下
    有三种方式避免长参数

1.提出更多的方法
2.使用辅助类保存这些参数
3.使用建造者模式(Builder)

  • 对于传入的参数,有接口可以传就使用接口
    如需要传入HashMap 则在方法中将参数类型改为Map 避免使用者只能使用HashMap,也可以传入其它Map接口的子类型
  • 对于布尔型参数,使用枚举更合适
    例如,您可能有一个带有静态工厂的温度计类型( Thermometer 类),其值为枚举:
    1
    public enum TemperatureScale { FAHRENHEIT, CELSIUS }
    Thermometer.newInstance(TemperatureScale.CELSIUS)不仅比Thermometer.newInstance(true)更有意义,而且可以在将来的发行版中将Kelvin添加到TemperatureScale,而不需要 在Thermometer 类中增加一个新的静态工厂方法
    Item41: 谨慎地使用重载
    看如下的例子,我们想区分放进去的是List或者set或者不知道什么类型的集合,想一下它会如何打印
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CollectionClassifier {
public static String classify(Set<?> s) {
return "Set";
}
public static String classify(List<?> lst) {
return "List";
}
public static String classify(Collection<?> c) {
return "Unknown Collection";
}
public static void main(String[] args) {
Collection<?>[] collections = {
new HashSet<String>(),
new ArrayList<BigInteger>(),
new HashMap<String, String>().values()
};
for (Collection<?> c : collections)
System.out.println(classify(c));
}
}

它会顺序打印”Set”,”List”,”Unknown Collection”吗?不,它会打印三次”Unknown Collection” 原因在于重载方法是在编译时执行,所以会以Collection<?>为准

我们应该避免使用相同参数数量的重载方法,使机器不懂,自己更易混淆
#####Item 42: 谨慎的使用可变参数
举一个例子

1
2
3
4
5
6
static int sum(int... args) {
int sum = 0;
for (int arg : args)
sum += arg;
return sum;
}

sum(1,2,3) –> 6
sum() –> 0

暂略
#####Item 43: Return empty arrays or collections, not nulls
像如下的代码很常见

1
2
3
4
5
6
7
8
9
10
11
//bad
private final List<Cheese> cheesesInStock = ...;
/**
* @return an array containing all of the cheeses in the shop,
* or null if no cheeses are available for purchase.
*/
public Cheese[] getCheeses() {
if (cheesesInStock.size() == 0)
return null;
...
}

调用者很可能粗心大意忘记判null导致异常,也许很多年之后才会发现
有人说返回null避免了内存开销,首先你要证明是这段代码导致的性能问题,其次我们可以使用不可变的静态常量声明一个空集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// The right way to return an array from a collection
private final List<Cheese> cheesesInStock = ...;
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
/**
* @return an array containing all of the cheeses in the shop.
*/
public Cheese[] getCheeses() {
if(cheesesInStock.size() <= 0)
return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}

//or

public List<Cheese> getCheeseList() {
if (cheesesInStock.isEmpty())
return Collections.emptyList(); // Always returns same list
else
return new ArrayList<Cheese>(cheesesInStock);
}

总之,使用集合或数组的任何情况下都不能返回null

Item 44: Write doc comments for all exposed API elements

为所有暴露出去的API写文档注释


上一章:枚举和注解
下一章:通用原则

4. 泛型

在JDK1.5中加入了泛型,类型不正确将在编译期间知道,而不是在运行时导致异常错误

Item23 不要使用原始类型

例如当使用到集合的时候

1
2
3
4
5
//bad
private final Collection stamps = ... ;

//good
private final Collection<String> stamps = ... ;

泛型保证数据的安全性

Item24 清除未经检查的异常

在Eclipse或者Android studio中,敲写的代码或多或少有一些红色或黄色的警告,尽可能的消除这些警告

无法避免的话而且确保代码无误的话,慎重使用 @SuppressWarnings(“unchecked”)注解

Item25 集合和数组倾向集合

数组存储类型较灵活,将会导致不可预期的运行时异常
尽量让错误在编译时暴露而不是在不可控的运行时

Item26 泛型类

Item27 泛型方法

Item28 使用有界通配符增加API的灵活性

List不是List的子类型,这个是有意义的
你给List中放入任何对象,但是List中只能存放String
但有时我们不想存放固定的类型,因此需要一些灵活性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Stack<E> {
public Stack();
public void push(E e);
public E pop();
public boolean isEmpty();
}
//push所有元素
public void pushAll(Iterable<E> src) {
for (E e : src)
push(e);
}

//使用
Stack<Number> numberStack = new Stack<Number>();
Iterable<Integer> integers = ... ;
numberStack.pushAll(integers);

上述代码编译正常,看着也没什么问题 然而当我们添加的src不是我们期望的类型呢 如Stack,我们添加push(int) int将被自动装箱成Integer Integer也是Number的子类型
然而

1
2
3
4
//error
pushAll(Iterable<Number>) in Stack<Number>
cannot be applied to (Iterable<Integer>)
numberStack.pushAll(integers);

幸运的是Java提供了一种机制来解决子类的问题

1
2
3
4
5
// Wildcard type for parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
for (E e : src)
push(e);
}

经典原则:PECS stands for producer-extends, consumer-super.

Item29 多参数的泛型安全


上一章:类和接口
下一章:枚举和注解

有时候UI设计颜色多少透明度的时候,都去网上找对应关系图,其实有一个公式可以自己算出来

十六进制的FF–>十进制的255

  • 90%透明度
    十进制255*0.9 = 229.5 ≈ 230(四舍五入) –>十六进制E6

其它透明度以此类推
程序员计算器

前言:最近对收音机的开发,遇到一个需求,将收藏列表显示在前,电台列表显示在后,所以需要对列表进行对象排序,在此做一个总结.

步骤1 创建比较器,指定排序规则

导入此包 java.util.Comparator

1
2
3
4
5
6
7
8
9
10
11
comparator = new Comparator<RadioNode>() {
public int compare(RadioNode s1, RadioNode s2) {
if (s1.isFavor == s2.isFavor) {
return s2.frequent - s1.frequent;
}else{
if(s1.isFavor) return -1;
if(s2.isFavor) return 1;
}
return -1;
}
};

这里面有两个对象s1和s2,下面是制定的比较规则,如果isFavor相同,则比较frequent
返回1表示s1比s2大,则s1的位置不动,s2继续与后面的比较
返回0表示俩一样大,位置不变
返回-1表示s1与s2交换位置,s1继续按规则比较

步骤2 将集合传入

导入此包java.util.Collections

Collections.sort(favorList,comparator);

end
感谢android

前言:之前使用的好好的时候,eclipse莫名报了一个outofXXX的弹框,没仔细看.整个eclipse动不了,不停弹框,网上搜索了一下解决方案

稳准狠

删除
[workspace]/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi

前言,在使用到adapter的时候,一般要用List来装数据实体,这里两种不同的写法容易遇到不同的问题。

  • 第一种写法
1
2
3
4
5
6
7
8
9
class TestAdapter{
private List<Node> list;
***

public TestAdapter(List list){
this.list = list;
***
}
}

这样的话,我们在list更新的时候直接调用adapter的notifydatasetchanged就可以了.

  • 第二种写法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class TestAdapter{
    private List<Node> mList = new ArrayList<Node>();
    ***

    public TestAdapter(List list){
    mList.addAll(list);
    ***
    }

    public void addAll(List list){
    mList.clear();
    mList.addAll(list);
    notifydatasetchanged();
    }

    public void addOne(Node node){

    mList.add(node);
    notifydatasetchanged();
    }
    }
    用这种写法在数据变化的时候,需要调用adapter.add*()的对应方法
  • 总结
    adapter更新是看对象的地址有没有变化,调用notifydatasetchanged()才会管用.
  • 问题
    notifydatasetchanged不管作用

1.一般情况下,遇到notifydatasetchanged不管作用是指向的对象已经不是初始化adapter时的那个对象了.比如使用了上面第二种写法,却调用的第一种的方式.
2.list的size==0;
3.***