== 和 equles()基于字符串、基本数据类型、包装类应用的不同和原理

这篇具有很好参考价值的文章主要介绍了== 和 equles()基于字符串、基本数据类型、包装类应用的不同和原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。



前言

对于 == 和 equals() 大家都很熟悉,大多也知道结论,但是运用的时候,有时候根据结论来,完全是摸不着头脑,所以我在这系统的简述一下关于两者在基本数据类型、字符串类型、包装类这三个应用上的区别。

下面我主要是说 == 的运用,因为equals()对于字符串就是比较内容,不重写就是等号操作,所以我主要从堆栈和代码方面说明等号操作在不同情况应用上的不同


一、关于堆栈内存的解释

如果大家对堆栈没有太大的概念,基本类型数据、字符串数据怎么存储的,可以先去看一下下面这篇
浅淡JVM内存结构

二、== 和 equlas() 结论定义

  • == 比较的是内存地址是否一致,是不是同一个对象;
  • equals()被String重写比较的是内容是否一致,如果没有被重写,相当于 ==

String重写equals()源码,发现如果地址一致,就直接返回true,而如果判断是字符串的时候,就会判断长度,长度不一致,返回false,一致才会判断字符串值是否一致。

 public boolean equals(Object anObject) {
        if (this == anObject) {  判断地址,相等返回true
            return true;
        }
        if (anObject instanceof String) {e
            String anotherString = (String)anObject; 强转,使其可以调用String特有方法
            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;
    }

二、基本类型数据

1、示例

  • 基本数据类型:int、long、short、byte、char、float、double、boolean
  • 针对基本数据类型时,只要数据类型,且值相同, 操作符== 输出结果则为 true
     public static  void main(String[] args) {
       int a = 10;
       double b = 10;
       float c =10f;
       System.out.println(a==b);   输出true
       System.out.println(b==c);   输出true
    }

可知,所有的变量都定义在主函数(方法)中,说明主函数(方法)在压栈时,才会处理这些数据,把在这些局部变量存储到栈帧的局部变量表中。
比如 int a = 10 进入栈顶的时候,JVM会查询局部变量表,如果局部变量表中,没有字面值为10的值,则把10存入。当其他局部变量查询到局部变量表中已经有字面值10的地址时,会把引用直接指向该地址,所以输出才都是true.

一句话,只要局部变量表中存入已有的数据类型值,定义其他数据类型时,直接指向该值地址。

== 和 equles()基于字符串、基本数据类型、包装类应用的不同和原理,java


三、字符串类型

1、示例

1、先来个经典示例

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

为什么输出true?

1、我们知道String是不可变的,所以它在编译期就已经确定了,所以String s2 = “a” + "b"会被编译器优化,直接赋值,即 s2 = “ab”
2、被加载到JVM时,方法被压栈,引用类型s1、s2随之入栈,但是JVM单独为String数据类型在堆中开辟一个字符串常量池空间,所以字面值 ab 会加载至字符串常量池中
3、s1的字面值存入字符串常量池后,s2在字符串常量词中查询到已存在 ab 的地址,所以直接s2引用该地址

可能有人问,都在方法内,为什么不是存入局部变量表?

1、局部变量表只存储基本数据类型和引用类型数据(不是对象本身),所以只存储了引用类型 s1、s2,没有存储字面值
2、JVM为String类型开辟了常量池技术,所以字符串会指向常量池的地址。


2、如下的代码示例,又为什么是false呢?

    public static  void main(String[] args) {
        String s1 = "ab";
        String s2 = new String("ab");
        System.out.println(s1 == s2);   输出false
    }

1、首先需要知道使用String s2 = new String(“ab”);创建字符串对象时的两大步骤

  • 其一:在编译过程中,会把字符串放入到静态常量池在类加载后,字符串会被加载至字符串常量池中,如果字符串常量池中已经存在该字符串,则忽略此步骤;
  • 其二:主要在于 new 关键字,new 关键字在堆中创建并初始化该对象,静态常量池字符串地址引用堆地址,建立关系。

2、所以s1压栈,ab进入字符串常量池,s2 在入栈的时候,先查询字符串常量池是否有字符串ab,发现已存在,则直接在堆中创建开辟一个新的内存空间,然后字符串常量池中引用s2堆地址
3、== 比较的是两者的地址,s1 的字面值地址在字符串常量池中,s2 的字面值地址则在堆中,所以自然两者不对等

== 和 equles()基于字符串、基本数据类型、包装类应用的不同和原理,java


3、两个字符拼接呢?再看如下代码

    public static  void main(String[] args) {
        String s1 = "ab";
        String s2 = "ab";
        String s3 = s1 + s2;
        String s4 = "abab";
        System.out.println(s3 == s4);  输出false

    }

1、当String的字面值不确定时,会到方法压栈的时候,即运行时才会确定
2、当s1、s2字面值都确定时,则在编译器就会存储至静态常量池中,但s3的字面值不确定,虽然它被s1+s2赋值,但是本身可改变,编译器不知道具体,所以到运行期间,才会 new 创建对象赋值于s3
3、s3的地址创建在堆中和s4的地址并不一致,所以输出为false

换个方式,加个final

    public static  void main(String[] args) {
        final String s1 = "ab";
        final String s2 = "ab";
        String s3 = s1 + s2;
        String s4 = "abab";
        System.out.println(s3 == s4);  输出true

    }

为什么输出 true 呢?

我们知道String的不可变性,String类不可更改,意味着每次定义和修改都是创建新的对象,所以定义的引用类型还是可以修改的,只是变成了一个新的对象,但是如果加上了final关键字,意味着引用类型变量也不可更改了,即s1、s2不可修改了,所以s3也确定了,不可更改,在编译期间就直接赋值于s4=“abab”,所以s3和s4相等

上面是我个人的理解,具体原理如下:
String s3 = s1 + s2;相当于String s3 = new StringBuilder(s1).append(s2).toString();
创建一个对象StringBuilder使用append()和toString()方法拼接两个字符串,然后创建并返回一个字符串

    public String toString() {
        return new String(value, 0, count); 创建并返回一个字符串
    }    

主要是因为String类的不可变性,每次字符串拼接都会创建一个新的字符串对象,存在大量的对象创建,如果没有及时回收,会造成大量的内存资源的浪费,所以JVM为了优化性能,其StringBuffer对象则代表一个字符序列可变的字符串,可动态改变不会产生新的对象,所以拼接使用StringBuilder的append方法,避免了这种对象的大量创建和消耗。


4、 intern() 方法

  • 当调用 intern() 方法时,如果字符串池中已经存在相同内容的字符串,则返回字符串池中的引用;否则,将该字符串添加到字符串池中,并返回对字符串池中的新引用
  • 简而言之,intern()方法就是为了把new出来的字符串加入字符串常量池中

看下面代码:此时字符串常量池中只有Hello World和!,但没有Hello World!,执行s0.intern()时,s0字符串加入字符常量池中,s1引用s0地址,所以s0、s1的地址都一致

        String s0=  new StringBuilder().append(new String("Hello World")).append(new String("!")).toString();; //创建String并返回
        String s1=s0.intern();  //此时字符串常量池中没有Hello World!
        System.out.println(s1 == s0);  输出true

== 和 equles()基于字符串、基本数据类型、包装类应用的不同和原理,java


如果字符串在字符串常量池中存在,又会是怎么的呢?

        String s0=  new StringBuilder().append(new String("Hello World")).append(new String("!")).toString();; //创建String并返回
        String s1="Hello World!";
        String s2=s0.intern();  //此时字符串常量池中存在Hello World!
        System.out.println(s0 == s1);  输出false
        System.out.println(s1 == s2);  输出true

因为字符串常量池中已存在字符串Hello World!,所以s0.intern()方法直接返回s1在字符串常量池中的地址,并赋予s2
== 和 equles()基于字符串、基本数据类型、包装类应用的不同和原理,java

再来个对比,如下

        String s4 = new String("abcd");
        String s5 = s4.intern();
        System.out.println(s4 == s5);  输出false

还记得new String()吧,String s4 = new String(“abcd”);会先查询字符串常量池,没有则创建一个字符串在常量池中,然后再在堆中,创建实例,s4.intern()方法调用时,发现字符串常量池中已经有abcd字符串,知道把字符串常量池中的地址赋予s5,所以s4的指向地址在堆中,而s5指向地址在常量池中,自然不对等。

当然也还有其他情况,但是记住原理,都能做出来


2、总结

  1. 普通定义字符串时,在编译期已经确定并优化加号,该字符串会加入常量池,如果字符串一致,则 == 输出true
  2. new字符串时,会在常量池中创建该字符串对象,并在堆中创建该字符串对象实例,即有两个对象。分为两个情况,字符串常量池没有该字符串,则创建,已有则常量池返回堆中地址,建立关系。
  3. intern() 方法主要是把字符串插入字符串常量池中,如果已有,则直接引用常量池地址,如果没有,则插入字符串,并使字符串地址引用堆中地址

四、包装类类型示例

1、装箱和拆箱

基本数据类型都有自己的包装类:Integer、Long、Character、Byte、Short、Boolean、Float、Double
Float、Double并没有实现自己的缓存机制
== 和 equles()基于字符串、基本数据类型、包装类应用的不同和原理,java
简单理解就是:装箱就是基本类型转换成封装类型, 拆箱就是封装类型转换成基本类型
用int、Integer举例

装箱:基本数据类型——>包装类

Integer a = 2;    等于 Integer a =Integer.valueOf(2);     

部分源代码

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)   判断是否在范围区间内【-128 -127return IntegerCache.cache[i + (-IntegerCache.low)];  是,则返回IntegerCache缓存中的数据
        return new Integer(i);  不是,新建一个对象
    }

