不可变类的示例

时间:2011-02-25 23:55:26

标签: java immutability

我已经知道了不可变类的定义,但我需要一些例子。

10 个答案:

答案 0 :(得分:85)

标准API中的一些着名的不可变类:

  • java.lang.String(已提及)
  • 基本类型的包装类:java.lang.Integer,java.lang.Byte,java.lang.Character,java.lang.Short,java.lang.Boolean,java.lang.Long,java.lang .Double,java.lang.Float
  • java.lang.StackTraceElement(用于构建异常堆栈跟踪)
  • 大多数枚举类都是不可变的,但实际上这取决于具体情况。 (不要实现可变的枚举,这会让你搞砸了。)我认为至少标准API中的所有枚举类实际上都是不可变的。

  • java.math.BigInteger和java.math.BigDecimal(至少这些类本身的对象,子类可能会引入可变性,虽然这不是一个好主意)

  • 的java.io.File。请注意,这表示VM外部的对象(本地系统上的文件),可能存在也可能不存在,并且有一些方法可以修改和查询此外部对象的状态。但File对象本身保持不变。 (java.io中的所有其他类都是可变的。)

  • java.awt.Font - 表示在屏幕上绘制文本的字体(可能有一些可变的子类,但这肯定没用)

  • java.awt.BasicStroke - 在图形上下文中绘制线条的辅助对象
  • java.awt.Color - (至少这个类的对象,一些子类可能是可变的或取决于一些外部因素(如系统颜色)),以及java.awt.Paint的大多数其他实现像
    • java.awt.GradientPaint,
    • java.awt.LinearGradientPaint
    • java.awt.RadialGradientPaint,
    • (我不确定java.awt.TexturePaint)
  • java.awt.Cursor - 表示鼠标光标的位图(这里也有一些子类可能是可变的或取决于外部因素)

  • java.util.Locale - 代表特定的地理,政治或文化区域

  • java.util.UUID - 尽可能多的全局唯一标识符
  • 虽然大多数集合都是可变的,但java.util.Collections类中有一些包装器方法,它们在集合上返回一个不可修改的视图。如果你传递一个在任何地方都不知道的集合,这些集合实际上是不可变的集合。此外,Collections.singletonMap().singletonList.singleton返回不可变的单元素集合,还有一些不可变的空集合。

  • java.net.URL和java.net.URI - 代表资源(在互联网或其他地方)

  • java.net.Inet4Address和java.net.Inet6Address,java.net.InetSocketAddress
  • java.security.Permission的大多数子类(表示某些操作所需的权限或某些代码所需的权限),但不包括java.security.PermissionCollection和子类。
  • java.time之外的所有DateTimeException类都是不可变的。 java.time的子包的大多数类也是不可变的。

可以说原始类型也是不可变的 - 你不能改变42的值,是吗?


  

类AccessControlContext是一个不可变类

AccessControlContext没有任何变异方法。它的状态包含一个ProtectionDomains列表(它是一个不可变类)和一个DomainCombiner。 DomainCombiner是一个接口,因此原则上实现可以在每次调用时执行不同的操作。

事实上,ProtectionDomain的行为也可能取决于当前有效的策略 - 是否将这样的对象称为不可变是有争议的。

  

和AccessController?

没有AccessController类型的对象,因为这是一个没有可访问构造函数的final类。所有方法都是静态的。可以说AccessController既不可变也不可变,或两者兼而有之。

