String类的一些笔记

前言

Java关于值传递和引用传递不用多说肯定是重点,但是String类有点特殊,因此对String类的引用做一个学习。
以下测试条件是jdk 1.8

创建一个String

1.首先了解字符串常量池

总结:字符串常量池存在运行时常量池之中(在JDK7之前存在运行时常量池之中,而运行时常量池在方法区中),在JDK7已经将其转移到堆中。请注意,是字符串常量池转移到了堆中。而方法区中仍然有运行时常量池。

2.区分字符串常量池和堆

这里谈下个人理解,1.7之前放在方法区,就意味这字符串常量池和方法区有着部分相同点。那么即使是字符串常量池现在在堆中了,我们还应该把它当做是常量池,有着常量的特征。因此在堆中的字符串常量池决不能和堆中的对象划等号。

那么区分这两个原因是,创建字符串有两种方式:
直接赋值
此方式在方法区中字符串常量池中创建对象
1、String str = "flyapi";
构造器
此方式在堆内存创建对象
1、String str = new String();

3.小测试

3.1 测试part1

针对标题2,做如下测试。

        String s1 = "abc";
        String s2 = "abc";
        System.out.println(s1==s2);

        String s3 = new String("xyz");
        String s4 = new String("xyz");
        System.out.println(s3==s4);

结果是

true
false

这是因为,s1和s2的创建时在字符串常量池中,常量池中同一份字符串只允许出现一个,因此当第二次出现abc时,会导致s1和s2指向的是同一引用,因此二者指向地址相同。而s3和s4不同,了解引用传递就知道,这里新建了两个对象,虽然这两个对象中的值相同,但是地址不同,因此返回false。

3.2 测试Part2

        String s1 = new String("go") +new String("od");
        String s2 = s1.intern();
        String s3 = "good";
        System.out.println(s1 == s2);
        System.out.println(s2 == s3);
        System.out.println(s1 == s3);

结果是

true
true
true

3.3 测试Part 3

        String s4 = new String("ja") + new String("va");
        String s5 =  s4.intern();
        String s6 = "java";
        System.out.println(s4 == s5);
        System.out.println(s5 == s6);

结果为

false
true

3.4 针对part2 和part 3的结论

首先搞清楚这些语句发生了什么。大环境是jdk 1.8。以3.2的测试分析:

s1后面是由两个new出来的字符串对象拼接,因此首先产生了go和od两个对象,然后s1也是一个对象。此时都在堆中,然后由于第一次出现,会在常量池中添加good,并让s1指向常量池中的good。然后第二句调用intern方法。对于1.7以后,是将字符串加入到池中,只不过此时的字符串常量池在堆中。那么s2其实指向的就是这个常量池中的good,同样,s3是直接去常量池中找是否有good,发现存在,因此指向常量池中的good,因此s2和s3表示的字符串的地址相同。

那么s1为什么和s2地址相同,而s4和s5却不同呢。那时因为,在第一次出现good这个字符串,并且要加入到池中时,系统认为,不需要在堆中还放一个good对象,因此将s1指向了字符串常量池中的good,因此s1、s2和s3都指向了字符串常量池中的good。那么s4和s5为什么不同,因为java不是第一次出现在常量池了,jvm在运行时,常量池中已经有了java这个字符串,因此s4变成了一个独立的对象,不会指向字符串常量池中的java,而s5和s6则指向了字符串常量池的java。

3.5 补充验证

        String s1 = "good";
        String s2 = new String("go")+new String("od");
        String s3 = s2.intern();

        System.out.println("s1 == s2? " + (s1 == s2));
        System.out.println("s1 == s3? " + (s1 == s3));

如果我们先出现good,再出现拼接的,就会导致和上面java相同的结果,出现第2次时,s2此时不会指向常量池中的字符,而是自己单独一个指向了堆中的对象。

s1 == s2? false
s1 == s3? true

3.6 intern方法

1.7版本,返回常量池中该字符串的引用
(1) 当常量池中不存在"abc"这个字符串的引用,将这个对象的引用加入常量池,返回这个对象的引用。
(2) 当常量池中存在"abc"这个字符串的引用,返回这个对象的引用;

3.7 总结

部分语言描述的可能不太严谨。
(1)String str = new String(“abc”);创建了几个对象,常量池有abc字段是1个,常量池没有"abc"字段则是2个。一个在堆,一个在常量池
(2)String str=“abc”;创建了几个对象(如果常量池中已经包含abc字符串或其引用,那么该行代码需要做的只有,将str指向常量池中的abc字符串或其引用,无需再创建对象 而如果不包含,则只需将abc字符串添加到常量池中,再将str指向常量池中的abc字符串,此需要创建一个对象);
(3)new String(“abc”).intern();创建了几个对象(如果常量池里面已经有该字符串对象了就是1个,如果没有就是两个,一个是常量池的,一个是堆中的)

4.另一组测试

4.1

        String str1 = "HelloFlyapi";
        String str2 = "Hello";
        String str3 = "Flyapi";
        String str4 = str2 + str3;

        System.out.println(str1 == str4); // false

4.2

        String str1 = "HelloFlyapi";
        String str6 = "Hello" + "Flyapi";

        System.out.println(str1 == str6); // true

分析

造成s1和s4地址指向不同的原因是,JVM会在堆(heap)中创建一个以str2为基础的一个StringBuilder对象,然后调用StringBuilder的append()方法完成与str3的合并,之后会调用toString()方法在堆(heap)中创建一个String对象,并把这个String对象的引用赋给str4。

这里我个人的理解,不一定对,str4的字符串是两个字符串对象合并的,因此可以认为str4最终是堆中的对象,,因此与字符串常量池中的地址不同。而str6的字符串是直接由两个字符串常量合并的,因此可以认为str6最终仍然指向的是字符串常量池中的字符。

4.3 补充测试

  String s4=new String("str") + new String("01");
//生成了3个对象,常量池中对象"str"和"01",常量池中的字符串对象"str01"
  System.out.println(s4.intern() == s4);

结果:

true

这个写法和3.1中测试一样。

        String s4=new String("str") + new String("01");
        String s5 = new String(s4);
        System.out.println(s5.intern() == s5)

显然根据上面的分析,结果是false。原因是s5是指向堆中的对象,而s5.intern是指向常量池中字符的地址。

杂记 文章被收录于专栏

记录日常中碰到的一些疑难杂症

全部评论

相关推荐

爱喝奶茶的垂耳兔拥抱太阳:感觉项目和实习没有技术亮点和难点,单纯说了自己干了啥
点赞 评论 收藏
分享
03-29 14:19
门头沟学院 Java
你背过凌晨4点的八股文么:加油同学,人生的容错率很高,只是一个暑期罢了,后面还有很多机会!
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务