Java: Why does this method have side effects?
-
05-07-2019 - |
Question
I have a method that is producing side effects, even though certain variables are marked final
. Why is this? Perhaps I am confused about what final
does.
@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;
}
Solution
The final
modifier only means that the reference cannot be reassigned. It does not prevent the object's state from being modified.
EDIT: Just for Tom:
public void doSomething1(Object arg)
{
arg = new Object(); // OK.
}
public void doSomething2(final Object arg)
{
arg = new Object(); // Compile error.
}
In both cases you can invoke methods on the object pointed to by arg
, including methods that modify its state.
OTHER TIPS
Dan has the right answer on final. What you are after is more like const in C++, which Java does not have. You can simulate it by doing this:
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;
}
}
then do:
void bar(final Foo foo)
{
foo.setX(5); // will not compile
}
void bar(final MutableFoo foo)
{
foo.setX(5); // will compile
}
Not pretty, but it works. The trick is to make sure that none of the methods in the parent class (Foo) make any changes to the instance variables - only MutableFoo can have methods that allow the state to change.
Of course the best thing to do, as much as possible, is to write immutable classes (make all the variables final) and do not call methods on instance/class variables that have side effects, so that things cannot change