Final 关键字的用法

Final 关键字的用法

本篇博客示例来自来自 https://github.com/BruceEckel/OnJava8-examples

且大多数内容引用 https://lingcoder.github.io/OnJava8/#/sidebar

一、final 在 Java中的用法

1、final类

final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会再被扩展,那么就设计为final类。 final方法不能被子类的方法覆盖,但可以被继承。

2、final方法

如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。

使用final方法的原因有二:

第一、把方法锁定,防止任何继承类修改它的意义和实现。

第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。

3、final变量(常量)

用final修饰的成员变量表示常量,只能被赋值一次,赋值后值无法改变!

final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。

另外,final变量定义的时候,可以先声明,而不给初值,这种变量也称为final空白,无论什么情况,编译器都确保空白final在使用之前必须被初始化。但是,final空白在final关键字final的使用上提供了更大的灵活性,为此,一个类中的final数据成员就可以实现依对象而有所不同,却有保持其恒定不变的特征。

4、final参数

当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。

final不能用于修饰构造方法。

注:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。

二、final 数据

下面例子展示了 final 属性的使用:

class Value {
    int i; // package access
    
    Value(int i) {
        this.i = i;
    }
}

public class FinalData {
    private static Random rand = new Random(47);
    private String id;
    
    public FinalData(String id) {
        this.id = id;
    }
    // 带有编译时值的final基本类型,它们都可用作编译时常量
    private final int valueOne = 9;
    private static final int VALUE_TWO = 99;
    // public意味着可以在包外访问,static强调只有一个,final说明是一个常量。
    public static final int VALUE_THREE = 39;
    // 不是编译时常量,因为i4 和 INT_5 都是在运行时才能被赋值
    //不同对象的 i4 值可能是不同的
    private final int i4 = rand.nextInt(20);
    //不同对象的 INT_5 值是同一个
    static final int INT_5 = rand.nextInt(20);
    private Value v1 = new Value(11);
    //对于被final修饰的引用,引用指向的对象不能进行修改,但是对象的属性或者值可以进行修改
    private final Value v2 = new Value(22);
    private static final Value VAL_3 = new Value(33);
    // 数组也是对象,同理如上
    private final int[] a = {1, 2, 3, 4, 5, 6};
    
    @Override
    public String toString() {
        return id + ": " + "i4 = " + i4 + ", INT_5 = " + INT_5;
    }
    
    public static void main(String[] args) {
        FinalData fd1 = new FinalData("fd1");
        //- fd1.valueOne++; // 不能修改被final修饰的基本数据类型
        fd1.v2.i++; // 可以修改被final修饰的引用指向的对象的属性
        fd1.v1 = new Value(9); // 可以修改引用指向的对象,因为没有被final修饰
        for (int i = 0; i < fd1.a.length; i++) {
            fd1.a[i]++; // 原理如 fd1.v2.i++
        }
        //- fd1.v2 = new Value(0); // 不能修改v2引用指向的对象
        //- fd1.VAL_3 = new Value(1); // 如上
        //- fd1.a = new int[3];	// 如上
        System.out.println(fd1);
        System.out.println("Creating new FinalData");
        FinalData fd2 = new FinalData("fd2");
        System.out.println(fd1);
        System.out.println(fd2);
    }
}

运行结果:

fd1: i4 = 15, INT_5 = 18
Creating new FinalData
fd1: i4 = 15, INT_5 = 18
fd2: i4 = 13, INT_5 = 18

对于基本类型,final 使数值恒定不变,而对于对象引用,final 使引用恒定不变。一旦引用被初始化指向了某个对象,它就不能改为指向其他对象。但是,对象本身是可以修改的,Java 没有提供将任意对象设为常量的方法。(你可以自己编写类达到使对象恒定不变的效果)这一限制同样适用数组,数组也是对象。

三、空final

空白 final 指的是没有初始化值的 final 属性。编译器确保空白 final 在使用前必须被初始化。这样既能使一个类的每个对象的 final 属性值不同,也能保持它的不变性。

class Poppet {
    private int i;
    
    Poppet(int ii) {
        i = ii;
    }
}

public class BlankFinal {
    private final int i = 0; // 正常定义的final常量
    private final int j; // 空final
    private final Poppet p; // Blank final reference
    // 空final必须在构造方法中进行初始化,如果未进行初始化,则编译器会报错
    public BlankFinal() {
        j = 1;
        p = new Poppet(1);
    }
    
    public BlankFinal(int x) {
        j = x;
        p = new Poppet(x);
    }
    
    public static void main(String[] args) {
        new BlankFinal();
        new BlankFinal(47);
    }
}

说白了,如果用final修饰数据,咱们就必须在你使用之前进行初始化,可以直接在构造函数中或者在属性定义时进行初始化。

四、final方法和private

使用 final 方法的原因有两个。第一个原因是给方法上锁,防止子类通过覆写改变方法的行为。这是出于继承的考虑,确保方法的行为不会因继承而改变。

class WithFinals {
  // 使用private和final共同修饰,和只使用private修饰效果一样
  private final void f() {
    System.out.println("WithFinals.f()");
  }
  // 自动添加了 final
  private void g() {
    System.out.println("WithFinals.g()");
  }
}

class OverridingPrivate extends WithFinals {
  private final void f() {
    System.out.println("OverridingPrivate.f()");
  }
  private void g() {
    System.out.println("OverridingPrivate.g()");
  }
}

class OverridingPrivate2 extends OverridingPrivate {
  public final void f() {
    System.out.println("OverridingPrivate2.f()");
  }
  public void g() {
    System.out.println("OverridingPrivate2.g()");
  }
}

public class FinalOverridingIllusion {
  public static void main(String[] args) {
    OverridingPrivate2 op2 = new OverridingPrivate2();
    op2.f();
    op2.g();
    // 可以向上转型,但是不能调用op.f()或者op.g()
    OverridingPrivate op = op2;
    //- op.f();
    //- op.g();
    // Same here:
    WithFinals wf = op2;
    //- wf.f();
    //- wf.g();
  }
}

如果放开 op2.f() 的注释,编译器会报红,如果进行编译,会提示 java: f() 在 reuse.OverridingPrivate 中是 private 访问控制,因为如果是使用 private 或者 final 修饰的方法,它只是隐藏在类内部的代码。在 WithFinalsOverridingPrivateOverridingPrivate2中, **f() **和 g() 都是属于各自的方法,他们之间不存在任何联系,细心的小伙伴可能会发现在 OverridingPrivate 和 **OverridingPrivate2 **没有 @override 注解,如果强行加上 @override 注解,编译器会报错,由此看来,它们之间并没有重写父类的 private 方法,只是恰好有相同的命名而已。这也不难解释为什么调用 op.f() 编译器会报错。

五、final类

当说一个类是 finalfinal 关键字在类定义之前),就意味着它不能被继承。之所以这么做,是因为类的设计就是永远不需要改动,或者是出于安全考虑不希望它有子类。

class SmallBrain {}

final class Dinosaur {
    int i = 7;
    int j = 1;
    SmallBrain x = new SmallBrain();
    
    void f() {}
}

//- class Further extends Dinosaur {}
public class Jurassic {
    public static void main(String[] args) {
        Dinosaur n = new Dinosaur();
        n.f();
        n.i = 40;
        n.j++;
    }
}

如果放开注释,显而易见的会出现error: Cannot extend final class ‘Dinosaur‘错误,因为 Dinosaur 是不能被继承的。当然,被 final 修饰的类,其中包含的方法都被隐式的定义为 final

Final 关键字的用法

上一篇:计算机系统结构


下一篇:SDN第一次实验