菜鸟Java基础教程 7. Java 修饰符

Java 修饰符


Java语言提供了很多修饰符,主要分为以下两类:

  • 访问修饰符
  • 非访问修饰符

修饰符用来定义类、方法或者变量,通常放在语句的最前端。我们通过下面的例子来说明:

public class ClassName {
   // ...
}
private boolean myFlag;
static final double weeks = 9.5;
protected static final int BOXWIDTH = 42;
public static void main(String[] arguments) {
   // 方法体
}

1. 访问控制修饰符_default_private_pubilc_protected


修饰符如何限制访问控制

在 Java 中,修饰符在底层通过访问控制列表(Access Control List,ACL)和访问修饰符的概念来限制对类、方法、变量等成员的访问。这种访问控制是由 Java 编译器在编译时执行的,并且在运行时由 Java 虚拟机(JVM)强制执行。

具体来说,修饰符的限制是通过以下方式实现的:

  1. 访问修饰符的规定:Java 定义了几种访问修饰符,包括 publicprotecteddefault(即默认,没有关键字修饰)和 private。每个修饰符都规定了不同的访问权限。例如,public 表示该成员对所有类都可见,而 private 表示该成员只对所属类可见。

  2. 编译器检查:在编译时,Java 编译器会检查对成员的访问是否符合修饰符的规定。如果访问违反了修饰符的限制,编译器将会产生错误或警告,阻止代码的编译。

  3. 字节码的生成:编译器在将 Java 源代码编译成字节码时,会将修饰符的信息嵌入到字节码中。这样,在运行时,Java 虚拟机可以根据修饰符的信息来判断是否允许对成员的访问。

  4. 运行时访问控制:Java 虚拟机在运行时会根据字节码中的访问修饰符信息来执行访问控制。如果代码尝试访问受限制的成员,虚拟机将会抛出 IllegalAccessException 异常或类似的异常,从而阻止非法的访问。

总的来说,Java 中的修饰符通过编译器和虚拟机的配合,以字节码的形式在运行时执行访问控制,从而实现对成员的访问权限的限制。这种访问控制机制是 Java 语言中实现封装、继承和多态等面向对象编程特性的重要基础。


Java中,可以使用访问控制符来保护对类、变量、方法和构造方法访问。Java 支持 4 种不同的访问权限

  • default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。

  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

  • public : 对所有类可见。使用对象:类、接口、变量、方法

  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

我们可以通过以下表来说明访问权限:

修饰符当前类同一包内子孙类(同一包)子孙类(不同包)其他包
publicYYYYY
protectedYYYY/NN
defaultYYYNN
privateYNNNN

1. 默认访问修饰符-不使用任何关键字

如果在类、变量、方法或构造函数的定义中没有指定任何访问修饰符,那么它们就默认具有默认访问修饰符

默认访问修饰符的访问级别是包级别(package-level),即只能被同一包中的其他类访问

如下例所示,变量和方法的声明可以不使用任何修饰符。

实例
// MyClass.java
 
class MyClass {  // 默认访问修饰符
 
    int x = 10;  // 默认访问修饰符
 
    void display() {  // 默认访问修饰符
        System.out.println("Value of x is: " + x);
    }
}
 
// MyOtherClass.java
 
class MyOtherClass {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.display();  // 访问 MyClass 中的默认访问修饰符变量和方法
    }
}

以上实例中,MyClass 类和它的成员变量 x 和方法 display() 都使用默认访问修饰符进行了定义。MyOtherClass 类在同一包中,因此可以访问 MyClass 类和它的成员变量和方法。

2. 私有访问修饰符-private

私有访问修饰符是最严格的访问级别,所以被声明为 private 的方法、变量和构造方法只能被所属类访问,并且类和接口不能声明为 private

声明为私有访问类型的变量只能通过类中公共的 getter 方法被外部类访问。

Private 访问修饰符的使用主要用来隐藏类的实现细节和保护类的数据。

下面的类使用了私有访问修饰符:

public class Logger {
   private String format;
   public String getFormat() {
      return this.format;
   }
   public void setFormat(String format) {
      this.format = format;
   }
}

实例中,Logger 类中的 format 变量为私有变量,所以其他类不能直接得到和设置该变量的值。为了使其他类能够操作该变量,定义了两个 public 方法:getFormat() (返回 format的值)和 setFormat(String)(设置 format 的值)

