今天大致的阅读了 String 类的源码, 并刷了常见的面试题, 在此做个笔记.
面试题一: 判断下列程序运行结果
- package String_test;
- public class test_1 {
- public static void main(String[] args) {
- String str1 = "HelloWorld";
- String str2 = "HelloWorld";
- String str3 = new String("HelloWorld");
- String str4 = "Hello";
- String str5 = "World";
- String str6 = "Hello" + "World";
- String str7 = str4 + str5;
- System.out.println("str1 == str2 result:" + (str1 == str2)); //1. true
- System.out.println("str1 == str3 result:" + (str1 == str3)); //2. false
- System.out.println("str1 == str6 result:" + (str1 == str6)); //3. true
- System.out.println("str1 == str7 result:" + (str1 == str7)); //4. false
- System.out.println("str1 == str7.intern() result:" + (str1 == str7.intern())); //5. true
- System.out.println("str3 == str3.intern() result:" + (str3 == str3.intern())); //6.false
- }
- }
画内存图逐个的分析每种情况: 第一个: str1 与 str2 指向同一个地址, 故相等.
第二个: new 代表创建了一个对象, str3 指向堆内存中的引用, 故 str1 与 str3 指向的地址不同. 需要注意的是: 字符串常量池中不可能存在两个一样的字符串值, 向这里堆内存指向的实际还是运行时常量池中的 HelloWorld 值
第三个: 由于 "Hello" 与 "World" 都是常量, 用 + 号在编译时会被自动编译成 String str6 = "HelloWorld", 所以两个引用都是指向常量池中的地址
第四个: 关键点在于理解 String str7 = str4+str5. 在 jdk 文档中有这么一段话
java 语言提供了字符串串联运算符特殊支持 ( + ), 和其他对象转换为字符串. 字符串连接是通过 StringBuilder 实施(或 StringBuffer) 类及其 append 方法. 字符串的转换是通过方法 toString 实施, 由 Object 和继承的所有类的 java.
可见 jvm 会在堆中创建一个以 str4 为基础的 StringBuilder 对象, 在通过 append 方法添加, 最后通过 toSting()返回一个 String 对象. 故 str7 指向的还是堆内存的对象而 str1 指向的是常量池中的地址, 两者指向地址不相同.
第五个: intern 方法使用: 一个初始为空的字符串池, 它由类 String 独自维护. 当调用 intern 方法时, 如果池已经包含一个等于此 String 对象的字符串 (用 equals(oject) 方法确定), 则返回池中的字符串. 否则, 将此 String 对象添加到池中, 并返回此 String 对象的引用. 这里 str1 指向常量池中的 "HelloWorld" 对象, str7.intern(): 此时常量池中已经有 "HelloWorld" 字符串值, 所以地址指向相同
第六个: str3 指向的是堆内存, 而 str3.intern()返回的是常量池中已有字符串 "HelloWorld" 的引用, 故两者指向地址不同.
面试题二: java 中的 String 为什么设计为 final 类?
1. 允许 String 对象缓存 hashCode 值: 在 java 中 String 类型是非常常用的, 涉及到大量的增删改查. 字符串不变性保证了 hashCode 的唯一性, 这是一种优化手段意味着不必每次都去计算 hash 值, 这也是为什么 HashMap 建议用 String,Integer 这种不可变对象当作 Key 值
2. 字符串常量池需要: java 中将字符串值存放在字符串常量池中, 如果 String 对象是可变的, 会产生很多逻辑错误, 比如改变一个对象会影响到另一个独立对象.
3. 安全性: 网络地址的 url, 文件路径 path 通常情况啊下都是用 String 类型来保存, 如果不是固定不变的可能产生很多安全隐患.
面视题三: 有什么办法可以改变 String?
如果问了这个就很尴尬, sun 公司特意设计的不可变, 要强行改变只能通过反射这种骚操作
- package String_test;
- import java.lang.reflect.Field;
- public class test_2 {
- public static void main(String[] args) throws Exception {
- String str = "王老吉真解渴";
- System.out.println("str=:"+str);
- // 通过反射改变获取内部的 value 字符数组
- Field field = String.class.getDeclaredField("value");
- field.setAccessible(true);
- field.set(str, new char[]{'加','多','宝','也','解','渴'});
- System.out.println("str=:"+str);
- }
- }
面试题四: 下列代码创建了几个对象?
String st1 = new String("abc");
常量池一个 "abc" 对象, 堆中一个 "abc" 对象, 总共两个.
- String st1 = new String("abc");
- String st2 = new String("abc");
3 个对象. 字符串在常量池中是唯一的, 堆内存中有两个, 常量池中一个.
面试题五: 谈一下 String,StringBuilder,StringBuffer 的区别?
1.String 类是字符串常量, 而 StringBuilder 与 StringBuffer 是字符串变量. 前者不可变后者可变
2.StringBuilder 是非同步的, StringBuffer 类的 API 都套上了一层 synchronized 同步修饰, 所以 StringBuffer 适合在多线程场景使用(实际基本不用),StringBuilder 类适合单线程使用, 它两用的多的就是 append 和 insert 方法
它三的适用场景可以看下知乎: https://www.zhihu.com/question/20101840
小结:
关于 String 类和它的成员方法, 都是被 final 修饰的, 意味着 Strin 类不可被继承. String 底层是采用字符数组对数据进行操作的, 关于 String 的一切操作 jdk 底层都是会 new 一个新的 String 对象, 在它的基础上进行操作. 所以 String 是不可变的.
关于 String, 基础性的理论大致就这些, 更多的还会考察字符串的一些算法, 这部分也是需要攻克的一个难点!!
参考链接: https://www.cnblogs.com/xiaoxi/p/6036701.html
参考链接:
来源: https://www.cnblogs.com/zengcongcong/p/11335927.html