Nano Hash - криптовалюты, майнинг, программирование

Лучше ли использовать дженерики или тип объекта для статического вспомогательного класса в Java?

На днях я писал программу, которая требовала от меня: получить частоту определенного объекта внутри ArrayList<String>, удалить все вхождения данного элемента и т. д. и т. д., не указанные в интерфейсе List. Я решил написать свой собственный вспомогательный класс и хотел сделать его максимально пригодным для повторного использования. Я решил указать List в качестве типа параметра коллекции, чтобы использовать его для любого класса, реализующего интерфейс List. Но эти классы обычно определяются с помощью дженериков, и я не знал, к какому классу относится удаляемый элемент. Поэтому мне пришлось либо определить статические вспомогательные методы в общем, поскольку статический класс не может явно содержать общие типы, либо определить тип класса удаляемого объекта как Object. Я реализовал это обоими способами, см. ниже, но мне интересно, есть ли какие-либо преимущества в использовании одного над другим.

Еще вопросы по теме:

  1. Почему я могу обойти ссылку универсального типа в статическом контексте, определив его в заголовке метода, а не в заголовке класса?
  2. Почему при использовании этого статического метода мне не нужно объявлять тип класса при его использовании? то есть ListTools_V2.getFrequencyOf(ArrayList<String> items, String s) все еще работает.

Реализация с использованием типа класса Object

import java.util.List;

/** General utility class for performing frequently needed operations
    on any class implementing the List interface **/ 
public class ListTools {

    public static void removeAllOccurrences(List items, Object o) {
        while(items.contains(o)) {
            items.remove(o);
        }
    }

    public static int getFrequencyOf(List items, Object o) {
        int frequency = 0;
        for(Object item : items) {
            if(item.equals(o)) {
                frequency++;
            }
        }
        return frequency;
    }

}

Реализация с использованием дженериков

import java.util.List;

/** General utility class for performing frequently needed operations
    on any class implementing the List interface. This implementation
    uses generics to maximize reusability. **/ 
public class ListTools_V2 {

    public static <E> void removeAllOccurrences(List<E> items, E o) {
        while(items.contains(o)) {
            items.remove(o);
        }
    }

    public static <E> int getFrequencyOf(List<E> items,E o) {
        int frequency = 0;
        for(E item : items) {
            if(item.equals(o)) {
                frequency++;
            }
        }
        return frequency;
    }

}
19.06.2015

  • Это и это хорошо читается. 19.06.2015
  • чувак, надеюсь, я не испортил тебе день: docs.oracle.com/javase/7/docs/api/java/util/ 19.06.2015
  • @jangroth Спасибо, что указали на это, но я все равно рад, что реализовал их сам. Определенно изучение некоторых основных принципов на протяжении всего процесса. 19.06.2015

Ответы:


1

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

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

public static void removeAllOccurrences(List<?> items, Object o)
public static int getFrequencyOf(List<?> items, Object o)
19.06.2015

2

Generic лучше, так как он обнаруживает проблемы, связанные с типом, во время компиляции.
Во время выполнения происходит «стирание», и дженерики не имеют «значения».

Например:

List apples = new ArrayList();
apples.add(new Apple());
apples.add(new Mango()); //compiles, but this is wrong
apples.add(new Chair()); //compiles, but this is wrong



Вы, конечно, должны знать о всевозможных "подводных камнях" универсального механизма. Примеры, которые я могу легко придумать:
List<Mango> не расширяет List<Fruit> , поэтому вы должны использовать List<? super Fruit> или List<? extends Fruit>

Вы не можете сделать что-то вроде:

T.getClassName()
19.06.2015
  • Поскольку вы можете столкнуться с проблемами во время выполнения, если вы собираетесь попытаться выполнить преобразование извлеченных объектов, например, - получить доступ к списку яблок, попытаться получить объект, привести его к Apple и иметь ClassCastException, поскольку по ошибке был добавлен Mango . Такие ошибки будут предотвращены во время компиляции, если используются дженерики. 22.06.2015
  • В этом вопросе нет возврата. 22.06.2015

  • 3

    Обобщения — это рекомендуемый способ реализации таких классов. Их можно использовать для предоставления некоторой неявной информации вашему пользователю (кто бы ни использовал этот класс). Например:

    List<Cat> cats = ...;
    ListTools.removeAllOccurrences(cats, new Dog());
    

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

    List<Cat> cats = ...;
    ListTools_V2.removeAllOccurrences(cats, new Dog()); // Compile time error because it is pointless
    

    Кроме того, есть прирост производительности. Если список кошек содержит тысячи кошек, вы действительно хотите искать эту собаку в большом количестве списка кошек? (метод contains фактически выполняет поиск по всему списку) или вы просто хотите избежать его во время компиляции? ;)

    19.06.2015
  • это бессмысленно, поскольку в списке кошек не может быть собак. Не правда. Dog может быть равен (как в .equals()) Cat, в зависимости от того, как реализованы Dog и Cat, и для удаления важно равенство. 19.06.2015
  • Да ты прав. Я не предоставлял код Cat и Dog с предположением, что это совершенно два разных класса. 20.06.2015

  • 4

    ваше тело метода getFrequencyOf() практически одинаково в обоих случаях, за исключением того, что в одном случае вы передаете необработанный список, а в другом вы параметризуете его для принятия любого типа.

    1-

    public static int getFrequencyOf(List items, Object o) {
            int frequency = 0;
            for(Object item : items) {
                if(item.equals(o)) {
                    frequency++;
                }
            }
            return frequency;
        }
    

    Здесь вы List - это необработанный тип. Ссылки на общий тип List должны быть параметризованы, а не параметризованы. Но он открыт для любого типа списка (целое, длинное, строковое и т. д.).

    2-

    public static <E> int getFrequencyOf(List<E> items,E o) {
            int frequency = 0;
            for(E item : items) {
                if(item.equals(o)) {
                    frequency++;
                }
            }
            return frequency;
        }
    

    Здесь вы List НЕ является необработанным типом. Ссылки на общий тип списка должны быть параметризованы, что и есть. И это также открыто для любого типа списка (целое, длинное, строковое и т. д.).

    19.06.2015
    Новые материалы

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

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

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

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..