1. static和final
1.1. static关键字
1.1.1. static修饰成员变量
static关键字可以修饰成员变量,它所修饰的成员变量不属于对象的数据结构,而是属于类的变量,通常通过类名来引用static成员。
当创建对象后,成员变量是存储在堆中的,而static成员变量和类的信息一起存储在方法区, 而不是在堆中,
一个类的static成员变量只有“一份”(存储在方法区),无论该类创建了多少对象。看如下的示例
:
class Cat {
private int age;
private static int numOfCats;
public Cat(int age) {
this.age = age;
System.out.println(++numOfCats);
}
}
在main方法中声明两个Cat类对象:
Cat c1 = new Cat( 2);
Cat c2 = new Cat( 3);
其内存分配如下图-2所示:

图- 2
如上的代码中,声明两个Cat类对象后,numOfCats的值为2。当声明第一个Cat类对象后,numOfCats值增1变为1,声明第二个Cat类对象后,因为numOfCats存在方法区中并且只有一份,所以其值在刚刚的1的基础之上变为2。
1.1.2. static修饰方法
通常的方法都会涉及到对具体对象的操作,这些方法在调用时,需要隐式的传递对象的引用(this),如下代码所示,在调用distance方法时,除了传递p2参数外,还隐式的传递了p1作为参数,在方法中的this关键字即表示该参数:
int d = p1.distance(p2);
而static修饰的方法则不需要针对某些对象进行操作,其运行结果仅仅与输入的参数有关,调用时直接用类名引用即可,如下代码所示:
double c = Math.sqrt(3.0 * 3.0 + 4.0 * 4.0);
上面的方法在调用时,没有隐式的传递对象引用,因此在static方法中是不可以使用this关键字的。另外,由于static在调用时没有具体的对象,因此在static方法中不能对非static成员(对象成员)进行访问。
static方法的作用在于提供一些“工具方法”和“工厂方法”(后面课程详细介绍)等。像如下的一些工具方法,只是完成某一功能,不需要传递this:
Point.distance(Point p1, Point p2)
RandomUtils.nextInt()
StringUtils.leftPad(String str, int size, char padChar);
Math.sqrt() Math.sin() Arrays.sort()
1.1.3. static块
static块为属于类的代码块,在类加载期间执行的代码块,只执行一次,可以用来在软件中加载静态资源(图像、音频等等)。如下代码演示了static块的执行:
class Foo {
static {
//类加载期间,只执行一次
System.out.println(" Load Foo.class ");
}
public Foo() {
System.out.println("Foo()");
}
}
class Test{
public static void main(String[] args){
Foo foo = new Foo();
}
}
上面代码的输出结果为:
- Load Foo.class
- Foo();
因为,在Foo类加载时,先运行了静态块,而后执行了构造方法,即,static块是在创建对象之前执行的。
1.2. final关键字
1.2.1. final修饰变量
final关键字修饰变量,意为不可改变。final可以修饰成员变量,也可以修饰局部变量,当final修饰成员变量时,可以有两种初始化方式:
- 声明同时初始化
- 构造函数中初始化
final关键字修饰局部变量,在使用之前初始化即可。参见如下示例:
public class Emp {
private final int no = 100; // final成员变量声明时初始化
public static void main(String[] args) {
no = 99;
}
}
如上的语句,no=99会出现编译期错误,因为final的变量不可被改变。
1.2.2. final修饰方法
final关键字修饰的方法不可以被重写。使一个方法不能被重写的意义在于:防止子类在定义新方法时造成的“不经意”重写。参见下面的代码:
public class Car {
// 点火
public void fire() {…}
… … …
}
public class Tank extends Car {
// 开炮
public void fire() {…}
… … …
}
上面的代码中,Car类有一个方法为fire()点火,当点火后即汽车启动,坦克类Tank继承自Car类,重写了fire()点火方法,而Tank类的点火即为开炮,而非坦克启动。此即Tank类误重写了Car类的fire()方法。
若想避免这种情况发生,可以将Car类的fire()方法声明为final的,那样该方法将不可以被子类重写了,如下代码所示:
public class Car {
// 点火
public final void fire() {…}
… … …
}
1.2.3. final修饰类
final关键字修饰的类不可以被继承。使一个类不能被继承的意义在于:可以保护类不被继承修改,可以控制滥用继承对系统造成的危害。在JDK中的一些基础类库被定义为final的,例如:String、Math、Integer、Double 等等。自己定义的类也可以声明为final的,如下代码所示:
final Foo { }
class Goo extends Foo { }
上面的代码中,声明了final的Foo,而后Goo继承了Foo,此句会出现编译错误,因为final修饰的类不可以被继承。
1.2.4. static final常量
static final 修饰的成员变量称为常量,必须声明同时初始化,并且不可被改变。常量建议所有字母大写。
实际应用中应用率较广,因为static final常量是在编译期被替换的,可以节约不必要的开支,如下代码演示了static final的用法:
class Foo {
public static final int NUM = 100;
}
class Goo {
public static void main(String[] args) {
Sytem.out.println(Foo.NUM);
// 代码编译时,会替换为:System.out.println(100);
}
}
说明:static final常量Foo.NUM会在编译时被替换为其常量值(100),在运行Goo类时,Foo类不需要被载入。这样减少了不必要的开支。