JAVA的初始化的坑以及内存分配

来源:互联网 发布:centos7 阿里云 挂载 编辑:程序博客网 时间:2024/06/05 19:46

最近在看JAVA程序猿16课,看到了变量的初始化和内存分配,然后觉得挺多坑的,就总结了下来。(顺便,书真的挺好的)

PART 1:首先,看一个例子:

package test;public class TestInit{   public static void main(String[] args)   {       new Dervied();   }}class Base{   static   {       System.out.println("Base static block");   }   public static int a = 10;      {       System.out.println("base block");   }   public Base()   {       // TODO Auto-generated constructor stub       System.out.println("base constructor");   }}class Dervied extends Base{   public static int b = 10;      static   {       System.out.println("de static block");   }   {       System.out.println("de block");   }   public Dervied()   {       // TODO Auto-generated constructor stub       System.out.println("de constructor");   }}//输出的结果是Base static blockde static blockbase blockbase constructorde blockde constructor

使用,javap -c *.class工具查看编译结果,字节码,可以看到运行的顺序。


在这个例子当中,我们把static变量和static block的位置兑换了一下,他的字节码



看到Field的编译提前了,类似的非static变量和非static的Field调换位置,也可以发现他们的编译顺序是调换了的。所以得出的结论是:

(父类static变量,父类static block)-->(子类static 变量,子类static block)-->(父类的非static block,非static Field)-->父类构造器-->(子类非static block,子类非static FIeld)-->子类构造器,这是初始化的顺序,在没有添加final关键字和其他关键字,只有static和权限修饰符之下的情况。

其中,括号内的表示需要看他们的顺序,比如父类的static变量和static block,谁在代码顺序的前面编译时候就先执行。

//注,javap <optioins> <classes>的使用

1,-c:分解方法代码

2,-l指定显示的行号和局部变量的列表                                                3,-public|protected|package|private:用于指定显示那种级别的类成员,分别对应JAVA的四种访问权限

4,-verbose:指定显示更进一步的详细信息


PART 2:

先看例子:


package test;public class PriceTest{public static void main(String[] args){System.out.println(Prince.INISTANCE.currentPrice);Prince p = new Prince(2.8);System.out.println(p.currentPrice);}}class Prince{static Prince INISTANCE = new Prince(2.8);static double initPrice = 20;//加上final之后,编译会提前double currentPrice;public Prince(double discount){// TODO Auto-generated constructor stubthis.currentPrice = initPrice - discount;}}//结果是-2.817.2

为什么不都是17.2呢?首先,根据前面的结论,我们知道系统先回编译INISTANCE 这个变量,他是static的,然后,他回去先执行new Prince(2.8),去掉用构造方法,但是,这个时候,我们的initPrice的值是0.0(默认值,不是初始值),所以是-2.8,然后第二次的时候,系统调用了new一个对象之前,我们的static变量和static block以及执行完(这里没有block)


PART 3:

package test1;public class Test{public static void main(String[] args){new Derived();}}class Base{public int i = 2;public Base(){System.out.println(this.i);this.display();System.out.println(this.getClass());}public void display(){System.out.println(i);}}class Derived extends Base{public int i = 22;public Derived(){i = 222;}public void display(){System.out.println(i);}}//结果20class test1.Derived

为什么this.i = 2,而输出的display()当中的i是0。首先,我们知道,JAVA为每一个成员变量(不管是不是静态的)都会有一个默认值(没有初始化),就是声明这个变量的时候,比如int的默认值是0,就是int a,a作为成员变量,他的默认值就是0,初始化是指为a赋值。

然后是this关键字:代表的是正在初始化的对象或者是传入的对象,在这里,我们看到this.getClass()输出的是Derived类不是Base,所以这里的this是Derived对象而不是Base对象,因为我们再Test的main函数里面new Derived()。然后Derived是继承了Base,调用了Base的默认构造器,到了Base构造器里面,this就是指Derived对象,但是,他是Base的引用指向了Derived对象。当编译类型和运行类型不同的时候,通过变量访问他的引用的对象的实例变量时,该实例变量由声明该变量的类型决定,但是通过该变量调用它引用的对象的实例方法的时候,该方法将有他的实际所引用的对象决定。这里的引用是this,属于Base类型,它指向的对象是Derived对像。所以他调用成员变量就是调用Base的成员变量而不是Derived的。然后它调用方法,假如该方法子类存在而且是父类可以访问到的(就是override的),就是调用子类方法,否则是调用父类的对应方法。这里的结果就是这样,this.i是Base的i,然后this.display()或者是diaplay()就是Derived的方法(Derived存在该方法,override了,不存在的时候调用的是父类的),但是这时候子类的i还没有初始化,所以是0。


PART4:继承方法和继承变量的不同:

package test;public class Test{public static void main(String[] args){Base1 b = new Base1();System.out.println(b.count);b.display();Deriver d = new Deriver();System.out.println(d.count);d.display();Base1 db = new Deriver();System.out.println(db.count);db.display();Base1 d2b = d;System.out.println(d2b.count);}}class Base1{int count = 2;public void display(){System.out.println(this.count);}}class Deriver extends Base1{int count = 20;@Overridepublic void display(){System.out.println(this.count);}}//结果2220202202

当使用父类引用指向子类对象的时候,父类引用调用的成员变量时父类引用的成员变量,父类引用调用成员方法的时候,假如是子类有override的就是调用子类的方法,否则是父类的方法。在子类的对象(也就是堆当中),除了存在子类的对象的变量的值之外,还有一块内存用于给父类的变量。


PART 5:final修饰符

一句话:final修饰的变量在声明时候初始化,那么他的作用就相当于一个宏变量。他的值是在编译时期就已经决定下来。在运行的时候,遇到改变了就会进行替换。

在PART 2的initPrice 变量中,加上final关键字,结果会不一样,都是17.2.

package test;public class FinalTest{public static void main(String[] args){final String book = "疯狂Java讲义:" + 99.0;// 宏变量final String str = "疯狂Java讲义:99.0";final String book2 = "疯狂Java讲义:" + String.valueOf(99.0);// 无法确定值。不能再编译期间确定,不会被当做宏变量,因为调用了String的方法,具有不缺定性System.out.println(book == "疯狂Java讲义:99.0");System.out.println(book2 == "疯狂Java讲义:99.0");System.out.println(str == book);System.out.println(str == book2);}}//结果:truefalsetruefalse
我们知道String字符串都是的存储是在常量池当中,首先是当常亮池没有这个字符串,我们天津进去,当有这个字符串的时候,我们就不会在新建一个字符串放在常量池当中。直接比如String a="A";String b = "A";。开始A不存在常量池,存放进去,a指向A,然后b的值"A"以在常量池存在,直接b指向A,这时候a == b的值是true。在这个demo也是类似的。

package test;public class FinalInstanceVaribaleTest{public static void main(String[] args){String s1 = "ABC";String s2 = "AB" + "C";System.out.println(s1 == s2);String s3 = "DEF";String s4 = s1 + s3;String s5 = "ABCDEF";System.out.println(s4 == s5);}}//结果truefalse把s1和s3的修饰符改为final之后,是truetrue
原因:s1和数是变量,s4不能再编译时期确定值,s5在编译时期确定值,然后使用了final之后s1和s3是宏变量,确定了s4,所以是true。



0 0
原创粉丝点击