Комментарии 27
val list = new ArrayList<int>(); // вместе с Lombok будет красота
Исторический костыль с int, Integer, double, Double очень раздражает.
Пожалуй самый бросающийся в глаза, если учесть что 95% работы рядового программиста, это работа со списками.
И хотя говорят что эффективность не фича Java, но вы прикиньте как бы мы разгрузили GC, если бы переложили часть операций на стек.
Согласен, может кому то и не понадобится, но кучу высоконагруженных проектов это спасет от жестких оптимизаций, когда все хранится в виде кучи полей-массивов из примитивов (привет процедурное программирование). Можно будет писать требующие быстродействия и экономии вещи в привычном ООП стиле.
Вот вырезка из PDF доклада
JVM decides if:
● allocate on heap
OR
● put on stack (locals, parameters, result)
● inline into container class
● inline into array (flattened array)
Посмотрим, конечно, что в финале выйдет. Но тем не менее это уже шаг вперед.
Мое мнение тут субъективно. Пока все последние оптимизации JVM и стандартной библиотеки очень даже радуют.
Ну в целом мы в наших проектах активно используем javax.annotation.nonnull и nullable. Вместе с современной IDE в нашем коде nptr ex не случается, максимум на стыке со сторонними библиотеками. Но есть побочные явления:
1) код естественно захламляется
2) можно забыть поставить аннотацию и ide ничего не скажет (код ревью нам в помощь)
3) в постороннем коде аннотаций может и не быть (и скорее всего и не будет)
Добавить в jdk такую аннотацию можно было бы и в compile time проверять. Это безусловно усложнит анализ кода. Надо будет отслеживать проверки на null. Кроме того весь багаж кода как-то надо будет поддержать по обратной совместимости. Не будут же все переписывать библиотеки?
Честно говоря мне очень нравится в этом плане Kotlin. nullable типы как отдельный класс типов очень удобен. Пока явно не указал, все notnull. Кроме того Kotlin компилятор хорошо понимает все виды java notnull аннтотации. Прям хорошая работа
Заранее извиняюсь, но любопытство не порок. Я крайне плохо разбираюсь в Java и как следствие некоторый код мне не понятен. В частности, что вот тут происходит:
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.toUpperCase());
}
Зачем делать преобразование типов, если переменна obj и так является String? Или это не преобразование?
public class Sample {
public void method(Object obj) {
System.out.println(obj.toUpperCase()); // compile-time error
}
public void methodB(Object obj) {
if (obj instanceof String) {
System.out.println(obj.toUpperCase()); // compile-time error
}
}
}
В этом плане Kotlin является более продвинутым в компиляции. Вот тот же пример на Kotlin
fun sampleFun(obj: Any) {
if (obj is String) {
print(obj.toUpperCase()) // it works
}
}
А если obj is String
вынести в функцию и вставить ее вызов в if
— сработает?
fun isNotEmptyString(obj: Any):Boolean {
contract {
returns(true) implies (obj is String)
}
return (obj is String) && obj.isNotEmpty()
//return (obj is Int) && obj > 0 //still valid with contract
}
Тут подробнее
Спасибо! Интересная вещь, хотя и не совсем понятно, в чем профит программиста от такого описания контракта? Мы добавляем какую-то метаинфу для компилятора, чтобы он лучше понимал основной код, и, видимо, не выдавал ворнинги. Не проще ли сразу задизайнить язык так, чтобы компилятор понимал код без подсказок?
Я имел ввиду, что можно было бы сделать полноценный паттерн-матчинг как синтаксическую конструкцию. Да, для компилятора это было бы чуть более напряжно. Но пусть уж лучше компилятор напрягается, чем программист, которому теперь надо держать в голове все неявные преобразования, которые делают смарткасты и контракты. Целых два разных механизма, решающих довольно узкую задачу, которая по идее должна достаточно редко возникать в языке с type-safety и null-safety.
enum class FigureType {
CIRCLE, RECTANGLE
}
sealed class Figure {
internal abstract val type: FigureType
}
class Circle(val x: Int, val y: Int, val radius: Int) : Figure() {
override val type = FigureType.CIRCLE
}
class Rectangle(val x0: Int, val y0: Int, val x1: Int, val y1: Int) : Figure() {
override val type = FigureType.RECTANGLE
}
@ExperimentalContracts
fun Figure.isCircle(): Boolean {
contract {
returns(true) implies (this@isCircle is Circle)
}
return type == FigureType.CIRCLE
}
@ExperimentalContracts
fun Figure.isRectangle(): Boolean {
contract {
returns(true) implies (this@isRectangle is Rectangle)
}
return type == FigureType.RECTANGLE
}
@ExperimentalContracts
fun test(figure: Figure) {
when {
figure.isCircle() -> {
println(figure.x)
println(figure.y)
println(figure.radius)
}
figure.isRectangle()-> {
println(figure.x0)
println(figure.y0)
println(figure.x1)
println(figure.y1)
}
}
}
Ну да, вы изобрели паттерн-матчинг на костылях контрактах. Осталось подождать, пока его таки изобретут авторы языка. После этого можно будет спокойно выкинуть все эти неявные конвертации.
Чисто для сравнения аналогичный вашему код на Scala:
sealed trait Figure
case class Circle(x: Int, y: Int, radius: Int) extends Figure
case class Rectangle(x0: Int, y0: Int, x1: Int, y1: Int) extends Figure
val test: Figure => Unit = _ match {
case Circle(x, y, radius) => println(s"$x, $y, $radius")
case Rectangle(x0, y0, x1, y1) => println(s"$x0, $y0, $x1, $y1")
}
_ match {
case Circle(x, y, radius) => println(s"$x, $y, $radius")
case Rectangle(x0, y0, x1, y1) => println(s"$x0, $y0, $x1, $y1")
}
внутри содержит instanceof (посмотрел декомпилированный код). А я как раз хотел показать пример где instanceof не используется и компилятор нуждается в подсказке.Код на котлине эквивалентеый вашему коду на Scala
when(figure){
is Circle -> with(figure) { println("$x $y $radius") }
is Rectangle -> with(figure) { println("$x0 $y0 $x1 $x1") }
}
А я как раз хотел показать пример где instanceof не используется и компилятор нуждается в подсказке.
Хмм, а зачем это нужно? Пусть компилятор под капотом проверяет типы как хочет. Обычно все-таки люди стремятся писать код, который решает задачи, а не подсказки для компилятора, которые выглядят гораздо страшнее, чем обычный каст, который они заменяют.
Код на котлине эквивалентеый вашему коду на Scala
Он не совсем эквивалентный.
Во-первых, в котлиновской версии одна и та же переменная figure
имеет сначала один тип, а потом уже другой. Это облегчает написание кода (не надо самому писать каст), но усложняет чтение. Даже в джаве, которая гораздо сильнее ограничена в плане синтаксиса, это сделали по-нормальному.
Во-вторых, насколько такой подход масштабируется на чуть более сложные случаи типа:
case Vacancy(id, _, Some(Salary(from, to, Some(currency))), Nil) => ...
По моему идея прятать не очень красивые, но более эффективные решения в глубины кода в целом неплохая. А то если совсем не заглядывать под капот, то можно получить проблемы с производительностью (но тут речь скорее про библиотеки, а не прикладные программы)
Ну из за особенностей синтаксиса я не мог написать на котлине точно так же как на Scala. Со сложными случаями выглядит интересно, спасибо почитаю.
Не проще ли сразу задизайнить язык так, чтобы компилятор понимал код без подсказок?
Подозреваю это противоречит необходимости не уходить синтаксически далеко от Java. Все же для такой точной компиляции надо менять систему типов, делая её похожей на Haskell.
К моменту выполнения кода, obj имеет тип Object, то есть, ссылки на что угодно, которое является объектом (не примитивным типом, как int). Мы тут не уверены, что это String; чтобы использовать, надо это проверить. Если мы попытаемся obj, который не String, «преобразовать» к String, получим исключение.
На уровне рантайма это не преобразование — это проверка типа и присвоение переменной, сама ссылка остаётся точно такой же. Но на уровне языка это метод проверить и, если тип подходит, сделать присвоение. Слово «преобразование» тут немного условно, но устоялось.
Ситуаций, в которых такое нужно — проверить и выполнить действие соответственно типу — очень много, хотя в основном это действия достаточно нижнего уровня (системные библиотеки, всякие универсальные фреймворки).
В языках, которые писались, когда грабли этой концепции были уже хоть частично пройдены, обычно есть совмещённый оператор проверки-и-конверсии, результатом которого является или ссылка уже точно нужного типа, или null. Например, C# использует для этого «as»: можно было бы написать
if ((String s = obj as String) != null) {
System.out.println(s.toUpperCase());
}
но авторы Java пропустили эту возможность (и не стоит их сильно винить за это, они были почти первопроходцы), а затем решили расширить синтаксис уже существующей проверки.
Упомянутый pattern matching — ещё более продвинутый вариант такой проверки (обычно со множественными вариантами).
Чего ждать от Java в 2020 году?