Java comparator interface and initializing comparator - java

I have a question regarding the Comparator interface. Below my class implements the interface to sort strings by length instead of by the default sort which sorts by character value.
After overriding the default compare, I used Arrays.sort() to sort my string array. Even though I have overridden the default method, if I use Arrays.sort, it calls the default compare instead of my overridden method. Is this because I am explicitly calling the super class method?
The other question is initializing the interface itself. I know you cannot initialize an interface and instead initialize a class object (that implements said interface) to reference the methods available to the interface. In this case, when I initialize a comparator, Arrays.sort(strArr, new Ideone()); the sort works correctly. How does the method know that I am passing it a comparator? I only initialized a class object and didn't call the compare(ob1, ob2) method explicitly.
Even if I do Comparator x = new Ideone();, how is the class object reduced to a comparator object? Any explanation on this would be helpful.
import java.util.*;
import java.lang.*;
import java.io.*;
/* Name of the class has to be "Main" only if the class is public. */
class Ideone implements Comparator
{
static String ThirdGreatest(String[] strArr)
{
Arrays.sort(strArr);
//Arrays.sort(strArr, new Ideone());
for(String x: strArr)
{
System.out.println(x);
}
return strArr[strArr.length-3];
}
#Override
public int compare(Object s1, Object s2)
{
return (s1.toString().length() - s2.toString().length());
}
public static void main (String[] args) throws java.lang.Exception
{
String[] myarr = {"coder","byte","code", "asfasfasfasf"};
System.out.println(ThirdGreatest(myarr));
}
}

Even though I have overridden the default method, if I use Arrays.sort, it calls the default compare instead of my overridden method. Is this because I am explicitly calling the super class method?
No, it's because in the example you've given, you don't pass in the comparator - so the sort method has no way of know that you're trying to use a custom comparison.
In this case, when I initialize a comparator, Arrays.sort(strArr, new Ideone()); the sort works correctly. How does the method know that I am passing it a comparator?
Because you're calling the overload that accepts a comparator. There is only one overload with two parameters:
public static <T> void sort(T[] a, Comparator<? super T> c)
Now admittedly it would be better if you implemented Comparator<String> instead of the raw Comparator type, but it's still valid.
I only initialized a class object and didn't call the compare(ob1, ob2) method explicitly.
Indeed - you don't have to call compare. That's the job of the sort method. You pass the comparator in so that it can call compare.

I think the issue here is that while you've created a comparator for use in Arrays.sort, you haven't actually told Arrays.sort to use it. By default, if you just say
Arrays.sort(strArr);
then the strings will be sorted using the default comparator. To tell the sorting algorithm to use your custom comparator, you need to pass it as a second argument to the method:
Arrays.sort(strArr, new Ideone());
Try seeing if that fixes things. As a fun test, put a println statement in your comparison function to see if you can watch it getting called.
That said, it's pretty unusual to implement the comparator this way. A more common route would be to do something like this:
Arrays.sort(strArr, new Comparator<String>() {
public int compare(Object s1, Object s2) {
return (s1.toString().length() - s2.toString().length());
}
});
Now, you don't need to implement compare on the Ideone class, which makes clearer that (1) it's not going to get called automatically, and (2) you only need it for this one sorting step.

Arrays.sort(strArr)
This sorts an array of string objects using the java comparator. You haven't told your sort function to use the compare function you created. There needs to be a reference to your Ideone class.
Comparator x = new Ideone();
Because Ideone() implements Comparator, it must implement all the methods within the Comparator interface. The variable x would use the compare method of the Ideone class as it is overridden. However, if you try to use a method from the Ideone class that isn't in the Comparator class then you'll throw an exception.

Related

Sorting list of objects from another Class with lambda in Java

