Consider the following
public enum tc implements {
NORESULTS(0), GOOD_RESULTS(1), EXCELLENT_RESULTS(2), NO_DATA_AVAILABLE(5), SOME_OTHER_VALUE(4);
private final Integer value;
// Code for the constructor, getters and setters for the value****
The enum tc values correspond to the testValue in the below class.
public class TestData {
private int testID;
private String testName;
private int testValue;
....
...
}
In the Results class, the TestDataList has to be sorted by a different order of ranking rather than testValue.For example Excellent followed by Good Results followed by NoResults etc..
public class Results {
List<TestData> TestDataList = getTestData();
I can code for the comparator etc..the question is since I require a different ordering for the enums which of the following two options is better
a) add private int rankTestValue in the enum tc. This option may require that I have to write a method getRank(int value) that would return the corresponding rankTestValue based on the value.
OR
b) add in Results class a map Map tcRankMap = new HashMap();. Populate this map with key values like (2,1) (1,2) corresponding to (enum values, ranking).For example (2,1) would be Excellent_Results has first ranking etc.
Which of these two options would be better. If there are better solutions then please let me know.
Option (a) looks better and according to Object Oriented Analysis and Design.
The good news is that the is a question of implementation detail which can be encapsulated into your Comparator anyway, so it doesn't matter so much.
As for style and readability, I would prefer (a), but it's down to personal preference.
There is also a solution (c) - use the ordinal(), and then sort them according to rank. Just add a comment to make it clear
public enum tc implements {
// NB: enum values are sorted according to rank
EXCELLENT_RESULTS(2),
GOOD_RESULTS(1),
NORESULTS(0),
NO_DATA_AVAILABLE(5),
SOME_OTHER_VALUE(4);
private final Integer value;
// Code for the constructor, getters and setters for the value****
}
Your first option would look like this:
enum TestScore {
EXCELLENT(5),
NO_RESULT(2),
POOR(1);
private final int order;
private TestScore(int order) {
this.order = order;
}
public int compareOrderTo(TestScore other) {
return this.order - other.order;
}
}
You could then add a comparison method to TestData
public int compareTestScore(TestData other) {
return this.testScore.compareOrderTo(other.testScore);
}
And sort your list with:
Collections.sort(testData, TestData::compareTestScore);
The problem with this is that the order field is really completely arbitrary and needs to be updated each time you add a new entry. However that's definitely better and more explicit than using the natural ordering of the enum (i.e. it's ordinal value which should be entirely incidental to avoid fragility).
Related
I need to be able to sort an object by multiple conditions, and these sorts need to be configurable from the application.properties file (as in it should be possible to specify in the file the sorts to apply and the order).
So in my design I created a comparator for each one of the sorts, and then an enum with a supplier such as:
public enum CarSort {
ENGINE(CarEngineComparator::new), BRAND(CarBrandComparator::new);
private final Supplier<Comparator<Car>> constructor;
CarSort(Supplier<Comparator<Car>> constructor){
this.constructor = constructor;
}
Comparator<Car> newComparator() {
return constructor.get();
}
}
With this design, I then would be able to load the sorts from a properties file:
myapp.cars.sorts=BRAND,ENGINE
And chain the sorts with something like:
Stream<Comparator<Car>> comparators = sorts.stream().map(CarSort::newComparator);
return comparators
.reduce(Comparator::thenComparing)
.map(comparator -> filteredCars.sorted((comparator.reversed())))
.orElse(filteredCars)
.collect(Collectors.toList());
The problem I have at the moment is that one of the comparators requires two parameters, so I don't think this solution holds anymore, but I can't think of any clean alternatives at the moment.
Do you know if it would be possible to adapt my current design to fit this requirement, or have any alternative viable solution?
I'm going to add more details. Let's say the specific comparator I need to add sorts cars based for example on how far they are from the client. So it could be something like this (details are not important really, only the fact that it needs an additional parameter):
public class CarDistanceComparator implements Comparator<Car> {
private Location origin;
CarDistanceComparator(Location origin) {
this.origin = origin;
}
#Override
public int compare(Car o1, Car o2) {
double distanceToFirstCar = DistanceService.calculateDistance(origin, o1.getLocation());
double distanceToSecondCar = DistanceService.calculateDistance(origin, o2.getLocation());
return Double.compare(distanceToFirstCar, distanceToSecondCar);
}
}
So then what I would like is to be able to sort by BRAND, ENGINE, DISTANCE (as specified in the config file) for multiple customers. Meaning that I would need to pass a different argument each time to the DistanceComparator.
I am currently processing a huge data input, including a lot of values, which I want to receive in getters for later use.
After writing a few methodes, I wondered if it might be a better idea to just use one get Method, with an enum-class containing all possible values, e.g.
public double getStat(StatType st) {
if(st != null)
return st.getValue();
}
instead of
public double getAvgReflection() {
return ...
}
public double getMaxLifesteal() {
return ...
}
Is there any convention for using either of the two possibilities?
Any dis/advantages?
Thanks in advance!
Using an Enum maxes it easier to add new stats, just add a constant in the Enum.
But all stats need to behave the same way, i.e. be doubles in your example. What happens if you want to add an int stat?
The "convention" you are asking about really boils down to the use and definition of your values. When you make the values Enums, then you must handle them as that type. Meaning, the fields in your class would have to be defined as such:
private MyEnum avgReflection;
private MyEnum maxLifesteal;
...
public MyEnum getAvgReflection {
return this.avgReflection;
}
And so forth.
Now, you could have your Enums return double values, but these values are static. I don't think you are concerned about static values, but, instead, perhaps a static set of values.
You then have two possible options: declare all possible parameters as fields, or create one aggregate field to hold all values and then use an Enum as an index:
public enum MyEnum {
averageReflection(0),
maximumLifeSteal(1);
private int value;
private MyEnum(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
...
private double[] attributes = new double[100]; // arbitrary initialization
public double getAttribute(MyEnum attribute) {
return this.attributes[attribute.getValue()];
}
The two restrictions on using an array (assuming you want primitive values and you are concerned about performance) is that all the values must be the same type, and the number of attributes will be set at compile time.
Additionally, you may just want to use a Map<String,double> or Map<MyEnum,double>, or even Map<MyEnum, Object>. A Map type will give you the ability to maintain a dynamically-sized set and possibly holding multiple types as well (with the costly overhead of converting your values).
You should base your decision on the amount of attributes you need to keep, the kind of overhead you are willing to tolerate, and your style.
This question already has answers here:
How to get an enum value from a string value in Java
(32 answers)
Closed 7 years ago.
Let's say I have an enum with 100 values. For simplicity's sake, take the following example:
public enum code
{
CODE_1("string1"),
CODE_2("string2"),
CODE_3("string3"),
CODE_4("string4"),
...
}
I want to create a public method to convert strings with a known format (like "string1", "string2"...) to the appropiate enum value CODE_1, CODE_2... Typically this is done by iterating over all values, and if a match is found, return that enum value. (Details can be found in this question.)
However, I'm concerned with reguraly looping over all values. Could this potentially be a huge bottleneck? What if instead of 100 element, there were 1000?
As an exercise for myself, I tried to optimize this lookup with a static map, which can assure O(1) lookup time given any string. I like this extra gimmick, but I only want to include it in my code if it is actually necessary. What are your thoughts and findings on using the iterating method vs the map method?
public enum Code
{
...
//enum values
...
//The string-to-Code map
private static final Map<String,Code> CODE_MAP = populateMap();
private static Map<String,Code> populateMap()
{
Map<String,Code> map = new HashMap<String,Code>();
for(Code c : Code.values())
{
map.put(c.getCode(), c);
}
return map;
}
private String code;
private Code(String code)
{
this.code = code;
}
public String getCode()
{
return this.code;
}
public Code convertFromString(String code)
{
//assume that the given string is actually a key value in the map
return (Code) CODE_MAP.get(code);
}
}
You want a Map<String, Code>, but how to populate it neatly? Enums don't allow you to initialize a static fields before the enum instances are initialized, but there's a neat little trick, called the Initialization-on-demand holder idiom, that makes using a statically initialized map needed for this functionality easy to implement:
public enum Code {
CODE_1("string1"),
CODE_2("string2"),
CODE_3("string3"),
// etc
;
private static class Holder {
static Map<String, Code> CODE_MAP = new HashMap<>();
}
private final String code;
private Code(String code) {
this.code = code;
Holder.CODE_MAP.put(code, this);
}
public String getCode() {
return this.code;
}
public Code convertFromString(String code) {
return Holder.CODE_MAP.get(code);
}
}
This works because the class loader initializes inner static classes before initializing the enum class, so the map is assigned ready to load during enum instance initialization.
No loops. No special code to load the map (done in constructor). Minimal code.
Map is good option : cleaner code and O(1) . If you use for-loop then the best you get is O(n)
Your provided solution is proper implementation.
As you will have to expose only one method and it is more readable.
And it is always good to use Map instead of iterating it manually.
And also as you mentioned the complexity is O(1).
+1 to your question, as it gives a cleaner approach to use enum in some usecases.
If your string code value is a known and consistent format you could avoid the use of a Map and the memory it consumes and construct the CODE enum lookup value on the fly:
public static Code convertFromString(String code) {
return valueOf("CODE_" + code.substring("string".length()));
}
Well, alternatives to the map-solution would be a giant switch-statement (could be automatically generated) or binary-searching an array containing the strings. I don't think either will beat HashMap's performance by a large margin, though if it really matters, you are probably best off by benchmarking.
One thing that has not been mentioned is how Enum.valueOf() let's you turn a String into an enum value, if it has the exact name of one of the enum members. If this is at all a possiblity in your case (looking just at your example, I don't see how Code.CODE_1 could not be easily renamed Code.string1 etc.), I would suggest using it, as it requires no additional coding at all and will hence be the most understandable.
I have a custom object like this :
Linkedlist<ClassInfo> classes = new LinkedList<ClassInfo>();
Within that, there are accessors for a teacher's name, the class name, the room number, etc. These are all Strings. I have run into a situation where the data in that LinkedList needs to displayed by different parameters (i.e. teacher name, class name, the room number, etc.).
Can anyone supply a quick implementation of how to do this? If I use the Compartor interface, how would I be able tell it which String field to sort the list by? My research also lead me to the Collator, and I was wondering if this would be of use.
Appreciate any help.
Write a different Comparator implementation for each field:
Comparator<ClassInfo> CLASS_NAME_COMPARATOR = new Comparator<ClassInfo>() {
public int compare(ClassInfo class1, ClassInfo class2) {
return class1.getClassName().compareTo(class2.getClassName());
}
};
... // implementations for other fields
...and then sort by whichever comparator is appropriate:
Collections.sort(classes, CLASS_NAME_COMPARATOR);
You will have to provide a custom comparator for every ordering you need to sort your collection to. Eg:
class TeacherComparator implements Comparator<ClassInfo> {
public int compare(ClassInfo c1, ClassInfo c2) {
String teacher1 = c1.getTeacher();
String teacher2 = c2.getTeacher();
return teacher1.compareTo(teacher2);
}
}
class ClassNameComparator implements Comparator<ClassInfo> {
...
}
My program implements a Product class whose objects contain the following instance variables: name, priority, price, and amount.
I have a LinkedList of Product objects that I need to sort before doing any other operations on the LinkedList.
I want to sort the list first by priority (from lowest to highest). If priority is the same, then look at the price (lowest to highest), then the name (alphabetical order).
I have done a lot of reading about Collections.sort, Comparable, and Comparator. I believe I need to use the Comparable interface and implement a compareTo method. My thinking is that because both priority, price, and name have a "natural" ordering it makes more sense to use Comparable.
public class Product extends ProductBase implements PrintInterface, Comparable<Product>{
private String name;
private int priority;
private int cents;
private int quantity;
// setters and getters
/**
* Compare current Product object with compareToThis
* return 0 if priority, price and name are the same for both
* return -1 if current Product is less than compareToThis
* return 1 if current Product is greater than compareToThis
*/
#override
public int compareTo(Product compareToThis)
}
Then when I want to sort my LinkedList I just call Collections.sort(LinkedList). Before I start writing the code, can you tell me if I am I missing or forgetting anything?
*************UPDATE*******************************
I just created a separate class called ProductComparator with a compare method.
This is part of the LinkedList class..
import java.util.Collections;
public class LinkedList {
private ListNode head;
public LinkedList() {
head = null;
}
// this method will sort the LinkedList using a ProductComparator
public void sortList() {
ListNode position = head;
if (position != null) {
Collections.sort(this, new ProductComparator());
}
}
// ListNode inner class
private class ListNode {
private Product item;
private ListNode link;
// constructor
public ListNode(Product newItem, ListNode newLink) {
item= newItem;
link = newLink;
}
}
}
I am getting the following error from the IDE when I compile.
The method sort(List, Comparator) in the type Collections is not applicable for the arguments (LinkedList, ProductComparator).
Does anyone know why I am getting this error and can point me in the right direction to resolve it?
If there is a "natural" ordering, use Comparable. Rule of thumb for figuring out if the ordering is "natural" is, whether the order of the objects will always be that.
Having said that, the decision whether to use Comparable or Camparator is not the kind of decision you need to think too much about. Most IDEs have refactoring tools which makes the conversion between a Comparable and a Comparator very easy. So if you choose to walk the wrong path now, changing that will not require too much effort.
The order you define here on your Product is very specific and
will probably change in future versions of your program
might be enriched with contextual parameterization
won't cover new features
So it can hardly been said "natural".
I'd suggest to define a constant, for example
public static Comparator<Product> STANDARD_COMPARATOR = new Comparator<Product>() {
public int compare(Product p1, Product p1) {
return ...
}
};
then you'll be able to easily sort anywhere with
Collections.sort(myProductList, Product.STANDARD_COMPARATOR);
Your code will evolve in a better manner as you'll add other comparators.
Just like you should generally prefer composition over inheritance, you should try to avoid defining the behavior of your objects in immutable manner.
If your order was based only on numbers, Comparable would be fine.
However, since your order (sometimes) involves lexical order of text,
a Comparator class is better, since use of Comparable would mean using
String.compareTo which would prevent you from having internationalization.
A separate class which implements Comparator can make use of a
localized Collator for comparing Strings. For instance:
public class ProductComparator
implements Comparator<Product> {
private final Collator collator;
public ProductComparator() {
this(Locale.getDefault());
}
public ProductComparator(Locale locale) {
this.collator = Collator.getInstance(locale);
}
public int compare(Product product1,
Product product2) {
int c = product1.getPriority() - product2.getPriority();
if (c == 0) {
c = product1.getPrice() - product2.getPrice();
}
if (c == 0) {
c = collator.compare(product1.getName(), product2.getName());
}
return c;
}
}
Regardless of whether you go with Comparable or Comparator, it is wise
to make sure Product has an equals method which checks the same
attributes as the comparison code.