从零开始的Kotlin:高级属性和类

Kotlin是一种现代编程语言,可编译为Java字节码。 它是免费的, 开放 源码 ,并承诺使编码为Android更有趣。

上一篇文章中 ,您了解了Kotlin中的类和对象。 在本教程中,我们将继续学习有关属性的更多信息,并通过探索以下内容来研究Kotlin中高级的类类型:

  • 后期初始化的属性
  • 内联属性
  • 扩展属性
  • 数据,枚举,嵌套和密封类

1.后期初始化属性

我们可以在Kotlin中将非null属性声明为late-initialized 。 这意味着非null属性不会在声明时使用值初始化-实际的初始化不会通过任何构造函数发生-而是会通过方法或依赖项注入来后期初始化。

让我们看一个例子来了解这个独特的属性修饰符。

class Presenter {
    private var repository: Repository? = null
    
    fun initRepository(repo: Repository): Unit {
        this.repository = repo
    }
}

class Repository {
    fun saveAmount(amount: Double) {}
}

在上面的代码中,我们在Presenter类内部声明了一个可变的可为null的repository属性,该属性为Repository类型,然后在声明过程中将此属性初始化为null。 Presenter类中有一个方法initRepository() ,稍后使用实际的Repository实例重新初始化此属性。 请注意,也可以使用依赖注入器(例如Dagger)为该属性分配值。

现在,要在此repository属性上调用方法或属性,我们必须执行空检查或使用安全调用运算符。 为什么? 因为repository属性是可空类型( Repository? )。 (如果您需要对Kotlin中的可空性进行复习,请访问Nullability,Loops和Conditions )。

// Inside Presenter class
fun save(amount: Double) {
    repository?.saveAmount(amount)
}

为避免每次需要调用属性的方法时都必须执行null检查,我们可以使用lateinit修饰符标记该属性-这意味着我们已将该属性(作为另一个类的实例)声明为延迟初始化 (即属性将在以后初始化)。

class Presenter {

    private lateinit var repository: Repository
    //...
}

现在,只要我们等到为属性赋值,就可以安全地访问属性的方法而无需执行任何空检查。 属性初始化可以通过setter方法或依赖注入进行。

repository.saveAmount(amount)

请注意,如果尝试在属性初始化之前访问属性的方法,则将获得kotlin.UninitializedPropertyAccessException而不是NullPointerException 。 在这种情况下,异常消息将为“ lateinit属性存储库尚未初始化”。

还要注意在延迟使用lateinit初始化属性时设置的以下限制:

  • 它必须是可变的(用var声明)。
  • 属性类型不能是原始类型,例如IntDoubleFloat等等。
  • 该属性不能具有自定义getter或setter。

2.内联属性

在“ 高级函数”中 ,我介绍了用于高阶函数的inline修饰符-这有助于优化将lambda作为参数的任何高阶函数。

在Kotlin中,我们还可以在属性上使用此inline修饰符。 使用此修饰符将优化对属性的访问。

让我们看一个实际的例子。

class Student {
    val nickName: String
        get() {
            println("Nick name retrieved")
            return "koloCoder"
        }
}


fun main(args: Array<String>) {
    val student = Student()
    print(student.nickName)
}

在上面的代码中,我们有一个普通属性nickName ,它没有inline修饰符。 如果我们使用Show Kotlin Bytecode功能反编译代码段(如果您在IntelliJ IDEA或Android Studio中,请使用Tools > Kotlin > Show Kotlin Bytecode ),我们将看到以下Java代码:

public final class Student {
   @NotNull
   public final String getNickName() {
      String var1 = "Nick name retrieved";
      System.out.println(var1);
      return "koloCoder";
   }
}

public final class InlineFunctionKt {
   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      Student student = new Student();
      String var2 = student.getNickName();
      System.out.print(var2);
   }
}

在上面生成的Java代码中(为了简洁起见,删除了部分代码),您可以看到在main()方法内部,编译器创建了一个Student对象,称为getNickName()方法,然后打印其返回值。

