热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

通过实例解析javaString不可变性

这篇文章主要介绍了通过实例解析javaString不可变性,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

一、原理

  1、不变模式(不可变对象)

  在并行软件开发过程中,同步操作似乎是必不可少的。当多线程对同一个对象进行读写操作时,为了保证对象数据的一致性和正确性,有必要对对象进行同步。而同步操作对系统性能是相当的损耗。为了能尽可能的去除这些同步操作,提高并行程序性能,可以使用一种不可改变的对象,依靠对象的不变性,可以确保其在没有同步操作的多线程环境中依然始终保持内部状态的一致性和正确性。这就是不变模式。

  不变模式天生就是多线程友好的,它的核心思想是,一个对象一旦被创建,则它的内部状态将永远不会发生改变。所以,没有一个线程可以修改其内部状态和数据,同时其内部状态也绝不会自行发生改变。基于这些特性,对不变对象的多线程操作不需要进行同步控制。

  同时还需要注意,不变模式和只读属性是有一定的区别的,不变模式是比读属性具有更强的一致性和不变性。对只读属性的对象而言,对象本身不能被其他线程修改,但是对象身状态却可能自行修改比如,一个对象的存活时间(对象创建时间和当前时间的时间差)是只读的,因为任何个第三方线程都不能修改这个属性,但是这是一个可变的属性,因为随着时间的推移,存活时司时刻都在发生变化。而不变模式则要求,无论出于什么原因,对象自创建后,其内部状态和数据保持绝对的稳定。

  2、怎么实现不可变对象

  在Java语言中,不变模式的实现很简单。为确保对象被创建后,不发生任何改变,并保证不变模式正常工作,只需要注意以下4点:

  • 去除 setter方法以及所有修改自身属性的方法。
  • 将所有属性设置为私有,并用final标记,确保其不可修改
  • 确保没有子类可以重载修改它的行为。
  • 有一个可以创建完整对象的构造函数。

是不是和final的功能很吻合。我们复习一下java中final的作用。

  • final修饰类,表示该类不能被继承,俗称断子绝孙类,该类的所有方法自动地成为final方法
  • final修饰方法,表示子类不可重写该方法
  • final修饰基本数据类型变量,表示该变量为常量,值不能再修改
  • final修饰引用类型变量,表示该引用在构造对象之后不能指向其他的对象,但该引用指向的对象的状态可以改变

  这里需要说明的是:当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。例如某个指向数组的final引用,它必须从此至终指向初始化时指向的数组,但是这个数组的内容完全可以改变。

二、String源码分析

以下是jdk1.8中String类的部分源码。 

