Что прохладно о дженериках, почему используют их?

Позвоните MyMath::calcSomething()

80
задан MrBoJangles 16 September 2008 в 21:57
поделиться

12 ответов

Универсальные шаблоны также дают вам возможность создавать больше повторно используемых объектов / методов, при этом обеспечивая поддержку конкретных типов. В некоторых случаях вы также получаете большую производительность. Я не знаю полной спецификации Java Generics, но в .NET я могу указать ограничения для параметра Type, например, реализует интерфейс, конструктор и деривацию.

0
ответ дан 24 November 2019 в 09:45
поделиться

Я использую их, например, в GenericDao, реализованном с помощью SpringORM и Hibernate, которые выглядят так

public abstract class GenericDaoHibernateImpl<T> 
    extends HibernateDaoSupport {

    private Class<T> type;

    public GenericDaoHibernateImpl(Class<T> clazz) {
        type = clazz;
    }

    public void update(T object) {
        getHibernateTemplate().update(object);
    }

    @SuppressWarnings("unchecked")
    public Integer count() {
    return ((Integer) getHibernateTemplate().execute(
        new HibernateCallback() {
            public Object doInHibernate(Session session) {
                    // Code in Hibernate for getting the count
                }
        }));
    }
  .
  .
  .
}

Используя дженерики, мои реализации этого DAO вынуждают разработчика передавать им только те объекты, для которых они предназначены, просто создавая подкласс GenericDao

public class UserDaoHibernateImpl extends GenericDaoHibernateImpl<User> {
    public UserDaoHibernateImpl() {
        super(User.class);     // This is for giving Hibernate a .class
                               // work with, as generics disappear at runtime
    }

    // Entity specific methods here
}

. Моя маленькая структура более надежна (есть такие вещи, как фильтрация, отложенная загрузка, поиск). Я просто упростил здесь, чтобы дать вам пример

Я, как Стив и вы, сказал в начале «Слишком беспорядочно и сложно» , но теперь я вижу его преимущества

1
ответ дан 24 November 2019 в 09:45
поделиться

Использовать универсальные шаблоны для коллекций просто и понятно. Даже если вы попробуете применить его повсюду, прибыль от коллекций будет для меня победой.

List<Stuff> stuffList = getStuff();
for(Stuff stuff : stuffList) {
    stuff.do();
}

vs

List stuffList = getStuff();
Iterator i = stuffList.iterator();
while(i.hasNext()) {
    Stuff stuff = (Stuff)i.next();
    stuff.do();
}

или

List stuffList = getStuff();
for(int i = 0; i < stuffList.size(); i++) {
    Stuff stuff = (Stuff)stuffList.get(i);
    stuff.do();
}

Одно это стоит предельной «стоимости» дженериков, и вам не нужно быть универсальным Гуру, чтобы использовать это и получить ценность.

0
ответ дан 24 November 2019 в 09:45
поделиться

Согласно статье Джоша Смита о MVVM, он был представлен миру в октябре 2005 года в блоге Джона Госсмана .

С тех пор я бы сказал, что потребовалось еще 2-3 года, чтобы WPF / MVVM действительно взлетел и был принят сообществом, но было уже слишком поздно модифицировать WPF для поддержки проблем с MVVM. Также я бы сказал, что WPF создал MVVM, поэтому кажется, что WPF изменился в обратном направлении для поддержки MVVM.

Наконец, не все, кто использует WPF, используют MVVM, поэтому API должен поддерживать стандартное использование событий и т. Д.

Итак , чтобы ответить на ваш вопрос, да, каждый в настоящее время создает свой собственный набор инструментов, поскольку "официальная" поддержка в настоящее время предоставляет только структуру DelegateCommands.

- это улучшенная читаемость и надежность . [курсив мой] "

2
ответ дан 24 November 2019 в 09:45
поделиться

Не забывайте, что дженерики используются не только классами, но и методами. Например, возьмите следующий фрагмент:

private <T extends Throwable> T logAndReturn(T t) {
    logThrowable(t); // some logging method that takes a Throwable
    return t;
}

Он прост, но может использоваться очень элегантно. Приятно то, что метод возвращает то, что ему было дано. Это помогает, когда вы обрабатываете исключения, которые необходимо повторно передать вызывающей стороне:

    ...
} catch (MyException e) {
    throw logAndReturn(e);
}

