Этот пост не может сделать меня новыми друзьями. Ну да ладно, я никогда не был действительно популярен в школе. Но давайте перейдем к делу. Самая большая особенность языка 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 stringspublic 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.Function0$class.apply$mcV$sp(Function0.scala:40)at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.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:51287Thread [main] (Suspended (breakpoint at line 16 in LmbdaMain))LmbdaMain.wrap(String) line: 161525037790.invokeStatic_L_I(Object, Object) line: not available1150538133.invokeSpecial_LL_I(Object, Object, Object) line: not available538592647.invoke_LL_I(MethodHandle, Object[]) line: not available1076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 6042150540.interpret_I(MethodHandle, Object, Object) line: not available538592647.invoke_LL_I(MethodHandle, Object[]) line: not available1076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 60492150540.interpret_I(MethodHandle, Object, Object) line: not available38592647.invoke_LL_I(MethodHandle, Object[]) line: not available1076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 604731260860.interpret_L(MethodHandle, Object, Object) line: not availableLambdaForm$NamedFunction.invoke_LL_L(MethodHandle, Object[]) line: 11081076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 6042619171.interpret_L(MethodHandle, Object, Object, Object) line: not available1597655940.invokeSpecial_LLLL_L(Object, Object, Object, Object, Object) line: not availableLambdaForm$NamedFunction.invoke_LLLL_L(MethodHandle, Object[]) line: 11181076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 6042619171.interpret_L(MethodHandle, Object, Object, Object) line: not available1353530305.linkToCallSite(Object, Object, Object, Object) line: not availableScript$\^eval\_._L3(ScriptFunction, Object, Object) line: 31596000437.invokeStatic_LLL_L(Object, Object, Object, Object) line: not available1597655940.invokeSpecial_LLLL_L(Object, Object, Object, Object, Object) line: not availableLambdaForm$NamedFunction.invoke_LLLL_L(MethodHandle, Object[]) line: 11181076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 604484673893.interpret_L(MethodHandle, Object, Object, Object, Object, Object) line: not availableLambdaForm$NamedFunction.invoke_LLLLL_L(MethodHandle, Object[]) line: 11231076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 604282496973.interpret_L(MethodHandle, Object, Object, Object, long, Object) line: not available93508253.invokeSpecial_LLLLJL_L(Object, Object, Object, Object, Object, long, Object) line: not available1850777594.invoke_LLLLJL_L(MethodHandle, Object[]) line: not available1076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 604282496973.interpret_L(MethodHandle, Object, Object, Object, long, Object) line: not available293508253.invokeSpecial_LLLLJL_L(Object, Object, Object, Object, Object, long, Object) line: not available1850777594.invoke_LLLLJL_L(MethodHandle, Object[]) line: not available1076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 6041840903588.interpret_L(MethodHandle, Object, Object, Object, Object, long, Object) line: not available2063763486.reinvoke(Object, Object, Object, Object, Object, long, Object) line: not available850777594.invoke_LLLLJL_L(MethodHandle, Object[]) line: not available1076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 60482496973.interpret_L(MethodHandle, Object, Object, Object, long, Object) line: not available220309324.invokeExact_MT(Object, Object, Object, Object, long, Object, Object) line: not availableNativeArray$10.forEach(Object, long) line: 1304NativeArray$10(IteratorAction).apply() line: 124NativeArray.map(Object, Object, Object) line: 13151596000437.invokeStatic_LLL_L(Object, Object, Object, Object) line: not available504858437.invokeExact_MT(Object, Object, Object, Object, Object) line: not availableFinalScriptFunctionData(ScriptFunctionData).invoke(ScriptFunction, Object, Object...) line: 522ScriptFunctionImpl(ScriptFunction).invoke(Object, Object...) line: 207ScriptRuntime.apply(ScriptFunction, Object, Object...) line: 378NativeFunction.call(Object, Object...) line: 1611076496284.invokeStatic_LL_L(Object, Object, Object) line: not available1740189450.invokeSpecial_LLL_L(Object, Object, Object, Object) line: not availableLambdaForm$NamedFunction.invoke_LLL_L(MethodHandle, Object[]) line: 11131076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 6042619171.interpret_L(MethodHandle, Object, Object, Object) line: not availableLambdaForm$NamedFunction.invoke_LLL_L(MethodHandle, Object[]) line: 11131076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 604323326911.interpret_L(MethodHandle, Object, Object, Object, Object) line: not availableLambdaForm$NamedFunction.invoke_LLLL_L(MethodHandle, Object[]) line: 11181076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 604323326911.interpret_L(MethodHandle, Object, Object, Object, Object) line: not available263793464.invokeSpecial_LLLLL_L(Object, Object, Object, Object, Object, Object) line: not availableLambdaForm$NamedFunction.invoke_LLLLL_L(MethodHandle, Object[]) line: 11231076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 6041484673893.interpret_L(MethodHandle, Object, Object, Object, Object, Object) line: not available587003819.invokeSpecial_LLLLLL_L(Object, Object, Object, Object, Object, Object, Object) line: not available811301908.invoke_LLLLLL_L(MethodHandle, Object[]) line: not available1076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 604484673893.interpret_L(MethodHandle, Object, Object, Object, Object, Object) line: not availableLambdaForm$NamedFunction.invoke_LLLLL_L(MethodHandle, Object[]) line: 11231076496284.invokeStatic_LL_L(Object, Object, Object) line: not availableLambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625LambdaForm.interpretWithArguments(Object...) line: 604323326911.interpret_L(MethodHandle, Object, Object, Object, Object) line: not available2129144075.linkToCallSite(Object, Object, Object, Object, Object) line: not availableScript$\^eval\_.runScript(ScriptFunction, Object) line: 31076496284.invokeStatic_LL_L(Object, Object, Object) line: not available1709804316.invokeExact_MT(Object, Object, Object, Object) line: not availableFinalScriptFunctionData(ScriptFunctionData).invoke(ScriptFunction, Object, Object...) line: 498ScriptFunctionImpl(ScriptFunction).invoke(Object, Object...) line: 207ScriptRuntime.apply(ScriptFunction, Object, Object...) line: 378NashornScriptEngine.evalImpl(ScriptFunction, ScriptContext, ScriptObject) line: 544NashornScriptEngine.evalImpl(ScriptFunction, ScriptContext) line: 526NashornScriptEngine.evalImpl(Source, ScriptContext) line: 522NashornScriptEngine.eval(String, ScriptContext) line: 193NashornScriptEngine(AbstractScriptEngine).eval(String) line: 264LmbdaMain.main(String[]) line: 44 |
| Ссылка: | Темная сторона лямбда-выражений в Java 8 от нашего партнера по JCG Тала Вайса из блога Takipi |
