【Java学习记录】六 Java中的static关键字

今天学习了java中的static关键字的用法,在这里做一下总结。

在类中,用static声明的成员变量为静态成员变量,也称为类变量。 类变量的生命周期和类相同,在整个应用程序执行期间都有效。它有如下特点:

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

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

3. 一般用“类名.类属性/方法”来调用。(也可以通过对象引用或类名(不需要实例化)访问静态成员。)

4. 在static方法中不可直接访问非static的成员。

static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。

static修饰的成员变量和方法,从属于类。

普通变量和方法是从属于对象的。

1.修饰成员变量

当一个变量的前面加上了static这个修饰符,就会在内存中为它分配唯一的一块存储空间。程序运行的时候,变量就存在在内存里了。谁要用它,就去访问一下它所在的内存。
假如内存是一辆公共汽车,static变量就像一个公交卡,一车的人都共用这个公交卡,不管公交车上有没有乘客,公交卡总是在这里挂着,它不属于任何一个乘客。编译器想使用公交卡的时候,直接吼出公交卡的名字就好了。

但是如果这是一个非static的变量,则是对每个对象有一份存储空间。程序运行的时候,是没有这个变量的。
相当于一公交车的乘客,每个乘客都拥有一张公交卡。如果没有乘客的话,车上也就没有公交卡。
编译器想要用公交卡来做点什么,必须先制造一个拿着公交卡的乘客才可以。

对于非静态变量:

public class Student {
    String name;
    int age;
    
    public String toString() {
        return "Name:" + name + ", Age:" + age;
    }
    
    public static void main(String[] args) {
        Student s1 = new Student();
        s1.name = "xiaoming";
        s1.age = 10;
        Student s2 = new Student();
        s2.name = "xiaogang";
        s2.age = 11;
        System.out.println(s1);
        System.out.println(s2);
    }
    /**Output
     * Name:xiaoming, Age:10
     * Name:xiaogang, Age:11
     *///~
}

根据上面的代码创建出来的每个对象都是相互独立的,拥有自己的成员变量,相互之间不会干扰。他们在内存中的示意:

s1和s2两个变量引用的对象分别存储在内存中堆区域的不同地址中,所以他们之间相互不会干扰。但其实,在这当中,我们省略了一些重要信息,相信大家也都会想到,对象的成员属性都在这了,由每个对象自己保存,那么他们的方法呢?实际上,不论一个类创建了几个对象,他们的方法都是一样的:

从上面的图中我们可以看到,两个Student对象的方法实际上只是指向了同一个方法定义。这个方法定义是位于内存中的一块不变区域(由jvm划分),我们暂称它为静态存储区。这一块存储区不仅存放了方法的定义,实际上从更大的角度而言,它存放的是各种类的定义,当我们通过new来生成对象时,会根据这里定义的类的定义去创建对象。多个对象仅会对应同一个方法,这里有一个让我们充分信服的理由,那就是不管多少的对象,他们的方法总是相同的,尽管最后的输出会有所不同,但是方法总是会按照我们预想的结果去操作,即不同的对象去调用同一个方法,结果会不尽相同。

static关键字可以修饰成员变量和方法,来让它们变成类的所属,而不是对象的所属,比如我们将Student的age属性用static进行修饰,结果会是什么样呢?请看下面的例子:

public class Student {
    String name;
    static int age;  //只有这里变化
    
    public String toString() {
        return "Name:" + name + ", Age:" + age;
    }
    
    public static void main(String[] args) {
        Student s1 = new Student();
        s1.name = "xiaoming";
        s1.age = 10;
        Student s2 = new Student();
        s2.name = "xiaogang";
        s2.age = 11;
        System.out.println(s1);
        System.out.println(s2);
    }
    /**Output
     * Name:xiaoming, Age:11
     * Name:xiaogang, Age:11
     *///~
}

