Comparator for Java classes? - java

I want to compare two Java classes.
class ClassComparator implements Comparator<Class> {
#Override
public int compare(Class arg0, Class arg1) {
return ....;
}
}
I could just compare class names, but I want parent classes be "smaller" than classes derived from them. And I want this less-than relation to be transitive and work for any two classes. (To be honest, in my real problem one class will always be a superclass of another, but I want some general-case code because in theory this can change.)
Maybe this is already done and someone can share a code snippet?
What comes to my mind is: if none of the classes is derived from another, find their two superclasses derived from the common ancestor, and compare their names. (Hmmm, it can even support interfaces, if any object class is greater than any interface.)

You can also compare classes that are not in one hierarchy by how deep are they and far away from Object class.
class ClassComparator implements Comparator<Class> {
#Override
public int compare(Class arg0, Class arg1) {
boolean arg0assignable = arg0.isAssignableFrom(arg1);
boolean arg1assignable = arg1.isAssignableFrom(arg0);
if (arg0assignable == arg1assignable && arg0assignable) {
return 0;
} else if (arg0assignable) {
return -1;
} else if (arg1assignable){
return 1;
} else {
return compareByDistanceToObject(arg0, arg1);
}
}
private int compareByDistanceToObject(Class arg0, Class arg1) {
int distanceToObject0 = getDistance(arg0);
int distanceToObject1 = getDistance(arg1);
if (distanceToObject0 == distanceToObject1) {
return 0;
} else if (distanceToObject0 < distanceToObject1) {
return -1;
} else {
return 1;
}
}
private int getDistance(Class clazz) {
if (clazz == Object.class) {
return 0;
}
return 1 + getDistance(clazz.getSuperclass());
}

I think you should be able to just do:
new Comparator<Class>() {
#Override
public int compare(Class o1, Class o2) {
if (o1 == o2)
return 0;
if (o1.isAssignableFrom(o2))
return -1;
if (o2.isAssignableFrom(o1))
return 1;
return o1.getSimpleName().compareTo(o2.getSimpleName());
}
}

Your constraints do not yield an ordered set.
class C {}
class B {}
class A extends C {}
Then you have:
A < B
B < C
C < A
EDIT: Since Comparator imposes a total ordering, there is no solution to your question.
EDIT 2: However, if there is no solution within your constraints, you can change them. If your objective is to define a total order among classes, so that a superclass is always less-than a subclass (ignoring interfaces) (i.e. we don't require to compare class names anymore), you can:
make a list of the (linear) hierarchy for each class
compare the resulting lists
I just realized that comparing class names was not a requirement in your question.
Let's take an example:
class C {}
class B {}
class A extends C {}
class D extends A {}
List the hierarchy of each class:
C → (C)
B → (B)
A → (C, A)
D → (C, A, D)
Then, you get a total order:
B → (B)
C → (C)
A → (C, A)
D → (C, A, D)

So, we want to define a total order on classes such that any parent class/interface is "smaller" than any derived class/interface.
The solution is:
Any interface class is smaller than any object class.
to compare two interfaces, we compare the number of their superinterfaces. If they are equal, we compare their names.
to compare two object classes, we compare the number their of superclasses. If they are equal, we compare their names.
Why this is correct. A derived class always have more ancestors than any of its its superclasses. So if we compare the number of ancestors, we guarantee that superclasses go before their descendants. And as to the ordering within the group of classes that have N parents, any ordering will do, alphabetic ordering is ok.
class ClassComparator implements Comparator<Class<?>> {
#Override
public int compare(Class<?> first, Class<?> second) {
int areInterfaces = first.isInterface() ? 1 : 0;
areInterfaces += second.isInterface() ? 2 : 0;
switch (areInterfaces) {
case 1 + 2:
return compareNumbersThenNames(getInterfaceCount(first), getInterfaceCount(second), first, second);
case 0 + 2:
return -1;
case 1 + 0:
return 1;
case 0 + 0:
default:
return compareNumbersThenNames(getAncestorCount(first), getAncestorCount(second), first, second);
}
}
private int compareNumbersThenNames(int f, int s, Class<?> first, Class<?> second) {
if (f-s != 0) {
return f-s;
} else {
return compareByName(first, second);
}
}
private int getAncestorCount(Class<?> objectClass) {
int res=0;
for (Class<?> i = objectClass; i != null ; i = i.getSuperclass()) {
res++;
}
return res;
}
private int getInterfaceCount(Class<?> interfaceClass) {
Set<Class<?>> superInterfaces = new HashSet<>();
addSuperinterfaces(superInterfaces, interfaceClass);
return superInterfaces.size();
}
private void addSuperinterfaces(Set<Class<?>>set, Class<?>interfaceClass) {
for (Class<?> s : interfaceClass.getInterfaces()) {
if (!set.contains(s)) {
set.add(s);
addSuperinterfaces(set, s);
}
}
}
private int compareByName(Class<?> a, Class<?> b) {
int res = a.getSimpleName().compareTo(b.getSimpleName());
if (res != 0) { return res; }
res = a.getName().compareTo(b.getName());
// we do not support different class loaders
return res;
}
}

A bit late to post (Orest Savchak posted a very similar-but-not-quite-the-same answer a bit before), but you can find the closest common ancestor and then calculate each class' distance to this object. All classes (except for classes for primitives) have a common ancestor (at the very worst, the Object class), and we can use this information in order to ensure there is some ordering: At the worst, you can simply compare the two classes on their "depth" in their respective inheritance hierarchies rooted from Object.
Additionally, if while navigating the class hierarchy one compared class is found, the other class is then proven to be a child and thus is "greater" than the other one:
public final class InheritanceDepthComparator<T> implements Comparator<Class<? extends T>> {
private static class B {};
private static class C {};
private static class A extends C {};
private static <SUP, SUB extends SUP> int calculateInheritanceDistance(final Class<? extends SUB> o1,
final Class<? extends SUP> o2) {
int result = 0;
Class<?> o1Parent = o1;
do {
o1Parent = o1Parent.getSuperclass();
result++;
} while (!Objects.equals(o1Parent, o2));
return result;
}
#Override
public int compare(final Class<? extends T> o1, final Class<? extends T> o2) {
int result = 0;
if (!o1.equals(o2)) {
// Walk up the inheritance hierarchy
int o1Depth = -1;
Class<?> o1Parent = o1;
do {
o1Parent = o1Parent.getSuperclass();
o1Depth++;
if (o1Parent.equals(o2)) {
// The first compared object is a child of the second and
// therefore "greater" than it
result = 1;
break;
} else if (o1Parent.isAssignableFrom(o2)) {
// Found the common ancestor class; At least by reaching
// "Object", this should always be executed anyway
// TODO: Check performance and see if manually going up the tree for o2 within this while loop itself is not faster
final int o2Depth = calculateInheritanceDistance(o2, o1Parent);
result = Integer.compare(o1Depth, o2Depth);
break;
}
} while (o1Parent != null);
}
return result;
}
public static void main(final String[] args) {
final InheritanceDepthComparator<Object> cComp = new InheritanceDepthComparator<>();
System.out.println("Parent compared to child: " + cComp.compare(C.class, A.class));
System.out.println("Child compared to parent: " + cComp.compare(A.class, C.class));
System.out.println("C (child) Compared to Object (parent): " + cComp.compare(A.class, Object.class));
System.out.println("Sibling classes:" + cComp.compare(A.class, B.class));
}
}
You can then use this Comparator in a chain in order to handle sibling classes:
final Comparator<Class<?>> cmp = new InheritanceDepthComparator<>().thenComparing(Class::getName);

Related

Java Comparing three generic elements

I want to compare a collection (ArrayList) of elements if they are the same type. In the beginning I do not know what type the elements are (generic types), so I decided to use Object type. But I still cannot compare them. The problem is in the function triplesort().The warning is:
Operator '>' cannot be applied to 'java.lang.Object', 'java.lang.Object'. If you have any possible solutions to that problem and you let me know, I would be grateful. <3
Triple.java
import java.util.ArrayList;
public class Triple<T, S, U> {
private T t;
private S s;
private U u;
private ArrayList<Object> array = new ArrayList<Object>();
Triple(T t, S s, U u) {
setT(t);
setS(s);
setU(u);
array.add(this.t);
array.add(this.s);
array.add(this.u);
}
public void setT(T t) {
this.t = t;
}
public void setS(S s) {
this.s = s;
}
public void setU(U u) {
this.u = u;
}
public T getFirst() {
return t;
}
public S getSecond() {
return s;
}
public U getThird() {
return u;
}
public String toString() {
return t + "\n" + s + "\n" + u + "\n";
}
public boolean isHomogeneous() {
return t.getClass() == s.getClass() && t.getClass() == u.getClass();
}
public void tripleSort() {
try {
for (int i = 1; i < array.size(); ++i) {
Object key = array.get(i);
int j = i - 1;
while (j > -1 && array.get(i) > key) {
array.set(j + 1, array.get(j));
j--;
}
array.set(j + 1, key);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
There are two main issues with code you've provided:
Relational operators <, <=, >, >= can be used only to compare numeric primitive types. Obviously, you can use it with objects.
To compare reference types you can use Comparator or these objects can implement Comparable interface (i.e. they basically are aware how to compare themselves). But it doesn't make since to compare BigDecimal and Boolean, or String and HashMap, how would you approach that? For that reason, these interfaces are generic and Comparator<T> can't be used with objects of type U.
That said, your Triple<T, S, U> would not be able to do a lot with these objects belonging to different type (definitely you can't sort them).
Hence, if you need a data-carrier holding references of three distinct types, that fine. It's still can be useful, but don't expect from it much.
A Java 16 record fits in this role perfectly well:
public record Triple<T, S, U>(T first, S second, U third) {}
But if you need to able to operate with these values comparing them with one another then consider changing the Triple to hold only elements of type T.
Here's an example of how it might be implemented:
public static class Triple<T> {
private List<T> list = new ArrayList<>(3);
private Comparator<T> comp;
private Triple(T first, T second, T third, Comparator<T> comp) { // no way and no need to invoke this constructor outside the class
this.comp = comp;
Collections.addAll(list, first, second, third);
}
public static <T> Triple<T> getInstance(T first, T second, T third, Comparator<T> comp) {
Triple<T> triple = new Triple<>(first, second, third, comp);
triple.init();
return triple;
}
public void init() {
list.sort(comp);
}
public T getFirst() {
return list.get(0);
}
public T getSecond() {
return list.get(1);
}
public T getThird() {
return list.get(2);
}
public boolean isHomogeneous() {
return comp.compare(getFirst(), getSecond()) == 0
&& comp.compare(getFirst(), getThird()) == 0
&& comp.compare(getSecond(), getThird()) == 0;
}
public String toString() {
return list.stream().map(T::toString).collect(Collectors.joining("\n"));
}
}
Usage example:
Let's consider a Triple storing integer value in Descending order.
public static void main(String[] args) {
Triple<Integer> intTriple = Triple.getInstance(5, 3, 12, Comparator.reverseOrder());
System.out.println(intTriple);
}
Output:
12
5
3

object sorting using comparable interface

i have one class which have 4 int fields . i want to sort objects array by some mathematical operation on fields .I tried below code but sorting is not happening.
class Cust1 implements Comparable<Cust1>{
int a;
int o;
int s;
int p;
#Override
public int compareTo(Cust1 b) {
if(this.a + this.s <= b.a + b.s)
{
return 1;
}
else {
return 0;
}
}
}
public class Test5 {
public static void main (String args[]) throws Exception
{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
Cust1[] cust = new Cust1[n];
for(int i=0;i<n ;i++)
{
Cust1 a = new Cust1();
String[] str = br.readLine().split(" ");
a.a = Integer.parseInt(str[0]);
a.o = Integer.parseInt(str[1]);
a.s = Integer.parseInt(str[2]);
a.p = Integer.parseInt(str[3]);
cust[i] =a;
}
Arrays.sort(cust, new Comparator<Cust1>() {
#Override
public int compare(Cust1 o1, Cust1 o2) {
return o1.compareTo(o2);
}
});
}
}
Based on your code snippet: you don't need to provide Comparator, since Cust1 already implements Comparable. So, this should be enough:
Arrays.sort(cust);
Also, Cust1 implementation of Comparable doesn't really tell, when one object is less then other. Probably, you meant something like this:
#Override
public int compareTo(Cust1 b) {
if(this.a + this.s < b.a + b.s) {
return 1;
} else if (this.a + this.s > b.a + b.s) {
return -1;
} else {
return 0;
}
}
But it's hard to tell, what exact implementation of Comparable should be without more details (for instance, for some reason fields o and p are not involved in comparison at all).
You comparator work wrong. It should return:
>0 - current object is greater than another one
0 - current object is equal to another one
<0 - current object is less than another one
class Cust1 implements Comparable {
int a;
int o;
int s;
int p;
#Override
public int compareTo(Cust1 b) {
return Integer.compare(a + s, b.a + b.s);
}
}
And you do not have to provide additional comparator to Arrays.sort() since Cust1 already implements Comparable.

Java generics array operation

I have a Vector generic class that contain an array of that contains elements of type T, and i want to have vector addition(for int, double, float, ... of course not boolean,... ) in my class, but i can't do "array_vector[i]", how use operator without know the type ?
public class vector<T> {
private T[] array_vector;
public vector(){
this.array_vector = null;
}
public vector(T[] array_p){
this.array_current = array_p;
}
public void vectorAddition(T[] array_other){
if(this.array_vector.length == array_other.length){
for(int i=0; i<this.array_vector.length ; i++){
this.array_vector[i] += array_other[i];
}
}
}
}
You can't do this directly; you need an instance of BinaryOperator<T>:
this.array_vector[i] = binaryOperator.apply(this.array_vector[i], array_other[i]);
For example:
BinaryOperator<Double> = (a, b) -> a + b;
BinaryOperator<Integer> = (a, b) -> a + b;
Pass one of these into the constructor of vector, e.g.
public vector(BinaryOperator<T> binaryOperator, T[] array_p){
// assign to a field.
}
First of all, if you always use numbers, maybe it's for the best to declare T extends Number to ensure strong typing checks no matter the solution applied:
public class vector<T extends Number> {
About solutions, I think that the more similar solution to the user's case is to use instanceof checks inside the vectorAddition method:
if (array_vector instanceof Integer[]) {
for (int i = 0; i < array_vector.length; ++i) {
array_vector[i] =
(T)(Integer)(array_vector[i].intValue() +
array_other[i].intValue());
}
} else if (array_vector instanceof Double[]) {
for (int i = 0; i < this.array.length; ++i) {
array_vector[i] =
(T)(Double)(array_vector[i].doubleValue() +
array_other[i].doubleValue());
}
//} else if (array_vector instanceof ...[]) {
// ... declare other supported types
} else {
// throw exception because not supported
}
However maybe is better to define some binary operator classes for common Number children and autoset it in the constructor, based on the array_vector instance with something like:
if (array_vector instanceof Integer[]) {
// ...
} else if (array_vector instanceof Double[]) {
// ...
} else if (array_vector instanceof ...[]) {
// ...
} else {
// throw exception because not supported
}
And maybe define a custom binary operator constructor if desired/needed.

How to tell compiler that two objects are of same - but unknown - class (with generics)?

Please consider the following code:
public class MyClass {
public static void main(String[] args) {
Object o1 = getObject(Math.random());
Object o2 = getObject(Math.random());
if (o1.getClass().equals(o2.getClass()) { // two Cars or two Apples
Comparable c1 = (Comparable) o1;
Comparable c2 = (Comparable) o2;
int x = c1.compareTo(c2); // unsafe
System.out.println(x);
)
}
public Object getObject(double d) { // given method that may not be changed
if (d < 0.5) return (new Car()); // Car implements Comparable<Car>
else return (new Apple()); // Apple implements Comparable<Apple>
}
}
The code works (given the classes Cat and Apple) but the compiler warns for unsafe operations because I use compareTo without generics. I do not know how to fix this, however, because I have no idea how to specify that c1 and c2 are of the same - but unknown - type (in the if clause). Is there any way (besides using #SuppressWarnings of course) to fix this?
I am aware that there is a similar question here: How to tell Java that two wildcard types are the same?
But the answer given there seems to be specific to the context of the asker. For example, it uses a key-value map which does not exist in my context.
Since the condition if (o1.getClass().equals(o2.getClass())) guarantees that the two objects are of the same class,
it's safe to ignore the warning. You can suppress it.
However, since their type is Object at that point,
it's not safe to cast them to Comparable.
You can make it safer with some minor tweaks, to make them of class Comparable:
public static void main(String[] args) {
Comparable<?> o1 = getComparable(Math.random());
Comparable<?> o2 = getComparable(Math.random());
if (o1.getClass().equals(o2.getClass())) {
// safe cast
Comparable c1 = (Comparable) o1;
Comparable c2 = (Comparable) o2;
// safe comparison
int x = c1.compareTo(c2);
System.out.println(x);
}
}
public Comparable<? extends Comparable<?>> getComparable(double d) {
if (d < 0.5) return (new Car());
return (new Apple());
}
The problem here is that a Comparable<Car> and a Comparable<Apple> cannot be compared against each other, as Comparable<T> has the "comfortable" feature just comparing Ts. And now we are paying for it.
For .equals this is not the case.
What we are trying
// WILL NOT WORK
if (o1.getClass().equals(o2.getClass()) { // two Cars or two Apples
int x = comparison(o1.getClass(), o1, o2);
System.out.println(x);
)
static <T extends Comparable<T>> int comparison(Class<T> type, T o1, T o2) {
return type.cast(o1).compareTo(type.cast(o2));
}
This is a feature of java, with a very minimally expressive type system.
Update your methods to deal with Comparable objects rather than Object objects.
public class MyClass {
public static void main(String[] args) {
Comparable o1 = getObject(Math.random());
Comparable o2 = getObject(Math.random());
if (o1.getClass().equals(o2.getClass()) { // two Cars or two Apples
int x = o1.compareTo(o2);
System.out.println(x);
)
}
public Comparable getObject(double d) { // given method that may not be changed
if (d < 0.5) return (new Car()); // Car implements Comparable<Car>
else return (new Apple()); // Apple implements Comparable<Apple>
}
}
Another alternative implementation:
public static void main(String[] args) {
Object o1 = getObject(Math.random());
Object o2 = getObject(Math.random());
if ( o1.getClass().equals(o2.getClass() ) && o1 instanceof Comparable ) {
int x = getObject(o1).compareTo(o2);
System.out.println(x);
}
}
public static Object getObject(double d) { // given method that may not be changed
if (d < 0.5) return ( new Car() ); // Car implements Comparable<Car>
else return ( new Apple() ); // Apple implements Comparable<Apple>
}
public static <T> Comparable<T> getObject(Object o) {
return (Comparable<T>)o;
}
You can use instanceof operators. You can write it as:
if(c1 instanceof Integer && c2 instanceof Integer)
int x = c.compareTo(c2);
or if(c1 instanceof String)
String s = c1;
You can work this way.

Type-casting and getting an array to peek generic results

I am writing a program that inherits some traits from MyStackGeneric called MyStackInteger. I am almost done with the assignment, but I am running into an issue. As soon as I get my two variables in the method binaryOperator, it tries to add, subtract or multiply strings which returns and error. I've tried type casting and moving things around but I cannot get it to work. One of the constraints of my work is that all the methods that are currently in MyStackGeneric have to stay in there. I cannot have them in MyStackInteger as we are going to be using this for Complex numbers in the future.
class `MyStackInteger`:
import java.util.ArrayList;
import java.util.Scanner;
import java.util.Iterator;
public class MyStackInteger extends MyStackGeneric<java.lang.Integer>{
//Creates a new ArrayList and runs readInput as long as there is input
public static void main(String[] args){
MyStackInteger my = new MyStackInteger(){};
my.readInput(new Scanner(System.in));
}
//Subtracts two variables
#Override
protected java.lang.Integer minus(java.lang.Integer o1, java.lang.Integer o2){
o2 = o2-o1;
return o2;
}
//Multiplies two variables
#Override
protected java.lang.Integer multiply(java.lang.Integer o1, java.lang.Integer o2){
o2 = o2*o1;
return o2;
}
//Creates a new element in the Array
#Override
protected java.lang.Integer newElement(java.lang.String w){
return new Integer(w);
}
//Adds two variables
#Override
protected java.lang.Integer plus(java.lang.Integer o1, java.lang.Integer o2){
o2 = o2+o1;
return o2;
}
//Adds a zero to the array
#Override
protected java.lang.Integer zero(){
Integer blank = 0;
return blank;
}
}
class MyStackGeneric<E>:
abstract class MyStackGeneric<E> extends ArrayList<E>{
//Generics being implemented by MyStackInteger
protected abstract E multiply(E o1, E o2);
protected abstract E minus(E o1, E o2);
protected abstract E plus(E o1, E o2);
protected abstract E zero();
protected abstract E newElement(java.lang.String w);
//Grabs the top element of the ArrayList
public E peek(){
return this.get(getSize()-1);
}
//Removes the top element of the ArrayList
public E pop(){
E o = this.get(getSize()-1);
this.remove(getSize()-1);
return o;
}
//Pushes an element onto the ArrayList
public void push(E o) {
this.add(o);
}
//Makes the ListArray A string
#Override
public String toString() {
return "stack: " + this.toString();
}
//Iterates while there is input
public void readInput(Scanner s) {
while (s.hasNext()) {
String s2 = s.next();
//Pushes any numerical input to the stack
if (s2.matches("[+-]?\\d+")) {
push((E) s2);
//Goes to binaryOperator if +, - or * is implemented
} else if (("+".equals(s2)) ||
("-".equals(s2)) ||
("*".equals(s2))) {
binaryOperator(s2);
//Prints the stack
} else if (s2.matches("p")) {
print();
//Runs an error if the input is too long
} else if (s2.length() > 1) {
System.out.println("Exception: too long: " + s2);
//Runs an error if there is one unknown char
} else if (s2.length() == 1) {
System.out.println("Exception: Unknown Command " + s2);
}
}
}
//Prints the stack
public void print(){
System.out.println("Print Stack: ");
Iterator<E> s = this.iterator();
while(s.hasNext()){
System.out.print(s.next() + (s.hasNext() ? ", " : "\n" ));
System.out.println("");
}
}
//Checks if the ArrayList is empty
public boolean empty(){
return this.isEmpty();
}
//Gets the total size of the ArrayList
public int getSize(){
return this.size();
}
//Tries to grab the top two elements of the ArrayList, then execute a
//arithmetic operation on them.
public void binaryOperator(java.lang.String op){
E var1;
E var2;
boolean exist = true;
try {
var1 = peek();
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Exception: Need two operands");
var1 = null;
exist = false;
}
if (exist)
pop();
try {
var2 = peek();
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Exception: Need two operands");
var2 = null;
exist = false;
}
if (exist)
pop();
//This is where the program breaks. At this point, both var1
//and var2 are Strings so when it tries to run plus or minus
//or multiply, it returns the error of not being able to turn
//a string into an int.
if ("+".equals(op)){
push(plus(var1, var2));
}
if ("-".equals(op)){
push(minus(var1, var2));
}
if ("*".equals(op)){
push(multiply(var1, var2));
}
}
}
if (s2.matches("[+-]?\\d+")) {
push((E) s2);
You cannot do this. You cannot take a String and execute an arbitrary cast on it. If you were paying attention to the compiler you would see the warning message
Type safety: Unchecked cast from String to E
The primary issue is with your design. The method readInput has no business being in the MyStackGeneric class. The class should do one thing only, which is serve as the base for implementing concrete stacks. Input and output should be handled by the users of the class, who could do the correct parsing/conversion for the input data.
Or, to put it another way, converting input data to match the generic type requires information (the concrete parameter type) that is not available to the MyStackGeneric class because of type erasure. You could put concrete versions of readInput() in your concrete classes. For MyStackInteger those lines would become
if (s2.matches("[+-]?\\d+")) {
push(Integer.valueOf(s2));
But that still violates the single-responsibility principle.

Categories