Difference in add and addAll I do not understand


#1

Hi,

sorry for asking a maybe stupid question but I aim fairly new to kotlin. I have this code which is accepted by IntelliJ (by the way version 2018.1.3 with kotlin 1.2.41-release-IJ2018.1-1)

val data = mutableListOf<XYChart.Data<LocalDate,Number>>()
P.eep.forEach {data.add(XYChart.Data(it.startDate,it.rate))}

where eep is declared as List and RP is a data class where startDate is of type java.time.LocaDate and rate is of type Double.

What I originally wanted to write is

val data = mutableListOf<XYChart.Data<LocalDate,Number>>()
data.addAll(P.eep.map {XYChart.Data(it.startDate,it.rate)})

but this is not accepted. The error is

Error:(178, 49) Kotlin: Type inference failed. Expected type mismatch: inferred type is List<XYChart.Data<LocalDate!, Double!>> but Collection<XYChart.Data<LocalDate, Number>> was expected

and I cannot understand why because for me adding all the elements or each one by one looks to be the same. Can you please enlighten me?

Thank you very much,
claudio


#2
  1. Are the fields startDate and rate nullable in class RP?
  2. Is eep defined as List<RP> or List<RP!>?

#3

Hi,

here a short self contained kotlin program showing the issue:

package ch.claudio.stuff

import javafx.scene.chart.XYChart
import java.time.LocalDate

data class RP(val startDate:LocalDate,val rate:Double)

fun main(args: Array<String>) {
    val eep=listOf(RP(LocalDate.of(2018, 5, 24), 1.0))
    val data=mutableListOf<XYChart.Data<LocalDate,Number>>()
    eep.forEach {data.add(XYChart.Data(it.startDate,it.rate))}
    data.addAll(eep.map {XYChart.Data(it.startDate,it.rate)})
}

claudio


#4

Please use β€œ```” at the beginning and end of code you post. That way it gets properly formatted.

So the problem is kind of complex. It has to do with how Kotlin handles Nullability in combination with Java types.
Normally a type can either be nullable ( Foo? ) or not ( Foo ). But there is a third option, which gets used for values returned by java code which can be either ( Foo! ).
You define data to be of type MutableList<Data<LocalDate, Number>>. When you try to call addAll it expects an argument of type Collection<Data<LocalDate, Number>> but instead you pass a list of type Collection<Data<LocalDate!, Number!>>. That is the reason it fails.
To solve this you could use a cast it explicitly

data.addAll(eep.map {
	@Suppress("UNCHECKED_CAST") 
	XYChart.Data(it.startDate, it.rate) as XYChart.Data<LocalDate, Number> 
})

So now the only question is why can you call add with an argument of type Data<LocalDate!, Number! but not add all? I guess this is due to the way the compiler handles generics.
This also does not work.

eep.forEach { data.add(XYChart.Data(it.startDate, it.rate)) } // this works

eep.forEach {
	val temp= XYChart.Data(it.startDate, it.rate)
	data.add(temp)  // this fails
}

In all I think this qualifies as a bug. And even if not the handling of Generics with unspecified nullability seems like it could be improved.


#5

You can specify the Data type so it generates a XYChart.Data<LocalDate, Number> instead of XYChart.Data<LocalDate!, Number!>:

data.addAll(eep.map { XYChart.Data<LocalDate, Number>(it.startDate, it.rate) })

#6

Woops yeah, definitely better than the cast I suggested. This does still not explain why addAll does not work the same way that add does.


#7

I think add works because it receives a specific type, so the compiler infer the type from java to be XYChart.Data<LocalDate, Number>.
But with addAll the map function is transforming it from java first and now it is XYChart.Data<LocalDate!, Number!>, then it is used as parameter for the addAll which can’t accept the, now specifc, type(with !).


#8

Hi,

thank you all for the solution and the explanation.

claudio