关于 Java Static关键字的作用与正确用法,你是否真的懂 ?

前言

某天登录自己的CSDN的时候突然看到多了二十多条评论,无一例外都是喷人的,然后点开一看才知道原来自己写的某篇文章取了一个很标题党的标题,由于时间匆忙只写了一个开头就发表了,本想等下次继续写,没想到文章被推上了热搜,然后好多人看的一头雾水。通过这件因缺思庭的事情说明了两个问题:
1、博客没写完保存草稿不要直接发表 。
2、文章起一个好的标题真的看的人会多一些。

好吧直接进入正题,最近一段时间中整出来一个bug,在广播中弹出的进度条dialog一直不停的闪,进度到了百分之百后又退到了95%。最后花了我好几个小时的时间回退代码才发现原来是receiver中声明的dialog对象被我去掉了static修饰字。
本来是 private static ProgressDialog dialog; 然后我在优化代码的时候改成了 private ProgressDialog dialog; ,就一个 static 关键字的区别造成了一个bug花了好几个小时,这个说明了我对static用法是一无所知的,本篇博客也记录一下static 关键字的用法,作为一个反思。

本篇博客分为两个部分:
1 、问题的分析
2 、static 关键字的正确用法

一、问题分析

伪代码如下:

  • 1、首先注册一个静态广播
        <receiver android:name=".DialogTestReceiver">
            <intent-filter >
                <action android:name="action.com.android.testStatic"/>
            </intent-filter>
        </receiver>
  • 2、在点击事件中模拟发送广播
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activitys_main);
        findViewById(R.id.bt_dialog).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent=new Intent();
                intent.setAction("action.com.android.testStatic");
                sendBroadcast(intent);
            }
        });
  • 3、在广播接收器中模拟弹出progressDialog
/**
 * Created by Administrator on 2019/4/1.
 */

public class DialogTestReceiver extends BroadcastReceiver {
    private static  final String TAG=DialogTestReceiver.class.getSimpleName();
    private static  final String TEST_ACTION="action.com.android.testStatic";
    private ProgressDialog dialog;
    private int progress=10;
    @Override
    public void onReceive(Context context, Intent intent) {
        String action=intent.getAction();
        if (action.equals(TEST_ACTION)){
            if (dialog ==null){
                dialog=new ProgressDialog(context);
                dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
                dialog.show();
                dialog.setProgress(progress++);
            }
            Log.d(TAG,"dialog.hashcode="+dialog.hashCode());
        }
    }
}
  • 4、分析log,打印的log如下
04-15 20:12:17.534 5640-5640/com.jmgo.jmgodemo D/DialogTestReceiver: dialog.hashcode=375025886
04-15 20:12:21.666 5640-5640/com.jmgo.jmgodemo D/DialogTestReceiver: dialog.hashcode=1016718015
04-15 20:12:41.996 5640-5640/com.jmgo.jmgodemo D/DialogTestReceiver: dialog.hashcode=257289612

通过log我们发现每次收到广播都会new一个新的dialog对象出来,但是明明这里dialog是全局的变量啊,难道每次这个静态的广播接收器都会被销毁掉嘛,接着我们在下面这句log打印receiver 的对象哈希值

   @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG,"this.hashcode="+this.hashCode());

新的log如下

04-16 19:43:56.172   DialogTestReceiver: this.hashcode=241410982
04-16 19:43:56.266   DialogTestReceiver: dialog.hashcode=110598025
04-16 19:44:39.419   DialogTestReceiver: this.hashcode=228510257
04-16 19:44:39.442   DialogTestReceiver: dialog.hashcode=127441996
04-16 19:44:43.937   DialogTestReceiver: this.hashcode=192741259
04-16 19:44:43.954   DialogTestReceiver: dialog.hashcode=151329710
04-16 19:44:46.570   DialogTestReceiver: this.hashcode=140029525
04-16 19:44:46.587   DialogTestReceiver: dialog.hashcode=25229632

