String类不可变分析以及普通不可变类 您所在的位置:网站首页 string不可变怎么理解 String类不可变分析以及普通不可变类

String类不可变分析以及普通不可变类

2023-05-25 15:43| 来源: 网络整理| 查看: 265

####一、String为什么不可变? 要了解String类创建的实例为什么不可变,首先要知道final关键字的作用:final的意思是“最终,最后”。

final关键字可以修饰类、方法、字段。 修饰类时,这个类不可以被继承; 修饰方法时,这个方法就不可以被覆盖(重写),在JVM中也就只有一个版本的方法--实方法; 修饰字段时,这个字段就是一个常量。

查看java.lang.String方法时,可以看到:

public final class String implements java.io.Serializable, Comparable, CharSequence { /** The value is used for character storage. */ private final char value[]; ... }

######String本质上是一个被final修饰的char数组。

我们在创建一个String时候 如:String str=new String("ab");实际上是创建了final char value['a','b'],而这里的str仅仅是保存的这个char数组的引用地址,我们在修改时候,比如str="ccc";实际上是将str的引用地址给改变了,但是我们原来的被final修饰的数组并没有改变. String一些方法诸如replace,substring等等看似改变了字符串的值,时间上只是改变了引用指向的地址,其实他们底层都是通过创建新的String对象来返回的,并不是修改以前的. 可以看下源码. ####二、String类不可变有什么好处?

最简单的就是为了安全和效率。 从安全上讲,因为不可变的对象不能被改变,他们可以在多个线程之间进行自由共享,这消除了进行同步的要求; 从效率上讲,设计成final,JVM才不用对相关方法在虚函数表中查询,而是直接定位到String类的相关方法上,提高执行效率; 总之,由于效率和安全问题,String被设计成不可变的,这也是一般情况下,不可变的类是首选的原因。

####三String对象真的不可变吗? 从上文可知String的成员变量是private final 的,也就是初始化之后不可改变。那么在这几个成员中, value比较特殊,因为他是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗? 比如将数组中的某个位置上的字符变为下划线“_”。 至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组。

那么用什么方式可以访问私有成员呢? 没错,用反射,可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。 示例代码:

public static void testReflection() throws Exception { //创建字符串"Hello World", 并赋给引用s String s = "Hello World"; System.out.println("s = " + s); //Hello World //获取String类中的value字段 Field valueFieldOfString = String.class.getDeclaredField("value"); //改变value属性的访问权限 valueFieldOfString.setAccessible(true); //获取s对象上的value属性的值 char[] value = (char[]) valueFieldOfString.get(s); //改变value所引用的数组中的第5个字符 value[5] = '_'; System.out.println("s = " + s); //Hello_World } //打印结果 s = Hello World s = Hello_World

在这个过程中,s始终引用的同一个String对象,但是再反射前后,这个String对象发生了变化, 也就是说,**通过反射是可以修改所谓的“不可变”对象的。**但是一般我们不这么做。这个反射的实例还可以说明一个问题:如果一个对象,他组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。 ####五、不可变类 **不可变类只是它的实例不能被修改的类。**每个实例中包含的所有信息都必须在创建该实例时就提供,并在对象 的整个生命周期内固定不变。String、基本类型的包装类、BigInteger和BigDecimal就是不可变得类。

为了使类成为不可变,必须遵循以下5条规则: ①不要提供任何会修改对象状态的方法。 ②保证类不会被扩展。 ③使所有的域都是final。 ④使所有的域都成为私有的。 ⑤确保 对于任何可变组件的互斥访问。如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用。 ####六、不可变类的优点和缺点 不可变类实例不可变性,具有很多优点。 ①不可变类对象比较简单。不可变对象可以只有一种状态,即被创建时的状态。 ②不可变对象本质上是线程安全的,它们不要求同步。当多个线程并发访问这样的对象时,它们不会遭到破坏。实际上,没有任何线程会注意到其他线程对于不可变对象的影响。所以,不可变对象可以被自由地分配。“不可变对象可以被自由地分配”导致的结果是:永远不需要进行保护性拷贝。 ③不仅可以共享不可变对象,甚至也可以共享它们的内部信息。 ④不可变对象为其他对象提供了大量的构件。如果知道一个复杂对象内部的组件不会改变,要维护它的不变性约束是比较容易的。

######不可变类真正唯一的缺点是,对于每个不同的值都需要一个单独的对象。创建这种对象的代价很高。 ###七、如何构建不可变类? 构建不可变类有两种方式:

用关键字final修饰类 让类的所有构造器都变成私有的或者包级私有的,并添加公有的静态工厂来替代公有的构造器。

为了具体说明用静态工厂方法来替代公有的构造器,下面以Complex为例:

//复数类 public class Complex{ //实数部分 private final double re; //虚数部分 private final double im; //私有构造器 private Complex(double re,double im){ this.re = re; this.im = im; } //静态工厂方法,返回对象唯一实例 public static Complex valueOf(double re,double im){ return new Complex(re,im); } ... }

不可变的类提供一些静态工厂方法,它们把频繁请求的实例缓存起来,从而当现在实例符合请求的时候,就不必创建新的实例。使用这样的静态工厂方法使得客户端之间可以共享现有的实例,而不是创建新的实例,从而减低内存占用和垃圾回收的成本。

总之,使类的可变性最小化。不要为每个get方法编写一个相对应的set方法,除非有很好的理由要让类成为可变的类,否则就应该是不可变的。如果有些类不能被做成是不可变的,仍然应该尽可能地限制它的可变性。不可变的类有很多优点,但唯一的缺点就是在特定的情况下存在潜在的性能问题。

PS:静态工厂方法是什么? 静态工厂方法只是一个返回类的实例的静态方法,如下面是一个Boolean的简单实例。这个方法将boolean基本类型值转换成一个Boolean对象引用。

public static Boolean valueOf(boolean b){ return b?Boolean.TRUE?Boolean.FALSE; }

#####静态工厂方法相对于构造器来说,具有很多优势: ①创建的方法有名字; ②不必在每次调用它们的时候都创建一个新的对象;

③**可以返回原返回类型的任何子类的对象。**这样我们在选择返回对象的类时就有更大的灵活性,这种灵活性的一种应用是API可以返回对象,同时又不会使对象的类变成公有的。以这种方式隐藏实现类会使API变得非常简洁,这项技术适用于基于接口的框架。

④在创建参数化类型实例时,它们**使代码变得更加简洁。**编译器可以替你找到类型参数,这被称为类型推导。如下面这个例子

public static HashMap newInstance(){ return new HashMap(); }

####静态工厂方法也有缺点:

①类如果不含公有的或者受保护的构造器,就不能被子类化。对于公有的静态工厂方法所返回的非公有类也同样如此。

②它们与静态方法实际上没有什么区别。

简而言之,静态工厂方法和公有构造器都各有用处,我们需要理解它们各自的长处。结合实际情况,再做选择。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有