如何使Java类不可变,不可变的需求是什么,使用它有什么好处?
如何使Java类不可变,不可变的需求是什么,使用它有什么好处?
@Immutable
从jcabi-aspects
Answers:
什么是不可变的对象?
不变对象是在实例化后不会改变状态的对象。
如何使对象不变?
通常,可以通过定义一个不暴露任何成员且没有任何设置方法的类来创建不可变对象。
以下类将创建一个不可变的对象:
class ImmutableInt {
private final int value;
public ImmutableInt(int i) {
value = i;
}
public int getValue() {
return value;
}
}
如上例所示, ImmutableInt
只能在实例化对象时设置,并且通过仅使用getter(getValue
),实例化后不能更改对象的状态。
但是,必须注意,该对象引用的所有对象也必须是不可变的,否则可能会更改该对象的状态。
例如,允许对数组的引用或ArrayList
通过getter获取将允许通过更改数组或集合来更改内部状态:
class NotQuiteImmutableList<T> {
private final List<T> list;
public NotQuiteImmutableList(List<T> list) {
// creates a new ArrayList and keeps a reference to it.
this.list = new ArrayList(list);
}
public List<T> getList() {
return list;
}
}
上面的代码的问题在于,ArrayList
可以通过获取getList
和操纵它们,从而导致对象本身的状态被改变,因此,它不是一成不变的。
// notQuiteImmutableList contains "a", "b", "c"
List<String> notQuiteImmutableList= new NotQuiteImmutableList(Arrays.asList("a", "b", "c"));
// now the list contains "a", "b", "c", "d" -- this list is mutable.
notQuiteImmutableList.getList().add("d");
解决此问题的一种方法是从getter调用时返回数组或集合的副本:
public List<T> getList() {
// return a copy of the list so the internal state cannot be altered
return new ArrayList(list);
}
不变性的优点是什么?
不变性的优点在于并发。在可变对象中很难保持正确性,因为多个线程可能试图更改同一对象的状态,从而导致某些线程看到同一对象的不同状态,具体取决于读取和写入对象的时间目的。
通过拥有一个不可变的对象,可以确保所有正在查看该对象的线程都将看到相同的状态,因为不可变的对象的状态不会改变。
return Collections.unmodifiableList(list);
可以返回列表上的只读视图。
final
。否则,可以使用setter方法(或其他类型的变异方法和可变字段)对其进行扩展。
final
如果类仅包含private
字段,则不必如此,因为它们不能从子类访问。
除了已经给出的答案之外,我还建议您阅读第二版《有效Java》中的不变性,因为有些细节很容易遗漏(例如防御性副本)。另外,Effective Java 2nd Ed。是每个Java开发人员必读的文章。
您可以将这样的类设为不可变的:
public final class Immutable
{
private final String name;
public Immutable(String name)
{
this.name = name;
}
public String getName() { return this.name; }
// No setter;
}
以下是使Java类不可变的要求:
final
(这样就不能创建子类)final
(这样,在创建对象后我们就无法更改其值)不可变的类很有用,因为
-它们是线程安全的。
-他们还表达了关于您的设计的深刻内容:“无法更改。”,适用时,正是您所需要的。
不变性主要可以通过两种方式实现:
final
实例属性避免重新分配不变性的优点是可以对这些对象进行以下假设:
不变类在实例化后不能重新分配值。构造函数将值分配给其私有变量。在对象变为空之前,由于无法使用设置方法,因此无法更改值。
要一成不变,应满足以下条件,
/**
* Strong immutability - by making class final
*/
public final class TestImmutablity {
// make the variables private
private String Name;
//assign value when the object created
public TestImmutablity(String name) {
this.Name = name;
}
//provide getters to access values
public String getName() {
return this.Name;
}
}
优点:不可变对象包含其初始化值,直到其死亡。
不变的阶级是指那些对象在创建后无法更改的类。
不可变的类对于
例
弦类
代码示例
public final class Student {
private final String name;
private final String rollNumber;
public Student(String name, String rollNumber) {
this.name = name;
this.rollNumber = rollNumber;
}
public String getName() {
return this.name;
}
public String getRollNumber() {
return this.rollNumber;
}
}
如何使Java类不可变?
从具有JEP 359的JDK 14+开始,我们可以使用“ records
”。这是创建Immutable类的最简单,最轻松的方式。
记录类是一组固定的字段(称为记录)的浅层不变的透明载体,为记录components
提供state
描述。每个component
都会产生一个final
字段,其中包含提供的值和accessor
检索该值方法。字段名称和访问者名称与组件名称匹配。
让我们考虑创建不可变矩形的示例
record Rectangle(double length, double width) {}
无需声明任何构造函数,无需实现equals&hashCode方法。只是任何记录都需要名称和状态描述。
var rectangle = new Rectangle(7.1, 8.9);
System.out.print(rectangle.length()); // prints 7.1
如果要在对象创建期间验证值,则必须显式声明构造函数。
public Rectangle {
if (length <= 0.0) {
throw new IllegalArgumentException();
}
}
记录的主体可以声明静态方法,静态字段,静态初始化器,构造函数,实例方法和嵌套类型。
实例方法
record Rectangle(double length, double width) {
public double area() {
return this.length * this.width;
}
}
静态字段,方法
由于状态应该是组件的一部分,因此我们无法将实例字段添加到记录中。但是,我们可以添加静态字段和方法:
record Rectangle(double length, double width) {
static double aStaticField;
static void aStaticMethod() {
System.out.println("Hello Static");
}
}
不变性有什么需求,使用它有什么好处?
先前发布的答案足以证明对不变性的需求,这是有利的
制作不可变对象的另一种方法是使用Immutables.org库:
假定已添加必需的依赖项,请使用抽象访问器方法创建一个抽象类。您可以通过使用接口甚至注释(@interface)进行注释来做同样的事情:
package info.sample;
import java.util.List;
import java.util.Set;
import org.immutables.value.Value;
@Value.Immutable
public abstract class FoobarValue {
public abstract int foo();
public abstract String bar();
public abstract List<Integer> buz();
public abstract Set<Long> crux();
}
现在可以生成并使用生成的不可变实现:
package info.sample;
import java.util.List;
public class FoobarValueMain {
public static void main(String... args) {
FoobarValue value = ImmutableFoobarValue.builder()
.foo(2)
.bar("Bar")
.addBuz(1, 3, 4)
.build(); // FoobarValue{foo=2, bar=Bar, buz=[1, 3, 4], crux={}}
int foo = value.foo(); // 2
List<Integer> buz = value.buz(); // ImmutableList.of(1, 3, 4)
}
}
不可变类只是其实例无法修改的类。
每个实例中包含的所有信息在对象的生存期内都是固定的,因此无法观察到任何变化。
不可变的类比可变的类更容易设计,实现和使用。
要使类不可变,请遵循以下五个规则:
不要提供修改对象状态的方法
确保该类不能扩展。
将所有字段定为最终值。
将所有字段设为私有。
确保以独占方式访问任何可变组件。
不可变的对象本质上是线程安全的。他们不需要同步。
不变的对象可以自由共享。
不可变的对象是其他对象的重要构建块
这里的大多数答案都是好的,有的提到了规则,但是当我们需要遵循这些规则时,我用言语“为什么?所以在给下面的解释
当然,如果我们尝试将Setters用于这些最终变量,则编译器将引发错误。
public class ImmutableClassExplored {
public final int a;
public final int b;
/* OR
Generally we declare all properties as private, but declaring them as public
will not cause any issues in our scenario if we make them final
public final int a = 109;
public final int b = 189;
*/
ImmutableClassExplored(){
this. a = 111;
this.b = 222;
}
ImmutableClassExplored(int a, int b){
this.a = a;
this.b= b;
}
}
我们需要将class声明为“ final”吗?
1.只有原始成员:如果类只有原始成员,那么我们就没有问题了,那么我们不需要将class声明为final。
2.将对象作为成员变量:如果我们将对象作为成员变量,则必须使这些对象的成员也成为最终成员。意味着我们需要遍历树的深处并使所有对象/图元最终成为不可能的对象。因此,解决方法是使类为final,以防止继承。因此,没有子类重写getter方法的问题。
Lombok的@Value注释可用于生成不可变的类。就像下面的代码一样简单。
@Value
public class LombokImmutable {
int id;
String name;
}
根据龙目岛网站上的文档:
@Value是@Data的不变变量;默认情况下,所有字段都设为私有和最终字段,并且不会生成设置器。默认情况下,该类本身也将最终确定为final,因为不可改变性不能强加于子类。像@Data一样,还会生成有用的toString(),equals()和hashCode()方法,每个字段都有一个getter方法,并且还会生成一个覆盖每个参数的构造函数(除了在字段声明中初始化的最终字段之外) 。
record
可以使用Java的情况下使用Lombok ?