【问题标题】:Java: Why does this method have side effects?Java:为什么这种方法有副作用?
【发布时间】:2010-12-20 20:03:26
【问题描述】:

我有一种方法会产生副作用,即使某些变量被标记为final。为什么是这样?也许我对final 的作用感到困惑。

@Test
public void testSubGraph() {
    WeightedGraph<String, DefaultWeightedEdge> g = generateSimpleCaseGraph();

    Graph<String, DefaultWeightedEdge> sub = ChooseRoot.subgraphInDirection(g, "alpha", "l");

    assertEquals(g, generateSimpleCaseGraph()); //fails 
}

public static <V, E extends DefaultEdge> Graph<V, E> subgraphInDirection(final Graph<V, E> g, final V start, final V sink) {
    Graph<V, E> sub = removeEdges(g, start, sink);
    return removeUnconnectedNodes(sub, start);
}

private static <Vertex, Edge extends DefaultEdge> Graph<Vertex, Edge> removeEdges(final Graph<Vertex, Edge> g, Vertex start, Vertex sink) {
    final Set<Edge> outEdges = new HashSet<Edge>(g.edgesOf(start));
    boolean removedEdge;

    for (Edge e : outEdges) {
        if (! (g.getEdgeTarget(e).equals(sink) || g.getEdgeSource(e).equals(sink))) {
            removedEdge = g.removeEdge(e);
            assert removedEdge;
        }
    }
    return g;
}

private static <Vertex, Edge> Graph<Vertex, Edge> removeUnconnectedNodes(Graph<Vertex, Edge> g, Vertex start) {
    ConnectivityInspector<Vertex, Edge> conn = new ConnectivityInspector<Vertex, Edge>((UndirectedGraph<Vertex, Edge>) g);
    boolean removedVertex;

    final Set<Vertex> nodes = new HashSet<Vertex>(g.vertexSet());
    for (Vertex v : nodes) {
        if (! conn.pathExists(start, v)) {
            removedVertex = g.removeVertex(v);
            assert removedVertex;
        }
    }
    return g;
}

【问题讨论】:

  • 那些副作用是什么?
  • g 变成子图,返回值也一样。

标签: java final testcase side-effects jgrapht


【解决方案1】:

final 修饰符仅表示不能重新分配引用。它不会阻止对象的状态被修改。

编辑:只为汤姆:

public void doSomething1(Object arg)
{
    arg = new Object(); // OK.
}

public void doSomething2(final Object arg)
{
    arg = new Object(); // Compile error.
}

在这两种情况下,您都可以调用arg 指向的对象的方法,包括修改其状态的方法。

【讨论】:

  • 我本来打算发布同样的内容,但举个例子......你给你的例子添加一个例子怎么样 :-)。
  • 好的,那么我怎样才能避免改变对象的状态呢?我必须克隆它吗?
  • 你避免改变对象状态,嗯,不改变对象状态(唯一的问题是编译器不会帮助你检测到)。自然地,deep 克隆对象将使您可以不受惩罚地更改生成的副本,因为您独自拥有它。
  • 第一种情况不会有副作用。在第 1 行之后对 arg 执行的任何操作都将保留在方法中。 doSomething1 完成后,arg 将保持不变。
  • @fastcodejava:是的,也许我的例子让它不太清楚。该代码旨在演示 final 的作用。关于能够修改对象状态的注释假定您尚未重新分配引用。
【解决方案2】:

Dan 在决赛中有正确的答案。你所追求的更像是 C++ 中的 const,而 Java 没有。您可以通过这样做来模拟它:

public class Foo
{
    protected int x;

    public Foo(final int val)
    {
        x = val;
    }

    public int getX()
    {
        return (x);
    }
}

public class MutableFoo 
    extends Foo
{
    public MutableFoo(final int val)
    {
        super(val);
    }

    public void setX(final int val)
    {
        x = val;
    }
}

然后做:

void bar(final Foo foo)
{
    foo.setX(5); // will not compile
}

void bar(final MutableFoo foo)
{
    foo.setX(5); // will compile
}

不漂亮,但它有效。诀窍是确保父类 (Foo) 中的任何方法都不会对实例变量进行任何更改 - 只有 MutableFoo 可以具有允许状态更改的方法。

当然最好的办法是尽可能写不可变的类(让所有的变量都是final),不要对有副作用的实例/类变量调用方法,这样事情就不会改变

【讨论】:

  • +1 关于创建不可变类,但这样做的方法不仅是让所有变量为 final,而且不具有可变属性,或者从不对它们调用变异方法,也不返回对可变属性的引用。跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-03
相关资源
最近更新 更多