public final class String implements java.io.Serializable, Comparable, 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; /**
   ...}

  首先可以看到,String类使用了final修饰符,表明String类是不可继承的。然后,我们主要关注String类的成员变量value,value是char[]类型,因此String对象实际上是用这个字符数组进行封装的。再看value的修饰符,使用了private,也没有提供setter方法,所以在String类的外部不能修改value,同时value也使用了final进行修饰,那么在String类的内部也不能修改value,也就是说value一旦赋予初始值之后,value指向的地址就不能再改变了。但是上面final修饰引用类型变量的内容提到,这只能保证value不能指向其他的对象,但value指向的对象的状态是可以改变的。通过查看String类源码可以发现,String类不可变,关键是因为SUN公司的工程师,在后面所有String的方法里都很小心的没有去动字符数组里的元素。所以String类不可变的关键都在底层的实现,而不仅仅是一个final。

三、修改String使其“可变”

  虽然value是final修饰的,只是说明value不能再重新指向其他的引用。但是value指向的数组可以改变,一般情况下我们是没有办法访问到这个value指向的数组的元素。But,反射,对,反射可以,牛逼吧。可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。

public static void main(String[] args) throws Exception {
 String str = "Hello World";
 System.out.println("修改前的str:" + str);
 System.out.println("修改前的str的内存地址" + System.identityHashCode(str));
 // 获取String类中的value字段
 Field valueField = String.class.getDeclaredField("value");
 // 改变value属性的访问权限
 valueField.setAccessible(true);
 // 获取str对象上value属性的值
 char[] value = (char[]) valueField.get(str);
 // 改变value所引用的数组中的字符
 value[3] = '?';
 System.out.println("修改后的str:" + str);
 System.out.println("修改前的str的内存地址" + System.identityHashCode(str));
}
// 运行结果
// 可以看到str的字符串序列已经被改变了,但是str的内存地址还是没有改变。
修改前的str:Hello World
修改前的str的内存地址1922154895
修改后的str:Hel?o World
修改前的str的内存地址1922154895

四、String设计成不可变性的原因

  在Java中,将String设计成不可变的是综合考虑到内存、同步、数据结构及安全等各种因素的结果,下文将为各种因素做一个小结。

  1、运行时常量池的需要

  比如执行 String s = "abc";执行上述代码时,JVM首先在运行时常量池中查看是否存在String对象“abc”,如果已存在该对象,则不用创建新的String对象“abc”,而是将引用s直接指向运行时常量池中已存在的String对象“abc”;如果不存在该对象,则先在运行时常量池中创建一个新的String对象“abc”,然后将引用s指向运行时常量池中创建的新String对象。
这样在运行时常量池中只会创建一个String对象"abc",这样就节省了内存空间。

  2、同步

  因为String对象是不可变的,所以是多线程安全的,同一个String实例可以被多个线程共享。这样就不用因为线程安全问题而使用同步。

  3、允许String对象缓存hashcode

  查看上文JDK1.8中String类源码,可以发现其中有一个字段hash,String类的不可变性保证了hashcode的唯一性,所以可以用hash字段对String对象的hashcode进行缓存,就不需要每次重新计算hashcode。所以Java中String对象经常被用来作为HashMap等容器的键。

  4、安全性

  如果String对象是可变的,那么会引起很严重的安全问题。比如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为String对象是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变String引用指向的对象的值,造成安全漏洞。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • 解读中台架构:微服务与分布式技术的区别及应用
    中心化与去中心化是长期讨论的话题。中心化架构的优势在于部署和维护相对简单,尤其在服务负载较为稳定的情况下,能够提供高效稳定的性能。然而,随着业务规模的扩大和技术需求的多样化,中心化架构的局限性逐渐显现,如扩展性和故障恢复能力较差。相比之下,微服务和分布式技术通过解耦系统组件,提高了系统的灵活性和可扩展性,更适合处理复杂多变的业务场景。本文将深入探讨中台架构中微服务与分布式技术的区别及其应用场景,帮助读者更好地理解和选择适合自身业务的技术方案。 ... [详细]
  • 本文作为探讨PHP依赖注入容器系列文章的开篇,将首先通过具体示例详细阐述依赖注入的基本概念及其重要性,为后续深入解析容器的实现奠定基础。 ... [详细]
  • CentOS 7环境下Jenkins的安装与前后端应用部署详解
    CentOS 7环境下Jenkins的安装与前后端应用部署详解 ... [详细]
  • 解决Android应用在手机安装时出现安全风险提示的方法与对策
    解决Android应用在手机安装时出现安全风险提示的方法与对策 ... [详细]
  • 本文深入探讨了在Spring Boot中处理RESTful风格的表单请求的方法,包括请求参数处理、请求映射以及RESTful设计原则的应用。文章详细介绍了如何利用HTTP动词(如GET、POST、PUT、DELETE)来操作资源,并结合Spring Boot的注解(如@GetMapping、@PostMapping等)实现高效、清晰的请求处理逻辑。通过实例分析,展示了如何在实际项目中应用这些技术,提高开发效率和代码可维护性。 ... [详细]
  • 在《Python编程基础》课程中,我们将深入探讨Python中的循环结构。通过详细解析for循环和while循环的语法与应用场景,帮助初学者掌握循环控制语句的核心概念和实际应用技巧。此外,还将介绍如何利用循环结构解决复杂问题,提高编程效率和代码可读性。 ... [详细]
  • 在托管C++中开发应用程序时,遇到了如何声明和操作字符串数组的问题。本文详细探讨了字符串数组在托管C++中的应用与实现方法,包括声明、初始化、遍历和常见操作技巧,为开发者提供了实用的参考和指导。 ... [详细]
  • 在Python编程语言中,字符串被视为不可变的Unicode字符序列。本文将详细介绍四种用于获取字符串长度的方法,并提供相应的代码示例,帮助读者更好地理解和应用这些技术。 ... [详细]
  • 掌握PHP编程必备知识与技巧——全面教程在当今的PHP开发中,了解并运用最新的技术和最佳实践至关重要。本教程将详细介绍PHP编程的核心知识与实用技巧。首先,确保你正在使用PHP 5.3或更高版本,最好是最新版本,以充分利用其性能优化和新特性。此外,我们还将探讨代码结构、安全性和性能优化等方面的内容,帮助你成为一名更高效的PHP开发者。 ... [详细]
  • 在第二课中,我们将深入探讨Scala的面向对象编程核心概念及其在Spark源码中的应用。首先,通过详细的实战案例,全面解析Scala中的类和对象。作为一门纯面向对象的语言,Scala的类设计和对象使用是理解其面向对象特性的关键。此外,我们还将介绍如何通过阅读Spark源码来进一步巩固对这些概念的理解。这不仅有助于提升编程技能,还能为后续的高级应用开发打下坚实的基础。 ... [详细]
  • 大家好,我是梅巴哥er。本文将深入探讨Redux框架中的第三个实战案例,具体实现每两秒自动点击按钮以触发颜色变化的功能。该案例中,一个关键点在于是否需要使用异步操作来处理定时任务,我们将详细分析其必要性和实现方式。通过这一实例,读者可以更好地理解Redux在实际项目中的应用及其异步处理机制。 ... [详细]
  • Java Web开发中的JSP:三大指令、九大隐式对象与动作标签详解
    在Java Web开发中,JSP(Java Server Pages)是一种重要的技术,用于构建动态网页。本文详细介绍了JSP的三大指令、九大隐式对象以及动作标签。三大指令包括页面指令、包含指令和标签库指令,它们分别用于设置页面属性、引入其他文件和定义自定义标签。九大隐式对象则涵盖了请求、响应、会话、应用上下文等关键组件,为开发者提供了便捷的操作接口。动作标签则通过预定义的动作来简化页面逻辑,提高开发效率。这些内容对于理解和掌握JSP技术具有重要意义。 ... [详细]
  • 深入解析Spring Boot启动过程中Netty异步架构的工作原理与应用
    深入解析Spring Boot启动过程中Netty异步架构的工作原理与应用 ... [详细]
  • 优化升级版数据采集与赋值方法,专为前文内容设计
    在前一篇文章中,方法的局限性主要体现在需要传递参数,并且参数数量受限。当页面布局与所需参数不匹配时,该方法将无法正常工作。为此,我们推出了优化升级版1.1,旨在解决这些问题并提高灵活性和适用性。 ... [详细]
  • 人人租机作为国内领先的信用免押租赁平台,为企业和个人提供全方位的新租赁服务。通过接入支付宝小程序功能,该平台实现了从零到百的迅猛增长,成为全国首家推出“新租赁小程序”开发服务的阿里巴巴小程序服务商(ISV)。这一创新举措不仅提升了用户体验,还显著增强了平台的市场竞争力。 ... [详细]
author-avatar
angel2502899287_238
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有