How to cast elements in an instance of List with streams in Java 8? - java-8

Given the code:
public Statement methodCallByName(MethodDeclaration method, String string) {
List<ExpressionStatement> expressions = method.getBody().statements().stream()
.filter(s -> s instanceof ExpressionStatement)
.map(ExpressionStatement.class::cast)
.collect(Collectors.toList());
return null;
}
I have the following error in Eclipse Oxygen:
Notice that statements() returns a List according to JDT docs.
What is wrong?

The problem is caused by statements() returning the raw type List (see also What is a raw type and why shouldn't we use it?).
Raw types may not only provoke unchecked operations, but also limit the applicability of type inference.
You may fix it with
public Statement methodCallByName(MethodDeclaration method, String string) {
List<?> statements = method.getBody().statements();
List<ExpressionStatement> expressions = statements.stream()
.filter(s -> s instanceof ExpressionStatement)
.map(ExpressionStatement.class::cast)
.collect(Collectors.toList());
// ...
return null;
}
The conversion from the raw type List to a list of unknown element type List<?>, is the only type safe conversion we can do here. Since you are going to check and cast the elements anyway, that’s no restriction.
But note that you should try to be consistent. Use either
.filter(s -> s instanceof ExpressionStatement)
.map(s -> (ExpressionStatement)s)
or
.filter(ExpressionStatement.class::isInstance)
.map(ExpressionStatement.class::cast)

Related

Iterate over Collected list in Java 8 GroupingBy

I have a List of Objects say List<Type1> that I have grouped using type.(using groupingBy)
Now I want to convert that Map> into Type2 that has both the list and the Id of that group.
class Type1{
int id;
int type;
String name;
}
class Type2{
int type;
List<Type1> type1List;
}
This is what I have written to achieve this:
myCustomList
.stream()
.collect(groupingBy(Type1::getType))
.entrySet()
.stream()
.map(type1Item -> new Type2() {
{
setType(type1Item.getKey());
setType1List(type1Item.getValue());
}
})
.collect(Collectors.toList());
This works perfectly. But I am trying to make the code even cleaner. Is there a way to avoid streaming this thing all over again and use some kind of flatmap to achieve this.
You can pass a finisher function to the collectingAndThen to get the work done after the formation of the initial map.
List<Type2> result = myCustomList.stream()
.collect(Collectors.collectingAndThen(Collectors.groupingBy(Type1::getType),
m -> m.entrySet().stream()
.map(e -> new Type2(e.getKey(), e.getValue()))
.collect(Collectors.toList())));
You should give Type2 a constructor of the form
Type2(int type, List<Type1> type1List) {
this.type = type;
this.type1List = type1List;
}
Then, you can write .map(type1Item -> new Type2(type1Item.getKey(), type1Item.getValue())) instead of
.map(type1Item -> new Type2() {
{
setType(type1Item.getKey());
setType1List(type1Item.getValue());
}
})
See also What is Double Brace initialization in Java?
In short, this creates a memory leak, as it creates a subclass of Type2 which captures the type1Item its entire lifetime.
But you can perform the conversion as part of the downstream collector of the groupingBy. This implies that you have to make the toList explicit, to combine it via collectingAndThen with the subsequent mapping:
Collection<Type2> collect = myCustomList
.stream()
.collect(groupingBy(Type1::getType,
collectingAndThen(toList(), l -> new Type2(l.get(0).getType(), l))))
.values();
If you really need a List, you can use
List<Type2> collect = myCustomList
.stream()
.collect(collectingAndThen(groupingBy(Type1::getType,
collectingAndThen(toList(), l -> new Type2(l.get(0).getType(), l))),
m -> new ArrayList<>(m.values())));
You can do as mentioned below:
type1.map( type1Item -> new Type2(
type1Item.getKey(), type1Item
)).collect(Collectors.toList());

HashMap null check in Merge Operation

Why HashMap merge is doing null check on value. HashMap supports null key and null values.So can some one please tell why null check on merge is required?
#Override
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
if (value == null)
throw new NullPointerException();
if (remappingFunction == null)
throw new NullPointerException();
Due to this I am unable to use Collectors.toMap(Function.identity(), this::get) to collect values in a Map
The behavior is mandated by the Map.merge contract:
Throws:
…
NullPointerException - if the specified key is null and this map does not support null keys or the value or remappingFunction is null
Note that using Map.merge for Collectors.toMap without a merge function is an implementation detail; it not only disallows null values, it does not provide the desired behavior for reporting duplicate keys, the Java 8 implementation wrongly reports one of the two values as key when there are duplicate keys.
In Java 9, the implementation has been completely rewritten, it does not use Map.merge anymore. But the new implementation is behavioral compatible, now having code explicitly throwing when the value is null. So the behavior of Collectors.toMap not accepting null values has been fixed in the code and is not an artifact of using Map.merge anymore. (Still speaking of the toMap collector without a merge function only.)
Unfortunately, the documentation does not tell.
Because internally for Collectors.toMap, Map#merge is used - you can't really do anything about it. Using the static Collectors.toMap is not an option (which by the way is documented to throw a NullPointerException).
But spinning a custom collector to be able to do what you want (which you have not shown) is not that complicated, here is an example:
Map<Integer, Integer> result = Arrays.asList(null, 1, 2, 3)
.stream()
.collect(
HashMap::new,
(map, i) -> {
map.put(i, i);
},
HashMap::putAll);
As a workaround for mentioned problems with null values in toMap and merge
you can try to use a custom collector in the following manner:
public static <T, R> Map<T, R> mergeTwoMaps(final Map<T, R> map1,
final Map<T, R> map2,
final BinaryOperator<R> mergeFunction) {
return Stream.of(map1, map2).flatMap(map -> map.entrySet().stream())
.collect(HashMap::new,
(accumulator, entry) -> {
R value = accumulator.containsKey(entry.getKey())
? mergeFunction.apply(accumulator.get(entry.getKey()), entry.getValue())
: entry.getValue();
accumulator.put(entry.getKey(), value);
},
HashMap::putAll);
}

Why do I get compilation error when doing flatmap() call?

Let's say I have the following object:
public class DaylyData {
private Date date;
private List<Integer> numersList;
// standard getters/setters
public Map<Integer, Date> getIntToDate() {
Map<Integer, Date> resultMap = new HashMap<>();
for(Integer number : getNumersList()) {
resultMap.put(number, getDate());
}
return resultMap;
}
Now, let say I have a list of DaylyData: List<DaylyData> resultList.
What will be the result of the following:
resultList.stream().flatMap(entity -> entity.getIntToDate());
If I assign the result of that to Stream<Map<Integer, Date>>, I am getting Type mismatch: cannot convert from Map<Integer,Date> to Stream<? extends Map<Integer,Date>>.
Thanks in advance.
The flatMap method is a special case of a map and is used for flattening nested Stream, Optional, and other monadic tools.
In your case, you are not providing a function that returns a Stream so it can't be used as flatMap param.
Your function will work fine with the standard map(), though:
resultList.stream()
.map(entity -> entity.getIntToDate()); // no compilation errors
You could make your example work by wrapping the result in a Stream instance but this does not give you any advantage over the example above - it makes sense to do that only for educational purposes:
resultList.stream()
.flatMap(entity -> Stream.of(entity.getIntToDate())); // no compilation error
It's "daily" not "dayly".

How to use lambda/Stream api to filter distinct elements by object Attribute/Property

I have a List of Object. Every object has a map with a key named "xyz". I want elements in the list which has unique value to that particular key.
I know we can do this easily with set/map but I'm particularly looking for lambda solution.
I thought this would work.
list.stream()
.filter(distinctByXyz(f -> f.getMap.get("xyz")))
.collect(Collectors.toList()));
I've a function to distinct them
private <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor){
Map<Object, Boolean> map = new ConcurrentHashMap<>();
return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
The problem is the function f.getMap() inside filter isnt working. Showing compilation error (Cannot resolve method)
You seem to have a few typos in your code, this should work:
list
.stream()
.filter(distinctByKey(f -> f.getMap().get("xyz")))
.collect(Collectors.toList());
You are using distinctByXyz when it should really be distinctByKey. Then f.getMap that should probably be f.getMap() and also you are slightly off with your parenthesis.

XmlReader.ReadContentAsObject always returns string type

According to the MSDN documentation, XMLWriter.WriteValue writes xsd type information to the xml for simple CLR types. Then XMLReader.ReadContentAsObject supposedly reads out the appropriately-typed object when the XML is parsed. However, this always seems to return a string object for me and the ValueType property of the XMLReader is string. I've tried inserting longs and DateTimes, but they always end up as strings. Any ideas what I'm doing wrong or is this a Windows Phone bug?
XML Writing Code
public void WriteXml(XmlWriter writer) {
// KeyValuePair<string, object> pair initialized previously
writer.WriteStartElement(pair.Key);
writer.WriteValue(pair.Value)
writer.WriteEndElement();
}
XML Parsing Code
public void ReadXml(XMLReader reader) {
while (reader.Read()) {
if (reader.NodeType == XmlNodeType.Element) {
Type T = reader.ValueType; // T is string
reader.ReadStartElement();
object o = reader.ReadContentAsObject(); // o is string
o = reader.ReadContentAs(T, null); // o is string
}
}
}
You need to use a schema file (XSD) so that the framework can infer the type of a node. Otherwise ValueType will always return System.String.
MSDN says:
If a validation error occurs while parsing the content and the reader is an XmlReader object created by the Create method, the reader returns the content as a string. In other words when a validation error or warning occurs, the content is considered to be untyped.
I was making this too difficult. My goal was to serialize a Dictionary with generic type (string, object) by traversing its KeyValuePairs , but that class doesn't seem to be serializeable using XmlSerializer. I just created another class with two public properties, Key and Value, so I could use XmlSerializer. When deserializing with XmlSerializer, the type of Value is restored as long as it is a supported CLR type.
public void WriteXml(XmlWriter writer) {
// KeyValuePair<string, object> pair initialized previously
writer.WriteStartElement("entry");
MyClass toSerialize = new MyClass(pair.Key, pair.Value);
XmlSerializer serializer = new XmlSerializer(typeof(MyClass));
serializer.Serialize(writer, toSerialize);
writer.WriteEndElement();
}

Resources