【高频面试】Java String 面试题总结 欢迎指正和补充

面试:String相关问题总结




1. String 是否可以被修改

这个问题其实有两个关注点:

  1. String的实例化对象的引用地址能否被修改
  2. String的实例化对象引用的值能否被修改
  1. 引用地址能否被修改,答案是可以。见**【2】**。

  1. 一个实例化String对象的值能否被修改,答案是不可以。

String设计的初衷就是将其作为常量使用的,一旦将其创建便不能修改它的值。
在源码中,String内部存储值的属性被final修饰,在初始化后value的引用地址就不能被修改了,而且String内部也并未提供任何可以修改value指定下标处值的任何方法,同时由于String本身也被final修饰,String类无法被继承,因此替换String的某个字符的操作也无法实现。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
	private final char value[];
	private int hash; // Default to 0
	
	public String() {
        this.value = "".value;
    }
	
	public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
}



延伸话题

Q:那你说说final关键字有什么作用
A:final关键字修饰在不同的地方有不同效果:

  • 【类】类不可继承
  • 【方法】方法不可重写
  • 【属性】属性上值不可修改、引用地址不可更换且需赋初值

Q:final修饰的属性真的不能修改吗
A:通过反射可以强制修改,但是由于虚拟机优化,修改可能不会起作用(这一点参考**【3】**)




2. String的值不可以被修改,为什么又可以被重新赋值呢,这是否有所矛盾?

问题如下所示:

String s = "Apple";
s = "Mango";

不矛盾。

这里首先要明白一个前提:这里s是一个对象,而"Apple"是另一个对象;由于s没有被final修饰,也就是说它的引用地址是可以改变的。而"Apple"它是一个字符串常量,它才是那个无法被变更的对象。

  • 这里的s,实际上就是一个对象,它只是保存了"Apple"这个常量的引用地址,修改s的值为"Mango"时,实际上只是在常量池新建了一个常量"Mango",同时将s的引用地址指向了"Mango"。

  • String创建后不可修改,指的是"Apple"这个String常量一旦被创建就不可以被修改了。任何指向它的对象,获取到的值将一直是"Apple"。

在这里插入图片描述




3. 直接赋值和new的区别

如下所示的两条语句有何区别:

String s1 = "Apple";
String s2 = new String("Mango");

思路:

  1. 直接赋值与new的区别
  2. 直接赋值的原理
  3. new的原理
  1. 首先"Apple"和"Mango"在Java中,已经是一个String类的实例对象;而new String(),则是创建一个新的String对象,并复制"Mango"的引用地址给这个新建的对象,这就是二者的显著区别。

  2. 直接赋值时,如果字符串常量池(string constant pool)里有该字符串,那么引用地址直接指向该字符串常量;没有则在字符串常量池中创建,然后再指向该地址。

  3. new String(s)时,先将传入的字符串参数"Mango"按照直接赋值的方法创建一个对象,然后在堆中创建一个新对象,且这个新对象的引用地址指向的是"Mango"这个对象。官方一般建议不要使用new String的形式创建字符串。

// 在String的单参构造函数中,是使用“=”赋值(即引用传递),相当于当前String对象的value,指向的是original.value。
// 因此这两个String对象,虽然不是同一个对象,但其内部属性value实际上都是指向"Mango"这个常量。
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

下图是直接赋值与new赋值时,对象引用的区别:
在这里插入图片描述


下面是关于上述表述的验证:

String mango = "Mango"; // 新建对象mango,引用地址指向常量池的"Mango",同时"Mango"这个String对象也指向常量池的"Mango"
String s2 = new String("Mango"); // 新建对象s2 指向一个新对象new String
String s3 = new String(mango); // 新建对象s3 指向一个新对象new String
String s4 = mango; // 新建对象s4 指向mango
// 内存地址即Object.hashCode(),String重写了hashCode方法导致其不能输出原本的内存地址
System.out.println(System.identityHashCode("Mango"));
System.out.println(System.identityHashCode(mango));
System.out.println(System.identityHashCode(s2));
System.out.println(System.identityHashCode(s3));
System.out.println(System.identityHashCode(s4));
System.out.println(mango == "Mango"); // 指向的地址相同
System.out.println(mango == s2);
System.out.println(mango == s3);
System.out.println(mango == s4);
System.out.println(s2 == s3);

执行结果:
在这里插入图片描述




延伸话题

Q:当上面两种情况都被final修饰时,二者有区别吗?
A:直接赋值时,Java虚拟机会对final修饰的String变量进行优化,将把这类String对象当成常量对待。而new则不会

情况如下:

 public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
     TestStr testStr = new TestStr();
     System.out.println(testStr.getApple());
     Class<? extends TestStr> aClass = testStr.getClass();
     // 反射修改final apple
     Field apple = aClass.getDeclaredField("apple");
     apple.setAccessible(true);
     apple.set(testStr, "Apple-update");
     System.out.println(testStr.getApple());
     System.out.println(testStr.apple);
 }
 static class TestStr {
     final String apple = "Apple";
     // final String apple = new String("Apple");
     public String getApple() {
         return apple;
     }
 }

猜一猜结果会输出什么?

在这里插入图片描述

可以看到,我们通过反射修改了final修饰的apple字段,但是,最终却输出了被修改之前的值(这里不是因为反射没有生效)。


原因是:apple这个属性和getApple方法都被虚拟机优化了,他们在编译期的时候,apple的值以及调用apple的任意方法,都以固定值被虚拟机写死在源文件上,因此运行时的任意调用实际上都是调用的固定值。如下面的class源文件所示:

在这里插入图片描述


如果我们把直接赋值改为new的形式,就会发现,虚拟机停止了对new对象的优化。
在这里插入图片描述




4. String中equals()和==的区别

只要注意以下两点的区别还是很容易知道String的equals方法和==判断的不同的:

  1. ==对于基本类型和引用类型的不同作用
    • 对于基本类型来说是值比较
    • 对于引用类型来说是引用地址的比较

  1. 区分Object.equals与String.equals的区别
    • Object.equals是直接进行的==比较,因此该方法其实就是比较的引用地址是否相等
    • String.equals则是重写了Object.equals这个方法,在判断引用地址不等后还会单独判断每个字符是否相等。

源码:

// Object#equals
public boolean equals(Object obj) {
    return (this == obj);
}
// String#equals
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值