拆箱:包装类——>基本数据类型

        Integer a = 2;
        int i = a.intValue();  

总之其他基本数据类型也差不多如此,这里不多说,原理挺简单的,大家可以看一下源码即可

结论:

  1. Byte、Integer、Short、Long缓存范围都是【-128 -127】,在这个范围内的数据,直接从缓存中取出该数据实例即可,如果不是这个范围内,则需要new,在堆中新建实例对象
  2. Float、Double并没有实现自己的缓存机制
  3. Character取值范围在【0-127】之间

2、示例

直接写结论:

  • Integer 在范围内时,直接从缓存区拿数据,数字相同即==输出true
  • 包装类数值不在范围内时,需要在堆中创建一个对象
  • 基本数据类型和包装类对比时,只要不是new,数字相同即==输出true
    public static  void main(String[] args) {
        int a = 128;
        Integer s1 = 127;   //相当于   Integer s1 = Integer.valueOf(127);
        Integer s2 = 127;   //相当于   Integer s2 = Integer.valueOf(127);
        Integer s3 = 128;   //相当于   Integer s3 = new Integer(128);
        Integer s4 = 128;   //相当于   Integer s4 = new Integer(128);
        Integer s5 = new Integer(127);


        System.out.println(s1==s2);  输出true  都在范围,直接从缓冲区取
        System.out.println(s2==s3);  输出false s3不在范围内,在堆中new对象,地址不一样
        System.out.println(s2==s4);  输出false s5在堆中new对象,地址不一样
        System.out.println(a==s4);   输出true  可以理解为a在局部变量表中,s4直接获取
    }

