云计算百科
云计算领域专业知识百科平台

大学生入门:static及其易踩坑的点

        static是什么?

        static关键字就像是程序界的“共享单车”,它可以让某些东西变成“公共财产”。

        ——拿生活中的例子来说,你们宿舍楼有一台公共洗衣机(static变量),所有宿舍成员(对象)都可以使用它,不需要每个宿舍都买一台。

        ——正经一点来说,static是java中的一个关键字,用于表示某个成员(属性/方法)属于类本身而不是类的实例。

一、三大用法

下面 三种用法 光看可能有点干巴,有不懂的地方也没事,介绍完我会一一解释说明。

1、修饰变量

作用:

        修饰成员变量,使其变成静态成员变量,静态成员变量属于类。

特性:

        ① 这个类下面的所有对象都可以使用;

        ② 对象能够访问静态成员属性,一般建议,对象做查询;

           类也能够访问静态成员属性,一般建议,类做修改;

        ③ 它随着类的加载而加载,先于对象存在;

        ④ 尽量不要在构造方法中设置静态成员变量的值,否则每次新建对象,这个值都会被重新设置。

好,那么问题来了!

为什么建议对象做查询,而类做修改呢?

        首先我们回忆一下前面说的,静态成员变量属于类

        既然如此,我们为什么要多此一举用“ 对象名.静态成员变量 ”呢?

        所以,为了避免修改数据时,误认为这个变量是属于对象的

        在对值进行操作的时候,一般使用“ 类名.静态成员变量

        而在查询时,一般是要查询各个对象单独的状态,这个时候可以使用“对象名.静态成员变量

好,那么问题又来了!

静态成员变量是怎么随着类加载而加载的呢?它先于对象又有什么作用吗?

        这个问题我们放到后面,讨论完静态方法和静态代码块再来详细解释 

2、修饰方法

作用:

        同样将成员方法修饰为静态,可以通过类名直接调用,主要用来操作static成员(属性/方法)

特性:

       ① static方法只能访问static成员(属性/方法);

       ② 也可以用对象来调用,但是推荐用类名;

       ③ static方法中调用的方法只能是static方法;

       ④ static方法中没有 this 和 super 关键字;

为什么static方法不可以访问非static成员,而非static方法可以访问static成员呢?

        前面提到了:静态成员随着类的加载而加载,先于对象存在

        这句话先用在这里,等会我们再解释

        那么也就是说,只要类存在,静态成员如果定义了就会跟着存在

        此时聪明的同学已经发现了

        非 static 方法访问时,static 成员一定存在;

        而 static 方法访问时,对象 不一定被创建了。

        所以 static 方法不能访问 非static 的成员,不存在还访问个der

第二个问题,为什么static方法中没有 this 关键字?

        这就不得不提到内存图了,我们通过内存图看一下 this 是什么作用,来吧展示:

public static void main(String[] args){
Student stu1 = new Student("jack",21);
stu1.sayHello();

Student stu2 = new Student("Tom".18);
stu2.sayHello();
}

        通过这个图不难发现,此时 this 的作用是代表当前对象实例的引用

        既然我们static属于类,都不需要对象,没有对象哪来的 this?

        super 呢也是大同小异,super代表的是当前对象的父类实例的引用

那么 this 和 super 的详细功能有哪些呢?

内个,this 我暂时还没写哈,等我写完把链接放到这里~

super 在下文讲加载顺序时会提到

3、修饰代码块

static{
//代码
}

特性:

        同样是类加载的时候执行

这个没什么好说的,我们直接老规矩吧

什么是代码块?

        代码块其实就是用 { } 括起来的代码

可以分为三类:

1、局部代码块

方法中的“临时工”,用完就消失

权限修饰符 数据类型 方法名(形参列表){
//局部代码块
}

定义位置:

        类的方法中定义

作用:

        用于限定变量的生命周期,使其尽早释放,提高栈空间的内存利用率

2、构造代码块(匿名代码块)

每个对象创建前的“热身运动”,没名字所以又叫匿名代码块

{
//构造代码块
}

定义位置:

        类中,方法外

作用:

        每次构造方法执行前,都会先执行该代码中的代码块

        如果每个构造方法中都有相同的方法,可以抽取出来放在构造代码中

        提高代码复用率

3、静态代码块

类加载时的“开机自检”,只执行一次

static{
//静态代码块
}

