【每日一题】基本数据类型与包装类型的面试题

基础知识:

1、java中的基本数据类型

  java是一个近乎纯洁的面向对象编程语音,引入基本数据类型是为了编程方便,但是为了能够将这些基本数据类型当成对象操作,java为每一个基本数据类型引入了对应的包装类型(wrapper class)。具体如下:

  int  ---  Integer

  short ---  Short

  long ---  Long

  double ---  Double

  float  ---  Float

  boolean ---  Boolean

  byte   ---  Byte

  char ---  Character

从java 5 开始引入了自动装箱/拆箱机制,使得两者可以相互转换。

2、基本类型和引用类型的区别。

java中的数据类型分为两大类:基本数据类型和引用数据类型

基本类型的值就是一个数字,一个字符,或者一个布尔值。存放在栈空间中,未初始化时为随机值。

引用类型是一个对象类型,值是指向内存空间的引用,就是地址。所指向的内存中保存着变量所表示的一个值或一组值。存放在堆空间中,未初始化时由默认的值,比如int未初始化时为0,boolean未初始化时为false。

基本数据类型,包括数值型,字符型和布尔型。

引用数据类型:类、接口类型、数组类型、枚举类型、注解类型

栈的数据大小和生存期都是确定的,特点是其中的数据可以共享,存取速度比对快,仅次于寄存器。主要存放基本类型的变量(byte,short,int,long,float,double,boolean,char)和对象引用。

例如:

int a=1;

int b=1;

以上变量的实现过程可以栈的数据共享,编译器首先处理a的实现,实现过程是先在栈中查找是否存在值为1的空间,如果有就直接将a指向该空间,如果没有就会开辟新空间,并把1存进来,因此,在处理b的实现时就会出现a,b都会指向同一个值为1的空间,实现共享。当修改b的值时,过程还是一样,比如,修改b=2,会查找为2的空间,有则指向,无则,保存。这样就会有利于空间的充分利用。

其中,String不是基本数据类型,是个引用类型,基础类型只表示简单的字符或数字,引用类型可以是任何复杂的数据结构,java虚拟机处理基础类型与引用类型的方式是不一样的,对于基本类型,java虚拟机会为其分配数据类型实际占用的内存空间,而对于引用类型变量,他仅仅是一个指向堆区中某个实例的指针。

如果整型字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象,java中基本类型的包装类的大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用常量池,也即对象不负责创建和管理大于127的这些类的对象。

Java包装类的常量池

 Integer a=Integer.valueOf(100);
Integer b=Integer.valueOf(100);
System.out.println(a==b);
Double d1=Double.valueOf(100);
Double d2=Double.valueOf(100);
System.out.println(d1==d2); 
 
为什么包装类 Ingeter两个值就相等 Double的就不相等了呢

在给Integer赋值时,实际上是自动装箱的过程,也就是调用了Integer.valueOf(int)方法,当这个值大于等于-128并且小于等于127时使用了常量池,所以前两个地址是相等的,但是后两个超过了127,故不使用常量池。

也就是说
Integer -128~127实际上你可以看成是整形int,所以第一个类的输出结果应该是==
Interger 128以上的数值就不能看成int了,他是对象,两个值相同的不同的对象如果用==判断肯定是不等的,可以用equals判断。

 Java的8种基本类型(Byte, Short, Integer, Long, Character, Boolean, Float, Double), 除Float和Double以外, 其它六种都实现了常量池, 但是它们只在大于等于-128并且小于等于127时才使用常量池。 

Java包装类常量池详解  

 

public class IntegerTest { 
        public static void main(String[] args) {     
            objPoolTest(); 
        } 
     
