纸上得来终觉浅
学习了java类的初始化问题,以及涉及到的This和Static。也思考了一些问题,主要有以下四个点:
1......Java 类的初始化顺序,以及在内存中的存储:
2......由初始化引出的This关键字和This的另外一种用途:
3......由This引出的static关键字和及其详细用法;
4......再谈类的初始化,引出static代码块,构造代码块和构造函数和详细用法和执行顺序以及为什么会存在这些代码块。
1.Java类的初始化,示例如下:
<pre name="code" class="java">package roadArchitectWeb.Test; public class Test1 { /*1.默认初始化*/ private String name = "Daomeixiong"; /*2.显示初始化*/ private int age; /*3.静态代码块*/ static{ System.out.println("Test1:static静态代码块"); } /*4.构造代码块*/ { System.out.println("Test1:构造代码块"); } /*5.构造函数*/ public Test1(){ System.out.println("Test1:构造函数,name="+name+" age="+age); } /*6.静态方法*/ public static void prt(){ System.out.println("Test1:静态方法"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public static void main(String[] args) { /*@1*/ Test1 test1 = new Test1(); /*@2*/ test1.setName("zhou"); /*@3*/ Test1.prt(); } }
先看一下java 虚拟机中内存分类,如图:
堆区:可以存放对象
栈区:可以存放对象的引用
方法区:存放类(除了类的非静态成员变量)
然后下面以执行的顺序和在内存中的分布两个点来说明:
1) 执行步骤@1,要进行哪些操作? 在内存中怎样分布?A:在栈内存中开辟空间,存放变量p;
B:在方法区中加载Test1()类文件,除了非静态成员变量(这是对象的特有属性),其他全部加载到方法区,然后执行其中的静态代码块(代码3) ;
同时注意到: Test1()这个类(除了非静态成员变量),在方法区中或者说整个内存中,只有一份。
C:在堆内存中开辟空间,分配内存地址m,用来存放Test1的对象(实际上存储的就是类中的非静态成员变量)和一个类的引用(后面会说到,标记为@next1),然后对数据进行默认初始化(代码1), 接着显示初始化(代码2);
D:执行构造代码块(代码4);
E:执行构造函数(代码5);
F:把x赋给变量p,这样p就指向一个Test1类型的对象,p就是对象的一个引用。
2)执行步骤@2
A:为setName这个操作在栈内存中开辟空间,空间里产生一个变量称为Y1,存放临时变量“zhou”,二是由于setName方法为非静态方法(静态方法由于没有this,所以不会产生变量Y2,后面会说到,标记为@next2), 所以还会产生一个变量Y2用来存放p值,也就是对象的引用;
B:然后这个函数String setName(String name)会变成一个虚函数(C++中这么说,不知道java是不是这个称呼)String setName(int *this,String name); 所以setName("zhou")会变为setName(Y2,Y1);接着执行类中的方法(位于方法区中), 就是把name的值,赋给this.name(就是p.name,位于堆中);
3)执行步骤@3
A:在堆内存中开辟空间,分配内存地址n,创建一个变量,存放一个指向Test1的引用:
B:根据应用找到prt方法,然后执行;
综上所述,结果如下(上述部分步骤要调试代码才能体现,下面结果体现了上述主要步骤的顺序):
Test1:static静态代码块 Test1:构造代码块 Test1:构造函数,name=Daomeixiong age=0 Test1:静态方法
2.上面遗留了两个问题,@Test1和@Test2,先说@Test2; (这里讲由初始化引出的This关键字和This的另外一种用途:)
java中有这样一个规则,即静态方法中没有this,这就是为什么Test2中不会产生Y2,因为方法区中的静态方法并不准备接收它;
至于为什么会有this,我想这应该是它与静态方法的一个区别,原因如下:
静态方法所在的类可以直接调用这个方法,有两个解释:
1)静态方法中没有this,所以不需要通过类初始化一个对象,然后把对象引用传给this;
2)上述1--1)--B的步骤,类被加载到方法区,这个时候不创建对象也可以执行其中的静态方法;
所以@3可以直接通过类来调用方法。
/*@3*/ Test1.prt();
除此之外this还有另外一个功能:
当非静态方法中的参数和类中的属性同名的时候,this可以显示的指明类的属性,如上面代码的:
public void setName(String name) { this.name = name; }
public void setAge(int age) { this.age = age; }3.上面一直说到了静态成员和非静态成员,静态方法和非静态方法;所以这里讲(由This引出的static关键字和及其详细用法)
Static关键字:
1) Static作用于类的成员变量:会使非静态成员变量变为静态成员变量,那么它们的加载顺序是什么?
如上面的的1--1)--B中所述,除了非静态成员变量,其他都会加载(包括静态成员变量),这个时候静态成员变量在方法区就会初始化,然后才会初始化非静态成员变量。
2)Static作用于类中的方法:
A:使得非静态方法变为静态方法
B:由前面讲述知道,变为静态方法后不会有this关键字;另一方面,因为没有了this关键字,那么也就无法用this.func()的方式(其他的方式可以)调用非静态的方法,就是说静态方法是无法调用本类中的其他非静态方法的。
至于“其他的方式可以”有两种:一个是在静态方法中new 一个对象,通过对象执行非静态方法;另一个是给静态方法一个参数,通过参数传递一个对象的引用,然后通过这个引用调用对象中的非静态方法,示例如下:
test1:
package com.roadArchitectWeb.Test; public class test1 { public void test1prt(){ System.out.println("test1"); } }test2:
package com.roadArchitectWeb.Test; public class test2 { /*通过new一个对象的方式*/ public static void test21(){ test1 mytest1 = new test1(); mytest1.test1prt(); } /*通过传递参数的方式*/ public static void test22(test1 mytest1){ mytest1.test1prt(); } public static void main(String[] args) { test21(); test1 mytest1 = new test1(); test22(mytest1); } }结果如下:
test1 test1C:总结下静态方法与非静态方法的相互调用:
C1)非静态方法可以调用非静态方法
C2)非静态方法可以调用静态方法
C3)静态方法可以调用静态方法
C4)静态方法可以调用非静态方法(上面讲的new对象和传参两种方式)
C5)通过类名可以调用静态方法
C6) 通过类名不可以调用非静态方法(还没有没有产生this)
C7)通过对象可以调用非静态方法
C8)通过对象可以调用静态方法(这个就是上面留下来的问题@Test1而产生的功能,因为new一个对象,除了在堆内存中创建非静态成员变量外,还会创建一个类的引用,所以可以通过“对象.静态方法”的方式调用所属类的静态方法)
3)Static作用于类名
突然想到这样一个问题,Static可以作用于成员变量,作用于方法,那Static能不能作用于类名?
答案是不能的,为什么? 因为java不支持,并不需要这样的一个场景需要在类前加Static,这就是唯一的原因。 我们不能说由于加了Static会导致不能创建,不能重写等等,因为我们并不知道加了Static会是什么功能,什么表现。
注: 有一个例外,内部类前可以加Static,以后会说到。
4.再谈类的初始化,引出static代码块,构造代码块和构造函数和详细用法和执行顺序以及为什么会存在这些代码块。
Static代码块,构造代码块和构造函数的用法及执行顺序在前面已经展示了;下面是功能介绍:
1)Static代码块
在类加载的时候会执行,只执行一次;
2)构造代码块
每次New一个对象都会执行
3)构造函数在上述之后执行
构造函数不用说了,Static代码块和构造代码块是为了在对象生成之前赋予我们更多的可操作性:比如在类加载的时候,我们要打印一条信息,或者说在每次new一个对象的时候打印一条信息,如下代码:
{ System.out.println("test2对象开始生成"); } static{ System.out.println("test2类开始加载"); }