深入解析Java编程中final关键字的使用

在Java中声明属性、方法和类时,可使用关键字final来修饰。final变量即为常量,只能赋值一次;final方法不能被子类重写;final类不能被继承。
1.final成员
声明 final 字段有助于优化器作出更好的优化决定,因为如果编译器知道字段的值不会更改,那么它能安全地在寄存器中高速缓存该值。final 字段还通过让编译器强制该字段为只读来提供额外的安全级别。
 
1.1关于final成员赋值
1)在java中,普通变量可默认初始化。但是final类型的变量必须显式地初始化。
 
2)final 成员能且只能被初始化一次。
 
3)final成员必须在声明时(在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 数值定义为static(i5) 和非static(i4) 的区别。此区别只有在数值在运行期内被初始化时才会显现,这是因为编译器对编译期数值一视同仁。(并且它们可能因优化而消失。)当你运行程序时,就会看到这个区别。请注意,在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");
  }
}

2.final方法
2.1final方法用途
1)为了确保某个函数的行为在继承过程中保持不变,并且不能被覆盖(overridding),可以使用final方法。
 
2)class中所有的private和static方法自然就是final。
 
2.2 final与private关键字
类中所有的private方法都隐式地指定是final的。由于无法取用private方法,所以也就无法覆盖它。
“覆盖”只有在某方法是基类的接口的一部分时才会出现。即,必须能将一个对象向上转型为它的基本类型并调用相同的方法。如果某方法为private,它就不是基类的接口的一部分。它仅是一些隐藏于类中的代码,只不过是具有相同的名称而已。但如果在导出类以相同的方法生成一个public、protected或包访问权限方法的话,该方法就不会产生在基类中出现的“仅具有相同名称”的情况。此时,你并没有覆盖该方法,仅是生成了一个新的方法。由于private方法无法触及而且能有效隐藏,所以除了把它看成是因为它所归属的类的组织结构的原因而存在外,其他任何事物都不需要考虑它。
3.final类
将某个类的整体定义为final 时,该类无法被继承。而且由于final类禁止继承,所以final类中所有的方法都隐式指定为final的,因为无法覆盖它们。
final 用于类或方法是为了防止方法间的链接被破坏。例如,假定类 X 的某个方法的实现假设了方法 M 将以某种方式工作。将 X 或 M 声明成 final 将防止派生类以这种方式重新定义 M,从而导致 X 的工作不正常。尽管不用这些内部相关性来实现 X 可能会更好,但这不总是可行的,而且使用 final 可以防止今后这类不兼容的更改。

PS:final,finally和finallize的区别

  1. final用于申明属性,方法和类,表示属性不可变,方法不可以被覆盖,类不可以被继承。
  2. finally是异常处理语句结构中,表示总是执行的部分。  
  3. finallize表示是object类一个方法,在垃圾回收机制中执行的时候会被调用被回收对象的方法。
(0)

