ClassCastException when collecting to set - java

I'm experiencing a ClassCastException with the following piece of code.
Set<Long> purchaseIds = confirmationCodeToPurchase
.entrySet()
.stream()
.map(Map.Entry::getValue)
.map(purchase -> (Long)purchase.getPurchaseId())
.collect(Collectors.toSet()))
confirmationCodeToPurchase is a map from a confirmation code (String) to a Purchase type.
I'm extracting just the values of the map (the purchases), getting the purchaseId of each, and putting them into a set.
I get this error:
java.lang.ClassCastException: java.lang.Long cannot be cast to Purchase
and the line at which it errors is the collection line. .collect(Collectors.toSet()))

From your error description, it looks like your method purchase.getPurchaseId() returns an object that is not of type Long. That's why you get a class cast exception. Instead of casting, you can build the Long object from the return value of this method, assuming it is either String or an integer type.
Replace
.map(purchase -> (Long)purchase.getPurchaseId())
with
.map(purchase -> Long.valueOf(purchase.getPurchaseId()))

Related

Spring - jdbcTemplate failed to get Pair object

Can I get Pair as an output for jdbcTemplate? I tried the following (which work for separate Integers)
Pair<Integer, Integer> result = jdbcTemplate.queryForObject(GET_PAIR, new Object[]{}, Pair.class);
But it returns exception
org.springframework.jdbc.IncorrectResultSetColumnCountException: Incorrect column count: expected 1, actual 2
at org.springframework.jdbc.core.SingleColumnRowMapper.mapRow(SingleColumnRowMapper.java:92)
at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:93)
at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:60)
at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:703)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:639)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:690)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:722)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:732)
at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:800)
Tried with org.apache.commons.lang3.tuple.Pair
queryForObject requires one result and just one result. So when you get EmptyResultDataAccessException it means that query for Object didn't find anything.
However I still don't think it will work, even if you get a result. A better way is to use a RowMapper.
jdbcTemplate.query(GET_PAIR, (rs, i) -> new Pair(rs.getInt(1), rs.getInt(2)))
Which will allow you to map the elements to a pair (this will return a list, one for each row).

How to translate a Optional mapping to Stream mapping in Java

