Выходные параметры в Java

Со сторонним API я заметил следующее.

Вместо того, чтобы использовать,

public static string getString(){
   return "Hello World";
}

он использует что-то вроде

public static void getString(String output){

}

и я получаю назначенную строку "output".

Мне любопытно о причине реализации такой функциональности. Каковы преимущества использования таких выходных параметров?

8 ответов

Решение

Что-то не так в вашем примере.

class Foo {

    public static void main(String[] args) {
        String x = "foo";
        getString(x);
        System.out.println(x);
    }

    public static void getString(String output){
        output = "Hello World"
    }
}

В приведенной выше программе будет выведена строка "foo", а не "Hello World".

Некоторые типы являются изменяемыми, и в этом случае вы можете изменить объект, переданный в функцию. Для неизменяемых типов (таких как String), вам придется создать некоторый класс-обертку, который вы можете вместо этого обойти:

class Holder<T> {
    public Holder(T value) {
        this.value = value;
    }
    public T value;
}

Тогда вы можете вместо этого обойти держатель:

public static void main(String[] args) {
    String x = "foo";
    Holder<String> h = new Holder(x);
    getString(h);
    System.out.println(h.value);
}

public static void getString(Holder<String> output){
    output.value = "Hello World"
}

Этот пример неверен, у Java нет выходных параметров.

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

public void doSomething(String[] output) {
    output[0] = "Hello World!";
}

Но ИМХО это отстой на нескольких уровнях.:)

Если вы хотите, чтобы метод возвращал что-то, сделайте так, чтобы он возвращал это. Если вам нужно вернуть несколько объектов, создайте контейнерный класс, чтобы поместить эти объекты и вернуть их.

Я не согласен с Джаспером: "На мой взгляд, это действительно уродливый и плохой способ вернуть более одного результата". В.NET есть интересная конструкция, которая использует выходные параметры:

bool IDictionary.TryGet(key, out value);

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

object obj;
if (myList.TryGet(theKey, out obj))
{
  ... work with the obj;
}

Я постоянно ругаю своих разработчиков, если вижу код в старом стиле, например:

if (myList.Contains(theKey))
{
  obj = myList.Get(theKey);
}

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

Иногда этот механизм может избежать создания нового объекта.

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

Это более эффективно, чем создавать новый объект внутри вызываемого метода, а также возвращать и присваивать его ссылку (создавая мусор, который нужно собирать когда-нибудь).

Строка неизменна, вы не можете использовать псевдо-выходные параметры Java с неизменяемыми объектами.

Кроме того, область вывода ограничена методом getString. Если вы измените выходную переменную, вызывающая сторона ничего не увидит.

Однако вы можете изменить состояние параметра. Рассмотрим следующий пример:

void handle(Request r) {
    doStuff(r.getContent());
    r.changeState("foobar");
    r.setHandled();
}

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

Преимущества:

  • Вам не нужно возвращать специальный объект, содержащий новое содержимое, и следует ли остановить обработку. Этот объект будет использоваться только один раз, что приведет к потере памяти и вычислительной мощности объекта.
  • Вам не нужно создавать еще один объект Request и позволить сборщику мусора избавиться от устаревшей старой ссылки.
  • В некоторых случаях вы не можете создать новый объект. Например, потому что этот объект был создан с использованием фабрики, и у вас нет к нему доступа, или потому что у объекта были слушатели, и вы не знаете, как сказать объектам, которые слушали старый запрос, что они должны вместо этого прослушать новый запрос.

На самом деле, невозможно иметь параметры в java, но вы можете обойти метод, заставляющий метод принимать разыменование для неизменяемой строки и примитивов, либо написав универсальный класс, где неизменяемый является универсальным со значением и установщиком и getter или с помощью массива, где элемент 0 (длина 1) является значением при условии, что он создается вначале, потому что существуют ситуации, когда вам нужно возвращать более одного значения, когда нужно написать класс, просто чтобы вернуть их, где класс используется только пустая трата текста и не может использоваться повторно.

Теперь, будучи C/C++, а также.Net (моно или MS), я убежден, что java не поддерживает хотя бы разыменование примитивов; так что вместо этого я прибегаю к массиву.

Вот пример. Допустим, вам нужно создать функцию (метод), чтобы проверить, является ли индекс действительным в массиве, но вы также хотите вернуть оставшуюся длину после проверки индекса. Давайте назовем его в c как 'bool validate_index(int index, int arr_len, int&rem)'. Способ сделать это в Java был бы "Boolean validate_index(int index, int arr_len, int[] rem1)". rem1 означает, что массив содержит 1 элемент.

public static Boolean validate_index(int index, int arr_len, int[] rem1)
{
    if (index < 0 || arr_len <= 0) return false;

    Boolean retVal = (index >= 0 && index < arr_len);

    if (retVal && rem1 != null) rem1[0] = (arr_len - (index + 1));

    return retVal;

}

Теперь, если мы используем это, мы можем получить и логическое возвращение, и остаток.

 public static void main(String[] args)
 {
    int[] ints = int[]{1, 2, 3, 4, 5, 6};
    int[] aRem = int[]{-1};
    //because we can only scapegoat the de-ref we need to instantiate it first.
    Boolean result = validate_index(3, ints.length, aRem);

    System.out.println("Validation = " + result.toString());
    System.out.println("Remainding elements equals " + aRem[0].toString());

 }

put: Validation = True Put: остальные элементы равны 2

Элементы массива всегда либо указывают на объект в стеке, либо на адрес объекта в куче. Таким образом, использование его в качестве разыменования абсолютно возможно даже для массивов, сделав его двойным массивом, создав его экземпляр как myArrayPointer = new Class[1][], а затем передав его, потому что иногда вы не знаете, какая длина массива будет до вызова, проходящего через алгоритм типа "Boolean tryToGetArray(SomeObject o, T[][] ppArray)", который будет таким же, как в c/ C++, как "template bool tryToGetArray (SomeObject* p, T** ppArray)" или C# 'bool tryToGetArray(SomeObject o, ref T[] array)'. Он работает и работает хорошо до тех пор, пока экземпляр [] [] или [] сначала создается в памяти хотя бы с одним элементом.

Эта функциональность имеет один большой недостаток - она ​​не работает. Параметры функции являются локальными для функции, и их присвоение не оказывает никакого влияния вне функции.
С другой стороны

void getString(StringBuilder builder) {
    builder.delete(0, builder.length());
    builder.append("hello world");
}

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

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

Другие вопросы по тегам