Let's say you have the following interface:
interface DatabaseManager {
DatabaseConnection getConnection();
}
And an implementation:
class MySqlDatabaseManager implements DatabaseManager {
MySqlDatabaseConnection getConnection() { ... }
}
If I have code that accepts a DatabaseManager
, you can pass it a MySqlDatabaseManager
. If I call getConnection
, you will give me back a MySqlDatabaseConnection
, which I am fine with because I've written my code to work with any DatabaseConnection
.
This is an example of covariance.
The following code does not actually work in Java.
No consider this interface:
interface DataLoader {
Data loadData(MySqlDatabaseConnection connection);
}
And an implementation:
class SpecialDataLoader implements DataLoader {
/** NOT VALID JAVA **/
Data loadData(DatabaseConnection connection) { ... }
}
From a type theory point of view, there is no reason why this cannot work.
If I write code using a DataLoader
, you can pass it a SpecialDataLoader
. If I call loadData
, I will be passing it MySqlDatabaseConnection
, which is fine because SpecialDataLoader.loadData
is written to work with any DatabaseConnection
.
This is an example of contravariance.
As mentioned a couple of times, Java does not support contravariant arguments.