同样适用于所有其他无法拥有对象(实例)的类,最着名的是:

  • java.lang.Void的
  • java.lang.System(但这有一些可变的静态 - inouterr
  • java.lang.Math(这也是 - 随机数生成器)
  • java.lang.reflect.Array中
  • java.util.Collections中
  • java.util.Arrays中

答案 1 :(得分:22)

构建后无法更改不可变类。因此,例如,Java String是不可变的。

要使类不可变,您必须将其设为final以及所有字段privatefinal。例如,以下类是不可变的:

public final class Person {

     private final String name;
     private final int age;
     private final Collection<String> friends;

     public Person(String name, int age, Collection<String> friends) {
         this.name = name;
         this.age = age;
         this.friends = new ArrayList(friends);
     }

     public String getName() { 
         return this.name;
     }

     public int getAge() {
         return this.age;
     }

     public Collection<String> getFriends() {
         return Collections.unmodifiableCollection(this.friends);
     }
}

我在代码示例中添加了一个方法,展示了如何处理集合,这是一个重点。

在可能的情况下你应该使类不可变,因为那时你不必担心线程安全等问题。

答案 2 :(得分:13)

重要的是要记住,将一个类声明为final并不意味着它是“不可变的”,这基本上意味着这个类不能被扩展(或专门化)。

不可变类必须具有私有和最终字段(没有setter),因此在构造之后,它的字段值不能更改。

答案 3 :(得分:9)

要创建一个不可变的类,您需要按照以下步骤操作:

  1. 将该类声明为final,因此无法扩展。
  2. 将所有字段设为私有,以便不允许直接访问。
  3. 不要为变量提供setter方法
  4. 将所有可变字段设为最终字段,以便分配其值 只有一次。
  5. 通过执行深层复制的构造函数初始化所有字段。
  6. 在getter方法中执行克隆对象以返回副本 而不是返回实际的对象引用。
  7. 可以找到一个示例here

    我们也可以使用Builder Pattern轻松创建不可变类,可以找到一个例子here

答案 4 :(得分:7)

LocalDate LocalTime LocalDateTime 类(自1.8起)也是不可变的。事实上,这个主题是在OCAJSE8(1Z0-808)考试中,这正是我决定将其视为仅仅是评论的原因。
所有原始包装类(例如布尔字符字节整数浮动双重)是不可变的。

资金货币 API(适用于Java9)也应该是不可变的。

顺便提一下,阵列支持的列表(由Arrays.asList(myArray)创建)结构上 -immutable。

此外,还有一些边界线情况,例如 java.util.Optional (在OCP考试中有特色,1Z0-809),如果包含的元素本身是不可变的,则它是不可变的。

答案 5 :(得分:3)

String是一个不可变类的好“真实世界”示例。你可以将它与可变的StringBuilder类进行对比。


用于反射的大多数Java类都是不可变的。其他一些是“几乎不可改变的”:例如实现Accessible的类只有setAccessible方法,可以更改Accessible实例的状态。


我确信标准类库中还有更多。

答案 6 :(得分:3)

Sun(Oracle)文档有一个关于如何创建不可变对象的优秀清单。

  
      
  1. 不提供“setter”方法 - 修改字段引用的字段或对象的方法。
  2.   
  3. 将所有字段设为最终字段并保密。
  4.   
  5. 不允许子类覆盖方法。最简单的方法是将类声明为final。更复杂的方法是使构造函数私有,并在工厂方法中构造实例。
  6.   
  7. 如果实例字段包含对可变对象的引用,则不允许更改这些对象:   
        
    • 不提供修改可变对象的方法。
    •   
    • 不要共享对可变对象的引用。永远不要存储对传递给构造函数的外部可变对象的引用;如有必要,创建副本并存储对副本的引用。同样,必要时创建内部可变对象的副本,以避免在方法中返回原始文件。
    •   
  8.   

来自:http://download.oracle.com/javase/tutorial/essential/concurrency/imstrat.html

该站点还提供了在并发上下文中使用它的示例,但在编写库时,不可变性也很有用。它确保图书馆的呼叫者只能改变我们允许的内容。

答案 7 :(得分:2)

不可变类是一个曾经创建过的类,它的内容无法更改。不可变对象是一旦构造就不能改变状态的对象。示例 - 字符串&amp;所有java包装类。

可变对象是一旦被构造就可以改变状态的对象.example- StringBuffer一旦值改变了内存位置就改变了。  见下面的例子 -

 public static void immutableOperation(){
    String str=new String("String is immutable class in Java object value cann't alter once created...");
    System.out.println(str);
    str.replaceAll("String", "StringBuffer");
    System.out.println(str);
    str.concat("Concating value ");
    System.out.println(str + "HashCode Value  " + str.hashCode());
    str=str.concat("Concating value ");
    System.out.println(str + "HashCode Val  " + str.hashCode());

}

public static void mutableOperation(){
    StringBuffer str=new StringBuffer("StringBuffer is mutable class in Java object value can  alter once created...");
    System.out.println(str + "HashCode Val - " + str.hashCode());
    str.replace(0, 12, "String");
    System.out.println(str + "HashCode Val - " + str.hashCode());

}

答案 8 :(得分:1)

我喜欢使用具有可变属性的示例。这有助于理解不可变类如何真正起作用。

可变类

class MutableBook {
    private String title;

    public String getTitle(){
        return this.title;
    }

    public void setTitle(String title){
        this.title = title;
    }
}

使用可变书籍实例的不可变实现。

public class ImmutableReader {
    private final MutableBook readersBook;
    private final int page;

    public ImmutableReader(MutableBook book) {
        this(book, 0);
    }

    private ImmutableReader(MutableBook book, int page){
        this.page = page;

        // Make copy to ensure this books state won't change.
        MutableBook bookCopy = new MutableBook();
        bookCopy.setTitle(book.getTitle());
        this.readersBook = bookCopy;
    }


    public MutableBook getBook() {
        // Do not return the book, but a new copy. Do not want the readers
        // book to change it's state if developer changes book after this call.
        MutableBook bookCopy = new MutableBook();
        bookCopy.setTitle(this.readersBook.getTitle());
        return bookCopy;
    }

    public int getPage() {
        // primitives are already immutable.
        return page;
    }

    /**
    * Must return reader instance since it's state has changed.
    **/ 
    public ImmutableReader turnPage() {
        return new ImmutableReader(this.readersBook, page + 1);
    }
}

为了让你的班级真正不变,它必须符合以下的cirteria:

  • 所有班级成员都被宣布为最终成员。
  • 在构造类时,必须实例化类级别的类中使用的所有变量。
  • 没有类变量可以有setter方法。
    • 这是从第一个声明中隐含的,但是要明确表示你不能改变类的状态。
  • 所有子对象也必须是不可变的,或者它们的状态在不可变类中永远不会改变。
    • 如果您的类具有可变属性,则必须将其锁定。将其声明为私有,并确保您永远不会更改它的状态。

要了解更多信息,请查看我的博文:http://keaplogik.blogspot.com/2015/07/java-immutable-classes-simplified.html

答案 9 :(得分:0)

在创建不可变类的对象时,必须确保不会存储外部引用。但是,这里的值确实很重要。在下面给出的示例中,我有一个名为Fruits的类,里面有一个List。我通过将List设为私有和final来使该类不可变,并且没有提供设置器。

  1. 如果我实例化Fruit的对象,则将向构造函数提供一个列表。客户端程序确实已经有该List的引用(客户端),因此可以轻松修改List,从而实现了不可变性。该课程将丢失。

  2. 为解决此问题,我在构造函数中创建了一个新列表,该列表复制了客户端提供的所有值。

  3. 现在,如果客户端在列表中添加更多值,则外部引用将受到影响,但是,我不再将该外部引用存储在我的不可变类中。

  4. 这可以通过覆盖不可变类中的hashcode()来验证。无论客户端修改列表多少次,我不变类对象的哈希码都将保持不变,因为它接受的列表不再指向外部列表。

公共类水果{

private final List<String> fruitnames;

public Fruit(List<String> fruitnames) {
    this.fruitnames = new ArrayList<>(fruitnames);
}

public List<String> getFruitnames() {
     return new ArrayList<>(fruitnames);
}

@Override
public int hashCode() {
    return getFruitnames() != null ? getFruitnames().hashCode(): 0;
}

}

//客户端程序

公共类ImmutableDemo {

public static void main(String args[]){

    List<String> fruitList = new ArrayList<>();
    fruitList.add("Apple");
    fruitList.add("Banana");
    //Immutable Object 1
    Fruit fruit1 = new Fruit(fruitList);
    //fruitHash is-689428840
    int fruitHash = fruit1.hashCode();

    System.out.println("fruitHash is" +fruitHash);
    //This value will not be added anymore as the state has already been defined and
    //now it cant change the state.
    fruitList.add("straberry");
    //fruitHash1 is-689428840
    int fruitHash1 = fruit1.hashCode();


    System.out.println("fruitHash1 is" +fruitHash1);
}

}