Kotlin data classes, sealed classes, inner classes and nested classes

Kotlin data classes, sealed classes, inner classes and nested classes

Kotlin has several special classes, among which data class and sealed class do not exist in Java, so the following will introduce how to use them, usage scenarios, what problems they solve, and syntactic sugar. The principle behind it. In addition, there are nested classes and inner classes in Kotlin. It should be noted that Kotlin's nested classes are not inner classes. This is different from Java, so inner nested classes cannot access outer class instances.

In Kotlin, the inner class declaration needs to use the inner keyword declaration separately 

1. Inner classes and nested classes

We all know that expressing an inner class in  Java actually means that an inner class is declared by nesting the inner class in an outer class, so that the inner class can access the private members of the outer class .

//PageAdapter
public abstract class PageAdapter {
    public abstract int getCount();

    public abstract String getItem(int position);
}

//PageTest
package com.imooc.test;

import java.util.Arrays;
import java.util.List;

public class PageTest {

    private List<String> mData = Arrays.asList("1", "2", "3");

    class TestPageAdapter extends PageAdapter {//在Java中只需要把内部类TestPageAdapter声明在外部类PageTest内部即可
        @Override
        public int getCount() {
            return mData.size();//内部类即可以访问外部私有成员
        }

        @Override
        public String getItem(int position) {
            return mData.get(position);
        }
    }
}

However, this is not the case in Kotlin. For Kotlin to declare a class inside a class, we call it a nested class . A nested class cannot directly access the private members of the outer class.

package com.imooc.test

class PageTestKt {
    private val mData = listOf<String>("1", "2", "3")

    class TestPageAdapter : PageAdapter() {
        override fun getItem(position: Int): String {
           return mData[position]//由于无法访问mData,所以mData[position]编译报错
        }

        override fun getCount(): Int {
            return mData.size//由于无法访问mData,所以mData.size编译报错
        }

    }

Why the Kotlin nested class can't directly access the private members of the outer class, we can decompile it into Java code at a glance:

public final class PageTestKt {
   private final List mData = CollectionsKt.listOf(new String[]{"1", "2", "3"});

   public static final class TestPageAdapter extends PageAdapter {//可以看到实际上Kotlin嵌套类就是一个static静态类,所以它肯定不能访问外部类PageTestKt私有成员mData
      @NotNull
      public String getItem(int position) {
         return "";
      }

      public int getCount() {
         return 0;
      }
   }
}

It can be seen that the Kotlin nested class is actually a static static class, so it must not be able to access the private member mData of the outer class PageTestKt. What should I do if I want to declare an inner class in Kotlin? It's very simple, just add a inner keyword statement on the basis of the nested class  .

package com.imooc.test

class PageTestKt {
    private val mData = listOf<String>("1", "2", "3")

    inner class TestPageAdapter : PageAdapter() {//inner关键字声明一个Kotlin中的内部类
        override fun getItem(position: Int): String {
            return mData[position]//由于TestPageAdapter是PageTestKt的内部类,那么它就可以直接访问外部类私有属性mData
        }

        override fun getCount(): Int {
            return mData.size
        }

    }
}

In order to further verify that  inner class it actually corresponds to the internal classes in Java, we can decompile the above code into Java code for verification:

public final class PageTestKt {
   private final List mData = CollectionsKt.listOf(new String[]{"1", "2", "3"});

   public final class TestPageAdapter extends PageAdapter {//可以看到TestPageAdapter确实是PageTestKt内部类,所以能直接访问外部类的私有成员mData
      @NotNull
      public String getItem(int position) {
         return (String)PageTestKt.this.mData.get(position);
      }

