0,0
рейтинг
20 марта 2014 в 00:32

Разработка → Новое в Java 8 перевод

JAVA*
Java еще не умерла — и люди начинают это понимать.

Добро пожаловать в ведение по Java 8. Этот материал шаг за шагом познакомит вас со всеми новыми фичами языка. Вы научитесь использовать методы интерфейсов по умолчанию (default interface methods), лямбда-выражения (lambda expressions), ссылки на методы (method references) и повторяемые аннотации (repeatable annotations). Все это будет сопровождаться короткими и простыми примерами кода. В конце статьи вы познакомитесь с наиболее свежими изменениями в API, касающихся потоков, функциональных интерфейсов, расширений для ассоциативных массивов, а также с изменениями в API работы с датами.

Методы интерфейсов по умолчанию


Java 8 позволяет вам добавлять неабстрактные реализации методов в интерфейс, используя ключевое слово default. Эта фича также известна, как методы расширения. Вот наш первый пример:

interface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

Кроме абстрактного метода calculate интерфейс Formula также определяет метод по умолчанию sqrt. Классы, имплементирующие этот интерфейс, должны переопределить только абстрактный метод calculate. Метод по умолчанию sqrt будет доступен без переопределения.

Formula formula = new Formula() {
    @Override
    public double calculate(int a) {
        return sqrt(a * 100);
    }
};

formula.calculate(100);     // 100.0
formula.sqrt(16);                // 4.0

formula реализован как анонимный объект. Этот код довольно избыточен: целых 6 строчек для такого простого вычисления как sqrt(a * 100). В следующем разделе мы увидим, что в Java 8 есть куда более изящный способ реализации объектов с одним методом.

Лямбда-выражения


Давайте начнем с простого примера: сортировка массива строк в предыдущих версиях языка.

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});

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

Java 8 предоставляет гораздо более короткий синтаксис — лямбда-выражения, чтобы вам не приходилось тратить время на создание анонимных объектов:

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});

Как видите, код гораздо короче и куда более читаем. И его можно сделать еще короче:

Collections.sort(names, (String a, String b) -> b.compareTo(a));

Для однострочных методов вы можете опустить скобки {} и ключевое слово return. Так еще короче:

Collections.sort(names, (a, b) -> b.compareTo(a));

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

Функциональные интерфейсы


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

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

Пример:

@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}

Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted);    // 123

Имейте в виду, что этот код останется корректным даже если убрать аннотацию @FunctionalInterface.

Ссылки на методы и конструкторы


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

Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted);   // 123

Java 8 позволяет вам передавать ссылки на методы или конструкторы. Для этого нужно использовать ключевое слово ::. Предыдущий пример иллюстрирует передачу ссылки на статический метод. Однако мы также можем ссылаться на экземплярный метод:

class Something {
    String startsWith(String s) {
        return String.valueOf(s.charAt(0));
    }
}

Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);    // "J"

Давайте посмотрим, как передавать ссылки на конструкторы. Сперва определим бин с несколькими конструкторами:

class Person {
    String firstName;
    String lastName;

    Person() {}

    Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

Затем определим интерфейс фабрики, которая будет использоваться для создания новых персон:

interface PersonFactory<P extends Person> {
    P create(String firstName, String lastName);
}

Теперь вместо реализации интерфейса мы соединяем все вместе при помощи ссылки на конструктор:

PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

Мы создаем ссылку на конструктор с помощью Person::new. Компилятор автоматически выбирает подходящий конструктор, сигнатура которого совпадает с сигнатурой PersonFactory.create.

Области действия лямбд


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

final int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

Но в отличии от анонимных объектов, переменная num не обязательно должна быть объявлена как final. Такой код тоже сработает:

int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

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

int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
num = 3;

Запись в переменную num в пределах лямбда-выражения также запрещена.

Доступ к полям и статическим переменным

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

class Lambda4 {
    static int outerStaticNum;
    int outerNum;

    void testScopes() {
        Converter<Integer, String> stringConverter1 = (from) -> {
            outerNum = 23;
            return String.valueOf(from);
        };

        Converter<Integer, String> stringConverter2 = (from) -> {
            outerStaticNum = 72;
            return String.valueOf(from);
        };
    }
}

Доступ к методам интерфейсов по умолчанию

Помните пример с формулой из первого раздела? В интерфейсе Formula определен метод по умолчанию sqrt, который доступен из каждой имплементации интерфейса, включая анонимные объекты. Однако это не сработает в лямбда-выражениях.

Внутри лямбда-выражений запрещено обращаться к методам по умолчанию. Следующий код не скомпилируется:

Formula formula = (a) -> sqrt( a * 100);

Встроенные функциональные интерфейсы


JDK 1.8 содержит множество встроенных функциональных интерфейсов. Некоторые из них хорошо известны по предыдущим версиям языка, например, Comparator или Runnable. Все эти интерфейсы были поддержаны в лямбдах добавлением аннотации @FunctionalInterface.

Однако в Java 8 также появилось много новых функциональных интерфейсов, которые призваны облегчить вам жизнь. Некоторые интерфейсы хорошо известны по библиотеке Google Guava. Даже если вы незнакомы с этой библиотекой, вам стоить взглянуть, как эти интерфейсы были дополнены некоторыми полезными методами расширений.

Предикаты

Предикаты — это функции, принимающие один аргумент, и возвращающие значение типа boolean. Интерфейс содержит различные методы по умолчанию, позволяющие строить сложные условия (and, or, negate).

Predicate<String> predicate = (s) -> s.length() > 0;

predicate.test("foo");              // true
predicate.negate().test("foo");     // false

Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;

Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();

Функции

Функции принимают один аргумент и возвращают некоторый результат. Методы по умолчанию могут использоваться для построения цепочек вызовов (compose, andThen).

Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);

backToString.apply("123");     // "123"

Поставщики

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

Supplier<Person> personSupplier = Person::new;
personSupplier.get();   // new Person

Потребители

Потребители (consumers) представляют собой операции, которые производятся на одним входным аргументом.

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));

Компараторы

Компараторы хорошо известны по предыдущим версиям Java. Java 8 добавляет в интерфейс различные методы по умолчанию.

Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);

Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");

comparator.compare(p1, p2);             // > 0
comparator.reversed().compare(p1, p2);  // < 0

Опциональные значения

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

Опциональные значение — это по сути контейнер для значения, которое может быть равно null. Например, вам нужен метод, который возвращает какое-то значение, но иногда он должен возвращать пустое значение. Вместо того, чтобы возвращать null, в Java 8 вы можете вернуть опциональное значение.

Optional<String> optional = Optional.of("bam");

optional.isPresent();           // true
optional.get();                 // "bam"
optional.orElse("fallback");    // "bam"

optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "b"

Потоки


Тип java.util.Stream представляет собой последовательность элементов, над которой можно производить различные операции. Операции над потоками бывают или промежуточными (intermediate) или конечными (terminal). Конечные операции возвращают результат определенного типа, а промежуточные операции возвращают тот же поток. Таким образом вы можете строить цепочки из несколько операций над одним и тем же потоком. Поток создаются на основе источников, например типов, реализующих java.util.Collection, такие как списки или множества (ассоциативные массивы не поддерживаются). Операции над потоками могут выполняться как последовательно, так и параллельно.

Сначала давайте посмотрим, как работать с потоком последовательно. Сперва создадим источник в виде списка строк:

List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");

В Java 8 вы можете быстро создавать потоки, используя вызовы Collection.stream() или Collection.parallelStream(). Следующие разделы объясняют наиболее распространенные операции над потоками.

Filter

Операция Filter принимает предикат, который фильтрует все элементы потока. Эта операция является промежуточной, т.е. позволяет нам вызвать другую операцию (например, forEach) над результатом. ForEach принимает функцию, которая вызывается для каждого элемента в (уже отфильтрованном) поток. ForEach является конечной операцией. Она не возращает никакого значения, поэтому дальнейший вызов потоковых операций невозможен.

stringCollection
    .stream()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

// "aaa2", "aaa1"

Sorted

Операция Sorted является промежуточной операцией, которая возвращает отсортированное представление потока. Элементы сортируются в обычном порядке, если вы не предоставили свой компаратор:

stringCollection
    .stream()
    .sorted()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

// "aaa1", "aaa2"

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

System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1

Map

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

stringCollection
    .stream()
    .map(String::toUpperCase)
    .sorted((a, b) -> b.compareTo(a))
    .forEach(System.out::println);

// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"

Match

Для проверки, удовлетворяет ли поток заданному предикату, используются различные операции сопоставления (match). Все операции сопоставления являются конечными и возвращают результат типа boolean.

boolean anyStartsWithA = 
    stringCollection
        .stream()
        .anyMatch((s) -> s.startsWith("a"));

System.out.println(anyStartsWithA);      // true

boolean allStartsWithA = 
    stringCollection
        .stream()
        .allMatch((s) -> s.startsWith("a"));

System.out.println(allStartsWithA);      // false

boolean noneStartsWithZ = 
    stringCollection
        .stream()
        .noneMatch((s) -> s.startsWith("z"));

System.out.println(noneStartsWithZ);      // true

Count

Операция Count является конечной операцией и возвращает количество элементов в потоке. Типом возвращаемого значения является long.

