您好,欢迎来到12图资源库!分享精神,快乐你我!我们只是素材的搬运工!!
  • 首 页
  • 当前位置:首页 > 开发 > WEB开发 >
    一文弄懂String s=a+b+c,究竟创立了几个对象?
    时间:2021-08-16 08:21 来源:网络整理 作者:网络 浏览:收藏 挑错 推荐 打印

    首先看一下这道常见的面试题,下面代码中,会创立几个字符串对象?

    String s="a"+"b"+"c";

    假设你比较一下Java源代码和反编译后的字节码文件,就可以直观的看到答案,只创立了一个String对象。

    一文弄懂String s=a+b+c,究竟创立了几个对象?

    估量大家会有疑问了,为什么源代码中字符串拼接的操作,在编译完成后会消逝,直接出现为一个拼接后的残缺字符串呢?

    这是由于在编译时期,运用了编译器优化中一种被称为常量折叠(Constant Folding)的技术,会将编译期常量的加减乘除的运算进程在编译进程中折叠。编译器经过语法剖析,会将常量表达式计算求值,并用求出的值来交流表达式,而不必等到运转时期再停止运算处置,从而在运转时期节省处置器资源。

    而上边提到的编译期常量的特点就是它的值在编译期就可以确定,并且需求残缺满足下面的要求,才能够是一个编译期常量:

    被声明为final

    基本类型或许字符串类型

    声明时就曾经初始化

    运用常量表达式停止初始化

    下面的前两条比较容易了解,需求留意的是第三和第四条,经过下面的例子停止阐明:

    final String s1="hello "+"Hydra";final String s2=UUID.randomUUID().toString()+"Hydra";

    编译器可以在编译期就失掉s1的值是hello Hydra,不需求等到顺序的运转时期,因此s1属于编译期常量。而对s2来说,虽然也被声明为final类型,并且在声明时就曾经初始化,但运用的不是常量表达式,因此不属于编译期常量,这一类型的常量被称为运转时常量。再看一下编译后的字节码文件中的常量池区域:

    一文弄懂String s=a+b+c,究竟创立了几个对象?

    可以看到常量池中只要一个String类型的常量hello Hydra,而s2对应的字符串常量则不在此区域。对编译器来说,运转时常量在编译时期无法停止折叠,编译器只会对尝试修正它的操作停止报错处置。

    另外值得一提的是,编译期常量与运转时常量的另一个不同就是能否需求对类停止初始化,下面经过两个例子停止比照:

    public class IntTest1 { public static void main(String[] args) { System.out.println(a1.a); }}class a1{ static { System.out.println("init class"); } public static int a=1;}

    运转下面的代码,输入:

    init class1

    假设对下面停止修正,对变量a添加final停止修饰:

    public static final int a=1;

    再次执行下面的代码,会输入:

    1

    可以看到在添加了final修饰后,两次运转的结果是不同的,这是由于在添加final后,变量a成为了编译期常量,不会招致类的初始化。另外,在声明编译器常量时,final关键字是必要的,而static关键字是非必要的,下面加static修饰只是为了验证类能否被初始化过。

    我们再看几个例子来加深对final关键字的了解,运转下面的代码:

    public static void main(String[] args) { final String h1 = "hello"; String h2 = "hello"; String s1 = h1 + "Hydra"; String s2 = h2 + "Hydra"; System.out.println((s1 == "helloHydra")); System.out.println((s2 == "helloHydra"));}

    执行结果:

    truefalse

    代码中字符串h1和h2都运用常量赋值,区别在于能否运用了final停止修饰,比照编译后的代码,s1停止了折叠而s2没有,可以印证下面的实际,final修饰的字符串变量属于编译期常量。

    一文弄懂String s=a+b+c,究竟创立了几个对象?

    再看一段代码,执行下面的顺序,结果会前往什么呢?

    public static void main(String[] args) { String h ="hello"; final String h2 = h; String s = h2 + "Hydra"; System.out.println(s=="helloHydra");}

    答案是false,由于虽然这里字符串h2被final修饰,但是初始化时没有运用编译期常量,因此它也不是编译期常量。

    在下面的一些例子中,在执行常量折叠的进程中都遵照了运用常量表达式停止初始化这一准绳,这里能够有的同窗还会有疑问,究竟什么样才能算得上是常量表达式呢?在Oracle官网的文档中,罗列了很多种状况,下面对常见的状况停止罗列(除了下面这些之外官方文档上还罗列了不少状况,假设有兴味的话,可以本人查看):

    基本类型和String类型的字面量

    基本类型和String类型的强迫类型转换

    运用+或-或!等一元运算符(不包括++和--)停止计算

    运用加减运算符+、-,乘除运算符*、 / 、% 停止计算

    运用移位运算符 >>、 <<、 >>>停止位移操作

    ……

    字面量(literals)是用于表达源代码中一个固定值的表示法,在Java中创立一个对象时需求运用new关键字,但是给一个基本类型变量赋值时不需求运用new关键字,这种方式就可以被称为字面量。Java中字面量主要包括了以下类型的字面量:

    //整数型字面量:long l=1L;int i=1;//浮点类型字面量:float f=11.1f;double d=11.1;//字符和字符串类型字面量:char c='h';String s="Hydra";//布尔类型字面量:boolean b=true;

    当我们在代码中定义并初始化一个字符串对象后,顺序会在常量池(constant pool)中缓存该字符串的字面量,假设前面的代码再次用到这个字符串的字面量,会直接运用常量池中的字符串字面量。

    除此之外,还有一类比较特殊的null类型字面量,这个类型的字面量只要一个就是null,这个字面量可以赋值给恣意援用类型的变量,表示这个援用类型变量中保存的地址为空,也就是还没有指向任何有效的对象。

    那么,假设不是运用的常量表达式停止初始化,在变量的初始化进程中引入了其他变量(且没有被final修饰)的话,编译器会怎样停止处置呢?我们下面再看一个例子:

    public static void main(String[] args) { String s1="a"; String s2=s1+"b"; String s3="a"+"b"; System.out.println(s2=="ab"); System.out.println(s3=="ab");}

    结果打印:

    falsetrue

    为什么会出现不同的结果?在Java中,String类型在运用==停止比较时,是判别的援用能否指向堆内存中的同一块地址,出现下面的结果那么阐明指向的不是内存中的同一块地址。

    经过之前的剖析,我们知道s3会停止常量折叠,援用的是常量池中的ab,所以相等。而字符串s2在停止拼接时,表达式中援用了其他对象,不属于编译期常量,因此不能停止折叠。

    那么,在没有常量折叠的状况下,为什么最后前往的是false呢?我们看一下这种状况下,编译器是如何完成,先执行下面的代码:

    public static void main(String[] args) { String s1="my "; String s2="name "; String s3="is "; String s4="Hydra"; String s=s1+s2+s3+s4;}

    然后运用javap对字节码文件停止反编译,可以看到在这一进程中,编译器异样会停止优化:

    一文弄懂String s=a+b+c,究竟创立了几个对象?

    (责任编辑:admin)