This question already has answers here:
How to use Comparator in Java to sort
(16 answers)
Closed 5 years ago.
I also have an ArrayList items. I have classes derived from Media. Given the code below, how would I sort the arraylist by duration? e.g.
Collections.sort(myMedia, ?);
Here is the class
import java.util.Comparator;
public abstract class Media implements Comparable<Media>{
private int duration;
private String title;
private String imageFileName;
private static String imageFileDirectory = "src/resources/";
public Media(String name, int seconds) {
this.title = name;
this.duration = seconds;
this.imageFileName = "";
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getDuration() {
return duration;
}
public void setDuration(int d) {
this.duration = d;
}
public String getImageFileName() {
return imageFileName;
}
public void setImageFileName(String imageFileName) {
this.imageFileName = imageFileName;
}
public static String getImageFileDirectory() {
return imageFileDirectory;
}
public static void setImageFileDirectory(String imageFileDirectory) {
Media.imageFileDirectory = imageFileDirectory;
}
#Override
public String toString() {
return this.getTitle()
+ ", Duration: " + this.getDuration() + "s, " +
"Cost: " + costInPence() + "p";
}
public abstract int costInPence();
#Override
public int compareTo(Media o) {
return this.getTitle().compareTo(o.getTitle());
}
public static class DurationComparator implements Comparator<Media>{
public int compare(Media m1, Media m2) {
return m2.getDuration() - m1.getDuration();
}
}
public static class CostComparator implements Comparator<Media>{
public int compare(Media m1, Media m2) {
return m2.costInPence() - m1.costInPence();
}
}
}
The Collections#sort method has two variants.
The first variant (documentation) only accepts a collection that is to be sorted. It will sort the elements of the collection by their natural order. Therefore the elements must implement the interface Comparable which yields a compareTo method. Your Media objects already implement this interface with a meaningful natural order, namely sorting by their titles:
public abstract class Media implements Comparable<Media> {
#Override
public int compareTo(Media o) {
return this.getTitle().compareTo(o.getTitle());
}
}
The other variant (documentation) accepts a collection and a Comparator object. It will then sort the elements based on the order defined by the Comparator. You can define Comparator on various ways, since Java 8 it became pretty compact. But first let us take a look at the Comparator you have already defined, it sorts by duration:
public static class DurationComparator implements Comparator<Media> {
public int compare(Media m1, Media m2) {
return m2.getDuration() - m1.getDuration();
}
}
So if you want to sort by titles you should use the first variant. If you want to sort by duration you need to create a new instance of DurationComparator and use the second variant, alternatively use the compact Java 8 statements. The same holds for your CostComparator:
// Sort by title
Collections.sort(myMedia);
// Sort by duration
Collections.sort(myMedia, new DurationComparator<>());
// Sort by duration with Java 8
Collections.sort(myMedia, Comparator.comparingInt(Media::getDuration));
// Sort by cost
Collections.sort(myMedia, new CostComparator<>());
// Sort by cost with Java 8
Collections.sort(myMedia, Comparator.comparingInt(Media::costInPence));
The Comparator#comparing (documentation) method creates a Comparator object that sorts the given elements based on the given keys. The method reference points to a method that yields the keys.
As the methods return int you may choose the method Comparator#comparing (documentation) instead, it is slightly faster since int doesn't need to be boxed into Integer then.
Note that since Java 8 Lists itself provide a sort method too (documentation). So you don't need to call Collections anymore:
myMedia.sort(Comparator.comparingInt(Media::getDuration));
Also note that Comparator now provides some useful methods (documentation), for example to first sort by one key and if keys are equal then sort by a second key:
myMedia.sort(Comparator.comparingInt(Media::getDuration)
.thenComparing(Media::costInPence));
Related
I am hard stuck on a problem I cannot find a good answer to. I've found
this one about custom comparators, but it is incomplete:
class YourClass {
static Comparator<YourClass> getAttribute1Comparator() {
return new Comparator<YourClass>() {
// compare using attribute 1
};
}
static Comparator<YourClass> getAttribute2Comparator() {
return new Comparator<YourClass>() {
// compare using attribute 2
};
}
}
That should work, but I don't know how the comparison part works. Here is my class:
package ZVCVolkel_Logic;
import java.util.Comparator;
public class Vliegtuig implements Comparator<Vliegtuig>{
private String naam;
private String type;
private String status;
private Hangaar hangaar;
public Vliegtuig(String naam, String type, String status, Hangaar hangaar){
this.naam = naam;
this.type = type;
this.status = status;
this.hangaar = hangaar;
}
}
Now I need a comparator for status and for Hangaar.getName(). Can someone help?
It is not the one, he has only 1 comparator. I can get that working too but not with 2 different ones in 1 class.
The comparator interface has a method compare return an int value to determine the relation ship between two objects.
It will return:
a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
static Comparator<Vliegtuig> hangaarNameComparator() {
return new Comparator<Vliegtuig>(){
public int compare(Vliegtuig one, Vliegtuig two) {
return one.getHangaar().getName().compareTo(two.getHangaar().getName());
}
}
}
Here you probably want to take care of NullPointerException if getHangaar() or hangaar.getName() return null.
In java 8 you could do this:
Comparator<Vliegtuig> hangaarNameComparator = Comparator.comparing(Vliegtuig::getHagaar,
Comparator.comparing(Hagaar::getName));
In the comparator implementation you need to compare 2 objects. You can refer to most of JDK classes for example, for instance java.lang.Integer.
In your case solution will be to use embedded compactors from objects like this:
Comparator<Vliegtuig> nameComparator = new Comparator<>() {
#Override
public int compare(Vliegtuig o1, Vliegtuig o2) {
return o1.getName().compareTo(o2.getName());
}
}
And you don't need to extend Comparator by the Vliegtuig.
I came across a problem while practising Java.
I have a class Book which stores the following information:
id (int), author and title
and I have another class BookShelf which store a collection of books using a Vector/ArrayList and have the following methods:
addBook: takes in a book object as input, adds the object into the bookshelf, method returns nothing.
returnListOfBooks: takes in no argument and returns a Vector/ArrayList of all books sorting by title in alphabetical order.
returnListOfBooksByAuthor: takes in author as input and returns a Vector/ArrayList of books by that author
My question is, how do I create the method returnListOfBooks and sort them by title in alphabetical order? It would also be great if you could check my methods and correct me if what i'm doing is wrong.
I have to implement the sorting (bubble sort, insertion sort, and such)
I'm new to java so i'm not quite good at it. Any help would be greatly appreciated!
In Java you typically sort a List with Collections.sort and if needed a custom comparator. Java 8 allows a concise syntax for that.
// easy to change for descending order
Collections.sort(listOfBooks, (a, b) -> a.getTitle().compareTo(b.getTitle()));
or even better
Collections.sort(listOfBooks, Comparator.comparing(Book::getTitle));
Mind you that both will sort listOfBooks in place (instead of returning a new sorted list). You probably don't want to do that every time you call returnListOfBooks. If for e.g. inside returnListOfBooksByAuthor you do
Collections.sort(listOfBooks, Comparator.comparing(Book::getAuthor));
The same listOfBooks will be sorted in place according to author this time
You would need to make your Book class implement the Comparable interface, and then, compare the names of the books.
Now, the Java Framework already provides a sorting mechanism to sort lists through Collections.sort. If you implement the interface above, you should be able to simply call Collections.sort(listOfBooks) and have your collection sorted.
Alternatively, if you need to implement your own sorting mechanism, you can simply do so and then compare the books by using the .compareTo method which the Comparable interface gives you.
While you do need to make your Book objects Comparable to one another, another error you'd likely see with your current code is a concurrent modification exception because you are sorting a list while iterating over it.
So your method should look like this since the requirements are to return a sorted list, not print it.
Note, if you want to keep the initial ordering of listOfBooks before and after calling this method, you'll need to copy the whole list out to another list which you sort and return instead.
public ArrayList<Book> returnListOfBooks()
{
Collections.sort(listOfBooks);
// could print them, also, if you wish
return listOfBooks;
}
Book Class
import java.util.Comparator;
import java.util.Objects;
public class Book implements Comparable {
private String bookName;
private String autherName;
private String isbn;
public Book(String bookName, String autherName, String isbn) {
this.bookName = bookName;
this.autherName = autherName;
this.isbn = isbn;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getAutherName() {
return autherName;
}
public void setAutherName(String autherName) {
this.autherName = autherName;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
#Override
public int compareTo(Book book) {
return this.bookName.compareTo(book.bookName);
}
#Override
public int hashCode() {
int hash = 7;
hash = 53 * hash + Objects.hashCode(this.bookName);
return hash;
}
#Override
public String toString(){
return bookName +" "+autherName + " "+isbn;
}}
Book Shelf
public class BookShelf {
private ArrayList<Book> bookList = new ArrayList<Book>();
public void addBook(Book book) {
bookList.add(book);
}
public ArrayList<Book> bookList(String sortBy) {
ArrayList<Book> list = new ArrayList<Book>(bookList);
SortingComparator comparator = null;
if (sortBy.equals("auther")) {
comparator = new AutherComparator();
} else if (sortBy.equals("bookname")) {
comparator = new BookNameComparator();
}
Collections.sort(list, comparator);
return list;
}}
Comparators
interface SortingComparator extends Comparator<Book> {}
class AutherComparator implements SortingComparator {
#Override
public int compare(Book b1, Book b2) {
return (b1.getAutherName().toUpperCase()).compareTo((b2.getAutherName().toUpperCase()));
}
}
class BookNameComparator implements Comparator<Book>,SortingComparator {
#Override
public int compare(Book b1, Book b2) {
return (b1.getBookName().toUpperCase()).compareTo((b2.getBookName().toUpperCase()));
}}
Main CLass
public static void main(String s[]) {
BookShelf bookShelf = new BookShelf();
bookShelf.addBook(new Book("Algorithm", "Toman", "12-34"));
bookShelf.addBook(new Book("DataBase", "Sethi", "12-35"));
bookShelf.addBook(new Book("DataStruture", "Ulman", "12-36"));
bookShelf.addBook(new Book("Network", "Tanenbom", "12-37"));
ArrayList<Book> list = bookShelf.bookList("auther");
System.out.println("----Sort by Auther-----------");
for (Book b : list) {
System.out.println(b);
}
System.out.println("----Sort by Book Name-----------");
list = bookShelf.bookList("bookname");
for (Book b : list) {
System.out.println(b);
}
}
I have a year object. For now lets say only two years and its getters and setters
private String mYearOne;
private String mYearTwo;
public String getmYearOne() {
return mYearOne; }
public void setmYearOne(String mYearOne) {
this.mYearOne = mYearOne; }
public String getmYearTwo() {
return mYearTwo; }
public void setmYearTwo(String mYearTwo) {
this.mYearTwo = mYearTwo; }
Then each year has three insurance plans. And its getters and setters.
private String healthPlan;
private String carPlan;
private String housePlan;
private String healthPlanTwo;
private String carPlanTwo;
private String housePlanTwo;
public String getHealthPlan() {
return healthPlan; }
public void setHealthPlan(String healthPlan) {
this.healthPlan = healthPlan; }
public String getCarPlan() {
return carPlan; }
public void setCarPlan(String carPlan) {
this.carPlan = carPlan; }
public String getHousePlan() {
return housePlan; }
public void setHousePlan(String housePlan) {
this.housePlan = housePlan; }
public String getHealthPlan() { //For the second year
return healthPlan; }
public void setHealthPlan(String healthPlan) {
this.healthPlan = healthPlan; }
public String getCarPlan() {
return carPlan; }
public void setCarPlan(String carPlan) {
this.carPlan = carPlan; }
public String getHousePlan() {
return housePlan; }
public void setHousePlan(String housePlan) {
this.housePlan = housePlan; }
public String getHealthPlanTwo() {
return healthPlanTwo; }
public void setHealthPlanTwo(String healthPlanTwo) {
this.healthPlanTwo = healthPlanTwo; }
public String getCarPlanTwo() {
return carPlanTwo; }
public void setCarPlanTwo(String carPlanTwo) {
this.carPlanTwo = carPlanTwo; }
public String getHousePlanTwo() {
return housePlanTwo; }
public void setHousePlanTwo(String housePlanTwo) {
this.housePlanTwo = housePlanTwo; }
You will notice the code is bulky. I need to define them in a <list> of year. So that if 10 years are considered, I would have 10 multiplied
by 3 = 30 plans and its getters and setters respectively.
How could this be done?
I think your best bet will be to maintain a count of number of years and arraylists for the insurance plans. This way, you can get the arraylist once and get the insurance plan details for whatever year you actually want. This will be characterized by a single insurance plan arraylist and a single arraylist for years.
private ArrayList mYear;
private ArrayList healthPlan;
private ArrayList carPlan;
private ArrayList housePlan;
public String getHousePlanForYear(String year){
return housePlan.get(mYear.indexOf(year));
}
public void setHousePlanForYear(String housePlan, String year){
this.housePlan.set(mYear.indexOf(year), housePlan);
}
Similarly for the other plans. Of course, all this is assuming that the year is always present and other boundary conditions. Just add your boundary checks in these getters and setters and you will be good to go. :)
I see a design/domain modelling problem here. A person can ideally have multiple "plans" and "riders" attached to each plan. This should clearly be abstracted away properly by creating a "PlanCollection" class or simply maintaining a list of "plans" which all extend/implement a common "Plan" class/interface.
Each plan can have a "plan" duration and a start date. Also, logically, you don't attach plans to "year" but the timeline information is encapsulated in the Plan itself (like start and duration as mentioned above).
Take a look at enums and maps. The enum would specify car, house etc.
You could create a map that takes an enum as key and a List of years as the key. Don't be tempted to create YearThree etc.
On a note of style: if you intend to use m to prefix fields, take the m out of the setter. E.g. setYearOne not setmYearOne.
Choose your types wisely, don't use a String if an int is better.
How can I sort a vector of my custom object and choose which property to sort by?
I did see this question & answer but I'm not too sure what its sorting it based on. Code example would be prefered to "methodology".
Sort a Vector of custom objects
public class ItemLocation {
String icon;
String title;
String message;
String subtext;
String deviceId;
double latCoords;
double lngCoords;
int expiary;
int id;
double proximity;
String locSeen;
}
Below is a example that will allow you to sort by a specified field of ItemLocation:
public void sort(final String field, List<ItemLocation> itemLocationList) {
Collections.sort(itemLocationList, new Comparator<ItemLocation>() {
#Override
public int compare(ItemLocation o1, ItemLocation o2) {
if(field.equals("icon")) {
return o1.icon.compareTo(o2.icon);
} if(field.equals("title")) {
return o1.title.compareTo(o2.title);
} else if(field.equals("message")) {
return o1.message.compareTo(o2.message);
}
.
. fill in the rest of the fields...
.
else if(field.equals("locSeen")) {
return o1.locSeen.compareTo(o2.locSeen);
}
}
});
}
See the JavaDocs for java.util.Comparable and java.util.Comparator.
A class that implements Comparable can be compared against other instances of that class. This is useful to implement a natural search order. To allow ordering other than the class's natural order you would need to implement a Comparator. A Comparator is a separate object that is capable of comparing two other objects using whatever criteria it wants.
In your case you'd probably want to implement a Comparator for each of the different properties that you want to order by, or one that can be configured.
Comparable and Comparator both use the same idea to determine ordering: A method returns less than 0, 0, or greater than 0 to inform the caller which of the 2 objects is ordered first. In the case of Comparable the first object is this.
This one works:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* ComparableDemo
* #author Michael
* #since 2/24/11
*/
public class ComparableDemo
{
public static void main(String[] args)
{
List<ItemLocation> itemLocations = new ArrayList<ItemLocation>();
for (String arg : args)
{
itemLocations.add(new ItemLocation(arg));
}
System.out.println("before sort: " + itemLocations);
Comparator<ItemLocation> comparator = new ItemLocationComparator();
Collections.sort(itemLocations, comparator);
System.out.println("after sort: " + itemLocations);
}
}
class ItemLocation
{
String icon;
String title;
String message;
String subtext;
String deviceId;
double latCoords;
double lngCoords;
int expiary;
int id;
double proximity;
String locSeen;
ItemLocation(String message)
{
this("", "", message, "", "", 0.0, 0.0, 0, 0, 0.0, "");
}
ItemLocation(String icon, String title, String message, String subtext, String deviceId, double latCoords, double lngCoords, int expiary, int id, double proximity, String locSeen)
{
this.icon = icon;
this.title = title;
this.message = message;
this.subtext = subtext;
this.deviceId = deviceId;
this.latCoords = latCoords;
this.lngCoords = lngCoords;
this.expiary = expiary;
this.id = id;
this.proximity = proximity;
this.locSeen = locSeen;
}
#Override
public String toString()
{
final StringBuilder sb = new StringBuilder();
sb.append("ItemLocation");
sb.append("{message='").append(message).append('\'');
sb.append('}');
return sb.toString();
}
}
class ItemLocationComparator implements Comparator<ItemLocation>
{
public int compare(ItemLocation o1, ItemLocation o2)
{
return o1.message.compareTo(o2.message);
}
}
Here's the output:
C:\JDKs\jdk1.6.0_21\bin\java -Didea.launcher.port=7534 "-Didea.launcher.bin.path=C:\Program Files\JetBrains\IntelliJ IDEA 10.0.2\bin" -Dfile.encoding=windows-1252 com.intellij.rt.execution.application.AppMain ComparableDemo zeb meme apple
before sort: [ItemLocation{message='zeb'}, ItemLocation{message='meme'}, ItemLocation{message='apple'}]
after sort: [ItemLocation{message='apple'}, ItemLocation{message='meme'}, ItemLocation{message='zeb'}]
Process finished with exit code 0
Let's say we have a class with an int and a string. I can define how one object of that class may be compared against other.
I could choose any criteria. For instance, I may decide to sort based on the int. If I happen to have two int's with the same value, I may decide the string as an additional criteria, something like this:
// this class *knows* how to "compare" against him self
class CustomObject implements Comparable<CustomObject> {
String aString;
int aInt;
...
public int compareTo(CustomObject two ) {
int diff = this.aInt - two.aInt;//<-- compare ints
if( diff != 0 ) { // they have different int
return diff;
}
return this.aString.compareTo( two.aString );//<-- compare strings...
}
...
}
Here's a complete running demo ...
import java.util.*;
class SortDemo {
public static void main( String ... args ) {
// create a bunch and sort them
List<CustomObject> list = Arrays.asList(
new CustomObject(3, "Blah"),
new CustomObject(30, "Bar"),
new CustomObject(1, "Zzz"),
new CustomObject(1, "Aaa")
);
System.out.println( "before: "+ list );
Collections.sort( list );
System.out.println( "after : "+ list );
}
}
// this class *knows* how to "compare" against him self
class CustomObject implements Comparable<CustomObject> {
String aString;
int aInt;
CustomObject( int i, String s ) {
aInt = i;
aString = s;
}
// comparable interface lets you
// specify "HOW" to compare two
// custom objects
public int compareTo(CustomObject two ) {
// I migth compare them using the int first
// and if they're the same, use the string...
int diff = this.aInt - two.aInt;
if( diff != 0 ) { // they have different int
return diff;
}
// else let the strings compare them selves
return this.aString.compareTo( two.aString );
}
public String toString(){
return "CustomObject[aInt="+aInt+", aString="+aString+"]";
}
}
Here's the output:
before: [CustomObject[aInt=3, aString=Blah], CustomObject[aInt=30, aString=Bar], CustomObject[aInt=1, aString=Zzz], CustomObject[aInt=1, aString=Aaa]]
after : [CustomObject[aInt=1, aString=Aaa], CustomObject[aInt=1, aString=Zzz], CustomObject[aInt=3, aString=Blah], CustomObject[aInt=30, aString=Bar]]
I hope that's clear enough
You can also pass a custom comparator. Let me know if you need a sample of that.
Lets say you have an Arraylist of HockeyPlayer objects.
How could you sort that if they all have a variable int goalsScored. How could you sort them by goalsScored?
You can use Collections.sort with a custom Comparator<HockeyPlayer>.
class HockeyPlayer {
public final int goalsScored;
// ...
};
List<HockeyPlayer> players = // ...
Collections.sort(players, new Comparator<HockeyPlayer>() {
#Override public int compare(HockeyPlayer p1, HockeyPlayer p2) {
return p1.goalsScored - p2.goalsScored; // Ascending
}
});
The comparision part can also be written this way :
players.sort(Comparator.comparingInt(HockeyPLayer::goalsScored));
Alternatively, you can make HockeyPlayer implementsComparable<HockeyPlayer>. This defines the natural ordering for all HockeyPlayer objects. Using a Comparator is more flexible in that different implementations can order by name, age, etc.
See also
Java: What is the difference between implementing Comparable and Comparator?
For completeness, I should caution that the return o1.f - o2.f comparison-by-subtraction shortcut must be used with extreme caution due to possible overflows (read: Effective Java 2nd Edition: Item 12: Consider implementing Comparable). Presumably hockey isn't a sport where a player can score goals in the amount that would cause problems =)
See also
Java Integer: what is faster comparison or subtraction?
As #user6158055 suggets, it's one liner with Java 8, as follows:
Collections.sort(
hockeyPlayerList,
(player1, player2) -> player1.getGoalsScored()
- player2.getGoalsScored());
Complete example to depict the same:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<HockeyPlayer> hockeyPlayerList = new ArrayList<>();
hockeyPlayerList.add(new HockeyPlayer("A", 3));
hockeyPlayerList.add(new HockeyPlayer("D", 10));
hockeyPlayerList.add(new HockeyPlayer("B", 2));
System.out.println("Before Sort based on goalsScored\n");
hockeyPlayerList.forEach(System.out::println);
System.out.println("\nAfter Sort based on goalsScored\n");
Collections.sort(
hockeyPlayerList,
(player1, player2) -> player1.getGoalsScored()
- player2.getGoalsScored());
hockeyPlayerList.forEach(System.out::println);
}
static class HockeyPlayer {
private String name;
private int goalsScored;
public HockeyPlayer(final String name, final int goalsScored) {
this.name = name;
this.goalsScored = goalsScored;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getGoalsScored() {
return goalsScored;
}
public void setGoalsScored(int goalsScored) {
this.goalsScored = goalsScored;
}
#Override
public String toString() {
return "HockeyPlayer [name=" + name + ", goalsScored="
+ goalsScored + "]";
}
}
}
Output:
Before Sort based on goalsScored
HockeyPlayer [name=A, goalsScored=3]
HockeyPlayer [name=D, goalsScored=10]
HockeyPlayer [name=B, goalsScored=2]
After Sort based on goalsScored
HockeyPlayer [name=B, goalsScored=2]
HockeyPlayer [name=A, goalsScored=3]
HockeyPlayer [name=D, goalsScored=10]
Just one line with Java 8 :
Collections.sort(players, (p1, p2) -> p1.getGoalsScored() - p2.getGoalsScored());
Write a custom Comparator to do the job.
Use a generic Comparator like the Bean Comparator.
With Java 8 this is simple
Collections.sort(playList, Comparator.comparingInt(HockeyPLayer::goalsScored))
Java has a set of sort() methods for this sort of thing. See Collections.sort (and Comparable) for details.