定义位置:

        类中,方法外

作用:

        初始化静态变量或者执行只需要执行一次的代码

二、静态成员变量和非静态成员变量的区别

接下来我们从不同角度分析一下这俩 “双胞胎” 的对比

1、存储位置

静态成员变量:存储在方法区中的静态区

非静态成员变量:存储在堆内存的对象内

public class StorageExample {
// 静态成员变量 – 存储在方法区的静态区
static int staticVar = 10;

// 非静态成员变量 – 存储在堆内存的对象内
int instanceVar = 20;

public static void main(String[] args) {
StorageExample obj1 = new StorageExample();
StorageExample obj2 = new StorageExample();

// 静态变量被所有实例共享
System.out.println("obj1.staticVar: " + obj1.staticVar); // 10
System.out.println("obj2.staticVar: " + obj2.staticVar); // 10

// 修改静态变量影响所有实例
StorageExample.staticVar = 30;
System.out.println("obj1.staticVar: " + obj1.staticVar); // 30
System.out.println("obj2.staticVar: " + obj2.staticVar); // 30

// 非静态变量每个实例独立拥有
obj1.instanceVar = 40;
obj2.instanceVar = 50;
System.out.println("obj1.instanceVar: " + obj1.instanceVar); // 40
System.out.println("obj2.instanceVar: " + obj2.instanceVar); // 50
}
}

2、生命周期

静态成员变量:从类加载开始到程序结束

非静态成员变量:从对象创建到对象被垃圾回收器回收

3、出现顺序

静态成员变量:随着类加载而加载出现

非静态成员变量:对象实例化时出现

4、调用方式

静态成员变量:通过类名直接调用

非静态成员变量:通过对象实例调用

5、初始化时机

静态成员变量:累加载时初始化

非静态成员变量:对象创建时初始化

6、内存占用

静态成员变量:每个类只有一份

非静态成员变量:每个对象都有一份

7、共享

静态成员变量:同一个类的所有对象共享一份数据

非静态成员变量:每个对象有独立的数据

这些比较容易理解,这里就偷个懒不解释啦

我们着重讨论后面的重点

三、static 方法和 非static 方法的区别,什么情况下用static方法?

区别:

除了前面提到的互相能否访问、能否使用 this 和 super 之外,还有以下区别:

        static 方法和 非static 方法虽然字节码都存储在方法区

        但是在运行时,非static 方法与 堆内存 有着密不可分的联系。

        原因在于,static 方法可以被类直接调用,而 非static 方法需要对象实例调用

        非static 方法操作的成员变量存储在对象实例中,而 对象实例 存储在堆内存中

        所以需要 this 指针隐式访问堆内存中的对象

使用:

1、工具类里的方法

2、若方法只需要访问静态成员,可以生命 static 防止冗余的对象创建

3、实现单例模式

什么是单例模式?

        单例模式就是一个类中只有一个实例(就像地球只有一个太阳),并且私有化构造方法,防止外部 new 操作,同时提供一个全局可以访问该实例的方法,直接上代码:

public class Singleton {
// 1. 静态变量存储唯一实例(类加载时立即初始化)
private static final Singleton instance = new Singleton();

// 2. 私有构造器阻断外部实例化
private Singleton() {
System.out.println("Singleton instance created");
}

// 3. 全局访问点
public static Singleton getInstance() {
return instance;
}

// 示例方法
public void showMessage() {
System.out.println("Hello from Singleton!");
}
}

注意,这里的数据类型就是我们创建的类,返回值就是对象:

public static Singleton getInstance() {
return instance;
}

四、能否继承 static 成员变量或方法?

在权限允许的情况下是可以的(比如 private 不可以)

        继承后,子类可以直接访问父类的静态成员变量和方法,但子类无法覆盖父类的静态方法,只能隐藏。

        什么意思呢?

        如果子类 没写 和父类同名的静态成员,那么 子类名.静态成员 或 父类名.静态成员 是等效的;如果写了同名静态成员,那么 子类名.静态成员 调用的就是子类的,父类名.静态成员 调用的就是父类的

        子类与父类共享同一个静态成员内存空间,修改一处全局生效

五、运行顺序

        前面我们提到了很多概念,比如说 类加载、各种代码块、静态方法等等

        那么它们的执行顺序是什么呢?我们先来看三个案例:

