Java关键字final有“不可改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而需要阻止改变:设计或效率。
注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。
final类不能被继承,没有子类,final类中的方法默认是final的。
final方法不能被子类的方法覆盖,但可以被继承。
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
final不能用于修饰构造方法。
final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。
在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。
package FinalDemo;
final class Father{
}
class Son extends Father{ //编译报错,不能继承final修饰的类
}
final修饰方法,方法不可以重写,不允许其子类覆盖,但是可以被子类访问 。
使用final方法的原因有二:
第一、把方法锁定,防止任何继承类修改它的意义和实现。
第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。
public class Test1 {
public static void main(String[] args) {
// TODO 自动生成方法存根
}
public void f1() {
System.out.println("f1");
}
//无法被子类覆盖的方法
public final void f2() {
System.out.println("f2");
}
public void f3() {
System.out.println("f3");
}
private void f4() {
System.out.println("f4");
}
}
public class Test2 extends Test1 {
public void f1(){
System.out.println("Test1父类方法f1被覆盖!");
}
public static void main(String[] args) {
Test2 t=new Test2();
t.f1();
t.f2(); //调用从父类继承过来的final方法
t.f3(); //调用从父类继承过来的方法
//t.f4(); //调用失败,无法从父类继承获得
}
}
用final修饰的成员变量表示常量,值一旦给定就无法改变。
final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。
初始化
final变量定义的时候,可以先声明,而不给初值,这中变量也称为final空白,无论什么情况,编译器都确保空白final在使用之前必须被初始化。但是,final空白在final关键字final的使用上提供了更大的灵活性,为此,一个类中的final数据成员就可以实现依对象而有所不同,却有保持其恒定不变的特征。
原生数据类型和引用数据类型
如果被final修饰的是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,一旦初始化赋值之后指向的对象不可变但该对象的内容可变。
class AA{
int i=1;
}
public class EmbellishVariable {
public final int a=1;
public final int b;
public int c;
public EmbellishVariable() { //构造方法
b=2; //在构造方法中将成员变量b进行初始化
}
public void method(){
// final修饰基本数据类型的变量
a=2; //编译失败,数值一旦在初始化之后便不能更改
// final修饰引用类型的变量
final AA a = new AA();
a=new AA(); //编译失败,被final修饰的引用变量一旦初始化赋值之后指向的对象不可变
System.out.println( ++a.i ); //输出值为2,说明内容可变
final StringBuffer buffer = new StringBuffer("final指向对象不可变");
buffer.append(",内容可变");
System.out.println( buffer ); //输出final指向对象不可变,内容可变
}
}
当final修饰一个原生数据类型时,表示该原生数据类型的值不能发生变化(比如说不能从10变为20);如果final修饰一个引用类型时,表示该引用类型不能再指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。
原生数据类型案例如下,图中的错误是无法为最终变量age分配值
引用类型案例如下,图中错误是无法为最终变量address分配值
该引用所指向的对象的内容是可以发生变化的
当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。
public class Test4 {
public static void main(String[] args) {
new Test4().f1(2);
}
public void f1(final int i) {
//i++; //i是final类型的,值不允许改变的.
System.out.print(i);
}
}
加载:static在类加载时初始化(加载)完成
含义:Static意为静态的,但凡被static 修饰,说明属于类,不属于类的对象。
可修饰: 可以修饰内部类、方法、成员变量、代码块、
静态导包。
不可修饰:static方法中不能用this和super关键字。static不可修饰外部类、局部变量
【static 属于类的,局部变量属于其方法,并不属于类】。
不推荐:普通方法中不推荐通过对象或者this关键字访问静态变量、静态方法【static代表类层次,this代表当前类的对象】,建议使用类名访问。
注意事项:静态只能访问静态。非静态既可以访问非静态的,也可以访问静态的。
static主要作用:创建独立于具体对象的域变量或者方法。即使没有创建对象,也能使用属性和调用方法。或者用来形成静态代码块以优化程序性能
被static修饰的变量,叫静态变量或类变量;没有被static修饰的变量,叫实例变量。两者的区别是:
初始化
在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,后面根据需要是可以再次赋值的。跟final一样。
访问权限限定
用public修饰的static成员变量和成员方法,本质是全局变量和全局方法,类的所有实例共享同一个static变量。
用private修饰的static成员变量,表示这个变量可以在类的静态代码块中,或者类的其他静态成员方法中使用,但是不能在其他类中通过类名来直接引用。private是访问权限限定,static表示不要实例化就可以使用。
被static修饰的成员变量和成员方法独立于该类的任何对象。它不依赖类特定的实例,被类的所有实例共享。
只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。
静态方法可以直接通过类名调用,因此静态方法中不能用this和super关键字,只能继承,不能重写(Override),且static方法必须被实现,而不能是抽象的abstract。
不可以从一个static方法内部发出对非static方法的调用。因为非static方法要与对象关联在一起,必须创建一个对象后,才可以在该对象上进行方法的调用。
public class StaticTest {
public static void main(String[] args) {
MyStatic.output();
}
}
class MyStatic{
public static void output(){
System.out.println("output");
}
}
static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置不固定,它不在任何的方法体内。不能在静态方法中访问非静态的成员变量。
静态代码块通常用来对静态变量进行一些初始化操作,比如定义枚举类
public enum WeekDayEnum {
SATURDAY(6, "周六"),
SUNDAY(7, "周日");
private int code;
private String desc;
WeekDayEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
private static final Map<Integer, WeekDayEnum> WEEK_ENUM_MAP = new HashMap<Integer, WeekDayEnum>();
// 对map进行初始化
static {
for (WeekDayEnum weekDay : WeekDayEnum.values()) {
WEEK_ENUM_MAP.put(weekDay.getCode(), weekDay);
}
}
public static WeekDayEnum findByCode(int code) {
return WEEK_ENUM_MAP.get(code);
}
public int getCode() {return code;}
public String getDesc() {return desc;}
}
多个类的继承中初始化块、静态初始化块、构造器的执行顺序
多个静态代码块JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。
静态代码块在类被加载的时候执行,而构造方法是在生成对象的时候执行;要调用某个类来生成对象,首先需要将类加载到Java虚拟机上(JVM),然后由JVM加载这个类来生成对象。
例子:子类C继承父类B,父类B继承父类A。每个类各有初始化块、静态初始化块、构造器。
在继承中,先后执行父类A的静态块,父类B的静态块,最后子类C的静态块, 然后再执行父类A的非静态块和构造器,然后是B类的非静态块和构造器,最后执行子类C的非静态块和构造器。
非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。
没有这个引用,就意味着静态内部类:
1、它的创建是不需要依赖外围类的创建。 2、它不能使用任何外围类的非static成员变量和方法。
代码举例(静态内部类实现单例模式)
// 当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存
public class Singleton {
// 声明为 private 避免调用默认构造方法创建对象
private Singleton() {
}
// 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
/**
* 只有当调用 getUniqueInstance()方法,SingletonHolder 才会被加载
* 此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。
* 这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
*/
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
当 Singleton
类加载时,静态内部类 SingletonHolder
没有被加载进内存。只有当调用 getUniqueInstance()
方法从而触发 SingletonHolder.INSTANCE
时 SingletonHolder
才会被加载,此时初始化 INSTANCE
实例,并且 JVM 能确保 INSTANCE
只被实例化一次。
这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
静态导包格式:import static
这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法
静态导包在书写代码的时候确实能省一点代码,可以直接调用里面的静态成员,但是会影响代码可读性,所以开发中一般情况下不建议这么使用
// Math. --- 将Math中的所有静态资源导入,这时候可以直接使用里面的静态方法,而不用通过类名进行调用
// 如果只想导入单一某个静态方法,只需要将换成对应的方法名即可
import static java.lang.Math.;
// 换成import static java.lang.Math.max;具有一样的效果
public class Demo {
public static void main(String[] args) {
int max = max(1,2);
System.out.println(max);
}
}
static修饰表示静态或全局(与对象无关),final修饰表示最终或不可修改。static和final都可以修饰方法和成员变量。
针对类:
final可以修饰类,static不可以(只可以修饰内部静态类)
final不可以修饰类的代码块,static可以。static修饰的代码块在JVM加载类时只会执行一次
final类不能被继承,没有子类,final类中的方法默认是final的
针对方法:
static修饰的属性和方法属于类,可以用类名.静态属性 / 方法名 访问
static方法中不能用this和super关键字,普通方法可以但还是推荐使用类名访问静态方法和静态变量
static方法必须被实现,而不能是抽象的abstract
final方法不能被子类覆盖重写,static方法只能被static方法覆盖重写
final不能用于修饰构造方法,private类型的方法默认是final类型的
针对变量:
final修饰的变量初始化可以在编译期或运行期(声明时或构造方法中赋值),static修饰的变量初始化在编译期(类加载的时候)
final修饰的变量跟具体对象有关,不同对象可以有不同的值。static修饰的属性所有对象都只有一个值
final修饰的变量初始化后不可以重新赋值。static修饰的变量可以重新赋值
final可以修饰方法内的局部变量(方法中的变量),static不可以
看下面的例子:
class StaticFinal{
public final double fin = Math.random(); //生成随机数
public static double sta = Math.random(); //生成随机数
int i=1;
}
public class Demo {
static int s1 = 0;
final int f2 = 2;
public static void main(String[] args) {
StaticFinal demo1 = new StaticFinal();
StaticFinal demo2 = new StaticFinal();
System.out.println(demo1.fin == demo2.fin );//打印false
System.out.println(demo1.sta == demo2.sta );//打印true
System.out.println("sta+1= "+ ++demo2.sta); //static修饰的变量可以重新赋值
// System.out.println( ++demo2.fin); //编译失败,static修饰的变量不可修改
final StaticFinal demo3= new StaticFinal();
System.out.println( ++demo3.i ); //输出值为2,final引用类型的内容可变
Demo.s1(); //static修饰的方法可以直接通过类名调用
Demo demo = new Demo();
demo.f2(); //final修饰的方法得先创建对象后调用
// demo.f2(f2); //编译失败,无法确定f2
}
public static void s1(){
// this.output(); //编译失败,无法确定this
// H5GoodsController.output(); //静态上下文中不能引用非静态方法
H5GoodsController.staticput(); //编译成功
s1++; //static修饰的变量可以重新赋值
System.out.println("s1:"+s1); //输出s1:1
}
public final void f2() {
this.output(); //编译成功
this.s1(); //编译成功,输出s1:2,但不应该通过类实例访问静态成员,应该通过类名访问
// f2 = 222; //编译失败,final变量不可修改
System.out.println("f2:"+f2); //输出f2:2
}
public void output() {
System.out.println("output");
}
public static void staticput() {
System.out.println("static output");
}
}
我们知道final修饰基本数据类型的变量时,则其数值一旦在初始化之后便不能更改。
但是,上面代码中被final修饰的变量是在运行时才初始化的,并没有在编译期就被初始化!由于值为随机数,运行时被初始化是不确定的一个值,也就是个随机数,仅仅当运行之后被初始化之后他的值才会不变。
至于static修饰的变量没有发生变化是因为static作用于成员变量只是用来表示保存一份副本,其不会发生变化。怎么理解这个副本呢?其实static修饰的在类加载的时候就加载完成了(初始化),而且只会加载一次也就是说初始化一次,所以不会发生变化。
代表static与final二者的共同体,static final和final static语法和用法上没有任何区别,一般习惯static写在前面。
static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数(创建后不能被修改)。
可修饰:依旧是取二者的共同体,所以只能修饰成员变量、方法、内部类
,被static final修饰意义分别如下:
1、成员变量:属于类的变量且只能赋值一次,并且可以通过类名访问。
2、方法:属于类的方法且不可以被重写,可以在不new对象的情况下通过类名访问。
3、内部类:属于外部类,且不能被继承
注意:
对于被static和final修饰过的容器类型(比如,ArrayList、HashMap)的实例变量,不可以改变容器变量本身,但可以修改容器中存放的对象。
public class TestStaticFinal {
private static final String strStaticFinalVar = "aaa";
private static String strStaticVar = null;
private final String strFinalVar = null;
private static final int intStaticFinalVar = 0;
private static final Integer integerStaticFinalVar = new Integer(8);
private static final ArrayList<String> alStaticFinalVar = new ArrayList<String>();
private void test() {
System.out.println("-------------值处理前----------\r\n");
System.out.println("strStaticFinalVar=" + strStaticFinalVar + "\r\n");
System.out.println("strStaticVar=" + strStaticVar + "\r\n");
System.out.println("strFinalVar=" + strFinalVar + "\r\n");
System.out.println("intStaticFinalVar=" + intStaticFinalVar + "\r\n");
System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + "\r\n");
System.out.println("alStaticFinalVar=" + alStaticFinalVar + "\r\n");
//strStaticFinalVar="哈哈哈哈"; //错误,final表示终态,不可以改变变量本身.
strStaticVar = "哈哈哈哈"; //正确,static表示类变量,值可以改变.
//strFinalVar="呵呵呵呵"; //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。
//intStaticFinalVar=2; //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。
//integerStaticFinalVar=new Integer(8); //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。
alStaticFinalVar.add("aaa"); //正确,容器变量本身没有变化,但存放内容发生了变化。这个规则是非常常用的,有很多用途。
alStaticFinalVar.add("bbb"); //正确,容器变量本身没有变化,但存放内容发生了变化。这个规则是非常常用的,有很多用途。
System.out.println("-------------值处理后----------\r\n");
System.out.println("strStaticFinalVar=" + strStaticFinalVar + "\r\n");
System.out.println("strStaticVar=" + strStaticVar + "\r\n");
System.out.println("strFinalVar=" + strFinalVar + "\r\n");
System.out.println("intStaticFinalVar=" + intStaticFinalVar + "\r\n");
System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + "\r\n");
System.out.println("alStaticFinalVar=" + alStaticFinalVar + "\r\n");
}
public static void main(String args[]) {
new TestStaticFinal().test();
}
}
运行结果如下:
-------------值处理前----------
strStaticFinalVar=aaa
strStaticVar=null
strFinalVar=null
intStaticFinalVar=0
integerStaticFinalVar=8
alStaticFinalVar=[]
-------------值处理后----------
strStaticFinalVar=aaa
strStaticVar=哈哈哈哈
strFinalVar=null
intStaticFinalVar=0
integerStaticFinalVar=8
alStaticFinalVar=[aaa, bbb]
Process finished with exit code 0
参考:
深入理解static关键字:https://blog.csdn.net/qq_44543508/article/details/102736466
深入理解final关键字:https://blog.csdn.net/qq_44543508/article/details/102720206
final 与 static 的区别:https://blog.csdn.net/meism5/article/details/89205253
static、final、static final的区别:https://www.cnblogs.com/dxllp/p/10721871.html
https://blog.51cto.com/lavasoft/18771
https://blog.csdn.net/weixin_37404604/article/details/80424562