Возврат нескольких примитивных объектов в Java.Unrecommended? - PullRequest
0 голосов
/ 24 ноября 2018

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

Решение, которое я задумал, состояло в том, чтобы создать класс, объединяющий переменные, которые я хотел вернуть, и вернуть экземплярэтого класса.Например, мне нужно было найти объект в массиве, и я хотел вернуть логическое значение (найдено или нет) и индекс.Я знаю, что мог бы сделать это, просто установив индекс в -1, если ничего не было найдено, но я думаю, что это более понятно с другой стороны

Дело в том, что мне сказал кто-то, кто знает о Java гораздо больше, чем я знаю, что я не должен создавать классы с целью возврата нескольких значений (даже если они связаны).Он сказал, что классы никогда не должны использоваться как структуры C ++, только для группировки элементов.Он также сказал, что методы не должны возвращать непримитивные объекты, они должны получать объект извне и только изменять его.Какие из этих вещей являются правдой?

Ответы [ 4 ]

0 голосов
/ 25 ноября 2018

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

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

  2. Объедините, если возможно, два или более примитивных значений в одно возвращаемое значение.Два int можно объединить в один long, четыре byte можно объединить в один int, boolean и без знака int меньше Integer.MAX_VALUE можно объединить в подписанный int (посмотрите, например, как методы Arrays.binarySearch(...) возвращают свои результаты), положительные double и boolean могут быть объединены в один знак double и т. Д. При возврате извлеките компоненты с помощью сравнений (еслиboolean среди них) и битовые операции (для сдвинутых целочисленных компонентов).

    2a.Один частный случай стоит отметить отдельно.Общепринято (и широко используется) соглашение возвращать null, чтобы указать, что фактически возвращаемое значение недопустимо.Строго говоря, это соглашение заменяет результат с двумя полями - одно неявное логическое поле, которое вы используете при проверке

    if (returnValue != null)
    

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

    ResultClass result = returnValue;
    
  3. Если вы не хотите связываться с классами данных, вы всегда можете вернуть массив Object s:

    public Object[] returnTuple() {
        return new Object[]{1234, "Text", true};
    }
    

    и затем типизируйте его компоненты к желаемым типам:

    public void useTuple() {
        Object[] t = returnTuple();
        int x = (int)t[0];
        String s = (String)t[1];
        boolean b = (boolean)t[2];
        System.out.println(x + ", " + s + ", " + b);
    }
    
  4. Вы можете ввести поле (поля) в свой класс для хранения вспомогательного компонента (компонентов) возврата и возвратаявно только основной компонент (вы решаете, какой из них является основным):

    public class LastResultAware {
        public static boolean found;
        public static int errorCode;
    
        public static int findLetter(String src, char letter) {
            int i = src.toLowerCase().indexOf(Character.toLowerCase(letter));
            found = i >= 0;
            return i;
        }
    
        public static int findUniqueLetter(String src, char letter) {
            src = src.toLowerCase();
            letter = Character.toLowerCase(letter);
            int i = src.indexOf(letter);
            if (i < 0)
                errorCode = -1; // not found
            else {
                int j = src.indexOf(letter, i + 1);
                if (j >= 0)
                    errorCode = -2; // ambiguous result
                else
                    errorCode = 0; // success
            }
            return i;
        }
    
        public static void main(String[] args) {
            int charIndex = findLetter("ABC", 'b');
            if (found)
                System.out.println("Letter is at position " + charIndex);
            charIndex = findUniqueLetter("aBCbD", 'b');
            if (errorCode == 0)
                System.out.println("Letter is only at position " + charIndex);
        }
    }
    

    Обратите внимание, что в некоторых случаях лучше генерировать исключение, указывающее на ошибку, чем возвращать код ошибки, который может вызвать вызывающая сторона.просто забудь проверить.В зависимости от использования эти расширяемые возвращаемые поля могут быть статическими или экземплярами.Когда они статичны, они могут даже использоваться несколькими классами, чтобы служить общей цели и избежать ненужного создания полей.Например, одного public static int errorCode может быть достаточно.Имейте в виду, однако, что этот подход не является потокобезопасным.

