第七章、Java修饰符

类、方法、成员变量和局部变量的可用修饰符:

修饰符成员方法构造方法成员变量局部变量
abstract 抽象的 ✓ \checkmark ✓ \checkmark
static 静态的 ✓ \checkmark ✓ \checkmark
public 公共的 ✓ \checkmark ✓ \checkmark ✓ \checkmark ✓ \checkmark
protected 受保护的 ✓ \checkmark ✓ \checkmark ✓ \checkmark
private 私有的 ✓ \checkmark ✓ \checkmark ✓ \checkmark
synchronized 同步的 ✓ \checkmark
native 本地的 ✓ \checkmark
transient 暂时的 ✓ \checkmark
volatile 易失的 ✓ \checkmark
final 不可改变的 ✓ \checkmark ✓ \checkmark ✓ \checkmark ✓ \checkmark

修饰顶层类的修饰符包括: abstract publicfinal,而 static protectedprivate 不能修饰顶层类。成员方法和成员变量可以有多种修饰符,而局部变量只能用 final 修饰。

7.1 访问控制修饰符

面向对象的基本思想之一是封装实现细节并且公开接口。 Java 语言采用访问控制修饰符来控制类,以及类的方法和变量的访问权限,从而向使用者只暴露接口,但隐藏实现细节。访问控制分 4 种级别:

  • 公开级别: 用 public 修饰,对外 公开。
  • 受保护级别:用 protected 修饰,向 子类同一个包中的类 公开。
  • 默认级别: 没有访问控制修饰符,向 同一个包中的类 公开。
  • 私有级别:用 private 修饰, 只有 类本身 可以访问,不对外公开。
访问级别修饰符同类同包子类不同的包
公开public ✓ \checkmark ✓ \checkmark ✓ \checkmark ✓ \checkmark
受保护protected ✓ \checkmark ✓ \checkmark ✓ \checkmark
默认 ✓ \checkmark ✓ \checkmark
私有private ✓ \checkmark

成员变量、成员方法和构造方法可以处于 4 个访问级别中的一个。顶层类只可以处于公开或默认访问级别。

private class Sample{...}       //编译出错,不能被private修饰
package mypack1;
public class ClassA {
    public int var1;
    protected int var2;
    int var3;
    private int var4;

    public void method() {
        var1 = 1;
        var2 = 1;
        var3 = 1;
        var4 = 1;

        ClassA a = new ClassA();
        a.var1 = 1;
        a.var2 = 1;
        a.var3 = 1;
        a.var4 = 1;
    }
}
class ClassB {
    public void method() {
        ClassA a = new ClassA();
        a.var1 = 1;
        a.var2 = 1;
        a.var3 = 1;
        a.var4 = 1;                 //编译出错
    }
}
package mypack2;
import mypack1.ClassA;
import mypack1.ClassB;
class ClassC extends ClassA {
    public void method() {
        var1 = 1;
        var2 = 1;                   //合法,ClassC 继承 ClassA 的 var2 变量

        ClassA a = new ClassA();
        a.var1 = 1;
        a.var2 = 1;                 
        a.var3 = 1;                 //编译出错
        a.var4 = 1;                 //编译出错
    }
}
class ClassD {
    public void method() {
        ClassA a = new ClassA();
        a.var1 = 1;
        a.var2 = 1;                 //编译出错
        a.var3 = 1;                 //编译出错
        a.var4 = 1;                 //编译出错

        ClassB b = new ClassB();    //编译出错,ClassB默认访问级别
    }
}

在一个类中,可以访问类本身或内部类的实例的私有成员。

class A {
    private int v;

    class B {
        private int v;

        class C {
            private int v;
        }
    }

    void test() {
        A a = new A();
        a.v = 1;                //合法
        
        B b = new B();
        b.v = 1;                //合法

        B.C c = new B().(new C());          //合法
        c.v = 1;
    }
}

7.2 abstract 修饰符

abstract 修饰符可用来修饰类和成员方法:

  • 用 abstract 修饰的类表示 抽象类,抽象类位于继承树的抽象层,抽象类不能被实例化,即不允许创建抽象类本身的实例。没有用 abstract 修饰的类称为具体类,具体类可以被实例化。
  • 用 abstract 修饰的方法表示 抽象方法,抽象方法没有方法体。抽象方法用来描述系统具有什么功能,但不提供具体的实现。没有用 abstract 修饰的方法称为具体方法,具体方法具有方法体。
public abstract class A {           //抽象类
    abstract void method1();        //抽象方法
    void method2() {...}            //具体方法
}

