kotlin 总结 - 类

292 阅读7分钟

最近的项目中使用到了kotlin, 所以做一个总结,首先贴出kotlin的官方文档 www.kotlincn.net/docs/refere…

类的声明

kotlin中声明对象和java一样,也是用class关键字声明,只不过在kotlin中更加的简洁, 有如下几种方式

/** 最类似java的 */
class Foo {
  // var 说明是一个具有getter, setter的变量, ? = null这句话是说name的初始默认值为null
  var name:String? = null 
}

/** kotlin的写法 */
class Foo (var name:String){
}
// 其实可以直接简写成下面的形式,省略后面的大括号
class Foo (var name:String) 

kotlin和Java还是有很多的不一样,如果String后面不加?, 编译器会报错, 因为kotlin里面的参数如果没带?号, 是不允许为空的.

肯定会有点陌生, 所以我们先来看一下使用kotlin bytecode反编译后的Java代码

// 下面的代码是使用idea -> Tools -> kotlin 下的反编译工具生成, 这里只截取了主要的信息
public final class Foo {
   @NotNull
   private String name;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name = var1;
   }

   public foo(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.name = name;
   }
}

注意上面的final关键字,下面会解释.

构造函数

kotlin的构造函数是跟在类名和可选参数后, 用关键字constructor声明

class Foo constructor(var name:String){
}

constructor 不是必须的, 如果主构造函数没有任何注解或者可见性修饰符,可以省略它, 上面类的声明中就省略了. 可见性修饰符会在后面提到, 就是private, public, protected,internal(kotlin特有)这些修饰符, 注解的话, 在kotlin和Spring结合的时候会经常用到,

如果构造函数之后的可选参数前面没有var 或者val, 那么这个可选参数不会生成类的属性, 如下

class foo constructor(name:String)
// 生成的java代码如下

public final class foo {
   public foo(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
   }
}

可以看到, 并没有为name生成一个属性, name只是构造函数中的一个形参而已.

注意事项

  1. 类、对象、接口、构造函数、方法、属性和它们的 setter 都可以有 可见性修饰符,在kotlin中默认可见性是 public
  2. kotlin中类默认都是final的, 不能被继承, 需要加上open 关键字使其可以被继承. eg. open class foo (var name:String) (在和Spring结合的时候, 一些代理类无法生成,可能就是这个的原因, 安装对应的插件即可解决)

静态类

内部类

参照Java中的内部类的分类, 来看看 kotlin中的内部类 ###1. 静态内部类

class Foo constructor(name:String){
  class InnerFoo()
}

生成的java代码如下

public final class Foo {
   public Foo(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
   }
   public static final class InnerFoo {
   }
}

记住上面说的, 默认的可见性都是public 需要注意, 因为生成的是静态内部类, 所以它不能访问外部类的成员变量(static的变量还是可以访问到的, 这些都是Java相关知识)

###2. 内部类 如果需要普通的内部类, 则只需要使用inner关键字即可,

class Foo constructor(name: String) {
  private val bar: Int = 1
  inner class InnerFoo() {
  // 可以访问到外部类的成员变量
    fun foo() = bar
  }
}

###3. 匿名内部类 这里是和java区别最大的, 在kotlin中, 需要使用object关键字来创建匿名内部类

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { …… }
    override fun mouseEntered(e: MouseEvent) { …… }
})

里面复写的方法必须使用 override 关键字, 不然编译器会报错

注意事项

注意kotlin中的this关键字, 先记住就近原则, 直接看图(图中代码来源于官网, 但是先去掉了比较复杂的例子,以后再单独记录)

可以看到b,c的值是一样的, 且b, c中都有对A的一个引用

数据类

kotlin中有一个数据类的概念(官方上说数据类型就是一些只保存数据的类), 某种程度上可以认为是PO, 使用起来非常方便, 在class前面加上data关键字即可

data class Foo(var name:String)

里面提供的方法可以参考下面的文档, 这里不细说了 www.kotlincn.net/docs/refere…

值得一提的的是, 数据类都是final的, 因此不可以被继承. 所以还是记住上面的那句话, 数据类只用来保存数据.

枚举类

kotlin中使用enum关键字来声明枚举类

enum class Color{
  red,blue,green
}

如果需要设置枚举类的初始值, 可以使用val关键字 (不能使用var, 因为枚举类的实例不可变)