相关推荐

  • 深入理解Java中的final关键字_动力节点Java学院整理

    Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了什么?使用final的好处是什么?最后也有一些使用final关键字的实例.final经常和static一起使用来声明常量,你也会看到final是如何改善应用性能的. final关键字的含义? final在Java中是一个保留的关键字,可以声明成员变量.方法.类以及本地变量.一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如

  • java中final关键字使用示例详解

    final经常和static一起使用来声明常量,你也会看到final是如何改善应用性能的.final关键字的含义?final在Java中是一个保留的关键字,可以声明成员变量.方法.类以及本地变量.一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如果你试图将变量再次初始化的话,编译器会报编译错误.什么是final变量?凡是对成员变量或者本地变量(在方法中的或者代码块中的变量称为本地变量)声明为final的都叫作final变量.final变量经常和static关键字一起使用,

  • java关键字final使用方法详解

    它所表示的是"这部分是无法修改的".不想被改变的原因有两个:效率.设计.使用到final的有三种情况:数据.方法.类. 一. final数据 有时候数据的恒定不变是很有用的,它能够减轻系统运行时的负担.对于这些恒定不变的数据我可以叫做"常量"."常量"主要应用与以下两个地方:1.编译期常量,永远不可改变.2.运行期初始化时,我们希望它不会被改变.对于编译期常量,它在类加载的过程就已经完成了初始化,所以当类加载完成后是不可更改的,编译期可以将它代入

  • Java中final关键字详解

    谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法. 主要介绍:一.final关键字的基本用法.二.深入理解final关键字 一.final关键字的基本用法 在Java中,final关键字可以用来修饰类.方法和变量(包括成员变量和局部变量).下面就从这三个方面来了解一下final关键字的基本用法. 1.修饰类 当用final修饰一个类时,表明这个类不能

  • java final 和instanceof 关键字的区别

    final 可以适用的范围:修饰类:使用这种修饰符的类无法被继承 修饰函数:被修饰的不能被重写 修饰属性:1.final修饰的成员变量是常量,值不能被修改      而java的命名规则:常量都要大写 当形参变量使用final修饰基本类型变量,在函数中该变量不能被修改   引用类型变量:不能改变地址 复制代码 代码如下: /* final class A{    public final void eat(){        System.out.println("测试");    }

  • 详解Java编程中static关键字和final关键字的使用

    Java static关键字以及Java静态变量和静态方法 static 修饰符能够与变量.方法一起使用,表示是"静态"的. 静态变量和静态方法能够通过类名来访问,不需要创建一个类的对象来访问该类的静态成员,所以static修饰的成员又称作类变量和类方法.静态变量与实例变量不同,实例变量总是通过对象来访问,因为它们的值在对象和对象之间有所不同. 请看下面的例子: public class Demo { static int i = 10; int j; Demo() { this.j

  • java 中的static关键字和final关键字的不同之处

    static 1.在类中,用static修饰的属性,称为静态属性.为这个类的所有对象所共有,存放在静态存储区,所有该类的对象都可以访问且访问的都是同一变量.可以用作计数器,来统计总共创建了多少个各类的对象. 2.在类中,用static 修饰的方法为静态方法,在静态方法中不可以访问非静态的属性和方法,但在非静态方法中可以访问静态方法和属性:且static方法多态失效,不能使用this. 3.由于静态属性和方法是属于该类的所有对象的,所以可以用类名.静态属性/方法名---来访问. 4.static

  • 深入解析Java编程中final关键字的作用

    final class 当一个类被定义成final class,表示该类的不能被其他类继承,即不能用在extends之后.否则在编译期间就会得到错误. package com.iderzheng.finalkeyword; public final class FinalClass { } // Error: cannot inherit from final class PackageClass extends FinalClass { } Java支持把class定义成final,似乎违背了

  • 深入解析Java编程中final关键字的使用

    在Java中声明属性.方法和类时,可使用关键字final来修饰.final变量即为常量,只能赋值一次:final方法不能被子类重写:final类不能被继承. 1.final成员 声明 final 字段有助于优化器作出更好的优化决定,因为如果编译器知道字段的值不会更改,那么它能安全地在寄存器中高速缓存该值.final 字段还通过让编译器强制该字段为只读来提供额外的安全级别.   1.1关于final成员赋值 1)在java中,普通变量可默认初始化.但是final类型的变量必须显式地初始化.   2

  • Java面向对象编程中final关键字的使用方法详解

    在Java中通过final关键字来声明对象具有不变性(immutable),这里的对象包括变量,方法,类,与C++中的const关键字效果类似. immutable指对象在创建之后,状态无法被改变 可以从三个角度考虑使用final关键字: 代码本身:不希望final描述的对象所表现的含义被改变 安全:final对象具有只读属性,是线程安全的 效率:无法修改final对象本身,对其引用的操作更为高效 final 变量 定义final Object a,则a只能被初始化一次,一旦初始化,a的数据无法

  • 详解Java编程中final,finalize,finally的区别

    final: final可以让你控制你的成员.方法或者是一个类是否可被覆写或继承等功能,这些特点使final在Java中拥有了一个不可或缺的地位,也是学习Java时必须要知道和掌握的关键字之一. final成员 当你在类中定义变量时,在其前面加上final关键字,那便是说,这个变量一旦被初始化便不可改变,这里不可改变的意思对基本类型来说是其值不可变,而对于对象变量来说其引用不可再变.其初始化可以在两个地方,一是其定义处,二是在构造函数中,两者只能选其一. 下面程序很简单的演示了final的常规用

  • 详解Java编程中super关键字的用法

    通过用static来定义方法或成员,为我们编程提供了某种便利,从某种程度上可以说它类似于C语言中的全局函数和全局变量.但是,并不是说有了这种便利,你便可以随处使用,如果那样的话,你便需要认真考虑一下自己是否在用面向对象的思想编程,自己的程序是否是面向对象的. 好了,现在开始讨论this&super这两个关键字的意义和用法.在Java中,this通常指当前对象,super则指父类的.当你想要引用当前对象的某种东西,比如当前对象的某个方法,或当前对象的某个成员,你便可以利用this来实现这个目的,当

  • 深入解析Java编程中面向字节流的一些应用

    文件输入输出流 文件输入输出流 FileInputStream 和 FileOutputStream 负责完成对本地磁盘文件的顺序输入输出操作. [例]通过程序创建一个文件,从键盘输入字符,当遇到字符"#"时结束,在屏幕上显示该文件的所有内容 import java.io.*; class ep10_5{ public static void main(String args[]){ char ch; int data; try{ FileInputStream a=new FileI

  • 完全解析Java编程中finally语句的执行原理

    可不能小看这个简单的 finally,看似简单的问题背后,却隐藏了无数的玄机.接下来我就带您一步一步的揭开这个 finally 的神秘面纱. 问题分析 首先来问大家一个问题:finally 语句块一定会执行吗? 很多人都认为 finally 语句块是肯定要执行的,其中也包括一些很有经验的 Java 程序员.可惜并不像大多人所认为的那样,对于这个问题,答案当然是否定的,我们先来看下面这个例子. 清单 1. public class Test { public static void main(St

  • 深入解析Java编程中接口的运用

    接口的本质--接口是一种特殊的抽象类,这种抽象类里面只包含常量和方法的定义,而没有变量和方法的实现. 抽象类所具有的一些东西接口可以具有,假如一个抽象类里面所有的方法全都是抽象的,没有任何一个方法需要这个抽象类去实现,并且这个抽象类里面所有的变量都是静态(static)变量,都是不能改变(final)的变量,这时可以把这样的抽象类定义为一个接口(interface).把一个类定义成一个接口的格式是把声明类的关键字class用声明接口的关键字interface替换掉即可. 接口(interface

  • 深入解析Java编程中方法的参数传递

    在阅读本文之前,根据自己的经验和理解,大家可以先思考并选择一下Java函数的参数传递方式: A. 是按值传递的? B. 按引用传递的? C. 部分按值部分按引用? 此处暂不宣布正确答案,我们通过一个简单的例子让大家自己找答案: 1. 先定义一个类型Value public static class Value { private String value = "value"; public String getValue() { return value; } public void

随机推荐