I have a list of objects, lets say Shapes.
I would like to process them using a stream and return another object - ShapeType - based on what is in the list.
Normally I will just return ShapeType.GENERIC, however if there is a Rectangle in there, I would like to return ShapeType.RECT. If there is a hexagon in a list I would like to return ShapeType.HEXA.
When both rectangle and square are present, I would like to return ShapeType.HEXA.
Now, when it comes to code I would like something like this:
public ShapeType resolveShapeType(final List<Shape> shapes) {
shapes.stream()
.filter(shape -> shape.getSideCount() == 6 || shape.getSideCount() == 4)
// I should have a stream with just rectangles and hexagons if present.
// what now?
}
You can use
public ShapeType resolveShapeType(final List<Shape> shapes) {
int sides = shapes.stream()
.mapToInt(Shape::getSideCount)
.filter(count -> count==4 || count==6)
.max().orElse(0);
return sides==6? ShapeType.HEXA: sides==4? ShapeType.RECT: ShapeType.GENERIC;
}
This maps each element to its side count and reduces them to the preferred type, which happens to be the maximum count here, so no custom reduction function is needed.
This isn’t short-circuiting, but for most use cases, it will be sufficient. If you want to reduce the number of operations to the necessary minimum, things will be more complicated.
public ShapeType resolveShapeType(final List<Shape> shapes) {
OptionalInt first = IntStream.range(0, shapes.size())
.filter(index -> {
int count = shapes.get(index).getSideCount();
return count == 6 || count == 4;
})
.findFirst();
if(!first.isPresent()) return ShapeType.GENERIC;
int ix = first.getAsInt(), count = shapes.get(ix).getSideCount();
return count==6? ShapeType.HEXA: shapes.subList(ix+1, shapes.size()).stream()
.anyMatch(shape -> shape.getSideCount()==6)? ShapeType.HEXA: ShapeType.RECT;
}
We know that we can stop at the first HEXA, but to avoid a second pass, it’s necessary to remember whether there was an occurence of RECT for the case there is no HEXA. So this searches for the first element that is either, a RECT or HEXA. If there is none, GENERIC is returned, otherwise, if the first was not a HEXA, the remaining elements are checked for an element of the HEXA kind. Note that for processing the remainder after the first RECT, no filter is needed as it is implied that shapes that are neither, RECT nor HEXA, can’t fulfill the condition.
But it should also be obvious that this code, trying to minimize the numbers of checks, is harder to read than an equivalent for loop.
Assuming that only the three types of shapes can be present in the list, an alternative would be:
Set<Integer> sides = shapes.stream()
.map(Shape::getSideCount)
.collect(toSet());
if (sides.contains(6)) return HEXA;
else if (sides.contains(4)) return RECTANGLE;
else return GENERIC;
But I think the most straightforward (and efficient) way would be a good old for loop:
ShapeType st = GENERIC;
for (Shape s : shapes) {
if (s.getSideCount() == 6) return HEXA;
if (s.getSideCount() == 4) st = RECTANGLE;
}
return st;
If I understand what you're trying to do, then you can use anyMatch. Like,
public ShapeType resolveShapeType(final List<Shape> shapes) {
if (shapes.stream().anyMatch(shape -> shape.getSideCount() == 6)) {
return ShapeType.HEXA;
} else if (shapes.stream().anyMatch(shape -> shape.getSideCount() == 4)) {
return ShapeType.RECT;
} else {
return ShapeType.GENERIC;
}
}
One way to do this (streaming shapes once) would be to preserve the shape presence with an array. Like,
public ShapeType resolveShapeType(final List<Shape> shapes) {
boolean[] bits = new boolean[2];
shapes.stream().forEach(shape -> {
int sides = shape.getSideCount();
if (sides == 4) {
bits[0] = true;
} else if (sides == 6) {
bits[1] = true;
}
});
if (bits[1]) {
return ShapeType.HEXA;
} else if (bits[0]) {
return ShapeType.RECT;
} else {
return ShapeType.GENERIC;
}
}
Can also do something like this:
ShapeType r = shapes.stream()
.map(s -> ShapeType.parse(s.getSides()))
.filter(c -> c == ShapeType.Hexagon || c==ShapeType.Square)
.max(ShapeType::compareTo)
.orElse(ShapeType.Generic);
Here, I've taken a little liberty with your ShapeType:
enum ShapeType {
Square(4), Hexagon(6), Generic(Integer.MAX_VALUE);
int v;
ShapeType(int v) {
this.v = v;
}
static ShapeType parse(int v) {
switch (v) {
case 4: return Square;
case 6: return Hexagon;
default:
break;
}
return Generic;
}
public String toString(){
return Integer.toString(v);
}
}
TBH you can avoid the parse operation if you add a getShapeType() method which returned the correct type per Derived type. Then the map() operation will only extract the type, for example .map(Shape::getShapeType).
The .filter() will find the group you are interested in, the largest shape is deemed the label of the collection...
Sounds like a case for 'reduce' or 'collect/max'.
Assume you have a method that selects the 'dominant' type (you can put it in a lambda, but IMHO it's more readable as a method):
public class Util{
public static ShapeType dominantType(ShapeType t1, ShapeType t2){
if(t1==HEXA || t2==HEXA) return HEXA;
else if (t1==RECTANGLE || t2==RECTANGLE) return RECTANGLE;
else return GENERIC;
}
}
There are several ways to use it, one reduce example would be:
shapes.stream()
.filter(shape -> shape.getSideCount() == 6 || shape.getSideCount() == 4)
.map(shape -> shape.getSideCount()==6? HEXA:RECTANGLE)
.reduce( GENERIC, Util::dominantType);
// using GENERIC in case of empty list
You may also want to look into collectors.maxBy.
BTW whatever approach you take, please give some thought to the behavior in case of an empty list...
Related
I recently refactored my code to support variable arguments in a method called searchForBuilding:
public static boolean searchForBuilding(Building[] buildings, ColorEnum ...c) {
for (Building building : buildings) {
for (ColorEnum color : c) {
if (building.getType().getColor() == color) {
return true;
}
}
}
return false;
}
Previously, I would still loop through the objects I wanted to check, but I'd use the || operator to compare the different BuildingEnums I wanted to evaluate.
for (Building building : BoardTraverser.getAdjacentBuildings(bArray, row, col)) {
if (building.getType().getColor() == ColorEnum.RED || building.getType().getColor() == ColorEnum.WHITE) {
return 2;
}
}
return 0;
The resulting change resembles this:
return BoardTraverser.searchForBuilding(BoardTraverser.getAdjacentBuildings(bArray, row, col), ColorEnum.RED, ColorEnum.WHITE) ? 2 : 0;
Is my new method the same in terms of computational time as comparing things using the OR operator?
Currently I have a method overloading the following method:
public boolean isHorizontalOrVertical(Point firstPoint, Point secondPoint) {
return firstPoint.getFirstComponent() == secondPoint.getFirstComponent()
|| firstPoint.getSecondComponent() == secondPoint.getSecondComponent();
}
public boolean isHorizontalOrVertical(List<Point> points) {
if (points == null || points.size() < 2) {
throw new IllegalArgumentException("invalid number of points");
}
Point start = points.get(0);
return points.stream()
.allMatch(p -> isHorizontalOrVertical(start, p));
}
The method is needed to check if two or three points are vertical/horizontal to each other. In the case of three points, it just has to check if the two last points are horizontal/vertical to the start point.
Does anyone have any idea how I can get it all into just one method?
First and foremost I have to note the fact that it doesn't make sense, to me at least, a method which calculates if two entities are horizontal or vertical and those entities are Points. How can two points be horizontal or vertical?
isHorizontalOrVertical is a bad name
Overcoming the above, you could create a single method which calculates if two points are horizontal or vertical.
Change the name isHorizontalOrVertical because it's redundant. A better name is isHorizontal or isVertical. The method will return a boolean so if isHorizontal returns false, then it's vertical and vice versa. Probably a better name could be areTwoPointsHorizontal but I am having trouble even writing that because it transmits the wrong message, but feel free to choose your own.
So the method,
public boolean isHorizontal(Point first, Point second){
boolean sameFirstComponents = firstPoint.getFirstComponent() ==
secondPoint.getFirstComponent();
boolean sameSecondComponents = firstPoint.getSecondComponent() ==
secondPoint.getSecondComponent();
return sameFirstComponents || sameSecondComponents;
}
Finally, create a method which calculates if an arbitary number of points in a list are all between them horizontal or vertical (assuming if point A is horizontal with point B, then if point C is horizontal with B, so is with A).
Oveload that method since it does the same thing and the only thing changing are the parameters. (Note the use of the simple isHorizontal method from above)
public boolean isHorizontal(List<Point> points){
boolean allPointsHorizontal = true;
for (int i=0; i<points.size(); i++) {
boolean nextPointExists = i<points.size() - 1;
if (nextPointExists) {
Point current = points.get(i);
Point next = points.get(i+1);
allPointsHorizontal = allPointsHorizontal && isHorizontal(current,next);
if (!allPointsHorizontal)
return false;
}
}
return allPointsHorizontal;
}
You can have just one method as follows:
public boolean isHorizontalOrVertical(List<Point> points) {
if (points == null || points.size() < 2) {
throw new IllegalArgumentException("invalid number of points");
}
if (points.size() == 2) {
return points.get(0).getFirstComponent() == points.get(1).getFirstComponent()
|| points.get(0).getSecondComponent() == points.get(1).getSecondComponent();
}
Point start = points.get(0);
return points.stream()
.allMatch(p -> isHorizontalOrVertical(List.of(start, p)));
}
Note: If you are not using Java version >= 9, please use Arrays.asList instead of List.of.
The method could be implemented like this:
public boolean isHorizontalOrVertical(Point firstPoint, Point secondPoint, Point thirdPoint) {
return isHorizontalOrVertical(Arrays.asList(firstPoint, secondPoint, thirdPoint));
}
Your isHorizontalOrVertical(List<Point>) method will fail when the list is empty, and the call doesn't make much sense when the list has only one element.
I think a better way to do this is with two required parameters, plus a variadic parameter, so that callers must at least give 2 points.
private boolean are2PointsHorizontalOrVertical(Point firstPoint, Point secondPoint) {
return firstPoint.getFirstComponent() == secondPoint.getFirstComponent()
|| firstPoint.getSecondComponent() == secondPoint.getSecondComponent();
}
public boolean arePointsHorizontalOrVertical(Point point1, Point point2, Point... rest) {
return are2PointsHorizontalOrVertical(point1, point2) &&
Arrays.stream(rest).allMatch(x -> are2PointsHorizontalOrVertical(point1, x));
}
This technically is still "one method" as far as the public interface is concerned. You can substitute the helper are2PointsHorizontalOrVertical back into the public method if you really want, but I don't see any benefit in doing that.
Actually, you can have only one method:
public boolean isHorizontalOrVertical(Point firstPoint, Point secondPoint, Point... others) {
// check firstPoint, secondPoint for null is ommited
if (others == null || others.length == 0) {
return firstPoint.getFirstComponent() == secondPoint.getFirstComponent()
|| firstPoint.getSecondComponent() == secondPoint.getSecondComponent();
} else {
// First, create a stream with second point + others elements
// then match against the first point
return Stream.of(new Point[]{secondPoint}, others).flatMap(e -> Stream.of(e))
.allMatch(p -> isHorizontalOrVertical(firstPoint, p));
}
}
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
I've got a situation in which I need to check multiple conditions, where every combination has a different outcome. In my specific condition, I've got 2 variables, which are enum types, that can each be 2 different values.
enum Enum1
{
COND_1,
COND_2
}
enum EnumA
{
COND_A,
COND_B
}
Enum1 var1;
EnumA varA;
This gives me 4 possible conditions, which requires 4 different outcomes. I've come up with a few different ways of doing this, either using if statements or switch statements:
if(var1 == Enum1.COND_1 && varA == EnumA.COND_A)
{
// Code
}
else if(var1 == Enum1.COND_1 && varA == EnumA.COND_B)
{
// Code
}
else if(var1 == Enum1.COND_2 && varA == EnumA.COND_A)
{
// Code
}
else if(var1 == Enum1.COND_2 && varA == EnumA.COND_B)
{
// Code
}
Or:
switch(var1)
{
case COND_1:
switch(varA)
{
case COND_A:
// Code
break;
case COND_B:
// Code
break;
}
break;
case COND_2:
switch(varA)
{
case COND_A:
// Code
break;
case COND_B:
// Code
break;
}
break;
}
I've thought of others, but don't want to fill this up with code :P I'd like to know what the best way to do this is. I think the switch is a bit easier to read, but the ifs are shorter. I think it'd be really cool if switches could have multiple conditions, but I haven't heard of it. This also begs the question: what's the best way to do this with an arbitrary number of variables and possible values?
For your small use case I would probably go for nested if statements. But if you have plenty of enum constants, perhaps a pattern using streams could make your code easier to read and maintain (for a small performance penalty). You could solve it using a stream like this:
Stream.of(new Conditional(COND_1, COND_A, () -> {/* do something */}),
new Conditional(COND_1, COND_B, () -> {/* do something */}),
new Conditional(COND_2, COND_A, () -> {/* do something */}),
new Conditional(COND_2, COND_B, () -> {/* do something */}))
.filter(x -> x.test(var1, varA))
.findAny()
.ifPresent(Conditional::run);
That would require a supporting class:
class Conditional implements BiPredicate<Enum1, EnumA>, Runnable
{
private final Enum1 var1;
private final EnumA varA;
private final Runnable runnable;
public Conditional(Enum1 var1, EnumA varA, Runnable runnable) {
this.var1 = var1;
this.varA = varA;
this.runnable = runnable;
}
#Override
public boolean test(Enum1 enum1, EnumA enumA) {
return var1 == enum1 && varA == enumA;
}
#Override
public void run() {
runnable.run();
}
}
Performance differences are probably negligible here, so I would focus on shortness and readability. So I would just simplify the if's a bit by using temporary variables:
boolean is_1 = (var1 == Enum1.COND_1);
boolean is_A = (varA == EnumA.COND_A);
if(is_1 && is_A)
{
// Code
}
else if(is_1 && !is_A)
{
// Code
}
else if(!is_1 && is_A)
{
// Code
}
else if(!is_1 && !is_A)
{
// Code
}
I prefer the if variant without nesting, since it is short and you have all the conditions in one line.
When stopping through the code during debugging, it can get tedious though, since you have to step over all preceding conditions, which is O(n). When executing the code, this shouldn't matter since the compiler will probably optimize the code.
There is no obvious best way, so you will have to experiment a bit.
I definitely prefer the flat version, it could just use a little less duplication:
// If you can't make the variables final, make some final copies
final Enum1 var1 = Enum1.COND_2;
final EnumA varA = EnumA.COND_B;
class Tester { // You could also make an anonymous BiPredicate<Enum1, EnumA>
boolean t(Enum1 v1, EnumA vA) {
return var1 == v1 && varA == vA;
}
};
Tester tes = new Tester();
if (tes.t(Enum1.COND_1, EnumA.COND_A)) {
// code
} else if (tes.t(Enum1.COND_1, EnumA.COND_B)) {
// code
} else if (tes.t(Enum1.COND_2, EnumA.COND_A)) {
// code
} else if (tes.t(Enum1.COND_2, EnumA.COND_B)) {
// code
}
Run it here. You could maybe make it even shorter and less redundant by doing a static import of the enums to avoid mentioning the enum names, e.g. tes.t(COND_1, COND_B). Or if you're willing to give up some compile time safety you can pass a string which gets converted to the two enum values, e.g. tes.t("COND_1 COND_A") (the implementation is left to the reader).
Maybe crazy idea but you could construct an int or a byte using the flags and use it in a single switch.
private int getIntegerStateForConditions(boolean... conditions ){
int state = 0;
int position = 0;
for(boolean condition: conditions){
if(condition){
state = state || (1 << position++);
}
}
return state;
}
...
switch(getIntegerStateForCondition((var1 == Enum1.COND_1), (var2 == EnumA.COND_A)){
case 0: ... //both condition false
case 1: ... //first condition true second false
case 2: ... //first false, second true ...
}
...
I think this is very far from being clean code but it looks better.
If I were you I would rely on bit flags in order to have only one byte (as you have only 4 use cases) to deal with and use a switch statement on this byte to manage all your use cases.
Something like this:
private static final int COND_2 = 1;
private static final int COND_B = 2;
private byte value;
public void setValue(Enum1 enum1) {
if (enum1 == Enum1.COND_1) {
this.value &= ~COND_2;
} else {
this.value |= COND_2;
}
}
public void setValue(EnumA enumA) {
if (enumA == EnumA.COND_A) {
this.value &= ~COND_B;
} else {
this.value |= COND_B;
}
}
public Enum1 getEnum1() {
return (this.value & COND_2) == COND_2 ? Enum1.COND_2 : Enum1.COND_1;
}
public EnumA getEnumA() {
return (this.value & COND_B) == COND_B ? EnumA.COND_B : EnumA.COND_A;
}
Then your tests would be:
switch (value) {
case 0 :
// 1-A;
break;
case 1 :
// 2-A;
break;
case 2 :
// 1-B;
break;
case 3 :
// 2-B;
break;
}
I would personally prefer this:
if(understandableNameInContextName1(var1, varA))
{
// Code
}
else if(understandableNameInContextName2(var1, varA))
{
// Code
}
else if(understandableNameInContextName3(var1, varA))
{
// Code
}
else if(understandableNameInContextName4(var1, varA))
{
// Code
}
private boolean understandableNameInContextName1(Object var1, Object varA){
return (var1 == Enum1.COND_1 && varA == EnumA.COND_A);
}
private boolean understandableNameInContextName2(Object var1, Object varA){
return (var1 == Enum1.COND_1 && varA == EnumA.COND_B);
}
private boolean understandableNameInContextName3(Object var1, Object varA){
return (var1 == Enum1.COND_2 && varA == EnumA.COND_A);
}
private boolean understandableNameInContextName4(Object var1, Object varA){
return (var1 == Enum1.COND_2 && varA == EnumA.COND_B);
}
And the names of the methods could be like, isOrderShippedAndDelivered(), isRequestSendAndAckRecieved().
The reason is that this is going to make the code a lot more readable.
Unless you have data that leads you back to these if statement there is not going to be much gain optimizing these.
See:
https://softwareengineering.stackexchange.com/questions/80084/is-premature-optimization-really-the-root-of-all-evil
Kind of depends on the complexity of the code and number of combinations but another option is a dictionary with the key comprising a Tuple of your enumerations and a value of a delegate to the code.
i need some help. I already runned my full code via JUnit but I still get errors. I think its because of my traverse code.
The purpose of the traverse code is to create a LinkedList in a in PreOrder.
for example: JUnit always says that something like that is wrong
assertArrayEquals( new Integer[]{2, 14, 26, 86, 122, 134, 182},
Arrays.copyOf(tree.traversePreOrder(), tree.getSize(), Integer[].class));
#Override
public Object[] traversePreOrder() {
BinaryTreeNode<T> x = root;
LinkedList<Object> y = new LinkedList<Object>();
if (x == null) {
return null;
} else {
y.add(x.value);
y.add(travPreOrd(x.getLeft()));
y.add(travPreOrd(x.getRight()));
}
return y.toArray();
}
public LinkedList<Object> travPreOrd(BinaryTreeNode<T> x) {
BinaryTreeNode<T> tmp = x;
LinkedList<Object> space = new LinkedList<Object>();
if (x == null) {
return null;
} else {
space.add(tmp.getValue());
space.add(travPreOrd(x.getLeft()));
space.add(travPreOrd(x.getRight()));
}
return space;
}
You have a big problem since you always add the results of travPreOrd, which is another List<Object> or null if the node doesn't exist.
The best solution for these cases is to maintain the overridden method as non-recursive and overload this method that uses recursion and has an argument where receives the container where it has to add the data:
public List<Object> travPreOrd(BinaryTreeNode<T> x) {
BinaryTreeNode<T> tmp = x;
List<T> space = new LinkedList<T>();
travPreOrd(x, space);
return space;
}
private void travPreOrd(BinaryTreeNode<T> x, List<T> space) {
if (x == null) {
return;
}
space.add(tmp.getValue());
travPreOrd(x.getLeft(), space);
travPreOrd(x.getRight(), space);
}
To add the contents of one list to another you use addAll.
So instead of:
y.add(x.value);
y.add(travPreOrd(x.getLeft()));
y.add(travPreOrd(x.getRight()));
you want
y.add(x.value);
y.addAll(travPreOrd(x.getLeft()));
y.addAll(travPreOrd(x.getRight()));
And then you're returning null from travPreOrd, which just makes your life harder - you then have to check for it and deal with it specially. Instead you can just return an empty list.
So instead of
if (x == null) {
return null;
}
you could do
if (x == null) {
return Collections.emptyList();
}
EDIT: Part of your problem is that you are using a List<Object> - which allows you to add anything to it, including other lists. If you instead used List<Integer>, or you used generics and had a List<T>, then the compiler would be able to tell you when you were doing the wrong thing.
I've a Vector of objects, and have to search inside for a random attribute of those objects (For example, a Plane class, a Vector containing Plane; and I've to search sometimes for destination, and others to pilotName).
I know I can traverse the Vector using an Iterator, but I've got stuck at how do I change the comparison made between a String and the attribute on the object. I thought of using switch, but a another opinion would be cool.
Update 1:
The code I've written is something like this (Java n00b alert!):
public int search(String whatSearch, String query){
int place = -1;
boolean found = false;
for ( Iterator<Plane> iteraPlane = this.planes.iterator(); iteraPlane.hasNext() && found == false; ) {
Plane temp = (Plane) iteraPlane.next();
/* Here is where I have to search for one of many attributes (delimited by whatSearch */
}
return place;
}
Seems I've to stick to linear search (and that's a price I've able to pay). Anyway, I was thinking if Java had something like variable variable name (ouch!)
I assume that your problem is that you want to have a method that searches for a result based on some property of the collection type. Java is weak on this because it is best expressed in a language which has closures. What you need is something like:
public interface Predicate<T> {
public boolean evaluate(T t);
}
And then your search method looks like:
public static <T> T findFirst(List<T> l, Predicate<T> p) { //use List, not Vector
for (T t : l) { if (p.evaluate(t)) return t; }
return null;
}
Then anyone can use this general-purpose search method. For example, to search for an number in a vector of Integers:
List<Integer> is = ...
findFirst(is, new Predicate<Integer> {
public boolean evaluate(Integer i) { return i % 2 == 0; }
});
But you could implement the predicate in any way you want; for any arbitrary search
Use Collections.binarySearch and provide a Comparator.
EDIT: This assumes that the Vector is sorted. Otherwise, one has to do a linear search.
the equals() method is the best option. For these iterations you could do something like this:
for (Plane plane: planes) {
if ("JFK".equals(plane.getDestination())) {
// do your work in here;
}
}
or you could override the equals() method within Plane to see if the String passed in matches your destination (or pilot). this will allow you to use the indexOf(Object) and indexOf(Object, index) methods on Vector to return you the index(es) of the object(s). Once you have that, you could use Vector.get(index) to return to Object for you.
in Plane.java:
public boolean equals(Object o) {
return o.equals(getDestination()) ||
o.equals(getPilot()) ||
super.equals(o);
}
there is more work to be done with this option, as you will need to override hashCode() as well (see documentation).
See #oxbow_lakes above -- I think what you want isn't to pass a String as whatSearch, it's to pass a little snippet of code that knows how to get the property you're interested in. For a less general version:
public static interface PlaneMatcher {
boolean matches(Plane plane, String query);
}
public int search(PlaneMatcher matcher, String query){
int place = -1;
boolean found = false;
for ( Iterator<Plane> iteraPlane = this.planes.iterator(); iteraPlane.hasNext() && found == false; ) {
Plane temp = (Plane) iteraPlane.next();
if (matcher.matches(temp, query) {
found = true;
}
place++;
}
return place;
}
...
// example
int pilotNameIndex = search(new PlaneMatcher() {
boolean matches(Plane plane, String query) {
// note: assumes query non-null; you probably want to check that earlier
return query.equals(plane.getPilotName());
}
}, "Orville Wright");
(By the way, if it's the index you're interested in rather than the Plane itself, I wouldn't bother with an Iterator -- just use an old-fashioned for (int i = 0; i < planes.size(); i++) loop, and when you have a match, return i.)
Now, the tricky bit here is if what you have to search for is really identified by arbitrary strings at run-time. If that's the case, I can suggest two alternatives:
Don't store these values as object fields -- plane.pilotName, plane.destination -- at all. Just have a Map<String, String> (or better yet, a Map<Field, String> where Field is an Enum of all the valid fields) called something like plane.metadata.
Store them as object fields, but prepopulate a map from the field names to PlaneMatcher instances as described above.
For instance:
private static final Map<String, PlaneMatcher> MATCHERS = Collections.unmodifiableMap(new HashMap<String, PlaneMatcher>() {{
put("pilotName", new PlaneMatcher() {
boolean matches(Plane plane, String query) {
return query.equals(plane.getPilotName());
});
...
put("destination", new PlaneMatcher() {
boolean matches(Plane plane, String query) {
return query.equals(plane.getDestination());
});
}}
...
public int search(String whatSearch, String query){
PlaneMatcher matcher = MATCHERS.get(whatSearch);
int place = -1;
boolean found = false;
for ( Iterator<Plane> iteraPlane = this.planes.iterator(); iteraPlane.hasNext() && found == false; ) {
Plane temp = (Plane) iteraPlane.next();
if (matcher.matches(temp, query) {
found = true;
}
place++;
}
return place;
}
Oh, and you might be tempted to use reflection. Don't. :)
A simple way is to pass a comparison function to your search routine. Or, if you need more speed, use generics.