您好!欢迎来到源码码网!

细说String和hashCode以及普通类、抽象类、接口和java的clone和Math等基础知识

  • 行业资讯
  • 来源:源码码网
  • 编辑:admin
  • 时间:2020-05-14 16:46
  • 阅读:1092

新手在刚开始学习的时候一定会遇到这个问题,那就是== 和 equals 的区别是什么?


这个问题在很多教程和博客会进行讲解并喜欢拿String对象的实例来举例子,而在实际,equals 本质上就是==,查看object.equals方法,我们可以知道,equals 默认情况下是引用比较,只是由于而很多类很多类重写了 equals 方法,例如String 和 Integer 把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。而== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用。


而既然提到String类,那么我们便来多说一说String的特殊之处:


关于String

在JAVA虚拟机(JVM)中存在着一个字符串池,其中保存着很多String对象,并且可以被共享使用,这样带来的好处是提高了效率。由于String类是final的,它的值一经创建就不可改变,因此我们不用担心String对象共享而带来程序的混乱。字符串池由String类维护,我们可以调用intern()方法来访问字符串池。


String的底层实现

String之所以可以存在常量池中,是因为这是JVM的一种规定。而实际上查看String源码,就可以看到,在 Java 8 中,String 内部使用 char 数组存储数据的;这是本人jdk1.8.0_181版本下String类的部分源码