0 голосов
/ 24 ноября 2018

Я не должен создавать классы с целью возврата нескольких значений

классы никогда не должны использоваться как структуры C ++, только для группировки элементов.

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

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

В Java самая близкая вещь, которую мы имеем к структуре, это POJO (обычный старый объект Java), обычноизвестный как классы данных в других языках.Эти классы являются просто группировкой данных.Практическое правило для POJO заключается в том, что он должен содержать только примитивы, простые типы (строки, штучные примитивы и т. Д.), Простые контейнеры (map, array, list и т. Д.) Или другие классы POJO.Основные классы, которые можно легко сериализовать.

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

public class Tuple2<T,U>{
    private final T first;
    private final U second;

    public Tuple2(T first, U second) {
        this.first = first;
        this.second = second;
    }

    public T getFirst() { return first; }
    public U getSecond() { return second; }
}

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

public interface Container<T> {
     ...
     public Tuple2<Boolean, Integer> search(T key);
}

Недостаток созданияПодобные классы данных заключаются в том, что для качества жизни мы должны реализовать такие вещи, как toString, hashCode, equals геттеры, сеттеры, конструкторы и т. д. Для каждого кортежа разного размера вы должны создать новый класс (Tuple2, Tuple3, Tuple4 и т. Д.).Создание всех этих методов вносит незначительные ошибки в наши приложения.По этим причинам разработчики часто избегают создания классов данных.

Такие библиотеки, как Lombok, могут быть очень полезны для преодоления этих проблем.Наше определение Tuple2 со всеми перечисленными выше методами можно записать так:

@Data
public class Tuple2<T,U>{
    private final T first;
    private final U second;
}

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

@Data
public class SearchResult {
    private final boolean found;
    private final int index;
}
...
public interface Container<T> {
     ...
     public SearchResult search(T key);
}

методы должны получать объект извне и изменять его только

Это плохой совет.Намного приятнее проектировать данные на основе неизменности.Начиная с Effective Java 2nd Edition, стр. 75

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

Неизменяемые объекты по своей природе поточно-ориентированы ;они не требуют синхронизации.Они не могут быть повреждены несколькими потоками, обращающимися к ним одновременно.Это, безусловно, самый простой подход к обеспечению безопасности потоков.Фактически, ни один поток не может наблюдать какое-либо влияние другого потока на неизменный объект.Следовательно, неизменяемые объекты могут свободно использоваться совместно .

0 голосов
/ 24 ноября 2018

Что касается вашего конкретного примера («как вернуть состояние ошибки и результат?»)

Мне нужно было найти объект в массиве, и я хотел вернуть логическое значение (найденоили нет) и индекс.Я знаю, что мог бы сделать это, просто установив индекс в -1, если ничего не было найдено, но я думаю, что это более понятно с другой стороны.

Возвращение специальных недопустимых значений результата, таких как -1 для "not"найдено "действительно очень часто, и я согласен с вами, что это не слишком красиво

Однако возвращение кортежа (statusCode, resultValue) - не единственная альтернатива.

Самый идиоматический способ сообщить об исключениях в Java - это, как вы уже догадались, использовать исключения.Поэтому возвращайте результат или, если результат не может быть получен, выведите исключение (NoSuchElementException в данном случае).Если это уместно, зависит от приложения: вы не хотите генерировать исключения для «правильного» ввода, его следует зарезервировать для нерегулярных случаев.

В функциональных языках они часто имеют встроенные структуры данных дляэто (например, Try, Option или Either), которые по существу также выполняют statusCode + resultValue для внутреннего использования, но убедитесь, что вы действительно проверяете этот код состояния, прежде чем пытаться получить доступ к значению результата.У Java теперь есть Optional.Если бы я захотел пойти по этому пути, я бы извлек эти типы обёрток из библиотеки и не создавал свои собственные специальные "структуры" (потому что это могло бы только сбить людей с толку).

"методы не должны возвращать не примитивные объекты, они должны получать объект извне и только изменять его "

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

0 голосов
/ 24 ноября 2018

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

В противном случае обычно и полезно использовать концепцию получения / установки для получения и установки параметров в ваших классах.

...