Java中finalize,hashcode和toString

2024-06-22 16:58

本文主要是介绍Java中finalize,hashcode和toString,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1 Finalize
    • 1.1 finalize方法作用
    • 1.2 怎么使用finalize
    • 1.3 finalize异常和问题
    • 1.4 finalize执行过程(生命周期)
  • 2 hashCode
    • 2.1 hashcode做什么用
    • 2.2 在类中覆盖equals的时候,为什么要同时覆盖hashCode
    • 2.3 hashCode一般规则
    • 2.4 如果覆盖了equals却不覆盖hashCode的后果
    • 2.5 重写HashCode的影响
  • 3 toString
    • 3.1 toString方法实现什么功能
    • 3.2 当toString方法没有被覆盖的时候,返回的字符串通常是什么样子
    • 3.3 如何得到字符串的表达形式
    • 3.4 System.out.println(o.toString()); 和 System.out.println(o) 的区别

1 Finalize

1.1 finalize方法作用

finalize()方法可以被子类对象所覆盖,然后作为一个终结者,当GC被调用的时候完成最后的清理工作(例如释放系统资源之类)。这就是终止。默认的finalize()方法什么也不做,当被调用时直接返回。
对于任何一个对象,它的finalize()方法都不会被JVM执行两次。如果你想让一个对象能够被再次调用的话(例如,分配它的引用给一个静态变量),注意当这个对象已经被GC回收的时候,finalize()方法不会被调用第二次。

一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法, 并且在下一次垃圾回收动作发生时,才会真正的回收对象占用的内存

finalize()Objectprotected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。
finalize()C++中的析构函数不是对应的。C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中的finalize的调用具有不确定性,不建议用finalize方法完成非内存资源的清理工作,但建议用于:

  1. 清理本地对象(通过JNI创建的对象);
  2. 作为确保某些非内存资源(如Socket、文件等)释放的一个补充:在finalize方法中显式调用其他资源释放方法

1.2 怎么使用finalize

可以遵循下面这个模式写finalize()方法:

@Override
protected void finalize() throws Throwable{   
try   {     // Finalize the subclass state.      // ...  }   finally   {  super.finalize();   }
}

子类终结器一般会通过调用父类的终结器来实现。当被调用时,先执行try模块,然后再在对应的finally中调用super.finalize();这就保证了无论try会不会抛出异常父类都会被销毁。

1.3 finalize异常和问题

finalize()抛出异常的时候会被忽略。而且,对象的终结将在此停止,导致对象处在一种不确定的状态。如果另一个进程试图使用这个对象的话,将产生不确定的结果。通常抛出异常将会导致线程终止并产生一个提示信息,但是从finalize()中抛出异常就不会

一些与finalize相关的方法,由于一些致命的缺陷,已经被废弃了,如System.runFinalizersOnExit()方法、Runtime.runFinalizersOnExit()方法
System.gc()与System.runFinalization()方法增加了finalize方法执行的机会,但不可盲目依赖它们
Java语言规范并不保证finalize方法会被及时地执行、而且根本不会保证它们会被执行
finalize方法可能会带来性能问题。因为JVM通常在单独的低优先级线程中完成finalize的执行
对象再生问题:finalize方法中,可将待回收对象赋值给GC Roots可达的对象引用,从而达到对象再生的目的
finalize方法至多由GC执行一次(用户当然可以手动调用对象的finalize方法,但并不影响GC对finalize的行为)

1.4 finalize执行过程(生命周期)

大致描述一下finalize流程:
当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,把对象移出即将回收集合并复活。之后,对象会再次出现没有引用存在的情况,在这个情况下finalize方法不会被再次调用,对象会直接变成不可触及的状态(一个对象的finalize方法只会被调用一次)。
(GC Roots相关知识点学习)

具体的finalize流程:
对象可由两种状态,涉及到两类状态空间,一是终结状态空间 F = {unfinalized, finalizable, finalized};二是可达状态空间 R = {reachable, finalizer-reachable, unreachable}
各状态含义如下:

  • unfinalized: 新建对象会先进入此状态,GC并未准备执行其finalize方法,因为该对象是可达的
  • finalizable: 表示GC可对该对象执行finalize方法,GC已检测到该对象不可达。正如前面所述,GC通过F-Queue队列和一专用线程完成finalize的执行
  • finalized: 表示GC已经对该对象执行过finalize方法
  • reachable: 表示GC Roots引用可达
  • finalizer-reachable(f-reachable):表示不是reachable,但可通过某个finalizable对象可达
  • unreachable:对象不可通过上面两种途径可达

2 hashCode

2.1 hashcode做什么用

hashCode()方法返回给调用者此对象的哈希码(其值由一个hash函数计算得来)。这个方法通常用在基于hash的集合类中,像java.util.HashMap,java.until.HashSet和java.util.Hashtable.

2.2 在类中覆盖equals的时候,为什么要同时覆盖hashCode

在覆盖equals()的时候同时覆盖hashCode()可以保证对象的功能兼容于hash集合。这是一个好习惯,即使这些对象不会被存储在hash集合中。

2.3 hashCode一般规则

在同一个Java程序中,对一个相同的对象,无论调用多少次hashCode()hashCode()返回的整数必须相同,因此必须保证equals()方法比较的内容不会更改。但不必在另一个相同的Java程序中也保证返回值相同。
如果两个对象用equals()方法比较的结果是相同的,那么这两个对象调用hashCode()应该返回相同的整数值。
当两个对象使用equals()方法比较的结果是不同的,hashCode()返回的整数值可以不同。然而,hashCode()的返回值不同可以提高哈希表的性能。

2.4 如果覆盖了equals却不覆盖hashCode的后果

当覆盖equals()却不覆盖hashCode()的时候,在hash集合中存储对象时就会出现问题。
当hash集合只覆盖equals()时的问题

final class Employee
{private String name;private int age;Employee(String name, int age){this.name = name;this.age = age;}@Overridepublic boolean equals(Object o){if (!(o instanceof Employee))return false;Employee e = (Employee) o;return e.getName().equals(name) && e.getAge() == age;}String getName(){return name;}int getAge(){return age;}
}public class HashDemo
{public static void main(String[] args){Map<Employee, String> map = new HashMap<>();Employee emp = new Employee("John Doe", 29);map.put(emp, "first employee");System.out.println(map.get(emp));System.out.println(map.get(new Employee("John Doe", 29)));}
}

代码中声明了一个Employee类,覆盖了equals()方法但是没有覆盖hashCode()。同时声明了一个HashDemo类,来演示将Employee作为键存储时产生的问题。
main()函数首先在实例化Employee之后创建了一个hashmap,将Employee对象作为键,将一个字符串作为值来存储。然后它将这个对象作为键来检索这个集合并输出结果。同样地,再通过新建一个具有相同内容的Employee对象作为键来检索集合,输出信息。
将看到如下输出结果:

first employee
null

如果hashCode()方法被正确的覆盖,将在第二行看到first employee而不是null,因为这两个对象根据equals()方法比较的结果是相同的,根据上文中提到的:如果两个对象用equals()方法比较的结果是相同的,那么这两个对象调用hashCode()应该返回相同的整数值。

2.5 重写HashCode的影响

在Java中,当你重写hashCode()方法时,需要遵循以下几个原则和最佳实践:

  • 一致性:如果对象的属性没有发生变化,那么多次调用hashCode()方法应该返回相同的结果。
  • 相等性:如果两个对象根据equals()方法判断为相等,那么它们的hashCode()方法应该返回相同的结果。
  • 效率:计算hashCode()方法应该尽量高效,以避免性能问题。

可以使用 java.util.Objects.hashCode(str);方法来重写 hashCode()
当重写后对对System.identityHashCode()hashCode() 获取对象地址影响:

  • hashCode()未被重写时,System.identityHashCode()hashCode()返回值相同,都是调用底层native方法
  • hashCode()被重写,则System.identityHashCode()hashCode()返回值不同。hashCode()返回重写结果,System.identityHashCode()返回底层生成的hashcode
  • 获取方式可以采用String.class.hashCode()方式或者System.identityHashCode(String.class)

3 toString

3.1 toString方法实现什么功能

toString() 方法将根据调用它的对象返回其对象的字符串形式,通常用于debug

3.2 当toString方法没有被覆盖的时候,返回的字符串通常是什么样子

toString() 没有被覆盖的时候,返回的字符串格式是 类名@哈希值,哈希值是十六进制。举例说,假设有一个 Employee 类,toString() 方法返回的结果可能是 Empoyee@1c7b0f4d

3.3 如何得到字符串的表达形式

根据对象的引用,调用引用的 toString() 。例如,假设 emp 包含了一个 Employee 引用,调用 emp.toString() 就会得到这个对象的字符串形式。

3.4 System.out.println(o.toString()); 和 System.out.println(o) 的区别

System.out.println(o.toString()); 和 System.out.println(o) 两者的输出结果中都包含了对象的字符串形式。区别是,System.out.println(o.toString()); 直接调用toString() 方法,而System.out.println(o) 则是隐式调用了 toString()

这篇关于Java中finalize,hashcode和toString的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1084875

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

一篇文章彻底搞懂macOS如何决定java环境

《一篇文章彻底搞懂macOS如何决定java环境》MacOS作为一个功能强大的操作系统,为开发者提供了丰富的开发工具和框架,下面:本文主要介绍macOS如何决定java环境的相关资料,文中通过代码... 目录方法一:使用 which命令方法二:使用 Java_home工具(Apple 官方推荐)那问题来了,

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

Java中的.close()举例详解

《Java中的.close()举例详解》.close()方法只适用于通过window.open()打开的弹出窗口,对于浏览器的主窗口,如果没有得到用户允许是不能关闭的,:本文主要介绍Java中的.... 目录当你遇到以下三种情况时,一定要记得使用 .close():用法作用举例如何判断代码中的 input

Spring Gateway动态路由实现方案

《SpringGateway动态路由实现方案》本文主要介绍了SpringGateway动态路由实现方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随... 目录前沿何为路由RouteDefinitionRouteLocator工作流程动态路由实现尾巴前沿S