【Java关键字】理解static关键字
前言
关于Java关键字,笔者打算有些结合具体Java基础一起写,如transient关键字就写在你真的有好好了解过序列化吗:Java序列化实现的原理里面了,有些就单独拿出来介绍,如static、final等。本文主要对static关键字进行介绍:
- static修饰成员变量
- static修饰成员方法
- static静态块
- static静态导包
- static静态内部类
(若文章有不正之处,或难以理解的地方,请多多谅解,欢迎指正)
1. static修饰成员变量
static最常用的功能就是修饰类的属性和方法,让他们成为类的成员属性和方法,通常我们称static修饰的成员成为类成员、静态成员或静态变量。
那么静态变量和非静态变量有什么区别呢?举个栗子:
public class Tangyuan {
private static String skin;
private String stuffing;
@Override
public String toString() {
return "Tangyuan{ skin='"+ skin + "',stuffing='" + stuffing + "'}";
}
public static void main(String[] args){
Tangyuan.skin = "糯米皮";
Tangyuan t1 = new Tangyuan();
t1.stuffing = "花生馅";
Tangyuan t2 = new Tangyuan();
t2.stuffing = "芝麻馅";
System.out.println(t1);
System.out.println(t2);
}
}
输出结果为:
Tangyuan{ skin='糯米皮',stuffing='花生馅'}
Tangyuan{ skin='糯米皮',stuffing='芝麻馅'}
我们可以看到,Tangyuan(汤圆)类构造出来的对象是独立的个体,每个对象中都有保存自己独立的非静态变量,相互不会影响。而static修饰的成员变量的改变,却会影响所有对象的对应值。为了静态变量会产生这种影响呢?
这由它们在内存中的存储结构决定的(JDK1.8):
由上图我们可以知道,我们创建的两个对象t1和t2存储在堆上,它们的引用地址是存放在栈中的,而且这两个对象是相互独立的,所以图中的两个汤圆对象,分别有着不同的馅料。但是汤圆类有一个静态成员变量skin,存储在汤圆对象都可以访问的地方。
在堆上会开辟一块内存空间,专门用于存储已加载的方法、static修饰的成员变量等,称为静态存储区。也就是说skin变量存储在汤圆类在静态存储区的内存中。虽然我们看到static可以让对象共享属性,但是实际上是不推荐这么用的,因为这样会让属性变得能以控制,因为它在任何地方都有可能被改变。
PS:本文是采用的是JDK1.8时运行时数据区域的结构。在JDK1.6时,静态存储区是存放在方法区中的,而且方法区不属于堆;但在JDK1.7时,方法区中的静态存储区搬到堆中了;且在JDK1.8之后,方法区被“元空间”所取代。
总结一下,static修饰的成员变量的生命周期与类的生命周期相同,不随对象的消亡而消亡,但是可以被所有对象共享,在内存中只有一个副本。我们可以通过**“类名.静态变量”、“对象名.静态变量”这两种方式来直接调用静态变量。而非静态变量是对象独有的属性,非静态变量随着对象的消亡而消亡**,各对象所拥有的副本不受影响。
2. static修饰成员方法
当然,如果用static关键字修饰成员方法,可以直接通过**“类名.静态方法名()”,“对象名.静态方法名()”**的方式来调用:
public class Tangyuan {
......
public static void printTangyuan(Tangyuan tangyuan){
System.out.println(tangyuan);
}
public static void main(String[] args){
......
t1.printTangyuan(t1);
Tangyuan.printTangyuan(t2);
}
}
运行结果为:
Tangyuan{ skin='糯米皮',stuffing='花生馅'}
Tangyuan{ skin='糯米皮',stuffing='芝麻馅'}
static修饰成员方法最大的用处在于,可以直接使用“类名.方法名”的方式操作方法,避免要先new出一个对象的资源消耗。
需要注意的是,因为静态方法是直接可以通过类名来调用的,所以在静态方法的世界里,是不存在对象的,即在静态方法中不能有this与super关键字。而且因为这个特性,在静态方法中不能访问类的非静态成员变量和方法,因为非静态成员变量/方法都必须依赖具体的对象才有意义,才能被调用。不然即使存在实例化的对象,也不清楚调用的是哪一个对象的非静态成员变量/方法。
总结一下,静态方法只能访问所属类的静态成员变量/方法,而非静态方法可以访问静态成员变量/方法。
3. static修饰静态块
static修饰的静态代码块可以用于优化程序性能,而且可以置于类中的任何地方,一个类中可以有多个静态代码块。在类的初次加载时,会按照静态代码块的顺序来执行每一个静态代码块,而且只会执行一次。
为什么说静态代码块可以优化程序性能呢?正是因为其特性:只能在类加载的时候执行一次。比如某个比较大的配置文件需要在创建对象的时候加载,这时候为了节省内存,我们可以将该配置文件的加载放到静态代码块中,这样,无论我们创建多少个对象,该配置文件也就加载了一次。
举个栗子:
public class Person {
private Date date;
public Person(Date date) {
this.date = date;
}
boolean isBornIn90s() {
Date startDate = Date.valueOf("1990");
Date endDate = Date.valueOf("1999");
return date.compareTo(startDate) >= 0 && date.compareTo(endDate) < 0;
}
}
可以看到,如果我想看看这汤圆是不是芝麻馅时,每次都需要创建一个“芝麻馅”的字符串对象,这样就比较浪费资源,如果这样子改效率会比较高:
public class Person {
private Date date;
private static Date startDate, endDate;
static {
startDate = Date.valueOf("1990");
endDate = Date.valueOf("1999");
}
public Person(Date date) {
this.date = date;
}
boolean isBornIn90s() {
return date.compareTo(startDate) >= 0 && date.compareTo(endDate) < 0;
}
}
而关于静态代码块、构造代码块、构造函数、普通代码块的执行顺序可以参考笔者这篇博客:Java的成员变量、局部变量(深入版)。
4. static静态导包
静态导包是JDK1.5以后的特性,用*import static com…ClassName.代替传统的import com…ClassName方式,意思是导入这个类里的静态方法,好处就是可以简化一些操作,相对于上述的三个功能来说,比较冷门。举个栗子:
静态导包之前:
public class Tangyuan {
public static void main(String[] args){
System.out.println("我想吃花生馅的汤圆");
}
}
静态导包之后:
import static java.lang.System.out;
public class Tangyuan {
public static void main(String[] args){
out.println("我想吃花生馅的汤圆");
}
}
不同于非static静态导包,采用static静态导包之后,无需使用“类名.方法名”的方式来调用类方法了,可以直接使用“方法名”去调用类方法,就好像是该类自己的方法一样使用即可。但是,静态导包只是减少了程序员的代码量,对于性能是没有任何提升的,反而会降低代码的可读性,所以需要权衡使用。
5. 静态内部类
内部类就是定义在类的内部的类,而相对的外部类就是包含内部类的类,用static修饰的内部类便是将要介绍的静态内部类。定义内部类的好处在于,外部类可以访问内部类的所有方法和属性,包括私有方法和私有属性。
如果我们要访问普通内部类,我们需要先创建外部类的对象,然后通过外部类名.new的方式创建内部类的实例。
class OutClass {
public class InnerClass{
private String name = "InnerName";
}
public void getInnerName(){
InnerClass innerClass = new OutClass().new InnerClass();
System.out.println(innerClass.name);
}
}
public class Test{
public static void main(String[] args){
OutClass oc = new OutClass();
oc.getInnerName();
}
}
访问静态内部类,我们不需要创建外部类的对象,而是直接通过外部类名.内部类名来创建实例。
class OutClass {
public static class InnerClass{
private String name = "InnerName";
}
public void getInnerName(){
InnerClass innerClass = new InnerClass();
System.out.println(innerClass.name);
}
}
public class Test{
public static void main(String[] args){
OutClass oc = new OutClass();
oc.getInnerName();
}
}
结语
写这篇文章的时候,进度很慢。因为杰伦的歌虽然很好听*10,但有时候不知道他唱什么,只能边看歌词边写…
如果觉得文章不错,请点一个赞吧,这会是我最大的动力~
参考资料: