Подтвердить что ты не робот

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

Чтение кода других людей, я видел много:

List<E> ints = new ArrayList<E>();
Map<K, V> map = new HashMap<K, V>();

Мой вопрос: какова точка/преимущество создания их таким образом, а не:

ArrayList<E> ints = new ArrayList<E>();
HashMap<K, V> map = new HashMap<K, V>();

Что также делает странным то, что я никогда не видел ничего подобного:

CharSequence s = new String("String");

или

OutputStream out = new PrintStream(OutputStream);


Дубликаты (первой части вопроса):

Когда/зачем использовать/определить интерфейс

Использовать интерфейс или тип для определения переменной в java?

Когда я должен использовать интерфейс в java?

почему создаются интерфейсы вместо их реализаций для каждого класса

В чем разница между этими двумя объявлениями переменной java?

4b9b3361

Ответ 1

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

class Account {
    private Collection<Transaction> transactions;

    public Account() {
        super();
        transactions = new ArrayList<Transaction>(4);
    }

    public Collection<Transaction> getTransactions() {
        return transactions;
    }
}

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

public Account() {
    super();
    transactions = new HashSet<Transaction>(4);
}

Что касается второго вопроса, я могу сказать, что вы используете принципала переносимости и инкапсуляции везде, где они имеют смысл. Там нет ужасной партии реализаций CharSequence, и String, безусловно, наиболее распространена. Таким образом, вы просто не увидите много разработчиков, объявляющих переменные CharSequence в своем коде.

Ответ 2

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

Ответ 3

Для

List<E> ints = new ArrayList<E>();
Map<K, V> map = new HashMap<K, V>();

List и Map являются интерфейсами, поэтому любой класс, реализующий эти интерфейсы, может быть назначен этим ссылкам.

ArrayList является одним из нескольких классов (другое - LinkedList), которые реализуют интерфейс List.

То же самое с Map. HashMap, LinkedHashMap, TreeMap все реализуют Map.

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

Если вы пишете

ArrayList<E> ints = new ArrayList<E>();
HashMap<K, V> map = new HashMap<K, V>();

ints и Map будет ArrayList и HashMap только навсегда.

Ответ 4

Является принципом дизайна, который вы программируете на интерфейс, а не на реализацию.

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

Из приведенной выше ссылки Эрик Гамма объясняет:

Этот принцип действительно связан с отношениями зависимостей, которые необходимо тщательно контролировать в большом приложении. Легко добавить зависимость от класса. Это почти слишком легко; просто добавьте оператор импорта и современные средства разработки Java, такие как Eclipse, даже напишите этот отчет для вас. Интересно, что обратное не так просто и избавление от нежелательной зависимости может быть реальной работой по рефакторингу или, что еще хуже, блокировать повторное использование кода в другом контексте. По этой причине вам нужно разрабатывать с открытыми глазами, когда дело доходит до введения зависимостей. Этот принцип говорит нам, что в зависимости от интерфейса часто полезно.

Здесь термин interface относится не только к артефакту Java, но и к общему интерфейсу данного объекта, который в основном состоит из методов, которые он имеет, поэтому он может быть интерфейсом Java (например, List в вашем примере) или конкретный суперкласс.

Итак, в вашем примере, если вы когда-нибудь захотите использовать LinkedList, это будет сложнее, потому что тип уже объявлен как ArrayList, когда только списка было бы достаточно.

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

Надеюсь, это поможет.

Ответ 5

@Bhushan ответил, почему. Чтобы ответить на вашу путаницу Почему никто не использует

CharSequence s = new String("String");

или

OutputStream out = new PrintStream(OutputStream);

CharSequence содержит только несколько общих методов. Другие классы, реализующие этот интерфейс, в основном являются буферами, и только String является неизменным. CharSequence определяет общий api для классов, поддерживаемых массивом char и . Этот интерфейс не уточняет общие контракты методов equals и hashCode (см. javadoc).

OutputStream является низкоуровневым api для записи данных. Поскольку PrintStream добавляет дополнительные удобные методы для записи - более высокий уровень абстракции, он используется для OutputStream.

Ответ 6

Вы сделаете это, чтобы убедиться, что позже, когда вы работаете с переменной, которую вы (или кто-либо из ваших классов) не будет полагаться на методы, специфичные для выбранной реализации (ArrayList, HashMap и т.д.)

Ответ 7

Причина этого не техническая, но материал, который вы должны прочитать между строками кода: примеры List и Map гласят: "Меня интересует только основной материал списка/карты, в основном вы можете используйте что-нибудь здесь". Крайним примером этого будет

Iterable<Foo> items = new ArrayList<Foo>();

когда вы действительно хотите сделать что-то для каждой вещи.

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

С другой стороны, пример String не встречается дико, потому что a) String является специальным классом в Java - каждый литерал "foo" автоматически является String, и рано или поздно вам придется указывать символы к некоторому методу, который принимает только String и b), CharSequence действительно ahh минимальный. Он даже не поддерживает Unicode за пределами BMP, и он пропускает большинство методов запроса/манипуляции String.

Ответ 8

Этот (хороший) стиль объявления типа как Interface реализует класс, потому что он заставляет нас использовать методы, определенные только в Interface.

В результате, когда нам нужно изменить наши реализации классов (т.е. наш ArraySet лучше, чем стандартный HashSet), мы гарантируем, что если мы изменим класс, наш код будет работать, потому что оба класса реализуют строго соблюдаемый Interface.

Ответ 9

Легче думать о String как о String. Так же как и проще (и более полезно) думать о WhateverList как о List.

Бонусы обсуждаются много раз, но вкратце вы просто разделяете проблемы: когда вам нужен CharSequence, вы его используете. Очень маловероятно, что вам нужно только ArrayList: обычно, любой список будет делать.

Ответ 10

Когда вы в какой-то момент решаете использовать другую реализацию, скажите:

List<E> ints = new LinkedList<E>();

вместо

List<E> ints = new ArrayList<E>();

это изменение должно выполняться только в одном месте.

Существует правильный баланс:

обычно вы используете тип, который дает вам наиболее подходящие гарантии. Очевидно, a List также a Collection, что также является чем-то Iterable. Но коллекция не дает вам ордера, а итерабельность не имеет метода "добавить".

Использование ArrayList для типа переменной также разумно, если вы хотите быть более явным в отношении необходимости быстрого произвольного доступа по позиции объекта - в LinkedList "get (100)" намного медленнее, (Было бы неплохо, если бы у Java был интерфейс для этого, но я не думаю, что он есть. Используя ArrayList, вы запрещаете кастинг массива как список.)

Ответ 11

 List<E> ints = new ArrayList<E>();

Если вы напишете код, который имеет дело только с List, тогда он будет работать для любого класса, который реализует List (например, LinkedList и т.д.). Но если ваш код напрямую связан с ArrayList, то он ограничивается ArrayList.

CharSequence s = new String("String");

Вручную создать экземпляр объекта String не очень хорошо. Вместо этого вы должны использовать строковый литерал. Я просто догадываюсь, почему вы не видите CharSequence, потому что это совершенно новое, а также строки неизменяемы.

Ответ 12

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