public class T1 {
public T1() {
System.out.println("构造器");
}
public void info() {
System.out.println("info");
}
static {
System.out.println("test static 1");
}
public static void main(String[] args) {
new T1().info();
}
{
System.out.println("代码块");
}
static {
System.out.println("test static 2");
}
}

 

结果:

test static 1

test static 2

代码块

构造器

Info

原因:

        首先进入main,需要创建Test对象,然后调用Test的方法,检查是否加载Test类,发现没有,开始加载Test类:

        由于没有静态成员初始化,直接加载两个静态代码块,然后开始创建对象,构造代码块执行顺序在构造方法之前,最后调用info方法

public class T2 {
public static void main(String[] args) {
Child child = new Child();
}
}

class Parent {
static {
System.out.println("静态代码块Parent");
}
{
System.out.println("构造代码块Parent");
}
public Parent() {
System.out.println("构造方法Parent");
}
}

class Child extends Parent {
static {
System.out.println("静态代码块Child");
}
{
System.out.println("构造代码块Child");
}
public Child() {
System.out.println("构造方法Child");
}
}

结果:

静态代码块Parent

静态代码块Child

构造代码块Parent

构造方法Parent

构造代码块Child

构造方法Child

原因:

在子类的构造方法中,编译器自动添加了无参的super()调用

都看到这了,给孩子点个赞吧~

super关键字:

作用:

        用于引用当前对象的直接父类实例

1、调用父类的构造方法

语法:

        super() 或 super(参数列表)

        必须在子类构造方法的第一行

        如果子类构造方法中没有显示调用 super() 或者 this() ,编译器会自动添加无参的super()调用。调用 this() 之后,指向本类另一个构造方法,不论这个调用链式有多长,最终都会调用到super()。

2、访问父类成员变量

        当子类和父类有同名成员变量时:

        super.变量名                 //调用父类的成员变量

3、调用父类成员方法

        当子类重写了父类的方法,但仍想调用父类的原方法:

        super.方法名                 //调用父类方法

class B {
public static B b = new B();
public static B b2 = new B();
{
System.out.println("构造块");
}
static {
System.out.println("静态块");
}
}
public class T3 {
public static void main(String[] args) {
B b = new B();
}
}

结果:

构造块

构造块

静态块

构造块

原因:

        最开始进入main之后发现需要创建对象,然后检查B类是否加载,发现没有加载于是开始加载B类:

        静态代码块需要在静态成员变量初始化之后执行,而此时静态成员变量恰好是对象,对象初始化需要通过构造来创建,所以先执行了两次构造代码块,然后才是静态代码块

        加载完B类,继续创建对象,执行构造代码块

六、总结及易踩坑的点

1、构造方法中修改静态变量

// 不推荐的做法
class Example {
static int count = 0;

public Example() {
count = 0; // 每次创建对象都会重置静态变量
}
}

2、静态方法访问非静态成员

class Example {
int instanceVar = 10;

public static void staticMethod() {
// 错误:无法直接访问实例变量
// System.out.println(instanceVar);
}
}

3、静态方法调用非静态方法

class Example {
public void instanceMethod() {
System.out.println("实例方法");
}

public static void staticMethod() {
// 错误:无法直接调用实例方法
// instanceMethod();
}
}

4、静态上下文中的 this 关键字

class Example {
static int staticVar = 10;

public static void staticMethod() {
// 错误:静态方法中没有 this
// this.staticVar = 20;
}
}

5、初始化顺序问题

多个静态变量和静态代码块同时存在时,谁在后最后结果就是谁的

public class Static {
//静态成员变量显示初始化
private static String Library = "栋梁图书馆";

//静态代码块给静态成员变量赋值
static{
Library = "凌云图书馆";
}

static{
Library1 = "凌云图书馆";
System.out.println("静态代码块");
}

public Static(){
System.out.println("构造方法");
}

private static String Library1 = "栋梁图书馆";

public static void main(String[] args) {
Static s = new Static();
System.out.println(Library);
System.out.println(Library1);
}
}

//结果为:
//静态代码块
//构造方法
//凌云图书馆
//栋梁图书馆

6、工具类设计

对于只包含静态方法的工具类:

——应该声明为 final 类防止继承

——构造方法应设为私有防止实例化

诶,

这次没有问题了

final关键字先欠着,下次再分享~

赞(0)
未经允许不得转载:网硕互联帮助中心 » 大学生入门:static及其易踩坑的点
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!