第十五条:最小化类和成员的可访问性

每日一问:为啥要强调可访问性?

就像有些小区可以随时进入,保安看到也不会问你是不是小区里的,有的小区会问你是不是业主道理一样的。你猜猜看可访问性越小好还是越大好?

区别设计良好的模块和设计不好的模块,最重要的因素在于,这个模块对于外部的其他模块而言,是否隐藏其内部数据和其他实现细节。设计良好的模块会隐藏所有的实现细节,把它的API于它的实现清晰地隔离开来。然后,模块之间通过它们的API进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念称为信息隐藏或封装。

封装的重要性在于:可以有效地解除组成系统的各模块之间的耦合关系,使得这些模块可以独立地开发、测试、优化、使用、理解和修改。封装本身不会带来更好的性能,但是它可以有效地调节性能:一旦完成一个系统,并通过剖析确定哪些模块影响了系统的性能,这部分模块就可以被进一步优化,而不影响其他模块的正确性,提高软件的可重用性

有个简单的经验法则:尽可能使每个类或成员不可访问。

对于顶层的(非嵌套的)类和接口,只有两种可能的访问级别,包级私有的和公有的。如果类或者接口可以被做成包级私有,就应该被做成包级私有,这样它实际上成了包的实现的一部分,而不是该包导出的API的一部分,在以后的发行版本中,可以对它进行修改、替换、删除,而无需担心影响现有的客户端程序。如果被做成公有的,就有责任永远支持它,以保持兼容性。
如果一个包级私有的顶层的类(或者接口)只是在某一个类的内部可以被用到,就应该考虑使它成为唯一使用它的那个类的私有嵌套类。这样可以进一步缩小可访问范围。

举个现实项目中的例子:

public class Person {
    private int age;
    private String name;
}

在现实项目中设置entity将参数全部设置成private类型,而不是public类型。

对于成员(域、方法、嵌套类和嵌套接口)有四种可能的访问级别:
1 私有的(private):只有在声明该成员的顶层类内部才可以访问这个成员。
2 包级私有的(package-private):声明该成员的包内部的任何类都可以访问这个成员。如果没有为成员指定访问修饰符,就采用这个访问级别,“缺省”。
3 受保护的(protected):声明该成员的子类可以访问这个成员(即使不在同一包),并且声明该成员的包内部任何类也可以访问这个成员。
4 公有的(public):在任何地方都可以访问该成员。

有一条规则限制了降低方法的可访问性的能力,如果方法覆盖了超类的一个方法,子类的访问级别不允许低于超类中的访问级别。
这样可以确保任何可使用超类的实例的地方都可以使用子类的实例。
比如,如果一个类实现了一个接口,那么接口中所有的类方法在这个类中也都必须被声明为公有的(接口的所有方法隐含公有访问级别)。【以上这条在面试八股文中重写和重载时会体现出来】

公有类的实例字段尽量不要设计为公有的。如果域是非final,或者是一个指向可变对象的final引用,那么一旦使这个域成为公有的,就放弃了对存储在这个域中的值进行限制的能力。因此,包含公有可变字段的类通常不是线程安全的。即使域是final的,并且引用不可变的对象,当把这个域变成公有的时候,也就放弃了“切换到一种新的内部数据表示法”的灵活性(因为公有的是导出API,必须始终坚持这种数据表示,保证兼容)。

请注意,长度非零的数组总是可变的,所以,类具有公有的静态final数组域,或者返回这种域的访问方法,这几乎是错误的。这是安全漏洞的一个常见根源:

public class TestDemo {
    // 存在安全漏洞
    public static final String[] VALUES = {"1", "2", "3"};


    public static void main(String[] args) {
        TestDemo UF = new TestDemo();
        UF.VALUES[1] = "8";// 设置final数组成员
        for (int i = 0; i < VALUES.length; i++) {
            System.out.println(UF.VALUES[i]);
        }
    }

}

结果:改变了

1
8
3

Process finished with exit code 0

两种解决方法:

1 把公有的数组变成私有的,并添加一个不可变列表:

public class TestDemo {


    // 消除安全漏洞
    private static final String[] PRIVATE_VALUES = {"1", "2", "3"};
    public static final List<String> PVALUES = Collections
            .unmodifiableList(Arrays.asList(PRIVATE_VALUES));

    public static void main(String[] args) {
        TestDemo UF = new TestDemo();
        System.out.println(UF.PVALUES);
        UF.PVALUES.add(1, "9");
        System.out.println(UF);


    }

}

结果:直接说不可变

[1, 2, 3]
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.Collections$UnmodifiableList.add(Collections.java:1506)
	at com.example.alibaba.test.TestDemo.main(TestDemo.java:18)

Process finished with exit code 1

2 另一种是把数组变成私有的,并添加一个公有的方法,让他返回该私有数组的一个副本:

public class TestDemo {


    // 消除安全漏洞
    private static final String[] PRIVATE_VALUES = { "1", "2", "3" };
    public static final String[] values () {
        return PRIVATE_VALUES.clone();
    }

    public static void main(String[] args) {
        TestDemo testDemo = new TestDemo();
        String[] UF = testDemo.values();
        for (int j = 0; j < UF.length; j++) {
            System.out.println(UF[j]);
        }



    }

结果:

1
2
3

Process finished with exit code 0

开头答案:可访问当然时越小越好!

总而言之,应该尽可能的降低程序元素的可访问性。在精心设计了一个最小的公有API后,应该防止任何游离的类、接口或者成员为这个API的一部分。除了作为常量的公有静态final字段外,公有类不应该有任何公有的字段。请确保公有的静态final字段所引用的对象时不可变的。

所有文章无条件开放,顺手点个赞不为过吧!

                                    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值