Thinking In Java----2017.1.8 访问权限、多态以及一些知识点。

 

1.Java的数学运算都是在栈中进行的

 

2.基本类型和基本类型的包装类。基本类型有:byte、short、char、int、long、boolean。基本类型的包装类分别是:Byte、Short、Character、Integer、Long、Boolean。注意区分大小写。二者的区别是:基本类型体现在程序中是普通变量,基本类型的包装类是类,体现在程序中是引用变量。因此二者在内存中的存储位置不同:基本类型存储在栈中,而基本类型包装类存储在堆中。上边提到的这些包装类都实现了常量池技术,另外两种浮点数类型的包装类则没有实现。另外,String类型也实现了常量池技术。常量池里如果有需要的string,则直接用,否则往常量池里加新的

3. 以上提到的几种基本类型包装类均实现了常量池技术,但他们维护的常量仅仅是【-128至127】这个范围内的常量,如果常量值超过这个范围,就会从堆中创建对象,不再从常量池中取。

4. String类型也实现了常量池技术,但是稍微有点不同。String型是先检测常量池中有没有对应字符串,如果有,则取出来;如果没有,则把当前的添加进去。

 

5. 冲突类处理:如果你import的2个以上的包都有同一个名称的类,在你使用这个名称的类时,你应该明确指定它来自哪里。如:java.util.Vector v = new Java.util.Vector();

6. privata关键字: 除了包含该成员的类之外,其他任何类都无法访问这个成员。任何可以肯定只是该类的助手的方法,都可以指定它为private的。

7. protected关键字:主要用于继承,但是它也实现了包访问权限,即包内的其他类也可以访问protected元素。一般都是把域作为private的,而把父类相关方法设置为protected的,这是一种机制。

       

8.封装的概念:访问权限的控制常被称为具体实现的隐藏。把数据和方法包装进类中,加上具体实现的隐藏,常共同被称作封装。

9.类访问权限:

a.)类只有public和包访问权限,无private和protected.

b.)一个class文件只能有1个public class,且文件名必须和这个public class完全相同。

c.)如果你不想别人生成这个类对象,你可以把这个类的构造器指定为private的,这样别人就无法应用了。

d.)如果你不想别人随意生成这个类对象,又可以使用这个类的话,有2中方法:

1.)class A{

private A(){}

public static A makeA(){

return new A();

}

}

2.)class A{

private static A a = new A();

private A(){}

public static A getA(){

return a;

}

}

这是一种单例模式。

 

没有public修饰的类都是包访问权限的类。

 

10.Java中初始化的地方 :

A.)定义对象的时候;

B.)构造器里;

C.)惰性初始化 例如if(string ==“Test);

D.)实例初始化(初始化块);

11.关于继承的规则:

在Java中,如果父类成员没有生命访问权限,那么只有在同1个包中的子类可以使用父类 成员,而其他包中的子类只能使用父类中生命了public的成员,因此,有一个很有用的规则如下。

通常情况下:我们应该做的是父类里的数据成员都指定为private的,而方法应该都指定public的(具体看你的应用场景。)

12.关于生成子类对象时,父类对象的问题:

当你生成1个子类对象的时候,这个子类对象其实包含了1个父类的子对象,这个子对象和你直接创建的对象是一样的。二者区别在于后者来自于外部,而前者的自对象被包装在子类对象的内部。

13.带参数的父类构造器:

如果你的父类构造器没有默认无参数的构造器,而是有参构造器,那么你的子类必须用super关键字显式的调用基类构造器,否则,编译器会显示找不到父类构造器,

并且你在子类构造器中第一件应该做的事就是调用父类带参构造器。

 

14.复用代码的几种形式:

a) 组合;

b) 继承;

c) 代理; 例如  class playerControl{

 

Public void play(String what){}

    Public void start(String what){}

 

}

 

Class  playerProxy{

 

playerControl control  = new playerControl();

 

Public void play(String what){

Control.play(what);

}

    Public void start(String what){

    Control.start(what);

    }

 

}

 

代理的优势可以提供想要的方法子集,而不用全盘接受。

