龙空技术网

高级程序员面试被问String为什么设计成final?答不出,回去等通知

小哈学Java 615

前言:

眼前看官们对“为什么string可以直接赋值”大致比较讲究,我们都想要知道一些“为什么string可以直接赋值”的相关知识。那么小编同时在网上搜集了一些关于“为什么string可以直接赋值””的相关文章,希望小伙伴们能喜欢,朋友们快快来了解一下吧!

分享阿里 P8 高级架构师吐血总结的 《Java 核心知识体系&面试资料.pdf》

阿里 P8 级高级架构师吐血总结的一份 Java 核心知识.pdf, 内容覆盖很广,Java 核心基础、Java 多线程、高并发、Spring、微服务、Netty 与 RPC、Zookeeper、Kafka、RabbitMQ、Habase、设计模式、负载均衡、分布式缓存、Hadoop、Spark、Storm、云计算等。

获取方式:【关注 + 转发】后,私信我,回复关键字【资源】,即可免费无套路获取哦~

以下是资源的部分目录以及内容截图:

重要的事再说一遍,获取方式:【关注 + 转发】后,私信我,回复关键字【资源】,即可免费无套路获取哦~

正文开始

因为我已烂熟于心,灵活运用了,别考了。哈哈,希望你能读后思考一下这样对面试官说。

String 简介

String定义:

public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {}

为什么设计为不可变类呢?

String设计为不可变类主要考虑到:效率和安全。

效率:

1.在早期的JVM实现版本中,被final修饰的方法会被转为内嵌调用以提升执行效率。而从Java SE5/6开始,就渐渐摈弃这种方式了。因此在现在的Java SE版本中,不需要考虑用final去提升方法调用效率。只有在确定不想让该方法被覆盖时,才将方法设置为final。

2.缓存hashcode,String不可变,所以hashcode不变,这样缓存才有意义,不必重新计算。

安全:

String常被作为网络连接,文件操作等参数类型,倘若可改变,会出现意想不到的结果。

测试掌握程度

为了不浪费你的时间,请看下面的题目,若你一目了然,可以跳过本文了。

public class Test { public static void main(String[] args) { String str1 = "HelloFlyapi"; String str2 = "HelloFlyapi"; String str3 = new String("HelloFlyapi"); String str4 = "Hello"; String str5 = "Flyapi"; String str6 = "Hello" + "Flyapi"; String str7 = str4 + str5; System.out.println("str1 == str2 result: " + (str1 == str2)); System.out.println("str1 == str3 result: " + (str1 == str3)); System.out.println("str1 == str6 result: " + (str1 == str6)); System.out.println("str1 == str7 result: " + (str1 == str7)); System.out.println("str1 == str7.intern() result: " + (str1 == str7.intern())); System.out.println("str3 == str3.intern() result: " + (str3 == str3.intern())); }}
String 的创建方式

从上面的题中你会知道,String的创建方式有两种:

直接赋值

此方式在方法区中字符串常量池中创建对象

1、String str = "flyapi";

构造器

此方式在堆内存创建对象

1、String str = new String();

分析

要理解String,那么要了解JVM内存中的栈(stack)、堆(heap)和方法区。简要图如下:

str1 == str2

String str1 = "HelloFlyapi"; String str2 = "HelloFlyapi"; System.out.println(str1 == str2); // true

当执行第一句时,JVM会先去常量池中查找是否存在HelloFlyapi,当存在时直接返回常量池里的引用;当不存在时,会在字符创常量池中创建一个对象并返回引用。

当执行第二句时,同样的道理,由于第一句已经在常量池中创建了,所以直接返回上句创建的对象的引用。

str1 == str3

 String str1 = "HelloFlyapi"; String str3 = new String("HelloFlyapi"); System.out.println(str1 == str3); // false

执行第一句,同上第一句。

执行第二句时,会在堆(heap)中创建一个对象,当字符创常量池中没有‘HelloFlyapi’时,会在常量池中也创建一个对象;当常量池中已经存在了,就不会创建新的了。

