Этот пост не может сделать меня новыми друзьями. Ну да ладно, я никогда не был действительно популярен в школе. Но давайте перейдем к делу. Самая большая особенность языка Java 8 — это, несомненно, лямбда-выражения. Это была ключевая функция для функциональных языков, таких как Scala и Clojure, в течение нескольких лет, и теперь Java наконец присоединилась.
Вторая самая большая особенность (в зависимости, конечно, от того, кого вы спрашиваете) — это Nashorn — новый движок JVM JavaScript, который должен привести Java в соответствие с другими движками JS, такими как V8 и его контейнер node.js.
Но эти новые функции имеют темную сторону к ним.
Я объясню. Платформа Java состоит из двух основных компонентов. JRE, который JIT компилирует и выполняет байт-код, и JDK, который содержит инструменты dev и компилятор исходного кода javac. Эти два компонента довольно (но не полностью) отделены друг от друга, что позволяет людям писать свои собственные языки JVM, и Scala стала популярной в последние несколько лет. И в этом заключается некоторая проблема.
JVM была разработана, чтобы быть независимой от языка в том смысле, что она может выполнять код, написанный на любом языке, при условии, что он может быть преобразован в байт-код. Сама спецификация байт-кода полностью OO, и была разработана, чтобы близко соответствовать языку Java. Это означает, что байт-код, скомпилированный из исходного кода Java, будет очень похож на него структурно
Но чем дальше вы получаете от Java — тем больше увеличивается это расстояние. Когда вы смотрите на Scala, который является функциональным языком, расстояние между исходным кодом и исполняемым байт-кодом довольно велико. Компилятор добавляет большое количество синтетических классов, методов и переменных, чтобы заставить JVM выполнять семантику и управление потоками, требуемые языком.
Когда вы смотрите на полностью динамические языки, такие как JavaScript , это расстояние становится огромным.
И теперь с Java 8 это начинает распространяться и на Java.
Так почему я должен заботиться?
Хотелось бы, чтобы это была теоретическая дискуссия, которая хотя и интересна, но не имеет практического значения для нашей повседневной работы. К сожалению, это так, и очень сильно. Благодаря стремлению добавлять новые элементы в Java увеличивается расстояние между вашим кодом и средой выполнения, что означает, что то, что вы пишете, и то, что вы отлаживаете, будут двумя разными вещами.
Чтобы увидеть, как давайте (повторно) посетить пример ниже.
Java 6 & 7
Это традиционный метод, с помощью которого мы будем перебирать список строк, чтобы отобразить их длину.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
// simple check against empty strings public static int check(String s) { if (s.equals( "" )) { throw new IllegalArgumentException(); } return s.length(); } //map names to lengths List lengths = new ArrayList(); for (String name : Arrays.asList(args)) { lengths.add(check(name)); } |
Это вызовет исключение, если передается пустая строка. Трассировка стека будет выглядеть так:
1
2
|
at LmbdaMain.check(LmbdaMain.java: 19 ) at LmbdaMain.main(LmbdaMain.java: 34 ) |
Здесь мы видим соотношение 1: 1 между трассировкой стека, которую мы видим, и кодом, который мы написали, что делает отладку этого стека вызовов довольно простой. Это то, к чему привыкли большинство разработчиков Java.
Теперь давайте посмотрим на Scala и Java 8.
Scala
Давайте посмотрим на тот же код в Scala. Здесь у нас есть две большие перемены. Первое — это использование лямбда-выражения для отображения длин, а второе — итерация выполняется структурой (т. Е. Внутренняя итерация).
1
|
val lengths = names.map(name = > check(name.length)) |
Здесь мы действительно начинаем замечать разницу между тем, как выглядит код, который вы написали, и тем, как JVM (и вы) увидят его во время выполнения. Если выдается исключение, стек вызовов на порядок длиннее , и его гораздо сложнее понять.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
at Main$.check(Main.scala : 6 ) at Main$$anonfun$ 1 .apply(Main.scala : 12 ) at Main$$anonfun$ 1 .apply(Main.scala : 12 ) at scala.collection.TraversableLike$$anonfun$map$ 1 .apply(TraversableLike.scala : 244 ) at scala.collection.TraversableLike$$anonfun$map$ 1 .apply(TraversableLike.scala : 244 ) at scala.collection.immutable.List.foreach(List.scala : 318 ) at scala.collection.TraversableLike$ class .map(TraversableLike.scala : 244 ) at scala.collection.AbstractTraversable.map(Traversable.scala : 105 ) at Main$delayedInit$body.apply(Main.scala : 12 ) at scala.Function 0 $ class .apply$mcV$sp(Function 0 .scala : 40 ) at scala.runtime.AbstractFunction 0 .apply$mcV$sp(AbstractFunction 0 .scala : 12 ) at scala.App$$anonfun$main$ 1 .apply(App.scala : 71 ) at scala.App$$anonfun$main$ 1 .apply(App.scala : 71 ) at scala.collection.immutable.List.foreach(List.scala : 318 ) at scala.collection.generic.TraversableForwarder$ class .foreach(TraversableForwarder.scala : 32 ) at scala.App$ class .main(App.scala : 71 ) at Main$.main(Main.scala : 1 ) at Main.main(Main.scala) |
* Помните, этот пример очень прост. С реальными вложенными лямбдами и сложными структурами вы будете смотреть на гораздо более длинные синтетические стеки вызовов, из которых вам нужно будет понять, что произошло.
Это долго было проблемой со Scala, и одной из причин, по которой мы создали Scala Stackifier .
А теперь в Java 8
До сих пор Java-разработчики были довольно невосприимчивы к этому. Это изменится, когда лямбда-выражения станут неотъемлемой частью Java. Давайте посмотрим на соответствующий код Java 8 и полученный стек вызовов.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
Stream lengths = names.stream().map(name -> check(name)); at LmbdaMain.check(LmbdaMain.java: 19 ) at LmbdaMain.lambda$ 0 (LmbdaMain.java: 37 ) at LmbdaMain$$Lambda$ 1 / 821270929 .apply(Unknown Source) at java.util.stream.ReferencePipeline$ 3 $ 1 .accept(ReferencePipeline.java: 193 ) at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java: 948 ) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java: 512 ) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java: 502 ) at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java: 708 ) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java: 234 ) at java.util.stream.LongPipeline.reduce(LongPipeline.java: 438 ) at java.util.stream.LongPipeline.sum(LongPipeline.java: 396 ) at java.util.stream.ReferencePipeline.count(ReferencePipeline.java: 526 ) at LmbdaMain.main(LmbdaMain.java: 39 ) |
Это становится очень похоже на Scala. Мы платим цену за более короткий и краткий код с более сложной отладкой и более длинными синтетическими стеками вызовов.
Причина в том, что, хотя javac был расширен для поддержки лямбда-функций, JVM по-прежнему не замечает их. Это было дизайнерским решением Java-пользователей, чтобы поддерживать работу JVM на более низком уровне и не вводить новые элементы в его спецификацию.
И хотя вы можете обсуждать достоинства этого решения, это означает, что, как разработчики Java, затраты на вычисление этих стеков вызовов при получении билета теперь печально лежат на наших плечах, хотим мы этого или нет.
JavaScript в Java 8
Java 8 представляет совершенно новый компилятор JavaScript. Теперь мы можем эффективно и просто интегрировать Java + JS. Однако нигде нет диссонанса между кодом, который мы пишем, и кодом, который мы отлаживаем, больше, чем здесь.
Вот та же самая функция в Nashorn —
1
2
3
4
5
6
7
|
ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName( "nashorn" ); String js = "var map = Array.prototype.map \n" ; js += "var a = map.call(names, function(name) { return Java.type(\"LmbdaMain\").check(name) }) \n" ; js += "print(a)" ; engine.eval(js); |
В этом случае код байт-кода динамически генерируется во время выполнения с использованием вложенного дерева лямбда-выражений. Существует очень небольшая корреляция между нашим исходным кодом и результирующим байт-кодом, выполняемым JVM. Стек вызовов теперь на два порядка длиннее . В острых словах Mr.T — мне жаль дураков, которым нужно будет отлаживать стек вызовов, который вы здесь получите.
Вопросы, комментарии? (при условии, что вы можете прокрутить весь путь ниже этого стека вызовов). Дайте мне знать в разделе комментариев.
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
LmbdaMain [Java Application] LmbdaMain at localhost: 51287 Thread [main] (Suspended (breakpoint at line 16 in LmbdaMain)) LmbdaMain.wrap(String) line: 16 1525037790 .invokeStatic_L_I(Object, Object) line: not available 1150538133 .invokeSpecial_LL_I(Object, Object, Object) line: not available 538592647 .invoke_LL_I(MethodHandle, Object[]) line: not available 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 2150540 .interpret_I(MethodHandle, Object, Object) line: not available 538592647 .invoke_LL_I(MethodHandle, Object[]) line: not available 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 92150540 .interpret_I(MethodHandle, Object, Object) line: not available 38592647 .invoke_LL_I(MethodHandle, Object[]) line: not available 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 731260860 .interpret_L(MethodHandle, Object, Object) line: not available LambdaForm$NamedFunction.invoke_LL_L(MethodHandle, Object[]) line: 1108 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 2619171 .interpret_L(MethodHandle, Object, Object, Object) line: not available 1597655940 .invokeSpecial_LLLL_L(Object, Object, Object, Object, Object) line: not available LambdaForm$NamedFunction.invoke_LLLL_L(MethodHandle, Object[]) line: 1118 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 2619171 .interpret_L(MethodHandle, Object, Object, Object) line: not available 1353530305 .linkToCallSite(Object, Object, Object, Object) line: not available Script$\^eval\_._L3(ScriptFunction, Object, Object) line: 3 1596000437 .invokeStatic_LLL_L(Object, Object, Object, Object) line: not available 1597655940 .invokeSpecial_LLLL_L(Object, Object, Object, Object, Object) line: not available LambdaForm$NamedFunction.invoke_LLLL_L(MethodHandle, Object[]) line: 1118 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 484673893 .interpret_L(MethodHandle, Object, Object, Object, Object, Object) line: not available LambdaForm$NamedFunction.invoke_LLLLL_L(MethodHandle, Object[]) line: 1123 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 282496973 .interpret_L(MethodHandle, Object, Object, Object, long , Object) line: not available 93508253 .invokeSpecial_LLLLJL_L(Object, Object, Object, Object, Object, long , Object) line: not available 1850777594 .invoke_LLLLJL_L(MethodHandle, Object[]) line: not available 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 282496973 .interpret_L(MethodHandle, Object, Object, Object, long , Object) line: not available 293508253 .invokeSpecial_LLLLJL_L(Object, Object, Object, Object, Object, long , Object) line: not available 1850777594 .invoke_LLLLJL_L(MethodHandle, Object[]) line: not available 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 1840903588 .interpret_L(MethodHandle, Object, Object, Object, Object, long , Object) line: not available 2063763486 .reinvoke(Object, Object, Object, Object, Object, long , Object) line: not available 850777594 .invoke_LLLLJL_L(MethodHandle, Object[]) line: not available 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 82496973 .interpret_L(MethodHandle, Object, Object, Object, long , Object) line: not available 220309324 .invokeExact_MT(Object, Object, Object, Object, long , Object, Object) line: not available NativeArray$ 10 .forEach(Object, long ) line: 1304 NativeArray$ 10 (IteratorAction).apply() line: 124 NativeArray.map(Object, Object, Object) line: 1315 1596000437 .invokeStatic_LLL_L(Object, Object, Object, Object) line: not available 504858437 .invokeExact_MT(Object, Object, Object, Object, Object) line: not available FinalScriptFunctionData(ScriptFunctionData).invoke(ScriptFunction, Object, Object...) line: 522 ScriptFunctionImpl(ScriptFunction).invoke(Object, Object...) line: 207 ScriptRuntime.apply(ScriptFunction, Object, Object...) line: 378 NativeFunction.call(Object, Object...) line: 161 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available 1740189450 .invokeSpecial_LLL_L(Object, Object, Object, Object) line: not available LambdaForm$NamedFunction.invoke_LLL_L(MethodHandle, Object[]) line: 1113 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 2619171 .interpret_L(MethodHandle, Object, Object, Object) line: not available LambdaForm$NamedFunction.invoke_LLL_L(MethodHandle, Object[]) line: 1113 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 323326911 .interpret_L(MethodHandle, Object, Object, Object, Object) line: not available LambdaForm$NamedFunction.invoke_LLLL_L(MethodHandle, Object[]) line: 1118 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 323326911 .interpret_L(MethodHandle, Object, Object, Object, Object) line: not available 263793464 .invokeSpecial_LLLLL_L(Object, Object, Object, Object, Object, Object) line: not available LambdaForm$NamedFunction.invoke_LLLLL_L(MethodHandle, Object[]) line: 1123 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 1484673893 .interpret_L(MethodHandle, Object, Object, Object, Object, Object) line: not available 587003819 .invokeSpecial_LLLLLL_L(Object, Object, Object, Object, Object, Object, Object) line: not available 811301908 .invoke_LLLLLL_L(MethodHandle, Object[]) line: not available 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 484673893 .interpret_L(MethodHandle, Object, Object, Object, Object, Object) line: not available LambdaForm$NamedFunction.invoke_LLLLL_L(MethodHandle, Object[]) line: 1123 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147 LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625 LambdaForm.interpretWithArguments(Object...) line: 604 323326911 .interpret_L(MethodHandle, Object, Object, Object, Object) line: not available 2129144075 .linkToCallSite(Object, Object, Object, Object, Object) line: not available Script$\^eval\_.runScript(ScriptFunction, Object) line: 3 1076496284 .invokeStatic_LL_L(Object, Object, Object) line: not available 1709804316 .invokeExact_MT(Object, Object, Object, Object) line: not available FinalScriptFunctionData(ScriptFunctionData).invoke(ScriptFunction, Object, Object...) line: 498 ScriptFunctionImpl(ScriptFunction).invoke(Object, Object...) line: 207 ScriptRuntime.apply(ScriptFunction, Object, Object...) line: 378 NashornScriptEngine.evalImpl(ScriptFunction, ScriptContext, ScriptObject) line: 544 NashornScriptEngine.evalImpl(ScriptFunction, ScriptContext) line: 526 NashornScriptEngine.evalImpl(Source, ScriptContext) line: 522 NashornScriptEngine.eval(String, ScriptContext) line: 193 NashornScriptEngine(AbstractScriptEngine).eval(String) line: 264 LmbdaMain.main(String[]) line: 44 |
Ссылка: | Темная сторона лямбда-выражений в Java 8 от нашего партнера по JCG Тала Вайса из блога Takipi |