比如说我们有1个类是Player,它有玩足球的一系列相关方法,它有玩篮球、羽毛球、游泳的一系列相关方法。但是我们可能只是需要1个BasketBallPlayer,它不应该有关于玩足球、羽毛球的方法。因此,如果通过继承来构造我们的BasketBallPlayer.class,这显得很不合理,我们应该利用代理,让我们的新类只拥有某个方法子集就好。

 

 

 

 

15.finally的子句的意思是无论如何,这部分代码一定要执行。

16.@Override注解可以防止我们在不想重载的时候进行了重载。

17.正常情况下,大部分采用组合的形势,但是如果要使用继承,应该考虑一下:

自己是否需要新的子类向基类进行向上转型,如果需要向上转型,则继承是必要的。

 

 

final关键字相关:

 

 

18.final修饰的四元素: a.) 成员变量; b.)方法; c.)类; d.)本地变量(方法中的或者代码块中的变量)。

19.使用final修饰数据的2种可能原因:

a) 我们不希望这个数据被改变,

b) 一个永不改变的编译时常量:编译器可以将编译时常量带入任何它需要的计算式,这就意味着在编译时就可以进行一些计算(执行计算式),可以减少运行时负担。

 

20.final关键字修饰对象引用:final关键字修饰的引用,即

 final Object o1 = new Object(1);

     那么就不能 o1 = new Object(2);了 ,因为它不能再指向另一个对象了。

     然而对象的值是可以变的,比如:

            Object里有1个int i ;

            o1.i++;是可以的,因为它改变的是对象的属性。 而不是引用。

      而数组也是引用,比如int[] a = new int[]{1,2,3}

     我们可以改变a[i],比如a[1] = 10;因为数组也是引用。

            所以一般情况下final修饰应用没有修饰基本类型作用大。

21.空白final:所谓空白final,就是指被声明是final的但是没有初始化的域。

22.空白final的作用:它可以使得一个类中的final域做到因对象不同而值不同,具体做法是在不同的构造器中对final域进行初始化。使得不同构造器中的final域是不同的

23.final一些原则:必须在域的定义处初始化或者在构造器中进行初始化,这正是final域在使用前总是被初始化的原因所在。

24.final参数(本地变量):引用------->无法改变引用指向新的对象。

    基本数据类型------>无法改变值。

25.final方法使用的2点原因:

a) 明确禁止覆盖。(其实更深层次的是禁止动态绑定)

b) 以前是为了效率,不过在JAVA SE5/6时,让编译器和JVM来处理效率的问题,所以该条省略。

26.private和final:private方法因为它是外部用不到的,所以它隐含是final的, 对于private方法,可以理解为它不是基类的接口的一部分,而是隐藏于类中的程序代码,如果我们在子类当中有相同名称的方法的话,它并不是覆写基类方法,因为基类方式是private的,而是新生成了1个方法,所以即使名字相同,也不会报错。

27.final类:出于设计或者安全,你不希望它有子类,不可以继承它。它的所有方法都是final的,无法覆写。

28.类的代码(可以理解为static代码)在初次使用时才加载:类的编译代码保存在它自己的独立文件中,该文件只在需要的时候才会被加载。

 

一个很好的关于final总结地址:

http://www.importnew.com/7553.html

 

Java中的final关键字非常重要,它可以应用于类、方法以及变量。这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了什么?使用final的好处是什么?最后也有一些使用final关键字的实例。final经常和static一起使用来声明常量,你也会看到final是如何改善应用性能的。

final关键字的含义?

final在Java中是一个保留的关键字,可以声明成员变量、方法、类以及本地变量。一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如果你试图将变量再次初始化的话,编译器会报编译错误。

什么是final变量?

凡是对成员变量或者本地变量(在方法中的或者代码块中的变量称为本地变量)声明为final的都叫作final变量。final变量经常和static关键字一起使用,作为常量。下面是final变量的例子:

1

2

public static final String LOAN = "loan";

LOAN = new String("loan") //invalid compilation error

final变量是只读的。

什么是final方法?

final也可以声明方法。方法前面加上final关键字,代表这个方法不可以被子类的方法重写。如果你认为一个方法的功能已经足够完整了,子类中不需要改变的话,你可以声明此方法为final。final方法比非final方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。下面是final方法的例子:

1

2

3

4

5

6

7

8

9

10

11

