美团面试题:String s = new String("111")会创建几个对象?

今日推荐编程大佬是否能记住代码,不用百度就能啪啪啪敲出来么?在 IDEA 中的各种调试技巧,轻松定位 Bug(超级全面)

后端接口如何提高性能?

16 个写代码的好习惯

为什么不推荐使用BeanUtils属性转换工具

盘点阿里巴巴 34 个牛逼 GitHub 项目

来源:blog.csdn.net/o9109003234/article/details/109523691

String不可变吗?

public class App {
    public static void main(String[] args) {
        String a = "111";
        a = "222";
        System.out.println(a);
    }
}

有的人会认为上面这段代码应该输出:111

这样才和上面的不变性吻合。

哈哈哈,但是并不是这样滴。

222

这不对呀,不是不变吗?怎么变了呢?

其实在JVM的运行中,会单独给一块地分给String。

上面的:

Stirng a="111";

我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:

使用字符串常量池。每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串。

这里先去JVM给常量池里找,找到了就不用创建对象了,直接把对象的引用地址赋给a。找不到会重新创建一个对象,然后把对象的引用地址赋给a。同理a="222";也是先找,找不到就重新创建一个对象,然后把对象的引用地址赋给a。

大家有没有发现我上面的描述中“引用地址”。比如说 Object obj = new Object();很多人喜欢成obj为对象,其实obj不是对象,他只是一个变量,然后这个变量里保存一个Object对象的引用地址罢了。

引用类型声明的变量是指该变量在内存中实际存储的是一个引用地址,实体在堆中。

所以网上很多文章老喜欢这么说

User user = new User()

创建了一个user对象,老喜欢把user称之为对象。这里不接受反驳。

所以上面String a = “111”;表达的是变量a里保存了“111”这个对象的引用地址。变量是可以变的,不能变的是“111”。

String 为什么是不可变的?

简单的来说,String 类中使用 final 关键字字符数组保存字符串。代码如下:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    private final char value[];
    public String() {
        this.value = "".value;
    }
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
}   

从上面的这段源码中可以看出三点:

String 类是final修饰

String存储内容使用的是char数组

char数组是final修饰

这里就得复习一下,final有啥用?

当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。

当final修饰的方法表示此方法已经是“最后的、最终的”含义,亦即此方法不能被重写(可以重载多个final修饰的方法)。此处需要注意的一点是:因为重写的前提是子类可以从父类中继承此方法,如果父类中final修饰的方法同时访问控制权限为private,将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与final的矛盾,而是在子类中重新定义了新的方法。(注:类的private方法会隐式地被指定为final方法。)

当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化。如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。另外final修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,(1.在申明的时候给其赋值,否则必须在其类的所有构造方法中都要为其赋值)

比如:

/**
 * Description: final修饰变量
 * @author : 田维常
 * 欢迎关注:java后端技术全栈
 */
public class FinalDemo {
    private final String name;

    public FinalDemo(String name) {
        this.name = name;
    }

    public FinalDemo() {
    }
}

这是会会报错

关于final就简单说到这里

下面来看一个使用String的 案例

/**
 * Description:
 *
 * @author : 田维常
 * @date : 2020/11/3
 * 欢迎关注公众号:java后端技术全栈
 */
public class StringDemo {
    public static void main(String[] args) {
        String name = "老田";
        name.concat("!");
        System.out.println(name);
        System.out.println(name.concat("!"));
    }
}

输出

顺道溜达溜达 String中几个常用方法源码

    public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            //啥都没有,就直接把当前字符串给你
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        //看到了吗?返回的居然是新的String对象
        return new String(buf, true);
    }
    void getChars(char dst[], int dstBegin) {
        System.arraycopy(value, 0, dst, dstBegin, value.length);
    }    
    public String replace(char oldChar, char newChar) {
        //如果两个是一样的,那就必要替换了,所以返回this
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            //把当前的char数组复制给val,然后下面基于val来操作
            char[] val = value; 

            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                //创建一个新的char数组
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                //创建一个新的String对象
                return new String(buf, true);
            }
        }
        return this;
    }

    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);
        }
        //正常返回的都是新new出来的String对象
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

    public String trim() {
        int len = value.length;
        int st = 0;
        char[] val = value;    /* avoid getfield opcode */

        while ((st < len) && (val[st] <= ' ')) {
            st++;
        }
        while ((st < len) && (val[len - 1] <= ' ')) {
            len--;
        }
        //如果是该字符串中包含了空格,调用substring方法,否则就是啥都没干原本返回
        //就是如果字符串里有空格,那么还是新生一个String对象返回
        return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
    }

无论是concat、replace、substring还是trim方法的操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。

