Java笔记,深入了解static关键字

在“Java笔记,方法(Method)”这篇中有说过java中有带static的方法、变量和没有带static的方法、变量的区别。

  • 关于java中的static关键字:

    • 1.static 英语单词翻译为静态的
    • 2.static 修饰的方法是静态方法 ,例如 static void dosome();
    • 3.static 修饰的变量是静态变量 ,例如 static int i;
    • 4.所有static 修饰的元素都称为静态的,都可以使用 “类名.” 的方式访问,当然也可以用 “引用.” 的方式访问【但不建议】
    • 5.static修饰的所有元素都是类级别的特征,和具体的对象无关。
  • 生命周期(Lifecycle):
      静态方法(Static Method)与静态成员变量一样,属于类本身,在类装载的时候被装载到方法区内存(Method area Memory),不自动进行销毁,会一直存在于方法区内存中,直到JVM关闭。
       非静态方法(Non-Static Method)又叫实例方法,属于实例对象,实例化(new)后会分配到堆内存中,必须通过类的实例来引用。不会常驻内存,当实例对象被JVM 回收机制(GC)回收之后,也跟着消失。

  • 在内存中存储位置
    静态方法和静态变量创建后始终使用同一块内存,是连续的。
    非静态方法会存在于内存的多个地方,是离散的。

  • 效率
    静态方法的使用效率比非静态方法的效率高。

  • 线程安全
    静态方法是共享代码段,静态变量是共享数据段。既然是“共享”就有并发(Concurrence)的问题。
    非静态方法是针对确定的一个对象的,所以不会存在线程安全的问题。

  • 使用范围
    静态方法:⒈ 有静态属性的类,一般会定义静态方法。⒉ 没有属性的类,一般会定义静态方法。⒊ 如果一个方法与他所在类的实例对象无关,那么它就应该是静态的。静态方法可以被继承但是不能被覆盖。

  • 总计
    如果静态方法在系统中定义太多,会占用大量的资源,最后造成内存溢出,所以静态方法不能滥用。如果从线程安全、性能、兼容性上来看,选用实例化方法为宜。

让我带你深入的了解了解static关键字吧 Ψ( ̄∀ ̄)Ψ
  • 什么时候成员变量声明为静态变量(带static的)呢?
      所有对象都有这个属性,并且所有的这个属性的值是一样的,建议定义为静态变量,节省内存的开销。

  • 什么时候成员变量声明为实例变量(不带static的)呢?
      所有对象都有这个属性,但是这个属性的值会随着对象的变化而变化【不同对象的这个属性具体的值不同】

  • 静态变量在类加载的时候初始化,内存在方法区中开辟。访问的时候不需要创建对象,直接使用“类名.静态变量名”的方式访问。


来看看例子吧,上菜了~~~ ლ(╹◡╹ლ)
"中国人"类 (Chinese.class),提示:看注释是理解的关键。
/**
 * 什么时候成员变量声明为实例变量呢?
     - 所有对象都有这个属性,但是这个属性的值会随着对象的变化而变化【不同对象的这个属性具体的值不同】
什么时候成员变量声明为静态变量呢?
     - 所有对象都有这个属性,并且所有的这个属性的值是一样的,建议定义为静态变量,节省内存的开销。
静态变量在类加载的时候初始化,内存在方法区中开辟。访问的时候不需要创建对象,直接使用“类名.静态变量名”的方式访问。
 */

public class Chinese {
    
    

    // 身份证号【每一个对象的身份证号不同】
    String id;

    // 姓名【每一个对象的姓名不同】
    String name;

    // 国籍【每一个对象由于都是由“Chinese类”实例化的,所以每一个中国人的国籍都是“中国”】
    // 无论通过Chinese类实例话多少给java对象,这些java对象的国籍都是“中国”
    // 实例变量【实例变量是一个java对象就有一份,100个对象,就有100个country】分析这种设计有什么缺点?
    // 实例变量存储在java对象内部,在堆内存当中,在构造方法执行的时候初始化。
    // 所有的中国人的国籍都是“中国”,这里声明为实例变量显然是不合适的,太浪费内存空间,没必要让每一个对象都保留一份“国籍”内存。
    String country;


    // 构造函数
    public Chinese() {
    
    
        /*
         this.id = null;
         this.name = null;
         this.country = null;
         */
    }

    public Chinese(String id, String name, String country) {
    
    
        this.id = id;
        this.name = name;
        this.country = country;
    }
"中国人"测试类(ChineseTest.class)