使用 abstract 修饰符需要遵守以下语法规则:

  1. 抽象类中可以没有抽象方法,但包含了抽象方法的类必须被定义为抽象类。如果子类没有实现父类中 所有 的抽象方法,那么子类也必须定义为抽象类,否则编译出错。
    abstract class Base {
        abstract void m1();
        abstract void m2();
    }
    class Sub extends Base {
        void m1() {/* implement */}         //编译出错,Sub类仍需声明为抽象类
    }
    
  2. 没有抽象静态方法, staticabstract 关键字是势不两立的冤家,不能连在一起使用,但抽象类中可以有静态方法。
    abstract class Base {
        static abstract void m1();          //编译出错
        static void me2();                  //合法
    }
    
  3. 抽象类中可以有非抽象的构造方法,创建子类的实例时可能会调用这些构造方法。抽象类不能被实例化,然而可以创建一个引用变量,其类型是一个抽象类,并让它引用非抽象的子类的一个实例。
    abstract class Base{}
    class Sub extends Base {
        public static void main(String[] args) {
            Base base1 = new Base();                //编译出错,不能创建抽象类的实例
            Base base2 = new Sub();                 //合法,可以创建具体子类的实例
        }
    }
    
    所谓的抽象类不能被实例化,是指不能创建抽象类本身的实例,尽管如此,可以创建一个苹果对象,并把它看作是水果对象。
  4. 抽象类及抽象方法不能被 final 修饰符修饰。abstract 修饰符与 final 修饰符不能连用,因为抽象类只有允许创建其子类,它的抽象方法才能被实现,并且只有它的具体子类才能被实例化,而用 final 修饰的类不允许拥有子类,用 final 修饰的方法不允许被子类方法覆盖。
    abstract final class Base1 {}               //编译出错
    abstract class Base2 {
        final abstract void m1();               //编译出错
        final void m2() {...}                   //合法,抽象类中允许拥有 final 类型的具体方法
    }
    
  5. 抽象方法不能被 private 修饰符修饰。这是因为如果方法是抽象的,表示父类只声明具备某种功能,但没有提供实现。这种方法有待于某个子类去实现它。父类中的 abstract 方法必须让子类是可见的。否则,在父类中声明一个永远无法实现的方法是无意义的。

7.3 final 修饰符

final 具有 “不可改变的” 的含义,它可以修饰非抽象类、非抽象成员方法和变量:

  • 用 final 修饰的 不能被继承,没有子类。
  • 用 final 修饰的 方法 不能被子类的方法覆盖。
  • 用 final 修饰的 变量 表示常量,只能被赋一次值。

final 不能用来修饰构造方法,因为 “方法覆盖” 这一概念仅适用于类的成员方法,而不适用于类的构造方法,父类的构造方法和子类构造方法之间不存在覆盖关系,因此用 final 修饰构造方法是无意义的 。父类中用 private 修饰的方法不能被子类的方法覆盖,因此 private 类型的方法默认是 final 类型的。

final 类

继承关系的弱点是打破封装,子类能够访问父类的实现细节 ,而且能以方法覆盖的方式修改实现细节。在以下情况,可以考虑把类定义为 final 类型 ,使得这个类不能被继承:

  • 不是专门为继承而设计的类,类本身的方法之间有复杂的调用关系。假如随意创建这些类的子类,子类有可能会错误地修改父类的实现细节。
  • 出于安全的原因,类的实现细节不允许有任何改动。
  • 在创建对象模型时,确信这个类不会再被扩展。

例如JDK中 java.lang.String 类被定义为final类型:

public final class String {...}
public class MyString extends String {...}      //编译出错,不允许建立子类

final 方法

在某些情况下,出于安全的原因,父类不允许子类覆盖某个方法, 此时可以把这个方法声明为 final 类型。例如,在 java.lang.Object 类中, getClass() 方法为 final 类型,而 equals() 方法不是 final 类型的。所有 Object 的子类都可以覆盖 equals() 方法,但不能覆盖 getClass() 方法。

public class Object {
    public final Class getClass() {...}         //返回包含类的类型信息的Class实例
    public boolean equals(Object o) {...}       //比较参数指定的对象与当前对象是否相同
}

final 变量

用 final 修饰的变量表示取值不会改变的常量。

//在 java.lang.Integer 类中定义的常量:
public static final int MAX_VALUE = 2147483647;
public static final int MIN_VALUE = -2147483648;

