java中的final修饰符

final关键字可用于修饰类、变量和方法,用于表示它修饰的类、方法和变量不可改变。
final修饰变量时,表示该变量一旦获得了初始值就不可被改变,final既可以修饰成员变量(包括类变量和实例变量),也可以修饰局部变量、形参。
由于final变量获得初始值之后不能被重新赋值,因此final修饰成员变量和修饰局部变量时有一定的不同。
1 final成员变量
成员变量是随类初始化或对象初始化而初始化的。当类初始化时,系统会为该类的类变量分配内存,并分配默认值;当创建对象时,系统会为该对象的实例变量分配内存,并分配默认值。
java语法规定:final修饰的成员变量必须由程序员显示地指定初始值
归纳起来,final修饰的类变量、实例变量能指定初始值的地方如下。
1.类变量:必须在静态初始化块中指定初始值或声明该类变量时指定初始值,而且只能在两个地方的其中之一指定。
2.实例变量:必须在非静态初始化块、声明该实例变量或构造器中指定初始值,而且只能在三个地方的其中之一指定。
final修饰的实例变量,要么在定义该实例变量时指定初始值,要么在普通初始化块或构造器中为该实例变量指定初始值。但需要注意的是,如果普通初始化块已经为某个实例变量指定了初始值,则不能再在构造器中为该实例变量指定初始值;final修饰的类变量,要么在定义该类变量时指定初始值,要么在静态初始化块中为该类变量指定初始值。
实例变量不能在静态初始化块中指定初始值,因为静态初始化块是静态成员,不可访问实例变量——非静态成员;类变量不能在普通初始化块中指定初始值,因为类变量在类初始化阶段已经被初始化了,普通初始化块不能对其重新赋值。
下面程序演示了final修饰成员变量的效果,详细示范了final修饰成员变量的各种具体情况。

public class FinalVariableTest
{
    //定义成员变量时指定默认值,合法
    final int a=6;
    //下面变量将在构造器或初始化块中分配初始值
    final String str;
    final int c;
    final static double d;
    //既没有指定默认值,又没有在初始化块、构造器中指定初始值
    //下面定义的ch实例变量是不合法的
    //final char ch;
    //初始化块,可对没有指定默认值的实例变量指定初始值
    {
        //在初始化块中为实例变量指定初始值,合法
        str="Hello";
        //定义a实例变量时已经指定了默认值
        //不能为a重新赋值,因此下面赋值语句非法
        //a=9;
    }
    //静态初始化块,可对没有指定默认值的类变量指定初始值
    static
    {
        //在静态初始化块中为类变量指定初始值,合法
        d=5.6;
    }
    //构造器,可对既没有指定默认值,又没有在初始化块中
    //指定初始值的实例变量指定初始值
    public FinalVariableTest()
    {
        //如果在初始化块中已经对str指定了初始值
        //那么在构造器中不能对final变量重新赋值,下面赋值语句非法
        //str="java";
        c=5;
    }
    public void changeFinal()
    {
        //普通方法不能为final修饰的成员变量赋值
        //d=1.2;
        //不能在普通方法中为final成员变量指定初始值
        //ch='a';
    }
    public static void main(String[] args)
    {
        FinalVariableTest ft=new FinalVariableTest();
        System.out.println(ft.a);
        System.out.println(ft.c);
        System.out.println(ft.d);
    }
}

2. final局部变量
系统不会对局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用final修饰局部变量时,既可以在定义时指定默认值,也可以不指定默认值。
如果final修饰的局部变量在定义时没有指定默认值,则可以在后面的代码中对该final变量赋初始值,但只能一次,不能重复赋值;如果final修饰的局部变量在定义时已经指定默认值,则后面代码中不能再对该变量赋值。下面程序示范了final修饰局部变量、形参的情形。

public class FinalLocalVariableTest
{
    public void test(final int a)
    {
        //不能对final修饰的形参赋值,下面语句非法
        //a=5;
    }
    public static void main(String[]  args)
    {
        //定义final局部变量时指定默认值,则str变量无法重新赋值
        final String str="hello";
        //下面赋值语句非法
        //str="java";
        //定义final局部变量时没有指定默认值,则d变量可被赋值一次
        final double d;
        //第一次赋初始值,成功
        d=5.6;
        //对final变量重复赋值,下面语句非法
        //d=3.4;
    }
}

3.final修饰基本类型变量和引用类型变量的区别
当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。
下面程序示范了final修饰数组和Person对象的情形

class Person 
{
    private int age;
    public Person(){}
    //有参数的构造器
    public Person(int age)
    {
        this.age=age;
    }
    //省略age的setter和getter方法
    //age的setter和getter方法
    ...
}
public class FinalReferenceTest
{
    public static void main(String[] args)
    {
        //final修饰数组变量,iArr是一个引用变量
        final int[] iArr={5,6,12,9};
        System.out.println(Arrays.toString(iArr));
        //对数组元素进行排序,合法
        Array.sort(iArr);
        System.out.println(Arrays.toString(iArr));
        //对数组元素赋值,合法
        iArr[2]=-8;
        System.out.println(Arrays.toString(iArr));
        //下面语句对iArr重新赋值,非法
        //iArr=null;
        //final修饰Person变量,p是一个引用变量
        final Person p=new Person(45);
        //改变Person对象的age实例变量,合法
        p.setAge(23);
        System.out.println(p.getAge());
        //下面语句对p重新赋值,非法
        //p=null;
    }
}

4.可执行“宏替换”的final变量
对一个final变量来说,不管它是类变量、实例变量、还是局部变量,只要该变量满足三个条件,这个final变量就不再是一个变量,而是相当于一个直接量。
1.使用final修饰符修饰。
2.在定义该final变量时指定了初始值。
3.该初始值可以在编译时就被确定下来。
**提示**java会使用常量池来管理曾经用过的字符串直接量,例如执行String a=”java”;语句之后,常量池中就会缓存一个字符串“java”;如果程序再次执行String b=”java”;,系统将会让b直接指向常量池中的“java”字符串,因此a==b将会返回true。

public class StringJoinTest
{
    public static void main(String[] args)
    {
        String s1="疯狂java";
        //s2变量引用的字符串可以在编译时就确定下来
        //因此s2直接引用常量池中已有的"疯狂java"字符串
        String s2="疯狂"+"java";
        System.out.println(s1==s2);
        //定义2个字符串直接量
        String str1="疯狂";     // ①
        String str2="java"     // ②
        //将str1和str2进行连接计算
        String s3=str1+str2;
        System.out.println(s1==s3);
    }
}
上面程序中两行粗体字代码分别判断s1和s2是否相等,以及s1和s3是否相等。s1是一个普通的字符串直接量“疯狂Java”,s2的值是两个字符串直接量进行连接计算,由于编译器可以在编译阶段就确定s2的值为“疯狂java”,所以系统会让s2直接指向常量池中缓存的“疯狂java”字符串。因此s1==s2将输出true。
对于s3而言,它的值由str1和str2进行连接运算后得到。由于str1和str2只是两个普通变量,编译器不会执行"宏替换",因此编译器无法在编译时确定s3的值,也就无法让s3指向字符串池中缓存的"疯狂java"。由此可见,s1==s3将输出false。
让s1==s3输出true也很简单,只要让编译器可以对str1和str2两个变量执行"宏替换",这样编译器即可在编译阶段就确定s3的值,就会让s3指向字符串池中缓存的"疯狂java"。也就是说,只要将①、②两行代码所定义的str1和str2使用final修饰即可。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值