Статьи

Java: локальный минимум по языку

Я написал сотни тысяч строк кода на Java в период между 1996 и 2002 годами. Я написал веб-фреймворки, электронные таблицы и многое другое на Java 1.0 через Java 1.4.

По сравнению с C ++ середины 90-х (предварительные шаблоны), Java был совершенно удивительным языком. И JVM — лучшее время выполнения для любого компьютерного языка.

Долгое странное путешествие

В 2002 году я начал заниматься C #. Тогда Руби. Тогда Скала. Немного Хаскелла и совсем недавно я делал много Clojure.

Итак, количество Java 1.5 / Generics, которое я разработал в этом году, очень ограничено.

Получил концерт Java

В этом году я получил концерт написания очень интересного кода (компилятор). Но клиент — это магазин Java. Проект уже имел базу Java. И клиент был непреклонен в том, что проект будет выполнен на Java… не Scala… не Clojure… не JRuby, а Java.

Итак, последние 6 месяцев я потратил на написание тысяч строк кода Java. Я видел, как Java развивалась за последние 10 лет, и это шокирует. Шокирует, насколько плохой стала Java.

По сути, Java бессмысленно многословна. Дженерики добавляют, насколько я могу судить, около 20% -30% к общему количеству персонажей в данной программе. В то время как типы Scala, с выводом типов, являются абсолютно положительными по сравнению с Ruby (я не могу судить о весе системы типов Scala и ее значении по сравнению с менее типизированным Clojure.) Удивительный многословный, визуально отвлекающий, уродливый Java как что-либо Я думаю, что Generics не стоит незначительного улучшения в проверке типов по сравнению с Java 1.4.

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

Это не «Скала лучше пост»

Обратите внимание, что этот пост не является постом «Scala лучше». Хотя некоторые идеи в этом посте исходят из Scala, я не считаю Scala (или Clojure) разумной заменой Java. Scala — это слишком много языка, и я видел массу неправильного использования Scala.

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

Некоторые из этих вещей могут быть предложены или в JDK 8

Это мои мысли. Я не игрок в JCP-стране. Я знаю о лямбдах в Java 8 благодаря отличному обеду с Сэмом Пулларой. Но я не слишком разбираюсь в трафике JCP, поэтому эти мысли могут быть охвачены предложениями JCP.

Общедоступный по умолчанию

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

Почему? Поскольку чем меньше части шума мы имеем как часть нашего кода, тем больше сигнал будет выделяться.

Последнее выражение — возвращаемое значение

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

1
int plusOne(int x) {x + 1;}

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

Блочные выражения

Все в фигурных скобках является выражением блока, а не оператором блока. Это означает, что если curly-brace-thing может быть выражением, оно рассматривается как таковое.

1
2
3
4
String s = {
  var d = new Date();
  "The current date is "+d;
}

Некоторая Типовая Инференция

Я хотел бы видеть автоматический вывод назначения. В принципе:

1
var name = "David"; // it's a String, duh

Нам не нужно делать var ключевым словом. По сути, тип var становится типом «выводим тип».

И основной вывод типа параметра слева (обратите внимание, что IntelliJ уже использует этот синтаксис):

1
List<String> stuff = new ArrayList<~>();

И вывод возвращаемого типа, если существует единственный путь к возвращаемому значению (без операторов return кроме последней строки метода):

1
2
3
4
plusOne(int x) // it's an int
 {
   x + 1;
 }

Все вышеперечисленные изменения могут быть сделаны с данными, уже известными компилятору. Там нет причудливых алгоритмов, необходимых. Это просто сокращение повторяющихся слов в программе … и с выводом типа Generics, это то, что IDE уже отображают кодеру в любом случае.

Лучшее тестирование на равенство

Добавьте === в качестве псевдонима к методу equals , но для правой части === необходимо использовать ко-вариант с правой стороной.

Кроме того, добавьте !== как !equals .

Простые неизменяемые структуры данных с метаданными

Представление простых данных в Java — огромная боль. Да, IDE очень помогают в создании геттеров / сеттеров. Но в конце концов, многие наши программы содержат необработанные данные. Итак, я предлагаю добавить внутренние структуры данных в Java.

Они представляют собой комбинацию классов случаев Scala с добавленной возможностью метаданных Clojure.

struct s может быть только внутри публичного класса верхнего уровня.