      public int getCount() {
         return PageTestKt.this.mData.size();
      }
   }
}

To summarize: when declaring a nested class, it is used inside the outer class in Java  static class A , while in Kotlin it only needs to be used inside the outer class  class A ;

When declaring internal, it only needs to be used inside an external class in Java  class A , while it needs to be used inside an external class in Kotlin  inner class A .

Class A is declared inside class B

In java

In Kotlin

Nested class (cannot directly access private properties of outer class)

static class A

class A

Inner class (can directly access private properties of outer class)

class A

inner class A

2. Sealed class

2.1 Why do you need a sealed class

In development, we may often encounter such a scenario, when using an  when expression, we eventually need to return a value of the expression. Because there  when must be a  else branch situation, because the Kotlin compiler will force the check of the default options. So we usually return an invalid value, but sometimes we can't return a suitable value, so we usually throw an exception to deal with. Let's take a look at an example:

interface Expression

class Num(val value: Int): Expression

class Sum(val left: Expression, val right: Expression): Expression

fun eval(e: Expression): Int = when(e) {
    is Num -> e.value
    is Sum -> eval(e.right) + eval(e.left)
    else -> throw IllegalArgumentException("Unknown Expression")//由于Kotlin编译器必须要有else分支,这种不存在分支我们只能抛出异常
}

In fact, the above example is not very elegant. At this time, we can use the sealed class to replace the  Expression interface, so that we can save this  else unnecessary branch, and the compiler will not report an error.

sealed class Expression {//使用sealed关键字声明一个密封类
    class Num(val value: Int) : Expression()//然后把需要的类声明在密封类的内部

    class Sum(val left: Expression, val right: Expression) : Expression()
}

fun eval(e: Expression): Int = when (e) {
    is Expression.Num -> e.value
    is Expression.Sum -> eval(e.right) + eval(e.left)//在when判断的时候就可以省去不必要else判断
}

2.2 How to use sealed class

The sealed class uses  sealed keyword declaration, only need to declare the corresponding class inside the sealed class, and then the inner class inherits the base class.

sealed class Expression {//使用sealed关键字声明一个密封类
    class Num(val value: Int) : Expression()//把需要的类声明在密封类的内部,然后内部类继承基类

    class Sum(val left: Expression, val right: Expression) : Expression()
}

2.3 The principle of the sealed class

The principle of sealed classes is actually quite simple. In fact, it is to declare multiple nested classes in Java inside a private abstract class  static class . You can decompile the above code into Java code for verification:

public abstract class Expression {
   private Expression() {//密封类构造器私有化,防止密封类被外部实例化
   }

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

   public static final class Num extends Expression {//声明成static class静态类,也就是Expression的嵌套类
      private final int value;

      public final int getValue() {
         return this.value;
      }

      public Num(int value) {
         super((DefaultConstructorMarker)null);
         this.value = value;
      }
   }

   public static final class Sum extends Expression {//声明成static class静态类,也就是Expression的嵌套类
      @NotNull
      private final Expression left;
      @NotNull
      private final Expression right;

      @NotNull
      public final Expression getLeft() {
         return this.left;
      }

      @NotNull
      public final Expression getRight() {
         return this.right;
      }

      public Sum(@NotNull Expression left, @NotNull Expression right) {
         Intrinsics.checkParameterIsNotNull(left, "left");
         Intrinsics.checkParameterIsNotNull(right, "right");
         super((DefaultConstructorMarker)null);
         this.left = left;
         this.right = right;
      }
   }
}

3. Data class

3.1 Why do we need data classes

Java development partners believe that we are very familiar with JavaBeans. When we want to define a data model, we need to define getter and setter methods for each property. If you want to compare object values, you even need to rewrite hashcode, equals and other methods. For Kotlin, sometimes you don't need such a verbose syntax template, it will make your implementation easier and more efficient.

import java.util.Objects;

public class Student {
    private String name;
    private int age;
    private double weight;
    private String nickName;
    private String address;