        public static void objPoolTest() { 
            Integer i1 = 40; 
            Integer i2 = 40; 
            Integer i3 = 0;

          

            Integer i4 = new Integer(40); 
            Integer i5 = new Integer(40); 
            Integer i6 = new Integer(0); 
             Integer i7 = 140; 
             Integer i8 = 140;
            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("i7=i8\t" + (i7 == i8);
            System.out.println();         
        } 
    }

i1=i2 true
i1=i2+i3 true
i4=i5 false
i4=i5+i6 true

i7=i8 false

Java为了提高性能提供了和String类一样的对象池机制,当然Java的八种基本类型的包装类(Packaging Type)也有对象池机制。

Integer i1=40;Java在编译的时候会执行将代码封装成Integer i1=Integer.valueOf(40);通过查看Source Code发现:

Integer.valueOf()中有个内部类IntegerCache(类似于一个常量数组,也叫对象池),它维护了一个Integer数组cache,长度为(128+127+1)=256,Integer类中还有一个Static Block(静态块)。

从这个静态块可以看出,Integer已经默认创建了数值【-128-127】的Integer缓存数据。所以使用Integer i1=40时,JVM会直接在该在对象池找到该值的引用。也就是说这种方式声明一个Integer对象时,JVM首先会在Integer对象的缓存池中查 找有木有值为40的对象,如果有直接返回该对象的引用;如果没有,则使用New keyword创建一个对象,并返回该对象的引用地址。因为Java中【==】比较的是两个对象是否是同一个引用(即比较内存地址),i2和i2都是引用 的同一个对象,So i1==i2结果为”true“;而使用new方式创建的i4=new Integer(40)、i5=new Integer(40),虽然他们的值相等,但是每次都会重新Create新的Integer对象,不会被放入到对象池中,所以他们不是同一个引用,输出 false。

对于i1==i2+i3、i4==i5+i6结果为True,是因为,Java的数学计算是在内存栈里操作的,Java会对i5、i6进行拆箱操作,其实比较的是基本类型(40=40+0),他们的值相同,因此结果为True。

好了,我想说道这里大家应该都会对Integer对象池有了更进一步的了解了吧,我在诺诺的问一句如果把40改为400猜猜会输出什么?

i1=i2false i1=i2+i3true i4=i5false i4=i5+i6true 
这是因为Integer i1=400,Integer i2=400他们的值已经超出了常量池的范围,JVM会对i1和i2各自创建新的对象(即Integer i1=new Integer(400)),所以他们不是同一个引用。

问题:如下运行结果是什么?

Long l1 = 128L;
Long 12 = 128L;
System,out,println("l1 == l2");   //1
System,out,println("l1 == 128L");   //2

Long l3 = 127L;
Long l4 = 127L;
System,out,println("l3 == l4");      //3
System,out,println("l3 == 127L");   //4

答:对于注释1 的语句,Long 的常量cache 为 -128 - 127 之间,所以l1 和 l2 变量属于两个对象, == 比较的是对象的地址,所以打印false;  对于注释2 由于l1 由于表达式中 至少有一个不是包装类型,所以其自动拆箱退化为基本数据类型,所以数值比较为true ; 对于注释3 ,由于 Long 包装类型 -128 - 127 之间的=维护值在常量池中,所以l3 和 l4 引用同一个对象,所以为true;注释4同理,为true 。

问:java 是否存在是的 i<j || i<=j结果为false的 i  j 值?

答:存在,java 的 NaN代表not a number ,无法用于比较,简单的说,比较两个int型或long型的数据没有什么问题,可以用==来判断,但对浮点数(float与double)来说,需要对Float.NaN和0.0这个两个特殊数字作额外的处理。
Float.NaN严格说来不是一个数字(它的字面意思也就是Not a Number),但是因为这个值可以被保存在一个float型的变量中(因为它常常是除0的结果),所以暂且当它是个数字吧。但它与一般的浮点数有些许不同,就是两个NaN用==比较的结果会得到false。
可以用下面的代码验证:

float  nan = Float.NaN;
float  anotherNan = Float.NaN;
System.out.println(nan
== anotherNan);

输出结果为false

我用另一种除0的方法得到NaN,可以看到使用==判断仍然得到false。代码如下:
float  overFlow = 0.0f / 0.0f ;
System.out.println(overFlow);
System.out.println(nan
== overFlow);


而当我们使用Float.compare()这个方法来比较两个NaN时,却会得到相等的结果。可以用下面的代码验证:
System.out.println(Float.compare(nan,anotherNan));
System.out.println(Float.compare(nan,overFlow));

compare()方法如果返回0,就说明两个数相等,返回-1,就说明第一个比第二个小,返回1则正好相反。
上面这两行语句的返回结果都是0。
一般来说,基本类型的compare()方法与直接使用==的效果“应该”是一样的,但在NaN这个问题上不一致,是利是弊,取决于使用的人作何期望。当程序的语义要求两个NaN不应该被认为相等时(例如用NaN来代表两个无穷大,学过高等数学的朋友们都记得,两个无穷看上去符号是一样,但不应该认为是相等的两样东西),就使用==判断;如果NaN被看得无足轻重(毕竟,我只关心数字,两个不是数字的东西就划归同一类好了嘛)就使用Float.compare()。

另一个在==和compare()方法上表现不一致的浮点数就是正0和负0(当然这也是计算机表示有符号数字的老大难问题),我们(万能的)人类当然知道0.0f和-0.0f应该是相等的数字,但是试试下面的代码:
float  negZero =- 0.0f ;
float  zero = 0.0f ;
System.out.println(zero
== negZero);
System.out.println(Float.compare(zero,negZero));

返回的结果是true和-1。看到了么,==认为正0和负0相等,而compare()方法认为正0比负0要大。所以对0的比较来说,==是更好的选择。


问:java1.5的自动装箱机制是编译特性还是虚拟机运行时特性?分别是怎么实现的?

答:java1.5开始的自动装箱拆箱机制其实是编译时自动完成替换的,装箱阶段自动替换为了valueOf方法,拆箱阶段自动替换为了xxxValue方法。

对于Integer、Short、Byte、Character、Long类型的valueOf方法,参数如果是-128~127之间的值会直接返回内部缓存池中已经存在对象的引用,参数是其他范围值则返回新建对象

而Double、Float类型与Integer类型类似,一样会调用Double、Float的valueOf方法,但是不管传入的参数值是多少都会new一个对象来表达该数值,因为在某个范围内的整型数值的个数是有限的,而浮点数却不是。

问:

public class Main {
    public static void main(String[] args) {
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Long g = 3L;
        Long h = 2L;
 
        System.out.println(c==(a+b));//1
        System.out.println(c.equals(a+b));//2
        System.out.println(g==(a+b));//3
        System.out.println(g.equals(a+b));//4
        System.out.println(g.equals(a+h));//5
    }
}
输出:
true
true
true
false
true

