从设计思想入手,深入理解java的访问权限控制

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

“java为什么需要设计访问权限控制?

“什么?”第一次,我没有听太明白。

“就是为什么要使用public、private这些关键字来实现方法和属性的访问控制。”面试官将问题描述得更具体了一些。

以上来自一次真实的面试,面试官没有问我怎样保证缓存和数据库的一致性,没有问我怎样实现MQ集群宕机的降级方案,也没有问我怎么保证系统99.99%的高可用性。

而是问了一个特别基础的问题。

我明明知道,访问权限控制是Java开发必不可少的设计,并且每天我都在coding时使用,也清楚地明白四种访问控制权限的用途。

但是为什么java语言会需要设计这样的机制呢?这个问题我从来没有想过。

于是我补充了基础知识储备库,从设计思想的角度进行了深入的理解,下一次我会这样回答。

设计访问控制的原因

  • 1.设定程序调用方可以使用和不可以使用的范围。
  • 2.让类的设计者可以更改类的内部实现。

1.设定使用范围

对于类的内部实现,在进行程序设计时,设计者不愿意(或者认为没有必要)让调用方使用的属性和方法,就可以使用private修饰,这样一来指定的方法和属性就仅能在类的内部使用。

本文从方法层面来说明访问控制机制,从单一的角度加以说明,会更加容易理解。

这里以String举例,String类中有很多方法,但能允许我们访问的只有一部分而已。

下图为String类的部分方法,在idea中通过快捷键 Alt+7,可查看所有方法。

经过统计,在java版本1.8.0_77中,String类总共有92个方法。

从设计思想入手,深入理解java的访问权限控制
image-20201231185600484

而我们能够调用的,却只有52个而已。(下图为部分可调用方法)

从设计思想入手,深入理解java的访问权限控制
image-20201231185848314

而这其中,不允许被调用的40个方法,为什么会被定义成private类型的,不允许在其他类中使用呢?

我们来看一个例子。

/* Common private utility method used to bounds check the byte array
* and requested offset & length values used by the String(byte[],..)
* constructors.
*/
private static void checkBounds(byte[] bytes, int offset, int length) {
 if (length < 0)
    throw new StringIndexOutOfBoundsException(length);
    if (offset < 0)
    throw new StringIndexOutOfBoundsException(offset);
    if (offset > bytes.length - length)
    throw new StringIndexOutOfBoundsException(offset + length);
}

checkBounds()方法,是我从String类中随意抽取的一个被private修饰的方法。

第一眼看上去是不是不明白是什么意思?没关系,首先读注释。

Common private utility method used to bounds check the byte array and requested offset & length values used by the String(byte[],..) constructors.

翻译:用于检查字节数组和字符串(byte[],…)构造函数使用的请求的偏移量和长度值的公共专用工具方法。

从方法的注释大概可以看出,这是String类中用于字节数组和字符串的安全检查的方法。

至于方法具体是什么地方调用、为了解决什么问题的,除非我们是为了研究String的源代码,否则没有必要再看下去。

因为,我们已经可以得出一个结论,checkBounds()方法并不是我们在程序开发的过程中需要关心的方法,所以它被定义成了private。

52个可用的方法,已经足够我们使用了,只关注必要的方法可以降低学习类库的成本。

所以,使用访问控制,可以让程序员关注只需要关注的部分,明确自己可操作和不可操作的权限范围。

2.更改类的内部实现

设置访问权限的另一点,是为了可以随意更改我们提供的java类,所以我们在进行程序开发时,只提供必要的public方法。

在此用反例来说明这一点,如果一个工具类中,所有的方法和属性都是public时,会发生什么情况。

当前有一个登陆工具类,类中有三个方法如下:

  • public User login(String userName,String pwd){ ... }    登陆方法
  • public ListgetRoles(){ ... }  根据当前用户,获取角色信息
  • public String encryption(String pwd){ ... }  加密方法,根据明文,返回密文

此时工具类投入使用,而事实上,工具类的使用方在开发时只需要用到登陆和获取角色的方法,至于加密的方法根本就不是设计给其他类调用的,加密方法的用途仅仅是登录时将用户传递的明文转换成密文,在login()内部使用。

此处我们的加密算法使用的是md5,恰好此时有一个需求是记录用户设置的某文档密码,组内的小伙伴一看登录工具类中提供了加密方法,直接就拿来用了!!

就在这时,我们意识到md5加密算法不能保证足够强的安全性,需要引入一个成熟又强大的工具包来进行密码的加密处理。

所以我们把加密方法encryption()从登陆工具类删除了。

紧接着,悲剧了,项目里所有用到encryption()方法的地方,全都报错了... ...

而如果我们一开始就将encryption()定义为private,也就不会发生这样的悲剧了。

借这个例子,我们就能理解java语言的设计者,要设计访问控制的第二个原因:为了让类的设计者能够修改类的内部工作方式(可以任意修改内部实现)

四个级别的权限控制

  • public修饰的变量和方法,可以在任意地方被调用,权限最为宽松。
从设计思想入手,深入理解java的访问权限控制
image-20201229222654449
  • private与public相反,只能在同一个类的内部访问,访问控制最为严格。
从设计思想入手,深入理解java的访问权限控制
image-20201229222544937
  • 包权限,当在开发过程中,变量和方法没有使用任何权限修饰词时,默认就是此权限。变量和方法可被同一个包下的类访问。
从设计思想入手,深入理解java的访问权限控制
image-20201229222246534

以上三种级别的权限控制都十分易于理解,并且在大部分情况下都足够使用了,但是却不能非常好的解决继承情况下的访问权限控制。

比如以下这种情况。

从设计思想入手,深入理解java的访问权限控制
image-20210101174848570

Animal类中有eat()和run()两个方法,Dog继承自Animal并且希望拥有Animal的所有方法,可他们又不在同一个包中。

此时的解决方法,是将eat()和run()都用public修饰。但这样一来,所有的类都能调用Animal中的方法,这和我们的初衷相违背,我们只希望Dog能继承到Animal中的方法。

从设计思想入手,深入理解java的访问权限控制
image-20210101175214032

而private修饰的方法只能在当前类的内部调用,外部不能访问也不能继承,不适用当前场景;至于包权限不能使用的原因也很简单,Animal和Dog两个类都不在同一个包下。

现有的三种访问控制不足以满足需要,所以引入了第四个关键字protected。

  • protected的作用就是解决继承关系下,不同包的子类可继承到父类中的方法。
从设计思想入手,深入理解java的访问权限控制
image-20210101175908573

总结

  • private修饰的方法,只能在当前类中使用;
  • public修饰的方法,可以在任意处被调用;
  • 包权限(默认权限)修饰的方法,只能在当前包内被调用;
  • protected修饰的方法,可被任意包下的子类继承;
  • 属性(变量)的使用同上;
  • 访问权限控制的设计思想:
  • 1.明确程序调用方可操作和不可操作的范围;
  • 2.让类的设计者能修改内部实现;

经历过这一次,我意识到作为开发工程师,不仅仅需要知道各种牛逼的解决方案,还应该明白“万丈高楼平地起”的道理,需要重视基础呀。


原文始发于微信公众号(猿月亮):从设计思想入手,深入理解java的访问权限控制