Статьи

Что не так в Java 8, часть V: кортежи

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

Какие кортежи

На первый взгляд, кортежи выглядят как упорядоченные наборы значений разных типов. В отличие от массивов или списков, которые могут содержать несколько значений одного типа, кортежи могут «содержать» значения смешанных типов. Чаще всего используются кортежи , как правило , даются имена конкретных: кортеж из двух элементов , как правило , называется парой (или двойной , пара , двойной , близнец ) и кортеж из трех элементов , как правило , называется тройным . Есть также такие названия, как четверка , пятёркаи так далее. Обратите внимание, что есть также кортежи с 0 и 1 элементом. Кортеж из одного элемента редко используется как таковой, поскольку вместо него может использоваться сам элемент. Однако для теоретического обсуждения может быть полезно рассмотреть такие кортежи. Кортеж из одного элемента иногда называют одним .

Необязательный класс в Java 8 фактически является кортежем из одного элемента.
Иногда проще использовать имя tuple n, где n равно количеству элементов.

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

(3, 5)

это кортеж, состоящий из двух целочисленных значений и:

(«Возраст», 42)

это кортеж, состоящий из одной строки и одного целого числа.

Есть ли в Java кортежи

Без сомнения, у Java есть кортежи. Он имеет неявные и явные. Неявные кортежи являются аргументом того, что мы называли мультиаргументными методами:

int mult(int x, int y) {
  return x * y;
}

int z = mult(3, 5);

В этом примере (3, 5)это можно рассматривать как кортеж, который, в свою очередь, заставляет multметод выглядеть как один аргумент один!

Это важно, если мы думаем о функциях. Функция является применение одного набора (функция в области ) в другой набор (функция в области значений ).

Важно отметить, что домен ОДИН установлен. Это не может быть два набора, ни три набора, ни что-либо еще. Однако это может быть многомерный набор, то есть набор кортежей! Итак, функция может принимать только один аргумент.

Наличие функций двух или более аргументов является только синтаксическим сахаром для функций пар, троек и так далее. И то, что выглядит как функция двух аргументов, как следующая лямбда в Java 8:

(int x, int y) -> x * y

на самом деле это форма функции int x int (декартово произведение) к int . И результатом декартового произведения int x int является один набор кортежей.

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

Примечание: функции двух аргументов не всегда являются синтаксическим сахаром для функций кортежей. Они также могут использоваться для функций с одним аргументом для функций. Это связано с оценкой аргументов. Для получения дополнительной информации см. Первую статью этой серии: Что не так в Java 8, Часть I: Curry vs Closures

Создание наших собственных кортежей

Если нам нужно вернуть кортежи, их очень легко создать. Например, если нам нужно создать функцию от double до (int, double), принимающую в doubleкачестве аргумента a и возвращающую ее целые и десятичные части, мы можем написать:

public class TupleExample {

  public static class Tuple {

    public final int integerPart;
    public final double decimalPart;

    public Tuple(int integerPart, double decimalPart) {
      super();
      this.integerPart = integerPart;
      this.decimalPart = decimalPart;
    }

    @Override
    public String toString() {
      return String.format("(%s, %s)", integerPart, decimalPart);
    }
  }

  private static Tuple split(Double x) {
    int integerPart = x.intValue();
    return new Tuple(integerPart, x - integerPart);
  };

  public static void main(String[] args) {
    System.out.println(split(5.3));
    System.out.println(split(-2.7));
  }
}

Эта программа будет отображать:

(5, 0.2999999999999998)
(-2, -0.7000000000000002)

Теперь мы видим, что большинство объектов в Java на самом деле являются кортежами!

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

public class TupleGenericExample {

  private static Tuple<Integer, Double> split(Double x) {
    int integerPart = x.intValue();
    return new Tuple<>(integerPart, x - integerPart);
  };

  public static void main(String[] args) {
    System.out.println(split(5.3));
    System.out.println(split(-2.7));
  }
}

public static class Tuple<T, U> {

  public final T _1;
  public final U _2;

  public Tuple(T arg1, U arg2) {
    super();
    this._1 = arg1;
    this._2 = arg2;
  }

  @Override
  public String toString() {
    return String.format("(%s, %s)", _1, _2);
  }
}

Итак, еще раз, поскольку так легко реализовать кортежи, в чем проблема? Есть много проблем:

  • Мы можем написать свои собственные кортежи, но мы не можем использовать их в общедоступном API. Для этого нам нужно, чтобы все разработчики Java согласовали общую Tupleреализацию. Мы не можем этого сделать, если Tupleв Java API не добавлена реализация. Конечно, мы, вероятно , потребуется Tuple3, Tuple4и так далее для небольшого числа элементов. (Хотя можно определить Tupleболее двух элементов в терминах пар, например Tuple<T, Tuple<U, V>>, используя структуры, такие как связанные списки или двоичные деревья, это не очень практично.)
  • Мы не можем проверить тип кортежа с instanceof. Итак, если мы хотим проверить, является ли объект экземпляром кортежа, нам нужно будет выполнить проверку для необработанного типа, а затем столько проверок, сколько элементов в кортеже.
  • Для Tupleпростоты использования нам понадобится тот же синтаксический сахар, который предлагается для аргументов в лямбда-нотации, то есть нам понадобятся литералы.
  • И есть вездесущая проблема примитивов. Работа только с четырьмя примитивными типами ( int, long, double, boolean) даст 25 различных типов пара, 125 различных типов троек, и так далее. Мы можем рассчитывать на автоматическую коробку / распаковку для этого. Намного лучше, нам снова понадобятся типы значений.

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

Что дальше?

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

Предыдущие статьи

Что не так в Java 8, часть I: каррирование против замыканий

Что не так в Java 8, часть II: функции и примитивы

Что не так в Java 8, часть III: потоки и параллельные потоки

Что не так в Java 8, часть IV: монады