Reputation: 4377
Is it possible to write a Comparator
so that I can pass the Object type, the field type and the field I like to sort on? I've made some small changes to http://www.davekoelle.com/files/AlphanumComparator.java to accommodate sorting on the field email
of type String
in the Object type User
. I have this code that works.
public class Main {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
users.add(new User(7, "user1", "[email protected]"));
users.add(new User(11, "user20", "[email protected]"));
users.add(new User(5, "admin20", "[email protected]"));
users.add(new User(10, "user11", "[email protected]"));
users.add(new User(6, "admin21", "[email protected]"));
users.add(new User(12, "user21", "[email protected]"));
users.add(new User(8, "user2", "[email protected]"));
users.add(new User(1, "admin1", "[email protected]"));
users.add(new User(3, "admin10", "[email protected]"));
users.add(new User(2, "admin2", "[email protected]"));
users.add(new User(9, "user10", "[email protected]"));
users.add(new User(4, "admin11", "[email protected]"));
for (User item : users) {
System.out.println(item.getEmail());
}
System.out.println("__________________________");
Collections.sort(users, new AlphanumComparator());
for (User item : users) {
System.out.println(item.getEmail());
}
}
}
.
public class User {
int id;
String name;
String email;
// Constructor, Getters and Setters
}
.
public class AlphanumComparator implements Comparator<User> {
private final boolean isDigit(char ch) {
return ((ch >= 48) && (ch <= 57));
}
/**
* Length of string is passed in for improved efficiency (only need to calculate it once)
**/
private final String getChunk(String s, int slength, int marker) {
StringBuilder chunk = new StringBuilder();
char c = s.charAt(marker);
chunk.append(c);
marker++;
if (isDigit(c)) {
while (marker < slength) {
c = s.charAt(marker);
if (!isDigit(c))
break;
chunk.append(c);
marker++;
}
} else {
while (marker < slength) {
c = s.charAt(marker);
if (isDigit(c))
break;
chunk.append(c);
marker++;
}
}
return chunk.toString();
}
public int compare(User u1, User u2) {
if ((u1 == null) || (u2 == null)) {
return 0;
}
int thisMarker = 0;
int thatMarker = 0;
int s1Length = u1.getEmail().length();
int s2Length = u2.getEmail().length();
while (thisMarker < s1Length && thatMarker < s2Length) {
String thisChunk = getChunk(u1.getEmail(), s1Length, thisMarker);
thisMarker += thisChunk.length();
String thatChunk = getChunk(u2.getEmail(), s2Length, thatMarker);
thatMarker += thatChunk.length();
// If both chunks contain numeric characters, sort them numerically
int result = 0;
if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0))) {
// Simple chunk comparison by length.
int thisChunkLength = thisChunk.length();
result = thisChunkLength - thatChunk.length();
// If equal, the first different number counts
if (result == 0) {
for (int i = 0; i < thisChunkLength; i++) {
result = thisChunk.charAt(i) - thatChunk.charAt(i);
if (result != 0) {
return result;
}
}
}
} else {
result = thisChunk.compareTo(thatChunk);
}
if (result != 0)
return result;
}
return s1Length - s2Length;
}
}
How can I pass Object type, field type and the field that I like to sort on from Collections.sort(users, new AlphanumComparator());
in the Main
class to AlphanumComparator
and how can I accommodate for this in AlphanumComparator
? So in this case I would pass object type User
field email
and field type String
. But if I like to sort on id
I would pass object type User
, field email
and field type int
.
Upvotes: 0
Views: 723
Reputation: 9650
I would keep the AlphanumComparator as is, and create a new class FieldComparator:
public class FieldComparator<T> implements Comparator<T> {
private static final Logger LOG = Logger.getLogger(
FieldComparator.class.getName());
private static final AlphanumComparator ALPHANUM = new AlphanumComparator();
private final Field field;
private final boolean isString;
private final boolean isComparable;
public FieldComparator(Class<T> clazz, String name) {
try {
field = clazz.getDeclaredField(name);
field.setAccessible(true);
Class<?> fieldType = field.getType();
isString = fieldType == String.class;
isComparable = Comparable.class.isAssignableFrom(fieldType);
} catch (NoSuchFieldException | SecurityException ex) {
LOG.log(Level.SEVERE, null, ex);
throw new RuntimeException(ex.getMessage());
}
}
@Override
public int compare(T o1, T o2) {
try {
Object value1 = field.get(o1);
Object value2 = field.get(o2);
if (value1 == null) {
return value2 == null ? 0 : -1;
} else if (value2 == null) {
return 1;
} else if (isString) {
return ALPHANUM.compare((String)value1, (String)value2);
} else if (isComparable) {
return ((Comparable)value1).compareTo(value2);
} else {
// don't know how to compare fields
return 0;
}
} catch (IllegalArgumentException | IllegalAccessException ex) {
LOG.log(Level.SEVERE, null, ex);
throw new RuntimeException(ex.getMessage());
}
}
}
UPDATE:
To deal with primitive types, you can change one line of method compare:
} else if (isComparable || value1 instanceof Comparable) {
UPDATE 2:
You main method would become:
public static void main(String[] args) {
List<User> users = new ArrayList<>();
users.add(new User(7, "user1", "[email protected]"));
users.add(new User(11, "user20", "[email protected]"));
users.add(new User(5, "admin20", "[email protected]"));
users.add(new User(10, "user11", "[email protected]"));
users.add(new User(6, "admin21", "[email protected]"));
users.add(new User(12, "user21", "[email protected]"));
users.add(new User(8, "user2", "[email protected]"));
users.add(new User(1, "admin1", "[email protected]"));
users.add(new User(3, "admin10", "[email protected]"));
users.add(new User(2, "admin2", "[email protected]"));
users.add(new User(9, "user10", "[email protected]"));
users.add(new User(4, "admin11", "[email protected]"));
for (User item : users) {
System.out.println(item.getEmail());
}
System.out.println("__________________________");
Collections.sort(users, new FieldComparator(User.class, "email"));
for (User item : users) {
System.out.println(item.getEmail());
}
System.out.println("__________________________");
Collections.sort(users, new FieldComparator(User.class, "name"));
for (User item : users) {
System.out.println(item.getEmail());
}
System.out.println("__________________________");
Collections.sort(users, new FieldComparator(User.class, "id"));
for (User item : users) {
System.out.println(item.getEmail());
}
}
Upvotes: 1