写点什么

深入理解 static 关键字

作者:编程江湖
  • 2022 年 1 月 10 日
  • 本文字数:4944 字

    阅读完需:约 16 分钟

1、static 存在的主要意义

static 的主要意义是在于创建独立于具体对象的域变量或者方法。以致于即使没有创建对象,也能使用属性和调用方法

static 关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static 块可以置于类中的任何地方,类中可以有多个 static 块。在类初次被加载的时候,会按照 static 块的顺序来执行每个 static 块,并且只会执行一次。

为什么说 static 块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。因此,很多时候会将一些只需要进行一次的初始化操作都放在 static 代码块中进行。

2、static 的独特之处

1、被 static 修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享

怎么理解 “被类的实例对象所共享” 这句话呢?就是说,一个类的静态成员,它是属于大伙的【大伙指的是这个类的多个对象实例,我们都知道一个类可以创建多个实例!】,所有的类对象共享的,不像成员变量是自个的【自个指的是这个类的单个实例对象】...我觉得我已经讲的很通俗了,你明白了咩?

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

3、static 变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。赋值的话,是可以任意赋值的!

4、被 static 修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。

3、static 应用场景

因为 static 是被类的实例对象所共享,因此如果某个成员变量是被所有对象所共享的,那么这个成员变量就应该定义为静态变量

因此比较常见的 static 应用场景有:

1、修饰成员变量 2、修饰成员方法 3、静态代码块 4、修饰类【只能修饰内部类也就是静态内部类】 5、静态导包

以上的应用场景将会在下文陆续讲到...

4、静态变量和实例变量的概念

静态变量: static 修饰的成员变量叫做静态变量【也叫做类变量】,静态变量是属于这个类,而不是属于是对象。

实例变量: 没有被 static 修饰的成员变量叫做实例变量,实例变量是属于这个类的实例对象。

还有一点需要注意的是:static 是不允许用来修饰局部变量,不要问我问什么,因为 java 规定的!

5、静态变量和实例变量区别

静态变量: 静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的加载过程中,JVM 只为静态变量分配一次内存空间。

实例变量: 每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。

6、访问静态变量和实例变量的两种方式

我们都知道静态变量是属于这个类,而不是属于是对象,static 独立于对象。

但是各位有木有想过:静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问【只要访问权限足够允许就行】,不理解没关系,来个代码就理解了

public class StaticDemo {
static int value = 666;
public static void main(String[] args) throws Exception{ new StaticDemo().method(); }
private void method(){ int value = 123; System.out.println(this.value); }
}
复制代码

猜想一下结果,我猜你的结果是 123,哈哈是咩?其实

运行结果: 666
复制代码

回过头再去品味一下上面的那段话,你就能非常客观明了了,java培训这个思想概念要有只是这种用法不推荐!

因此小结一下访问静态变量和实例变量的两种方法:

静态变量:

类名.静态变量

对象.静态变量(不推荐)

静态方法:

类名.静态方法

对象.静态方法(不推荐)

7、static 静态方法

static 修饰的方法也叫做静态方法,不知道各位发现咩有,其实我们最熟悉的 static 静态方法就是 main 方法了~小白童鞋:喔好像真的是哦~。由于对于静态方法来说是不属于任何实例对象的,this 指的是当前对象,因为 static 静态方法不属于任何对象,所以就谈不上 this 了。

还有一点就是:构造方法不是静态方法

8、static 静态代码块

先看个程序吧,看看自个是否掌握了 static 代码块,下面程序代码继承关系为 BaseThree——> BaseTwo——> BaseOne

BaseOne 类

package com.gx.initializationblock;
public class BaseOne {
public BaseOne() { System.out.println("BaseOne构造器"); }
{ System.out.println("BaseOne初始化块"); System.out.println(); }
static { System.out.println("BaseOne静态初始化块");
}
}
复制代码

BaseTwo 类

package com.gx.initializationblock;
public class BaseTwo extends BaseOne { public BaseTwo() { System.out.println("BaseTwo构造器"); }
{ System.out.println("BaseTwo初始化块"); }
static { System.out.println("BaseTwo静态初始化块"); }}
复制代码

BaseThree 类

package com.gx.initializationblock;
public class BaseThree extends BaseTwo { public BaseThree() { System.out.println("BaseThree构造器"); }
{ System.out.println("BaseThree初始化块"); }
static { System.out.println("BaseThree静态初始化块"); }}
复制代码

