Android筑基——Kotlin by 关键字详解

Posted willwaywang6

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android筑基——Kotlin by 关键字详解相关的知识,希望对你有一定的参考价值。

1.前言

Kotlin 中的 by 关键字在 Java 中是没有的,这使我对它感到非常陌生。

Kotlin 中为什么要新增 by 关键字呢?by 关键字在 Kotlin 中是如何使用的?

本文会介绍 by 关键字的使用分类,具体的示例,Kotlin 内置的 by 使用,希望能够帮助到大家。

2.正文

by 关键字的使用分为两种:类委托和委托属性。

2.1 类委托

现在有一个需求,统计向一个 HashSet 尝试添加元素的尝试次数,该怎么实现?

2.1.1 使用继承方式实现

简单,继承一个 HashSet,创建一个变量,负责统计尝试添加元素的个数,代码如下:

class CountingSet1<T>: HashSet<T>() {

    var objectAdded = 0

    override fun add(element: T): Boolean {
        objectAdded++
        return super.add(element)
    }

    override fun addAll(elements: Collection<T>): Boolean {
        // 因为 super.addAll 内部调用了 add 方法,所以这里不必统计添加个数了。
        return super.addAll(elements)
    }
}

fun main() {
    val cset = CountingSet1<Int>()
    cset.add(1)
    cset.addAll(listOf(2, 2, 3))
    println("${cset.objectAdded} objects were added, ${cset.size} remain")
}
/*
打印如下:
4 objects were added, 3 remain
*/

需求是满足了。

但是这样的实现,CountingSet1HashSet 的具体实现是高度耦合的,也就是说,CountingSet1 严重依赖于 HashSet 类的实现细节。

这有什么问题吗?

当基类的实现被修改或者新的方法被添加进去时,可能改变之前进行继承时的类行为,从而导致子类的行为不符合预期。

当某个类是 final 类时,它是不可以被继承的,这时就不能采用继承的方式来复用它的代码了。

那我们该怎么办呢?

想一下,我们新建的类无非是想复用 HashSet的功能,前面我们是采用继承的方式,除了采用继承的方式之外,我们还可以采用组合的方式。

实际上,在 《Java编程思想》中有写道:

由于继承在面向对象程序设计中如此重要,所以它经常被高度强调,于是程序员新手就会有这样的印象:处处都应该使用继承。这会导致难以使用并过分复杂的设计。实际上,在建立新类时,应该优先考虑组合,因为它更加简单灵活。如果采用这种方式,设计会变得更加清晰。

2.1.2 使用组合方式实现

使用组合方式实现如下:

class CountingSet2<T> : MutableSet<T> {
    private val innerSet = HashSet<T>()
    var objectAdded = 0
    override fun add(element: T): Boolean {
        objectAdded++
        return innerSet.add(element)
    }

    override fun addAll(elements: Collection<T>): Boolean {
        objectAdded += elements.size
        return innerSet.addAll(elements)
    }

    override fun clear() {
        innerSet.clear()
    }

    override fun iterator(): MutableIterator<T> {
        return innerSet.iterator()
    }

    override fun remove(element: T): Boolean {
        return innerSet.remove(element)
    }

    override fun removeAll(elements: Collection<T>): Boolean {
        return innerSet.removeAll(elements)
    }

    override fun retainAll(elements: Collection<T>): Boolean {
        return innerSet.retainAll(elements)
    }

    override val size: Int
        get() = innerSet.size

    override fun contains(element: T): Boolean {
        return innerSet.contains(element)
    }

    override fun containsAll(elements: Collection<T>): Boolean {
        return innerSet.containsAll(elements)
    }

    override fun isEmpty(): Boolean {
        return innerSet.isEmpty()
    }
}

fun main() {
    val cset = CountingSet2<Int>()
    cset.add(1)
    cset.addAll(listOf(2, 2, 3))
    println("${cset.objectAdded} objects were added, ${cset.size} remain")
}
/*
打印如下:
4 objects were added, 3 remain
*/

同样实现了需求。

可以看到,CountingSet2 实现了 MutableSet 接口,具体的实现是委托给了 innerSet 来完成的。

这有什么好处呢?

CountingSet2HashSet 不再耦合了,它们都实现了 MutableSet 接口。

这种方式从代码设计上看确实好,但是却需要非常多的模板代码,这点很烦人啊。

2.1.3 使用类委托实现

现在就该 Kotlin 的类委托出场了,它可以解决需要写非常多的模板代码的问题。

class CountingSet3<T>(
    val innerSet: MutableSet<T> = HashSet<T>()
) : MutableSet<T> by innerSet {
    var objectAdded = 0
    override fun add(element: T): Boolean {
        objectAdded++
        return innerSet.add(element)
    }

    override fun addAll(elements: Collection<T>): Boolean {
        objectAdded += elements.size
        return innerSet.addAll(elements)
    }
}

fun main() {
    val cset = CountingSet3<Int>()
    cset.add(1)
    cset.addAll(listOf(2, 2, 3))
    println("${cset.objectAdded} objects were added, ${cset.size} remain")
}
/*
打印如下:
4 objects were added, 3 remain
*/

完美实现了需求。

Kotlin 是如何帮我们减少了模板代码了呢?

使用 android Studio 的 Tools -> Kotlin -> Show Kotlin Bytecode,再点击 Decompile 按钮,查看对应的 Java 代码:

public final class CountingSet3 implements Set, KMutableSet {
   private int objectAdded;
   @NotNull
   private final Set innerSet;

   public final int getObjectAdded() {
      return this.objectAdded;
   }

   public final void setObjectAdded(int var1) {
      this.objectAdded = var1;
   }

   public boolean add(Object element) {
      int var10001 = this.objectAdded++;
      return this.innerSet.add(element);
   }

   public boolean addAll(@NotNull Collection elements) {
      Intrinsics.checkNotNullParameter(elements, "elements");
      this.objectAdded += elements.size();
      return this.innerSet.addAll(elements);
   }

   @NotNull
   public final Set getInnerSet() {
      return this.innerSet;
   }

   public CountingSet3(@NotNull Set innerSet) {
      Intrinsics.checkNotNullParameter(innerSet, "innerSet");
      super();
      this.innerSet = innerSet;
   }

   // $FF: synthetic method
   public CountingSet3(Set var1, int var2, DefaultConstructorMarker var3) {
      if ((var2 & 1) != 0) {
         var1 = (Set)(new HashSet());
      }

      this(var1);
   }

   public CountingSet3() {
      this((Set)null, 1, (DefaultConstructorMarker)null);
   }

   public int getSize() {
      return this.innerSet.size();
   }

   // $FF: bridge method
   public final int size() {
      return this.getSize();
   }

   public void clear() {
      this.innerSet.clear();
   }

   public boolean contains(Object element) {
      return this.innerSet.contains(element);
   }

   public boolean containsAll(@NotNull Collection elements) {
      Intrinsics.checkNotNullParameter(elements, "elements");
      return this.innerSet.containsAll(elements);
   }

   public boolean isEmpty() {
      return this.innerSet.isEmpty();
   }

   @NotNull
   public Iterator iterator() {
      return this.innerSet.iterator();
   }

   public boolean remove(Object element) {
      return this.innerSet.remove(element);
   }

   public boolean removeAll(@NotNull Collection elements) {
      Intrinsics.checkNotNullParameter(elements, "elements");
      return this.innerSet.removeAll(elements);
   }

   public boolean retainAll(@NotNull Collection elements) {
      Intrinsics.checkNotNullParameter(elements, "elements");
      return this.innerSet.retainAll(elements);
   }

   public Object[] toArray() {
      return CollectionToArray.toArray(this);
   }

   public Object[] toArray(Object[] var1) {
      return CollectionToArray.toArray(this, var1);
   }
}

这不就是 CountingSet2 的代码吗?

原来 Kotlin 的编译器默默地帮我们生成了这些模板代码,而仅仅要求我们通过声明并初始化一个 HashSet 类型的成员变量,并在类声明后添加 by innerSet

class CountingSet3<T>(
    val innerSet: MutableSet<T> = HashSet<T>()
) : MutableSet<T> by innerSet {

需要注意的是,

  • CountingSet3 必须实现一个接口,而不能继承于一个类;
  • innerSet 的类型必须是 CountingSet3 所实现接口的子类型;
  • 可以直接在 by 创建委托对象,如下所示:
    class CountingSet4<T>(
    ) : MutableSet<T> by HashSet<T>() {
    }
    
    但是,这样的话,在 CountingSet4 类中无法获取到委托对象的引用了。

Kotlin 的类委托虽然看起来很简洁,但是它自身又有一些限制:类必须实现一个接口,委托类必须是类所实现接口的子类型。这是需要注意的。

同时,我们在实际开发中,要尽力去使用这种委托的思想,来使代码解耦,使代码更加清晰。这一点,比掌握 Kotlin 的类委托更加重要。

2.2 委托属性

委托属性是一个依赖于约定的功能,也是Kotlin 中最独特和最强大的功能之一。

本部分我们仍然是从一个小例子开始,展示一下委托属性的使用,作用;然后,会说明委托属性的一些特点以及其他使用。

需求:现在有一个简单的 Person 类:

class Person2 {
    var name: String = ""
    var lastname: String = ""  
}

需要对 namelastname 赋值时,做一些格式化工作:首字母大写其余字母小写,并统计格式化操作的次数,再获取 namelastname 值的时候,把它们的值的长度拼接在值得后面返回。

2.2.1 仅仅完成需求的代码

这不难,代码实现如下:

class Person2 {
    var name: String = ""
        set(value) {
            field = value.lowercase().replaceFirstChar { it.uppercase() }
            updateCount++
        }
        get() {
            return field + "-" + field.length
        }

    var lastname: String = ""
        set(value) {
            field = value.lowercase().replaceFirstChar { it.uppercase() }
            updateCount++
        }
        get() {
            return field + "-" + field.length
        }
    var updateCount = 0
}

fun main() {
    val person2 = Person2()
    person2.name = "peter"
    person2.lastname = "wang"
    println("name=${person2.name}")
    println("lastname=${person2.lastname}")
    println("updateCount=${person2.updateCount}")
}
/*
打印日志:
name=Peter-5
lastname=Wang-4
updateCount=2
*/

ps:这里面使用 Kotlin 中的支持字段 field ,需要学习可以查看笔者的这篇文章:Kotlin 的 Backing Fields 和 Backing Properties

OK,查看日志,可以看到需求实现了。

2.2.2 抽取重复代码为方法

但是,这里面有着重复的代码,并且如果其他类也需要这样的格式化操作,这些代码也不可以复用。

为了提供代码复用性,我们可以把代码抽取出来,放在一个方法里面。代码实现如下:

class Person3 {
    var name: String = ""
        set(value) {
            field = format(value)
        }
        get() {
            return getter(field)
        }

    var lastname: String = ""
        set(value) {
            field = format(value)
        }
        get() {
            return getter(field)
        }
    var updateCount = 0

    fun format(value: String): String {
        updateCount++
        return value.lowercase().replaceFirstChar { it.uppercase() }
    }
    fun getter(value: String): String {
        return value + "-" + value.length
    }
}

fun main() {
    val person = Person3()
    person.name = "peter"
    person.lastname = "wang"
    println("name=${person.name}")
    println("lastname=${person.lastname}")
    println("updateCount=${person.updateCount}")
}
/*
name=Peter-5
lastname=Wang-4
updateCount=2
*/

查看日志打印,这样做可以实现需求。

2.2.3 使用类来封装重复代码

现在需求又来了,有一个 Student 类:

class Student {
    var name: String = ""

    var address: String = ""
}

也需要对属性做同样的格式化操作并统计进行格式化操作的次数,在获取值的时候把长度拼接在后面。

这时,我们可以使用面向对象的思想来解决,把 format 方法和 getter 方法封装在一个类里面:

class Delegate {
    fun format(thisRef: Any, value: String): String {
        if (thisRef is Person4) {
            thisRef.updateCount++
        } else if (thisRef is Student2) {
            thisRef.updateCount++
        }
        return value.lowercase().replaceFirstChar { it.uppercase() }
    }

    fun getter(value: String): String {
        return value + "-" + value.length
    }
}

Person 类修改如下:

class Person4 {

    private val delegate = Delegate()

    var name: String = ""
        set(value) {
            field = delegate.format(this, value)
        }
        get() {
            return delegate.getter(field)
        }

    var lastname: String = ""
        set(value) {
            field = delegate.format(this, value)
        }
        get() {
            return delegate.getter(field)
        }

    var updateCount = 0
}

Student 类修改如下:

class Student2 {
    private val delegate = Delegate()

    var name: String = ""
        set(value) {
            field = delegate.format(this, value)
        }
        get() {
            return delegate.getter(field)
        }
    var address: String = ""
        set(value) kotlin-代理属性(by)

Kotlin基础 关键字:lateinit和by lazy

Kotlin -by 详解

Kotlin -by 详解

Android项目Java调用Kotlin类报错:错误: 程序包com.xw.bookshelf.ui.adapter不存在 import com.xw.bookshelf.ui.adapter

深入kotlin - 委托