我已经知道了不可变类的定义,但我需要一些例子。
答案 0 :(得分:85)
标准API中的一些着名的不可变类:
大多数枚举类都是不可变的,但实际上这取决于具体情况。 (不要实现可变的枚举,这会让你搞砸了。)我认为至少标准API中的所有枚举类实际上都是不可变的。
java.math.BigInteger和java.math.BigDecimal(至少这些类本身的对象,子类可能会引入可变性,虽然这不是一个好主意)
的java.io.File。请注意,这表示VM外部的对象(本地系统上的文件),可能存在也可能不存在,并且有一些方法可以修改和查询此外部对象的状态。但File对象本身保持不变。 (java.io中的所有其他类都是可变的。)
java.awt.Font - 表示在屏幕上绘制文本的字体(可能有一些可变的子类,但这肯定没用)
java.awt.Cursor - 表示鼠标光标的位图(这里也有一些子类可能是可变的或取决于外部因素)
java.util.Locale - 代表特定的地理,政治或文化区域。
虽然大多数集合都是可变的,但java.util.Collections类中有一些包装器方法,它们在集合上返回一个不可修改的视图。如果你传递一个在任何地方都不知道的集合,这些集合实际上是不可变的集合。此外,Collections.singletonMap()
,.singletonList
,.singleton
返回不可变的单元素集合,还有一些不可变的空集合。
java.net.URL和java.net.URI - 代表资源(在互联网或其他地方)
java.time
之外的所有DateTimeException
类都是不可变的。 java.time
的子包的大多数类也是不可变的。可以说原始类型也是不可变的 - 你不能改变42的值,是吗?
类AccessControlContext是一个不可变类
AccessControlContext没有任何变异方法。它的状态包含一个ProtectionDomains列表(它是一个不可变类)和一个DomainCombiner。 DomainCombiner是一个接口,因此原则上实现可以在每次调用时执行不同的操作。
事实上,ProtectionDomain的行为也可能取决于当前有效的策略 - 是否将这样的对象称为不可变是有争议的。
和AccessController?
没有AccessController类型的对象,因为这是一个没有可访问构造函数的final类。所有方法都是静态的。可以说AccessController既不可变也不可变,或两者兼而有之。
同样适用于所有其他无法拥有对象(实例)的类,最着名的是:
in
,out
,err
)答案 1 :(得分:22)
构建后无法更改不可变类。因此,例如,Java String
是不可变的。
要使类不可变,您必须将其设为final
以及所有字段private
和final
。例如,以下类是不可变的:
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)
要创建一个不可变的类,您需要按照以下步骤操作:
答案 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)文档有一个关于如何创建不可变对象的优秀清单。
- 不提供“setter”方法 - 修改字段引用的字段或对象的方法。
- 将所有字段设为最终字段并保密。
- 不允许子类覆盖方法。最简单的方法是将类声明为final。更复杂的方法是使构造函数私有,并在工厂方法中构造实例。
- 如果实例字段包含对可变对象的引用,则不允许更改这些对象:
醇>
- 不提供修改可变对象的方法。
- 不要共享对可变对象的引用。永远不要存储对传递给构造函数的外部可变对象的引用;如有必要,创建副本并存储对副本的引用。同样,必要时创建内部可变对象的副本,以避免在方法中返回原始文件。
来自: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:
要了解更多信息,请查看我的博文:http://keaplogik.blogspot.com/2015/07/java-immutable-classes-simplified.html
答案 9 :(得分:0)
在创建不可变类的对象时,必须确保不会存储外部引用。但是,这里的值确实很重要。在下面给出的示例中,我有一个名为Fruits的类,里面有一个List。我通过将List设为私有和final来使该类不可变,并且没有提供设置器。
如果我实例化Fruit的对象,则将向构造函数提供一个列表。客户端程序确实已经有该List的引用(客户端),因此可以轻松修改List,从而实现了不可变性。该课程将丢失。
为解决此问题,我在构造函数中创建了一个新列表,该列表复制了客户端提供的所有值。
现在,如果客户端在列表中添加更多值,则外部引用将受到影响,但是,我不再将该外部引用存储在我的不可变类中。
这可以通过覆盖不可变类中的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);
}
}