java基础:关于String类

区别于java中的基本类型,用来表示字符串类型的这个String可谓是独树一帜,玉树临风。估计搞java的用到的最多的,或者面试中遇到最多的,就是这个String了,今天,我们就好好会一会这个String类。

到底创建了几个对象

一般的,我们创建String对象的方式有两种:

  • 方式一:String str1 = “abc”;
  • 方式一:String str2 = new String(“abc”);

    两种方式有什么不同呢?直接用代码验证下:

/**
 * Created by fei on 2017/7/11.
 */
public class StringTest {
    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = new String("abc");
        String str3 = "a"+"b"+"c";

        System.out.println(str1 == str2);
        System.out.println(str1.equals(str2));
        System.out.println(str1 == str3);
        System.out.println(str2 == str3);

        System.out.println("str1 的哈希值:" + str1.hashCode());
        System.out.println("str2 的哈希值:" + str2.hashCode());
        System.out.println("str3 的哈希值:" + str3.hashCode());
    }
}

运行结果:

false
true
true
false
str1 的哈希值:96354
str2 的哈希值:96354
str3 的哈希值:96354

如果你对这个结果并不感到疑惑,那你就没必要看这篇文章了。如果你大概明白是怎么一回事或者你压根没想到是这样的结果,那么可以继续跟着博主一起往下探寻。

想要讲清楚上面的示例,那么不得不说说“==”、“equals”和“hashCode”这三个东西。

关于String中的“==”与“equals”

“==”其实就是对比的内存单元上的内容,对于基本类型来说,内存单元中的内容就是基本类型的值,也就是说比较的是基本类型的值。而对于String对象来说,它的内存单元上储存的内容就是对象的逻辑地址值,也就是所说的引用值。每一个对象有一个对应的引用值。如果用“==”判断两个对象返回结果为“true”,那么证明这两个对象的逻辑地址是一样的,换句话说,这两个相比较的对象是同一个对象。例如上面例子中的“str1”和“str3”。
那么这个“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;
    }

可以看出,一开始先用“==”进行比较,如果是同一个对象,那么直接返回true。然后在判断anObject参数类型是否为“String”的基础上再判断这个anObject中维护的char数组的每一个值是否与相比较的String中的char数组中的值一样,如果都一样,就返回true,否则返回false。
我们要知道的是,这个equals方法是String类重写了Object类中的equals方法,在Object中是这样定义的:直接使用“==”进行判断。

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

现在,我们可以根据了解到的“==”与“equals”来对上面例子的结果来进行一下分析,来对这小结题目进行回答。
对于str1与str3,根据结果可以看出,它们两个是同一个对象,为什么是这样的结果呢?因为这是编译器的优化得到的结果,在编译器看来,“a”+“b”+“c”这三个字符都是常量,都是已经确定的值,没有必要等到运行期间就能确定,所以在编译期间,就已经做完了”+”操作,所以它的结果和”abc”是一样的.
当然,这只解释了str1和str3引用指向的数值是一样,但没有解决str1和str3的引用值是一样的.解释这个结果也和简单,因为String类指向得具体字符串是存放在常量池中的,因为空间对于程序来说是一种宝贵的资源,当然要尽可能的最大限度利用好,那么好,如果你知道了,这个字符串是已经确定的而且不会变的了,你会开辟两块或者更多一样多的空间来存放一样的字符串吗?当然没必要这么做!
还有我们必须知道的是,String类是用final关键字修饰的,也就是不可变的。(关于final关键字,可以查看博主的另一篇博客:java基础:关于final关键字)那么好,String类型不可变,它所指向常量池中的字符串也不可变并且是相同的,那么,语言设计者还有必要把这两个对象设计成不同的对象吗?
弄清楚这些,我们再来看看str2,看看这种创建方式,关键在这个new关键字,与str1或者str3不同的是,一旦使用了new关键字,那么就会新开辟一个内存空间来存放这个str2对象,换句话说,也就是说,不管我这个str2指向的常量池中是否已近有了相同的字符串了,我都会新创建一个新的对象来指向这个常量池的”abc”.这个new操作相当于做了这两个动作:

String object = "abc";
String str2 = new String(object );

现在应该能解决本小结题目的问题了吧?

关于String中的hashCode

可能还有人对这个hashCode打印的结果比较疑惑,为啥博主说了这么多,不管是不是同一个对象,它们三个的hashCode都是一样的呢?
上源码就能很容易弄明白为啥会是这结果

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

