11_Static关键字

静态修饰符static:

可以修饰成员变量和成员方法
可以通过对象和类来调用(调用的都是同一个)
修饰成员变量表示静态变量,静态变量是所有对象公用的,在内存中只占有一份内存,即对静态的变量进行的赋值无论赋值几次,新的值都会覆盖旧的值。
修饰成员方法表示静态方法,静态方法里面只能访问静态变量。

Static方法:

“static方法就是没有this的方法。静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,仅仅通过类本身来调用static方法,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是须依赖具体的对象才能够被调用。可以在没有创建任何对象的前提下这实际上正是static方法的主要用途。”
简而言之,一句话来描述就是:
被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问,方便在没有创建对象的情况下来进行调用(方法/变量)。
因此,如果说想在不创建对象的情况下调用某个方法,就可以将这个方法设置为static。我们最常见的static方法就是main方法,至于为什么main方法必须是static的,现在就很清楚了。因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。

Static变量:

静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。static成员变量的初始化顺序按照定义的顺序进行初始化。
静态修饰符static:
可以修饰成员变量和成员方法
可以通过对象和类来调用(调用的都是同一个)
修饰成员变量表示静态变量,静态变量是所有对象公用的,在内存中只占有一份内存,即对静态的变量进行的赋值无论赋值几次,新的值都会覆盖旧的值。
修饰成员方法表示静态方法,静态方法里面只能访问静态变量。
Static方法:
“static方法就是没有this的方法。静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,仅仅通过类本身来调用static方法,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是须依赖具体的对象才能够被调用。可以在没有创建任何对象的前提下这实际上正是static方法的主要用途。”
  这段话虽然只是说明了static方法的特殊之处,但是可以看出static关键字的基本作用,简而言之,一句话来描述就是:
被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问,方便在没有创建对象的情况下来进行调用(方法/变量)。
因此,如果说想在不创建对象的情况下调用某个方法,就可以将这个方法设置为static。我们最常见的static方法就是main方法,至于为什么main方法必须是static的,现在就很清楚了。因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。

Java static静态方法

方法在什么情况下会声明为静态的呢?方法实际上描述的是行为动作,我认为当某个动作在触发的时候需要对象的参与,这个方法应该定义为实例方法,例如:每个高中生都有考试的行为,但是你考试和学霸考试最终的结果是不一样的,一个上了“家里蹲大学”,一个上了“清华大学”,显然这个动作也是需要对象参与才能完成的,所以考试这个方法应该定义为实例方法。

以上描述是从设计思想角度出发来进行选择,其实也可以从代码的角度来进行判断,当方法体中需要直接访问当前对象的实例变量或者实例方法的时候,该方法必须定义为实例方法,因为只有实例方法中才有this,静态方法中不存在this。请看代码:

public class Customer {
	String name;
	public Customer(String name){
		this.name = name;
	}
	public void shopping(){
		//直接访问当前对象的name
		System.out.println(name + "正在选购商品!");
		//继续让当前对象去支付
		pay();
	}
	public void pay(){
		System.out.println(name + "正在支付!");
	}
}
 
 
public class CustomerTest {
	public static void main(String[] args) {
		Customer jack = new Customer("jack");
		jack.shopping();
		Customer rose = new Customer("rose");
		rose.shopping();
	}
}
 

运行结果如下图所示:

在这里插入图片描述

图11-26:运行结果

在以上的代码中,不同的客户购物,最终的效果都不同,另外在shopping()方法中访问了当前对象的实例变量name,以及调用了实例方法pay(),所以shopping()方法不能定义为静态方法,必须声明为实例方法。

另外,在实际的开发中,“工具类”当中的方法一般定义为静态方法,因为工具类就是为了方便大家的使用,将方法定义为静态方法,比较方便调用,不需要创建对象,直接使用类名就可以访问。请看以下工具类,为了简化“System.out.println();”代码而编写的工具类:

