Java5: параметризация внутренних классов

Jul 28, 2008 18:59

Внутренний класс - это нестатический вложенный класс.
Правило 1. (http://www.javable.com/tutorials/fesunov/lesson9/#%3F%3F%3F%3F%3F%3F%3F%3F%3F+%3F%3F%3F%3F%3F%3F) в объекте внутреннего класса содержится неявная ссылка на объект объемлющего класса с доступом ко всем "потрохам" объекта объемлющего класса. Т.е. на самом деле они в памяти располагаются не матрешкой (на что дает ассоциации слово "вложенный"), а в обратную линеечку. Если же в класс вложен другой статический класс, то никаких матрешек, никаких линеечек нет - просто одному классу дается более красивое имя ( "что-то . что-то" ) + правила на область видимости. Вот вроде бы и все правила, если бы классы не имели типовых переменных...
Правило 2 (JLS 4.8). При использовании параметризованного класса в raw-виде (класс объявлен с типовыми параметрами, но используется без оных) всё его нестатическое неотнаследованное содержимое тоже принимает raw-вид.

А теперь представим ситуацию, когда появляются типовые переменные. Для примера рассмотрим совсем простой, но довольно общий, случай:

public class a
{
public class b { }
}

По сути это всё синтаксический сахар. На самом деле, исходя из Правила 1, будет так:

public class a { }
public class b { a "a.this" ; }

в качестве имени для поля в классе b я специально написал не идентификатор, как это разрешено в Java, а выражение, с помощью которого можно получить доступ к объекту класса А. И всё бы было хорошо, если бы в такой код был правильным :) Он содержит ошибку! Какую? Посмотрите в код класса b - откуда при создании объекта класса b узнать тип T ? Если он не будет передан в качестве типового аргумента, то совершенно никак! Итого, правильное определение будет таким:

public class a { }
public class b { a "a.this" ; }

Какая интересная ситуация получается - был у b один типовой аргумент, а на самом деле их у него два! Кроме того, не забудем про способ создания внутреннего класса (через "объект-объемлющего-класса.конструктор()"). Таким образом, более полное определение будет таким:

class a
{
public b newb()
{
b B = new b();
B."a.this" = this;
return B;
}
}
class b { a "a.this"; }

Исходя из правила 2, получаем, если класс "a" используется в raw-виде, то и его метод newb станет raw :

class aRAW
{
public b newb()
{
b B = new b();
B."a.this" = this;
return B;
}
}
class bRAW { a "a.this"; }

Исходя из наличия неявного типового аргумента и правила 2 (которое распространяется на именно на последнюю версию класса a), можно объяснить поведение javac на некоторых примерчиках с типовыми переменными у внутренних классов. Как раз и переходим к ним.

class a
{
public class b {}
}

a g = null;
improperly formed type, some parameters are missing
a.b B1 = g.new b();
^

Метод "new b()" вызывается у объекта параметризованного типа - значит, вызов будет идти по версии "a". Но тогда получается, что не хватает параметра U! Что и сообщает компилятор.

Следующий пример.

class a
{
public class b {}
}
a g = null;
improperly formed type, some parameters are missing
a.b B2 = null;
^

Здесь та же ситуация - неизвестен типовой параметр U, хотя он требуется, поскольку тип "a" употреблен с типовым параметром. Из-за этого и "improperly formed type" - типы надо уметь составлять правильно ;)

class a
{
public class b {}
}
a g = null;
improperly formed type, some parameters are missing
a.b B22 = g.new b();
^

всё то же самое, дело до ошибки справа даже не доходит.

class a
{
public class b {}
}
a g = null;
improperly formed type, type parameters given on a raw type
a.b B3 = null;
^

Поскольку тип "а" употреблен в raw-виде, то надо смотреть определение типа "а" по версии "aRAW", а в ней тип "b" тоже raw ;) Происходит попытка дать raw-типу "b" параметр "String", о чем и сообщает компилятор.

class a
{
public class d {}
}
a g = null;
type a.d does not take parameters
a.d B4 = g.new d();
^

внутренний тип d не имеет параметров в объявлении, хотя его ему дают! Ошибка " type a.d does not take parameters " вполне законна.

class a
{
public class d {}
}
a g = null;
a.d B5 = null;

ошибок нет, всё в соответствии с определением

class a
{
public class d {}
}
a g = null;
type a.d does not take parameters
a.d B55 = g.new d();
^

а здесь справа происходит попытка дать типовой параметр типу, который их не имеет. Идет попытка задать типовой параметр для d, хотя таковые не предусмотрены определением.

class a
{
public class d {}
}
type a.d does not take parameters
a.d B6 = null;
^

класс "a.d" не считается raw в любом случае (т.к. у "d" нет типовых параметров в определении). Значит, здесь должна возникать ошибка с помещением типовых параметров типу, который их не имеет в определении - уже известная нам ошибка " type a.d does not take parameters ".

class a1
{
public class d {}
}
a1 g1 = null;
type a1 does not take parameters
a1.d B7 = g1.new d();
^

здесь класс a1 не имеет типовых переменных, хотя и делается попытка их туда засунуть :) нарываемся на "type a1 does not take parameters". До типа d даже дело не доходит.

class a1
{
public class b {}
}
a1 g1 = null;
a1.b B77 = g1.new b();

Тип левой части - a1.b - всё верно - у b только один типовой параметр. Вызывается метод newb без указания типового аргумента. Значит, смотрим raw-вариант этого метода:

class a1
{
public b newbRAW()
{
b B = new b();
B."a.this" = this;
return B;
}
}

значит, выражение "g1.new b()" имеет тип "a1.b" - точно такой же, как и левая часть :) ошибки нет :)

На этом закончу мое понимание семантики типовых переменных у внутренних классов. Надеюсь, я не особо сильно отклонился от javac.

P.S. может, это куда-нибудь перепостить?

java

Previous post Next post
Up