功能修饰符:final和static

一、final(不可变的、最终的)

  • final修饰类------不能被继承的类(final修饰的类:String、8个基本数据的包装类);
  • final修饰方法------不能被重写的方法;
  • final修饰变量------不可改变的变量,即变量第一次被赋值后不可再被赋值
  • 成员变量------可以用final修饰,但是定义时必须赋值;
  • 局部变量------可以用final修饰,定义时可以不赋值,但是在使用前必须赋值;
  • 参数变量-------可以用final修饰,方法调用传递参数时,形参被第一次赋值;
  • 引用类型变量--------可以用final修饰,不可被再次赋值,但该引用变量可以改变变量内的成员。

二、static(静态的)

主要修饰目标:成员变量、成员方法、代码块。

2.1 静态变量的调用

格式类名.静态变量

  • 对象调用静态成员和类调用静态成员作用相同,即对象可以调用静态成员,但是不推荐用对象来调用静态成员,因为类在未实例化对象时即可以使用静态成员,这也是对象调用静态成员时会出现警告的原因。
public class StaticPractice {
	
	static int a = 100;

	public static void main(String[] args) {
		//静态变量---标准调用格式
		StaticPractice.a = 101;
		//静态方法中可以省略类名,直接使用当前类中的静态变量。
		System.out.println(a);
	}

}

静态变量—标准调用格式:类名、变量名;
静态方法中可以直接使用静态变量。原因是静态方法是类来调用,而静态成员也是类来调用并且为调用当前静态方法的类。

2.2 静态变量与实例变量的区别

  • 同个类的不同对象的实例变量互不影响
  • 同个类的不同对象的静态变量共用一个

1、实例变量调用:

public class StaticPractice {
	
	static int a = 100;
	int b = 10;

	public static void main(String[] args) {
		
		StaticPractice s1 = new StaticPractice();
		StaticPractice s2 = new StaticPractice();
		
		System.out.println("s1.b:"+s1.b);
		System.out.println("s2.b:"+s2.b);
		s1.b = 11;
		s2.b = 12;
		System.out.println("s1.b:"+s1.b);
		System.out.println("s2.b:"+s2.b);
	}

}

结果:

s1.b:10
s2.b:10
s1.b:11
s2.b:12

同个类的不同对象的实例变量互不影响

2、静态变量的调用:

public class StaticPractice {
	
	static int a = 100;
	int b = 10;

	public static void main(String[] args) {
		
		StaticPractice s1 = new StaticPractice();
		StaticPractice s2 = new StaticPractice();
		
		System.out.println("--------------");
		System.out.println("s1.a:"+s1.a);
		System.out.println("s2.a:"+s1.a);
		System.out.println("StaticPractice.a:"+StaticPractice.a);
		s1.a = 101;
		System.out.println("+++++++++++++");
		System.out.println("s1.a:"+s1.a);
		System.out.println("s2.a:"+s1.a);
		System.out.println("StaticPractice.a:"+StaticPractice.a);
		s2.a = 102;
		System.out.println("@@@@@@@@@@@@@@");
		System.out.println("s1.a:"+s1.a);
		System.out.println("s2.a:"+s1.a);
		System.out.println("StaticPractice.a:"+StaticPractice.a);
	}

}

结果:

--------------
s1.a:100
s2.a:100
StaticPractice.a:100
+++++++++++++
s1.a:101
s2.a:101
StaticPractice.a:101
@@@@@@@@@@@@@@
s1.a:102
s2.a:102
StaticPractice.a:102

同个类的不同对象的静态变量共用一个

2.3 方法中调用变量的规则

  • 实例方法中可以直接调用当前类的实例成员,若两者调用的对象相同则可以省略
  • 静态方法中可以直接调用当前类的静态成员,即两者调用的类相同则可以省略
  • 实例方法中可以直接调用当前类的静态成员,即调用实例方法的对象来调用静态成员
  • 静态方法中不可以直接调用当前类的实例成员,只能使用标准调用格式

详解:

1、实例方法中可以调用静态变量吗?

答:可以。因为对象调用静态变量和类调用静态变量是一个意思。

public class StaticPractice {
	
	static int a = 100;
	
	@Test
	public void test1() {
		System.out.println(a);
		System.out.println(this.a);//不推荐
		System.out.println(StaticPractice.a);
	}
}

结果:

100
100
100

2.1 :静态方法中可以调用实例变量吗?

