Java基础面试题
最近在准备面试,所以将一些常见的面试题解析整理出来,方便复习。
01_字符串常量Java内部加载-上
由于运行时常量池是方法区的一部分,所以这两个区域的溢出测试可以放到一起进行。HotSpot从JDK 7开始逐步“去永久代”的计划,并在JDK 8中完全使用元空间来代替永久代的背景故事,在此我们就以测试代码来观察一下,使用"永久代"还是“元空间"来实现方法区,对程序有什么实际的影响。
String:intern()是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象的引用;否则,会将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。
public class StringInternDemo {
public static void main(String[] args) {
String str1 = new StringBuilder("58").append("tongcheng").toString();
System.out.println(str1); // 58tongcheng
System.out.println(str1.intern()); // 58tongcheng
System.out.println(str1 == str1.intern()); // true
System.out.println();
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2); // java
System.out.println(str2.intern()); //java
System.out.println(str2 == str2.intern()); //false
}
}
02_字符串常量Java内部加载-下
按照代码结果,Java字符串答案为false必然是两个不同的java,那另外一个java字符串如何加载进来的?
有一个初始化的Java字符串(JDK出娘胎自带的),在加载sun.misc.Version这个类的时候进入常量池。
递推步骤
- System代码解析 System -> initializeSystemClass() -> Version
-
类加载器和rt.jar - 根加载器提前部署加载rt.jar
-
OpenJDK8源码
- http://openjdk.java.net/
- openjdk8\jdk\src\share\classes\sun\misc
JDK 7(以及部分其他虚拟机,字符串常量池已经移到Java堆中,只需要在常量池里记录一下首次出现的实例引用即可,因此intern()返回的引用和由StringBuilder创建的那个字符串实例就是同一个。而对str2比较返回false,这是因为“java”这个字符串在执行StringBuilder.toString()之前就已经出现过了,字符串常量池中已经有它的引用,不符合intern()方法要求“首次遇到"”的原则,“计算机软件"这个字符串则是首次出现的,因此结果返回true。
sun.misc.Version类会在JDK类库的初始化过程中被加载并初始化,而在初始化时它需要对静态常量字段根据指定的常量值(ConstantValue〉做默认初始化,此时被sun.misc.Version.launcher静态常量字段所引用的"java"字符串字面量就被intern到HotSpot VM的字符串常量池——StringTable里了。
03_String经典面试题
class StringTest {
String str = new String("good"); //数组和对象传递地址,其他数据类型传递的是数据
char[] ch = { 't', 'e', 's', 't' };
public void change(String str, char ch[]) { //String str str是新的一个变量,在常量池中是test ok
str = "test ok"; //this.str = "test ok"
ch[0] = 'b';
}
public static void main(String[] args) {
StringTest ex = new StringTest();
ex.change(ex.str, ex.ch);
System.out.println(ex.str); //good
System.out.println(ex.ch); //best
}
}
public class TestTransferValue {
public void changeValue1(int age){
age = 30;
}
public void changeValue2(Person person){
person.setPersonName("xxx");
}
public void changeValue3(String str){
str = "xxx";
}
public static void main(String[] args) {
TestTransferValue test = new TestTransferValue();
/**
* 在changeValue1方法中对age复制了一份,不影响main中的age值【基本类型传复印件age】
*/
int age = 20;
test.changeValue1(age);
System.out.println("age -----" + age); // age -----20
/**
* main和changeValue2方法都指向了 new Person("abc")的内存地址,所以会改变main中的person值
* 指针指向了同一个引用地址
*/
Person person = new Person("abc");
test.changeValue2(person);
System.out.println("personName-----" + person.getPersonName()); // personName-----xxx
/**
* String直接赋值,str = "abc",则main指向abc,changeValue3方法中String刚开始指向abc,
* 然后 str = "xxx",没有这个值,需要新建一个xxx,这时changeValue3的指向了xxx
*/
String str = "abc";
test.changeValue3(str);
System.out.println("String--------" + str); // String--------abc
person.personName = "jjjjj";
System.out.println(person.personName); //jjjjj
}
}
@Test
public void test3(){
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop"; //在方法区的常量池中
String s5 = s1 + "hadoop"; //在堆空间中,类似于new
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop” 在方法区的常量池中
System.out.println(s3 == s8);//true
}