public class U {
	public static void p(int data){
		System.out.println(data);
	}
	public static void p(long data){
		System.out.println(data);
	}
	public static void p(float data){
		System.out.println(data);
	}
	public static void p(double data){
		System.out.println(data);
	}
	public static void p(boolean data){
		System.out.println(data);
	}
	public static void p(char data){
		System.out.println(data);
	}
	public static void p(String data){
		System.out.println(data);
	}
}
 

运行结果如下图所示:
在这里插入图片描述

图11-27:测试工具类

Java中static静态变量

Static变量:

静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。static成员变量的初始化顺序按照定义的顺序进行初始化。

java中的变量包括:局部变量和成员变量,在方法体中声明的变量为局部变量,有效范围很小,只能在方法体中访问,方法结束之后局部变量内存就释放了,在内存方面局部变量存储在栈当中。在类体中定义的变量为成员变量,而成员变量又包括实例变量和静态变量,当成员变量声明时使用了static关键字,那么这种变量称为静态变量,没有使用static关键字称为实例变量,实例变量是对象级别的,每个对象的实例变量值可能不同,所以实例变量必须先创建对象,通过“引用”去访问,而静态变量访问时不需要创建对象,直接通过“类名”访问。实例变量存储在堆内存当中,静态变量存储在方法区当中。实例变量在构造方法执行过程中初始化,静态变量在类加载时初始化。那么变量在什么情况下会声明为静态变量呢?请看以下代码,定义一个“男人”类:

public class Man {
	//身份证号
	int idCard;
	//性别(所有男人的性别都是“男”)
	//true表示男,false表示女
	boolean sex = true;
	public Man(int idCard){
		this.idCard = idCard;
	}
}
 
public class ManTest {
	public static void main(String[] args) {
		Man jack = new Man(100);
		System.out.println(jack.idCard + "," + (jack.sex ? "男" : "女"));
		Man sun = new Man(101);
		System.out.println(sun.idCard + "," + (sun.sex ? "男" : "女"));
		Man cok = new Man(102);
		System.out.println(cok.idCard + "," + (cok.sex ? "男" : "女"));
	}
}

运行结果如下图所示:

在这里插入图片描述

图11-16:运行结果

我们来看一下以上程序的内存结构图:

在这里插入图片描述

图11-17:内存结构图

“男人类”创建的所有“男人对象”,每一个“男人对象”的身份证号都不一样,该属性应该每个对象持有一份,所以应该定义为实例变量,而每一个“男人对象”的性别都是“男”,不会随着对象的改变而变化,性别值永远都是“男”,这种情况下,性别这个变量还需要定义为实例变量吗,有必要让每一个“男人对象”持有一份吗,这样岂不是浪费了大量的堆内存空间,所以这个时候建议将“性别=男”属性定义为类级别的属性,声明为静态变量,上升为“整个族”的数据,这样的变量不需要创建对象直接使用“类名”即可访问。请看代码:

 
public class Man {
	//身份证号
	int idCard;
	//性别(所有男人的性别都是“男”)
	//true表示男,false表示女
	static boolean sex = true;
	public Man(int idCard){
		this.idCard = idCard;
	}
}
 
public class ManTest {
	public static void main(String[] args) {
		Man jack = new Man(100);
		System.out.println(jack.idCard + "," + (Man.sex ? "男" : "女"));
		Man sun = new Man(101);
		System.out.println(sun.idCard + "," + (Man.sex ? "男" : "女"));
		Man cok = new Man(102);
		System.out.println(cok.idCard + "," + (Man.sex ? "男" : "女"));
	}
}
 

运行结果如下图所示:

在这里插入图片描述

图11-18:运行结果

我们来看一下以上程序的内存结构图:

在这里插入图片描述

图11-19:静态变量内存图

