【问题标题】:How to create immutable class in java如何在java中创建不可变类
【发布时间】:2013-12-25 04:03:50
【问题描述】:

如何在java中创建不可变类。 如果学生类有关系(地址)如何创建不可变类。 我想让下面的类不可变

  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);

        }
        }

在这里我们可以修改地址实例。 请给个思路

【问题讨论】:

标签: java


【解决方案1】:

不可变的关键点是:

  • 没有设置方法
  • 将变量设为私有和最终变量
  • 使用 Collections.unmodifiableList 返回列表 - 从不返回任何可变字段;始终返回该字段的副本(如果合适,则为深)或不可变版本
  • 使课程最终化
  • 如果变量在类内部发生更改,则此更改不可见,并且在类外部没有影响(包括影响 equals()hashcode() 等内容)。

【讨论】:

  • 所有字段都是私有的,最终没有setter方法
  • 您的地址类定义也必须遵守上述规则。
  • 第三个项目符号应该概括为:“永远不要返回任何可变字段;总是返回该字段的副本(如果合适,则为深)或不可变版本”。
  • 有趣的巧合,我刚刚又看了一遍:infoq.com/presentations/Are-We-There-Yet-Rich-Hickey 没有回答你的问题,但高度相关。
  • @Bill - 我们在同一页上吗?
【解决方案2】:

record

从 Java 16 开始,我们可以使用 records 功能(也可以使用 previewed in Java 14previewed in Java 15)。使用record 是创建不可变类的最简单、轻松的方式。

记录类是一个浅层不可变的透明载体,用于一组称为记录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 printRectanglesIntersect(Rectangle rectangleA, Rectangle rectangleB) {
    System.out.println("Checking Rectangle intersection..");
  }
}

【讨论】:

    【解决方案3】:

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

    来自 Effective Java - 第三版 - 第 4 章

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

    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)";
        }
    }
    

    【讨论】:

      【解决方案4】:

      好吧,您使 Student 成为半不可变的:

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

      你应该对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;
          }
      
          ....
      }
      

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

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

      例如,如果您的 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());
          }
      
          ....
      
      }
      

      【讨论】:

      • public Student(int sid, String name, Address add) { super();这个.sid = sid; this.name = 名称; this.add = 添加; }//在构造函数中初始化的地址
      • 嗯,我提到他首先应该让Address 不可变。
      • 我希望地址类不可变,没有日期类请不要提供网络解决方案。
      【解决方案5】:

      够了。 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 将是更好的字段名称。

      【讨论】:

      • 我认为这并不满足不可变类的所有条件。您的地址对象可以修改。
      • @manikantanvsr 仅当 Address 具有可变字段时。声明为 final 的 Address 实例的 Student 字段,因此无法更改。我提出了一种考虑类的一般方法,而不仅仅是一个类的实现。
      【解决方案6】:

      在您的课程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 方法都会产生编译器错误)。

      【讨论】:

      • 所有字段都是私有的,最终没有setter方法私有最终地址添加
      【解决方案7】:

      首先我们需要讨论一下 java 中什么是不可变的。
      在 Java 中,不可变意味着您的状态一旦被初始化就不会改变。不可变类的最佳示例是 String。

      我们也可以创建自己的不可变类,你必须做以下步骤。

      • 将类声明为final:

        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());
        
                }
        
            }
        

      【讨论】:

        【解决方案8】:

        您可以使用 lombok @Valueannotation 创建一个不可变的类,即。它使所有字段为privatefinal,并使类本身也为final。 used 集合也是不可变的:

        @Value
        @Builder
        public class Immutable {
        
            private String str;
            private int value;
            private List<String> strings;
        
        }
        

        【讨论】:

          猜你喜欢
          • 2011-07-04
          • 2015-08-01
          • 2010-09-26
          • 2021-05-04
          • 1970-01-01
          • 2021-06-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多