Продолжение заметок о впечатлениях после программирования на Java. Предыдущие части здесь:
Часть I
Часть II
В этот раз я расскажу о том, что хотелось бы иметь в языке Java, если бы его более-менее серьезно переработать (как язык, так и саму JVM).
Свой рассказ я разделил на две части. В первой части описываются то, что имеет более-менее осязаемые очертания. Тогда как во второй части речь пойдет о том, что хотелось бы видеть, но пока не понятно, как именно это должно выглядеть.
Итак, сначала о том, что я себе хорошо представляю.
1. Мне пришлось в своем проекте на Java перепаковывать биты в байты и обратно – после C++ без unsigned char-ов и unsigned int-ов было непривычно. Приходилось делать лишнюю работу, мелкую и нудную. Поэтому очень хотелось бы видеть в Java хотя бы unsigned byte. Чтобы можно было писать так:
class ByteTest { static public void main(String[] args) { ubyte v = 0x83; ubyte b = v >> 1; } } |
Насколько я понимаю, беззнаковые целые не поддерживаются на уровне JVM. Зря они так.
2. Очень бы не помешали туплы для того, чтобы из методов можно было возвращать несколько значений. Этого и в C++ нынешнем нет, но в C++ хотя бы есть возможность передавать аргументы по неконстантным ссылкам/указателям. Поэтому в C++ возврат нескольких int-ов из функции/метода гораздо проще, чем в Java. Так что, имхо, в Java туплы даже нужнее, чем в C++.
Очевидно, что туплы – это штука неоднозначная (как и автоматический вывод типов). Сегодня одному разработчику будет удобно возвращать из метода тупл. А через год другой разработчик будет гадать – такой же смысл имеет третий элемент возвращенного тупла. Поэтому использование туплов можно было бы ограничить: например, запретить их возврат из public-методов классов. Тогда бы и разработка на Java упростилась бы, и сопровождаемость кода не пострадала бы. Лично мне было бы приятнее писать так:
class Demo { public void doSomething() { int min; int max; (min, max) = findMinAndMax(); ... } private (int, int) findMinAndMax() { int min = someInternalData[0]; int max = someInternalData[0]; for( int item : someInternalData ) { if( item < min ) min = item; if( item > max ) max = item; } return (min, max); } ... } |
а не так, как сейчас:
class Demo { public void doSomething() { MinAndMax minMax = findMinAndMax(); ... } private (int, int) findMinAndMax() { MinAndMax result = new MinAndMax( someInternalData[0], someInternalData[0] ); for( int item : someInternalData ) { if( item < result.min ) result.min = item; if( item > result.max ) result.max = item; } return result; } ... } private class MinAndMax { int min; int max; MinAndMax(int min, int max) { this.min = min; this.max = max; } } |
3. В Java очень сильно не хватает константности объектов. В C++ я к этому привык. Бывало, что компилятор бил разработчика по рукам, предупреждая ошибки. Да и в связи с hype вокруг функционального программирования и многоядерных процессоров, языки без константности для объектов уже выглядят опасными.
За основу я бы взял вариант из языка D, но без хвостовой константности. Разделил бы все ссылки на три типа: обычные, readonly и immutable. Если есть обычная ссылка на объект, то у объекта можно вызывать любые методы. Если есть readonly-ссылка или immutable-ссылка, то можно вызывать только immutable-методы.
Соответственно, объекты в программе делятся на обычные (изменяемые) и immutable (изменить которые нельзя). Обычные объекты можно передавать по обычным или readonly ссылкам, но нельзя передавать по immutable-ссылке.
Смысл у readonly-ссылок такой же, как у const-ссылок и указателей в C++: мы не можем модифицировать объект, но эта модификация может быть разрешена для кого-то другого. Например:
class UserInfo { private String name; UserInfo(String name) { this.name = name; } immutable public String name() { return this.name; } public String setName(String name) { this.name = name; } } class UserObserver { readonly private UserInfo user; UserObserver(readonly UserInfo user) { this.user = user; } public void print() { System.out.println(user.name()); } } class Demo { static public void main(String[] args) { UserInfo ui = new UserInto("John Smith"); UserObserver observer = new UserObserver(ui); observer.print(); // => John Smith ui.setName("John Woo"); observer.print(); // => John Woo } } |
А вот смысл immutable в том, что компилятор гарантирует, что объект никогда не изменится. Если есть immutable ссылка на объект A, то компилятор разрешает вызывать только те методы объекта A, которые помечены как immutable. И компилятор следит за тем, чтобы внутри immutable-методов состояние объекта не изменялось.
Представим, что в примере выше в классе UserObserver хранилась бы не readonly, а immutable ссылка на UserInfo. В этом случае UserObserver был бы защищен от “внезапных” модификаций объекта UserInfo где-то в других местах программы.
Отсутствие хвостовой константности (т.е. когда все ссылки внутри immutable объекта автоматически становятся immutable ссылками) позволяет легко организовывать обмен сообщениями между нитями в программе. Например, пусть есть сообщение ChangeLogStreamNotify, которое рассылается сразу нескольким нитям:
class ChangeLogStreamNotify { public LogStream stream; ChangeLogStreamNotify(LogStream stream) { this.stream = stream; } } |
Сам объект сообщения поступает подписчикам в виде immutable объекта. Поэтому подписчики не смогут заменить в нем значение stream. Т.е. при доступе к экземпляру ChangeLogStreamNotify из разных потоков не нужно никакой синхронизации.
Однако, ссылка на LogStream внутри ChangeLogStreamNotify является обычной ссылкой. Поэтому для объекта LogStream можно вызывать любые методы:
class WorkThread { public void onChangeLogStreamNotify( immutable ChangeLogStreamNotify notify) { notify.stream.debug("log stream changed"); } } |
Ну а теперь перейдем к тем возможностям, которые я хотел бы видеть в Java, но пока не представляю себе, как бы они выглядели.
1. В Java не хватает хоть какого-то подобия нормального множественного наследования или же каких-то механизмов подмешивания общего кода в разные классы. Что-то типа механизма mixin-ов из Ruby или trait-ов из Scala. Хотя в Scala, имхо, trait-ы переусложнили. Нужно бы чего-нибудь попроще.
Например, был у меня интерфейс TlvCompoundItem с одним методом tlvFields.
public interface TlvCompoundItem extends TlvItem { TlvFieldInfo[] tlvFields(); } |
Этот интерфейс реализует базовый класс для PDU (элементов прикладного протокола):
abstract public class Pdu implements TlvCompoundItem { @Override abstract public TlvFieldInfo[] tlvFields(); ... } |
И затем каждый наследник Pdu должен определять у себя tlvFields одним и тем же способом:
public class SomeConcretePdu extends Pdu { ... @Override public TlvFieldInfo[] tlvFields() { return tlvFieldsDecription; } static private TlvFieldInfo[] tlvFieldsDescription = ...; } |
Т.е. в каждом наследнике Pdu есть статический атрибут с описанием TLV-полей, и всегда он имеет имя tlvFieldsDescription. И реализация метода tlvFields всегда возвращает этот атрибут.
Вот это дублирование одинакового кода tlvFields и не нравится. Можно было бы поступить как-то так (это только набросок):
mixin PduFieldsGetter requires(TlvFieldInfo[] fields) { public TlvFieldInfo[] tlvFields() { return fields; } } public class SomeConcretePdu extends Pdu uses PduFieldsGetter(tlvFieldsDescription) { ... // Здесь уже не нужно объявлять tlvFields(). static private TlvFieldInfo[] tlvFieldsDescription = ...; } |
2. Хотелось бы иметь в Java ссылки, которые гарантированно не могут быть нулевыми. Т.е., чтобы Java предлагала бы решение проблемы нулевых ссылок, как это сейчас делает Eiffel.
Только вот как это делать – это вопрос. Либо же в язык вводится понятие ненулевой ссылки (например, String! вместо String) и компилятор будет отслеживать действия с этой ссылкой (так попытались сделать в языке Nice). Либо же нужно добавлять в язык что-то вроде типа Option (с сопутствующими ему вариантами None и Some) и паттерн-матчинга, чтобы нужно было явно разделять ситуации с нулевыми и ненулевыми ссылками.
Я бы предпочел вариант без паттерн-матчинга и типа Option. Запись String! более привычна для императивного программиста. Да и гигатонны уже написанного Java кода в таком случае было бы гораздо проще переиспользовать.
Вот такие у меня мысли о том, как сделать из Java нормальный ;) язык. Если собрать эти предложения и то, о чем я писал раньше, то получится неплохой, прагматичный язык. На который я бы лично перешел бы с C++. Наверное :)