您好!欢迎来到源码码网

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

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

新手在刚开始学习的时候一定会遇到这个问题,那就是== 和 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)
推荐阅读
  • 工程项目一体化自动管理软件解决方案
  • 工程项目一体化自动管理软件解决方案
  • 1.项目概述1.1项目背景在工程建设行业数字化转型浪潮下,传统项目管理面临信息孤岛、协同困难、进度不可控、成本超支等痛点。本方案旨在构建一个覆盖工程项目全生命周期、全参与方、全业务流程的一体化智能管理平台。1.2解决方案愿景打造数据驱动、智能协同、风险预警、自动执行的工程大脑,实现:管理流程自动化率≥80%项目协同效率提升40%成本偏差率降低至±3%以内安全事故发生率降低60%1.3目标用户矩阵┌───────────────┬
  • 行业资讯
  • 来源:源码码网
  • 编辑:源码码网
  • 时间:2026-01-09 11:26
  • 阅读:165
  • 车辆管理系统需求文档与技术架构PC端+小程序
  • 车辆管理系统需求文档与技术架构PC端+小程序
  • 第一部分:需求文档1.项目概述1.1项目背景为企事业单位、车队运营商、租赁公司等提供一套完整的车辆全生命周期管理解决方案,实现车辆管理数字化、智能化。1.2项目目标建立车辆从购置到报废的全流程管理体系实现用车申请、调度、监控、结算的闭环管理通过数据分析优化车辆使用效率降低车辆运维成本20%以上1.3用户角色矩阵┌──────────────┬─────────────────────────────┬──────────────
  • 行业资讯
  • 来源:源码码网
  • 编辑:源码码网
  • 时间:2026-01-09 11:11
  • 阅读:149
  • 智慧农业/渔业物联网系统需求文档
  • 智慧农业/渔业物联网系统需求文档
  • 智慧农业/渔业物联网系统需求文档文档版本: V1.0项目目标: 构建一个集环境智能监测、设备自动化控制、生长模型分析、溯源管理与远程指挥于一体的综合物联网管理平台,实现降本增效、提质增产、风险预警与品牌增值。1.系统总体概述1.1核心价值: 数据驱动决策,解放人力,实现农业/渔业生产的精准化、自动化与智能化。1.2用户角色:生产员/养殖员: 现场巡视、接收告警、执行设备手动控制、查看实时环境
  • 行业资讯
  • 来源:源码码网
  • 编辑:源码码网
  • 时间:2026-01-09 11:04
  • 阅读:63
  • 程序员AI编程工具推荐
  • 程序员AI编程工具推荐
  • AI编程工具是当前开发者的“副驾驶”,能够极大提升开发效率。以下我将从通用型、代码专用型、垂直领域型以及开源/自部署型几个维度为您分类推荐,并附上它们的核心特点和适用场景,帮助您选择。一、通用型AI对话助手(编程是核心能力之一)这类工具本质是“更懂代码的ChatGPT”,适合处理广泛的编程问题、解释代码、生成文档等。ChatGPT(GPT-4/4o)简介:行业标杆,尤其在GPT-4版本下,代码理解和生成能力极强。优点:上下文能力强,
  • 源码教程
  • 来源:源码码网
  • 编辑:源码码网
  • 时间:2026-01-09 10:56
  • 阅读:91
  • 中医考证在线学习小程序系统需求文档
  • 中医考证在线学习小程序系统需求文档
  • 中医考证在线学习小程序系统需求文档文档版本: V1.0目标用户: 中医执业医师、助理医师、确有专长、师承等考证学员核心价值: 利用移动化、碎片化、智能化工具,提升学习效率与考试通过率。1.项目概述1.1项目目标开发一款专为中医考证学员设计的微信小程序,提供从课程学习、题库练习、考点记忆、模考冲刺到学习社区的一站式闭环学习体验。旨在帮助学员充分利用碎片时间,系统化、高效地备考。1.2用户角色学员(主要用
  • 行业资讯
  • 来源:源码码网
  • 编辑:源码码网
  • 时间:2026-01-09 10:53
  • 阅读:27
联系客服
源码代售 源码咨询 技术开发 联系客服
029-84538663
手机版

扫一扫进手机版
返回顶部