Effective Java学习笔记(四)

[color=blue][size=large][b]第四章:类和接口(条目13-22)[/b][/size][/color]
类和接口是Java程序设计的核心,Java语言提供了许多强大的基本元素,供程序员来设计类和接口。 本章阐述的一些指导原则,可以帮助你更好地利用这些元素,设计出更加有用、健壮和灵活的类/接口。

[b][size=medium][color=red]第13条:使类和成员的可访问性最小化[/color][/size][/b]
要区别设计良好的模块和设计不好的模块,最重要的因素在于,这个模块对于外部的其他模块而言,是否隐藏其内部数据和其他实现细节。设计良好的模块会隐藏所有的实现细节,把它的API与它的实现清晰地隔离出来。然后,模块之间只通过它们的API进行通信,一个模块不需要指导其他模块的内部工作情况。这个概念被称为信息隐藏(information hiding)或者封装(encapsulation),是软件设计的基本原则之一。
Java提供了许多机制(facility)来协助信息隐藏。访问控制(access control)机制决定了类、接口和成员的可访问性(accessibility)。对于顶层的(非嵌套的)类和接口,只有两种可能的访问级别:包级私有的(package-private)和公有的(public)。对于成员(域、方法、嵌套类和嵌套接口)有四种可能的访问级别:private/package-private(default)/protected/public。
对于公有类的成员,当访问级别从包级私有变成保护级别时,会大大增强可访问性。受保护的成员是类的导出API的一部分,导出的类的受保护成员也代表了该类对于某个实现细节的公开承诺。受保护的成员应该尽量少用。
如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别。这条规则的特殊情形是:如果一个类实现了一个接口,那么接口中所有的方法在这个类中必须被声明为公有的,之所以如此,是因为接口中的所有方法都隐含着公有访问级别。
下面还有几个规则:
1.实例域不能是公有的;
2.静态域不能是公有的,除了static final常量(命名由大写字母加下划线组成);
在第2点数组的特殊情况中,请注意下面这个安全漏洞:

// Potential security hole!
public static final Integer[] VALUES_1 = { 1, 2, 3 };

// 上面VALUES_1有安全漏洞,请用下面这两种方式替换。
private static final Integer[] VALUES_2 = { 1, 2, 3 };
public static final List<Integer> VALUES_2_EX = Collections
.unmodifiableList(Arrays.asList(VALUES_2));

// 或者下面这种方式:
private static final Integer[] VALUES_3 = { 1, 2, 3 };
public static final Integer[] VALUES_3_EX() {
return VALUES_3.clone();
}

总而言之,你应该至始至终尽可能降低可访问性。除了静态final域的特殊情形外,公有类都不应该包含公有域。并且要保证公有静态final域所引用的对象都是不可变的。

[b][size=medium][color=red]第14条:在公有类中使用访问方法而非公有域[/color][/size][/b]
有时候,可能会编写一些退化类(degenerate class),没有什么作用,只是用来集中实例域:

// Degenerate class like this should not be public!
class Point {
public double x;
public double y;
}

上面这种方式会让坚持面向对象的程序员深恶痛绝,认为应该用包含私有域的公有访问方法(getter)来替代。毫无疑问,说到公有类的时候,肯定是得用getter的,但是如果类是package-private的,或者是私有的嵌套类,上面的这种方式并没有本质错误(优点是比getter更不会产生视觉混乱)。
Java库中几个类违反了“公有类不应该直接暴露数据域”的告诫,比如java.awt包中的Point和Dimension类,它们是不值得效仿的,是反面例子。
总之,公有类永远都不应该暴露可变的域。让公有类暴露不可变的域危害比较小,但还是有问题。但是,有时会需要用包级私有的或者私有的嵌套类来暴露域,无论这个类是可变的还是不可变的(比如上面的Point类)。