通过以上内容的学习我们得知,当一个类的所有对象的某个“属性值”不会随着对象的改变而变化的时候,建议将该属性定义为静态属性(或者说把这个变量定义为静态变量),静态变量在类加载的时候初始化,存储在方法区当中,不需要创建对象,直接通过“类名”来访问。如果静态变量使用“引用”来访问,可以吗,如果可以的话,这个访问和具体的对象有关系吗?来看以下代码:

 
public class ManTest {
	public static void main(String[] args) {
		//静态变量比较正式的访问方式
		System.out.println("性别 = " + Man.sex);
		//创建对象
		Man jack = new Man(100);
		//使用“引用”来访问静态变量可以吗?
		System.out.println("性别 = " + jack.sex);
		//对象被垃圾回收器回收了
		jack = null;
		//使用“引用”还可以访问吗?
		System.out.println("性别 = " + jack.sex);
	}
}

运行结果如下图所示:

在这里插入图片描述

图11-20:静态变量使用“引用”访问

通过以上代码以及运行结果可以看出,静态变量也可以使用“引用”去访问,但实际上在执行过程中,“引用”所指向的对象并没有参与,如果是空引用访问实例变量,程序一定会发生空指针异常,但是以上的程序编译通过了,并且运行的时候也没有出现任何异常,这说明虽然表面看起来是采用“引用”去访问,但实际上在运行的时候还是直接通过“类”去访问的。静态方法是这样吗?请看以下代码:

 
public class Man {
	//身份证号
	int idCard;
	//性别(所有男人的性别都是“男”)
	//true表示男,false表示女
	static boolean sex = true;
	public Man(int idCard){
		this.idCard = idCard;
	}
	//静态方法
	public static void printInfo(){
		System.out.println("-----" + (Man.sex ? "男" : "女") + "------");
	}
}
 
public class ManTest {
	public static void main(String[] args) {
		//静态变量比较正式的访问方式
		System.out.println("性别 = " + Man.sex);
		//创建对象
		Man jack = new Man(100);
		//使用“引用”来访问静态变量可以吗?
		System.out.println("性别 = " + jack.sex);
		//对象被垃圾回收器回收了
		jack = null;
		//使用“引用”还可以访问吗?
		System.out.println("性别 = " + jack.sex);
		//静态方法比较正式的访问方式
		Man.printInfo();
		//访问静态方法可以使用引用吗?并且空的引用可以吗?
		jack.printInfo();
	}
}
 

运行结果如下图所示:

在这里插入图片描述

图11-21:静态方法可以使用引用访问吗

通过以上代码测试得知,静态变量和静态方法比较正式的方式是直接采用“类名”访问,但实际上使用“引用”也可以访问,并且空引用访问静态变量和静态方法并不会出现空指针异常。实际上,在开发中并不建议使用“引用”去访问静态相关的成员,因为这样会让程序员困惑,因为采用“引用”方式访问的时候,程序员会认为你访问的是实例相关的成员。

总之,所有实例相关的,包括实例变量和实例方法,必须先创建对象,然后通过“引用”的方式去访问,如果空引用访问实例相关的成员,必然会出现空指针异常。所有静态相关的,包括静态变量和静态方法,直接使用“类名”去访问。虽然静态相关的成员也能使用“引用”去访问,但这种方式并不被主张。

Static代码块:

可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
Java static静态代码块

静态代码块的语法格式是这样的:

{
//静态代码块
static{
java语句;
}
}

静态代码块在类加载时执行,并且只执行一次。开发中使用不多,但离了它有的时候还真是没法写代码。静态代码块实际上是java语言为程序员准备的一个特殊的时刻,这个时刻就是类加载时刻,如果你想在类加载的时候执行一段代码,那么这段代码就有的放矢了。例如我们要在类加载的时候解析某个文件,并且要求该文件只解析一次,那么此时就可以把解析该文件的代码写到静态代码块当中了。我们来测试一下静态代码块:

public class StaticTest01 {
	//静态代码块
	static{
		System.out.println(2);
	}
	//静态代码块
	static{
		System.out.println(1);
	}
	//main方法
	public static void main(String[] args) {
		System.out.println("main execute!");
	}
	//静态代码块
	static{
		System.out.println(0);
	}
}

运行结果如下图所示:

在这里插入图片描述

图11-22:静态代码块运行结果