测试 demo2 类

package com.gx.initializationblock;
/* 注:这里的ABC对应BaseOne、BaseTwo、BaseThree * 多个类的继承中初始化块、静态初始化块、构造器的执行顺序 在继承中,先后执行父类A的静态块,父类B的静态块,最后子类的静态块, 然后再执行父类A的非静态块和构造器,然后是B类的非静态块和构造器,最后执行子类的非静态块和构造器 */public class Demo2 { public static void main(String[] args) { BaseThree baseThree = new BaseThree(); System.out.println("-----"); BaseThree baseThree2 = new BaseThree();
}}
复制代码

运行结果

BaseOne静态初始化块BaseTwo静态初始化块BaseThree静态初始化块BaseOne初始化块
BaseOne构造器BaseTwo初始化块BaseTwo构造器BaseThree初始化块BaseThree构造器-----BaseOne初始化块
BaseOne构造器BaseTwo初始化块BaseTwo构造器BaseThree初始化块BaseThree构造器
复制代码


以上仅仅是让各位明确代码块之间的运行顺序,显然还是不够的,静态代码块通常用来对静态变量进行一些初始化操作,比如定义枚举类,代码如下:

public enum WeekDayEnum {    MONDAY(1,"周一"),    TUESDAY(2, "周二"),    WEDNESDAY(3, "周三"),    THURSDAY(4, "周四"),    FRIDAY(5, "周五"),    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 void setCode(int code) {        this.code = code;    }     public String getDesc() {        return desc;    }     public void setDesc(String desc) {        this.desc = desc;    }}&emsp;
复制代码

当然不仅仅是枚举这一方面,还有我们熟悉的单例模式同样也用到了静态代码块,如下:

public class Singleton {    private static Singleton instance;     static {        instance = new Singleton();    }     private Singleton() {}     public static Singleton getInstance() {        return instance;    }}
复制代码

9、static 变量与普通变量区别

static 变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。

还有一点就是 static 成员变量的初始化顺序按照定义的顺序进行初始化。

10、静态内部类

静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。没有这个引用就意味着:

1、它的创建是不需要依赖外围类的创建。 2、它不能使用任何外围类的非 static 成员变量和方法。

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

public class Singleton {       // 声明为 private 避免调用默认构造方法创建对象    private Singleton() {    }       // 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问    private static class SingletonHolder {        private static final Singleton INSTANCE = new Singleton();    }
public static Singleton getUniqueInstance() { return SingletonHolder.INSTANCE; }}
复制代码

当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance()方法从而触发 SingletonHolder.INSTANCE 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。

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

11、静态导包

静态导包格式: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);	}}
复制代码

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

12、static 注意事项

1、静态只能访问静态。 2、非静态既可以访问非静态的,也可以访问静态的。

13、final 与 static 的藕断丝连

到这里文章本该结束了的,但是 static 的使用始终离不开 final 字眼,二者可谓藕断丝连,常常繁见,我觉得还是很有必要讲讲,那么一起来看看下面这个程序吧。

package Demo;
class FinalDemo { public final double i = Math.random(); public static double t = Math.random();}
public class DemoDemo { public static void main(String[] args) {
FinalDemo demo1 = new FinalDemo(); FinalDemo demo2 = new FinalDemo(); System.out.println("final修饰的 i=" + demo1.i); System.out.println("static修饰的 t=" + demo1.t); System.out.println("final修饰的 i=" + demo2.i); System.out.println("static修饰的 t=" + demo2.t);
System.out.println("t+1= "+ ++demo2.t );// System.out.println( ++demo2.i );//编译失败 }}运行结果: final修饰的 i=0.7282093281367935 static修饰的 t=0.30720545678577604 final修饰的 i=0.8106990945706758 static修饰的 t=0.30720545678577604 t+1= 1.307205456785776
复制代码

static 修饰的变量没有发生变化是因为 static 作用于成员变量只是用来表示保存一份副本,其不会发生变化。怎么理解这个副本呢?其实 static 修饰的在类加载的时候就加载完成了(初始化),而且只会加载一次也就是说初始化一次,所以不会发生变化!

用户头像

编程江湖

关注

IT技术分享 2021.11.23 加入

还未添加个人简介

评论

发布
暂无评论
深入理解static关键字