[b][size=medium][color=red]第15条:使可变性最小化[/color][/size][/b]
不可变类只是其实例不能被修改的类,每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期(lifetime)内固定不变。Java库中包含的不可变类有:String、基本类型的包装类、BigInteger和BigDecimal。不可变类易于设计,不容易出错,且更加安全。为了使类称为不可变,要遵循下面5条规则:
1.不要提供任何会修改对象状态的方法(也称为mutator,比如setter)。
2.保证类不会被扩展,即不可被子类化。一般做法是使这个类称为final的。
3.使所有的域都成为私有的。
4.使所有的域都成为私有的。
5.确保对于任何可变组件的互斥访问。(这个点要再理解下)
在前面的PhoneNumber类中,针对每个属性都有accessor,但没有mutator。下面是个稍微复杂点的复数例子:

/**
* 复数
* @author chasegalaxy
* @since 2012-08-05
*/
public class Complex {
private final double re; // 实部
private final double im; // 虚部

public Complex(double re, double im) {
this.re = re;
this.im = im;
}

// Accessors with no corresponding mutators
public double realPart() {
return re;
}

public double imaginaryPart() {
return im;
}

public Complex add(Complex c) {
return new Complex(re + c.re, im + c.im);
}

public Complex substract(Complex c) {
return new Complex(re - c.re, im - c.im);
}

public Complex multiply(Complex c) {
return new Complex(re * c.re - im * c.im, re * c.im + im * c.re);
}

public Complex divide(Complex c) {
double tmp = c.re * c.re + c.im * c.im;
return new Complex((re * c.re - im * c.im) / tmp,
(im * c.re - re * c.im) / tmp);
}

@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Complex)) {
return false;
}
Complex c = (Complex) o;
// 下面不使用==,而使用compare是因为?
return Double.compare(re, c.re) == 0 && Double.compare(im, c.im) == 0;
}

@Override
public int hashCode() {
int result = 17;
result = result * 31 + hashDouble(re);
result = result * 31 + hashDouble(im);
return result;
}

private int hashDouble(double val) {
long longBits = Double.doubleToLongBits(val);
return (int) (longBits ^ (longBits >>> 32));
}

@Override
public String toString() {
// return String.format("(%f + %fi)", re, im);
return "(" + re + " + " + im + "i)";
}
}

在Complex类中,加减乘除法是创建并返回新的Complex实例,而不是修改实例。大多数不可变类都使用了这种模式,它被称为函数(functional)的做法,因为这些方法返回了一个函数的结果,这些函数对操作数进行运算但并不修改它。与之相对应的便是过程的(procedural)或者命令式的(imperative)。
不可变对象本质上是线程安全的,它们不要求同步,可以被自由地共享,还可以共享它们的内部信息。不可变对象唯一的缺点是,对于每个不同的值都需要一个单独的对象。但不可变的String提供了StringBuilder(和基本已经废弃的StringBuffer)作为可变配套类。
上面讨论过:保证类不会被扩展(子类化),一般做法是使这个类称为final的,除了这种方法,还有另外一种更加灵活的办法,那就是让类的所有构造器都变成私有的或者包级私有的,并且添加公有的静态工厂来替代公有构造器。虽然这种方法不常用,但它经常是最好的替代方法:

public static Complex valueOf(double re, double im) {
return new Complex(re, im);
}

总之,坚决不要为每个get方法编写一个相应的set方法,除非有很好的理由让你这么做。每个域添加final,除非你有很好的理由不添加final。你也应该认真考虑把一些较大的值对象做成不可变的,比如String和BigInteger。

[b][size=medium][color=red]第16条:复合优先于继承[/color][/size][/b]
继承(inheritance)是实现代码重用的有力手段,但它并非永远是完成这项工作的最佳工具,使用不当会导致软件变得很脆弱。在包内部使用继承是非常安全的,在那里,子类和超类的实现都处在同一个程序员的控制之下。提示一下,这里使用的继承,指实现继承(implementation inheritance,类扩展类),而不是接口继承(interface inheritance,类实现接口/接口扩展接口)。
先来看一个例子:

// Broken - Inappropriate use of inheritance!
public class InstrumentedHashSet<E> extends HashSet<E> {
// The number of attempted element insertions
private int addCount; // 统计添加元素的总次数
public InstrumentedHashSet() {
}
public InstrumentedHashSet(int initCap, float loadFactor) {
super(initCap, loadFactor);
}
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
// 测试代码及运行结果:
InstrumentedHashSet<String> set = new InstrumentedHashSet<String>();
set.addAll(Arrays.asList("a", "b", "c"));
System.out.println(set.getAddCount());
// the result is 6, not 3.

上面运行错误的原因在于,HashSet内部addAll方法是调用add方法的,子类中只要去掉addAll方法,运行就会正确。所有上面得到的instrumentedHashSet非常脆弱。幸运的是,有一种办法可以避免前面提到的所有问题。不用扩展现有的类,而是在新类中添加一个私有域,它引用现有类的一个实例,这种设计被称为“复合(composition)”。请看下面的例子,它采用复合/转发的方法来替代InstrumentedHashSet类。

public class ForwardSet<E> implements Set<E> {
private final Set<E> s;
public ForwardSet(Set<E> s) { this.s = s; }

@Override public int size() { return s.size(); }
@Override public boolean isEmpty() { return s.isEmpty(); }
@Override public boolean contains(Object o) { return s.contains(o); }
// 其他方法略...
}

public class InstrumentedExHashSet<E> extends ForwardSet<E> {
private int addCount = 0;
public InstrumentedExHashSet(Set<E> s) {
super(s);
}
@Override
public boolean add(E e) {
System.out.print("myClass add invoke...");
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}

// 测试代码及结果:
InstrumentedExHashSet<String> set2 =
new InstrumentedExHashSet<String>(new HashSet<String>());
set2.addAll(Arrays.asList("a", "b", "c"));
System.out.println(set2.getAddCount());
// the result is 3.(correct)

因为每一个InstrumentedExHashSet实例都把另一个Set实例包装起来了,所以InstrumentedExHashSet被称做包装类(wrapper class)。这也正是Decorator模式,因为InstrumentedExHashSet类对一个集合进行了修饰,为它增加了计数特性。包装类几乎没什么缺点,但需要注意一点,包装类不适合在回调框架(callback framework)中使用。(在回调框架中,对象把自身的引用传递给其他对象,用于回调)
只有当子类真正是超类的子类型(subtype)时,才适合用继承。Java类库中,有些地方违反了这条原则,不值得效仿。比如Stack扩展了Vector,Properties扩展了Hashtable,这两种情况用复合更适合。继承机制会把超类API中的所有缺陷传播到子类中,而复合则允许设计新的API来隐藏这些缺陷。
简而言之,继承很强大,但也有诸多问题,因为它违背了封装原则。只有当子类和超类具有父子关系时才是适合的用继承的。即便如此,如果子类和超类处于不同的包,并且超类不是为了继承而设计的,那么继承会导致脆弱性(fragility)。为了避免这种脆弱性,可以用复合和转发机制来代替继承,尤其是当存在适当的接口可以实现包装类的时候。包装类不仅比子类更加健壮,而且功能也更加强大。

[b][size=medium][color=red]第17条:要么为继承而设计,并提供文档说明,要么就禁止继承[/color][/size][/b]
上一条提醒我们,对于不是为了继承而设计、并且没有文档说明的“外来”来进行子类化多么危险。那么对于专门为了继承而设计的类必须要由良好的文档,说明它可覆盖(overridable)的方法的自用性(self-use)。这里可覆盖的方法指public/protected的方法。
当你为了继承而设计类的时候,如何决定该暴露哪些受保护的方法或者域呢?遗憾的是,并没有神奇的方法可供你使用。你所能做的最佳途径就是努力思考,发挥最好的想象,然后编写一些子类进行测试。你应该尽可能少地暴露受保护成员,但又不能暴露的太少。
对于为了继承而设计的类,唯一的测试方法就是编写子类。经验表明,3个子类通常就足以测试一个可扩展的类。为了允许继承,类还必须遵守下面这个约束:构造器决不能调用可被覆盖的方法,无论是直接调用还是间接调用。请看下面的例子:

public class Super {
// Broken - constructor invokes an overridable method
public Super() {
overrideMe();
}
public void overrideMe() { }
}

public class Sub extends Super {
private final Date date; // Blank final, set by constructor

Sub() {
date = new Date();
}
// Overriding method invoked by superclass constructor
@Override
public void overrideMe() {
System.out.println(date);
}

public static void main(String[] args) {
Sub sub = new Sub();
sub.overrideMe();
}
// The result is:
// null
// Mon Aug 06 17:15:06 GMT+08:00 2012
}

这边打印的结果并不是预期的,因为overrideMe方法被Super构造器调用的时候,构造器Sub还没有机会初始化date域。
为了继承而设计类的时候,Cloneable和Serializable接口出现了特殊的困难。如果类是为了继承而设计,实现上面两个接口都不是个好主意,因为它们把一些实质性的负担转嫁到了扩展这个类的程序员的身上。
对于普通的类该怎么办呢?这个问题的最佳解决方案是禁止其子类化:class用final修饰或者所有构造器变成私有或者包级私有,并增加一些共有的静态工厂来替代构造器。这条建议可能会引来争议,因为许多程序员已经习惯于对普通的具体类进行子类化,以便增加新的功能。如果类实现了某个能够反映其本质的接口,比如Set、List或Map,就不应该为了禁止子类化而感到后悔。包装类(wrapper class)模式提供了另一种更好的办法,让继承机制实现更多的功能。
总之,为了继承而设计类时需要努力思考,发挥最好的想象,然后多编写子类做测试。

[b][size=medium][color=red]第18条:接口优于抽象编程[/color][/size][/b]
Java提供了两种机制,可以用来定义允许多个实现的类型:接口和抽象类。
如果使用接口,现有的类很容易被更新;但如果希望让两个类扩展同一个抽象类,就必须把抽象类放到类型层次(type hierarchy)的高处,以便这两个类称为它的子类。遗憾的是,这样做会间接伤害到类层次,迫使这个公共祖先的所有后代都扩展这个新的抽象类,无论它对于这些后代是否合适。
接口是定义mixin(混合类型)的理想选择,允许我们构造非层次接口的类型框架,通过使用包装类(wrapper class)模式i,接口使得安全地增强类的功能成为可能。如果使用抽象类来定义类型,那么程序员除了使用继承的手段来增加功能,没有其他的选择。这样得到的类与包装类相比,功能更差,也更加脆弱。
虽然接口不允许包含方法的实现,但是,使用接口来定义类型并不妨碍你为程序员提供实现上的帮助。通过对你导出的每个重要接口都提供一个抽象的骨架实现(skeletal implementation)类,把接口和抽象类的优点结合起来。接口的作用仍然是定义类型,但是骨架实现类接管了所有域接口实现相关的工作。按照惯例,骨架实现被称为AbstractInterface,这里的Interface是指所实现的接口的名字。请看下面的例子:

/**
* 抽象类测试(注意学习它的这种写法)
* @author chasegalaxy
* @since 2012-08-06
*/
public class AbstractInterfaceMain {
// Concrete implementation built atop skeletal implementation
static List<Integer> intArrayAsList(final int[] arr) {
if (null == arr) {
throw new NullPointerException();
}
return new AbstractList<Integer>() {
@Override
public Integer get(int index) {
return arr[index];
}
// 如果不重写下面的set方法,那么下面的list.set(2, 1000)会出现运行时异常。
@Override
public Integer set(int index, Integer val) {
int oldVal = arr[index];
arr[index] = val;
return oldVal;
}
@Override
public int size() {
return arr.length;
}
};
}

public static void main(String[] args) {
final int[] arr = { 1, 2, 3 };
List<Integer> list = intArrayAsList(arr);
list.set(2, 1000);
for (Integer obj: list) {
System.out.println(obj);
}
// The result is: 1 2 1000
}
}

上面这个例子充分演示了骨架实现的强大功能。顺便提一下,这个例子是个Adapter,它允许将int数组看做Integer实例的列表。由于在int和Integer之间转换需要开销,它的性能不会很好。注意,这个例子中只提供一个静态工厂,并且这个类还是个不可被访问的匿名类(anonymous class),它被隐藏在静态工厂的内部。
骨架实现的美妙之处在于,它们为抽象类提供了实现上的帮助,但又不强加“抽象类被用作类型定义时”所特有的严格限制。骨架实现类是为了继承而设计的,所以强烈建议提供好的说明文档。使用抽象类与使用接口相比有一个明显的优势:抽象类的演变比接口的演变要容易的多。
设计公有的接口要非常谨慎。接口一旦被公开发行,并且已被广泛实现,想再改变这个接口几乎不可能。你必须在初次设计的时候就保证接口是正确的。如果接口具有严重的缺陷,它可能导致API的彻底失败。
简而言之,接口通常是定义允许多个实现的最佳途径。但这条规则有个例外,即当演变的容易性比灵活性和功能更为重要的时候,应该使用抽象类来定义类型,但前提必须理解并且可以接受这些局限性。如果你导出了一个重要接口,就应该坚决考虑同时提供骨架实现类。最后,应该尽可能地设计所有的公共接口,并通过编写多个实现来对他们进行全面的测试。

[b][size=medium][color=red]第19条:接口只用于定义类型[/color][/size][/b]
当类实现接口时,接口就充当可以引用这个类的实例的类型(type)。因此,类实现了接口,就表明客户端可以对这个类的实例实施某些动作。为了任何其他目的而定义接口是不恰当的。
有一种接口被称为常量接口(constant interface),比如java.io.ObjectStreamConstants。常量接口模式是对接口的不良使用,不应该使用。如果要导出常量,可以有几种合理的解决方案。
1.常量与某个现有的类或接口密切相关,则可以把常量 添加到这个类或接口中。比如Integer.MIN_VALUE/MAX_VALUE。
2.常量最好被看做枚举类型的成员,则可以用枚举。
3.使用不可实例化的工具类(utility class)来导出常量。如果大量利用工具类导出的常量,在JDK1.5之后,可以通过利用静态导入(static import)机制,避免用类名来修饰常量名。请看下面例子:

// Constant utility class
public class PhysicalConstants {
private PhysicalConstants() { } // Prevents instantiation
public static final double AVOGADROS_NUMBER = 6.02214199e23;
public static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
}

import static com.chasegalaxy.effectivejava.chapter4.rule19.PhysicalConstants.*;

public class TestMain {
double atoms(double mols) {
return AVOGADROS_NUMBER * mols;
}
// ...
// Many more uses of PhysicalConstants justify static import
}

总而言之,接口应该只用来定义类型(声明各种行为),它们不应该被用来导出常量。

[b][size=medium][color=red]第20条:类层次优于标签类[/color][/size][/b]
有时候,可能会遇到带有两种甚至更多种风格的实例的类,并包含表示实例风格的标签(tag)域。例如,考虑下么这个类,它能够表示圆形或者矩形:

// Tagged class - vastly inferior to a class hierarchy!
public 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
public Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}

// Constructor for rectangle
public Figure(double length, double width) {
shape = Shape.RECTANGLE;
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();
}
}
}

