1.1、数据类型
1.1.1、为什么要设计封装类?Integer和int有什么区别?
答:
Java本身是一门面向对象的语言,所以对象是Java的基础操作单元,很多时候传递数据也需要对象类型,比如ArrayList、HashMap这些集合,只能存储对象类型,所以封装类型的意思就很大。
设计和使用封装类的好处其实也就是封装的好处:
- 第一、安全性更好,可以避免外部操作随意修改成员变量的值,保证了成员变量和数据传递的安全性;
- 第二、隐藏了实现细节,对使用者更加友好,只需要调用对象提供的方法就可以对应的操作;
Integer和int的区别有很多,主要区别比如:
- Integer是类,封装类其实也就是类嘛,属于对象类型,而int是基本数据类型
- Integer的初识值是null,int的初始值是0
- Integer存储在堆内存,int直接存储在栈空间
- Integer是对象类型、内部封装了很多方法和属性,使用的时候更灵活;
扩展:
Integer 是 Java 中封装了基本数据类型 int 的包装类。它提供了以下常用的属性和方法
属性:
- MAX_VALUE:表示 int 类型的最大值(2147483647)
- MIN_VALUE:表示 int 类型的最小值(-2147483648)
方法:
- intValue():将 Integer 对象转换为 int。
- doubleValue():将 Integer 对象转换为 double。
- longValue():将 Integer 对象转换为 long。
- floatValue():将 Integer 对象转换为 float。
- toString():将 Integer 对象转换为字符串。
- parseInt(String s):将字符串解析为一个整数,并返回解析得到的整数值。
Integer i = new Integer(1000);
System.out.println("intValue: " + i.intValue()); // 输出:intValue: 1000
System.out.println("doubleValue: " + i.doubleValue()); // 输出:doubleValue: 1000.0
System.out.println("longValue: " + i.longValue()); // 输出:longValue: 1000
System.out.println("floatValue: " + i.floatValue()); // 输出:floatValue: 1000.0
System.out.println("toString: " + i.toString()); // 输出:toString: 1000
String str = "12345";
int num = Integer.parseInt(str);
System.out.println("Parsed integer: " + num); // 输出:Parsed integer: 12345
除了上述属性和方法,Integer 还可以参与数值比较、相等判断等操作,并且可以通过 Integer.valueOf(int) 方法将一个 int 转换为 Integer 对象。
int类型和Integer类型混合使用时,Java会自动通过拆箱和装箱实现类型转换。
1.1.2、为什么"100 == 100"为true,而"1000 == 1000"为false
问题详述:
Integer a = 100,b = 100,c = 1000,d = 1000;
System.out.println((a == b) + "," + (c == d));
运行结果是true,false
这是为什么呢,这就要对照Integer的源码来分析
public final class Integer extends Number implements Comparable<Integer> {
...
public static Integer valueOf(int i) {
if(i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
...
}
以上是从Integer源码中摘取的片段,它有一个valueOf()方法。这个方法做了一个条件判断,其中IntegerCache.low的值是-128,IntegerCache.high的值是127。也就是说,如果目标值在-128~127之间,会直接从cache数组中取值,否则就会创建新对象。
答:
用Integer a = 100来定义变量时,Java会默认调用Integer.valueOf()方法进行装箱操作,相当于变成了Integer a = Integer.valueOf(100),于是就触发了IntegerCache中的缓存设计,使得最终的执行结果为true。
扩展:
那为什么默认值是-128~127,而不是其他数值呢?
这是因为-128~127的数据在int范围内是使用最频繁的,为了减少频繁创建对象所带来的内存消耗,这里其实用了享元模式,以提高空间和时间上的性能。在JDK中,这样的应用不止是int,其他基本数据类型和一些包装类型都应用了这种方法。
1.1.3、new String(“hello”)之后,到底创建了几个对象?
答:
这个问题分两种情况:
如果hello这个字符串常量在常量池中不存在,则会创建两个对象,分别是hello这个字符串常量和new String()这个实例对象;
如果hello这个字符串常量在常量池中存在,则只会创建new String()这一个实例对象;
扩展:
- 首先,语句中有一个new关键字,程序运行时,会在堆内存中实例化一个字符串对象;
- 在这个String的构造方法中,传递了一个hello字符串,由于String里面的字符串成员变量是用final修饰的,所以这个hello字符串是一个字符串常量,在字符串常量池中被创建;
- JVM会用字面量hello在字符串常量池中试图获取它对应的String对象引用,如果获取不到,就会在堆内存中创建一个hello的String对象,并且把引用保存到字符串常量池中;
- 后面如果再有字面量hello的定义,因为字符串常量池里已经存在了字面量hello的引用,所以只需从常量池中获取对应的引用就行了,不需要在堆内存中创建对象了
再来看这样一段代码
public static void main(String args[]) {
String s1 = "hello";
String s2 = "hello";
String s3 = "he" + "llo";
String s4 = "hel" + new String("lo");
String s5 = new String("hello");
String s6 = s5.intern();
String s7 = "h";
String s8 = "ello";
String s9 = s7 + s8;
}
以上代码的输出结果如下:
System.out.println(s1 == s2); //true
System.out.println(s1 == s3); //true
System.out.println(s1 == s4); //false
System.out.println(s1 == s9); //false
System.out.println(s4 == s5); //false
System.out.println(s1 == s6); //true
简单分析一下,由于s2指向的字面量hello在常量池中已经存在(s1先于s2),于是JVM就返回了这个字面量绑定的引用,所以s1 == s2;
(引用只是个标识符,其底层实现是指向某个内存地址,但引用并不是直接的内存地址,它只是个句柄)
s3中字面量的拼接其实就是hello,在编译期间就已经对它进行了优化,所以s1和s3也是相等的;
s4中的new String(“lo”)生成了两个对象:lo和new String(“lo”)。lo存在于字符串常量池中,new String(“lo”)存在于堆内存中,String s4 = “hel” + new String(“lo”)实质上是两个对象的相加,编译器不会进行优化,相加的结果存在于堆中,而s1存在于字符串常量池中,自然不相等。s1 == s9的原理也一样;
字面量的拼接,编译器在编译期间会对其进行优化,拼接后的字面量存在于字符串常量池中,如果字符串常量池中已经有了这个拼接后的字面量,则不会在字符串常量池中创建新的字面量;
而两个对象的相加,编译器不会对其进行优化,相加的结果存在于堆中。
对于s4 == s5,因为两个相加的结果都在堆中,所以肯定不相等;
对于s1 == s6,s5.intern()能使一个位于堆中的字符串,在运行期间动态的加入到字符串常量池中,如果字符串常量池中已经有了该对象对应的字面量,则返回该字面量在字符串常量池中的引用;否则,复制一份该字面量到字符串常量池中并返回它的引用。所以s1 == s6返回true。
1.1.4、String、StringBuffer、StringBuilder的区别是什么?
答:
- 值的可变性方面:String内部的value值是final修饰的,所以它是不可变类,所以,每次修改String的值都会产生一个新的对象;而StringBuffer和StringBuilder都是可变类,它们在字符串变更的时候不会产生新的对象,可以节省内存。
- 线程安全方面:String是不可变类,所以它是线程安全的;StringBuffer是线程安全的,因为它的每个操作方法都加了synchronized关键字;StringBuilder不是线程安全的。所以在多线程环境下对字符串进行操作时使用StringBuffer,在单线程环境下才使用StringBuilder。
- 性能方面:String的性能是最低的,因为它不可变,在做字符串拼接和修改的时候,需要反复的重新创建对象和分配内存;其次是StringBuffer,它要比String性能高,因为它的可变性使得字符串可以直接被修改;性能最高的是StringBuilder,因为StringBuffer加了同步锁,而StringBuilder是无阻塞的。
- 数据存储方面:String存储在字符串常量池中(对于编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池中。并且,字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化),而StringBuffer和StringBuilder都存储在堆内存中。
扩展:
StringBuffer和StringBuilder都派生自AbstractStringBuilder抽象类