Использование параметризованного (универсального) класса Java против обычного класса

#java #generics

#java #общие

Вопрос:

если мне нужно написать класс, который работает с данными типа ‘Comparable’, я могу сделать это двумя способами:

1)

 public class MyClass<T extends Comparable>
{

    private T value;

    MyClass(T value)
    {
        this.value = value;    
    }

    other code...
}
  

2)

 public class MyClass
{

    private Comparable value;

    MyClass(Comparable value)
    {
        this.value = value;    
    }

    other code...
}
  

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

Комментарии:

1. Ваша версия generics должна выдавать предупреждение компилятору об использовании необработанного типа.

Ответ №1:

Это зависит от остальной части вашего класса. Если, например, у вас есть метод getValue , то общий подход был бы предпочтительнее, поскольку вы могли бы сделать это:

 public class MyClass<T extends Comparable>
{

    private T value;

    MyClass(T value)
    {
        this.value = value;    
    }

    T getValue() {
        return this.value;
    }

    other code...
}
  

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

Ответ №2:

Как правило, это не так. Вы должны использовать Generics только тогда, когда хотите добавить безопасность типов, которая в противном случае не существовала бы. Так ли это в вашем примере класса, мы действительно не можем сказать; если ваш класс коллективно обрабатывал несколько Comparable объектов, то можно было бы использовать дженерики, чтобы настаивать на том, чтобы все они имели Comparable<T> одинаковое значение T ; но если нет, то, вероятно, нет причин использовать дженерики здесь.

Ответ №3:

Универсальные классы обеспечивают большую безопасность типов во время компиляции. Из-за стирания типа они не оказывают (прямого) влияния на производительность во время выполнения.

Тем не менее, ваш пример, вероятно, мог бы принести пользу при использовании дженериков. В нынешнем виде это должно сгенерировать жалобу компилятора на использование необработанных типов. Его нужно было бы изменить на что-то вроде этого:

 public class MyClass<T extends Comparable<? extends SomeBaseClass>>
{

    private T value;

    MyClass(T value)
    {
        this.value = value;    
    }

    // other code...
}
  

Затем компилятор мог бы проверить типы задействованных объектов, чтобы убедиться, что они сопоставимы друг с другом.

Ответ №4:

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

 //Generic class 
class CGeneric<T extends Comparable>
{
    private T obj;
    public T getObj() { return obj; }
    public void setObj(T value) { obj = value;: }
}

//Non-generic class
class CNormal
{
  private Comparable obj;
  public Comparable getObj() { return obj; }
  public void setObj(Comparable value) {obj = value;}
}
  

При вызове getObj для экземпляра CGeneric вы можете использовать объект как MyComparable (если вы хотите использовать специальные методы, объявленные там). Если вы вызываете getObj для CNormal, вы получаете сопоставимый (во время компиляции), и вам нужно преобразовать его в MyComparable, чтобы использовать специальные методы, которые у вас могут быть там.

Таким образом, дженерики предоставляют вам проверки типов во время компиляции, и вы не столкнетесь с CastException.

Ответ №5:

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

Ответ №6:

Ну, самый заметный недостаток дженериков прост: их ужасно сложно получить в точности. T расширяет сопоставимые? Хорошо будет работать, но это мешанина дженериков и не дженериков. Таким образом, первый, который можно было бы попробовать, был бы T extends Comparable< T>, который будет работать нормально, но, к сожалению, слишком ограничен, потому что предполагает класс:

класс Foo реализует сопоставимый< объект>

Итак, на самом деле реализация должна быть сопоставима с T extends< ? super T>. Приятно и не так уж сложно. Но тогда мы получаем что-то вроде Enum< E расширяет Enum< E>> и действительно, объясните это кому-нибудь, не вводя его в заблуждение — удачи с этим.

Итак, дженерики полезны и просты в ИСПОЛЬЗОВАНИИ, но их чрезвычайно сложно правильно написать (и не заставляйте меня начинать писать правильные двоичные версии, обратно совместимые с существующей библиотекой — см. Библиотеку collection в JDK). К счастью, только горстка авторов библиотек должна сделать это правильно, чтобы все остальные могли извлечь из этого выгоду