Reputation: 2253
I have some experience in Java and I am learning Ruby. I encountered a ruby program as below:
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}
When I looked at the power of ruby language, I thought to write similar code in Java as below:
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();
}
}
}
Question: I know that the java version here is not much flexible as Ruby.Is there anything similar in Java that I can do to achieve the level of flexibility like ruby does provides?
Upvotes: 0
Views: 193
Reputation: 198446
def visit(&block)
block.call self
end
is more nicely written as
def visit
yield self
end
Also, visit_all
and visit
would be more idiomatically written as conforming to the Enumerable
module:
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
This way, you get various other things for free, like e.g. max
... and also, everyone knows each
applies a block to all elements, while they would have to dig through your API docs or your code to see that the function is called visit_all
.
EDIT: a chunk removed because I'm apparently an idiot. Thanks to steenslag for setting me right.
Upvotes: 0
Reputation: 369584
First, a word of caution: that code is absolutely horrible. It provides almost no encapsulation, it leaks implementation details left and right, there's no way that a Tree
object can maintain its own invariants or state. Secondly, it doesn't integrate at all with Ruby's collection framework.
As a consequence, my Java translation is also equally horrible, and it also doesn't integrate with Java's collection framework.
The two biggest drawbacks that your Java code has compared to your Ruby are
String
, whereas in the Ruby version, it can be any object, and even a mixture of objects within the same tree, and The first problem cannot be easily solved in Java. You can make the collection generic, so that it can hold elements of any type, but making it heterogeneous (i.e. being able to hold elements of different types in the same collection) is going to be a lot of work. So, I stuck with the partial solution: making the Tree
generic.
The second problem can be solved by having the iterators take an object which contains the code. After all, a first-class subroutine is basically the same as an object with only one method. (Java 8 is going to take some of that pain away, I included examples in the code.)
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()));
}
}
Upvotes: 1