如何为类似的Ruby功能编写最好的Java代码?

时间:2013-09-04 08:57:14

标签: java ruby

我有一些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一样提供的灵活性水平吗?

2 个答案:

答案 0 :(得分: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()));
    }
}

答案 1 :(得分:0)

def visit(&block)
  block.call self
end

更好地写成

def visit
  yield self
end

此外,visit_allvisit会更加惯用地编写为符合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让我做对了。