我们发现,结果发生了一点变化,在给s2的age属性赋值时,干扰了s1的age属性,这是为什么呢?我们还是来看他们在内存中的示意:

给age属性加了static关键字之后,Student的对象就不再拥有age属性了,age属性会统一交给Student类去管理,即多个Student对象只会对应一个age属性,一个对象如果对age属性做了改变,其他的对象都会受到影响。我们看到此时的age和toString()方法一样,都是交由类去管理。

虽然我们看到static可以让对象共享属性,但是实际中我们很少这么用,也不推荐这么使用。因为这样会让该属性变得难以控制,因为它在任何地方都有可能被改变。如果我们想共享属性,一般我们会采用其他的办法:

public class Student {
    private static int count = 0;
    int id;
    String name;
    int age;
    
    public Student() {
        id = ++count;
    }

    public String toString() {
        return "Id:" + id + ", Name:" + name + ", Age:" + age;
    }
    
    public static void main(String[] args) {
        Student s1 = new Student();
        s1.name = "xiaoming";
        s1.age = 10;
        Student s2 = new Student();
        s2.name = "xiaogang";
        s2.age = 11;
        System.out.println(s1);
        System.out.println(s2);
    }
    /**Output
     * Id:1,Name:xiaoming, Age:10
     * Id:2,Name:xiaogang, Age:11
     *///~
}

上面的代码起到了给Student的对象创建一个唯一id以及记录总数的作用,其中count由static修饰,是Student类的成员属性,每次创建一个Student对象,就会使该属性自加1然后赋给对象的id属性,这样,count属性记录了创建Student对象的总数,由于count使用了private修饰,所以从类外面无法随意改变。 

2.修饰成员方法

static的另一个作用,就是修饰成员方法。相比于修饰成员属性,修饰成员方法对于数据的存储上面并没有多大的变化,因为我们从上面可以看出,方法本来就是存放在类的定义当中的。static修饰成员方法最大的作用,就是可以使用"类名.方法名"的方式操作方法进行调用,避免了先要new出对象的繁琐和资源消耗,我们可能会经常在帮助类中看到它的使用:

public class PrintHelper {

    public static void print(Object o){
        System.out.println(o);
    }
    
    public static void main(String[] args) {
        PrintHelper.print("Hello world");
    }
}

我们可以看到它的作用,使得static修饰的方法成为类的方法,使用时通过“类名.方法名”的方式就可以方便的使用了,相当于定义了一个全局的函数(只要导入该类所在的包即可)。不过它也有使用的局限,一个static修饰的类中,不能使用非static修饰的成员变量和方法,这很好理解,因为static修饰的方法是属于类的,如果去直接使用对象的成员变量,它会不知所措(不知该使用哪一个对象的属性)。

3.静态块

我们其实在工作中一直用到的代码块,所谓代码块是指使用“{}”括起来的一段代码。根据位置不同,代码块可以分为四种:普通代码块、构造块、静态代码块、同步代码块。其中静态代码块只执行一次,构造代码块在每次创建对象是都会执行。

例如:

package com.dotgua.study;

class Book{
    public Book(String msg) {
        System.out.println(msg);
    }
}

public class Person {

    Book book1 = new Book("book1成员变量初始化");
    static Book book2 = new Book("static成员book2成员变量初始化");
    
    public Person(String msg) {
        System.out.println(msg);
    }
    
    Book book3 = new Book("book3成员变量初始化");
    static Book book4 = new Book("static成员book4成员变量初始化");
    
    public static void main(String[] args) {
        Person p1 = new Person("p1初始化");
    }
    /**Output
     * static成员book2成员变量初始化
     * static成员book4成员变量初始化
     * book1成员变量初始化
     * book3成员变量初始化
     * p1初始化
     *///~
}