通过以上的测试可以得知一个类当中可以编写多个静态代码块(尽管大部分情况下只编写一个),并且静态代码块遵循自上而下的顺序依次执行,所以有的时候放在类体当中的代码是有执行顺序的(大部分情况下类体当中的代码没有顺序要求,方法体当中的代码是有顺序要求的,方法体当中的代码必须遵守自上而下的顺序依次逐行执行),另外静态代码块当中的代码在main方法执行之前执行,这是因为静态代码块在类加载时执行,并且只执行一次。再来看一下以下代码:

public class StaticTest02 {
	int i = 100;
	static{
		System.out.println(i);
	}
}
 

编译结果如下图所示:
在这里插入图片描述

图11-23:静态代码块中访问实例变量编译报错

为什么编译报错呢?那是因为i变量是实例变量,实例变量必须先创建对象才能访问,静态代码块在类加载时执行,这个时候对象还没有创建呢,所以i变量在这里是不能这样访问的。可以考虑在i变量前添加static,这样i变量就变成静态变量了,静态变量访问时不需要创建对象,直接通过“类”即可访问,例如以下代码:

public class StaticTest02 {
	static int i = 100;
	static{
		System.out.println("静态变量i = " + i);
	}
	public static void main(String[] args) {
	}
}

运行结果如下图所示:

在这里插入图片描述

图11-24:静态代码块中访问静态变量

代码修改为这样呢?

public class StaticTest02 {
	static{
		System.out.println("静态变量i = " + i);
	}
	static int i = 100;
}

编译报错了,请看下图:
在这里插入图片描述

图11-25:编译报错信息

通过测试,可以看到有的时候类体当中的代码也是有顺序要求的(类体当中定义两个独立的方法,这两个方法是没有先后顺序要求的),静态代码块在类加载时执行,静态变量在类加载时初始化,它们在同一时间发生,所以必然会有顺序要求,如果在静态代码块中要访问i变量,那么i变量必须放到静态代码块之前。

Static修饰的特点总结:

属性:
static属性:表示整个类型共享一份的属性 不是每个对象都有一份的属性
特点:应该拿着类名去调用
普通属性:表示每个对象都有一份的属性 需要拿着对象去调用
方法:
static方法 需要拿着类名去调用
普通方法:需要拿着对象去调用
代码块:

普通代码块:给普通属性赋值 普通属性每个对象都有一份的属性
当创建对象的时候执行 创建几个对象 执行几次
static代码块:给静态属性赋值 静态属性整个类型共享一份的属性
可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

*随着类的加载而加载
*优先于对象存在
*所有对象共享(可以通过对象或者类来访问)

面试题:

java中是变量大,还是属性大?
变量
static为什么不能修饰局部变量?

static修饰的变量要求类一加载就要在内存里面找到他,但是局部变量只有方法调用并且代码执行到那一行的时候才能创建,类的加载永远在前面方法调用永远在后面,所以static不能修饰局部变量。

普通方法和静态方法谁调用简单?
静态方法

既然静态方法调用简单,为什么开发的时候没有把一个类的所有方法都写成静态的呢?
静态方法里面只能直接的调用静态的成员,如果想要在静态的方法里面访问非静态的成员,需要创建对象,拿着对象去调用

static误区:

1.static关键字会改变类中成员的访问权限吗?

有些初学的朋友会将java中的static与C/C++中的static关键字的功能混淆了。在这里只需要记住一点:与C/C++中的static不同,Java中的static关键字不会影响到变量或者方法的作用域。在Java中能够影响到访问权限的只有private、public、protected(包括包访问权限)这几个关键字。

2.能通过this访问静态成员变量吗?

虽然对于静态方法来说没有this,那么在非静态方法中能够通过this访问静态成员变量吗?先看下面的一个例子,这段代码输出的结果是什么?

public  class  Main {   
     static  int  value =  33 ; 
  
     public  static  void  main(String[] args)  throws  Exception{ 
         new  Main().printValue(); 
     } 
  
     private  void  printValue(){ 
         int  value =  3 ; 
         System.out.println( this .value); 
     } 
} 

结果:33