得出两个结论:

String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何变化性的操作都会生成新的对象。

String对象每次有变化性操作的时候,都会从新new一个String对象(这里指的是有变化的情况)。

回到前面的例子

//String a = "111";相当于
char data [] ={'1','1','1'};
Stirng a = new String(data);
//a = "222";
char data [] ={'2','2','2'};
a = new String(data);

这会变量a里保存的是"222"对应String对象的引用。

继续看下面的代码

public class App {
    public static void main(String[] args) {
        String a = "111";
        String a1 = "111";

        String b = new String("111");

        //对象地址是同一个
        System.out.println(a==a1);
        //对象内容是一样的
        System.out.println(a.equals(a1));
        //对象地址不一样
        System.out.println(a==b);
        //对象内容是一样的
        System.out.println(a.equals(b));
    }
}

输出

true
true
false
true

第一个输出true,说明a和a1两个变量保存的引用地址是同一个。

第二个也输出true,说明a和a1引用地址中内容是一样的。

a和a1放在栈上,存放着对象的引用地址。

new的对象是在堆中。

常量其实是要看jdk版本的。

所以String a = "111"; 在JVM申请内存存放"111"对应的对象,并将对象保存起来。当String a1="1111";的时候,会先去JVM的那块地里寻找是否存在"111",刚好前面保存过,所以找到,然后直接把对象的引用地址给了a1。所以此时的a和a1都保存着同一个引用地址。

接触java后都知道可以new一个对象。所以 String b = new String("111");就是创建一个对象然后把对象引用地址赋给变量b。但是这里有个特殊点,那就是(“111”),这里会先去JVM里的那块地里找找,找到了直接存放引用地址。找不到创建一个对象然后把引用地址给String的有参构造方法里。

所以第三个中输出false,因为a和b所保存的对象引用是不一样的。

最后一个输出true。那是因为两个变量所保存的引用地址中的内容都是“111”.

答案:

如果常量池中存在,则只需创建一个对象,否则需要创建两个对象。

推荐文章
更多项目源码


  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