enum class Color(val rgb: Int) {
        red(0xFF0000),
        green(0x00FF00),
        blue(0x0000FF)
}

kotlin还可以定义枚举常量的匿名类

enum class ProtocolState {
  WAITING {
    override fun signal() = WAITING
  };
  abstract fun signal(): ProtocolState
}

// todo 这里反编译后的java代码比较奇怪, 不知道背后的原因,

public enum ProtocolState {
   WAITING;

   @NotNull
   public abstract ProtocolState signal();

   private ProtocolState() {
   }

   // $FF: synthetic method
   public ProtocolState(DefaultConstructorMarker $constructor_marker) {
      this();
   }
	// 在java中不能继承枚举,所以这里的代码在java编译器中无法通过
   static final class WAITING extends ProtocolState {
      @NotNull
      public ProtocolState signal() {
         return (ProtocolState)this;
      }

      WAITING(String $enum_name_or_ordinal$0, int $enum_name_or_ordinal$1) {
         super((DefaultConstructorMarker)null);
      }
   }
}

密封类

密封类用来表示受限的类继承结构, 什么意思呢, 一句话概括就是是所有子类都必须在与密封类自身相同的文件中声明(密封类的构造函数是私有的)

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

上面的代码必须在同一个kt文件中, 否则编译会报错

object对象, 单例

用object关键字创建的对象, 默认就是final的, 且里面的方法和成员变量都是静态的, 可以认为是一个单例(饿汉式) ###用object创建对象

open class A(x: Int) {
    public open val y: Int = x
}

interface B { /*……*/ }

val ab: A = object : A(1), B {
    override val y = 15
}

上面代码用object创建了一个对象(java中的匿名对象), 它继承了A和接口B

伴生对象

在java中,我们想创建一个对象,里面既有静态属性,也有普通的成员属性,这个时候就可以用伴生对象

class Foo {
  val name: String? = null
  // 用companion关键字声明
  companion object {
    // 使用val的时候,stat是private的,背后会生成一个get方法
    val stat = ""
    // 使用const关键字的时候,不会生成get方法, 查看反编译后的代码可以看到constName是public的
    const val constName: String = "hhhhh"
  }
}

const的效果和直接在属性前面加上@JvmField效果是一样的 反编译后的代码

public final class Foo {
   @Nullable
   private final String name;
   @NotNull
   private static final String stat = "";
   @NotNull
   // const修饰的变量和java一样
   public static final String constName = "hhhhh";
   public static final Foo.Companion Companion = new Foo.Companion((DefaultConstructorMarker)null);

   @Nullable
   public final String getName() {
      return this.name;
   }

   public static final class Companion {
      // 这个方法就是生成的get
      @NotNull
      public final String getStat() {
         return Foo.stat;
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

##内联类

有时候,业务逻辑需要围绕某种类型创建包装器。然而,由于额外的堆内存分配问题,它会引入运行时的性能开销。此外,如果被包装的类型是原生类型,性能的损失是很糟糕的,因为原生类型通常在运行时就进行了大量优化,然而他们的包装器却没有得到任何特殊的处理。

内联类是为了解决上面的问题, 因此内联类具有如下的限制

  1. 内联类不能含有 init 代码块
  2. 内联类不能含有幕后字段
  3. 只能含有简单的计算属性(不能含有延迟初始化/委托属性)

可以参考 Kotlin中内联类

类型别名

可以为类型取一个别名, 让代码更加简洁

typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K, MutableList<File>>

//为函数类型提供另外的别名:
typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate<T> = (T) -> Boolean

//为内部类和嵌套类创建新名称
class A {
    inner class Inner
}
typealias AInner = A.Inner

类型别名不会引入新类型。 它们等效于相应的底层类型,生成后的代码和引入别名前没有什么区别

类中的属性

  1. kotlin 类中的属性默认都是private的

  2. var 表示的是可以被修改的属性(既可以被setter和getter), val表示不可改的(在java中用final)的属性

  3. 在var之前可以使用可见性修饰符进行修饰

  • private 意味着只在这个类内部(包含其所有成员)可见;
    • protected—— 和 private一样 + 在子类中可见。
    • internal —— 本模块内 的任何客户端都可见其 internal 成员;
    • public —— 能见到类声明的任何客户端都可见其 public 成员。 下面的代码说明name属性只能在当前类中被访问,哪怕是同一个kt文件下的不同类也不能访问 class foo constructor(private var name:String)