12

class PersonalLoan{

    public final String getName(){

        return "personal loan";

    }

}

 

class CheapPersonalLoan extends PersonalLoan{

    @Override

    public final String getName(){

        return "cheap personal loan"; //compilation error: overridden method is final

    }

}

什么是final类?

使用final来修饰的类叫作final类。final类通常功能是完整的,它们不能被继承。Java中有许多类是final的,譬如String, Interger以及其他包装类。下面是final类的实例:

1

2

3

4

5

6

7

    final class PersonalLoan{

 

    }

 

    class CheapPersonalLoan extends PersonalLoan{  //compilation error: cannot inherit from final class

 

}

final关键字的好处

下面总结了一些使用final关键字的好处

1. final关键字提高了性能。JVM和Java应用都会缓存final变量。

2. final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。

3. 使用final关键字,JVM会对方法、变量及类进行优化。

不可变类

创建不可变类要使用final关键字。不可变类是指它的对象一旦被创建了就不能被更改了。String是不可变类的代表。不可变类有很多好处,譬如它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销等等。

相关阅读:为什么String是不可变的以及如何写一个不可变类

关于final的重要知识点

1. final关键字可以用于成员变量、本地变量、方法以及类。

2. final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。

3. 你不能够对final变量再次赋值。

4. 本地变量必须在声明时赋值。

5. 在匿名类中所有变量都必须是final变量。

6. final方法不能被重写。

7. final类不能被继承。

8. final关键字不同于finally关键字,后者用于异常处理。

9. final关键字容易与finalize()方法搞混,后者是在Object类中定义的方法,是在垃圾回收之前被JVM调用的方法。

10. 接口中声明的所有变量本身是final的。

11. final和abstract这两个关键字是反相关的,final类就不可能是abstract的。

12. final方法在编译阶段绑定,称为静态绑定(static binding)。

13. 没有在声明时初始化final变量的称为空白final变量(blank final variable),它们必须在构造器中初始化,或者调用this()初始化。不这么做的话,编译器会报错“final变量(变量名)需要进行初始化”。

14. 将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。

15. 按照Java代码惯例,final变量就是常量,而且通常常量名要大写:

1

private final int COUNT = 10;

1. 对于集合对象声明为final指的是引用不能被更改,但是你可以向其中增加,删除或者改变内容。譬如:

1

2

3

4

private final List Loans = new ArrayList();

list.add(“home loan”);  //valid

list.add("personal loan"); //valid

loans = new Vector();  //not valid

我们已经知道final变量、final方法以及final类是什么了。必要的时候使用final,能写出更快、更好的代码的。

 

多态:

1.将1个方法调用和方法体关联起来---被称作绑定:绑定分为前期绑定后期绑定。Java中除了static方法和final方法之外,其他所有方法都是后期绑定。

2.后期绑定机制:就是在运行时根据对象的类型进行绑定,又叫运行时绑定。

3.final方法其实就是旨在关闭动态绑定。

 

 

4.方法向上转型: class Circle extends Shape

 public Shape next(){

return new Circle();

}

 

5.多态是一像“将改变的事物与未变的事物分离开来的重要事物。

-------------------------------------------------------------------------------------------------------------------------------

                                         其他知识点:

1.一个对象创建的过程:

http://blog.csdn.net/lingzhou1/article/details/8476709

 

如果是Person p = new Person();

 

1.首先在硬盘指定位置找到Person.class文件,然后将该文件加载进内存。

2.执行main方法时,在内存的栈区中为main()方法开辟1个空间,然后再main方法的栈区中分配了1个变量p

3.在堆内存中开辟了一段实体空间,分配了1个内存首地址。

4.在该空间中进行属性的空间分配,并进行默认初始化。

5.对空间的属性进行显示初始化。

6.进行实体的构造代码块初始化。

7.构造函数初始化。

8.将首地址赋值给pp变量就引用了该实体。

 

 

 

 

 

 

http://blog.csdn.net/anjayxc/article/details/6063210

 

 

这是没有涉及继承的情况,对于涉及了继承的情况总结如下:

假设有个名为Mouse4j的类,继承自MouseMouse又继承自Animal

 

1.当首次创建型为Mouse4j的对象时,Java解释器查找类路径,定位Mouse4j.class文件。


