I'm facing problem while trying to sort an ArrayList of custom object. In fact, after the sorting, nothing has change in my ArrayList. Is something wrong with my method?
Here's my Artist custom object property :
public class Artist {
String mNickname;
String mName;
String mDescription;
String mScene;
String mDay;
String mTime;
String mImageURL;
Date mDate;
// getters and setters below like getDate() for mDate...
And here's the method use to sort :
static public ArrayList<Artist> sortArrayByDate(ArrayList<Artist> list) {
Collections.sort(list, new Comparator<Artist>() {
#Override
public int compare(Artist lhs, Artist rhs) {
if (lhs.getDate().getTime() < rhs.getDate().getTime())
return -1;
else if (lhs.getDate().getTime() == rhs.getDate().getTime())
return 0;
else
return 1;
}
});
return list;
}
I know this topic as been discuss many time on StackOverflow, but I can't find why I'm not able to make it work properly. Thanks for your understanding
EDIT : Dates (java.util.date) are create using SimpleDateFormatter
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.CANADA_FRENCH);
Why not simply use Date#compareTo() for the comparison since java.util.Date implements the Comparable interface.
It is not necessary for the method to return an instance of the List after it is sorted because the underlying List object will be modified by invoking sort. Basically, the method is invoked by passing a reference value as an argument. So when modifications are made, the changes are reflected on the underlying object pointed to by the reference value. With this approach, the code simply passes the List into the method and then continues using the same reference in the proceeding code, which will point to an underlying List which has been sorted.
Another item to consider is modifying the method to accept an argument of the type Listas opposed to ArrayList, since List is an interface, hence more abstract. This would allow the code to switch the implementation of List being passed to the method. This is important because ArrayList does not guarantee the order of the items in the list is maintained. To guarantee the order of items in the List is maintained used LinkedList.
static public void sortArrayByDate(List<Artist> list) {
Collections.sort(list, new Comparator<Artist>() {
#Override
public int compare(Artist lhs, Artist rhs) {
return lhs.getDate().compareTo(rhs.getDate());
}
});
}
Here is a GitHub Gist I created, that show this method in action, with a complete working example.
Related
I want to check whether a List contains an object that has a field with a certain value. Now, I could use a loop to go through and check, but I was curious if there was anything more code efficient.
Something like;
if(list.contains(new Object().setName("John"))){
//Do some stuff
}
I know the above code doesn't do anything, it's just to demonstrate roughly what I am trying to achieve.
Also, just to clarify, the reason I don't want to use a simple loop is because this code will currently go inside a loop that is inside a loop which is inside a loop. For readability I don't want to keep adding loops to these loops. So I wondered if there were any simple(ish) alternatives.
Streams
If you are using Java 8, perhaps you could try something like this:
public boolean containsName(final List<MyObject> list, final String name){
return list.stream().filter(o -> o.getName().equals(name)).findFirst().isPresent();
}
Or alternatively, you could try something like this:
public boolean containsName(final List<MyObject> list, final String name){
return list.stream().map(MyObject::getName).filter(name::equals).findFirst().isPresent();
}
This method will return true if the List<MyObject> contains a MyObject with the name name. If you want to perform an operation on each of the MyObjects that getName().equals(name), then you could try something like this:
public void perform(final List<MyObject> list, final String name){
list.stream().filter(o -> o.getName().equals(name)).forEach(
o -> {
//...
}
);
}
Where o represents a MyObject instance.
Alternatively, as the comments suggest (Thanks MK10), you could use the Stream#anyMatch method:
public boolean containsName(final List<MyObject> list, final String name){
return list.stream().anyMatch(o -> name.equals(o.getName()));
}
You have two choices.
1. The first choice, which is preferable, is to override the `equals()` method in your Object class.
Let's say, for example, you have this Object class:
public class MyObject {
private String name;
private String location;
//getters and setters
}
Now let's say you only care about the MyObject's name, that it should be unique so if two `MyObject`s have the same name they should be considered equal. In that case, you would want to override the `equals()` method (and also the `hashcode()` method) so that it compares the names to determine equality.
Once you've done this, you can check to see if a Collection contains a MyObject with the name "foo" by like so:
MyObject object = new MyObject();
object.setName("foo");
collection.contains(object);
However, this might not be an option for you if:
You are using both the name and location to check for equality, but you only want to check if a Collection has any `MyObject`s with a certain location. In this case, you've already overridden `equals()`.
`MyObject` is part of an API that you don't have liberty to change.
If either of these are the case, you'll want option 2:
2. Write your own utility method:
public static boolean containsLocation(Collection<MyObject> c, String location) {
for(MyObject o : c) {
if(o != null && o.getLocation.equals(location)) {
return true;
}
}
return false;
}
Alternatively, you could extend ArrayList (or some other collection) and then add your own method to it:
public boolean containsLocation(String location) {
for(MyObject o : this) {
if(o != null && o.getLocation.equals(location)) {
return true;
}
}
return false;
}
Unfortunately there's not a better way around it.
This is how to do it using Java 8+ :
boolean isJohnAlive = list.stream().anyMatch(o -> "John".equals(o.getName());
Google Guava
If you're using Guava, you can take a functional approach and do the following
FluentIterable.from(list).find(new Predicate<MyObject>() {
public boolean apply(MyObject input) {
return "John".equals(input.getName());
}
}).Any();
which looks a little verbose. However the predicate is an object and you can provide different variants for different searches. Note how the library itself separates the iteration of the collection and the function you wish to apply. You don't have to override equals() for a particular behaviour.
As noted below, the java.util.Stream framework built into Java 8 and later provides something similar.
Collection.contains() is implemented by calling equals() on each object until one returns true.
So one way to implement this is to override equals() but of course, you can only have one equals.
Frameworks like Guava therefore use predicates for this. With Iterables.find(list, predicate), you can search for arbitrary fields by putting the test into the predicate.
Other languages built on top of the VM have this built in. In Groovy, for example, you simply write:
def result = list.find{ it.name == 'John' }
Java 8 made all our lives easier, too:
List<Foo> result = list.stream()
.filter(it -> "John".equals(it.getName())
.collect(Collectors.toList());
If you care about things like this, I suggest the book "Beyond Java". It contains many examples for the numerous shortcomings of Java and how other languages do better.
Binary Search
You can use Collections.binarySearch to search an element in your list (assuming the list is sorted):
Collections.binarySearch(list, new YourObject("a1", "b",
"c"), new Comparator<YourObject>() {
#Override
public int compare(YourObject o1, YourObject o2) {
return o1.getName().compareTo(o2.getName());
}
});
which will return a negative number if the object is not present in the collection or else it will return the index of the object. With this you can search for objects with different searching strategies.
Map
You could create a Hashmap<String, Object> using one of the values as a key, and then seeing if yourHashMap.keySet().contains(yourValue) returns true.
Eclipse Collections
If you're using Eclipse Collections, you can use the anySatisfy() method. Either adapt your List in a ListAdapter or change your List into a ListIterable if possible.
ListIterable<MyObject> list = ...;
boolean result =
list.anySatisfy(myObject -> myObject.getName().equals("John"));
If you'll do operations like this frequently, it's better to extract a method which answers whether the type has the attribute.
public class MyObject
{
private final String name;
public MyObject(String name)
{
this.name = name;
}
public boolean named(String name)
{
return Objects.equals(this.name, name);
}
}
You can use the alternate form anySatisfyWith() together with a method reference.
boolean result = list.anySatisfyWith(MyObject::named, "John");
If you cannot change your List into a ListIterable, here's how you'd use ListAdapter.
boolean result =
ListAdapter.adapt(list).anySatisfyWith(MyObject::named, "John");
Note: I am a committer for Eclipse ollections.
Predicate
If you dont use Java 8, or library which gives you more functionality for dealing with collections, you could implement something which can be more reusable than your solution.
interface Predicate<T>{
boolean contains(T item);
}
static class CollectionUtil{
public static <T> T find(final Collection<T> collection,final Predicate<T> predicate){
for (T item : collection){
if (predicate.contains(item)){
return item;
}
}
return null;
}
// and many more methods to deal with collection
}
i'm using something like that, i have predicate interface, and i'm passing it implementation to my util class.
What is advantage of doing this in my way? you have one method which deals with searching in any type collection. and you dont have to create separate methods if you want to search by different field. alll what you need to do is provide different predicate which can be destroyed as soon as it no longer usefull/
if you want to use it, all what you need to do is call method and define tyour predicate
CollectionUtil.find(list, new Predicate<MyObject>{
public boolean contains(T item){
return "John".equals(item.getName());
}
});
Here is a solution using Guava
private boolean checkUserListContainName(List<User> userList, final String targetName){
return FluentIterable.from(userList).anyMatch(new Predicate<User>() {
#Override
public boolean apply(#Nullable User input) {
return input.getName().equals(targetName);
}
});
}
contains method uses equals internally. So you need to override the equals method for your class as per your need.
Btw this does not look syntatically correct:
new Object().setName("John")
If you need to perform this List.contains(Object with field value equal to x) repeatedly, a simple and efficient workaround would be:
List<field obj type> fieldOfInterestValues = new ArrayList<field obj type>;
for(Object obj : List) {
fieldOfInterestValues.add(obj.getFieldOfInterest());
}
Then the List.contains(Object with field value equal to x) would be have the same result as fieldOfInterestValues.contains(x);
Despite JAVA 8 SDK there is a lot of collection tools libraries can help you to work with, for instance:
http://commons.apache.org/proper/commons-collections/
Predicate condition = new Predicate() {
boolean evaluate(Object obj) {
return ((Sample)obj).myField.equals("myVal");
}
};
List result = CollectionUtils.select( list, condition );
so I've build these two classes:
1. Genre which implements Comparable
2. GenreManager which takes a Collection of genres and creates an internal copy of it. Later in GenreManager, I will need to add new Genres by getting a name as an input, and I need to assign this Genre the next free id number, which is basically the next smallest positive number after the smallest used id.
I am trying to use Collections.sort() to sort my list but I am getting the following error:
"no instance(s) of type variable(s) T exist so that Collection conforms to List." and I am not sure what this is referring to... I've tried ready a bunch of posts about this on here but couldn't figure out the solution... Here is part of the code:
public class Genre implements Comparable<Genre>{
private int id;
private String name;
public Genre(int id, String name){
this.id = Validate.requireNonNegative(id);
this.name = Validate.requireNonNullNotEmpty(name);
}
#Override
public int compareTo(Genre o) {
int res = Integer.valueOf(id).compareTo(o.id);
if (res != 0){
return res;
}
else{
return this.name.compareToIgnoreCase(o.name);
}
}
}
public class GenreManager{
private Collection<Genre> genres;
private Collection<Genre> sortedTree;
public GenreManager(){
this.genres = new ArrayList<Genre>();
}
public GenreManager(Collection<Genre> genres){
// check for duplicates
for (Genre x : genres){
for (Genre y : genres){
if (x.equals(y) || x.getName().equals(y.getName()))
throw new IllegalArgumentException("List contains duplicates");
}
}
this.genres = new ArrayList<Genre>(Collections.sort(genres));
}
}
I am trying to do the sorting in the constructor above. Can someone tell me how to go around this?
I tried playing around a little bit, trying to change the private variable from Collection<Genre> to List<Genre> for example and similar things but nothing worked... I also tried casting the input of the .sort method to (List<Genre>) but it didn't work either.
PS: I can't change any of the method header or class headers.
Thanks!
As per request, here's a compilation of my comments to answer the question:
The immediate problem is that Collections.sort(List<T>) takes a List parameter and not just a Collection because collections in general don't have to be sortable (e.g. hash sets aren't). Additionally the method returns void and sorts the passed list in place, i.e. the way you call it won't compile.
Taking all this into consideration your code might be changed to something like this:
public class GenreManager{
private List<Genre> genres;
...
public GenreManager(Collection<Genre> genres){
...
//create a list out of the passed collection
this.genres = new ArrayList<Genre>( genres );
//sort the list
Collections.sort(this.genres);
}
}
The other problem with the code you posted is that for any non-empty collection it will throw the IllegalArgumentException because elements are compared to themselves. Adding a check for x != y to the condition would solve that but the code is still somewhat slow because it has a time complexity of O(n2).
This can be solved to use a set instead of a list. However, a HashSet would depend on how equals() and hashCode() define equality, which doesn't seem to match your requirements. That could be solved by using a wrapper object that implements both methods as needed.
A better approach might be to use a TreeSet though. TreeSet uses comparisons to determine order and equality (if the compare result is 0) and thus would allow you to either let your Genre class implement Comparable as you did or provide a separate Comparator (e.g. if you need multiple different definitions of equality).
If you just want to eliminate duplicates, your code could then look like this:
public class GenreManager{
private SortedSet<Genre> genres;
...
public GenreManager(Collection<Genre> genres){
this.genres = new TreeSet<>( genres );
}
}
If you want to know what duplicates are in the collection you could do it like this:
public GenreManager(Collection<Genre> genres){
this.genres = new TreeSet<>(); //the generic type is inferred from this.genres
for( Genre element : genres ) {
//If the element didn't exist in the set add() will return true, false if it existed
boolean nonDuplicate = this.genres.add( element );
//handle the duplicate element here
}
}
As it was mentioned before, your code has several errors which makes it unusable:
Checking equality of elements with themselves.
Collections.sort method takes a List of Comparable as an argument, when Collection is a little higher in a hierarchy, which means you can't use it as a parameter. To resolve it change declaration of variable genres to List.
method Collections.sort returns void, so you can't pass its return value as an argument to ArrayList constructor. Instead, try assigning genres variable first and then sorting it via Collections.sort as
this.genres = new ArrayList/LinkedList(genres)
Collections.sort(this.genres)
Again, you may consider using TreeSet as it holds all elements sorted and without duplicates, so your constructor will just look like
this.genres = new TreeSet(genres)
In addition, it prevents duplicates even during adding, so if you have 10 elements, adding already existing one won't make any changes to your set. But using this data structure you should check variable for null before adding, as it will produce NullPointerException
I have two types of for loops, fileList in this case is just a custom class that extends AbstractList<Object>
1) For loop iterating by index:
public String getExtensionByDescription(String description)
{
for (int i = 0; i < fileList.size(); i++)
if (description.contains(fileList.get(i).getDescription()))
return fileList.get(i).getExtension();
}
2) For loop iterating by item in list:
public String getExtensionByDescription(String description)
{
for (FileType obj : fileList)
if (description.contains(obj.getDescription()))
return obj.getExtension();
}
Are method 1) and 2) logically the same or no? Because 1) returns the value i expect but method 2) returns the wrong value. Thanks for the help!
The implementation of list with various other get methods.
public class FileList extends AbstractList<Object>
{
private ArrayList<FileType> fileList;
public FileList()
{
fileList = new ArrayList<FileType>();
}
public void add(String search, String type, String extension, String description, String htmlicon)
{
FileType data = new FileType(fileList.size(), search, type, extension, description, htmlicon);
if (!fileList.contains(data))
{
fileList.add(data);
}
}
#Override
public Object get(int index)
{
return fileList.toArray()[index];
}
and the other class is
public FileType(int index, String search, String type, String extension, String description, String icon)
with function:
public String getDescription()
{
return description;
}
If it was correct implementation of List, then they would be logically the same (although they could differ in terms of performance).
If they give different results then either get(int) method or iterator() method doesn't behave as expected, i.e. doesn't return i-th element or doesn't traverse through all elements respectively.
EDIT: After update of the question the issue is clear - you override get(int) method (although it returns Object but in the code it's accessed as FileType - looks suspicious). But there is no override of iterator() method. The Iterator which is returned by iterator() is actually used in the second for loop transparently by the compiler. Unless you override the code could never work.
You may have a few things conflated here.
Your enclosing class also had an iterator when all you wanted to do was iterate over your backing ArrayList.
You overwrote get, which was guaranteed to give you back the result you wanted when using it, but didn't consider the iterator().
Ultimately, there was no reason for you to extend AbstractList at all. Just use the backing list instead.
To do that, create a getter for it:
public ArrayList<FileType> getFileList() {
return fileList;
}
...and then use it in your iteration:
for(FileType type : fileList.getFileList()) {
// logic here
}
It would then behave no differently to your get.
I have a server, from which i get the values that i need(name, date, city, picture_url). To get them are in getValues class. I'm using Json.
All the values are saved in an ArrayList called array. I would use them in multiple classes. I would like to call the array in FragmentB. This is the code for the ArrayList
private ArrayList<String> array;
`array = new ArrayList<String>();
array.add(finalresult.getString("picture"));
array.add(finalresult.getString("name"));
array.add(finalresult.getString("date"));
array.add(finalresult.getString("city"));`
Then i thought i needed some kind of function, so it can be called, from other classes. I wanted to name the function, then arguments are numbers, so you can select which element you want, then you just return the object you wanted.
public ArrayList<String> getEvent(int pos)
{
return array.get(pos);
}
But here i get an error:
Required: java.util.ArrayList <java.lang.String>
Found: java.lang.String
In Fragment, i want the specific element of the array, and save it in one string, so i can call it later.
Something like this:
public class FragmentB extends android.support.v4.app.ListFragment{
private GetEvents getEvents = new GetEvents();
private String picture1, name1, city1, date1;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
picture1 = getEvents.getArray(0);
name1 = getEvents.getArray(1);
city1 = getEvents.getArray(2);
date1 = getEvents.getArray(3);
}
}
I know that this is wrong. What is the correct way to pass the elements, and then call them in the fragment?
Change
public ArrayList<String> getEvent(int pos)
{
return array.get(pos);
}
to:
public String getEvent(int pos)
{
return array.get(pos);
}
But I think you should consider passing info to your fragments via arguments
For the error, it is just that getEvent(int pos) should return a String and not a List<String>.
If (name, city, date, picture) is an object of your application model (I understand that these are used in several places), you may take advantage to create a simple pojo class to hold these infos, e.g.
public class Event {
String name, city, url;
Date date;
// +constructor
// +getters
}
Then you can store, pass as parameter, filter, sort Events and access properties with specific methods without ambiguity.
Actually storing values with different meaning in an array is bad practice: an array should contain objects of the same nature, e.g. a list of Event, a list of city, etc. Think of what will happen if the order in the array changes. Does it make sense to have city at position 0 and name at 1?
Beginner, can't seem to wrap my head around this.
Item Class
public class Item implements Comparable {
private int number;
private String description;
public Item(int num, String descript){
this.number = num;
this.description = descript;
}
public int getNumber(){
return number;
}
public String getDescription(){
return description;
}
#Override public int compareTo(Object o){
Item i = (Item) o;
if(this.getNumber() < i.getNumber())
return -1;
if(this.getNumber() > i.getNumber())
return 1;
return 0;
}
}
Main method
Item[] items = new Item[3];
items[0] = new Item(102, "Duct Tape");
items[1] = new Item(103, "Bailing wire");
items[2] = new Item(101, "Chewing Gum");
Arrays.sort(items);
for (Item i : items){
System.out.println(i.getNumber() + ": " + i.getDescription());
}
When the main method instantiate items[0] = new Item(102, "Duct Tape"); Which goes through Item constructor to set current variable number, description.
My problem is I can't seem to understand what is being passed in the compareTo argument, therefore I can't seem to understand this.getNumber() < i.getNumber() is doing...
Return a negative number if current object is less than passed object?
any help is much appreciated.
The raw interface Comparable that you are using allows this object to be comparable to another Object. It is then cast to Item to ensure that the this Item is only compared to another Item.
According to the Comparable Javadocs,
Compares this object with the specified object for order. Returns a
negative integer, zero, or a positive integer as this object is less
than, equal to, or greater than the specified object.
Additionally, your compareTo will throw a ClassCastException at runtime if the Object isn't an Item.
It's better to use the generic form of the interface:
public class Item implements Comparable<Item> {
Then you can specify Item instead of Object to compare against:
#Override public int compareTo(Item i){
and you don't have to cast the Object to an Item. When calling compareTo, the compiler will enforce that the object to compare to must be an Item.
My problem is I can't seem to understand what is being passed in the compareTo argument
Array.sort uses the compareTo method to know how to compare pairs of items from the array.
Your sort method assumes that it will only be called with instances of Item, and that's all Array.sort will pass along to compareTo assuming that your array only holds instances of Item.
You will get a runtime exception if compareTo is ever invoked with the item being compared to not being an instance of Item.
I can't seem to understand this.getNumber() < i.getNumber() is doing...
It's comparing two instance of Item based on the value of getNumber, that is, the value of their number fields. It assumes that two instances of Item are equal if Item.number is the same for each instance, otherwise one is less than the other if its Item.number is less than the other's Item.number.
You are trying to override the default compareTo() method, which takes in a single Object as a parameter. Thus, in your implementation, the parameter needs to be of type Object. However, you assume that you will be passing in a Item object, so you can cast it accordingly.
So in the end, your Item object is treated like an Object, which is casted back to an Item in your compareTo() method.
The Object being passed to your compareTo method is an Item object. The Arrays.sort() calls the Overrided method method when you have it implemented, the best implementation is to return the compareTo method for the primitive java types
#Override
public int compareTo(Object o){
return (this.getNumber().compareTo((Item)o.getNumber());
}
(this requires you to have number as type Integer though)