Суммирование списка BigIntegers с использованием потоков в Java [duplicate]

Что вы должны знать о this

this (иначе говоря, «контекст») - это специальное ключевое слово внутри каждой функции, и его значение зависит только от , как была вызвана функция, а не как / когда / где она была определена. Лексические области не затрагиваются, как и другие переменные. Вот несколько примеров:

function foo() {
    console.log(this);
}

// normal function call
foo(); // `this` will refer to `window`

// as object method
var obj = {bar: foo};
obj.bar(); // `this` will refer to `obj`

// as constructor function
new foo(); // `this` will refer to an object that inherits from `foo.prototype`

Чтобы узнать больше о this, просмотрите документацию MDN .


Как сделать обратитесь к правильному this

Не используйте this

Фактически вы не хотите иметь доступ к this в частности, но объект, на который он ссылается на . Вот почему простое решение - просто создать новую переменную, которая также относится к этому объекту. Переменная может иметь любое имя, но общие - self и that.

function MyConstructor(data, transport) {
    this.data = data;
    var self = this;
    transport.on('data', function() {
        alert(self.data);
    });
}

Поскольку self является нормальной переменной, она подчиняется лексическим правилам области и доступна внутри обратного вызова. Это также имеет то преимущество, что вы можете получить доступ к значению this самого обратного вызова.

Явно установить this обратного вызова - часть 1

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

Каждая функция имеет метод .bind [docs] , который возвращает новую функцию с this, привязанную к значению. Функция имеет то же поведение, что и тот, который вы назвали .bind, только то, что this было установлено вами. Независимо от того, как и когда эта функция вызывается, this всегда будет ссылаться на переданное значение.

function MyConstructor(data, transport) {
    this.data = data;
    var boundFunction = (function() { // parenthesis are not necessary
        alert(this.data);             // but might improve readability
    }).bind(this); // <- here we are calling `.bind()` 
    transport.on('data', boundFunction);
}

В этом случае мы привязываем обратный вызов this к значению MyConstructor 's this.

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

ECMAScript 6: Используйте функции стрелок

В ECMAScript 6 представлены функции стрелок , которые можно рассматривать как лямбда-функции. У них нет собственной привязки this. Вместо этого this просматривается в области видимости как обычная переменная. Это означает, что вам не нужно называть .bind. Это не единственное особое поведение, которое у них есть. Дополнительную информацию см. В документации MDN.

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', () => alert(this.data));
}

Установите this обратного вызова - часть 2

Некоторые функции / методы, которые принимают обратные вызовы, также принимают значение, к которому должен обращаться обратный вызов this. Это в основном то же самое, что и привязывать его самостоятельно, но функция / метод делает это за вас. Array#map [docs] - такой метод. Его подпись такова:

array.map(callback[, thisArg])

Первый аргумент - это обратный вызов, а второй аргумент - значение this. Вот надуманный пример:

var arr = [1, 2, 3];
var obj = {multiplier: 42};

var new_arr = arr.map(function(v) {
    return v * this.multiplier;
}, obj); // <- here we are passing `obj` as second argument

Примечание. Можно ли передать значение для this, как правило, упоминается в документации этой функции / метода. Например, метод $.ajax jQuery [docs] описывает параметр, называемый context:

Этот объект станет контекстом всех обратных вызовов, связанных с Ajax.


Общая проблема: использование объектных методов в качестве обработчиков обратных вызовов / событий

Еще одно распространенное проявление этой проблемы - когда объектный метод используется как обработчик обратного вызова / события , Функции являются первоклассными гражданами в JavaScript, а термин «метод» - просто разговорный термин для функции, которая является значением свойства объекта. Но эта функция не имеет конкретной ссылки на ее «содержащий» объект.

Рассмотрим следующий пример:

function Foo() {
    this.data = 42,
    document.body.onclick = this.method;
}

Foo.prototype.method = function() {
    console.log(this.data);
};

Функция this.method назначается как обработчик события click , но если щелкнуть document.body, зарегистрированное значение будет undefined, потому что внутри обработчика события this ссылается на document.body, а не на экземпляр Foo. Как уже упоминалось в начале, то, что относится к [49], зависит от того, как называется функция, а не от того, как она определена. Если код выглядит следующим образом, может быть более очевидно, что функция не имеет неявной ссылки на объект:

function method() {
    console.log(this.data);
}


function Foo() {
    this.data = 42,
    document.body.onclick = this.method;
}

Foo.prototype.method = method;

Решение такое же, как указано выше: если доступно, используйте .bind явно привязать this к определенному значению

document.body.onclick = this.method.bind(this);

или явно вызвать функцию как «метод» объекта, используя анонимную функцию в качестве обработчика обратного вызова / события и назначить object (this) к другой переменной:

var self = this;
document.body.onclick = function() {
    self.method();
};

или использовать функцию стрелки:

document.body.onclick = () => this.method();
104
задан ryvantage 11 December 2015 в 19:46
поделиться

6 ответов

Оригинальный ответ

Да, это возможно:

List<BigDecimal> bdList = new ArrayList<>();
//populate list
BigDecimal result = bdList.stream()
        .reduce(BigDecimal.ZERO, BigDecimal::add);

