文章目录
一、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上边的200是静态代码块的输出,因为main方法的调用为当前类的使用;
- 下边的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,静态代码块的部分可以理解为:
static int a = 11;
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
*
具体顺序解读:
- 进入main方法时,第一次使用该类,这时会先加载类的静态成员变量的定义(即c1、c2的定义),然后按照顺序由上往下执行;
- 先执行c1的实例化,这是对象实例化的过程,所以类似的,先加载实例成员变量的定义(本例中无),然后再按照顺序执行非静态代码块,输出
A1
和A2
;- 然后执行到静态代码块部分,输出
2
;- 执行到c2的实例化,过程和c1一样,输出
A1
和A2
;- 最后执行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
*
顺序解读:
- 在C类中对b1进行实例化时,第一次用到B类,所以B类中的静态代码块会被运行,输出
100
;- 同时,b1实例化的过程调用到非静态代码块,输出
50
;- b2进行时实例化时,不会再调用到B类的静态代码块(不是第一次使用),只会因为实例化调用到非静态代码块,输出
50
;- 最后执行
*
的输出。
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
在执行
BB bb =new BB()
时,是一个实例化BB类的过程,而BB类是AA类的子类,所以实例化BB类之前先实例化父类AA类;第一步:实例化AA类时会使用到AA类,所以AA类的静态代码块先执行,也就是输出
a23
和a22
;第二步:实例化子类也要用到BB类,所以BB类的静态代码块也要执行,输出
b23
和b22
;
注:即使存在上下级关系,子类实例化时也要把所有的静态代码块都执行完,再执行实例化;而静态代码块的执行顺序是先父类、后子类。第三步:执行完代码块后执行实例化,先实例化父类,并且顺序是先实例代码块,后构造函数,所以输出
a13
和aa
;第四步:同理,实例化子类顺序也是如此,输出
b13
和bb
;第五步:执行
bb.b12()
语句,这仅仅是一个方法调用语句,所以输出b12
和a12
。