long startsWithB = 
    stringCollection
        .stream()
        .filter((s) -> s.startsWith("b"))
        .count();

System.out.println(startsWithB);    // 3

Reduce

Эта конечная операция производит свертку элементов потока по заданной функции. Результатом является опциональное значение.

Optional<String> reduced =
    stringCollection
        .stream()
        .sorted()
        .reduce((s1, s2) -> s1 + "#" + s2);

reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

Параллельные потоки


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

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

Сперва создадим большой список из уникальных элементов:

int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
    UUID uuid = UUID.randomUUID();
    values.add(uuid.toString());
}

Теперь измерим время сортировки этого списка.

Последовательная сортировка

long t0 = System.nanoTime();

long count = values.stream().sorted().count();
System.out.println(count);

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));

// sequential sort took: 899 ms

Параллельная сортировка

long t0 = System.nanoTime();

long count = values.parallelStream().sorted().count();
System.out.println(count);

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));

// parallel sort took: 472 ms

Как вы можете видеть, оба куска кода практически идентичны, однако параллельная сортировка почти в два раза быстрее. Все, что вам нужно сделать, это заменить вызов stream() на parallelStream().

Ассоциативные массивы


Как уже упоминалось, ассоциативные массивы (maps) не поддерживают потоки. Вместо этого ассоциативные массивы теперь поддерживают различные полезные методы, которые решают часто встречаемые задачи.

Map<Integer, String> map = new HashMap<>();

for (int i = 0; i < 10; i++) {
    map.putIfAbsent(i, "val" + i);
}

map.forEach((id, val) -> System.out.println(val));

Этот код в особых комментариях не нуждается: putIfAbsent позволяет нам не писать дополнительные проверки на null; forEach принимает потребителя, который производит операцию над каждым элементом массива.

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

map.computeIfPresent(3, (num, val) -> val + num);
map.get(3);             // val33

map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9);     // false

map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23);    // true

map.computeIfAbsent(3, num -> "bam");
map.get(3);             // val33

Затем мы узнаем, как удалить объект по ключу, только если этот объект ассоциирован с ключом:

map.remove(3, "val3");
map.get(3);             // val33

map.remove(3, "val33");
map.get(3);             // null

Еще один полезный метод:

map.getOrDefault(42, "not found");  // not found

Объединить записи двух массивов? Легко:

map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9

map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9concat

В случае отсутствия ключа Merge создает новую пару ключ-значение. В противном случае — вызывает функцию объединения для существующего значения.

API для работы с датами


Java 8 содержит совершенно новый API для работы с датами и временем, расположенный в пакете java.time. Новый API сравним с библиотекой Joda-Time, однако имеются отличия. Следующие разделы рассказывают о наиболее важных частях нового API.

Clock

Тип Clock предоставляет доступ к текущей дате и времени. Этот тип знает о часовых поясах и может использоваться вместо вызова System.currentTimeMillis() для возвращения миллисекунд. Такая точная дата также может быть представлена классом Instant. Объекты этого класса могут быть использованы для создания объектов устаревшего типа
java.util.Date.

Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();

Instant instant = clock.instant();
Date legacyDate = Date.from(instant);   // legacy java.util.Date

Часовые пояса

Часовые пояса представлены типом ZoneId. Доступ к ним можно получить при помощи статических фабричных методов. Часовые пояса содержат смещения, которые важны для конвертации дат и времени в местные.

System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids

ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());

// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]

LocalTime

Тип LocalTime представляет собой время с учетом часового пояса, например, 10pm или 17:30:15. В следующем примере создаются два местных времени для часовых поясов, определенных выше. Затем оба времени сравниваются, и вычисляется разница между ними в часах и минутах.

LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);

System.out.println(now1.isBefore(now2));  // false

long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

System.out.println(hoursBetween);       // -3
System.out.println(minutesBetween);     // -239

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

LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late);       // 23:59:59

DateTimeFormatter germanFormatter =
    DateTimeFormatter
        .ofLocalizedTime(FormatStyle.SHORT)
        .withLocale(Locale.GERMAN);

LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime);   // 13:37

LocalDate

Тип LocalDate представляет конкретную дату, например, 2014-03-11. Объекты LocalDate неизменяемы и являются аналогом LocalTime. Пример демонстрирует вычисление новой даты путем сложения или вычитания дней, месяцев или годов. Помните, что каждая операция возвращает новый экземпляр.

LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);

LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek);    // FRIDAY

Создание экземпляра LocalDate путем парсинга строки:

DateTimeFormatter germanFormatter =
    DateTimeFormatter
        .ofLocalizedDate(FormatStyle.MEDIUM)
        .withLocale(Locale.GERMAN);

LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas);   // 2014-12-24

LocalDateTime

Тип LocalDateTime представляет собой комбинацию даты и времени. Объекты LocalDateTime неизменяемы и работают аналогично LocalTime и LocalDate. Мы можем использовать различные методы для извлечения конкретных значений из даты-времени:

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek);      // WEDNESDAY

Month month = sylvester.getMonth();
System.out.println(month);          // DECEMBER

long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay);    // 1439

Путем добавления информации о часовом поясе мы можем получить Instant.

Instant instant = sylvester
        .atZone(ZoneId.systemDefault())
        .toInstant();

Date legacyDate = Date.from(instant);
System.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014

Форматирование даты-времени работает так же, как и форматирование даты или времени. Мы можем использовать библиотечные или свои собственные шаблоны.

DateTimeFormatter formatter =
    DateTimeFormatter
        .ofPattern("MMM dd, yyyy - HH:mm");

LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string);     // Nov 03, 2014 - 07:13

В отличии от java.text.NumberFormat, новый DateTimeFormatter является неизменяемым и потокобезопасным.

Подробно о синтаксисе шаблонов можно почитать здесь.

Аннотации


Аннотации в Java 8 являются повторяемыми. Давайте сразу посмотрим пример, чтобы понять, что это такое.

Сперва мы определим аннотацию-обертку, которая содержит массив аннотаций:

@interface Hints {
    Hint[] value();
}

@Repeatable(Hints.class)
@interface Hint {
    String value();
}

Java 8 позволяет нам использовать множество аннотаций одного типа путем указания аннотации @Repeatable.

Вариант 1: использовать аннотацию-контейнер (старый способ)
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}

Вариант 2: использовать повторяемую аннотацию (новый способ)
@Hint("hint1")
@Hint("hint2")
class Person {}

При использовании варианта 2 компилятор автоматически подставляет аннотацию @Hints. Это важно при чтении информации об аннотациях через рефлексию.

Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint);                   // null

Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length);  // 2

Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);
System.out.println(hints2.length);          // 2

Хотя мы никогда не объявляли аннотацию @Hints в классе Person, она доступна нам при вызове getAnnotation(Hints.class). Однако более удобным является метод getAnnotationsByType, который напрямую предоставляет доступ ко всем аннотациям @Hint.

Более того, аннотации в Java 8 можно использовать еще на двух элементах:

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}

Вот и все


На этом введение в программирование на Java 8 завершено. Вам остается самостоятельно изучить другие новинки JDK 1.8, например, Arrays.parallelSort, StampedLock, CompletableFuture и другие.