总结

以上内容,不懂可以评论区询问,我看到,就回你
包装类有一部分知识大家可以去了解一下,比如类型转换,笔试题通常有(嘿嘿,虽然不多)文章来源地址https://www.toymoban.com/news/detail-841618.html

到了这里,关于== 和 equles()基于字符串、基本数据类型、包装类应用的不同和原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【⑭MySQL | 数据类型(二)】字符串 | 二进制类型

    ✨欢迎来到小K的MySQL专栏,本节将为大家带来MySQL字符串 | 二进制类型类型的分享 ✨ 5 字符串类型 字符串类型用来存储字符串数据,还可以存储图片和声音的二进制数据。字符串可以区分或者不区分大小写的串比较,还可以进行正则表达式的匹配查找。 下表中列出了 MySQL 中

    2024年02月11日
    浏览(50)
  • python教程 入门学习笔记 第6天 数据类型转换 字符串转换成数值 数值之间互转 其它类型转字符串

    s1=\\\"188\\\" #字符串 ns1=int(s1) #转换成整型数值 print(ns1+8) #打印数值结果 s1=\\\"3.14\\\" #字符串 ns1=float(s1) #转换成浮点型数值 print(ns1+3) #打印数值结果(数值结果为6.140000000000001,出现误差,后面讲解决办法) print(type(ns1)) #获取新数值的数据类型属性 z1=78 nz1=float(z1) print(nz1) #打印结果

    2024年02月14日
    浏览(75)
  • 【byte类型数据转换16进制字符串】

    1.byte类型数据长度为8bit(8位), 例如00101110。 2.16进制字符长度4bit(4位), 例如1101,表示D。 3.那么一个byte可以用2(8bit/4bit=2)个16进制字符表示。 4. 1中的00101110可分为0010和1110两部分。 5. 0010可以由00101110右移动4位获得,即001011104。注意,在java中byte是无符号的,全为正的,所有应该做

    2024年02月16日
    浏览(54)
  • Python标准数据类型-String(字符串)

    ✅作者简介:CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1 📃个人主页:hacker707的csdn博客 🔥系列专栏:零基础入门篇 💬个人格言:不断的翻越一座又一座的高山,那样的人生才是我想要的。这一马平川,一眼见底的活,我不想要,我的人生

    2024年02月03日
    浏览(49)
  • Python标准数据类型-字符串常用方法(下)

    ✅作者简介:CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1 📃个人主页:hacker707的csdn博客 🔥系列专栏:零基础入门篇 💬个人格言:不断的翻越一座又一座的高山,那样的人生才是我想要的。这一马平川,一眼见底的活,我不想要,我的人生

    2024年02月04日
    浏览(61)
  • 各个数据类型的内置方法(字符串和列表)

    数字类型主要就是用来做数学运算与比较运算,因此数字类型除了与运算符结合使用之外,并无需要掌握的内置方法 strip, lstrip, rstrip lower(), upper() startswith, endswith 格式化输出之format format的其他使用方式 split,rsplit join replace isdigit

    2024年02月10日
    浏览(43)
  • python教程 入门学习笔记 第4天 数据类型 获取数据类型 字符串拼接

    数据类型 1、能直接处理的基本数据类型有5个:整型、浮点型、字符串、布尔值、空 1)整型(int)=整数,例如0至9,-1至-9,100,-8180等,人数、年龄、页码、门牌号等 没有小数位的数字,是整型 2)浮点型(float)=小数,例如金额、身高、体重、距离、长度、π等 精确到小

    2024年02月14日
    浏览(54)
  • Rust字符串:安全、高效和灵活的数据类型

    Rust是一种现代的系统级编程语言,以其出色的内存安全性和高性能而受到广泛关注。在Rust中,字符串是一种重要的数据类型,它具有独特的特点,使其在处理文本和字符数据时成为理想的选择。本文将深入探讨Rust字符串的特性,包括安全性、高效性和灵活性,以帮助您更好

    2024年01月19日
    浏览(53)
  • Python标准数据类型-字符串常用方法(上)【文末送书】

    ✅作者简介:CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1 📃个人主页:hacker707的csdn博客 🔥系列专栏:零基础入门篇 💬个人格言:不断的翻越一座又一座的高山,那样的人生才是我想要的。这一马平川,一眼见底的活,我不想要,我的人生

    2024年02月03日
    浏览(79)
  • Python标准数据类型-字符串常用方法(下)【文末送书】

    ✅作者简介:CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1 📃个人主页:hacker707的csdn博客 🔥系列专栏:零基础入门篇 💬个人格言:不断的翻越一座又一座的高山,那样的人生才是我想要的。这一马平川,一眼见底的活,我不想要,我的人生

    2024年02月11日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包