Java|static关键字的使用详解

static关键字

static 表示静态,共有的含义

static可修饰的内容:

1.修饰属性,类属性,类变量

2.修饰方法,类方法。工具方法

3.static修饰代码块,静态代码块

4.static修饰内部类,静态内部类

今天主要讲前三种,static修饰内部类,后续会在讲到内部类的时候单独解释

类属性

static修饰的属性称为类属性,类变量,所有对象共享

代码示例:

class Person{
    //成员变量或者说实例变量必须通过该类的对象来访问
    String name;
    int age;
    String sex;
    String country;
    //成员方法,也是必须通过对象来访问
    void show(){
        System.out.println("name = " + name + ", age = " + age + ", country = " + country);
    }
}

为什么要使用static关键字修饰属性?

country是成员变量,所有对象都在堆中保存有自己的country属性

有可能所有对象的这个值都是同一个值,比如中国人的所有对象的country = “中国”

假设有一天,中国光复了日本,那么对日本来说,所有对象的country属性值都要改成China

对country来说,有很多对象的属性值都是一样的,因此可以设置成共享,于是使用static修饰这个属性,该属性就称为静态属性

当一个成员属性/实例变量被static关键字修饰,他就表示类的属性,该类的所有对象共享这个属性,所有对象的属性值大家都一样

被static修饰的属性在JVM方法区中存储,所有该类对象共享此属性,也就是说该类的所有对象都只是使用了这个属性的值而已

被static修饰后的属性,可以直接通过类名称就可以访问,无需通过对象访问,不过通过对象也可以访问

Person.country = China;

内存图:

在这里插入图片描述

想调用new Person()来产生Person对象,得先要有Person类才能产生对象

因此创建对象的时候:

首先将Person类加载到内存中,Person中的所有static变量就会被加载到方法区中

static修饰的属性是全局唯一的,所有对象共享的,修改一个对象的属性值,就相当于把所有对象的属性值都该改变了

public static void main(String[] args) {
    Person per1 = new Person();
    Person per2 = new Person();
    per1.name = "hello";
    per1.age = 18;
    per2.name = "hi";
    per2.age = 20;
    per1.show();//执行结果 name = hello, age = 18, country = Japan
    per2.show();//执行结果 name = hi, age = 20, country = Japan
    per2.country = "China";
    per1.show();//执行结果 name = hello, age = 18, country = China
    per2.show();//执行结果 name = hi, age = 20, country = China
}
class Person{
    String name;
    int age;
    static String country = "Japan";

    void show(){
        System.out.println("name = " + name + ", age = " + age + ", country = " + country);
    }
}

前面我们提到类属性保存在方法区中,那么方法区里还有什么呢?

方法区内存储的内容:

类中的所有方法的信息都存储在方法区,当方法被调用的时候,就被压入栈帧

所有常量、静态变量都存储在方法区中

能否在方法中定义一个static变量

