First of, I have read many explanations on SO and blogs about covariance and contravariance and a big thanks goes out to Eric Lippert for producing such a great series on Co
Covariance and Contravariance are not things you can observe when instancing classes. Thus it is wrong to speak about one of them when looking at a simple class instantiation, like in your example:
Animal someAnimal = new Giraffe(); //covariant operation
These terms do not classify operations. The terms Covariance, Contravariance and Invariance describe the relationship between certain aspects of classes and their subclasses.
We generally regard the following aspects, when talking about Cov., Contrav. and Inv.:
Let us have a look at a few examples to get a better understanding of the terms.
class T
class T2 extends T
//Covariance: The return types of the method "method" have the same
//direction of inheritance as the classes A and B.
class A { T method() }
class B extends A { T2 method() }
//Contravariance: The parameter types of the method "method" have a
//direction of inheritance opposite to the one of the classes A and B.
class A { method(T2 t) }
class B { method(T t) }
In both cases, "method" gets overridden! Further, the above examples are the only legal occurrences of Cov. and Contrav. in object oriented languages.:
Let us have a look at some counter examples to better understand the above list:
//Covariance of return types: OK
class Monkey { Monkey clone() }
class Human extends Monkey { Human clone() }
Monkey m = new Human();
Monkey m2 = m.clone(); //You get a Human instance, which is ok,
//since a Human is-a Monkey.
//Contravariance of return types: NOT OK
class Fruit
class Orange extends Fruit
class KitchenRobot { Orange make() }
class Mixer extends KitchenRobot { Fruit make() }
KitchenRobot kr = new Mixer();
Orange o = kr.make(); //Orange expected, but got a fruit (too general!)
//Contravariance of parameter types: OK
class Food
class FastFood extends Food
class Person { eat(FastFood food) }
class FatPerson extends Person { eat(Food food) }
Person p = new FatPerson();
p.eat(new FastFood()); //No problem: FastFood is-a Food, which FatPerson eats.
//Covariance of parameter types: NOT OK
class Person { eat(Food food) }
class FatPerson extends Person { eat(FastFood food) }
Person p = new FatPerson();
p.eat(new Food()); //Oops! FastFood expected, but got Food (too general).
This topic is so sophisticated, that I could go on for a very long time. I advise you to check Cov. and Contrav. of Generics by yourself. Further, you need to know how dynamic binding works to fully understand the examples (which methods get exactly called).
The terms arose from the Liskov substitution principle, which defines necessary criteria for modelling a data type as a sub type of another one. You might also want to investigate it.