现在让我们将属性指定为inline ,然后比较生成的字节码。

// ...
inline val nickName: String
// ...

我们只是在变量修饰符之前插入inline修饰符: varval 这是为此内联属性生成的字节码:

// ... 
public static final void main(@NotNull String[] args) {
  Intrinsics.checkParameterIsNotNull(args, "args");
  Student student = new Student();
  String var3 = "Nick name retrieved";
  System.out.println(var3);
  String var2 = "koloCoder";
  System.out.print(var2);
}
// ...

再次删除了一些代码,但要注意的关键是main()方法。 他编译器复制属性get()函数体,并将其粘贴到调用点(此机制类似于内联函数)。

我们的代码已经过优化,因为不需要创建对象并调用属性getter方法。 但是,如内联函数文章中所述,我们将拥有比以前更大的字节码-请谨慎使用。

还要注意,此机制适用于没有后备字段的属性(请记住,后备字段只是当您想要修改或使用该字段数据时属性使用的字段)。

3.扩展属性

高级功能中,我还讨论了扩展功能-这些功能使我们能够使用新功能扩展类,而不必从该类继承。 Kotlin还为属性提供了一种类似的机制,称为扩展属性

val String.upperCaseFirstLetter: String
    get() = this.substring(0, 1).toUpperCase().plus(this.substring(1))

高级功能文章中,我们定义了一个接收器类型为StringuppercaseFirstLetter()扩展函数。 在这里,我们将其转换为顶级扩展属性。 请注意,您必须在属性上定义一个getter方法,此方法才能起作用。

因此,有了有关扩展属性的新知识,您就会知道,如果您曾经希望某个类应具有不可用的属性,则可以自由地创建该类的扩展属性。

4.数据类

让我们从一个典型的Java类或POJO(普通的旧Java对象)开始。

public class BlogPost {
    private final String title;
    private final URI url;
    private final String description;
    private final Date publishDate;

    //.. constructor not included for brevity's sake

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        BlogPost blogPost = (BlogPost) o;

        if (title != null ? !title.equals(blogPost.title) : blogPost.title != null) return false;
        if (url != null ? !url.equals(blogPost.url) : blogPost.url != null) return false;
        if (description != null ? !description.equals(blogPost.description) : blogPost.description != null)
            return false;
        return publishDate != null ? publishDate.equals(blogPost.publishDate) : blogPost.publishDate == null;
    }

    @Override
    public int hashCode() {
        int result = title != null ? title.hashCode() : 0;
        result = 31 * result + (url != null ? url.hashCode() : 0);
        result = 31 * result + (description != null ? description.hashCode() : 0);
        result = 31 * result + (publishDate != null ? publishDate.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "BlogPost{" +
                "title='" + title + '\'' +
                ", url=" + url +
                ", description='" + description + '\'' +
                ", publishDate=" + publishDate +
                '}';
    }
    //.. setters and getters also ignored for brevity's sake
}

如您所见,我们需要显式地编写类属性访问器的代码:getter和setter以及hashcodeequalstoString方法(尽管IntelliJ IDEA,Android Studio或AutoValue库可以帮助我们生成它们)。 我们通常在典型的Java项目的数据层中看到这种样板代码。 (为简便起见,我删除了字段访问器和构造函数)。

很棒的事情是,Kotlin团队为我们提供了类的data修饰符,以消除编写这些样板的麻烦。

现在让我们用Kotlin编写前面的代码。

data class BlogPost(var title: String, var url: URI, var description: String, var publishDate: Date)

太棒了! 我们只需要在class关键字之前指定data修饰符即可创建数据类,就像我们在上述BlogPost Kotlin类中所做的一样。 现在将为我们创建equalshashcodetoStringcopy和多个组件方法。 请注意,数据类可以扩展其他类(这是Kotlin 1.1的新功能)。

equals方法

此方法比较两个对象是否相等,如果相等则返回true,否则返回false。 换句话说,它比较两个类实例是否包含相同的数据。

