类加载,构造器初始化及static关键字

来源:互联网 发布:深证指数收盘价数据 编辑:程序博客网 时间:2024/06/05 19:52

一.类加载与构造器初始化
1.类加载:Java命令的作用是启动虚拟机,虚拟机通过输入流,从磁盘上将字节码文件(.class文件)中的内容读入虚拟机,并保存起来的过程就是类加载。

2.类加载特性: 在Java中,类是按需加载,只有当需要用到这个类的时候,才会加载这个类,并且在虚拟机的生命周期中一个类只被加载一次。

3.类加载的原则:延迟加载,能少加载就少加载,因为虚拟机的空间是有限的

4.从概念上讲,初始化与创建是彼此独立的,然而java中,初始化和创建是捆绑在一起的,两者不能分离

5.对于方法内的局部变量,声明后不会自动初始化赋值,如果没手动初始化,那么使用该变量时,编译器会报错
对于类中的数据成员(也就是属性),声明后系统会自动初始化为该类数据类型的初始值

6.在生成对象的过程中,会先初始化对象的成员变量,然后再执行构造器。也就是说类中的变量会在任何方法(包括构造器)调用之前得到初始化,即使变量散步于方法定义之间。

7.类加载的步骤:
在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:

  • 装载:查找和导入类或接口的二进制数据
  • 链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的
  • 校验:检查导入类或接口的二进制数据的正确性
  • 准备:给类的静态变量分配并初始化存储空间
  • 解析:将符号引用转成直接引用
  • 初始化:激活类的静态变量的初始化Java代码和静态Java代码块

8.类加载的时机:
(1)第一次创建对象时要加载类(因为创建对象会调用构造方法,而构造方法也是static方法,只不过是隐式的)
(2)调用静态方法时要加载类,访问静态属性时会加载类
(3)加载子类时必定会先加载父类
(4)创建对象引用不加载类
(5)子类调用父类的静态方法时

  • 当子类没有与父类同名的静态方法时(这时是运行父类的方法,所以只需加载父类),只加载父类,不加载子类
  • 当子类有与父类同名的静态方法时(这是是运行子类的方法,需要加载子类,所以必须先加载父类),既加载父类,又加载子类

(6)访问静态常量,如果编译器可以计算出常量的值,则不会加载类,例如:public static final int a =123;否则会加载类,例如:public static final int a = math.PI;
总的来说,类是在其任何static成员被访问时加载的.初次使用之处也是static初始化之处,所有static对象和static代码段都会在加载时依程序中的顺序(即定义类时的书写顺序)而依次初始化,当然定义为static的东西只会被初始化一次

9.对象的初始化顺序:首先执行父类静态的内容,父类静态的内容执行完毕后,接着去执行子类的静态的内容,当子类的静态内容执行完毕之后,再去看父类有没有非静态代码块,如果有就执行父类的非静态代码块,父类的非静态代码块执行完毕,接着执行父类的构造方法;父类的构造方法执行完毕之后,它接着去看子类有没有非静态代码块,如果有就执行子类的非静态代码块。子类的非静态代码块执行完毕再去执行子类的构造方法。
总之一句话,静态代码块内容先执行,接着执行父类非静态代码块和构造方法,然后执行子类非静态代码块和构造方法。
注意:子类的构造方法,不管这个构造方法带不带参数,默认的它都会先去寻找父类的不带参数的构造方法。如果父类没有不带参数的构造方法,那么子类必须用supper关键子来调用父类带参数的构造方法,否则编译不能通过。

10.一个类中对象的定义一般都是分为以下两步来进行的:
(1)A a; //定义了一个类A的引用
(2)a=new A(“10″,”2563”); //真正地建立了对象a,也就是a指向了内存中一块连续的区域.

11.本地方法是一种在java中调用非java代码的方式


二.static关键字
static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但是Java语言中没有全局变量的概念。

被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享。只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。

用public修饰的static成员变量和成员方法本质是全局变量和全局方法,当声明它类的对象时,不生成static变量的副本,而是类的所有实例共享同一个static变量。

static变量前可以有private修饰,表示这个变量可以在类的静态代码块中,或者类的其他静态成员方法中使用(当然也可以在非静态成员方法中使用),但是不能在其他类中通过类名来直接引用,这一点很重要。实际上只需要搞明白,private是访问权限限定,static表示不要实例化就可以使用,这样就容易理解多了。static前面加上其它访问权限关键字的效果也以此类推。

static修饰的成员变量和成员方法习惯上称为静态变量和静态方法,可以直接通过类名来访问,访问语法为:

  • 类名.静态方法名(参数列表…)
  • 类名.静态变量名

用static修饰的代码块表示静态代码块,当Java虚拟机(JVM)加载类时,就会执行该代码块。


三.Static关键字的用途
1.Static变量
static变量也称作静态变量(也叫类变量),静态变量和非静态变量(实例变量)的区别是:静态变量被所有的对象所共享,在内存中只有一个副本(因此在程序中任何对象对静态变量做修改,其他对象看到的是修改后的值),它当且仅当在类初次加载时会被初始化。可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
static成员变量的初始化顺序按照定义的顺序进行初始化。
注意:不能把任何方法体内的变量声明为静态(即static是不允许用来修饰局部变量)

2.Static方法
静态方法可以直接通过类名调用(任何的实例也可以调用),因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员方法),只能访问所属类的静态成员变量和静态成员方法。因为实例成员和实例方法与特定的对象关联.
因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。(即静态方法不能是抽象的)
如果说想在不创建对象的情况下调用某个方法,就可以将这个方法设置为static。我们最常见的static方法就是main方法,至于为什么main方法必须是static的,现在就很清楚了。因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。
另外记住,即使没有显示地声明为static,类的构造器实际上也是静态方法。

