В Java все переменные, которые вы объявляете, на самом деле являются «ссылками» на объекты (или примитивы), а не самими объектами.
При попытке выполнить один метод объекта , ссылка просит живой объект выполнить этот метод. Но если ссылка ссылается на NULL (ничего, нуль, void, nada), то нет способа, которым метод будет выполнен. Тогда runtime сообщит вам об этом, выбросив исключение NullPointerException.
Ваша ссылка «указывает» на нуль, таким образом, «Null -> Pointer».
Объект живет в памяти виртуальной машины пространство и единственный способ доступа к нему - использовать ссылки this
. Возьмем этот пример:
public class Some {
private int id;
public int getId(){
return this.id;
}
public setId( int newId ) {
this.id = newId;
}
}
И в другом месте вашего кода:
Some reference = new Some(); // Point to a new object of type Some()
Some otherReference = null; // Initiallly this points to NULL
reference.setId( 1 ); // Execute setId method, now private var id is 1
System.out.println( reference.getId() ); // Prints 1 to the console
otherReference = reference // Now they both point to the only object.
reference = null; // "reference" now point to null.
// But "otherReference" still point to the "real" object so this print 1 too...
System.out.println( otherReference.getId() );
// Guess what will happen
System.out.println( reference.getId() ); // :S Throws NullPointerException because "reference" is pointing to NULL remember...
Это важно знать - когда больше нет ссылок на объект (в пример выше, когда reference
и otherReference
оба указывают на null), тогда объект «недоступен». Мы не можем работать с ним, поэтому этот объект готов к сбору мусора, и в какой-то момент VM освободит память, используемую этим объектом, и выделит другую.
Допустим, у вас есть класс Person и класс, который вытекает из него, Учитель. У вас есть некоторые операции, которые принимают аргумент IEnumerable<Person>
. В вашем классе School у вас есть метод, который возвращает IEnumerable<Teacher>
. Ковариация позволяет вам напрямую использовать этот результат для методов, которые принимают IEnumerable<Person>
, заменяя более производный тип для менее производного (более общего) типа. Контравариантность, контр-интуитивно, позволяет использовать более общий тип, где указан более производный тип. См. Также https://msdn.microsoft.com/en-us/library/dd799517.aspx
public class Person
{
public string Name { get; set; }
}
public class Teacher : Person { }
public class MailingList
{
public void Add(IEnumerable<out Person> people) { ... }
}
public class School
{
public IEnumerable<Teacher> GetTeachers() { ... }
}
public class PersonNameComparer : IComparer<Person>
{
public int Compare(Person a, Person b)
{
if (a == null) return b == null ? 0 : -1;
return b == null ? 1 : Compare(a,b);
}
private int Compare(string a, string b)
{
if (a == null) return b == null ? 0 : -1;
return b == null ? 1 : a.CompareTo(b);
}
}
...
var teachers = school.GetTeachers();
var mailingList = new MailingList();
// Add() is covariant, we can use a more derived type
mailingList.Add(teachers);
// the Set<T> constructor uses a contravariant interface, IComparer<T>,
// we can use a more generic type than required. See https://msdn.microsoft.com/en-us/library/8ehhxeaf.aspx for declaration syntax
var teacherSet = new SortedSet<Teachers>(teachers, new PersonNameComparer());
Вот что я собрал, чтобы помочь понять разницу
public interface ICovariant<out T> { }
public interface IContravariant<in T> { }
public class Covariant<T> : ICovariant<T> { }
public class Contravariant<T> : IContravariant<T> { }
public class Fruit { }
public class Apple : Fruit { }
public class TheInsAndOuts
{
public void Covariance()
{
ICovariant<Fruit> fruit = new Covariant<Fruit>();
ICovariant<Apple> apple = new Covariant<Apple>();
Covariant(fruit);
Covariant(apple); //apple is being upcasted to fruit, without the out keyword this will not compile
}
public void Contravariance()
{
IContravariant<Fruit> fruit = new Contravariant<Fruit>();
IContravariant<Apple> apple = new Contravariant<Apple>();
Contravariant(fruit); //fruit is being downcasted to apple, without the in keyword this will not compile
Contravariant(apple);
}
public void Covariant(ICovariant<Fruit> fruit) { }
public void Contravariant(IContravariant<Apple> apple) { }
}
tldr
ICovariant<Fruit> apple = new Covariant<Apple>(); //because it's covariant
IContravariant<Apple> fruit = new Contravariant<Fruit>(); //because it's contravariant
Из MSDN
В следующем примере кода показана поддержка ковариации и контравариантности для групп методов
static object GetObject() { return null; } static void SetObject(object obj) { } static string GetString() { return ""; } static void SetString(string str) { } static void Test() { // Covariance. A delegate specifies a return type as object, // but you can assign a method that returns a string. Func<object> del = GetString; // Contravariance. A delegate specifies a parameter type as string, // but you can assign a method that takes an object. Action<string> del2 = SetObject; }
Ключи ввода и вывода управляют правилами литья компилятора для интерфейсов и делегатов с общими параметрами:
interface IInvariant<T> {
// This interface can not be implicitly cast AT ALL
// Used for non-readonly collections
IList<T> GetList { get; }
// Used when T is used as both argument *and* return type
T Method(T argument);
}//interface
interface ICovariant<out T> {
// This interface can be implicitly cast to LESS DERIVED (upcasting)
// Used for readonly collections
IEnumerable<T> GetList { get; }
// Used when T is used as return type
T Method();
}//interface
interface IContravariant<in T> {
// This interface can be implicitly cast to MORE DERIVED (downcasting)
// Usually means T is used as argument
void Method(T argument);
}//interface
class Casting {
IInvariant<Animal> invariantAnimal;
ICovariant<Animal> covariantAnimal;
IContravariant<Animal> contravariantAnimal;
IInvariant<Fish> invariantFish;
ICovariant<Fish> covariantFish;
IContravariant<Fish> contravariantFish;
public void Go() {
// NOT ALLOWED invariants do *not* allow implicit casting:
invariantAnimal = invariantFish;
invariantFish = invariantAnimal; // NOT ALLOWED
// ALLOWED covariants *allow* implicit upcasting:
covariantAnimal = covariantFish;
// NOT ALLOWED covariants do *not* allow implicit downcasting:
covariantFish = covariantAnimal;
// NOT ALLOWED contravariants do *not* allow implicit upcasting:
contravariantAnimal = contravariantFish;
// ALLOWED contravariants *allow* implicit downcasting
contravariantFish = contravariantAnimal;
}//method
}//class
// .NET Framework Examples:
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable { }
public interface IEnumerable<out T> : IEnumerable { }
class Delegates {
// When T is used as both "in" (argument) and "out" (return value)
delegate T Invariant<T>(T argument);
// When T is used as "out" (return value) only
delegate T Covariant<out T>();
// When T is used as "in" (argument) only
delegate void Contravariant<in T>(T argument);
// Confusing
delegate T CovariantBoth<out T>(T argument);
// Confusing
delegate T ContravariantBoth<in T>(T argument);
// From .NET Framework:
public delegate void Action<in T>(T obj);
public delegate TResult Func<in T, out TResult>(T arg);
}//class
// Contravariance
interface IGobbler<in T> {
void gobble(T t);
}
// Since a QuadrupedGobbler can gobble any four-footed
// creature, it is OK to treat it as a donkey gobbler.
IGobbler<Donkey> dg = new QuadrupedGobbler();
dg.gobble(MyDonkey());
// Covariance
interface ISpewer<out T> {
T spew();
}
// A MouseSpewer obviously spews rodents (all mice are
// rodents), so we can treat it as a rodent spewer.
ISpewer<Rodent> rs = new MouseSpewer();
Rodent r = rs.spew();
Для полноты ...
// Invariance
interface IHat<T> {
void hide(T t);
T pull();
}
// A RabbitHat…
IHat<Rabbit> rHat = RabbitHat();
// …cannot be treated covariantly as a mammal hat…
IHat<Mammal> mHat = rHat; // Compiler error
// …because…
mHat.hide(new Dolphin()); // Hide a dolphin in a rabbit hat??
// It also cannot be treated contravariantly as a cottontail hat…
IHat<CottonTail> cHat = rHat; // Compiler error
// …because…
rHat.hide(new MarshRabbit());
cHat.pull(); // Pull a marsh rabbit out of a cottontail hat??
void feed(IGobbler<Donkey> dg)
. Если вы взяли IGobbler & lt; Quadruped & gt; как параметр вместо этого, вы не смогли бы пройти в дракона, который только ест ослов.
– Marcelo Cantos
27 February 2016 в 01:41
class A {}
class B : A {}
public void SomeFunction()
{
var someListOfB = new List<B>();
someListOfB.Add(new B());
someListOfB.Add(new B());
someListOfB.Add(new B());
SomeFunctionThatTakesA(someListOfB);
}
public void SomeFunctionThatTakesA(IEnumerable<A> input)
{
// Before C# 4, you couldn't pass in List<B>:
// cannot convert from
// 'System.Collections.Generic.List<ConsoleApplication1.B>' to
// 'System.Collections.Generic.IEnumerable<ConsoleApplication1.A>'
}
В принципе, всякий раз, когда у вас есть функция, которая принимает перечислимый тип одного типа, вы не можете передавать в Enumerable производного типа без явно его литья.
Просто, чтобы предупредить вас о ловушка хотя:
var ListOfB = new List<B>();
if(ListOfB is IEnumerable<A>)
{
// In C# 4, this branch will
// execute...
Console.Write("It is A");
}
else if (ListOfB is IEnumerable<B>)
{
// ...but in C# 3 and earlier,
// this one will execute instead.
Console.Write("It is B");
}
Это ужасный код в любом случае, но он существует, и изменение поведения на C # 4 может привести к тонким и трудным для поиска ошибкам, если вы используете такую конструкцию.
Вот простой пример использования иерархии наследования.
Учитывая простую иерархию классов:
Giraffe
/
LifeForm <- Animal <-
\
Zebra
В коде:
public abstract class LifeForm { }
public abstract class Animal : LifeForm { }
public class Giraffe : Animal { }
public class Zebra : Animal { }
Инвариантность ( Общий с параметризованным типом, украшенным ни in
, ни out
)
По-видимому, такой метод, как этот
public static void PrintLifeForms(IList<LifeForm> lifeForms)
{
foreach (var lifeForm in lifeForms)
{
Console.WriteLine(lifeForm.GetType().ToString());
}
}
..., должен принимать гетерогенный набор: (который это делает)
var myAnimals = new List<LifeForm>
{
new Giraffe(),
new Zebra()
};
PrintLifeForms(myAnimals); // Giraffe, Zebra
Однако передача набора из более производного типа не выполняется!
var myGiraffes = new List<Giraffe>
{
new Giraffe(), // "Jerry"
new Giraffe() // "Melman"
};
PrintLifeForms(myGiraffes); // Compile Error!
blockquote>
cannot convert from 'System.Collections.Generic.List<Giraffe>' to 'System.Collections.Generic.IList<LifeForm>'
Почему? Поскольку общий параметр
IList<LifeForm>
не является ковариантным -IList<LifeForm>
является инвариантным и принимает только коллекции (которые реализуют IList), где параметризованный типT
должен бытьLifeForm
.Если я злонамеренно изменяю реализацию метода
PrintLifeForms
(но оставляю одну и ту же сигнатуру метода), причина, по которой компилятор предотвращает передачуList<Giraffe>
, становится очевидным:public static void PrintLifeForms(IList<LifeForm> lifeForms) { lifeForms.Add(new Zebra()); }
Поскольку
IList
позволяет добавлять или удалять элементы, любой подклассLifeForm
может быть добавлен к параметруlifeForms
и будет нарушать тип любой коллекции производных типов, переданных методу. (В этом случае вредоносный метод попытается добавитьZebra
вvar myGiraffes
). К счастью, компилятор защищает нас от этой опасности.Ковариация (общий с параметризованным типом, украшенный
out
)Ковариация широко используется с неизменяемыми коллекциями (то есть, когда новые элементы не могут быть добавлено или удалено из коллекции)
Решение вышеприведенного примера заключается в том, чтобы использовать ковариантный общий тип, например
IEnumerable
(определяется какIEnumerable<out T>
). Это предотвращает изменение коллекции, и в результате теперь может быть передана любая коллекция с подтипомLifeForm
:public static void PrintLifeForms(IEnumerable<LifeForm> lifeForms) { foreach (var lifeForm in lifeForms) { Console.WriteLine(lifeForm.GetType().ToString()); } }
PrintLifeForms()
теперь можно вызвать с помощьюZebras
,Giraffes
и любойIEnumerable<>
любого подклассаLifeForm
Контравариантность (общий с параметризованным типом, украшенным
in
)Контравариантность часто используется, когда функции передаются как Параметры.
Вот пример функции, которая принимает параметр
Action<Zebra>
в качестве параметра и вызывает его на известном экземпляре Zebra:public void PerformZebraAction(Action<Zebra> zebraAction) { var zebra = new Zebra(); zebraAction(zebra); }
Как и ожидалось, это прекрасно работает:
var myAction = new Action<Zebra>(z => Console.WriteLine("I'm a zebra")); PerformZebraAction(myAction); // I'm a zebra
Интуитивно это не удастся:
var myAction = new Action<Giraffe>(g => Console.WriteLine("I'm a giraffe")); PerformZebraAction(myAction);
blockquote>
cannot convert from 'System.Action<Giraffe>' to 'System.Action<Zebra>'
Однако, это преуспевает
var myAction = new Action<Animal>(a => Console.WriteLine("I'm an animal")); PerformZebraAction(myAction); // I'm an animal
, и даже это также удается:
var myAction = new Action<object>(a => Console.WriteLine("I'm an amoeba")); PerformZebraAction(myAction); // I'm an amoeba
Почему? Поскольку
Action
определяется какAction<in T>
, то естьcontravariant
.Хотя это может быть сначала неинтуитивным (например, как можно передать
Action<object>
как параметр, требующийAction<Zebra>
]?), если вы распакуете этапы, вы заметите, что сама вызываемая функция (PerformZebraAction
) отвечает за передачу данных (в данном случае экземпляраZebra
) в функцию - данные не поступают из вызывающий код.Из-за инвертированного подхода к использованию функций более высокого порядка таким образом, к тому времени, когда вызывается
Action
, это более производный экземпляр объекта, который вызывается против функцииzebraAction
(передается как параметр), который сам использует менее производный тип.
Делегатор конвертера помогает мне визуализировать обе совместно работающие концепции:
delegate TOutput Converter<in TInput, out TOutput>(TInput input);
TOutput
представляет ковариацию, в которой метод возвращает более конкретный тип .
TInput
представляет собой контравариантность, когда метод передается менее конкретным типом .
public class Dog { public string Name { get; set; } }
public class Poodle : Dog { public void DoBackflip(){ System.Console.WriteLine("2nd smartest breed - woof!"); } }
public static Poodle ConvertDogToPoodle(Dog dog)
{
return new Poodle() { Name = dog.Name };
}
List<Dog> dogs = new List<Dog>() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } };
List<Poodle> poodles = dogs.ConvertAll(new Converter<Dog, Poodle>(ConvertDogToPoodle));
poodles[0].DoBackflip();