本篇文章主要对java中经常使用finalstatic两个关键字的用法做一下总结,主要参考了《Java编程思想》和网上的一些博客。

final

final应该是程序中经常使用的关键字之一,final关键字使用的对象是:类、方法、变量,下面依次介绍这几种使用final的情况。

final类

当一个类声明为final类,也就证明这个类是不能够被继承的,即禁止继承,因此final类的成员方法是没有机会被覆盖的,这个final类的功能是完整的。在Java中有很多类是final的,如String、Interger以及其他包装类。

final类的好处:

  • 不可变类有很多的好处,它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的开销。

下面是final类的实例:

1
2
3
4
5
final class PersonalLoan{
}

class CheapPersonalLoan extends PersonalLoan{ //compilation error: cannot inherit from final class
}

final方法

如果一个类不允许其子类覆盖某个方法,即不能被重写,则可以把这个方法声明为final方法。(类中所有的private方法都隐式的指定为final)。

使用final方法的原因:

  • 方法锁定,防止任何继承类修改它的含义,确保在继承中使方法行为保持不变且不被覆盖;
  • 效率,将一个方法指明为final,就是同意编译器将针对该方法的所有调用都转化为内嵌调用(相当于在编译的时候已经静态绑定,不需要在运行时再动态绑定)。

下面是final方法的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test1 {
public static void main(String[] args) {
}
public void f1() {
System.out.println("f1");
}
//final方法
public final void f2() {
System.out.println("f2");
}
}

public class Test2 extends Test1 {
public void f1(){
System.out.println("Test1父类方法f1被覆盖!");
}
public static void main(String[] args) {
Test2 t=new Test2();
t.f1(); //子类重写父类的方法
t.f2(); //调用从父类继承过来的final方法
}
}

final变量

程序中有些数据的恒定不变是很有必要的,比如:

  • 一个永不改变的编译时常量
  • 一个在运行时被初始化的值,而在程序的后面不希望它被改变。

这种类型的变量只能被赋值一次,一旦被赋值之后,就不能够再更改了。

有几点要注意的:

  • 一个既是static又是final的域只占据一段不能改变的存储空间,一般用大写来表示;
  • final使数值恒定不变,而当用于对象时,final使引用恒定不变(一旦引用指向一个对象,就无法再把它改为指向另一个对象);

final变量的好处:

  • 提高性能,JVM和Java应用程序都会缓存final变量;
  • final变量可以在安全的在多线程环境下进行共享,而不需要额外的开销。

下面是final类的实例:

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static final int PI = 3.14;//这个变量是只读的
public final int INIT; //final空白,必须在初始化对象的时候赋初值
public Test(int x) {
INIT = x;
}
public static void main(String[] args) {
Test t = new Test(2);
//t.PI=3.1415;//出错,final变量的值一旦给定就无法改变
System.out.println(t.INIT);
}
}

final数据还有另外三个比较特殊的情况:与static合用,空白final和final参数。

static final

一个既是static又是final的域只占据一段不能改变的存储空间:

  • 对于变量,它表示一旦给定就不可更改,并且可以通过类名直接访问(使用大写和下划线命名);
  • 对于方法,表示不可覆盖,并且可以通过类名直接访问。

空白final

从上面的例子中,我们就可以看到空白final的使用方法,它指的是:被声明为final但又未给出初值的域。无论什么情况下,编译器都确保空白final在使用前必须被初始化(在域的定义处或每个构造器中用表达式对final赋值)。

final参数

Java时允许在参数列表的声明中以声明的方式将参数指明为final。

  • 当对象被指明为final时,这就意味着我们无法在方法中更改参数引用所指向的对象。
  • 当基本类型的参数被指明为final时,我们可以读取参数但是无法修改参数。

总结

总结一下final关键字的一些重要特点:

  1. 本地变量必须在声明的时候赋值;
  2. 在匿名类中所有变量都必须是final变量;
  3. final方法不能被重写;
  4. final类不能被继承;
  5. final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误;
  6. 接口中声明的所有变量本身是final的;
  7. final方法在编译阶段绑定,称为静态绑定(static binding);
  8. 对于集合对象声明为final指的是引用不能被更改,但是你可以向其中增加,删除或者改变内容;

static关键字

通常来说,当我们创建类时,就是在描述那个类的外观与行为。除非使用new创建那个类的对象,否则并为获得任何对象。执行new来创建对象时,数据存储空间才被分配,其方法才能被外界调用。

但是,试想在一种情况下,我们只想为某一特定区域分配单一存储空间,而不用去考虑它创建了多少对象。另一种情形是,希望某个方法不与包含它的类的任何对象联系在一起,也就是说,即使没有创建对象,也能够调用这个方法。这就是static关键字主要使用的地方。

static一个很重要的用途就是多个对象可以共享一些存储空间,static关键字使用的对象为:类、方法、代码块。

static关键字有几个需要注意的地方:

  • 被static修饰的成员变量和成员方法独立于该类的任何对象,它不依赖类的特定实例,被类的所有实例共享;
  • 用public修饰的static成员变量和成员方法本质是全局变量和全局方法,当声明它类的对象市,不生成static变量的副本,而是类的所有实例共享同一个static变量;
  • static变量前可以有private修饰,这时就不能使用类名直接访问流。

static修饰的成员变量和成员方法习惯上称为静态变量和静态方法,可以直接通过类名来访问,访问语法为:

  • ClassName.Varibale
  • ClassName.method()

static变量

按照是否静态的对类成员变量进行分类可分两种:

  • 一种是被static修饰的变量,叫静态变量类变量
  • 另一种是没有被static修饰的变量,叫实例变量

两者的区别是:

  • 对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问;
  • 对于实例变量,每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。

一般在在对象之间使用共享值和方便访问变量时,就会去使用静态变量。

例子如下:

1
2
3
4
5
6
7
8
9
10
public class Test {
public static int i;
int j;
public Test(int j) {
this.j=j;
}
public static void main(String[] args) {
System.out.println("Test.i=" + Test.i);
}
}

static方法

静态方法也叫做类方法,静态方法可以直接通过类名调用,任何的实例也都可以调用,因此静态方法中不能用thissuper关键字,不能直接访问所属类的实例变量和实例方法(因为它们是与具体方法关联的),只能访问所属类的静态成员变量和成员方法。

因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。

Java中很多使用static的方法,如Math类中所有的方法都是静态的,而一般类内部的static方法也是方便其它类对该方法的调用。

例子如下:

1
2
3
4
5
6
7
8
9
10
public class Test {
static int sum(int x, int y){
return x + y;
}

public static void main(String[] args) {
int sum = Test.sum(10, 10);
System.out.println("10+10=" + sum);
}
}

static代码块

static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
public static int i;
static{
i = 10;
System.out.println("Now in static block.");
}
public void test() {
System.out.println("test method: i=" + i);
}

public static void main(String[] args) {
System.out.println("Test.i=" + Test.i);
new Test().test();
}
}

总结

下面是关于静态变量和静态方法的一些总结:

  • 一个类的静态方法只能访问其静态变量;
  • 一个类的静态方法不能够直接调用非静态方法;
  • 静态方法中不存在当前对象,因而不能使用 this,当然也不能使用 super;
  • 静态方法不能被非静态方法覆盖;
  • 构造方法不允许声明为 static 的;
  • 局部变量不能使用static修饰。