在Java编程语言中,访问修饰符是一类非常关键的关键字,它们用来定义类、方法、变量等成员的访问级别,从而控制不同代码组件之间的交互和封装性。主要有public
、protected
和private
三种访问修饰符。
public
使用public
修饰的类、接口或者成员可以被任何其他类访问,不受包的限制。这意味着,当你想提供一个API给其他开发者使用时,通常会将其设置为public
。
例如,定义在一个包中的public
类Hello
:
package abc;
public class Hello {
public void hi() {
System.out.println("Hello, World!");
}
}
这个Hello
类以及其中的hi
方法都可以被其他包中的类访问。例如,在另一个包xyz
中的Main
类可以这样使用Hello
类:
package xyz;
public class Main {
public void main(String[] args) {
Hello h = new Hello(); // 创建Hello类的实例
h.hi(); // 调用Hello类的hi方法
}
}
private
在Java中,private
访问修饰符用于限制成员(字段或方法)的访问范围,使其只能在定义它的类内部被访问。这意味着其他类无法直接访问这些private
成员,无论它们是否在同一个包中。
例如,Hello
类中的hi
方法被声明为private
,因此它不能被其他类直接调用:
package abc;
public class Hello {
// 私有方法,无法被其他类直接访问
private void hi() {
System.out.println("Private method hi called.");
}
// 公共方法,可以被其他类调用
public void hello() {
// 但是可以通过公共方法间接调用私有方法
this.hi();
}
}
在这个例子中,尽管Main
类在同一个包中,它也无法直接调用Hello
的hi
方法:
package xyz;
public class Main {
public void main(String[] args) {
Hello h = new Hello();
// 下面的代码会编译错误,因为hi方法是私有的
// h.hi();
h.hello(); // 调用公共方法,间接执行私有方法
}
}
然而,Java允许在类内部定义嵌套类,这些嵌套类可以访问包含它们的外部类的private
成员。在上面的例子中,Main
类定义了一个静态内部类Inner
,这个内部类可以访问外部类Main
的private
静态方法hello
:
public class Main {
public static void main(String[] args) {
Inner i = new Inner();
i.hi(); // 静态内部类可以访问外部类的私有静态方法
}
// 私有静态方法
private static void hello() {
System.out.println("Static private hello!");
}
// 静态内部类
static class Inner {
public void hi() {
// 静态内部类可以访问外部类的私有静态方法
Main.hello();
}
}
}
定义在一个class
内部的class
称为嵌套类(nested class
),Java支持好几种嵌套类。
protected
在Java中,protected
关键字是一种访问修饰符,它允许子类以及子类的子类访问其修饰的字段和方法。这种访问控制机制在类的继承结构中非常有用,因为它提供了一种受控的封装方式,使得类的实现细节可以在继承层次中共享,而不暴露给其他不相关的类。
例如,定义在abc
包中的Hello
类有一个protected
方法hi
:
package abc;
public class Hello {
// 子类和子类的子类可以访问这个protected方法
protected void hi() {
System.out.println("Hello from protected method hi.");
}
}
当xyz
包中的Main
类继承自Hello
类时,它可以直接访问继承自父类的protected
方法hi
:
package xyz;
public class Main extends Hello {
void foo() {
// Main作为子类,可以访问父类的protected方法hi
hi();
}
}
进一步地,如果Main
类还有其他子类,这些子类同样能够访问Main
类继承自Hello
的protected
方法hi
。这种继承关系确保了protected
成员的访问权限仅在类的继承结构内部传递,而不扩展到其他非子类的关系中。
package
在Java中,包作用域是指当一个类、字段或方法没有明确的访问修饰符(如public
、protected
或private
)时,它们的可见性限制在同一个包内。这意味着,如果一个类或其成员声明在某个包内,而不带有访问修饰符,那么它们只能被同一包中的其他类访问。
例如,abc
包中有一个没有访问修饰符的类Hello
和一个同样没有访问修饰符的方法hi
:
package abc;
// 没有访问修饰符的类Hello具有包作用域
class Hello {
// 没有访问修饰符的方法hi也具有包作用域
void hi() {
System.out.println("Hello from method hi.");
}
}
在同一个包abc
中的Main
类可以创建Hello
类的实例,并调用其hi
方法:
package abc;
class Main {
void foo() {
// 可以访问并实例化具有包作用域的Hello类
Hello h = new Hello();
// 可以调用包作用域的方法hi
h.hi();
}
}
需要注意的是,包作用域与包的结构密切相关。Java中的包名必须完全匹配,才能被认为是同一个包。例如,com.apache
和com.apache.abc
是完全不同的两个包。因此,只有当两个类位于同一个包中时,它们才能访问彼此的包作用域成员。
局部变量
在Java程序设计中,局部变量是在方法内部、构造器内部或者控制块内部定义的变量。局部变量的作用域被限定在其声明的块内,也就是说,它们只能在定义它们的代码块中被访问和使用。
以Hello
类中的hi
方法为例,我们可以分析其中局部变量的作用域:
package abc;
public class Hello {
void hi(String name) { // 方法参数name是局部变量,作用域从声明处开始,覆盖整个方法体
String s = name.toLowerCase(); // 变量s从声明处开始,一直有效到方法结束
int len = s.length(); // 变量len同样从声明处开始,有效期持续到方法结束
if (len < 10) { // 进入if语句块
int p = 10 - len; // 变量p的作用域仅限于if语句块及其嵌套的块
for (int i = 0; i < 10; i++) { // 进入for循环块
System.out.println(); // 此处的System.out.println()调用并不需要局部变量参与
// for循环结束后,变量i不再有效
} // 循环结束,局部变量i的作用域结束
} // if语句块结束,局部变量p的作用域结束
} // 方法结束,方法参数name、变量s和len的作用域结束
}
从上述代码可以看出,局部变量的作用域是被精确控制的。方法参数name
作为局部变量,其作用域覆盖整个hi
方法。变量s
和len
的作用域从它们各自的声明点开始,一直到方法结束。变量p
的作用域限定在if
语句块及其嵌套块内,而变量i
的作用域则限定在for
循环块内。
final
在Java中,final
修饰符是一个非常有用的关键字,它可以用于类、方法和变量,以提供不可变性的特性。使用final
修饰的元素一旦被初始化后,就不能被改变。
final
修饰类 当final
修饰符用于类定义时,这个类不能被继承。这意味着没有其他类可以继承这个final
类。这通常用于创建不可变的类,确保类的安全性和稳定性。
package abc;
public final class Hello {
// 这个类不能被继承
private int n = 0;
// 这个方法也不能被子类覆写
protected void hi(int t) {
// 方法体
}
}
final
修饰方法 使用final
修饰符修饰的方法不能被子类覆写。这确保了方法的行为在继承层次中保持一致,防止子类改变父类方法的实现。
package abc;
public class Hello {
// 这个方法是final的,不能被子类覆写
protected final void hi() {
// 方法体
}
}
final
修饰字段final
修饰的字段必须在声明时或构造器中初始化,一旦赋值后,其值就不能被改变。这样的字段是只读的,保证了字段值的不变性。
package abc;
public class Hello {
// 这个字段是final的,一旦赋值就不能被改变
private final int n = 0;
// 尝试在方法中改变final字段的值会导致编译错误
protected void hi() {
this.n = 1; // 编译错误
}
}
final
修饰局部变量 同样地,final
也可以修饰局部变量。一旦final
局部变量被赋予一个值,它就不能被重新赋值。
package abc;
public class Hello {
protected void hi(final int t) {
// 尝试在方法中改变final局部变量的值会导致编译错误
t = 1; // 编译错误
}
}
最佳实践
在Java编程中,封装是一种基本的面向对象原则,它强调将数据(字段)和行为(方法)捆绑在一起,并对外界隐藏其内部实现细节。遵循这一原则,我们可以最小化类对外界的暴露程度,从而增强代码的安全性和可维护性。
-
谨慎使用
public
修饰符:当你在设计一个类时,如果不确定某个方法或字段是否需要公开给其他类使用,那么最好不要将其声明为public
。默认情况下,类中的成员(字段和方法)是包级别的,即它们只能被同一个包中的其他类访问。这样的设计使得类具有更好的封装性,同时也减少了外部对类内部实现的干扰。 -
利用包级别的访问权限进行测试:在进行单元测试时,通常会将测试类和被测试类放在同一个包中。这样做的好处是,测试类可以直接访问被测试类的包级别方法,而无需将这些方法声明为
public
。这不仅保证了被测试类的封装性,也使得测试代码能够访问并测试私有或保护级别的方法。这种方法有助于编写更加全面和准确的测试用例。 -
一个
.java
文件中的公共类和非公共类:在Java中,每个.java
文件只能包含一个public
类,并且该文件的名称必须与public
类的名字相匹配。这意味着,如果你需要在一个文件中定义多个类,只能有一个是public
的,其他类则可以是非公共的(默认为包级别访问权限)。这种做法有助于避免不必要的公开类和方法,同时也使得代码组织更加清晰和有条理。