如何在java中创建不可变类

时间:2013-12-25 04:03:50

标签: java

如何在java中创建不可变类。 如果Student类有一个关系(地址)如何创建不可变类。 我想让下面的类成为不可变的

  final public class Student {
        private final Address add;
            private final int sid;
            public Student(int sid, String name, Address add) {
                super();
                this.sid = sid;
                this.name = name;
                this.add = add;
            }
            private final String name;
            public int getSid() {
                return sid;
            }
            public final String getName() {
                return name;
            }
            @Override
            public String toString() {
                return "Student [add=" + add + ", name=" + name + ", sid=" + sid + "]";
            }
            public Address getAdd() {
                return add;
            }


        }

        //I want to make the class below immutable
        public class Address {
            public int getAid() {
                return aid;
            }
            public String getStreet() {
                return street;
            }
            @Override
            public String toString() {
                return "Address [aid=" + aid + ", street=" + street + "]";
            }
            int aid;
            String street;
            public Address(int aid, String street) {
                super();
                this.aid = aid;
                this.street = street;
            }

        }


        public class First {
        public static void main(String[] args) {
            Address myAdd=new Address(179,"Maihill");
            Student st=new Student(99,"anoj",myAdd);
            System.out.println(st.toString());
            myAdd.aid=2376;
            System.out.println(st);
            System.out.println("***************");
            Address pAdd=st.getAdd();
            //Here modified address instance then how we can make immutable.
                pAdd.aid=788;
            System.out.println(st);

        }
        }

这里我们可以修改地址实例。 请给我一点想法

8 个答案:

答案 0 :(得分:16)

不可变的关键点是:

  • 没有setters方法
  • 将变量设为私有和最终
  • 使用Collections.unmodifiableList返回列表 - 永远不会返回任何可变字段;总是返回一个副本(如果合适的话深)或字段的不可变版本
  • make class final
  • 如果在课堂内部更改了变量,则此更改不可见,并且在课程之外无效(包括影响equals()hashcode()等内容。)

答案 1 :(得分:4)

在您的班级Address中,您应该将字段设为private(应该)和final(必须),就像这样 -

public final class Address {       // so no sub-classes can be made.
  private final int aid;           // private and final.
  private final String street;     // private and final.
  // as before.
}

您也不能拥有setter方法,但是当字段是final时没有太大问题(因为任何setter方法都会产生编译器错误)。

答案 2 :(得分:3)

嗯,你让学生半不可变:

  • 其属性为最终
  • 它们是不可变类型(地址除外)
  • 并在构造函数中初始化它们。

你应该对Address类应用同样的东西,所以它变成不可变的,然后Student的所有状态都是不可变的。所以它将是:

public final class Address {
    private final int aid;
    private final String street;

    public Address(int aid, String street) {
        this.aid = aid;
        this.street = street;
    }


    public int getAid() {
        return aid;
    }

    public String getStreet() {
        return street;
    }

    ....
}

幸运的是,你没有任何可修改的类型(一些最知名的类型是DateCollectionMaps),否则你也应该考虑它们。

如果你有任何可变属性,你应该在构造函数中复制保护它,并且你应该在状态泄漏时返回一个不可修改的或者它的副本。

例如,如果您的Student类具有birthDate属性,则应执行以下操作:

public final class Student {
    private final Date birthDate;

    public Student(int sid, String name, Address address, Date birthDate) {
        this.sid = sid;
        this.name = name;
        this.address = address;
        this.birthDate = (birthDate == null) ? null : new Date(birthDate.getTime());
    }

    public Date getBirthDate() {
       return (birthDate == null) ? null : new Date(birthDate.getTime());
    }

    ....

}

答案 3 :(得分:1)

这就够了。声明的final不能被改变,因为构造函数中需要作为参数,getter是多余的。

final public class Student {

    public final Address add;
    public final int sid;
    public final String name;

    public Student(int sid, String name, Address add) {
        super();
        this.sid = sid;
        this.name = name;
        this.add = add;
    }

    @Override
    public String toString() {
        return "Student [add=" + add + ", name=" + name + ", sid=" + sid + "]";
    }
}

addressstudentId/id会为这些字段提供更好的名称。

答案 4 :(得分:0)

要使类不可变,请遵循以下五条规则:

  
     - 第三版 - 第4章

  
     
      
  1. 不提供修改对象状态(known as mutators)的方法。
  2.   
  3. 确保无法扩展类。这可以防止粗心或恶意子类通过表现为对象的状态发生变化来破坏类的不可变行为。防止子类化通常通过使类最终提供私有构造函数
  4. 来实现。   
  5. 所有字段设为最终字段。这清楚地以系统强制执行的方式表达您的意图。此外,如果对新创建的实例的引用在没有同步的情况下从一个线程传递到另一个线程,则必须确保正确的行为,如内存模型中所述
  6.   
  7. 所有字段设为私有。这可以防止客户端获取对字段引用的可变对象的访问权限并直接修改这些对象。虽然技术上允许不可变类具有包含原始值的公共final字段或对不可变对象的引用,但不推荐使用它,因为它排除了在以后的版本中更改内部表示。
  8.   
  9. 确保对任何可变组件的独占访问权限。如果您的类具有引用可变对象的任何字段,请确保该类的客户端无法获取对这些对象的引用。 永远不要将此类字段初始化为客户提供的对象引用,也不要从访问者返回字段。在构造函数,访问器和readObject方法中创建防御性副本(第50项)。
  10.   

Complex.java - 示例不可变类

public final class Complex {
    private final double re;
    private final double im;

    public Complex(double re, double im) {
        this.re = re;
        this.im = im;

    }

    public double realPart() {
        return re;
    }

    public double imaginaryPart() {
        return im;
    }

    public Complex plus(Complex c) {
        return new Complex(re + c.re, im + c.im);

    }

    public Complex minus(Complex c) {
        return new Complex(re - c.re, im - c.im);
    }

    public Complex times(Complex c) {
        return new Complex(re * c.re - im * c.im,
                re * c.im + im * c.re);
    }

    public Complex dividedBy(Complex c) {

        double tmp = c.re * c.re + c.im * c.im;
        return new Complex((re * c.re + im * c.im) / tmp,
                (im * c.re - re * c.im) / tmp);
    }

    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Complex))
            return false;

        Complex c = (Complex) o;
        // See page 47 to find out why we use compare instead of ==
        return Double.compare(c.re, re) == 0
                && Double.compare(c.im, im) == 0;
    }

    @Override
    public int hashCode() {
        return 31 * Double.hashCode(re) + Double.hashCode(im);

    }

    @Override
    public String toString() {
        return "(" + re + " + " + im + "i)";
    }
}

答案 5 :(得分:0)

首先,我们需要讨论Java中什么是不可变的。
在Java中,不可变表示初始化后状态不会改变。不变类的最好例子是字符串。

我们还可以创建自己的不可变类,您必须执行以下步骤。

  • 将类声明为最终的:

    Why? : As per the java final class can not be extended.
    
  • 将所有字段声明为私有字段。

    Why? : Because private member  have not direct access out side of the class
    
  • 不提供该私有字段的setter方法

    Why? : If you provide the setter method for the private members so you can access it out side of the class.    
    
  • 将所有字段都设为最终字段。

    Why?: As per the java final variable can be assigned only once.    
    
  • 使用深度复制通过构造函数初始化所有字段。

                import java.util.HashMap;
        import java.util.Iterator;
    
        public final class ImmutableClassExample {
        private final int id;   
        private final String name;  
        private final HashMap<String,String> testMap;   
        public int getId() {
            return id;
        }
    
    
        public String getName() {
        return name;
        }
    
        /**
        * Accessor function for mutable objects
        */
        public HashMap<String, String> getTestMap() {
        //return testMap;
        return (HashMap<String, String>) testMap.clone();
        }
    
        /**
            * Constructor performing Deep Copy
            * @param i
            * @param n
            * @param hm
        */
    
        public ImmutableClassExample(int i, String n, HashMap<String,String> hm){
            System.out.println("Performing Deep Copy for Object initialization");
            this.id=i;
            this.name=n;
            HashMap<String,String> tempMap=new HashMap<String,String>();
            String key;
            Iterator<String> it = hm.keySet().iterator();
            while(it.hasNext()){
                key=it.next();
                tempMap.put(key, hm.get(key));
            }
            this.testMap=tempMap;
        }
    
    
      /**
        * Constructor performing Shallow Copy
        * @param i
        * @param n
     * @param hm
     */
    /**
    public ImmutableClassExample(int i, String n, HashMap<String,String> hm){
    System.out.println("Performing Shallow Copy for Object initialization");
    this.id=i;
    this.name=n;
    this.testMap=hm;
    } 
    
    */
        /**
        * To test the consequences of Shallow Copy and how to avoid it with Deep Copy for creating immutable classes
        * @param args
    */
    public static void main(String[] args) {
        HashMap<String, String> h1 = new HashMap<String,String>();
        h1.put("1", "first");
        h1.put("2", "second");
    
    String s = "original";
    
    int i=10;
    
    ImmutableClassExample ce = new ImmutableClassExample(i,s,h1);
    
        //Lets see whether its copy by field or reference
        System.out.println(s==ce.getName());
        System.out.println(h1 == ce.getTestMap());
        //print the ce values
        System.out.println("ce id:"+ce.getId());
        System.out.println("ce name:"+ce.getName());
        System.out.println("ce testMap:"+ce.getTestMap());
        //change the local variable values
        i=20;
        s="modified";
        h1.put("3", "third");
        //print the values again
            System.out.println("ce id after local variable change:"+ce.getId());
            System.out.println("ce name after local variable change:"+ce.getName());
            System.out.println("ce testMap after local variable change:"+ce.getTestMap());
    
            HashMap<String, String> hmTest = ce.getTestMap();
            hmTest.put("4", "new");
    
            System.out.println("ce testMap after changing variable from accessor 
            methods:"+ce.getTestMap());
    
            }
    
        }
    

答案 6 :(得分:0)

在具有JEP 359的JDK 14+中,我们可以使用“ records ”。这是创建Immutable类的最简单,最轻松的方式。

记录类是浅的不可变透明载体,用于固定的一组称为记录components的字段,该字段为记录提供了state描述。每个component都会引起一个final字段,该字段保存提供的值和一个accessor方法来检索该值。字段名称和访问者名称与组件名称匹配。

让我们考虑创建不可变矩形的示例

record Rectangle(double length, double width) {}

无需声明任何构造函数,无需实现equalshashCode方法。只是任何记录都需要名称和状态说明。

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 printRectanglesIntersect(Rectangle rectangleA, Rectangle rectangleB) {
    System.out.println("Checking Rectangle intersection..");
  }
}

答案 7 :(得分:0)

您可以使用lombok @Value注释创建一个不可变的类,即。它使所有字段为privatefinal,并使类本身为final。使用过的收藏集也是不可变的:

@Value
@Builder
public class Immutable {

    private String str;
    private int value;
    private List<String> strings;

}