这种标签类(tagged class)有着许多缺点。它们中充斥着样板代码,包括枚举、标签域及条件语句。由于多个实现乱七八糟地挤在了单个类中,破坏了可读性。一句话,标签类过于冗长、容易出错,且效率低下。
幸运的是,面向对象语言比如Java,就提供了其他更好地方法来定义能表示多种风格对象的单个数据类型:子类型化(subtyping)。标签类正是类层次的一种简单效仿。下面是将标签类转换为类层次后的代码:

// Class hierarchy replacement for a tagged class
abstract class FigureEx {
abstract double area();
}

class Circle extends FigureEx {
final double radius;
Circle(double radius) {
this.radius = radius;
}
double area() {
return Math.PI * (radius * radius);
}
}

class Rectangle extends FigureEx {
final double length;
final double width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double area() {
return length * width;
}
}

总之,标签类很少有适用的时候。当你想要编写一个包含显式标签域的类时,应该考虑一下,这个标签是否可以被取消,这个类是否可以用类层次来代替。当你遇到一个包含标签域的现有类时,就要考虑将它重构到一个层次结构中去。

[b][size=medium][color=red]第21条:用函数对象表示策略[/color][/size][/b]
有些语言支持函数指针(function pointer)、代理(delegate)、lambda表达式(lambda expression),或者支持类似的机制,允许函数的调用者通过传入第二个函数,来指定自己的行为。例如,C语言标准库中的qsort函数要求用一个指向comparator(比较器)函数的指针作为参数,它用这个函数来比较待排序的元素。通过传递不同的比较器函数,就可以获得各种不同的排列顺序。这正是策略(strategy)模式的一个例子。