2.Java解释器会根据Mouse4j.class定位其基类Mouse.class、再根据Mouse.class定位到基类Animal.class文件,有关静态初始化的动作从基类到子类依次执行。 


3.当你用new Mouse4j()创建对象的时候,首先将在堆上为Mouse4j对象(包括其基类Mouse和Animal中的域)分配足够的存储空间。 (这里要注意:在堆上的子类对象是拥有其基类的域的!)


4.这块存储空间会被清零,这就自动地将Mouse4j中的所有基本类型数据(包括其基类MouseAnimal中的)设置成了默认值(对数字来说就是0,对布尔型和字符型也相同),而引用(包括其基类MouseAnimal中的)则被置成了null


5.执行基类Animal中所有出现于域定义处的初始化动作。


6.执行基类Animal构造器。


7.执行基类Mouse中所有出现于域定义处的初始化动作。


8.执行基类Mouse构造器。


9.执行子类Mouse4j中所有出现于域定义处的初始化动作。


10.执行子类Mouse4j构造器。

 


即:class是从子类到基类依次查找,有关静态初始化的动作从基类到子类依次执行。 

在为所创建对象的存储空间清零后,找到继承链中最上层的基类,执行ab两步:
a.执行其出现在域定义处的初始化动作
b.然后再执行其构造器
然后从基类到子类依次执行这两步操作。

 

2.关于静态块什么时候初始化的问题:

博客地址:

http://www.cnblogs.com/ivanfu/archive/2012/02/12/2347817.html

 

 

java的static块执行时机 

之前一直认为static块是在class load的时候执行,今天在验证Spring初始化Context loader的时候,发现bean的static块并没有执行。

Java代码:

1 Class A{   
2    static{
3       System.out.println("static block invoked!")
4    }
5 }

 

那么static块到底在什么时候运行的呢?如果了解JVM原理,我们知道,一个类的运行分为以下步骤:

1. 装载

2. 连接

3. 初始化

    其中装载阶段又三个基本动作组成

1.     通过类型的完全限定名,产生一个代表该类型的二进制数据流

2.     解析这个二进制数据流为方法区内的内部数据结

3.     构创建一个表示该类型的java.lang.Class类的实例

    另外如果一个类装载器在预先装载的时遇到缺失或错误的class文件,它需要等到程序首次主动使用该类时才报告错误。

 

    连接阶段又分为三部分

1. 验证,确认类型符合Java语言的语义,检查各个类之间的二进制兼容性(比如final的类不用拥有子类等),另外还需要进行符号引用的验证。

2. 准备,Java虚拟机为类变量分配内存,设置默认初始值。

3. 解析(可选的) ,在类型的常量池中寻找类,接口,字段和方法的符号引用,把这些符号引用替换成直接引用的过程。

   

  当一个类被主动使用时,Java虚拟就会对其初始化,如下六种情况为主动使用

1. 当创建某个类的新实例时(如通过new或者反射,克隆,反序列化等)

2. 当调用某个类的静态方法时

3. 当使用某个类或接口的静态字段时

4. 当调用Java API中的某些反射方法时,比如类Class中的方法,或者java.lang.reflect中的类的方法时

5. 当初始化某个子类时

6. 当虚拟机启动某个被标明为启动类的类(即包含main方法的那个类)

    Java编译器会收集所有的类变量初始化语句和类型的静态初始化器,将这些放到一个特殊的方法中:clinit。 

 
实际上,static块的执行发生在“初始化”的阶段。初始化阶段,jvm主要完成对静态变量的初始化,静态块执行等工作。

下面我们看看执行static块的几种情况:

1、第一次new A()的过程会打印"";因为这个过程包括了初始化

2、第一次Class.forName("A")的过程会打印"";因为这个过程相当于Class.forName("A",true,this.getClass().getClassLoader());

3、第一次Class.forName("A",false,this.getClass().getClassLoader())的过程则不会打印""。因为false指明了装载类的过程中,不进行初始化。不初始化则不会执行static块。

 参考资料:深入Java虚拟机 

 3.static的优点和缺点:

 

