final 、static等关键字是刚开始学习Java语言比较蒙的一类,并不是它有多难,而是容易忽略它们,一旦在代码中碰到又不知所以然,所以还是学习一下它吧。
final常用的场景有三种:数据、方法和类。
final数据
Java中有一种方法,来向编译器告知某一块数据是恒定不变的,就是final数据。有时final数据比较有用:
- 一个编译时恒定不变的常量
- 在运行时初始化,而你不希望它被改变
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基本类型全用大写字母命名,并且字母与字母之间用下划线分隔。
我们不能因为某些数据是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,但这不会产生任何意义。
<由于原文章链接一时找不到了,若有侵权 即删>