这里面主要考察对this和static的理解。this代表什么?this代表当前对象,那么通过new Main()来调用printValue的话,当前对象就是通过new Main()生成的对象。而static变量是被对象所享有的,因此在printValue中的this.value的值毫无疑问是33。在printValue方法内部的value是局部变量,根本不可能与this关联,所以输出结果是33。在这里永远要记住一点:静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。

3.static能作用于局部变量么?

在C/C++中static是可以作用域局部变量的,但是在Java中切记:static是不允许用来修饰局部变量。不要问为什么,这是Java语法的规定。

常见题目:

1.下面这段代码的输出结果是什么?

public  class  Test  extends  Base{ 
  
     static { 
         System.out.println( "test static" ); 
     } 
      
     public  Test(){ 
         System.out.println( "test constructor" ); 
     } 
      
     public  static  void  main(String[] args) { 
         new  Test(); 
     } 
} 
  
class  Base{ 
      
     static { 
         System.out.println( "base static" ); 
     } 
      
     public  Base(){ 
         System.out.println( "base constructor" ); 
     } 
} 

结果
base static
test static
base constructor
test constructor
至于为什么是这个结果,我们先不讨论,先来想一下这段代码具体的执行过程,在执行开始,先要寻找到main方法,因为main方法是程序的入口,但是在执行main方法之前,必须先加载Test类,而在加载Test类的时候发现Test类继承自Base类,因此会转去先加载Base类,在加载Base类的时候,发现有static块,便执行了static块。在Base类加载完成之后,便继续加载Test类,然后发现Test类中也有static块,便执行static块。在加载完所需的类之后,便开始执行main方法。在main方法中执行new Test()的时候会先调用父类的构造器(构造方法),然后再调用自身的构造器。因此,便出现了上面的输出结果。

2.这段代码的输出结果是什么?

public  class  Test { 
     Person person =  new  Person( "Test" ); 
     static { 
         System.out.println( "test static" ); 
     } 
      
     public  Test() { 
         System.out.println( "test constructor" ); 
     } 
      
     public  static  void  main(String[] args) { 
         new  MyClass(); 
     } 
} 
  
class  Person{ 
     static { 
         System.out.println( "person static" ); 
     } 
     public  Person(String str) { 
         System.out.println( "person " +str); 
     } 
} 
  
  
class  MyClass  extends  Test { 
     Person person =  new  Person( "MyClass" ); 
     static { 
         System.out.println( "myclass static" ); 
     } 
      
     public  MyClass() { 
         System.out.println( "myclass constructor" ); 
     } 
} 

结果
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
首先加载Test类,因此会执行Test类中的static块。接着执行new MyClass(),而MyClass类还没有被加载,因此需要加载MyClass类。在加载MyClass类的时候,发现MyClass类继承自Test类,但是由于Test类已经被加载了,所以只需要加载MyClass类,那么就会执行MyClass类的中的static块。在加载完之后,就通过构造器来生成对象。而在生成对象的时候,必须先初始化父类的成员变量,因此会执行Test中的Person person = new Person(),而Person类还没有被加载过,因此会先加载Person类并执行Person类中的static块,接着执行父类的构造器,完成了父类的初始化,然后就来初始化自身了,因此会接着执行MyClass中的Person person = new Person(),最后执行MyClass的构造器。

3.这段代码的输出结果是什么?

public  class  Test { 
      
     static { 
         System.out.println( "test static 1" ); 
     } 
     public  static  void  main(String[] args) { 
          
     } 
      
     static { 
         System.out.println( "test static 2" ); 
     } 
} 

结果
test static 1
test static 2

执行过程:
1.加载类:通过jvm调用ClassLoader类中的loaderClass方法加载我们要执行的类
2.执行static修饰的内容(从上往下执行)
3.调用构造器
4.成员变量初始化
5.执行构造方法,对象创建完毕

总结:
1.static修饰的属性和方法,使用类名.xx的形式访问。
2.使用的位置:那些不需要通过创建对象就可以访问方法的情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

朱茂强

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值