Contents

Effective Java 2 Object

第1条:考虑静态工厂方法代替构造器

获取一个类的实例,最常用的是提供一个public constructor。还有一种就是提供一个static factory method,它只返回该类的一个实例。

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

优势:

  1. 静态工厂方法有名字会是API更为清晰。
  2. 他们返回自己的一个已经构建好的实例,避免创建不必要的重复对象。
  3. 可以返回任何子类型对象,提高APT灵活性
  4. 在创建参数化类型实例的时候,是代码更加简洁。如下:
1
2
3
4
5
6
7
public static void main(String[] args) {
    Map<String, List<String>> map = newInstance();
}

public static <K,V>HashMap<K,V> newInstance(){
    return new HashMap<K, V>();
}

缺点:

  1. 如果不含public/protected constructor,就不能子类化。子类在实例化的时候会调用父类的constructor,所有父类必须要给权限
  2. 他们和其他static method没有任何区别。一般给一些惯用名称。如:getInstance/newInstance/getType/newType/of/valueof

总结:遇到构建类的时候,要考虑static factory method

第2条:遇到多个构造器参数时考虑构建器

当一个类有许多个可选属性,那么在初始化时采用哪种构造器呢?

一般习惯用重叠构造器模式:第一个constructor有1个可选参数,第二个constructor有2个可选参数,第三个constructor有3个可选参数…这样下来当有许多参数的时候,会使代码难写阅读性差

采用Builder模式解决

 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
37
38
39
40
41
public class Demo {
private final int required1;
private final int required2;
private final int option1;
private final int option2;
private final int option3;
private final int option4;

public static class DemoBuilder{
    //Required parameters
    private final int required1;
    private final int required2;
    //Optional parameters
    private int option1 = -1;
    private int option2 = -1;
    private int option3 = -1;
    private int option4 = -1;

    public DemoBuilder(int required1, int required2) {
        this.required1 = required1;
        this.required2 = required2;
    }

    public DemoBuilder option1(int val){option1 = val;return this;}
    public DemoBuilder option2(int val){option2 = val;return this;}
    public DemoBuilder option3(int val){option3 = val;return this;}
    public DemoBuilder option4(int val){option4 = val;return this;}

