这篇文章总结一下我认为面试中最应该掌握的关于基本类型和引用类型的面试题目。
面试题目1:值传递与引用传递
对于没有接触过C++这类有引用传递的Java程序员来说,很容易误将引用类型的参数传递理解为引用传递,而基本类型的传递理解为值传递,这是错误的。要理解值传递与引用传递,首先要理清值传递、引用传递与指针传递三个概念。
值传递与引用传递最重要的就是看在传递的过程中,值是否发生了复制。在Java中没有指针的概念,但是引用类型做为参数进行传递时,JVM将其实现为指针传递,那么重点就是搞清楚指针传递到底是值传递还是引用传递了。指针在传递时也会复制,所以是值传递,Java中不存在引用传递。
面试题目2:int类型的范围
Java中4种基本类型表示的范围如下图所示。
Java中不能明确指示某个数为无符号类型,所以最高位一般为符号位。拿占一个字节的byte来说,由于最高位需要表示符号,所以只能用剩下的7位来表示数。所以最大可表示的数为
0111 1111(二进制)
max = (2^0+2^1+2^2+...+2^6) = 127
最小可表示数的范围用二进制表示应该为:
1111 1111(二进制)
但是对于计算机来说,负数其实是用补码表示的,也就是反码加1,所以在计算机中存储的二进制为1000 0001(补码),这个值才是-127。
我们要对待一种特殊情况,如下:
1000 0000(原码)
1111 1111(反码)
1000 0000(补码)
如果1000 0000表示0的话,那岂不是有了0和-0之分了,所以可以用1000 0000表示-128。
由于符号位的存在,所以在许多的情况下,我们其实只是想保持二进制位的原样,而不是十进制的值。举个例子如下:
public static void main(String[] args) { byte b = -127;//10000001 int a = b; System.out.println(a); a = b & 0xff; System.out.println(a); }
输出结果-127,129。
byte类型在转换为int类型时,符号位发生扩展,所以第一次打印a的值时,十进制保持一致,二进制表示为
1111 1111 1111 1111 1111 1111 1000 0001
现在将b和&0xff做与后,十进制已经无法保持一致,因为此时的二进制表示为
0000 0000 0000 0000 0000 0000 1000 0001
这个值是129。有什么用呢?其实在实际操作中,我们经常看到读取字节流时,转换过程中要加&0xff,就是为了保持二进制的原有样子,因为此时关注的并不是十进制值。
面试题目3:Java中对基本类型的赋值是原子操作吗?引用类型呢?
根据虚拟机规范:(https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7)
Java虚拟机规范表示,Java 基础类型中,long 和 double 是 64 位长的。32 位架构 CPU 的算术逻辑单元(ALU)宽度是 32 位的,在处理大于 32 位操作时需要处理两次。所以有可能在读long的高32位时,低32位被另外一个并发的线程写了,读出的值可就谁也说不准了。对于引用类型来说是原子操作。
举个例子如下:
public class P1 { private long b = 0; public void set1() { b = 0; } public void set2() { b = -1; } public void check() { System.out.println(b); if (0 != b && -1 != b) { System.err.println("Error"); } } }
在32位下跑这个程序,可能会打印Error,原因就是上面提到的。引起这个问题的最主要原因就是并发,所以如果要解决这个问题,可以让线程序列化访问b,也就是通过加锁来实现;可以使用AtomicLong对长整形进行原子操作;根据虚拟机规范所说,还可以加volatile关键字。
这里只所以volatile关键字能解决这个原子性,其实还是因为为了保证可见性而对内存进行了独占访问,这样在独占操作时,就不会有其它线程改写其中的值了。
面试题目4:关于字符串的面试题
首先来看一下下面的面试题:
另外一个高频的问题就是字符串创建了多少个对象的问题,如下:
String str = new String("ab");
上面一行代码将会创建1或2个字符串。如果在字符串常量池中已经有一个字符串“ab”,那么就只会创建一个“ab”字符串。如果字符串常量池中没有“ab”,那么首先会在字符串池中创建,然后才在堆内存中创建,这种情况就会创建2个对象。
再来看一下下面的面试题:
String a = "ab"; String b = "a" + "b"; a == b
如上的代码会对b进行常量折叠,所以相当于如下程序:
String a = "ab"; String b = "ab"; System.out.println(a == b)
对于Java来说,==对基本类型比较的是值,而对于引用类型比较的是地址,所以要想让a==b输出true,那只能a和b指向同一个对象。"ab" 属于字符串字面量,因此编译时期会在常量池中创建一个字符串对象,如果常量池中已经存在该字符串对象则直接引用,所以最终的结果为true。
String类型的常量池比较特殊。它的主要使用方法有两种:
(1)直接使用双引号声明出来的String对象会直接存储在常量池中。
(2)如果不是用双引号声明String对象,可以使用 String 提供的 intern()方法。它的作用是: 如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用; 如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。
举个例子如下:
String s1 = new String("ab");// s1保存堆中的引用 String s2 = s1.intern(); // s2保存的是字符串常量池中的引用 String s3 = "ab"; // s3保持的是字符串常量池中的引用 System.out.println(s1 == s2);// false,因为一个是堆内存中的String对象一个是常量池中的String对象, System.out.println(s2 == s3);// true, s1,s2指向常量池中的”ab“