翻译说明:java
原标题: Effective Java in Kotlin, item 1: Consider static factory methods instead of constructors数组
原文地址: blog.kotlin-academy.com/effective-j…缓存
原文做者: Marcin Moskalaapp
由Joshua Bloch撰写的Effective Java这本书是Java开发中最重要的书之一。我常常引用它,这也就是为何我常常被要求说起更多有关于它的缘由。我也对它和Kotlin相关的一些内容很是感兴趣,这就是为何我决定用Kotlin去一个一个去阐述它们,这是Kotlin学院的博客。只要我看到读者的兴趣,我就继续下去;)ide
这是Effective Java的第一条规则:函数
考虑使用静态工厂方法替代构造器
工具
让咱们一块儿来探索吧。post
Effective Java的第一条规则就代表开发者应该更多考虑使用静态工厂方法来替代构造器。静态工厂方法是一个被用来建立一个对象实例的静态方法。这里有一些有关静态工厂方法使用的Java例子:性能
Boolean trueBoolean = Boolean.valueOf(true);
String number = String.valueOf(12);
List<Integer> list = Arrays.asList(1, 2, 4);
复制代码
静态工厂方法是替代构造器一种很是高效的方法。这里列举了一些他们优势:学习
new ArrayList(3)
.你能猜到3
表明什么意思吗?它是应该被认为是数组的第一元素仍是一个集合的size呢?这无疑不能作到一目了然。例如,ArrayList.withSize(3)
这个拥有方法名场景就会消除全部疑惑。这是方法名很是有用的一种:它解释了对象建立的参数或特征方式。拥有方法名的另外一个缘由是它解决了具备相同参数类型的构造函数之间的冲突。null
,就像Connections.createOrNull()
方法同样,当Connection
对象因为某些缘由不能被建立时就返回一个null
.listOf(1,2,3)
,若是是在Kotlin/JVM平台下运行就会返回一个ArrayList
对象。相同的调用若是是在Kotlin/JS平台将会返回一个JavaScript的数组。这是一个优化的实现,并非一个已存在的问题,由于二者的集合类都是实现了Kotlin中的List接口。listOf
返回的类型是List,这是一个咱们正在运行的接口。通常来讲隐藏在底层引擎下的实际类型和咱们并无多大关系. 相似地,在任何静态工厂方法中,咱们能够返回不一样类型甚至更改类型的具体实现,只要它们隐藏在某些超类或接口后面,而且被指定为静态工厂方法返回类型便可。虽然以上那些都是支持静态工厂方法的使用很是有力的论据,可是Joshua Bloch也指出了一些有关静态工厂方法缺点:
它们不能用于子类的构造. 在子类构造中,咱们须要使用父类构造函数,而不能使用静态工厂方法。
它们很难和其余静态方法区分开来. 除了如下状况:valueOf
,of
,getInstance
,newInstance
, getType
和newType
.这些是不一样类型的静态工厂方法的通用名称。
在以上论点讨论完后,得出的直观结论是,用于构造对象或者和对象结构紧密相关的对象构造的函数应该被指定为构造函数。另外一方面,当构造与对象的结构没有直接关联时,则颇有可能应该使用静态工厂方法来定义。
让咱们来到Kotlin吧,当我在学习Kotlin的时候,我感受有人正在设计它,同时在他面前有一本Effective Java。它解答了本书中描述的大多数Java问题。Kotlin还改变了工厂方法的实现方式。让咱们一块儿来分析下吧。
在Kotlin中不容许有static关键字修饰的方法,相似于Java中的静态工厂方法一般被称为伴生工厂方法,它是一个放在伴生对象中的工厂方法:
class MyList {
//...
companion object {
fun of(vararg i: Int) { /*...*/ }
}
}
复制代码
用法与静态工厂方法的用法相同:
MyList.of(1,2,3,4)
复制代码
在底层实现上,伴生对象实际上就是个单例类,它有个很大的优势就是Companion对象能够继承其余的类。这样咱们就能够实现多个通用工厂方法并为它们提供不一样的类。使用的常见例子是Provider类,我用它做为DI的替代品。我有下列这个类:
abstract class Provider<T> {
var original: T? = null
var mocked: T? = null
abstract fun create(): T
fun get(): T = mocked ?: original ?: create()
.apply { original = this }
fun lazyGet(): Lazy<T> = lazy { get() }
}
复制代码
对于不一样的元素,我只须要实现具体的建立函数便可:
interface UserRepository {
fun getUser(): User
companion object: Provider<UserRepository>() {
override fun create() = UserRepositoryImpl()
}
}
复制代码
有了这样的定义,我能够经过UserReposiroty.get()
方法获取repository实例对象,或者在代码的任何地方经过val user by UserRepository.lazyGet()
这种懒加载方式得到相应的实例对象。我也能够为测试例子指定不一样的实现或者经过UserRepository.mocked = object: UserRepository { /*...*/ }
实现模拟测试的需求。
与Java相比,这是一个很大的优点,其中全部的SFM(静态工厂方法)都必须在每一个对象中手动实现。此外经过使用接口委托来重用工厂方法的方式仍然被低估了。在上面的例子中咱们可使用这种方式:
interface Dependency<T> {
var mocked: T?
fun get(): T
fun lazyGet(): Lazy<T> = lazy { get() }
}
abstract class Provider<T>(val init: ()->T): Dependency<T> {
var original: T? = null
override var mocked: T? = null
override fun get(): T = mocked ?: original ?: init()
.apply { original = this }
}
interface UserRepository {
fun getUser(): User
companion object: Dependency<UserRepository> by Provider({
UserRepositoryImpl()
})
}
复制代码
用法是相同的,但请注意,使用接口委托咱们能够从单个伴生对象中的不一样类获取工厂方法,而且咱们只能得到接口中指定的功能(依据接口隔离原则的设计很是好)。了解更多有关接口委托
请注意另外一个优势就是考虑将工厂方法放在一个伴生对象里而不是被定义为一个静态方法: 咱们能够为伴生对象定义扩展方法。所以若是咱们想要把伴生工厂方法加入到被定义在外部类库的Kotlin类中,咱们仍是能够这样作的(只要它能定义任意的伴生对象)
interface Tool {
companion object { … }
}
fun Tool.Companion.createBigTool(…) : BigTool { … }
复制代码
或者,伴生对象被命名的状况:
interface Tool {
companion object Factory { … }
}
fun Tool.Factory.createBigTool(…) : BigTool { … }
复制代码
让咱们从代码中共享外部库的使用这是一种很强大的可能,据我所知 Kotlin如今是惟一提供这种可能性的语言。
在Kotlin中,更多的是定义顶层函数而不是CFM(伴生对象工厂方法)。好比一些常见的例子listOf,setOf和mapOf,一样库设计者正在制定用于建立对象的顶级函数。它们将会被普遍使用。例如,在Android中,咱们传统上定义一个函数来建立Activity Intent做为静态方法:
//java
class MainActivity extends Activity {
static Intent getIntent(Context context) {
return new Intent(context, MainActivity.class);
}
}
复制代码
在Kotlin的Anko库中,咱们可使用顶层函数intentFor
加reified类型来替代:
intentFor<MainActivity>()
复制代码
这种解决方案的问题在于,虽然公共的顶层函数随处可用,可是很容易让用户丢掉IDE的提示。这个更大的问题在于当有人建立顶级函数时,方法名不直接指明它不是方法。使用顶级函数建立对象是小型和经常使用对象建立方式的完美选择,好比List
或者Map
,由于listOf(1,2,3)
比List.of(1,2,3)
更简单而且更具备可读性。可是公共的顶层函数须要被谨慎使用以及不能滥用。
Kotlin中的构造函数与顶级函数的工做方式相似:
class A()
val a = A()
复制代码
它们也能够和顶层函数同样被引用:
val aReference = ::A
复制代码
类构造函数和函数之间惟一的区别是函数名不是以大写开头的。虽然技术上容许,可是这个事实已经适用于Kotlin的不少不一样地方其中包括Kotlin标准库在内。List
和MutableList
都是接口,可是它们没有构造器,可是Kotlin开发者但愿容许如下List的构造:
List(3) { "$it" } // same as listOf("0", "1", "2")
复制代码
这就是为何在Collections.kt中就包含如下函数(自Kotlin 1.1起):
public inline fun <T> List(size: Int, init: (index: Int) -> T): List<T> = MutableList(size, init)
public inline fun <T> MutableList(size: Int, init: (index: Int) -> T): MutableList<T> {
val list = ArrayList<T>(size)
repeat(size) { index -> list.add(init(index)) }
return list
}
复制代码
它们看起来很像构造器,不少开发人员都没有意识到它们是底层实现的顶层函数。同时,它们具备SFM(静态工厂方法)的一些优势:它们能够返回类型的子类型,而且它们不须要每次都建立对象。它们也没有构造器相关的要求。例如,辅助构造函数须要当即调用超类的主构造函数或构造函数。当咱们使用伪构造函数时,咱们能够推迟构造函数的使用:
fun ListView(config: Config) : ListView {
val items = … // Here we read items from config
return ListView(items) // We call actual constructor
}
复制代码
咱们可能想要在类以外建立工厂方法的另外一个缘由是咱们想要在某个特定做用域内建立它。就像咱们只在某个特定的类或文件中须要工厂方法同样。
有些人可能会争辩说这种使用会产生误导,由于对象建立做用域一般与该类可见做用域相关联。全部的这些可能性都是表达意图的强有力的工具,它们须要被理智地使用。虽然对象建立的具体方式包含有关它的信息,但在某些状况下使用这种可能性是很是有价值的。
Kotlin中有个很好的特性叫作主构造器。在Kotlin类中只能有一个主构造器,可是它们比Java中已知的构造函数(在Kotlin中称为辅助构造器)更强大。主构造器的参数能够被用在类建立的任何地方。
class Student(name: String, surname: String) {
val fullName = "$name $surname"
}
复制代码
更重要的是,能够直接定义这些参数做为属性:
class Student(val name: String, val surname: String) {
val fullName
get() = "$name $surname"
}
复制代码
应该清楚的是,主构造器与类建立是密切相关的。请注意,当咱们使用带有默认参数的主构造器时,咱们不须要伸缩构造器。感谢全部这些,主构造器常常被使用(我在个人项目上建立了数百个类,我发现只有少数没有用主构造器),而且不多使用辅助构造器。这很棒。我认为就应该是这样的。主构造器与类结构和初始化紧密相关,所以在咱们应该定义构造器而不是工厂方法时,它彻底符合需求条件。对于其余状况,咱们极可能应该使用伴随对象工厂方法或顶级函数而不是辅助构造器。
Kotlin的工厂方法优势并不只仅是Kotlin如何改进对象的建立。在下一篇文章中,咱们将描述Kotlin如何改进构建器模式。例如,包含多个优化,容许用于建立对象的DSL:
val dialog = alertDialog {
title = "Hey, you!"
message = "You want to read more about Kotlin?"
setPositiveButton { makeMoreArticlesForReader() }
setNegativeButton { startBeingSad() }
}
复制代码
我想起来了。在本文中,我最初只描述了静态工厂方法的直接替代方法,由于这是Effective Java的第一项。与本书相关的其余吸引人的Kotlin功能将在下一篇文章中描述。若是您想收到通知,请订阅消息推送。
虽然Kotlin在对象建立方面作了不少改变,可是Effective Java中有关静态工厂方法的争论仍然是最火的。改变的是Kotlin排除了静态成员方法,而是咱们可使用以下具备SFM优点的替代方法:
它们中的每个都是在不一样的需求场景下使用,而且每一个都具备与Java SFM不一样的优势。
通常规则是,在大多数状况下,咱们建立对象所需的所有是主构造器,默认状况下链接到类结构和建立。当咱们须要其余构建方法时,咱们应该最有可能使用一些SFM替代方案。
首先,说下为何我想要翻译有关Effective Kotlin一系列的文章。总的来讲Kotlin对你们来讲已经不陌生,相信有不少小伙伴,不管是在公司项目仍是本身平时demo项目去开始尝试使用kotlin.当学完了Kotlin的基本使用的时候,也许你是否能感觉已经到了一个瓶颈期,那么我以为Effective Kotlin就是不错选择。Effective Kotlin教你如何去写出更好更优的Kotlin代码,有的人会有疑问这和Java有什么区别,咱们都知道Kotlin和Java极大兼容,可是它们区别仍是存在的。若是你已经看过Effective Java的话,相信在对比学习状态,你能把Kotlin理解更加透彻。
而后还有一个缘由,在Medium上注意到该文章的做者已经几乎把对应Effective Java 的知识点用Kotlin过了一遍,从中指出了它们相同点、不一样点以及Kotlin在相同的场景下实现的优点所在。
最后,用原做者一句话结尾 “I will continue as long as I see an interest from readers”,这也是我想说只要读者感兴趣,我会一直坚持把该系列文章翻译下去,一块儿学习。
欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不按期翻译一篇Kotlin国外技术文章。若是你也喜欢Kotlin,欢迎加入咱们~~~