http://blog.csdn.net/lingzhou1/article/details/8476709

 

 

  static:关键字是一个修饰符。用于修饰成员(成员变量 和成员函数)。

    特点:

       1.想要实现对象中的共性数据的对象共享。可以讲这个数据进行静态修饰。

       2.被静态修饰的成员,可以直接被类名所调用。也就是说,静态的成员多了一种调用方式。类名.静态方式。

       3.静态随着类的加载而加载。而且优先于对象存在。 

     弊端:

      1.有些数据是对象特有的数据,是不可以被静态修饰的。因为那样的话,特有数据会变成对象的共享数据。这样对事物的描述就出了问题。所以,在定义静态时,必须要明确,这个数据是否是被对象所共享的。

      2.静态方法只能访问静态成员,不可以访问非静态成员。

        因为静态方法加载时,优先于对象存在,所以没有办法访问对象中的成员。

     3.静态方法中不能使用this,super关键字。

       因为this代表对象,而静态在时,有可能没有对象,所以this无法使用。

     4.主函数是静态的。

      什么时候定义静态成员呢?

     成员分两种:

     1.成员变量。(数据共享时静态化)

      该成员变量的数据是否是所有对象都一样;

     如果是,那么该变量需要被静态修饰,因为是共享数据。如果不是,那么就说这是对象的特有数据,要存储到对象中。

     2.成员函数。(方法中没有调用特有数据时就定义成静态)

     如何判断成员函数是否需要被静态修饰呢?

     只要参考,该函数内是否访问了对象中的特有数据。如果有访问特有数据,那方法不能被静态修饰。如果没有访问过特有数据,那么这个方法需要被静态修饰。

 

     静态的生命周期很长。静态代码块就是一个有静态关键字标示的一个代码区域。定义在类中。完成类的初始化。静态代码块随着类的加载而执行,而且只执行一次(new多个对象就只执行一次)。如果和主函数在同一个类中,优先于主函数执行。

    主函数的解释:保证所在类的独立运行。是程序的入口。被JVM调用。

    静态代码块、构造代码块、构造函数同时存在时的执行顺序:

   静态代码块--构造代码块--构造函数。

 

4.Java内存分配的问题:

 

http://blog.csdn.net/yangyuankp/article/details/7651251

 

 

 

 

         l  寄存器:JVM内部虚拟寄存器,存取速度非常快,程序不可控制。

         l  栈:保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,即堆区对象的引用(指针)。也可以用来保存加载方法时的帧。

         l  堆:用来存放动态产生的数据,比如new出来的对象注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。

         l  常量池:JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用(1)。池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。常量池存在于堆中

总结常量值:在堆中,保存了除float之外的基本类型和String类型,所有的对其他类型、方法、字段的符号引用。

         l  代码段:用来存放从硬盘上读取的源程序代码。

         l  数据段:用来存放static定义的静态成员。

 

下面是内存表示图:

 

 

 

 

         上图中大致描述了Java内存分配,接下来通过实例详细讲解Java程序是如何在内存中运行的(注:以下图片引用自尚学堂马士兵老师的J2SE课件,图右侧是程序代码,左侧是内存分配示意图,我会一一加上注释)。

 

预备知识:

 

         1.一个Java文件,只要有main入口方法,我们就认为这是一个Java程序,可以单独编译运行。

         2.无论是普通类型的变量还是引用类型的变量(俗称实例),都可以作为局部变量,他们都可以出现在栈中。只不过普通类型的变量在栈中直接保存它所对应的值,而引用类型的变量保存的是一个指向堆区的指针,通过这个指针,就可以找到这个实例在堆区对应的对象。因此,普通类型变量只在栈区占用一块内存,而引用类型变量要在栈区和堆区各占一块内存。

 