Например:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MyStuff {
  struct Name(firstName = "David", lastName = "Pollak",
              age = 49, Date birthday = null)
 
  public static boolean funWithNames() {
    var archer = Name("Archer").age(10);
    var tessa = Name().firstName("Tessa").lastName("kitten").
                age(1);
 
    assert(tessa.age === 1);
    assert(tessa.getAge() === 1);
 
    archer !== tessa;
  }
 
  public static void metaData() {
    var david = Name("David", "Pollak");
    Name d2 = david.setMeta("geek", "true");
    assert(david === d2); // metadata not part of equality testing
    assert(david != d2); // object instances not the same
    assert(david.getMeta("geek") === null);
    assert(d2.getMeta("geek") === "true");
 
  }
}

Что вы получаете со struct ?

  • Создание неизменных объектов без new и с позиционными конструкторами для каждого параметра, а также просто, номинальная цепочка создания и новых значений.
  • Map<String, String> метаданных, связанных с каждой struct .
  • Значения по умолчанию для каждого из полей. Это позволяет добавлять поля, и новые версии библиотеки с дополнительными параметрами не будут нарушать доступ к старым версиям кода.
  • Метод toString который печатает имена и значения полей.
  • Правильно реализованный метод equals который выполняет сравнение всех полей с правильной обработкой пустых полей.
  • Метод hashCode который вычисляет хеш-код (его следует кэшировать, если известно, что все поля являются неизменяемыми).
  • Методы доступа уровня поля.
  • Копировщики для каждого поля: field(param) Это позволяет создавать новый экземпляр с новым значением. Метаданные скопированы.
  • Для каждой struct доступно множество метаданных, включая список имен полей, тип каждого поля. Эти метаданные могут использоваться для быстрого, эффективного автоматически генерируемого набора сериализаторов / десериализаторов (например, JSON, JDBC, XML).

Также обратите внимание на синтаксис определения. Тип выводится, если это возможно.

Для каждого класса, который содержит struct , будет интерфейс ClassName.struct . Интерфейс реализован всеми struct и интерфейс содержит все общие поля. Например:

1
2
3
4
5
6
class People {
  struct Parent(String name = null, List<People.struct> kids = null)
  struct Kid(String name = null, Parent parent = null)
 
  String getName(struct person) {person.name;}
}

Есть множество вещей, которые также можно сделать с помощью аннотаций (например, принудительное использование сериализуемой JSON struct и т. Д.)

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

Умнее кастинг

Приведение Java слишком многословно и подвержено ошибкам. Использование шаблона if / instanceof / cast занимает много кода и очень подвержено ошибкам, поскольку класс указывается дважды … один раз в тесте и один раз в приведении.

Мы можем позаимствовать одну из моих любимых функций в Scala: сопоставление с образцом, но сделаем это просто:

01
02
03
04
05
06
07
08
09
10
11
String testIt(Object o) {
  switch o {
    String s: s;
    Number n: "It's a number: "+n;
    Date d if d.millis() > 0: {
       var formattedDate = formatDate(d);
       "It's a modern date: "+formattedDate;
       }
    default: "No clue";
  }
}

Хорошо … что у нас есть? Класс проверен. Если класс совпадает, то присвойте его переменной (которая имеет только область видимости строки). Затем, запустите охрану, чтобы увидеть, если дело должно быть принято. Если это так, запустите выражение справа от :

Обратите внимание, что эта форма переключения является выражением, а не утверждением. Кроме того, обратите внимание, что нет провала.

Итак, что еще это облегчает?

1
2
3
4
5
6
7
String whatSize(int i) {
  switch i {
  v if s < 10 : "small";
  v if s < 100 : "medium";
  default: "large";
  }
}

Следующее, что мы можем сделать, это применить его к struct s (нет, это не сопоставление с образцом):

1
2
3
4
5
6
String aboutPerson(People.struct p) {
  switch p all {
  Parent p: "A parent named "+p.name+" #"+p.kids.size();
  Kid k: "A kid with "+(k.parent.kids.size() - 1)+" sibs";
  }
}

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

Неизменные Коллекции

Java отчаянно нуждается в превосходном неизменяемом пакете коллекции.

По сути, этот пакет будет чистым плагиатом отличных классов Clojure для Map и Vector . Нам не нужно много модных Scala-подобных коллекций.

Неизменяемые коллекции, которые не реализуют java.util.List или java.util.Map . По сути, эти коллекции будут автономными.

В сочетании с лямбдами JDK 8 я получу целую тонну того, что мне нравится, от Scala и Clojure в плане манипуляций с коллекцией.

Итак, у вас есть это

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

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

Что еще я хотел бы увидеть? Параметры вызова по имени (я не знаю, будут ли они частью лямбды). Свойства. Также было бы интересно узнать, возможно ли иметь какую-либо форму глобального вывода типов в Java. Но все это на другой день.

Раскачать!

Ссылка: Java: локальный минимум на языке от нашего партнера JCG Дэвида Поллака в блоге DPP в блоге.