一、类加载机制
- 类加载的时机
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用、卸载7的阶段:加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的。类的加载过程必须按这种顺序按部就班的 开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持java语言的运行时绑定。注意。这里是按部就班的开始,而不是按部就班的进行或完成。强调这点是因为这些阶段通常都 是互相交叉地混合式进行的。通常会在一个阶段执行的过程中调用,激活另外一个阶段。
java虚拟机规范中对什么时候需要开始类的加载并没有进行强制约束,但规定的5种情况必须立即进行初始化(而加载、验证、准备自然需要在此之前开始)- 遇到new,getstatic,putstatic,invokestatic这4条字节码指令时,若类没有进行过初始化,则需要先初始化。对应到java代码中的场景是:使用new关键字,读取或设置一个静态字段,对于final修辞的,编译期已经把结果放入常量池的除外,的时候,以及调用一个类的静态方法的时候。
- 使用reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化
- 子类初始化,父亲先初始化
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类)虚拟机会先初始化这个主类
- 当使用jdk1.7的动态语言支持时,如果一个MethodHandle实例最后的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄 ,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化
这5种会触发类进行初始化的行为称为对一个类的主动引用,除此之外,所有引用类的方式都称为被动引用,不会触发初始化,下面举3个例子来说明被动引用:
@Test
public void test14() {
/* 通过子类引用父类的静态字段,不会导致子类初始化 */
System.out.println(Demo2_2.value);
/*
* super 12
*/
} class Demo1_2 {
public static int value = 12;
static {
System.out.println("super");
}
} class Demo2_2 extends Demo1_2 {
static {
System.out.println("sub");
}
}对于静态字段,只能直接定义这个字段的类才会被初始化
@Test
public void test14() {
/* 通过数组定义来引用类,不用触发对该类的初始化*/
Demo2_2[] d=new Demo2_2[10]; } class Demo1_2 {
public static int value = 12;
static {
System.out.println("super");
}
} class Demo2_2 extends Demo1_2 {
static {
System.out.println("sub");
}
}虽然没有对Demo2_2初始化,但是我们要知道,虚拟机自动生成另外一个类,并进行初始化,这个类代表了一个元素类型为Demo2_2的一维数组,数组中应有的方法和属性都实现在这个类中
@Test
public void test14() {
/*对于static常量,编译时会存入常用类的常量池中,不会对类进行初始化*/
System.out.println(Demo1_2.value);//
} class Demo1_2 {
public final static int value = 12;
static {
System.out.println("super");
}
}
-
类加载中的加载
在加载阶段,虚拟机需要完成3件事:
1、通过一个类的全限定名来获取定义此类的二进制字节流
2、将这个字节流所代表静态存储结构转化为方法区的运行时数据结构
3、在内存中生成一个代表这个类的Class对象,作为方法区类的各种数据的访问入口
注意:对于数组类而言,情况就有所不同,数组类本身不能过加载器创建,它是由java虚拟机直接创建的。但数组类与类加载器有很密切的关系,因为数组类的类型最终是要靠类加载器去创建加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的存储在方法区之中,方法区中的数据存储格式由虚拟机实现自行定义,然后在内存中实例化一个Class类的对象,这个对象将作为程序访问方法区中的这些类型数据的外部接口
- 类加载中的验证
验证的目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全(Class文件并不一定是由java编译过来的)验证有4个阶段:- 文件格式验证:主、次版本号是否在当前虚拟机处理的范围之内、常量池中是否有不被支持的常量类型等等
- 元数据的验证:这个类是否有父类、这个类的父类是否继承了不被允许继承的类等等
- 字节码验证:主要目的是通过数据流和控制流分析,确定程序语义是合法的
- 符号引用验证:解析阶段验证:符号引用中通过字符串描述的全限定名是否能找到对应的类。符号引用中的类,字段,方法的访问性等等
- 类加载中的准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配,这里所说的初始值“通常情况”下是数据类型的零值,例:public static int i=124;在这个准备阶段过后的初始值是0,而不是124,因为这个时候尚未开始执行任何java方法当然对public final static int i=124的情况就不是这样了,其在编译的时候就已经确定了
- 类加载中的解析
解析阶段是虚拟机阶段是虚拟机将常量池内的符号引用替换为直接引用的过程 - 类加载中的初始化
这个时候才开始执行类中定义的java程序码
在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器<clinit>()方法的过程,下面是关于<clinit>()- <clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{})中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义到静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是晃能访问
<clinit>()方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法
- <clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{})中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义到静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是晃能访问
二、对象的初始化-----用构建器自动初始化(constructor)
三、方法的重载
- 方法的重载就根据入参的类型,入参数目,入参顺序来决定的,不能根据返回值来确定(因为有些时候我们根本不关心方法的返回值)这里有个比较有趣的例子:
@Test
public void test14() {
a(1);//
} public void a(int... i){
for(int j:i){
System.out.println(j+"*********");
} }
public void a(int i){
System.out.println(i);
} - 基本类型的过载
这里,thinking in java中给的例子感觉可以说明一切了@Test
public void test08() {
testConstVal();
testChar();
testByte();
testShort();
testInt();
testLong();
testFloat();
testDouble();
}
static void prt(String s) {
System.out.println(s);
} void f1(char x) { prt("f1(char)"); }
void f1(byte x) { prt("f1(byte)"); }
void f1(short x) { prt("f1(short)"); }
void f1(int x) { prt("f1(int)"); }
void f1(long x) { prt("f1(long)"); }
void f1(float x) { prt("f1(float)"); }
void f1(double x) { prt("f1(double)"); }
void f2(byte x) { prt("f2(byte)"); }
void f2(short x) { prt("f2(short)"); }
void f2(int x) { prt("f2(int)"); }
void f2(long x) { prt("f2(long)"); }
void f2(float x) { prt("f2(float)"); }
void f2(double x) { prt("f2(double)"); }
void f3(short x) { prt("f3(short)"); }
void f3(int x) { prt("f3(int)"); }
void f3(long x) { prt("f3(long)"); }
void f3(float x) { prt("f3(float)"); }
void f3(double x) { prt("f3(double)"); }
void f4(int x) { prt("f4(int)"); }
void f4(long x) { prt("f4(long)"); }
void f4(float x) { prt("f4(float)"); }
void f4(double x) { prt("f4(double)"); }
void f5(long x) { prt("f5(long)"); }
void f5(float x) { prt("f5(float)"); }
void f5(double x) { prt("f5(double)"); }
void f6(float x) { prt("f6(float)"); }
void f6(double x) { prt("f6(double)"); }
void f7(double x) { prt("f7(double)"); }
void testConstVal() {
prt("Testing with 5");
f1(5);f2(5);f3(5);f4(5);f5(5);f6(5);f7(5);
}
void testChar() {
char x = 'x';
prt("char argument:");
f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
}
void testByte() {
byte x = 0;
prt("byte argument:");
f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
}
void testShort() {
short x = 0;
prt("short argument:");
f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
}
void testInt() {
int x = 0;
prt("int argument:");
f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
}
void testLong() {
long x = 0;
prt("long argument:");
f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
}
void testFloat() {
float x = 0;
prt("float argument:");
f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
}
void testDouble() {
double x = 0;
prt("double argument:");
f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
}输出结果:
Testing with 5
f1(int)
f2(int)
f3(int)
f4(int)
f5(long)
f6(float)
f7(double)
char argument:
f1(char)
f2(int)
f3(int)
f4(int)
f5(long)
f6(float)
f7(double)
byte argument:
f1(byte)
f2(byte)
f3(short)
f4(int)
f5(long)
f6(float)
f7(double)
short argument:
f1(short)
f2(short)
f3(short)
f4(int)
f5(long)
f6(float)
f7(double)
int argument:
f1(int)
f2(int)
f3(int)
f4(int)
f5(long)
f6(float)
f7(double)
long argument:
f1(long)
f2(long)
f3(long)
f4(long)
f5(long)
f6(float)
f7(double)
float argument:
f1(float)
f2(float)
f3(float)
f4(float)
f5(float)
f6(float)
f7(double)
double argument:
f1(double)
f2(double)
f3(double)
f4(double)
f5(double)
f6(double)
f7(double) - 默认构建器
意思是创建一个类时,即使没有为其创建一个创造器,程序本身也会默认给这个类创建一个无入参的构造器,要注意的是,如果人为了已经构造了一个构造器这后,默认的构造器就没有了 - this关键字
如果有两个同类型的对象,分别叫作a 和b,那么您也许不知道如何为这两个对象同时调用一个 f()方法:
class Banana { void f(int i) { /* ... */ } }
Banana a = new Banana(), b = new Banana();
a.f(1);
b.f(2);
若只有一个名叫f()的方法,它怎样才能知道自己是为 a 还是为b 调用的呢?
为了能用简便的、面向对象的语法来书写代码——亦即“将消息发给对象”,编译器为我们完成了一些幕后
工作。其中的秘密就是第一个自变量传递给方法f(),而且那个自变量是准备操作的那个对象的句柄。所以
前述的两个方法调用就变成了下面这样的形式:
Banana.f(a,1);
Banana.f(b,2);
这是内部的表达形式,我们并不能这样书写表达式,并试图让编译器接受它。但是,通过它可理解幕后到底
发生了什么事情。
假定我们在一个方法的内部,并希望获得当前对象的句柄。由于那个句柄是由编译器“秘密”传递的,所以
没有标识符可用。然而,针对这一目的有个专用的关键字:this。this 关键字(注意只能在方法内部使用)
可为已调用了其方法的那个对象生成相应的句柄。可象对待其他任何对象句柄一样对待这个句柄。但要注
意,假若准备从自己某个类的另一个方法内部调用一个类方法,就不必使用this。只需简单地调用那个方法
即可。当前的this 句柄会自动应用于其他方法。- 当成员变量和局部变量重名时,在方法中使用this时,表示的是该方法所在类中的成员变量。(this是当前对象,就是哪个对象调用这个方法或属性这个this就是这个对象的句柄)
public class t3 {
int i=;//这个类的成员属性
public void testThis(int i){
//i=i;显然这是没有意义的,这相当于把方法入参本身的值赋给本身,那怎么样取的到当前对象的成员属性i呢,用this.i,this表示当前对象
this.i=i;
}
} - 把自己作为返回对象使用,常见的链式api就是这样来的:
@Test
public void test13() {
Demo1_3 d=new Demo1_3();
d.add().add().add().add();
} class Demo1_3{
public Demo1_3 add(){
return this;
}
} - 把自己当作参数传递时,也可以用this.(this作当前参数进行传递)
class Demo1_3{
public void test(Demo1_3 d){ }
public void test2(){
test(this);
}
} - 当在匿名类中用this时,这个this则指的是匿名类或内部类本身。
@Test
public void test13() {
Demo1_3 d=new Demo1_3(); } class Demo1_3{
public Demo1_3() {
Thread t=new Thread(){
int i=10;
public void run() {
System.out.println(this.i);
};
};
t.start();
}
} - 在构造函数中,通过this可以调用同一类中别的构造函数
class Demo1_3{
int i=0;
char a='a';
String c="c";
byte d=2;
public Demo1_3(int i,char a,String c) {
this.i=i;
this.a=a;
this.c=c;
}
public Demo1_3(int i,char a,String c,byte d) {
/*this.i=i;
this.a=a;
this.c=c;
this.d=d;这样写是不是太麻烦了???*/ this(i,a,c);//因为代码功能一样,直接调用本类中的另一个构造函数
this.d=d;//这样是不是更简捷呢?
}
}值得注意的是:
1:在构造调用另一个构造函数,调用动作必须置于最起始的位置。
2:不能在构造函数以外的任何函数内调用构造函数。
3:在一个构造函数内只能调用一个构造函数。
- 当成员变量和局部变量重名时,在方法中使用this时,表示的是该方法所在类中的成员变量。(this是当前对象,就是哪个对象调用这个方法或属性这个this就是这个对象的句柄)
-
static关键字(http://www.cnblogs.com/dolphin0520/p/3799052.html)
理解了this 关键字后,我们可更完整地理解 static(静态)方法的含义。它意味着一个特定的方法没有this。我们不可从一个 static方法内部发出对非 static方法的调用,尽管反过来说是可以的。而且在没有任何对象的前提下,我们可针对类本身发出对一个 static方法的调用。事实上,那正是 static方法最基本的意义。它就好象我们创建一个全局函数的等价物(在C 语言中)。除了全局函数不允许在Java中使用以外,若将一个 static方法置入一个类的内部,它就可以访问其他static 方法以及static 字段。
static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,this又从何而来?所以静态方法是不可以访问非静态的成员属性及方法的,原因很简单,静态方法,是随着类的存在而存在的。非静态成员属性及方法是随对象而存在的。对象都没有,非静态成员属性及方法也就没有,所以哪来的调用呢?反过来则是可以的
-
static变量
static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
static成员变量的初始化顺序按照定义的顺序进行初始化。
-
static代码块
static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。这个特性可以让我们有的时候很大程度上的提高我们的程序性能,例:class Demo2_3{
//这样的代码每执行一个方法就要生成一个Connection对象,适合吗?
static Connection c=null; public static void add() throws SQLException{
c=DriverManager.getConnection("****");
//程序代码 }
public static void find() throws SQLException{
c=DriverManager.getConnection("****");
//程序代码 }
public static void delete() throws SQLException{
c=DriverManager.getConnection("****");
//程序代码 }
}改成这样???怎么样?
class Demo2_3{
//这样在类加载的时候只会生成一个connection对象,避免造成资源的浪费
static Connection c=null;
static{
try {
c=DriverManager.getConnection("****");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} public static void add() throws SQLException{
//程序代码 }
public static void find() throws SQLException{
//程序代码 }
public static void delete() throws SQLException{
//程序代码 }
} -
下面列举一些面试笔试中经常遇到的关于static关键字的题目(转:http://www.cnblogs.com/dolphin0520/p/3799052.html)
-
下面这段代码的输出结果是什么?
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()的时候会先调用父类的构造器,然后再调用自身的构造器。因此,便出现了上面的输出结果。
- 下面这段代码的输出结果是什么?
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的构造器
- 下面这段代码的输出结果是什么?
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虽然在main方法中没有任何语句,但是还是会输出,原因上面已经讲述过了。另外,static块可以出现类中的任何地方(只要不是方法内部,记住,任何方法内部都不行),并且执行是按照static块的顺序执行的。
-
四,成员初始化
- 局部变量:没有初始化,编译就会报错
- 成员变量:
- 可以声明的时候就初始化
class A{
int i=0;
} - 通过构造函数初始化
class A{
int i;
public A(){
i=0;
}
} - 默认初始化
如果成员变量没有显示的初始化,那程序会默认对成员变量进行一个初始化@Test
public void test02() {
Demo3_3 d=new Demo3_3();
d.print();
}
class Demo3_3{
Demo3_3 d3;
boolean t;
char c;
byte b;
short s;
int i;
long l;
float f;
double d;
void print() {
System.out.println(
"Data type Inital value\n" +
"对象 " + d3 + "\n" +
"boolean " + t + "\n" +
"char " + c + "\n" +
"byte " + b + "\n" +
"short " + s + "\n" +
"int " + i + "\n" +
"long " + l + "\n" +
"float " + f + "\n" +
"double " + d);
}
}输出结果:
Data type Inital value
对象 null
boolean false
char //这里是个空格
byte 0
short 0
int 0
long 0
float 0.0
double 0.0 - 初始化顺序
在一个类里,初始化的顺序是由变量在类内的定义顺序决定的。即使变量定义大量遍布于方法定义的中间,那些变量仍会在调用任何方法之前得到初始化——甚至在构建器调用之前
@Test
public void test03() {
new Demo4_4();//Demo4_3 Demo4_4
}
class Demo4_3{
public Demo4_3(){
System.out.println("Demo4_3");
}
}
class Demo4_4{
Demo4_3 d=new Demo4_3();
public Demo4_4() {
System.out.println("Demo4_4");
}
} - 静态成员变量的初始化
若数据是静态的(static),那么同样的事情就会发生;如果它属于一个基本类型(主类型),而且未对其初始化,就会自动获得自己的标准基本类型初始值;如果它是指向一个对象的句柄,那么除非新建一个对象,并将句柄同它连接起来,否则就会得到一个空值(NULL)。如果想在定义的同时进行初始化,采取的方法与非静态值表面看起来是相同的。但由于static 值只有一个存储区域,所以无论创建多少个对象,都必然会遇到何时对那个存储区域进行初始化的问题。下面这个例子可将这个问题说更清楚一些
@Test
public void test13() { Demo4_5 d=new Demo4_5(); } class Demo4_3{
public Demo4_3(){
System.out.println("Demo4_3");
}
}
class Demo4_5{
Demo4_3 d2=new Demo4_3();
static Demo4_6 d=new Demo4_6();
public Demo4_5(){
System.out.println("Demo4_5");
}
}
class Demo4_6{
public Demo4_6(){
System.out.println("Demo4_6");
}
}Demo4_6
Demo4_3
Demo4_5static初始化只有在必要的时候才会进行,这点与static块不同。
初始化的顺序是首先static(如果它们尚未由前一次对象创建过程初始化),接着是非static 对象。大家
可从输出结果中找到相应的证据在这里有必要总结一下对象的创建过程。请考虑一个名为 Dog的类:
(1) 类型为 Dog的一个对象首次创建时,或者Dog 类的static方法/static 字段首次访问时,Java 解释器
必须找到Dog.class(在事先设好的类路径里搜索)。
(2) 找到Dog.class 后(它会创建一个 Class对象,这将在后面学到),它的所有 static初始化模块都会运
行。因此,static初始化仅发生一次——在 Class 对象首次载入的时候。
(3) 创建一个new Dog()时,Dog 对象的构建进程首先会在内存堆(Heap)里为一个 Dog对象分配足够多的存
储空间。
(4) 这种存储空间会清为零,将Dog中的所有基本类型设为它们的默认值(零用于数字,以及 boolean和
char 的等价设定)。
(5) 进行字段定义时发生的所有初始化都会执行。
(6) 执行构建器。正如第6 章将要讲到的那样,这实际可能要求进行相当多的操作,特别是在涉及继承的时
候。 - 非静态代码块
@Test
public void test12() {
new Demo1_4();
} class Demo1_4 {
static Demo2_4 d = new Demo2_4();
Demo4_4 d3 = new Demo4_4();
{
Demo3_4 d2 = new Demo3_4();
}
} class Demo2_4 {
public Demo2_4() {
System.out.println("Demo2_4");
}
} class Demo3_4 {
public Demo3_4() {
System.out.println("Demo3_4");
}
} class Demo4_4 {
public Demo4_4() {
System.out.println("Demo4_4");
}
}输出结果:可见非静态代码块的执行顺序和非静态成员初始化是执行权是一样的
Demo2_4
Demo4_4
Demo3_4 - 数组初始化
数组代表一系列对象或者基本数据类型,所有相同的类型都封装到一起——采用一个统一的标识符名称。数组的定义和使用是通过方括号索引运算符进行的([])。为定义一个数组,只需在类型名后简单地跟随一对空方括号即可:int[] al;也可以将方括号置于标识符后面,获得完全一致的结果:int al[];
- int[] a1 = { 1, 2, 3, 4, 5 };
- int[] a2=new int[]{1, 2, 3, 4, 5};
- int[] a3=new int[5];
a3[0]=1;a3[1]=2....
- 多维数组:(这个只要理解了在java中多维数据就是数组的数组就很简单了)
- 可以声明的时候就初始化