使用final修饰符不仅会保持对象的引用不会改变,而且编译器还会持续维护这个对象在回调方法中的生命周期.所以这才是final变量和final参数的根本意义.
final修饰符
final 基本类型变量:表示数值不能改变的,
final 引用类型变量:表示引用不能改变,但对象本身属性可以改变;
final 方法:方法不能被重写修改
final 类:不能被继承
final 变量:
import static java.lang.System.out;
import java.util.Random;
/**
* 此类用于测试final的各种用法
* @author 百度
*/
class Test{
int i=0;
}
public class FinalData
{
static Random rand=new Random();
private final int VALUE_1=9; //声明一个final常量
private static final int VALUE_2=10; //声明一个final、static常量
private final Test test=new Test(); //声明一个final引用
private Test test2=new Test(); //声明一个不是final的引用
private final int[] a={1,2,3,4,5,6}; //声明一个定义为final的数组
private final int i4=rand.nextInt(20);
private static final int i5=rand.nextInt(20);
public String toString()
{
return i4+" "+i5+" ";
}
public static void main(String[] args)
{
FinalData data=new FinalData();
out.print(data.test.i++); //可以对指定为final的引用中的成员变量赋值
out.print(' ');
//data.test=new Test(); //不能将定义为final的引用指向其他引用
//data.value2++; //不能改变定义为final的常量值
data.test2=new Test(); //可以将没有定义为final的引用指向其他引用
for(int i=0;i<data.a.length;i++)
{
// a[i]=9; //不能对定义为final的数组赋值
}
out.println(data);
out.println("data2");
out.println(new FinalData());
}
}
在本实例中,被定义为final的常量需要定义时使用大写字母命名,并且中间需要使用下划线进行连接,这是Java中的编码规则。同时,定义为final的数据不论是常量、对象引用还是数组在主函数中都不可以被改变。
我们知道一个被定义为final的对象引用只能指向唯一一个对象,不可以将它再指向其他对象,但是一个对象本身的值却是可以改变的,那么为了使一个常量真正做到不可更改,可以将常量声明为static final。
i5由于它被声明为static final形式,所以在内存中为它开辟一个恒定不变的区域,当再次实例化一个FinalData对象时,仍然指向i5这块内存区域,所以i5的值保持不变。i5是在装载时被初始化,而不是每次创建新对象时都被初始化;而i4会在重新实例化对象时被更改。
技巧:在Java中定义全局常量,通常使用public static final修饰,这样的常量只能在定义时被赋值。
int Test(final int x)
{ return x+1;
}
不可改变x参数的值
比如:new一个对象 FinalTest ft=new FinalTest(10); 这时要是再new一个对象,那个10就不能变了,必须是10
在一般情况下 final int a=4; 这就可以让a成为常量4,即使有不同的对象调用它,a还是4
但是 先用Random new出个对象rand,final int a=rand.nextInt(20); 这时再用不同的对象调用a,a就会有不同的值,所以将 static 和 final 一起用,会将a彻底定义成常量,因为static修饰的,不用对象来调用
final方法:
定义为final的方法不能被重写。将方法定义为final类型可以防止任何子类修改该类的定义与实现方式,同时定义为final的方法执行效率要高于非final方法。在修饰权限中曾经提到过private修饰符,如果一个父类的某个方法被设置为private修饰符,子类将无法访问该方法,自然无法覆盖该方法,所以一个定义为private的方法隐式被指定为final类型,这样无须将一个定义为private的方法再定义为final类型。
class Parents{
//定义private final的方法
private final void Do1()
{
System.out.println("Parents.Do1");
}
final void Do2()
{
System.out.println("Parents.Do2");
}
public void Do3()
{
System.out.println("Parents.Do3");
}
}
class Son extends Parents
{
public final void Do1()
{
System.out.println("Son.Do1");
}
// final void Do2() //final方法无法被继承,会报错
// {
// System.out.println("Son.Do2");
// }
public void Do3()
{
System.out.println("Son.Do3");
}
}
public class FinalMothed
{
public static void main(String[] args) {
Son son=new Son(); //实例化
son.Do1(); //调用doit()方法
Parents p=son; //执行向上转型操作
p.Do2();
p.Do3();
// p.Do1(); //p就点不出Do1(),不能调用private方法
}
}
结果:
son.Do1();
Parents.Do2();
son.Do3();
作为final方法不能被覆盖,例如do2()方法就不能在子类中被重写,但是在父类中定义了一个static final的doit()方法,同时在子类中也定义了一个do1()方法,从表面上来看,在子类中覆盖了父类的do1()方法,但是覆盖必须可以满足一个对象向上转型为它的基本类型并调用相同方法这样一个条件,例如在主方法中使用Parents p=son;语句执行向上转型操作,对象p只能调用正常覆盖的do3()方法,却不能调用do1()方法,可见在子类中的do1()方法并不是正常覆盖,此时只是生成一个新的方法而已作为final方法不能被覆盖,例如doit2()方法就不能在子类中被重写,但是在父类中定义了一个static final的doit()方法,同时在子类中也定义了一个doit()方法,从表面上来看,在子类中覆盖了父类的doit()方法,但是覆盖必须可以满足一个对象向上转型为它的基本类型并调用相同方法这样一个条件,例如在主方法中使用Parents p=s;语句执行向上转型操作,对象p只能调用正常覆盖的doit3()方法,却不能调用doit()方法,可见在子类中的doit()方法并不是正常覆盖,此时只是生成一个新的方法而已。(相当于子类中生成一个和父类同名的方法,而父类的方法被删除)
final类:
定义为final的类不能被继承。
如果希望一个类不允许任何类继承,并且不允许别人对这个类有任何改动,可以将这个类设置为final形式。
由于将某个类设置为final形式,所以类中的所有方法都被隐式设置为final形式,但是final类中的成员变量可以被定义为final或非final形式。
附加一片讲final的文章:
从jdk1.0到今天,JAVA技术经过十余年的发展,技术上已经发生了巨大的变化.但final变量的定义从它
诞生那天起,就没有发生任何变化,也就是这十多年它就一直表示它原来的意思.
但遗憾的是,经过十多年仍然有90%的人没有理解它的真实含义,也没有一篇文章,包括我所见到的所有介绍
JAVA的书籍(包括TKJ)都没有说清楚,我相信肯定有些作者是理解的,但没有一个作者向读者说清楚.而中国网友
大多数人被一篇胡说八道的<<浅谈Java中final,finalized,finally>>的文章跑过马.(脸红一下:当初我也是).
final变量的定义本身并不复杂,就是变量一经初始化就不能再指向其它对象.在c++中它是一个const指针,而
不是指向const变量的指针,const指针的意思是说它只能一直指向初始化时的那个地址.但那个地址中对象本身
是可以修改的.而指向const变量的指针是说所指对象本身是不能修改的.
如:final StringBuffer sb = new StringBuffer("Axman");
sb = new StringBuffer("Sager");//错误,sb不能再指向其它对象.
sb.append(" was changed!"); //sb指向的地象本身可以修改.
先说final变量初始化:
很多文章都这么说:其初始化可以在两个地方,一是其定义处,二是在构造函数中,两者只能选其一。
胡说八道!
final变量可以在任何可以被始化的地方被始化,但只能被初始化一次.一旦被初始化后就不能再次赋
值(重新指向其它对象),作为成员变量一定要显式初始化,而作为临时变量则可以只定义不初始化(当然也不能引用)
即使是作为一个类中的成员变量,也还可以在初始化块中初始化,所以"其初始化可以在两个地方,一是其定义处,
二是在构造函数中,两者只能选其一"是错误的.
作为成员变量时,final字段可以设计不变类,是不变类的一个必要条件但不是一个充要条件.至少可以保证字段不
会以setXXX()这样的方式来改变.但无法保证字段本身不被修改(除非字段本身也是不变类);
对于方法参数的final变量:
对于方法参数的变量定义为final,90%以上的文章都说"当你在方法中不需要改变作为参数的对象变量时,明确使
用final进行声明,会防止你无意的修改而影响到调用方法外的变量。"
胡说八道!
我不知道这个修改是说重新赋值还是修改对象本身,但无论是哪种情况,上面的说法都是错误的.
如果是说重新赋值,那么:
public static void test(int[] x){
x = new int[]{1,2,3};
}
int[] out = new int[]{4,5,6};
test(out);
System.out.println(out[0]);
System.out.println(out[1]);
System.out.println(out[2]);
调用test(out);无论如何也不会影响到外面变量out.你加不加final根本没有意义.final只会强迫方法内
多声明一个变量名而已,即把x = new int[]{1,2,3};改成int y = new int[]{1,2,3}; 其它没有任何实际意义.
如果说是修改对象本身:
public static void test(final int[] x){
x[0] = 100;
}
int[] out = new int[]{4,5,6};
test(out);
System.out.println(out[0]);
难道你用final修饰就不可以修改了?所以说对于方法参数中final是为了不影响调用方法外的变量那是胡说八道的.
那我们到底为什么要对参数加上final?其实对方法参数加final和方法内变量加上final的作用是相同的,即为了将它们
传给内部类时保证调用的一致性:
abstract class ABSClass{
public abstract void m();
}
现在我们来看,如果我要实现一个在一个方法中匿名调用ABSClass.应该:
public static void test(final String s){
//或final String s = "axman";
ABSClass c = new ABSClass(){
public void m(){
int x = s.hashCode();
System.out.println(x);
}
};
//其它代码.
}
后面直接调用c.m();应该是没有意义的.但这不重要,重要的是只要有可能是在其它线程中调用,那我们就必须
从代码上看,在一个方法内部定义的内部类的方法访问外部方法内局部变量或方法参数,是非常自然的事,但内部类编译的时候如何获取这个变量,因为内部类除了它的生命周期是在方法内部,其它的方面它就是一个普通类。那么它外面的那个局部变量或方法参数怎么被内部类访问?编译器在实现时实际上是这样的:
public static void test(final String s){
//或final String s = "axman";
ABSClass c = new ABSClass(s){
private final String s;
public ABSClass(String s){
this.s = s;
}
public void m(){
int x = s.hashCode();
System.out.println(x);
}
};
//其它代码.
}
即外部类的变量被作为构造方法的参数传给了内部类的私有成员.
假如没有final,那么:
public static void test(String s){
//或String s = "axman";
ABSClass c = new ABSClass(){
public void m(){
s = "other";
}
};
System.out.println(s);
}
就会编译成:
public static void test(String s){
//或String s = "axman";
ABSClass c = new ABSClass(s){
private final String s;
public ABSClass(String s){
this.s = s;
}
public void m(){
s = "other";
}
};
System.out.println(s);
}
内部类的s重新指向"other"并不影响test的参数或外部定义的那个s.而你看到的
public static void test(String s){
//或String s = "axman";
ABSClass c = new ABSClass(){
public void m(){
s = "other";
}
};
System.out.println(s);
}
同理如果外部的s重新赋值内部类的s也不会跟着改变。
在语法上是一个s,在内部类中被改变了,但结果打印的出来的你认为是同一的s却还是原来的"axman",
你能接收这样的结果吗?
所以final从语法上约束了两个不同变量的一致性.