Java关键字之“final”

final 、static等关键字是刚开始学习Java语言比较蒙的一类,并不是它有多难,而是容易忽略它们,一旦在代码中碰到又不知所以然,所以还是学习一下它吧。

final常用的场景有三种:数据、方法和类。

final数据

Java中有一种方法,来向编译器告知某一块数据是恒定不变的,就是final数据。有时final数据比较有用:

  1. 一个编译时恒定不变的常量
  2. 在运行时初始化,而你不希望它被改变

1)编译器常量的情况:编译器可以将该常量值带入任何可能用到它的计算式中,也就是说,可以在编译期就执行计算式,这减轻了一些运行时的负担。 在Java中,这类常量必须是基本类型,并且以final表示。在对这个常量定义时,必须进行赋值。

2)一个既是static又是final的域只占一段不能改变的存储空间(它与其他常量一同初始化时,它优先初始化;初始化之后再次创建不会被初始化)。

3)当final应用于对象引用时,它域基本类型的区别是,基本类型使用final,数值不能改变;而对于对象而言,不能改变的是它的引用,对象本身是可以修改的

4)当一个final引用被初始化指向一个对象后,这个引用将不能再指向其他对象。

5)Java并未提供对任何对象恒定不变的支持,这一限制也同样适用于数组,数组也是对象。

注意:根据惯例,既是static又是final的域(即编译器常量)将用大写表示,并用下划线分割单词:

class Value{
    int i;
    public 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;
    }
    //can be compile-time constants
    private final int valueOne=9;
    private static final int VALUE_TWO=99;
    //typical public constant
    public static final int VALUE_THREE=39;
    //cannot be compile-time constants
    private final int i4=rand.nextInt(20);
    static final int INT_5=rand.nextInt(20);
    private Value v1=new Value(11);
    private final Value v2=new Value(22);
    private static final Value VAL_3=new Value(33);
    //arrays
    private final int[] a={1,2,3,4,5,6};
    public String toString(){
        return id+":"+"i4="+i4+",INT_5="+INT_5;
    }

    
    public static void main(String[] args){
        FinalData fd1=new FinalData("fd1");
        //! fd1.valueOne++;   //error:cannot change value
        fd1.v2.i++;    //Object isnot constant
        fd1.v1=new Value(9);//OK--not final
         for(int i = 0; i < fd1.a.length; i++)  
      fd1.a[i]++; // Object isn't constant!  
    //! fd1.v2 = new Value(0); // Error: Can't  
    //! fd1.VAL_3 = new Value(1); // change reference  
    //! fd1.a = new int[3];  
    print(fd1);  
    print("Creating new FinalData");  
    FinalData fd2 = new FinalData("fd2");  
    print(fd1);  
    print(fd2);  
  }  
}   
  