Дело в том, что вы не теряете тип, передавая его через метод. Вы можете генерировать исключение правильного типа, а не просто Throwable , что было бы всем, что вы могли бы обойтись без дженериков.

Это всего лишь простой пример использования универсальных методов. Есть еще немало других интересных вещей, которые вы можете сделать с помощью универсальных методов. На мой взгляд, самое крутое - это определение типов с помощью дженериков.

3
ответ дан 24 November 2019 в 09:45
поделиться

Разве вы никогда не писали метод (или класс), в котором ключевая концепция метода / класса не была бы жестко привязана к конкретному типу данных переменных параметров / экземпляра (подумайте связанный список, функции max / min, двоичный поиск и т. д.).

Разве вы никогда не хотели, чтобы вы могли повторно использовать алгоритм / код, не прибегая к повторному использованию cut-n-paste или компрометации строгой типизации (например, я хочу Список строк, а не Список вещей, которые, как я надеюсь , являются строками!)?

Вот почему вы должны захотеть использовать дженерики (или что-то получше).

4
ответ дан 24 November 2019 в 09:45
поделиться

Чтобы дать хороший пример. Представьте, что у вас есть класс Foo

public class Foo
{
   public string Bar() { return "Bar"; }
}

Пример 1 Теперь вы хотите иметь коллекцию объектов Foo. У вас есть два варианта: LIst или ArrayList, оба из которых работают одинаково.

Arraylist al = new ArrayList();
List<Foo> fl = new List<Foo>();

//code to add Foos
al.Add(new Foo());
f1.Add(new Foo());

В приведенном выше коде, если я попытаюсь добавить класс FireTruck вместо Foo, ArrayList добавит его, но общий список Foo вызовет исключение.

Пример 2.

Теперь у вас есть два списка массивов, и вы хотите вызвать функцию Bar () для каждого из них. Поскольку список ArrayList заполнен объектами, вы должны преобразовать их, прежде чем вы сможете вызвать bar. Но поскольку общий список Foo может содержать только Foo, вы можете вызвать Bar () непосредственно для них.

foreach(object o in al)
{
    Foo f = (Foo)o;
    f.Bar();
}