    public Demo build(){
        return new Demo(this);
    }
}

private Demo(DemoBuilder builder){
    required1 = builder.required1;
    required2 = builder.required2;
    option1 = builder.option1;
    option2 = builder.option2;
    option3 = builder.option3;
    option4 = builder.option4;
}

注意把必须要求的参数放到Builder constructor里,可选参数通过option设置,最后通过build返回一个demo1对象。

1
demo1 dm = new demo1.Builder(1,2).option1(1).option2(1).option3(1).build();

可以参照建造者模式把build抽象为接口,方便理解

第3条:用private constructor或enum强化singleton属性

singleton实现有三种方式:

第1种:公有的实例

1
2
3
4
public class singleton {
    public static final singleton INSTANCE = new singleton();
    private singleton(){}
}

第2种:私有的实例+静态工厂方法

1
2
3
4
5
public class singleton {
    private static final singleton INSTANCE = new singleton();
    private singleton(){}
    public static singleton getInstance(){return INSTANCE;}
}

第3种:单元素的枚举类型实现单例(最佳方法)

设计思想:通过公有的静态final域为每个枚举常量导出实例的类,因为没有可以访问的构造器(private constructor),枚举类型是真正的final。既不能创建枚举类型的实例,也不能对他进行扩张。换句话说,枚举类型是受实例控制的,是单例的泛型化,本质是单元素的枚举。

并且枚举提供了序列化机制,防止了多次实例化,它是线程安全的,同时可以防止反射攻击

1
2
3
4
5
6
public enum singleton {
    INSTANCE;
    public void operation(){
        System.out.println("I am singleton");
    };
}

可以通过javap得到证明

1
2
3
4
5
6
7
8
Compiled from "singleton.java"
public final class com.demo2.singleton extends java.lang.Enum<com.demo2.singleton> {
  public static final com.demo2.singleton INSTANCE;
  public static com.demo2.singleton[] values();
  public static com.demo2.singleton valueOf(java.lang.String);
  public void operation();
  static {};
}

第4条:通过private constructor强化不可实例化的能力

对于有些工具类(utility class)不希望被实例化。然而在缺少constructor的情况下,编译器会自动提供一个公有的无参缺省constructor。

此时可以提供一个private constructor(不能被实例化和extends),或修饰该类为final(不能extends)。

第5条:避免创建不必要的对象

编写code的时候,需要考虑性能问题。如一个实例需要重复使用,不妨把实例创建放到static块里,在创建类的时候初始化;Java里要考虑自动装箱和拆箱带来的性能问题。等等相关问题,在coding的时候需要考虑。

第6条:消除过期的对象引用

Java虽然拥有GC功能,但是内存泄露是很隐蔽的存在(或者称为无意识的对象保持)。如果一个对象被无意识的保留下来,那么GC就不会处理这个对象和它引用的其他对象,从而对性能产生重大影响。

解决方法:把这个对于引用=null。注意这是一种例外,只要当类自己管理内存时,一旦元素被释放掉,则该元素中包含的任何对象引用都应该=null。如:Stack的pop方法。

然而规范的做法是:让包含该引用的变量结束其生命周期,即将局部变量的作用域最小化

其他内存泄露情况:cache等,需要coding的时候具体考虑。

第7条:避免使用终结方法

Java中的finalize()方法详解

使用finalizer方法通常不可预测一个对象从不可到达开始,到它的finalizer被执行,所花费的时间是不可预测的

所以,一般通过显式的终止方法及时释放资源,如InputStream,OutputStream里的close方法,

第8条:覆盖equals时遵守通用约定

1.自反性

对象必须等于自身,即对于非null的引用值x,x.equals(x)必须返回true。

2.对称性

对于非null的引用值x和y,当且仅当x.equals(y)返回true时,y.equals(x)必须返回true

3.传递性

对于非null的引用值x、y和z,如果x.equals(y)=true并且y.equals(z)=true,那么x.equals(z)也必须返回true。

Demo:一个Point类包含x和y坐标,现在有个ColorPoint继承Point,并添加了color属性。如下进行初始化:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ColorPoint p1 = new ColorPoint(1,2,Color.RED);
Point p2 = new Point(1,2);
ColorPoint p3 = new ColorPoint(1,2,Color.BLUE);
/**
 * Point的equals方法
 */
public boolean equals(Object obj) {
    if(!(obj instanceof Point))
        return false;
    Point p  = (Point)obj;
    return p.x == x && p.y == y;
}
/**
 * ColorPoint的equals方法
 */
public boolean equals(Object o){
	if(!(o instanceof Point))
		return false;	
	//o is a normal Point
	if(!(o instanceof ColorPoint))
		return o.equals(this);
	//o is a ColorPoint do a full comparison
	return super.equals(o) && ((ColorPoint)o).color == color;
}

此时p1.equals(p2)和p2.equals(p3)都返回true,但是p1.equals(p3)返回false。显然违反了传递性。此时可以采用复合优先于继承解决问题。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ColorPoint {
    private final Point point;
    private final Color color;

    public ColorPoint(Point point, Color color) {
        this.point = point;
        this.color = color;
    }

    /**
     * Return the point-view of this ColorPoint
     */
    public Point getPoint() {
        return point;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof ColorPoint))
            return false;
        ColorPoint cp = (ColorPoint) obj;
        return cp.point.equals(point) && cp.color.equals(color);
    }
}

此时p1.equals(p2)和p2.equals(p3)都返回false,并且p1.equals(p3)返回false满足条件。

4.一致性

如果2个对象相等,它们就必须始终保持相等,除非其中有对象被修改了。即无论类是否是可变的,都不要使equals依赖于不可靠的资源。