// Strategy interface
public interface ComparatorEx<T> extends Comparator<T> {
public int compare(T t1, T t2);
}

// concrete strategy
public class StringLengthComparator implements ComparatorEx<String> {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}

public class ComparatorExMain {
private static class StrLenCmp implements ComparatorEx<String> {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
};

public static void main(String[] args) {
String[] arr1 = { "abc", "ab", "abcd", "a", "abcde" };
Arrays.sort(arr1, new ComparatorEx<String>(){
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});
for (String s: arr1) {
System.out.print(s + ",");
}
// The result is:a,ab,abc,abcd,abcde,

String[] arr2 = { "abc", "ab", "abcd", "a", "abcde" };
Arrays.sort(arr2, new StrLenCmp());
for (String s: arr2) {
System.out.print(s + ",");
}
// The result is also:a,ab,abc,abcd,abcde,
}
}

简而言之,函数指针的主要用途就是实现策略(Strategy)模式。为了在Java中实现这种模式,要声明一个接口来表示该策略,并且为每个具体策略声明一个实现了该接口的类。当一个具体策略只被使用一次时,通常使用匿名类来声明和实例化这个具体策略类。如果会重复使用,它的类通常就要实现为私有的静态成员类,并通过公有的静态final域被导出,其类型为该策略接口。