答:不可以。原因是因为静态方法是当前类来调用,而静态方法中的实例变量不能用类来直接调用。
在这里插入图片描述
2.2 换一种思想,能不能在静态方法中用this调用到实例变量?
答:不可以。静态方法中不允许使用this关键字,因为类在未实例化对象时即可调用静态成员

在这里插入图片描述

3 静态方法可以被null的类或对象调用吗?

答:可以

class E{
	private static void testMethod() {
		System.out.println("testMethod");
	}
	
	public static void main(String[] args) {
		((E)null).testMethod();
		
		E e1 = (E)null;
		e1.testMethod();
		
		E e2 = null;
		e2.testMethod();
	}
}

输出:

testMethod
testMethod
testMethod

:空指针异常发生在对象为null时,而静态方法是类来调用的,没有对象也可以调用。

2.4 代码块

2.4.1 代码块介绍

  • 概念:类文件中的大括号区域。
  • 格式
public class CodeBlock {
	{
		//代码块
	}
}
  • 别称:代码片段、代码域、匿名方法。
  • 分类:实例代码块 和 静态代码块。

2.4.2 实例代码块、静态代码块

【1、概念】
实例代码块:每次实例化对象时,在调用构造方法前,自动执行一次。

{
		System.out.println(100);
}

静态代码块:仅在该类被第一次使用时,自动执行一次

static {
	System.out.println(200);
}

【2、代码分析】
1、静态代码块的使用。

public class CodeBlock {
	//实例代码块
	{
		System.out.println(100);
	}
	
	//静态代码块
	static {
		System.out.println(200);
	}
	
	//构造函数
	public CodeBlock(){
		System.out.println(300);
	}
	
	public static void main(String[] args) {
		
	}
}

输出:

200

在调用main方法时,已经开始使用类,所以会执行静态代码块。

2、静态代码块、实例代码块、构造函数之间的调用顺序。

public class CodeBlock {
	//实例代码块
	{
		System.out.println(100);
	}
	
	//静态代码块
	static {
		System.out.println(200);
	}
	
	//构造函数
	public CodeBlock(){
		System.out.println(300);
	}
	
	public static void main(String[] args) {
		System.out.println("1----------");
		CodeBlock c1 = new CodeBlock();
		System.out.println("2----------");
		CodeBlock c2 = new CodeBlock();
		System.out.println("3----------");
		CodeBlock c3 = new CodeBlock();
		
	}
}

输出:

200
1----------
100
300
2----------
100
300
3----------
100
300
  1. 条目1上边的200是静态代码块的输出,因为main方法的调用为当前类的使用;
  2. 下边的3组数据中,100为每次构造函数调用之前,实例代码块的输出,300为构造函数的输出,在实例代码块之后。

【3、难点解析—成员变量与代码块的位置关系】

3.1 变量先定义,后使用。

class Demo1 {
	static int a;
	static {
		a = 11;
		System.out.println(a);
	}
	 int b;
	{
		b = 12;
		System.out.println(b);
	}

	public static void main(String[] args) {
		System.out.println(a);
		System.out.println(new Demo1().b);
	}
}

输出:

11
11
12
12

遵循代码块的调用规则。

3.2 变量在代码块先赋值,再定义。

class Demo2 {
	static {
		a = 11;
		//System.out.println(a);
	}
	static int a;
	 
	{
		b = 12;
		//System.out.println(b);
	}
	int b;

	public static void main(String[] args) {
		System.out.println(a);
		System.out.println(new Demo2().b);
	}
}

输出:

11
12

也就是说先在代码块给变量赋值,再定义变量是成立的。这是因为jvm在编译时,会自动将static int a;a = 11;整合为一句,即:static int a = 11,所以编译不会有问题。

将上述代码的注释解开后会出错:

在这里插入图片描述

原因是因为输出的过程是一个变量使用的过程,而变量使用的规则是:先定义,后使用。这里违背了这个原则。

3.3 变量在代码块先赋值,再在类中定义,并且定义时赋初值。

class Demo3 {
	static {
		a = 11;
	}
	static int a = 21;
	 
	{
		b = 12;
	}
	int b = 22;

	public static void main(String[] args) {
		System.out.println(a);
		System.out.println(new Demo3().b);
	}
}

输出:

21
22

结合3.1,静态代码块的部分可以理解为:

  1. static int a = 11;
  2. a = 21;

也就相当于定义时先赋初值11,在给a赋值为21。实例代码块也是类似。

