kotlin官方文档中文翻译(三) 类和对象
2017-07-23 18:35
435 查看
类和继承
类
Kotlin 中使用关键字 class 声明类class Invoice { }
类声明由类名、类头(指定其类型参数、主 构造函数等)和由大括号包围的类体构成。类头和类体都是可选的; 如果一个类没有类体,可以省略花括号。
class Empty
构造函数
在 Kotlin 中的一个类可以有一个主构造函数和一个或多个次构造函数。主 构造函数是类头的一部分:它跟在类名(和可选的类型参数)后。class Person constructor(firstName: String) { }
如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。
class Person(firstName: String) { }
主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块(initializer blocks)中:
class Customer(name: String) { init { logger.info("Customer initialized with value ${name}") } }
注意,主构造的参数可以在初始化块中使用。它们也可以在类体内声明的属性初始化器中使用:
class Customer(name: String) { val customerKey = name.toUpperCase() }
事实上,声明属性以及从主构造函数初始化属性,Kotlin 有简洁的语法:
class Person(val firstName: String, val lastName: String, var age: Int) { // …… }
与普通属性一样,主构造函数中声明的属性可以是 可变的( var )或只读的( val )。
如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且 这些修饰符
在它前面:
class Customer public @Inject constructor(name: String) { …… }
次构造函数
类也可以声明前缀有 constructor 的次构造函数:class Person { constructor(parent: Person) { parent.children.add(this) } }
如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数 用 this 关键字即可:
class Person(val name: String) { constructor(name: String, parent: Person) : this(name) { parent.children.add(this) } }
如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的 不带参数的主构造函数。构造函数的可见性是 public。如果你不希望你的类 有一个公有构造函数,你需要声明一个带有非默认可见性的空的主构造函数:
class DontCreateMe private constructor () { }
注意:在 JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成 一个额外的无参构造函数,它将使用默认值。这使得 Kotlin 更易于使用像 Jackson 或者 JPA 这样的通过无参构造函数创建类的实例的库。
class Customer(val customerName: String = "") {:.info}
创建类的实例
要创建一个类的实例,我们就像普通函数一样调用构造函数:val invoice = Invoice() val customer = Customer("Joe Smith")
注意 Kotlin 并没有 new 关键字。
创建嵌套类、内部类和匿名内部类的类实例在嵌套类中有述。
类成员
类可以包含构造函数和初始化块
函数
属性
嵌套类和内部类
对象声明
继承
在 Kotlin 中所有类都有一个共同的超类 Any ,这对于没有超类型声明的类是默认超类:class Example // 从 Any 隐式继承
Any 不是 java.lang.Object ;尤其是它除了 equals() 、 hashCode() 和 toString() 外没有任何成员。 更多细节请查阅Java互操作性部分。
要声明一个显式的超类型,我们把类型放到类头的冒号之后:
open class Base(p: Int) class Derived(p: Int) : Base(p)
如果该类有一个主构造函数,其基类型可以(并且必须) 用(基类型的)主构造函数参数就地初始化。
如果类没有主构造函数,那么每个次构造函数必须 使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点。 注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数:
class MyView : View { constructor(ctx: Context) : super(ctx) constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) }
类上的 open 标注与 Java 中 final 相反,它允许其他类 从这个类继承。默认情况下,在Kotlin 中所有的类都是 final, 对应于 Effective Java书中的 第 17 条:要么为继承而设计,并提供文档说明,要么就禁止继承。
覆盖方法
我们之前提到过,Kotlin 力求清晰显式。与 Java 不同,Kotlin 需要显式 标注可覆盖的成员(我们称之为开放)和覆盖后的成员:open class Base { open fun v() {} fun nv() {} } class Derived() : Base() { override fun v() {} }
Derived.v() 函数上必须加上 override标注。如果没写,编译器将会报错。 如果函数没有标注open 如 Base.nv() ,则子类中不允许定义相同签名的函数, 不论加不加 override。在一个final 类中(没有用 open 标注的类),开放成员是禁止的。
标记为 override 的成员本身是开放的,也就是说,它可以在子类中覆盖。如果你想禁止再次覆盖,使用 final 关键字:
open class AnotherDerived() : Base() { final override fun v() {} }
覆盖属性
属性覆盖与方法覆盖类似;在超类中声明然后在派生类中重新声明的属性必须以 override开头,并且它们必须具有兼容的类型。每个声明的属性可以由具有初始化器的属性或者具有getter 方法的属性覆盖。open class Foo { open val x: Int get { …… } } class Bar1 : Foo() { override val x: Int = …… }
你也可以用一个 var 属性覆盖一个 val 属性,但反之则不行。这是允许的,因为一个val 属性本质上声明了一个 getter 方法,而将其覆盖为 var 只是在子类中额外声明一个setter 方法。
请注意,你可以在主构造函数中使用 override 关键字作为属性声明的一部分。
interface Foo { val count: Int } class Bar1(override val count: Int) : Foo class Bar2 : Foo { override var count: Int = 0 }
覆盖规则
在 Kotlin 中,实现继承由下述规则规定:如果一个类从它的直接超类继承相同成员的多个实现, 它必须覆盖这个成员并提供其自己的实现(也许用继承来的其中之一)。 为了表示采用从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 super ,如 super :open class A { open fun f() { print("A") } fun a() { print("a") } } interface B { fun f() { print("B") } // 接口成员默认就是“open”的 fun b() { print("b") } } class C() : A(), B { // 编译器要求覆盖 f(): override fun f() { super<A>.f() // 调用 A.f() super<B>.f() // 调用 B.f() } }
同时继承 A 和 B 没问题,并且 a() 和 b() 也没问题因为 C 只继承了每个函数的一个实现。 但是 f() 由 C 继承了两个实现,所以我们必须在 C 中覆盖 f() 并且提供我们自己的实现来消除歧义。
抽象类
类和其中的某些成员可以声明为 abstract 。 抽象成员在本类中可以不用实现。 需要注意的是,我们并不需要用 open 标注一个抽象类或者函数——因为这不言而喻。
我们可以用一个抽象成员覆盖一个非抽象的开放成员
open class Base { open fun f() {} } abstract class Derived : Base() { override abstract fun f() }
伴生对象
与 Java 或 C# 不同,在 Kotlin 中类没有静态方法。在大多数情况下,它建议简单地使用包级函数。如果你需要写一个可以无需用一个类的实例来调用、但需要访问类内部的 函数(例如,工厂方法),你可以把它写成该类内对象声明 中的一员。更具体地讲,如果在你的类内声明了一个伴生对象, 你就可以使用像在 Java/C# 中调用静态方法相同的语法来调用其成员,只使用类名作为限定符。
属性和字段
声明属性
Kotlin的类可以有属性。 属性可以用关键字 var 声明为可变的,否则使用只读关键字 val 。class Address { var name: String = …… var street: String = …… var city: String = …… var state: String? = …… var zip: String = …… }
要使用一个属性,只要用名称引用它即可,就像 Java 中的字段:
fun copyAddress(address: Address): Address { val result = Address() // Kotlin 中没有“new”关键字 result.name = address.name // 将调用访问器 result.street = address.street // …… return result }
Getters 和 Setters
声明一个属性的完整语法是var <propertyName>[: <PropertyType>] [= <property_initializer>] [<getter>] [<setter>]
其初始器(initializer)、getter 和 setter 都是可选的。属性类型如果可以从初始器 (或者从其 getter 返回值,如下文所示)中推断出来,也可以省略。
例如:
var allByDefault: Int? // 错误:需要显式初始化器,隐含默认 getter 和 setter var initialized = 1 // 类型 Int、默认 getter 和 setter
一个只读属性的语法和一个可变的属性的语法有两方面的不同:
只读属性的用 val 开始代替 var
只读属性不允许 setter
val simple: Int? // 类型 Int、默认 getter、必须在构造函数中初始化 val inferredType = 1 // 类型 Int 、默认 getter
我们可以编写自定义的访问器,非常像普通函数,刚好在属性声明内部。这里有一个自定义getter 的例子:
val isEmpty: Boolean get() = this.size == 0 一个自定义的 setter 的例子: var stringRepresentation: String get() = this.toString() set(value) { setDataFromString(value) // 解析字符串并赋值给其他属性 }
按照惯例,setter 参数的名称是 value ,但是如果你喜欢你可以选择一个不同的名称。
自 Kotlin 1.1 起,如果可以从 getter 推断出属性类型,则可以省略它:
val isEmpty get() = this.size == 0 // 具有类型 Boolean
如果你需要改变一个访问器的可见性或者对其注解,但是不需要改变默认的实现, 你可以定义访问器而不定义其实现:
var setterVisibility: String = "abc" private set // 此 setter 是私有的并且有默认实现 var setterWithAnnotation: Any? = null @Inject set // 用 Inject 注解此 setter
幕后字段
Kotlin 中类不能有字段。然而,当使用自定义访问器时,有时有一个幕后字段(backing field)有时是必要的。为此 Kotlin 提供一个自动幕后字段,它可通过使用 field 标识符访问。var counter = 0 // 此初始器值直接写入到幕后字段 set(value) { if (value >= 0) field = value }
field 标识符只能用在属性的访问器内。如果属性至少一个访问器使用默认实现,或者自定义访问器通过 field 引用幕后字段,将会为该属性生成一个幕后字段。
例如,下面的情况下, 就没有幕后字段:
val isEmpty: Boolean get() = this.size == 0
幕后属性
如果你的需求不符合这套“隐式的幕后字段”方案,那么总可以使用 幕后属性(backing property):private var _table: Map<String, Int>? = null public val table: Map<String, Int> get() { if (_table == null) { _table = HashMap() // 类型参数已推断出 } return _table ?: throw AssertionError("Set to null by another thread") }
从各方面看,这正是与 Java 相同的方式。因为通过默认 getter 和 setter 访问私有属性会被优化,所以不会引入函数调用开销。
编译期常量
已知值的属性可以使用 const 修饰符标记为 编译期常量。 这些属性需要满足以下要求:位于顶层或者是 object 的一个成员
用 String 或原生类型 值初始化
没有自定义 getter
这些属性可以用在注解中:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated" @Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { …… }
惰性初始化属性
一般地,属性声明为非空类型必须在构造函数中初始化。 然而,这经常不方便。例如:属性可以通过依赖注入来初始化, 或者在单元测试的 setup 方法中初始化。 这种情况下,你不能在构造函数内提供一个非空初始器。 但你仍然想在类体中引用该属性时避免空检查。为处理这种情况,你可以用 lateinit 修饰符标记该属性:public class MyTest { lateinit var subject: TestSubject @SetUp fun setup() { subject = TestSubject() } @Test fun test() { subject.method() // 直接解引用 } }
该修饰符只能用于在类体中(不是在主构造函数中)声明的 var 属性,并且仅当该属性没有自定义 getter 或 setter 时。该属性必须是非空类型,并且不能是原生类型。
在初始化前访问一个 lateinit 属性会抛出一个特定异常,该异常明确标识该属性被访问及它没有初始化的事实。
委托属性
最常见的一类属性就是简单地从幕后字段中读取(以及可能的写入)。另一方面,使用自定义 getter 和 setter 可以实现属性的任何行为。 介于两者之间,属性如何工作有一些常见的模式。一些例子:惰性值、10498
通过键值从映射读取、访问数据库、访问时通知侦听器等等。这些常见行为可以通过使用委托属性实现为库。
接口
Kotlin 的接口与 Java 8 类似,既包含抽象方法的声明,也包含实现。与抽象类不同的是,接口无法保存状态。它可以有属性但必须声明为抽象或提供访问器实现。使用关键字 interface 来定义接口
interface MyInterface { fun bar() fun foo() { // 可选的方法体 } }
实现接口
一个类或者对象可以实现一个或多个接口。class Child : MyInterface { override fun bar() { // 方法体 } }
接口中的属性
你可以在接口中定义属性。在接口中声明的属性要么是抽象的,要么提供 访问器的实现。在接口中声明的属性不能有幕后字段(backing field),因此接口中声明的访问器不能引用它们。interface MyInterface { val prop: Int // 抽象的 val propertyWithImplementation: String get() = "foo" fun foo() { print(prop) } } class Child : MyInterface { override val prop: Int = 29 }
解决覆盖冲突
实现多个接口时,可能会遇到同一方法继承多个实现的问题。例如interface A { fun foo() { print("A") } fun bar() } interface B { fun foo() { print("B") } fun bar() { print("bar") } } class C : A { override fun bar() { print("bar") } } class D : A, B { override fun foo() { super<A>.foo() super<B>.foo() } override fun bar() { super<B>.bar() } }
上例中,接口 A 和 B 都定义了方法 foo() 和 bar()。 两者都实现了 foo(), 但是只有 B 实现了bar() (bar() 在 A 中没有标记为抽象, 因为没有方法体时默认为抽象)。因为 C 是一个实现了A 的具体类,所以必须要重bar() 并实现这个抽象方法。
然而,如果我们从 A 和 B 派生 D,我们需要实现 我们从多个接口继承的所有方法,并指明 D应该如何实现它们。这一规则 既适用于继承单个实现(bar())的方法也适用于继承多个实现(foo())的方法。
可见性修饰符
类、对象、接口、构造函数、方法、属性和它们的 setter 都可以有可见性修饰符。 (getter总是与属性有着相同的可见性。) 在 Kotlin 中有这四个可见性修饰符: private 、protected 、 internal 和 public 。 如果没有显式指定修饰符的话,默认可见性是public 。下面将根据声明作用域的不同来解释。
包名
函数、属性和类、对象和接口可以在顶层声明,即直接在包内:
// 文件名:example.kt package foo fun baz() {} class Bar {}
如果你不指定任何可见性修饰符,默认为 public ,这意味着你的声明 将随处可见;
如果你声明为 private ,它只会在声明它的文件内可见;
如果你声明为 internal ,它会在相同模块内随处可见;
protected 不适用于顶层声明。
例如:
// 文件名:example.kt package foo private fun foo() {} // 在 example.kt 内可见 public var bar: Int = 5 // 该属性随处可见 private set // setter 只在 example.kt 内可见 internal val baz = 6 // 相同模块内可见
类和接口
对于类内部声明的成员:private 意味着只在这个类内部(包含其所有成员)可见;
protected —— 和 private 一样 + 在子类中可见。
internal —— 能见到类声明的本模块内的任何客户端都可见其 internal 成员;
public —— 能见到类声明的任何客户端都可见其 public 成员。
注意 对于Java用户:Kotlin 中外部类不能访问内部类的 private 成员。
如果你覆盖一个 protected 成员并且没有显式指定其可见性,该成员还会是 protected 可见性。
例子:
open class Outer { private val a = 1 protected open val b = 2 internal val c = 3 val d = 4 // 默认 public protected class Nested { public val e: Int = 5 } }
class Subclass : Outer() { // a 不可见 // b、c、d 可见 // Nested 和 e 可见 override val b = 5 // “b”为 protected } class Unrelated(o: Outer) { // o.a、o.b 不可见 // o.c 和 o.d 可见(相同模块) // Outer.Nested 不可见,Nested::e 也不可见 }
构造函数
要指定一个类的的主构造函数的可见性,使用以下语法(注意你需要添加一个 显式constructor 关键字):class C private constructor(a: Int) { …… }
这里的构造函数是私有的。默认情况下,所有构造函数都是 public ,这实际上等于类可见的地方它就可见(即 一个 internal 类的构造函数只能 在相同模块内可见).
局部声明
局部变量、函数和类不能有可见性修饰符。模块
可见性修饰符 internal 意味着该成员只在相同模块内可见。更具体地说, 一个模块是编译在一起的一套 Kotlin 文件:一个 IntelliJ IDEA 模块;
一个 Maven 或者 Gradle 项目;
一次 <kotlinc> Ant 任务执行所编译的一套文件。
扩展
Kotlin 同 C# 和 Gosu 类似,能够扩展一个类的新功能而无需继承该类或使用像装饰者这样的任何类型的设计模式。 这通过叫做扩展的特殊声明完成.Kotlin 支持扩展函数和扩展属性。扩展函数
声明一个扩展函数,我们需要用一个接收者类型也就是被扩展的类型来作为他的前缀。 下面代码为 MutableList 添加一个 swap 函数:fun MutableList<Int>.swap(index1: Int, index2: Int) { val tmp = this[index1] // “this”对应该列表 this[index1] = this[index2] this[index2] = tmp }
这个 this 关键字在扩展函数内部对应到接收者对象(传过来的在点符号前的对象) 现在,我们对任意 MutableList 调用该函数了:
val l = mutableListOf(1, 2, 3) l.swap(0, 2) // “swap()”内部的“this”得到“l”的值
当然,这个函数对任何 MutableList 起作用,我们可以泛化它:
fun <T> MutableList<T>.swap(index1: Int, index2: Int) { val tmp = this[index1] // “this”对应该列表 this[index1] = this[index2] this[index2] = tmp }
为了在接收者类型表达式中使用泛型,我们要在函数名前声明泛型参数。
扩展是静态解析的
扩展不能真正的修改他们所扩展的类。通过定义一个扩展,你并没有在一个类中插入新成员, 仅仅是可以通过该类型的变量用点表达式去调用这个新函数。我们想强调的是扩展函数是静态分发的,即他们不是根据接收者类型的虚方法。 这意味着调用的扩展函数是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的。例如:
open class C class D: C() fun C.foo() = "c" fun D.foo() = "d" fun printFoo(c: C) { println(c.foo()) } printFoo(D())
这个例子会输出 “c”,因为调用的扩展函数只取决于参数 c 的声明类型,该类型是 C 类。
如果一个类定义有一个成员函数和一个扩展函数,而这两个函数又有相同的接收者类型、相同的名字并且都适用给定的参数,这种情况总是取成员函数。 例如:
class C { fun foo() { println("member") } } fun C.foo() { println("extension") }
如果我们调用 C 类型 c 的 c.foo() ,它将输出“member”,而不是“extension”。当然,扩展函数重载同样名字但不同签名成员函数也完全可以:
class C { fun foo() { println("member") } } fun C.foo(i: Int) { println("extension") }
调用 C().foo(1) 将输出 “extension”。
可空接收者
注意可以为可空的接收者类型定义扩展。这样的扩展可以在对象变量上调用, 即使其值为null,并且可以在函数体内检测 this == null ,这能让你 在没有检测 null 的时候调用 Kotlin中的toString():检测发生在扩展函数的内部。
fun Any?.toString(): String { if (this == null) return "null" // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString() // 解析为 Any 类的成员函数 return toString() }
扩展属性
和函数类似,Kotlin 支持扩展属性:val <T> List<T>.lastIndex: Int get() = size - 1
注意:由于扩展没有实际的将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的 getters/setters 定义。
例如:
val Foo.bar = 1 // 错误:扩展属性不能有初始化器
伴生对象的扩展
如果一个类定义有一个伴生对象 ,你也可以为伴生对象定义 扩展函数和属性:class MyClass { companion object { } // 将被称为 "Companion" } fun MyClass.Companion.foo() { // …… }
就像伴生对象的其他普通成员,只需用类名作为限定符去调用他们MyClass.foo()
扩展的作用域
大多数时候我们在顶层定义扩展,即直接在包里:package foo.bar fun Baz.goo() { …… }
要使用所定义包之外的一个扩展,我们需要在调用方导入它:
package com.example.usage import foo.bar.goo // 以名字 "goo" 导入所有扩展 // 或者 import foo.bar.* // 从 "foo.bar" 导入一切 fun usage(baz: Baz) { baz.goo() )
扩展声明为成员
在一个类内部你可以为另一个类声明扩展。在这样的扩展内部,有多个 隐式接收者 —— 其中的对象成员可以无需通过限定符访问。扩展声明所在的类的实例称为 分发接收者,扩展方法调用所在的接收者类型的实例称为扩展接收者 。class D { fun bar() { …… } } class C { fun baz() { …… } fun D.foo() { bar() // 调用 D.bar baz() // 调用 C.baz } fun caller(d: D) { d.foo() // 调用扩展函数 } }
对于分发接收者和扩展接收者的成员名字冲突的情况,扩展接收者 优先。要引用分发接收者的成员你可以使用 限定的 this 语法。
class C { fun D.foo() { toString() // 调用 D.toString() this@C.toString() // 调用 C.toString() }
声明为成员的扩展可以声明为 open 并在子类中覆盖。这意味着这些函数的分发对于分发接收者类型是虚拟的,但对于扩展接收者类型是静态的。
open class D { } class D1 : D() { } open class C { open fun D.foo() { println("D.foo in C") } open fun D1.foo() { println("D1.foo in C") } fun caller(d: D) { d.foo() // 调用扩展函数 } } class C1 : C() { override fun D.foo() { println("D.foo in C1") } override fun D1.foo() { println("D1.foo in C1") } } C().caller(D()) // 输出 "D.foo in C" C1().caller(D()) // 输出 "D.foo in C1" —— 分发接收者虚拟解析 C().caller(D1()) // 输出 "D.foo in C" —— 扩展接收者静态解析
动机
在Java中,我们将类命名为“*Utils”: FileUtils 、 StringUtils 等,著名的java.util.Collections 也属于同一种命名方式。 关于这些 Utils-类的不愉快的部分是代码写成这样:// Java Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Col lections.max(list))
这些类名总是碍手碍脚的,我们可以通过静态导入达到这样效果:
// Java swap(list, binarySearch(list, max(otherList)), max(list))
这会变得好一点,但是我们并没有从 IDE 强大的自动补全功能中得到帮助。如果能这样就更好了:
// Java list.swap(list.binarySearch(otherList.max()), list.max())
但是我们不希望在 List 类内实现这些所有可能的方法,对吧?这时候扩展将会帮助我们。
数据类
我们经常创建一些只保存数据的类。在这些类中,一些标准函数往往是从 数据机械推导而来的。在 Kotlin 中,这叫做数据类并标记为 data :data class User(val name: String, val age: Int)
编译器自动从主构造函数中声明的所有属性导出以下成员:
equals() / hashCode() 对,
toString() 格式是 “User(name=John, age=42)” ,
componentN() 函数 按声明顺序对应于所有属性,
copy() 函数(见下文)。
如果这些函数中的任何一个在类体中显式定义或继承自其基类型,则不会生成该函数。
为了确保生成的代码的一致性和有意义的行为,数据类必须满足以下要求:
主构造函数需要至少有一个参数;
主构造函数的所有参数需要标记为 val 或 var ;
数据类不能是抽象、开放、密封或者内部的;
(在1.1之前)数据类只能实现接口。
自 1.1 起,数据类可以扩展其他类(示例请参见密封类)。
在 JVM 中,如果生成的类需要含有一个无参的构造函数,则所有的属性必须指定默认值。
data class User(val name: String = "", val age: Int = 0)
复制
在很多情况下,我们需要复制一个对象改变它的一些属性,但其余部分保持不变。 copy()函数就是为此而生成。对于上文的 User 类,其实现会类似下面这样:fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
这让我们可以写
val jack = User(name = "Jack", age = 1) val olderJack = jack.copy(age = 2)
- 数据类和解构声明
为数据类生成的 Component 函数 使它们可在解构声明中使用:val jane = User("Jane", 35) val (name, age) = jane println("$name, $age years of age") // 输出 "Jane, 35 years of age"
标准数据类
标准库提供了 Pair 和 Triple 。尽管在很多情况下命名数据类是更好的设计选择, 因为它们通过为属性提供有意义的名称使代码更具可读性。相关文章推荐
- Kotlin官方文档翻译,类和对象:类和继承
- kotlin官方文档中文翻译(一)基础语法,习惯用法,编码习惯
- Spark 大规模机器学习官方文档 - 中文翻译
- Redis 3.0中文官方文档翻译和源码解析
- keepalived配置参数官方文档中文翻译版
- 【Netty官方文档翻译】引用计数对象(reference counted objects)
- 上传组件SWFUpload 2.5.0版 官方说明文档 中文翻译版
- django 1.8 官方文档翻译: 3-3-2 File对象
- Spark官方文档 - 中文翻译(1.6)
- TensorFlow 官方文档 Programmer's Guide 中文翻译 —— 引言
- Spark SQL 官方文档-中文翻译
- Spring Boot中文文档(官方文档翻译 基于1.5.2.RELEASE)
- hadoop2官方文档中文翻译(2)---单节点配置
- SWFUpload 2.5.0版 官方说明文档 中文翻译版
- wordcloud制作中文词云图(官方文档参数翻译)
- Redis 3.0中文官方文档翻译计划(5) ——从入门到精通(下)
- SPARK官方文档中文翻译
- Applying Styles and Themes - 应用Style和Theme - Android官方文档中文翻译
- Android camera2官方API文档中文翻译
- Python3.2官方文档翻译--实例对象和方法对象