文章目录
类、方法、成员变量和局部变量的可用修饰符:
修饰符 | 类 | 成员方法 | 构造方法 | 成员变量 | 局部变量 |
---|---|---|---|---|---|
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
public
和 final
,而 static
protected
和 private
不能修饰顶层类。成员方法和成员变量可以有多种修饰符,而局部变量只能用 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 修饰符需要遵守以下语法规则:
- 抽象类中可以没有抽象方法,但包含了抽象方法的类必须被定义为抽象类。如果子类没有实现父类中 所有 的抽象方法,那么子类也必须定义为抽象类,否则编译出错。
abstract class Base { abstract void m1(); abstract void m2(); } class Sub extends Base { void m1() {/* implement */} //编译出错,Sub类仍需声明为抽象类 }
- 没有抽象静态方法,
static
和abstract
关键字是势不两立的冤家,不能连在一起使用,但抽象类中可以有静态方法。abstract class Base { static abstract void m1(); //编译出错 static void me2(); //合法 }
- 抽象类中可以有非抽象的构造方法,创建子类的实例时可能会调用这些构造方法。抽象类不能被实例化,然而可以创建一个引用变量,其类型是一个抽象类,并让它引用非抽象的子类的一个实例。
所谓的抽象类不能被实例化,是指不能创建抽象类本身的实例,尽管如此,可以创建一个苹果对象,并把它看作是水果对象。abstract class Base{} class Sub extends Base { public static void main(String[] args) { Base base1 = new Base(); //编译出错,不能创建抽象类的实例 Base base2 = new Sub(); //合法,可以创建具体子类的实例 } }
- 抽象类及抽象方法不能被
final
修饰符修饰。abstract
修饰符与final
修饰符不能连用,因为抽象类只有允许创建其子类,它的抽象方法才能被实现,并且只有它的具体子类才能被实例化,而用final
修饰的类不允许拥有子类,用final
修饰的方法不允许被子类方法覆盖。abstract final class Base1 {} //编译出错 abstract class Base2 { final abstract void m1(); //编译出错 final void m2() {...} //合法,抽象类中允许拥有 final 类型的具体方法 }
- 抽象方法不能被 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 修饰,那么它就是实例方法 。在实例方法中可以直接访问所属类的静态变量、静态方法、实例变量和实例方法。
- 静态方法必须被实现
- 静态方法用来表示某个类所特有的功能,这种功能的实现不依赖于类的具体实例,也不依赖于它的子类。既然如此,当前类必须为静态方法提供实现。换句话说,一个静态的方法不能被定义为抽象方法。
static
和abstract
不能在一起使用。如果一个方法是静态的 , 它就必须自力更生,自己实现该方法;如果一个方法是抽象的,那么它就只表示类所具有的功能,但不会实现它,在子类中才会实现它。
- 静态方法用来表示某个类所特有的功能,这种功能的实现不依赖于类的具体实例,也不依赖于它的子类。既然如此,当前类必须为静态方法提供实现。换句话说,一个静态的方法不能被定义为抽象方法。
- 作为程序入口的 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"));
}
}