不可变的课程很棒,但有一个很大的问题我无法想出一个明智的解决方法 - 周期。
class Friend {
Set<Friend> friends();
}
我是如何模仿我将你作为朋友而反过来让我作为朋友回来的?
不变性 来自外部世界的这类绝对应该是不可改变的。为了进行平等检查,内部持有的价值应保持不变。
答案 0 :(得分:8)
[[[编辑:添加代码以演示完全不可变的概念]]]
这就是为什么构建者对于不可变的东西非常好 - 它们允许在构造期间的可变性在你“冻结”它之前完成所有设置。在这种情况下,我猜你需要一个支持创建周期的朋友构建器。
final FriendBuilder john = new FriendBuilder().setName("john");
final FriendBuilder mary = new FriendBuilder().setName("mary");
final FriendBuilder susan = new FriendBuilder().setName("susan");
john
.likes(mary)
.likes(susan);
mary
.likes(susan)
.likes(john);
susan
.likes(john);
// okay lets build the immutable Friends
Map<Friend> friends = FriendsBuilder.createCircleOfFriends(john, mary, susan);
Friend immutableJohn = friends.get("john");
编辑:在下面添加了不可变示例以演示方法:
评论中有一些关于是否可以使用不可变版本的讨论。
字段是最终的且不可变的。构造函数中使用了可修改的集合,但在构造之后只保留了不可修改的引用。
我有另一个版本,它使用Guava ImmutableSet作为真正不可变的集合,而不是JDK的不可修改的包装器。它的工作原理相同,但使用了Guava的优秀设置构建器。
代码:
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
/**
* Note: potentially cycle graph - be careful of deep equals/hashCode/toString/etc.
* Immutable
*/
public class Friend {
public static class Builder {
private final String name;
private final Set<Builder> friends =
new HashSet<Builder>();
Builder(final String name) {
this.name = name;
}
public String getName() {
return name;
}
public Set<Builder> getFriends() {
return friends;
}
void likes(final Builder... newFriends) {
for (final Builder newFriend : newFriends)
friends.add(newFriend);
}
public Map<String, Friend> createCircleOfFriends() {
final IdentityHashMap<Builder, Friend> existing =
new IdentityHashMap<Builder, Friend>();
// Creating one friend creates the graph
new Friend(this, existing);
// after the call existingNodes contains all the nodes in the graph
// Create map of the all nodes
final Map<String, Friend> map =
new HashMap<String, Friend>(existing.size(), 1f);
for (final Friend current : existing.values()) {
map.put(current.getName(), current);
}
return map;
}
}
final String name;
final Set<Friend> friends;
private Friend(
final Builder builder,
final Map<Builder, Friend> existingNodes) {
this.name = builder.getName();
existingNodes.put(builder, this);
final IdentityHashMap<Friend, Friend> friends =
new IdentityHashMap<Friend, Friend>();
for (final Builder current : builder.getFriends()) {
Friend immutableCurrent = existingNodes.get(current);
if (immutableCurrent == null) {
immutableCurrent =
new Friend(current, existingNodes);
}
friends.put(immutableCurrent, immutableCurrent);
}
this.friends = Collections.unmodifiableSet(friends.keySet());
}
public String getName() {
return name;
}
public Set<Friend> getFriends() {
return friends;
}
/** Create string - prints links, but does not traverse them */
@Override
public String toString() {
final StringBuffer sb = new StringBuffer();
sb.append("Friend ").append(System.identityHashCode(this)).append(" {\n");
sb.append(" name = ").append(getName()).append("\n");
sb.append(" links = {").append("\n");
for (final Friend friend : getFriends()) {
sb
.append(" ")
.append(friend.getName())
.append(" (")
.append(System.identityHashCode(friend))
.append(")\n");
}
sb.append(" }\n");
sb.append("}");
return sb.toString();
}
public static void main(final String[] args) {
final Friend.Builder john = new Friend.Builder("john");
final Friend.Builder mary = new Friend.Builder("mary");
final Friend.Builder susan = new Friend.Builder("susan");
john
.likes(mary, susan);
mary
.likes(susan, john);
susan
.likes(john);
// okay lets build the immutable Friends
final Map<String, Friend> friends = john.createCircleOfFriends();
for(final Friend friend : friends.values()) {
System.out.println(friend);
}
final Friend immutableJohn = friends.get("john");
}
}
输出:
Node 11423854 {
value = john
links = {
susan (19537476)
mary (2704014)
}
}
Node 2704014 {
value = mary
links = {
susan (19537476)
john (11423854)
}
}
Node 19537476 {
value = susan
links = {
john (11423854)
}
}
答案 1 :(得分:0)
建模周期的正确方法是使用Graph。单个源代码行注释足以强制执行inmutability:“can't touch this”。
您在寻找什么样的无法改变的执法?你想要一个快速变形金刚出现whenever you modify the inmutable Set吗? mutable
和inmutable
之间的差异只是一种约定。但是,可以很容易地修改RAM上的位,使用Reflection API可以破坏任何封装和数据隐藏约定。
暂时忽略velociraptor,Java不支持不可变类型。作为一种变通方法,您需要建模一个行为类似的数据类型。
为了使inmutable属性有意义,你需要使Friend
成为interface
,有一个实现类:InmutableFriend
,并且构造对象的构造应该完全发生在构造函数中
然后,由于图形包含循环,因此在创建最终的不可变实例之前,您需要将图形节点存储在一些可变的临时结构中。您还需要在InmutableFriend.friends()
方法上返回unmodifiableSet。
最后,要克隆图表,您需要在Mutable图上实现Deep-copy算法,如Breadth-first search。但问题是,当图表不是fully connected时会发生什么。
interface Friend {
public Set<Friend> friends();
}
class MutableFriend {
private Set<MutableFriend> relations = new HashSet<MutableFriend>();
void connect(MutableFriend otherFiend) {
if (!relations.contains(otherFriend)) {
relations.add(otherFiend);
otherFriend.connect(this);
}
}
Friend freeze() {
Map<MutableFriend, InmutableFriend> table = ...;
/*
* FIXME: Implement a Breadth-first search to clone the graph,
* using this node as the starting point.
*
* TODO: If the graph is not connected this won't work.
*
*/
}
}
class InmutableFriend() implements Friend {
private Set<Friend> connections;
public Set<Friend> friends() {
return connections;
}
public InmutableFriend(Set<Friend> connections) {
// Can't touch this.
this.connections = Collections.unmodifiableSet(connections);
}
}
答案 2 :(得分:-1)
不变性不需要编译器强制执行有效的架构。您可以拥有一个合法的不可变对象,它接受构造后的初始化参数。例如......
private Object something;
public void init( final Object something )
{
if( this.something != null )
{
throw new IllegalStateException();
}
this.something = something
}
成员字段“某事物”不是最终的,但也不能设置多次。
基于评论中讨论的更复杂的变体......
private boolean initialized;
private Object a;
private Object b;
public void init( final Object a, final Object b )
{
if( this.initialized )
{
throw new IllegalStateException();
}
this.initialized = true;
this.a = a;
this.b = b;
}
public Object getA()
{
assertInitialized();
return this.a;
}
public Object getB()
{
assertInitialized();
return this.b;
}
private void assertInitialized()
{
if( this.initialized )
{
throw new IllegalStateException( "not initialized" );
}
}