student.equals(student3)
// using the == in Kotlin
student == student3 // same as using equals()

在Kotlin中,使用等于运算符==将在后台调用equals方法。

hashCode方法

此方法返回一个整数值,该值用于快速存储和检索存储在基于哈希的集合数据结构中的数据,例如HashMapHashSet集合类型中的数据。

toString方法

此方法返回对象的String表示形式。

data class Person(var firstName: String, var lastName: String)

val person = Person("Chike", "Mgbemena")
println(person) // prints "Person(firstName=Chike, lastName=Mgbemena)"

通过仅调用类实例,我们得到了一个返回给我们的字符串对象-Kotlin为我们在幕后将对象称为toString() 。 但是,如果我们不输入data关键字,请查看我们的对象字符串表示形式是什么:

com.chike.kotlin.classes.Person@2f0e140b

信息少得多!

copy方法

此方法使我们可以创建具有所有相同属性值的对象的新实例。 换句话说,它创建对象的副本。

val person1 = Person("Chike", "Mgbemena")
println(person1) // Person(firstName=Chike, lastName=Mgbemena)
val person2 = person1.copy()
println(person2) // Person(firstName=Chike, lastName=Mgbemena)

关于Kotlin中的copy方法,一个很酷的事情是在复制过程中更改属性的能力。

val person3 = person1.copy(lastName = "Onu")
println(person3) //Person3(firstName=Chike, lastName=Onu)

如果您是Java程序员,则此方法类似于您已经熟悉的clone()方法。 但是Kotlin copy方法具有更强大的功能。

破坏性声明

Person类中,由于在类中放置了data关键字,因此编译器还为我们自动生成了两个方法。 这两种方法都以“ component”为前缀,然后加上数字后缀: component1()component2() 。 这些方法中的每一个都代表类型的各个属性。 请注意,后缀对应于在主构造函数中声明的属性的顺序。

因此,在我们的示例中,调用component1()将返回名字,而调用component2()将返回姓氏。

println(person3.component1()) // Chike
println(person3.component2()) // Onu

但是,使用这种样式调用属性很难理解和阅读,因此显式调用属性名称要好得多。 但是,这些隐式创建的属性确实具有非常有用的目的:它们使我们可以进行解构声明,在声明中可以将每个组件分配给局部变量。

val (firstName, lastName) = Person("Angelina", "Jolie")
println(firstName + " " + lastName) // Angelina Jolie

我们在这里所做的是直接将Person类型的第一个和第二个属性( firstNamelastName )分别分配给变量firstNamelastName 。 我还在Packages and Basic Functions帖子的最后部分中讨论了这种称为解构声明的机制。

5.嵌套类

在“ 函数更多乐趣”一文中 ,我告诉您Kotlin支持局部或嵌套函数,该函数在另一个函数中声明。 好吧,Kotlin同样也支持嵌套类-在另一个类内部创建的类。

class OuterClass {

    class NestedClass {
        fun nestedClassFunc() { }
    }
}

我们甚至可以调用嵌套类的公共函数,如下所示-Kotlin中的嵌套类等效于Java中的static嵌套类。 请注意,嵌套类不能存储对其外部类的引用。

val nestedClass = OuterClass.NestedClass()
nestedClass.nestedClassFunc()

我们还可以自由设置嵌套类的私有这意味着我们只能创建一个实例NestedClass的范围内, OuterClass

内部阶层

另一方面,内部类可以引用在其中声明的外部类。要创建内部类,我们在嵌套类中将inner关键字放在class关键字之前。

class OuterClass() {
    val oCPropt: String = "Yo"

    inner class InnerClass {
        
        fun innerClassFunc() {
            val outerClass = this@OuterClass
            print(outerClass.oCPropt) // prints "Yo"
        }
    }
}

在这里,我们引用OuterClassInnerClass通过使用this@OuterClass

6.枚举类

枚举类型声明一组由标识符表示的常量。 这种特殊的类是由在class关键字之前指定的关键字enum创建的。

enum class Country {
    NIGERIA,
    GHANA,
    CANADA
}

