kotlin 不支持void可以有形参吗了吗

基于Spring Boot和Kotlin的联合开发
版权声明:本文为博主chszs的原创文章,未获得博主授权均不能转载,否则视为侵权。一、概述Spring官方最近宣布,将在Spring Framework 5.0版本中正式支持Kotlin语言。这意味着Spring Boot 2.x版本将为Kotlin提供一流的支持。这并不会令人意外,因为Pivotal团队以广泛接纳JVM语言(如Scala和Groovy)而闻名。下面我们用Spring Boot 2.x和Kotlin应用程序。二、搭建环境1、环境IntelliJ和Eclipse都对Kotlin提供了支持,可以根据自己的喜好搭建Kotlin开发环境。2、构建应用首先创建一个Spring Boot 2项目,然后修改POM配置,让项目保护指定的Java版本和Kotlin版本。依赖关系如下:org.jetbrains.kotlinkotlin-stdlib-jre81.1.2org.jetbrains.kotlinkotlin-reflect1.1.2com.fasterxml.jackson.modulejackson-module-kotlin1.1.2注意,我们正在为Kotlin源码文件和测试文件指定文件位置:${project.basedir}/src/main/kotlin${project.basedir}/src/test/kotlin要编译Kotlin模块和源码,需要使用kotlin-maven-plugin插件:1.1.21.8org.jetbrains.kotlinkotlin-maven-allopen1.1.2到此为止,构建Kotlin应用程序所需的一切就搭建好了。注意,可以去Maven中央仓库寻找以下组件的最新版本:spring-boot-starter-web、kotlin-stdlib-jre8、kotlin-reflect、jackson-module-kotlin、spring-boot-starter-test。下面设置应用程序的上下文。3、应用程序上下文@SpringBootApplication class KotlinDemoApplication fun main(args: Array) { SpringApplication.run(KotlinDemoApplication::class.java, *args) } 可以看到熟悉的@SpringBootApplication注解。我们有一个类定义了KotlinDemoApplication类。在Kotlin中,类的默认范围是public,所以可以省略。另外,如果一个类没有变量、没有函数,它可以被声明为没有大括号。所以,从本质上讲,我们只是定义了一个类。另外,方法或函数默认是公开的,所以不必在这里声明。另外,不返回任何内容的函数不需要指定一个void返回类型。最后,在一个类的外部定义的任何函数都是自动静态的。这使得这些函数可以在启动时得到执行。现在让我们从根目录运行我们的应用程序,使用mvn spring-boot: run。应用程序得以启动,应该可以看到应用程序在端口8080上运行。接下来,构建一个控制器。4、控制器现在添加一个控制器到服务中:@RestController class Controller { @("/hello") fun helloKotlin: String { return "" } } 与标准的Spring控制器没有太大的不同,但是肯定代码量更精简。我们为此控制器添加一个测试类和案例来验证我们的工作:@RunWith(SpringRunner::class) @SpringBootTest(classes = arrayOf(KotlinDemoApplication::class), webEnvironment = SpringTest.WebEnvironment.RANDOM_PORT) class KotlinDemoApplicationTests { @Autowired lateinit var testRestTemplate: TestRestTemplate @Test fun whenCalled_shouldReturnHello {
result = testRestTemplate // ... .getForEntity("/hello", String::class.java) (result) (result?.statusCode, HttpStatus.OK) assertEquals(result?.body, "") } } 这个测试显示了Kotlin强大的功能之一——null安全!可以为null的Kotlin变量必须使用“?”声明。然后,编译器知道在访问该属性之前需要进行防御性编码。在我们的测试中,TestRestTemplate被定义为可空类型,每次访问它时,我们使用null合并运算符“?”来实现——如果被调用对象为空,则返回null。这声明了在程序中使用null,并强制开发人员在使用它们时编写安全的代码。接下来,我们添加一个服务并将其集成到我们的控制器中。5、服务服务很容易添加到我们的项目中。这样做:@Service class HelloService { fun getHello: String { return "hello service" } } 这里的简单服务与单个函数返回一个String。接下来,让我们将服务连接到控制器中并使用它来返回值:@RestController class HelloController(val helloService: HelloService) { // ... @GetMapping("/hello-service") fun helloKotlinService: String { return helloService.getHello } } 啊,看起来不错!在Kotlin中,主构造函数可以与类声明一起定义。我们从构造函数中省略了@Autowired注释,因为它不是一段时间的强制性的。这些参数将自动转换为类中的字段。Kotlin称它们为属性。无需定义getter或setter方法,因为它们是自动创建的。当然,如果需要,您可以覆盖这些默认值。在Kotlin中,函数中的类和变量的属性可以使用var或val来定义。var表示可变属性,val表示final属性。这允许编译器检查非法访问。由于HelloService是一个单例,所以我们把它连接成一个val来防止突变。接下来,我们为此控制器方法添加一个测试:@Test fun whenCalled_shouldReturnHelloService { var result = testRestTemplate // ... .getForEntity("/hello-service", String::class.java) assertNotNull(result) assertEquals(result?.statusCode, HttpStatus.OK) assertEquals(result?.body, "hello service") } 最后,我们来看看一个POJO在Kotlin中的样子。6、Kotlin的数据类在Java中,我们使用POJO来表示数据对象。在Kotlin中,可以更简洁地表达这种类型的对象——一个数据类。我们写一个数据对象返回到控制器中:data class (val greeting: String) 这里没有什么窍门,自动省略。使用data修饰符,可以获得很多好处。此关键字会自动创建一个equals方法和hashcode方法,以及toString方法和copy方法。所有这些方法一个修饰符就搞定了。现在我们来添加一个返回新数据类的方法:// ... @("/hello-dto") fun helloDto: HelloDto { return HelloDto("Hello from the dto") } 数据修饰符不添加默认构造函数,这对于像Jackson这样的库很重要。为了支持这种类型的类,我们将jackson-module-kotlin添加到我们的POM文件中以支持编组。最后,我们添加一个这个控制器功能的测试:@Test fun whenCalled_shoudlReturnJSON {
result = testRestTemplate // ... .getForEntity("/hello-dto", HelloDto::class.java) (result) assertEquals(result?.statusCode, HttpStatus.OK) (result?.body, (" from the dto")) } 三、结论在本文中,结合Spring Boot 2.x和Kotlin语言,我们完成了一个Demo应用。从示例中可以看到,Kotlin可以通过强制来精简代码,保证更安全的代码来简化和增强我们的应用程序。Kotlin还支持一些惊人的功能,如数据类、类扩展,并与现有的Java代码完全兼容。这意味着开发者可以编写Kotlin代码,并从Java类中调用它,反之亦然。此外,Kotlin是从一开始就建立起来的,在IDE中能得到非常好的支持。Google和Spring都开始支持Kotlin语言,或许使用Kotlin的时候到了。
(来源:,如对本网转载内容、版权有异议,请联系我们: )
安卓网官方微信
微信号 Hiapk2013
这里有最无节操的小编和最鲜辣的资讯!
手机扫描二维码访问Kotlin-45.Java调用kotlin之三(Call Kotlin from Java) - lioil.win - CSDN博客
Kotlin-45.Java调用kotlin之三(Call Kotlin from Java)
8.@JvmName解决java方法签名相同(Handling signature clashes)
最突出的例子是由于类型擦除(type erasure)引发:
// 类型擦除: 无法区分List&String&和List&Int&
fun List&String&.filterValid(): List&String&
fun List&Int&.filterValid(): List&Int&
这两个函数在java中不能同时定义,因为它们的JVM签名相同: filterValid(Ljava/util/L)Ljava/util/List
在Kotlin中用相同名称,需要用@JvmName标注其中的一个(或两个),并指定不同名称:
fun List&String&.filterValid(): List&String&
@JvmName("filterValidInt")
fun List&Int&.filterValid(): List&Int&
在Kotlin中,可以用相同名称filterValid()访问;
在Java中,需要分别用filterValid()和filterValidInt()访问
同样注解@JvmName也适用于属性x和函数getX():
val x: Int
@JvmName("getX_prop")
get() = 15
fun getX() = 10
在Java中,需要分别用getX_prop()和getX()访问
9.Java方法重载(Overloads Generation)
如果Kotlin函数的参数有默认值并且使用@JvmOverloads注解,
那么在Java中多个重载方法, 示例如下:
@JvmOverloads fun f(a: String, b: Int = 0, c: String = "abc") {
// kotlin函数的每一个有默认值的参数,都会重载生成一个额外java方法:
void f(String a, int b, String c) {
void f(String a, int b) {
void f(String a) {
该注解也适用于构造函数和静态方法, 但不能用于抽象方法(包括接口的方法)
对于次构造函数(Secondary Constructors), 如果所有参数都有默认值,
那么会生成一个公有public的无参构造函数(没有@JvmOverloads注解也会生效)!
10.受检异常(Checked Exception)
从前几章《kotlin-33.异常(Exception)》可知,Kotlin没有受检异常!
所以Kotlin函数签名不会声明抛出异常(throws Exception),例如:
// kotlin (example.kt), 抛出异常
package demo
fun foo() {
throw IOException()
// kotlin编译生成的Java方法
public void foo() { // 错误: foo()没有声明throws IOException
throw IOException()
因为由kotlin函数生成的Java方法foo()没有声明throws IOException, 所以Java编译器报错!
为了解决这个问题,需要在Kotlin中使用@Throws注解(相当于在Java中声明throws IOException)
@Throws(IOException::class)
fun foo() {
throw IOException()
// kotlin编译生成的Java方法
public void foo() throws IOException {
throw IOException()
11.型变泛型(Variant generics)
当Kotlin类使用声明处型变(declaration-site variance)时,在Java中有两种用法!
class Box&out T&(val value: T)
interface Base
class Derived : Base
fun boxDerived(value: Derived): Box&Derived&{
fun unboxBase(box: Box&Base&): Base {
unboxBase(boxDerived("s")) // 正确: 在Java中Box&Base&泛型是不型变的!
// 将上述kotlin函数转换成Java方法
Box&Derived& boxDerived(Derived value) {
Base unboxBase(Box&Base& box) {
unboxBase(boxDerived("s")) // 错误: 因为在Java中Box&Base&泛型是不型变的!
//使用Java通配符类型&? extends Base&模拟kotlin声明处型变
Base unboxBase(Box&? extends Base& box) {
unboxBase(boxDerived("s")) // 正确
此外, 对于协变定义的Box(在kotlin中Box&in T&), 在java中使用Box&? extends Super&
注意:当参数类型是final时,通配符? extends没有意义,
例如在Box&String&中的String类是final,没有子类(不能被继承extends)
如果在默认没有通配符处要求java泛型通配符, 可以使用@JvmWildcard注解:
// kotlin函数
fun boxDerived(value: Derived): Box&@JvmWildcard Derived& {
// 转换成java方法
Box&? extends Derived& boxDerived(Derived value) {
相反,如果不需要泛型通配符,可以使用@JvmSuppressWildcards注解;
@JvmSuppressWildcards不仅可用于单个类型参数,还可用于整个声明(如函数或类),从而抑制其中的所有通配符!
// kotlin函数
fun unboxBase(box: Box&@JvmSuppressWildcards Base&): Base {
// 转换成java方法
Base unboxBase(Box&Base& box) {
12.Nothing类型翻译(Translation of type Nothing)
类型Nothing是Kotlin特有的,在Java中没有对应类型!
每个Java引用类型(包括java.lang.Void)都接受null, 但是kotlin的Nothing不行!
Nothing类型不能在Java世界中准确表示,所以Nothing类型在java中会消失(原始类型raw type):
// kotlin的Nothing类型
fun emptyList(): List&Nothing& = listOf()
// 翻译成转换成java方法, List&Nothing&变成原始类型List, Nothing类型消失了
List emptyList() {
GitHub博客:
Coding博客:
我的热门文章Kotlin语法(十二)-泛型(Generics)
跟一样,Kotlin也支持泛型类:
classBox(t: T) {
var value = t
在具体使用的时候,需要传入具体的类型:
valbox: Box = Box(1)
另:通过值可以推断出类型,也可以省略类型参数:
// 1 has type Int, so the compiler figures out that we are talking about Box
val box = Box(1)
变异(Variance)
Java通配符(wildcard types)
该部分都是讲的Java泛型中的通配符。
在java泛型使用中,提供类型通配符&?&,这块只是简单介绍java通配符的设计目的及基础,详细可以自行去了解下&Java 泛型通配符?&,如:
voidparseList(ListdataList) {
《》中解析,使用通配符为了提高API的使用灵活性(Use bounded wildcards to increase APIflexibility)。
因为在java中,泛型类型是不可变的,比如:&List&不是&List&的子类型,而是两个独立的类型,如下:
Liststrs = new ArrayList();
Listobjs = //编译错误,类型不匹配(imcompatible type)
假设前面的方式可行的话,会带来更多的新问题,导致在使用时类型不匹配问题:
objs.add(1); // Here we put an Integer into a list of Strings
String s = strs.get(0); // !!! ClassCastException: Cannot cast Integer to String
所以,为了确保在运行时类型安全,Java的泛型设计成了不可变。
但是这种不可变的泛型类型,又会带来下面的问题:
现在定义一个泛型,是为&Collection&增加&addAll()&方法,应该是下面实现方式:
interfaceCollection ... {
void addAll(Collection items);
那么在使用的时候,往&Collection&中添加&Collection&,该方法就没法使用了;而理论上应该是合法的,String是Object的子类,String实例是可以添加到Object的集合中的:
voidcopyAll(Collectionto, Collection from) {
to.addAll(from); // !!! Would not compilewith the naive declaration of addAll:
//Collection is not a subtype ofCollection
为了解决上面的问题,Java中使用了类型通配符方式,如&? extends T&表示T 及T的子类参数都可以使用,实现如下:
interfaceCollection ... {
void addAll(Collectionitems);
通配符的上界
通配符类型参数(wildcard type argument):&? extends T&(T表示通配符的上界),表示该方法可以接收T及T的子类参数。意味着可以安全的读&T& (所有的实例都是T的子类)的实例;但不能向其添加元素,因为没法确定添加的实例类型跟定义的类型是否匹配,无法检验这个操作的安全性:
//Bird Cat都是Animal的子类
public voidtestAdd(Listlist) {
    //下面都会报编译错误的
//因为testAdd方法传入的list的类型不确定的,就没法确保它可以添加下面的全部类型
//若传入&List&,只能add(bird),只有②可以添加
//若传入&List&,都不能添加
list.add(new Animal(&animal&)); //①
list.add(new Bird(&bird&));  //②
list.add(new Cat(&cat&));//③
可以理解成:&Collection&是&Collection&的子类型。
这种通配符(wildcard)是通过继承一个范围类(通配符上界,upper bound)来实现类型协变。
通配符的下界
通配符类型参数(wildcard type argument):&? super T&(T表示通配符的下界)。
如:&Collection&是&Collection&的父类型;可以调用集合将String作为参数的方法(如:add(String)orset(int, String));但当从集合中获取元素时,得到是Objec对象而不是String对象。
PECS stands for Producer-Extends,Consumer-Super
1) 通配符的上界方式,只能从中读取元素,不能添加元素,形象的称为生产者(Producers)
2) 通配符的下界方式,只能添加元素,没法直接读取下界类型的元素,形象的称为消费者(Consumers)
Kotlin泛型
Kotlin没有提供相关的类型通配机制,而是通过下面两种方式:
? 声明位置变异(declaration-site variance)
? 类型推测(type projections)
声明位置变异(declaration-site variance)
在Java中,假设有一个泛型类&Source&,只有一个方法,返回参数T,没有使用T作为参数的方法:
interfaceSource {
T nextT();
现在,&Source&实例作为一个&Source&参数应该是类型安全的,因为&Source&类没有消费者方法(consumer-methods,见PECS部分的解析);但是Java编译器不清楚这点,所以判定为非法:
voiddemo(Source strs) {
Sourceobjects = // !!! Not allowed in Java
// Sourceobjs = //valid
在Java中,为解决这种情况,需要声明为&Source&类型;实际上这种方式没什么意义,新实例调用的还是原来实例一样的方法,定义的更复杂类型并没有添加新值。但是java的编译器不知道。
在Kotlin中,使用声明位置变异(declaration-site variance)方式来处理这种问题。
声明位置变异:通过将参数T注解成只能作为返回值而不是作为传入参数;使用&out&关键字标识。
abstract class Source {
abstract fun nextT(): T
//下面的函数编译错误: type parameter T is declared as 'out'
// abstract fun add(value: T)
fun demo(strs: Source) {
val objects: Source = strs // This is OK, since T is an out-parameter
通用规则:当一个类C的参数化类型使用&out&关键字修饰,那么该参数化类型只能作为类C中函数的返回值;&C
&可以作为&C&的父类型返回。
即,类C在参数T上是协变的,T是一个协变类型参数;可以理解C是参数T的生产者(producer)而非消费者(consumer)。
&out&修饰符称为异变注解;当在类型参数的声明位置使用它,称之为声明位置变异(declaration-site variance)。跟Java使用位置变异(use-site variance)对比,Java是在使用位置使类型协变。
有&out&对应,Kotlin还提供一个互补的变异注解:&in&,它使类型参数逆变(contravariant),即修饰的类型参数只能作为一个消费品(consumed)而不能作为一个生产品(produced)。&Comparable&类就是一个很好使用&in&的逆变实例:
abstract class Comparable {
abstract fun compareTo(other: T): Int
fun demo(x: Comparable) {
x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number
// Thus, we can assign x to a variable of type Comparable
val y: Comparable = x // OK!
类型推测(Type projections)
使用位置变异:类型推测
将类型参数T使用&out&修饰,可以非常方便解决使用位置泛型子类问题。但是向下面这种,既有做返回参数又有做函数入参的:
class Array(val size: Int) {
fun get(index: Int): T { /* ... */ }
fun set(index: Int, value: T) { /* ... */ }
该类不能对类型参数T做变异或逆变;考虑到有下面一个函数:
fun copy(from: Array, to: Array) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
该函数将一个数组中的元素复制到另外一个数组,现在来具体使用下:
val ints: Array= arrayOf(1, 2, 3)
val any =Array(3)
copy(ints, any) // Error: expects(Array, Array)
现在出现了前面java部分关于泛型参数传递的问题。
泛型类型参数是不可变的,那么&Array&与&Array&不是子父类关系,故无法将&Array&的实例当做&Array&使用。
只需要确保&copy()&能正常即可;那么需要禁止&from&不能添加,可以这样:
fun copy(from:Array&out Any&, to:Array) {
这种实现方式,称为类型推测(type projection):参数&from&不是一个简单的数组,而是受限制的(限制为一个生产者,projected),它只能调用返回参数为T的方法,意味着只能调用&get()&函数。
就是一种使用位置变异(use-site variance),类似于Java中的&Array&,实现起来较为简单些。
另外与&out&对应的是&in&,用法如下:
fun fill(dest:Array, value: String) {
&Array&类似于Java中的&Array&。
主角推测(Star-projections)
有时候,对类型参数没有任何了解,但又需要安全的使用。一种安全的方式是,定义一个该泛型类型的推测(projection)类型,使泛型类型的每一个具体实例应该是推测(projection)类型的子类型。
在Kotlin中,称之为主角推测(star-projection),语法规则:
? 对于&Foo&,&T&是一个协变类型参数;&TUpper&表示类型参数上界(upper bound);那么&Foo&*&&等同于&Foo&。当无法知道&T&时,可以从&Foo&*&&安全的读取得到&TUpper&。
? 对于&Foo&,&T&是一个逆变类型参数;&Foo&*&&等同于&Foo&;意味着当&T&不可知时,不能往&Foo&*&&添加任何元素。
? 对于&Foo&,&T&是一个不可变的类型参数;&TUpper&表示类型参数上限;当从中读取数据时&Foo&*&&同等于&Foo&;当向其添加数据时,&Foo&*&&等同于&Foo&。
若一个泛型类型,有几个类型参数,每个类型参数都可以单独进行推测(projected)。比如,一个泛型定义&interface Function&,那么可以得到下面的主角推测(Star-projections):
? &Function&*, String&&等同于&Function&
? &Function&等同于&Function&
? &Function&*, *&&等同于&Function&
注:主角推测(Star-projections)跟Java中的原始类型(raw types)非常相似,但是更安全。
泛型函数(Genericfunctions)
不仅类可以有类型参数,函数也可以。下面是一个使用类型参数的函数使用:
singletonList(item: T): List {
T.basicToString() : String { // extension function 扩展函数
要使用一个泛型函数,在函数名称后面指定具体的类型参数:
vall = singletonList(1)
泛型约束条件(Generic constraints)
上界(Upper bounds)
在Kotlin中,泛型约束大部分是类型上界限制,对应于Java中的&extends&,如:
fun & sort(list: List) {
该函数限制只有&Comparable&的子类才能够作为&T&:
sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable
// Error: HashMap is not a subtype of Comparable<hashmap&
sort(listOf(HashMap()))</hashmap
默认的类型上界(未明确定义上界)是&Any?&。在&&&&一次只能定义一个上界,如果相同类型参数需要定义多个上界,可以通过使用&where&关键字来实现:
fun cloneWhenGreater(list: List, threshold: T): List
whereT : Comparable,
T : Cloneable {
return list.filter { it & threshold }.map{ it.clone() }

我要回帖

更多关于 kotlin void 的文章

 

随机推荐