public final class String

    implements java.io.Serializable, Comparable<String>, CharSequence {

    /** The value is used for character storage. */

    private final char value[];


    /** Cache the hash code for the string */

    private int hash; // Default to 0


    /** use serialVersionUID from JDK 1.0.2 for interoperability */

    private static final long serialVersionUID = -6849794470754667710L;


    /**

     * Class String is special cased within the Serialization Stream Protocol.

     *

     * A String instance is written into an ObjectOutputStream according to

     * <a href="{@docRoot}/../platform/serialization/spec/output.html">

     * Object Serialization Specification, Section 6.2, "Stream Elements"</a>

     */

    private static final ObjectStreamField[] serialPersistentFields =

        new ObjectStreamField[0];


    /**

     * Initializes a newly created {@code String} object so that it represents

     * an empty character sequence.  Note that use of this constructor is

     * unnecessary since Strings are immutable.

     */

    public String() {

        this.value = "".value;

    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

这里可以看出,实际上String 采用了一个final的char数组,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。

而我在oracle官网(https://docs.oracle.com/javase/9/whatsnew/toc.htm#JSNEW-GUID-B6CD8C25-FD93-4CAA-9286-19A39CC0F26A)也看到了这个:


Adopts a more space-efficient internal representation for strings. Previously, the String class stored characters in a char array, using two bytes (16 bits) for each character. The new internal representation of the String class is a byte array plus an encoding-flag field.


This is purely an implementation change, with no changes to existing public interfaces.


See the CompactStrings option of the java command in Java Platform, Standard Edition Tools Reference.

1

2

3

4

5

这是jdk9的更新内容,这里说的是String 类的实现改用更节省空间的 byte 数组存储字符串,同时数组使用 coder 来标识使用了哪种编码。


String 的创建和初始化

当你在创建String对象并进行初始化的时候,若你用的是直接声明赋值的方法,例如:

Sting a = "abc";那么JAVA虚拟机会用String本身的equals方法在字符串池中查找是否已经存在了值为"abc"的这么一个对象,如果有,则不再创建新的对象,直接返回这个在字符串池的"abc"对象的引用(此时没有新建String对象);如果字符串池中不存在"abc"对象,则先创建这个对象,然后把它加入到字符串池中,再将它的引用返回(此时新建1个String对象)。


若你使用的是Sting a = new String("abc");那么JVM也会对字符串池进行查找,如果有,则返回在字符串池的"abc"对象的引用,没有则创建再返回引用,而由于使用的是new String方法,那么构造器会在堆内存开辟新空间,再把字符串池中符合要求的对象(也就是"abc")的引用放到所开辟的空间中(new String一定会开辟新空间,也就是如果字符串池有对应的对象,则只创建一个String对象,如果没有则创建两个,一个在字符串池,一个在堆空间)。可以看到,不同的创建和初始化方法可能会产生不同个数的String对象。在这个过程中,可能也会涉及到宏变量(用final定义了并同时指定了初始值,并且这个初始值是在编译时就被确定下来的)和宏替换,这方面的可以单独了解一下,很简单,一看就懂就不说了。


很多博客讲到String a = "ab"+"cd";创建了三个对象,实际上不是的,因为实际上在jvm编译时,如果出现+操作,并且符号左右都是已经确定了的值,那么jvm会认为+是没有作用的,会直接变成String a = "abc"。(我是查看编译文件得出的,不详述)


String拼接

String的值既然是不可变的,那么我们常用到的String的拼接应该并不是我们所看到的那么简单,实际上是因为字符串拼接太常用了,java才支持可以直接用+号进行拼接,在上面说到如果出现+操作,并且符号左右都是已经确定了的值,那么jvm会认为+是没有作用的;但是一般情况下,你一定不会这样进行拼接,一般都会是一些不确定的值;这种情况下,其真正实现的原理是中间通过new创建一个临时的StringBuilder对象,然后调用初始化方法再调用append方法,最后再做一个toString()操作来实现,这里的new StringBuilder和toString(toString方法源码可以看到会建立一个String对象)的内容拷贝都是发生在堆中;因此建议程序有大量字符串拼接(特别是有循环)或者经常改变内容的字符串,最好直接写StringBuilder实现,就不需要底层new很多临时对象了。对系统性能等都会产生影响;同时提醒,当进行字符串拼接的时候,null拼接会变成字符串"null"。


StringBuffer和StringBuilder

我们也知道操作字符串的类有:String、StringBuffer、StringBuilder。其中String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以再次提醒在经常改变字符串内容的情况下最好不要使用 String。而StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer


基本类型包装类也有缓冲池

实际上不止String有缓冲池,基本类型的包装类也有缓冲池,在使用这些基本类型对应的包装类型时,如果该数值范围在缓冲池范围内,就可以直接使用缓冲池中的对象。


在 jdk 1.8 所有的数值类缓冲池中,Integer 的缓冲池 IntegerCache 很特殊,这个缓冲池的下界是 - 128,上界默认是 127,但是这个上界是可调的,在启动 jvm 的时候,通过 -XX:AutoBoxCacheMax= 来指定这个缓冲池的大小,该选项在 JVM 初始化的时候会设定一个名为 java.lang.IntegerCache.high 系统属性,然后 IntegerCache 初始化的时候就会读取该系统属性来决定上界。


关于boolean(摘自java虚拟机规范)

boolean是基础类型,但它有点特殊。指令集对boolean的支持有限,当编译器把Java代码编译为字节码的时候,会用int或byte来表示boolean。在Java虚拟机中,jvm的规范中是没有boolean类型。这是因为jvm中对boolean值的操作是通过int类型来进行处理的,而boolean数组则是通过byte数组来进行处理,但是在“堆”区,它也可以被表示为位域。(false会用整数零来表示,所有非零整数都表示true)。


使用 += 或者 ++ 运算符可以执行隐式类型转换。

java一般不支持隐式类型转换,但是使用 += 或者 ++ 运算符可以执行隐式类型转换。


 public  static int  test5(){

        short a =3;

        System.out.println(a);

        // a = a + 1; //此处无法编译通过

        a += 1;

        a ++;

        System.out.println(a);


        return a;


    }

1

2

3

4

5

6

7

8

9

10

11

上面的语句中a 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型下转型为 short 类型。所以a = a + 1无法编译通过;但是 a ++;这个操作却能通过编译,这是因为这里a ++相当于将 a + 1 的计算结果进行了向下转型:

a = (short) (a + 1);


关于hashCode

两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?

1

一般情况下, 使用hashcode的目的在于使用一个对象查找另一个对象。在散列表中,hashCode()相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等。两个不同的键值对,哈希值却相等,就是哈希冲突。我们可以举一个例子,比如说来看一下HashSet,因为这是set集合最常用的实现类,在这个过程中我们可以顺便学习一下set集合是如何保证元素不重合的。实际上,HashSet的内部使用HashMap实现,所有放入HashSet中的集合元素都会转为HashMap的Key-value来保存。HashMap使用散列表来存储,也就是数组+链表+红黑树;在这个过程中,每个需要存储的对象都被转化成key-value形式,此时,会对每一个key(null或者对象或者数字或者字符串)调用hashcode()方法计算hashcode值,然后对hashcode值进行运算,确定出在数组上存放的位置,最后再存储。每一个Hash值对应一个数组下标,数组下标是根据hash值和数组长度计算得来,这个下标决定对象的存储位置。如果两个key的hashcode值相同,那么它们的存储位置(是指在散列表中的存储位置)相同。


所以当我们想hashSet存入同一地址元素时,会先进行哈希值比较后(对于同一地址元素,他们哈希值一般情况下是相同的),接着会比较他们的地址是否相同,如果相同就不会调用equals()方法,如果不同,则调用equals()方法。比较过程中,只要地址相同或者equals()返回true,HashSet中add()中map.put(e,PRESENT)==null则返回false,此时会把相同的这个元素舍弃,HashSet添加元素失败。因此,如果向HashSet中添加一个已经存在的元素,新添加的集合元素不会覆盖原来已有的集合元素,也没办法插入已经存在的元素。


而如果这两个key的equals比较返回false,则说明这两个不是同一个对象,这就是哈希冲突;而对于哈希冲突,有开放定址法、链地址法、公共溢出区法等解决方案。具体实现可以自行百度,数据结构都会涉及到的,不难理解。


关于final

final 修饰的类叫最终类,该类不能被继承。


final 修饰的方法不能被重写。如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。


final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。


关于Math的取整

Math类中提供了三个与取整有关的方法:ceil,floor,round,这些方法的作用于它们的英文名称的含义相对应,例如:ceil的英文意义是天花板,该方法就表示向上取整,Math.ceil(11.3)的结果为12,Math.ceil(-11.6)的结果为-11;floor的英文是地板,该方法就表示向下取整,Math.floor(11.6)的结果是11,Math.floor(-11.4)的结果-12;最难掌握的是round方法,他表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整,所以,Math.round(11.5)的结果是12,Math.round(-11.5)的结果为-11.Math.round( )符合这样的规律:小数点后大于5全部加,等于5正数加,小于5全不加。


普通类、抽象类、接口

普通类和抽象类的区别:

普通类不能包含抽象方法,抽象类可以包含抽象方法。


抽象类不能直接实例化,普通类可以直接实例化。


接口和抽象类的区别:

实现:


抽象类的子类使用 extends 来继承;


接口必须使用 implements 来实现接口。


构造函数:抽象类可以有构造函数;接口不能有。


实现数量:类可以实现很多个接口;但是只能继承一个抽象类。


访问修饰符:接口中的方法默认使用public修饰;抽象类中的方法可以是任意访问修饰符。


关于clone

clone() 是 Object 的protected方法,它不是public,一个类不显式去重写clone(),其它类就不能直接去调用该类实例的 clone() 方法。应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是Object的一个protected方法。Cloneable接口只是规定,如果一个类没有实现 Cloneable接口又调用了clone()方法,就会抛出CloneNotSupportedException。使用clone()方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java书上讲到,最好不要去使用clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。

————————————————


原文链接:https://blog.csdn.net/weixin_43390562/java/article/details/100108520


特别声明:
1、如无特殊说明,内容均为本站原创发布,转载请注明出处;
2、部分转载文章已注明出处,转载目的为学习和交流,如有侵犯,请联系客服删除;
3、编辑非《源码码网》的文章均由用户编辑发布,不代表本站立场,如涉及侵犯,请联系删除;
全部评论(0)
推荐阅读
  • 陀螺匠OA办公系统源码 PHP + Vue 前后端分离 完整可商用
  • 陀螺匠OA办公系统源码 PHP + Vue 前后端分离 完整可商用
  • 项目介绍《陀螺匠》是一套采用Laravel9框架结合Swoole高性能框架与Vue.js前端技术栈构建的AI+低代码双引擎智能办公系统。陀螺匠深度融合了AI智能体以及低代码开发平台,系统简约,易于功能扩展,方便二次开发。旨在为企业提供一站式、数智化转型的全方位解决方案,助力企业高效运营与智能决策。源码说明源码说明:项目代码100%开源,基于AGPL-3.0,支持免费商用!技术栈:系统基于Laravel9+Swoole+Vue.
  • 软件教程
  • 来源:CRMEB
  • 编辑:CRMEB
  • 时间:2026-03-27 15:33
  • 阅读:654
  • open claw最近很火,如何理性看待?
  • open claw最近很火,如何理性看待?
  • openclaw是2026年初科技圈最火爆的现象级话题。它不仅仅是又一个AI产品,更被百川智能的王小川誉为与ChatGPT诞生同等重要的里程碑——标志着AI从”只会说”的对话者,进化到了”能够做”的行动者。对于这股全民”养虾”热潮,我的观察是:它代表了一次人机关系的革命性跃迁,但同时也是一场充满风险与泡沫的狂欢,其真正的价值需要在喧嚣褪去、安全与成本的挑战被克服后才能显现。下
  • 行业资讯
  • 来源:源码码网
  • 编辑:源码码网
  • 时间:2026-03-17 11:06
  • 阅读:716
  • 原生小程序和封装的小程序有什么区别
  • 原生小程序和封装的小程序有什么区别
  • 原生小程序和封装的小程序是两种不同的开发模式,主要区别在于开发方式、性能表现、代码维护以及跨平台能力。为了让你更清晰地理解,我们可以把“原生”比作用钢筋水泥直接盖楼,而“封装”则是用预制板或套件来组装楼。以下是详细的对比分析:1.定义与本质原生小程序:定义: 直接使用微信官方提供的开发工具、语法和API进行开发。语言: 使用微信自己定义的一套技术栈(WXML(微信标记语言)做结构、WXSS(微信样式表)做样式、JS
  • 行业资讯
  • 来源:源码码网
  • 编辑:源码码网
  • 时间:2026-03-06 11:54
  • 阅读:944
  • 基于NetCore(Razor Page)开发的Cms建站系统MIT协议
  • 基于NetCore(Razor Page)开发的Cms建站系统MIT协议
  • FytSoaCms一款基于NetCore2.2/3.1(RazorPage)功能强大的Cms建站系统,支持前后端分离。简介:模块化:全新的架构和模块化的开发机制,便于灵活扩展和二次开发。模型/栏目/分类信息体系:通过栏目和模型绑定,以及不同的模型类型,不同栏目可以实现差异化的功能,轻松实现诸如资讯、下载、讨论和图片等功能。通过分类信息和栏目绑定,可以自动建立索引表,轻松实现复杂的信息检索。FytSoa是一套基于NetCore+SqlS
  • 开发工具
  • 来源:gitee
  • 编辑:源码码网
  • 时间:2026-01-24 22:21
  • 阅读:799
  • 基于JAVA开发的企业级平台微信公众号管理系统SmartWx
  • 基于JAVA开发的企业级平台微信公众号管理系统SmartWx
  • SmartWx微信公众号管理系统是一个完整的微信公众号web操作版,直接编译即可运行。让用户不再用关心微信的底层接口,直接使用页面进行操作,简单方便。包括服务器绑定、文本管理、图文管理、菜单管理、粉丝管理、群发消息等。技术框架开发语言:JAVA数据库:MYSQLJAVA开发框架:SpringMVC+Spring+Mybatis缓存框架:j2cache前端开发框架:Layui+JQuery+html前台模板引擎:art-templat
  • 源码教程
  • 来源:gitee
  • 编辑:源码码网
  • 时间:2026-01-24 21:53
  • 阅读:833
联系客服
源码代售 源码咨询 技术开发 联系客服
029-84538663
手机版

扫一扫进手机版
返回顶部