看明白了吗?这个String重写的hashCode方法求哈希值的方式完全是根据String中char数组的值来计算的,也就是说,只要指向常量池中的字符串是相同的,那么它们的hashCode值也就是一样的.不信?那我们继续来看看:

        String a = "a";
        String b = "b";
        String c = "ab";
        String d = a+b;
        System.out.println("c 的哈希值:" + c.hashCode());
        System.out.println("d 的哈希值:" + d.hashCode());

结果:

c 的哈希值:3105
d 的哈希值:3105

注意 : 我们要知道hashCode到底是个啥玩意 ? 要注意hashCode绝不是用来判断两个对象是否是同一个对象的 , hashCode的设计思想就是用数字来标识一个对象 , 但这个数字代表的意义绝对不是对象的地址值.而且两个不同的对象它们的hashCode完全有可能是相同的 !
HashMap就在hashCode的设计思想表现的淋漓尽致 : HashMap通过对象的hashCode值将不同的对象尽可能的分散存放在一个链表数组(数组中的元素是一条链表)中 , 把拥有相同hashCode的不同对象存放在一条链表上.在HashMap中,hashCode就起到了”标识对象”与”分散对象”的作用.

再谈谈final

讲完上面的内容,随博主再看几个例子:

/**
 * Created by fei on 2017/7/11.
 */
public class StringTest {
    public static void main(String[] args) {

        String a = "a";
        String b = "b";
        String c = "ab";
        String d = a+b;
        final String f_a = "a";
        final String f_b = "b";
        String f_d = f_a + f_b ;
        System.out.println(c == d);
        System.out.println(c == f_d);

        System.out.println("c 的哈希值:" + c.hashCode());
        System.out.println("d 的哈希值:" + d.hashCode());
        System.out.println("f_d 的哈希值:" + d.hashCode());
    }
}

运行结果:

false
true
c 的哈希值:3105
d 的哈希值:3105
f_d 的哈希值:3105

这是咋回事 ? d 不就是”ab”吗 ? 怎么返回false ? 在这里我们要知道 , 虽然我们知道d的结果显然是”ab”,但是编译器可不知道 , 在还没进入运行期间 , 编译器可不知道这个 a 、b变量指向的是常量池的哪个值 , 这个时候 , 编译器当然要给d创建一个新的对象啦。
那么好,我们在看看final修饰的String。由于是用final修饰,那么编译器看到这个final可就知道了,这个 f_a 和 f_b 都是不可变的引用变量了,那我就能把你看成常量对待了。所以,在编译期间,这个“f_a + f_b”操作就相当于“ “a”+”b” ”操作了。当然这个返回结果就是true了。

String的不可变性

看一眼String的源码,就会知道,String类是用final关键字修饰的,那么就是说,String对象一旦创建了就不可变了。这是什么意思呢?

        String a = "ab";
        String b = a + "c";
        String c = "a"+"b"+"c";
        System.out.println(b == c);

结果应该不用再解释了,肯定是false。那么 b 与 c 这两种方式到底有什么不用呢?哪里体现了String的不可变性呢?重点说一下b 这种创建方式,其实,它类似于下面的代码:

        StringBuilder temp = new StringBuilder();
        temp.append(a).append("b");
        String b =  temp.toString();

可以看出,我们每次类似这样进行“+”操作的时候(要区别在String进行初始化用的这个方式:String c = “a”+”b”+”c”),都会使用一个临时的StringBuilder 对象进行append操作,并且 b 是根据进行append操作得到的最后的结果toString( )方法得到的新的对象。所以以后使用String类进行 “+” 操作的时候一定要注意,因为会创建StringBuilder 对象,”+” 操作很频繁的话,对内存是一种不小的消耗。尽量使用StringBuilder 或者StringBuffer 类代替。
关于String的不可变性,我们可以再看看String中的源码,其中涉及到String的方法,都是通过新建一个String对象来实现的。比如substring方法:

    public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
                //如果beginIndex 不为0 并且 endIndex 不为value.length
                //(也就是说,事实上截取的是整个字符串),那么,就返回一个新建的字符串
    }

哈哈,最后给大家出一道思考题:

public class StringTest {
    public static void main(String[] args) {
        String a = "ab";
        String b = getA() + "b";
        System.out.println(a == b);
    }

    public static String getA(){
        return "a";
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值