[b][size=medium][color=red]第22条:优先考虑静态成员类[/color][/size][/b]
嵌套类(nested class)是指被定义在另一个类的内部的类。嵌套类存在的目的应该只是为了它的外围类(enclosing class)服务。如果嵌套类将来可能会用于其他的某个环境中,它就应该是顶层类(top-leve class)。嵌套类共有四种:静态成员类(static member class)、非静态成员类(nonstatic member class)、匿名类(anonymous class)和局部类(local class)。除了第一种外,其他三种都称为内部类(inner class)。请看下面计算器类(仅说明静态成员类的用法,不做实际用):

public class Calculator {
private Calculator() { }
private static Calculator calculator = null;
public static Calculator getInstance() {
if (null == calculator) {
calculator = new Calculator();
}
return calculator;
}

// static member class
static class Operation {
public static final int PLUS = 0;
public static final int MINUS = 1;
}

public double calc(double a, double b, int operation) {
switch (operation) {
case Operation.PLUS:
return a + b;
case Operation.MINUS:
return a - b;
default:
throw new AssertionError();
}
}
}

// test code
double result = Calculator.getInstance().calc(
1.2d, 2.34d, Calculator.Operation.PLUS);
System.out.println(result); // The result is 3.54

上面是静态成员类。非静态成员类的每个实例都隐含着与外围类的一个外围实例(enclosing instance)相关联。在非静态成员类的实例方法内部,可以调用外围实例上的方法,或者利用修饰过的this构造外围实例的引用。
非静态成员类的一种常见用法是定义一个Adapter,它允许外部类的实例被看作是另一个不相关的类的实例。例如,Map接口的实现往往使用非静态成员类来实现它们的集合视图(collection view),这些集合视图是由Map的keySet、entrySet和Values方法返回的。
匿名类不同于Java的任何其他语法单元。它并不与其他的成员一起被声明,而是在使用的同时被声明和实例化。匿名类可以出现在代码中任何允许存在表达式的地方。当且仅当匿名类出现在非静态的环境中,它才有外围实例。但是即使它们出现在静态的环境中,也不可能拥有任何静态成员。
匿名类的一种常见用法是动态地创建函数对象(function object);第二种常见用法是创建过程对象(process object),比如Runable、Thread和TimerTask实例;第三种常见用法是在静态工厂方法的内部。
局部类是四种嵌套类中用的最少的类。在任何“可以声明局部变量”的地方,都可以声明局部类,并且局部类也遵守通用的作用域规则。四种嵌套类具体使用请多看JDK源码和结合实践来学习。[color=blue][b](第四章完)[/b][/color]

[b][color=red]文章本人原创,转载请注明作者和出处,谢谢。[/color][/b]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值