【Java关键字】之final

java的final关键字表示最终的,不可改变的,就取其字面意思”不可改变的“。
final可以修饰类、方法、变量。那么分别是什么作用呢?

  1. 修饰类:表示类不可被继承
  2. 修饰方法:表示方法不可被覆盖
  3. 修饰变量:表示变量一旦被赋值就不可以更改它的值。java中规定final修饰成员变量必须由程序员显示指定变量的值。

在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)下面我们一一做介绍:

修饰类

当用final修饰一个类时,表明这个类不能被继承。
final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。

修饰方法

使用final方法的原因有两个:
第一个原因是把方法锁定,final方法不可被继承成员重新定义,即不能通过改写方法来实现多态性;
第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌(inline)调用。所谓内联方式,是指编译器不用像平常调用函数那样的方式来调用方法,而是直接将方法内的代码通过一定的修改后copy到原代码中(将方法主体直接插入到调用处,而不是进行方法调用)。这样可以让代码执行的更快(因为省略了调用函数的开销)。
注:类的private方法会隐式地被指定为final方法。

修饰变量

对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
final修饰成员变量时,要在哪里执行初始值?

(1)如果final修饰的是类变量,只能在静态初始化块中指定初始值或者声明该类变量时指定初始值。
  
(2)如果final修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值。

注意,只能在一个地方指定初始值!!

  • 说明:
    类变量是声明在class内,method之外,且使用static修饰的变量。
    实例变量是声明在class内,method之外,且未使用static修饰的变量。
    类变量与实例变量的区别是:
    1. 存储位置不同。静态变量存储于方法区,而实例变量存储于堆区。
    2. 生命周期不同。静态变量在加载类过程中优先加载,其生命周期取决于类的生命周期;实例变量在创建实例时才创建,它的生命周期取决于实例的生命周期。
    3. 引用对象不同。静态变量属于类,被类的所有实例共享,可以直接使用类名来引用也可以通过类的实例引用;而实例变量则属于某个对象,它必须在创建对象后才可以通过这个对象来使用。
    4. 使用方法不同。一个类只能有一个同名静态变量,无论是通过类或者任何一个实例对静态变量重新赋值,结果都是一样;而一个类创建多少个实例就会有多少个同名实例变量,各实例变量存储空间不同,对其中一个实例变量重新赋值不影响其它实例的同名变量。

深入理解final关键

类的final变量和普通变量有什么区别?

当用final作用于类的成员变量时(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可),成员变量必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值。

public class Test {
    public static void main(String[] args)  {
        String a = "hello2";
        final String b = "hello";
        String d = "hello";
        String c = b + 2;
        String e = d + 2;
        System.out.println((a == c));
        System.out.println((a == e));
    }
}
  • 为什么第一个比较结果为true,而第二个比较结果为fasle。这就是final变量和普通变量的区别了,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。因此在上面的一段代码中,由于变量b被final修饰,因此会被当做编译期常量,所以在使用到b的地方会直接将变量b替换为它的值。而对于变量d的访问却需要在运行时通过链接来进行。

  • 不过要注意,只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化,比如下面的这段代码就不会进行优化:

public class Test {
    public static void main(String[] args)  {
        String a = "hello2";
        final String b = getHello();
        String c = b + 2;
        System.out.println((a == c));

    }

    public static String getHello() {
        return "hello";
    }
}
false

被final修饰的引用变量指向的对象内容可变吗?

public class Test {
    public static void main(String[] args)  {
        final MyClass myClass = new MyClass();
        System.out.println(++myClass.i);

    }
}

class MyClass {
    public int i = 0;
}

这段代码可以顺利编译通过并且有输出结果,输出结果为1。这说明引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。

关于final参数的问题

  • 关于网上流传的“当在方法中不需要改变作为参数的对象变量时,明确使用final进行声明,会防止无意的修改而影响到调用方法外的变量”这句话,个人理解这样说是不恰当的。
    因为无论参数是基本数据类型的变量还是引用类型的变量,使用final声明都不会达到上面所说的效果。

  • 上面这段代码好像让人觉得用final修饰之后,就不能在方法中更改变量i的值了。殊不知,方法changeValue和main方法中的变量i根本就不是一个变量,因为java参数传递采用的是值传递,对于基本类型的变量,相当于直接将变量进行了拷贝。所以即使没有final修饰的情况下,在方法内部改变了变量i的值也不会影响方法外的i。

public class Test {
    public static void main(String[] args)  {
        MyClass myClass = new MyClass();
        StringBuffer buffer = new StringBuffer("hello");
        myClass.changeValue(buffer);
        System.out.println(buffer.toString());
    }
}

class MyClass {
    void changeValue(final StringBuffer buffer) {
        buffer.append("world");
    }
}
  • 运行这段代码就会发现输出结果为 helloworld。很显然,用final进行修饰并没有阻止在changeValue中改变buffer指向的对象的内容。
    假如把final去掉,万一在changeValue中让buffer指向了其他对象怎么办。如果把final去掉,然后在changeValue中让buffer指向了其他对象,也不会影响到main方法中的buffer,原因在于java采用的是值传递,对于引用变量,传递的是引用的值,也就是说让实参和形参同时指向了同一个对象,因此让形参重新指向另一个对象对实参并没有任何影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值