str1 == str6

 String str1 = "HelloFlyapi"; String str6 = "Hello" + "Flyapi"; System.out.println(str1 == str6); // true

由于”Hello”和”Flyapi”都是常量,编译时,第二句会被自动编译为‘String str6 = “HelloFlyapi”;

str1 == str7

 String str1 = "HelloFlyapi"; String str4 = "Hello"; String str5 = "Flyapi"; String str7 = str4 + str5; System.out.println(str1 == str7); // false

其中前三句变量存储的是常量池中的引用地址。

第四句执行时,JVM会在堆(heap)中创建一个以str4为基础的一个StringBuilder对象,然后调用StringBuilder的append()方法完成与str5的合并,之后会调用toString()方法在堆(heap)中创建一个String对象,并把这个String对象的引用赋给str7。

常用方法

下面是 String 类支持的方法,更多详细,参看 Java String API 文档:

方法描述char charAt(int index)返回指定索引处的 char 值。int compareTo(Object o)把这个字符串和另一个对象比较。int compareTo(String anotherString)按字典顺序比较两个字符串。boolean endsWith(String suffix)测试此字符串是否以指定的后缀结束。boolean equals(Object anObject)将此字符串与指定的对象比较。boolean equalsIgnoreCase(String anotherString)将此 String 与另一个 String 比较,不考虑大小写。byte[] getBytes()使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。byte[] getBytes(String charsetName)使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。int indexOf(int ch)返回指定字符在此字符串中第一次出现处的索引。int indexOf(int ch, int fromIndex)返回在此字符串中第一次出现指定字符处的索引,从指定的索引开始搜索。int indexOf(String str)返回指定子字符串在此字符串中第一次出现处的索引。int indexOf(String str, int fromIndex)返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。String intern()返回字符串对象的规范化表示形式。int lastIndexOf(int ch)返回指定字符在此字符串中最后一次出现处的索引。int lastIndexOf(String str)返回指定子字符串在此字符串中最右边出现处的索引。int length()返回此字符串的长度。boolean matches(String regex)告知此字符串是否匹配给定的正则表达式。String replace(char oldChar, char newChar)返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。String[] split(String regex)根据给定正则表达式的匹配拆分此字符串。boolean startsWith(String prefix)测试此字符串是否以指定的前缀开始。String substring(int beginIndex)返回一个新的字符串,它是此字符串的一个子字符串。String substring(int beginIndex, int endIndex)返回一个新字符串,它是此字符串的一个子字符串。char[] toCharArray()将此字符串转换为一个新的字符数组。String toLowerCase()使用默认语言环境的规则将此 String 中的所有字符都转换为小写。String toUpperCase()使用默认语言环境的规则将此 String 中的所有字符都转换为大写。String trim()返回字符串的副本,忽略前导空白和尾部空白。

String相关

由于String的不可变性导致,字符串变更时效率低下,在之后得JDK版本中出现了StringBuilder和StringBuffer 。

类可变性线程安全String不可变安全StringBuffer可变安全StringBuilder可变非安全

使用选择

1、当有少量连接操作时,使用String

2、当单线程下有大量连接操作时,使用StringBuilder

3、当多线程下有大量连接操作时,使用StringBuffer

常见String面试题

String str = new String(“abc”)创建了多少个实例?

这个问题其实是不严谨的,但面试一般会遇到,所以我们要补充来说明。

类的加载和执行要分开来讲:

创建了两个

1、当加载类时,”abc”被创建并驻留在了字符创常量池中(如果先前加载中没有创建驻留过)。

2、当执行此句时,因为”abc”对应的String实例已经存在于字符串常量池中,所以JVM会将此实例复制到会在堆(heap)中并返回引用地址。

通过字节码我们可以看到:

源码:String str = new String(“abc”)

字节码:

 Code: 0: new #2 // class java/lang/String 3: dup 4: ldc #3 // String abc 6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V 9: astore_1 10: return

执行时仅(#2)创建了一个对象。

关于这个面试题,可以看看一个超大牛的回答:

来源:码上实战

标签: #为什么string可以直接赋值