public static void main(String[] args) { static int a = 10;//编译报错 }

一定不能在方法中定义static静态变量

方法中定义的变量全是局部变量,局部变量都在栈中存储,方法调用结束后,所有变量都是要销毁的,因此不可能定义出一个即在栈中又在方法区中的属性

final和static有什么区别?

class Person{
  final int age = 18;//成员常量,都在堆中存储
  static String country = "China";//静态变量,在方法区中存储
}

内存图:

在这里插入图片描述

类中定义的final修饰的是成员常量,这些成员常量存储在堆中,这和前面提到的方法区中存有常量似乎有一些矛盾。

但实际上,方法区中存储的常量指的是字面量,比如18这个数字,而final修饰的成员常量依然是存储在堆中

成员常量在定义的时候就要初始化

final int age;//error

final int age = 18;

若在类中定义了一个常量,我们通常情况下都会把static和final共同使用,称为类的常量

原因如下:

比如上述代码中的age这个属性是成员常量,在类定义的时候就被赋值为18,Person的所有对象都有age属性,且属性值均为18,而同一个值没必要重复存储多次,何不定义成静态变量,让所有对象共享,这样就只在内存中存储了1次

final关键字这就像是个给我们的天然的共享概念,因此为避免在每个对象中都存储一次,导致空间的浪费,可以直接定义为静态常量,使得所有对象都能共享这个属性,全局唯一

综上所述:在类中定义常量,一般会使用全局常量,由static final共同修饰

常量的命名规则:

所有单词全部大写,多个单词使用下划线分隔

static final String STUDENT_SCHOOL = "清华大学";

能否通过null引用访问static变量?笔试题常考!

Person per = null;
System.out.println(per.country);
//编译通过

static属性称为类属性,通过类名称即可直接访问,也就是说,没有对象也能调用,包括该类的null引用

per是Person类型的引用,哪怕是per没有存任何东西,也能通过Person类访问到类属性

这么理解:

创建引用类型变量per的时候,就已经和Person类建立了一种关系,即Person是变量per的类型,通过对象访问类属性的时候,实际上是通过per的类型Person去访问到类属性的,因此程序访问类属性的时候不会去在意引用类型变量里存的是不是null,而是直接根据per的类型去访问类属性

实际上这个案例就说明了,静态属性和对象无关

static、final总结:

static变量称为类属性,在方法区中存储,该类的所有对象共享此变量

若在类中定义了常量,一般我们使用static和final共同修饰,全局常量

要使用类属性,我们通常直接通过类名称.属性名称定义,不推荐使用对象来调用类属性,不规范

类属性可以通过类名称直接访问,包含该类的null引用

静态方法

static修饰方法 - 类方法/工具方法/静态方法

static修饰的方法同样也是通过类名称直接访问,没有对象也能访问

思考几个问题

public static void main(String[] args) {}

  • 为什么Java中主方法是个静态方法?

主方法是一个程序的入口,如果主方法是个成员方法,得通过对象调用,创建对象又需要一个入口来创建,而现在连入口都没有,更何谈产生对象

程序从主方法开始执行,主方法要能调用起来,需要静态方法来直接调用,而无需产生对象

  • 静态方法能否访问成员属性和成员方法?

从static的语义来看,静态方法是静态的,无需产生对象就能调用的方法,或者说静态方法是没有对象的

成员域(即成员方法和成员变量)必须通过对象来调用,而静态方法没有对象或者说没有通过对象调用方法,必然无法直接访问成员变量和方法

  • 成员方法能否访问静态变量和静态方法?

静态变量和方法在没有对象的情况下就能访问,不在乎有没有对象

代码示例:

public class Test {
    public static void main(String[] args) {
        Person.fun();
    }
}

class Person{
    String name;
    int age;
    static String country = "China";

    void show(){
        System.out.print("调用非静态方法:");
        System.out.println("name = " + name + ", age = " + age + ", country = " + country);
    }

    static void fun(){
        System.out.println("调用静态方法");
        show();//error 
      	System.out.println(Person.country);//编译通过
    }
}

为什么在静态方法中调用非静态方法会出错

其实这个问题和静态方法中访问成员属性类似,fun是静态方法,调用时没有通过对象来引用,想要在静态方法内部执行的成员方法,需要通过对象调用,所以这里不能被调用,编译报错

相反,如果是static属性,可以在static修饰的方法中调用,因为都属于静态域,不需要对象就能访问

根据上面的内容,可以总结出:

在静态方法中只能调用静态方法或者静态属性,static家族之间可以相互调用。不能直接调用成员方法和成员属性,必须通过对象来调用

在成员方法中既可以调用成员方法,也可以调用静态方法(此时都已经产生了该类对象,一定能够访问静态域)

什么时候设置static变量、方法

共享的变量如country属性,我们设计为静态变量

而工具类的方法一般设计为static方法,比如Array.sort(int [])

普通类能否使用static关键字修饰

类定义出来是为了产生对象的,然后通过对象调用类里面定义的内容

假设static能修饰一个类,那么这个类甚至不需要创建对象就能调用了

“类本身”这种东西是不会保存在内存里的。他的作用就是告诉编译器怎么样生成代码,编译完了就没用了。就像其他简单的类型int或结构体,也是一样的

既然内存中都不存在“类”,那么用static去定义类是完全没有意义的

静态代码块

静态代码块是指定义在类中,使用static修饰的代码块,在类加载的时候执行一次,只会执行一次

代码示例:

public class Test {
    public static void main(String[] args) {
        Animal animal1 = new Animal("dog");
        Animal animal2 = new Animal();
    }
}
class Animal{
    String name;
    {
        name = "test";
        System.out.println("2.Animal的构造块");
    }
    public Animal(){
        System.out.println("1.Animal的无参构造");
    }
    public Animal(String name){
        this.name = name;
        System.out.println("1.Animal的有参构造");
    }
    static{
        System.out.println("3.Animal的静态代码块");
    }
}
//执行结果 32121
3.Animal的静态代码块
2.Animal的构造块
1.Animal的有参构造
2.Animal的构造块
1.Animal的无参构造

静态代码块在类加载时执行一次,于对象无关,无论产生多少对象,都只执行一次,并且静态代码块在加载内存的时候就执行了,相比构造块、构造方法都要快

如果是定义在主类中的静态代码块,其执行顺序甚至要比main主方法更早

主类中的静态方法:

public class Test {
    static {
        System.out.println("主类中的静态代码块");
    }
    public static void main(String[] args) {
        System.out.println("进入主方法");
    }
}
//执行结果
主类中的静态代码块
进入主方法

结论:

主类中的静态块优先于主方法执行,JVM要执行主方法,首先得加载主类,主类一加载, 静态块就执行了

如果类中定义了静态属性和静态代码块:

public class Test {
    public static void main(String[] args) {
        System.out.println(Animal.age);
    }
}
class Animal{
    String name;
    static int age = 10;
    static{
        age = 100;
        System.out.println("3.Animal的静态代码块");
    }
}
//执行结果
3.Animal的静态代码块
100

静态属性存在于方法区中,类定义的时候就会有初始值,初始值为10,这个类就被放入方法区,此时这个类只是定义了,还没有加载

当主方法中使用了Animal,也就是当程序读取到Animal的时候,就需要把Animal类从方法区加载内存,类在加载时,静态代码块就执行了,于是age就被静态代码块里的100给覆盖了

在主方法执行之前, 类就已经定义好了,其中的类属性也已经被定义,只有当类被调用的时候,类才会加载到内存中,于是类中的静态代码块在加载的过程中执行了,当加载结束后,再依次执行成员代码块和构造方法(如果有的话)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值