In my taks I need to sort names with lambda. Class lambdaComparator extends class Car.
I make class LambdaComparator like this:
public class LambdaComparator extends Car
public LambdaComparator(String name) {super(name);}
In this class I need to sort objects of type Car.
In main class I have list of objects and with function I need to sort it.
In class LambdaComparator I have this:
Collections.sort(list, new Comparator<Car>() {
public int compare(Car x, Car y) {
return x.getName().compareTo(y.getName()));
}
});
How should I call function in main to get this sorted, should I make function of type void in class to somehow call it.
Edit: lambda expression
class LambdaSort<T extends Car>
private List<T> listOfCars;
public LambdaComparator(){
this.listOfCars = new ArrayList<T>();
}
public void sortCars(T cars)
listOfCars.sort((Car o1, Car o2)->o1.getName().compareTo(o2.getName());
In main function I add objects of type car to that list.
A lambda comparator would be something like this.
Comparator<Car> comp = (c1, c2)-> c1.getName().compareTo(c2.getName());
In the above example, the Comparator is comparing on a specific field of the Car class, name. Since name probably returns a string, one can use compareTo since the String class implements the Comparable (not Comparator) interface for comparing Strings.
But the lambda could be specified much more easily using one of the methods in the Comparator interface. The comparing method may take a lambda or a method reference (shown below).
Comparator<Car> comp = Comparator.comparing(Car::getName);
Then when it is passed to the sort method, the sort method will apply it to the objects under sort as comp.compare(obj1, obj2) (although it may not be called comp in the method that uses it)
For more information, check out The Java Tutorials
Take a look at what lambda is here.
If you have a list in your main class, it's as simple as just sorting it. You probably don't even need the class LambdaComparator.
List<Car> list = new ArrayList<>();
Collections.sort(list, (a, b) -> a.getName().compareTo(b.getName()));

Comparator initialisation inside constructor of another class

I came across the following code. Here, how are we initializing a comparator in the constructor of priority queue?
I know comparator is an interface in Java and should be implemented in another class.
Ideally, we should implement comparator in a class and use its initialization in the PriorityQueue constructor.
But the following code works. How?
PriorityQueue<ArrayList<Integer>> heap = new PriorityQueue<ArrayList<Integer>>(10, new Comparator<ArrayList<Integer>>(){
public int compare(ArrayList<Integer> list1, ArrayList<Integer> list2) {
return list1.get(2) - list2.get(2);
}
well in reference code snap, priority queue object has been created using constructor which takes two object as arguments viz type int and of type Comparator.
In reference code snap, Comparator type object has been created as anonymous inner class and passed to PriorityQueue constructor as second object.
for better understanding you can break that statement in below two statement.
1st will create comparator object, 2nd will pass that object to blocking queue.
Comparator> comparator = new Comparator>(){
public int compare(ArrayList list1, ArrayList list2) {
return list1.get(2) - list2.get(2);
}
};
PriorityQueue> heap = new PriorityQueue>(10, comparator );

Type Inference in Method Reference

I recently put my hands on Java 8 and tried using Method References.
I was trying different kinds of Method references and got stuck in the type "Reference to an Instance Method of an Arbitrary Object of a Particular Type".
String[] arr = {"First", "Second", "Third", "Fourth"};
Arrays.sort(arr, String::compareToIgnoreCase);
This works perfectly well. But when I try to refer a method of a user defined class through its type :
Demo2[] arr = {a, b};
Arrays.sort(arr, Demo2::compare);
This displays compile-time error as "Non-static method cannot be referenced from a static context".
Here's the Demo2 class :
public class Demo2 implements Comparator<Demo2> {
Integer i;
Demo2(Integer i1){
i = i1;
}
public Integer getI() {
return i;
}
#Override
public int compare(Demo2 o1, Demo2 o2) {
return o1.getI().compareTo(o2.getI());
}
}
As greg-449 pointed to you, your code has a bug.
By making a method reference like YourObjet::yourMethod you make a static reference to the method of the instance. So the method will be called for each object and thus the signature needs to be different than the earlier
A code that will compile will be of the following form :
Demo2[] demos = {a, b};
Arrays.sort(demos, Demo2::compareTo);
public class Demo2 {
Integer i;
Demo2(Integer i1){
i = i1;
}
public Integer getI() {
return i;
}
public int compareTo(Demo2 other) {
return this.getI().compareTo(other.getI());
}
}
But as RealSkeptic pointed out, this is not the correct way to implement and objet comparison. You should give the Arrays.sort method a comparator instead :
Arrays.sort(demos, (obj1, obj2) -> obj1.getI().compareTo(obj2.getI()));
The Comparator interface required for Arrays.sort(T[],Comparator<T>) has a method that accepts two object references of the same type T, and returns an integer.
There is a bit of "magic" in method references. What Java does is wrap the method in such a way that it will fit the interface requirement.
The interface, of course, doesn't require a static method. But the wrapping can create a method that calls a static method, as in the Tutorial's first example:
public static int compareByAge(Person a, Person b) {
return a.birthday.compareTo(b.birthday);
}
It wraps it in such a way that you get something similar to
new Comparator<Person>() {
#Override
public int compare(Person a, Person b) {
return Person.compareByAge(a,b);
}
}
Which satisfies the interface.
But in the example in the section "Reference to an Instance Method of an Arbitrary Object of a Particular Type", it wraps it differently. It needs a method that receives two strings, but it has a method that only receives one. This is how String::compareToIgnoreCase is defined:
public int compareToIgnoreCase(String str)
But in this case, it's an instance method. What Java does now is, because this method belongs to an object of type String, and accepts an object of type String it is easy to build a "wrap" around it that makes it into a method that accepts two objects, much like the lambda expression:
(String a, String b) -> {
return a.compareToIgnoreCase( b );
}
Or, if we imagine a formal wrapping as a Comparator:
new Comparator<String>() {
#Override
public int compare(String a, String b) {
return a.compareToIgnoreCase(b);
}
}
So, the fact that it's an instance method that belongs to type T, accepts type T and returns int allows Java to wrap it appropriately so it fits the Comparator interface.
But int compare(Demo2 o1, Demo2 o2) doesn't fit that pattern. It accepts two parameters. If a method accepts two parameters, it must be a static method to fit the wrapping rules - there is no way to pass the "this" object into the Comparator interface. So it tries to wrap it as a static method, and fails, as it is not a static method.
Bottom line: you got the error, because for this particular type of method reference, you need an instance method with only one parameter of the same type as the class.
As #Holger mentions in a comment, if you have a new class you are building, you shouldn't put a comparison method in it specifically for this sort of sorting task. If the class has a natural ordering, make it Comparable and use Arrays.sort(Object[]). If it doesn't, and you need to sort it sometimes based on any of its attributes, use a lambda expression or Comparator.comparing(Demo2::getI) which makes better use of an existing getter for the specific purpose of comparison.
As a convention, Comparator<T> is implemented with a lambda expression and it looks very odd to implement it in a class.
Demo2[] array = new Demo2[2];
array[0] = new Demo2(12);
array[1] = new Demo2(32);
Comparator<Demo2> demo2Comparator = (e1,e2)->e1.getI().compareTo(e2.getI());
Arrays.sort(array, demo2Comparator);

Java ArrayList Collections sort

I've seen a few questions about the sort for collections having errors in Java. The error I am showing is this:
The method sort(List<T>) in the type Collections is not applicable for the arguments (ArrayList<Time>)
I have imported java.util.Collections and ArrayList. I also imported the class I am calling from. Here is my code:
In the class being called from:
private ArrayList<Time> times;
...
public ArrayList<Time> getTimes() {
return this.times;
}
In the class I am calling the array list to:
public class TimeTUI {
private Scanner scan;
private TimeManager timeManager;
...
private ArrayList<Time> getSortedTimes() {
ArrayList<Time> sortedTimes = this.timeManager.getTimes();
Collections.sort(sortedTimes);
return sortedTimes;
}
The error is appearing on the line showing:
Collections.sort(sortedTimes);
The class Time has to be a Comparable.
Collections.sort(List) expects that the class T implements Comparable interface. If you have used many of the inbuilt classes, you wouldn't find problem, but for the custom classes sort doesn't know how to sort them. So, by implementing Comparable interface, you give definition to a method compareTo.
public class Time implements Comparable {
public int compareTo(Object o) {
// provide your logic of how to sort Time objects.
}
}
Your class type in the List or ArrayList must implement the Interface comparable and override properly the compareTo(...) method,
Is you break this contract and dont implement the interface. the Class Collections has not a valid criteria/rule to compare/sort your list, and therefore your compiler will complain...
I don't think that it is the ArrayList that is the issue here. For example:
ArrayList<String> names = new ArrayList<>();
...
Collections.sort(names);
works just fine.
The content of the list must be comparable so that the sort can work. In this case the Time class and any sub-type must implement Comparable.

Why would a method that accepts an interface reject an implementation of that interface?

I am trying to figure out an answer to a problem in a textbook, but am having trouble. The question is asking me to have an input array checked to see if it contains an object that fits some feature. Every feature of this seems to be working fine except for when I attempt to implement the method using an input that is one of the classes implementing the interface.
Example:
main(){
boolean r1 = has(input, checkFor2);
}
public static <T> boolean has(T[] input, Check<T> c){
// does its check algorithm and returns a boolean
}
static public interface Check<T>{
boolean isContained(T item);
}
static public class checkFor2 implements Check<Integer>{
public boolean isContained(Integer val){
// does a check algorithm
}
}
// Other check algorithms implementing Check<T> follow as well.
The error I am getting is when "has" is called in the main method. It says:
The method has(T[], main.Check<T>) in the type main is not applicable for the arguments (int[], main.checkFor2)
Its suggestion is to change the class in the method to be a specific class instead of the interface which defeats the purpose of writing it this way.
Have I made some kind of rookie mistake in coding this?
The problem is actually unrelated to your interface. It has to do with the fact you are giving a primitive int array int[] instead of an Integer[] array.
In the method
public static <T> boolean has(T[] input, Check<T> c){
the type T is inferred from what you give as parameter. In this case, you are giving int[] and Check<Integer>. However, an int[] cannot be boxed to an Integer[], so the type inference fails: T can't be inferred as an Integer.
The solution is therefore to change your primitive array into an Integer[] array, and send that as the first parameter to your method.

Categories

Resources