0

我有一些 Java 经验,我正在学习 Ruby。我遇到了一个 ruby​​ 程序,如下所示:

class Tree
  attr_accessor :children, :node_name
  def initialize(name, children=[])
    @children = children
    @node_name = name
  end
  def visit_all(&block)
    visit &block
    children.each {|c| c.visit_all &block}
  end
  def visit(&block)
    block.call self
  end
end
ruby_tree = Tree.new( "Ruby" ,
                      [Tree.new("Reia" ),
                       Tree.new("MacRuby" )] )
puts "Visiting a node"
ruby_tree.visit {|node| puts node.node_name}
puts
puts "visiting entire tree"
ruby_tree.visit_all {|node| puts node.node_name}

当我看到 ruby​​ 语言的强大功能时,我想用 Java 编写类似的代码,如下所示:

public class Tree {

    private String name;
    private Tree[] children;

    Tree(String name, Tree[] children) {
        this.name = name;
        this.children = children;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Tree[] getChildren() {
        return children;
    }

    public void setChildren(Tree[] children) {
        this.children = children;
    }

    public static void main(String[] args) {
        Tree myTree = new Tree("Ruby", new Tree[] {
                new Tree("Reia", new Tree[] {}),
                new Tree("MacRuby", new Tree[] {}) });
        myTree.visit();
        myTree.visit_all();
    }

    public void visit() {
        System.out.println(getName());
    }

    public void visit_all() {
        visit();
        for (Tree tree : children) {
            tree.visit();
        }
    }
}

问题:我知道这里的 java 版本不如 Ruby 灵活。Java 中是否有类似的东西可以实现 ruby​​ 提供的灵活性级别?

4

2 回答 2

1

首先,请注意:该代码绝对是可怕的。它几乎不提供任何封装,它会左右泄漏实现细节,Tree对象无法保持自己的不变量或状态。其次,它根本没有Ruby 的集合框架集成。

结果,我的 Java 翻译同样糟糕,而且它没有与 Java 的集合框架集成。

Java 代码与 Ruby 相比的两个最大缺点是

  • 在 Java 版本中,元素类型被硬编码为String,而在 Ruby 版本中,它可以是任何对象,甚至是同一棵树中对象的混合,并且
  • 在 Java 版本中,迭代器被硬编码以打印名称,而在 Ruby 版本中,迭代器采用块参数和要执行的代码。

第一个问题在 Java 中不容易解决。您可以使集合成为通用的,这样它就可以容纳任何类型的元素,但是使其成为异构的(即能够在同一个集合中容纳不同类型的元素)将是很多工作。所以,我坚持使用部分解决方案:制作Tree泛型。

第二个问题可以通过让迭代器获取一个包含代码的对象来解决。毕竟,一流的子程序与只有一个方法的对象基本相同。(Java 8 将消除一些痛苦,我在代码中包含了示例。)

import java.util.Collection;
import java.util.ArrayList;

interface Consumer<T> {
    void accept(T e);
}
// In Java 8, this interface is already part of the JRE.
// Just replace the 3 lines above with this import:
//import java.util.function.Consumer;

class Tree<T> {
    private String nodeName;
    private Collection<Tree<T>> children = new ArrayList<>();

    Tree(String name, Collection<Tree<T>> children) {
        nodeName = name;
        this.children = children;
    }

    Tree(String name) {
        nodeName = name;
    }

    public String getNodeName() { return nodeName; }
    public void setNodeName(String name) { nodeName = name; }

    public Collection<Tree<T>> getChildren() { return children; }
    public void setChildren(Collection<Tree<T>> children) { this.children = children; }

    void visitAll(Consumer<Tree<T>> block) {
        visit(block);
        for (Tree<T> tree : children) tree.visitAll(block);
    }

    void visit(Consumer<Tree<T>> block) {
        block.accept(this);
    }

    public static void main(String... args) {
        ArrayList<Tree<String>> children = new ArrayList<>();
        children.add(new Tree<String>("Reia"));
        children.add(new Tree<String>("MacRuby"));
        Tree<String> rubyTree = new Tree<>("Ruby", children);

        System.out.println("Visiting a node");
        rubyTree.visit(new Consumer<Tree<String>>() {
            public void accept(Tree<String> node) {
                System.out.println(node.getNodeName());
            }
        });
        // In Java 8, you can use a lambda.
        // Just replace the 5 lines above with this line:
        //rubyTree.visit(node -> System.out.println(node.getNodeName()));

        System.out.println();

        System.out.println("Visiting entire tree");
        rubyTree.visitAll(new Consumer<Tree<String>>() {
            public void accept(Tree<String> node) {
                System.out.println(node.getNodeName());
            }
        });
        // In Java 8, you can use a lambda.
        // Just replace the 5 lines above with this line:
        //rubyTree.visitAll(node -> System.out.println(node.getNodeName()));
    }
}
于 2013-09-04T13:31:44.793 回答
0
def visit(&block)
  block.call self
end

写得更好

def visit
  yield self
end

此外,visit_all并且visit会更惯用地写成符合Enumerable模块:

class Tree
  include Enumerable

  # ...

  def each(&cb)
    cb.call(@element)
    children.each end |child|
      child.each(&cb) if child.respond_to?(:each)
    end
  end
end

通过这种方式,您可以免费获得各种其他东西,例如max......而且,每个人都知道each将块应用于所有元素,而他们必须深入挖掘您的 API 文档或代码才能看到该函数被调用visit_all


编辑:删除了一大块,因为我显然是个白痴。感谢 steenslag 让我正确。

于 2013-09-04T09:17:21.343 回答