This must be a very basic misunderstanding on my part. It appears that assignments of parametric types are covariant without any indication on my part that that's what I'd want. I'm pasting Scala code for brevity, but it behaves identically in Java.
class Pet
class Fish extends Pet
class Guppy extends Fish
case class Box[T](value: T)
val guppyBox: Box[Fish] = Box(new Guppy()) // Mysteriously, this works.
An instance of type X can only be assigned to a val of type Y if Y is a supertype of X. In my case, this would require Box to be covariant, which I didn't say it is.
I wouldn't be too hung up on this, but it leads to the following odd, in my view, behavior:
def unboxFish(fish: Box[Fish]) = ???
unboxFish(Box(new Guppy())) // Oddly, compiles ok
val guppyBox2 = Box(new Guppy())
unboxFish(guppyBox2) // The compilation error I'd expect.
Any help greatly appreciated!
In Scala type inference goes not only from right to left
val guppyBox: Box[??] = Box[Something](...)
but also from left to right
val guppyBox: Box[Something] = Box[??](...)
(so it's bidirectional).
So in
val guppyBox: Box[Fish] = Box(new Guppy())
aka
val guppyBox: Box[Fish] = Box[??](new Guppy())
the type parameter ?? is inferred to be Fish.
when does it need explicit type when declare variable in scala?
But Box is now not covariant. Box[Guppy] is not a subtype of Box[Fish]
implicitly[Box[Guppy] <:< Box[Fish]] // doesn't compile
You can't assign a value of type Box[Guppy] to a variable of type Box[Fish]
val guppyBox: Box[Fish] = Box[Guppy](new Guppy()) // doesn't compile
Box isn't covariant. What's happening here is that Box(new Guppy()) needs to infer the type parameter for Box, and the inference depends on context. When you do
val guppyBox2 = Box(new Guppy())
it infers the type parameter as Guppy, but when you do
val guppyBox: Box[Fish] = Box(new Guppy())
the compiler knows that the RHS should be a Box[Fish], so it infers that the type parameter should be Fish instead of Guppy, as if you had written
val guppyBox: Box[Fish] = Box[Fish](new Guppy())
In your example, Box[Fish] is a subtype of Box[Pet] because Fish is a subtype of Pet. This is known as covariant type parameterization, and it is allowed in Scala and Java. This is why you are able to assign a Box[Guppy] to a variable of type Box[Fish].
Regarding the second part of the example, when you pass a Box[Guppy] as an argument to the unboxFish method, the compiler will automatically upcast it to a Box[Fish] as the method expects a Box[Fish] as an argument. However, when you try to pass guppyBox2 to the unboxFish method, the compiler doesn't know that it's a Box[Guppy] because the type of the variable is erased at runtime, so it can't upcast it to a Box[Fish]. This is why you get a compilation error.
Related
I've read some articles about Covariance, Contravariance, and Invariance in Java, but I'm confused about them.
I'm using Java 11, and I have a class hierarchy A => B => C (means that C is a subtype of B and A, and B is a subtype of A) and a class Container:
class Container<T> {
public final T t;
public Container(T t) {
this.t = t;
}
}
for example, if I define a function:
public Container<B> method(Container<B> param){
...
}
here is my confusion, why does the third line compile?
method(new Container<>(new A())); // ERROR
method(new Container<>(new B())); // OK
method(new Container<>(new C())); // OK Why ?, I make a correction, this compiles OK
if in Java Generics are invariant.
When I define something like this:
Container<B> conta = new Container<>(new A()); // ERROR, Its OK!
Container<B> contb = new Container<>(new B()); // OK, Its OK!
Container<B> contc = new Container<>(new C()); // Ok, why ? It's not valid, because they are invariant
Covariance is the ability to pass or specify a subtype when a supertype is expetced. If your C class extends B, then C is a child class of B. This relationship between C and B is also called is-a relationship, where an instance of C is also an instance of B. Therefore when your variable contc is expecting a B instance and you're passing new C(), since new C() is an instance of C and C instance is (also)-an instance of B, then the compiler allows the following writing:
Container<B> contc = new Container<>(new C());
Conversely, when you're writing
Container<B> conta = new Container<>(new A());
you're receiving an error because A is a supertype of B, there is no is-a relationship from A to B, but rather from B to A. This is because every instance of B is also an instance of A, but not every instance of A is an instance of B (To make a silly example, every thumb is a finger but not every finger is a thumb). A is a generalization of B; therefore it cannot appear where a B instance is expected.
Here there's a good article expanding the concept of covariance in java.
https://www.baeldung.com/java-covariant-return-type
The question's examples don't demonstrate the invariance of generics.
An example which does demonstrate this would be:
ArrayList<Object> ao = new ArrayList<String>(); // does not compile
(You might incorrectly expect the above to compile, because String is a subclass of Object.)
The question shows us different ways to construct Container<B> objects - some of which compile and others which do not, because of the inheritance hierarchy of A, B and C.
That diamond operator <> means that the created container is of type B in every case.
If you take the following example:
Container<B> contc = new Container<>(new C()); // compiles
And re-write it by populating the diamond with C, the you will see that the following does not compile:
Container<B> contc = new Container<C>(new C()); // does not compile
That will give you the same "incompatible types" compilation error as my ArrayList example.
One of the boons introduced with Java 7 is the so-called diamond operator <>.
And it has been with us for so long, that it's easy to forget that every time when diamond is being used while instantiating a generic class the compiler should infer the generic type from the context.
If we define a variable which will hold a reference to a list of Person objects like this:
List<Person> people = new ArrayList<>(); // effectively - ArrayList<Person>()
the compiler will infer the type of the ArrayList instance from the type of the variable people on the left.
In the Java language specification, the expression new ArrayList<>() is being described as a class instance creation expression and because it doesn't specify the generic type parameter and is used within a context, it should be classified as being a poly expression. A quote from the specification:
A class instance creation expression is a poly expression (§15.2) if
it uses the diamond form for type arguments to the class, and it
appears in an assignment context or an invocation context (§5.2,
§5.3).
I.e. when diamond <> is used with a generic class instantiation, the actual type will depend on the context in which it appears.
The three statements below represent the case of so-called assignment context. And all three instances Container will be inferred as being of type B.
Container<B> conta = new Container<>(new A()); // 1 - ERROR because `B t = new A()` is incorrect
Container<B> contb = new Container<>(new B()); // 2 - fine because `B t = new B()` is correct
Container<B> contc = new Container<>(new C()); // 3 - fine because `B t = new C()` is also correct
Since all instances of container are of type B and of parameter type expected by the contractor also will be B. I.e. can provide an instance of B or any of its subtypes. Therefore, in the case 1 we are getting a compilation error, meanwhile 2 and 3 (B and subtype of B) will compile correctly.
And it in't a violation of invariant behavior. Think about it this way: we can store in a List<Number> instances of Integer, Byte, Double, etc., that would not lead to any problem since they all can represent their super type Number. But the compiler will not allow assigning this list to any list that is not of type List<Number> because otherwise it would be impossible to ensure that this assignment is safe. And that is what the invariance means - we can assign only List<Number> to a variable of type List<Number> (but we are free to store any subtype of Number in it, it's safe).
As an example, let's consider that there's a setter method in the Container class:
public class Container<T> {
public T t;
public Container(T t) {
this.t = t;
}
public void setT(T t) {
this.t = t;
}
}
Now let's use it:
Container<B> contb = new Container<>(null); // to avoid any confusion initialy `t` will be assigned to `null`
contb.setT(new A()); // compilation error - because expected type is `B` or it's subtype
contb.setT(new B()); // fine
contb.setT(new C()); // fine because C is a subtype of B
When we deal with a class instance creation expression using diamond <>, which is passed to a method as an argument, the type will be inferred from the invocation context as the quote from the specification provided above states.
Because method() expects Container<B>, all instances above will be inferred as being of type B.
method(new Container<>(new A())); // Error
method(new Container<>(new B())); // OK - because `B t = new B()` is correct
method(new Container<>(new C())); // OK - because `B t = new C()` is also correct
Note
The important thing to mention that prior to Java 8 (i.e. with Java 7, because we are using diamond) the expression new Container<>(new C()) will be interpreted by the compiler as a standalone expression (i.e. the context will be ignored) creating an instance of Container<C>. It means your initial guess was somewhat correct: with Java 7 the below statement would not compile.
Container<B> contc = new Container<>(new C()); // Container<B> = Container<C> - is an illegal assignment
But Java 8 has introduced a feature called target types and poly expressions (i.e. expressions that appear within a context) that insures that context will always be taken into account by the type inference mechanism.
let's consider:
public class Text extends BinaryComparable
implements WritableComparable<BinaryComparable> {
We can see that Text is BinaryComparable.
Then, let's consider
#InterfaceAudience.Public
#InterfaceStability.Stable
public interface WritableComparable<T> extends Writable, Comparable<T> {
I have some class in Scala:
trait MyClass[A <: WritableComparable[A]] {
It is not possible to create
MyClass[Text] = new MyClass[Text]()
due to type mismatch. Why? After all, Text is BinaryComparable How to resolve it?
WritableComparable[BinaryComparable] is not the same as or a supertype of WritableComparable[Text], as WritableComparable is invariant in T (Java generics doesn't really have covariance or contravariance).
If WritingComparable had been declared as trait WritingComparable[-A], then it would compile.
Your last code snippet does not make much sense, though, as MyClass does not take 2 type parameters (as Luis Miguel Mejia Suarez mentioned). You shouldn't get a type mismatch error, it should tell you you have too many parameters. I think you meant only MyClass[Text].
Here's a question asking about invariance, contravariance, and covariance.
You can try to add one more type parameter
trait MyClass[B >: A, A <: WritableComparable[B]]
val mc: MyClass[BinaryComparable, Text] = new MyClass[BinaryComparable, Text] {}
On contrary to trait MyClass[A <: WritableComparable[_ >: A]] this doesn't produce illegal cyclic reference.
Alternatively you can define bounds in MyClass as
trait MyClass[B, A <: B with WritableComparable[B]]
val mc: MyClass[BinaryComparable, Text] = new MyClass[BinaryComparable, Text] {}
You can even exclude B with existential type (as #user proposed)
trait MyClass[A <: B with WritableComparable[B] forSome { type B }]
val mc: MyClass[Text] = new MyClass[Text] {}
Such existential types will be deprecated in Scala 3
http://dotty.epfl.ch/docs/reference/dropped-features/existential-types.html
When learning generics in Kotlin, I read in a book the following :
In general, a class or interface generic type may be prefixed with out if the class has functions that use it as a return type, or if the class has val properties of that type. You can’t, however, use out if the class has function parameters or var properties of that generic type.
I understand what the rule says, but i will be happy to understand (by examples) what may be without this rule (i.e there weren't constraint when using out when declaring a generic Class/Interface), and also why it isn't "dangerous" that the return type can be from type T and still class/Interface can contain out T.
Example where can't understand what is the problem that class property will behave as covariant:
class Pet{....}
class Dog:Pet{...}
class PetSomething <T : Pet>
{
T t;
public fun petDoSomething(T t)
{
.... // what can be the problem here?
}
}
class DogSomething
{
dogDoSomething()
{
d : Dog = Dog()
petDoSomething(d)
//what is the problem here???
}
}
In addition the book display the following code:
abstract class E<out T> (t:T) { val x = t }
and the code is being compiled although generic type is an input of constructor. Doesn't it break the rule?
You quoted: "You can’t, however, use out if the class has function parameters or var properties of that generic type."
A constructor is not a member function or property, so it is not subject to this rule. It is safe to use the type for a parameter at the site of the constructor, because the type is known when you are constructing it.
Consider these classes:
abstract class Pet
class Cat: Pet()
class Dog: Pet()
class PetOwner<out T: Pet>(val pet: T)
When you call the PetOwner constructor and pass in a Cat, the compiler knows you are constructing a PetOwner<out Cat> because it knows the value passed to the constructor satisfies the type of <out Cat>. It doesn't have to upcast Cat to Pet before the object is constructed. Then the constructed object can be safely upcast to PetOwner<Pet> because no T is ever going to be passed to the instance again. There is nothing unsafe that can happen, because no casting is done to the parameter.
Function parameters and var properties would be unsafe for an out type because the object is already constructed and might have been passed to some variable that has already upcast it to something else.
Imagine that the compiler let you define out T for a var property like this:
class PetOwner<out T: Pet>(var pet: T)
Then you could do this:
val catOwner: PetOwner<out Cat> = PetOwner(Cat())
val petOwner: PetOwner<out Pet> = catOwner
petOwner.pet = Dog()
val cat: Cat = catOwner.pet // ClassCastException!
The type safety rules prevent this scenario from being possible. But this isn't possible for a val constructor parameter. There is no way to pass the object to other variables and upcast its type in between passing the parameter to the constructor and having an instance that you can pass around.
The problem is this:
val x = DogSomething()
val y: PetSomething<Pet> = x // would be allowed by out
y.petDoSomething(Cat())
Note that petDoSomething on DogSomething only has to handle Dogs.
and the code is being compiled although generic type is an input of constructor. Doesn't it break the rule?
It doesn't, because the constructor isn't a member in the relevant sense; it couldn't be called on y above.
First lets clearfy what do we get by prefixing a type parameter with out keyword. consider the following class:
class MyList<out T: Number>{
private val list: MutableList<T> = mutableListOf()
operator fun get(index: Int) : T = list[index]
}
out keyword here makes MyList covariant in T, which essentially means you can do the following :
// note that type on left side of = is different than the one on right
val numberList: MyList<Number> = MyList<Int>()
if you remove the out keyword and try to compile again, you will get type mismatch error.
by prefixing the type parameter with out, you are basically declaring the type to be a producer of T's, in the example above MyList is a producer of Numbers.
which means no matter if you instantiate T as Int or Double or some other subtype of Number, you will always be able to get a Number from MyList (Because every subtype of Number is a Number). which also allows you to to do the following:
fun process(list: MyList<Number>) { // do something with every number }
fun main(){
val ints = MyList<Int>()
val doubles = MyList<Double>()
process(ints) // Int is a Number, go ahead and process them as Numbers
process(doubles) // Double is also a Number, no prob here
}
// if you remove out, you can only pass MyList<Number> to process
Now lets answer With out keyword why T should only be in return position? and what can happen without this constraint?, that is if MyList had a function taking T as parameter.
fun add(value: T) { list.add(T) } // MyList has this function
fun main() {
val numbers = getMyList() // numbers can be MyList<Int>, MyList<Double> or something else
numbers.add(someInt) // cant store Int, what if its MyList<Double> ( Int != Double)
numbers.add(someDouble) // cant do this, what if its MyList<Int>
}
// We dont know what type of MyList we going to get
fun getMyList(): MyList<Number>(){
return if(feelingGood) { MyList<Int> () }
else if(feelingOk> { MyList<Double> () }
else { MyList<SomeOtherSubType>() }
}
that is why constraint is required, its basically there to guarantee type safety.
as for abstract class E<out T> (t:T) { val x = t } being compiled, Kotlin In Action has following to say
Note that constructor parameters are in neither the in nor the out
position. Even if a type parameter is declared as out, you can still
use it in a constructor parameter. The variance protects the class
instance from misuse if you’re working with it as an instance of a
more generic type: you just can’t call the potentially dangerous
methods. The constructor isn’t a method that can be called later
(after an instance creation), and therefore it can’t be potentially
dangerous.
Consider this piece of code:
public class Main {
public static void main(String[] args) {
Cat<Integer> cat = new Cat();
Integer i= cat.meow();
cat.var = 6;
}
}
public class Cat<E> {
public E var;
public E meow() {
return null;
}
}
As per my understanding since I've not specified the type parameter on LHS, it would be taken as Object. And Cat should become Cat<Object> because for the variable declaration to make any sense T must translate to a Class/Interface reference. Is this a correct understanding of how it works? How is type parameter T handled in case of raw types?
I've discussed this on chat and got following explanation which is way over my head:
Generics works because types are erased. Consider T an erasure of
#0-capture-of-Object. When T isn't specified (rawtyped), it is #0-capture-of-(nothing)
What does #0-capture-of-(nothing) mean?
Side note: since generic types are implemented as compile time transformations it would be easier to understand them if one could see the final translated code. Does anyone know a way to see the translated code (not byte code) for a class?
No,
raw types are not as if they are paramterized with Object, nor are they like wildcard types (<?>).
For raw types, generics are turned off.
This code is compiles (with warnings):
Cat c1 = new Cat<String>();
Cat<Integer> c2 = c1;
This code does not:
Cat<? extends Object> c1 = new Cat<String>(); // btw: this is the same as Cat<?>
Cat<Integer> c2 = c1; // error
neither does this:
Cat<Object> c1 = new Cat();
Cat<Integer> c2 = c1; // error
As for the type of var:
the type of the field after compilation is whatever the upper-bound of the parameter is (Object if none is specified). But what does the compiler do if we access var?
Cat<String> c1 = ...
String c1Var = c1.var;
This code compiles without error, but what the compiler will actually compile is this:
Cat c1 = ...
String c1Var = (String) c1.var;
As you can see, var is always treated as a field of type Object, but with generics, the compiler automatically inserts type-safe casts. That's all. If you use raw types, you have to do it yourself. Either way, when you put a Cat that stores an integer in a Cat<String> variable, you will only get a runtime exception if you try to read var.
A quiz
Now look at the declaration of Collections.max. Why do you think the parameter is defined as T extends Object & Comparable<? super T>?
Answer encoded in rot13:
Fb gung nsgre pbzcvyngvba gur erghea glcr vf Bowrpg, abg Pbzcnenoyr. Guvf vf arrqrq sbe onpxjneqf pbzcngvovyvgl (gur zrgubq vgfrys vf byqre guna trarevpf).
Edit:
Here is another good example that I just stumbled upon:
class Foo<T> {
public <V> V bar(V v) { return v; }
}
//compiles
Foo<Object> foo = new Foo<Object>();
Integer i = foo.bar(1);
//compiles
Foo<?> foo = new Foo<String>();
Integer i = foo.bar(1);
// fails
Foo foo = new Foo();
Integer i = foo.bar(1); // error: Object cannot be converted to Integer
Using no parameters disables generics entirely.
This code is valid:
Cat c = new Cat<Integer>();
c is now of the Raw Type Cat.
This is not valid:
Cat<Object> c = new Cat<Integer>(); // Compiler error
So, it's not exactly the same. Though you can, after the first line, do things like:
c.var = 5;
System.out.println(c.var);
c.var = 1;
System.out.println(c.var);
c.var = "test";
System.out.println(c.var);
Outputs:
5
1
test
#Cephalopod has provided the correct answer, however I'd like to expand on that with some of my own explanation.
for the variable declaration to make any sense T must translate to a Class/Interface reference.
That is correct. Generics are a compile time transformation. Runtime system has no notion of abstract types. So before the class is loaded into memory the abstract type T must be replaced by an actual type reference.
Run the following code:
System.out.println(Cat.class.getMethod("meow").getReturnType());
System.out.println(Cat.class.getField("var").getType());
The output is:
class java.lang.Object
class java.lang.Object
The formal type parameter E has been replaced with Object.
Cat should become Cat<Object>
Wrong. Cat will stay Cat. Why? Look at the decompiled class file for Main:
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
Integer i = (Integer)cat.meow();
cat.var = Integer.valueOf(6);
}
}
The purpose of specifying formal type parameter with <> is to enable compiler to generate explicit casts.
When you say new Cat() it doesn't have to turn into anything, the compiler simply won't generate a cast and the method call would look like:
Integer i = cat.meow(); // No cast at all
Are generic type parameters converted to Object for raw types?
To clarify what is being asked here, the above questions means: Is E replaced with java.lang.Object if I don't specify anything when instantiating Cat.
Actually E would be replaced with java.lang.Object even if you specified <Integer> when instantiating Cat. The replacement/transformation is done at compile time while the instantiation is at runtime. How you use the type isn't going to change its class definition.
Generic types defined in objects like the
Cat c = new Cat<Integer>();
are only intended to provide the compiler with the chance to check that the types will match at runtime.
Generic types assigned in class definitions are retained in the compiled class.
public class Cat<T extends Number>{}
Or
public class Intcat extends Cat<Integer>{}
The runtime knows that the generic argument is bound by Number in the first case and is Integer in the first case.
I have no links to back this up, but I'd rather assume that c becomes raw type Cat, not Cat<Object>.
Raw types don't handle parameter T, which is why they are prone to errors.
javadoc says: A raw type is the name of a generic class or interface without any type arguments.
That chat log seems to mean exactly that, but in a confusing manner.
I actually Do not know How it is actually implemented in the bytecode But from my understanding Cat c = new Cat<Integer>(); Stores the new instance of Cat created by new Cat<Integer>() in the variable c. Now if you query c to know what is the type of var it will reply Integer and not Object because the instance that was created has a type of Integer.
Now If you execute c.var = "Text"; and query c to know what is the type of var. It would reply String. This does not means that by default it is converting <T> to Object. It means that c does not know what is the type of var.
I feel that is why the <?> wild card is used. Cat<?> c = new Cat<Integer>(); it would always convert <T> to Object. That is the reason why it is always advised not the use raw types for generics.
I think Cat c is a RAW type and could be considered as a "wildcard type" like Cat<?>. Since Cat<?> is the supertype of each type of Cat including Cat<Integer>, Cat c may take a new Cat<Integer> object.
This is also mentioned here: Interoperating with Legacy Code
"Most people's first instinct is that Collection really means Collection. However, as we saw earlier, it isn't safe to pass a Collection in a place where a Collection is required. It's more accurate to say that the type Collection denotes a collection of some unknown type, just like Collection."
...
"So raw types are very much like wildcard types, but they are not typechecked as stringently. This is a deliberate design decision, to allow generics to interoperate with pre-existing legacy code."
Say I have the following class defined in java:
public class A
{
public class B
{
}
public B[] someFunc() {...}
}
And I am trying to access it in scala as follows:
val array: Array[A#B] = a.someFunc()
The compiler gives me the following warning:
*type mismatch; found : Array[a.B] required: Array[A#B] Note: a.B <: A#B, but class Array is invariant in type T. You may wish to investigate a wildcard type such as `_ <: A#B*
I am not sure of the correct syntax I should use to get over this error. I tried using the following but it will not compile:
val array: Array[T <: A#B] = a.someFunc()
But I have found away to get over the problem by passing it to a function:
def test[T <: A#B](array: Array[T]) = ...
test(a.someFunc())
which compiles fine.
How would I achieve the correct type assignment without having to define this test function?
Thanks
Des
Your B inner class is not marked static, which means, from the scala point of view, it is not a member of the companion object (i.e. a static member) A#B but it is a member of the instantiated object itself a.B.
So you should just declare your value this way:
val array: Array[a.B] = a.someFunc()
Or let type inference do it:
val array = a.someFunc() // typed as Array[a.B]
Edit: in case you do not have a reference to a lying around, usually you can just upcast a.B to A#B. Array is invariant, so that won't work, but there is a ruse: wrap it in something covariant.
val array: IndexedSeq[A#B] = a.someFunc()
You can use that one like an array, it doesn't actually convert anything (it is a WrappedArray), and you can call toArray on it if you really want to get an Array[A#B].
That is just a trick to avoid doing a.someFunc().asInstanceOf[Array[A#B]], but of course you could directly do that.