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)强制执行。
具体来说,修饰符的限制是通过以下方式实现的:
-
访问修饰符的规定:Java 定义了几种访问修饰符,包括
public
、protected
、default
(即默认,没有关键字修饰)和private
。每个修饰符都规定了不同的访问权限。例如,public
表示该成员对所有类都可见,而private
表示该成员只对所属类可见。 -
编译器检查:在编译时,Java 编译器会检查对成员的访问是否符合修饰符的规定。如果访问违反了修饰符的限制,编译器将会产生错误或警告,阻止代码的编译。
-
字节码的生成:编译器在将 Java 源代码编译成字节码时,会将修饰符的信息嵌入到字节码中。这样,在运行时,Java 虚拟机可以根据修饰符的信息来判断是否允许对成员的访问。
-
运行时访问控制:Java 虚拟机在运行时会根据字节码中的访问修饰符信息来执行访问控制。如果代码尝试访问受限制的成员,虚拟机将会抛出
IllegalAccessException
异常或类似的异常,从而阻止非法的访问。
总的来说,Java 中的修饰符通过编译器和虚拟机的配合,以字节码的形式在运行时执行访问控制,从而实现对成员的访问权限的限制。这种访问控制机制是 Java 语言中实现封装、继承和多态等面向对象编程特性的重要基础。
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
-
default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
-
private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
-
public : 对所有类可见。使用对象:类、接口、变量、方法
-
protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
我们可以通过以下表来说明访问权限:
修饰符 | 当前类 | 同一包内 | 子孙类(同一包) | 子孙类(不同包) | 其他包 |
---|---|---|---|---|---|
public | Y | Y | Y | Y | Y |
protected | Y | Y | Y | Y/N | N |
default | Y | Y | Y | N | N |
private | Y | N | N | N | N |
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
成员在继承和封装中具有一定的灵活性。
考虑以下情况:
-
同一包内的其他类:
在同一个包内,所有类都可以访问被声明为protected
的成员。这意味着不同类之间可以相互访问彼此的protected
成员,不受访问权限的限制。 -
不同包中的子类:
在不同包中,子类可以访问其父类的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
的方法 protectedMethod
。Child
类是 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 中用来修改类、方法、变量等元素的行为或性质,但与访问权限无关的修饰符。以下是一些常见的非访问修饰符:
-
static
:static
修饰符用于创建静态成员,静态成员属于类而不是特定的实例。静态成员在类加载时被初始化,并且可以通过类名直接访问,不需要创建对象实例。例如,static int count = 0;
定义了一个静态变量count
。 -
final
:final
修饰符表示一个成员是常量或者一个类、方法或变量是不可改变的。对于变量,一旦赋值后就不能再次修改其值;对于方法,表示方法不能被子类重写(final
方法是无法被继承的);对于类,表示类不能被继承(final
类不能有子类)。例如,final double PI = 3.14159;
定义了一个常量PI
。 -
abstract
:abstract
修饰符用于创建抽象类和抽象方法。抽象类不能被实例化,只能被用作其他类的父类;抽象方法只有声明而没有实现,必须在子类中重写以提供具体实现。例如,abstract class Shape { abstract void draw(); }
定义了一个抽象类Shape
和一个抽象方法draw()
。 -
synchronized
:synchronized
修饰符用于控制多线程访问共享资源时的同步,确保只有一个线程可以访问某个方法或代码块。例如,public synchronized void method() { // 同步代码块 }
定义了一个同步方法。 -
volatile
:volatile
修饰符用于标记变量,确保多线程之间对该变量的操作是可见的,禁止进行指令重排序优化。它适用于多个线程之间共享变量的场景。例如,volatile boolean flag = true;
定义了一个 volatile 变量flag
。 -
transient
:transient
修饰符用于阻止变量的序列化。当对象被序列化时,带有transient
关键字的变量的值不会被持久化保存。例如,transient int securityCode;
定义了一个 transient 变量securityCode
。 -
native
:native
修饰符用于指示方法的实现是由本地代码提供的,通常是使用其他编程语言(如 C 或 C++)实现的。例如,public native void nativeMethod();
定义了一个 native 方法。
这些非访问修饰符用于增强 Java 语言的功能和灵活性,允许程序员更加精确地控制类、方法、变量等的行为。
非访问修饰符使用指南
非访问修饰符在 Java 中具有各自的用途,可以根据特定的需求来选择使用。以下是一些常见的情况和建议使用非访问修饰符的场景:
-
static
:- 当需要在类的所有实例之间共享数据时,可以使用
static
修饰符来创建静态变量。 - 当需要在类级别上执行操作 而不是实例级别时,可以使用
static
修饰符来创建静态方法。 - 常用于实现工具类、常量类等。
- 当需要在类的所有实例之间共享数据时,可以使用
-
final
:- 当需要创建不可变的常量时,可以使用
final
修饰符。 - 当希望阻止子类重写方法或继承类时,可以使用
final
修饰符。 - 常用于创建常量、防止类的继承、防止方法的重写等场景。
- 当需要创建不可变的常量时,可以使用
-
abstract
:- 当需要创建只包含抽象方法的类时,可以使用
abstract
修饰符来创建抽象类。 - 当需要强制子类提供特定方法的实现时,可以在父类中使用
abstract
修饰符来创建抽象方法。 - 常用于实现模板模式、接口类、多态等。
- 当需要创建只包含抽象方法的类时,可以使用
-
synchronized
:- 当多个线程需要访问共享资源时,可以使用
synchronized
修饰符来确保线程安全性。 - 当需要保护关键代码段以避免并发问题时,可以使用
synchronized
修饰符来创建同步方法或同步代码块。 - 常用于实现多线程编程、线程安全的数据结构等。
- 当多个线程需要访问共享资源时,可以使用
-
volatile
:- 当多个线程需要共享变量且变量的值可能会被不同线程修改时,可以使用
volatile
修饰符来确保变量的可见性。 - 当需要禁止编译器对变量进行优化时,可以使用
volatile
修饰符来防止指令重排序。 - 常用于实现双重检查锁定、标记线程状态等。
- 当多个线程需要共享变量且变量的值可能会被不同线程修改时,可以使用
-
transient
:- 当需要阻止某些变量被序列化时,可以使用
transient
修饰符来标记这些变量。 - 当对象中存在不需要持久化保存的临时数据时,可以使用
transient
修饰符来标记这些变量。 - 常用于序列化和反序列化对象时,排除不必要的字段。
- 当需要阻止某些变量被序列化时,可以使用
-
native
:- 当需要与本地代码(如 C 或 C++)进行交互时,可以使用
native
修饰符来声明本地方法。 - 当需要在 Java 中调用本地库或操作系统提供的功能时,可以使用
native
修饰符来调用本地方法。 - 常用于与硬件交互、调用操作系统特定功能等。
- 当需要与本地代码(如 C 或 C++)进行交互时,可以使用
这些非访问修饰符提供了丰富的功能和灵活性,可以根据具体的需求来选择使用。通过合理地使用这些修饰符,可以使代码更加健壮、可靠和易于维护。
为了实现一些其他的功能,Java 也提供了许多非访问修饰符。
-
static 修饰符,用来修饰类方法和类变量。
-
final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
-
abstract 修饰符,用来创建抽象类和抽象方法。
-
synchronized 和 volatile 修饰符,主要用于线程的编程。
非访问修饰符使用指南(实例版):
非访问修饰符使用指南(实例版)
非访问修饰符在 Java 中具有各自的用途,可以根据特定的需求来选择使用。以下是一些常见的情况和建议使用非访问修饰符的场景:
-
static
:-
当需要在类的所有实例之间共享数据时,可以使用
static
修饰符来创建静态变量。静态变量属于类,而不是特定的实例。class Counter { static int count = 0; void increment() { count++; } }
在这个例子中,
count
是Counter
类的静态变量,可以通过类名直接访问,而不需要创建类的实例。 -
当需要在类级别上执行操作而不是实例级别时,可以使用
static
修饰符来创建静态方法。静态方法可以直接通过类名调用,无需创建类的实例。class MathHelper { static int add(int a, int b) { return a + b; } }
在这个例子中,
add()
方法是一个静态方法,可以直接通过MathHelper.add(3, 5)
调用。
-
-
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
字段是一个不可变的常量。
-
-
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()
方法的具体实现。
-
-
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
的安全访问。
-
-
volatile
:-
当多个线程需要共享变量且变量的值可能会被不同线程修改时,可以使用
volatile
修饰符来确保变量的可见性。volatile
保证每次读取该变量都从主存中读取,而不是线程的本地缓存。volatile boolean flag = false;
在这里,
flag
变量被声明为volatile
,当一个线程修改了flag
的值时,其他线程可以立即看到这个变化。 -
当需要禁止编译器对变量进行优化时,可以使用
volatile
修饰符来防止指令重排序。volatile boolean initialized = false; void initialize() { // 初始化操作 initialized = true; }
在这个例子中,
initialized
变量的修改可能会影响其他线程,因此使用volatile
修饰符确保了其对其他线程的可见性和顺序性。
-
-
transient
:- 当需要阻止某些变量被序列化时,可以使用
transient
修饰符来标记这些变量。被transient
修饰的变量在
- 当需要阻止某些变量被序列化时,可以使用
序列化时会被忽略。
java class Person implements Serializable { String name; transient String password; }
在这个例子中,password
字段被声明为 transient
,在对象被序列化时,password
字段的值不会被保存。
- 当对象中存在不需要持久化保存的临时数据时,可以使用
transient
修饰符来标记这些变量。
native
:- 当需要与本地代码(如 C 或 C++)进行交互时,可以使用
native
修饰符来声明本地方法。native
方法的具体实现由本地代码提供。
在这个例子中,class NativeExample { native void nativeMethod(); }
nativeMethod()
方法是一个本地方法,其具体实现由本地代码提供。
- 当需要与本地代码(如 C 或 C++)进行交互时,可以使用
这些非访问修饰符提供了丰富的功能和灵活性,可以根据具体的需求来选择使用。通过合理地使用这些修饰符,可以使代码更加健壮、可靠和易于维护。
1. static 修饰符_在类的所有实例之间共享数据_在类级别上执行操作
- 静态变量:
static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。
- 静态方法:
static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。
对类变量和方法的访问可以直接使用 classname.variablename 和 classname.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,所以该循环会停止。