【原创博文,我会不断修改整理的...】
两个Java关键字static 和 final,平常也经常用,或许是因为用得多了,成了一种习惯了,反而只知其然,而不知其所以然了。下面我就来结合自己的经验做一个总结,也算作一种备忘吧。
2.3.3小节中,对方法内部类使用的局部变量为什么要用final声明,进行了详细深入的探讨。
1.static
(待写。。。)
2.final
final可用于修饰非抽象类、方法、数据,其中,方法包括类方法(static方法)、成员方法,数据包括类变量、成员变量、局部变量。
2.1修饰类
final修饰类,其表示的意思是,被修饰的这个类不能被继承,并且这个类里所有的方法都隐含的被声明为final。final方法和非final方法有什么区别2.2会谈到。
2.2修饰方法
final可以修饰静态方法和成员方法。final修饰方法通常表示这个方法不能在子类中重写,当然,前提是子类能够继承这个方法。
final修饰的方法,可以由编译器根据“实际情况”进行内联操作,从而提高性能。什么是内联呢?就是A方法内调用了B方法,如果对B方法进行内联操作,那么在A中执行到调用B时,不会对B新启一个压栈操作,从而可以减小B方法出入栈的开销。那么怎么内联的呢?简单说就是编译器根据“实际情况”确定要内联某个方法时,就会把这个方法的代码语句直接插在调用这个方法的地方,和原来的方法融合在一起成为一个整块,这样在调用这个被内联的方法时,就不重新开辟栈了。当然,内联有好处也有弊端,如果被内联的方法体太大,很多地方都内联这个方法,就显得很臃肿,增加了内存空间的开销。虽然编译器会对此有一定的“权衡”,并不会内联所有的final方法,但是这种“权衡”并非万无一失的,所以需要内联的方法,我们最好让它们精炼简洁一些。
2.2.1静态方法
说到静态方法,我就想起一个问题,静态方法可以被继承,可以被覆盖(override),被重写(overwrite)吗?带着问题,我们来看例子。
(1)
public class Parent {
public static void foo() {
System.out.println("Parent.foo()");
}
public static void main(String[] args) {
Parent.foo();
Son.foo();
}
}
class Son extends Parent {
}
执行结果:
Parent.foo()
Parent.foo()
可见,静态方法被继承是没问题的。
(2)
public class Parent {
public static void foo() {
System.out.println("Parent.foo()");
}
public static void main(String[] args) {
//case 1
Parent.foo();
Son.foo();
//case 2
Parent parent2 = new Parent();
Parent son2 = new Son();
parent2.foo();
son2.foo();
//case 3
Parent parent3 = new Parent();
Son son3 = new Son();
parent3.foo();
son3.foo();
}
}
class Son extends Parent {
public static void foo() {
System.out.println("Son.foo()");
}
}
执行结果:
Parent.foo()
Son.foo()
Parent.foo()
Parent.foo()
Parent.foo()
Son.foo()
例子中case2说明static方法调用不是动态绑定的,只是根据类名和引用的类型(引用声明时类型)来静态决定的。那到底Son中的foo()方法是不是对Parent中foo()方法的重写(overwrite)?不是,Eclipse里也不会显示重写的标记(Eclipse会对重写的方法进行一个三角的标记,并显示为“overrides 某某”,其实个人觉得这有些概念不清,显示为“overwrites 某某”更好)。其实这是一种“覆盖”(override),就是在Son中重新定义一个相同签名的方法把继承来的相同签名方法“覆盖”了,继承来的方法没有被放弃,只是对子类不可见了。
(3)
public class Parent {
public static final void foo() {
System.out.println("Parent.foo()");
}
}
class Son extends Parent {
public static void foo() {
System.out.println("Son.foo()");
}
}
执行结果:
编译错误!
Eclipse错误提示信息:Cannot override the final method from Parent
例子说明,final修饰的方法,子类不能覆盖(override)这个方法,Eclipse信息也这么提示的。
综上,static方法是可以被继承的,可以被覆盖(override)的,不叫重写(overwrite)。但是被final修饰的static方法是不可以被覆盖的。
2.2.3成员方法
成员方法加上final理解起来就很简单了,就是不能被子类重写(overwrite)。重写指:重写继承到的那个方法的代码,继承来的原方法被放弃。
另外,可以被内联,只是可以,最终还需要编译器的做一个“权衡”。
2.3修饰数据
final修饰数据包括类变量(static声明的变量),成员变量,局部变量。其中,局部变量包括方法参数变量,方法体内定义的变量。
final修饰的数据变量,通常表示该变量不可一旦初始化就不可更改。
final修饰的数据还可分为基本变量、引用变量。其中,若修饰基本变量,则表示这个基本变量一旦被初始赋值了,这个变量就再不能被赋其它值了。若修饰引用变量,则表示这个引用变量一旦被初始赋值,指向某个对象,就不能再被赋予另外一个引用值,不能再指向另外一个对象了,但是对象里面的内容仍是可以改变的。
2.3.1类变量
final修饰的类变量,必须在声明变量时就做显示赋值。显示复制包括编译期赋值和运行期赋值,且一旦赋值就不可更改,也就成了我们常说的“常量”。
public class FinalData {
private static final int I1 = 1;
private static final int I2 = getData();
private static int getData() {
return 2;
}
}
其中,I1的值是编译期就确定的。I2值是运行时确定的。两者的值一旦确定,都不可更改。
2.3.2成员变量
final修饰成员变量,该变量可以在声明时赋值,也可以在声明时不赋值(即blank final),但必须在构造方法返回之前赋值。且一旦赋值就不可更改。
public class FinalData {
private final int i1 = 1;
private final int i2 = getData();
private final int i3 = getStaticData();
private final int i4;
private final int i5;
//private final int i6; //编译错误:The blank final field i6 may not have been initialized
FinalData() {
i4 = 4;
i5 = getData();
}
private int getData() {
return 2;
}
private static int getStaticData() {
return 3;
}
}
2.3.3局部变量
final修饰局部变量,包括方法参数变量,方法体内变量。
有个比较重要的点就是,如果一个方法内声明创建了一个方法内部类,且该内部类使用了该方法的局部变量,即参数定义的变量以及方法体内定义的变量,则该变量必须用final修饰。这是为什么呢?
(2.3.3清单1)
public class World {
int foo() {return 0;}
private static World getWorld(final int input) {
World w = new World() {
int foo() {
return input;
}
};
return w;
}
public static void main(String[] args) {
World w = getWorld(100);
System.out.println(w.foo());
}
}
执行结果:
100
World类中有一个成员方法foo(),有一个静态方法getWorld(final int),调用getWorld(final int)会返回一个匿名方法内部类,该内部类中重写的一个方法将返回传给getWorld()的参数100。
疑问来了,传给方法getWorld()的的参数只存在于栈中,一旦getWorld()方法返回,传入的input就不存在了,那么在main方法中调用w.foo()怎么还能正确返回传入的那个值100呢?
其实,对于方法内部类访问方法内的局部变量时,在new内部类实例时,会将内部类用到的变量拷贝一份作为该内部类的成员变量存起来,所以栈中变量销毁对其并无影响,这中操作是编译器做的,对程序员不可见。上例中方法内部类其实经过编译器处理就是下面这个样:
(2.3.3清单2)
public class World {
int foo() {return 0;}
private static World getWorld(final int input) {
class WorldInner extends World {
private final int data;
WorldInner(final int input) {
this.data = input;
}
int foo() {
return data;
}
}
World w = new WorldInner(input);
return w;
}
public static void main(String[] args) {
World w = getWorld(100);
System.out.println(w.foo());
}
}
执行结果:
100
一目了然了吧?豁然开朗了吧?
那为什么要把input定义成final的(2.3.3清单1),不这样还不让编译通过呢?其实如果我们按照编译器处理后的代码(2.3.3清单2)写,是可以不把input定义为final的。但是按照前面那种写法,为什么就非要把input弄成final的呢?
答案是:Java语言设计者为了不给程序员增加麻烦,尽量从语言的角度给程序员们消除语义歧义。
因为使用方法内部类,会进行数据拷贝,一旦拷贝结束,就有两份数据,如“2.3.3清单2”中WorldInner中的成员变量data和传给getWorld()方法的参数input。但是从代码表面上看来,foo()方法就是用的外面传进来的input的值嘛,如果不给input加final,input在方法内部随时可以改动,设想一下,如果给WorldInner的data赋了值后,再把input的值修改成99,我们在外面调用WorldInner的foo()方法,感觉是操作的input值,也就是修改后的99,实际上是操作的拷贝的data,即100,要是让不了解其中原理的程序员看到明明修改成了99的input参数,却输出的是100, 还不纳闷死!
所以,为了避免这种语义歧义,Java设计者干脆就让这个方法内部类使用到的局部变量为final,不让修改得了。
那有人可能想问,可不可以不让这个设计成必须是final的呢?反正我觉得可以,只要理解了其中的原理,明白什么情况下是对什么变量值进行操作,“2.3.3清单2”不就突破这种限制了嘛。当然,至于哪种更好,反正我是给不了定论。
好吧,熬夜写了这么多,够辛苦的。。。
分享到:
相关推荐
"Java关键字final、static使用总结" 在 Java 编程语言中,final 和 static 是两个非常重要的关键字,它们可以用来修饰类、方法、变量等,了解它们的使用方法和限制非常重要。本文将详细总结 final 和 static 的使用...
Java中的`final`和`static`关键字是编程中非常重要的两个修饰符,它们分别用于不同的场景,以确保代码的稳定性和高效性。 首先,`final`关键字主要用于表示不可变性。它可以用来修饰类、方法和变量。对于类,如果一...
在Java编程语言中,`static`关键字是一个非常重要的修饰符,它有多种用途,涉及到类、对象以及变量和方法的生命周期。本视频教程详细讲解了`static`关键字在Java中的应用及其背后的原理。 首先,我们要理解`static`...
Java关键字大全是Java编程语言中最基本和最重要的组成部分。这些关键字是Java语言的基础构建块,用于定义类、方法、变量、控制流、异常处理等。了解和掌握Java关键字是每一个Java开发者必备的技能。 下面是Java...
Java 中的 `final` 关键字是一个至关重要的概念,它被用来声明类、方法和变量,以确保它们在程序执行过程中不可更改。深入理解 `final` 关键字可以帮助开发者写出更加安全、高效的代码。 首先,`final` 关键字可以...
Java编程中static和final关键字的陷阱.pdf Java编程中static和final关键字的陷阱是Java开发中两个重要的关键字,但是它们的使用也存在一些陷阱,需要开发者注意掌握。 static关键字可以用于修饰属性、方法和类。...
面向对象部分让人搞晕的几个关键字,我这里专门针对这几个关键字进行了整理相关的面试题,只要你能做对80%,就证明你面向对象基本学懂了
关键字Static就是为了满足上面提到的两种特殊情况而诞生的。当某一个变量或方法被声明为Static的时候,这个变量或方法就不再和任何一个类的实例对象绑定,即便没有创建任何一个实例对象,也可以方位Static的变量或者...
Java 关键字、标识符、注释、转义字符详解 Java 语言中有四个重要的概念:关键字、标识符、注释和...理解 Java 关键字、标识符、注释和转义字符是编写 Java 代码的基础,这些概念对于 Java 语言的学习和应用非常重要。
### Java关键字总结 Java是一种广泛使用的面向对象编程语言,它具备简单性、可移植性、高性能等特点。在Java中,关键字具有特殊的语法含义,不能用作标识符(例如变量名或方法名)。以下是对给定文件中提到的Java...
`static`关键字在Java中主要用于创建静态成员,包括静态变量和静态方法。与普通成员不同,静态成员属于类级别,而不是实例级别。这意味着无论创建了多少个类的实例,静态成员只有一个副本存在于内存中。 #### 二、...
而"Linux中Java变量.txt"和"java关键字.txt"则分别详细阐述了这两个主题,提供了更深入的学习材料。 总的来说,熟练掌握Linux环境下Java编程的变量和关键字,了解并运用MyEclipse的快捷键,以及理解和运用Java方法...
### Java关键字分类解释 #### 一、类与接口定义关键字 - **class**: 在Java中,`class`关键字用于声明一个类。类是面向对象编程的基本单元,它定义了一组属性(成员变量)和方法(成员函数),这些共同构成了一个...
这篇文档《Java关键字详细解》将深入探讨Java中的关键字及其用途。 首先,我们来看看Java中的主要关键字。`public`、`private`、`protected`是访问修饰符,用于控制类、方法和变量的访问权限。`public`可以被任何...
此外,`final`关键字还有其他用途,如`final`修饰的匿名内部类和`final`局部变量在匿名内部类中的特殊规则,以及`final`与`static`结合时的静态常量等。理解并熟练应用`final`关键字对于编写高效、安全的Java代码至...
通过这份总结,我们可以看到Java关键字在定义行为和数据时起到的作用。理解这些关键字的含义和使用方式是掌握Java语言基础的关键。这些概念包括区分对象内部变量和方法、处理继承关系中的方法覆盖、定义类级别的属性...
在本节中,我们将详细介绍Java关键字static的概念、特性和应用场景。 一、static的概念和特性 在Java中,static关键字用于修饰成员变量和成员方法,以实现“伪全局”概念。static修饰的成员变量和成员方法是独立于...
Java关键字大全 Java语言中有多种关键字,每个关键字都有其特定的用途和作用。下面是Java关键字大全的详细解释: 1. abstract关键字 abstract关键字可以修改类或方法。abstract类可以扩展(增加子类),但不能...