Ununderstood type variance using kotlin variance

Viewed 31

I'm using generics to not reuse code and I' running into a lack of understanding for type generics. I have a class Writer (java code from another library).

public class Writer<T>

A class FileWriter (java code from another library)

public class FileWriter<D>{

  FileWriter(Writer<D> writer){
    this.writer=writer
  }
  
  public void append(D datum){
  //Does something
  }
}

Now I'm initiating this in kotlin:

val writer = FileWriter(Writer(AGenratedJavaClassThatIMplementsSpecificRecord::class.java))

I can now call writer.append with AGenratedJavaClassThatIMplementsSpecificRecord(). It works just fine

I would like to pass this writer to a function.

funDoSomethingExtra(writer: FileWriter<in SpecificRecord>)

This gives me an error that I do not understand.

Type mismatch: inferred type is FileWriter<AGenratedJavaClassThatIMplementsSpecificRecord!>! but FileWriter<in SpecificRecord> was expected

Changing this to

funDoSomethingExtra(writer: FileWriter<out SpecificRecord>)

Makes writers.append give the error

Required Nothing, found AGenratedJavaClassThatIMplementsSpecificRecord.

Without the use of methods, all works fine. Which details Am I missing? It is probably something small,

Kind regards, Jelmew

1 Answers

This line of your code:

val writer = FileWriter(Writer(AGenratedJavaClassThatIMplementsSpecificRecord::class.java))

does not specify the type of the FileWriter, so it is inferred from your arguments to the constructors, so the type of writer is FileWriter<AGenratedJavaClassThatIMplementsSpecificRecord>.

Your signature funDoSomethingExtra(writer: FileWriter<in SpecificRecord>) is correct for calling a method on the writer that does something to a SpecificRecord or subtype of SpecificRecord. However, your

FileWriter<AGenratedJavaClassThatIMplementsSpecificRecord>

cannot be cast to a FileWriter<in SpecificRecord> because AGenratedJavaClassThatIMplementsSpecificRecord is a subtype of SpecificRecord, not a supertype. The compiler knows your file writer can consume AGenratedJavaClassThatIMplementsSpecificRecord, but it doesn't know it can consume the less-specific type SpecificRecord. There's the possibility of you calling some function of the subtype that doesn't exist in the supertype.

So to be able to pass your writer to this function, it needs to be a FileWriter<SpecificRecord> or FileWriter<in SpecificRecord>. You can't safely cast it after its type is already assigned, but you can assign it the proper type right at the declaration site instead of letting the compiler try to infer it:

val writer: FileWriter<SpecificRecord> = FileWriter(Writer(AGenratedJavaClassThatIMplementsSpecificRecord::class.java))
Related