Когда следует конкретизировать тип списка, который вы хотите - PullRequest
0 голосов
/ 10 июня 2009

Допустим, у меня есть класс, который внутренне хранит список данных:

import java.util.List;

public class Wrapper
{
    private List<Integer> list;

    public Wrapper(List<Integer> list)
    {
        this.list = list;
    }

    public Integer get(int index) { return list.get(index); }
}

Ради этого примера представьте, что это полезная и необходимая абстракция. Теперь вот мое беспокойство: как программисту, который знает базовую реализацию этого класса, должен ли я указать, какой тип List я запрашиваю в конструкторе? Чтобы продемонстрировать, я сделал этот тест:

import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;

public class Main
{
    public static void main(String[] args)
    {
        long start;
        List<Integer> list1 = new ArrayList<Integer>();
        List<Integer> list2 = new LinkedList<Integer>();
        Wrapper wrapper1, wrapper2;

        for(int i = 0; i < 1000000; i++)
        {
            list1.add(i);
            list2.add(i);
        }

        wrapper1 = new Wrapper(list1);
        wrapper2 = new Wrapper(list2);

        start = System.currentTimeMillis();

        wrapper1.get(500000);

        System.out.println(System.currentTimeMillis() - start);

        start = System.currentTimeMillis();

        wrapper2.get(500000);

        System.out.println(System.currentTimeMillis() - start);
    }
}

Как вы, скорее всего, знаете, случайный доступ к элементу занимает немного больше времени со связанным списком, чем с массивом. Итак, возвращаясь к конструктору Wrapper, должен ли я быть общим и разрешать любой тип List, или я должен указать, что пользователь передает ArrayList для обеспечения наилучшей возможной производительности? Хотя в этом примере пользователю может быть легко угадать, какова базовая реализация метода get , вы можете представить, что это что-то более сложное. Заранее спасибо!

Ответы [ 5 ]

3 голосов
/ 10 июня 2009

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

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

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

2 голосов
/ 10 июня 2009

Итак, возвращаясь к конструктору Wrapper, должен ли я быть общим и разрешить любой тип List?

Намерение оболочки - поддерживать какой-либо тип списка? или только ArrayList?

... или я должен указать, что пользователь передает ArrayList для обеспечения наилучшей производительности?

Если вы покинете общий список, все будет в порядке. Вы позволяете «клиенту» этого класса решать, может ли он использовать ArrayList. Зависит от клиента.

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

Итак, снова достаточно оставить его в качестве общего списка.

0 голосов
/ 10 июня 2009

Я думаю, это зависит от того, чего вы пытаетесь достичь с помощью класса Wrapper и / или ожидаете, что клиенты из Wrapper будут его использовать.

Если ваше намерение состоит в том, чтобы просто предоставить оболочку для List, где клиенты не должны ожидать определенного уровня производительности от get() (или любого другого имени метода), то ваш класс выглядит для меня как -is, за исключением того факта, что копируется только ссылка на параметр list конструктора, а не содержимое самого списка (подробнее об этом в 3-м пункте ниже).

Если, однако, вы говорите своим клиентам, что get() будет очень отзывчивым, на ум приходят некоторые альтернативы (их может быть и больше):

  1. Напишите набор конструкторов, которые принимают только те реализации List, которые, как вы знаете, имеют высокую производительность для любых операций, которые get() будет выполнять над ним. Например:

} // если бы я знал, как избежать этой скобки, я бы ...

 public Wrapper {
     Wrapper(ArrayList<Integer> list) { ... }
     Wrapper(KnownListImplementationThatWillMakeMyGetMethodFast<Integer> list) { ... }

     //...
 } 

Один из недостатков этого состоит в том, что если появится другая эффективная реализация List, вам придется добавить для нее еще один конструктор.

  1. Оставьте ваш Wrapper класс нетронутым, но проинформируйте ваших клиентов (с помощью некоторой формы документации, например, комментариев к классу, файла README и т. Д.), Что определенные операции над любой реализацией List, которую они передают, являются Ожидается, что будет иметь определенную производительность (например, «get() должен вернуться в постоянное время»). Если они затем «неправильно» используют вашу оболочку, передав LinkedList, это их вина.

  2. Если вы хотите гарантировать, что ваша реализация get() будет быстрой, стоит скопировать список, полученный в конструкторе, в структуру данных члена, соответствующую вашим ограничениям производительности. Это может быть ArrayList, другая известная реализация List или контейнер, который не реализует интерфейс List в целом (например, что-то специально написанное вами). Что касается того, что я говорил ранее о копировании содержимого List, а не о копировании ссылки на него, любые операции «записи», которые вы выполняете над имеющейся у вас ссылкой, не будут переданы в копию вашего клиента List, если Вы копируете его содержимое. Обычно это хороший способ избежать того, чтобы клиент задавался вопросом, почему его копия списка затрагивается при вызове операций на вашем Wrapper, если вы явно не скажете ему ожидать такого поведения.

0 голосов
/ 10 июня 2009

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

Используйте LinkedList, если вы собираетесь использовать элементы последовательно в большинстве случаев.

0 голосов
/ 10 июня 2009

Об этом, наверное, можно спорить.

Мои аргументы для , требующие определенного типа:

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

Аргумент против , требующий определенного типа:

  • Класс обертки используется в разных сценариях, и существуют разные реализации List, которые более или менее подходят для конкретного сценария. То есть, до вызывающей стороны должен быть поиск реализации, которая наилучшим образом соответствует сценарию. Также могут быть случаи, когда лучшая (TM) реализация List еще не была изобретена к моменту написания класса-оболочки.
...