5.非空性

所有对象都必须不等于null,即非空对象x满足x.equals(null)必须返回false。

一般不需要对obj==null进行判断,因为

1
if(!(obj instance Type)) return false;

就包含了obj为null的情况。

Effective equals的诀窍

1.使用==检查是否是本对象的引用

如果是直接返回true。这样在比较操作代价高的时候,可以提高性能。

2.使用instance检查参数是否为正确的类型

即检查obj是否为equals方法所在的那个类

3.把参数obj转换为正确的类型

4.把类中的每个关键域都进行比较

应该先比较最可能不一致的关键域

5.写测试

通过单元测试来验证对称、传递、一致性。

第9条:覆盖equals时总有覆盖hashCode

Object规范:

  • 只要equals比较的域没有被修改,则该对象的hashCode方法必须返回同一个整数。
  • 如果2个对象equals比较相等,则他们的hashCode返回值也相等
  • 如果2个对象equals比较不相等,则他们任一个对象hashCode返回的值不一定要产生不同的结果。但是,给不相等的对象不同的hashCode可能提高hash table(散列表)的性能。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class PhoneNumber {
    private final short areaCode;
    private final short prefix;
    private final short lineNumber;

    public PhoneNumber(short areaCode, short prefix, short lineNumber) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNumber = lineNumber;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) return true;
        if (!(obj instanceof PhoneNumber)) return false;
        PhoneNumber pn = (PhoneNumber) obj;
        return pn.areaCode == areaCode && pn.prefix == prefix && pn.lineNumber == lineNumber;
    }
1
2
3
Map<PhoneNumber, String> map = new HashMap<PhoneNumber, String>();
map.put(new PhoneNumber(111, 222, 333), "fedomn");
System.out.println(map.get(new PhoneNumber(111,222,333)));

我们希望打印出fedomn,但是为null。说明这个2个实例不相等,因为没有override hashCode方法,导致2个实例具有不相等的散列码,当在put时候把key放在一个散列桶,而get的时候根据key在另一个散列桶中查找,必然返回null。 解决方法:

1
2
3
4
5
6
7
8
@Override
public int hashCode() {
    int result = 17;
    result = 31 + result + areaCode;
    result = 31 + result + prefix;
    result = 31 + result + lineNumber;
    return result;
}

其他类型的关键域都采用类似方法最终转成int类型。

第10条:始终要覆盖toString

虽然Object提供了toString方法,但是返回的是类名@散列码的无符号十六进制

所以重写toString方法,输出需要的信息。注意要写好返回值的注释。

第11条:谨慎覆盖clone

准备知识Java如何复制对象

首先,Cloneable接口表明该对象允许克隆(clone),但是它内部并不包含任何方法。

如果一个类实现了Cloneable,则Object的clone方法就会返回该对象的逐域拷贝

所以一个类实现Cloneable,它重写Object的clone方法如下:

1
2
3
4
@Override
protected PhoneNumber clone() throws CloneNotSupportedException {
    return (PhoneNumber)super.clone();
}

注意返回的类型是PhoneNumber,体现了一条通则:永远不要让客户去做任何类库能够替客户完成的事情

但是,这样做依然有问题。如果类中包含引用类型如Object[] elements,调用super.clone()时,逐域拷贝的只是该对象的引用,指向的仍是原来的内存。此时可以通过递归调用clone,如下:

1
2
3
4
5
6
@Override
protected Stack clone() throws CloneNotSupportedException {
    Stack res = (Stack)super.clone();
	res.elements = elements.clone();
	return res;
}

貌似这样就解决了问题,但是如果elements域为final的,就不可以通过clone方法赋值了。除非去除final修饰符。