   在这里,每次实例化对象的时候都要传递同样的(country)属性值“中国”,这样显得代码重复 o( ̄ヘ ̄o#)

public class ChineseTest {
    
    

    public static void main(String[] args) {
    
    

        // 创建中国人对象1
        Chinese zhangsan = new Chinese("1","张三","中国");
        System.out.println(zhangsan.id+zhangsan.name+zhangsan.country);

        // 创建中国人对象2
        Chinese lisi = new Chinese("2","李四","中国");
        System.out.println(zhangsan.id+zhangsan.name+zhangsan.country);
    }
}

JVM内存图分析

在这里插入图片描述

"中国人"类 (Chinese.class)

  在22行发生了改变,String country;—> static String country = “中国”;
  在33行也发送了改变,构造方法需要传递的参数少了 ”Sting country”,同样也要把“this.country = country”去掉

/**
 * 什么时候成员变量声明为实例变量呢?
     - 所有对象都有这个属性,但是这个属性的值会随着对象的变化而变化【不同对象的这个属性具体的值不同】
什么时候成员变量声明为静态变量呢?
     - 所有对象都有这个属性,并且所有的这个属性的值是一样的,建议定义为静态变量,节省内存的开销。
静态变量在类加载的时候初始化,内存在方法区中开辟。访问的时候不需要创建对象,直接使用“类名.静态变量名”的方式访问。
 */

public class Chinese {
    
    

    // 身份证号【每一个对象的身份证号不同】
    String id;

    // 姓名【每一个对象的姓名不同】
    String name;

    // 国籍【每一个对象由于都是由“Chinese类”实例化的,所以每一个中国人的国籍都是“中国”】
    // 无论通过Chinese类实例话多少给java对象,这些java对象的国籍都是“中国”
    // 实例变量【实例变量是一个java对象就有一份,100个对象,就有100个country】分析这种设计有什么缺点?
    // 实例变量存储在java对象内部,在堆内存当中,在构造方法执行的时候初始化。
    // 所有的中国人的国籍都是“中国”,这里声明为实例变量显然是不合适的,太浪费内存空间,没必要让每一个对象都保留一份“国籍”内存。
    // String country;

    // 国籍【所有对象国籍一样,这种特征属性级别的特征,可以提升为整个模板的特征,可以在变量前添加static关键字修饰】
    // 静态变量存储在方法区内存当中。
    static String country = "中国";

    // 构造函数
    public Chinese() {
    
    
        /*
         this.id = null;
         this.name = null;
         this.country = null;
         */
    }

    public Chinese(String id, String name) {
    
    
        this.id = id;
        this.name = name;
    }
}
"中国人"测试类(ChineseTest.class)

  这时实例化对象的时候,只传参只需要传递两个属性值(id,name),而访问country属性时是采用"类名.属性名"的方式访问。

public class ChineseTest {
    
    

    public static void main(String[] args) {
    
    

        // 创建中国人对象1
        // Chinese zhangsan = new Chinese("1","张三","中国");
        // System.out.println(zhangsan.id + zhangsan.name + zhangsan.country);
        Chinese zhangsan = new Chinese("1","张三");
        System.out.println(zhangsan.id + zhangsan.name + Chinese.country);

        // 这里可以说明静态属性country与对象没有关系。
        // Chinese lisi = new Chinese("2","李四","中国");
        // System.out.println(zhangsan.id+zhangsan.name+zhangsan.country);        
        zhangsan = null;
        System.out.println(zhangsan.country);

        // 创建中国人对象2
        // Chinese lisi = new Chinese("2","李四","中国");
        // System.out.println(zhangsan.id + zhangsan.name + zhangsan.country);
        Chinese lisi = new Chinese("2","李四");
        System.out.println(lisi.id + lisi.name + Chinese.country);
    }
}
JVM内存图分析

  此时的内存图是这样的,堆内存对象里的country实例属性已近没有了,在方法区内存中生成一个静态的属性,而且这个静态属性属于Chinese.class的。
在这里插入图片描述

用static定义的数据,是在什么时候执行的呢?

可以使用static关键字来定义"静态代码块":

  • 1.语法格式:
    static{
    java语法;
    }
  • 2.静态代码块在类加载时(实例化的时候)执行,并且执行一次。
  • 3.静态代码块在一个类中可以编写多个,并且准寻自上而下的顺序依次执行。
  • 4.静态代码块的作用是什么?怎么用?用在哪儿?什么时候用?
    - 这当然喝具体的需求有关,例如项目要求在类加载的时刻/时机执行代码完成日志的记录。
    那么这段日志的代码就可以编写到静态代码块当中,完成日志记录。
    - 静态代码块是java程序为程序员准备一个特殊的时刻,这个特殊的时刻被称为类加载时刻。
    若希望在此刻执行一段特殊的程序,这段程序可以直接放到静态代码块当中。
  • 5 通常静态代码块当中完成预备工作,先完成数据的准备工具,例如:初始化连接池,解析XML配置文件…

public class StaticTest01 {
    
    

    static{
    
    
        System.out.println("类加载——>1");
    }
    static{
    
    
        System.out.println("类加载——>2");
    }
    static{
    
    
        System.out.println("类加载——>3");
    }

    public static void main(String[] args) {
    
    
        System.out.println("main begin");
    }
}

运行结果:

类加载——>1
类加载——>2
类加载——>3
main begin
这里补充一个少见的知识点,叫做“实例代码块”,它有点像static,因为它是在实例化对象时刻前先执行的。
public class Test {
    
    

     // 构造函数
    public Test(){
    
    
        System.out.println("Test类的无参构造函数执行");
    }

    // 实例代码块
    {
    
    
        System.out.println(1);
    }

    // 实例代码块
    {
    
    
        System.out.println(2);
    }

    // 实例代码块
    {
    
    
        System.out.println(3);
    }

    // 主方法
    public static void main(String[] args) {
    
    
        System.out.println("main begin");
        new Test();
        new Test();
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_44630560/article/details/105772878