3.Static代码块
static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会自动执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,静态代码块没有名字,因此不能显式调用,每个代码块只会在类加载时被自动执行一次(因为在虚拟机的生命周期中一个类只被加载一次;又因为static{}是伴随类加载执行的,所以,不管你new多少次对象实例,static{}都只执行一次)。静态块常用来执行类属性的初始化.
总结如下:

  • 每个静态代码块只会在类加载时被执行一次
  • 当一个类中有多个static{}的时候,按照static{}的定义顺序,从前往后执行
  • 先执行完static{}语句块的内容,才会执行调用语句
  • 静态代码快中不能访问非static成员(属性和方法)
  • 如果静态变量在定义的时候就赋给了初值(如 static int X=100),那么赋值操作也是在类加载的时候完成的,并且当一个类中既有static{}又有static变量的时候,同样遵循“先定义先执行”的原则

例如:

class Test{    public static int X=300;    static{      System.out.println(X);      X=200;      System.out.println(X);    }}public class StaticBlockTest{    public static void main(String args[]){      System.out.println(Test.X);    }}

结果:程序会依次输出300,200,200,先执行完X=300,再执行static{}语句块。

4.Static类
一个普通类是不能声明为静态的,只有一个内部类才可以。这时这个声明为静态的内部类可以直接作为一个普通类来使用,而不需实例化一个外部类。
例如:

public class StaticCls{    public static void main(String[] args){      OuterCls.InnerCls oi=new OuterCls.InnerCls();    }} class OuterCls{    public static class InnerCls{       InnerCls(){           System.out.println("InnerCls");      }   }} 

结果:程序输出InnerCls

5.import static静态导入:详情见博文《静态导入》

6.static和final一块用表示什么?
static final用来修饰成员变量和成员方法,可简单理解为“全局常量”

  • 对于变量,表示一旦给值就不可修改,并且通过类名可以访问。
  • 对于方法,表示不可覆盖,并且可以通过类名直接访问。

注意:对于被static和final修饰过的实例常量,实例本身不能再改变了,但对于一些容器类型(比如,ArrayList、HashMap)的实例变量,不可以改变容器变量本身,但可以修改容器中存放的对象,这一点在编程中用到很多。
例如:

public class TestStaticFinal {         private static final String strStaticFinalVar = "aaa";         private static String strStaticVar = null;         private final String strFinalVar = null;         private static final int intStaticFinalVar = 0;         private static final Integer integerStaticFinalVar = new Integer(8);         private static final ArrayList<String> alStaticFinalVar = new ArrayList<String>();         private void test() {                 System.out.println("-------------值处理前----------\r\n");                 System.out.println("strStaticFinalVar=" + strStaticFinalVar + "\r\n");                 System.out.println("strStaticVar=" + strStaticVar + "\r\n");                 System.out.println("strFinalVar=" + strFinalVar + "\r\n");                 System.out.println("intStaticFinalVar=" + intStaticFinalVar + "\r\n");                 System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + "\r\n");                 System.out.println("alStaticFinalVar=" + alStaticFinalVar + "\r\n");                 //strStaticFinalVar="哈哈哈哈";            //错误,final表示终态,不可以改变变量本身.                 strStaticVar = "哈哈哈哈";                 //正确,static表示类变量,值可以改变.                 //strFinalVar="呵呵呵呵";                  //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。                 //intStaticFinalVar=2;                    //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。                 //integerStaticFinalVar=new Integer(8);   //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。                 alStaticFinalVar.add("aaa");        //正确,容器变量本身没有变化,但存放内容发生了变化。这个规则是非常常用的,有很多用途。                 alStaticFinalVar.add("bbb");        //正确,容器变量本身没有变化,但存放内容发生了变化。这个规则是非常常用的,有很多用途。                 System.out.println("-------------值处理后----------\r\n");                 System.out.println("strStaticFinalVar=" + strStaticFinalVar + "\r\n");                 System.out.println("strStaticVar=" + strStaticVar + "\r\n");                 System.out.println("strFinalVar=" + strFinalVar + "\r\n");                 System.out.println("intStaticFinalVar=" + intStaticFinalVar + "\r\n");                 System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + "\r\n");                 System.out.println("alStaticFinalVar=" + alStaticFinalVar + "\r\n");         }         public static void main(String args[]) {                 new TestStaticFinal().test();         } }

四.分析几道题目
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
结果分析:
虽然在main方法中没有任何语句,但是还是会输出,原因上面已经讲述过了。另外,static块可以出现类中的任何地方(只要不是方法内部,记住,任何方法内部都不行),并且执行是按照static块的顺序执行的。

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

public class StaticInitialization {    public static void main(String[] args) {        System.out.println("Creating new Cupboard() in main");        new Cupboard();        System.out.println("Creating new Cupboard() in main");        new Cupboard();        table.f2(1);        cupboard.f3(1);    }    static Table table = new Table();    static Cupboard cupboard = new Cupboard();}class Bowl{    Bowl(int marker){        System.out.println("Bowl("+marker+")");    }    void f1(int marker){        System.out.println("f1("+marker+")");    }}class Table{    static Bowl bowl1 = new Bowl(1);    Table(){        System.out.println("Table()");    }    void f2(int marker){        System.out.println("f2("+marker+")");    }    static Bowl bowl2 = new Bowl(2);}class Cupboard{    Bowl bowl3 = new Bowl(3);    static Bowl bowl4 = new Bowl(4);    Cupboard(){        System.out.println("Cupboard");        bowl4.f1(2);    }    void f3(int marker){        System.out.println("f("+marker+")");    }    static Bowl bowl5 = new Bowl(5);}

输出结果:
Bowl(1)
Bowl(2)
Table()
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)

0 0
原创粉丝点击