递归clone也存在问题。如Demo类里包含一个自己实现的list类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Demo implements Cloneable{
	private list[] buckets;
	
	private static class list{
		final Object key;
		Object value;
		list next;
		
		list(Object key, Object value, list next){
			this.key = key;this.value = value;this.next = next;
		}
	}

	@Override
	public Demo clone(){
		try{
			Demo res = (Demo)super.clone();
			res.buckets = buckets.clone();
			return res;
		}catch(CloneNotSupportedException e)}		
		}
	}
}

此时对bucket进行的clone方法,产生的链表与原始链表是一样的(next看出),很容易产生混乱,此时就要进行deep copy 如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@Override
public Demo clone(){
	try{
		Demo res = (Demo)super.clone();
		res.buckets = new list[buckets.length];
		for(int i=0; i<buckets.length; i++)
			if(buckets[i] != null)
				res.buckets[i] = buckets[i].deepCopy();
		return res;
	}catch(CloneNotSupportedException e)}		
	}
}
//采用迭代来deep copy
list deepCopy(){
	list res = new list(key,value,next);
	for(list p=res; p.next!=null; p=p.next)
		p.next = new list(p.next.key,p.next.value,p.next.next);
	return res;
}

总结:

clone复杂对象,先调用super.clone,把对象中的引用类型设置空白状态,再重新产生新的状态。如上deepCopy。

事实上实现拷贝对象可以提供copy constructor或者copy factory。而不是采用Cloneable/clone方法。

第12条:考虑实现Comparable接口

补充知识

  • Comparable

    compareTo方法没有在Object里声明,它是Comparable接口中唯一的方法。 如果类实现了Comparable接口,就表明它的实例具有内在的排序关系。 实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort)进行自动排序。 ComparaTo方法中域的比较是顺序的比较,而不是等同性比较,比较对象引用域可以通过递归地调用ComparTo方法实现

  • Comparator

    如果一个域没有实现Comparable接口,可以使用一个显示的Comparator来代替 Comparable与Comparator区别 如PriorityQueue里的Comparator用来创建一个非标准的排序关系,来实现插入时排序(只能保证队列头的是最大或最小)

第13条:使类和成员的可访问性最小化

一个设计良好的模块会隐藏所有的实现细节,把它的API与实现清晰的隔离开,实现信息隐藏。 Java实现信息隐藏规则:

  • 尽可能地使每个类或者成员不被外界访问 对于顶层的类,只有2种访问级别:包级私有的(默认无修饰符)和公有的(public修饰符)。 对于成员有4中访问级别:
    • 私有的(private):只有在声明该成员的顶层类内部才可以访问这个成员
    • 包级私有的:无修饰符,声明该成员的包内部的任何类都可以访问这个成员
    • 受保护的(protected):声明该成员的类的子类,声明该成员的类的包内任何类都可以访问
    • 公有的(public):在任何地方都可以访问该成员

设计类的公有API时,始终尽可能的降低可访问性。公有类不应该包含公有域,除了不可变的公有静态final域(如Boolean.FALSE)因为包含公有可变域的类并不是线程安全的

第14条:在公有类中使用访问方法而非公有域

如果类可以在它所在包的外部进行访问,就提供访问方法,不应该暴露可变的域。

如果类是包级私有的,或者是私有的嵌套类,直接暴露其数据域是可以的。

第15条:使可变性最小化

不可变的类指:每个实例包含的所有信息必须在创建该实例的时候就提供,并且在其生命周期内不能改变。(如final修饰的String类)

设计不可变类规则

  • 不要提供任何会修改对象状态的方法(如Setter)。
  • 保证类不会被扩展。一般通过final修饰该类,防止子类化。
  • 使所有的域都是final的。(如String类里的private final char value[])
  • 使所有的域都是私有的。防止客户端修改这些对象。
  • 确保对于任何可变组件的互斥访问。
    • 如果类包含可变对象的域,则必须保证客户端无法获得这些对象的引用,不要用客户端来初始化这些域(可采用保护性拷贝)。

不可变的对象本质是线程安全的,它们不要求同步。

不可变对象缺点:对于每个不同的值都需要一个单独的对象,创造这种对象的代价可能很高。

