Effective Java:类和接口

类和接口是Java程序设计的核心,它们也是Java语言的基本抽象单元。Java语言提供了许多强大的基本元素,供程序员用来设计类和接口。本章阐述的一些指导原则,可以帮助你更好地利用这些元素,设计出更加有用、健壮和灵活的类和接口。

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

设计良好的模块会隐藏所有的实现细节,把它的API与它的实现清晰地隔离开来。然后,模块之间只通过他们的API进行通信,一个模块不需要知道其他模块的内部工作情况。这个概念被称为信息隐藏或者封装(encapsulation),是软件设计的基本原则之一。

封装能够有效地在各个模块之间解耦合,使得模块能够独立地开发、测试、优化、使用、理解和修改。这样可以加快系统开发的速度,因为这些模块之间依赖很少,能够并行开发。并且减轻了维护负担,在调试它们的时候不影响其他模块。封装还提高了软件的可复用性,因为模块之间依赖很少,除了开发这些模块所使用的环境之外,它们在其他的环境中往往也很有用。最后封装也降低了构建大型系统的风险,因为即使整个系统不可用,但是这些独立的模块却有可能是可用的。

Java提供了许多机制来协助信息隐藏。访问控制机制决定了类、接口和成员的可访问性。实体的可访问性是由该实体声明所在的位置、以及该实体声明中所出现的访问修饰符共同决定的。

规则一:尽可能使每个类或者成员不被外界访问。换言之,应该使用与你正在编写的软件的对应功能相一致的、尽可能最小的访问级别。

如果一个包级私有的顶层类(或者接口)只是在某一个类的内部被用到,就应该考虑使它成为唯一使用它的那个类的私有嵌套类。这样可以将它的访问范围从包中的所有类缩小到了使用它的那个类。

创建和销毁对象部分中提到了开发中有个实例,我需要为一个创建PDF的方法提供一个现成的对象,创建这个对象的构建器只有在组装这个对象的地方才会用到,因此没有必要像我目前这样,单独声明一个构建器的包级访问类。

如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别。

如果一个类实现了一个接口,那么接口中所有的类方法在这个类中也都必须声明为公有的,因为接口中的所有方法都隐含着公有访问级别。

实例域决不能是公有的。如果域是非final的,或者是一个指向可变对象的final引用,那么一旦这个域成为公有的,就放弃了对存储在这个域中的值进行限制的能力,这意味着,你也放弃了强制这个域不可变的能力。同时,当这个域被修改的时候,你也是去了对它采取任何行动的能力。因此,包含公有可变域的类并不是线程安全的。即使域是final的,并且引用不可变的对象,当把这个域变成公有的时候,也就放弃了“切换到一种新的内部数据表示法”的灵活性。

同样的建议也适用于静态域,只是有一种例外情况。假设常量构成了类提供的整个抽象中的一部分,可以通过公有的静态final域来暴露这些常量。按惯例,这种域的名称由大写字母组成,单词之间用下划线分开。很重要的一点就是,这些域要么包含基本类型的值,要么包含指向不可变对象的引用。如果final域包含可变对象的引用,它便失去了final域的特性,虽然本身引用不可修改,但是所指向的对象却可以被修改,这个后果比较严重。

长度非0的数组总是可变的,因此类具有公有的静态final数组域,或者返回这种域的访问方法,这几乎总是错误的。如果类具有这样的域或者访问方法,客户端将能够修改数组中的内容。这是安全漏洞的一个常见的根源:

public static final Thing[] VALUES = { ... };

要注意许多IDE会产生返回指向私有数组域的引用的访问方法。修正这个问题有两种方法,一种是使公邮数组变成私有的,并增加一个公有的不可变列表:

private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

另一种方法是使数组变成私有的,并添加一个公有方法,它返回私有数组的一个备份:

private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values(){
    return PRIVATE_VALUES.clone();
}

总而言之,你应该始终尽可能地降低可访问性。在设计一个最小的公有API之后,应该防止把任何散乱的类、接口和成员变成API的一部分。除了公有静态final域的特殊情形之外,公有类都不应该包含公有域。并且要确保公有静态final域所引用的对象都是不可变的。

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

在编写一些bean的时候,我通常的习惯是将所有的属性都私有化,能够提供给外界访问的属性都为其创建公有访问方法——getter和setter:

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;}

如果类可以在它所在的包的外部进行访问,就提供访问方法,以保留将来改变内部表示法的灵活性。如果公有类暴露了它的数据域,想要在将来改变其内部表示法是不可能的,因为公有类的客户端代码已经遍布各处了。

然而,如果类是包级私有的,或者是私有的嵌套类,直接暴露它的数据域并没有本质的错误。比起访问方法的做法,直接访问数据域反而更不会产生视觉混乱。

让公有类直接暴露域虽然不是好的做法,但如果域是不可变的,这种做法的危害就比较小一些。如果不改变类的API,就无法改变这种类的表示法,当域被读取的时候,也无法采取任何辅助行动,但是可以强加约束条件。例如,这个类确保了每个实例都表示一个有效时间:

//Public class with exposed immutable fields —— questionable
public final class Time{
    private static final int HOURS_PER_DAY    = 24;
    private static final int MINUTES_PER_HOUR = 60;

    public final int hour;
    public final int minute;

    public Time(int hour, int minute){
        if(hour < 0 || hour >= HOURS_PER_DAY)
            throw new IllegalArgumentException("Hour:" + hour);
        if(minute < 0 || minute > MINUTES_PER_HOUR)
            throw new IllegalArgumentException("Min:" + minute);
        this.hour = hour;
        this.minute = minute;
    }
}

总之,公有类永远都不应该暴露可变域。暴露不可变域虽然危害比较小,但还是有问题的。有时候会需要用保级私有的或者私有的嵌套类来暴露域,无论这个类是可变的还是不可变的。

第15条:使可变性最小化

第16条:复合优先于继承

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

第18条:接口优于抽象类

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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值