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

2020-12-14 15:15:47 浏览数 (1)

参考链接: 有关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语言

0 人点赞