第16条:复合优先于继承

继承虽然实现了代码重用,但是跨包边界的继承,则是非常危险脆弱的。(如:子类依赖超类中的特定功能而实现,当超类随着版本更新发生改变,子类就可能会受到破坏。)

解决办法:不用去扩展现有类,在新类中增加一个私有域,来引用现有类的实例。这种方法称为复合composition

新类中的每个方法,都可以调用现有类中对应的方法,这被称为转发forwarding。新类中的方法称为转发方法forwarding method

总之,只有当子类真正是超类的子类型的时候,才适合继承。如果子类和超类位于不同的包中,并且超类不是为继承而实现的,这时可以用复合和转发机制来代替继承。示例详见Decorator模式

第17条:要么为继承而设计,并提供文档说明,要么就禁止继承

为继承而设计的类,文档里必须包含覆盖该方法给其他方法所带来的影响

构造器决不能调用可被覆盖的方法:

因为超类的constructor在子类的constructor之前运行,所以子类覆盖版本的方法将会在子类constructor运行之前被调用,很有可能抛出NullPointerException。

那么对于普通的具体类,它们既不是final的,也不是为了子类化而设计的。这种情况下,每次对其修改,都会对扩展这个类的客户类产生破坏。

此时,最佳方案是:对于那些并非为了安全地进行子类化而设计和编写文档的类,要禁止子类化

一般的解决方法:

  1. 把类声明为final的。
  2. 把constructor变成private的或者package-private。

第18条:接口优于抽象类

由于Java只允许单继承,抽象类这种为继承而设计的类受到了很大的限制。

1.现有的类可以很容易被跟新,以实现新的接口。

如当Java引入Comparable接口时,会更新许多类以实现比较功能,此时就可实现Comparable接口,重写方法。

2.接口是定义mixin(混合类型)的理想选择。

mixin类型指:类除了实现它的基本类型外,还可以实现这个mixin类型,以表明它提供了某种可供选择的行为。 如类实现Comparable接口,表明它提供了可以比较对象的方法,允许该功能混入到类的主要功能中。

3.接口允许我们构造非层次结构的类型框架

如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public interface Singer{
	void sing();
} 
public interface Writer{
	void write();
}
public interface SingerWriter extends Singer,Writer {
    void Song();
    void write();
}

现实生活中,如果一个Person既是Singer也是Writer,就可以定义SingerWriter接口,然后提供一个骨架抽象类(包含一些最基本的方法)来实现该接口。

虽然接口不允许包含实现方法,但是可以通过导出的接口提供一个抽象的骨架实现(skeletal implementation)类。

骨架实现被称为AbstractInterface,如:AbstractCollection、AbstractSet、AbstractList、AbstractMap。

骨架实现类,为一些最基本的方法实现。如:JDK里的HashSet继承骨架实现类AbstractSet,并且实现了Set接口。

公有接口一旦公开,并广泛使用,修改几乎就不可能了。

总结,接口通常是定义允许多个实现的类型的最佳途径。如果导出了一个重要接口,就应该同时提供骨架实现类。

第19条:接口只用于定义类型

当类实现接口时,接口就充当可以引用这个类实例的类型。

有一种常量接口如下:

1
2
3
public interface demo{
	static final int MESS = 1233456;
}

常量接口模式是对接口的不良使用,它的所有子类的命名空间也会被接口中的常量污染。

如要要导出常量可以使用枚举类型或不可实例化的工具类。

第20条:类层次优于标签类

标签类指:一个类中包含显式的标签域。如下Figure类包含了RECTANGLE,CIRCLE。这样破坏了可读性,内存占用也增加了。

 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
class Figure {
    enum Sharp{RECTANGLE, CIRCLE};

    final Sharp sharp;

    double length,width;
    double radius;

    public Figure(double radius) {
        sharp = Sharp.CIRCLE;
        this.radius = radius;
    }