Что он делает:

  1. Получите List<BigDecimal>.
  2. Включите его в Stream<BigDecimal>
  3. Вызовите метод уменьшения. 3.1. Мы добавляем значение идентичности для добавления, а именно BigDecimal.ZERO. 3.2. Мы указываем BinaryOperator<BigDecimal>, который добавляет два BigDecimal, через ссылку на метод BigDecimal::add.

Обновленный ответ после редактирования

Я вижу, что вы добавили новые данные, поэтому новый ответ станет следующим:

List<Invoice> invoiceList = new ArrayList<>();
//populate
Function<Invoice, BigDecimal> totalMapper = invoice -> invoice.getUnit_price().multiply(invoice.getQuantity());
BigDecimal result = invoiceList.stream()
        .map(totalMapper)
        .reduce(BigDecimal.ZERO, BigDecimal::add);

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

Затем я получаю Stream<Invoice>, сопоставляю его с Stream<BigDecimal>, а затем уменьшаю его до BigDecimal.

Теперь, из точки проектирования ООП, я бы посоветовал вам также использовать метод total(), который вы уже определили, тогда он становится еще проще:

List<Invoice> invoiceList = new ArrayList<>();
//populate
BigDecimal result = invoiceList.stream()
        .map(Invoice::total)
        .reduce(BigDecimal.ZERO, BigDecimal::add);

Здесь мы непосредственно используем ссылку на метод в методе map.

210
ответ дан skiwi 20 August 2018 в 16:55
поделиться
  • 1
    @ryvantage Я обновил свой ответ, чтобы ответить на ваш последний вопрос после редактирования. – skiwi 25 March 2014 в 20:11
  • 2
    +1 для Invoice::total vs invoice -> invoice.total(). – ryvantage 25 March 2014 в 20:50
  • 3
    +1 для ссылок на методы и для добавления разрывов строк между потоковыми операциями, оба из которых IMHO значительно улучшают читаемость. – Stuart Marks 26 March 2014 в 02:37
  • 4
    Аккуратное решение. +1 – Jagger 10 March 2015 в 14:49
  • 5
    Этот подход может привести к исключению Null Exception – gstackoverflow 20 September 2017 в 12:17

Используйте этот подход для суммирования списка BigDecimal:

List<BigDecimal> values = ... // List of BigDecimal objects
BigDecimal sum = values.stream().reduce((x, y) -> x.add(y)).get();

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

Вот еще один простой способ сделать то же суммирование:

List<BigDecimal> values = ... // List of BigDecimal objects
BigDecimal sum = values.stream().reduce(BigDecimal::add).get();

Обновить

Если бы я должен был написать класс и лямбда-выражение в отредактированном вопросе, Я бы написал это следующим образом:

import java.math.BigDecimal;
import java.util.LinkedList;

public class Demo
{
  public static void main(String[] args)
  {
    LinkedList<Invoice> invoices = new LinkedList<>();
    invoices.add(new Invoice("C1", "I-001", BigDecimal.valueOf(.1), BigDecimal.valueOf(10)));
    invoices.add(new Invoice("C2", "I-002", BigDecimal.valueOf(.7), BigDecimal.valueOf(13)));
    invoices.add(new Invoice("C3", "I-003", BigDecimal.valueOf(2.3), BigDecimal.valueOf(8)));
    invoices.add(new Invoice("C4", "I-004", BigDecimal.valueOf(1.2), BigDecimal.valueOf(7)));

    // Java 8 approach, using Method Reference for mapping purposes.
    invoices.stream().map(Invoice::total).forEach(System.out::println);
    System.out.println("Sum = " + invoices.stream().map(Invoice::total).reduce((x, y) -> x.add(y)).get());
  }

  // This is just my style of writing classes. Yours can differ.
  static class Invoice
  {
    private String company;
    private String number;
    private BigDecimal unitPrice;
    private BigDecimal quantity;

    public Invoice()
    {
      unitPrice = quantity = BigDecimal.ZERO;
    }

    public Invoice(String company, String number, BigDecimal unitPrice, BigDecimal quantity)
    {
      setCompany(company);
      setNumber(number);
      setUnitPrice(unitPrice);
      setQuantity(quantity);
    }

    public BigDecimal total()
    {
      return unitPrice.multiply(quantity);
    }

    public String getCompany()
    {
      return company;
    }

    public void setCompany(String company)
    {
      this.company = company;
    }

    public String getNumber()
    {
      return number;
    }

    public void setNumber(String number)
    {
      this.number = number;
    }

    public BigDecimal getUnitPrice()
    {
      return unitPrice;
    }

    public void setUnitPrice(BigDecimal unitPrice)
    {
      this.unitPrice = unitPrice;
    }

    public BigDecimal getQuantity()
    {
      return quantity;
    }

