Java从入门到放弃(三十一):作用域

在Java编程语言中,访问修饰符是一类非常关键的关键字,它们用来定义类、方法、变量等成员的访问级别,从而控制不同代码组件之间的交互和封装性。主要有publicprotectedprivate三种访问修饰符。

public

使用public修饰的类、接口或者成员可以被任何其他类访问,不受包的限制。这意味着,当你想提供一个API给其他开发者使用时,通常会将其设置为public

例如,定义在一个包中的publicHello

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类在同一个包中,它也无法直接调用Hellohi方法:

package xyz;

public class Main {
    public void main(String[] args) {
        Hello h = new Hello();
        // 下面的代码会编译错误,因为hi方法是私有的
        // h.hi();
        h.hello(); // 调用公共方法,间接执行私有方法
    }
}

然而,Java允许在类内部定义嵌套类,这些嵌套类可以访问包含它们的外部类的private成员。在上面的例子中,Main类定义了一个静态内部类Inner,这个内部类可以访问外部类Mainprivate静态方法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类继承自Helloprotected方法hi。这种继承关系确保了protected成员的访问权限仅在类的继承结构内部传递,而不扩展到其他非子类的关系中。

package

在Java中,包作用域是指当一个类、字段或方法没有明确的访问修饰符(如publicprotectedprivate)时,它们的可见性限制在同一个包内。这意味着,如果一个类或其成员声明在某个包内,而不带有访问修饰符,那么它们只能被同一包中的其他类访问。

例如,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.apachecom.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方法。变量slen的作用域从它们各自的声明点开始,一直到方法结束。变量p的作用域限定在if语句块及其嵌套块内,而变量i的作用域则限定在for循环块内。

final

在Java中,final修饰符是一个非常有用的关键字,它可以用于类、方法和变量,以提供不可变性的特性。使用final修饰的元素一旦被初始化后,就不能被改变。

  1. final修饰类 当final修饰符用于类定义时,这个类不能被继承。这意味着没有其他类可以继承这个final类。这通常用于创建不可变的类,确保类的安全性和稳定性。
package abc;

public final class Hello {
    // 这个类不能被继承
    private int n = 0;
    // 这个方法也不能被子类覆写
    protected void hi(int t) {
        // 方法体
    }
}
  1. final修饰方法 使用final修饰符修饰的方法不能被子类覆写。这确保了方法的行为在继承层次中保持一致,防止子类改变父类方法的实现。
package abc;

public class Hello {
    // 这个方法是final的,不能被子类覆写
    protected final void hi() {
        // 方法体
    }
}
  1. final修饰字段 final修饰的字段必须在声明时或构造器中初始化,一旦赋值后,其值就不能被改变。这样的字段是只读的,保证了字段值的不变性。
package abc;

public class Hello {
    // 这个字段是final的,一旦赋值就不能被改变
    private final int n = 0;
    // 尝试在方法中改变final字段的值会导致编译错误
    protected void hi() {
        this.n = 1; // 编译错误
    }
}
  1. final修饰局部变量 同样地,final也可以修饰局部变量。一旦final局部变量被赋予一个值,它就不能被重新赋值。
package abc;

public class Hello {
    protected void hi(final int t) {
        // 尝试在方法中改变final局部变量的值会导致编译错误
        t = 1; // 编译错误
    }
}

最佳实践

在Java编程中,封装是一种基本的面向对象原则,它强调将数据(字段)和行为(方法)捆绑在一起,并对外界隐藏其内部实现细节。遵循这一原则,我们可以最小化类对外界的暴露程度,从而增强代码的安全性和可维护性。

  1. 谨慎使用public修饰符:当你在设计一个类时,如果不确定某个方法或字段是否需要公开给其他类使用,那么最好不要将其声明为public。默认情况下,类中的成员(字段和方法)是包级别的,即它们只能被同一个包中的其他类访问。这样的设计使得类具有更好的封装性,同时也减少了外部对类内部实现的干扰。

  2. 利用包级别的访问权限进行测试:在进行单元测试时,通常会将测试类和被测试类放在同一个包中。这样做的好处是,测试类可以直接访问被测试类的包级别方法,而无需将这些方法声明为public。这不仅保证了被测试类的封装性,也使得测试代码能够访问并测试私有或保护级别的方法。这种方法有助于编写更加全面和准确的测试用例。

  3. 一个.java文件中的公共类和非公共类:在Java中,每个.java文件只能包含一个public类,并且该文件的名称必须与public类的名字相匹配。这意味着,如果你需要在一个文件中定义多个类,只能有一个是public的,其他类则可以是非公共的(默认为包级别访问权限)。这种做法有助于避免不必要的公开类和方法,同时也使得代码组织更加清晰和有条理。

 

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