Java编程:类设计、嵌套类与泛型的最佳实践
立即解锁
发布时间: 2025-08-18 00:34:25 阅读量: 1 订阅数: 4 

### Java编程:类设计、嵌套类与泛型的最佳实践
#### 1. 避免使用标签类,优先选择类层次结构
在编程中,有时会遇到一种类,其实例有多种类型,并且包含一个标签字段来指示实例的类型。例如,下面的`Figure`类可以表示圆形或矩形:
```java
// 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(shape);
}
}
}
```
这种标签类存在诸多缺点:
- 代码冗余,包含枚举声明、标签字段和`switch`语句。
- 可读性差,多种实现混杂在一个类中。
- 内存占用增加,实例会包含其他类型的无关字段。
- 字段难以设为`final`,除非构造函数初始化无关字段,导致更多冗余代码。
- 构造函数必须设置标签字段并初始化正确的数据字段,编译器无法提供帮助,若初始化错误,程序将在运行时失败。
- 若要添加新类型,必须修改源代码,并在每个`switch`语句中添加新的`case`,否则类将在运行时失败。
- 实例的数据类型无法表明其具体类型。
幸运的是,像Java这样的面向对象语言提供了更好的选择——子类型化。可以将标签类转换为类层次结构,步骤如下:
1. 定义一个抽象类,为标签类中行为依赖于标签值的每个方法定义一个抽象方法。在`Figure`类中,只有`area`方法符合条件。这个抽象类是类层次结构的根。
2. 为原始标签类的每种类型定义根类的具体子类。在示例中,有圆形和矩形两个子类。在每个子类中包含特定于该类型的数据字段,并实现根类中的抽象方法。
以下是对应的类层次结构:
```java
// 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; }
@Override 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;
}
@Override double area() { return length * width; }
}
```
这个类层次结构解决了标签类的所有缺点:
- 代码简单清晰,无冗余。
- 每种类型的实现都有自己的类,且不包含无关数据字段。
- 所有字段都是`final`的。
- 编译器确保每个类的构造函数初始化其数据字段,并为根类中声明的每个抽象方法提供实现,消除了因缺少`switch``case`导致的运行时失败的可能性。
- 多个程序员可以独立扩展层次结构,无需访问根类的源代码。
- 每种类型都有单独的数据类型,允许程序员指定变量的类型,并将变量和输入参数限制为特定类型。
此外,类层次结构还可以反映类型之间的自然层次关系,增加灵活性并提供更好的编译时类型检查。例如,如果标签类还允许表示正方形,可以将其作为矩形的特殊子类:
```java
class Square extends Rectangle {
Square(double side) {
super(side, side);
}
}
```
#### 2. 优先选择静态成员类而非非静态成员类
嵌套类是在另一个类中定义的类,应仅为其外部类服务。嵌套类有四种类型:静态成员类、非静态成员类、匿名类和局部类。除了静态成员类,其他三种都称为内部类。
静态成员类是最简单的嵌套类,可看作是恰好声明在另一个类中的普通类,能访问外部类的所有成员,包括私有成员。它是外部类的静态成员,遵循与其他静态成员相同的可访问性规则。例如,计算器类的操作枚举可以作为其公共静态成员类:
```java
// 假设的计算器类
public class Calculator {
public static enum Operation {
PLUS, MINUS;
}
}
```
客户端可以使用`Calculator.Operation.PLUS`和`Calculator.Operation.MINUS`来引用操作。
非静态成员类的每个实例都隐式关联其外部类的一个实例。在非静态成员类的实例方法中,可以调用外部类实例的方法或使用限定的`this`构造获取外部类实例的引用。如果嵌套类的实例可以独立于外部类实例存在,则该嵌套类必须是静态成员类,因为没有外部类实例就无法创建非静态成员类的实例。
非静态成员类的一个常见用途是定义适配器,使外部类的实例可以被视为某个无关类的实例。例如,`Map`接口的实现通常使用非静态成员类来实现其集合视图:
```java
// Typical use of a nonstatic member class
public class MySet<E> extends AbstractSet<E> {
// Bulk of the class omitted
@Override public Iterator<E> iterator() {
return new MyIterator();
}
private class MyIterator implements Iterator<E> {
// 迭代器的具体实现
}
}
```
如果声明的成员类不需要访问外部类实例,应始终在其声明中添加`static`修饰符,使其成为静态成员类。否则,每个实例都会有一个隐藏的额外引用指向其外部类实例,这会占用时间和空间,还可能导致内存泄漏。
匿名类没有名称,在使用点同时声明和实例化。它只能在表达式合法的任何代码点使用。匿名类在非静态上下文中有外部类实例,且在静态上下文中除了常量变量外不能有其他静态成员。匿名类有很多限制,如只能在声明点实例化、不能进行`instanceof`测试、不能声明实现多个接口或同时扩展类和实现接口等。在Java添加`lambda`表达式之前,匿名类是动态创建小函数对象和处理对象的首选方式,但现在`lambda`表达式更受青睐。
局部类是四种嵌套类中使用最少的,它可以在局部变量可以声明的几乎任何地方声明,遵循相同的作用域规则。局部类与其他嵌套类有共同的属性,如像成员类一样有名称且可重复使用,像匿名类一样在非静态上下文中有外部类实例且不能包含静态成员,且应保持简短以不影响可读性。
选择嵌套类的建议总结如下:
| 嵌套类类型 | 使用场景 |
| ---- | ---- |
| 成员类 | 若嵌套类需要在单个方法之外可见或太长而不适合放在方法内 |
| 非静态成员类 | 成员类的每个实例都需要外部类实例的引用 |
| 静态成员类
0
0
复制全文
相关推荐









