一、final
根据程序上下文环境,java中的final关键字有无法修改的、最终形态的含义。它可以修饰非抽象类、非抽象成员方法和变量。
final关键字修饰的类不能被继承、没有子类,其类中的方法也默认是final的。
final修饰的方法不能被子类中的方法覆盖,但是可以被继承。
final修饰的成员变量表示常量,只能被赋值一次,且赋值后值就不再改变。
final不能用于修饰构造方法。
值得注意的一点是:父类中的private私有方法是不能被子类方法覆盖的,因此,private类型的方法默认是final类型的。
1. final类
如上说明,final类不能被继承,因此其内的成员方法也不能被覆盖,默认都是final的。我们在设计一个类的时候,如果不需要有子类,类的实现细节不允许改变,且能够确信这个类不会被再次扩展,那么就可以将这个类设计为final类。例如String类就是final类,代码如下:
public final class String
extends Object
implements Serializable, Comparable<String>, CharSequence
我们不能继承或者重写String类,而能直接使用该类。
2. final方法
我们下面使用子类继承的方式来演示final修饰符在实际中修饰方法的应用
testfinal.java
public class testfinal {
public void method1() {
System.out.println("This is method1");
}
//不能被改变的方法,此方法无法被子类覆盖
public final void method2() {
System.out.println("This is final method2");
}
public void method3() {
System.out.println("This is method3");
}
//私有方法,不能被子类覆盖,也不能被子类继承
private void method4() {
System.out.println("This is private method4");
}
}
keywordfinal.java
public class keywordfinal extends testfinal {
//对于父类中的method1方法进行了覆盖
public void method1() {
System.out.println("This is keywordfinal's method1");
}
public static void main(String[] args) {
keywordfinal keywordfinal = new keywordfinal();
keywordfinal.method1();
keywordfinal.method2();
keywordfinal.method3();
//keywordfinal.method4();//父类中的private方法,子类无法继承和覆盖
}
}
执行结果为
This is keywordfinal's method1
This is final method2
This is method3
通过上述演示的结果,我们可以发现,在父类中声明的final方法,无法在子类覆盖
编译器在遇到final方法的时候,会转入内嵌机制,这种方式可以大大提高代码的执行效率。
3. final变量(常量)
使用final修饰符修饰的变量,用于表示常量,因为值一旦给定,就无法改变!
可以使用final修饰的变量有三种:静态变量、实例变量和局部变量,这三种类型分别可以代表三种类型的常量。
我们可以在类中使用PI值的时候,将其声明为常量,这样就可以在整个运行过程中,值都不会改变。
在声明final变量的时候,可以先声明,不给定初始值,这种变量也可以称之为final空白。无论什么情况,编译器都必须确final空白在被使用之前初始化值。但是这种方式也提供了更大的灵活性,我们可以实现,一个类中的final常量依据对象的不同而有所不同,但是又能保持其恒定不变的特征。
下面的代码对于上面的说明进行了具体实现:
public class Finalword {
private final String finalS = "finalS";
private final int finalI = 100;
public final int finalIntB = 90;
public static final int staticfinalC = 80;
private static final int staticfinalD=70;
public final int finalIntE;//final空白,必须要在初始化对象的时候给定初始值,如果声明为静态变量,必须要给定初始值。
public Finalword(int e) {
this.finalIntE = e;
}
//public Finalword() {}//在类中有未给定值的final常量时,无法声明不给定初始值的构造方法,会提示finalIntE未初始化。
public static void main(String[] args) {
Finalword finalword = new Finalword(60);
//finalword.finalI = 101;//提示值已分配错误,final变量的值一旦给定,无法进行改变
//finalword.finalIntB = 91;//提示值已分配错误,final变量的值一旦给定,无法进行改变
//finalword.staticfinalC = 81;//提示值已分配错误,final变量的值一旦给定,无法进行改变
//finalword.staticfinalD = 71;//提示值已分配错误,final变量的值一旦给定,无法进行改变
System.out.println(finalword.finalI);
System.out.println(finalword.finalIntB);
System.out.println(finalword.staticfinalC);//不推荐使用实例的方式调用静态常亮,推荐使用类名.常量名的方式,例如Finalword.staticfinalC
System.out.println(finalword.staticfinalD);//不推荐使用实例的方式调用静态常亮,推荐使用类名.常量名的方式,例如Finalword.staticfinalD
System.out.println(Finalword.staticfinalC);
System.out.println(Finalword.staticfinalD);
//System.out.println(Finalword.finalIntE);//无法调用非静态变量
System.out.println(finalword.finalIntE);
Finalword finalword2 = new Finalword(50);
System.out.println(finalword2.finalIntE);//final空白变量finalIntE可以根据实例化时给定值的不同而不同
}
private void testMethod() {
final int a;//final空白,在需要的时候才赋值
final int b = 4;//局部常量--final用于局部变量的情形
final int c;//final空白,一直没有给赋值.
a = 3;
//a=4;//出错,已经给赋过值了.
//b=2;//出错,已经给赋过值了.
}
}
4. final参数
当函数的参数为final类型时,在方法内部可以读取和使用该参数,但是无法改变值
public class FinalWord2 {
public void method1(final int i) {
//i++;//提示值已初始化错误,final修饰的参数的值不允许改变
System.out.println(i);
}
public static void main(String[] args) {
new FinalWord2().method1(5);
}
}
2. static
static表示有“全局”或者“静态”的意思,用来修饰成员变量和成员方法,可以形成静态static代码块,但是目前在java语言中并没有全局变量的概念。
被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它并不依赖类特定的实例,被类的所有实例共享。只要这个类被加载,java虚拟机就能根据类名在运行时数据区的方法区内找到静态内容。也是因此,static修饰的对象可以在他的任何对象创建之前访问,无需引用任何对象。
使用public修饰的static成员变量和成员方法本质上就是全局变量和全局方法,当声明其类的对象时,不生成static变量的副本,而是类的所有实例共享同一个static变量。
static变量前的权限修饰,影响static的可调用范围,如果使用private修饰,则表示该变量可以在类的静态代码块中,或者类的其他静态成员方法中调用,也可以用于非静态成员方法,但是不能在其他类中通过类名直接引用。因此static是不需要实例化就可以使用,而权限控制前缀只是限制其使用范围。
调用静态成员或者静态方法的方式也很简单,可以直接使用类名来访问,语法如下:
类名.静态方法名(参数)
类名.静态变量名
使用static修饰的代码块就是静态代码块,当java虚拟机(JVM)加载类的时候,就会执行该代码块。
1. static变量
类中变量根据是否使用static进行修饰,可以分为两种:
一种是使用static修饰的,成为静态变量或者类变量
另一种是没有使用static修饰的,成为实例变量
静态变量在内存中只有一个拷贝(节省内存),JVM中只为静态变量分配一次内存,在类加载的过程中就完成了对于静态变量的内存分配,可以使用类名直接访问,也可以使用实例化后的对象进行访问(这种方式不推荐)。
实例变量是每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中存在多个拷贝,且互不影响,相较于静态变量来讲更为灵活。
2. 静态方法
静态方法使用类名可以直接进行调用,任何实例也可以直接调用,因此静态方法中不能使用this和super关键字,不能直接访问所属类的实例变量和实例方法(指的是不使用static修饰符修饰的方法和变量),只能访问所属类中的静态成员变量和静态成员方法。这个问题主要是因为实例成员对象同特定的对象关联,而静态方法同具体的实例无关。
因为static方法独立于任何对象,所以就要求static方法必须被实现,且不能是抽象abstract的。
3. static代码块
static代码块也成为静态代码块,指的是类中独立于类成员的static语句块,在一个类中可以存在多个静态代码块,位置也可以随便放,它不在任何的方法体内,JVM加载类的时候会首先执行这些静态代码块,如果static代码块有多个,则JVM会根据它们在类中出现的先后顺序依次执行,每个代码块都只会被执行一次,例如:
public class StaticBlock {
private static int a;
private int b;
static {
StaticBlock.a = 5;
System.out.println(a);
StaticBlock staticBlock = new StaticBlock();
staticBlock.f();
staticBlock.b = 1000;
System.out.println(staticBlock.b);
}
static {
StaticBlock.a = 4;
System.out.println(a);
}
public static void main(String[] args) {
StaticBlock staticBlock = new StaticBlock();
staticBlock.b = 990;
System.out.println("This is method main()");
System.out.println(staticBlock.b);
System.out.println(StaticBlock.a);
}
static {
StaticBlock.a = 6;
System.out.println(a);
}
public void f() {
System.out.println("This is method f()");
}
}
执行结果
5
This is method f()
1000
4
6
This is method main()
990
6
从上可以看出,我们可以使用静态代码块对于静态变量进行赋值。main方法也是静态的,这样JVM在运行main方法的时候可以直接调用,而不需要创建实例调用。静态变量、静态方法、静态方法块的运行都在main方法执行之前。
三、 static同final一起使用
static final用来修饰成员变量和成员方法,可以理解为“全局常量”。
对于变量,表示一旦给定初始值,就不可以修改,而且可以直接通过类名访问。
对于方法,表示不可覆盖,而且可以通过类名直接访问。
对于被static final修饰过的实例常量,实例本身不能再改变,但是对于一些容器类型,例如(ArrayList、HashMap),不可以改变容器变量本身,但是可以修改容器内存放的对象。这种特性在编程中用到很多。
例子如下:
public class TestStaticFinal {
private static final String strStaticFinalVar = "aaa";//全局常量
private static String strStaticVar = null;//静态变量
private final String strFinalVar = null;//不可变常量
private static final int intStaticFinalVar = 0;
private static final Integer integerStaticFinalVar = new Integer(8);
private static final ArrayList<String> arrStaticFinalVar = new ArrayList<String>();
private void test() {
System.out.println("-------------值处理前----------");
System.out.println("strStaticFinalVar = " + strStaticFinalVar);
System.out.println("strStaticVar = " + strStaticVar);
System.out.println("strFinalVar = " + strFinalVar);
System.out.println("intStaticFinalVar = " + intStaticFinalVar);
System.out.println("integerStaticFinalVar = " + integerStaticFinalVar);
System.out.println("arrStaticFinalVar = " + arrStaticFinalVar);
//strStaticFinalVar = "新值";//错误,final变量修饰,不可变,所以不能修改
strStaticVar = "新的静态变量值";//正确,static修饰的变量表示全局变量,可以改变值
//strFinalVar = "新的不可变值";//错误,final变量修饰,在使用前必须给出初始值,null值也算,且该初始值给定之后就不可修改。
//intStaticFinalVar = 1;//错误,final变量修饰,不可变,所以不能修改
//integerStaticFinalVar = new Integer(9);//错误,final变量修饰,不可变,所以不能修改
arrStaticFinalVar.add("item1");//正确,容器变量本身没有变化,变的是其内部的存放内容。该方法使用范围较为广泛
arrStaticFinalVar.add("item2");//正确,容器变量本身没有变化,变的是其内部的存放内容。该方法使用范围较为广泛
System.out.println("-------------值处理后----------");
System.out.println("strStaticFinalVar = " + strStaticFinalVar);
System.out.println("strStaticVar = " + strStaticVar);
System.out.println("strFinalVar = " + strFinalVar);
System.out.println("intStaticFinalVar = " + intStaticFinalVar);
System.out.println("integerStaticFinalVar = " + integerStaticFinalVar);
System.out.println("arrStaticFinalVar = " + arrStaticFinalVar);
}
public static void main(String[] args) {
new TestStaticFinal().test();
}
}
执行结果
-------------值处理前----------
strStaticFinalVar = aaa
strStaticVar = null
strFinalVar = null
intStaticFinalVar = 0
integerStaticFinalVar = 8
arrStaticFinalVar = []
-------------值处理后----------
strStaticFinalVar = aaa
strStaticVar = 新的静态变量值
strFinalVar = null
intStaticFinalVar = 0
integerStaticFinalVar = 8
arrStaticFinalVar = [item1, item2]
通过上面的例子可以总结得出,使用final修饰的变量给定初始值之后就不可以改变了,但是使用final修饰的容器在不改变容器本身的情况下,修改容器内部存储的内容,这个改动是允许的。