Java关键字介绍之final

简介:
Java中声明属性、方法和类时,可使用关键字final来修饰。final变量即为常量,只能赋值一次;final方法不能被子类重写;final类不能被继承。
1final成员
声明 final 字段有助于优化器作出更好的优化决定,因为如果编译器知道字段的值不会更改,那么它能安全地在寄存器中高速缓存该值。final 字段还通过让编译器强制该字段为只读来提供额外的安全级别。
 
1.1关于final成员赋值
1)在java中,普通变量可默认初始化。但是final类型的变量必须显式地初始化。
 
2final 成员能且只能被初始化一次。
 
3final成员必须在声明时(在final变量定义时直接给其赋值)或者在构造函数中被初始化,而不能在其它的地方被初始化。
示例1 Bat.java
public   class  Bat {
     final   double   PI  = 3.14;  //  在定义时赋值
     final   int   i //  因为要在构造函数中进行初始化 所以此处便不可再赋值
     final  List<Bat>  list //  因为要在构造函数中进行初始化 所以此处便不可再赋值
 
    Bat() {
        i  = 100;
        list  =  new  LinkedList<Bat>();
     }
 
    Bat( int  ii, List<Bat> l) {
        i  = ii;
        list  = l;
    }
 
     public   static   void  main(String[] args) {
       Bat b =  new  Bat();
       b. list .add( new  Bat());
        // b.i=25;
        // b.list=new ArrayList<Bat>();
       System. out .println( "I="  + b. i  +  " List Type:"  + b. list .getClass());
       b =  new  Bat(23,  new  ArrayList<Bat>());
       b. list .add( new  Bat());
       System. out .println( "I="  + b. i  +  " List Type:"  + b. list .getClass());
    }
}
 
结果:
I=100 List Type:class java.util.LinkedList
I=23 List Type:class java.util.ArrayList
 
main方法中有两行语句注释掉了,如果你去掉注释,程序便无法通过编译,这便是说,不论是i的值或是list的类型,一旦初始化,确实无法再更改。然而b可以通过重新初始化来指定i的值或list的类型。
 
1.2 final引用字段的无效初始化
要正确使用final字段有点麻烦,对于其构造子能抛出异常的对象引用来说尤其如此。因为 final 字段在每个构造器中必须只初始化一次,如果 final 对象引用的构造器可能抛出异常,编译器可能会报错,说该字段没有被初始化。编译器一般比较智能化,足以发现在两个互斥代码分支(比如,if...else 块)的每个分支中的初始化恰好只进行了一次,但是它对 try...catch 块通常不会如此“宽容”。
下面这段代码通常会出现问题。
class  Thingie {
     public   static  Thingie getDefaultThingie() {
        return   new  Thingie();
    }
}
 
public   class  Foo {
     private   final  Thingie  thingie ;
 
     public  Foo() {
        try  {
            thingie  =  new  Thingie();
        catch  (Exception e) {
            thingie  = Thingie.getDefaultThingie(); //Error:The final field thingie may already have been assigned
       }
    }
}
 
你可以这样修改。
public   class  Foo {
     private   final  Thingie  thingie ;
 
     public  Foo() {
       Thingie tempThingie;
        try  {
           tempThingie =  new  Thingie();
        catch  (Exception e) {
           tempThingie = Thingie.getDefaultThingie();
       }
        thingie  = tempThingie;
    }
}
 
1.3关于final成员使用
当你在类中定义变量时,在其前面加上final关键字,那便是说,这个变量一旦被初始化便不可改变,这里不可改变的意思对基本类型来说是其值不可变,而对于对象变量来说其引用不可再变。然而,对象其本身却是可以被修改的,Java并未提供使任何对象恒定不变的途径。这一限制同样适合数组,它也是对象。
示例2
private   final   int   VAL_ONE =9;
private   static   final   int   VAL_TWO =99;
public   static   final   int   VAL_THREE =999;
由于VAL_ONE VAL_TOW 是带有编译期数值的final 原始类型,所以它们二者均可以用作编译期常量,并且没有重大区别。VAL_THREE是一种更加典型的对常量进行定义的方式:定义为 public,则可以被用于包之外;定义为 static 来强调只有一份;定义为 final 来说明它是一个常量。
final标记的变量即成为常量,但这个常量也只能在这个类的内部使用,不能在类的外部直接使用。但是当我们用public static final 共同标记常量时,这个常量就成为全局的常量(一个既是static又是final的字段只占据一段不能改变的存储空间)。而且这样定义的常量只能在定义时赋值,其他地方都不行。
示例3
class  Value {
     int   i ;
 
     public  Value( int  i) {
        this . i  = i;
    }
}
 
public   class  FinalData {
     private   static  Random  rand  =  new  Random();
 
     private  String  id ;
 
     public  FinalData(String id) {
        this . id  = id;
    }
 
     private   final   int   i4  =  rand .nextInt(20);
 
     static   final   int   i5  =  rand .nextInt(20);
 
