coding……
但行好事 莫问前程

Java编程拾遗『final详解』

之前讲String类的时候,讲了String类是不可变类,其实现方式中有一个重要的保障是final—final修饰class以及final修饰了内部成员变量char[]。本文会详细讲一下final的使用场景以及使用final的好处。

1. final的基本用法

final是Java中的一个关键字,可以用来声明类、变量、方法和方法参数,分别表示类不可继承、成员变量引用不可改变、方法不可覆盖以及参数引用不可修改。如果一个成员变量或者参数被声明为final类型的,那么该引用就是不可修改的,编译器会进行检查,如果再次改变引用地址的话,会编译报错。

1.1 final类

使用final修饰的类表示是不可被继承的,如果一个类功能是完整的,并且你不希望它被子类继承并重写自己定义的方法实现,那么就可以将类声明为final,final类中的所有成员方法都会被隐式地指定为final方法。比如之前讲的String类,以及Java中基本类型的包装类都是final修饰的。

@Getter
@Setter
public final class FinalTest {

    private String Name;

    private Integer Age;
}

//以下编译报错,Cannot inherit from final 'com.zhuoli.service.thinking.java.final0.FinalTest'
public class FinalClassExtendTest extends FinalTest {
    
}

1.2 final变量

final可以用来修饰成员变量或者本地变量(方法或者代码块中的变量),比如上节讲的String内部的final char数组,来限定数组在初始化后,引用地址不可再修改。在修饰成员变量时,final变量经常和static关键字一起使用,表示成员变量时所有实例共享的常量。如下:

public static final String APP_NAME = "zhuoli";

另外看一下,final变量的不可变性。不可变有两重含义:一是引用不可变,二是对象不可变。final修饰的变量到底属于哪一种?

public static void main(String[] args) {
    final StringBuilder stringBuilder = new StringBuilder("hello");
    stringBuilder.append(" world!");

    System.out.println(stringBuilder);
}

public static void main(String[] args) {
    final StringBuilder stringBuilder = new StringBuilder("hello");
    
    //编译报错
    stringBuilder = new StringBuilder("hello world!");
}

说明final变量的不可变指的是引用不可变,即它只能只能指向初始化时指向的那个对象,而不关心内容的变化。所以final修饰的变量必须被初始化,否则会编译报错(对于本地变量,如果未初始化并且未被使用,不会报错)。对于final成员变量,可以在以下位置初始化:

  • 在定义时直接初始化
  • 非静态final成员变量可以在非静态代码块中初始化(非静态final成员变量不可在静态代码块中初始化)
  • 静态final成员变量可以在静态代码块中初始化(静态final成员变量不可以在非静态代码块中初始化)
  • 非静态final成员变量可以在构造函数中初始化(静态final成员变量不可以在构造函数中初始化)

1.3 final方法

当一个方法被声明为final时,表示该方法不允许子类重写。《Java编程思想》中讲述了使用final方法的原因,如下:

使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。

final只用于修饰public方法,因为对于private方法,本来就不会被子类重写,再用final修饰就属于冗余了。

1.4 final参数

final修饰参数,表示方法中不可以修改这个参数(同样是引用不可变)。对于final参数,有一种比较常见的错误观念,防止方法内无意的修改而影响到调用方法外的变量。下面来解释一下,这个观点的问题在哪。

public final class FinalTest {

    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("hello ");
        StringBuilder sb1 = new StringBuilder("hello ");
        StringBuilder sb2 = new StringBuilder("hello ");
        changeStringBuilder(sb, sb1, sb2);
        System.out.println("sb: " + sb); //hello change
        System.out.println("sb1: " + sb1); //hello change
        System.out.println("sb1: " + sb2); //hello

        int i = 9;
        int j = 9;
        changeBasicType(i, j);
        System.out.println("i: " + i);
        System.out.println("j: " + j);
    }

    public static void changeStringBuilder(final StringBuilder finalStringBuilder, StringBuilder commonStringBuilder0, StringBuilder commonStringBuilder1) {
        //以下编译报错,final参数不可重新赋值
        //finalStringBuilder = new StringBuilder("change StringBuilder");
        
        finalStringBuilder.append("change");

        commonStringBuilder0.append("change ");

        commonStringBuilder1 = new StringBuilder("change StringBuilder");
    }

    public static void changeBasicType(final int i, int j) {
        //以下编译报错,final参数不可重新赋值
        //i = 10;

        j = 10;
    }
}

运行结果:

sb: hello change
sb1: hello change 
sb1: hello 
i: 9
j: 9

对于基本数据类型,由于值传递的原因,形参只是实参值的一个副本,对于形参的改变并不会修改实参的值。所以基本类型参数使用final修饰与否,都不会对实参产生任何影响(final参数不允许修改,非final参数即使修改了也不影响实参)。所以final修饰基本类型参数,并没有起到所谓的防止方法内无意的修改而影响到调用方法外的变量的作用。

对于对象类型,参数传递是”引用”传递(实质上还是值传递,只不过传的是实参对象的地址),所以实参和形参其实指向同一个地址,所以在方法中对于对象内容的改变,会影响到实参。对于对象类型而言,final只是约束引用地址不可变,对象内容是可变的,所以在方法中改变对象类型参数的内容,无论参数使用final修饰与否,都会影响到实参。对于上述示例中的commonStringBuilder1,虽然没有使用final修饰,并且在方法中也重新赋值了,但是并没有影响到实参(final限定参数不能被重新赋值,非final限定的参数重新赋值了,对实参也没有什么影响,是不是有一种final约束其实没什么作用的感觉)。所以final修饰对象类型参数,并没有起到所谓的防止方法内无意的修改而影响到调用方法外的变量的作用。

所以我认为在final修饰参数的作用就是告诉方法,这个参数在方法中不可以重新赋值。能用到final参数的地方也就是之前在Java编程拾遗『内部类』这篇文章中讲到的局部内部类和匿名内部类访问方法的参数时,参数必须是final的。除此之外,无特殊要求,不需要把参数声明为final。

赞(0) 打赏
Zhuoli's Blog » Java编程拾遗『final详解』
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址