前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java关键字final、static总结与对比

Java关键字final、static总结与对比

作者头像
chenchenchen
发布2022-03-09 12:36:28
7210
发布2022-03-09 12:36:28
举报
文章被收录于专栏:chenchenchenchenchenchen

final

Java关键字final有“不可改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而需要阻止改变:设计或效率。

注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。

final类不能被继承,没有子类,final类中的方法默认是final的。

final方法不能被子类的方法覆盖,但可以被继承。

final成员变量表示常量,只能被赋值一次,赋值后值不再改变。

final不能用于修饰构造方法。

final类

final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。

在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。

代码语言:javascript
复制
package FinalDemo;

final class Father{

}
class Son extends Father{   //编译报错,不能继承final修饰的类

}

final方法

final修饰方法,方法不可以重写,不允许其子类覆盖,但是可以被子类访问 。

使用final方法的原因有二:

第一、把方法锁定,防止任何继承类修改它的意义和实现。

第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。

代码语言:javascript
复制
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数据成员就可以实现依对象而有所不同,却有保持其恒定不变的特征。

原生数据类型和引用数据类型

如果被final修饰的是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,一旦初始化赋值之后指向的对象不可变但该对象的内容可变。

代码语言:javascript
复制
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参数

当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。

代码语言:javascript
复制
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 修饰,说明属于类,不属于类的对象。

可修饰: 可以修饰内部类、方法、成员变量、代码块、静态导包。

不可修饰:static方法中不能用this和super关键字。static不可修饰外部类、局部变量【static 属于类的,局部变量属于其方法,并不属于类】。

不推荐:普通方法中不推荐通过对象或者this关键字访问静态变量、静态方法【static代表类层次,this代表当前类的对象】,建议使用类名访问。

注意事项:静态只能访问静态。非静态既可以访问非静态的,也可以访问静态的。

static主要作用:创建独立于具体对象的域变量或者方法。即使没有创建对象,也能使用属性和调用方法。或者用来形成静态代码块以优化程序性能

  • 当定义一个static的变量的时候JVM会将其分配在内存堆上,所有程序对它的引用都会指向这一个地址而不会重新分配内存
  • 当修饰一个类方法时候可以直接通过类来调用而不需要创建对象
  • 当修饰一个程序块的时候,虚拟机就会优先加载静态块中代码,用于系统初始化

static静态变量

被static修饰的变量,叫静态变量或类变量;没有被static修饰的变量,叫实例变量。两者的区别是:

  • 对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便)。
  • 对于实例变量,每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。

初始化

在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,后面根据需要是可以再次赋值的。跟final一样。

访问权限限定

用public修饰的static成员变量和成员方法,本质是全局变量和全局方法,类的所有实例共享同一个static变量。

用private修饰的static成员变量,表示这个变量可以在类的静态代码块中,或者类的其他静态成员方法中使用,但是不能在其他类中通过类名来直接引用。private是访问权限限定,static表示不要实例化就可以使用。

static静态方法

被static修饰的成员变量和成员方法独立于该类的任何对象。它不依赖类特定的实例,被类的所有实例共享。

只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。

静态方法可以直接通过类名调用,因此静态方法中不能用this和super关键字,只能继承,不能重写(Override),且static方法必须被实现,而不能是抽象的abstract。

不可以从一个static方法内部发出对非static方法的调用。因为非static方法要与对象关联在一起,必须创建一个对象后,才可以在该对象上进行方法的调用。

代码语言:javascript
复制
public class StaticTest {
    public static void main(String[] args) {
        MyStatic.output();
    }
}
class MyStatic{
    public static void output(){
        System.out.println("output");
    }
}

static静态代码块

static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置不固定,它不在任何的方法体内。不能在静态方法中访问非静态的成员变量。

静态代码块通常用来对静态变量进行一些初始化操作,比如定义枚举类

代码语言:javascript
复制
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成员变量和方法。

代码举例(静态内部类实现单例模式)

代码语言:javascript
复制
// 当 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.INSTANCESingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。

这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。

静态导包

静态导包格式:import static

这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法

静态导包在书写代码的时候确实能省一点代码,可以直接调用里面的静态成员,但是会影响代码可读性,所以开发中一般情况下不建议这么使用

代码语言:javascript
复制
//  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修饰表示最终或不可修改。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不可以

看下面的例子:

代码语言:javascript
复制
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二者的共同体,static final和final static语法和用法上没有任何区别,一般习惯static写在前面。

static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数(创建后不能被修改)。

可修饰:依旧是取二者的共同体,所以只能修饰成员变量、方法、内部类,被static final修饰意义分别如下:

1、成员变量:属于类的变量且只能赋值一次,并且可以通过类名访问。

2、方法:属于类的方法且不可以被重写,可以在不new对象的情况下通过类名访问。

3、内部类:属于外部类,且不能被继承

注意:

对于被static和final修饰过的容器类型(比如,ArrayList、HashMap)的实例变量,不可以改变容器变量本身,但可以修改容器中存放的对象。

代码语言:javascript
复制
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

https://blog.csdn.net/qq_44543508/article/details/102691425

https://www.cnblogs.com/dengyungao/p/7524996.html

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • final
    • final类
      • final方法
        • final变量(常量)
          • final参数
          • static
            • static静态变量
              • static静态方法
                • static静态代码块
                  • 内部类(静态内部类或嵌套内部类)
                    • 静态导包
                    • 总结
                      • static和final区别
                        • static final
                        相关产品与服务
                        容器服务
                        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档