咱们说Java是一种面向对象编程的语言,而对象是把数据及对数据的操做方法放在一块儿,做为一个相互依存的总体,对同类对象抽象出其共性,即是Java中的类,咱们能够用类描述世间万物,也能够说万物皆对象。可是这里有个特殊的东西——static,它不属于对象,那么为何呢?html
static 是Java的一个关键字,能够用来修饰成员变量、修饰成员方法、构造静态代码块、实现静态导包以及实现静态内部类,下面咱们来分别介绍。java
用 static 修饰成员变量能够说是该关键字最经常使用的一个功能,一般将用 static 修饰的成员变量称为类成员或者静态成员,那么静态成员和不用 static 修饰的非静态成员有什么区别呢?程序员
咱们先看看不用 static 修饰的成员变量在内存中的构造。编程
1 package com.ys.bean; 2 3 /** 4 * Create by YSOcean 5 */ 6 public class Person { 7 private String name; 8 private Integer age; 9 10 public Person(String name, Integer age) { 11 this.name = name; 12 this.age = age; 13 } 14 15 @Override 16 public String toString() { 17 return "Person{" + 18 "name='" + name + '\'' + 19 ", age=" + age + 20 '}'; 21 } 22 //get和set方法省略 23 }
首先,咱们建立一个实体类 Person,有两个属性 name 和 age,都是普通成员变量(没有用 static 关键字修饰),接着咱们经过其构造方法建立两个对象:数组
1 Person p1 = new Person("Tom",21); 2 Person p2 = new Person("Marry",20); 3 System.out.println(p1.toString());//Person{name='Tom', age=21} 4 System.out.println(p2.toString());//Person{name='Marry', age=20}
这两个对象在内存中的存储结构以下:jvm
由上图可知,咱们建立的两个对象 p1 和 p2 存储在堆中,可是其引用地址是存放在栈中的,并且这两个对象的两个变量互相独立,咱们修改任何一个对象的属性值,是不改变另一个对象的属性值的。ide
下面咱们将 Person 类中的 age 属性改成由 static 关键字修饰:函数
1 package com.ys.bean; 2 3 /** 4 * Create by YSOcean 5 */ 6 public class Person { 7 private String name; 8 private static Integer age; 9 10 public Person(String name, Integer age) { 11 this.name = name; 12 this.age = age; 13 } 14 15 @Override 16 public String toString() { 17 return "Person{" + 18 "name='" + name + '\'' + 19 ", age=" + age + 20 '}'; 21 } 22 //get和set方法省略 23 24 }
一样咱们仍是向上面同样,建立 p1 和 p2 两个对象,并打印这两个对象,看看和上面打印的有啥区别呢?工具
1 Person p1 = new Person("Tom",21); 2 Person p2 = new Person("Marry",20); 3 System.out.println(p1.toString());//Person{name='Tom', age=20} 4 System.out.println(p2.toString());//Person{name='Marry', age=20}
咱们发现第三行代码打印的 p1 对象 age 属性变为 20了,这是为何呢?oop
这是由于用在 jvm 的内存构造中,会在堆中开辟一块内存空间,专门用来存储用 static 修饰的成员变量,称为静态存储区,不管咱们建立多少个对象,用 static 修饰的成员变量有且只有一份存储在静态存储区中,因此该静态变量的值是以最后建立对象时设置该静态变量的值为准,也就是因为 p1 先设置 age = 21,后来建立了 p2 对象,p2将 age 改成了20,那么该静态存储区的 age 属性值也被修改为了20。
PS:在 JDK1.8 之前,静态存储区是存放在方法区的,而方法区不属于堆,在 JDK1.8 以后,才将方法区干掉了,方法区中的静态存储区改成到堆中存储。
总结:static 修饰的变量被全部对象所共享,在内存中只有一个副本。因为与对象无关,因此咱们能够直接经过 类名.静态变量 的方式来直接调用静态变量。对应的非静态变量是对象所拥有的,多少个对象就有多少个非静态变量,各个对象所拥有的副本不受影响。
用 static 关键字修饰成员方法也是同样的道理,咱们能够直接经过 类名.静态方法名() 的方式来调用,而不用建立对象。
1 public class Person { 2 private String name; 3 private static Integer age; 4 5 public static void printClassName(){ 6 System.out.println("com.ys.bean.Person"); 7 } 8 public Person(String name, Integer age) { 9 this.name = name; 10 this.age = age; 11 } 12 13 @Override 14 public String toString() { 15 return "Person{" + 16 "name='" + name + '\'' + 17 ", age=" + age + 18 '}'; 19 } 20 //get和set方法省略 21 22 }
调用静态方法:
1 Person.printClassName();//com.ys.bean.Person
用 static 修饰的代码块称为静态代码块,静态代码块能够置于类的任意一个地方(和成员变量成员方法同等地位,不可放入方法中),而且一个类能够有多个静态代码块,在类初次载入内存时加载静态代码块,而且按照声明静态代码块的顺序来加载,且仅加载一次,优先于各类代码块以及构造函数。
关于静态代码块、构造代码块、构造函数、普通代码块的区别能够参考个人这篇博客。
1 public class CodeBlock { 2 static{ 3 System.out.println("静态代码块"); 4 } 5 }
因为静态代码块只在类载入内存时加载一次的特性,咱们能够利用静态代码块来优化程序性能,好比某个比较大配置文件须要在建立对象时加载,这时候为了节省内存,咱们能够将该配置文件的加载时机放入到静态代码块中,那么咱们不管建立多少个对象时,该配置文件也只加载了一次。
用 static 来修饰成员变量,成员方法,以及静态代码块是最经常使用的三个功能,静态导包是 JDK1.5之后的新特性,用 import static 包名 来代替传统的 import 包名 方式。那么有什么用呢?
好比咱们建立一个数组,而后用 JDK 自带的 Arrays 工具类的 sort 方法来对数组进行排序:
1 package com.ys.test; 2 3 import java.util.Arrays; 4 /** 5 * Create by YSOcean 6 */ 7 public class StaticTest { 8 9 public static void main(String[] args) { 10 int[] arrays = {3,4,2,8,1,9}; 11 Arrays.sort(arrays); 12 } 13 }
咱们能够看到,调用 sort 方法时,须要进行 import java.util.Arrays 的导包操做,那么变为静态导包呢?
1 package com.ys.test; 2 3 import static java.util.Arrays.*; 4 /** 5 * Create by YSOcean 6 */ 7 public class StaticTest { 8 9 public static void main(String[] args) { 10 int[] arrays = {3,4,2,8,1,9}; 11 sort(arrays); 12 } 13 }
咱们能够看到第三行代码的 import java.util.Arrays 变为了 import static java.util.Arrays.*,意思是导入 Arrays 类中的全部静态方法,固然你也能够将 * 变为某个方法名,也就是只导入该方法,那么咱们在调用该方法时,就能够不带上类名,直接经过方法名来调用(第 11 行代码)。
静态导包只会减小程序员的代码编写量,对于性能是没有任何提高的(也不会下降性能,Java核心技术第10版卷1第148页4.7.1章节类的导入有介绍),反而会下降代码的可读性,因此实际如何使用须要权衡。
首先咱们要知道什么是内部类,定义在一个类的内部的类叫内部类,包含内部类的类叫外部类,内部类用 static 修饰即是咱们所说的静态内部类。
定义内部类的好处是外部类能够访问内部类的全部方法和属性,包括私有方法和私有属性。
访问普通内部类,咱们须要先建立外部类的对象,而后经过外部类名.new 建立内部类的实例。
1 package com.ys.bean; 2 3 /** 4 * Create by hadoop 5 */ 6 public class OutClass { 7 8 public class InnerClass{ 9 10 } 11 }
1 * OuterClass oc = new OuterClass(); 2 * OuterClass.InnerClass in = oc.new InnerClass();
访问静态内部类,咱们不须要建立外部类的对象,能够直接经过 外部类名.内部类名 来建立实例。
1 package com.ys.bean; 2 3 /** 4 * Create by hadoop 5 */ 6 public class OutClass { 7 8 public static class InnerClass{ 9 10 } 11 }
1 OuterClass.StaticInnerClass sic = new OuterClass.StaticInnerClass();
①、静态变量能存在于普通方法中吗?
能。很明显,普通方法必须经过对象来调用,静态变量均可以直接经过类名来调用了,更不用说经过对象来调用,因此是能够存在于普通方法中的。
②、静态方法能存在普通变量吗?
不能。由于静态方法能够直接经过类名来直接调用,不用建立对象,而普通变量是必须经过对象来调用的。那么将普通变量放在静态方法中,在直接经过类来调用静态方法时就会报错。因此不能。
③、静态代码块能放在方法体中吗?
不能。首先咱们要明确静态代码块是在类加载的时候自动运行的。
普通方法须要咱们建立对象,而后手工去调用方法,所静态代码块不能声明在普通方法中。
那么对于用 static 修饰的静态方法呢?一样也是不能的。由于静态方法一样也须要咱们手工经过类名来调用,而不是直接在类加载的时候就运行了。
也就是说静态代码块可以自动执行,而不论是普通方法仍是静态方法都是须要手工执行的。
④、静态导包会比普通导包消耗更多的性能?
不会。静态导包实际上在编译期间都会被编译器进行处理,将其转换成普通按需导包的形式,因此在程序运行期间是不影响性能的。
⑤、static 能够用来修饰局部变量吗?
不能。不论是在普通方法仍是在静态方法中,static 关键字都不能用来修饰局部变量,这是Java的规定。稍微想一想也能明白,局部变量的声明周期是随着方法的结束而结束的,由于static 修饰的变量是全局的,不与对象有关的,若是用 static 修饰局部变量容易形成理解上的冲突,因此Java规定 static 关键字不能用来修饰局部变量。