I have an application where a user provides me with the name of a field, e.g name or costInCents, and I have to sort by that field. I have ways of guaranteeing that the field name will be correct. This application causes the complication that I simply cannot make my class Comparable and implement a specific compareTo(), since with a custom implementation of compareTo() I need to know which fields / methods to use at implementation time.
So to achieve this goal, I am trying to use reflection in order to match the field to its accessor. Here's a MWE of what I want to do.
Class Product is a simple POJO class whose instances I want to pairwise compare:
public class Product
{
final String name;
final Integer quantity;
final Long costInCents;
public Product(final String name, final Integer quantity, final Long costInCents)
{
this.name = name;
this.quantity = quantity;
this.costInCents = costInCents;
}
public String getName()
{
return name;
}
public Integer getQuantity()
{
return quantity;
}
public Long getCostInCents()
{
return costInCents;
}
}
And my Main class, which is currently incomplete:
public class Main {
public static void main(String[] args) {
final Product[] productArray =
{
new Product("Clorox wipes", 50, 700L),
new Product("Desk chair", 10, 12000L),
new Product("TV", 5, 30000L),
new Product("Bookcase", 5, 12000L),
new Product("Water bottle", 20, 700L),
};
// The following void methods are supposed to sort in-place with something like Arrays.sort() or Collections.sort(),
// but I am also open to solutions involving stuff like Stream::sorted() or similar ones, which return a sorted array.
sortByField(productArray, "costInCents");
sortByField(productArray, "name");
}
private void sortByField(final Product[] productArray, final String sorterFieldName)
{
final Field sorterField = getSorterField(sorterFieldName, LiteProduct.class); // Gets the Field somehow
final Method sorterAccessor = getSorterAccessor(sorterField, LiteProduct.class); // Given the Field, this is easy
Arrays.sort((Product p1, Product p2)->((Comparable<?>)sorterAccessor.invoke(p1)).compareTo(sorterAccessor.invoke(p2)) > 0); // Capture of ? instead of Object
}
}
Unfortunately, the Arrays.sort() line results in a compile-time error with message Capture of ? instead of Object. I have tried casting the second argument to Comparable<?>, Comparable<? super sorterField.getType(), etc, with no luck. Ideas?
Possibly the best way - with sorting strategies. No need for reflection, compatible with more complex sorting logic:
Map<String, Comparator<Product>> sortingStrategies = new HashMap<>(){
{
put("costInCents", Comparator.comparingLong(p->p.costInCents));
put("quantity", Comparator.comparingLong(p->p.quantity));
put("name", Comparator.comparing(p->p.name));
}
};
private void sortByField(final Product[] productArray, final String sorterFieldName)
{
Arrays.sort(productArray, sortingStrategies.get(sorterFieldName));
}
You could write a Comparator for each field and use it by name via a Map:
public class Product
{
private final static Map<String,Comparator<Product>> COMPARATORS;
static {
COMPARATORS = new HashMap<>();
COMPARATORS.put("name", new NameComparator());
COMPARATORS.put("costInCents", new CostInCentsComparator());
}
final String name;
final Integer quantity;
final Long costInCents;
public Product(final String name, final Integer quantity, final Long costInCents)
{
this.name = name;
this.quantity = quantity;
this.costInCents = costInCents;
}
public String getName()
{
return name;
}
public Integer getQuantity()
{
return quantity;
}
public Long getCostInCents()
{
return costInCents;
}
static class NameComparator implements Comparator<Product> {
#Override
public int compare(Product o1, Product o2) {
return o1.getName().compareTo(o2.getName());
}
}
static class CostInCentsComparator implements Comparator<Product> {
#Override
public int compare(Product o1, Product o2) {
return o1.getCostInCents().compareTo(o2.getCostInCents());
}
}
static Comparator<Product> getComparator(String name) {
return COMPARATORS.get(name);
}
}
and use that in the main class
public class Main {
public static void main(String[] args) {
final Product[] productArray =
{
new Product("Clorox wipes", 50, 700L),
new Product("Desk chair", 10, 12000L),
new Product("TV", 5, 30000L),
new Product("Bookcase", 5, 12000L),
new Product("Water bottle", 20, 700L),
};
// The following void methods are supposed to sort in-place with something like Arrays.sort() or Collections.sort(),
// but I am also open to solutions involving stuff like Stream::sorted() or similar ones, which return a sorted array.
sortByField(productArray, "costInCents");
sortByField(productArray, "name");
}
private static void sortByField(final Product[] productArray, final String sorterFieldName)
{
Arrays.sort(productArray, Product.getComparator(sorterFieldName));
}
}
You may have to do minor changes like makeing it null safe or something
you can try this:
public class Test{
public static void main(String arg[]){
final Product[] productArray =
{
new Product("Clorox wipes", 50, 700L),
new Product("Desk chair", 10, 12000L),
new Product("TV", 5, 30000L),
new Product("Bookcase", 5, 12000L),
new Product("Water bottle", 20, 700L),
};
Arrays.sort(productArray,(p1, p2) -> p1.getName().compareTo(p2.getName()));
for(Product p: productArray){
System.out.println(p.getName());
}
}
}
in this case Comparator is a functional interface so I used lambad expression. but you can do this too
With this class:
public class MyClass implements Comparable<MyClass> {
private String status;
private String name;
private String firstName;
#Override
public int compareTo(MyClass o) {
return 0;
}
}
I'd like to sort a list of MyClass objects with this order:
Firstly, status = "open", then "working" then, "close"
Secondly, name = "toto", then "titi"
Finally, firstName = "tutu", "tata"
How can I do this with the Comparable interface ?
I would do this like so: first define a set of lists which define the order for each field:
private static List<String> statusOrder = Arrays.asList("open", "working", "close");
private static List<String> nameOrder = Arrays.asList("toto", "titi");
private static List<String> firstNameOrder = Arrays.asList("tutu", "tata");
Then use List.indexOf to get the position of the element in the list, and then simply subtract the results:
#Override
public int compareTo(MyClass o) {
final int statusComp = statusOrder.indexOf(status) - statusOrder.indexOf(o.status);
if (statusComp != 0) return statusComp;
final int nameComp = nameOrder.indexOf(name) - nameOrder.indexOf(o.name);
if (nameComp != 0) return nameComp;
return firstNameOrder.indexOf(firstName) - firstNameOrder.indexOf(o.firstName);
}
The issue with this approach is that indexOf will return -1 if the element is not in the list. You would need to define the behaviour in the case where MyClass contains non-standard values (perhaps it will never happen).
I have created a class like this, which contains a bunch of arraylist as you can see. I've been setting the array with the methods add.. and then retrieving it with get.., when i tried to System.out.println numberofcitizen for example it is returning 0. Note that i have instantiated the class in another class to set the values.
public int numberOfCitizen;
private final ArrayList<Integer> citizenid = new ArrayList<>();
private final ArrayList<String> citizenName = new ArrayList<>();
private final ArrayList<Integer> citizenWaste = new ArrayList<>();
private final ArrayList<Float> longitude = new ArrayList<>();
private final ArrayList<Float> latitude = new ArrayList<>();
private final ArrayList<String> address = new ArrayList<>();
public void working() {
System.out.println("executing fine");
}
public void setnoOfcit(int number) {
this.numberOfCitizen = number;
}
public int getnumber() {
return this.numberOfCitizen;
}
public void addCitizenId(int citizen) {
citizenid.add(citizen);
}
public int getCitizenid(int i) {
int citId = citizenid.get(i);
return citId;
}
public void addCitizenName(String citizenname) {
citizenName.add(citizenname);
}
public String getCitizenName(int i) {
return citizenName.get(i);
}
public void addCitizenWaste(int waste) {
citizenWaste.add(waste);
}
public int getCitizenWaste(int i) {
return citizenWaste.get(i);
}
public void addLatitude(float lat) {
latitude.add(lat);
}
public float getLat(int i) {
return latitude.get(i);
}
public void addlng(float lng) {
longitude.add(lng);
}
public float getlng(int i) {
return longitude.get(i);
}
com.graphhopper.jsprit.core.problem.VehicleRoutingProblem.Builder vrpBuilder = com.graphhopper.jsprit.core.problem.VehicleRoutingProblem.Builder.newInstance();
public void runVPRSolver() {
System.out.println(numberOfCitizen);
System.out.println(getCitizenName(0));
//create a loop to fill parameters
Probable source of problem :
numberOfCitizen is a member attribute that you seem to never change. If you want it to represent the number of elements in your lists, either use citizenName.size() or increment the value of numberOfCitizen in one of the add methods.
Design flaw :
Your design takes for granted that your other class always use that one properly. Anytime you or someone uses that class, he must make sure that he add every single element manually. This adds code that could be grouped inside your class, which would be cleaner and easier to maintain.
So instead of several add method like this :
addCitizenid();
addCitizenName();
addCitizenWaste();
addLongitude();
addLatitude();
addAddress();
Design an other Citizen class which will contain those elements, and use a single list of instances of that class. That way you can use only one method :
private List<Citizen> citizenList = new ArrayList<>();
public void addCitizen(Citizen c) {
/*Add element in your list*/
citizenList.add(c);
}
This programming methodology is called "Encapsulation" which you can read about here
You need to increment numberOfCitizen in your add methods. For example:
public void addCitizenId(int citizen){
citizenid.add(citizen);
numberOfCitizen++;
}
I would also suggest encapsulating your variables into Objects, so create a citizen class:
public class Citizen {
private Integer id;
private Integer name;
private Integer waste;
}
And change your variable to an ArrayList of objects:
ArrayList<Citizen> citizens;
I want to sort a Set<TailleDetail> by one of its string attributes (mesure), but the only solutions I saw in the internet (TreeSet and Comparator) don't work, can you help me?
My class:
public class TailleDetail {
private Integer id;
private String sexe;
private String mesure;
private Taille Taille;
}
EDIT -
For TreeSet I just try this:
Set<TailleDetail> tailles = new TreeSet<TailleDetail>();
to remplace :
Set<TailleDetail> tailles = new HashSet<TailleDetail>();
And For Comparator I try this :
Set<TailleDetail> tailles = new HashSet<TailleDetail>();
Comparator<TailleDetail> comparatorTaille = new Comparator<TailleDetail>() {
#Override
public int compare(TailleDetail left, TailleDetail right) {
return left.toString().compareToIgnoreCase(right.toString());
}
};
List<TailleDetail> tai = tailleDetailViewManager.search(param, true);
Collections.sort(tai, comparatorTaille);
tailles = new HashSet<TailleDetail>(tai);
The following code should make the trick :
final Set tailleDetails = new TreeSet(new TailleDetailComparator());
tailleDetails.add(...);
public class TailleDetailComparator implements Comparator {
#Override
public int compare(final TailleDetail o1, final TailleDetail o2) {
return o1.mesure.compareTo(o2.mesure);
}
}
Note that comparison is going to be done using the string comparison rules (alphabetically).
You're comparator does not compare by mesure, but by it's toString() method.
You should adapt your comparator to sort by mesure.
You List is already sorted, so why use a set ?
List<TailleDetail> tai = tailleDetailViewManager.search(param, true);
Collections.sort(tai, (td1, td2) -> td1.getMesure().compareTo(td2.getMesure())); // tai will be sorted by mesure
If you do want to use a Set, use a TreeSet:
SortedSet<TailleDetail> set = new TreeSet<>((td1, td2) -> td1.getMesure().compareTo(td2.getMesure())); // also sorted by mesure
set.addAll(list)
Also see When should a class be Comparable and/or Comparator?
I want to iterate a list in a specify order using the Interface Iterator.
In this case, I want to iterate the list (listofproducts) in descending product.prize order.
public class Invoice {
private static int static_id;
private int id;
private String date;
private List<Product> listofproduct = new ArrayList<Product>();
private boolean open;
}
public class Product {
private static int count = 0;
private int code;
private String name;
private String description;
private double price;
}
I have a public method to get the price.
How can I solve this?
If the amount of data isn't too big you can do the following without thinking about the performace:
List<Product> sortList = new ArrayList<>(origList);
Collections.sort(sortList, new Comparator<Product>() {
#Override
public int compare(Product arg0, Product arg1) {
return (int)(arg1.getPrice() - arg0.getPrice());
}
});
This will create a copy of the original list which will be sorted by a comparator. After that, iterating over sortList will be sorted by price (descending)
You should use the Comparable<T> interface to implement your comparaison logic :
class Product implements Comparable<Product>{
private static int count = 0;
private int code;
private String name;
private String description;
private Double price;
#Override
public int compareTo(Product p) {
return price.compareTo(p.getPrice());
}
}
Now to sort the list you can simply use :
Collections.sort(listOfProduct);
There are two way to achieve that. With Comparator it is easy to use, you should create a new implementation of the interface Comparator and then implement the compare and equals methods. It is really powerful because you can create several comparator to sort in different fields.
You can also make you Product as Comparable and then implements the compareTo method.
You can see sample with camparator and comparable interfaces