Полный исходный код статьи доступен на GitHub.
Перевод: Benjamin Winterberg
Андрей Часовских @andreycha
карма
128,0
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (138)

  • +7
    Вот теперь астрологи точно могут объявить неделю Java 8 открытой :) Спасибо за перевод и за то, что выбрали именно этот туториал — возможно, что на данный момент он один из лучших по соотношению полезной информации к количеству знаков.
  • +2
    Честно говоря, какой-то странный релиз: с одной стороны, вроде бы добавили то, чего все так долго ждали, а с другой — как всегда сделали это по-своему. Лямбды ввели, а функции как объекты первого рода — нет (вместо этого использовав новое понятие функционального интерфейса). Вывод типов для этих лямбд есть, а для всего остального кода — нет. Множественное наследование запретили, а методы по-умолчанию — наоборот, добавили. Синтаксис и названия тоже радуют. Двойное двоеточия для получения ссылки на метод? Вы серьёзно? .putIfAbsent? .getOrDefault? Для всех этих операций в других языках уже давно придумали более или менее устоявшийся синтаксис/названия, равно как для всех новых для Java понятий в computer science уже есть хорошие, отточенные варианты дизайна. Но в Java нужно обязательно пойти своим путём и переизобрести колесо.
    • 0
      да, ощущение, что получилось сложнее, чем должно быть.
    • +1
      > Лямбды ввели, а функции как объекты первого рода — нет (вместо этого использовав новое понятие функционального интерфейса).

      А для чего вам нужны функции, как объекты первого рода? Можете считать java.util.function.* таким объектом первого рода. То, что ввели функциональный интерфейс это просто превосходно, т.к. позволяет использовать лямбды для огромного числа старых библиотек.

      > Вывод типов для этих лямбд есть, а для всего остального кода — нет.

      Есть мнение, что вывод типов для всех переменных слишком опасный инструмент и даёт ухудшение читабельности кода. Я не имею четкого собственного мнения по этому поводу. но большой проблемы в этом не вижу.

      > Множественное наследование запретили, а методы по-умолчанию — наоборот, добавили.

      А какие задачи вы хотите решать с множественным наследованием?

      > Двойное двоеточия для получения ссылки на метод? Вы серьёзно?

      Чем плохо? По-моему нормально. Ваш вариант?

      > .putIfAbsent? .getOrDefault? Для всех этих операций в других языках уже давно придумали более или менее устоявшийся синтаксис/названия,

      Названия в стиле Java и для Java программистов будут понятны и удобны. Для других языков… Java это свой мир со своими традициями.

      > равно как для всех новых для Java понятий в computer science уже есть хорошие, отточенные варианты дизайна. Но в Java нужно обязательно пойти своим путём и переизобрести колесо.

      По-моему очень хорошо переизобрели. У меня лично почти нет придирок. Есть, но мелкие. Жить и работать с этим точно можно.
      • 0
        Чем плохо? По-моему нормально. Ваш вариант?

        В шарпе обошлись всё той же универсальной точкой. Если нет скобок после названия метода, то это считается ссылкой на метод (грубо говоря).
        • +8
          В Java это синтаксис для поля. Причём поля могут иметь такие же имена как и методы. И по понятным причинам это уже не изменить, т.к. это ломает существующий код, что для Java недопустимо. Хорошо это или плохо (разные пространства имён для методов и переменных) это отдельный вопрос, но это есть и проектировать нужно с этим наследием.
        • 0
          Обратная совместимость. Такой код бы перестал компилироваться:
          int x;
          
          int x() {
              return x;
          }
          
          @Override
          public String toString() {
              return "x = " + x; // x - ссылка на поле или метод x()?
          }
          
          • 0
            Очень плохой пример. Даже если заменить x на this::x, то javac Вам радостно скажет
            Error:(11, 25) java: method reference not expected here


            Но основная мысль, конечно же, верная. Вот это уже сломалось бы.
            Runnable x;
            
            void x() {
            }
            
            test() {
                Runnable r = x;
            }
            
      • +2
        А для чего вам нужны функции, как объекты первого рода? Можете считать java.util.function.* таким объектом первого рода. То, что ввели функциональный интерфейс это просто превосходно, т.к. позволяет использовать лямбды для огромного числа старых библиотек.


        Функциональные интерфейсы — это хорошо, но наличие огромного числа интерфейсов Function, Consumer, Predicate, Supplier умноженное на Bi*, Int*, Long*, Double* и множество других, при этом не покрыть все возможности, требует запоминания, вместо того, чтобы ввести функции как объекты первого рода без ненужных имен, и так же оборачивать их, чтобы они удовлетворяли функциональным интерфейсам, как это например в том же котлине сделано.

        Жить и работать с этим точно можно.


        Более того, это здорово, что сделали хотя бы это. И сделали в целом неплохо, хоть и много спорных моментов. Но в целом придерживаются своей очень аккуратной позиции во введении «новинок». Спасибо, что не полная стагнация.
      • +1
        А для чего вам нужны функции, как объекты первого рода?

        Хотя бы для того, чтобы не писать Integer::valueOf. В большинстве языков программирования, где функции являются first class citizens, к ним можно обратиться напрямую, как к любым другим переменным. Их можно напрямую передавать в дргуие функции или возвращать из них. В других языках, таких как Ruby, методы автоматически или полуавтоматически конвертируются в вызываемые объекты (lambdas, procs). В Clojure методы считаются «бедными функциями» и при необходимости явно оборачиваются в «полноценные» объекты функций. В Java же получается, что есть метод, а есть указатель на метод, который на самом деле функциональный интерфейс и вообще указывает не на конкретный кусок кода, а ссылается по имени и диспетчеризируется по количеству и типу параметров. Я знаю, как работать с функциями как объектами первого класса, и даже как оборачивать методы в полноценные объекты, но я с трудом понимаю, как управлять этим новым зоопарков в Java.

        java.util.function — это, пардон, вообще издевательство. Во многих языках со статической типизацией есть функциональные типы. "(Int -> Int) -> Int" в Haskell или «POINTER TO PROCEDURE (a: Integer; b: Integer)» в Oberon или "(Int, Int) => Int" в Scala. В Java же почему-то решили, что пакет с ограниченным количеством предопределённых типов покроет все потребности разработчиков. Я искренне не понимаю, почему вместо всех этих нововведений нельзя было добавить синтаксический сахар для анонимных классов — семантика бы не поменялась, совместимость бы осталась, и при этом не пришлось бы добавлять новые, ещё не проверенные временем концепции.

        Есть мнение, что вывод типов для всех переменных слишком опасный инструмент и даёт ухудшение читабельности кода. Я не имею четкого собственного мнения по этому поводу. но большой проблемы в этом не вижу.

        Так а в чём концептуальное различие между лямбдами и обычыми функциями? Я знаю языки, которые стараются выводить типы везде, где возможно. Я знаю языки, где типы нужно всегда объявлять. Оба варианта имеют свои плюсы и свои минусы, но они, по крайней мере, следуют своим идеям. В Java идея частичного выведения типов ставит меня в тупик. Должен я его использовать или нет? Если да, то почему не везде? Если нет, то почему кое-где это позволено?

        А какие задачи вы хотите решать с множественным наследованием?

        Множественное наследование (или, что по сути то же самое, mixins и traits) — это стандартный способ расширения функциональности существующих объектов. По сути оно имеет смысл только в ОО языках с сильными идеями инкапсуляции — в остальных случаях вполне можно написать внешнюю к объекту функцию. Надо оно или нет — сложный вопрос, но сначала запрещать и долго всех пугать ужасами множественного наследования, а потом вводить его (хоть и с другим названием) в язык — это как-то странно.

        Чем плохо? По-моему нормально. Ваш вариант?

        Даже если забыть про C++, где двойное двоеточие широко используется для доступа к элементам неймспейса, семантика оператора для получения ссылки на метод совершенно не ясна. Большинство символьных операторов во всех языках программирования имеют определённую историю, идею, стоящую за ними. Оператор «точка» (.), например, аналогичен точке в номерах глав и разделов книг (напр., глава 1.4.2) — вы спускаетесь по иерархии объектов точно так же, как по главам рукописи. Оператор «стрелка» (->) в C можно прочитать как «перейти по ссылке». «Равно» для присвоения — как «теперь эта переменная равна...», и т.д. Как прочитать двойное двоеточие для взятия ссылки на метод — я не знаю.

        Альтернативных вариантов куча. Самый простой, который сразу приходит в голову, это поставить амперсанд или «собачку» перед методом (&Integer.valueOf / @ Integer.valueOf) — тогда название «ссылка на метод» становится вполне интуитивным.

        По-моему очень хорошо переизобрели.

        Так а зачем вообще переизобретать? Одно дело, когда вы хотите добавить в язык принципиально новые возможности (напр., макросы в Лиспе), или пересмотреть дизайн существующих вещей, потому что они чем-то вас не устраивают (более строгая типизация в Scala), или проверить возможность использования принципиально другого базиса (ленивые вычисления в Haskell вместо активных в большинстве языков, или lambda calculus вместо машины Тьюринга). Но переизобретать что-то просто так, да ещё и вбрасывать новые непроверенные временем идеи в мейнстримовый язык — это какое-то ну очень странно.
        • +1
          Даже если забыть про C++, где двойное двоеточие широко используется для доступа к элементам неймспейса, семантика оператора для получения ссылки на метод совершенно не ясна.



          Альтернативных вариантов куча. Самый простой, который сразу приходит в голову, это поставить амперсанд или «собачку» перед методом (&Integer.valueOf / @ Integer.valueOf) — тогда название «ссылка на метод» становится вполне интуитивным.


          Да, это просто великолепно впишется в семантику явы.
          • 0
            @ часто ассоциируется с адресацией (например, это короткий синтаксис для dereference в Clojure, ну и электронная почта сюда же), & — устоявшийся символ получения ссылки в C. Чем они вас смущают и почему :: лучше?

            Вопрос без подколов. Я абсолютно не пытаюсь затроллить ни вас, ни Java, и мне правда интересно, какова логика за этим новым оператором (равно как и другими нововведениями).
            • +1
              Для начала, ява всю свою жизнь адресацию от вас прячет.
              Поэтому сама запись как (& операция взятия адреса) (Integer.toString имя ссылки) в традиции явы не вписывается.

              Для явы получение прямого адреса на кусок памяти — это вообще святотатство :)

              @ помимо прочего занято для аннотаций.

              doSomething () {
                   @Annotation("Blah blah blah")
                   collection.forEach(@Integer.toString); 
                   //Для меня это аннотация, либо обращение ко внутренним членам аннотации.
              }
              


              :: — лучше как раз таки потому что это отсылка к неймспейсам, потому что вы указываете
              очень.длинное.полное.имя.Класса (:: доступ к пространству имен дефолтных реализаций интерфейсов) имяМетода.

              Т.е. эта запись для явы читается не как «дай-ка мне ссылку на область памяти где размещен метод», а как «поищи среди интерфесов которые имплементит (очень.длинное.полное.имя.Класса) дефолтную реализацию метода (имяМетода)».
              • 0
                Так ведь в том то и дело, что это не обращение к элементу неймспейса — для этого уже есть точка :) Здесь как раз речь идёт о взятии метода «как есть», о получении ссылки на него (на что намекает название) или, что даже более правильно, о создании объекта-обёртки для него. Какой то оператор (хоть символьный, хоть буквенный) перед методом имел бы смысл и читался просто, а вот два двоеточия после имени класса/инстанса выглядят как-то странно :)

                Ну и да, Джава всю жизнь прячет адресную арифметику, сами же ссылки активно используются.
                • 0
                  Какой то оператор (хоть символьный, хоть буквенный) перед методом имел бы смысл и читался просто…


                  Ага, примерно так же как именование/разименование ссылок в сях, когда их лепят по 3-4 подряд.
                  • 0
                    Ну оператор получения ссылки на метод два раза применить вы не сможете.
                    • 0
                      Да, но с чего вы решили что унарный оператор будет удобнее чем :: после имени класса?

                      Почему, по вашему, коннотация к оператору взятия адреса в си здесь удобнее чем коннотация к неймспейсам? Учитывая что в яве нет ни неймспейсов ни оператора для получения адреса?
                      • 0
                        Как я уже сказал, смысл этого оператора покрыт тайной. Это не отсылка к неймспейсам из C++, поскольку смысл оператора совершенно другой. И даже не определение типа функции как, в Haskell. И не добавление элемента в список, как в OCaml. Совершенно непонятно, как его «читать» и что он точно делает (это специальный метаметод над Object или внешний к нему оператор языка? он возвращает объект или встраивает вызов функции в нужное место? и почему он в середине между именем объекта/класса и самим методом?). Всё это не смертельно и, как уже было сказано выше, круто, что хоть как сделали. Но, ёлы-палы, почему опять всё по-своему и игнорируя опыт других языков.
                        • 0
                          Как я уже сказал, смысл этого оператора покрыт тайной.

                          Дак это и не оператор.
                          • 0
                            Тогда что же это?
                            • 0
                              Некая конструкция языка позволяющая использовать метод как лямбду.

                              Я понимаю что это звучит как некий чит. Дескать очень похоже на оператор но мы его так не называем. Зато позволяет разным реализациям jvm реализовывать лямбды по своему.
                              • 0
                                Т.е. даже объяснить, чем по сути является эта новая фича, оказалось не так просто. В чём и был мой пойнт.
                                • 0
                                  Дак суть зависит от конкретной реализации.

                                  Причем разработчики языка уже обжигались на внесении реализации в стандарт языка с java memory model. Прошлая версия описывалась на уровне буфферов процессов, общих буфферов и т.д. нынешняя версия оперирует лишь тем кто что должен видеть и какие отношения должны сохраняться, не привязываясь к конкретной реализации.
                                  • 0
                                    Как раз суть конструкции языка от реализации зависеть не должна. Это абстрактное понятие, но и оно должно что-то выражать. Что-то конкретное, пусть даже и не привязанное к имплементации.
                                    • 0
                                      Сдаюсь.

                                      Пишите в JCP пусть меняют на & SomeClass.someMethod
            • +1
              Ставить дополнительный символ перед вызовом или использовать :: для вызова — скорее дело вкуса. Вызов :: смотрится вполне нормально. Можно было бы точно так же спросить, почему не вызов через -> (Integer->valueOf) или через какой нибудь другой символ (Integer#valueOf). Для таких вопросов вполне подходит ответ «потому что гладиолус» :)
              • 0
                Ну вот и получается, что «потому что гладиолус». А вот отцы-основатели всё-таки как-то более ответственно подходили к именованию ключевых элементов языка :)
                • 0
                  Если уж на то пошло, Java — это язык, где именованию, выбору синтаксиса, конвенциям и т. д. уделяют наибольшее внимание среди всех известных мне языков. Я слежу за этой темой, изучал API в Rust, Julia, Scala — полное впечатление, что авторам этих языков просто наплевать. Методы названы первым пришедшим в голову способом, порядок аргументов.

                  В Java-сообществе есть серьезные холивары, называть методы с префиксом get или нет, сначала публичные методы или приватные в классе, импорты звездочкой или каждый класс по-отдельности. Во всех остальных сообществах на это всем насрать.
                  • 0
                    Остальные сообщества не ограничиваются перечисленным списком. Посмотрите для примера на Python, в частности, на тот же мап (а вернее словарь, dict в терминологии Питона). Если вы там найдёте хоть один метод, названный нелогично или делающий не то, что обещает, сразу пишите Гвидо — он уже 20 лет с фанатизмом выпиливает все сложные и неочевидные конструкции.
                    • +1
                      Python — это мой второй основной язык, поэтому можете не рассказывать. Разумеется, там в API тоже тонны говна, имен, взятых из unix и шелл-скриптов, о качестве и согласованности которых можно промолчать. Что касается конкретно dict:

                      1. Почему все методы, за исключением (внезапно) has_key, пишутся маленькими буквами без пробелов, что само по себе мерзко, а не через подчеркивание, как по идее принято в языке, несмотря на то, что тут завязки на Unix никакой быть не может?

                      2. setdefault — сомнительное название для этой семантики. putIfAbsent и то лучше

                      3. Как в нормальном АПИ могли одновременно оказаться методы items, iteritems и viewitems? Аналогично с keys и values.

                      — Но я не виню Python. Он ведь старый, старее Java. В пример приводил свежие языки, где, казалось бы, можно было учесть весь опыт предшественников и с чистого листа сделать реально хорошо.
                      • 0
                        Разумеется, там в API тоже тонны говна, имен, взятых из unix и шелл-скриптов, о качестве и согласованности которых можно промолчать.

                        Если вы говорите про модули «sys» и «os», то не делать отсылок к shell скриптам и юниксам (где Python чаще всего является системным языком) в модулях, которые работают с системой, было бы как-то странно. Или вы про что?

                        Про dict.

                        1. В style guide от Гвидо довольно чётко прописано, что есть определённые рекомендации по именованию, но, если вы считаете, что альтернативное имя для переменной или функции будет смотреться лучше, то ок, используйте его. В конечно счёте вы пишете для себя и таких же как вы. Стиль имён без подчёркиваний активно используется во встроенных функциях, и подавляющим большинством питонщиков воспринимается вполне горячо. То, что не все имена написаны в одном стиле, не лишает их красоты ;)

                        2. «setdefault» — это типичное сокращение, подпадающее под то же правило. В полной версии метод назывался бы «getOrSetDefault», но так слишком уж страшно и неудобно. Хотя да, семантика set противоречит поведению с get, что периодически вызывает конфуз.

                        В целом про putIfAbsent я позже напишу, а то я вижу, что мой главный пойнт в обсуждении уже потерялся.

                        3. iteritems() появился как новый стиль для items(), в Py3k приставка iter была убрана и все указанные методы теперь по умолчанию возвращают итератор. А вот с view* всё и так хорошо: view объекты, как бы, не эквивалентны ни спискам, ни итераторам.
                        • –1
                          Имена из сплошных маленьких букв это вообще не стиль, а недоразумение, ноги которого растут все из тех же юниксов, где, наверное, были какие-то проблемы с поддержкой регистра букв…

                          Короче говоря. В Python-сообществе тоже определенно уделяется внимание к именованию, синтаксису, стилю. Но в меньшей степени, чем в Java-сообществе.
    • +1
      .putIfAbsent? .getOrDefault? Для всех этих операций в других языках уже давно придумали более или менее устоявшийся синтаксис/названия

      Не поделитесь, какие именно? А то у словарей в шарпе метода для получения элемента с фолбэком нет, поэтому я всё время таскаю extension метод GetOrDefault. Если есть устоявшееся название, я бы переименовал. :)
      • 0
        Для GetOrDefault чаще всего используется что-то вроде:

        x = map["foo"] or 1 # x = map.get("foo") or 1
        Либо, если метод .get при отсутсвии элемента бросает эксепшен, то:
        x = map.get("foo", defaultValue)
        В Java Map.get всегда принимает один параметр, поэтому введение перегруженного метода со вторым параметром не поломает старого кода.

        putIfAbsent — это явная отсылка к кешам (я даже погуглил другие варианты использования, но ничего внятного не нашёл). Общепринятого названия здесь нет, но обычно называется getOrCompute, getOrStore и т.д. Всё-таки метод возвращает значение, а не просто кладёт его, если раньше не было.
        • +2
          x = map["foo"] or 1 # x = map.get("foo") or 1

          Положим, это не одно и то же. Если в мапе по ключу «foo» лежит false или 0, я бы не хотел получить единицу.

          Общепринятого названия здесь нет, но обычно называется getOrCompute, getOrStore и т.д. Всё-таки метод возвращает значение, а не просто кладёт его, если раньше не было.

          В шарпе GetOrAdd называется (но есть только в concurrent словаре, для обычных приходится extension-метод таскать...).

          Что-то не сходится теория устоявшегося названия. :)
          • –1
            Возможно, я не очень точно выразился. В CS есть определённые принципы и терминология, которые позволяют, среди прочего, быстрее ориентироваться в новых незнакомых системах. Например, вызов функции принято записывать как `func(x)`, обращение к элементу массива — через `[]` и т.д. Это те названия, которые мы ожидаем увидеть, даже не владея полностью технологией. Те слова, которые мы ищем в гугле или документации, пытаясь выразить то, что нам нужно. Использование общих названий и ожидаемой нотации не обязательно, но обычно для её нарушения существуют веские причины (в Лиспе, например, пишут `(func x)` вместо `func(x)` не потому что так больше нравится, а потому что это даёт гомогенность кода и данных).

            Название `putIfAbsent` я вижу первый раз (и, судя по гуглу, оно не является особо распространённым). Если я открою документацию и попробую найти метод для извлечения старого элемента или обновления хранимого значения, то я буду ожидать увидеть что-то вроде «getOrStore» или «fetchOrCompute», или «getOrAdd», как в C#, но никак не «putIfAbsent». Я вообще не буду ожидать, что метод «put*» будет возвращать значение. Я вполне уверен, что автор этого нового метода знает, что есть такой язык C#, и что в нём для отображений есть метод «GetOrAdd». Тогда мне интересно, почему автор решил не использовать это название.

            Опять же, я не пытаюсь доколебаться или обвинить авторов языка. Я знаю много разных подходов, видел совершенно разные взгляды на одни и те же проблемы, и, как правило, эти взляды основывались на какой-то логической системе. В Java 1.4 я видел чёткую систему. В Java 1.5-1.7 — почти чёткую. Для 1.8 я пока не увидел концепций, которые авторы пытались в неё вложить.
            • +4
              Логика хотя бы в том, что методы, начинающиеся с «get», никогда не меняют состояние коллекции. На это можно полагаться. В этом смысле getOrAdd — плохое название, да и вообще, глаголы add, store в API мапы не встречаются. Тогда уж getOrPut, что уже совсем хрень.

              put возвращает предыдущее значение с самого начала, это нельзя изменить.
              • 0
                Ну по названию «getOrAdd» довольно очевидно, что оно может изменить коллекцию. По крайней мере здесь не будет неожиданностей для случайного пользователя. Если хочется оставить «get» для чего-то, что не модифицирует коллекцию, есть ещё куча синонимов, например, «fetch» (неоднократно видел названия типа «fetchOrAdd», «fetchOrUpdate» и т.д.).

                То, что «put» возвращает старое значение, меня тоже всегда забавляло. Но это уже другой вопрос.
            • +1
              \troll mode on
              Если вы видете putIfAbsent в первый раз за его 9.5 летнее публичное существование в стандартной либе, то увы вам. Значит вы с Java работаете эпизодически.

              > Я вообще не буду ожидать, что метод «put*» будет возвращать значение
              Еще одно подтверждение, что Java вы знаете поверхностно.
              \troll mode off

              Ответ прост: так сложилось в экосистеме Java и менять чего-то ради абстратного CS нет смысла.
              И еще, автор этого метода хоть и занятой, но достаточно открытый человек, спросите почему он так назвал.
              Автор тут
              Спрашивать лучше тут
              • 0
                Если вы видете putIfAbsent в первый раз за его 9.5 летнее публичное существование в стандартной либе, то увы вам. Значит вы с Java работаете эпизодически.

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

                Я вообще не буду ожидать, что метод «put*» будет возвращать значение

                Я как бы в курсе, что в Java «put» со странностями. Но отойдите от конкретного языка программирования и посмотрите на название метода. Что вы ожидаете от метода «положить»? Наверное, то, что он что-то куда-то положит. А какой метод вы будете искать, чтобы что-то извлечь? Наверное «извлечь» или какой-нибудь синоним.

                Ответ прост: так сложилось в экосистеме Java и менять чего-то ради абстратного CS нет смысла.

                Не забывайте только, что лямбды, вывод типов и большая часть нововведений в Java также пришла из этого «абстрактного CS».
                Хотя ответ «так сложилось исторически» меня устраивает, да.
                • 0
                  > Но отойдите от конкретного языка программирования и посмотрите на название метода. Что вы ожидаете от метода «положить»? Наверное, то, что он что-то куда-то положит. А какой метод вы будете искать, чтобы что-то извлечь? Наверное «извлечь» или какой-нибудь синоним.

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

                  Вы бы лучше на оператор [] в С++ std::map посмотрели ;)
                • 0
                  > Не забывайте только, что лямбды, вывод типов и большая часть нововведений в Java также пришла из этого «абстрактного CS».
                  Да нет, все это пришло из практического CS.
                  А вы аппелируете как раз к абстрактному.
                  • 0
                    Да нет, все это пришло из практического CS.
                    А вы аппелируете как раз к абстрактному.

                    И чем же моё абстрактное CS отличается от вашего практического?

                    Вы бы лучше на оператор [] в С++ std::map посмотрели ;)

                    Пример странностей в C++ не оправдывает нелогичной семантики в Java.

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

                    Если вам нужен метод, который вытесняет старое значение, ну так и назовите его «вытеснить». По-английски это очень красиво — «displace». Если хотите точного описания семантики, то назовите «putOrDisplace».

                    Так или иначе, я не против дополнительной функциональности в стандартных методах. Если «put» используется для добавления элементов в отображение, но при этом имеет ещё фишку в виде возврата предыдущего значения, то это нормально. Это добавляет удобства, а значит имеет смысл. Изначально меня возмутило название метода «putIfAbsent», который используется для получения значения. Для однопоточных коллекций это выглядит довольно странно. Для многопоточных — имеет смысл. Сохранить название метода при переносе с многопоточных в однопоточные коллекции — тоже имеет смысл. Мне это кажется некрасивым, но в этом есть какая-то логика. Однако это пока что единственное решение из перечисленных, за которым я увидел какую-то систему.
                    • 0
                      Изначально меня возмутило название метода «putIfAbsent», который используется для получения значения.

                      Так он так и не используется, где Вы это нашли? Спец. поискал его использование в посте, нашел единственное место, где результат вообще игнорируется. Основное назначение метода — именно put.
                      • 0
                        В статье синтетический пример, показывающий только основной смысл. А вот кусок из первого же результата в Гугле (пропуская javadocs):

                        AtomicLong newValue = new AtomicLong(value);
                        AtomicLong oldValue = map.putIfAbsent(key, newValue);
                        if ( oldValue != newValue ) {
                            result = oldValue.addAndGet(value);
                        }
                        


                        Т.е. мы добавляем элемент, а затем проверяем, заменился ли он. Поправьте меня, если я ошибаюсь, но по-моему это как раз типичный вариант использования «putIfAbsent» в concurrent map.
                    • +2
                      Пример странностей в C++ не оправдывает нелогичной семантики в Java.

                      Практика показывает следующее, что куча людей пишет про некоторую нелогичность. И хочет сделать «вот так». Начинаем разбираться, собирать фидбеки, и выясняется, что это «вот так» у разных людей практически не пересекается. Сколько людей столько и мнений. У меня есть свои места в Java, которые я считаю нелогичными, но они во первых не пересекаются (по результатам обсуждения) с вашими, а во вторых не мешают мне жить.
                      Можно сколько угодно пытаться изобрести идеальный велосипед, но боюсь в этом случае мы бы с вами не обсуждали бы Java8, а комментировали очередной пост Марка про поезд. ;) Знаете, лучшее враг хорошего.
                      Сейчас сюда прибежит 23derevo с докладом про trade-offs. :)

                      Если вам нужен метод, который вытесняет старое значение, ну так и назовите его «вытеснить». По-английски это очень красиво — «displace». Если хотите точного описания семантики, то назовите «putOrDisplace».

                      Ничего более нелогичного в жизни не видел. ;))))

                      Так или иначе, я не против дополнительной функциональности в стандартных методах. Если «put» используется для добавления элементов в отображение, но при этом имеет ещё фишку в виде возврата предыдущего значения, то это нормально. Это добавляет удобства, а значит имеет смысл. Изначально меня возмутило название метода «putIfAbsent», который используется для получения значения.


                      Все же просто. Есть put который всегда вытесняет старое значение, и put которые никогда не вытесняет старое значение. Два полезных метода. дальше мы их называем и живем. И кстати, контракт на результат у обоих одинаковый! Консистенси однако.

                      Я не зря поставил ссылку на автора, упрекать Doug Lea в том, что он не знает CS по меньшей мере смешно.
                      • 0
                        Ничего более нелогичного в жизни не видел. ;))))

                        А чем это нелогично? Вы назваете метод тем именем, которое описывает его поведение. По-моему, вполне.

                        Дуга никто не упрекал. Как я уже сказал, для многопоточных коллекций, над которыми он работал, такой метод имеет смысл.
            • 0
              Учитывая наличие методов get и put, лично я ожидал бы метод getOrPut. С другой стороны, в джаве метод put тоже возвращает значение, так что возвращение значения методом putIfAbsent не настолько уж неожиданно.

              Гм. Прочитал документацию. Внезапно, putIfAbsent возвращает существующее значение, если оно есть, и удалённое значение, если его не было (то есть null). Вот это — сюрприз.
    • +7
      Инженеру неплохо бы понимать, что язык с 20-и летней историей нельзя просто взять и переделать под «другие языки». Если это сделать, то это будет не java, а просто еще один новый язык программирования, в чем-то отдаленно похожий по синтаксису на C++. Ценность 20и летней экосистемы именно в том и состоит, что все за это время созданное — библиотеки, паттерны, стилистические рекомендации, опыт и сообщество — остается процентов на 90 актуальным да при выходе новой версии.
      • 0
        Речь не о том, чтобы переделать язык под что-то другое. Речь о том, что варианты реализации многих новых для Java фич уже хорошо известны в computer science. И они совсем необязательно должны ломать существующую экосистему — наоборот, они расширяют эту экосистему, позволяя использовать не только достижения конкретного языка с 20-летней историей, но и всех компьютерных наук с больше чем 60-летней историей. При переходе между Scala, Clojure, Python, Ruby, C++ и т.д. у меня есть готовые рецепты, паттерны решения повторяющихся задач. При переходе на Java мне придётся изобретать все эти рецепты заново.
        • +8
          Вы знаете, то, что сейчас выкатили в 8-ке — обсуждалось в сообществе в течении лет 3х уже. Если лично вы все это время знали решение всего комплекса проблем лучшее, чем то, что было реализовано, и если вам судьба джавы не безразлична — то где вы были все это время?

          У меня пока складывается ощущение, что это претензии из категории «Как жаль, что все, кто знают как правильно управлять страной — уже работают дворниками и таксистами» (не помню откуда)
          • 0
            Эти изменения обсуждались гораздо больше 3-х лет, и я долгое время наблюдал за развитием идей. Вместо меня высказывались люди гораздо более уважаемые в сообществе. Однако, сообщество фрагментировано, а решение принимает коммитет под давлением крупных компаний. В итоге после выхода каждой новой версии языка можно было наблюдать, как всё больше количество людей уходило в другие языки. Та же Scala три года назад воспринималась как модная игрушка, которую можно попробовать разве что на своих домашних проектах. Сейчас же многие крупные системы пишутся полностью на Scala, а для Java создаётся только интерфейс совместимости.
    • +3
      putIfAbsent — давно устоявшееся в Java название из интерфейса ConcurrentMap (ещё с Java 1.5). Просто до появления default-методов нельзя было добавить такие штуки в интерфейс Map: куча стороннего кода бы сломалась. А теперь можно, и этот метод из ConcurrentMap легко перекочевал в вышестоящий интерфейс. Было бы в высшей степени странно, если б его назвали по-другому.
      • 0
        Вот это уже имеет смысл, да. Спасибо за разъяснение.
  • –24
    for (int i = 0; i < max; i++) {…

    Вот за это я и не люблю Java. В XXI веке, когда в в этом высокоуровневом языке появились элементы ФП, насколько мне известно до сих пор нет нормального синтаксического сахара для коллекций/итераторов. Всё как-то через одно место с привкусом C++ и седых веков. Это как inline-стили в html в духе «мы из 90-х». Там вообще есть нормальный foreach в стиле (k, v in hash) {}? Или он есть, но все привыкли писать подобным образом?

    Возможно, я не прав и Java в этом вопросе подтянулась, хотя бы, до уровня Python — просветите, пожалуйста.
    • +4
      Можно итерировать вот так:

      for (Item item: somethingIterable) {
         ...
      }
      
      
      • 0
        не туда
    • +4
      Я подозреваю, что вы даже эту статью не прочитали полностью, уже не говоря про какую-нибудь книгу по Java. Но уже за что-то не любите ее.
      Даже в этой статье указано 2 красивых способа обхода колекций.
      • –2
        Посоветуйте хорошие книги/ресурсы для изучения, пожалуйста. Я начинал читать «какие-нибудь» книги по Java и забрасывал, доходя в них до этих уродливых «for (int i = 0; i < 10; i++) {» и не находя нормальных альтернатив. В интернете с материалами по Java похожая ситуация как с материалами по HTML — много устаревшего и неактуального шлака времён HTML 3.2, на первых страницах поисковиков.

        В этой ветке обсуждений я уже увидел, что в Java всё-таки есть нормальные способы — только где о них можно почитать в концентрированном виде, чтобы иметь дело с современными best practices, а не с привычками бородатых программеров из 90-х?
        • +2
        • +1
          … много устаревшего и неактуального шлака времён HTML 3.2, на первых страницах поисковиков.

          Иногда, в этих случаях, помогает выставить в параметрах поиска ограничение на последние N лет :)
        • 0
          В этой ветке обсуждений я уже увидел, что в Java всё-таки есть нормальные способы — только где о них можно почитать в концентрированном виде, чтобы иметь дело с современными best practices, а не с привычками бородатых программеров из 90-х?

          Java — это не C++, этот язык вполне по силам полноценно изучить. После «уродливых» циклов for дошли бы до итерации. Всё равно знать надо и то, и другое. Не страдайте фигнёй, просто прочитайте от корки до корки, а потом уже выбирайте по вкусу.
    • +1
      Вообще-то да, теперь вместо

      for (int i = 0; i < 10; i++) {
          map.putIfAbsent(i, "val" + i);
      }
      

      можно писать

      IntStream.range(1, 10).forEach(i -> map.put(i, "val" + i));
      
      • 0
        В общем то с Java 5 можно было сделать
        for (int i : Range.from(0).until(10)) { ... }
        for (int i : Range.indices(myList)) { ... }
        


        только в стандартной библиотеке этого не было.
      • +1
        Вместо
        IntStream.range(1, 10).forEach(i -> map.put(i, "val" + i));
        

        Лучше (и безопаснее) написать:
        IntStream.range(1, 10).boxed().collect(Collectors.toMap(i -> i, i -> "val" + i));
        

        • +1
          Простите, но можете пояснить?
          А то я, честно признать, с лямбдами почти не работал, а понять их хочется.
    • 0
      > Там вообще есть нормальный foreach в стиле (k, v in hash) {}?
      Теперь да:

      map.forEach((k, v) -> ...);
  • +1
    Странно, что нормальных getter'ов setter'ов нету (или есть?), даже в js добавили уже.
    • –2
      Было бы замечательно что то типа:
      Property int prop: read GetProp write SetProp;
  • 0
    Параллельная сортировка неплохо масштабируется, кстати. Запустил на 40-ядерной тачке, ускорение в 25 раз.
    • +1
      С параллельными штуками, я так понимаю, проблема в том, что оно «под капотом» и непонятно, как на это дело влиять. Нет апи для взаимодействия с ExecutorService-ом соответствующим и тд. Для определенных задач подходит, а вообще загрузить 40 ядер продакшн сервера, обслуживающего клиентов одной сортировкой не хочется. Как загрузить только 5? Можно ли? Как сделать так, чтобы app server управлял потоками, включая создание и reclycling. Если каждый запрос будет по 40 потоков запускать, это не очень хорошо.
      • +3
        Там под капотом ForkJoinPool. По умолчанию берётся ForkJoinPool.commonPool(), который использует столько потоков, сколько ядер в системе по версии Runtime.getRuntime().availableProcessors(), либо сколько указано в проперти java.util.concurrent.ForkJoinPool.common.parallelism.
        Однако, если запустить parallelStream() изнутри ForkJoinPool таски, он останется в этом пуле. Так что можно сделать примерно так:
        ForkJoinPool pool = new ForkJoinPool(5);
        long count = pool.submit(() -> values.parallelStream().sorted().count()).get();
        
        • +1
          Вроде понял, но кажется не настолько гибко, как это могло бы быть. Просто обычно в Java на пустом месте создают всяких провайдеров и фабрики, а тут вроде как было бы уместно. Ну надеюсь, этого хватит, чтобы использовать такие штуки в app server-е не боясь, что большая сортировка загрузит все процессоры.
          • +2
            На мой взгляд, довольно удобно. По умолчанию просто использует все доступные ресурсы, без всяких провайдеров и фабрик. Zero configuration. Если надо ресурсы ограничить, это делается одной строчкой.
    • +1
      Интересно, как оно будет масштабироваться в реальных условиях. Например, когда таких сортировок несколько.
      UPD: собственно, согласен с vsb.
      • 0
        Чем больше сортировок будут постоянно сидеть на системном пуле, тем лучше он будет утилизирован :-)
  • +1
    Того и гляди к Java 15 из нее получится Scala.
    • +1
      Я даже боюсь представить (в хорошем смысле) что будет со Scala к тому времени как выйдет Java 15.
      • 0
        Не думаю. Мне кажется рано или поздно настанет момент стагнации.
        • +1
          Уже сейчас настал. Сейчас они двигаются в направлении улучшения стабильности и производительности. В Scala 2.11 нет ни одного значительно улучшения синтаксиса.
          • 0
            Ну если развитие языка определять только количеством новых фич, то можно с вами согласиться.
          • 0
            Да ладно. Кто же измеряет развитие, изменением синтаксиса?
            • 0
              // offtopic
              Всегда было интересно, какими умозаключениями руководствуются люди, ставящие, запятые, где, попало =)
              • +2
                // offtopic
                А, кто сказал, что, они, руководствуются, какими бы, то ни было, умозаключениями?
                Они, просто, ставят запятые, где, им хочется)
    • +2
      Не получится. Я ходил на лекции ребят, которые пилили лямбды для 1.8, когда они приходили к нам в гости, и там этот вопрос поднимался отдельно. Официальный ответ был — если вам нужна Скала со всеми её наворотами, то она уже есть, а мы рассматриваем джаву как консервативный язык, требующий очень вдумчивого и аккуратного точечного осовременивания без фанатизма.
  • 0
    Имейте в виду, что этот код останется корректным даже если убрать аннотацию @FunctionalInterface.

    Тогда какой смысл в аннотации?

    Но в отличии от анонимных объектов, переменная num не обязательно должна быть объявлена как final.

    Почему на анонимные объекты не распространили фичу? Хотя фича странная, учитывая, что содержательно всё тот же final…

    Запись в переменную num в пределах лямбда-выражения также запрещена.

    А если очень хочется, то как предполагается переписать? Как этот код на шарпе перевести на джаву?

    var list = new List<int> { 1, 2, 3 };
    int sum = 0;
    list.ForEach(i => sum += i);

    JDK 1.8 содержит множество встроенных функциональных интерфейсов.

    Есть аналоги дженериковых Acton<T1, ... , TN> и Func<T1, ... , TN, TResult> из шарпа, чтобы не плодить тьму бесполезных интерфейсов ради одного вызова?

    P.S. Пардон, если вопросы идиотские. Я в джаве не бум-бум.
    • +1
      А если очень хочется, то как предполагается переписать? Как этот код на шарпе перевести на джаву?


      Обычно делают извратом:
      int[] sum = new int[1];
      list.forEach(i -> sum[0] += i);
      return sum[0];
      

      видимо от него пока не избавиться.

      Есть аналоги дженериковых Acton<T1,…, TN> и Func<T1,…, TN, TResult> из шарпа, чтобы не плодить тьму бесполезных интерфейсов ради одного вызова?


      Нет. Есть для 0, 1, 2 аргументов. Хотя думаю в какой-нибудь guava появятся быстро, если не уже.
      • +2
        > Обычно делают извратом:
        >
        > int[] sum = new int[1];
        > list.forEach(i -> sum[0] += i);
        > return sum[0];
        > 

        DON't DO IT!!! проблем не оберетесь при параллелизации.

        IntStream s = ....
        s.sum(); // PROFIT
        
        • +1
          Ну если есть  понятный способ написать reduce функцию (или готовая типа sum есть), конечно лучше так и сделать, вопрос был именно в том, что делать, если очень надо модифицировать локальную переменную. Я так понимаю, тут ничего не прибавилось.

          Кстати, если не секрет, почему не сделали поддержку записи в локальную переменную? По логике вроде ничего сложного. Если точно видно, что лямбда-объект не выходит за пределы функции, то запись идет выше по стеку, если есть шансы, что «убежит» — локальная переменная превращается в какой-нибудь объект-держатель значения и все операции идут через промежуточную ссылку. Вроде так обычно реализуют везде и на первый взгляд ничего сложного.
          • +1
            > Вроде так обычно реализуют везде и на первый взгляд ничего сложного.
            Мы несколько лет зазывали людей в OpenJDK — приходите, пишите. Не хотите писать, так хоть в обсуждениях поучаствуйте, вопросы поднимите и т.д. А воз и ныне там.

            > лямбда-объект не выходит за пределы функции
            лямбда (by design) должна убегать не только за пределы функции, но и за пределы родного треда. Внутри функции — неинтересный частный случай.

            Вкратце: было принятно обоснованное решение не делать лямбды с захватом контекста.
            • +1
              И очень зря, кстати. По сути, уперлись в один узкий юзкейс (тупая параллелизация), проигнорировав кучу остальных, где все эти ограничения на фиг не сдались (callback chains, например).

              (да, я подписан на lambda-dev с момента открытия оного, и внимательно читал все дискуссии по дизайну)
          • 0
            > вопрос был именно в том, что делать, если очень надо модифицировать локальную переменную

            Я не видел еще ни одного настоящего примера, когда действительно «надо модифицировать локальную переменную».
            Все что мне «предлагалось» раньше — от лени да непонимания яыка.
            Если кто-нибудь, приведет мне настоящий пример требующий модификации локала — я буду только рад.
            • 0
              Ну вот, например, стандартный кусок на Scala (сайт на Lift):

              def render = {
                var name="";
                
                def process {
                   //do something with name
                   println(name);
                }
              
                ".input" #> SHtml.text(name, name=_) & //первая лямбда - модификация локальной переменной
                ".submit" #> SHtml.submit("Send", process _) //вторая лямбда - использует локальную переменную, сама функция см. выше
              }
              


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

              Тоже самое можно сделать и с полями объекта, но с локальными переменными как-то логичнее — поля могут использоваться всеми методами объекта, а локальные переменные — только этой группой лямбд.
              • 0
                Ага! Давайте разбираться.

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

                То вы написали — реализация ака «как».
                Скажите на более верхнем уровне — что требуется сделать?
                • 0
                  Если я правильно нашел источник, то задача ассоциировать элементы форм с действиями на серверной стороне. С точки зрения скалы мне кажется странным такой подход, потому что передаваемые методы (process в примере) по определению работают через сайд эффекты.
                • 0
                  Глобальная задача — обработать введенные пользователем в HTML-форму данные на сервере.

                  Для этого в Lift с каждым элементом формы связывается лямбда, которая выполняется при отправке формы на сервер (в том порядке, в котором создавались элементы формы) и обрабатывает введенные в этот элемент данные. Плюс лямбда на кнопку submit, которая обрабатывает всю форму в целом.

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

                  То есть в данном случае модификация локальной переменной — удобный способ написать код. Можно сделать без них, можно сделать даже, наверное, правильнее, с точки зрения чистоты кода, но указанный способ мне кажется довольно оптимальным в плане удобства.
                  • 0
                    Еще раз, почему вы не можете обмениваться инфой через кучу? В чем реальная необходимость локала?

                    Java8 Stream API задизайнено для parallel + unordered обработки. Да, можно аккуратно все заограничивать и сделать sequential + ordered обрабоку и тогда можно работать с локалами. Но тогда на 1 разработчика, который все аккуратно сделает sequential + ordered, найдется 100500 кодеров, которые огребут в полный рост работая с parallel + unordered через локалы.

                    И потом, вы же работаете со Scala. Прекрасно, я вообще считаю, что для современного программиста считающего себя специалистом — знание Scala это must have (даже если он на ней не пишет). У Java нет задачи вытеснить Scala.
                    • +1
                      Я могу сделать аналогичную вещь через кучу, не проблема. Сильно сомневаюсь что можно найти случай, когда нельзя заменить запись в локальную переменную работой с кучей, если понимать реальную необходимость как невозможность решить какую-то задачу другим способом, то её в данном случае нет. Но ведь синтаксический сахар вводится не ради реальной реобходимости, а ради упрощения разработки. Почему компилятор не может заменить работу с локальной переменной работой с кучей автоматически?

                      Окей, заменяем запись в локальную переменную работой с кучей, как это видится наиболее прямолинейным способом — разве в этом случае кодеры (parallel+unordered) ничего не огребут? Возможность выстрелить себе в ногу сохраняется в любом случае.

                      • 0
                        Почему компилятор не может заменить работу с локальной переменной работой с кучей автоматически?

                        Может, но не хочет. ;)
                        Куча она всегда куча, а автохипинг (хиппинг? ;)) локала вещь неоднозначная и меняющая семантику. Просматривать весь код метода только для того чтобы понять есть у меня там лямбда меняющая поведение локала или нет — увольте.
                        Когда у тебя язык где все переменные в куче — вопросов вообще нет, но Java не такой язык. И да, кстати, в байткоде никаких локальных переменных нет.

                        Окей, заменяем запись в локальную переменную работой с кучей, как это видится наиболее прямолинейным способом — разве в этом случае кодеры (parallel+unordered) ничего не огребут? Возможность выстрелить себе в ногу сохраняется в любом случае.

                        Выстрелить в ногу можно всегда. Просто хипом дуло торчит чуть побольше и более заметно.
            • +1
              Если я могу записать то же самое короче и понятнее с модификацией локальной переменной, а параллелизация мне при этом абсолютно не нужна — это не лень, это разумный выбор кратчайшего пути.
      • 0
        List<Integer> list = Arrays.asList(1,2,3,4,5);
        
        int sum = list.stream().mapToInt(Integer::intValue).sum();
        
        • 0
          Это, конечно, немного звиздец (использовано недостаточно фабрик для сложения пяти чисел), но не суть важно. Я тоже могу написать list.Sum(). Вопрос был именно в использовании изменяемой локальной переменной.
          • 0
            Можно использовать AtomicInteger или LongAdder вместо локальной переменной типа int. Кстати, это будет потокобезопасно, и можно суммировать параллельный стрим.
        • +2
          По-моему, так гораздо лучше:

          list.stream().reduce((x, y) -> x + y);
          
          • 0
            Мне оба решения не очень нравятся. Первое выпячивает проблемы конвертации между объектами и примитивными типами. Второе скрывает за страшной конструкцией банальный sum («очевидно» это только для функциональщиков). Оба решения слишком далеко от лаконичного list.Sum() из шарпа.

            Реально ли сделать метод а-ля FooLibrary.sum(list.stream()) (если он уже не сделан)?
            • 0
              Тогда лучше

              list.stream().reduce(Reducers::sum);
              
              • 0
                Такое точно реализуемо? Просто не знаю, как в джаве, а в шарпе в чистом виде (без рефлексии и динамических типов) такое невозможно: operator+ не определён для Object, а в ограничениях на тип это требование не сформулировать.
                • 0
                  Могу ошибиться т.к. еще не пробовал. Но если записать с дженериками — то должон уметь.
                  (Точнее ошибка уже есть т.к. в reduce должен быть еще 1 аргумент)

                  Stream<Integer> stream = (List<Integer> ).stream();
                  stream.reduce(0/*Должен заавтобоксится в Integer*/,  BinaryOperator<Integer> Reducers::sum);
                  
                  //Если подставить дженерики будет
                  @FunctionalInterface
                  BinaryOperator <Integer> {
                     public Integer operate(Integer a, Integer b);
                  }
                  
                  //Соответсвенно sum должен быть:
                  
                  Integer sum(Integer a, Integer b) {
                      /*Интеджеры мы складывать умеем, если что анбокснем*/
                      return a + b;
                  }
                  


                  Но вообще вроде как у IntegerStream есть просто метод sum();
                  • 0
                    Ты не ошибся, пробовал как ты написал.
            • 0
              Можно вообще сделать метод ListExtensions.sum(list). Для этого вам даже Java 8 не нужна.
    • +2
      Да, кстати, effectively final на анонимные классы распространили, т.е. код
              int x = 1;
              new Runnable() {
                  @Override
                  public void run() {
                      System.out.println(x);
                  }
              }.run();
      

      работает как ожидается.
    • +3
      >> Имейте в виду, что этот код останется корректным даже если убрать аннотацию @FunctionalInterface.
      > Тогда какой смысл в аннотации?
      Проверка, что интерфейс действительно функциональный. Выдаст ошибку при несоостветсвии требованию на функциональный интерфейс.
    • +3
      Тогда какой смысл в аннотации?

      Если аннотацию @ Override убрать, код тоже останется корректным, однако она сильно упрощает жизнь. Тут, видимо, аналогичная ситуация.
    • 0
      Имейте в виду, что этот код останется корректным даже если убрать аннотацию @FunctionalInterface.


      Тогда какой смысл в аннотации?


      она в компайл-тайм проверяет, что у вашего интерфейса ровно один абстрактный метод. Если ноль или несколько — будет ошибка компиляции.
  • +4
    Хорошая статья, спасибо! Про потоки стоит еще добавить важное замечание, что все промежуточные операции над потоками — ленивые. Т.е. они не будут выполнены пока не вызвана терминальная операция.
  • +3
    Нужно бы напомнить, что в текущем релизе Java 8 есть серьёзные ошибки. Есть тонкости работы с reflection и самое главное — не работают множественные catch внутри лямбда функций (кстати, про множественный catch, Вы как-то умолчали). Причём не работает оно страшно — внешне всё в порядке, просто код для catch не генерируется.
    Обещают исправить в ближайших релизах.
  • +3
    Не холивара ради, а из чистого любопытства: т.е. в Java лямбды появились только сейчас, спустя 10 лет после .net? Чем вызван столь огромный разрыв во времени (для IT-индустрии просто целая эпоха) — в Java какая-то своя специфика, что ни лямбды, ни атрибуты, ни Linq просто не нужны (компенсируется возможностями библиотек, иной спектр задач....)? В каком тогда направлении эти годы развивалась Java?

    Опять же, не хочу принижать Java, наверняка это замечательный язык, просто не представляю уже современную разработку без лямбд, позволяющих писать очень лаконичный и выразительный код, в стиле, близком к декларативному.
    • +5
      Я подозреваю, что у Сан/Оракл и Микрософт сильно разные подходы к развитию. Микрософт пытается впихнуть весь мир в один язык, Сан/Оракл же скорее видят яву как один из элементов экосистемы. Т.е. «java is a new C», а если вам нужно что-то более высокоуровневое — так есть Scala/Closure/JRuby/напишите свое.

      Мне кажется, что в мире .net людей, которые реально глубоко знают свою платформу — в разы меньше, чем в мире явы. Просто потому, что .net-разработчики больше вынуждены гнаться за поверхностными новинками, чем за глубиной (об этом еще Спольски хорошо писал). Во всяком случае, статьи про устройство и особенности работы .net VM которые я вижу последние годы — в мире явы были опубликованы лет 5-7 назад.
      • 0
        а если вам нужно что-то более высокоуровневое — так есть Scala/Closure/JRuby/напишите свое

        Насколько это работает? Та же скала набрала достаточно популярности, чтобы конкурировать с джавой?
        • –1
          Я сейчас в основном работаю с двумя довольно большими распределёнными фреймворками — Spark и Kafka. Оба вполне популярны и претендуют на то, чтобы занять лидирующие позиции в нише распределённых систем. Оба написаны на Scala.
        • 0
          >Та же скала набрала достаточно популярности, чтобы конкурировать с джавой?

          Популярности у нее хоть отбавляй. Всякие яндексы и прочий крупняк пишут на ней свои сервисы.
          А вот конкуренция с Java вообще бессмысленна. И не ставится целью.
    • +2
      У джавы в основе идеологии минимализм, чистый ООП, уход в абстракции и подход «это не нужно» (язык родился из идеи «возьмём C++ и выкинем всё лишнее»). Шарп — полная противоположность: давайте напихаем всего что можно, побольше, намажем сахаром и сверху положим вишенку (язык родился из идеи «возьмём Java и вернём напрасно выкинутое»).

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

      Для джавы обратная совместимость — это святое. Майкрософт же время от времени позволяет себе ломать совместимость во имя развития (впрочем, от авторов шарпа тоже временами слышно: «Эх, жаль, что сделали так, теперь уже не изменить»). Джава старше, поэтому наследия царского режима больше, поэтому проблем больше и развивать сложнее.

      в Java какая-то своя специфика, что ни лямбды, ни атрибуты, ни Linq просто не нужны

      А в других мейнстримовых языках есть все перечисленные вами фичи? В JS замыкания есть, но синтаксис длинный; атрибутов нет, да и сами классы ещё не ввели; сторонние реализации LINQ есть, но из-за первого пункта выразительность хромает. В PHP замыкания работают через пень-колоду; атрибуты парсят как текст в комментариях, если вдруг понадобится; LINQ в такой же ситуации, как в JS. В C++ такие же тормоза с новыми фичами, как в джаве. Вот питон и руби ворвались в мейнстрим как раз на подобных фичах в частности и выразительности в общем, это да.

      Это когда программируешь на локомотивах прогресса, кажется, что всё вокруг изменилось, расцвела функциональщина, асинхронность поросла сахаром и прочее. Но остальной мир-то не так торопится.
      • +4
        По крайней мере по лямбдам, Java оказалась замыкающей из всех мэйнстримных языков. Кивать на плюсы здесь не получится — я писал на них код с лямбдами еще в 2009-м (когда работал над/в VS 2010), а в финальном стандарте они появились три года назад.
  • 0
    Спасибо за перевод!
    но все-таки на мой взгляд перевод Map = Ассоциативный массив не очень удачен
    • +1
      Это и не перевод вовсе, а устоявшийся дословный пересказ.

      en.wikipedia.org/wiki/Map_(computer_science)
      ru.wikipedia.org/wiki/Ассоциативный_массив
      • –1
        не убедили!
        In Smalltalk, Objective-C, .NET,[7] Python, and REALbasic they are called dictionaries; in Perl and Ruby they are called hashes; in C++, Java, Go, Clojure, Scala, OCaml, Haskell they are called maps
        (they — Associative array)

        В языке Java ассоциативный массив именуется картой(map)


        То есть, правильно (хоть и тоже режет глаз :( ) было бы написать «карта».

        • 0
          «Карта» это перевод по промту. Правильных переводов два: «ассоциативный массив» и «отображение», но они оба слишком длинные, поэтому я бы уже официально пополнил русский язык заимствованным словом «мапа».
          • 0
            Мапа, дикта, хаша, словаря…
            • –1
              Преимущество английского языка в том, что там слова очень лихо сокращаются. Map же тоже от mapping пошло, а существительного такого не было, только глагол to map. Поэтому в английском dictionary -> dict это нормально, а у нас словарь -> слов? — и получается хрень.

              Хеш-таблицы — это множество, пересекающееся с мапами. Есть хеш-множества, не являющиеся мапами, и есть мапы на основе деревьев, не являющиеся хеш-таблицами.
          • 0
            в принципе не только словом «мапа», а и однокоренными — «маппинг»/«маппирование»
          • +3
            «Ассоциативный массив» — это все-таки associative array, а map — это именно «отображение».
  • 0
    Мы создаем ссылку на конструктор с помощью Person::new. Компилятор автоматически выбирает подходящий конструктор, сигнатура которого совпадает с сигнатурой PersonFactory.create.

    Тут важно упомянуть, что работать такая конструкция будет только с функциональными интерфейсами. В противном случае получим Compilation error.
  • 0
    Multiline string literals так и не добавили?

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