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。
- JAVA的初始化的坑以及内存分配
- java数组的初始化与内存分配
- java学习笔记1--数组初始化时的内存分配
- Java基础-对象的内存分配与初始化
- Java变量的声明、内存分配及初始化
- Java基础-对象的内存分配与初始化
- 指针以及内存的分配
- java的内存分配
- Java的内存分配
- Java的内存分配
- Java的内存分配
- Java的内存分配
- Java的内存分配
- java的内存分配
- java的内存分配
- Java的内存分配
- java的内存分配
- java的内存分配
- fragment中加入tabhost以及修改TabWidget
- SQL注入
- UIWebView 使用详解
- linux 常用命令
- Unity 坐标系笔记
- JAVA的初始化的坑以及内存分配
- PresentViewController
- 吊车装家电,真的是有钱任性吗?
- 如何删除SharePoint 2007 Server的Default SSP?
- 查询数据库所有触发器
- Ubuntu Error:couldn't to server 127.0.0.1:27017 at src/mongo/shell/mongo.js:145
- Spring之FactoryBean接口
- selenium(webdriver)--Microsoft Edge browser
- IOS APP提交给App Store时出现的问题