如何在Java中创建不可变对象?
哪些对象应称为不可变的?
如果我的所有静态成员都上课,那是不可变的吗?
如何在Java中创建不可变对象?
哪些对象应称为不可变的?
如果我的所有静态成员都上课,那是不可变的吗?
Answers:
以下是不可变对象的严格要求。
final
是可变的,但对象仍然可以是可变的。即private final Date imStillMutable
)。defensive copies
在这种情况下,您应该这样做。上课的final
原因非常微妙,常常被忽视。如果不是最终的人,他们可以自由地扩展您的类,覆盖public
或protected
行为,添加可变属性,然后提供其子类作为替代。通过声明该类,final
您可以确保不会发生这种情况。
要查看实际问题,请考虑以下示例:
public class MyApp{
/**
* @param args
*/
public static void main(String[] args){
System.out.println("Hello World!");
OhNoMutable mutable = new OhNoMutable(1, 2);
ImSoImmutable immutable = mutable;
/*
* Ahhhh Prints out 3 just like I always wanted
* and I can rely on this super immutable class
* never changing. So its thread safe and perfect
*/
System.out.println(immutable.add());
/* Some sneak programmer changes a mutable field on the subclass */
mutable.field3=4;
/*
* Ahhh let me just print my immutable
* reference again because I can trust it
* so much.
*
*/
System.out.println(immutable.add());
/* Why is this buggy piece of crap printing 7 and not 3
It couldn't have changed its IMMUTABLE!!!!
*/
}
}
/* This class adheres to all the principles of
* good immutable classes. All the members are private final
* the add() method doesn't modify any state. This class is
* just a thing of beauty. Its only missing one thing
* I didn't declare the class final. Let the chaos ensue
*/
public class ImSoImmutable{
private final int field1;
private final int field2;
public ImSoImmutable(int field1, int field2){
this.field1 = field1;
this.field2 = field2;
}
public int add(){
return field1+field2;
}
}
/*
This class is the problem. The problem is the
overridden method add(). Because it uses a mutable
member it means that I can't guarantee that all instances
of ImSoImmutable are actually immutable.
*/
public class OhNoMutable extends ImSoImmutable{
public int field3 = 0;
public OhNoMutable(int field1, int field2){
super(field1, field2);
}
public int add(){
return super.add()+field3;
}
}
实际上,在依赖注入环境中遇到上述问题是很常见的。您没有显式实例化事物,并且给出的超类引用实际上可能是子类。
要解决的问题是,要坚决保证不变性,您必须将类标记为final
。这在Joshua Bloch的Effective Java中进行了深入介绍,并在Java内存模型规范中明确引用。
只是不要在类中添加公共mutator(setter)方法。
private
或类应为final
。只是为了避免继承。因为继承违反了封装。
类不是一成不变的,对象是一成不变的。
不变是指:初始化后,我的公开可见状态无法更改。
字段不必声明为final,尽管它可以极大地确保线程安全
如果您的类只有静态成员,则该类的对象是不可变的,因为您无法更改该对象的状态(也可能无法创建它:)。
要使类在Java中不可变,可以注意以下几点:
1.不要提供setter方法来修改该类的任何实例变量的值。
2.将课程声明为“最终”。这将防止任何其他类对其进行扩展,从而阻止其覆盖可能修改实例变量值的任何方法。
3.将实例变量声明为private和final。
4.您还可以将类的构造函数声明为私有,并在需要时添加工厂方法来创建类的实例。
这些要点应该有帮助!
private
?
不变对象是创建后不会更改其内部状态的对象。它们在多线程应用程序中非常有用,因为它们可以在线程之间共享而无需同步。
1.不要添加任何设置方法
如果您要构建一个不可变的对象,其内部状态将永远不会改变。setter方法的任务是更改字段的内部值,因此您无法添加它。
2.声明所有字段为最终字段和私有字段
从班级外部看不到私有字段,因此无法对其进行任何手动更改。
声明字段final将保证,如果它引用原始值,则该值在引用对象时永远不会改变,引用也不会更改。这还不足以确保只有私有final字段的对象是不可变的。
3.如果字段是可变对象,则为获取方法创建其防御性副本
我们之前已经看到,仅定义字段final和private是不够的,因为可以更改其内部状态。为了解决这个问题,我们需要创建该字段的防御性副本,并在每次请求时返回该字段。
4.如果必须将传递给构造函数的可变对象分配给字段,请为其创建防御性副本
如果您保留传递给构造函数的引用,则会发生相同的问题,因为可以更改它。因此,持有对传递给构造函数的对象的引用可以创建可变的对象。为了解决此问题,如果参数是可变对象,则必须创建该参数的防御性副本。
请注意,如果字段是对不可变对象的引用,则不必在构造函数中以及在getter方法中创建其防卫副本,将字段定义为final和private即可。
5.不允许子类覆盖方法
如果子类覆盖方法,则它可以返回可变字段的原始值,而不是其防御性副本。
如果遵循这些简单规则,则可以在线程之间自由共享不可变对象,因为它们是线程安全的!
没有对与错,这取决于您的喜好。这仅取决于您的喜好以及要实现的目标(并且能够轻松使用这两种方法而不会疏远一方或另一方的顽固支持者是某些语言所追求的圣杯)。
首先,您知道为什么需要创建不可变对象,以及不可变对象的优点是什么。
不可变对象的优点
并发和多线程自动线程安全,因此同步问题...等
不需要复制构造函数 不需要执行克隆。 不能覆盖类 使字段成为私有和最终的 Force调用者,以一步完成一个对象,而不是使用无参数构造函数
不可变的对象只是对象,其状态意味着在构造了不可变的对象之后,对象的数据无法更改。
请参见下面的代码。
public final class ImmutableReminder{
private final Date remindingDate;
public ImmutableReminder (Date remindingDate) {
if(remindingDate.getTime() < System.currentTimeMillis()){
throw new IllegalArgumentException("Can not set reminder" +
" for past time: " + remindingDate);
}
this.remindingDate = new Date(remindingDate.getTime());
}
public Date getRemindingDate() {
return (Date) remindingDate.clone();
}
}
最小化可变性
不可变类只是其实例无法修改的类。每个实例中包含的所有信息在创建时都会提供,并且在对象的生命周期内是固定的。
JDK不可变类:字符串,装箱的原始类(包装类),BigInteger和BigDecimal等。
如何使一类不变?
制作防御性副本。确保以独占方式访问任何可变组件。
公共列表getList(){返回Collections.unmodifiableList(list); <===可变字段的防御性副本,然后将其返回给调用方}
如果您的类具有引用可变对象的任何字段,请确保该类的客户端无法获取对这些对象的引用。切勿将此类字段初始化为客户端提供的对象引用,也不要从访问器返回该对象引用。
import java.util.Date;
public final class ImmutableClass {
public ImmutableClass(int id, String name, Date doj) {
this.id = id;
this.name = name;
this.doj = doj;
}
private final int id;
private final String name;
private final Date doj;
public int getId() {
return id;
}
public String getName() {
return name;
}
/**
* Date class is mutable so we need a little care here.
* We should not return the reference of original instance variable.
* Instead a new Date object, with content copied to it, should be returned.
* */
public Date getDoj() {
return new Date(doj.getTime()); // For mutable fields
}
}
import java.util.Date;
public class TestImmutable {
public static void main(String[] args) {
String name = "raj";
int id = 1;
Date doj = new Date();
ImmutableClass class1 = new ImmutableClass(id, name, doj);
ImmutableClass class2 = new ImmutableClass(id, name, doj);
// every time will get a new reference for same object. Modification in reference will not affect the immutability because it is temporary reference.
Date date = class1.getDoj();
date.setTime(date.getTime()+122435);
System.out.println(class1.getDoj()==class2.getDoj());
}
}
有关更多信息,请参阅我的博客:http :
//javaexplorer03.blogspot.in/2015/07/minimize-mutability.html
当您要将任何类作为不可变类时,必须考虑以下几个步骤。
让我们看一下上面键入的内容:
//ImmutableClass
package younus.attari;
public final class ImmutableExample {
private final String name;
private final String address;
public ImmutableExample(String name,String address){
this.name=name;
this.address=address;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
}
//MainClass from where an ImmutableClass will be called
package younus.attari;
public class MainClass {
public static void main(String[] args) {
ImmutableExample example=new ImmutableExample("Muhammed", "Hyderabad");
System.out.println(example.getName());
}
}
添加到由@ nsfyn55提供的答案,以下几个方面还需要考虑对象不变性,这是首要的重要性
考虑以下类别:
public final class ImmutableClass {
private final MutableClass mc;
public ImmutableClass(MutableClass mc) {
this.mc = mc;
}
public MutableClass getMutClass() {
return this.mc;
}
}
public class MutableClass {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
public class MutabilityCheck {
public static void main(String[] args) {
MutableClass mc = new MutableClass();
mc.setName("Foo");
ImmutableClass iMC = new ImmutableClass(mc);
System.out.println(iMC.getMutClass().getName());
mc.setName("Bar");
System.out.println(iMC.getMutClass().getName());
}
}
以下是MutabilityCheck的输出:
Foo
Bar
请务必注意,
(通过构造函数)在不可变对象上构造可变对象,方法是“复制”或“关闭”到以下更改所描述的不可变对象的实例变量:
public final class ImmutableClass {
private final MutableClass mc;
public ImmutableClass(MutableClass mc) {
this.mc = new MutableClass(mc);
}
public MutableClass getMutClass() {
return this.mc;
}
}
public class MutableClass {
private String name;
public MutableClass() {
}
//copy constructor
public MutableClass(MutableClass mc) {
this.name = mc.getName();
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
仍然不能确保完全不变,因为以下内容对MutabilityCheck类仍然有效:
iMC.getMutClass().setName("Blaa");
但是,使用在1.中所做的更改运行MutabilityCheck将会导致输出为:
Foo
Foo
为了实现对象的完全不变性,其所有从属对象也必须是不变的
从具有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");
}
}