1、修饰类
final修饰类时,这个类不能被继承。
final类的成员变量可以根据需要设为final,但是final类中的所有成员方法都会被隐性地指定为final方法,不可继承。
类的private()方法会隐式地指定为final方法。
除非这个类在之后都不会用来继承或者出于安全考虑,尽量不要将类设计为final类。
2、修饰方法
a.把方法锁定,不可重写,以防任何继承类修改它的含义(但可以重载);
b.效率。早期的Java实现版本中,会将final方法转为内嵌调用。如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。
只有在想确定禁止该方法在子类中被覆盖的情况下,才将方法设置为final。
3、修饰变量
表示该变量在编译之后变成一个常量,不可以被修改。对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用数据类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
4、类的final变量和普通变量有什么区别?
当final作用于类的成员变量时,成员变量必须再定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。
public class Final01 {
public static void main(String[] args) {
String a = "hello2";
final String b = "hello";
String c = "hello";
String d = b+2;
String e = c+2;
System.out.println((a==d)); //true
System.out.println((a==e)); //false
}
}
//当final变量是基本数据类型或String类型时,如果在编译期间能知道它的确切值,则编译器会把它当作编译期常量使用。也就是在用到该final变量的地方会直接访问这个常量,不需要在运行时确定(和C语言的宏替换有点像)。
//代码中由于b变量被final修饰,因此会被当做编译器常量,在使用b的地方会直接使用b的值。访问c却需要在运行时通过链接来进行。
//只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化。
public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = getHello();
String c = b+2;
System.out.println(a==c); //false
}
public static String getHello() {
return "hello";
}
}
//只有在编译期间确切知道final变量值的情况下,编译器才会对此进行优化。
//编译期间变化可见文章“Java编译与运行时”(http://t.csdn.cn/H7lGy)
5、被final修饰的引用类型变量指向的对象内容可变。
6、final和static和static final
A.final
a1.final的作用是用来保证变量不可变。
a2.Java中的String类和Integer类都是final类型。
a3.final类中所有的变量都必须是final变量。
a4.接口中声明的所有变量本身都是final的,类似匿名类。
a5.final和abstract这两个关键字是相反的,final类不可能是abstract。
a6.可以修饰:类、方法、成员变量、局部变量;
不能修饰:构造方法、代码块、修饰的方法不能重写。
public class Test {
public static void main(String[] args) {
MyClass myClass1 = new MyClass();
MyClass myClass2 = new MyClass();
System.out.println(myClass1.i); //与myClass2.i不相等
System.out.println(myClass1.j); //与myClass2.j相等
System.out.println(myClass2.i);
System.out.println(myClass2.j);
}
class MyClass {
public final double i = Math.random(); // 大于0小于1的随机数
public static double j = Math.random();
}
}
// static修饰变量只创建一次
B.static
b1.static表示全局的 or 静态的。
b2.在JVM中,被static修饰的方法和变量存放在方法区中(JDK8称为元空间),它属于全局贡献的,而不是某个线程私有的。同时,独立于该类中的任何对象,它不依赖于类的特定实例(对象),被类的所有实例共享,可以直接用类名调用类中的任何方法和变量(静态方法中,只能调用今天该方法的方法和属性;非静态方法中,既可以调用非静态的方法和属性,也可以调用静态的方法和属性)。
b3.static作用于成员变量用来表示只保存一份副本。
b4.修饰属性(静态变量或类变量):类中的多个对象如果共享一个静态变量,当通过某一个变量改变静态对象时,所有的对象对应的值都会发生改变。(注意:1、由于类只会加载一次,所以静态变量在类加载时创建并只会在内存中存一份,存在方法去的静态域中;2、静态变量的加载要早于对象的创建)
b5.修饰代码块(即静态代码块):当JVM加载类时,会执行该代码块,只会被执行一次。
b6.static方法不能使用this或super关键字,且必须被实现,不能是抽象的abstract。
b7. 可以修饰:类、方法、成员变量、代码块、属性;
不可修饰:构造器、局部变量、static方法不能被重写。
C.static final
c1.static final和final static没有区别,两者不会相互修饰,javac成 .java后没有任何区别。修饰变量一旦赋值不可修改,作为类常量,并且可以用类直接访问或调用;修饰方法不可覆盖,并且可以通过类直接访问或调用。
7、匿名内部类中使用的外部局部变量只能是final变量。(原因在:)
8、final修饰基本数据类型和引用数据类型变量
方法中作为参数的基本数据类型变量,在方法中被修改时,无法影响方法外该变量的值,除非重新赋值。
public class final02 {
public static void main(String[] args) {
MyClass myClass = new MyClass();
int i = 0;
myClass.ChangeValue(i);
System.out.println(i); //0
}
}
class MyClass {
int ChangeValue(int i){
return ++i;
}
}
// changeValue和main方法中的变量i不是同一个地址,java参数传递采用的是值传递,对于基本类型的变量,相当于直接将变量进行了拷贝。
方法中作为参数的引用数据类型变量,在方法中被修改时,无论是否在形参前面加上final都会改变方法外改变量的值。
Public class Final03{
public static void main(String[] args) {
MyClass myClass = new MyClass();
StringBuffer buffer = new StringBuffer("hello");
myClass.changeValue(buffer);
System.out.println(buffer); //helloworld
System.out.println(buffer.toString); //helloworld
}
}
class MyClass {
void changeValue(final StringBuffer buffer) {
buffer.append("world");
}
}
//用final进行修饰并没有阻止changeValue中改变buffer指向的对象的内容。
//java采用的是值传递,对于引用变量,参数传递的是引用的值。即让实参和形参都指向了同一个对象。
9、final的好处
a.提高了性能,JVM在常量池中会缓存final变量;
b.final变量在多线程中并发安全,无需额外的同步开销;
c.final方法是静态编译的,提高了调用速度
d.final类创建的对象是只可读的,在多线程中可以安全贡献。
10、final内存分配
当调用一个函数时,除了函数本身的执行时间之外,还需要额外的时间去寻找这个函数(类内部有一个函数签名和函数地址的映射表)。所以减少函数调用的次数就等于降低性能消耗。final修饰的函数会被编译器优化,优化的结果是减少了函数调用的次数,具体实现如下:
public class Test{
final void func(){
System,out.println("g");
}
public void main(String[] args){
for(int i=0;i<1000;i++){
func();
}
}
}
//经过编译器优化之后,变成以下
public class Test{
final void func(){
System.out.peintln("g");
}
public void main(String[] args){
for(int i=0;i<1000;i++){
System.out.println("g");
}
}
}
//编译器直接将func的函数体内嵌到调用函数的地方,这样做节省了1000次函数调用。
//当然编译器处理成字节码,只是我们可以想象成这样
不过如果函数体太长的话,用final可能会适得其反,因为经过编译器内嵌之后的代码长度大大增加,于是就增加了jvm解释字节码的时间。
在使用final修饰方法的时候,编译器会把被final修饰过的方法插入到调用处,提高运行速度和效率,但被final修饰的方法体不能过大,编译器可能会放弃内联。
11、使用final修饰方法会提高速度和效率吗?
——加了比不加好一点
public class Test{
public static void getJava(){
String str1="Java";
String str2="final";
for(int i =0;i<10000;i++){
str1 += str2;
}
}
public static final void getJava_Final(){
String str1="Java";
String str2="final";
for(int i=0;i<10000;i++){
str1 += str2;
}
}
public static void main(String[] args){
long start = System.currentTimeMillis(); // 查看从1970.1.01 00:00:00:000到当前时间的时刻距离,类型为long
getJava();
System.out.println("调用不带final修饰的方法执行时间为" + (System.currentTimeMillis() - start) + "毫秒时间"); //1732、1217、1154、1139、1186
start = System.currentTimeMillis();
String str1 = "Java";
String str2 = "final";
for(int i=0; i<10000; i++){
str1 += str2;
}
System.out.println("正常的执行时间为:" + (System.currentTimeMillis() - start)+"毫秒时间"); //1498、1031、1140、999、1030
start = System.currentTimeMillis();
getJava_Final();
System.out.println("调用final修饰方法的执行时间为:" + (System.currentTimeMillis() - start) + "毫秒时间"); //1593、1124、1202、1092、1109
}
}
//执行5次后可以看出,执行最快的是”正常的执行“即代码直接编写。使用final修饰的方法,位于两者之间(有些书上说使用final修饰的方法速度和效率与”正常执行“无异)。最慢的是不加final修饰的方法。