/* Output: 
fd1: i4 = 15, INT_5 = 18 
Creating new FinalData 
fd1: i4 = 15, INT_5 = 18 
fd2: i4 = 13, INT_5 = 18 

思路:由于valueone和VALUE_TWO都是带有编译时数值的final基本类型,所以它们二者均可以用作编译期常量,并且没有重大区别。VALUE_THREE是一种更加典型的对常量进行定义的方式:

定义为public,可以被任何人访问,定义为static,强调只有一份,定义为final,说明它是个常量。注意:带有初始值(即编译器常量)的final static基本类型全用大写字母命名,并且字母与字母之间用下划线分隔。

扫描二维码关注公众号,回复: 4284061 查看本文章

我们不能因为某些数据是final的就认为在编译时可以知道它的值。在运行时使用随机数来初始化i4和INT_5的值则说明了这一点。fd1和fd2中i4的值是唯一的,每次都会被初始化为15,13.

INT_5的值是不可以通过创建第二个FinalData对象加以改变的。这是因为它是static的,在加载类时(也就是第一次创建这个类对象时)已经被初始化,而不是每次创建都被初始化。

如果看上面的事例来理解我标记颜色的的部分有点困难的话,请看下面的事例:

   

[java] view plain copy
public class B3 {  
    static Random r =new Random(12);  
    final int int1= r.nextInt(100);//产生0-99的随机数  
    static final int INT_2= r.nextInt(100);  
      
  
    public static void main(String[] args) {  
        B3 b1=new B3();  
        System.out.println("int1:"+b1.int1+"    INT_2:"+b1.INT_2);  
        B3 b2=new B3();  
        //b2.INT_2=100;//错误的赋值  
        System.out.println("int1:"+b2.int1+"    INT_2:"+b2.INT_2);  
  
    }  
  
}  


启动main()先执行的是B3 b1=new B3();,创建B3的第一个对象,这将会先初始化static final int INT_2= r.nextInt(100);,
然后是初始化final int int1= r.nextInt(100);,所以第一条输出语句的结果是int1:12    INT_2:66。
接下来创建B3的第二个对象,这也会导致B3类中成员的初始化,但static final int INT_2= r.nextInt(100);
不会在被初始化,为什么前面已经提过。输出的结果是int1:56    INT_2:66。两次的输出INT_2的值都是一样的。

   在说回我们的第一个事例,V1到VAL_3说明final引用的意义。正如在main()方法中看见的,可以改变对象数组a的值,
   但不能将a的引用指向另一个对象。看起来使基本类型成为fianl比引用类型成为final的用处大。

    java也许生成"空白final",所谓空白final是指被声明为final但又未给初值的域。
    无论什么情况下编译器都会保证final域在使用前初始化。但空白final在fianl的使用上提供了很大的灵活性,
    为此,一个fianl域可以根据某些对象有所不同,却又保持恒定不变的特性。下面的事例说明了一点。 
 

[java] view plain copy
class Poppet {  
  private int i;  
  Poppet(int ii) { i = ii; }  
}  
  
public class BlankFinal {  
  private final int i = 0; // Initialized final  
  private final int j; // Blank final  
  private final Poppet p; // Blank final reference  
  // Blank finals MUST be initialized in the constructor:  
  public BlankFinal() {  
    j = 1; // Initialize blank final  
    p = new Poppet(1); // Initialize blank final reference  
  }  
  public BlankFinal(int x) {  
    j = x; // Initialize blank final  
    p = new Poppet(x); // Initialize blank final reference  
  }  
  public static void main(String[] args) {  
    new BlankFinal();  
    new BlankFinal(47);  
  }  
} //  

final参数

Java中也许将参数列表中的参数以声明的方式指明为final。这意味着你无法改变参数所指向的对象。

class Gizmo{
    public void spin(){}
}

public class FinalArguments{
    void with(final Gizmo g){
        //!g=new Gizmo();  //illegal--g is final
    }
    void without(Gizmo g){
        g=new Gizmo();//OK --g is not final
        g.spin();
    }
    //void f(final int i){i++;} //cannot change
    //you can only read from a final primitive
    int g(final int i){return i+1;}
    public static void main(String[] args){
        FinalArguments bf=new FinalArguments();
        bf.without(null);
        bf.with(null);    
     }
}

声明:方法fg展示的基本类型的参数被指定为final时所出现的结果:你可以读参数,但不能修改参数。这一特性只是用来向匿名内部类传递数据。

final 方法

通常使用final修饰方法的原因是把方法锁定,以防止任何继承它的类修改它的含义。想要确保在继承中使用的方法保持不变,并且不会被覆盖。

final和private关键字

类中的所有private方法都是隐式的制定为final的。由于你无法访问private方法,也就无法覆盖它,可以对private方法添加final修饰符,但这毫无意义。

class WithFinals {  
  // Identical to "private" alone:  
  private final void f() { print("WithFinals.f()"); }  
  // Also automatically "final":  
  private void g() { print("WithFinals.g()"); }  
}  
  
class OverridingPrivate extends WithFinals {  
  private final void f() {  
    print("OverridingPrivate.f()");  
  }  
  private void g() {  
    print("OverridingPrivate.g()");  
  }  
}  
  
class OverridingPrivate2 extends OverridingPrivate {  
  public final void f() {  
    print("OverridingPrivate2.f()");  
  }  
  public void g() {  
    print("OverridingPrivate2.g()");  
  }  
}

"覆盖"只有在某方法是基类接口的一部分时才会发生。即,必须将一个对象向上转型为它的基类并条用相同的方法。
     如果某方法是private的,它就不是基类接口的一部分。它仅是一些隐藏于类中的程序代码,
     如果一个基类中存在某个private方法,在派生类中以相同的名称创建一个public,protected或包访问权限方法的话,
     该方法只不过是与基类中的方法有相同的名称而已,并没有覆盖基类方法。由于private方法无法触及且有很好的隐藏性,
     所以把它看成是因为他所属类的组织结的原因而存在外,其他任何事物都不用考虑。

final类

当你将类定义为final时,就表明你不打算继承该类,而且也不允许别人这么做。换句话说,你对该类的设计永不需要做任何变动,或者出于安全的考虑,你不希望它有子类

class SmallBrain {}  
  
final class Dinosaur {  
  int i = 7;  
  int j = 1;  
  SmallBrain x = new SmallBrain();  
  void f() {}  
}  
  
//! class Further extends Dinosaur {}  
// error: Cannot extend final class 'Dinosaur'  
  
public class Jurassic {  
  public static void main(String[] args) {  
    Dinosaur n = new Dinosaur();  
    n.f();  
    n.i = 40;  
    n.j++;  
  }  
} 

请注意,final类的域可以根据个人的意愿选择是或不是final。不论类是否被定义为final,
    相同的规则同样适用于定义为final的域。然而,由于final是无法继承的,所以被final修饰的类中的方法都隐式的制定为fianl,
    因为你无法覆盖他们。在fianl类中可以给方法添加final,但这不会产生任何意义。

<由于原文章链接一时找不到了,若有侵权 即删>

猜你喜欢

转载自blog.csdn.net/weixin_38664232/article/details/84319032