上面的例子中,Person类中组合了四个Book成员变量,两个是普通成员,两个是static修饰的类成员。我们可以看到,当我们new一个Person对象时,初始化的顺序是:1.static修饰的成员变量被初始化,2.随后是普通成员,3.最后调用Person类的构造方法完成初始化。也就是说,在创建对象时,static修饰的成员会首先被初始化,而且我们还可以看到,如果有多个static修饰的成员,那么会按照他们的先后位置进行初始化。

实际上,static修饰的成员的初始化可以更早的进行,请看下面的例子:

class Book{
    public Book(String msg) {
        System.out.println(msg);
    }
}

public class Person {

    Book book1 = new Book("book1成员变量初始化");
    static Book book2 = new Book("static成员book2成员变量初始化");
    
    public Person(String msg) {
        System.out.println(msg);
    }
    
    Book book3 = new Book("book3成员变量初始化");
    static Book book4 = new Book("static成员book4成员变量初始化");
    
    public static void funStatic() {
        System.out.println("static修饰的funStatic方法");
    }
    
    public static void main(String[] args) {
        Person.funStatic();
        System.out.println("****************");
        Person p1 = new Person("p1初始化");
    }
    /**Output
     * static成员book2成员变量初始化
     * static成员book4成员变量初始化
     * static修饰的funStatic方法
     * ***************
     * book1成员变量初始化
     * book3成员变量初始化
     * p1初始化
     *///~
}

在上面的例子中我们可以发现两个有意思的地方,第一个是当我们没有创建对象,而是通过类去调用类方法时,尽管该方法没有使用到任何的类成员,类成员还是在方法调用之前就初始化了,这说明,当我们第一次去使用一个类时,就会触发该类的成员初始化。第二个是当我们使用了类方法,完成类的成员的初始化后,再new该类的对象时,static修饰的类成员没有再次初始化,这说明,static修饰的类成员,在程序运行过程中,只需要初始化一次即可,不会进行多次的初始化。

再看一下下面的这种情况:

class Book{
    public Book(String msg) {
        System.out.println(msg);
    }
}

public class Person {

    static Book book1;
    
    static {
        book1 = new Book("static成员book1成员变量初始化");
        book4 = new Book("static成员book4成员变量初始化");
    }
    
    public Person(String msg) {
        System.out.println(msg);
    }
    
    Book book3 = new Book("book3成员变量初始化");
    Book book5 = new Book("book5成员变量初始化");
    static Book book4;
    
    public static void funStatic() {
        System.out.println("static修饰的funStatic方法");
    }
    
    public static void main(String[] args) {
        Person.funStatic();
        System.out.println("****************");
        Person p1 = new Person("p1初始化");
    }
    /**Output
     *static成员book1成员变量初始化
     *static成员book4成员变量初始化
     *static修饰的funStatic方法
     ****************
     *book3成员变量初始化
     *book5成员变量初始化
     *p1初始化
     *///~
}

 首先使用类调用静态成员函数,调用之前系统先进性行了static块的初始化,也就是说程序执行时先使用static进行类的初始化,然后才调用funStatic(),接下来对于new出一个对象的时候,首先对类中的非静态成员变量进行初始化。

那么看一下这个会输出什么呢?

static {  
  _i = 10;  
}  

public static int _i = 20;  
     
public static void main(String[] args) {  
   System.out.println(_i);  
}

 上面的结果是10还是20?如果存在多个代码块呢?

static {  
      _i = 10;  
 }    public static int _i =30;  static {  
     _i = 20;  
 }   
 public static void main(String[] args) {  
     ystem.out.println(_i);
 }

 测试过后你会发现两个答案结果都是20。

那是因为public static int _i = 10;  和如下代码:

public static int _i;  
static {  
   _i = 10;  
}

 是没有区别的,他们在编译后的字节码完全一致,所以两个例子的结果就是最后一次赋值的数值。

4.静态导包

 

参考文献:https://www.cnblogs.com/dotgua/p/6354151.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

编程芝士

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

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

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

打赏作者

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

抵扣说明:

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

余额充值