zoukankan      html  css  js  c++  java
  • java基础(五)-----关键字static

    在Java中并不存在全局变量的概念,但是我们可以通过static来实现一个“伪全局”的概念,在Java中static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,当然也可以修饰代码块。

    Static变量

    • 在类中用static声明的成员变量为静态成员变量,它为该类的公用变量,在第一次使用时初始化,对于该类的所有对象来说,static成员变量只有一份。

    • 可以通过引用或者类名访问静态成员

    • 在类中,用static声明的成员变量为静态变量,或者叫:类属性、类变量。

    (注意:静态变量是从属于类,在对象里面是没有这个属性的;成员变量是从属于对象的,有了对象才有那个属性)

    • 它为该类的公用变量,属于类,被该类的所有实例共享,在类被载入时被显示初始化。

    • 对于该类所有对象来说,static成员变量只有一份.被该类的所有对象共享!!

    • 可以使用“对象.类属性”来调用。不过,一般都是用"类名.类属性”。

    • static变量置于方法区中。

    • 在静态的方法里面不可以调用非静态的方法或变量;但是在非静态的方法里可以调用静态的方法或变量。

    Static方法

    ◆用static声明的方法为静态方法。

    • 不需要对象,就可以调用(类名.方法名

    • 在调用该方法时,不会将对象的引用传递给它,所以在static方法中不可访问非static的成员

    静态初始化块

      静态初始化块是在类被加载的时候就执行的一块程序,并且一直存在直到程序关闭。也就是说当程序被执行,即classloader将该java程序编译后的class文件加载后,就能执行到静态初始化块这段程序;当程序关闭,我的个人理解也就是java.exe进程被结束的时候,静态初始化块结束(例如在静态初始化块里对一个类的静态变量进行赋值,该变量一直存在到程序关闭)。

    下面我们来举例说明:

     1 public class Test {
     2     //静态变量
     3     public static String testStatic = "testStatic";
     4     //静态初始化块
     5     static {
     6           System.out.println(testStatic);
     7           System.out.println("Proc begin");
     8           testStatic = "testProc";
     9           System.out.println("Proc end");
    10     }
    11     //主方法
    12     public static void main(String[] args) {
    13           System.out.println(testProc);
    14           System.out.println("main begin");
    15           System.out.println("main end");
    16     }
    17 }

    执行main方法输出结果:

    1 testStatic
    2 Proc begin
    3 Proc end
    4 testProc
    5 main begin
    6 main end

    也就是说当JVM将要执行main方法的时候,先要将Test.class加载到JVM中,此时就执行了静态初始化块中的程序;然后再执行执行main方法中的程序。这个例子没有将这个类实例化,所以没有用到构造函数。倘若需要实例化该类的时候,则构造方法的执行顺序也是在静态初始化块之后的。

    最后我们可以得出这么一个结论:Java类的执行优先顺序

    该类的静态变量->该类的静态初始化块->该类的构造方法

    若存在父类的情况下则是:

    父类的静态变量->父类的静态初始化块->子类的静态变量->子类的静态初始化块

    内存分析Static

    静态成员变量:

     1 package cn.galc.test;
     2 public class Cat {
     3     /**
     4      * 静态成员变量
     5      */
     6     private static int sid = 0;
     7     private String name;
     8     int id;
     9     Cat(String name) {
    10         this.name = name;
    11         id = sid++;
    12     }
    13     public void info() {
    14         System.out.println("My Name is " + name + ",NO." + id);
    15     }
    16     public static void main(String[] args) {
    17         Cat.sid = 100;
    18         Cat mimi = new Cat("mimi");
    19         Cat pipi = new Cat("pipi");
    20         mimi.info();
    21         pipi.info();
    22     }
    23 }

    通过画内存分析图了解整个程序的执行过程:

    执行程序的第一句话:Cat.sid = 100;时,这里的sid是一个静态成员变量,静态变量存放在数据区(data seg),所以首先在数据区里面分配一小块空间sid,第一句话执行完后,sid里面装着一个值就是100。

    此时的内存布局示意图如下所示

    接下来程序执行到:

    1 Cat  mimi = new Cat(“mimi”);

    这里,调用Cat类的构造方法Cat(String name),构造方法的定义如下:

    1 Cat ( String name){
    2   this.name = name;
    3   id=sid++;
    4 }

      调用时首先在栈内存里面分配一小块内存mm,里面装着可以找到在堆内存里面的Cat类的实例对象的地址,mm就是堆内存里面Cat类对象的引用对象。这个构造方法声明有字符串类型的形参变量,所以这里把“mimi”作为实参传递到构造方法里面,由于字符串常量是分配在数据区存储的,所以数据区里面多了一小块内存用来存储字符串“mimi”。此时的内存分布如下图所示:

      当调用构造方法时,首先在栈内存里面给形参name分配一小块空间,名字叫name,接下来把”mimi”这个字符串作为实参传递给name,字符串也是一种引用类型,除了那四类8种基础数据类型之外,其他所有的都是引用类型,所以可以认为字符串也是一个对象。所以这里相当于把”mimi”这个对象的引用传递给了name,所以现在name指向的是”mimi”。所以此时内存的布局如下图所示:

    接下来执行构造方法体里面的代码:

    1 this.name=name;

      这里的this指的是当前的对象,指的是堆内存里面的那只猫。这里把栈里面的name里面装着的值传递给堆内存里面的cat对象的name属性,所以此时这个name里面装着的值也是可以找到位于数据区里面的字符串对象“mimi”的,此时这个name也是字符串对象“mimi”的一个引用对象,通过它的属性值就可以找到位于数据区里面的字符串对象“mimi”。此时的内存分布如下图所示:

    接下来执行方法体内的另一句代码:

    1 id=sid++;

    这里是把sid的值传递给id,所以id的值是100,sid传递完以后,自己再加1,此时sid变成了101。此时的内存布局如下图所示。

     

    到此,构造方法调用完毕,给这个构造方法分配的局部变量所占的内存空间全部都要消失,所以位于栈空间里面的name这块内存消失了。栈内存里面指向数据区里面的字符串对象“mimi”的引用也消失了,此时只剩下堆内存里面的指向字符串对象“mimi”的引用没有消失。此时的内存布局如下图所示:

    接下来执行:Cat  pipi = new Cat(“pipi”);

    这里是第二次调用构造方法Cat(),整个调用过程与第一次一样,调用结束后,此时的内存布局如下图所示:

    最后两句代码是调用info()方法打印出来,打印结果如下:

    1 My name is mimi,NO.100
    2 My name is pipi,NO.101

      通过这个程序,看出来了这个静态成员变量sid的作用,它可以计数。每当有一只猫new出来的时候,就给它记一个数。让它自己往上加1。

      这里调用构造方法Cat(String name) 创建出两只猫,首先在栈内存里面分配两小块空间mimi和pipi,里面分别装着可以找到这两只猫的地址,mimi和pipi对应着堆内存里面的两只猫的引用。这里的构造方法声明有字符串类型的变量,字符串常量是分配在数据区里面的,所以这里会把传过来的字符串mimi和pipi都存储到数据区里面。所以数据区里面分配有存储字符串mimi和pipi的两小块内存,里面装着字符串“mimi”和“pipi”,字符串也是引用类型,除了那四类8种的基础数据类型之外,其他所有的数据类型都是引用类型。所以可以认为字符串也是一个对象。

      这里是new了两只猫出来,这两只猫都有自己的id和name属性,所以这里的id和name都是非静态成员变量,即没有static修饰。所以每new出一只新猫,这只新猫都有属于它自己的id和name,即非静态成员变量id和name是每一个对象都有单独的一份。但对于静态成员变量来说,只有一份,不管new了多少个对象,哪怕不new对象,静态成员变量在数据区也会保留一份。如这里的sid一样,sid存放在数据区,无论new出来了多少只猫在堆内存里面,sid都只有一份,只在数据区保留一份。

      静态成员变量是属于整个类的,它不属于专门的某个对象。那么如何访问这个静态成员变量的值呢?首先第一点,任何一个对象都可以访问这个静态的值,访问的时候访问的都是同一块内存。第二点,即便是没有对象也可以访问这个静态的值,通过“类名.静态成员变量名”来访问这个静态的值,所以以后看到某一个类名加上“.”再加上后面有一个东西,那么后面这个东西一定是静态的,如”System.out”,这里就是通过类名(System类)再加上“.”来访问这个out的,所以这个out一定是静态的。

    总结

    • 无论是变量,方法,还是代码块,只要用static修饰,就是在类被加载时就已经"准备好了",也就是可以被使用或者已经被执行,都可以脱离对象而执行。反之,如果没有static,则必须要依赖于对象实例。
    • static的方法只能调用static的变量和方法,非static的方法既可以调用static的,又可以调用非static的。
  • 相关阅读:
    learnyou 相关网站
    hdu 3038 How Many Answers Are Wrong
    hdu 3047 Zjnu Stadium 并查集高级应用
    poj 1703 Find them, Catch them
    poj 1182 食物链 (带关系的并查集)
    hdu 1233 还是畅通工程
    hdu 1325 Is It A Tree?
    hdu 1856 More is better
    hdu 1272 小希的迷宫
    POJ – 2524 Ubiquitous Religions
  • 原文地址:https://www.cnblogs.com/java-chen-hao/p/10394769.html
Copyright © 2011-2022 走看看