2015-06-10 3 views
4

Можно ли написать один метод total, чтобы сделать сумму всех элементов ArrayList, где она имеет тип <Integer> или <Long>?Сумма ArrayList для разных типов

Я не могу просто написать

public long total(ArrayList<Integer> list) 

и

public long total(ArrayList<Long> list) 

вместе, как будет ошибка стирания и Integer автоматически не распространяется на Long и наоборот ... но код внутри одинаково!

ответ

11

Да, вы можете реализовать такой метод, так как оба Integer и Long продлить Number. Например, вы можете использовать wildcard type для типа элемента списка:

public static long total(List<? extends Number> list) { 
    long sum = 0; 
    for (Number n : list) { 
     if (!(n instanceof Byte || n instanceof Short || n instanceof Integer || n instanceof Long)) { 
      throw new IllegalArgumentException(); 
     } 
     sum += n.longValue(); 
    } 
    return sum; 
} 

это работает только для целочисленных типов однако, так как sum переменных и возвращаемое значение имеют типа long.

В идеале вы хотели бы иметь возможность использовать метод с Float с и Double с и возвращает объект того же типа, что и тип элемента списка, но это не так легко сделать по двум причинам:

  1. Единственное, что вы можете сделать с помощью Number, это получить его значение как один из примитивных типов номеров. Вы не можете суммировать два из них в зависимости от числа.
  2. Невозможно создать 0-объект правого класса.

EDIT:Намного позже ...

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

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

interface MonoidOps<T> { 
    T id(); 
    T op(T o1, T o2); 
} 

Метод total теперь может быть реализован, чтобы принять объект этого типа в дополнение к списку:

public static <T> T total(List<T> list, MonoidOps<T> ops) { 
    T sum = ops.id(); 
    for (T e : list) { 
     sum = ops.op(e, sum); 
    } 
    return sum; 
} 

Чтобы предоставить MonoidOps реализации для числовых классов, создайте простой вспомогательный класс:

class SimpleMonoidOps<T> implements MonoidOps<T> { 
    private final T idElem; 
    private final BinaryOperator<T> operation; 

    public SimpleMonoidOps(T idElem, BinaryOperator<T> operation) { 
     this.idElem = idElem; 
     this.operation = operation; 
    } 

    public T id() { 
     return idElem; 
    } 

    public T op(T o1, T o2) { 
     return operation.apply(o1, o2); 
    } 
} 

В MonoidOps реализации теперь могут быть написаны аккуратно, как это:

static final MonoidOps<Integer> INT_MONOID_OPS = new SimpleMonoidOps<>(0, Integer::sum); 
static final MonoidOps<Double> DOUBLE_MONOID_OPS = new SimpleMonoidOps<>(0.0, Double::sum); 

И метод total будет называться так:

int sum = total(Arrays.asList(1, 2, 3), INT_MONOID_OPS); 
+0

Узнавайте что-то новое каждый день :) Спасибо! – Ivan

1

Вы можете использовать Java's дженерики для этого

public <T extends Number> T total(List<T> list) { 
    T sum = 0; 
    for (T n : list) { 
     sum += n.longValue(); 
    } 
    return sum; 
} 

Зная, что T всегда будет Number, все упрощается. Однако это решение может работать и для String s, если это необходимо. Единственное изменение будет в части extends.

+0

Я думаю, что лучше всегда возвращать 'long', а не общий тип' T', потому что OP уже ожидает 'long', и это предотвращает переполнение немного больше. – Tom

+1

компилируется? – Reimeus

+0

Это не будет компилироваться в 'T sum = 0;' и 'sum + = n;'. – manish

1

Вы также можете использовать потоки в Java 8

public static <T extends Number> long sumList(List<T> list) 
{ 
    return list.stream().mapToLong(a -> a.longValue()).sum(); 
} 
Смежные вопросы