    public Student(String name, int age, double weight, String nickName, String address) {
        this.name = name;
        this.age = age;
        this.weight = weight;
        this.nickName = nickName;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;
        Student student = (Student) o;
        return age == student.age &&
                Double.compare(student.weight, weight) == 0 &&
                name.equals(student.name) &&
                nickName.equals(student.nickName) &&
                address.equals(student.address);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, weight, nickName, address);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", weight=" + weight +
                ", nickName='" + nickName + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

Kotlin uses data classes to implement  Student POJO classes

//你没看错,子啊Java中需要86行代码,在Kotlin中实现仅仅需要一行代码即可,实际上data class数据类编译器背后做了很多处理
data class Student(var name: String, var age: Int, var weight: Double, var nickName: String, var address: String)

3.2 How to use data classes

The use of data classes in Kotlin is very simple, as long as data you add keywords in front of the basis of ordinary class declarations  . Data classes are generally used in Kotlin to describe data models.

data class Student(var name: String, var age: Int, var weight: Double, var nickName: String, var address: String)

3.3 Principle of Data Class

Through comparison, we found that Kotlin only needs one line of code to implement the 86 lines of code in Java. What is the mystery of data classes that can do this? In fact, these are automatically generated by the compiler for us, and we can decompile Look at the data type Kotlin code:

public final class Student {
   @NotNull
   private String name;
   private int age;
   private double weight;
   @NotNull
   private String nickName;
   @NotNull
   private String address;

    //为属性生成了setter和getter方法
   @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 final double getWeight() {
      return this.weight;
   }

   public final void setWeight(double var1) {
      this.weight = var1;
   }

   @NotNull
   public final String getNickName() {
      return this.nickName;
   }

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

   @NotNull
   public final String getAddress() {
      return this.address;
   }

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

    //生成了构造器方法
   public Student(@NotNull String name, int age, double weight, @NotNull String nickName, @NotNull String address) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      Intrinsics.checkParameterIsNotNull(nickName, "nickName");
      Intrinsics.checkParameterIsNotNull(address, "address");
      super();
      this.name = name;
      this.age = age;
      this.weight = weight;
      this.nickName = nickName;
      this.address = address;
   }

    //生成了component 1...N的解构函数,这个下面会详细说明
   @NotNull
   public final String component1() {
      return this.name;
   }

   public final int component2() {
      return this.age;
   }

   public final double component3() {
      return this.weight;
   }

   @NotNull
   public final String component4() {
      return this.nickName;
   }

   @NotNull
   public final String component5() {
      return this.address;
   }

    //生成了copy函数,这个下面会详细说明
   @NotNull
   public final Student copy(@NotNull String name, int age, double weight, @NotNull String nickName, @NotNull String address) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      Intrinsics.checkParameterIsNotNull(nickName, "nickName");
      Intrinsics.checkParameterIsNotNull(address, "address");
      return new Student(name, age, weight, nickName, address);
   }

   // $FF: synthetic method
   public static Student copy$default(Student var0, String var1, int var2, double var3, String var5, String var6, int var7, Object var8) {
      if ((var7 & 1) != 0) {
         var1 = var0.name;
      }

      if ((var7 & 2) != 0) {
         var2 = var0.age;
      }

      if ((var7 & 4) != 0) {
         var3 = var0.weight;
      }

      if ((var7 & 8) != 0) {
         var5 = var0.nickName;
      }

      if ((var7 & 16) != 0) {
         var6 = var0.address;
      }

      return var0.copy(var1, var2, var3, var5, var6);
   }

    //自动生成了toString
   @NotNull
   public String toString() {
      return "Student(name=" + this.name + ", age=" + this.age + ", weight=" + this.weight + ", nickName=" + this.nickName + ", address=" + this.address + ")";
   }

    //自动生成了hashCode方法
   public int hashCode() {
      String var10000 = this.name;
      int var1 = ((var10000 != null ? var10000.hashCode() : 0) * 31 + this.age) * 31;
      long var10001 = Double.doubleToLongBits(this.weight);
      var1 = (var1 + (int)(var10001 ^ var10001 >>> 32)) * 31;
      String var2 = this.nickName;
      var1 = (var1 + (var2 != null ? var2.hashCode() : 0)) * 31;
      var2 = this.address;
      return var1 + (var2 != null ? var2.hashCode() : 0);
   }

