面试题答案
一键面试Java访问控制符与类的封装性
-
访问控制符概述
- Java有四种访问控制符:
public
、protected
、default
(默认,不写关键字)和private
。 private
:- 被
private
修饰的成员(字段、方法等)只能在本类内部访问。这是实现封装性最关键的访问控制符。例如,一个类Person
有私有字段private String name
,外部类无法直接访问name
字段,只能通过Person
类提供的公共方法(如public String getName()
和public void setName(String name)
)来间接操作该字段。这样就隐藏了数据的具体存储方式,只暴露必要的接口给外部,实现了数据封装。
- 被
default
:- 当成员没有明确指定访问控制符时,即为
default
访问权限。具有default
权限的成员可以被同一包内的其他类访问,但不能被其他包的类访问。例如,在一个com.example.util
包下有类Utils
,其内部default
修饰的方法void internalMethod()
可以被同一包内的其他类调用,但如果在com.example.main
包下的类,即使导入了com.example.util.Utils
,也无法访问internalMethod()
。这有助于将相关功能在包内进行封装,限制外部不必要的访问。
- 当成员没有明确指定访问控制符时,即为
protected
:protected
修饰的成员,除了可以被同一包内的类访问外,还可以被不同包中的子类访问。比如,有一个Animal
类在com.example.animals
包中,它有一个protected
方法void move()
。在com.example.domestic
包中有一个Dog
类继承自Animal
,Dog
类就可以访问Animal
类的move()
方法。这在实现继承体系下的封装与扩展时很有用,既保证了一定的封装性,又允许子类进行合理的扩展。
public
:public
修饰的成员可以被任何类访问,无论该类在哪个包中。一般用于对外提供的接口,例如一个工具类MathUtils
中有一个public static int add(int a, int b)
方法,其他任何类只要导入了包含MathUtils
的包,都可以调用这个方法。但过多使用public
可能会破坏封装性,因为外部可以随意访问和修改相关成员。
- Java有四种访问控制符:
-
通过访问控制符实现封装性的好处
- 数据保护:防止外部类随意修改对象的内部状态,保证数据的一致性和完整性。例如,上述
Person
类中,通过private
修饰name
字段,避免了外部非法设置name
为空字符串等不合理情况,只有通过合理的setName
方法进行校验后才能修改。 - 内部实现隐藏:外部类只关心类提供的功能(接口),不关心其内部实现细节。如果
Person
类内部存储name
的方式从String
改为CharSequence
,只要getName
和setName
方法的接口不变,外部类不受影响,提高了代码的可维护性和可扩展性。
- 数据保护:防止外部类随意修改对象的内部状态,保证数据的一致性和完整性。例如,上述
在大型项目模块化设计中合理使用访问控制符优化模块间交互并避免耦合
-
模块划分与访问控制
- 模块内封装:在每个模块内部,尽可能将内部实现细节设置为
private
或default
访问权限。例如,一个电商项目中有一个订单模块order - module
,模块内订单数据处理的具体类OrderProcessor
,其内部用于计算订单总价的私有方法private double calculateTotalPrice(Order order)
,只在OrderProcessor
类内部使用,不需要暴露给其他模块,这样避免了外部模块对该实现细节的依赖。 - 模块间接口定义:通过
public
定义模块对外提供的接口。例如,订单模块对外提供一个OrderService
接口,其中的方法如public Order createOrder(User user, List<Product> products)
等,其他模块通过这个公共接口与订单模块交互,而不需要了解订单模块内部复杂的实现逻辑,降低了模块间的耦合度。 - 合理使用
protected
:如果模块间存在继承关系,例如在一个基础模块base - module
中有一个基础类BaseEntity
,其他模块(如user - module
)可能有类继承自BaseEntity
。此时,BaseEntity
中一些需要被子类扩展的方法可以设置为protected
,如protected void init()
方法,既保证了在基础模块内的封装性,又允许其他模块的子类合理扩展。
- 模块内封装:在每个模块内部,尽可能将内部实现细节设置为
-
示例 假设我们有一个大型游戏开发项目,包含图形渲染模块(
graphics - module
)、游戏逻辑模块(game - logic - module
)和用户输入模块(input - module
)。- 图形渲染模块:
- 内部的图形绘制算法类
GraphicsRenderer
,其内部计算图形坐标的方法private Point calculateCoordinates(GraphicObject object)
设置为private
,因为这是图形渲染模块的内部实现细节,不需要被其他模块访问。 - 对外提供
public
接口GraphicsService
,其中有方法public void renderScene(Scene scene)
,游戏逻辑模块通过这个接口将需要渲染的场景传递给图形渲染模块,而不关心图形渲染的具体实现。
- 内部的图形绘制算法类
- 游戏逻辑模块:
- 游戏角色类
Character
中一些控制角色内部状态变化的方法如private void updateState()
设置为private
,只在Character
类内部使用。 - 对外提供
public
接口GameLogicService
,例如public void startGame()
方法,用户输入模块可以调用这个方法来启动游戏,通过这种方式,用户输入模块只与游戏逻辑模块的公共接口交互,减少了与游戏逻辑模块内部细节的耦合。
- 游戏角色类
- 用户输入模块:
- 处理键盘输入的内部类
KeyboardInputHandler
中,处理单个按键事件的方法private void handleKeyPress(KeyEvent event)
设置为private
。 - 对外提供
public
接口InputService
,如public void registerInputListener(InputListener listener)
,其他模块(如游戏逻辑模块)可以通过这个接口注册输入监听器,实现与用户输入模块的交互,而不依赖于用户输入模块内部处理输入的具体方式。
- 处理键盘输入的内部类
- 图形渲染模块:
通过合理使用访问控制符,每个模块实现了良好的封装,模块间通过公共接口进行交互,有效避免了不必要的耦合,提高了大型项目的可维护性和可扩展性。