    public Figure(double length, double width) {
        sharp = Sharp.RECTANGLE;
        this.length = length;
        this.width = width;
    }

    double area(){
        switch (sharp){
            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
abstract class Figure(){
    abstract double area();
}
class Circle extends Figure{
    final double radiux;
    public Circle(double radiux) { this.radiux = radiux;}
    double area(){ return Math.PI *(radiux * radiux);}
}
class Rectangle extends Figure{
    final double length;
    final double width;
    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }
    double area(){return length * width;}
}

第21条:用函数对象表示策略

有些语言支持函数指针、代理、lambda表达式等,来允许程序具有调用特殊函数的能力。

Java里没有提供函数指针,但是可以调用对象上的方法来执行其它对象操作。

如果一个类仅仅导出这样的一个方法,它的实例实际上就等同于一个指向该方法的指针。这样的实例被称作函数对象。如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class StringLengthComparator implements Comparator<String>{
    private StringLengthComparator(){}

    public static final StringLengthComparator INSTANCE = new StringLengthComparator();

    @Override
    public int compare(String o1, String o2) {
        return o1.length() - o2.length();
    }
}

其中,把该策略定义为一个接口,为每个具体的策略声明一个实现该接口的类。

  • 当策略只使用一次时,可使用匿名类来声明和实例化具体策略类。
  • 当策略被反复使用时,通常设计为私有的静态成员类,通过公有的静态实例来使用策略如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Host {
    private static class StrLenCmp implements Comparator<String>, Serializable{
        @Override
        public int compare(String o1, String o2) {
            return o1.length() - o2.length();
        }
    }
    //Returned comparator is serializable
    public static final Comparator<String> STRING_COMPARATOR = new StrLenCmp();
}

String类也利用了这种模式,通过它的CASE_INSENSITIVE_ORDER域,导出一个不区分大小写的字符串比较器。

第22条:优先考虑静态成员类

嵌套类只定义在一个类内部的类,它为外围类提供服务。 嵌套类有4种:

  • 静态成员类(static member class)
  • 非静态成员类(nonstatic member class)
  • 匿名类(anonymous class)
  • 局部类(local class)

1.静态成员类和非静态成员类

static member class是外围类的一个静态成员,与其他静态成员一样。

nonstatic member class的每个实例都包含一个额外指向外围对象的引用,在其内部可以访问外围实例上的方法

如HashMap里的KeySet类的contains方法就调用了外围类的containsKey方法。或者利用修饰过的this构造获得外围实例的引用

如HashMap里的KeySet类的clear方法,通过HashMap.this.clear()来获取外围实例执行方法。下面代码就是来自HashMap类里的非静态成员类KeySet

1
2
3
4
5
6
7
private final class KeySet extends AbstractSet<K> {
    public Iterator<K> iterator() {return newKeyIterator();}
    public int size() {return size;}
    public boolean contains(Object o) {return containsKey(o);}
    public boolean remove(Object o) {return HashMap.this.removeEntryForKey(o) != null;}
    public void clear() {HashMap.this.clear();}
}

注意:非静态成员类不能在没有外围类实例的情况下独立存在。一般在外围类里提供方法来创建非静态内部类。 如HashMap里的public Set keySet()来返回一个KeySet实例。

总结:

  • 非静态成员类常用于定义一个Adapter,它允许外部类的实例被看做是另一个不相关的类的实例

    (解释:HashMap的实例也可以看做成KeySet的实例,因为HashMap通过keySet()方法会返回一个KeySet的实例。)而这些非静态成员类就可以来实现外围类的集合视图。

  • 如果声明的成员类不要求访问外部实例,就要加上static变成静态的。

2.匿名内部类

它没有名字,使用的同时被声明和实例化。常用动态地创建函数对象。比如Runnable

3.局部类

在任何可以声明局部变量的地方,都可以声明局部类。它与成员类一样,有名字,可重复使用。

如果它要访问外围类实例,就可以为非静态的,否则为静态的。它要求简短,否则影响可读性。