     public  String toString() {
        return   id  +  ":"  +  "i4:"  +  i4  +  ", i5="  +  i5 ;
    }
 
     public   static   void  main(String[] args) {
       FinalData fd1 =  new  FinalData( "fd1" );
       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:6, i5=3
Creating new FinalData
fd1:i4:6, i5=3
fd2:i4:17, i5=3
示例部分展示了将final 数值定义为statici5 和非statici4 的区别。此区别只有在数值在运行期内被初始化时才会显现,这是因为编译器对编译期数值一视同仁。(并且它们可能因优化而消失。)当你运行程序时,就会看到这个区别。请注意,在fd1 fd2 中, i5 的值是不可以通过创建第二个FinalData 对象而加以改变的。这是因为它是 static,在装载时已被初始化,而不是每次创建新对象时都初始化。
示例4
class  Value {
     int   i ;
 
     public  Value( int  i) {
        this . i  = i;
    }
}
 
public   class  … {
     private  Value  v1 = new  Value(11);
     private   final  Value  v2 = new  Value(22);
     private   static   final  Value  v3 = new  Value(33);
   
}
 
public   static   void  main(String[] args) {
    
    fd1. v2 . i ++; // OK--Object isn't constant!
    fd1. v1 = new  Value(9); //OK--not final
    fd1. v2 = new  Value(0); //Error:Can't change reference
    fd1. v3 = new  Value(1); //Error:Can't change reference
    
}
v1 v3 的变量说明了final 引用的意义。正如你在main( )中所看到的,不能因为v2 final 的,就认为你无法改变它的值。由于它是一个引用,final 意味着你无法将v2 再次指向另一个新的对象。
示例5
public   class  … {
     private   final   int []  a ={1,2,3,4,5,6};
    
}
 
public   static   void  main(String[] args) {
    
     for ( int  i=0;i<fd1. a . length ;i++)
 fd1. a [i]++; // OK--Object isn't constant!
    fd1. a = new   int [3]; //Error:Can't change reference     
}
对数组具有同样的意义(可以改变它的值,但不能指向一个新的对象),数组是另一种引用。
 
1.4解决final数组的局限性
尽管数组引用能被声明成 final,但是该数组的元素却不能。这意味着暴露 public final 数组字段的或者通过它们的方法将引用返回给这些字段的类都不是不可改变的。
// Not immutable -- the states array could be modified by a malicious
// callerpublic
class  DangerousStates {
     private   final  String[]  states  =  new  String[] {  "Alabama" "Alaska" "ect"  };
 
     public  String[] getStates() {
        return   states ;
    }
}
 
同样,尽管对象引用可以被声明成 final 字段,而它所引用的对象仍可能是可变的。如果想要使用 final 字段创建不变的对象,必须防止对数组或可变对象的引用“逃离”你的类。要不用重复克隆该数组做到这一点,一个简单的方法是将数组转变成 List
// Immutable -- returns an unmodifiable List insteadpublic
class  SafeStates {
     private   final  String[]  states  =  new  String[] {  "Alabama" "Alaska" "ect"  };
 
     private   final  List  statesAsList  =  new  AbstractList() {
        public  Object get( int  n) {
            return   states [n];
       }
 
        public   int  size() {
            return   states . length ;
       }
    };
 
     public  List getStates() {
        return   statesAsList ;
    }
}
 
1.5关于final参数使用
还有一种用法是定义方法中的参数为final,对于基本类型的变量,这样做并没有什么实际意义,因为基本类型的变量在调用方法时是传值的,也就是说你可以在方法中更改这个参数变量而不会影响到调用语句,然而对于对象变量,却显得很实用,因为对象变量在传递时是传递其引用,这样你在方法中对对象变量的修改也会影响到调用语句中的对象变量,当你在方法中不需要改变作为参数的对象变量时,明确使用final进行声明,会防止你无意的修改而影响到调用方法。
 
1.6关于内部类中的参数变量
另外方法中的内部类在用到方法中的参变量时,此参数变量必须声明为final才可使用。
示例6 INClass.java
public   class  INClass {
     void  innerClass( final  String str) {
        class  IClass {
           IClass() {
              System. out .println(str);
           }
       }
       IClass ic =  new  IClass();
    }
 
     public   static   void  main(String[] args) {
       INClass inc =  new  INClass();
       inc.innerClass( "Hello" );
    }
}
2final方法
2.1final方法用途
1)为了确保某个函数的行为在继承过程中保持不变,并且不能被覆盖(overridding),可以使用final方法。
 
2class中所有的privatestatic方法自然就是final
 
2.2 finalprivate关键字
类中所有的private方法都隐式地指定是final的。由于无法取用private方法,所以也就无法覆盖它。
“覆盖”只有在某方法是基类的接口的一部分时才会出现。即,必须能将一个对象向上转型为它的基本类型并调用相同的方法。如果某方法为private,它就不是基类的接口的一部分。它仅是一些隐藏于类中的代码,只不过是具有相同的名称而已。但如果在导出类以相同的方法生成一个publicprotected或包访问权限方法的话,该方法就不会产生在基类中出现的“仅具有相同名称”的情况。此时,你并没有覆盖该方法,仅是生成了一个新的方法。由于private方法无法触及而且能有效隐藏,所以除了把它看成是因为它所归属的类的组织结构的原因而存在外,其他任何事物都不需要考虑它。
3final
将某个类的整体定义为final 时,该类无法被继承。而且由于final类禁止继承,所以final类中所有的方法都隐式指定为final的,因为无法覆盖它们。
final 用于类或方法是为了防止方法间的链接被破坏。例如,假定类 X 的某个方法的实现假设了方法 M 将以某种方式工作。将 X  M 声明成 final 将防止派生类以这种方式重新定义 M,从而导致 X 的工作不正常。尽管不用这些内部相关性来实现 X 可能会更好,但这不总是可行的,而且使用 final 可以防止今后这类不兼容的更改。


本文转自zhangjunhd51CTO博客,原文链接:http://blog.51cto.com/zhangjunhd/20664,如需转载请自行联系原作者
相关文章
|
存储 缓存 安全
除了变量,final还能修饰哪些Java元素
在Java中,final关键字不仅可以修饰变量,还可以用于修饰类、方法和参数。修饰类时,该类不能被继承;修饰方法时,方法不能被重写;修饰参数时,参数在方法体内不能被修改。
166 3
|
10月前
|
缓存 安全 Java
Volatile关键字与Java原子性的迷宫之旅
通过合理使用 `volatile`和原子操作,可以在提升程序性能的同时,确保程序的正确性和线程安全性。希望本文能帮助您更好地理解和应用这些并发编程中的关键概念。
252 21
|
9月前
|
Java C语言
课时8:Java程序基本概念(标识符与关键字)
课时8介绍Java程序中的标识符与关键字。标识符由字母、数字、下划线和美元符号组成,不能以数字开头且不能使用Java保留字。建议使用有意义的命名,如student_name、age。关键字是特殊标记,如蓝色字体所示。未使用的关键字有goto、const;特殊单词null、true、false不算关键字。JDK1.4后新增assert,JDK1.5后新增enum。
176 4
|
8月前
|
存储 安全 Java
深入理解 Java 中的 instanceof 关键字
本文深入解析了 Java 中的 `instanceof` 关键字,探讨其在类型判断中的作用。作为二元操作符,`instanceof` 可用于检查对象是否为某类实例或实现特定接口,避免类型转换异常 (`ClassCastException`)。文章通过多态性下的类型判断、安全类型转换、接口实现检测及集合元素类型判定等实际应用场景,展示了 `instanceof` 的强大功能。掌握该关键字可提高代码健壮性,确保运行时类型安全。
571 0
|
12月前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
314 5
Java 并发编程——volatile 关键字解析
|
11月前
|
Java 编译器 开发者
Java中的this关键字详解:深入理解与应用
本文深入解析了Java中`this`关键字的多种用法
1701 9
|
11月前
|
JSON Java 数据挖掘
利用 Java 代码获取淘宝关键字 API 接口
在数字化商业时代,精准把握市场动态与消费者需求是企业成功的关键。淘宝作为中国最大的电商平台之一,其海量数据中蕴含丰富的商业洞察。本文介绍如何通过Java代码高效、合规地获取淘宝关键字API接口数据,帮助商家优化产品布局、制定营销策略。主要内容包括: 1. **淘宝关键字API的价值**:洞察用户需求、优化产品标题与详情、制定营销策略。 2. **获取API接口的步骤**:注册账号、申请权限、搭建Java开发环境、编写调用代码、解析响应数据。 3. **注意事项**:遵守法律法规与平台规则,处理API调用限制。 通过这些步骤,商家可以在激烈的市场竞争中脱颖而出。
|
12月前
|
缓存 安全 Java
Java volatile关键字:你真的懂了吗?
`volatile` 是 Java 中的轻量级同步机制,主要用于保证多线程环境下共享变量的可见性和防止指令重排。它确保一个线程对 `volatile` 变量的修改能立即被其他线程看到,但不能保证原子性。典型应用场景包括状态标记、双重检查锁定和安全发布对象等。`volatile` 适用于布尔型、字节型等简单类型及引用类型,不适用于 `long` 和 `double` 类型。与 `synchronized` 不同,`volatile` 不提供互斥性,因此在需要互斥的场景下不能替代 `synchronized`。
3435 3
|
JavaScript 前端开发 Java
java中的this关键字
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。自学前端2年半,正向全栈进发。若我的文章对你有帮助,欢迎关注,持续更新中!🎉🎉🎉
200 9
|
设计模式 JavaScript 前端开发
java中的static关键字
欢迎来到瑞雨溪的博客,博主是一名热爱JavaScript和Vue的大一学生,致力于全栈开发。如果你从我的文章中受益,欢迎关注我,将持续分享更多优质内容。你的支持是我前进的动力!🎉🎉🎉
262 8