  1. a拆箱,b拆箱,相加结果为int,c拆箱,int和int相比较.
  2. a拆箱,b拆箱,相加结果为int,方法equals的参数是Object,所以int又要装箱,引用比较,又因为数据3在cache里,所以为true.
  3. a拆箱,b拆箱,相加结果为int,转化为long型,g拆箱,数值相比较,为true.
  4. a拆箱,b拆箱,相加结果为int,方法equals的参数是Object,所以int又要装箱为Integer,Long和Integer比较,所以为false.
  5. a拆箱,b拆箱,相加结果为long,方法equals的参数是Object,所以要装箱为Long,Long和Long相比较,所以为true。

从以上测试中,可以得出以下结论:
当基本数据类型和包装器类型做'=='运算时,包装器类型会拆箱为基本数据类型,再做‘==’运算。

4.问:下面是一组java包装类型、自动拆箱、装箱的题目,请写出运行结果?

Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);//true
System.out.println(i3==i4);//false


Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);//false
System.out.println(i3==i4);//false

//在Boolean中定义了2个静态成员属性
Boolean i1 = false;
Boolean i2 = false;
Boolean i3 = true;
Boolean i4 = true;
System.out.println(i1==i2);//true
System.out.println(i3==i4);//true

Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;
System.out.println(c==d);//true 比较cache
System.out.println(e==f);//false 比较引用
System.out.println(c==(a+b));//true 比较值
System.out.println(c.equals(a+b));//true 比较值
System.out.println(g==(a+b));//true //比较值
System.out.println(g.equals(a+b));//false //比较引用(对于包装器类型,equals方法并不会进行类型转换)
System.out.println(g.equals(a+h));//true比较引用

Integer  a = 444;
int b = 444;
System.out.println(a==b);    //true 比较值
System.out.println(a.equals(b));//true比较值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

答案解析: 
对于两边都是包装类型的比较==比较的是引用,equals比较的是值。 
对于两边有一边是表达式(包含算数运算)则==比较的是数值(自动触发拆箱过程),对于包装类型equals方法不会进行类型转换。


5.问:java语句Integeri=1;i+=1;做了哪些事情?

首先 Integer i = 1; 做了自动装箱(使用 Integer.valueOf() 方法将 int 装箱为 Integer 类型),接着 i += 1; 先将 Integer 类型的 i 自动拆箱成 int(使用Integer.intValue() 方法将 Integer 拆箱为 int),完成加法运行之后的 i 再装箱成 Integer 类型。


6.问:下面程序的运行结果是什么?

Integer i1 = new Integer(127);
Integer i2 = new Integer(127);
System.out.println(i1 == i2);//false
System.out.println(i1.equals(i2));//true
  • 1
  • 2
  • 3
  • 4

注意,new Integer(xxx)这种创建对象的方法不是自动装箱,没有用到cache,因此i1 == i2是false。


7.谈谈Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别。

  1)第一种方式不会触发自动装箱的过程;而第二种方式会触发。 
  2)在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)。

摘自微信公众号:码农每日一题

猜你喜欢

转载自blog.csdn.net/durenniu/article/details/81051402