要根据其名称检索枚举值(就像在Java中一样),我们可以这样做:

Country.valueOf("NIGERIA")

或者我们可以使用Kotlin enumValueOf<T>()帮助程序方法以通用方式访问常量:

enumValueOf<Country>("NIGERIA")

同样,我们可以像这样获取所有值(例如对于Java枚举):

Country.values()

最后,我们可以使用Kotlin enumValues<T>()帮助程序方法以通用方式获取所有枚举条目:

enumValues<Country>()

这将返回一个包含枚举条目的数组。

枚举构造函数

就像普通的类一样, enum类型可以具有自己的构造函数,该构造函数具有与每个枚举常量关联的属性。

enum class Country(val callingCode: Int) {
    NIGERIA (234),
    USA (1),
    GHANA (233)
}

Country枚举类型的主要构造函数中,我们为每个枚举常量定义了不可变的属性callingCodes 。 在每个常量中,我们将一个参数传递给构造函数。

然后,我们可以像下面这样访问constants属性:

val country = Country.NIGERIA
print(country.callingCode) // 234

7.密封班

Kotlin中的密封类是抽象类(您永远都不想用它创建对象),其他类可以扩展该抽象类。 这些子类在密封的类体内定义在同一文件中。 因为所有这些子类都在密封的类体内定义,所以我们只需查看文件就可以知道所有可能的子类。

让我们看一个实际的例子。

// shape.kt

sealed class Shape

class Circle : Shape()
class Triangle : Shape()
class Rectangle: Shape()

要声明一个类是密封的,我们插入sealed的前修改class的类声明修饰符头,在我们的例子中,我们宣布Shape类作为sealed 。 没有其子类的密封类是不完整的,就像典型的抽象类一样,因此我们必须在同一文件(在本例中为shape.kt)中声明各个子类。 请注意,您不能从另一个文件定义密封类的子类。

在上面的代码中,我们指定Shape类只能由CircleTriangleRectangle类扩展。

Kotlin中的密封类具有以下附加规则:

  • 我们可以将修饰符abstract添加到密封类中,但这是多余的,因为缺省情况下密封类是抽象的。
  • 密封的类不能具有openfinal修饰符。
  • 我们还可以自由地将数据类和对象声明为密封类的子类(它们仍需要在同一文件中声明)。
  • 密封的类不允许具有公共构造函数-默认情况下,其构造函数是私有的。

扩展密封类的子类的类可以放在同一文件中,也可以放在另一个文件中。 密封类的子类必须用open修饰符标记(您将在下一篇文章中了解有关Kotlin继承的更多信息)。

// employee.kt
sealed class Employee
open class Artist : Employee()

// musician.kt
class Musician : Artist()

密封类及其子类在when表达式中非常方便。 例如:

fun whatIsIt(shape: Shape) = when (shape) {
    is Circle -> println("A circle")
    is Triangle -> println("A triangle")
    is Rectangle -> println("A rectangle")
}

在这里,编译器是聪明的,以确保我们覆盖了所有可能的when的情况。 这意味着无需添加else子句。

如果要改为执行以下操作:

fun whatIsIt(shape: Shape) = when (shape) {
    is Circle -> println("A circle")
    is Triangle -> println("A triangle")
}

该代码无法编译,因为我们没有包括所有可能的情况。 我们将出现以下错误:

Kotlin: 'when' expression must be exhaustive, add necessary 'is Rectangle' branch or 'else' branch instead.

因此,我们可以包含is Rectangle大小写,也可以包含else子句来完成when表达式。

结论

在本教程中,您了解了有关Kotlin中的类的更多信息。 我们介绍了有关类属性的以下内容:

此外,您还了解了一些很酷的高级类,例如数据,枚举,嵌套和密封类。 在Kotlin From Scratch系列的下一篇教程中,将向您介绍Kotlin中的接口和继承。 再见!

翻译自: https://code.tutsplus.com/tutorials/kotlin-from-scratch-advanced-properties-and-classes--cms-29613

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值