java学习总结-------String类(详解)

String详解

String类型是继 byte char short int long float double 及boolean八大类型之外的又一重要类型,任何语言中都离不开它,String又称为字符串或者字符串常量。在任何语言的底层都不会提供直接的字符串类型,现在所谓的字符串类型只是高级语言为了给用户方便开发而已。在Java中字符串也是用字符数组来存放的。

源码:
private final char value[]; //就是该数组,被final修饰,也是字符串不可变的原因

String对象的实例化方式

  • 直接赋值(用得最多)

String str = “hello java”;

  • 调用构造方法(传统方法)

String str = new String(“hello Java”);

那么这两种方式到底有何区别呢?在分析其区别之前先看这样一段代码:

public class Test {
    public static void main(String[] args) {
        int x= 20;
        int y = 20;
        System.out.println(x == y);       //true
        String str1 = new String("hello");
        String str2 = new String("hello");
        System.out.println(str1 == str2); //false
    }
}

输出结果:
在这里插入图片描述
为啥会是这样的结果呢?先来看一下它在内存中的存放。
java学习总结-------String类(详解)_第1张图片

        String str1 = new String("hello");
        String str2 = new String("hello");
        System.out.println(str1.equals(str2));//true

面试题:请叙述String类 == 与 equals 的区别

== 比较的是两个变量栈空间的数值。这里的变量分为基本数据类型和引用数据类型,对于基本数据类型,栈空间存的就是它们自身的数值,因此对于基本数据类型 == 比较的是它们自身的数值是否相等,而对于引用数据类型,栈空间存的是引用数据类型的地址,因此对于引用数据类型来说 == 比较的是两个引用数据类型的地址值是否相等。

equals方法:在String类中,覆写了Object类的equals()方法,比较的是两个字符串内容是否相同。而对于别的没有覆写equals()的类,仍然比较的是该类对象的引用(地址)是否相同,基本类型不能调用该方法。

Object类的equals方法源码:

public boolean equals(Object obj) {
return (this == obj);
}

String的匿名对象

public class Test {
    public static void main(String[] args) {
       String str1 = new String("hello");
       System.out.println("hello".equals(str1));
       System.out.println(str1.equals("hello"));
    }
}

代码中直接写的“hello”就是String类的匿名对象,那该如何证明它是一个对象呢?其实不难证明,在代码中我们可以看到该匿名 字符串调用了equals方法,因此它是一个String类的对象。而在之前的第一种定义中String str = "hell";本质上就是为匿名字符串设置名字。

注:在String对象与匿名字符串比较时,强烈建议使用"匿名字符串".equals(String类对象)的方式。

这是为何呢?不妨看这样一段代码: 空指针异常的问题

public class Test {
    public static void main(String[] args) {
        String str1 = null;// 假设str的值根据用户输入而定
        System.out.println(str1.equals("hello"));
    }
}

运行结果:
在这里插入图片描述
换个书写方法:

public class Test {
    public static void main(String[] args) {
        String str1 = null;// 假设str的值根据用户输入而定
        System.out.println("hello".equals(str1));
    }
}

输出结果 :
在这里插入图片描述

实例化的区别及内存结构

public class Test {
    public static void main(String[] args) {
        String str1 = "javaSE";
        String str2 = "javaSE";
        String str3 = new String ("javaSE");
        String str4 = new String ("javaSE");
        String str5 = str3.intern();//字符串入池函数
        System.out.println(str1 == str2); //true
        System.out.println(str3 == str4); //false
        System.out.println(str1 == str5);  //true
    }
}

输出结果:
java学习总结-------String类(详解)_第2张图片
在分析代码之前,必须先了解一个非常重要的东西:共享设计模式

在JVM底层,有一个叫做对象池(字符常量池)的东西,对于直接赋值产生的String类对象如String str = “hello”,它先会在常量池中找有没有该字符串即“hello”,如果没有则在常量池中创建一个,然后把该字符串在常量池中的地址保存在栈空间中,如果有“hello”则直接引用(保存地址),不再创建。
而对于通过new关键字创建的对象如:String str = new String(“hello”),首先会在堆上开辟一块空间,然后去字符串常量池中找要创建的字符串(即找“hello”),看有没有一致的,如果有,则直接引用,即直接把该字符串在常量池中的地址保存到堆空间内,如果没有呢?则先在常量池中创建该字符串匿名对象即“hello”,然后再将字符串在常量池的地址保存到堆空间中。
看一下内存结构图:
java学习总结-------String类(详解)_第3张图片
想必此时已经非常清楚了入池操作,仅是改变栈空间的指向,由由以前的堆空间改变为常量池。
为什么说,堆空间保存得是常量池的地址呢?

String类的构造方法源码;
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}

在这里插入代码片

引用类型做参数,为引用传递,函数体中是将一个数组名赋给String类的Value数组,本质是两个数组名都指向了常量区的字符串。如下代码所示

public class Test {
    public static void main(String[] args) {
        int[] arr1 = new int[2];
        int[] arr2 = new int[2];
        arr2 = arr1;  //arr1,arr2都指向了arr1所指向的堆空间。
    }
}

面试题:new String ("javaSE"); 创建了几个对象?

答:一个或者两个,若常量池中原来就有“JavaSE”,则只创建一个对象,否则创建两个对象。一个在堆上,一个在常量池。

字符串的不可变更特性

在String类内部,维护者着一个char类型的数组,且该数组被final修饰因此String类型是不可变更的。

private final char value[];

但是我们好像可以对String对象做很多变更操作,正如这样一段代码:

public static void main(String[] args) {
     String str = "hello";
     str += "world";
     System.out.println(str);
}

输出结果:helloworld

这不是与字符串不可变更相矛盾吗?对于这段代码,会在堆上重新创建一个String对象,里面存储的是world,然后再重新开辟一块堆空间,存放两个字符串相加的结果即helloworld,并让str指向这块堆空间,因为前面的两块堆空间都没有了具体的栈空间指向,因此成为了垃圾值,会被垃圾回收系统回收。
具体图解:
java学习总结-------String类(详解)_第4张图片

由此可见对于String类来说,执行每一次+操作就会生成两块垃圾空间,为了解决这个问题,引入了StringBuffer,和 StringBuilder这两个类。

看这样一段代码

public static void main(String[] args) {
    String str = "hehe";
    for(int i = 0; i < 100; i++){
        str += i;
    }
    System.out.println(str);
}

如果使用String类型每次+操作都会生成两块垃圾空间,造成资源浪费,因此在JVM内部会使用StringBuilder来优化String的+操作;
具体字节码如下
java学习总结-------String类(详解)_第5张图片那岂不是说我们只要使用Sting就好了,反正底层都会帮我们优化。
再看这样一段代码

public static void main(String[] args) {
    String str1 = "hello";
    String str2 = str1 + "world";
    String str3 = str1 + new String("java");
    String str4 = str1 + str3;
}

反编译后:
java学习总结-------String类(详解)_第6张图片可以发现new了很多StringBuilder对象。因此在涉及大量的字符串拼接工作还是使用StringBuffer或者String Builder。

你可能感兴趣的:(JavaSE)