final 变量具有以下特征:

  • final 修饰符可以修饰静态变量、实例变量和局部变量,分别表示静态常量、实例常量和局部常量。
  • 类的成员变量可以不必显式初始化,但是 final 类型的变量都必须显式初始化。对于 final 类型的实例变量,可以在定义变量时,或者在构造方法中进行初始化;对于 final 类型的静态变量 ,可以在定义变量时进行初始化,或者在静态代码块中初始化。
    public class Sample {
        static final int a = 1;         //合法
        static final int b;
        static { b = 1; }               //合法
    }
    
  • final 变量只能赋一次值。
  • 如果将引用类型的变量用 final 修饰,那么该变量只能始终引用一个对象,但可以改变对象的内容。
    public class Sample {
        public int var;
        public Sample(int var) {
            this.var = var;
        }
        public static void main (String[] args) {
            final Sample s = new Sample(1);     //合法,定义并初始化 final 类型的引用变量s
            s.var = 2;                          //合法,修改引用变量所引用的 Sample对象的 var属性
            s = new Sample(2);                  //编译出错,不能改变引用变量 s 所引用的 Sample 对象
        }
    }
    

7.4 static 修饰符

static 修饰符可以用来修饰类的成员变量、成员方法和代码块:

  • 用 static 修饰的成员变量表示 静态变量,可以直接通过类名来访问。
  • 用 static 修饰的成员方法表示 静态方法 ,可以直接通过类名来访问。
  • 用 static 修饰的程序代码块表示 静态代码块,当 Java 虚拟机加载类时,就会执行该代码块。

被 static 所修饰的成员变量和成员方法表明归某个类所有,它不依赖于类的特定实例,被类的所有实例共享 。只要这个类被加载,Java 虚拟机就能根据类名在运行时数据区的方法区内定位到它们。

static 变量

类的成员变量有两种 一种是被 static 修饰的变量,叫类变量,或静态变量;一种是没有被 static 修饰的变量,叫实例变量。静态变量和实例变量的区别如下:

  • 静态变量在内存中只有一个备份,运行时 Java 虚拟机只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配,可以直接通过类名访问静态变量。
  • 对于实例变量,每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个备份,互不影响。

在类的内部,可以在任何方法内直接访问静态变量;在其他类中,可以通过某个类的类名来访问它的静态变量。

public class Sample1 {
    public static int var;              //定义静态变量
    public void method() {
        int x = var;                    //类的内部直接访问静态变量
    }
}
public class Sample2 {
    public void method() {
        int x = Sample1.var;            //通过Sample1类名访问var静态变量
    }
}

下面再用具体例子说明静态变量的用法。假设有一群选民进行投票,每个选民只允许投一次票,并且当投票总数达到 100 时,就停止投票。

import java.util.HashSet;
public class Voter {
    private static final int MAX_COUNT = 100;
    private static count;
    private static Set<Voter> voters = new HashSet<Voter>();
    private String name;

    public Voter(String name) {
        this.name = name;
    }

    public void voteFor() {
        if (count == MAX_COUNT) {
            Sytem.out.println("投票结束");
            return;
        }
        if (voters.contains(this))
            Sytem.out.println("不允许重复投票");
        else {
            count++;
            voters.add(this);
            Sytem.out.println("感谢投票");
        }
    }
    public static void printResult() {
        Sytem.out.println("当前票数" + count);
        Sytem.out.println("选民名单:");
        for (Voter v : voters) {
            Sytem.out.println(v.name);
        }
    }

    public static void main(String[] args) {
        Voter tom = new Voter("Tom");
        Voter mike = new Voter("Mike");
        Voter jack = new Voter("Jack");

        tom.voteFor();
        tom.voteFor();
        mike.voteFor();
        jack.voteFor();
        Voter.printResult();                //调用Voter类的静态方法
    }
}

static方法

成员方法分为静态方法和实例方法。用 static 修饰的方法叫静态方法或类方法。静态方法也和静态变量一样,不需创建类的实例,可以直接通过类名来访问。

  • 静态方法可以访问的内容
    • 因为静态方法不需通过它所属的类的任何实例就会被调用,因此在静态方法中不能使用 this 关键字,也不能直接访问所属类的实例变量和实例方法(因为 Java虚拟机无法定位它们所属对象),但是可以直接访问所属类的静态变量和静态方法。假如静态方法需要访问某个特定对象的属性,须通过对应对象的引用来访问其属性。
    • 静态方法中也不能使用 super 关键字。super 关键字用来访问当前子类实例从父类中继承的方法和属性。既然 super 关键字与类的特定实例相关 ,那么和 this 关键字一样,在静态方法中也不能使用 super 关键字。
  • 实例方法可访问的内容
    • 如果一个方法没有用 static 修饰,那么它就是实例方法 。在实例方法中可以直接访问所属类的静态变量、静态方法、实例变量和实例方法。
  • 静态方法必须被实现
    • 静态方法用来表示某个类所特有的功能,这种功能的实现不依赖于类的具体实例,也不依赖于它的子类。既然如此,当前类必须为静态方法提供实现。换句话说,一个静态的方法不能被定义为抽象方法。staticabstract 不能在一起使用。如果一个方法是静态的 , 它就必须自力更生,自己实现该方法;如果一个方法是抽象的,那么它就只表示类所具有的功能,但不会实现它,在子类中才会实现它。
  • 作为程序入口的 main() 方法是静态方法
    • 因为把 main() 方法定义为静态方法,可以使得 Java 虚拟机只要加载了 main() 方法所属的类,就能执行 main() 方法,而无须先创建这个类的实例。
  • 方法的字节码都位于方法区
    • 不管是实例方法,还是静态方法,它们的字节码都位千方法区内。 Java 编译器把Java 方法的源程序代码编译成二进制的编码,称为字节码, Java 虚拟机的解析器能够解析这种字节码。

