我希望Java可以从Kotlin语言中窃取的10个功能

本文已过期。 在围绕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表达式功能强大whenKotlin对此的解释 。 您可以混合使用任何种类的谓词表达式,类似于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()方法的任何库一起使用。

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

同样,这是一个有争议的话题。 例如,因为

虽然给出了虚拟的假象,但扩展功能实际上只是加糖的静态方法。 进行面向对象的应用程序设计的技巧是很大的风险,这就是为什么此功能可能不会将其纳入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);

可选的是单子

是。 它允许您将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。

当然, 锡兰语是唯一使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 "";
}

现在,如果您真的很眼,这些只是产生常数值的方法,因为注释只是其实现使用生成的字节码的接口。 我们可以讨论语法。 当然, 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);

这只是噪音。 从本质上说,一个函数与其参数类型是互变的,而其结果类型协变的,那么对FunctionStream的更好定义是:

interface Function<in T, out R> {}
interface Stream<out T> {}

如果可能的话,那是全部? super ? super? extends ? extends垃圾可以删除而不会丢失任何功能。

如果您想知道我在说什么?

结论

Kotlin是一种很有前途的语言,即使对于似乎已经决定的游戏来说太晚了,也不赞成在JVM上使用其他语言。 但是,这是一种非常有趣的语言,可以学习,并且可以对一些简单的事情做出很多非常好的决定。

这些决定中的一些希望有望被Java语言之神采纳并集成到Java中。 此列表显示了一些可能“容易”添加的功能。

翻译自: https://www.javacodegeeks.com/2016/04/10-features-wish-java-steal-kotlin-language.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值