LINUX.ORG.RU

[java] Генрики. Что я делаю не так?

 


1

0

В продолжение этого. Написал пример, чтобы понять разницу между обобщенными типами (generics, <T>) и подстановочными выражениями (wildcards, <?>) Вопрос №1: почему List<Child> pl6 = makeList1(new Grandson()); ругается на несовместимые типы? Ведь в сигнатуре makeList1 уже прописан Child. Или такая конструкция хотя и задает ограничения на тип, но позволяет пихать в параметризированный контейнер только одинаковые типы List<Child> - только Child, в List<Grandson> только Grandson, но не позволяет создавать List<Parent>?
Вопрос №2: конструкция List<Child> pl2 = makeList(new Grandson(), new Child()); работает, хотя, насколько я знаю, не должна. может дело в сигнануре makeList()?
Вопрос №3: какие ереси и идеологически неверные косяки я допустил в примере?
Собственно сам пример:

import java.util.*;

class Parent{}
class Child extends Parent{}
class Grandson extends Child{}

class GenericsVsWildcards{
  static <T> List<T> makeList(T ... args){
    List<T> result = new ArrayList<T>(Arrays.<T>asList(args));
    return result;
  }
  static <T extends Child> List<T> makeList1(T arg){
    List<T> result = new ArrayList<T>();
    result.add(arg);
    return result;
  }
  public static void main(String[] args){
    /********* Wildcard tests ********/
    //Только указанный тип и наследники
    List<? extends Child> pl = new ArrayList<Child>(Arrays.<Child>asList(
                                                         //new Parent(), //Низзя
                                                         new Child(),
                                                         new Grandson()
                                                         ));
    //Получается, допустимы указанный тип и наследники
    List<? super Child> pl1 = new ArrayList<Child>(Arrays.<Child>asList(
                                                         //new Parent() //Тоже низзя, Эккель был прав 
                                                         new Child(),
                                                         new Grandson()
                                                         ));
    /********* Ceneric tests ********/
    //Так работает, хотя, в теории, не должно. Может быть из-за объявления Т не массивом?
    List<Child> pl2 = makeList(new Grandson(), new Child());
    //A так - нет
    //List<Child> pl3 = makeList(new Grandson());
    //List<Child> pl4 = makeList( new Child(), new Grandson(), new Parent());

    List<Child> pl5 = makeList1(new Child()); //Работает, как и ожидалось
    //List<Child> pl6 = makeList1(new Grandson()); //А так нет, хотя ожидалось
  }
}

★★★★

Последнее исправление: cab (всего исправлений: 1)

Вопрос номер 1 простой: потому что у тебя из makeList1(new Grandson()) получается List<Grandson>, который не приводится к List<Child>. Это можно полечить явно указав что ты хочешь List<Child>. Происходит это, насколько я понимаю, из-за того что аргументы имеют приоритет перед возвращаемым значение при выводе типа

maxcom ★★★★★
()
Ответ на: комментарий от maxcom

Это понял, спасибо. Теперь вижу, что в моем вопросе и была половина ответа :)

cab ★★★★
() автор топика

Второй вопрос - тут на основе аргументов T выводится как Child, потом Grandson к нему приводится и получается на выходе List<Child>. Вот List<Parent> pl2 = makeList(new Grandson(), new Child()) не скомпилируется

maxcom ★★★★★
()
Ответ на: комментарий от maxcom

Что интересно, правила вывода для массивов и переменного числа аргументов - разные. Замена T ... args на T [] args тоже выдает ошибку типов.

cab ★★★★
() автор топика
Ответ на: комментарий от cab

Массивы с генериками плохо совмещаются из-за того что массивы имеют свои хитрые правила наследования

maxcom ★★★★★
()

>//List<Child> pl6 = makeList1(new Grandson()); //А так нет, хотя ожидалось

В этой строке не работает из-за отсутствия ковариантности (либо из-за того, что List не объявлен таковым, джависты поправьте). Выше maxcom описал, что в результате разрешения типов у тебя получается такое: List<Child> = List<Grandson>. Если параметер T у List<> не объявлен ковариантным, то компилятор не может автоматически выполнять такое преобразование, несмотря на то, что Grandson потомок Child.

Sectoid ★★★★★
()
Ответ на: комментарий от Sectoid

Спасибо, уже понял. Вопрос по пунктам 1 и 2 закрыт.

cab ★★★★
() автор топика
Ответ на: комментарий от yoghurt

Шаблоны круче, чем генерики!

Вырыли мочало, начали сначала.

using System;
interface ScalarProduct<A> {
  int scalarProduct(A second);
}
class Nil : ScalarProduct<Nil> {
  public Nil(){}
  public int scalarProduct(Nil second) {
    return 0;
  }
}
class Cons<A> : ScalarProduct<Cons<A>> where A : ScalarProduct<A> {
  public int value;
  public A tail;
  public Cons(int _value, A _tail) {
    value = _value;
    tail = _tail;
  }
  public int scalarProduct(Cons<A> second){
    return value * second.value + tail.scalarProduct(second.tail);
  }
}
class _Test{
  public static int main(int n){
    return _main(n, 0, new Nil(), new Nil());
  }
  public static int _main<A>(int n, int i, A first, A second) where A : ScalarProduct<A> {
    if (n == 0) {
      return first.scalarProduct(second);
    } else {
      return _main(n-1, i+1, new Cons<A>(2*i+1,first), new Cons<A>(i*i, second)); // Works
      //return _main(n-1, i+1, first, new Cons<A>(i*i, second)); // Doesn't work (which is good)
    }
  }
}
public class Test{
  public static void Main(){
    Console.Write("Enter a number: ");
    int val = Convert.ToInt32(Console.ReadLine());
    Console.WriteLine(_Test.main(val));
  }
}

Это шарп, но разница не принципиальна (на жабу переписывается один в один).

Повтори с шаблонами.

Miguel ★★★★★
()

> Вопрос №3: какие ереси и идеологически неверные косяки я допустил в примере?

new ArrayList<T>(Arrays.<T>asList(args));

Зачем? Нужен мутабельный лист?

pitekantrop ★★★
()
Ответ на: комментарий от Miguel

Они полностью разворачиваются на этапе компиляции, это даёт (относительно дженериков) как свои плюсы, так и свои минусы, очевидно же.

В _main мы же имеем дело с рантаймовой структурой(?), весьма сомнительный профит

yoghurt ★★★★★
()
Ответ на: комментарий от yoghurt

Они полностью разворачиваются на этапе компиляции, это даёт (относительно дженериков) как свои плюсы, так и свои минусы, очевидно же.

Угу. Точнее сказать, они просто ДРУГИЕ, нежели дженерики. Что круче чего — неуместный подход.

весьма сомнительный профит

Просто наглядная демонстрация того факта, что шаблоны — не настоящий параметрический полиморфизм, а всего лишь его имитация навороченной макросистемой. В отличие от.

Miguel ★★★★★
()
Ответ на: комментарий от pitekantrop

Я в жабе новичек, помнится просто в ArrayList args добавляться не захотели, а так пошло. «Мутабельный» означает то, что можно изменить какой-то элемент? Что, в уже сформированной коллекции нельзя изменит произвольный элемент, подобно тому, как в String нельзя изменить произвольный символ?

cab ★★★★
() автор топика
Ответ на: комментарий от cab

ArrayList полностью мутабельный, т.е. можно добавлять, удалять и заменять элементы.

Arrays.asList даёт список фиксированной длины. Но заменять элементы можно.

А если мутабельный список не нужен, я бы использовал ImmutableList из Guava. Всячески рекомендую эту библиотеку.

pitekantrop ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.