     //自动生成了equals方法
   public boolean equals(@Nullable Object var1) {
      if (this != var1) {
         if (var1 instanceof Student) {
            Student var2 = (Student)var1;
            if (Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age && Double.compare(this.weight, var2.weight) == 0 && Intrinsics.areEqual(this.nickName, var2.nickName) && Intrinsics.areEqual(this.address, var2.address)) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}

3.4 copy method

We now focus on analyzing the  copy method

   @NotNull
   public final Student copy(@NotNull String name, int age, double weight, @NotNull String nickName, @NotNull String address) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      Intrinsics.checkParameterIsNotNull(nickName, "nickName");
      Intrinsics.checkParameterIsNotNull(address, "address");
      return new Student(name, age, weight, nickName, address);
   }

   // $FF: synthetic method
   public static Student copy$default(Student var0, String var1, int var2, double var3, String var5, String var6, int var7, Object var8) {//var0表示被copy的对象
      if ((var7 & 1) != 0) {
         var1 = var0.name;//copy时如果未指定具体属性的值,则会使用被copy对象的属性值
      }

      if ((var7 & 2) != 0) {
         var2 = var0.age;
      }

      if ((var7 & 4) != 0) {
         var3 = var0.weight;
      }

      if ((var7 & 8) != 0) {
         var5 = var0.nickName;
      }

      if ((var7 & 16) != 0) {
         var6 = var0.address;
      }

      return var0.copy(var1, var2, var3, var5, var6);
   }

It should be noted that the copy method is mainly to help us copy a new data class object from an existing data class object. Of course, you can also pass in different parameters to generate different objects
, but you can see from the code that if an attribute does not specify a specific attribute value, then the value of the newly generated object will use the attribute value of the object being copied, that is, we Commonly known as shallow copy.

3.5 componentN and object destructuring declaration

Object deconstruction declaration definition

Destructuring declaration is to treat an object as a set of separate variables. Sometimes it becomes easier to treat an object as a set of separate variables. Note: The class of the object that supports the deconstruction declaration must be a data class (class modified with the data keyword), because only the data class will generate the corresponding component () method (this will be explained later), in the data class Each property will have a corresponding component () method.

Use of object deconstruction

//1.定义一个data class
data class Student(var name: String, var age: Int, var weight: Double, var nickName: String, var address: String)

//2. 解构声明调用
fun main() {
    val student = Student("imooc", 18, 88.0, "imooc", "北京市")
    val (name, age, weight, nickName, address) = student//将一个student对象解构成一组5个单独的变量
    println("my name is $name , I'm $age years old, weight is $weight, nickname is $nickName, address is $address")//解构后的5个变量可以脱离对象,直接单独使用
}

Principles of object deconstruction

The destructuring declaration is actually to deconstruct all the properties in the object into a set of property variables, and these variables can be used separately, why can they be used separately, because the acquisition of each property value is finally compiled into the corresponding component ( ) Method, each component () method corresponds to the value of each attribute in the class, and then defines the local variable of each attribute in the scope. These local variables store the value of each corresponding attribute, so it seems that the variable can be used alone. In fact Local variables are used. The following decompiled Java code

public final class StudentKt {
   public static final void main() {
      Student student = new Student("imooc", 18, 88.0D, "imooc", "北京市");
      String name = student.component1();//对应的component1()方法,返回对应就是Student中name属性,并赋值给创建局部变量name
      int age = student.component2();//对应的component2()方法,返回对应就是Student中age属性,并赋值给创建局部变量age
      double weight = student.component3();//对应的component3()方法,返回对应就是Student中weight属性,并赋值给创建局部变量weight
      String nickName = student.component4();//对应的component4()方法,返回对应就是Student中nickName属性,并赋值给创建局部变量nickName
      String address = student.component5();//对应的component5()方法,返回对应就是Student中address属性,并赋值给创建局部变量address
      String var7 = "my name is " + name + " , I'm " + age + " years old, weight is " + weight + ", nickname is " + nickName + ", address is " + address;
      boolean var8 = false;
      System.out.println(var7);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}

4. Summary

Up to this point, several special classes in Kotlin object-oriented are described one by one. Among them, data classes and internal classes are used a little more. The use of sealed classes is not used much in usual development, but through this article, you You should know when to use the sealed class, and hope that you can use it well in the corresponding scenarios, so that the code can definitely reflect the very solid foundation of Kotlin.

Guess you like

Origin blog.csdn.net/PrisonJoker/article/details/114108822