<p style="color:#666666;"> <span style="font-size:14px;">本门课程重实战,将基础知识拆解到项目里,让你在项目情境里学知识。</span> </p> <p style="color:#666666;"> <span style="font-size:14px;">这样的学习方式能让你保持兴趣、充满动力,时刻知道学的东西能用在哪、能怎么用。</span> </p> <p style="color:#666666;"> <span style="font-size:14px;">平时不明白的知识点,放在项目里去理解就恍然大悟了。</span> </p> <p style="color:#666666;"> <span></span> </p> <p style="color:#666666;"> <span style="font-size:14px;"> </span> </p> <p style="color:#666666;"> <span style="color:#FF0000;font-size:14px;"><strong>一、融汇贯通</strong></span> </p> <p style="color:#666666;"> <span style="font-size:14px;">本视频采用了前后端分离的开发模式,前端使用Vue.js+Element UI实现了Web页面的呈现,后端使用Python 的Django框架实现了数据访问的接口,前端通过Axios访问后端接口获得数据。在学习完本章节后,真正理解前后端的各自承担的工作。</span> </p> <p style="color:#666666;"> <span style="font-size:14px;"> </span> </p> <p style="color:#666666;"> <span style="color:#FF0000;font-size:14px;"><strong>二、贴近实战</strong></span> </p> <p style="color:#666666;"> <span style="font-size:14px;">本系列课程为练手项目实战学生管理系统v4.0的开发,项目包含了如下几个内容项目的总体介绍、基本功能的演示、Vuejs的初始化、Element UI的使用、在Django中实现针对数据的增删改查的接口、在Vuejs中实现前端增删改查的调用、实现文件的上传、实现表格的分页、实现导出数据到Excel、实现通过Excel导入数据、实现针对表格的批量化操作等等,所有的功能都通过演示完成、贴近了实战</span> </p> <p style="color:#666666;"> <span style="font-size:14px;"> </span> </p> <p style="color:#666666;"> <span style="color:#FF0000;font-size:14px;"><strong>三、课程亮点</strong></span> </p> <p style="color:#666666;"> <span style="font-size:14px;">在本案例中,最大的亮点在于前后端做了分离,真正理解前后端的各自承担的工作。前端如何和后端交互</span> </p> <p style="color:#666666;"> <span style="font-size:14px;"> </span> </p> <p style="color:#666666;"> <span style="color:#FF0000;font-size:14px;"><strong>适合人群</strong></span> </p> <p style="color:#666666;"> <span style="font-size:14px;">1、有Python语言基础、web前端基础,想要深入学习Python Web框架的朋友;</span> </p> <p style="color:#666666;"> <span style="font-size:14px;">2、有Django基础,但是想学习企业级项目实战的朋友;</span> </p> <p style="color:#666666;"> <span style="font-size:14px;">3、有MySQL数据库基础的朋友</span> </p> <p style="color:#666666;"> <span style="font-size:14px;"> </span> </p> <p style="color:#666666;"> <span style="font-size:14px;"><img alt="" src="https://img-bss.csdnimg.cn/202009070752197496.png" /><br /> </span> </p> <p style="color:#666666;"> <span style="font-size:14px;"><br /> </span> </p>
<div style="color:rgba(0,0,0,.75);"> <span style="color:#4d4d4d;"> </span> <div style="color:rgba(0,0,0,.75);"> <span style="color:#4d4d4d;"> </span> <div style="color:rgba(0,0,0,.75);"> <div style="color:rgba(0,0,0,.75);"> <span style="color:#4d4d4d;">当前课程中商城项目的实战源码是我发布在 GitHub 上的开源项目 newbee-mall (新蜂商城),目前已有 6300 多个 star,</span><span style="color:#4d4d4d;">本课程是一个 Spring Boot 技术栈的实战类课程,课程共分为 3 大部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 商城项目功能的讲解,让大家实际操作并实践上手一个大型的线上商城项目,并学习到一定的开发经验以及其中的开发技巧。<br /> 商城项目所涉及的功能结构图整理如下<br /> </span> </div> <div style="color:rgba(0,0,0,.75);">   </div> <div style="color:rgba(0,0,0,.75);"> <p style="color:#4d4d4d;"> <img alt="modules" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3N0b3JlL25ld2JlZS1tYWxsLXMucG5n?x-oss-process=image/format,png" /> </p> </div> <p style="color:rgba(0,0,0,.75);"> <strong><span style="color:#e53333;">课程特色</span></strong> </p> <p style="color:rgba(0,0,0,.75);">   </p> <div style="color:rgba(0,0,0,.75);">   </div> <div style="color:rgba(0,0,0,.75);"> <ul> <li> 对新手开发者十分友好,无需复杂的操作步骤,仅需 2 秒就可以启动这个完整的商城项目 </li> <li> 最终的实战项目是一个企业级别的 Spring Boot 大型项目,对于各个阶段的 Java 开发者都是极佳的选择 </li> <li> 实践项目页面美观且实用,交互效果完美 </li> <li> 教程详细开发教程详细完整、文档资源齐全 </li> <li> 代码+讲解+演示网站全方位保证,向 Hello World 教程说拜拜 </li> <li> 技术栈新颖且知识点丰富,学习后可以提升大家对于知识的理解和掌握,可以进一步提升你的市场竞争力 </li> </ul> </div> <p style="color:rgba(0,0,0,.75);">   </p> <p style="color:rgba(0,0,0,.75);"> <span style="color:#e53333;">课程预览</span> </p> <p style="color:rgba(0,0,0,.75);">   </p> <div style="color:rgba(0,0,0,.75);">   </div> <div style="color:rgba(0,0,0,.75);"> <p style="color:#4d4d4d;"> 以下为商城项目的页面和功能展示,分别为 </p> </div> <div style="color:rgba(0,0,0,.75);"> <ul> <li> 商城首页 1<br /> <img alt="" src="https://img-bss.csdnimg.cn/202103050347585499.gif" /> </li> <li> 商城首页 2<br /> <img alt="" src="https://img-bss.csdn.net/202005181054413605.png" /> </li> <li>   </li> <li> 购物车<br /> <img alt="cart" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3Byb2R1Y3QvY2FydC5wbmc?x-oss-process=image/format,png" /> </li> <li> 订单结算<br /> <img alt="settle" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3Byb2R1Y3Qvc2V0dGxlLnBuZw?x-oss-process=image/format,png" /> </li> <li> 订单列表<br /> <img alt="orders" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3Byb2R1Y3Qvb3JkZXJzLnBuZw?x-oss-process=image/format,png" /> </li> <li> 支付页面<br /> <img alt="" src="https://img-bss.csdn.net/201909280301493716.jpg" /> </li> <li> 后台管理系统登录页<br /> <img alt="login" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3Byb2R1Y3QvbWFuYWdlLWxvZ2luLnBuZw?x-oss-process=image/format,png" /> </li> <li> 商品管理<br /> <img alt="goods" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3Byb2R1Y3QvbWFuYWdlLWdvb2RzLnBuZw?x-oss-process=image/format,png" /> </li> <li> 商品编辑<br /> <img alt="" src="https://img-bss.csdnimg.cn/202103050348242799.png" /> </li> </ul> </div> </div> </div> </div>
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值