前言
本文主要介绍一下kotlin中的类种类。
嵌套类和内部类
kotlin的嵌套类和内部类在写法上只有inner关键字的区别,嵌套类直接在类中声明即可,内部类需要加上inner关键字
class Outer{
var x:Int=10;
class Inner{
fun foo(){
//嵌套类不持有外部类引用
// println(x)
}
}
}
class Outer1{
var x:Int=0;
inner class Inner{
fun foo(){
//内部类持有外部类的引用
println(x)
}
}
}
如果是嵌套类是不能持有外部类的引用,如果是内部类则可以持有外部类的引用。这是因为嵌套类是一个静态内部类,他不能持有外部类的引用。
java字节码如下
public final class Outer {
private int x = 10;
public final int getX() {
return this.x;
}
public final void setX(int var1) {
this.x = var1;
}
@Metadata(
mv = {1, 1, 10},
bv = {1, 0, 2},
k = 1,
d1 = {"\u0000\f\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002¨\u0006\u0003"},
d2 = {"Lcom/zhqy/javademo/Outer$Inner;", "", "()V", "production sources for module app"}
)
public static final class Inner {
}
}
数据类
在java中我们往往会将json字符串解析成一个个的java的类来保存数据,这些类称为实体类,kotlin中也为我们准备了类似的类结构,称为数据类。数据类的写法如下
data class Person(var name:String,var age:Int){
var num:Int=0
}
Java字节码
public final class Person {
private int num;
@NotNull
private String name;
private int age;
public final int getNum() {
return this.num;
}
public final void setNum(int value) {
this.num = value;
}
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name = var1;
}
public final int getAge() {
return this.age;
}
public final void setAge(int var1) {
this.age = var1;
}
public Person(@NotNull String name, int age) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
this.name = name;
this.age = age;
}
@NotNull
public final String component1() {
return this.name;
}
public final int component2() {
return this.age;
}
@NotNull
public final Person copy(@NotNull String name, int age) {
Intrinsics.checkParameterIsNotNull(name, "name");
return new Person(name, age);
}
// $FF: synthetic method
// $FF: bridge method
@NotNull
public static Person copy$default(Person var0, String var1, int var2, int var3, Object var4) {
if((var3 & 1) != 0) {
var1 = var0.name;
}
if((var3 & 2) != 0) {
var2 = var0.age;
}
return var0.copy(var1, var2);
}
public String toString() {
return "Person(name=" + this.name + ", age=" + this.age + ")";
}
public int hashCode() {
return (this.name != null?this.name.hashCode():0) * 31 + this.age;
}
public boolean equals(Object var1) {
if(this != var1) {
if(var1 instanceof Person) {
Person var2 = (Person)var1;
if(Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age) {
return true;
}
}
return false;
} else {
return true;
}
}
}
从字节码中我们可以看到数据类为我们自动生成了很多函数和成员变量的get和set方法,当然我们也可以重写其中的函数和getset方法
data class Person(var name:String,var age:Int){
var num:Int=0
set(value) {
field=value
}
get(){
return field
}
override fun toString(): String {
return ""
}
}
数据类特点
(1)自动生成get/set方法
(2)自动创建component1-N(用于析构)
(3)自动创建toString/equals/hashCode
自动创建函数规则
(1)如果父类有toString/equals/hashCode方法且用final进行了修饰,子类则使用父类的相应方法
(2)如果父类正确实现了component,且使用open关键字进行修饰则子类可以重写该方法,否则报错
(3)不允许在类体中实现component和copy函数
Object
kotin中使用object替代Java中的匿名内部类来实现一个轻量级的对象。代码如下
interface A{
fun A()
}
interface B{
fun B()
}
val value: Any = object : A, B {
override fun B() {
println("B执行了")
}
override fun A() {
println("A执行了")
}
}
可以看到kotlin中的object可以实现或继承对个接口或类
object充当匿名对象
object作为匿名对象只能作用于局部变量或私有声明
//局部变量
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val value= object : A, B {
override fun B() {
Log.e("B","执行了")
}
override fun A() {
Log.e("A","执行了")
}
}
value.A()
value.B()
}
class C{
//类中私有声明
private fun foo()=object {
var x:Int=0;
}
fun printX(){
println("${foo().x}")
}
}
object可以修改闭包范围内的的变量
Java在匿名内部类中对外部变量需要用final进行修饰,并不能改变当前外部变量。而在kotlin中可以在object中对外部变量进行修改。
class MainActivity : AppCompatActivity() {
open var x:Int=0;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var value=object:A{
override fun foo() {
x++
x++
}
}
value.foo()
Log.e("X","$x");
}
interface A{
fun foo()
}
}
object充当单例类
在kotlin中使用object修饰的类充当单例类是一个非常不错的方式
class Car{
}
object CarFactory{
fun create():Car{
return Car()
}
}
字节码如下
public final class CarFactory {
public static final CarFactory INSTANCE;
@NotNull
public final Car create() {
return new Car();
}
static {
CarFactory var0 = new CarFactory();
INSTANCE = var0;
}
}
可以看到在便以为Java字节码后,生成了一个变量INSTANCE,该变量在静态代码块中被赋值,且类的构造方法被private修饰,这意味着该类只有唯一的对象,该对象被INSTANCE引用。使用object充当单例类是线程安全的
需要注意的是如果想要在java使用该单例类需要这样使用
CarFactory.INSTANCE.create()
或者在方法上打上 @JvmStatic,告知JVM虚拟机该函数是一个静态函数
伴随对象
伴随对象是一个特殊的对象类型,用于处理类似静态方法的问题
class Car{
companion object {
fun create():Car{
Log.e("Car","伴随对象中的函数执行了")
return Car()
}
}
}
//调用函数,类似于调用静态方法
Car.create()
测试结果
11-04 11:57:16.343 25050-25050/com.zhqy.javademo E/Car: 伴随对象中的函数执行了
Java字节码
public final class Car {
public static final Car.Companion Companion = new Car.Companion((DefaultConstructorMarker)null);
@Metadata(
mv = {1, 1, 10},
bv = {1, 0, 2},
k = 1,
d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0000\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004¨\u0006\u0005"},
d2 = {"Lcom/zhqy/javademo/Car$Companion;", "", "()V", "create", "Lcom/zhqy/javademo/Car;", "production sources for module app"}
)
public static final class Companion {
@NotNull
public final Car create() {
Log.e("Car", "伴随对象中的函数执行了");
return new Car();
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
从Java字节码中可以看出,伴随对象被解释成名为Companion内部静态类,如果Java代码需要调用的话,需要这样使用
Car.Companion.create
或者在伴随对象的方法上添加 @JvmStatic注解
class Car{
companion object {
@JvmStatic
fun create():Car{
Log.e("Car","伴随对象中的函数执行了")
return Car()
}
}
}
中缀调用(infix)
中缀表达式调用可以省略点和括号,通过空格的方式实现方法调用。中缀表达式是一种较为特殊的方法调用方式,需要满足以下几个条件:
(1)必须是成员函数或者是扩展函数
(2)必须仅有一个参数
(3)不能使用可变参数,且参数不能有默认值
class MainActivity : AppCompatActivity() {
open var x:Int=0;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val car=Car()
//省略点以空格代替
car foo(x)
}
class Car{
infix fun foo(x:Int){
Log.e("foo","$x")
}
}
}
测试结果如下
11-04 15:21:09.038 14872-14872/com.zhqy.javademo E/foo: 0
枚举
枚举主要是声明安全类型
class GPS{
var direction:Direction=Direction.WEST
fun gps(){
if (direction==Direction.WEST){
Log.e("direction","West")
}
}
}
enum class Direction{
EAST,WEST,SOUTH,NORTH
}
并且可以通过如下的方式获取枚举变量的名称和位置
Log.e("Enum", enumValues<Direction>().joinToString { "${it.name}:${it.ordinal}" });
打印结果如下
11-04 15:31:30.599 16041-16041/com.zhqy.javademo E/Enum: EAST:0, WEST:1, SOUTH:2, NORTH:3
其中name代表枚举常量名称,ordinal代表枚举常量位置
枚举类允许有抽象方法和实现接口
抽象继承类
/**
* 抽象枚举类
*/
enum class AbstractEnumClass {
MON {
override val abstractParam: String
get() = "星期一"
override fun abstractFun() {
println(abstractParam)
}
},
TUES {
override val abstractParam: String
get() = "星期二"
override fun abstractFun() {
println(abstractParam)
}
},
WED {
override val abstractParam: String
get() = "星期三"
override fun abstractFun() {
println(abstractParam)
}
};
abstract val abstractParam: String
abstract fun abstractFun()
}
fun main(args: Array<String>) {
AbstractEnumClass.MON.abstractFun()//调用枚举对象实现的抽象方法 结果:星期一
println(AbstractEnumClass.MON.abstractParam)//调用枚举对象实现的抽象属性 结果:星期一
}
实现接口
/**
* 定义一个枚举类
* 定义枚举类接口,接口给只读属性复制
*/
enum class EnumClass(val enumParam: String) : EnumInterface {
MON("星期一"),
TUES("星期二"),
WED("星期三");//逗号隔开,分号结尾
override fun interfaceFun() {
println("统一实现的接口的抽象方法")
}
override val interfaceParam: String
get() = "统一实现的抽象属性"
}
fun main(args: Array<String>) {
EnumClass.MON.interfaceFun()//调用枚举对象实现的抽象方法 结果:统一实现的接口的抽象方法
println(EnumClass.MON.interfaceParam)//调用枚举对象实现的抽象属性 结果:统一实现的抽象属性
}
interface EnumInterface {
fun interfaceFun()
val interfaceParam: String
}
密封类(sealed)
密封类用来表示受限的类继承结构;当一个值为有限几种的类型,而不能有任何其他类型时,在某种意义上,他们是枚举类的扩展,枚举类中每个枚举常量只存在一个实例,而密封类的一个子类可能有包含状态的多个实例。
sealed不能修饰interface,abstract class
sealed class SealedExpr{
data class Person(val num1 : Int, val num2 : Int) : SealedExpr()
object Add : SealedExpr() // 单例模式
object Minus : SealedExpr() // 单例模式
}
// 其子类可以定在密封类外部,但是必须在同一文件中 v1.1之前只能定义在密封类内部
object NotANumber : SealedExpr()
注意即所定义的子类都必须继承于密封类,表示一组受限的类
var person1=sealedClass.Person("张三",10)
var person2=sealedClass.Person("李四",11)
sealed类使用规范
(1)不允许直接实例化,本身是抽象类,允许有抽象方法
(2)不允许有非私有的构造方法
(3)sealed类及其子类必选在同一个文件中声明
(4)sealed的子类以及派生类可以不再同一个文件中
sealed类的好处:
代码保护
在使用when表达式 的时候,如果能够验证语句覆盖了所有情况,就不需要为该语句再添加一个else子句了。
sealed class SealedExpr{
data class Person(val name : String, val age : Int) : SealedExpr()
object Add : SealedExpr()
companion object Minus : SealedExpr()
}
object NotANumber : SealedExpr()
fun eval(expr: SealedExpr) = when(expr){
is SealedExpr.Add -> println("is Add")
is SealedExpr.Minus -> println("is Minus")
is SealedExpr.Person -> println(SealedExpr.Person("Koltin",22))
NotANumber -> Double.NaN
}
以上就是文本的全部内容