J
Jason Cavett
In my original code, I implemented a method that searched through a
tree structure to find children of a certain type beneath a parent
node. This is demonstrated in the following method. Note that this
method is in DataModel (which is a node in the tree, and an abstract
class that has to be implemented by any node in the tree).
public <E extends DataModel> List<E> getAllDescendentsOfType(
Class<? extends E> classType, boolean getArchived) {
List<E> foundDescendents = new ArrayList<E>();
for (DataModel child : children) {
if (!child.isArchived() || getArchived) {
if (classType.isInstance(child)) {
foundDescendents.add(classType.cast(child));
}
}
foundDescendents.addAll(child.getAllDescendentsOfType(
classType, getArchived));
}
return foundDescendents;
}
This method is relatively quick. (See the next part of my post for
why I'm saying this.)
So, because I wanted to separate the traversal of the tree from what
I'm actually trying to do on the tree (the implementation of an
algorithm), I decided to implement a visitor pattern where the
developer can provide a visitor that will perform whatever task it is
I want performed on a node and the traversal happens in the DataModel
itself. So, something like this...
// In DataModel
public <E extends DataModel> List<E> getAllDescendentsOfType(
Class<E> classType, boolean getArchived) {
AllDescendantsOfTypeVisitor<E> visitor = new
AllDescendantsOfTypeVisitor<E>(
classType, getArchived);
this.visit(visitor);
return visitor.getDescendants();
}
// Also in DataModel
public final void visit(Visitor visitor) {
visitor.startVisit(this);
synchronized (childrenLock) {
// Recurse through the children.
for (DataModel child : children) {
child.visit(visitor);
}
}
visitor.stopVisit(this);
}
// A visitor to find all the descendants of a certain type.
public class AllDescendantsOfTypeVisitor<E extends DataModel>
implements
Visitor {
private Class<E> classType;
private List<E> descendants = new ArrayList<E>();
private boolean archived = false;
public AllDescendantsOfTypeVisitor(Class<E> classType, boolean
archived) {
this.classType = classType;
this.archived = archived;
}
public List<E> getDescendants() {
return descendants;
}
@Override
public void startVisit(DataModel model) {
if (!model.isArchived() || this.archived) {
if (classType.isInstance(model)) {
descendants.add(classType.cast(model));
}
}
}
@Override
public void stopVisit(DataModel model) {
}
}
Basically, I pass in an instance of the visitor to
DataModel.visit(Visitor) and when the visit is completed, this
specific visitor will have the children I am looking for. (And I can
do any task on the tree - searching, preparing for writing, copying,
etc. It doesn't really matter.)
When I switched to this method, the application began running
noticeably slower. After profiling, there is seconds difference
between the first method that I implemented and the visitor method.
Why would this be happening? Are Java method calls so expensive that
I would notice this. (Please note that I do this search a lot since
I'm doing a lot of tree traversal to find things. We're talking
millions of traversals, so if a method is expensive, I would notice
it.)
Or...am I just doing something stupid that makes this more expensive
than it should be? Any help would be appreciated, because this is a
bit exasperating.
Thanks!
tree structure to find children of a certain type beneath a parent
node. This is demonstrated in the following method. Note that this
method is in DataModel (which is a node in the tree, and an abstract
class that has to be implemented by any node in the tree).
public <E extends DataModel> List<E> getAllDescendentsOfType(
Class<? extends E> classType, boolean getArchived) {
List<E> foundDescendents = new ArrayList<E>();
for (DataModel child : children) {
if (!child.isArchived() || getArchived) {
if (classType.isInstance(child)) {
foundDescendents.add(classType.cast(child));
}
}
foundDescendents.addAll(child.getAllDescendentsOfType(
classType, getArchived));
}
return foundDescendents;
}
This method is relatively quick. (See the next part of my post for
why I'm saying this.)
So, because I wanted to separate the traversal of the tree from what
I'm actually trying to do on the tree (the implementation of an
algorithm), I decided to implement a visitor pattern where the
developer can provide a visitor that will perform whatever task it is
I want performed on a node and the traversal happens in the DataModel
itself. So, something like this...
// In DataModel
public <E extends DataModel> List<E> getAllDescendentsOfType(
Class<E> classType, boolean getArchived) {
AllDescendantsOfTypeVisitor<E> visitor = new
AllDescendantsOfTypeVisitor<E>(
classType, getArchived);
this.visit(visitor);
return visitor.getDescendants();
}
// Also in DataModel
public final void visit(Visitor visitor) {
visitor.startVisit(this);
synchronized (childrenLock) {
// Recurse through the children.
for (DataModel child : children) {
child.visit(visitor);
}
}
visitor.stopVisit(this);
}
// A visitor to find all the descendants of a certain type.
public class AllDescendantsOfTypeVisitor<E extends DataModel>
implements
Visitor {
private Class<E> classType;
private List<E> descendants = new ArrayList<E>();
private boolean archived = false;
public AllDescendantsOfTypeVisitor(Class<E> classType, boolean
archived) {
this.classType = classType;
this.archived = archived;
}
public List<E> getDescendants() {
return descendants;
}
@Override
public void startVisit(DataModel model) {
if (!model.isArchived() || this.archived) {
if (classType.isInstance(model)) {
descendants.add(classType.cast(model));
}
}
}
@Override
public void stopVisit(DataModel model) {
}
}
Basically, I pass in an instance of the visitor to
DataModel.visit(Visitor) and when the visit is completed, this
specific visitor will have the children I am looking for. (And I can
do any task on the tree - searching, preparing for writing, copying,
etc. It doesn't really matter.)
When I switched to this method, the application began running
noticeably slower. After profiling, there is seconds difference
between the first method that I implemented and the visitor method.
Why would this be happening? Are Java method calls so expensive that
I would notice this. (Please note that I do this search a lot since
I'm doing a lot of tree traversal to find things. We're talking
millions of traversals, so if a method is expensive, I would notice
it.)
Or...am I just doing something stupid that makes this more expensive
than it should be? Any help would be appreciated, because this is a
bit exasperating.
Thanks!