I'm a little embarrassed to be asking this because I'm sure this is a very basic question but having searched and thought about it I still can't work this out.
The code is from a class the tutor walked through developing during a tutorial on collections for my course. The class represents a crime sheet of districts with associated crime codes. The sheet is implemented as a SortedMap where the keys are of type String (representing the district) and the values are an ArrayList of Integers (representing the crime codes).
There is one method that we didn't manage to finish during the tutorial that the tutor sent on a copy of and I can't follow the logic. The code for the method is below
/**
* displays districts affected by given crime code
*
* #param Integer crime code
* #return none
*/
public void displayAffectedDistricts(Integer crimeCode)
{
ArrayList<Integer> searchCode;
for(String eachDistrict: crimeTable.keySet())
{
searchCode = new ArrayList<Integer>();
searchCode.add(crimeCode);
if(!(searchCode.retainAll(crimeTable.get(eachDistrict))))
{
System.out.println("Code "+crimeCode+" found in district "+eachDistrict);
}
}
}
I've run the code and can see that this works but I can't follow the logic of the if condition.
My understanding is that searchCode.retainAll(crimeTable.get(eachDistrict)) will evaluate to the reference to the list searchCode and that, at that point, searchCode will contain the single value given by the crimeCode argument if the Map entry for eachDistrict has crimeCode in it's set of values or will otherwise be empty.
As far as I'm aware that's not a boolean value so can't be used as an if condition without a comparison operator.
Could someone please explain what I'm missing?
It's pretty bad code. But, here's how it works:
The retainAll() method returns true if the list changed because of the operation. A list is created that contains the target code, and if the other list contains it, the list will not change.
I would severely mark down the person who wrote this if I was marking it, because it's obtuse. Here's a better implementation, uses much less code, is straightforward and easy to understand and performs better too:
public void displayAffectedDistricts(Integer crimeCode) {
for (String eachDistrict : crimeTable.keySet()) {
if (crimeTable.get(eachDistrict).contains(crimeCode)) {
System.out.println("Code "+crimeCode+" found in district "+eachDistrict);
}
}
}
But even this can be improved upon, by using even less code and performs much better:
public void displayAffectedDistricts(Integer crimeCode) {
for (Map.Entry<String, ArrayList<Integer>> entry : crimeTable.entrySet()) {
if (entry.getValue().contains(crimeCode)) {
System.out.println("Code "+crimeCode+" found in district "+entry.getKey());
}
}
}
This last version avoids all the numerous calls to get(). It is the most elite way of coding it. Suggest this to your teacher, who by the way should find another job.
retainAll does return a boolean according to http://docs.oracle.com/javase/1.5.0/docs/api/java/util/AbstractCollection.html#retainAll(java.util.Collection)
retainAll returns true if this list changed as a result of the call.
So
!(searchCode.retainAll(crimeTable.get(eachDistrict)))
is equals to
crimeTable.get(eachDistrict).contains(crimeCode)
The javadoc of retainAll() says:
Returns:
true if this list changed as a result of the call
The method could also be designed to return the resulting list. However, this would obscure that the change was not performed on a copy, but the original list. Moreover, it is pretty handy to know if the set operation has caused a change.
retainAll is defined in java.util.List and return a boolean as return value. So this value can be used as the condition of the if statement.
Related
This is a bit of a fundamental question with an example project, as I'm still learning the best practices of Java 8 features.
Say I have an Order object, that cointains a List of OrderDetail. At the same time, OrderDetail contains a source and a destiny, along with quantity and product. OrderDetail also has an order (fk).
For this example, I will be moving Products from source to destiny, which both are ProductWarehouse objects with an availableStock property, which will be affected by the result of the Order.
Now, I need to update the availableStock for all the sources and destiny-ies. The stock should increase for destiny-ies and decrease for sources by the quantity of the OrderDetail. Since destiny and source are of the same type I can update them at the same time. The problem is that, of course, they are different properties.
My first thought was as follows:
//Map keeps record of the sum of products moved from the source. Key is the id the Product
HashMap<Integer, Double> quantities;
ArrayList<OrderDetail> orderDetails;
/**
* This could've been a single stream, but I divided it in two for readability here
* If not divided, the forEach() method from ArrayList<> might been enough though
*/
public void updateStock() {
List<ProductWarehouse> updated = new ArrayList<>();
orderDetails.stream()
.map(OrderDetail::getSource)
.forEach(src -> {
src.setAvailableStock(src.getAvailableStock - quantities.get(src.getProductId()));
updated.add(src);
});
orderDetails.stream()
.map(OrderDetail::getDestiny)
.forEach(dst -> {
dst.setAvailableStock(dst.getAvailableStock + quantities.get(dst.getProductId()));
updated.add(dst);
});
productWarehouseRepository.save(updated);
}
While this works, there is a problem: This is like the example in the Javadoc about "unnecesary side efects". It is not strictly the same, but given how similar they are makes me think I'm taking avoidable risks.
Another option I thought of is using peek() which has it's own implications since that method was thought mainly as a debugging tool, according to the javadoc.
HashMap<Integer, Double> quantities;
ArrayList<OrderDetail> orderDetails;
/**
* Either make it less readable and do one save or do one save for source and one for destiny
* I could also keep the updated list from before and addAll() the result of each .collect()
*/
public void updateStock() {
productWarehouseRepository.save(
orderDetails.stream()
.map(/*Same as above*/)
.peek(src -> {/*Same as above*/})
.collect(toList()) //static import
);
}
I've also read in a few blog post around that peek should be avoided. This, again, because the doc says what it's usage should be (mostly).
Hence the question: If I'd want to modify the properties of a Collection using the Stream API, what would be the best way to do so, following best-practices? For an example like this, is it better to use Iterable.forEach() or a simple enhanced for-loop? I honestly don't see where the side-effect might arise with operations like these using Stream, but that's probably due lack of experience and understanding of the API.
The code in this quesiton is just for example purposes, but if it's easier to understand with models or more info, I can add it.
You want to perform two different operations.
Update each ProductWarehouse object
Collect all ProductWarehouse objects into a list, for the save
Don’t mix the two operations.
public void updateStock() {
orderDetails.forEach(o -> {
ProductWarehouse src = o.getSource();
src.setAvailableStock(src.getAvailableStock()-quantities.get(src.getProductId()));
ProductWarehouse dst = o.getDestiny();
dst.setAvailableStock(dst.getAvailableStock()+quantities.get(dst.getProductId()));
});
List<ProductWarehouse> updated = orderDetails.stream()
.flatMap(o -> Stream.of(o.getSource(), o.getDestiny()))
.collect(Collectors.toList());
productWarehouseRepository.save(updated);
}
There is no point in trying to perform everything in a single stream operation at all costs. In the rare case where iterating the source twice is expense or not guaranteed to produce the same elements, you can iterate over the result list, e.g.
public void updateStock() {
List<ProductWarehouse> updated = orderDetails.stream()
.flatMap(o -> Stream.of(o.getSource(), o.getDestiny()))
.collect(Collectors.toList());
for(int ix = 0; ix < updated.size(); ) {
ProductWarehouse src = updated.get(ix++);
src.setAvailableStock(src.getAvailableStock()-quantities.get(src.getProductId()));
ProductWarehouse dst = updated.get(ix++);
dst.setAvailableStock(dst.getAvailableStock()+quantities.get(dst.getProductId()));
}
productWarehouseRepository.save(updated);
}
This second iteration is definitely cheap.
In both cases, forEach is a terminal action, so you should use map instead
If you want to avoid side-effects, then call your setter method within a map, then collect that to a list, which you add to the outer list.
updated.addAll(orderDetails.stream().map()...collect(Collectors.toList())
Related solution - How to add elements of a Java8 stream into an existing List
So for school I am making a program where we are creating a booking system where people can book a ticket for a movie that has a capacity of 10 people. People are allowed to change the time of the booking to the next day as long as the theater is not full for that day.
An array will be no good in this situation as I need to be able to remove an object from the array and make another free space in said array, and then add the removed object to a different Array for the different day. This part is suitable for an ArrayList but it has no size limit so I'm stuck with what the best solution is. Any ideas that I can look into?
Thanks
You can try the below, start from an array and convert it to list via Arrays.asList. Just note you could only use the set() method on the List, and not the add/remove methods as these would modify its size :
String[] array = {"a1","b2","c3"};
List<String> fixed = Arrays.asList(array);
fixed.set(0, "new_string"); // OK
fixed.add("aNewString"); // This will throw an exception
You can extend a class which already has the functionality you need and only override the methods required to implement new functionality (i.e. enforce a size limit).
Consider the following solution:
public class CappedList<T extends Object> extends ArrayList<T> {
private final int maxSize;
public CappedList(int maxSize) {
this.maxSize = maxSize;
}
#Override
public boolean add(T e) {
if (this.size() == this.maxSize) {
//Already reached max size, abort adding
throw new IllegalStateException("List is maxed out");
} else {
return super.add(e);
}
}
}
For completeness, you need to also override all add and addAll overloaded methods. You can then use this CappedList class instead of a simple ArrayList. A utility function isMaxedOut would be handy, in order to avoid exception handling:
public boolean isMaxedOut() {
return this.size() == this.maxSize;
}
It all depends how far you are in understanding of the language.
Let's say, first of all as a base logic that you might consider is, that you should find what is unique for 10 tickets. Obviously it's a group, which has own unique identifier and that's a date, so you have to concentrate on binding your tickets to a date groups, then you should control the amount what you are populating per date, you might need advanced control logic, rather than an usual variable that might do the job for you.
First of all, I would not store tickets in a different variables per day.
Let's go into details.
If you are obsessed by using only one specific property and that's ArrayList, I found nice answer here
But to have more precise population and access to the list later, for example to filter out specific date and see those tickets that are sold that day you should do it with a bit more structured logic, rather than a simple ArrayList(), may be you should even use different Type of variable that you should store that data in.
If you are on a bit advanced programming course, from the brief observation, for a simple task I might say that there is the way to use getter and setter methods to implement limitations and you could use any type of object to store that data, beside ArrayList.
Or you could write own functions that can control adding and removing elements from a list.
Or in more advanced way, if you have been introduced to a class concept, you could use class to define a ticket and then you could construct any logic behind it. This last one is the best way to go - to write easily readable, understandable and reusable code, or store specific data and control it the way you want.
working on my app I came across a behavior I have not expected or have previously encountered.
Consider this simple class:
public class A {
public long id;
public long date;
public List<Long> list;
/* constructors */
}
Now consider these 2 approaches to doing the same thing:
/* Approach #1 */
List<A> mList = new ArrayList<A>();
long mLong = ......;
A mA = new A(id, date);
if(!mList.contains(mA))
mList.add(mA);
mA = mList.get(mList.indexOf(mA));
if(!mA.list.contains(mLong))
mA.list.add(mLong);
/* Approach #2 */
List<A> mList = new ArrayList<A>();
long mLong = ......;
A mA = new A(id, date);
if(!mA.list.contains(mLong))
mA.list.add(mLong);
if(!mList.contains(mA))
mList.add(mA);
As you can see, approach #2 is more efficient than approach #1, and also much easier to understand.
Apparently, though, approach #2 does not work as expected.
The code actually runs in a loop, and it is expected that there could be various objects of type A inside mList, and an unknown (more than 1) amount of long values inside the list field of each object.
What really happens is that the first approach works fine, while the second approach results in a situation where there is always 1 long value inside list of every object (even when there should be more).
I personally can't see what could possibly cause it to work that way, which is why I'm here, asking for the answer to this 'mystery'. My wildest guess would say it's related to pointers, or maybe some default behavior of List<T> I'm not aware of.
With that being said, what could be the cause to this unexpected behavior?
P.S: I did try to run a search before posting, but I really had no idea what to search for, so I didn't find anything useful.
second approach results in a situation where there is always 1 long value inside list of every object (even when there should be more).
problem
A mA = new A(id, date);
if(!mA.list.contains(mLong))
As you can see you are not getting the reference of the class A from the mList and you are checking if the value long contains on the list that was just created which will only add one. So basically what you are doing is creating a new intance of class A with 1 long value on the list of long and add to the mList
on the other hand your first Approach is getting the instance of already added class A and checking if that long contains in the list if not then add it on the list long.
This is because mList.contains(mA) internally check for the equality of the objects by calling o1.equals(o2). The default implementation of equals() looks like this:
public boolean equals(Object o) {
return this == o;
}
Obviously the instances are not the same so you are adding a new instance every time. Override equals() in your class A to fix the problem. I guess the instances are the same if they have the same id?
public boolean equals(Object o) {
return this.mId == o.mId;
}
If there was a method mList.addIfAbsent(mA) (which returns either mA after adding it to the list, or the object which was already present and matches mA on equals), it would make your operation as trivial as
mA = mList.addIfAbsent(mA);
mA.list.addIfAbsent(mLong);
In your second example you obviously break this mechanism for the case when the mA equivalent is already in there. Basically, you change the definition of addIfAbsent(mA) to "adds mA to the list if no other object in the list is equal to it, and returns mA."
You can improve performance, achieving the identical result as your second example (sans the bug) like this:
int indOfOld = mList.indexOf(ma);
if (indOfOld != -1)
ma = mList.get(indOfOld);
else
mList.add(mA);
if(!mA.list.contains(mLong))
mA.list.add(mLong);
This won't cut your big-O complexity, but will at least make do with just one O(n) operation (compared with two in your working code).
BTW this may be obvious to you and everyone else; if so, excuse me—but if those lists get any larger than thousands of elements, you could get a significant improvement in performance if you used a HashSet, or even a LinkedHashSet if you care for the insertion order. In that case, you would just try to add an object, getting false if it was already there, and this would cost you just O(1) time. Then you would get(mA) from the set instead of your roundabout way with indexOf, also in O(1) time.
I've got an ArrayList called conveyorBelt, which stores orders that have been picked and placed on the conveyor belt. I've got another ArrayList called readyCollected which contains a list of orders that can be collected by the customer.
What I'm trying to do with the method I created is when a ordNum is entered, it returns true if the order is ready to be collected by the customer (thus removing the collected order from the readyCollected). If the order hasn't even being picked yet, then it returns false.
I was wondering is this the right way to write the method...
public boolean collectedOrder(int ordNum)
{
int index = 0;
Basket b = new Basket(index);
if(conveyorBelt.isEmpty()) {
return false;
}
else {
readyCollected.remove(b);
return true;
}
}
I'm a little confused since you're not using ordNum at all.
If you want to confirm operation of your code and generally increase the reliability of what you're writing, you should check out unit testing and the Java frameworks available for this.
You can solve this problem using an ArrayList, but I think that this is fundamentally the wrong way to think about the problem. An ArrayList is good for storing a complete sequence of data without gaps where you are only likely to add or remove elements at the very end. It's inefficient to remove elements at other positions, and if you have just one value at a high index, then you'll waste a lot of space filling in all lower positions with null values.
Instead, I'd suggest using a Map that associates order numbers with the particular order. This more naturally encodes what you want - every order number is a key associated with the order. Maps, and particularly HashMaps, have very fast lookups (expected constant time) and use (roughly) the same amount of space no matter how many keys there are. Moreover, the time to insert or remove an element from a HashMap is expected constant time, which is extremely fast.
As for your particular code, I agree with Brian Agnew on this one that you probably want to write some unit tests for it and find out why you're not using the ordNUm parameter. That said, I'd suggest reworking the system to use HashMap instead of ArrayList before doing this; the savings in time and code complexity will really pay off.
Based on your description, why isn't this sufficient :
public boolean collectedOrder(int ordNum) {
return (readyCollected.remove(ordNum) != null);
}
Why does the conveyorBelt ArrayList even need to be checked?
As already pointed out, you most likely need to be using ordNum.
Aside from that the best answer anyone can give with the code you've posted is "perhaps". Your logic certainly looks correct and ties in with what you've described, but whether it's doing what it should depends entirely on your implementation elsewhere.
As a general pointer (which may or may not be applicable in this instance) you should make sure your code deals with edge cases and incorrect values. So you might want to flag something's wrong if readyCollected.remove(b); returns false for instance, since that indicates that b wasn't in the list to remove.
As already pointed out, take a look at unit tests using JUnit for this type of thing. It's easy to use and writing thorough unit tests is a very good habit to get into.
I'm having problems with Iterator.remove() called on a HashSet.
I've a Set of time stamped objects. Before adding a new item to the Set, I loop through the set, identify an old version of that data object and remove it (before adding the new object). the timestamp is included in hashCode and equals(), but not equalsData().
for (Iterator<DataResult> i = allResults.iterator(); i.hasNext();)
{
DataResult oldData = i.next();
if (data.equalsData(oldData))
{
i.remove();
break;
}
}
allResults.add(data)
The odd thing is that i.remove() silently fails (no exception) for some of the items in the set. I've verified
The line i.remove() is actually called. I can call it from the debugger directly at the breakpoint in Eclipse and it still fails to change the state of Set
DataResult is an immutable object so it can't have changed after being added to the set originally.
The equals and hashCode() methods use #Override to ensure they are the correct methods. Unit tests verify these work.
This also fails if I just use a for statement and Set.remove instead. (e.g. loop through the items, find the item in the list, then call Set.remove(oldData) after the loop).
I've tested in JDK 5 and JDK 6.
I thought I must be missing something basic, but after spending some significant time on this my colleague and I are stumped. Any suggestions for things to check?
EDIT:
There have been questions - is DataResult truly immutable. Yes. There are no setters. And when the Date object is retrieved (which is a mutable object), it is done by creating a copy.
public Date getEntryTime()
{
return DateUtil.copyDate(entryTime);
}
public static Date copyDate(Date date)
{
return (date == null) ? null : new Date(date.getTime());
}
FURTHER EDIT (some time later):
For the record -- DataResult was not immutable! It referenced an object which had a hashcode which changed when persisted to the database (bad practice, I know). It turned out that if a DataResult was created with a transient subobject, and the subobject was persisted, the DataResult hashcode was changed.
Very subtle -- I looked at this many times and didn't notice the lack of immutability.
I was very curious about this one still, and wrote the following test:
import java.util.HashSet;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
public class HashCodeTest {
private int hashCode = 0;
#Override public int hashCode() {
return hashCode ++;
}
public static void main(String[] args) {
Set<HashCodeTest> set = new HashSet<HashCodeTest>();
set.add(new HashCodeTest());
System.out.println(set.size());
for (Iterator<HashCodeTest> iter = set.iterator();
iter.hasNext();) {
iter.next();
iter.remove();
}
System.out.println(set.size());
}
}
which results in:
1
1
If the hashCode() value of an object has changed since it was added to the HashSet, it seems to render the object unremovable.
I'm not sure if that's the problem you're running into, but it's something to look into if you decide to re-visit this.
Under the covers, HashSet uses HashMap, which calls HashMap.removeEntryForKey(Object) when either HashSet.remove(Object) or Iterator.remove() is called. This method uses both hashCode() and equals() to validate that it is removing the proper object from the collection.
If both Iterator.remove() and HashSet.remove(Object) are not working, then something is definitely wrong with your equals() or hashCode() methods. Posting the code for these would be helpful in diagnosis of your issue.
Are you absolutely certain that DataResult is immutable? What is the type of the timestamp? If it's a java.util.Date are you making copies of it when you're initializing the DataResult? Keep in mind that java.util.Date is mutable.
For instance:
Date timestamp = new Date();
DataResult d = new DataResult(timestamp);
System.out.println(d.getTimestamp());
timestamp.setTime(System.currentTimeMillis());
System.out.println(d.getTimestamp());
Would print two different times.
It would also help if you could post some source code.
You should all be careful of any Java Collection that fetches its children by hashcode, in the case that its child type's hashcode depends on its mutable state. An example:
HashSet<HashSet<?>> or HashSet<AbstaractSet<?>> or HashMap variant:
HashSet retrieves an item by its hashCode, but its item type
is a HashSet, and hashSet.hashCode depends on its item's state.
Code for that matter:
HashSet<HashSet<String>> coll = new HashSet<HashSet<String>>();
HashSet<String> set1 = new HashSet<String>();
set1.add("1");
coll.add(set1);
print(set1.hashCode()); //---> will output X
set1.add("2");
print(set1.hashCode()); //---> will output Y
coll.remove(set1) // WILL FAIL TO REMOVE (SILENTLY)
Reason being is HashSet's remove method uses HashMap and it identifies keys by hashCode, while AbstractSet's hashCode is dynamic and depends upon the mutable properties of itself.
Thanks for all the help. I suspect the problem must be with equals() and hashCode() as suggested by spencerk. I did check those in my debugger and with unit tests, but I've got to be missing something.
I ended up doing a workaround-- copying all the items except one to a new Set. For kicks, I used Apache Commons CollectionUtils.
Set<DataResult> tempResults = new HashSet<DataResult>();
CollectionUtils.select(allResults,
new Predicate()
{
public boolean evaluate(Object oldData)
{
return !data.equalsData((DataResult) oldData);
}
}
, tempResults);
allResults = tempResults;
I'm going to stop here-- too much work to simplify down to a simple test case. But the help is miuch appreciated.
It's almost certainly the case the hashcodes don't match for the old and new data that are "equals()". I've run into this kind of thing before and you essentially end up spewing hashcodes for every object and the string representation and trying to figure out why the mismatch is happening.
If you're comparing items pre/post database, sometimes it loses the nanoseconds (depending on your DB column type) which can cause hashcodes to change.
Have you tried something like
boolean removed = allResults.remove(oldData)
if (!removed) // COMPLAIN BITTERLY!
In other words, remove the object from the Set and break the loop. That won't cause the Iterator to complain. I don't think this is a long term solution but would probably give you some information about the hashCode, equals and equalsData methods
The Java HashSet has an issue in "remove()" method. Check the link below. I switched to TreeSet and it works fine. But I need the O(1) time complexity.
https://bugs.openjdk.java.net/browse/JDK-8154740
If there are two entries with the same data, only one of them is replaced... have you accounted for that? And just in case, have you tried another collection data structure that doesn't use a hashcode, say a List?
I'm not up to speed on my Java, but I know that you can't remove an item from a collection when you are iterating over that collection in .NET, although .NET will throw an exception if it catches this. Could this be the problem?