5. 面向对象编程(中)
文章目录
5.1面向对象特征之二:继承性
为什么要继承?将多个类中相同的属性和行为抽象出来作为父类,使子类也具有相同的属性和行为。继承减少了代码冗余,提高代码复用性
5.1.1 类继承语法
class Student extend Person{ }
Student是Person子类,Student继承Person
5.1.2 继承作用
减少了代码冗余,提高代码复用性
利于功能的扩展(只需要在父类中扩展,子类相应的功能也得到扩展)
继承的出现让类与类之间产生了关系,提供了多态的前提
5.1.3 继承的使用
子类继承父类,就相应的继承了父类的属性和方法
继承用extend,可见子类是父类的扩展
父类中私有属性和方法在子类中存在但不可见!但可以通过子类中可见的方法访问子类中private属性和方法。
Java只支持单继承和多层继承,不允许多重继承(也就是减少了代码冗余,提高代码复用性。可以用接口来实现多继承)
多重继承:
多层继承:(直接继承和间接继承)
单继承和多继承举例:
5.2 方法的重写(override/overwrite)
子类根据需要对从父类中继承来的方法进行改造,称为方法重写。在程序执行时,子类的方法将覆盖父类的方法,这在后续框架开发打下基础!
(重载是类允许存在多个形参列表不同的重名方法)
重写注意事项:相同方法名+不大于父类的返回值类型和异常类型+不小于父类访问权限
子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
- 父类被重写的方法的返回类型是void,则子类重写的方法的返回值类型只能是void
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
- 父类被重写的方法的返回值是基本数据类型,则子类重写的方法也必须是相同的基本数据类型.(没有自动类型提升)
子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限(换句话就是不能降低重写方法的可见性!(多态基础捏,合理规定)
子类不能重写父类中声明为private权限的方法
子类重写方法抛出的异常类型不能大于父类被重写方法抛出的异常类型
子类与父类中同名同参数的方法必须同时声明为非static的才视为重写,如果子父类中同名同参数的方法为static,语法上允许,调用的时候按照调用对象声明类型中定义的方法执行,具体案例如下。因为static方法是属于类的,子类无法覆盖父类的方法。
public class Person { static void add() { System.out.println("Person.add()"); } } public class Student extends Person{ static void add() { System.out.println("Student.add()"); } public static void main(String[] args) { Student stu = new Student(); stu.add();//调用的是Student中的add Person per = new Student(); per.add();//调用的是Person中的add } }
案例分析:
class Student extends Person {
public void eat()
{
System.out.println("Student.eat()");
}
}
class Person {
public void eat()
{
System.out.println("Person.eat()");
}
public void walk()
{
System.out.println("Person.walk()");
eat();
}
}
public class OverwriteTest
{
public static void main(String[] args)
{
Student s = new Student();
s.walk();
Person p =new Person();
p.walk();
}
}
这个的执行结果一样嘛?不一样
walk()里面eat(),什么时候指的是Person类的eat()什么时候执行的是Student类里面的eat()?
答案:Person对象调用walk()时对应Person的eat(),Student对象调用walk()时对应的Student重写的eat(),重写了嘛!
5.3 四种权限修饰符
Java权限修饰符public、protected、 (缺省)、 private置于类的成员定义前,用来限定对象对该类成员的访问权限。
对于class的权限修饰只可以用public和default(缺省)。
public类可以在任意地方被访问。
default类只可以被同一个包内部的类访问。
所以同一个包是不允许出现同名类的!重定义!
5.4 super关键字
使用super关键字来调用父类中的属性和行为,一般来说我们调用父类属性方法时直接省略了,super和this都是类内使用的关键字,类外禁止使用。this在方法中指代调用方法的对象,在构造器中指代正在创建的对象。而super则用于调用父类的属性和方法,我们通常在以下情况使用super:
子父类中出现同名的成员(属性和方法)时,可以使用super关键字表明调用的是父类的成员(属性没有重写这一说,只有方法才有重写)
值得注意super关键字的追溯不仅限于直接父类,先去直接父类找,没找到,再去间接父类一次向上找。此外**super和this的用法很像,this代表本类对象的引用,super代表父类的内存空间标识**。
this和super区别:
- 在调用属性和方法上,this就是先从本类中找,找不到去父类找。super是直接去父类找
- 在调用构造器方面this只能调用本类构造器,super只能调用父类构造器
在子类的构造器中显示使用super(形参列表)来调用父类中指定的构造器
子类中所有的构造器都会默认访问父类中空参数的构造器
子类的构造器首行没有显示声明this(形参列表)–调用自己类内的构造器和super(形参列表)—调用的是父类的构造器,则默认使用的是父类中空参的构造器,也就是super()
如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错(无论子父类,创建对象就要调用构造器)。也就是子类中必须要明确调用父类中哪种构造器
创建对象就要调构造器,子类创建对象,先创建父类对象,所以必须指明父类的构造器,没有指明?那就调用父类默认的构造器(空参),没有空参构造器?就报错。所以要么指明调用父类的哪个构造器,要么提供默认构造器共父类调用
当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)或者super(参数列表)语句指定调用本类或者父类中相应的构造器。同时,只能”二选一”,且必须放在构造器的首行
每个类中至少由一个构造器使用了super(形参列表)。Object作为根父类,Object()一定是会调用的
this和super区别
- 在调用属性和方法上,this就是先从本类中找找不到去父类找。super是直接去父类找
- 在调用构造器方面this只能调用本类构造器,super只能调用父类构造器
5.5 子类对象实例化
- 子类对象实例化的全过程
从结果上看(继承性)
子类继承父类以后,就获得了父类中声明的属性或方法。创建子类的对象,在堆空间就会加载所有父类中声明的属性。
从过程上看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用器其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器Object()位置。(虽然创建子类对象时,调用了父类的构造器,但是至始至终就创建了一个对象,即为子类对象)
5.6 面向对象特征之三:多态性
对象的多态性:父类的引用指向子类的对象(可以直接引用在抽象类和接口上),因为某种程度上子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。
**Java引用变量有两个类型:编译时类型和运行时类型。**编译时类型由声明该变量的类型决定,运行时类型由实际赋给该变量的对象决定。
若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism[ˌpɒlɪ’mɔ:fɪzəm])。多态情况下,
编译时:要查看引用变量所声明的类中是否有所调用的方法。(不允许父类声明的变量引用子类对象,还直接调用子类中独有的方法)
运行时:调用实际new的对象所属的类中的重写方法。
多态前提:继承或者实现关系+方法的重写(多态是指的方法多态,可没成员变量什么事儿呢)
多态性使用
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量,而只能使用父类本身部分的属性和方法(否则编译不过,编译看的是编译类型)。
Student m = new Student(); m.school = “pku”; //合法,Student类有school成员变量 Person e = new Student(); e.school = “pku”; //非法,Person类没有school成员变量
Person e = new Student();
该语句引用类型变量类型是Person,但由于调用的构造函数时Student(),堆空间中是加载有子类特有的属性和方法,e指向这一片空间。但编译时,只能调用父类中的属性和方法。子类特有的属性和方法不能调用。多态性作用:提高了代码的通用性,常称作接口重用
5.6.1虚拟方法调用
虚拟方法调用(就是多态情况下调用方法)就是父类引用调用指向不同子类对象的重写方法
子类中定义了与父类同名同参数的方法(重写),在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用子类的同名方法方法,就叫虚拟方法调用。这样的方法调用在编译期是无法确定的,我们也将这样的行为称为运行时行为。
Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。——动态绑定
5.6.2 深入方法重载与重写
定义上:
重写是子类对父类中继承来的方法进行改造。是虚拟方法调用的基础,体现的是多态性
重载是类允许存在多个形参列表不同的重名方法
从编译和运行的角度看:
重载: 类允许存在多个形参列表不同的重名方法。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”(编译时行为);**
多态(重写),只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”(运行时行为)。
引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”
返回值类型看
重载可以改变返回值类型,但重写(覆盖)是要求子类重写的方法返回值类型不得大于父类返回值类型(也就是子父类不允许出现名字相同,形参相同但返回类型不同的函数)
5.6.3 instanceof操作符
x instanceof A:测验x是否是A类的实例,返回值为boolean的数据类型。
补充说明
- 若x是A类或A类的子类,x instanceof A值即为true(也就是说A是x的类型或者是x的父类,均返回true)
- instanceof要求左边是对象,右边是类(null用instanceof跟任何类型比较时都是false)
- instanceof左边显式声明的类型与右边操作元必须是同种类或存在继承关系,也就是说需要位于同一个继承树,否则会编译错误(也就是说必须时同一个继承树下才能使用instanceof)
5.6.4 对象类型转换(Casting)
对象类型转化向上转型就是多态不用太关注,向下转型是给多态留的后路,目的是让指向子类对象的父类引用能够转化为子类引用(还他原来的地位)。而不是是实实在在的将父类对象(此处父类对象没有多态)转化成子类对象
注意这里的父类转化为子类并非我们理解的直接的将父类类型转化为子类类型,而是将指向子类对象的父类引用转化为子类引用(还他原本的地位),目的就是使用子类中的属性和方法
基本数据类型的类型转换,有自动类型提升和强制类型转换。在类对象里也存在这样的类型转换,分别是向上转型(多态)和向下转型(造型,也叫强制类型转换,让指向子类对象的父类引用转化为子类引用)。
基本数据据类型的强制类型转化没有强制要求,但类类型的父类强制类型转换成子类是有要求的,判断是否能类类型强转的方法下面再讲。
基本数据类型的转换(Casting)
自动类型转换:小的数据类型可以自动转换成大的数据类型如
long g=20; double d=12.0f
强制类型转换:可以把大的数据类型强制转换(casting)成小的数据类型如
float f=(float)12.0; int a=(int)1200L
类类型转换(Casting)
多态:父类的引用指向子类的对象,子类到父类的类型转换自动进行
造型(类类型强制类型转换):父类到子类的类型转换需要强制类型转换(考虑到多态,这里的类型指的是引用变量声明的类型,并非在堆空间开辟的类型)
- 非法强制类型转换编译虽然不会报错,但运行会报错ClassCastException类类型转换异常,我们可以在强制类型转换前用instanceof来进行判断,instanceof判断为true一定可以进行强制类型转换,所以所有的类都能强制类型转换成Object类。因为Object是根父类!
判断是否能强制类型转换方法
x instanceof A,如果返回true,那么x就能强转为A类型(看下面的技巧,无敌)
判断是否可以进行强制类型转换有诀窍。首先必须在同一继承树下,其次只要强制类型转换的目标类型不超过对象在堆空间实际大小,就能进行强制类型转换。
满足这两个条件,变量引用类型往大了转换,就叫强制类型转换;变量引用类型往小了转换,就叫多态。
//Person是Women父类 Object obj = new Women(); Person p = (Person)obj;//正确 //Person类不过是Women类的一部分罢了,往小转ok的
“愚蠢的”编译器(毫不相干的两个类之间转化,因为以Object类为桥梁,等式两边都在统一继承树下,骗过的编译器)
String str = new Date();//错误 两个类型好不相干
Object o = new Date();
String str1 = (String)o;//编译能过 运行可过不了 报错:ClassCastException
5.6.5 子父类同名的变量与方法区别
变量没有多态,方法才有多态
若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法。(当然在子类中可以通过super关键字调用父类的方法)
对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
用案例理解,子父类中同名的变量和方法
class Base {
int count = 10;
public void display() {
System.out.println(this.count);
}
}
class Sub extends Base {
int count = 20;
public void display() {
System.out.println(this.count);
}
}
public class PolymorphismTest {
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.count);
s.display();
Base b = s;
System.out.println(b == s);
System.out.println(b.count);//编译时行为
b.display();
}
}
答案:20 20 true 10 20,
记住一点,多态用父类引用指向子类对象,调用成员时,还是回到声明的类型里面找成员!找成员变量没有争议的,父类成员不会被覆盖。找方法,如果有重写,那么子类的方法会覆盖父类的方法,所以调用的时子类的方法。看下面的案例:
//考查多态的笔试题目:
public class InterviewTest1 {
public static void main(String[] args) {
Base1 base = new Sub1();
base.add(1, 2, 3);
Sub1 s = (Sub1)base;
s.add(1,2,3);
}
}
class Base1 {
public void add(int a, int... arr) {
System.out.println("base");
}
}
class Sub1 extends Base1 {
public void add(int a, int[] arr) {
System.out.println("sub_1");
}
public void add(int a, int b, int c) {
System.out.println("sub_2");
}
}
输出结果:sub_1 sub_2
字符类中是否重写?那两个重写?
父类add(int a, int… arr)和子类add(int a, int[] arr)重写。可变个数形参和相应数据类型的数组再编译器看来时同种数据类型,所以这里构成重写。
为什么base.add(1, 2, 3)没有调用子类中三个形参的add?
不是说优先匹配形参个数确定的函数吗?挺搞笑的。前面说过,要弄清多态时如何形成的,调用成员的时候,是返回声明的数据类型中找!声明中匹配的是add(int a, int… arr),但它被子类add(int a, int[] arr)重写,所以调用的是add(int a, int[] arr),输出sub_1。与之对应的s.add(1,2,3);,再返回声明的对象中优先直接匹配三个参数的add,所以直接输出sub_2。
5.7 Object类的使用
Object类是所有Java类的根父类。如果在类的声明中未使用extends指明其父类,则默认父类为java.lang.Object类。
Object只有一个构造器就是无参构造器Object()
5.7.3 clone()
5.7.2 equals()
面试题==和equals()的区别:
==是运算符,用于比较基本数据类型和引用数据类型
- 比较基本数据类型变量存储的数值是否相等(类型不同自动类型提升,所以boolean只能和自己类型比较)
- 比较引用数据类型变量地址值是否相等,也就是两个引用是否指向同一个对象实体。
String name1 = "lin"; String name2 = new String("lin"); String name3 = "lin"; System.out.println(name1==name2); System.out.println(name1==name3);
输出结果:false true(两种方式早String有区别的)
- equals() 方法(根父类Object中定义的equals作用同==,部分常用类如String,List等等重写了该方法,比较内容相等)下面具体讲
equals() 方法(根父类Object中定义好的)
Object类中定义的equals定义和==作用相同,对应的源码:
public boolean equals(Object obj) { return (this == obj); }
但像String、Data、File、包装类等都重写了Object类中的equals()方法,重写后的方法比较的不再是地址值,而是比较内容是否相等。如果我们希望自定义类也比较具体的内容,可以对equals()进行重写,可以自己手动重写,也能自动生成。操作:按下ALT+S快捷键,弹出:
这里看看String的equals源代码
public boolean equals(Object anObject) {
if (this == anObject) {//考虑同引用变量的问题
return true;
}
if (anObject instanceof String) {//考虑多态的问题,可能传进来的是父类的引用指向String
String anotherString = (String)anObject;
int n = value.length;//String底层是value数组实现的
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
//注意看源码,String很特殊,因为String不能成为其他类的父类(final),所以再比较类的时候用的是anObject instanceof String,只要为真,那么就一定是同一个类型了
//但如果自己重写equals的时候用getclass()==obj.getclass()这样才能准确的判断是否是同一个类,此外还有很多细节,所以推荐系统自动生成的equals,健壮性更好。
此外比较String的内容是否相等的方式有两种:equals和equalslgnoreCase两种方法
值得注意:引用数据类型有类,数组,接口。Object也是数组的根父类,所以一定程度上可以认为数组也是一种特殊的类(你用数组调用equals方法,你会发现对应的就是Object中的equals方法,比较地址),所以可以用Object指向数组
System.out.println(array.toString());//[I@7cef4e59
System.out.println(array.getClass());//class [I
System.out.println(array.getClass().getSuperclass());//class java.lang.Object
5.7.3 toString()
当我们输出一个对象的引用的时候,实际上就是调用当前对象的toString()。
Object类中的toString()方法return getClass().getName() + ‘@’ + Integer.toHexString(hashCode())
getClass().getName()获得类名,Integer.toHexString(hashCode())通过hashCode值计算其再堆空间存储的位置并转化为十六进制,最后用String的方法链接输出。(返回类名和它的引用地址。 )
但像String、Date、File、包装类等都重写了Object类中的toString()方法,调用时输出对象内容。对于自定义类希望也输出对象内容,可以进行重写。可以手动重写,也能自动生成
前面说过,当我们输出一个对象的引用的时候,实际上就是调用当前对象的toString()。一般来说如此,但特殊的情况另说
String name = "lin"; System.out.println(name);//lin System.out.println(name.toString());//lin name=null; System.out.println(name);//null System.out.println(name.toString());//NullPointerException空指针异常
//因为println内部有空指针保护源码,非空才调用toString()
5.7.4 JUnit单元测试
eclipse中步骤:
选中当前工程,右键选择:build path - add libraries - JUnit 4 下一步
创建Java类,进行单元测试
此时Java类要求:此类是public,此类提供公共的无参的构造器
此类中声明单元测试方法权限是public,没有返回值没有形参
此单元测试的方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
声明好单元测试方法以后,就可以在方法体内测试相关代码
写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test
也可以上来直接@Test,然后Alt+/+回车
说明:
- 如果执行结果没有异常:绿条。
- 如果执行结果异常:红条
5.7.5 包装类Wrapper
java一切皆对象,因此Java提供了八种基本数据类型对应的包装类,使得基本数类型的变量具有类的特征。 (就是让基本数类型作为属性出现在包装类中,封装一些方法)
数值型包装类父类Number
基本数据类型、包装类、String三者之间的相互转换
- 基本数据类型和包装类有自动装箱和自动拆箱,互通
- 基本数据类型转为String,用String.valueOf
- 包装类转String,用toString
- 剩余String转基本数据类型或包装类,统一用包装类的构造器
剩下的方式就不记了
基本数据类型->包装类:调用包装类的构造器
(基本数据类型包装成包装类的过程叫装箱)
Character包装类提供唯一构造器
Float包装类提供三个构造器
其余六种包装类均提供两个构造器,String形参的构造器和包装类对应的基本数据类型形参的构造器。
注意一般来说String形参的构造器要求是传进来的形参必须是包装类对应的基本数据类型的String。比如
Integer i = new Integer("123abc"); //错误要求必须是int对应的String
这里强调Boolean包装类。
Boolean包装类的构造器进行了优化,其String的构造器传进来的String,在不区分大小写的情况下,和String"true"比对,相等就就返回true,其余的都返回false
额外留意:
boolean b;//默认值是false Boolean B;//默认值是null
包装类很多构造器在新版本已弃用,JDK5.0以后允许出现自动装箱和自动拆箱,就不需要调用这些构造函数了。
包装类->基本数据类型:调用包装类的xxxValue(),这个过程叫拆箱
Integer in1 = new Integer(12); int i = in1.intValue();
基本数据类型->String类型:调用String重载的valueOf()
方法一:
int num1 = 10; String str = num1 + "";//巧妙利用+
方式二:调用String重载的valueOf(xxx xxx)
float f1 = 12.3f; String str2 = String.valueOf(f1); //valueOf(基本数据类型)
包装类->String类型:调用String重载的valueOf()
Double d = 19.5; String str2 = String.valueOf(d);//valueOf(Object)
String类型->基本数据类型或包装类:调用包装类的方法parseXxx(String s)
string str = "123"; int num1 = (int)str;//错误写法 Integer in1=(Integer)str;//instanceof为真才能转换,首先得有字符类关系才能进行类类型转换
正确写法:
String str = "123"; int num = Inter.parseInt(str);
总结一句,要转成String的就用String重载的valueOf()。String要转成基本数据类型或包装类,就调用包装类的方法parseXxx(String s);
5.7.6 自动装箱与自动拆箱(JDK5.0后)
自动装箱----自动完成基本数据类型包装成包装类
int num = 10; Integer num1 = num;//自动装箱,相当于调用了new Integer(num); int num2 = num1;//自动拆箱相当于调用了num1.intValue();
自动拆箱----自动获得包装类对象中包装的基本类型变量
有了自动装箱和自动拆箱,基本数据类型和包装类就能堪称同一个类型,所以基本数据类型,包装类,String之间的转换只用记得valueOf和parseXxx两个方法与自动装箱和自动拆箱就能实现三类型转换了
类型转换的代码案例
@Test
public void typeConvertTest() {
/**
* 基本数据类型->包装类
* 1.调用包装类的构造器 (一般来说有两个,Character是只有一个,Float是两个)
* 2.实际上可以直接赋值:自动装箱
*/
int num1 = 10;
Integer num2 = new Integer(num1);
Integer num3 = new Integer("10");
Integer num4 = num1;//自动装箱
/**
* 包装类->基本数据类型
* 1.调用包装类的xxxValue();获取包装类中包含的基本数据类型
* 3.实际上可以直接赋值:自动拆箱
*/
Double num5 = new Double(12.6);
double num6 = num5.doubleValue();
double num7 = num5;//自动拆箱
/**
* 基本数据类型->String类
* 1.用+的链接效果基本数据类型+""
* 2.调用String重载的valueOf()
*/
float f1 = 12.6f;
String str1 = f1 + "";
String str2 = String.valueOf(f1);
/**
* String类->基本数据类型
* 1.调用相应包装类的parseXxx(String)静态方法
* 2.通过包装类的String参数的构造器
*/
String str3 = "123";
int num8 = Integer.parseInt(str3);
int num9 = new Integer(str3);//自动拆箱
/**
* String类->包装类
* 1.调用包装类中String形参的构造器
*/
String str4 = "45678";
Integer num10 = new Integer(str4);
/**
* 包装类->String类
* 1.调用包装类重载过的的toString()方法
*/
Integer num11 = new Integer(123);
String str5 = num11.toString();
5.8 面试题
输出结果?
Object o1 = true ? new Integer(1) : new Double(2.0); System.out.println(o1);
输出结果为1.0,三目运算符要求类型一致,Integer和Double类型统一后就是double了,所以就是1.0
输出结果?
Integer i = new Integer(1); Integer j = new Integer(1); System.out.println(i == j); Integer m = 1; Integer n = 1; System.out.println(m == n);// Integer x = 128; Integer y = 128; System.out.println(x == y);//
输出结果:false true false
Integer内部定义了一个静态类
/** * Cache to support the object identity semantics of autoboxing for values between * -128 and 127 (inclusive) as required by JLS. * * The cache is initialized on first usage. The size of the cache * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option. * During VM initialization, java.lang.Integer.IntegerCache.high property * may be set and saved in the private system properties in the * jdk.internal.misc.VM class. * * WARNING: The cache is archived with CDS and reloaded from the shared * archive at runtime. The archived cache (Integer[]) and Integer objects * reside in the closed archive heap regions. Care should be taken when * changing the implementation and the cache array should not be assigned * with new Integer object(s) after initialization. */ private static class IntegerCache { static final int low = -128; static final int high; static final Integer[] cache; static Integer[] archivedCache; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { h = Math.max(parseInt(integerCacheHighPropValue), 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(h, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; // Load IntegerCache.archivedCache from archive, if possible CDS.initializeFromArchive(IntegerCache.class); int size = (high - low) + 1; // Use the archived cache if it exists and is large enough if (archivedCache == null || size > archivedCache.length) { Integer[] c = new Integer[size]; int j = low; for(int i = 0; i < c.length; i++) { c[i] = new Integer(j++); } archivedCache = c; } cache = archivedCache; // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }
意思:Integer内部定义了IntegerCach结构,IntegerCach定义了Integer[],存储-128-127(byte的范围)的整数对应的Integer,在范围之内时们可以直接使用数组中的元素,不用再去new了。主要目的时提高效率。所以上述面试题中第二个true就是因为在范围之内,所以地址相同。第三个范围之外,创建新的空间,地址不同。