3. 公有访问修饰符-public

被声明为 public 的类、方法、构造方法和接口能够被任何其他类访问。

如果几个相互访问的 public 类分布在不同的包中,则需要导入相应 public 类所在的包。由于类的继承性,类所有的公有方法和变量都能被其子类继承。

以下函数使用了公有访问控制:

public static void main(String[] arguments) { // ... }

`
Java 程序的 main() 方法必须设置成公有的,否则,Java 解释器将不能运行该类。

4. 受保护的访问修饰符-protected


当一个成员被声明为 protected 时,它对于同一包内的其他类是可见的,并且对于不同包中的子类也是可见的。这使得 protected 成员在继承和封装中具有一定的灵活性。

考虑以下情况:

  1. 同一包内的其他类
    在同一个包内,所有类都可以访问被声明为 protected 的成员。这意味着不同类之间可以相互访问彼此的 protected 成员,不受访问权限的限制。

  2. 不同包中的子类
    在不同包中,子类可以访问其父类的 protected 成员,但其他类无法直接访问父类的 protected 成员。这样,protected 成员提供了一种限制对父类部分成员访问的方式,但允许子类继承和使用这些成员。

例如,考虑以下代码示例:

package com.example.package1;

public class Parent {
    protected int protectedField;

    protected void protectedMethod() {
        // 可以在本类、同一包内的其他类以及子类中访问 protectedField 和 protectedMethod
    }
}

package com.example.package2;

import com.example.package1.Parent;

public class Child extends Parent {
    public void accessProtectedMember() {
        protectedField = 10; // 子类可以访问父类的 protectedField
        protectedMethod();   // 子类可以调用父类的 protectedMethod
    }
}

package com.example.package3;

import com.example.package1.Parent;

public class OtherClass {
    public void accessProtectedMember() {
        Parent parent = new Parent();
        parent.protectedField = 10; // 错误:在不同包中无法直接访问父类的 protectedField
        parent.protectedMethod();   // 错误:在不同包中无法直接调用父类的 protectedMethod
    }
}

在上面的示例中,Parent 类有一个被声明为 protected 的字段 protectedField 和一个被声明为 protected 的方法 protectedMethodChild 类是 Parent 的子类,在其内部可以直接访问和使用 Parent 类的 protected 成员。但是,OtherClass 类不是 Parent 的子类,因此无法直接访问 Parent 类的 protected 成员。


protected 需要从以下两个点来分析说明:

  • 子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问

  • 子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。

protected 可以修饰数据成员,构造方法,方法成员,不能修饰类(内部类除外)

接口及接口的成员变量和成员方法不能声明为 protected。 可以看看下图演示:

子类能访问 protected 修饰符声明的方法和变量,这样就能保护不相关的类使用这些方法和变量。

下面的父类使用了 protected 访问修饰符,子类重写了父类的 openSpeaker() 方法。

class AudioPlayer { 
	protected boolean openSpeaker(Speaker sp) { // 实现细节 
	} 
} 
class StreamingAudioPlayer extends AudioPlayer {
	protected boolean openSpeaker(Speaker sp) {
	 // 实现细节 
	 } 
}

如果把 openSpeaker() 方法声明为 private,那么除了 AudioPlayer 外,其他类将不能访问该方法。

如果把 openSpeaker() 声明为 public,那么所有的类都能够访问该方法。

如果我们只想让该方法对其所在类的子类可见,则将该方法声明为 protected。

protected 是最难理解的一种 Java 类成员访问权限修饰词。

5. 访问控制和继承

请注意以下方法继承的规则:

  • 父类中声明为 public 的方法在子类中也必须为 public。

  • 父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public,不能声明为 private

  • 父类中声明为 private 的方法,不能够被子类继承。


2. 非访问修饰符


非访问修饰符简介

非访问修饰符是 Java 中用来修改类、方法、变量等元素的行为或性质,但与访问权限无关的修饰符。以下是一些常见的非访问修饰符:

  1. static static 修饰符用于创建静态成员,静态成员属于类而不是特定的实例。静态成员在类加载时被初始化,并且可以通过类名直接访问,不需要创建对象实例。例如,static int count = 0; 定义了一个静态变量 count

  2. final final 修饰符表示一个成员是常量或者一个类、方法或变量是不可改变的。对于变量,一旦赋值后就不能再次修改其值;对于方法,表示方法不能被子类重写(final 方法是无法被继承的);对于类,表示类不能被继承(final 类不能有子类)。例如,final double PI = 3.14159; 定义了一个常量 PI

  3. abstract abstract 修饰符用于创建抽象类和抽象方法。抽象类不能被实例化,只能被用作其他类的父类;抽象方法只有声明而没有实现,必须在子类中重写以提供具体实现。例如,abstract class Shape { abstract void draw(); } 定义了一个抽象类 Shape 和一个抽象方法 draw()

  4. synchronized synchronized 修饰符用于控制多线程访问共享资源时的同步,确保只有一个线程可以访问某个方法或代码块。例如,public synchronized void method() { // 同步代码块 } 定义了一个同步方法。

  5. volatile volatile 修饰符用于标记变量,确保多线程之间对该变量的操作是可见的,禁止进行指令重排序优化。它适用于多个线程之间共享变量的场景。例如,volatile boolean flag = true; 定义了一个 volatile 变量 flag

  6. transient transient 修饰符用于阻止变量的序列化。当对象被序列化时,带有 transient 关键字的变量的值不会被持久化保存。例如,transient int securityCode; 定义了一个 transient 变量 securityCode

  7. native native 修饰符用于指示方法的实现是由本地代码提供的,通常是使用其他编程语言(如 C 或 C++)实现的。例如,public native void nativeMethod(); 定义了一个 native 方法。

这些非访问修饰符用于增强 Java 语言的功能和灵活性,允许程序员更加精确地控制类、方法、变量等的行为。


非访问修饰符使用指南

非访问修饰符在 Java 中具有各自的用途,可以根据特定的需求来选择使用。以下是一些常见的情况和建议使用非访问修饰符的场景:

  1. static

    • 当需要在类的所有实例之间共享数据时,可以使用 static 修饰符来创建静态变量。
    • 当需要在类级别上执行操作 而不是实例级别时,可以使用 static 修饰符来创建静态方法。
    • 常用于实现工具类、常量类等。
  2. final

    • 当需要创建不可变的常量时,可以使用 final 修饰符。
    • 当希望阻止子类重写方法或继承类时,可以使用 final 修饰符。
    • 常用于创建常量、防止类的继承、防止方法的重写等场景。
  3. abstract

    • 当需要创建只包含抽象方法的类时,可以使用 abstract 修饰符来创建抽象类。
    • 当需要强制子类提供特定方法的实现时,可以在父类中使用 abstract 修饰符来创建抽象方法。
    • 常用于实现模板模式、接口类、多态等。
  4. synchronized

    • 当多个线程需要访问共享资源时,可以使用 synchronized 修饰符来确保线程安全性。
    • 当需要保护关键代码段以避免并发问题时,可以使用 synchronized 修饰符来创建同步方法或同步代码块。
    • 常用于实现多线程编程、线程安全的数据结构等。
  5. volatile

    • 当多个线程需要共享变量且变量的值可能会被不同线程修改时,可以使用 volatile 修饰符来确保变量的可见性。
    • 当需要禁止编译器对变量进行优化时,可以使用 volatile 修饰符来防止指令重排序。
    • 常用于实现双重检查锁定、标记线程状态等。
  6. transient

    • 当需要阻止某些变量被序列化时,可以使用 transient 修饰符来标记这些变量。
    • 当对象中存在不需要持久化保存的临时数据时,可以使用 transient 修饰符来标记这些变量。
    • 常用于序列化和反序列化对象时,排除不必要的字段。
  7. native

    • 当需要与本地代码(如 C 或 C++)进行交互时,可以使用 native 修饰符来声明本地方法。
    • 当需要在 Java 中调用本地库或操作系统提供的功能时,可以使用 native 修饰符来调用本地方法。
    • 常用于与硬件交互、调用操作系统特定功能等。

这些非访问修饰符提供了丰富的功能和灵活性,可以根据具体的需求来选择使用。通过合理地使用这些修饰符,可以使代码更加健壮、可靠和易于维护。


为了实现一些其他的功能,Java 也提供了许多非访问修饰符。

  • static 修饰符,用来修饰类方法和类变量。

  • final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。

  • abstract 修饰符,用来创建抽象类和抽象方法。

  • synchronized 和 volatile 修饰符,主要用于线程的编程。


非访问修饰符使用指南(实例版):

非访问修饰符使用指南(实例版)

非访问修饰符在 Java 中具有各自的用途,可以根据特定的需求来选择使用。以下是一些常见的情况和建议使用非访问修饰符的场景:

  1. static

    • 当需要在类的所有实例之间共享数据时,可以使用 static 修饰符来创建静态变量。静态变量属于类,而不是特定的实例。

      class Counter {
          static int count = 0;
          
          void increment() {
              count++;
          }
      }
      

      在这个例子中,countCounter 类的静态变量,可以通过类名直接访问,而不需要创建类的实例。

    • 当需要在类级别上执行操作而不是实例级别时,可以使用 static 修饰符来创建静态方法。静态方法可以直接通过类名调用,无需创建类的实例。

      class MathHelper {
          static int add(int a, int b) {
              return a + b;
          }
      }
      

      在这个例子中,add() 方法是一个静态方法,可以直接通过 MathHelper.add(3, 5) 调用。

  2. final

    • 当需要创建不可变的常量时,可以使用 final 修饰符。final 修饰的变量的值无法在运行时被修改。

      final double PI = 3.14159;
      

      在这个例子中,PI 是一个常量,其值无法在程序运行时被修改。

    • 当希望阻止子类重写方法或继承类时,可以使用 final 修饰符。final 类无法被继承,final 方法无法被子类重写。

      final class ImmutableClass {
          final int value;
          
          ImmutableClass(int value) {
              this.value = value;
          }
      }
      

      在这个例子中,ImmutableClass 是一个不可变类,无法被子类化,value 字段是一个不可变的常量。

  3. abstract

    • 当需要创建只包含抽象方法的类时,可以使用 abstract 修饰符来创建抽象类。抽象类不能被实例化,只能被继承。

      abstract class Shape {
          abstract double area();
      }
      

      在这里,Shape 是一个抽象类,其中的 area() 方法是一个抽象方法,需要由 Shape 的子类提供具体实现。

    • 当需要强制子类提供特定方法的实现时,可以在父类中使用 abstract 修饰符来创建抽象方法。子类必须实现抽象方法。

      abstract class Animal {
          abstract void makeSound();
      }
      
      class Dog extends Animal {
          void makeSound() {
              System.out.println("Woof");
          }
      }
      

      在这个例子中,Animal 类声明了一个抽象方法 makeSound(),而 Dog 类是其子类,必须提供 makeSound() 方法的具体实现。

  4. synchronized

    • 当多个线程需要访问共享资源时,可以使用 synchronized 修饰符来确保线程安全性。只有持有对象的锁的线程可以执行被 synchronized 修饰的方法或代码块。

      class Counter {
          private int count = 0;
          
          synchronized void increment() {
              count++;
          }
      }
      

      在这个例子中,increment() 方法使用 synchronized 修饰符,确保了对 count 的操作是线程安全的。

    • 当需要保护关键代码段以避免并发问题时,可以使用 synchronized 修饰符来创建同步方法或同步代码块。

      class SynchronizedExample {
          private int value = 0;
          
          void synchronizedMethod() {
              // 同步方法
              value++;
          }
          
          void someMethod() {
              synchronized(this) {
                  // 同步代码块
                  value--;
              }
          }
      }
      

      在这个例子中,synchronizedMethod() 是一个同步方法,而 someMethod() 中的同步代码块也确保了对 value 的安全访问。

  5. volatile

    • 当多个线程需要共享变量且变量的值可能会被不同线程修改时,可以使用 volatile 修饰符来确保变量的可见性。volatile 保证每次读取该变量都从主存中读取,而不是线程的本地缓存。

      volatile boolean flag = false;
      

      在这里,flag 变量被声明为 volatile,当一个线程修改了 flag 的值时,其他线程可以立即看到这个变化。

    • 当需要禁止编译器对变量进行优化时,可以使用 volatile 修饰符来防止指令重排序。

      volatile boolean initialized = false;
      
      void initialize() {
          // 初始化操作
          initialized = true;
      }
      

      在这个例子中,initialized 变量的修改可能会影响其他线程,因此使用 volatile 修饰符确保了其对其他线程的可见性和顺序性。

  6. transient

    • 当需要阻止某些变量被序列化时,可以使用 transient 修饰符来标记这些变量。被 transient 修饰的变量在

序列化时会被忽略。
java class Person implements Serializable { String name; transient String password; }
在这个例子中,password 字段被声明为 transient,在对象被序列化时,password 字段的值不会被保存。

  • 当对象中存在不需要持久化保存的临时数据时,可以使用 transient 修饰符来标记这些变量。
  1. native
    • 当需要与本地代码(如 C 或 C++)进行交互时,可以使用 native 修饰符来声明本地方法。native 方法的具体实现由本地代码提供。
      class NativeExample {
          native void nativeMethod();
      }
      
      在这个例子中,nativeMethod() 方法是一个本地方法,其具体实现由本地代码提供。

这些非访问修饰符提供了丰富的功能和灵活性,可以根据具体的需求来选择使用。通过合理地使用这些修饰符,可以使代码更加健壮、可靠和易于维护。


1. static 修饰符_在类的所有实例之间共享数据_在类级别上执行操作

- 静态变量:
static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。
- 静态方法:
static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。

对类变量和方法的访问可以直接使用 classname.variablenameclassname.methodname 的方式访问。

如下例所示,static 修饰符用来创建类方法和类变量。

abstract class Caravan{
   private double price;
   private String model;
   private String year;
   public abstract void goFast(); //抽象方法
   public abstract void changeColor();
}

以上实例运行编辑结果如下:

Starting with 0 instances
Created 500 instances

2. final 修饰符

final 变量:

final 表示"最后的、最终的"含义,变量一旦赋值后,不能被重新赋值。被 final 修饰的实例变量必须显式指定初始值。

final 修饰符通常和 static 修饰符一起使用来创建类常量。

实例
public class Test{ 
	final int value = 10; 
	// 下面是声明常量的实例 
	public static final int OXWIDTH = 6; 
	static final String TITLE =  "Manager"; 
	public void changeValue(){ 
		value = 12; //将输出一个错误 
	} 
}
final 方法

父类中的 final 方法可以被子类继承,但是不能被子类重写。

声明 final 方法的主要目的是防止该方法的内容被修改。

如下所示,使用 final 修饰符声明方法。

public class Test{ 
	public final void changeName(){ // 方法体 
	} 
}
final 类

final 类不能被继承,没有类能够继承 final 类的任何特性。

实例
public final class Test { 
// 类体 
}

3.abstract 修饰符

抽象类:

抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。

一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。

抽象类可以包含抽象方法和非抽象方法。

实例
abstract class Caravan{ 
	private double price; 
	private String model; 
	private String year; 
	public abstract void goFast(); 
	//抽象方法 
	public abstract void changeColor(); 
	}
抽象方法

抽象方法是一种没有任何实现的方法,该方法的具体实现由子类提供。

抽象方法不能被声明成 final 和 static。

任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。

如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。

抽象方法的声明以分号结尾,例如:public abstract sample();

实例
public abstract class SuperClass{ 
	abstract void m(); 
	//抽象方法 
	} 
class SubClass extends SuperClass{
//实现抽象方法 
	void m(){ ......... 
	} 
}

4. synchronized 修饰符

synchronized 关键字声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。

实例
public synchronized void showDetails(){
....... 
}

5.transient 修饰符

序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。

该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。

实例
public transient int limit = 55; // 不会持久化 
public int b; // 持久化

6. volatile 修饰符

volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

一个 volatile 对象引用可能是 null。

实例
public class MyRunnable implements Runnable { 
	private volatile boolean active; 
	public void run() { 
		active = true; 
		while (active) // 第一行 
		{ // 代码 
		} 
	} 
public void stop() { 
	active = false; // 第二行 
	} 
}

通常情况下,在一个线程调用 run() 方法(在 Runnable 开启的线程),在另一个线程调用 stop() 方法。 如果 第一行 中缓冲区的 active 值被使用,那么在 第二行 的 active 值为 false 时循环不会停止。

但是以上代码中我们使用了 volatile 修饰 active,所以该循环会停止。

  • 8
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值