示例:

 

         1.JVM自动寻找main方法,执行第一句代码,创建一个Test类的实例,在栈中分配一块内存,存放一个指向堆区对象的指针110925。

         2.创建一个int型的变量date,由于是基本类型,直接在栈中存放date对应的值9。

         3.创建两个BirthDate类的实例d1、d2,在栈中分别存放了对应的指针指向各自的对象。他们在实例化时调用了有参数的构造方法,因此对象中有自定义初始值。

 

         调用test对象的change1方法,并且以date为参数。JVM读到这段代码时,检测到i是局部变量,因此会把i放在栈中,并且把date的值赋给i。

 

         把1234赋给i。很简单的一步。

 

         change1方法执行完毕,立即释放局部变量i所占用的栈空间。

 

         调用test对象的change2方法,以实例d1为参数。JVM检测到change2方法中的b参数为局部变量,立即加入到栈中,由于是引用类型的变量,所以b中保存的是d1中的指针,此时b和d1指向同一个堆中的对象。在b和d1之间传递是指针。

 

         change2方法中又实例化了一个BirthDate对象,并且赋给b。在内部执行过程是:在堆区new了一个对象,并且把该对象的指针保存在栈中的b对应空间,此时实例b不再指向实例d1所指向的对象,但是实例d1所指向的对象并无变化,这样无法对d1造成任何影响。

 

         change2方法执行完毕,立即释放局部引用变量b所占的栈空间,注意只是释放了栈空间,堆空间要等待自动回收。

 

         调用test实例的change3方法,以实例d2为参数。同理,JVM会在栈中为局部引用变量b分配空间,并且把d2中的指针存放在b中,此时d2和b指向同一个对象。再调用实例b的setDay方法,其实就是调用d2指向的对象的setDay方法。

 

         调用实例b的setDay方法会影响d2,因为二者指向的是同一个对象。

 

         change3方法执行完毕,立即释放局部引用变量b。

 

         以上就是Java程序运行时内存分配的大致情况。其实也没什么,掌握了思想就很简单了。无非就是两种类型的变量:基本类型和引用类型。二者作为局部变量,都放在栈中,基本类型直接在栈中保存值,引用类型只保存一个指向堆区的指针,真正的对象在堆里。作为参数时基本类型就直接传值,引用类型传指针。

 

小结:

 

         1.分清什么是实例什么是对象。Class a= new Class();此时a叫实例,而不能说a是对象。实例在栈中,对象在堆中,操作实例实际上是通过实例的指针间接操作对象。多个实例可以指向同一个对象。

         2.栈中的数据和堆中的数据销毁并不是同步的。方法一旦结束,栈中的局部变量立即销毁,但是堆中对象不一定销毁。因为可能有其他变量也指向了这个对象,直到栈中没有变量指向堆中的对象时,它才销毁,而且还不是马上销毁,要等垃圾回收扫描时才可以被销毁。

         3.以上的栈、堆、代码段、数据段等等都是相对于应用程序而言的。每一个应用程序都对应唯一的一个JVM实例,每一个JVM实例都有自己的内存区域,互不影响。并且这些内存区域是所有线程共享的。这里提到的栈和堆都是整体上的概念,这些堆栈还可以细分。

         4.类的成员变量在不同对象中各不相同,都有自己的存储空间(成员变量在堆中的对象中)。而类的方法却是该类的所有对象共享的,只有一套,对象使用方法的时候方法才被压入栈,方法不使用则不占用内存。

 

         以上分析只涉及了栈和堆,还有一个非常重要的内存区域:常量池,这个地方往往出现一些莫名其妙的问题。常量池是干嘛的上边已经说明了,也没必要理解多么深刻,只要记住它维护了一个已加载类的常量就可以了。接下来结合一些例子说明常量池的特性。

 

预备知识:

 

         基本类型和基本类型的包装类。基本类型有:byte、short、char、int、long、boolean。基本类型的包装类分别是:Byte、Short、Character、Integer、Long、Boolean。注意区分大小写。二者的区别是:基本类型体现在程序中是普通变量,基本类型的包装类是类,体现在程序中是引用变量。因此二者在内存中的存储位置不同:基本类型存储在栈中,而基本类型包装类存储在堆中。上边提到的这些包装类都实现了常量池技术,另外两种浮点数类型的包装类则没有实现。另外,String类型也实现了常量池技术。

 

实例:

 

[java] view plain copy print?

