Java递归按值/引用传递

时间:2013-07-23 17:22:46

标签: java recursion pass-by-reference

我在PHP工作2年后回到Java。 对不起,如果这看起来很傻:

这是代码(图表的深度优先遍历):

public List<List<Edge>> paths = new ArrayList<>();

public void traverse(Edge edge, List<Edge> currentPath){
    String vertex = graph.getEdgeTarget(edge);
    if(edge!=null) currentPath.add(edge);
    if(vertex=="TARGET_VERTEX"){
        System.out.println(currentPath);  // prints fine

        paths.add(currentPath); // elements are empty at end of reccursion

        if(edge!=null) currentPath.remove(edge);
        return;
    }
    for(Edge e : graph.outgoingEdgesOf(vertex)){
        traverse(e, currentPath);
    }
    if(edge!=null) path.remove(edge);
}

public void search(){
    //graph is initalized, vertices and edges are added

    for(Edge e : graph.outgoingEdgesOf("START_VERTEX")){
        traverse(e, new ArrayList<Edge>());
    }
    System.out.println("############################");
    System.out.println(paths);
    System.out.println(paths.size());
}

有人可以解释为什么在递归结束时paths有空元素,以及如何使其包含我需要的路径?
似乎通过引用传递使我成为一个问题...

ArrayList有一个浅clone()方法,不会复制元素(按照JavaDoc)。
我是否需要创建一个临时变量,它将手动复制currentPath(迭代值)?

我仍然对通过Java传递值和引用感到困惑,在PHP中可以通过引用传递(&variable来轻松区分。)

感谢。

已编辑,因此我不会抱怨字符串比较

4 个答案:

答案 0 :(得分:1)

if(edge!=null) currentPath.remove(edge);行删除了List的元素。

这可能会导致您的问题,因为currentPath会被递归。

关于不相关的问题,您要将String==进行比较,而不是使用equals,这是不好的做法。 (有关更多说明,请参阅here)。

答案 1 :(得分:1)

这两行造成了问题:

      paths.add(currentPath);
      currentPath.remove(edge);

currentPath添加到paths时,会添加currentPath的引用。最后,currentPath为空,因此paths留有空引用。

要避免此问题,请创建currentPath的副本,然后在paths中添加副本。

同时更新以下行:

 if(vertex=="TARGET_VERTEX"){

as

  if("TARGET_VERTEX".equals(vertex)){

使用正确的字符串相等性检查并避免NullPointerException。

如果您想忽略检查的情况,请使用equalsIgnoreCase()方法。

答案 2 :(得分:1)

您正在添加和删除路径。它似乎错误地做了,你可以尝试调试应用程序。

Java是按值传递的。这意味着像

这样的方法
public void myMethod(MyObject instance) {...}

收到instance引用值的副本。如果在你做的方法中

instance.setField(newValue);

然后您正在访问您传递的同一对象,因为该引用具有相同的值。但是,如果您在方法

中执行此操作
instance = new MyObject(newValue);

然后用于调用方法的对象将保持不变。发生这种情况是因为您更改了副本中的引用值,而不是原始

中的值

您可以在javadude详细了解它。

最后,您应该使用.equals方法而不是使用==来比较字符串和其他对象。 This answer should help you

对你的代码进行鸟瞰,我会纠正它(没试过):      通过 为字符串常量创建常量。

//These two lines, minor improvements
public static final String TARGETV= "TARGET_VERTEX";
public static final String STARTV= "START_VERTEX";

更改

if(vertex=="TARGET_VERTEX"){

  if(vertex.equals(TARGETV)){

关于打印paths变量,System.out.println将打印一个String,您正在传递一个Object(边缘列表列表)。每个对象都有一个toString()方法,当需要Object作为String时,该方法会自动调用。正如文档中所述,默认情况下:

  

Object类的toString方法返回一个由。组成的字符串   对象是实例的类的名称,at-sign   字符“@”,以及散列的无符号十六进制表示   对象的代码。

所以你可以:

创建一个新类(在内部实现List<List<Edge>>并覆盖toString()方法)您可以实现如下方法:

public static String printPath(List<List<Edge>> paths){
   StringBuffer sb = new StringBuffer();
   for(List<Edge> le : paths){
     for(Edge e: le){
         sb.append(le); //or similar method to print edges to String
      }
   }
   return sb.toString();

}

而不是:

System.out.println(paths);

这样做:

System.out.println(printPaths(paths));

答案 3 :(得分:0)

您需要知道的第一件事是对象不是Java中的值。 Java中唯一的类型是基本类型和引用类型,因此Java中唯一的值是基元和引用。 “引用”是指向Object的指针。代码中的pathscurrentPathedge等等都是引用pathscurrentPath的元素也是引用。分配或传递引用时,您将获得指向同一对象的另一个引用。基本上只有在执行new ...时才能创建新对象。

因此,应该更加明显地发生了什么。在代码中创建边缘列表的唯一位置是search()函数,当它调用traverse()函数时。 traverse()函数不包含任何对象创建表达式。因此,在所有递归和所有这些中,它正在使用并修改相同的列表对象。每次调用traverse()都会添加然后删除一个元素。因此,在递归结束时,列表中将没有元素。这是您添加到paths的引用的列表,因此您最后会在paths中看到对空列表的引用。

你说你在PHP工作过。在PHP5中,对象以相同的方式工作 - 对象不是PHP中的值,它们只能通过指向对象的指针来操作。但是,PHP中的数组(不是对象)是不同的; PHP中的数组是值,因此在分配或传递时,数组将被复制。