static关键字的四种用法
1、修饰成员变量
我们通常将static修饰的成员变量称为类变量或者静态变量,这是相对于对象的属性来说的。请看下面的例子:
1 public class Person { 2 String name; 3 int age; 4 5 public String toString() { 6 return "Name:" + name + ", Age:" + age; 7 } 8 9 public static void main(String[] args) { 10 Person p1 = new Person(); 11 p1.name = "zhangsan"; 12 p1.age = 10; 13 Person p2 = new Person(); 14 p2.name = "lisi"; 15 p2.age = 12; 16 System.out.println(p1); 17 System.out.println(p2); 18 } 19 /**Output 20 * Name:zhangsan, Age:10 21 * Name:lisi, Age:12 22 *///~ 23 }
Person类构造出的每个对象都是独立存在的,如p1和p2都有自己的成员变量,互不影响,它们在内存的示意图如下图1.1:
图1.1
p1和p2两个引用变量指向的对象分别存储在堆区域的不同地址中,所以它们互不影响。对象的成员变量都在这了,由每个对象自己保存,但是对象的方法却不在堆区域中。其实,无论创建多少对象,它们都共用同一个方法,如下图1.2所示:
图1.2
从图1.2中可以看到,两个对象指向同一个方法定义,且该方法定义在一块不变的区域(有JVM划分),暂时称为静态存储区。这块区域不仅存放了方法的定义,各种类的定义也存放在该区域,当用new创建对象时,会根据该区域定义的类去创建对象。
那么,用static关键字修饰成员变量,让它们变成类所属,会变成不同的样子,请看下面的例子:
public class Person { String name; static int age; /* 其余代码不变... */ /**Output * Name:zhangsan, Age:12 * Name:lisi, Age:12 *///~ }
图1.3
我们可以看到,static修饰的age变量同方法一样由类统一管理,都存放的静态存储区,该变量让对象共享,但实际中很少这么用。
public class Person { private static int count = 0; int id; String name; int age; public Person() { id = ++count; } public String toString() { return "Id:" + id + ", Name:" + name + ", Age:" + age; } public static void main(String[] args) { Person p1 = new Person(); p1.name = "zhangsan"; p1.age = 10; Person p2 = new Person(); p2.name = "lisi"; p2.age = 12; System.out.println(p1); System.out.println(p2); } /**Output * Id:1, Name:zhangsan, Age:10 * Id:2, Name:lisi, Age:12 *///~ }
上面的例子可以用count记录一共创建了多少个对象,且count是由private修饰的,在类外边无法随意改变。
2、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"); } }
上面的例子可以看出,print()方法相当于一个全局函数,只要导入该类的包,就可以使用“类名.方法名”方式调用它。但是在一个static修饰的方法中,不能使用非静态的方法和成员变量,因为static修饰的方法是属于类的,它不能直接访问对象的成员变量,它不知道使用哪个对象的属性。
3、静态块
在说明static的第三种用法时,先介绍一下对象的初始化过程。以下面的代码为例:
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初始化 *///~ }
上面的例子可以看出,new对象时,会先初始化static修饰成员变量book2和book4,接着再初始化普通的成员变量book1和book2,最后调用方法的构造器完成初始化。并且如果有多个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修饰的成员变量初始化,普通的成员变量不会初始化。还可以看到,紧接着new对象的时候,static修饰的成员方法不会再次被初始化,只需要初始化一次。
接着看static的第三种用法,以下面的代码为例:
class Book{ public Book(String msg) { System.out.println(msg); } } public class Person { Book book1 = new Book("book1成员变量初始化"); static Book book2; static { book2 = new Book("static成员book2成员变量初始化"); book4 = new Book("static成员book4成员变量初始化"); } public Person(String msg) { System.out.println(msg); } Book book3 = new Book("book3成员变量初始化"); 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成员book2成员变量初始化 * static成员book4成员变量初始化 * static修饰的funStatic方法 * *************** * book1成员变量初始化 * book3成员变量初始化 * p1初始化 *///~ }
当我们初始static修饰的成员变量时,可以将它们统一放在一个以static开始,花括弧裹起来的块状语句中。
4、静态导包
相比上边三种用法,这种用法很少见,以下面的代码为例:
/* PrintHelper.java文件 */ package com.dotgua.study; public class PrintHelper { public static void print(Object o){ System.out.println(o); } }
/* App.java文件 */ import static com.dotgua.study.PrintHelper.*; public class App { public static void main( String[] args ) { print("Hello World!"); } /**Output * Hello World! *///~ }
上面代码来自两个Java文件,PrintHelper类包含一个static修饰的静态方法。在App.java中,导入PrintHelper类使用了static关键字,并且在引入类的最后加了“.*”。这样,再不与当前类的方法名冲突的情况下,就可以直接采用“方法名”的方式调用方法,就像该类在使用自己的方法一样。
总结
1.用来修饰成员变量,将成员变量由类来统一管理,所有实现该类的对象共享该成员变量;
2.修饰成员方法,可以使用“类名.方法名”调用方法,常用于工具类;
3.静态块用法,将多个static修饰的成员变量放在一起初始化,使程序更加规整,理解对象的初始化过程非常关键;
4.静态导包法,将类的方法直接导入到当前类中,用“方法名”直接调用类方法,更加方便。
5、修饰内部类
内部类可以当普通类 来使用,实例化static内部类的时候不需要依赖外部类。