Java,使类不可变

时间:2018-05-09 15:44:19

标签: java immutability

我在网上遇到这个练习,我有两个课程,我应该让Tutor课程一成不变。但是,我唯一能想到的是在name字段中添加final。当进入构造函数时,我不认为我需要更改name变量的初始化,因为String是不可变的。我不确定如何处理集合以及如何使构造函数的这一部分不可变。 根据练习,我不应该改变学生班(我能看到的是可变的)

public class Student {

private String name;
private String course;


public Student(String name, String course){

this.name = name;
this.course = course;

}

public String getName(){ 
return name; 
}

public String getCourse(){ 
return course; 
}

public void setName(String name) {
this.name = name;
}

public void setCourse(String course){
this.course = course;
}

}




public final class Tutor {
private String name;
private final Set<Student> tutees;

public Tutor(String name, Student[] students){
this.name = name;
tutees = new HashSet<Student>();
for (int i = 0; i<students.length; i++)
tutees.add(students[i]);
}


public Set<Student> getTutees(){
return Collections.unmodifiableSet(tutees);
}

public String getName(){ 
return name; 
}

}

5 个答案:

答案 0 :(得分:3)

list2env课程提出了促进其不变性的许多方面:

  • 课程是最后的
  • list2env(lst, envir = .GlobalEnv) Data1 # SaleID Country #1 Sale1 US #2 Sale3 US Data2 # SaleID Country #1 Sale2 Mexico #2 Sale6 Mexico Data3 # SaleID Country #1 Sale4 Canada #2 Sale5 Canada #3 Sale7 Canada 受到保护以免受修改
  • 没有允许直接更改班级状态的方法

然而,构造函数的防御性副本并不完整 它还必须复制传递的数组的Tutor个元素。否则,构造函数的客户端可能会更改它们的任何实例,并使Set<Student>实例变为可变,例如:

Student

你应该写一些类似的东西:

Tutor

另请注意,Student[] students = ...; Tutor tutor = new Tutor(name, students); students[0].setName("new Name!"); // break the immutability of Tutor 返回的public Tutor(String name, Student[] students){ this.name = name; tutees = new HashSet<Student>(); for (Student student : students){ Student copy = new Student(student.getName(), student.getCourse()); tutees.add(copy); } } 是不可修改的,但包含在Set中的元素是可变的。 因此,为了使Tutor不可变,您还必须在返回getTutees()时创建Student元素的副本,例如:

Student

正如您可能已经注意到的那样,在这些条件下获得不变性(我们希望不可变但包含引用可变实例的集合的实例)需要编写更多代码(读取/维护/测试)并执行更多处理(执行起来慢得多) 如果getTutees()是一个不可变类,原始public Set<Student> getTutees(){ Set<Student> students = new HashSet<>(); for (Student student : tutees){ Student copy = new Student(student.getName(), student.getCourse()); students.add(copy); } return Collections.unmodifiableSet(students); } 和原始构造函数就足够了。

答案 1 :(得分:2)

正确的方法是使对象不可变为:

  1. 声明对象最终
  2. 不提供setter方法
  3. 将所有字段设为私有
  4. 让可变字段最终
  5. 在构造函数中使用深层复制
  6. 在getter方法中克隆对象,因此您不会返回实际引用。

答案 2 :(得分:1)

你真的需要归还学生集吗?如果你真的需要它,你可以通过使用只提供getter的接口来隐藏它,比如

interface IStudent {
    public String getName();
    public String getCourse(); 
}

class Student : implements IStudent { ...} 

并在导师中返回Set<IStudent>

答案 3 :(得分:0)

要使Tutor类不可变,您应该在Tutor中的所有字段上使用“final”修饰符,而不是在Tutor的类定义上。

答案 4 :(得分:0)

Java SE 16

您可以使用 JEP 395: Records 特性(作为 Java SE 16 的一部分引入)来创建一个不可变的类,而无需太多仪式。

如果您已经浏览了上面的链接,那么您一定已经想出可以简单地做到这一点

record Tutor(String name, Set<Student> tutees) { }

反过来你得到的是:

  1. A final class Tutor
  2. 签名与标头 Tutor(String name, Set<Student> tutees) 相同的规范构造函数。
  3. private final 字段、nametutees 及其对应的具有相同名称和返回类型的 public 访问器方法。
  4. 自动创建 equalshashCodetoString 方法。

演示:

Student.java

record Student(String name, String course) { }

Tutor.java

import java.util.Set;

record Tutor(String name, Set<Student> tutees) { }

Main.java

import java.util.Set;

class Main {
    public static void main(String[] args) {
        Set<Student> cscStudents = Set.of(
                                            new Student("Harry", "Java-8"),
                                            new Student("Tina", "Java-9"),
                                            new Student("Andy", "Java-11")
                                        );

        Set<Student> scienceStudents = Set.of(
                                            new Student("Tony", "Phy"),
                                            new Student("Kerry", "Chem"),
                                            new Student("John", "Bio")
                                        );

        Tutor t1 = new Tutor("Mark", cscStudents);
        Tutor t2 = new Tutor("Robin", scienceStudents);
        Tutor t3 = new Tutor("Mark", Set.of(
                                            new Student("Andy", "Java-11"),
                                            new Student("Harry", "Java-8"),
                                            new Student("Tina", "Java-9")
                                        )
                            );

        System.out.println(t1);
        System.out.println();

        System.out.println(t1.tutees());
        System.out.println();

        System.out.println("Students of " + t1.name() + ":");
        t1.tutees()
            .stream()
            .forEach( t -> System.out.println(t.name()) );
        System.out.println();

        System.out.println(t1.equals(t2));
        System.out.println(t1.equals(t3));
    }
}

输出:

Tutor[name=Mark, tutees=[Student[name=Andy, course=Java-11], Student[name=Harry, course=Java-8], Student[name=Tina, course=Java-9]]]

[Student[name=Andy, course=Java-11], Student[name=Harry, course=Java-8], Student[name=Tina, course=Java-9]]

Students of Mark:
Andy
Harry
Tina

false
true