I have this current logic:
List<String> priceUnitCodes = ofNullable(product.getProductPrices())
.map(ProductPrices::getProductPrices)
.flatMap(productPrices -> productPrices.stream()) // << error highlight
.map(ProductPrice::getPriceBase)
.map(PriceBase::getPriceUnit)
.map(UniversalType::getCode)
.collect(Collectors.toList());
Where in IntelliJ the flatMap part is highlighted and shows the following error hint:
no instance(s) of type variable(s) U exist so that Stream<ProductPrice> conforms to Optional<? extends U>
I know that Optionals and Stream are two different things but I wonder if there is a way to combine them so I can follow up an Optional<List<?>> with a Stream afterwards.
If you're on Java 9+, you can use Optional.stream, followed by flatMap:
ofNullable(product.getProductPrices())
.map(ProductPrices::getProductPrices)
.stream()
.flatMap(Collection::stream) //assuming getProductPrices returns a Collection
...
Optional.stream returns an empty stream if the optional is empty.
Since you are starting with an Optional, you have to decide what to return when that Optional is empty.
One way is to put the Stream pipeline inside the Optional's map:
List<String> priceUnitCodes = ofNullable(product.getProductPrices())
.map(ProductPrices::getProductPrices)
.map(productPrices -> productPrices.stream()
.map(ProductPrice::getPriceBase)
.map(PriceBase::getPriceUnit)
.map(UniversalType::getCode)
.collect(Collectors.toList())
.orElse(null);
Or course, if the map operations inside the Stream pipeline may return null, additional changes will be required (to avoid NullPointerException).
On the other hand, if they can never return null, they can be chained into a single map:
List<String> priceUnitCodes = ofNullable(product.getProductPrices())
.map(ProductPrices::getProductPrices)
.map(productPrices -> productPrices.stream()
.map(pp -> pp.getPriceBase().getPriceUnit().getCode())
.collect(Collectors.toList())
.orElse(null);
An alternate solution would be to get the value of the Optional using orElse and this can be done without upgrading to Java-9. It would look like:
List<String> priceUnitCodes = Optional.ofNullable(product.getProductPrices())
.map(ProductPrices::getProductPrices)
.orElse(Collections.emptyList()) // get the value from Optional
.stream()
.map(ProductPrice::getPriceBase)
.map(PriceBase::getPriceUnit)
.map(UniversalType::getCode)
.collect(Collectors.toList());

Updating list of POJOs using Java Stream API

I have the following POJO:
public class Order {
private String name;
private String status;
private BigDecimal total;
// getters, setters and ctors down here
}
I am looping through a List<Order> and trying to update all their status fields to a value of "ORDERED". The old (pre Streaming API) way of doing this was:
for (Order order : orders) {
order.setStatus("ORDERED");
}
I'm trying to figure out the Java 8 ("Streaming") way of accomplishing the same thing. My best attempt thus far:
orders.stream().map(order -> order.setStatus("H"));
Produces a compiler error:
"Incompatible types. Required List but 'map' was inferred to Stream: no instance(s) of type variable(s) R exist so that Stream conforms to List"
Any ideas where I'm going awry?
Use forEach:
orders.forEach(order -> order.setStatus("H"));
You do not want to use Stream.map() because it requires a return value which replaces the original value in the stream. You are also missing a terminal operation in your stream, so even if you fix that by returning the original value it wont work. Stream.forEach() is a terminal operation you can use for this.
To update each object in your list you can just use orders.forEach(). This is the same as orders.stream().forEach().
orders.forEach(o -> o.setStatus("H"));
If you want to update only some values of your List you can use Stream.filter() before:
orders.stream()
.filter(o -> "ABC".equals(o.getName())
.forEach(o -> o.setStatus("H"));

Cast int to long in Java

This questions has many answers and I have seen them. I have code that has been working but today suddenly it starting throwing java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long
for the line
return null == input.get(keyName) ? 0L : (long) input.get(keyName);
Error is coming from (long) input.get(keyName). I wonder why it starting breaking all of sudden. (long) input.get(keyName) this looks good to me.
I thought of doing ((Integer) input.get(keyName)).longValue() but was getting java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Integer as map sometimes contains long values. Any suggestions
Stacktrace:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long
at accountservice.adapter.batch.testJob.SyncDriverPartitioner.getLongValueFromMap(SyncDriverPartitioner.java:78) ~[classes/:?]
at accountservice.adapter.batch.testJob.SyncDriverPartitioner.partition(SyncDriverPartitioner.java:47) ~[classes/:?]
at accountservice.adapter.batch.testJob.SyncDriverPartitioner$$FastClassBySpringCGLIB$$6f3315e4.invoke(<generated>) ~[classes/:?]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.3.18.RELEASE.jar:4.3.18.RELEASE]
You can take advantage of the fact that all numeric primitive wrappers extend java.lang.Number:
return null == input.get(keyName) ? 0L : ((Number) input.get(keyName)).longValue();
As to why it started giving errors suddenly; really the only likely reason is that up until it started failing, you were always putting java.lang.Long objects in the input Map, and it changed so you're also putting java.lang.Integer in them now.
It's quite easy for that to happen with auto-boxing and numeric constants:
long v = 42;
input.put("key", v); // Puts a java.lang.Long in the map
input.put("key", 42); // Puts a java.lang.Integer in the map
input.put("key", 42L); // Puts a java.lang.Long in the map
You can avoid it by declare your Map type-safely (Map<String, Long>). If you do that, input.put("key", 42) will give a compile-time error.
Rather than casting an int to a long I would suggest:
return null == input.get(keyName) ? 0L : Integer.toUnsignedLong(input.get(keyName))
At least this way you should get some more information about why it cannot be converted to a long rather than just a ClassCastException
Answer Update
Based on your comments I guess you are going to have to check type of the entry in Map before processing it, I would suggest the following:
Object keyValue = input.get(keyName);
if (null == keyValue) return 0L;
String valueType = keyValue.getClass().getTypeName();
if (valueType.equals(Long.class.getTypeName())) {
return (long) keyValue;
}
if (valueType.equals(Integer.class.getTypeName())) {
return Integer.toUnsignedLong((int) keyValue);
}
throw new TypeMismatchException(String.format("Type '%s' is not supported...", valueType));
This will let you define different operations for different types of entry, it is extendable to any types you want to support. You can tweak the exception thrown as well to provide you relevant information.
The snippet above shows you the actual type so that you can extend your code to support that type, or work out why something of that type got into your map.
It should be noted that it would probably be better to do this when data is entered into the map, not when it's taken out of the map. In which case you should be able to change your map to a type . It's always better to sanitise your imputes than trying to deal with a mishmash of mixed data types at a later date.

Freemarker: iterating nested list in hash

I want to iterate a List nested in a Map, the data structure is like:
Map<Integer, List<Integer>> groups = new TreeMap<>()
// Some code else to put values into groups ...
Freemarker template:
<#list groups?keys as groupKey>
${groupKey} // It's OK here.
<#list groups[groupKey] as item> // Exception threw here, detail message is pasted below
${item}
</#list>
</#list>
Detail exception message:
FreeMarker template error:
For "...[...]" left-hand operand: Expected a sequence or string or something automatically convertible to string (number, date or boolean), but this evaluated to an extended_hash (wrapper: f.t.SimpleHash):
==> groups
So, what is the problem?
P.S.
I have tried groups.get(groupKey) instead of groups[groupKey], it throws a new Exception stack:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
java.lang.String.compareTo(String.java:108)
java.util.TreeMap.getEntry(TreeMap.java:346)
java.util.TreeMap.get(TreeMap.java:273)
freemarker.template.SimpleHash.get(SimpleHash.java:160)
freemarker.core.Dot._eval(Dot.java:40)
freemarker.core.Expression.eval(Expression.java:76)
The problem in the original question is that FTL's hash type is not like Map. It's a collection of "variables", that is, the keys must be String-s. (Even that ?keys works is a glitch in BeansWrapper... though now it comes handy.) Since the key is a number, FTL assumes that you want to get an item from a sequence (a List or array), or that you want to get a character from a string, hence the original error message.
The solution is using the Java API-s, like get in Dev-an's answer. (On the long term FTL meant to introduce the map type, so all this problems with non-string keys will end, but who knows when that will be...)
Update: Since 2.3.22 there's ?api to access the Java API of objects, like myMap?api.get(nonStringKey). However, it's by default not allowed (see the api_builtin_enabled configuration setting and more in the Manual: http://freemarker.org/docs/ref_builtins_expert.html#ref_buitin_api_and_has_api). Also note that as Java maps are particular about the numerical type, if the key is not an Integer coming from a Java, you have to use myMap?api.get(myNumericalKey?int).
Try the following:
<#list groups?keys as groupKey>
${groupKey}
<#list groups.get(groupKey) as item>
${item}
</#list>
</#list>

Categories