通过log我们再次发现了其实每次广播接收器的对象都不一样,这说明静态广播每次都被销毁然后重新被创建。
至于为什么静态广播会每次接收完就被销毁已经Android 广播的分发流程,请移步这里,本篇博客就不讨论了,下面讨论下造成这个问题的static关键字的正确用法是怎样。

二、static 关键字的作用与正确用法

当时我做代码调优的时候觉得static 变量不能被回收,于是把static 关键字给去掉了,我们来看下Java中的内存分配。

java内存分配
寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制.实际开发中好像只有硬件驱动才会用。

:存放基本类型(int ,string,boolean)的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中。在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。

堆: 存放代码中new 出来的数据,堆内存用来存放由new创建的对象和数组。 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
  在堆中产生了一个数组或对象后,还可以 在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。引用变量就相当于是为数组或者对象起的一个名称。
  引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍 然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。这也是 Java 比较占内存的原因。

静态域: 存放在对象中用static定义的静态成员,这里面用static修饰的包括静态变量、静态方法、静态块、内部静态类和静态接口方法(Java8以上支持)都在这里存放。

常量池: 常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值(final)还包含一些以文本形式出现的符号引用

非RAM存储:

接下来总结一下 static 关键字的常见的用法:

1、静态成员变量和方法
这种方法很常见,我们用的也比较多。

2、静态代码块
静态代码块是随着类的加载而加载,它是自动加载的,实际开发中用的不多。

    static {
        Log.d(TAG,"这是静态代码块 !");
    }

3、静态内部类
顾名思义静态内部类是用static修饰的内部类了。我们分别对比下普通内部类和静态内部类。

public class OutClass {
    public class OutClass {
        public OutClass (){
            System.out.println("OutClass ");
        }
     public static class InnerClass{
        public innerClass(){
            System.out.println("innerClass");
        }
    }    
    }
  
}
//获取内部类的对象的写法
//普通内部类
OutClass.InnerClass innerClass=new OutClass().new InnerClass();
//静态内部类
OutClass.InnerClass innerClass=new OutClass.InnerClass();

通过代码我们发现,非静态内部类是附属在外部类对象上的,需要先实例化一个外部类的对象,通过外部类对象才能实例化非静态内部类;而静态内部类可以看做是直接附属在外部类上的,这个静态代表附属体是外部类,而不是外部类实例;外部类在进程中是唯一的,而静态内部类不需要唯一,可以生成多个实例。

最后,总结一下 static 是一种很特殊的用法,我们在使用的时候要考虑到他的生命周期。
首先静态变量的生命周期取决于类的生命周期,当类被加载的时候,静态变量被创建并分配内存空间,当类被卸载时,静态变量被摧毁,并释放所占有的内存。
静态内部类对象的生命跟普通的对象一样,生命开始于开发者创建它,结束于系统回收它。

后记

今天是进入A公司刚好满一年的时间,也许是需要一点Surprise,我给自己挖了一个大坑。我负责维护的了大半年来的升级应用出现了功能缺陷性的重大bug,导致出去的机器不得不返厂返工。虽然这个不全是我的责任,但是开发和维护都我,从一开始,我没发现这个问题,导致埋下了隐患现在终于爆发了出来。即使我没有被领导责备,但是相关的同事被责备也让我感到很愧疚,如果一开始,我接手的时候对接清楚需求,就不会有今天这样子的情况了。这件事也给我敲响了警钟,作为一个技术开发者,对待自己的工作一定要非常严谨!同时也要编写有规范的开发流程文档!

过去的这一周有点丧,我一直都在不停的填坑,除了这个重大bug之外我又改出了新的问题,最后不得不一条条回退代码找原因。作为一个工作即将满3三年的程序猿,却表现的像一个小白,实在是不应该。虽然这件事让我对自己产生了怀疑,但是无论如何还是好好总结自身的问题,我相信能力的提高是一个渐变的过程,日积月累才会厚积薄发!

参考文章

1、Java内存分配之堆、栈和常量池
2、浅谈Java中的对象和引用
3、静态内部类的生命周期

  • 20
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mrsongs的心情杂货铺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值