foreach(Foo f in fl)
{
   f.Bar();
}
5
ответ дан 24 November 2019 в 09:45
поделиться
  • Типизированные коллекции - даже если вы не хотите их использовать, вам, вероятно, придется иметь дело с ними из других библиотек, других источников.

  • Общая типизация при создании класса:

    открытый класс Foo { public T get () ...

  • Избегание приведения типов - мне всегда не нравились такие вещи, как

    new Comparator { public int compareTo (Object o) { if (o instanceof classIcareAbout) ...

По сути, вы проверяете условие, которое должно существовать только потому, что интерфейс выражается в терминах объектов.

Моя первоначальная реакция на дженерики была похожа на вашу - "тоже грязно, слишком сложно ". Мой опыт показывает, что после некоторого использования вы к ним привыкаете, и код без них кажется менее четко определенным и просто менее удобным. Кроме того, весь остальной мир Java использует их, так что в конечном итоге вам придется заняться программой, верно?

5
ответ дан 24 November 2019 в 09:45
поделиться

Если бы вы провели поиск в базе данных ошибок Java незадолго до выпуска версии 1.5, вы бы обнаружили в семь раз больше ошибок с NullPointerException , чем ClassCastException . Так что не кажется, что это отличная возможность находить ошибки или, по крайней мере, ошибки, которые сохраняются после небольшого дымового тестирования.

Для меня огромное преимущество дженериков в том, что они документируют в коде важная информация о типе. Если бы я не хотел, чтобы эта информация о типе документировалась в коде, я бы использовал язык с динамической типизацией или, по крайней мере, язык с более неявным выводом типа.

Хранение коллекций объекта в себе - неплохой стиль ( но тогда общий стиль - игнорировать инкапсуляцию). Это скорее зависит от того, что вы делаете. Передача коллекций в "

15
ответ дан 24 November 2019 в 09:45
поделиться

Обобщения в Java упрощают параметрический полиморфизм . С помощью параметров типа вы можете передавать аргументы типам. Подобно тому, как метод вроде String foo (String s) моделирует некоторое поведение не только для конкретной строки, но и для любой строки s , такой тип, как List моделирует некоторое поведение не только для определенного типа, но для любого типа . List говорит, что для любого типа T существует тип List , элементами которого являются T s . Итак, List на самом деле является конструктором типа . Он принимает тип в качестве аргумента и в результате создает другой тип.

Вот несколько примеров универсальных типов, которые я использую каждый день. Первый, очень полезный общий интерфейс:

public interface F<A, B> {
  public B f(A a);
}

Этот интерфейс говорит, что для некоторых двух типов, A и B , есть функция (называемая f ) который принимает A и возвращает B . Когда вы реализуете этот интерфейс, A и B могут быть любыми типами, которые вы хотите, при условии, что вы предоставляете функцию f , которая принимает первое и возвращает второе. . Вот пример реализации интерфейса:

F<Integer, String> intToString = new F<Integer, String>() {
  public String f(int i) {
    return String.valueOf(i);
  }
}

До дженериков полиморфизм достигался путем подкласса с использованием ключевого слова extends . С помощью дженериков мы фактически можем отказаться от создания подклассов и вместо этого использовать параметрический полиморфизм. Например, рассмотрим параметризованный (общий) класс, используемый для вычисления хэш-кодов для любого типа. Вместо переопределения Object.hashCode () мы могли бы использовать общий класс вроде этого:

public final class Hash<A> {
  private final F<A, Integer> hashFunction;

  public Hash(final F<A, Integer> f) {
    this.hashFunction = f;
  }

  public int hash(A a) {
    return hashFunction.f(a);
  }
}

Это гораздо более гибко, чем использование наследования, потому что мы можем придерживаться темы использования композиции и параметрического полиморфизма, не блокируя хрупкие иерархии.

Однако дженерики Java не идеальны. Вы можете абстрагироваться от типов, но, например, вы не можете абстрагироваться от конструкторов типов. То есть вы можете сказать «для любого типа T», но вы не можете сказать «для любого типа T, который принимает параметр типа A».

Я написал статью об этих ограничениях универсальных шаблонов Java здесь.

Одним из огромных преимуществ дженериков является то, что они позволяют избежать создания подклассов. Создание подклассов, как правило, приводит к появлению хрупких иерархий классов, которые неудобно расширять, и классов, которые трудно понять по отдельности, не глядя на всю иерархию.

10
ответ дан 24 November 2019 в 09:45
поделиться

I really hate to repeat myself. I hate typing the same thing more often than I have to. I don't like restating things multiple times with slight differences.

Instead of creating:

class MyObjectList  {
   MyObject get(int index) {...}
}
class MyOtherObjectList  {
   MyOtherObject get(int index) {...}
}
class AnotherObjectList  {
   AnotherObject get(int index) {...}
}

I can build one reusable class... (in the case where you don't want to use the raw collection for some reason)

class MyList<T> {
   T get(int index) { ... }
}

I'm now 3x more efficient and I only have to maintain one copy. Why WOULDN'T you want to maintain less code?

This is also true for non-collection classes such as a Callable or a Reference that has to interact with other classes. Do you really want to extend Callable and Future and every other associated class to create type-safe versions?

I don't.

47
ответ дан 24 November 2019 в 09:45
поделиться

Отсутствие необходимости в приведении типов - одно из самых больших преимуществ дженериков Java , так как они будут выполнять проверку типов во время компиляции. Это уменьшит вероятность возникновения исключений ClassCastException , которые могут быть сгенерированы во время выполнения, и может привести к созданию более надежного кода.

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

Каждый раз, когда я посмотрите на Generics, которые он дает у меня болит голова. Я нахожу лучшую часть Java - это простота и минимализм синтаксис и обобщения не просты и добавить значительное количество новых синтаксис.

Поначалу я тоже не видел пользы от дженериков. Я начал изучать Java с синтаксиса 1.4 (хотя Java 5 в то время отсутствовала), и когда я столкнулся с дженериками, я почувствовал, что нужно писать больше кода, и я действительно не понимал преимуществ.

Современные IDE упростить написание кода с универсальными шаблонами.

Большинство современных достойных IDE достаточно умны, чтобы помочь с написанием кода с универсальными шаблонами, особенно с автозавершением кода.

Вот пример создания Map с HashMap . Код, который мне нужно было бы ввести, следующий:

Map<String, Integer> m = new HashMap<String, Integer>();

И действительно, это очень много, чтобы ввести только для создания новой HashMap . Однако на самом деле мне нужно было набрать столько же, прежде чем Eclipse узнал, что мне нужно:

Map m = new Ha Ctrl + Space

Верно, мне действительно нужно было выбрать HashMap из списка кандидатов, но в основном среда IDE знала, что добавить, включая общие типы. С правильными инструментами использование универсальных шаблонов не так уж и плохо.

Кроме того, поскольку типы известны, при извлечении элементов из общей коллекции IDE будет действовать так, как если бы этот объект уже является объектом своего объявленного типа - - в среде IDE нет необходимости выполнять приведение типов, чтобы узнать тип объекта.

Ключевое преимущество дженериков заключается в том, как они хорошо сочетаются с новыми функциями Java 5. Вот пример подбрасывания целых чисел в Set и вычисления его суммы:

Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(42);

int total = 0;
for (int i : set) {
  total += i;
}

В этом фрагменте кода представлены три новые функции Java 5:

Сначала, дженерики и автоматическая упаковка примитивов позволяют использовать следующие строки:

set.add(10);
set.add(42);

Целое число 10 автоматически упаковывается в Целое число со значением 10 . (То же самое для 42 ). Затем это Integer бросается в Set , который, как известно, содержит Integer s. Попытка добавить String приведет к ошибке компиляции.

Затем цикл for-each принимает все три из них:

for (int i : set) {
  total += i;
}

Во-первых, Set , содержащий ] Целые числа используются в цикле for-each. Каждый элемент объявлен как int , и это разрешено, поскольку Integer распаковывается обратно в примитив int . И тот факт, что происходит распаковка, известен, потому что дженерики использовались, чтобы указать, что в Set содержались Integer .

Generics может быть связующим звеном, которое объединяет новые функции, представленные в Java 5, и это просто делает кодирование более простым и безопасным. И в большинстве случаев IDE достаточно умны, чтобы помочь вам с хорошими предложениями, так что, как правило, печатать не намного сложнее.

И, честно говоря, как видно из примера Set , Я считаю, что использование функций Java 5 может сделать код более кратким и надежным.

Edit - Пример без универсальных шаблонов

Ниже приведена иллюстрация приведенного выше примера Set без использования универсальных шаблонов. Это возможно, но не совсем приятно:

Set set = new HashSet();
set.add(10);
set.add(42);

int total = 0;
for (Object o : set) {
  total += (Integer)o;
}

(Примечание: Приведенный выше код будет генерировать предупреждение о непроверенном преобразовании во время компиляции.)

При использовании неуниверсальных коллекций типы, которые вводятся в коллекцию, являются объектами типа Object . Следовательно, в этом примере объект - это то, что добавляет в набор.

set.add(10);
set.add(42);

В приведенных выше строках задействован автобокс - примитив int value 10 и 42 автоматически упаковываются в объекты Integer , которые добавляются в Set . Однако имейте в виду, что объекты Integer обрабатываются как Object s, так как нет информации о типе, которая помогала бы компилятору узнать, какой тип должен быть установлен в Set . ожидайте.

for (Object o : set) {

Это самая важная часть. Причина, по которой цикл for-each работает, заключается в том, что набор Set реализует интерфейс Iterable , который возвращает Iterator с информацией о типе, если она есть. ( Iterator , то есть.)

Однако, поскольку информация о типе отсутствует, Set вернет Iterator , который вернет значения в Установите как Object s, и именно поэтому элемент, извлекаемый в цикле for-each , должен иметь тип Object ].

Теперь, когда объект получен из набора , его необходимо вручную преобразовать в Integer , чтобы выполнить сложение:

  total += (Integer)o;

Здесь приведение типа выполняется из объекта в целое число . В этом случае мы знаем, что это всегда будет работать, но ручное приведение типов всегда заставляет меня думать, что это хрупкий код, который может быть поврежден, если незначительное изменение будет сделано в другом месте. (Я чувствую, что каждое приведение типов - это ClassCastException , ожидающее своего появления, но я отвлекаюсь ...)

Целое число теперь распаковано в int и разрешено выполнять добавление в переменную int total .

Я надеюсь, что смогу проиллюстрировать, что новые функции Java 5 можно использовать с неуниверсальным кодом, но это просто не так чисто и прямолинейно, как писать код с помощью дженериков. И, на мой взгляд, чтобы в полной мере воспользоваться новыми функциями Java 5, следует обратить внимание на дженерики, если, по крайней мере,

20
ответ дан 24 November 2019 в 09:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: