Вернуть 2 подсчитанных значения из одной функции - PullRequest
6 голосов
/ 26 сентября 2019

Я хочу создать функцию, которая возвращает два подсчитанных значения.Значения подсчитываются путем итерации по циклу for.

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

За последние несколько лет я писал в javascript, и вот как бы я сделал это в javascript.

function countBoysAndWomen() {
    var womenCounter = 0;
    var boysCounter = 0;

    for (var p of persons) {
        if (p.isAdult() && p.isFemale()) womenCounter++;
        else if (p.isChild() && p.isMale()) boysCounter++;
    }

    return {amountOfWomen: womenCounter, amountOfBoys: boysCounter};
}

Теперь моя проблема, как я могу вернуть такой объект в Java?Нужно ли создавать новый класс?Как бы вы назвали этот класс?

Разве это не крайне неэффективно, имея полный класс только для этой маленькой цели?

Что если бы я хотел посчитать другую пару значений?Должен ли я создать еще один совершенно новый класс?

Является ли это ниже на самом деле лучшим способом создания такой функции?

private Counter countBoysAndWomen() {
     Counter counter = new Counter();

     for (Person p:persons) {
         if (p.isBoy()) counter.addBoy();
         else if (p.isWomen()) counter.addWoman();
     }

     return counter;
}

Конечно, другой вариант - разделить функцию нафункции «countBoys ()» и «countWomen ()», но тогда мне придется дважды перебирать массив, и это не было бы оптимально, верно?

Ответы [ 8 ]

3 голосов
/ 26 сентября 2019

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

public HashMap<String,Integer>  countBoysAndWomen() {

        int womenCounter = 0;
        int boysCounter = 0;
        HashMap<String,Integer> hm = new HashMap<>();
        for (int i = 0 ; i < persons.size() ; i++ ) {

            if (p.isAdult() && p.isFemale()){
              womenCounter++;
            }
            else if (p.isChild() && p.isMale()) {
            boysCounter++;
            }
        }
         hm.put("women",womenCounter );
         hm.put("boy" , boysCounter);

         return hm;
     }

для получения значения hashmap: -

 for (Map.Entry<String,String> entry : hm.entrySet())  
            System.out.println("Key = " + entry.getKey() + 
                             ", Value = " + entry.getValue()); 
    } 
1 голос
/ 29 сентября 2019

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

Преждевременная оптимизация

Разве это не крайне неэффективно, имея полный класс только для этой маленькой цели?

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

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

Что если бы я хотел посчитать другую пару значений?Должен ли я создать еще один совершенно новый класс?

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

Давайте напишем некоторый код.Во-первых, класс Person.

Enum

Обратите внимание, что мы вложили два перечисления, Gender & Maturity.Если вы не знакомы, см. Oracle Tutorial .Средство enum в Java на намного более полезно, гибко и мощно, чем в других языках.Использование строк в качестве флагов значений неуклюже и подвержено ошибкам.Компилятор не может помочь вам с опечатками.Напротив, компилятор может помочь вам с перечислениями, добавив type-safety в ваш код, обеспечивая при этом допустимые значения.

Преимущества использования перечислений: очень мало используемой памяти и очень быстрое выполнение.

package work.basil.example;

import java.util.Objects;
import java.util.UUID;

public class Person
{
    public enum Gender
    {
        FEMALE, MALE
    }

    public enum Maturity
    {
        ADULT, CHILD
    }

    // Members
    private UUID id;
    private Gender gender;
    private Maturity maturity;

    // Constructor
    public Person ( UUID id , Gender gender , Maturity maturity )
    {
        Objects.requireNonNull ( id );
        Objects.requireNonNull ( gender );
        Objects.requireNonNull ( maturity );

        this.id = id;
        this.gender = gender;
        this.maturity = maturity;
    }

    // Accessors

    public UUID getId ( )
    {
        return id;
    }

    public Gender getGender ( )
    {
        return gender;
    }

    public Maturity getMaturity ( )
    {
        return maturity;
    }

    // Object overrides

    @Override
    public String toString ( )
    {
        return "Person{" +
                "id=" + id +
                ", gender=" + gender +
                ", maturity=" + maturity +
                '}';
    }

    @Override
    public boolean equals ( Object o )
    {
        if ( this == o ) return true;
        if ( o == null || getClass () != o.getClass () ) return false;
        Person person = ( Person ) o;
        return id.equals ( person.id );
    }

    @Override
    public int hashCode ( )
    {
        return Objects.hash ( this.id );
    }
}

Напишите метод countPeople, который принимает List из Person объектов, вместе с вашей парой критериев (пол и зрелость).

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

private Integer countPeople ( List < Person > people , Person.Gender gender , Person.Maturity maturity )
{
    Objects.requireNonNull ( people );
    Objects.requireNonNull ( gender );
    Objects.requireNonNull ( maturity );

    Integer count = 0;
    for ( Person person : people )
    {
        if ( ( person.getGender ().equals( gender ) ) && ( person.getMaturity ().equals( maturity ) ) )
        {
            count = ( count + 1 );
        }
    }
    return count;
}

И напишите некоторый код для осуществления этого подсчета кода.

Синтаксис List.of является новым в Java 9 и более поздних версиях, создавая неизменяемый объект List неопределенного конкретногокласс в одну простую строку кода.

List < Person > people = List.of (
        new Person ( UUID.randomUUID () , Person.Gender.FEMALE , Person.Maturity.CHILD ) ,
        new Person ( UUID.randomUUID () , Person.Gender.FEMALE , Person.Maturity.ADULT ) ,
        new Person ( UUID.randomUUID () , Person.Gender.MALE , Person.Maturity.ADULT ) ,
        new Person ( UUID.randomUUID () , Person.Gender.FEMALE , Person.Maturity.CHILD ) ,
        new Person ( UUID.randomUUID () , Person.Gender.MALE , Person.Maturity.ADULT ) ,
        new Person ( UUID.randomUUID () , Person.Gender.MALE , Person.Maturity.CHILD ) ,
        new Person ( UUID.randomUUID () , Person.Gender.FEMALE , Person.Maturity.ADULT )
);

Integer women = this.countPeople ( people , Person.Gender.FEMALE , Person.Maturity.ADULT );
Integer boys = this.countPeople ( people , Person.Gender.MALE , Person.Maturity.CHILD );

Report.

System.out.println ( "people = " + people );
System.out.println ( "women = " + women );
System.out.println ( "boys = " + boys );