1. public class test {  

2.     public static void main(String[] args) {      

3.         objPoolTest();  

4.     }  

5.   

6.     public static void objPoolTest() {  

7.         int i = 40;  

8.         int i0 = 40;  

9.         Integer i1 = 40;  

10.         Integer i2 = 40;  

11.         Integer i3 = 0;  

12.         Integer i4 = new Integer(40);  

13.         Integer i5 = new Integer(40);  

14.         Integer i6 = new Integer(0);  

15.         Double d1=1.0;  

16.         Double d2=1.0;  

17.           

18.         System.out.println("i=i0\t" + (i == i0));  

19.         System.out.println("i1=i2\t" + (i1 == i2));  

20.         System.out.println("i1=i2+i3\t" + (i1 == i2 + i3));  

21.         System.out.println("i4=i5\t" + (i4 == i5));  

22.         System.out.println("i4=i5+i6\t" + (i4 == i5 + i6));      

23.         System.out.println("d1=d2\t" + (d1==d2));   

24.           

25.         System.out.println();          

26.     }  

27. }  

public class test {

    public static void main(String[] args) {    

        objPoolTest();

    }

 

    public static void objPoolTest() {

        int i = 40;

        int i0 = 40;

        Integer i1 = 40;

        Integer i2 = 40;

        Integer i3 = 0;

        Integer i4 = new Integer(40);

        Integer i5 = new Integer(40);

        Integer i6 = new Integer(0);

        Double d1=1.0;

        Double d2=1.0;

        

        System.out.println("i=i0\t" + (i == i0));

        System.out.println("i1=i2\t" + (i1 == i2));

        System.out.println("i1=i2+i3\t" + (i1 == i2 + i3));

        System.out.println("i4=i5\t" + (i4 == i5));

        System.out.println("i4=i5+i6\t" + (i4 == i5 + i6));    

        System.out.println("d1=d2\t" + (d1==d2));

        

        System.out.println();        

    }

}

 

结果:

[plain] view plain copy print?

1. i=i0    true  

2. i1=i2   true  

3. i1=i2+i3        true  

4. i4=i5   false  

5. i4=i5+i6        true  

6. d1=d2   false  

i=i0    true

i1=i2   true

i1=i2+i3        true

i4=i5   false

i4=i5+i6        true

d1=d2   false

 

结果分析:

 

         1.i和i0均是普通类型(int)的变量,所以数据直接存储在栈中,而栈有一个很重要的特性:栈中的数据可以共享。当我们定义了int i = 40;,再定义int i0 = 40;这时候会自动检查栈中是否有40这个数据,如果有,i0会直接指向i的40,不会再添加一个新的40。

         2.i1和i2均是引用类型,在栈中存储指针,因为Integer是包装类。由于Integer 包装类实现了常量池技术,因此i1、i2的40均是从常量池中获取的,均指向同一个地址,因此i1=12。

         3.很明显这是一个加法运算,Java的数学运算都是在栈中进行的Java会自动对i1、i2进行拆箱操作转化成整型,因此i1在数值上等于i2+i3。

         4.i4和i5 均是引用类型,在栈中存储指针,因为Integer是包装类。但是由于他们各自都是new出来的,因此不再从常量池寻找数据,而是从堆中各自new一个对象,然后各自保存指向对象的指针,所以i4和i5不相等,因为他们所存指针不同,所指向对象不同。

         5.这也是一个加法运算,和3同理。

         6.d1和d2均是引用类型,在栈中存储指针,因为Double是包装类。但Double包装类没有实现常量池技术,因此Doubled1=1.0;相当于Double d1=new Double(1.0);,是从堆new一个对象,d2同理。因此d1和d2存放的指针不同,指向的对象不同,所以不相等。

 

小结:

 

         1.以上提到的几种基本类型包装类均实现了常量池技术,但他们维护的常量仅仅是【-128至127】这个范围内的常量,如果常量值超过这个范围,就会从堆中创建对象,不再从常量池中取。比如,把上边例子改成Integer i1 = 400; Integer i2 = 400;,很明显超过了127,无法从常量池获取常量,就要从堆中new新的Integer对象,这时i1和i2就不相等了。

         2.String类型也实现了常量池技术,但是稍微有点不同。String型是先检测常量池中有没有对应字符串,如果有,则取出来;如果没有,则把当前的添加进去。

       

 

         凡是涉及内存原理,一般都是博大精深的领域,切勿听信一家之言,多读些文章。我在这只是浅析,里边还有很多猫腻,就留给读者探索思考了。希望本文能对大家有所帮助!

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值