参考链接: 有关Java中数组分配的有趣事实
kotlin和java语言
本文已过期。 在围绕Kotlin 1.0的发行大肆宣传之后,让我们认真看一下我们也应该在Java中拥有的一些Kotlin语言功能。
在本文中,我不会希望有独角兽。 但是有一些悬而未决的成果(据我天真地看到),可以将它们引入Java语言而不会带来很大的风险。 在阅读本文时,请确保将粘贴示例复制到http://try.kotlinlang.org (Kotlin的在线REPL)
1.数据类别
语言设计师几乎从未同意类是什么的必要性和功能范围。 奇怪的是,在Java中,每个类始终具有标识这个概念,而在现实世界中所有Java类的80%到90%都不需要这个概念。 同样, Java类始终具有可在其上进行同步的监视器 。
在大多数情况下,编写类时,您实际上只是想对值进行分组,例如字符串,整数,双精度型。 例如:
public class Person {
final String firstName;
final String lastName;
public JavaPerson(...) {
...
}
// Getters
...
// Hashcode / equals
...
// Tostring
...
// Egh...
}
当您完成上述所有操作时,手指将不再用力。 Java开发人员针对上述情况实施了丑陋的解决方法,例如IDE代码生成或lombok ,这是所有黑客中最大的。 在一个更好的Java中,Lombok中实际上不需要任何东西。
例如,如果Java具有Kotlin的数据类 :
data class Person(
val firstName: String,
val lastName: String
)
以上就是声明与前面的Java代码等效的全部内容。 因为数据类用于存储数据(duh)(即值),所以hashCode() , equals() , toString()很明显,并且可以默认提供。 此外,数据类是一等元组,因此它们可以照此使用,例如在各个引用中再次对其进行分解:
val jon = Person("Jon", "Doe")
val (firstName, lastName) = jon
在这种情况下,我们可能希望。 正在设计Valhalla / Java 10及其值类型 。 我们将看到直接在JVM和Java语言上提供多少功能。 这无疑将是一个令人兴奋的补充。
请注意,在Kotlin中val是如何可能的: 局部变量类型推断。 现在正在为将来的Java版本进行讨论 。
2.默认参数
您会重载API多少次,如下所示:
interface Stream<T> {
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
}
上面是完全相同的JDK Stream操作。 第一个简单地将Comparator.naturalOrder()应用于第二个。 因此,我们可以在Kotlin中编写以下代码 :
fun sorted(comparator : Comparator<T>
= Comparator.naturalOrder()) : Stream<T>
当只有一个默认参数时,这种优势不会立即显现出来。 但是想象一下一个带有大量可选参数的函数:
fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
...
}
可以通过以下任何一种方式调用它:
reformat(str)
reformat(str, true, true, false, '_')
reformat(str,
normalizeCase = true,
upperCaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)
默认参数的功能在于,当按名称而不是按索引传递参数时,它们特别有用。 JVM当前不支持此功能,直到Java 8才完全不保留参数名称( 在Java 8中,您可以为此打开JVM标志 ,但是使用Java的所有传统,则不应依赖在此呢)。
哎呀,此功能是我每天在PL / SQL中使用的功能。 当然, 在Java中,您可以通过传递参数object来解决此限制 。
3.简化的检查实例
如果您愿意,这实际上是switch的instanceof。 某些人可能会声称这些东西是邪恶的,糟糕的OO设计。 Nja nja。 我说,这种情况时有发生。 显然,在Java 7中,字符串开关被认为足够通用以修改语言以允许它们。 为什么不使用instanceof开关?
val hasPrefix = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
这不仅可以执行instanceof开关,而且还以可分配表达式的形式进行。 when表达式功能强大when Kotlin对此when 。 您可以混合使用任何种类的谓词表达式,类似于SQL的CASE表达式。 例如,这也是可能的:
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
与SQL比较(并非在所有方言中都实现):
CASE x
WHEN BETWEEN 1 AND 10 THEN 'x is in the range'
WHEN IN (SELECT * FROM validNumbers) THEN 'x is valid'
WHEN NOT BETWEEN 10 AND 20 'x is outside the range'
ELSE 'none of the above'
END
如您所见,只有SQL比Kotlin更强大。
4.映射键/值遍历
现在,仅使用语法糖就可以非常轻松地完成此操作。 当然,具有局部变量类型推断将是一个加号,但请检查一下
val map: Map<String, Int> = ...
现在,您可以执行以下操作:
for ((k, v) in map) {
...
}
毕竟,在大多数情况下,遍历地图都是通过Map.entrySet() 。 Map可能已经增强,可以在Java 5中扩展Iterable<Entry<K, V>> ,但没有。 真可惜。 毕竟,它已在Java 8中得到增强,以允许通过Map.forEach()对Java 8中的条目集进行内部迭代:
map.forEach((k, v) -> {
...
});
JDK上帝,还不算太晚。 您仍然可以让Map<K, V> extend Iterable<Entry<K, V>>
5.地图访问文字
这一功能将为Java语言增加无数的价值。 像大多数其他语言一样,我们有数组。 与大多数其他语言一样,我们可以使用方括号访问数组元素:
int[] array = { 1, 2, 3 };
int value = array[0];
还要注意一个事实,我们在Java中拥有数组初始化文字,这很棒。 那么,为什么不同时允许使用相同的语法访问地图元素呢?
val map = hashMapOf<String, Int>()
map.put("a", 1)
println(map["a"])
实际上, x[y]只是x.get(y)支持的方法调用的语法糖。 太好了,我们立即将Record.getValue()方法重命名为Record.get() (当然,将旧方法保留为同义词),这样您现在就可以像这样取消引用数据库记录值了。 ,位于Kotlin
ctx.select(a.FIRST_NAME, a.LAST_NAME, b.TITLE)
.from(a)
.join(b).on(a.ID.eq(b.AUTHOR_ID))
.orderBy(1, 2, 3)
.forEach {
println("""${it[b.TITLE]}
by ${it[a.FIRST_NAME]} ${it[a.LAST_NAME]}""")
}
由于jOOQ将所有列类型信息保存在单个记录列上,因此您实际上可以预先知道it[b.TITLE]是String表达式。 很好,是吗? 因此,此语法不仅可以与JDK映射一起使用,而且可以与公开基本get()和set()方法的任何库一起使用。
请继续关注此处的更多jOOQ和Kotlin示例: https : //github.com/jOOQ/jOOQ/blob/master/jOOQ-examples/jOOQ-kotlin example / src / main / kotlin / org / jooq / example / kotlin / FunWithKotlinAndJOOQ .kt
6.扩展功能
这是一个有争议的话题,当语言设计师对此一无所知时,我可以完全理解。 但是时不时地, 扩展功能非常有用。 实际上,这里的Kotlin语法只是为了让函数假装为接收器类型的一部分:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
现在,这将允许交换列表中的元素:
val l = mutableListOf(1, 2, 3)
l.swap(0, 2)
这对于jOOλ之类的库将非常有用,该库通过将Java 8 Stream API封装为jOOλ类型来扩展Java 8 Stream API( 另一个此类库是StreamEx ,重点稍有不同)。 该jOOλ Seq包装类型是不是真的很重要,因为它伪装成一个Stream的类固醇。 如果可以通过导入将jOOλ方法人工地应用于Stream上,那就太好了:
list.stream()
.zipWithIndex()
.forEach(System.out::println);
zipWithIndex()方法并不真正存在。 上面的代码只会翻译成以下不太易读的代码:
seq(list.stream())
.zipWithIndex()
.forEach(System.out::println);
实际上,扩展方法甚至允许绕过将所有内容显式地包装在stream() 。 例如,您可以这样做:
list.zipWithIndex()
.forEach(System.out::println);
由于所有jOOλ的方法都可以设计为也可应用于Iterable 。
同样,这是一个有争议的话题。 例如,因为
@rafaelcodes面向对象的优点是什么? 现在,我们编写了receive.send(message),而不是send(receiver,message)。
— Lukas Eder(@lukaseder) 2016年1月28日
虽然给人一种虚拟的假象,但扩展功能实际上只是加糖的静态方法。 进行这种欺骗对于面向对象的应用程序设计是一个巨大的风险,这就是为什么此功能可能不会将其纳入Java的原因。
7.安全呼叫接线员(以及:猫王接线员)
可选的是meh。 可以理解,需要引入一个Optional类型,以便在缺少基本类型值(不能为null)的情况下进行抽象。 现在,我们有了OptionalInt东西,例如,为以下事物建模:
OptionalInt result =
IntStream.of(1, 2, 3)
.filter(i -> i > 3)
.findFirst();
// Agressive programming ahead
result.orElse(OR_ELSE);
可选的是单子
Google似乎对Monad是什么也有些困惑…… pic.twitter.com/eJp9jY9cwG
— Mario Fusco(@mariofusco) 2013年10月13日
是。 它允许您将flatMap()的值缺失。
当然,如果您想进行复杂的函数式编程,则将开始在各处键入map()和flatMap() 。 像今天一样,当我们键入getter和setter时。 随之而来的是lombok生成平面映射调用,而Spring将添加一些@AliasFor样式标注以进行平面映射。 只有开明的人才能解密您的代码。
在回到日常业务之前,我们只需要一个简单的空安全操作员即可。 喜欢:
String name = bob?.department?.head?.name
我真的很喜欢Kotlin中的这种实用主义。 还是您更喜欢(平面)映射?
Optional<String> name = bob
.flatMap(Person::getDepartment)
.map(Department::getHead)
.flatMap(Person::getName);
你能读懂这个吗? 我不能。 我也不能写这个。 如果您弄错了,您将被Boxoxed。
“ @EmrgencyKittens :盒子里的猫,盒子里的猫。 pic.twitter.com/ta976gqiQs ”而且我认为flatMap
— Channing Walton(@channingwalton) 2014年3月23日
当然, 锡兰语是唯一使null正确的语言 。 但是Ceylon具有Java 42之前无法提供的大量功能,我也不希望有独角兽。 我希望有安全调用运算符(还有Elvis运算符,两者稍有不同),也可以用Java实现。 上面的表达式只是用于以下目的的语法糖:
String name = null;
if (bob != null) {
Department d = bob.department
if (d != null) {
Person h = d.head;
if (h != null)
name = h.name;
}
}
这种简化可能有什么问题?
8.一切都是一种表达
现在,这可能只是一个独角兽。 我不知道是否存在JLS /解析器限制,这将永远使我们陷入语句和表达式之间史前区分的痛苦之中。
在某个时间点上,人们开始对产生副作用的事物使用语句,而对更具功能性的事物使用表达式。 因此,毫不奇怪,所有的String方法都是真正的表达式,对不可变的字符串进行操作,并始终返回新的字符串。
例如,这似乎与Java中的if-else不合适,后者可能包含块和语句,而每个块和语句都可能产生副作用。
但这真的是必要条件吗? 我们也不能用Java编写类似的东西吗?
val max = if (a > b) a else b
好的,我们使用?:有这个奇怪的条件表达式。 但是Kotlin的when (即Java的switch )呢?
val hasPrefix = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
难道不是比以下等效的工具有用吗?
boolean hasPrefix;
if (x instanceof String)
hasPrefix = x.startsWith("prefix");
else
hasPrefix = false;
(是的,我知道?: 。我只是觉得if-else更容易阅读,而且我不明白为什么那应该是一个陈述,而不是一个表达。Heck,在Kotlin中,甚至try是一个表达,而不是一个陈述。 :
val result = try {
count()
} catch (e: ArithmeticException) {
throw IllegalStateException(e)
}
美丽!
9.单表达函数
现在这个。 这将节省大量的时间来阅读和编写简单的粘合代码。 实际上,我们已经在批注中包含了语法。 例如,查看Spring神奇的@AliasFor批注。 它产生:
public @interface AliasFor {
@AliasFor("attribute")
String value() default "";
@AliasFor("value")
String attribute() default "";
}
现在,如果您真的很quin眼,这些只是产生常数值的方法,因为注释只是其实现使用生成的字节码的接口。 我们可以讨论语法。 当然, default这种不规则用法很奇怪,因为默认情况下Java 8中没有重复使用它,但是我想Java总是需要额外的语法,以便开发人员可以更好地感觉自己的打字手指,使他们活着。 没关系。 我们可以忍受。 但是话又说回来,为什么我们必须这样做? 为什么不仅仅收敛于以下内容?
public @interface AliasFor {
String value() = "";
String attribute() = "";
}
和类/接口默认方法也一样吗?
// Stop pretending this isn't an interface
public interface AliasFor {
String value() = "";
String attribute() = "";
}
现在才好看。 但是鉴于Java现有的语法,这可能只是一个独角兽,所以让我们继续...
10.流量敏感型
现在这个 。 这个!
我们之前已经在博客中介绍了总和类型。 自Java 7以来,Java的总和类型有所例外:
try {
...
}
catch (IOException | SQLException e) {
// e can be of type IOException and/or SQLException
// within this scope
}
但是不幸的是,Java没有流敏感类型。 流敏感类型在支持求和类型的语言中至关重要,但在其他方面也很有用。 例如,在Kotlin中:
when (x) {
is String -> println(x.length)
}
显然,我们不需要强制转换,因为我们已经检查了x is String 。 相反,在Java中:
if (x instanceof String)
System.out.println(((String) x).length());
Aaagh,所有这些输入。 IDE自动补全功能非常聪明,足以提供上下文类型的方法,然后为您生成不必要的强制转换。 但是,如果永远不需要这样做,那就很好了,每次我们使用控制流结构显式缩小类型时,它就很棒。
有关更多信息,请参阅有关流量敏感类型的Wikipedia条目 。 可以绝对添加到Java语言中的功能。 毕竟,自Java 8以来,我们已经获得了对流量敏感的最终局部变量。
11.(奖金)声明地点差异
最后但并非最不重要的一点是,通过声明网站的variance获得更好的泛型 。 许多其他语言也知道这一点,例如C#的IEnumerable :
公共接口IEnumerable <out T>:IEnumerable
这里的关键字out表示通用类型T是由IEnumerable类型产生的(与in相对,它代表消费)。 在C#,Scala,Ceylon,Kotlin和许多其他语言中,我们可以在类型声明中声明它,而不是在其用法上声明(尽管许多语言都允许这两种)。 在这种情况下,我们说IEnumerable与它的类型T是协变的,这再次意味着IEnumerable<Integer>是IEnumerable<Object>的子类型。
在Java中,这是不可能的,这就是为什么Java新手在Stack Overflow上有一个不计其数的问题 。 我为什么不能...
Iterable<String> strings = Arrays.asList("abc");
Iterable<Object> objects = strings; // boom
在像Kotlin这样的语言中,以上是可能的。 毕竟,为什么不呢? 可以产生字符串的事物也可以产生对象,我们甚至可以在Java中以这种方式使用它:
Iterable<String> strings = Arrays.asList("abc");
for (Object o : strings) {
// Works!
}
缺少声明站点差异已使许多API变得非常易懂。 考虑Stream :
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
这只是噪音。 从本质上说,一个函数与其参数类型是互变的,而其结果类型是协变的,那么对Function或Stream的更好定义是:
interface Function<in T, out R> {}
interface Stream<out T> {}
如果可能的话,那是全部? super ? super和? extends ? extends垃圾可以删除而不会丢失任何功能。
如果您想知道我在说什么?
解释了协方差和自变量。 资料来源: https: //t.co/2S4ChNeAvq pic.twitter.com/BfOME8puj2
— Lukas Eder(@lukaseder) 2016年1月12日
好消息是,正在为Java的(近)未来版本进行讨论: http : //openjdk.java.net/jeps/8043488
结论
Kotlin是一种很有前途的语言,即使对于似乎已经决定的游戏来说太晚了,也不赞成在JVM上使用其他语言。 但是,这是一种非常有趣的语言,可以学习,并且可以对一些简单的事情做出很多非常好的决定。
这些决定中的一些希望有望被Java语言之神采纳并整合到Java中。 此列表显示了一些可能“容易”添加的功能。
@BrianGoetz @lukaseder设计一种语言有多困难? 这只是您放入解析器生成器中的语法! #getthepopcorn
— AlekseyShipilëv(@shipilev) 2016年3月11日
有关Kotlin习语的更多信息: https : //kotlinlang.org/docs/reference/idioms.html
翻译自: https://www.javacodegeeks.com/2016/04/10-features-wish-java-steal-kotlin-language.html
kotlin和java语言