情不知所起,一往而深。恨不知所踪,一笑而泯。

修饰符final详解

最近又翻看了一遍Java的基础知识,发现当初自己关于修饰符final的理解就曾经迷茫过,为了帮助Java初学者更好度过迷茫期,特此献上此文(此文将重点讲解final修饰的变量,至于final修饰的类和方法,由于比较简单,就不再下面列出了)。

一、概述:

    final关键字可用于修饰类、变量和方法。final修饰变量时,表示该变量一旦获得了初始值就不可被改变(可以赋初值,但是不可以被改变),final既可以修饰成员变量(包括类变量和实例变量),也可以修饰局部变量、形参。

二、final成员变量:

1) 类变量:

final修饰类变量,要么在定义该类变量时指定初始值;要么在静态初始化块中指定初始值。

    /*正确代码演示*/  
    public class FinalClassTest{  
        final static int a=210;//在定义该类变量时指定初始值;  
        final static int b;  
      
        static{  
            b=211;//在静态初始化块中指定初始值;  
        }  
    }  
      
    /*错误代码演示*/  
    public class FinalClassErrorTest{  
        final static int aa=110;  
        final static int bb;  
        //系统不会为final成员变量(类变量和实例变量)进行隐形初始化;而此类变量既没有在定义时指定初始值,又没有在静态初始化块中指定初始值,所以非法。  
      
        final static int cc;  
      
        static{  
            aa=111;//类变量已经定义了初始值,不能再次赋值,因此此语句非法;  
        }  
      
        public void finalClassChange(){  
            bb=112;//在普通方法中为类变量赋值,因此此语句非法;  
        }  
    }  


2) 实例变量:

final修饰实例变量,要么在定义该实例变量时指定初始值;要么在普通初始化块中指定初始值;要么在构造器中指定初始值。


    /*正确代码演示*/  
    public class FinalInstanceTest{  
        final int c=310;//在定义该实例变量时指定初始值;  
        final int d;  
        final int e;  
      
        {  
            d=311;//在普通初始化块中指定初始值;  
        }  
          
        public FinalInstanceTest(){  
            e=312;//在构造器中指定初始值;  
        }  
    }  
      
    /*错误代码演示*/  
    public class FinalInstanceErrorTest{  
        final int cc=410;  
        final int dd;  
    //系统不会为final成员变量(类变量和实例变量)进行隐形初始化;而此实例变量既没有在定义时指定初始值,又没有在普通初始化块中指定初始值,又没有在构造器中赋值,所以非法。  
      
        final int ee;  
      
        {  
            cc=411;//实例变量已经定义了初始值,不能再次赋值,因此此语句非法;  
        }  
      
        public void finalInstanceChange(){  
            dd=412;//在普通方法中为实例变量赋值,因此此语句非法;  
        }  
      
        public FinalInstanceErrorTest(){  
      
        }  
    }  

三、final局部变量:

系统不会对局部变量进行隐形初始化,需要程序员显性初始化。

    public class FinalLocalTest{  
      
        public void invokTest(final int f){  
      
            f=510;//对final修饰的形参变量赋值,非法;  
      
            //该方法被调用时,由系统根据传入的参数来对形参完成初始化;  
      
        }  
      
        public void mainTest(){  
      
            final int g=511;//定义final变量时指定初始值,合法;  
      
            final int h;  
      
            h=512;//第一次赋初始值,合法;  
      
        }  
    }  


四、final修饰基本类型变量和引用类型变量的区别:

final修饰基本类型变量时,不能对基本类型变量重新赋值;对于引用类型而言,它保存的只是一个引用,final只保证这个引用类型变量所引用的地址不变,即一直引用同一个对象,但是这个对象的成员可以改变;

    class Student{  
        privte int age;  
        public Student(int age){  
      
            this.age=age;  
      
        }  
        //此处省略age的setter和getter方法;  
    }  
      
    public class FinalReferenceTest{  
      
        public static void main(String[]  args){  
            //final修饰数组变量,iArray是一个引用变量;  
            final int[] iArray={5,6,7,8};  
            Arrays.sort(iArray);//对数组元素进行排序,合法;  
            iArray[3]=9;//对数组元素赋值,合法;  
            iArray=null;//对iArray重新赋值,非法;  
            final Student st=new Student(21);  
            st.setAge(23);//改变Student对象的age实例变量,合法;  
            st=null;//对st重新赋值,非法;  
        }  
    }  


五、可执行"宏替换"的final变量:

对于一个final变量而言,不管是类变量、实例变量还是局部变量,只要该变量满足以下三条,则final变量就不再是一个变量,而是相当于是一个直接值:

1、使用final修饰符修饰;

2、在定义该final变量时指定了初始值;

3、该初始值可以在编译时就确定下来。


    public class MacroFinalTest{  
        public static void main(String[] args){  
            final String domainName="pplns.com";  
            final String domain="pplns"+".com";  
            System.out.println(domainName==domain);//返回值为true;  
      
            final String domainPrefix="pplns";  
            final String domainSuffix=".com";  
            final String myDomain=domainPrefix+domainSuffix;  
            System.out.println(domainName==myDomain);//返回值为true;  
        }  
    }  


    public class MacroTest{   
        public static void main(String[] args){   
            String domainName="pplns.com";   
            String domain="pplns"+".com";   
            System.out.println(domainName==domain);//返回值为true;  
            String domainPrefix="pplns";   
            String domainSuffix=".com";   
            String myDomain=domainPrefix+domainSuffix;   
            System.out.println(domainName==myDomain);//返回值为false;  
        }  
    }   


以上特意用final修饰的变量和无final修饰的变量进行对比,从而发现不同之处。

Java通过一个常量池来管理曾经用过的字符串常量,例如执行String domain="pplns.com";语句之后,常量池中就缓存了一个字符串“pplns.com”;如果程序再执行String domainName="pplns.com";,系统将会让domainName直接指向常量池中“pplns”字符串,因此domain==domainName将返回true;但是在上例MacroTest类中,myDomain是由两个变量拼接而成,而变量在编译期间是不会确定下来,只有在运行期间才会确定下来,所以domainName==myDomain返回的是false;而在上例MacroFinalTest类中,domainPrefix和domainSuffix是由final修饰的变量,所以这两个变量在编译期间就会当作“宏变量”,即常量来处理,所以myDomain在编译期间实际是由"pplns"和".com"拼接而成,所以domainName==myDomain返回的是true。

联系我们 联系人:Neal Zhang 联系邮箱:master@pplns.com
© 2017 pplns.com 版权所有.