static关键字
1.详解
- static翻译为静态。
- 所有static修饰的都是类相关的,和类一起加载。
- 所有static修饰的都采用”类名.“的方式访问。
- static修饰的变量:静态变量
- static修饰的方法:静态方法
2.变量的分类
根据作用域的不同,分为:
- 定义在方法中的称为局部变量。
- 定义在方法外部的称为成员变量。
成员变量中,带static修饰的称为静态变量(类变量),不带static修饰的称为实例变量!
public class StaticTest01{
//成员变量中的实例变量
int i;
//实例方法
public void m1(){
int i;//局部变量
}
//成员变量中的静态变量
static int j;
//静态方法
public static void m2(){
int i;//局部变量
}
}
注意:
-
实例变量,实例方法都是对象相关的,采用”引用.“的方式访问,必须先new对象
-
实例相关的,必须先有对象才能访问,且通过引用去访问可能出现空指针异常
-
静态变量和静态方法都是类相关的,通过”类名.“的方式访问,不需要创建对象
-
类相关的不需要对象的参与即可访问,也没有空指针异常发生
那么,应该思考:什么时候变量声明成实例变量,什么时候声明成静态变量?
3.static变量和实例变量在内存中区别:
1.属性中未定义静态变量
public class StaticTest02{
public static void main(String[] args){
Chinese c1 = new Chinese("111","张三","中国");
System.out.println(c1.idCard);
System.out.println(c1.name);
System.out.println(c1.country);
Chinese c2 = new Chinese("222","李四","中国");
System.out.println(c2.idCard);
System.out.println(c2.name);
System.out.println(c2.country);
}
}
class Chinese{
String idCard;
String name;
String country;
public Chinese(){}
public Chinese(String s1,String s2,String s3){
this.idCard = s1;
this.name = s2;
this.country = s3;
}
}
上述代码执行内存图:
不难看出,对于中国人类中的国籍中国,是一个类级别的变量,不论对象多么因人而异,中国人的国籍都是中国。此时,将国籍定义为实例变量,会浪费内存空间。
2.属性中含有静态变量
public class StaticTest03{
public static void main(String[] args){
System.out.println(Chinese.country);
System.out.println("========================");
Chinese c1 = new Chinese("111","张三");
System.out.println(c1.idCard);
System.out.println(c1.name);
//System.out.println(c1.country);
System.out.println("========================");
Chinese c2 = new Chinese("222","李四");
System.out.println(c2.idCard);
System.out.println(c2.name);
//System.out.println(c2.country);
}
}
class Chinese{
String idCard;
String name;
static String country = "中国";
public Chinese(){}
public Chinese(String s1,String s2){
this.idCard = s1;
this.name = s2;
}
}
上述代码内存图:
可以发现,static修饰的变量和方法是和类一起加载初始化的
而实例变量是通过构造方法创建对象时初始化的
4.空指针异常
什么情况下会出现空指针异常?
通过“空引用”去访问实例相关的,都会出现空指针异常
public class StaticTest04{
public static void main(String[] args){
//静态变量通过”类名.“的方式来访问
System.out.println(Chinese.country);
System.out.println("========================");
Chinese c1 = new Chinese("111","张三");
System.out.println(c1.idCard);
System.out.println(c1.name);
//静态变量也可以通过”引用.“方式访问,
//但本质上还是编译System.out.println(Chinese.country);
System.out.println(c1.country);
c1 = null;
//即使指针指向空,下一行仍能访问静态变量,因为静态变量不需要对象的存在
System.out.println(c1.country);
//下一行会出现空指针异常
System.out.println(c1.idCard);
}
}
class Chinese{
String idCard;
String name;
static String country = "中国";
public Chinese(){}
public Chinese(String s1,String s2){
this.idCard = s1;
this.name = s2;
}
}
总之:静态变量是类相关的,不需要对象的存在。虽然也可以通过“引用.的”方式进行访问,但是本质上编译器会转化成“类名.”的方式去访问。所以不会出现空指针异常。
5.什么时候方法定义为实例方法?
从面向对象的思想上来说,实例方法是对象级别的方法,对象的行为因人而异,方法也应该定义为实例方法。
从代码的角度来看,一个方法的方法体中访问了实例变量的话,该方法必须定义为实例方法而不是静态方法,否则系统会报错。
一般工具类为了方便调用,都定义为static类型,静态方法的优点就是可以通过“类名.”的方式去引用
6.静态代码块
1.使用static关键字定义静态代码块:
public class A{
static{
//java 语句;
//java语句;
}
}
2.static静态代码块什么时候执行呢?
A:**类加载的时候执行,并且只执行一次。**注意,任何一个代码的执行都是从类加载开始的,所以静态代码块在main方法之前执行
B: 一个类中可以定义多个静态代码块,类加载时,静态代码块按照自上而下的顺序执行:
public class StaticTest05{
int i;
//静态代码块
static{
System.out.println("A");
}
//静态代码块
static{
System.out.println("B");
}
public static void main(String[] args){
System.out.println("main begin!");
}
//静态代码块
static{
System.out.println("C");
}
/*
//执行结果
A
B
C
main begin!
*/
}
通过上述代码看出,不论静态代码块在类中的什么位置,都自上向下依次执行。
3.静态代码块有什么作用?
对程序员而言,可以把静态代码块当作一个类加载的时机:
项目经理说了:大家注意了,所有我们编写的程序中,只要是类加载了,请记录一下
类加载的日志信息(在哪年哪月哪日几时几分几秒,哪个类加载到JVM当中了)。
思考:这些记录日志的代码写到哪里呢?
写到静态代码块当中。
4.静态代码块与静态变量的代码执行顺序
静态代码块与静态变量都是在类加载时执行或初始化的,那么就要思考两者之间的执行顺序问题:
由于都是在类加载时执行,所以二者的执行顺序取决于二者在代码中出现的先后顺序
public class StaticTest06{
static int i;
//这里静态变量在静态代码块之前,
//所以在方法区中先定义静态变量i,再执行静态代码块
static{
//这里能访问i吗?
System.out.println(i);
}
/*
static{
//这里能访问name吗?
//不能!
//错误: 非法前向引用
System.out.println(name);
}
*/
static String name;
public static void main(String[] args){
System.out.println("main begin");
System.out.println("main over");
}
}
//0
//main begin
//main over
5.静态代码块/实例代码块(匿名代码块 】)/构造方法的执行顺序问题
- 什么是实例代码块?
语法机制:
{
//java语句;
}
实例代码块并没有在类加载时执行
public class InstanceCode{
//实例代码块(又叫匿名代码块)
//实例代码块并没有在类加载时执行
{
System.out.println("实例代码块");
}
public static void main(String[] args){
System.out.println("main begin!");
}
/*
结果:
main begin!
*/
}
-
实例代码块和构造方法的执行顺序问题:
实例代码块在每次调用构造方法时都会自动先执行,这也是SUN公司提供的一种对象构建机制
public class InstanceCode{
//实例代码块(又叫匿名代码块)
//实例代码块并没有在类加载时执行
{
System.out.println("实例代码块执行了");
}
public InstanceCode(){
System.out.println("无参构造器执行了");
}
public InstanceCode(String name){
System.out.println("有参构造器执行了");
}
public static void main(String[] args){
System.out.println("main begin!");
new InstanceCode();
new InstanceCode("abc");
}
/*
结果:
main begin!
实例代码块执行了
无参构造器执行了
实例代码块执行了
有参构造器执行了
*/
}
-
三者的执行顺序问题:
-
静态方法在类加载的时执行,并且按照静态代码块自上而下的顺序逐个执行每个静态代码块,并且只执行一次。
-
实例代码块在调用构造方法时,自动在构造器执行前执行一次。
public class CodeOrder{
int i;
//静态代码块
static{
System.out.println("A");
}
//实例代码块
{
System.out.println("B");
}
public CodeOrder(){
System.out.println("C");
}
public static void main(String[] args){
System.out.println("D");
new CodeOrder();
}
static{
System.out.println("E");
}
static{
System.out.println("F");
}
}
/*
执行顺序: A>E>F>D>B>C
*/