static 代码块

类中可以包含静态代码块,它不存在于任何方法体中。Java 虚拟机加载类时,会执行这些静态代码块。如果类中包含多个静态块,那么 Java 虚拟机按它们在类中出现的顺序依次执行它们,每个静态代码块只会被执行一次。

public class Sample {
    static int i = 0;
    static {
        System.out.println("first static code" + i);
        i++;
    }
    static {
        System.out.println("second static code" + i);
        i++;
    }
    ...
}

类的构造方法用于初始化类的实例,而类的静态代码块则可用于初始化类,给类的静态变量赋初始值。

public class ShapeFactory { 

/**定义形状类型常量 */
public static final int SHAPE_TYPE_CIRCLE = 1; 
public static final int SHAPE_TYPE_RECTANGLE = 2; . 
public static final int SHAPE_TYPE_LINE = 3;
private static Map<Integer,String> shapes=new HashMap<Integer,String>();

static{ 
    //静态代码块,当 Java 虚拟机加载 ShapeFactory 类的代码时,就会执行这段代码
    //建立形状类型和形状类名的对应关系
    shapes.put(new Integer(SHAPE_TYPE_CIRCLE), "Circle"); 
    shapes.put(new Integer(SHAPE_TYPE_RECTANGLE), "Rectangle"); 
    shapes.put(new Integer(SHAPE_TYPE_LINE), "Line"); 
}

用 static 进行静态导入

从JDK5开始引入了静态导入语法(import static),其目的是为了在需要经常访问同一个类的方法或成员变量的场合,简化程序代码。

//未使用静态导入的例子
class TestStatic {
    public static void.main(String[] args) { 
        System.out.println(Integer.MIN_VALUE);
        System.out.println(Integer.MAX_VALUE); 
        System.out.println(Integer.parseInt("223")); 
    } 
} 

//以上代码需要频繁地访问 System 类的 out 成员变量,以及 Integer 类的一些静态成员。
//以下代码静态导入了 System.out 成员变量及 Integer 类的所有静态成员,在程序中可以接访问被导入的内容:
import static java.lang.Integer.*;
import static java.lang.System.out;
class TestStatic {
    public static void.main(String[] args) { 
        out.println(MIN_VALUE);
        out.println(MAX_VALUE); 
        out.println(parseInt("223")); 
    } 
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 面向对象编程的特点: - 封装:将数据和方法组合成一个类,并对外提供接口,隐藏内部实现细节,保证数据安全性和代码的可维护性。 - 继承:通过子类继承父类的属性和方法,实现代码的复用和扩展。 - 多态:同一种行为在不同情境下的不同表现形式,可以通过重载、重写和接口实现。 2. 类和对象: - 类是一种抽象的概念,是描述对象具有的属性和方法的模板。 - 对象是类的一个实例,具有这个类所描述的属性和方法。 3. 成员变量和成员方法: - 成员变量是描述对象状态的数据,可以是基本类型、对象类型或数组类型。 - 成员方法是描述对象行为的操作,可以是构造方法、普通方法、静态方法和抽象方法。 4. 构造方法和析构方法: - 构造方法是创建对象时调用的特殊方法,用于初始化对象的成员变量。 - 析构方法是销毁对象时调用的特殊方法,用于释放对象占用的资源。Java中不需要显式地调用析构方法,由垃圾回收器自动回收。 5. 访问控制: - 访问控制可以限制类的成员变量和成员方法的访问范围,保证数据的安全性和代码的可维护性。 - Java中有四种访问控制修饰符:public、protected、default、private。 6. 静态变量和静态方法: - 静态变量属于类,不属于对象,被所有对象共享。 - 静态方法可以通过类名调用,不需要创建对象。 7. final关键字: - final可以修饰类、成员变量和成员方法。 - final修饰的类不能被继承,修饰的变量是常量,修饰的方法不能被重写。 8. 抽象类和接口: - 抽象类是一种不能被实例化的类,只能作为父类被继承,可以包含抽象方法和普通方法。 - 接口是一种完全抽象的类型,只包含抽象方法和常量,用于定义规范和约束。类可以实现多个接口。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值