从源码学习访问控制符使用
Java中的访问控制符
在Java中,有四个访问控制符:public、protected、default(默认或缺省,不使用关键字)和private。
它们的访问范围如下:
- public:公共访问权限,具有最宽松的访问权限。可以被任何类访问,无论是否在同一个包中。
- protected:受保护的访问权限。可以在同一个包中的其他类中被访问,以及在不同包的子类中访问。但是,它不能被同一个包中的非子类类访问。
- default:默认的访问权限,没有使用任何关键字。包级访问权限,只能在同一个包中的其他类中访问。
- private:私有的访问权限,具有最严格的访问权限。只能在定义它的类中访问,其他任何类都无法访问。
下面是四个访问控制符的访问范围总结:
访问控制符 | 同一个类 | 同一个包 | 不同包的子类 | 不同包的非子类 |
---|---|---|---|---|
public | Yes | Yes | Yes | Yes |
protected | Yes | Yes | Yes | No |
default | Yes | Yes | No | No |
private | Yes | No | No | No |
通过合理使用访问控制符,可以控制类、方法、变量等成员的访问权限,从而提高代码的封装性和安全性。
访问控制符,可以用来修饰类、方法和变量。使用不同的控制符,可以达到不同的权限控制效果。
根据迪米特法则,也即最少知道原则,我们应该尽可能少的暴露内部细节,只需要暴露需要对外暴露的部分,已提高封装性和安全性。接下来就从源码层面,看下访问控制符是如何实际使用。
修饰类
-
public修饰的类,是公共类,不同包下可以访问的类,也是最常见的类修饰符。比如Collections类,需要它的时候,我们随时都可以访问到。
-
protected修饰符不能直接用于类。(内部类允许但很少用)
-
default修饰的类,具有包级访问权限,可以用来限定此类只在本包内使用。
如SignedMutableBigInteger类,它就使用了缺省的控制符,它只在包内被使用。具体到default修饰的类的场景,有以下的情况
-
封装性:可以隐藏其实现细节,只对同一个包内的其他类可见。这种封装性可以防止其他包的类直接访问该类,从而保护类的内部状态和行为。
-
模块化和组织:可以帮助开发者将相关的类组织在同一个包内,使得代码更加清晰和易于维护。
-
API设计:有时候某个类的功能仅适用于同一个包内的其他类,不适合对外公开。这样的类可以被声明为包级访问权限,以避免误用和滥用。
-
-
private修饰符不能直接用于类。但可以用来修饰内部类,表示此内部类仅能在此类中使用。
比如Hashtable中的EntrySet类,就是被private修饰的内部类。通常一个类被设计为private修饰的内部类,往往有以下考虑:
可以实现封装和隐藏内部细节,提高代码的可维护性和安全性。
修饰方法
-
public修饰方法,表示是公开的方法,不同包下可以调用到此方法,这也是最常见的方法修饰符
-
protected修饰的方法,可以被包内访问,同时也可以被包外的子类访问。提供了一种介于公共访问和私有访问之间的访问级别。在继承、封装和包内访问等场景下起到了重要的作用,帮助实现类的继承、封装和扩展。
例如AbstractList的removeRange方法就满足上述用法。
当一个方法被protected修饰时,通常是基于以下场景:
-
继承和重写:protected修饰的方法可以被子类继承和重写。这样可以在子类中对父类方法进行自定义实现,扩展或修改其行为。
-
子类调用:protected修饰的方法可以在父类中调用,同时也可以在子类中通过super关键字调用。这使得父类可以在特定情况下,控制子类对方法的访问权限,提供更加灵活的设计和实现。
-
-
default修饰的方法,可在同一个包中的其他类中访问。
它限制了方法的访问范围,只允许同一个包中的类使用该方法,提高了类的封装性和安全性。
同时,这些方法也没有被声明为public,避免了对外部类的不必要暴露。
-
private修饰的方法,只能在当前类中被使用。
-
比较常用的是视为是内部方法,不对外暴露。
比如LinkedHashMap的一个private修饰的方法,就只在其内部调用使用,隐藏了内部细节,没有对外暴露。
-
当一个构造方法被private修饰时,可以确保类示例不可对外通过new的创建,这可以用来实现单例模式或工厂模式等。
比如DirectMethodHandle类,就是将其构造方法私有化,然后通过静态的外部接口来获取示例,来实现工厂模式。
-
还有工具类或者辅助类也使用private修饰构造方法。这些类不需要实例化对象,只提供静态方法或者静态字段来完成特定的功能。
比如Math就是一个工具类,它将构造方法私有,然后通过静态方法提供所需的功能。
-
修饰变量
-
public修饰的变量可以被任何类访问。当需要将变量暴露给外部类或其他模块使用时,可以使用public修饰。
public修饰的变量通常用于定义常量,以供其他类直接使用。常量在Java中通常使用static final修饰,例如公共的静态常量。
比如Math类中的常量E和PI,用public修饰,可以供外部直接调用。
需要注意的是,公共变量的可见性较高,可以被任何类直接访问和修改。这可能导致类的内部状态被不合理地修改,破坏了封装性。
因此,在设计和使用公共变量时,需要慎重考虑其对类的封装性和安全性的影响。建议在设计类时,尽量使用私有变量,并提供公共的访问方法(getter和setter)来控制对变量的访问和修改。
-
protected修饰的变量,一般是为了给继承类提供一些内部数据结构或状态的访问权限,而不是直接暴露给外部使用。
因此,在使用这些变量时,仍然需要遵循面向对象的封装原则,尽量减少直接访问这些变量的场景,而是通过提供合适的方法来进行操作。
例如Calendar的time字段,它被
protected
修饰,是为了方便继承类访问和修改日历的时间。 -
default较少用来修饰变量。
一些使用了默认修饰符,但它们通常被声明为transient(瞬态),表示它们在序列化过程中将被忽略。这是因为在序列化过程中,只有具有public或private修饰符的变量才会被序列化和反序列化。
其次,还有一些常量,使用了默认修饰符,用来表示可在同一个包下访问此静态常量。
-
private修饰的变量,只能在本类中使用,往往视为类的内部变量,不对外暴露。如果要访问,往往通过暴露的公共方法来访问。
比如图中Calendar中,被private修饰的变量和常量,都只在类的内部使用。
使用private修饰变量或方法是一种常见的封装和隐藏内部细节的方式。这样做有以下几个考虑:
- 封装和隐藏:private修饰符可以将变量或方法隐藏在类的内部,不允许外部类直接访问。这样可以有效地封装类的内部实现细节,避免外部类对内部实现的依赖和直接操作,提高代码的可维护性和安全性。
- 访问控制和安全:使用private修饰符可以限制对变量或方法的访问范围,只允许本类中的其他方法访问。这样可以更好地控制类的使用方式,避免不相关的类直接访问和操作内部状态,减少耦合性。
- 数据封装:private修饰符可以将变量封装在类的内部,通过公共的getter和setter方法来访问和修改变量的值。这样可以控制访问方式和对变量的操作,实现数据的封装和保护。
需要注意的是,尽管使用private修饰符可以隐藏内部细节,但仍然可以通过反射机制来绕过访问限制。
因此,在设计类时,除了使用private修饰符外,还需要考虑其他安全性措施,如不可变性、防御性编程和安全检查等。