people = [Person {id = 1ac225e6-f21c-49f5-82d5-8e0f289f16e0, пол =ЖЕНЩИНА, зрелость = РЕБЕНОК, Лицо {id = 333828cc-48e6-4d0c-9937-66f3168445bd, пол = ЖЕНЩИНА, зрелость = ВЗРОСЛЫЙ}, Лицо {id = 4d37bc08-1e1f-4806-8d84-dc314b6b2cd8, пол = МУЖ, зрелость =ADULT}, Person {id = 0cd2a38a-5b01-4091-9cb2-c0284739aa70, пол = FEMALE, зрелость = РЕБЕНОК), Person {id = 36d9af87-3cbb-44bc-bf03-67df45a5d8c8, пол = MALE, зрелость = ADULT}, Person{id = 2fef944a-79c9-4b29-9191-4bc694b58a4d, пол = MALE, зрелость = РЕБЕНОК), Person {id = ffc8f355-9a4b-47c3-8092-f64b6da87483, пол = FEMALE, зрелость = ADULT}]

women = 2

boys = 1


Streams

Мы могли бы придумать и использовать вместо этого потоки for loсоч.Не обязательно лучше в этом случае, но веселее.

Мы собираемся позвонить Stream::count.Это возвращает long, поэтому измените наше использование 32-битного целого числа на 64-bit long.Или же вы можете привести long к целому числу.

Вызов List::stream генерирует поток объектов Person, содержащихся в списке.

A Predicate объект содержит наш тест на пол и наш тест на зрелость.Вызов Stream::filter применяет предикат для выбора объектов.Они подсчитываются по телефону Stream::count.

private Long countPeople ( List < Person > people , Person.Gender gender , Person.Maturity maturity )
{
    Objects.requireNonNull ( people );
    Objects.requireNonNull ( gender );
    Objects.requireNonNull ( maturity );

    Predicate < Person > predicate = ( Person person ) -> ( person.getGender ().equals ( gender ) && person.getMaturity ().equals ( maturity ) );
    Long count = people.stream ().filter ( predicate ).count ();
    return count;
}

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


Базы данных

Если эти данные поступают из базы данных, вы должны выполнять эту работу в этой базе данных, а не на стороне Java.Механизмы баз данных, такие как Postgres , оптимизированы для * , чтобы выполнять только такие операции выбора, сравнения, сортировки и подсчета.

1 голос
/ 26 сентября 2019

Так что в Java, к сожалению, нет концепции возврата нескольких значений из метода.Ниже приведены разумные варианты, каждый из которых имеет свои преимущества / недостатки:

  • Создание объекта модели для хранения значений (одно поле для каждого используемого значения)
  • Возвращение массива или спискасо значениями
  • Возвращает карту из функции с ключевыми значениями
  • Возвращает Map.Entry с двумя значениями (другие, похожие объекты существуют)
  • Передать аргумент вметод, который будет содержать результаты (например, передать в карту)
  • Переместить метод в новый собственный класс, в котором хранятся результаты, добавить одно поле для каждого значения и добавить получатели для получения результатов

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

Одним из ключевых недостатков большинства решений является отсутствие быстрого отказа, когда все меняется, или вызывающий абонент не соблюдаетЭлектронный контракт.«Fail Fast» (то есть получение ошибок, которые могут привести к сбоям как можно раньше в процессе разработки) приносится в жертву при использовании универсальных конструкций.

Например, при использовании объекта модели IDE или компилятор сообщит вам, когдавызывающая функция пытается получить доступ к несуществующему полю.

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

Для чего это стоит - большую часть времени я идудля модельного объектного подхода, если только это не одноразовое внутреннее использование, которое все в одном файле класса.Хотя добавление класса является «накладным», отказоустойчивая и самодокументируемая природа решения является ценной.

В: «Разве это не является крайне неэффективным, имея полный класс только для этой небольшой цели?? "

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

Q.«Что если бы я хотел посчитать другую пару значений? Должен ли я создать еще один совершенно новый класс?»

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

1 голос
/ 26 сентября 2019

Вместо решений, использующих Map, я бы предложил вам создать закрытый класс, в котором вы можете сохранить результат:

private class People {
    public int boys = 0;
    public int women = 0;
}

Этот класс можно использовать как внутренний класс, где ваш countBoysAndGirls метод находится.Вместо того, чтобы возвращать метод Map, вы можете заставить его возвращать People (я не могу придумать лучшего названия для класса People).

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

0 голосов
/ 27 сентября 2019

Я покажу вам современный и истинный подход Java, используемый в Stream API и реализованный в классах [Int/Long/Double]SummaryStatistics.

final class Person {
    private final Gender gender;

    public Person(Gender gender) {
        this.gender = gender;
    }

    // one method that returns the gender
    // no isWoman, isMan methods
    public Gender getGender() {
        return gender;
    }
}

enum Gender {
    MAN,
    WOMAN;
}

final class PersonGenderStatistics implements Consumer<Person> {
    private final Map<Gender, Integer> count = new HashMap<>();

    // the number is calculated for every gender
    @Override
    public void accept(Person person) {
        count.compute(person.getGender(),
             (gender, count) -> count == null ? 1 : ++count);
    }

    // combines two statistics
    public void combine(PersonGenderStatistics other) {
        count.putAll(other.count);
    }

    // gives the summary for the given gender
    public Integer getCountByGender(Gender gender) {
        return count.get(gender);
    }
}

final class Main {
    public static void main(String[] args) {
        final List<Person> persons = Arrays.asList(
                new Person(Gender.MAN),
                new Person(Gender.WOMAN),
                new Person(Gender.WOMAN),
                new Person(Gender.WOMAN),
                new Person(Gender.WOMAN),
                new Person(Gender.WOMAN),
                new Person(Gender.MAN),
                new Person(Gender.MAN)
        );

        final PersonGenderStatistics statistics =
                persons.stream()
                        .collect(
                                PersonGenderStatistics::new,
                                PersonGenderStatistics::accept,
                                PersonGenderStatistics::combine
                        );

        System.out.println(statistics.getCountByGender(Gender.MAN));    // 3
        System.out.println(statistics.getCountByGender(Gender.WOMAN));  // 5
    }
}
0 голосов
/ 27 сентября 2019

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

Optional<Integer> boysCount;
Optional<Integer> womenCount;

private void countBoysAndWomen() {
  int boys = 0;
  int women = 0;
  for( Person p: persons ) {
    if( p.isBoy() ) boys++;
    else if( p.isWomen() ) women++;
  }
  boysCount = Optional.of( boys );
  womenCount = Optional.of( women );
}

countBoysAndWomen();

System.out.println( "boys: " + boysCount.get() );
System.out.println( "women: " + womenCount.get() );
0 голосов
/ 26 сентября 2019

Итак, во-первых, давайте предположим, что у нас есть некоторая коллекция Persons:

var persons = initializePersons();

Далее, мы предполагаем, что Person имеет несколько информационных методов, где реализующие классы могут определить, что именно эти методы возвращают:

public interface Person
{
    boolean isAdult();
    boolean isMale();
    default boolean isChild()
    {
        return !isAdult();
    }
    default boolean isFemale()
    {
        return !isMale();
    }
}

Теперь мы можем обработать persons следующим образом:

var women = persons.stream().filter(Person::isAdult).filter(Person::isFemale).count();
var boys = persons.stream().filter(Person::isChild).filter(Person::isMale).count();

Наконец, мы можем вернуть значения в Map<String, Long>, например:

return Map.of("women", women, "boys", boys);

Всего:

public Map<String, Long> countBoysAndWomen()
{
    var persons = initializePersons();
    var women = persons.stream().filter(Person::isAdult).filter(Person::isFemale).count();
    var boys = persons.stream().filter(Person::isChild).filter(Person::isMale).count();
    return Map.of("women", women, "boys", boys);
}
0 голосов
/ 26 сентября 2019

Другой вариант - создать CountPair интерфейс с фабрикой и вернуть анонимный и неизменный CountPair с вашим результатом:

public interface CountPair {
    int getBoysCount();
    int getWomenCount();

    static CountPair create(int boys, int women) {
        return new CountPair() {
            public int getBoysCount() { return boys; }
            public int getWomenCount() { return women; }
        };
    }
}

Тогда вы можете просто вернуть:

CountPair.create(boysCount, womenCount);

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...