    public void setQuantity(BigDecimal quantity)
    {
      this.quantity = quantity;
    }
  }
}
5
ответ дан Aman Agnihotri 20 August 2018 в 16:55
поделиться
  • 1
    Разве это .map(n -> n) бесполезно? Также get() не требуется. – Rohit Jain 25 March 2014 в 15:48
  • 2
    @RohitJain: Обновлено. Благодарю. Я использовал get(), когда он возвращает значение Optional, которое возвращается вызовом reduce. Если вы хотите работать с Optional или просто распечатать сумму, тогда да, get() не требуется. Но печать опций напрямую печатает синтаксис на основе Optional[<Value>], который я сомневаюсь в необходимости пользователя. Поэтому get() необходим для получения значения из Optional. – Aman Agnihotri 25 March 2014 в 15:56
  • 3
    @AmanAgnihotri, проверьте мой OP для моего редактирования. – ryvantage 25 March 2014 в 19:56
  • 4
    @ryvantage: Да, ваш подход - именно то, как я бы это сделал. :) – Aman Agnihotri 25 March 2014 в 20:09
  • 5
    Не используйте безусловный вызов get! Если values является пустым списком, опциональный не будет содержать значения и будет вызывать NoSuchElementException, когда вызывается get. Вместо этого вы можете использовать values.stream().reduce(BigDecimal::add).orElse(BigDecimal.ZERO). – eee 2 April 2016 в 10:31

Я придумал следующую реализацию Collector для решения этой проблемы. Это написано в Groovy, поэтому вам придется адаптировать его, если вы используете только Java, но у него есть преимущество в поддержке потока произвольных типов, если эти типы поддерживаются ctor BigDecimal:

public static <T> Collector<T, ?, BigDecimal> summingBigDecimal() {
    new java.util.stream.Collectors.CollectorImpl<?, ?, BigDecimal>(
            { [BigDecimal.ZERO].toArray(new BigDecimal[1]) },
            { BigDecimal[] a, Object t ->
                a[0] = (t instanceof BigDecimal ? a[0].add(t) : a[0].add(new BigDecimal(t)))
            },
            { BigDecimal[] a, BigDecimal[] b -> a[0].add(b[0]) },
            { BigDecimal[] a -> a[0] }, Collections.emptySet());
}

Я уверен, что его можно немного очистить, но он может делать такие вещи, как:

Stream.of("1", 3L, new BigDecimal("5")).collect(Collectors.summingBigDecimal())

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

-1
ответ дан C. Springer 20 August 2018 в 16:55
поделиться
  • 1
    Почему голос? Это абсолютно правильный ответ OP? – C. Springer 7 January 2018 в 17:23
  • 2
    Возможно, для доступа к внутреннему классу JRE, например java.util.stream.Collectors.CollectorImpl. Или для обеспечения ужасно сложного решения вопроса, который уже получил гораздо более простое решение за несколько лет до вашего ответа. Или для размещения кода Groovy для Java-вопроса. – Holger 12 July 2018 в 17:43

Если вы не возражаете против зависимости третьей стороны, в Collectors2 есть класс с именем Коллекции Eclipse , который содержит методы, возвращающие коллекторы для суммирования и , суммируя BigDecimal и BigInteger. Эти методы принимают функцию в качестве параметра, поэтому вы можете извлечь значение BigDecimal или BigInteger из объекта.

List<BigDecimal> list = mList(
        BigDecimal.valueOf(0.1),
        BigDecimal.valueOf(1.1),
        BigDecimal.valueOf(2.1),
        BigDecimal.valueOf(0.1));

BigDecimal sum =
        list.stream().collect(Collectors2.summingBigDecimal(e -> e));
Assert.assertEquals(BigDecimal.valueOf(3.4), sum);

BigDecimalSummaryStatistics statistics =
        list.stream().collect(Collectors2.summarizingBigDecimal(e -> e));
Assert.assertEquals(BigDecimal.valueOf(3.4), statistics.getSum());
Assert.assertEquals(BigDecimal.valueOf(0.1), statistics.getMin());
Assert.assertEquals(BigDecimal.valueOf(2.1), statistics.getMax());
Assert.assertEquals(BigDecimal.valueOf(0.85), statistics.getAverage());

Примечание: я являюсь коммиттером для коллекций Eclipse.

3
ответ дан Donald Raab 20 August 2018 в 16:55
поделиться

Вы можете суммировать значения потока BigDecimal с использованием многоразового коллектора с именем summingUp:

BigDecimal sum = bigDecimalStream.collect(summingUp());

. Collector можно реализовать следующим образом: :

public static Collector<BigDecimal, ?, BigDecimal> summingUp() {
    return Collectors.reducing(BigDecimal.ZERO, BigDecimal::add);
}
3
ответ дан Igor Akkerman 20 August 2018 в 16:55
поделиться

У этого сообщения уже есть проверенный ответ, но ответ не фильтрует для нулевых значений. Правильный ответ должен предотвращать пустые значения, используя функцию Object :: nonNull как предикат.

BigDecimal result = invoiceList.stream()
    .map(Invoice::total)
    .filter(Objects::nonNull)
    .filter(i -> (i.getUnit_price() != null) && (i.getQuantity != null))
    .reduce(BigDecimal.ZERO, BigDecimal::add);

Это предотвращает попытку суммирования нулевых значений по мере уменьшения.

1
ответ дан Siraj 20 August 2018 в 16:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: