Kotlin教程笔记(14) - 伴生对象与静态成员

简介: Kotlin教程笔记(14) - 伴生对象与静态成员

本系列学习教程笔记属于详细讲解Kotlin语法的教程,需要快速学习Kotlin语法的小伙伴可以查看“简洁” 系列的教程

快速入门请阅读如下简洁教程:
Kotlin学习教程(一)
Kotlin学习教程(二)
Kotlin学习教程(三)
Kotlin学习教程(四)
Kotlin学习教程(五)
Kotlin学习教程(六)
Kotlin学习教程(七)
Kotlin学习教程(八)
Kotlin学习教程(九)
Kotlin学习教程(十)

imgKotlin - 伴生对象与静态成员

#object 关键字

Kotlin 中有一种特殊的类,它本身也是一个实例(单例),这种既是类又是对象的类需要使用 object 关键字声明(普通类声明使用 class),它跟普通类一样,也可以实现接口和继承父类:

object MusicPlayer : Player(), OnStatusChangeListener {

    var state: Int = 0

    fun play(url: String) {
        ...
    }

    fun stop() {
        ...
    }

    override fun onMount(driver: Driver) {
    }

    override fun onUnmount(driver: Driver) {
    }
}

这种 object 类的方法可以通过类名直接调用:

fun main() {
    MusicPlayer.play("http://qqmusic.com/123213.mp3")
    MusicPlayer.stop()
}

借助 Show Kotlin Bytecode 工具,将上述 Kotlin 代码反编译成 java 代码如下:

public final class MusicPlayer extends Player implements OnStatusChangeListener {
   
   public static final MusicPlayer INSTANCE;

   private MusicPlayer() {
   
   }

   static {
   
      MusicPlayer var0 = new MusicPlayer();
      INSTANCE = var0;
   }
   ...
}

可以发现,Kotlin 中的 object 本质上就是 Java 中简单的单例模式,同时它的构造方法是 private 的,因此 object 类不能自定义构造方法!

#伴生对象(companion object)

日常开发中,经常会编写一些 Bean 类,用于描述一类事物,Bean 中会包含事物的属性,有的 Bean 类还会提供一系列"工具方法"来帮助开发者快速创建 Bean 对象,就比如在 Java 中,Integer.valueOf() 就可以帮助开发者快速将字符串转成整型,进入源码一看,可以发现这是一个使用了 static 关键字修饰的静态方法:

public static Integer valueOf(String s) throws NumberFormatException {
   
    return Integer.valueOf(parseInt(s, 10));
}

可是,在 Kotlin 中是没有 static 关键字的!!!想要实现通过 类名.xxx() 的方式调用工具方法,可以使用上述的 object 类,但 object 本身就是一个对象,我们无法在外部调用其构造方法,既然想拥有 object 类(通过类名调用方法)与 class 类(外部可以调用构造方法)的两种特性,这时伴生对象 companion object 就完全可以满足这个需求:

class Rectangle(val width: Int, val height: Int) {
    companion object {
        fun ofSize(width: Int, height: Int): Rectangle {
            return Rectangle(width, height)
        }

        fun ofRectangle(rectangle: Rectangle): Rectangle {
            return Rectangle(rectangle.width, rectangle.height)
        }
    }
}

fun main() {
    val width = 4
    val height = 5
    val rectangle1 = Rectangle(width, height) // 调用构造方法
    val rectangle2 = Rectangle.ofRectangle(rectangle1) // 通过类名调用方法
    val rectangle3 = Rectangle.ofSize(width, height) // 通过类名调用方法
}

伴生对象顾名思义,就是与类一起诞生的对象,类一加载,它的伴生对象也就被创建了,每个类都可以对应一个伴生对象,并且该伴生对象的成员全局独一份,也就是说伴生对象 companion object 也是一种单例,再次借助 Show Kotlin Bytecode 工具,将上述 Kotlin 代码反编译成 java 代码如下:

public final class Rectangle {
   
   ...
   public static final Rectangle.Companion Companion = new Rectangle.Companion((DefaultConstructorMarker)null);

   public static final class Companion {
   
      @NotNull
      public final Rectangle ofSize(int width, int height) {
   
         return new Rectangle(width, height);
      }

      @NotNull
      public final Rectangle ofRectangle(@NotNull Rectangle rectangle) {
   
         Intrinsics.checkParameterIsNotNull(rectangle, "rectangle");
         return new Rectangle(rectangle.getWidth(), rectangle.getHeight());
      }

      private Companion() {
   
      }

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

可以发现,Kotlin 中的 companion object 其实对应到 Java 中也就只是 Rectangle 的一个静态内部类实例而已。

#JVM 静态成员(@JvmStatic)

我们把上面 Kotlin 的 main 方法中的代码用 java 重写一遍:

int width = 4;
int height = 5;
Rectangle rectangle1 = new Rectangle(width,height);
Rectangle rectangle2 = Rectangle.Companion.ofSize(width,height);
Rectangle rectangle3 = Rectangle.Companion.ofRectangle(rectangle2);

发现 ofSizeofRectangle 方法并不是通过 Rectangle 类直接调用的,结合上述反编译后的 java 代码,应该不难理解,因为 Kotlin 中 Rectangle 的伴生对象本质上就是这个 Rectangle.Companion 实例,ofSizeofRectangle 都是 Rectangle.Companion 的方法,所以,用 Rectangle.Companion 对象调用它自己的方法这完全没有毛病,可是,难道就不能在 Java 中也像 Kotlin 那样,直接通过 类名.xxx() 的方法来调用伴生对象方法吗?答案肯定是有的啦,Kotlin 提供了 @JvmStatic@JvmField,可以让伴生对象中的方法和属性在 java 中通过类名直接调用:

class Rectangle(val width: Int, val height: Int) {
    companion object {
        @JvmStatic
        fun ofSize(width: Int, height: Int): Rectangle {
            return Rectangle(width, height)
        }

        @JvmStatic
        fun ofRectangle(rectangle: Rectangle): Rectangle {
            return Rectangle(rectangle.width, rectangle.height)
        }

        @JvmField
        val Tag = "Rectangle"
    }
}

这次 java 代码也可以使用 Rectangle 类名来调用 ofSize 和 ofRectangle 方法了:

int width = 4;
int height = 5;
Rectangle rectangle1 = new Rectangle(width, height);
Rectangle rectangle2 = Rectangle.ofSize(width, height);
Rectangle rectangle3 = Rectangle.ofRectangle(rectangle2);
System.out.println(Rectangle.Tag);

再来反编译一次,看其究竟是什么原理,原来就只是在 Rectangle 中增加了对应的 static 属性和方法而已:

public final class Rectangle {
   
   public static final Rectangle.Companion Companion = new Rectangle.Companion((DefaultConstructorMarker)null);

   @JvmField
   @NotNull
   public static final String Tag = "Rectangle";

   @JvmStatic
   @NotNull
   public static final Rectangle ofSize(int width, int height) {
   
      return Companion.ofSize(width, height);
   }

   @JvmStatic
   @NotNull
   public static final Rectangle ofRectangle(@NotNull Rectangle rectangle) {
   
      return Companion.ofRectangle(rectangle);
   }

   public static final class Companion {
   
      @JvmStatic
      @NotNull
      public final Rectangle ofSize(int width, int height) {
   
          ...
      }

      @JvmStatic
      @NotNull
      public final Rectangle ofRectangle(@NotNull Rectangle rectangle) {
   
          ...
      }
   }
}
相关文章
|
1天前
|
数据安全/隐私保护 Kotlin
|
1天前
|
Java 物联网 Kotlin
Kotlin教程笔记(10) - 面向对象之抽象类与接口
Kotlin教程笔记(10) - 面向对象之抽象类与接口
21 7
|
1天前
|
Java Kotlin
Kotlin教程笔记(13) - 类及成员的可见性
Kotlin教程笔记(13) - 类及成员的可见性
13 4
|
1天前
|
Java Kotlin
Kotlin教程笔记(12) - 面向对象之继承与实现
Kotlin教程笔记(12) - 面向对象之继承与实现
10 3
|
1天前
|
Kotlin
Kotlin教程笔记(9)- 参数与异常
Kotlin教程笔记(9)- 参数与异常
18 3
|
1天前
|
Java Kotlin 索引
Kotlin教程笔记(8)- 分支与循环
Kotlin教程笔记(8)- 分支与循环
17 2
|
1月前
|
Android开发 开发者 Kotlin
告别AsyncTask:一招教你用Kotlin协程重构Android应用,流畅度飙升的秘密武器
【9月更文挑战第13天】随着Android应用复杂度的增加,有效管理异步任务成为关键。Kotlin协程提供了一种优雅的并发操作处理方式,使异步编程更简单直观。本文通过具体示例介绍如何使用Kotlin协程优化Android应用性能,包括网络数据加载和UI更新。首先需在`build.gradle`中添加coroutines依赖。接着,通过定义挂起函数执行网络请求,并在`ViewModel`中使用`viewModelScope`启动协程,结合`Dispatchers.Main`更新UI,避免内存泄漏。使用协程不仅简化代码,还提升了程序健壮性。
54 1
|
2月前
|
调度 Android开发 开发者
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
43 4
|
3月前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
**Kotlin中的`by lazy`和`lateinit`都是延迟初始化技术。`by lazy`用于只读属性,线程安全,首次访问时初始化;`lateinit`用于可变属性,需手动初始化,非线程安全。`by lazy`支持线程安全模式选择,而`lateinit`适用于构造函数后初始化。选择依赖于属性特性和使用场景。**
112 5
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
|
3月前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin中常见作用域函数
**Kotlin作用域函数概览**: `let`, `run`, `with`, `apply`, `also`. `let`安全调用并返回结果; `run`在上下文中执行代码并返回结果; `with`执行代码块,返回结果; `apply`配置对象后返回自身; `also`附加操作后返回自身
44 8