Что такое типы raw в Java и почему я часто слышу, что они не должны использоваться в новом коде?
blockquote>Необработанные типы - это древняя история Java язык. В начале были
Collections
, и они больше не держалиObjects
и не меньше. Каждая операция наCollections
требовала отличных отObject
желаемого типа.List aList = new ArrayList(); String s = "Hello World!"; aList.add(s); String c = (String)aList.get(0);
Хотя это продолжалось большую часть времени, произошли ошибки
List aNumberList = new ArrayList(); String one = "1";//Number one aNumberList.add(one); Integer iOne = (Integer)aNumberList.get(0);//Insert ClassCastException here
Старый нетривиальные коллекции не могли обеспечить безопасность типов, поэтому программисту приходилось запоминать то, что он хранил в коллекции. Генераторы, изобретенные, чтобы обойти это ограничение, разработчик объявит сохраненный тип один раз, и компилятор сделает это вместо этого.
List
aNumberList = new ArrayList (); aNumberList.add("one"); Integer iOne = aNumberList.get(0);//Compile time error String sOne = aNumberList.get(0);//works fine Для сравнения:
// Old style collections now known as raw types List aList = new ArrayList(); //Could contain anything // New style collections with Generics List
aList = new ArrayList (); //Contains only Strings Более сложный Интерфейс Compareable:
//raw, not type save can compare with Other classes class MyCompareAble implements CompareAble { int id; public int compareTo(Object other) {return this.id - ((MyCompareAble)other).id;} } //Generic class MyCompareAble implements CompareAble
{ int id; public int compareTo(MyCompareAble other) {return this.id - other.id;} } Обратите внимание, что невозможно реализовать интерфейс
CompareAble
сcompareTo(MyCompareAble)
с необработанными типами. Почему вы не должны их использовать:
- Любое
Object
, хранящееся вCollection
, должно быть выполнено до его использования- Использование обобщений позволяет проверять время компиляции
- Использование исходных типов - это то же самое, что и сохранение каждого значения как
Object
Что делает компилятор: Дженерики обратно совместимы, они используют одни и те же классы java, типы делают.
List
someStrings = new ArrayList (); someStrings.add("one"); String one = someStrings.get(0); Будет скомпилирован как:
List someStrings = new ArrayList(); someStrings.add("one"); String one = (String)someStrings.get(0);
Это тот же код, который вы бы написали, если вы использовали исходные типы напрямую. Думаю, я не уверен, что происходит с интерфейсом
CompareAble
, я предполагаю, что он создает две функцииcompareTo
, одна из которых принимаетMyCompareAble
, а другая принимаетObject
и передает ее первой после ее литья.Каковы альтернативы сырым типам: используйте generics
Да - возьмите копию переменной внутри цикла:
while (variable < 5)
{
int copy = variable;
actions.Add(() => copy * 2);
++ variable;
}
Вы можете думать об этом, как если бы компилятор C # создавал «новую» локальную переменную каждый раз, когда он попадает в объявление переменной. На самом деле он создаст соответствующие новые объекты закрытия, и он становится сложным (с точки зрения реализации), если вы ссылаетесь на переменные в нескольких областях, но он работает:)
Обратите внимание, что более распространенное появление этого проблема заключается в использовании for
или foreach
:
for (int i=0; i < 10; i++) // Just one variable
foreach (string x in foo) // And again, despite how it reads out loud
Подробнее об этом см. в разделе 7.14.4.2 спецификации C # 3.0, а моя статья о замыканиях еще несколько примеров.
Да, вам нужна область видимости variable
в цикле и передать ее лямбда таким образом:
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
int variable1 = variable;
actions.Add(() => variable1 * 2);
++variable;
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
Console.ReadLine();
Это поведение срабатывает, потому что вы используете лямбда-выражение () => variable * 2
, где внешняя область variable
фактически не определена во внутренней области лямбда.
Лямбда-выражения (в C # 3 +, а также анонимные методы в C # 2) по-прежнему создают реальные методы. Передача переменных этим методам связана с некоторыми дилеммами (передать по значению? Pass по ссылке? C # идет по ссылке - но это открывает еще одну проблему, когда ссылка может пережить реальную переменную). Что C # для решения всех этих дилемм заключается в создании нового вспомогательного класса («замыкание») с полями, соответствующими локальным переменным, используемым в лямбда-выражениях, и методам, соответствующим фактическим лямбда-методам. Любые изменения в variable
в вашем коде фактически преобразуются для изменения в этом ClosureClass.variable
. Таким образом, ваш цикл while обновляет ClosureClass.variable
до тех пор, пока он не достигнет 10, тогда вы для циклов выполняете действия, которые все работают на одном и том же ClosureClass.variable
.
Чтобы получить ожидаемый результат, вам необходимо создать разделение между переменной цикла и переменной, которая закрывается. Вы можете сделать это, введя другую переменную, то есть:
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
var t = variable; // now t will be closured (i.e. replaced by a field in the new class)
actions.Add(() => t * 2);
++variable; // changing variable won't affect the closured variable t
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
Вы также можете переместить замыкание на другой метод для создания этого разделения:
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
actions.Add(Mult(variable));
++variable;
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
Вы можете реализовать Mult как лямбда-выражение (неявное замыкание)
static Func<int> Mult(int i)
{
return () => i * 2;
}
или с фактическим вспомогательным классом:
public class Helper
{
public int _i;
public Helper(int i)
{
_i = i;
}
public int Method()
{
return _i * 2;
}
}
static Func<int> Mult(int i)
{
Helper help = new Helper(i);
return help.Method;
}
В любом случае «Closures» не являются концепцией, связанной с циклами, но скорее, к анонимным методам / лямбда-выражениям используют локальные переменные с областью - хотя некоторые неосторожное использование циклов демонстрируют закрытие ловушек.
То же самое происходит в многопоточном режиме (C #, .NET 4.0].
См. следующий код:
Цель печати 1,2,3,4,5 в порядке.
for (int counter = 1; counter <= 5; counter++)
{
new Thread (() => Console.Write (counter)).Start();
}
Результат интересный! (Это может быть как 21334 ...)
Единственное решение - использовать локальные переменные.
for (int counter = 1; counter <= 5; counter++)
{
int localVar= counter;
new Thread (() => Console.Write (localVar)).Start();
}
За кулисами компилятор генерирует класс, который представляет собой замыкание для вызова метода. Он использует этот единственный экземпляр класса закрытия для каждой итерации цикла. Код выглядит примерно так: это облегчает просмотр ошибки:
void Main()
{
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
var closure = new CompilerGeneratedClosure();
Func<int> anonymousMethodAction = null;
while (closure.variable < 5)
{
if(anonymousMethodAction == null)
anonymousMethodAction = new Func<int>(closure.YourAnonymousMethod);
//we're re-adding the same function
actions.Add(anonymousMethodAction);
++closure.variable;
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
}
class CompilerGeneratedClosure
{
public int variable;
public int YourAnonymousMethod()
{
return this.variable * 2;
}
}
На самом деле это не скомпилированный код из вашего примера, но я изучил свой собственный код и этот очень похоже на то, что на самом деле генерирует компилятор.
Я считаю, что то, что вы испытываете, - это что-то известное как Closure http://en.wikipedia.org/wiki/Closure_ (computer_science) . Ваша lamba имеет ссылку на переменную, которая находится вне самой функции. Ваш lamba не интерпретируется до тех пор, пока вы его не вызовите, и как только он получит значение, которое имеет переменная во время выполнения.
Способ сохранения значения, необходимого вам в прокси-переменной, и получить эту переменную.
I.E.
while( variable < 5 )
{
int copy = variable;
actions.Add( () => copy * 2 );
++variable;
}