final修饰的功能:
一、修饰变量
final修饰变量的作用——使得该变量以但获得了初始值就不可以被改变。无论是修饰成员变量还是普通变量,又或者是基础数据类型变量和引用类型变量都是一样的。只是在细节上,有些许差别。
修饰变量主要考虑两种情况:
1、该变量是基础数据类型还是引用数据类型
2、该变量是成员变量还是局部变量
二、修饰方法
三、修饰类
我们先看成员变量(包括类成员变量和普通成员变量),当类和对象在创建的时候,都是先由系统根据其类型对其进行相应的初始化操作,为其赋初始值。然后再执行程序员进行的初始化操作(如果有则执行,没有就不执行)。所以一个类或一个对象一但创建完成,成员变量一定是完成了初始化操作的。因为final修饰符在修饰变量的时候,规定被修饰的变量一但获得初始值就不能被改变。所以,Java语法规定,由final修饰的成员变量,系统将不再进行自动初始化操作(也可以说隐式初始化操作),必须由程序员进行手动的初始化操作。
总结以下final修饰成员变量的语法要求——final修饰的成员变量必须由程序员进行手动初始化操作。
我们试想一下,如果final修饰的成员变量仍然允许系统对其进行初始化操作,那么我就只能一直使用该成员变量的默认值了,几乎所有的情况系统给的默认值并不是我们想要的。
既然final修饰的成员变量规定必须由程序员对其进行显示初始化操作,那么接下来,看下程序员能在哪些地方对成员变量进行初始化操作。
对于成员变量,程序员能在哪些地方对其进行初始化操作呢?
对于一个类成员变量来说,允许程序员对类成员变量进行手动初始化操作的只有两个地方。
1.声明完类变量并初始化——static int a = 1;//不可以这样写final static int a; a = 1;语法错误
2.在静态初始化块中对类成员变量手动进行初始化。
对于一普通成员变量来说,允许程序员对普通成员变量进行手动初始化操作的只有三个地方。
1.声明完普通成员变量并赋值——int a = 1;//不可以这样写int a; a = 5;语法错误
2.在普通初始化块中,对普通成员变量进行初始化操作。
3.在构造器中,对普通成员变量进行初始化操作。
特别指出,当final修饰成员变量(类成员变量、普通成员变量)的时候,必须由程序员手动的进行初始化操作,所以对于final修饰的成员变量一定在上述说明的地方进行相应的初始化赋值,同时要注意避免在其他地方对final修饰的成员变量进行二次赋值,从而产生语法错误。
特别强调,对于final修饰的成员变量来说,由于系统不会取其进行自动初始化操作,所以对于声明既赋值和初始化块这两点要注意它们的执行顺序,在前面的先执行,永远比构造器先执行。避免造成在final修饰的成员变量在还没进行初始化的时候,就被使用。(Java语言规定任何变量在没有初始值的时候,是不允许被使用的除非是赋值操作)
final修饰局部成员变量。我们再看一遍final关键字的作用以加深印象。final关键字修饰变量的时候,使得变量一但获得了初始值就不可以被改变。此处需要注意,在声明一个局部变量的时候,系统是不会对其进行自动初始化的,局部变量的初始化操作必须由程序员手动完成。同时Java语法规定任何变量,如果该变量没有被初始化或赋值是不能使用的。例如一个局部变量int a;虽然此时对变量a进行了声明,但是并没有对其进行初始化和赋值操作,所以是无法直接使用变量a,除非对其进行赋值操作,之后才可以使用。所以,当用final修饰局部变量的时候,只能由程序员显示得给局部变量初始值。
特别注意,对于java语言的局部变量声明,声明的变量必须由程序员手动的初始化之后才能使用,这一点和C语言是有很大区别的,C语言定义的局部变量程序员不手动进行初始化也是可以使用的。
个人理解:
一个局部变量获得初始值有两种方式(方法的参数除外,必须在调用的时候进行赋值)
1、通过初始化语句——int a = 1;
2、通过声明语句之后的第一次赋值。——int a; a = 1;
这两种情况都使得a变量获得初始值。
注意上述两种情况都可以称为对变量的初始化操作,但是只有第一种语句称为初始化语句(个人理解)
所以对于final修饰局部变量,上述两种情况都是可以的。
1、final int a = 1;//a变量已经获得了初始化,即不能再对a进行赋值操作了。
2、final int b; b = 2;//注意,当变量b被第一次赋值之后就获得了初始值,以后将不能再对b变量进行赋值操作。
总结下,final修饰的变量一但获得了初始值,其值将永远不会改变。简单来说,一但获得初始值,就是不能被进行二次赋值了。
final关键字修饰基础数据类型变量和引用数据类型变量的区别
首先,回顾下定义fianl的变量,final关键字修饰的变量一但被初始化,里面的数值将永远不会被改变这一点指的是所有的类型的变量(无论是基础数据类型还是引用数据类型的变量都是适用的)
再分析下基本数据类型变量和引用数据类型变量当中存储数据的区别。基本数据类型当中存储的数据为数值变量。引用类型变量当中存储的数据为对象的引用,也就是对象所在内存空间的地址。
那么根据定义——final修饰的变量,一但获得初始值,该变量将永远不会被改变,即里面存储的将永远是初始化时候的数值。
例如,final int a = 5;根据定义此时a变量当中存储的数据将永远都是数值5。
再看引用数据类型变量,例如
class A
{
int a;
}
final A a = new A();
注意,此时引用类型变量a当中存储的将永远是初始化时对象的地址,即永远只能指向初始化的那个对象。注意,final修饰变量的语法要求为修饰的变量一但获得初始值,该变量将永远不会改变,对于final修饰的引用数据类型变量来说,由于引用数据类型存储的是对象的地址,所以该变量将永远只能指向初始化时指向的那个对象。但是不会影响该引用变量所指向的那个对象本身的改变。例如:
可以通过引用变量a来修改所指向对象中的内容。
A a = new A();
a.a = 5;
a. a = 6;都是可以的。只是引用类型变量再也无法指向其他的对象了。例如
a = new A();//属于二次赋值,错误。
可以执行宏替换的final变量
满足什么条件,属于可执行宏替换的final变量。
1、变量必须用final关键字修饰
2、在定义该final变量时指定了初始值。
关于这句话,个人理解由于对于一个变量来说,进行初始化有两种形式。第一种为定义时就指定初始值,即执行初始化语句。第二种,定义完之后的第一次赋值。例如:int a = 5;//属于初始化语句,定义时就指定了初始值。例如:int a; a = 5;//属于定义完之后,的第一次赋值。同样对变量进行了初始化。针对第二点的要求——只有满足定义时并且赋值,即int a = 5;这种情况的变量,被final修饰符修饰的时候才有可能称为是“可执行宏替换的final变量”。
3、该初始值可以在编译的时候就确定下来。
个人分析,最难理解的是第三点。前两点可以很好理解,即写成如下样子就满足前两点的要求了。final 变量类型 变量名 = 初始值。例如 final int a = 5;就满足了前两点,当然该例子同样满足第三点。
第三点指的是在满足前两点的前提条件下,当等号右边的值满足在编译的时候就可以确定下来,那么该变量就是"可以执行宏替换的final变量"。
那么重点分析什么样的数值属于在编译的时候就可以确定下来的数值这一要求。
class A
{
int a;
}
A a = new A();//因为等号右边是对象,所以编译时无法确定下来。
A a1 = a;//等不可以,等号右边是变量
int b = 5;//等号右边为普通常量,可以。
int c = b;//不可以,等右边是变量。
int d = a.a + b + c +5;//不可以等号右边的表达式含有变量或者通过对象方法调用返回的数值。
int e = 1 + 2 + 3;可以,等号右边的表达式中都是普通常量。
String str = 1 +2 + 3 + “adfdsf”;等号右边的表达式为字符串连接运算,可以。
总结一下,final 变量类型 变量名 = 初始值。如果该变量是可执行宏替换的final变量,则对于初始值的要求为等号右边必须是在编译时就可以确定下来的值。所以,可以先简单的理解为等号右边的表达式只能是常量表达式或字符串的连接运算,即不能含有变量或方法或对象。编译器会将该final修饰的变量当成可执行宏替换的final变量。
那么接下来我们来看下什么是宏替换:
例如:
final int a = 5;//根据定义,改变量a就是一个可执行宏替换的final变量。
int b = a;
int c = a;
注意由于变量a是一个可执行宏替换的final变量。所以,当执行编译的时候。编译器会将上述代码中出现a变量的地方,先用常量5进行替换,然后再编译各个语句。即先进行宏替换,再将各个语句编译成相应的字节码语句。
注意,编译器的作用是将Java源代码先编译生成字节码文件,但是注意了,再执行编译的时候还会进行好多的准备工作,其中宏替换就是其中的一项。即在编译正式开始之前,将所有的可执行宏替换的final变量替换成相应的常量,对于上述的例子来说,会将所有使用的a变量先替换成常量5,然后才开始进行宏替换。
由此可知,可执行宏替换的final变量就完全的相当于了一个常量。那么问题来了,为什么要有这一设定,即通过final修饰符使得被修饰的变量完完全全的相当于一个常量呢?
个人理解有一个十分重要的一点就是代码的可读性,例如对于我们圆周率π来说,就可以定义一个可执行宏替换的final变量即final float PI = 3.14,这样比3.14看起来更加具有可读性。而且对于一个程序中,经常使用的常量也可以将它定义成可执行宏替换的final变量,这样使用起来也会更加的方便。
还有一点需要说明,个人感觉对于可以执行宏替换的final变量在使用的时候要完完全全的把它当成一个常量。
例如:
final int a = 5;
final int b = a;
注意,此时对于变量b来说,虽然等号右边是利用a变量对其进行赋值,但是由于a变量本身是可以执行宏替换的final变量,所以,b也是可以执行宏替换的final变量。
final修改方法
final修饰的方法不可以被子类重写,如果希望一些方法不可以被子类重写,那么可以用final关键字对该方法进行修饰。
final修饰类
final修饰的类,不能被继承,也就是不允许有子类。否则会报语法错误
因为子类可以获得父类的成员,同时还可以对其中的成员进行修改,所以如果一个类不希望被继承,则可以用final修饰