总结:

  • 静态部分:类被第一次使用时,先加载静态成员变量的定义,再按照书写顺序由上到下执行;
  • 实例部分:对象每次被实例化时,先加载实例成员变量的定义,再按照书写顺序由上到下执行。

2.5 有关代码块中执行顺序的深入探讨

【写在开头】在2.4中已经对代码块的执行顺序有了一定的了解,但是都只局限于在main方法中执行,下面就在main方法之外和在其他类中执行时的顺序探知一二。

2.5.1 区分

  • 类中静态部分:在类第一次被使用时,先加载静态成员变量的定义,再按照书写顺序由上向下执行,如静态代码块内的代码和静态变量定义时初始化赋值过程;
  • 类中实例部分:在对象每次被实例化时,先加载实例成员变量的定义,再按照书写顺序由上向下执行,如实例代码块内的代码和实例变量定义时初始化赋值过程。

2.5.2 main方法之外有这个类静态对象的初始化时调用顺序会是怎样的?

public class C {
	static C c1 = new C();

	static public void f() {
		System.out.println(1);
	}

	{
		System.out.println("A1");
	}

	{
		System.out.println("A2");
	}

	static {
		System.out.println(2);
	}

	static C c2 = new C();

	public static void main(String[] args) {
		System.out.println("*");
	}
}

输出:

A1
A2
2
A1
A2
*

具体顺序解读:

  1. 进入main方法时,第一次使用该类,这时会先加载类的静态成员变量的定义(即c1、c2的定义),然后按照顺序由上往下执行;
  2. 先执行c1的实例化,这是对象实例化的过程,所以类似的,先加载实例成员变量的定义(本例中无),然后再按照顺序执行非静态代码块,输出A1A2
  3. 然后执行到静态代码块部分,输出2
  4. 执行到c2的实例化,过程和c1一样,输出A1A2
  5. 最后执行main方法中的输出*

2.5.3 在其他类中有自身对象的声明时,执行顺序又是怎样的?

public class B {
	static {
		System.out.println(100);
	}
	{
		System.out.println(50);
	}
}
public class C {
	public static void main(String[] args) {
		B b1 = new B();
		B b2 = new B();
		System.out.println("*");
	}
}

结果:

100
50
50
*

顺序解读:

  1. 在C类中对b1进行实例化时,第一次用到B类,所以B类中的静态代码块会被运行,输出100
  2. 同时,b1实例化的过程调用到非静态代码块,输出50
  3. b2进行时实例化时,不会再调用到B类的静态代码块(不是第一次使用),只会因为实例化调用到非静态代码块,输出50
  4. 最后执行*的输出。

2.6 有关代码块与继承中执行顺序的典型例题

class AA{
	
	public AA() {
		System.out.println("----aa");
	}
	public void a12() {
		System.out.println("----a12");
	}
	public static void a22() {
		System.out.println("----a22");
	}
	{
		System.out.println("----a13");
	}
	static {
		System.out.println("----a23");
		a22();
	}	
}

class BB extends AA{
	public BB(){
		System.out.println("----bb");
	}
	public void b12() {
		System.out.println("----b12");
		a12();
	}
	public static void b22() {
		System.out.println("----b22");
	}
	{
		System.out.println("---b13");
	}
	static {
		System.out.println("----b23");
		b22();
	}	
}

class CC {
	public static void main(String[] args) {
		BB bb =new BB();
		bb.b12();
	}
}

输出:

----a23
----a22
----b23
----b22
----a13
----aa
---b13
----bb
----b12
----a12
  1. 在执行BB bb =new BB()时,是一个实例化BB类的过程,而BB类是AA类的子类,所以实例化BB类之前先实例化父类AA类;

  2. 第一步:实例化AA类时会使用到AA类,所以AA类的静态代码块先执行,也就是输出a23a22

  3. 第二步:实例化子类也要用到BB类,所以BB类的静态代码块也要执行,输出b23b22
    :即使存在上下级关系,子类实例化时也要把所有的静态代码块都执行完,再执行实例化;而静态代码块的执行顺序是先父类、后子类。

  4. 第三步:执行完代码块后执行实例化,先实例化父类,并且顺序是先实例代码块,后构造函数,所以输出a13aa

  5. 第四步:同理,实例化子类顺序也是如此,输出b13bb

  6. 第五步:执行bb.b12()语句,这仅仅是一个方法调用语句,所以输出b12a12

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值