Java关键字之static
static关键字五种用法的总结
平时我们都会很经常的去使用static关键字去修饰变量呀,方法呀,都大概的知道被static所修饰之后,有一种“全局”的味道,那么我就在这里详细的总结一下static关键字的四种用法。有问题欢迎留言
- 修饰成员变量
- 修饰成员方法
- 修饰代码块
- 修饰内部类
- 静态导包
修饰成员变量
注意:
首先声明一下,这里所修饰的是成员变量,而不是局部变量,局部变量是不允许被static
修饰的。(了解成员变量和局部变量的概念和区别)
通常我们在开发过程中,用static
去修饰一个成员变量,是为了声明一个全局变量,让它对该类的所有对象共享。或者配合final
关键字声明一个全局的常量。被static所修饰的成员变量称为静态变量(或类变量),是属于类的范畴。没有被static
修饰的变量称为实例变量,属于对象的范畴。
静态变量特性:
- 全局共享,该类的所有实例对象都可以引用到同一个静态变量。
- 别的类可以通过类名.变量名去调用该静态变量
- 静态变量不同于实例变量,静态变量存放在内存的方法区中,而实例变量存放在堆中
- 静态变量在类加载的时候被加载,在程序运行期间有且仅加载一次
代码测试:
以下是测试代码,我们从JVM内存模型来分析一下,静态变量和实例变量在内存中区别
public class Student {
//实例变量
String name;
int age;
//成员方法
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
public static void main(String[] args) {
//声明两个Student类型的变量s1,s2,分别为这两个变量赋于两个Student实例对象的地址
Student s1 = new Student();
s1.name = "Tom";
s1.age = 22;
Student s2 = new Student();
s2.name = "Jerry";
s2.age = 31;
}
}
此刻的内存模型为:
从上图和测试代码结合来看,我们可以看出,s1
和s2
两个实例变量所指向的实例对象都存储在堆中。实例变量name
和age
都属于对象的数据,对象的变量应该交给对象自己去管理,所以实例变量是存储在堆的对象当中。
接下来,我们在稍微修改一下代码,用static
修饰age
变量,使其成为静态变量(类变量)
public class Student {
String name;
static int age; //使用static修饰符修饰age变量
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
public static void main(String[] args) {
Student s1 = new Student();
s1.name = "Tom";
s1.age = 22;
Student s2 = new Student();
s2.name = "Jerry";
s2.age = 31;
}
}
此刻的内存模型为:
从图中内存模型中,我们可以看出,静态变量age
不再交给对象管理,堆中Student
对象的数据也不再含有age
这个变量。因为静态变量是属于类的一部分,与对象无关,所以age
变量会统一交给Student
类去管理,既存储在方法区(静态区)中。这也印证了静态变量是属于该类所有对象所共享的,静态变量被改变,是对该类所有对象可见的。
补充:
- Student类型的局部变量s1和s2是存储在虚拟机栈中的,所储存的值是所指向对象的地址,该变量称为”引用”或”对象的引用”
- 堆中对象的方法空间所存放的值是方法区类信息中具体方法的地址,所以同类的不同对象,同一个方法都是指向同一个地址的,既方法区具体方法的地址。如
s1
对象和s2
对象存放toString
方法的空间实际存放的是方法区中Student
类的toString
方法的地址,他们所引用的都是同一片地址。 - 方法区所存放的类的信息可以说是类的定义,实例化的实际流程就是根据方法区中类的信息去实例化出具体的对象,然后将这个对象的存放在堆中。
修饰成员方法
使用static来修饰方法,相比于修饰成员变量,修饰方法对于数据的存储方面并没有多大的变化,因为方法本身就是存放在类的定义当中的,既方法区类的信息中。但是静态方法还是有作用的,比如可以使用类名.方法名
的方式操作方法,避免了要new对象的资源消耗,静态方法的应用在工具类中经常被使用到,同时在JDK8新特性(接口的增强)中,可以在接口中使用static去修饰的具体方法,从而使得接口拥有具体的方法。
public class Demo {
public static int add(int a ,int b){
return a + b ;
}
}
class Test {
public static void main(String[] args) {
System.out.println(Demo.add(1, 2));
}
}
特性:
- 可以使用
类名.方法名
去调用方法 - 静态方法不允许直接调用非静态成员,因为在类的加载中,静态成员在类加载时优先级高于非静态成员(实例变量,非静态方法等)。所以总不能在静态方法已经被JVM加载识别的时候,方法体内存在着一个未知的东西(因为还没有被JVM所加载识别)。
- 静态方法体内不允许使用
this
,super
关键字。因为静态方法属于类的范畴,是通过类名.方法名进行调用的,不需要实例化出对象来调用,所以不存在对象这个概念.但this
和super
是属于对象的概念范畴,this
代表着当前对象,super
代表当前对象的父类对象。
修饰代码块
static只允许修饰构造代码块(在类中,方法体外的代码块),被static修饰的代码块称为静态代码块。静态代码块的一大作用是帮助类进行的静态变量的初始化。在类加载的优先级与静态变量相同,谁先加载,只看实际代码位置。
public class Demo{
static a ;
static {
a = 1;
System.out.println(a);
}
}
补充:
【Java学习笔记系列】继承方式下静态成员变量、普通成员变量、静态代码块、构造代码块、构造函数在JVM的加载顺序,可以看下
修饰内部类
我们知道内部类分为四种,有成员内部类,静态内部类,局部内部类和匿名内部类。而静态内部类就是被static所修饰的内部类。我们这里要声明一下,只允许能被static修饰的类只有两种:
- 顶级类下的内部类
- 静态内部类下的内部类
public class OuterClass {
//顶级类(top-level class)下的内部类能被static修饰
public static class InnerClass {
//静态内部类下的内部类能被static修饰
publis static class InnerClassB{
}
}
}
特性:
- 被static所修饰的内部类称为静态内部类
- 静态内部类切断了与其外部类对象之间的联系。
静态导包
静态导包是 jdk1.5 提供的一种新的机制,使用方式为 import static packageName.className.*
或 import static packageName.className.methodName
,其中 *
表示导入类的所有静态方法。静态导包后,当调用类的静态方法时,不需要加上类名。
下面是一个静态导包的 demo
StaticDemo
类
package com.jas.test;
public class StaticDemo {
public static void sayHi() {
System.out.println("Hi");
}
public static void sayBye() {
System.out.println("Bye");
}
}
静态导包测试
package com.jas.test;
import static com.jas.test.StaticDemo.*;
public class StaticDemoDriven {
public static void main(String[] args) {
sayHi();
sayBye();
}
}
静态导包简化了 StaticClass.staticMethod()
调用静态方法的操作,当调用静态方法时,就像调用当前类的静态方法一样简洁。
参考资料:
[java]static关键字的四种用法
Java中的static关键字解析
在此谢谢查询和参考过的网站和博客的作者,感谢你们的知识!