kotlin_kotlinx - CSDN
kotlin 订阅
Kotlin (科特林)是一个用于现代多平台应用的静态编程语言 [1]  ,由 JetBrains 开发。Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行。除此之外Kotlin还可以编译成二进制代码直接运行在机器上(例如嵌入式设备或 iOS)。 [1]  Kotlin已正式成为Android官方支持开发语言。 展开全文
Kotlin (科特林)是一个用于现代多平台应用的静态编程语言 [1]  ,由 JetBrains 开发。Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行。除此之外Kotlin还可以编译成二进制代码直接运行在机器上(例如嵌入式设备或 iOS)。 [1]  Kotlin已正式成为Android官方支持开发语言。
信息
发布时间
2016年2月15日
语言版本
1.3.72
开发公司
JetBrains
推出时间
2011年7月
中文名
科特林
性    质
编程语言
外文名
Kotlin
开源协议
Apache 2
Kotlin简介
2011年7月,JetBrains推出baiKotlin项目,这是一个面向duJVM的新语言 [2]  ,它已被开发一年之久。JetBrains负责人Dmitry Jemerov说,大多数语言没有他们正在寻找的特性,Scala除外。但是,他指出了Scala的编译时间慢这一明显缺陷。Kotlin的既定目标之一是像Java一样快速编译。 [1]  2012年2月,JetBrains以Apache 2许可证开源此项目。 [2]  Jetbrains希望这个新语言能够推动IntelliJ IDEA的销售。 [3]  Kotlin v1.0于2016年2月15日发布。这被认为是第一个官方稳定版本,并且JetBrains已准备从该版本开始的长期向后兼容性。 [2]  在Google I/O 2017中,Google宣布在Android上为Kotlin提供一等支持。 [2] 
收起全文
精华内容
参与话题
  • Kotlin基本语法到Android实战开发

    千人学习 2018-10-22 21:38:11
    第一阶段:Kotlin的基本语法,包括类和方法的定义及调用、数组、条件判断、循环控制、接口定义及实现、泛型等等; 第二阶段:用Kotlin创建Android项目,运用kotlin初始化界面、控件、网络请求及数据处理并展示等
  • 抛弃 Java 改用 Kotlin 的六个月后,我后悔了

    万次阅读 多人点赞 2018-06-20 14:51:20
    点击上方“CSDN”,选择“置顶公众号”关键时刻,第一时间送达!毫无疑问,Kotlin 目前很受欢迎,业界甚至有人认为其将取代 Java 的霸主地位。它提供了 Null ...
        

    点击上方“CSDN”,选择“置顶公众号”

    关键时刻,第一时间送达!

    毫无疑问,Kotlin 目前很受欢迎,业界甚至有人认为其将取代 Java 的霸主地位。它提供了 Null 安全性,从这一点来说它确实比 Java 更好。那么是不是这就意味着开发者应该毫不犹豫地拥抱 Kotlin,否则就落伍了?

    640?wx_fmt=jpeg

    等等,或许事情并非如此。

    在开始使用 Kotlin 编程之前,本文想要分享个故事给你。在这个故事中,作者最早使用 Kotlin 来编写一个项目,后来 Kotlin 的各种怪异模式以及一些其他障碍越来越让人厌烦,最终,他们决定重写这个项目。

    以下为译文:

    一直以来,我对基于 JVM 的语言都非常情有独钟。我通常会用 Java 来编写主程序,再用 Groovy 编写测试代码,两者配合使用得心应手。

    2017年夏天,团队发起了一个新的微服务项目,和往常一样,我们需要对编程语言和技术进行选型。部分团队成员是 Kotlin 的拥护者,再加上我们都想尝试一下新的东西,于是我们决定用 Kotlin 来开发这个项目。由于 Spock 测试框架不支持 Kotlin,因此我们决定坚持使用 Groovy 来测试。

    2018年春天,使用 Kotlin 开发几个月之后,我们总结了 Kotlin 的优缺点,最终结论表明 Kotlin 降低了我们的生产力。

    于是我们使用 Java 来重写这个微服务项目。

    那么 Kotlin 主要存在哪些弊端?下面来一一解释。

    名称遮蔽

    这是 Kotlin 最让我震惊的地方。看看下面这个方法:

    fun inc(num : Int) {
        val num = 2
        if (num > 0) {
            val num = 3
        }
        println ("num: " + num)
    }

    当你调用 inc(1) 会输出什么呢?在 Kotlin 中, 方法的参数无法修改,因此在本例中你不能改变 num。这个设计很好,因为你不应该改变方法的输入参数。但是你可以用相同的名称定义另一个变量并对其进行初始化。

    这样一来,这个方法作用域中就有两个名为 num 的变量。当然,你一次只能访问其中一个 num,但是 num 值会被改变。

    在 if 语句中再添加另一个 num,因为作用域的原因 num 并不会被修改。

    于是,在 Kotlin 中,inc(1) 会输出 2。同样效果的 Java 代码如下所示,不过无法通过编译: 

    void inc(int num{
        int num = 2//error: variable 'num' is already defined in the scope
        if (num > 0) {
            int num = 3//error: variable 'num' is already defined in the scope
        }
        System.out.println ("num: " + num);
    }

    名字遮蔽并不是 Kotlin 发明的,这在编程语言中很常见。在 Java 中我们习惯用方法参数来映射类字段:

    public class Shadow {
        int val;
        public Shadow(int val) {
            this.val = val;
        }
    }

    在 Kotlin 中名称遮蔽有些严重,这是 Kotlin 团队的一个设计缺陷。

    IDEA 团队试图通过向每个遮蔽变量显示警告信息来解决这个问题。两个团队在同一家公司工作,或许他们可以互相交流并就遮蔽问题达成共识。我从个人角度赞成 IDEA 的做法因为我想不到有哪些应用场景需要遮蔽方法参数。

    类型推断

    在Kotlin中,当你声明一个var或是val,你通常会让编译器从右边的表达式类型中猜测变量类型。我们称之为局部变量类型推断,这对程序员来说是一个很大的改进。它允许我们在不影响静态类型检查的情况下简化代码。

    例如,这个Kotlin代码:

    var a = "10"

    Kotlin 编译器会将其翻译成: 

    var a : String = "10"

    Java 同样具备这个特性,Java 10中的类型推断示例如下:  

    var a = "10";

    实话实说,Kotlin 在这一点上确实更胜一筹。当然,类型推断还可应用在多个场景。关于 Java 10中的局部变量类型推断,点击以下链接了解更多:

    • https://medium.com/@afinlay/java-10-sneak-peek-local-variable-type-inference-var-3022016e1a2b

    Null 安全类型

    Null 安全类型是 Kotlin 的杀手级功能。

    这个想法很好,在 Kotlin 中,类型默认不可为空。如果你需要添加一个可为空的类型,可以像下列代码这样: 

    val a: String? = null      // ok
    val b: String = null       // compilation error

    假设你使用了可为空的变量但是并未进行空值检查,这在 Kotlin 将无法通过编译,比如:

    println (a.length)          // compilation error
    println (a?.length)         // fine, prints null
    println (a?.length ?: 0)    // fine, prints 0

    那么是不是如果你同时拥有不可为空和可为空的变量,就可以避免 Java 中最常见的 NullPointerException 异常吗?事实并没有想象的简单。

    当 Kotlin 代码必须调用 Java 代码时,事情会变得很糟糕,比如库是用 Java 编写的,我相信这种情况很常见。于是第三种类型产生了,它被称为平台类型。Kotlin 无法表示这种奇怪的类型,它只能从 Java 类型推断出来。 它可能会误导你,因为它对空值很宽松,并且会禁用 Kotlin 的 NULL 安全机制。

    看看下面这个 Java 方法:

    public class Utils {
        static String format(String text{
            return text.isEmpty() ? null : text;
        }
    }

    假如你想调用 format(String)。应该使用哪种类型来获得这个 Java 方法的结果呢?你有三个选择。

    第一种方法:你可以使用 String,代码看起来很安全,但是会抛出 NullPointerException 异常

    fun doSth(text: String) {
        val f: String = Utils.format(text)       // compiles but assignment can throw NPE at runtime
        println ("f.len : " + f.length)
    }

    那你就需要用 Elvis 来解决这个问题:

    fun doSth(text: String) {
        val f: String = Utils.format(text) ?: ""  // safe with Elvis
        println ("f.len : " + f.length)
    }

    第二种方法:你可以使用 String,能够保证 Null 安全性。

    fun doSth(text: String) {
        val f: String? = Utils.format(text)   // safe
        println ("f.len : " + f.length)       // compilation error, fine
        println ("f.len : " + f?.length)      // null-safe with ? operator
    }

    第三种方法:让 Kotlin 做局部变量类型推断如何? 

    fun doSth(text: String) {
        val f = Utils.format(text)            // f type inferred as String!
        println ("f.len : " + f.length)       // compiles but can throw NPE at runtime
    }

    馊主意!这个 Kotlin 代码看起来很安全、可编译,但是它容忍了空值,就像在 Java 中一样。

    除此之外,还有另外一个方法,就是强制将 f 类型推断为 String:

    fun doSth(text: String) {
        val f = Utils.format(text)!!          // throws NPE when format() returns null
        println ("f.len : " + f.length)
    }

    在我看来,Kotlin 的所有这些类似 scala 的类型系统过于复杂。Java 互操作性似乎损害了 Kotlin 类型推断这个重量级功能。

    类名称字面常量

    使用类似 Log4j 或者 Gson 的 Java 库时,类文字很常见。

    Java 使用 .class 后缀编写类名: 

    Gson gson = new GsonBuilder().registerTypeAdapter(LocalDate.class, new LocalDateAdapter()).create();

    Groovy 把类进行了进一步的简化。你可以忽略 .class,它是 Groovy 或者 Java 类并不重要。

    def gson = new GsonBuilder().registerTypeAdapter(LocalDate, new LocalDateAdapter()).create()

    Kotlin 把 Kotlin 类和 Java 类进行了区分,并为其提供了语法规范:

    val kotlinClass : KClass<LocalDate> = LocalDate::class
    val javaClass : Class<LocalDate> = LocalDate::class.java

    因此在 Kotlin 中,你必须写成如下形式:

    val gson = GsonBuilder().registerTypeAdapter(LocalDate::class.javaLocalDateAdapter()).create()

    这看起来非常丑陋。

    反向类型声明

    C 系列的编程语言有标准的声明类型的方法。简而言之,首先指定一个类型,然后是该符合类型的东西,比如变量、字段、方法等等。

    Java 中的表示方法是:

    int inc(int i) {
        return i + 1;
    }

    Kotlin 中则是:

    fun inc(i: Int)Int {
        return i + 1
    }

    这种方法有几个原因令人讨厌。

    首先,你需要在名称和类型之间加入这个多余的冒号。这个额外角色的目的是什么?为什么名称与其类型要分离?我不知道。可悲的是,这让你在 Kotlin 的工作变得更加困难。

    第二个问题,当你读取一个方法声明时,你首先看到的是名字和返回类型,然后才是参数。

    在 Kotlin 中,方法的返回类型可能远在行尾,所以需要浏览很多代码才能看到: 

    private fun getMetricValue(kafkaTemplate : KafkaTemplate<String, ByteArray>, metricName : String) : Double {
        ...
    }

    或者,如果参数是逐行格式的,则需要搜索。那么我们需要多少时间才能找到此方法的返回类型呢?

    @Bean
    fun kafkaTemplate(
            @Value("\${interactions.kafka.bootstrap-servers-dc1}") bootstrapServersDc1: String,
            @Value("\${interactions.kafka.bootstrap-servers-dc2}") bootstrapServersDc2: String,
            cloudMetadata: CloudMetadata,
            @Value("\${interactions.kafka.batch-size}") batchSize: Int,
            @Value("\${interactions.kafka.linger-ms}") lingerMs: Int,
            metricRegistry : MetricRegistry
    )
    : KafkaTemplate<String, ByteArray> {
        val bootstrapServer = if (cloudMetadata.datacenter == "dc1") {
            bootstrapServersDc1
        }
        ...
    }

    第三个问题是 IDE 中的自动化支持不够好。标准做法从类型名称开始,并且很容易找到类型。一旦选择一个类型,IDE 会提供一些关于变量名的建议,这些变量名是从选定的类型派生的,因此你可以快速输入这样的变量: 

    MongoExperimentsRepository repository

    Kotlin 尽管有 IntelliJ 这样强大的 IDE,输入变量仍然是很难的。如果你有多个存储库,在列表中很难实现正确的自动补全,这意味着你不得不手动输入完整的变量名称。

    repository : MongoExperimentsRepository

    伴生对象

    一位 Java 程序员来到 Kotlin 面前。

    “嗨,Kotlin。我是新来的,我可以使用静态成员吗?"他问。

     “不行。我是面向对象的,静态成员不是面向对象的。” Kotlin 回答。

     “好吧,但我需要 MyClass 的 logger,我该怎么办?” 

    “这个没问题,使用伴生对象即可。”

     “那是什么东西?” “这是局限到你的类的单独对象。把你的 logger 放在伴生对象中。”Kotlin解释说。

     “我懂了。这样对吗?”

    class MyClass {
        companion object {
            val logger = LoggerFactory.getLogger(MyClass::class.java)
        }
    }

    “正确!”

     “很详细的语法,”程序员看起来很疑惑,“但是没关系,现在我可以像 MyClass.logger 这样调用我的 logger,就像 Java 中的一个静态成员?” 

    “嗯......是的,但它不是静态成员!这里只有对象。把它看作是已经实例化为单例的匿名内部类。事实上,这个类并不是匿名的,它的名字是 Companion,但你可以省略这个名字。看到了吗?这很简单。"

    我很欣赏对象声明的概念——单例很有用。但从语言中删除静态成员是不切实际的。在 Java 中我们使用静态 Logger 很经典,它只是一个 Logger,所以我们不关心面向对象的纯度。它能够工作,从来没有任何坏处。

    因为有时候你必须使用静态。旧版本 public static void main() 仍然是启动 Java 应用程序的唯一方式。

    class AppRunner {
        companion object {
            @JvmStatic fun main(args: Array<String>) {
                SpringApplication.run(AppRunner::class.java*args)
            }
        }
    }

    集合字面量

    在Java中,初始化列表非常繁琐:

    import java.util.Arrays;
    ...
    List<String> strings = Arrays.asList("Saab""Volvo");

    初始化地图非常冗长,很多人使用 Guava:

    import com.google.common.collect.ImmutableMap;
    ...
    Map<StringString> string = ImmutableMap.of("firstName""John""lastName""Doe");

    在 Java 中,我们仍然在等待新的语法来表达集合和映射。语法在许多语言中非常自然和方便。

    JavaScript:

    const list = ['Saab''Volvo']
    const map = {'firstName''John''lastName' : 'Doe'}

    Python:

    list = ['Saab''Volvo']
    map = {'firstName''John''lastName''Doe'}

    Groovy:

    def list = ['Saab''Volvo']
    def map = ['firstName''John''lastName''Doe']

    简单来说,集合字面量的整齐语法就是你对现代编程语言的期望,特别是如果它是从头开始创建的。Kotlin 提供了一系列内置函数,比如 listOf()、mutableListOf()、mapOf()、hashMapOf() 等等。

    Kotlin: 

    val list = listOf("Saab""Volvo")
    val map = mapOf("firstName" to "John""lastName" to "Doe")

    在地图中,键和值与 to 运算符配对,这很好。但为什么一直没有得到广泛使用呢?令人失望。

    Maybe

    函数式语言(比如 Haskell)没有空值。相反,他们提供 Maybe monad(如果你不熟悉monad,请阅读 Tomasz Nurkiewicz 的这篇文章:http://www.nurkiewicz.com/2016/06/functor-and-monad-examples-in-plain-java.html)。

    Maybe 很久以前就被 Scala 以 Option 引入到 JVM 世界,然后在 Java 8 中被采用为 Optional。如今,Optional 是在 API 边界处理返回类型中的空值的非常流行的方式。

    Kotlin 中没有 Optional 的等价物,所以你大概应该使用 Kotlin 的可空类型。让我们来调查一下这个问题。

    通常情况下,当你有一个 Optional 的时候,你想要应用一系列无效的转换。

    例如,在 Java 中: 

    public int parseAndInc(String number) {
        return Optional.ofNullable(number)
                       .map(Integer::parseInt)
                       .map(it -> it + 1)
                       .orElse(0);
    }

    在 Kotlin 中,为了映射你可以使用 let 函数:

    fun parseAndInc(number: String?)Int {
        return number.let { Integer.parseInt(it) }
                     .let { it -> it + 1 } ?: 0
    }

    上面的代码是错误的,parseInt() 会抛出 NPE 。map() 仅在有值时执行。否则,Null 就会跳过,这就是为什么 map() 如此方便。不幸的是,Kotlin 的 let 不会那样工作。它从左侧的所有内容中调用,包括空值。

    为了保证这个代码 Null 安全,你必须在每个代码之前添加 let: 

    fun parseAndInc(number: String?)Int {
        return number?.let { Integer.parseInt(it) }
                     ?.let { it -> it + 1 } ?: 0
    }

    现在,比较 Java 和 Kotlin 版本的可读性。你更倾向哪个?

    数据类

    数据类是 Kotlin 在实现 Value Objects 时使用的方法,以减少 Java 中不可避免的样板问题。

    例如,在 Kotlin 中,你只写一个 Value Object :

    data class User(val name: String, val age: Int)

    Kotlin 对 equals()、hashCode()、toString() 以及 copy() 有很好的实现。在实现简单的DTO 时它非常有用。但请记住,数据类带有严重的局限性。你无法扩展数据类或者将其抽象化,所以你可能不会在核心模型中使用它们。

    这个限制不是 Kotlin 的错。在 equals() 没有违反 Liskov 原则的情况下,没有办法产生正确的基于价值的数据。

    这也是为什么 Kotlin 不允许数据类继承的原因。

    开放类

    Kotlin 类默认为 final。如果你想扩展一个类,必须添加 open 修饰符。

    继承语法如下所示: 

    open class Base
    class Derived : Base()

    Kotlin 将 extends 关键字更改为: 运算符,该运算符用于将变量名称与其类型分开。那么再回到 C ++语法?对我来说这很混乱。

    这里有争议的是,默认情况下类是 final。也许 Java 程序员过度使用继承,也许应该在考虑扩展类之前考虑三次。但我们生活在框架世界,Spring 使用 cglib、jassist 库为你的 bean 生成动态代理。Hibernate 扩展你的实体以启用延迟加载。

    如果你使用 Spring,你有两种选择。你可以在所有 bean 类的前面添加 open,或者使用这个编译器插件: 

    buildscript {
        dependencies {
            classpath group: 'org.jetbrains.kotlin', name: 'kotlin-allopen', version: "$versions.kotlin"
        }
    }

    陡峭的学习曲线

    如果你认为自己有 Java 基础就可以快速学习 Kotlin,那你就错了。Kotlin 会让你陷入深渊,事实上,Kotlin 的语法更接近 Scala。这是一项赌注,你将不得不忘记 Java 并切换到完全不同的语言。

    相反,学习 Groovy 是一个愉快的过程。Java 代码是正确的 Groovy 代码,因此你可以通过将文件扩展名从 .java 更改为 .groovy。

    最后的想法

    学习新技术就像一项投资。我们投入时间,新技术让我们得到回报。但我并不是说 Kotlin 是一种糟糕的语言,只是在我们的案例中,成本远超收益。

    以上内容编译自 From Java to Kotlin and Back Again,作者 Kotlin ketckup。

    他是一名具有15年以上专业经验的软件工程师,专注于JVM 。在 Allegro,他是一名开发团队负责人,JaVers 项目负责人,Spock 倡导者。此外,他还是 allegro.tech/blog 的主编。

    本文一出就引发了业内的广泛争议,Kotlin 语言拥护者 Márton Braun 就表示了强烈的反对。

    Márton Braun 十分喜欢 Kotlin 编程,目前他在 StackOverflow 上 Kotlin 标签的最高用户列表中排名第三,并且是两个开源 Kotlin 库的创建者,最著名的是 MaterialDrawerKt。此外他还是 Autosoft 的 Android 开发人员,目前正在布达佩斯技术经济大学攻读计算机工程硕士学位。

    以下就是他针对上文的反驳:

    当我第一次看到这篇文章时,我就想把它转发出来看看大家会怎么想,我肯定它会是一个有争议的话题。后来我读了这篇文章,果然证明了它是一种主观的、不真实的、甚至有些居高临下的偏见。

    有些人已经在原贴下进行了合理的批评,对此我也想表达一下自己的看法。

    名称遮蔽

    “IDEA 团队”(或者 Kotlin 插件团队)和“Kotlin 团队”肯定是同样的人,我从不认为内部冲突会是个好事。语言提供这个功能给你,你需要的话就使用,如果讨厌,调整检查设置就是了。

    类型推断

    Kotlin 的类型推断无处不在,作者说的 Java 10 同样可以简直是在开玩笑。

    Kotlin 的方式超越了推断局部变量类型或返回表达式体的函数类型。这里介绍的这两个例子是那些刚刚看过关于 Kotlin 的第一次介绍性讲话的人会提到的,而不是那些花了半年学习该语言的人。

    例如,你怎么能不提 Kotlin 推断泛型类型参数的方式?这不是 Kotlin 的一次性功能,它深深融入了整个语言。

    编译时 Null 安全

    这个批评是对的,当你与 Java 代码进行互操作时,Null 安全性确实被破坏了。该语言背后的团队曾多次声明,他们最初试图使 Java 可为空的每种类型,但他们发现它实际上让代码变得更糟糕。

    Kotlin 不比 Java 更差,你只需要注意使用给定库的方式,就像在 Java 中使用它一样,因为它并没有不去考虑 Null 安全。如果 Java 库关心 Null 安全性,则它们会有许多支持注释可供添加。

    也许可以添加一个编译器标志,使每种 Java 类型都可以为空,但这对 Kotlin 团队来说不得不花费大量额外资源。

    类名称字面常量

    :: class 为你提供了一个 KClass 实例,以便与 Kotlin 自己的反射 API 一起使用,而:: class.java为你提供了用于 Java 反射的常规 Java 类实例。

    反向类型声明

    为了清楚起见,颠倒的顺序是存在的,这样你就可以以合理的方式省略显式类型。冒号只是语法,这在现代语言中是相当普遍的一种,比如 Scala、Swift 等。

    我不知道作者在使用什么 IntelliJ,但我使用的变量名称和类型都能够自动补全。对于参数,IntelliJ 甚至会给你提供相同类型的名称和类型的建议,这实际上比 Java 更好。

    伴生对象

    原文中说:

    有时候你必须使用静态。旧版本 public static void main() 仍然是启动 Java 应用程序的唯一方式。

    class AppRunner {
        companion object {
            @JvmStatic fun main(args: Array<String>) {
                SpringApplication.run(AppRunner::class.java*args)
            }
        }
    }

    实际上,这不是启动 Java 应用程序的唯一方式。你可以这样做:

     fun main(args:Array <String>){ SpringApplication.run(AppRunner :: class.java,* args)} 

    或者这样:

     fun main(args:Array <String>){ runApplication <AppRunner>(* args)}

    集合字面量

    你可以在注释中使用数组文字。但是,除此之外,这些集合工厂的功能非常简洁,而且它们是另一种“内置”到该语言的东西,而它们实际上只是库函数。

    你只是抱怨使用:进行类型声明。而且,为了获得它不必是单独的语言结构的好处,它只是一个任何人都可以实现的功能。

    Maybe

    如果你喜欢 Optional ,你可以使用它。 Kotlin 在 JVM 上运行。

    对于代码确实这有些难看。但是你不应该在 Kotlin 代码中使用 parseInt,而应该这样做(我不知道你使用该语言的 6 个月中为何错过这个)。你为什么要明确地命名一个 Lambda 参数呢?

    数据类

    原文中说:

    这个限制不是 Kotlin 的错。在 equals() 没有违反 Liskov 原则的情况下,没有办法产生正确的基于价值的数据。

    这就是为什么 Kotlin 不允许数据类继承的原因。

    我不知道你为什么提出这个问题。如果你需要更复杂的类,你仍然可以创建它们并手动维护它们的 equals、hashCode 等方法。数据类仅仅是一个简单用例的便捷方式,对于很多人来说这很常见。

    公开类

    作者再次鄙视了,对此我实在无话可说。

    陡峭的学习曲线

    作者认为学习 Kotlin 很难, 但是我个人并不这么认为。

    最后的想法

    从作者列举的例子中,我感觉他只是了解语言的表面。

    很难想象他对此有投入很多时间。

    原文:

    https://allegro.tech/2018/05/From-Java-to-Kotlin-and-Back-Again.html

    https://zsmb.co/on-from-java-to-kotlin-and-back-again/

    译者:安翔,责编:郭芮

      征稿啦!

    CSDN 公众号秉持着「与千万技术人共成长」理念,不仅以「极客头条」、「畅言」栏目在第一时间以技术人的独特视角描述技术人关心的行业焦点事件,更有「技术头条」专栏,深度解读行业内的热门技术与场景应用,让所有的开发者紧跟技术潮流,保持警醒的技术嗅觉,对行业趋势、技术有更为全面的认知。
    如果你有优质的文章,或是行业热点事件、技术趋势的真知灼见,或是深度的应用实践、场景方案等的新见解,欢迎联系 CSDN 投稿,联系方式:微信(guorui_1118,请备注投稿+姓名+公司职位),邮箱(guorui@csdn.net)。





    ————— 推荐阅读 —————

    点击图片即可阅读

    640?wx_fmt=png

    640?wx_fmt=png

    640?wx_fmt=png

    640?wx_fmt=png

    640?wx_fmt=gif

    展开全文
  • Kotlin基础与Android实战

    2020-01-08 10:24:43
    - 带你学习Google强推的新静态语言Kotlin - 通俗易懂的囊括Kotlin语言基础 - 通过所学的Kotlin进行Android开发 - 所使用的的技术点  - Kotlin  - Android项目实战  - Retrofit网络请求框架
  • Kotlin初探

    千次阅读 2017-04-26 17:56:42
    示例源码传送门前言Kotlin是一种在 Java虚拟机上执行的静态型别编程语言,它主要是由俄罗斯圣彼得堡的JetBrains开发团队所发展出来的编程语言。该语言有几个优势 1. 简洁 它大大减少你需要写的样板代码的数量。 ...

    示例源码传送门

    前言

    Kotlin是一种在 Java虚拟机上执行的静态型别编程语言,它主要是由俄罗斯圣彼得堡的JetBrains开发团队所发展出来的编程语言。该语言有几个优势
    1. 简洁
    它大大减少你需要写的样板代码的数量。
    2. 安全
    避免空指针异常等整个类的错误。
    3. 通用
    构建服务器端程序、Android 应用程序或者在浏览器中运行的前端程序。
    4. 互操作性
    通过 100% Java 互操作性,利用 JVM 既有框架和库。

    配置

    在我们的AndroidStudio开发工具中,要想使用Kotlin这个优秀的开发语言,我们需要安装插件,直接在安装插件界面搜索Kotlin然后安装。之后再gradle文件增加如下配置

    apply plugin:'kotlin-android'
    apply plugin:'kotlin-android-extensions'
    
    dependencies {
        compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    }
    

    项目gradle文件

    buildscript {
        ext.kotlin_version = '1.1.1'
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:2.3.1'
            classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    }

    完成上面的配置后,我们就可以愉快的玩耍了。

    Kotlin示例

    首先我们还和以前一样,创建一个Android项目,自动创建一个Activity之后我们再创建一个java类

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
    
            FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
            fab.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                            .setAction("Action", null).show();
                }
            });
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.menu_main, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            // Handle action bar item clicks here. The action bar will
            // automatically handle clicks on the Home/Up button, so long
            // as you specify a parent activity in AndroidManifest.xml.
            int id = item.getItemId();
    
            //noinspection SimplifiableIfStatement
            if (id == R.id.action_settings) {
                return true;
            }
    
            return super.onOptionsItemSelected(item);
        }
    }
    public class Test {
        private static String str = null;
    
        public static void main(String[] args) {
            str = "Code4Android";
            System.out.println(str);
        }
    }

    那上面的代码如果用kotlin实现是什么样子呢。尽管现在我们还不能写出Kotlin代码,但是在安装插件后AS中提供了自动转换Kotlin代码的功能

    这里写图片描述

    转换后的Kotlin代码

    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            val toolbar = findViewById(R.id.toolbar) as Toolbar
            setSupportActionBar(toolbar)
    
            val fab = findViewById(R.id.fab) as FloatingActionButton
            fab.setOnClickListener { view ->
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show()
            }
        }
    
        override fun onCreateOptionsMenu(menu: Menu): Boolean {
            // Inflate the menu; this adds items to the action bar if it is present.
            menuInflater.inflate(R.menu.menu_main, menu)
            return true
        }
    
        override fun onOptionsItemSelected(item: MenuItem): Boolean {
            val id = item.itemId
            if (id == R.id.action_settings) {
                return true
            }
            return super.onOptionsItemSelected(item)
        }
    }
    
    
    object Test {
        private var str: String? = null
    
        @JvmStatic fun main(args: Array<String>) {
            str = "Code4Android"
            println(str)
        }
    }
    

    注意:AS提供的java代码自动转换功能,我们不要轻易使用,更不要转化我们成熟的项目,如果需要就需要我们自己去重构实现。否则会有意向不到的事情等着你,毕竟转换不是那么智能。上面的代码只是让你先简单熟悉下Kotlin代码时什么样子的,接下来我们先去学习一下Kotlin的基本语法。相信很容易上手。

    Hello World!

    我们由一个简单的”Hello World!”输出程序开始。与新建java文件类似,如下图,我们选择Kotlin File/Class.创建一个Kotlin文件。

    这里写图片描述

    package com.learnrecord
    
    /**
     *Created by Code4Android on 2017/4/21.
     */
    
    var str: String = ""
    
    fun main(args: Array<String>) {
        str = "Hello World!"
        println(str)
    }

    上述代码就是简单的输出一个字符串“Hello World”,package 后面跟的是包名,我们看出了和java文件的区别,在包名后面没有以分号“;”结尾。在Kotlin语法中,语句结尾都不在有分号“;”。

    在Kotlin中变量声明有两种类型,val修饰变量是只读变量即只能赋值一次,再次赋值时就会编译错误
    ,如果我们需要多次修改值就需要使用var。在上面的 var str: String = “”中,str是变量名,:String,表明该变量是String类型变量,后面就是赋值语句。我们也可以这样写var str= “”省略了生命变量类型,它可以根据赋的值而自动推断出类型。如果我们使用下面赋值语句str=null,发现null是不能赋值的,这就是Kotlin的特性,如果我们想赋值null,可以修改为 var str:String?=”“。
    fun就是函数生命,而这个main函数就和我们java中的main方法一样,是程序执行的入口。println就是一个打印输出。

    Kotlin声明类型

    在Kotlin中有如下几种Number类型,他们都是继承自Number抽象类。
    Float(32位),Double(64),Int(32),Byte(8),Short(16),Long(64,类型用大写L,如12L),Any(任意类型),数组类型Array 根据传入的泛型数据自动匹配类型,Kotlin还提供了指定类型的Array,如ByteArray,CharArray,ShortArray,IntArray,LongArray,FloatArray,DoubleArray,BooleanArray。在数组类型中都提供了get(index),set(index,value)及iterator()方法供我们使用。

        val iArray: IntArray = intArrayOf(1, 2, 3)
        val sArray: Array<String> = Array<String>(3, { i -> i.toString() })
        val anyArray: Array<Any> = arrayOf(1, "2", 3.0, 4.1f) // 可将类型进行混排放入同一个数组中
        val lArray: LongArray = longArrayOf(1L, 2L, 3L)

    函数

    我们先来实现一个简单的数值求和的函数,通用实现方法如下

        fun sum(a: Int, b: Int): Int {
            return a + b
        }

    传入两个Int型数值,sum是函数名,括号后面的:Int表示该函数返回Int的值,函数体中对两个数字相加并返回。在Kotlin中表达式也可以作为函数体,编译器可以推断出返回类型,可以简化为

        fun sum(a: Int, b: Int) = a + b

    为了更好理解表达式可以作为函数体,我们可以创建一个函数获取两个数的最大值,如下:

      fun max1(a:Int,b:Int)=if (a>b) a else b

    需要注意的是若if后有多个表达式,如下

        fun max1(a:Int,b:Int)= if (a > b) {
            println(a)
            a
        } else {
            println(b)
            //如果我们将println(b)放到b的下面,运行会返回kotlin.Unit为无类型,返回值总是最后一个表达式的返回值或值
            b
        }
        println(max1(1,3))

    括号中的表达式顺序决定了返回的值及其类型。

    如果我们的方法体仅仅是打印字符串,并不返回值则

        fun printInt(a: Int): Unit {
            println(a)
        }

    Unit就类似我们java中的void,即没有返回值,此时我们可以省略

        fun printInt(a: Int) {
            println(a)
        }

    对于函数体,方法或者类等和java一样也有一些修饰符,如下

    • abstract //抽象类标示
    • final //标示类不可继承,默认属性
    • enum //标示类为枚举
    • open //类可继承,类默认是final的
    • annotation //注解类
    • private //仅在同一个文件中可见
    • protected //同一个文件中或子类可见,不可修饰类
    • public //所有调用的地方都可见
    • internal //同一个模块中可见,若类不加修饰符,则默认为该修饰符,作用域为同一个应用的所有模块,起保护作用,防止模块外被调用。

    操作符

    直接上代码如下

        //操作符  shl 下面对Int和Long
        var a: Int = 4
        var shl: Int = a shl (1)  //Java中的左移运算符 <<
        var shr: Int = a shr (1) //Java中的右移运算符 >>
        var ushr: Int = a ushr (3) //无符号右移,高位补0 >>>
        var and: Int = 2 and (4)   //按位与操作 &
        var or: Int = 2 or (4) //按位或操作 |
        var xor: Int = 2 xor (6)  //按位异或操作 ^
        print("\nshl:" + shl + "\nshr:" + shr + " \nushr:" + ushr + "\nand:" + and + "\nor:" + or + "\nxor:" + xor)

    输出信息为

    shl:8
    shr:2 
    ushr:0
    and0
    or:6
    xor:4

    在上面的部分操作符可达到逻辑操作符, 当我们使用Boolean时,or() 相当于 ||,and() 相当于 &&, xor() 当操作符两边相反时为true,否则为false ,not()时取反。

    数组遍历及控制语句

    遍历数组

        fun forLoop(array: Array<String>) {
            //第一种方式直接输出字符(类似java foreach)
            for (str in array) {
                println(str)
            }
            //Array提供了forEach函数
            array.forEach {
              println(it)
             }
            //array.indices是数组索引
            for (i in array.indices) {
                println(array[i])
            }
            //(类似javafor(int i=0;i<arry.length;i++))
            var i = 0
            while (i < array.size) {
                println(array[i++])
            }
        }

    使用when判断类型

    fun whenFun(obj: Any) {
            when (obj) {
                25 -> println("25")
                "Kotlin" -> println("Kotlin")
                !is String -> println("Not String")
                is Long -> println("Number is Long")
                else -> println("Nothing")
            }
        }

    is 和java中instanceof是一个作用判断是否为某个类型。!is即判断不是某个类型。

    //@定义label,一般用在内层循环中跳到外层循环:i in 0..2等价于java中 for(int i=0;i<=2;i++)效果
        loop@ for (i in 0..2) {
            for (j in 0..3) {
                println("i:" + i + "  j:" + j)
                if (j == 2)
                //continue@loop//跳到外层循环,继续往下执行
                    break@loop  //跳到外层循环label处,跳出改层循环
            }
        }

    倒序输出是downTo

        //倒序输出5 4 3 2 1 0
        for (i in 5 downTo 0) {
            println(i)
        }
        //设置输出数据步长
         for (i in 1..5 step 3) print(i) // 输出 14
         //step和downTo混合使用
         for (i in 5 downTo 1 step 3) print(i) //输出52

    类与枚举

    /*** constructor:构造函数
     * constructor无修饰符(如:private)时,constructor可以省略:
     * 当是无参构造函数时,整个构造函数部分也可以省略,省略的构造函数默认是public的
     * 由于primary constructor不能包含任何代码,因此使用 init 代码块对其初始化,
     * 同时可以在初始化代码块中使用构造函数的参数
     */
    open class People private constructor(var id: String, var name: String) {
        //可以类中初始化属性:
        var customName = name.toUpperCase() //初始化属性
        //次构造函数,使用constructor前缀声明,且必须调用primary constructor,使用this关键字
        constructor( id: String, name: String, age: Int) : this(id, name) {
            println("constructor")
        }
        init {
            println("初始化操作,可使用constructor参数")
        }
        //需要open修饰,子类才可以
        open fun study() {
            print("study")
        }
        //People前的冒号":"是继承的意思,实现类接口的时候也是冒号
    class Student(id: String, name: String) : People(id, name) {
        var test: Number = 3
        private var name1: String?
            get() {
                return name1
            }
            set(value) {
                name1 = value
            }
      //override修饰的方法,默认是可以被继承的。若希望不被继承,可以使用 final 关键词修饰
        override fun study() {
            super.study()
        }
    }
    
    }

    数据类用来保存数据,类似于POJO类,使用data关键词进行定义,编译器默认会为数据类生成以下四个方法

    • equals()
    • hashCode()
    • toString()
    • copy()
      通过数据类你会看到Kotlin的简洁性,我们创建一个Staff类,有String类型的name,position和泛型T(使用泛型仅仅是为了在Kotlin中接触以下泛型)
      java实现代码:
    public class StaffJ<T> {
        private String name;
        private String position;
        private T age;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getPosition() {
            return position;
        }
    
        public void setPosition(String position) {
            this.position = position;
        }
        public T getAge() {
            return age;
        }
        public void setAge(T age) {
            this.age = age;
        }
    }
    

    Kotlin数据类:

    data class Staff<T>(var name: String,  val position: String,var age:T)

    通过对比我们就看出了优点了,一行代码就实现了,具体使用

     var staff = Staff("code4Android","Android工程师","22")  //实例化对象

    要获取某个属性如获取名字staff.name,赋值就是staff.name=”code4Android2”,既然说了这样可以赋值,但是动手的小伙伴说为什么我敲的报错啊,如下

        staff.position="前端"

    编译报错了,在前面我们说过val修饰的属性只能赋值一次,那在这里val修饰的属性我们是不能再次赋值的。

    fun main(arg:Array<String>){
        var staff = Staff("code4Android","Android工程师","22")  //实例化对象
        staff.name="code4Android2"
        var staff1=staff.copy()
        //使用copy的时候可以指定默认值,可以指定任意个用逗号","隔开
        var staff2=staff.copy(name="ccc",position = "kotlin")
        println("name:${staff2.name} position:${staff2.position} age ${staff2.age}")
        //staff.position="Kotiln" //val不能再次赋值
        var anotherStaff= Staff("code4Android","Android工程师",22) //实例化对象
    
        println("staff toString(): ${staff.toString()} anotherStaff toString(): ${anotherStaff.toString()}")
        println("staff hashCode(): ${staff.hashCode()} anotherStaff hashCode(): ${anotherStaff.hashCode()}")
        println("staff is equals another staff ? ${staff.equals(anotherStaff)}")
    }

    上面使用了字符模板,在Kotlin中有两种字符模板形式,$<变量>、${<变量>}

        var name:String="Code4Android"
        println("我的名字叫$name")
        println("我的名字叫${name}")
    /**
     * java允许使用匿名内部类;kotlin也有类似的概念,称为对象表达式-object expressions
     */
    open class KeyBord{
        open fun onKeyEvent(code:Int):Unit = Unit
    }
    
    /**匿名内部类**/
    var key=object :KeyBord(){
        override open fun onKeyEvent(code:Int):Unit = Unit
    }

    枚举

    enum class Color{
        RED,BLACK,BLUE,GREEN,WHITE
    }
    fun display(){
        var color:Color=Color.BLACK
        Color.valueOf("BLACK") // 转换指定name为枚举值,若未匹配成功,会抛出IllegalArgumentException
        Color.values() //已数组的形式,返回枚举值
        println(color.name)获取枚举名称
        println(color.ordinal)//获取枚举值在所有枚举数组中定义的顺序,0开始
    }

    在Kotlin中枚举还支持方法。

    扩展

    /**
     * fun receiverType.functionName(params){
     *body
     *}
     * receiverType : 待扩展的类名
     * .            :修饰符为扩展符
     * functionName :为自定义的扩展函数名,
     * params       :为自定义的扩展函数参数,可为空
     * 扩展函数作用域,受函数的visibility modifiers影响
     * 扩展函数并没有对原类做修改,而是为被扩展类的对象添加新的函数。
     * 有一条规则,若扩展函数和类原有函数一致,则使用该函数时,会优先使用类本身的函数。
     */
    class Employee(var name: String) {
        fun print() {
            println("Employee")
        }
    }
    
    //扩展函数
    fun Employee.println() {
        print("println:Employee name is $name")
    }
    
    
    /**
     * 可以扩展一个空对象
     */
    fun Any?.toString1(): String {
        if (this == null)
            return "toString1:null"
        else {
            return "toString1" + toString()
        }
    }
    
    /**
     * 扩展属性
     * 由于扩展属性实际上不会向类添加新的成员,
     * 因此无法让一个扩展属性拥有一个后端域变量. 所以,对于扩展属性不允许存在初始化器.
     * 扩展属性的行为只能通过明确给定的取值方法与设值方法来定义,也就意味着扩展属性只
     * 能被声明为val而不能被声明为var.如果强制声明为var,即使进行了初始化,
     * 在运行也会报异常错误,提示该属性没有后端域变量。
     */
    val Employee.lastName: String
        get() {
            return "get:"+name
        }

    使用

    fun main(arg: Array<String>) {
        var employee = Employee("Code4Android")
        employee.print()
        employee.println()
        println(employee.toString1())
        println(employee.lastName)
    }

    代理

    **
     * 被代理接口
     */
    interface Base {
        fun display()
    }
    
    /**
     * 被代理类
     */
    open class BaseImpl : Base {
        override fun display() = print("just display me.")
    }
    
    /**
     * 代理类,使用:以及关键词by进行声明
     * 许代理属性和其他继承属性共用
     * class Drived(base: Base) : Base by base,BaseImpl()
     */
    class Drived(base: Base) : Base by base
    
    //使用
    fun show() {
        var b = BaseImpl()
        var drived = Drived(b)
        drived.display()
    
    }
    
    **
     * 代理类型:
     * 懒加载:Lazy
     * 观察者:Delegates.observable()
     * 非空属性:Delegates.notNull<>()
     */
    class Water {
    
        public var weight:Int by Delegates.notNull()
        /**
         * 代理属性
         */
        public val name: String by lazy {
            println("Lazy.......")
            "Code4Android"
        }
        public var value: String by Delegates.observable("init value") {
            d, old, new ->
            println("$d-->$old->$new")
        }
    }
    
    fun main(arg: Array<String>) {
        show()
        var water = Water()
        println(water.name)
        println(water.name)
        water.value = "11111"
        water.value = "22222"
        water.value = "33333"
        println(water.value)
        println(water.value)
        //必须先赋值否则IllegalStateException: Property weight should be initialized before get.
        water.weight=2
        print(water.weight)
    }

    操作符::

    val String.lastChar: Char
        get() = this[this.length - 1]
    
    class A(val p: Int)
       //1,反射得到运行时的类引用:
        val c = Student::class
        //2,函数引用
        fun isOdd(x: Int) = x % 2 != 0
        val numbers = listOf(1, 2, 3)
        println(numbers.filter(::isOdd)) 
    
        //3,属性引用(在此引用main函数主体外声明的变量)
        println(::x.get())
        ::x.set(2)
        println(x)
        //4,::x 表达式评估为 KProperty<Int> 类型的属性,它允许我们使用 get() 读它的值或者使用名字取回它的属性
        val prop = A::p
        println(prop.get(A(1))) 
    
        //5,对于扩展属性
        println(String::lastChar.get("abc")) 
    
        //6,与 java 反射调用
        println(A::p.javaClass),
        var f: Array<Field> = A::p.javaClass.declaredFields

    伴生对象

    伴生对象(companion object )类似于java中的静态关键字static。在Kotlin没有这个关键字,而是伴生对象,具体用法

    open class People constructor(var id: String,  var name: String){
        //可以类中初始化属性:
        var customName = name.toUpperCase() //初始化属性
    
        //使用constructor前缀声明,且必须调用primary constructor,使用this关键字
        constructor( id: String, name: String, age: Int) : this(id, name) {
            println("constructor")
        }
    
        init {
            println("初始化操作,可使用constructor参数")
        }
       //,Kotlin的class并不支持static变量,所以需要使用companion object来声明static变量,
       // 其实这个platformStatic变量也不是真正的static变量,而是一个伴生对象,
        companion object {
            val ID = 1
        }
    }

    使用的话直接People.ID。

    单例模式

    在Kotlin中使用object修饰类的时候,。该类是单例对象。

    /**
     * 使用object定义类,该类的实例即为单例,访问单例直接使用类名,不能通过构造函数进行访问,不允许有构造函数
     * Place.doSomething() // 访问单例对象
     */
    object Singleton {
        fun doSomething() {
            println("doSomething")
        }
    }
    
    
    /**
     * 实例化的时候,单例是懒加载,当使用的时候才去加载;而对象表达式是在初始化的地方去加载。
     *
     * 当在类内部使用 object 关键词定义对象时,允许直接通过外部类的类名访问内部对象进而访问其相关属性和方法,相当于静态变量
     * 可以使用companion修饰单例,则访问其属性或方法时,允许省略单例名
     * MyClass.doSomething() // 访问内部单例对象方法
     */
    class MyClass {
        companion object Singleton {
            fun doSomething() {
                println("doSomething")
            }
        }
    }

    好了,今天就介绍到这里,文中若有错误欢迎指出,Have a wonderful day.

    Kotlin英文官网
    Kotlin学习中文官网
    在线学习示例

    展开全文
  • Android快速转战Kotlin教程

    万次阅读 多人点赞 2019-08-03 16:44:15
    kotlin是啥?这里就不用多说了,想必看这篇文章的童鞋肯定是有所了解的。 那么这篇文章你可以收获什么? 答:本文主要通过本人如何从java转战到kotlin并应用在实际项目中的个人经历,给大家提供一些学习思路、...

    前言

    kotlin是啥?这里就不用多说了,想必看这篇文章的童鞋肯定是有所了解的。

    那么这篇文章你可以收获什么?

    答:本文主要通过本人如何从java转战到kotlin并应用在实际项目中的个人经历,给大家提供一些学习思路、学习方法以及一些学习资料和个人总结。

    前提:你的项目(包含个人项目)即将开始用到kotlin(没有项目作为依托你会缺少十足的动力,而且缺少应用场景乘热打铁那也是白学)
    建议:建议没有切换kotlin的小伙伴快来转战kotlin吧!最近一段时间搞了kotlin之后发现写起来确实比java爽多了,语法非常精简,而且据统计现已有30%安卓项目使用了kotlin,所以小伙伴们行动起来吧,这必定是大势所趋,可千万别被淘汰了啊

    入门

    俗话说万事开头难,不过我们先把Kotlin语法学习一遍,你就会发现so easy,而且语言思想都是相通的

    第一步:学习语法

    当然是去官网学习喽:http://kotlinlang.org/docs/reference/

    如下图:

    不过英文吃力的小伙伴可以去菜鸟教程网站学习

    地址:http://www.runoob.com/kotlin/kotlin-tutorial.html

    如下图:

    内容与官网一致。

    不过不能光看,一定要写,就算照着抄也要多写,尽量在学习时候多和java语法做对比,会印象深刻。
    如下图,本人的练习代码:

    第二步:对比学习

    大家可以参考下面的链接进行学习:

    from-java-to-kotlin : https://github.com/MindorksOpenSource/from-java-to-kotlin

    from-java-to-kotlin中给出了我们常用的语法对比

    如图:

    第三步:Demo练习

    通过上面的学习我们此刻已经熟悉了kotlin的基本语法,可以来尝试写一个万年历的Demo。

    1、新建工程

    我们新建一个工程,点击Include kotlin support
    如图:

    我们看一下Include kotlin support都帮我们做了什么事情

    首先module中gradle文件

    如图:

    比我们之前的工程多了下面两个引用和一个依赖:

    // 使用Kotlin插件
    apply plugin: 'kotlin-android'
    // 使用Kotlin Android扩展插件
    apply plugin: 'kotlin-android-extensions'
    
    dependencies {
        //...
        //添加Kotlin 标准库
        compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
        //...
    }
    

    知识点: kotlin-android-extensions相当于DataBinding,同样的目的为了偷懒不用写findViewByIdAndroid 开发必备。

    我们再看一下project中的gradle文件
    如图:

    比我们之前的工程多了Kotlin编译插件:

    // 添加了Kotlin编译插件
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    

    2、Demo说明

    该项目使用MVP模式,里面用到了Retrofit2+RxJava2,然后使用了聚合的万年历接口,Demo非常简单便于初学者快速掌握。

    Demo使用展示:

    工程目录结构如图:

    3、Activity

    看下布局文件非常简单,我们可以在activity里面直接将控件的id当成变量来使用

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        ....">
    
        <DatePicker
            android:id="@+id/dataPicker"
            .... />
    
        <Button
            android:id="@+id/selectButton"
            .... />
    
        <TextView
            android:id="@+id/titleTextView"
            .... />
    
        <TextView
            android:id="@+id/contentTextView"
           ....
           />
    
    </android.support.constraint.ConstraintLayout>
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    
        selectButton.setOnClickListener {
            titleTextView.visibility = View.GONE
            selectButton.visibility = View.GONE
            contentTextView.visibility = View.GONE
            dataPicker.visibility = View.VISIBLE
        }
        ....
        ....
    }
    

    注意:直接使用id作为变量的时候,要在Module的gradle里面加入扩展,才能使用,不然会报错

    apply plugin: 'kotlin-android-extensions'
    

    这个上面已经说过,我们创建工程的时候如果选中Include kotlin support怎会自动在gradle中生成。

    4、Retrofit+RxJava

    Retrofit结合RxJava能快捷的使用网络请求。

    创建Service接口,Kotlin的类型是写在后面

    interface RetrofitService {
    
        /**
         *  获取当天详细信息
         *  @param date 日期
         */
        @GET("calendar/day")
        fun calenderDay(
                @Query("date") date: String,
                @Query("key") key: String
        ): Observable<CalentarDayBean>
    
        /**
         *  获取近期假期
         *  @param date 日期
         */
        @GET("calendar/month")
        fun calenderMonth(
                @Query("date") date: String
        ): Observable<CalentarMonthBean>
    
        /**
         *  获取当年假期列表
         *  @param date 日期
         */
        @GET("calendar/year")
        fun calenderYear(
                @Query("date") date: String
        ): Observable<CalentarYearBean>
    }
    
    

    创建Retrofit,Kotlin的class并不支持static变量,所以需要使用companion object来声明static变量,其实这个变量也不是真正的static变量,而是一个伴生对象

    伴生对象可以实现静态调用,通过类名.属性名或者类名.方法名进行调用

    class RetrofitUtil {
        companion object {
            /**
             * 创建Retrofit
             */
            fun create(url: String): Retrofit {
                //日志显示级别
                val level: HttpLoggingInterceptor.Level = HttpLoggingInterceptor.Level.BODY
                //新建log拦截器
                val loggingInterceptor: HttpLoggingInterceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger {
                    message -> Logger.e("OkHttp: " + message)
                })
                loggingInterceptor.level = level
                // okHttpClientBuilder
                val okHttpClientBuilder = OkHttpClient().newBuilder()
    
                okHttpClientBuilder.connectTimeout(60, TimeUnit.SECONDS)
                okHttpClientBuilder.readTimeout(10, TimeUnit.SECONDS)
                //OkHttp进行添加拦截器loggingInterceptor
                //okHttpClientBuilder.addInterceptor(loggingInterceptor)
    
                return Retrofit.Builder()
                        .baseUrl(url)
                        .client(okHttpClientBuilder.build())
                        .addConverterFactory(GsonConverterFactory.create())
                        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                        .build()
            }
    
            val retrofitService: RetrofitService = RetrofitUtil.getService(Constants.REQUEST_BASE_URL, RetrofitService::class.java)
    
            /**
             * 获取ServiceApi
             */
            fun <T> getService(url: String, service: Class<T>): T {
                return create(url).create(service)
            }
        }
    }
    

    通过伴生对象,结合Retrofit结合RxJava 我们直接就可以调用接口了

    RetrofitUtil
        .retrofitService
        .calenderDay(date,"933dc930886c8c0717607f9f8bae0b48")
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe({ result ->
            view?.showDayCalentarData(result)
            Logger.e(result.toString())
        }, { error ->
            view?.showError(error.message.toString())
            Logger.e(error.message.toString())
        })
    

    5、使用对象声明

    在写项目的时候,一般会将常量统一写到一个类里面,然后设置静态变量,由于在Kotlin中不存在静态变量,所有就有对象声明的存在,对象声明比较常用的地方就是在这里,对象声明用Objcet关键字表示。

    object Constants {
    
        val REQUEST_BASE_URL = "http://v.juhe.cn/"
    
        val KEY = "1be865c0e67e3"
    }
    

    使用的时候直接类名加.加变量名,如Constants.REQUEST_BASE_URL

    6、使用数据类

    Kotlin有专门的数据类,就是用data修饰的类
    首先我们先看一下json数据:

    {
    	"reason":"Success",
    	"result":{
    		"data":{
    			"date":"2018-4-4",
    			"weekday":"星期三",
    			"animalsYear":"狗",
    			"suit":"订盟.纳采.冠笄.拆卸.修造.动土.安床.入殓.除服.成服.移柩.安葬.破土.启攒.造仓.",
    			"avoid":"作灶.开光.嫁娶.开市.入宅.",
    			"year-month":"2018-4",
    			"lunar":"二月十九",
    			"lunarYear":"戊戌年"
    		}
    	},
    	"error_code":0
    }
    

    再来看一下我的数据类:

    data class CalentarDayBean(
        val reason: String,
        val result: CalentarDayResult,
        val error_code: Int
    )
    
    data class CalentarDayResult(
            val data: CalentarDayData
    )
    
    data class CalentarDayData(
            val date: String,
            val weekday: String,
            val animalsYear: String,
            val suit: String,
            val avoid: String,
            val yearMonth: String,
            val holiday: String,
            val lunar: String,
            val lunarYear: String,
            val desc: String
    )
    

    就是如此方便

    7、MVP

    kotlin的MVP和java原理一模一样我先定义了IBaseModelIBaseView

    IBaseModel

    interface IBaseModel<T> {
    
        fun onDestroy()
    
        fun attachView(view: T)
    }
    

    IBaseView

    interface IBaseView {
    
        fun showLoading()
    
    
        fun hideLoading()
    
        fun showMessage(message: String)
    
        fun killMyself()
    }
    

    然后完成ICalentarContract,这个类似合同类的接口把P和V的所有方法全部写在一起,看起来代码格外清楚

    interface ICalentarContract {
        /**
         * 对于经常使用的关于UI的方法可以定义到IBaseView中,如显示隐藏进度条,和显示文字消息
         */
        interface View : IBaseView {
            fun showDayCalentarData(calentarDayBean: CalentarDayBean)
            fun showError(errorMsg: String)
        }
    
        /**
         * Model层定义接口,外部只需关心Model返回的数据,无需关心内部细节,如是否使用缓存
         */
        interface Model : IBaseModel<ICalentarContract.View> {
            fun getDayCalentarData(date: String)
        }
    }
    

    然后activity去实现ICalentarContract.View,presenter去实现ICalentarContract.Model

    class CalentarDatePresenter : ICalentarContract.Model {
    ....
    }
    
    class MainActivity : AppCompatActivity(), ICalentarContract.View {
    ...
    }
    

    so easy~~~ 到这里我们的Demo就完成了,可以尽情玩乐。

    项目地址:待上传。。。。。。。。。。。。。

    好了,到这里我们基本掌握了Kotlin在安卓中的应用,那么接下来就需要去学习一下kotlin设计模式以及一些进阶知识~

    进阶

    一、Kotlin设计模式

    本文只列出几个常用的设计模式

    1、观察者模式( observer pattern )

    Example

    interface TextChangedListener {
        fun onTextChanged(newText: String)
    }
    
    class PrintingTextChangedListener : TextChangedListener {
        override fun onTextChanged(newText: String) = println("Text is changed to: $newText")
    }
    
    class TextView {
    
        var listener: TextChangedListener? = null
    
        var text: String by Delegates.observable("") { prop, old, new ->
            listener?.onTextChanged(new)
        }
    }
    

    Usage

    val textView = TextView()
    textView.listener = PrintingTextChangedListener()
    textView.text = "Lorem ipsum"
    textView.text = "dolor sit amet"
    

    Output

    Text is changed to: Lorem ipsum
    Text is changed to: dolor sit amet
    

    2、策略模式( strategy pattern )

    Example

    class Printer(val stringFormatterStrategy: (String) -> String) {
        fun printString(string: String) = println(stringFormatterStrategy.invoke(string))
    }
    
    val lowerCaseFormatter: (String) -> String = { it.toLowerCase() }
    
    val upperCaseFormatter = { it: String -> it.toUpperCase() }
    

    Usage

    val lowerCasePrinter = Printer(lowerCaseFormatter)
    lowerCasePrinter.printString("LOREM ipsum DOLOR sit amet")
    
    val upperCasePrinter = Printer(upperCaseFormatter)
    upperCasePrinter.printString("LOREM ipsum DOLOR sit amet")
    
    val prefixPrinter = Printer({ "Prefix: " + it })
    prefixPrinter.printString("LOREM ipsum DOLOR sit amet")
    

    Output

    lorem ipsum dolor sit amet
    LOREM IPSUM DOLOR SIT AMET
    Prefix: LOREM ipsum DOLOR sit amet
    

    3、单例模式(singleton pattern)

    Example

    class Singletone private constructor() {
        init {
            println("Initializing with object: $this")
        }
    
        companion object {
            val getInstance =SingletonHolder.holder
        }
    
        private object SingletonHolder {
            val holder = Singletone()
        }
    
        fun print() = println("Printing with object: $this")
    }
    
    

    Usage

    Singletone.getInstance.print()
    Singletone.getInstance.print()
    

    Output

    Initializing with object: advance.Singletone@266474c2
    Printing with object: advance.Singletone@266474c2
    Printing with object: advance.Singletone@266474c2
    

    4、工厂模式(Factory Method)

    Example

    interface Currency {
        val code: String
    }
    
    class Euro(override val code: String = "EUR") : Currency
    class UnitedStatesDollar(override val code: String = "USD") : Currency
    
    enum class Country {
        UnitedStates, Spain, UK, Greece
    }
    
    class CurrencyFactory {
        fun currencyForCountry(country: Country): Currency? {
            when (country) {
                Country.Spain, Country.Greece -> return Euro()
                Country.UnitedStates          -> return UnitedStatesDollar()
                else                          -> return null
            }
        }
    }
    

    Usage

    val noCurrencyCode = "No Currency Code Available"
    
    val greeceCode = CurrencyFactory().currencyForCountry(Country.Greece)?.code() ?: noCurrencyCode
    println("Greece currency: $greeceCode")
    
    val usCode = CurrencyFactory().currencyForCountry(Country.UnitedStates)?.code() ?: noCurrencyCode
    println("US currency: $usCode")
    
    val ukCode = CurrencyFactory().currencyForCountry(Country.UK)?.code() ?: noCurrencyCode
    println("UK currency: $ukCode")
    

    Output

    Greece currency: EUR
    US currency: USD
    UK currency: No Currency Code Available
    

    5、代理模式(Protection Proxy)

    Example

    interface File {
        fun read(name: String)
    }
    
    class NormalFile : File {
        override fun read(name: String) = println("Reading file: $name")
    }
    
    //Proxy:
    class SecuredFile : File {
        val normalFile = NormalFile()
        var password: String = ""
    
        override fun read(name: String) {
            if (password == "secret") {
                println("Password is correct: $password")
                normalFile.read(name)
            } else {
                println("Incorrect password. Access denied!")
            }
        }
    }
    

    Usage

    val securedFile = SecuredFile()
    securedFile.read("readme.md")
    
    securedFile.password = "secret"
    securedFile.read("readme.md")
    

    Output

    Incorrect password. Access denied!
    Password is correct: secret
    Reading file: readme.md
    

    6、建造者模式(builder pattern)

    Example

    // Let's assume that Dialog class is provided by external library.
    // We have only access to Dialog public interface which cannot be changed.
    
    class Dialog() {
    
        fun showTitle() = println("showing title")
    
        fun setTitle(text: String) = println("setting title text $text")
    
        fun setTitleColor(color: String) = println("setting title color $color")
    
        fun showMessage() = println("showing message")
    
        fun setMessage(text: String) = println("setting message $text")
    
        fun setMessageColor(color: String) = println("setting message color $color")
    
        fun showImage(bitmapBytes: ByteArray) = println("showing image with size ${bitmapBytes.size}")
    
        fun show() = println("showing dialog $this")
    }
    
    //Builder:
    class DialogBuilder() {
        constructor(init: DialogBuilder.() -> Unit) : this() {
            init()
        }
    
        private var titleHolder: TextView? = null
        private var messageHolder: TextView? = null
        private var imageHolder: File? = null
    
        fun title(init: TextView.() -> Unit) {
            titleHolder = TextView().apply { init() }
        }
    
        fun message(init: TextView.() -> Unit) {
            messageHolder = TextView().apply { init() }
        }
    
        fun image(init: () -> File) {
            imageHolder = init()
        }
    
        fun build(): Dialog {
            val dialog = Dialog()
    
            titleHolder?.apply {
                dialog.setTitle(text)
                dialog.setTitleColor(color)
                dialog.showTitle()
            }
    
            messageHolder?.apply {
                dialog.setMessage(text)
                dialog.setMessageColor(color)
                dialog.showMessage()
            }
    
            imageHolder?.apply {
                dialog.showImage(readBytes())
            }
    
            return dialog
        }
    
        class TextView {
            var text: String = ""
            var color: String = "#00000"
        }
    }
    

    Usage

    //Function that creates dialog builder and builds Dialog
    fun dialog(init: DialogBuilder.() -> Unit): Dialog {
        return DialogBuilder(init).build()
    }
    
    val dialog: Dialog = dialog {
    	title {
        	text = "Dialog Title"
        }
        message {
            text = "Dialog Message"
            color = "#333333"
        }
        image {
            File.createTempFile("image", "jpg")
        }
    }
    
    dialog.show()
    

    Output

    setting title text Dialog Title
    setting title color #00000
    showing title
    setting message Dialog Message
    setting message color #333333
    showing message
    showing image with size 0
    showing dialog Dialog@5f184fc6
    

    2、相关书籍

    个人认为还是需要找一本书籍好好地阅读一遍,一下提供了相关书籍可以选择适合自己的。

    NO.1

    《Kotlin for Android Developers》

    Kotlin是编写Android应用程序的新官方语言,多亏了这本书,你很快就能写出代码。直奔主题,实用和完整的例子,它将在开发Android应用程序的同时展示你的语言。学习Kotlin并开始使用这个强大而现代的语言再次享受Android开发。

    NO.2

    《Kotlin开发快速入门与实战》

    学习本书之前不需要具备任何的计算机专业背景,任何有志于APP开发的读者都能利用本书从头学起。

    资深软件开发工程师根据Kotlin最新版本撰写,系统讲解Kotlin开发技巧和项目实战。全书共分为7章,内容层次清晰,难度循序渐进。希望通过阅读本书,能够让你成为一个全栈工程师。

    NO.3

    《疯狂Kotlin讲义》

    本书尤其适合从Java转Kotlin的读者,对于没有Java功底的读者,可忽略“对比”部分,直接学习本书也可掌握Kotlin编程。

    本书对Kotlin的解读十分系统、全面,超过Kotlin官方文档本身覆盖的内容。本书很多地方都会结合Java字节码进行深入解读,比如对Kotlin扩展的解读,对Kotlin主、次构造器的解读,这种解读目的不止于教会读者简单地掌握Kotlin的用法,而是力求让读者深入理解Kotlin,且更好地理解Java。

    NO.4

    《Kotlin实战》

    本书主要面向有一定Java 经验的开发者。

    本书将从语言的基本特性开始,逐渐覆盖其更多的高级特性,尤其注重讲解如何将 Koltin 集成到已有 Java 工程实践及其背后的原理。本书分为两个部分。第一部分讲解如何开始使用 Kotlin 现有的库和API,包括基本语法、扩展函数和扩展属性、数据类和伴生对象、lambda 表达式,以及数据类型系统(着重讲解了可空性和集合的概念)。第二部分教你如何使用 Kotlin 构建自己的 API,以及一些深层次特性——约定和委托属性、高阶函数、泛型、注解和反射,以及领域特定语言的构建。

    本书适合广大移动开发者及入门学习者,尤其是紧跟主流趋势的前沿探索者。

    NO.5

    《揭秘Kotlin编程原理》

    本书深入介绍Kotlin面向对象设计的语法特性及其背后的实现方式。

    在本书中,读者不仅能清晰地了解Kotlin的语法、高级特性,还能真正地掌握Kotlin背后的实现机制和设计哲学,形成对Kotlin语言既直观、又深刻的认识——在此基础上,读者能准确、快速地上手实践,大大提升自己的移动开发能力。

    Kotlin的这些特性和实现机制,可以帮助开发者扫清开发道路上的一些障碍,让开发变得更加简单!本书是一本值得拥有,能切实帮助读者加薪提职的好书!

    项目

    学习一门语言最快的方式就是看其如何在实际项目中运用,有了上面的基础和进阶,下面我们看一些开源项目:

    1.Kotlin-for-Android-Developers(★1676)

    介绍:这个项目其实是Kotlin-for-Android-Developers这本书的配套代码,如果你是kotlin的初学者,那么这绝对是你学习kotlin的不二之选。项目通过一个天气的例子很好的展示了kotlin带来的强大功能,比如网络数据的请求,数据的缓存设计,数据库的操作,各种扩展函数的妙用等等。

    地址:https://github.com/antoniolg/Kotlin-for-Android-Developers

    2.Bandhook-Kotlin (★1494)

    介绍:Kotlin版本的音乐播放器,数据来源于LastFm。

    地址:https://github.com/antoniolg/Bandhook-Kotlin

    3.GankClient-Kotlin (★1216)

    介绍:gank.io kotlin实现的干货集中营Android客户端,风格采用了Material Design。

    地址:https://github.com/githubwing/GankClient-Kotlin

    4.PoiShuhui-Kotlin(★897)

    介绍:一个用Kotlin写的简单漫画APP。

    地址:https://github.com/wuapnjie/PoiShuhui-Kotlin

    5.Eyepetizer-in-Kotlin(★1167)

    介绍:Kotlin版本的Eyepetizer客户端

    地址:https://github.com/LRH1993/Eyepetizer-in-Kotlin

    6.Tucao(★792)

    介绍:Kotlin版本的吐槽客户端

    地址:https://github.com/blackbbc/Tucao

    资源

    一、重要资源

    Kotlin 官网

    https://kotlinlang.org/docs/reference/

    Kotlin 官方网站是学习 Kotlin 好去处。在参考部分,你可以找到该语言的所有概念和功能的深入解析文档。在教程部分有关于设置工作环境并使用编译器的实用分步指南。

    这里还有个 Kotlin 编译器,是一个浏览器 APP,你可以在上面尝试使用这门语言。它能加载许多示例,包括 Koans 课程 — 这是目前熟悉 Kotlin 语法的最好方式。

    Kotlin 官博

    https://blog.jetbrains.com/kotlin/

    Kotlin 的官方博客由 JetBrains 的一位作者负责。你可以在这里找到所有与 Kotlin 相关的新闻、更新、教程、使用技巧等的内容。

    在 Android 上开始使用 Kotlin

    https://developer.android.com/kotlin/get-started.html

    一篇很牛叉的文章,向我们展示了如何使用 Kotlin 编写和运行 Android 应用程序的测试

    从 Java 到 Kotlin

    https://github.com/MindorksOpenSource/from-java-to-kotlin

    实用的快速提醒列表工具包含了一些简短的代码块,藉由这个来帮助你快速找到通用 Java 操作符、功能以及声明的 Kotlin 替代方案。

    Kotlin 教学插件

    https://blog.jetbrains.com/kotlin/2016/03/kotlin-educational-plugin/

    用于 IntelliJ IDEa 的插件,可让你在本地离线环境下使用 Koans 课程。

    Kotlin on GitHub

    https://github.com/jetbrains/kotlin

    Kotlin 于 2012 年开源,你可以对该语言进行贡献。

    Kotlin Android 模板

    https://github.com/nekocode/Kotlin-Android-Template

    Android 项目模板,使其非常容易设置稳定的 Kotlin 工作区,并快速引导你开发应用程序。

    不可错过的 Kotlin 资源列表

    https://github.com/KotlinBy/awesome-kotlin

    这是一个比较完整的 Kotlin 资源列表,包括各种实用链接、书籍、库、框架和视频等。该列表的组织结构非常好,kotlin.link 也提供了一个风格化的版本。

    kotlin设计模式

    https://github.com/dbacinski/Design-Patterns-In-Kotlin

    DariuszBaciński 创建了一个 GitHub repo,其中有在 Kotlin 中实现的常见设计模式,也有用其他语言编写的类似项目,包括 Java,Swift,Java 和 PHP,如果你是其中一项语言的使用者,可以用它们作为参考点。

    二、视频资源

    Kotlin 介绍

    https://www.youtube.com/watch?v=X1RVYt2QKQE

    来自 Google I / O 2017 的演讲,大会首次向人们介绍 Kotlin,并提出了改进工作流程的想法。它涵盖了许多基础知识,并展示了一些很酷的 Kotlin 技巧。

    明日胜于今,我用 Kotlin

    https://www.youtube.com/watch?v=fPzxfeDJDzY

    Google I / O 2017 大会关于 Kotlin 的第二个演讲。这个演讲涵盖了更多高级话题,如设计模式,最佳实践和其他常见规则。 演讲也揭示了在生产中使用 Kotlin 的意义,以及在工作中采用新兴语言将面临的挑战。

    Peter Sommerhoff 教你学 Kotlin

    https://www.youtube.com/playlist?list=PLpg00ti3ApRweIhdOI4VCFFStx4uXC__u

    这是一个免费的 Kotlin 课程,适合初学者,前面介绍了从变量到条件循环和函数的所有基础知识,后面会深入到更高级的主题,如 Kotlin 中的面向对象以及像 lambda 表达式的功能编程。

    使用 Kotlin&Gradle 更好地开发 Android

    https://www.youtube.com/watch?v=_DaZQ374Chc

    这个讲座从 2016 年开始,它介绍了现实世界中的编程语言功能,你将了解到 Kotlin 是如何适应 Android 工作流程中存在的工具。

    使用 Kotlin&Gradle 更好地开发 Android

    https://www.youtube.com/watch?v=ZlQhmkp_jyk

    一个 8 分钟的浓缩教程,让你快速了解 Kotlin 的主要功能,如变量声明、Lambdas、扩展功能等等。

    Jake Wharton:用 Kotlin 进行 Android 开发

    https://www.youtube.com/watch?v=A2LukgT2mKc&t

    关于 Kotlin 的介绍,演讲向我们解释了新语言是如何改进 Android 生态系统的,并展示了许多炫酷的方式,我们可以使用智能的 Kotlin 语法来获得优势。

    扫码关注公众号“伟大程序猿的诞生“,更多干货新鲜文章等着你~

    公众号回复“资料获取”,获取更多干货哦~

    有问题添加本人微信号“fenghuokeji996” 或扫描博客导航栏本人二维码

    我的博客即将同步至腾讯云+社区,邀请大家一同入驻:
    https://cloud.tencent.com/developer/support-plan?invite_code=ncnq0hloo5yw

    展开全文
  • Java-Android开发之MVP模式+Retrofit2.0框架封装》和《Kotlin-Android开发之MVP模式+Retrofit2.0+RxJava1.0+Dagger2框架封装》这两篇博客,对MVP模式、网络的访问有个比较全面的理解,但是上述两...

    前言:没有万能的框架,也没有万能的模版,我们需要做的就是根据需求选对框架,然后进行修改,最终达到自己的目的。相信你通过《Kotlin&Java-Android开发之MVP模式+Retrofit2.0框架封装》和《Kotlin-Android开发之MVP模式+Retrofit2.0+RxJava1.0+Dagger2框架封装》这两篇博客,对MVP模式、网络的访问有个比较全面的理解,但是上述两个框架也有很大的缺点,比如没有实现对RecycleView下拉刷新和上拉加载更多的封装,所以今天我们来实现这一个功能,当然单独这一个框架也有缺点,等我慢慢道来。(注意:代码使用kotlin语言)


    老样子还是先介绍一下MVP模式:M(Model)即模型层,功能是提供数据,放在安卓中对应的是数据库、Beans、网络请求等。V(View)即视图层,功能是提供数据的展示,放在安卓中对应的是activity、fragment等。P(Presenter)即协调者,功能是负责主要逻辑功能的实现,将M和V进行联系起来,M和V彻底分离。

    OkHttp3:是一个非常优秀的网络请求的开源框架,谷歌推荐使用。好处很多这里不再过多介绍,这里有个不错的博客想看的可以移步《OKHttp3 基本用法

    RecyclerView:Android5.0之后,开发就慢慢的从ListView转向RecyclerView了,如果你还没开始看RecyclerView的话,欢迎请看我的RecyclerView系列博客

    1)《Android开发之RecyclerView的基本使用(实现常用的4种效果)

    2)《Android开发之RecyclerView实现点击事件和长按事件

    3)《Android开发之RecyclerView的间隔线处理

    4)《Android开发之RecyclerView添加头部和底部

    5)《Android开发之实现滑动RecyclerView,浮动按钮的显示和隐藏(一)

    6)《Android开发之实现滑动RecyclerView,浮动按钮的显示和隐藏(二)

    7)《Android开发之RecyclerView的交互动画(实现拖拽和删除)

    8)《Kotlin-Android开发之RecyclerView+StickyListHeadersListView(粘列表标题控件)实现商品类别和商品的左右联动


    现在开始对撸起代码

    1.先看一下整体实现效果

    2.包的结构和要解析的JSON数据结构

    3.需要添加的依赖

        implementation "org.jetbrains.anko:anko:0.10.1"
        implementation 'com.google.code.gson:gson:2.2.4'
        implementation 'com.squareup.okhttp3:okhttp:3.8.1'
        implementation 'com.android.support:design:28.0.0'

    4.先完成base包代码的编写:分别对Activity基类的编写,下拉刷新上滑加载跟多的基类的编写。

    BaseActivity.kt:是所有Activity的基类,在次类里面可以完成获取布局id,initData()方法和initLinstener()方法,同样的方法我们也可以完成对BaseFragment.kt的编写,不过此项目中不涉及Fragment所以没有写。

    import android.os.Bundle
    import androidx.appcompat.app.AppCompatActivity
    import org.jetbrains.anko.AnkoLogger
    import org.jetbrains.anko.startActivity
    import org.jetbrains.anko.toast
    
    /**
     * 所有activity类的基类
     */
    abstract class BaseActivity : AppCompatActivity(),AnkoLogger {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(getLayoutId())
            initListener()
            initData()
        }
    
        /*
         * 初始化数据
         */
        open protected fun initData() {
        }
    
        /**
         * adapter listener
         */
        open protected fun initListener() {
        }
    
        /**
         * 获取布局id
         */
        abstract fun getLayoutId(): Int
    
        open protected fun myToast(msg:String){
            runOnUiThread { toast(msg) }
        }
    
        inline fun <reified T:BaseActivity> startActivityAndFinish(){
            startActivity<T>()
            finish()
        }
    }
    BaseView.kt:是所有下拉刷新和上拉加载更多列表界面的view的接口基类,通过指定的泛型,实现接口里面的三个方法,来更新UI
    /**
     * ClassName:BaseView
     * Description:所有下拉刷新和上拉加载更多列表界面的view的基类
     */
    interface BaseView<RESPONSE> {
        /**
         * 获取数据失败
         */
        fun  onError(message: String?)
    
        /**
         * 初始化数据或者刷新数据成功
         */
        fun  loadSuccess(reponse: RESPONSE?)
    
        /**
         * 加载更多成功
         */
        fun  loadMore(response: RESPONSE?)
    }

    BaseListActivity.kt:所有具有下拉刷新和上拉加载更多列表界面的基类

    import android.graphics.Color
    import android.util.Log
    import android.view.View
    import androidx.recyclerview.widget.DividerItemDecoration
    import androidx.recyclerview.widget.LinearLayoutManager
    import androidx.recyclerview.widget.RecyclerView
    import com.fly.demo01.R
    import com.fly.demo01.utils.ThreadUtil
    import kotlinx.android.synthetic.main.activity_list.*
    
    
    /**
     * ClassName:BaseListFragment
     * Description:所有具有下拉刷新和上拉加载更多列表界面的基类
     * 基类抽取
     * mainView->BaseView
     * Presenter->BaseListPresenter
     * Adapter->BaseListAdapter
     */
    abstract class BaseListActivity<RESPONSE,ITEMBEAN,ITEMVIEW:View> : BaseActivity(), BaseView<RESPONSE> {
    
    
        override fun getLayoutId(): Int {
            return R.layout.activity_list
        }
    
        override fun onError(message: String?) {
            Log.e("--onError--",message)
            recycleView.visibility=View.INVISIBLE//隐藏RecycleView
            ThreadUtil.handler.postDelayed(Runnable {
                refreshLayout.isRefreshing = false//延迟隐藏刷新
                ThreadUtil.runOnMainThread(Runnable {
                    myToast("加载数据失败")
                })
            },1500)
        }
    
        override fun loadSuccess(response:RESPONSE?) {
            Log.e("--loadSuccess--",response.toString())
            //隐藏刷新控件
            refreshLayout.isRefreshing = false
            //刷新列表
            adapter.updateList(getList(response))
        }
    
    
    
        override fun loadMore(response: RESPONSE?) {
            adapter.loadMore(getList(response))
        }
    
        //适配
        val adapter by lazy { getSpecialAdapter() }
    
        val presenter by lazy { getSpecialPresenter() }
    
    
        override fun initListener() {
            //初始化recycleview
            recycleView.layoutManager = LinearLayoutManager(this)
            recycleView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
            recycleView.adapter = adapter
    
            //初始化刷新控件
            refreshLayout.setColorSchemeColors(Color.RED, Color.YELLOW, Color.GREEN)
            //刷新监听
            refreshLayout.setOnRefreshListener {
                //刷新监听
                presenter.loadDatas()
            }
            //监听列表滑动
            recycleView.addOnScrollListener(object : RecyclerView.OnScrollListener(){
                override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                    if(newState== RecyclerView.SCROLL_STATE_IDLE){
                        //是否最后一条已经显示
                        val layoutManager = recyclerView.layoutManager
                        if(layoutManager is LinearLayoutManager){
                            val manager: LinearLayoutManager = layoutManager
                            val lastPosition = manager.findLastVisibleItemPosition()
                            if(lastPosition==adapter.itemCount-1){
                                //最后一条已经显示了
                                presenter.loadMore(adapter.itemCount-1)//adapter.itemCount-1当前的个数
                            }
                        }
                    }
                }
            })
        }
    
        override fun initData() {
            //初始化数据
            presenter.loadDatas()
        }
        /**
         * 获取适配器adapter
         */
        abstract fun getSpecialAdapter():BaseListAdapter<ITEMBEAN,ITEMVIEW>
        /**
         * 获取presenter
         */
        abstract fun getSpecialPresenter():BaseListPresenter
        /**
         * 从返回结果中获取列表数据集合
         */
        abstract fun getList(response: RESPONSE?): List<ITEMBEAN>?
    }

    对应的布局文件activity_list.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="match_parent">
        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
            android:id="@+id/refreshLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recycleView"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
    
    </LinearLayout>

    BaseListAdapter.kt:所有下拉刷新和上拉加载更多列表界面adapter基类

    import android.content.Context
    import android.view.View
    import android.view.ViewGroup
    import androidx.recyclerview.widget.RecyclerView
    import com.fly.demo01.ui.widget.LoadMoreView
    
    
    /**
     * ClassName:BaseListAdapter
     * Description:所有下拉刷新和上拉加载更多列表界面adapter基类
     */
    abstract class BaseListAdapter<ITEMBEAN, ITEMVIEW : View> : RecyclerView.Adapter<BaseListAdapter.BaseListHolder>() {
    
        private var list = ArrayList<ITEMBEAN>()
        /**
         * 更新数据
         */
        fun updateList(list: List<ITEMBEAN>?) {
            list?.let {
    
                this.list.clear()
                this.list.addAll(list)
                notifyDataSetChanged()
            }
        }
    
        /**
         * 加载更多
         */
        fun loadMore(list: List<ITEMBEAN>?) {
            list?.let {
                this.list.addAll(list)
                notifyDataSetChanged()
            }
        }
    
        override fun onBindViewHolder(holder: BaseListHolder, position: Int) {
            //如果是最后一条 不需要刷新view
            if (position == list.size) return
            //条目数据
            val data = list.get(position)
            //条目view
            val itemView = holder?.itemView as ITEMVIEW
            //条目刷新
            refreshItemView(itemView, data)
            //设置条目点击事件
            itemView.setOnClickListener {
                //条目点击事件
                listener?.let {
                    it(data)
                }
            }
        }
    
        //定义函数类型变量
        var listener: ((itemBean: ITEMBEAN) -> Unit)? = null
    
        fun setMyListener(listener: (itemBean: ITEMBEAN) -> Unit) {
            this.listener = listener
        }
    
        override fun getItemViewType(position: Int): Int {
            if (position == list.size) {
                //最后一条
                return 1
            } else {
                //普通条目
                return 0
            }
        }
    
        override fun getItemCount(): Int {
            return list.size + 1
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseListHolder {
            if (viewType == 1) {
                //最后一条
                return BaseListHolder(LoadMoreView(parent?.context))
            } else {
                //普通条目
                return BaseListHolder(getItemView(parent?.context))
            }
        }
    
        class BaseListHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    
        }
    
        /**
         * 刷新条目view
         */
        abstract fun refreshItemView(itemView: ITEMVIEW, data: ITEMBEAN)
    
        /**
         * 获取条目view
         */
        abstract fun getItemView(context: Context?): ITEMVIEW
    }

     BaseListPresenter.kt所有下拉刷新和上拉加载更多列表界面presenter的基类

    /**
     * ClassName:BaseListPresenter
     * Description:所有下拉刷新和上拉加载更多列表界面presenter的基类
     */
    interface BaseListPresenter {
        companion object {
            val TYPE_INIT_OR_REFRESH = 1//初始化或下拉刷新
            val TYPE_LOAD_MORE = 2
        }
        fun loadDatas()
        fun loadMore(offset: Int)//offset当前的个数
        //解绑presenter和view
        fun destoryView()
    }

    5.M层代码的编写:完成bean的编写和网络请求的编写

    ResponseInfo.kt通过分析json的数据结构可以得到如下代码

    //没用到
    data class ResponseInfo (
        var error_code: String,
        var reason: String,
        var result: Result
    )
    //没用到
    data class Result(
        var stat: String,
        var data: List<Data>
    )
    //用到
    data class Data(
        var author_name: String,
        var category: String,
        var date: String,
        var thumbnail_pic_s: String,
        var title: String,
        var uniquekey: String,
        var url: String
    )

     ResponseHandler.kt:请求回调成功和失败的接口

    interface ResponseHandler<RESOPONSE> {
        fun onError(type:Int, msg:String?)
        fun onSuccess(type:Int, msg:RESOPONSE)
    }

    NetManager.kt:发送网络请求类,也是OkHttp的实现(重点)

    import com.fly.demo01.utils.ThreadUtil
    import java.io.IOException
    import okhttp3.*
    import org.json.JSONObject
    
    
    /**
     * ClassName:NetManager
     * Description:发送网络请求类
     * 单例模式
     */
    class NetManager private constructor(){
        val client by lazy { OkHttpClient() }
        companion object {
            val manager by lazy { NetManager() }
        }
    
        /**
         * 发送网络请求
         */
        fun <RESPONSE>sendRequest(req: MRequest<RESPONSE>){
    
            val request = Request.Builder()
                    .url(req.url)
                    .get()
                    .build()
            client.newCall(request).enqueue(object : Callback {
                /**
                 * 子线程调用
                 */
                override fun onFailure(call: Call?, e: IOException?) {
                    ThreadUtil.runOnMainThread(object : Runnable {
                        override fun run() {
                            //隐藏刷新控件
                            req.handler.onError(req.type,e?.message)
                        }
                    })
                }
    
                /**
                 * 子线程调用
                 */
                override fun onResponse(call: Call?, response: Response?) {
                    val json = response?.body()?.string()
                    val jsonObject = JSONObject(json)
                    val error_code:String = jsonObject.getString("error_code")
                    if (error_code.equals("0")){
                        val result: JSONObject = jsonObject.getJSONObject("result")
                        val data = result.getString("data")
                        val parseResult = req.parseResult(data)
                        ThreadUtil.runOnMainThread(object : Runnable {
                            override fun run() {
                                req.handler.onSuccess(req.type,parseResult)
                            }
                        })
                    }else if (error_code.equals("10001")){
                        //隐藏刷新控件
                        req.handler.onError(req.type,"错误的请求KEY")
                    }else if (error_code.equals("10022")){
                        //隐藏刷新控件
                        req.handler.onError(req.type,"接口地址不存在")
                    } else{
                        //隐藏刷新控件
                        req.handler.onError(req.type,"请求的数据格式有问题")
                    }
                }
            })
        }
    }

    MRequest.kt:所有的请求基类

    import android.util.Log
    import com.google.gson.Gson
    import org.json.JSONObject
    import java.lang.reflect.ParameterizedType
    
    
    /**
     * ClassName:MRequest
     * Description:所有请求基类
     */
    open class MRequest<RESPONSE>(val type:Int, val url:String, val handler: ResponseHandler<RESPONSE>) {
    
        /**
         * 解析网络请求结果
         */
        fun parseResult(result: String?): RESPONSE {
            val gson = Gson()
            //获取泛型类型
            val type = (this.javaClass.genericSuperclass as ParameterizedType).getActualTypeArguments()[0]
            val list = gson.fromJson<RESPONSE>(result, type)
            return list
        }
    
        /**
         * 发送网络请求
         */
        fun excute(){
            NetManager.manager.sendRequest(this)
        }
    }

    MainActivityRequest.kt:MainActivity数据请求类

    import android.util.Log
    import com.fly.demo01.model.beans.Data
    
    /**
     * Description:MainActivity数据请求类
     */
    
    class MainActivityRequest(type:Int, offset:Int,handler: ResponseHandler<List<Data>>):
            MRequest<List<Data>>(type,
                    "http://v.juhe.cn/toutiao/index?type=caijing&key=自己申请",
                    handler){
            init {
                Log.e("--MainRequest-offset-",offset.toString())//请求的数据个数
            }
    }

    6.P层代码的实现:主要实现的功能是UI页面通过P层调用M层访问数据,然后经由P层调用V层进行页面的刷新。

    MainActivityPresenter.kt:继承BaseListPresenter类,然后可以在内部再定义一些接口。(此类中没有实现,有需求的话可以自己实现)

    import com.fly.demo01.base.BaseListPresenter
    
    interface MainActivityPresenter: BaseListPresenter {
    
    }

    MainActivityPresenterImpl.kt:Main对应的Presenter实现类,完成数据的加载成功失败、更新、加载更多以及解绑view和presenter

    import com.fly.demo01.base.BaseListPresenter
    import com.fly.demo01.base.BaseView
    import com.fly.demo01.model.beans.Data
    import com.fly.demo01.model.net.MainActivityRequest
    import com.fly.demo01.model.net.ResponseHandler
    import com.fly.demo01.presenter.interf.MainActivityPresenter
    
    class MainActivityPresenterImpl(var mainView: BaseView<List<Data>>?): MainActivityPresenter, ResponseHandler<List<Data>> {
    
        /**
         * 解绑view和presenter
         */
        override fun destoryView(){
            if(mainView!=null){
                mainView = null
            }
        }
    
        //失败
        override fun onError(type:Int,msg: String?) {
            mainView?.onError(msg)
        }
    
        /**
         * 加载数据成功
         */
        override fun onSuccess(type:Int,result: List<Data>) {
            //区分初始化  加载更多
            when(type){
                BaseListPresenter.TYPE_INIT_OR_REFRESH->mainView?.loadSuccess(result)
                BaseListPresenter.TYPE_LOAD_MORE->mainView?.loadMore(result)
            }
        }
    
        /**
         * 初始化数据或者刷新
         */
        override fun loadDatas() {
            //定义request
            MainActivityRequest(BaseListPresenter.TYPE_INIT_OR_REFRESH,0,this).excute()
        }
    
        //volley
        override fun loadMore(offset: Int) {
            //定义request
            MainActivityRequest(BaseListPresenter.TYPE_LOAD_MORE,offset,this).excute()
        }
    
    }

    7.V层代码:通过调用P层来完成UI页面的显示

    MainActivity.kt

    import com.fly.demo01.base.BaseListActivity
    import com.fly.demo01.base.BaseListAdapter
    import com.fly.demo01.base.BaseListPresenter
    import com.fly.demo01.model.beans.Data
    import com.fly.demo01.presenter.impl.MainActivityPresenterImpl
    import com.fly.demo01.ui.adapter.MainAdapter
    import com.fly.demo01.ui.widget.DataItemView
    
    class MainActivity : BaseListActivity<List<Data>, Data, DataItemView>() {
    
        lateinit var mainAdapter: MainAdapter
    
        override fun initListener() {
            super.initListener()
            mainAdapter.setMyListener {
                myToast(it.title)
            }
        }
    
        override fun getSpecialAdapter(): BaseListAdapter<Data, DataItemView> {
            mainAdapter = MainAdapter()
            return mainAdapter
        }
    
        override fun getSpecialPresenter(): BaseListPresenter {
            return MainActivityPresenterImpl(this)
        }
    
        override fun getList(response: List<Data>?): List<Data>? {
            return response
        }
    
        override fun onDestroy() {
            super.onDestroy()
            //解绑presenter
            presenter.destoryView()
        }
    
    }

    MainAdapter.kt

    import android.content.Context
    import com.fly.demo01.base.BaseListAdapter
    import com.fly.demo01.model.beans.Data
    import com.fly.demo01.ui.widget.DataItemView
    
    class MainAdapter: BaseListAdapter<Data, DataItemView>() {
        override fun refreshItemView(itemView: DataItemView, data: Data) {
            itemView.setData(data)
        }
        override fun getItemView(context: Context?): DataItemView {
            return DataItemView(context)
        }
    }

    DataItemView.kt

    import android.content.Context
    import android.util.AttributeSet
    import android.view.View
    import android.widget.RelativeLayout
    import com.fly.demo01.R
    import com.fly.demo01.model.beans.Data
    import kotlinx.android.synthetic.main.item_data.view.*
    
    
    /**
     * ClassName:DataItemView
     * Description:
     */
    class DataItemView:RelativeLayout {
        constructor(context: Context?) : super(context)
        constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
        constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
    
        /**
         * 初始化方法
         */
        init {
            View.inflate(context, R.layout.item_data,this)
        }
    
        /**
         * 刷新条目view数据
         */
        fun setData(data: Data) {
            title.setText(data.title)
        }
    }

    对应的item_data.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/selector_bg"
        android:orientation="vertical">
    
        <TextView
            android:id="@+id/title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="20dp"
            android:text="你好"
            android:textColor="#000000"
            android:textSize="20sp" />
    </LinearLayout>

    LoadMoreView.kt

    import android.content.Context
    import android.util.AttributeSet
    import android.view.View
    import android.widget.RelativeLayout
    import com.fly.demo01.R
    
    
    /**
     * ClassName:LoadMoreView
     * Description:
     */
    class LoadMoreView:RelativeLayout {
        constructor(context: Context?) : super(context)
        constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
        constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
        init {
            View.inflate(context, R.layout.view_loadmore,this)
        }
    }

    对应的布局代码view_loadmore.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:orientation="vertical"
                  android:gravity="center"
                  android:padding="10dp"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content">
    
        <ProgressBar
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>

    ThreadUtil.kt:一个工具类,判断当前是否运行在主线程中

    import android.os.Handler
    import android.os.Looper
    
    object ThreadUtil {
        val handler = Handler(Looper.getMainLooper());
        /**
         * 运行在主线程中
         */
        fun runOnMainThread(runnable:Runnable){
            handler.post(runnable)
        }
    
    }

    8.添加网络权限

    <uses-permission android:name="android.permission.INTERNET"/>

    存在的一些问题:比如在NetManager.kt类中,通过对访问成功的数据进行解析

    这里就出现了一个问题,如果每个ListActivity或者是ListFragment需要访问的JSON数据格式不一样怎么办?json数据第一层的参数是“error”(没有“_code”),如果直接丢一个json字符串过去在V层解析,又达不到我们的初衷了,所以一定要和后台服务器的小伙伴们商量好接口的事情


    代码下载:《Kotlin-Android开发之MVP模式+OkHttp3+RecyclerView下拉刷新和上拉加载更多框架封装代码


    如有意见或者建议请跟帖,谢谢,各位晚安!!!

    展开全文
  • 前言:最近一直再看Kotlin的安卓代码,通过对比Java,Kotlin确实是一个趋势,好了今天我介绍一下如何把网络请求融入到MVP模式之中,可能有的人对一些概念性的东西理解起来很好,可是实际操作起来,却又是另外一回事....
  • 不知道什么时候,kotlin被google指定为官方开发语言,所以学习一下还是有必要的,这里主要说明使用kotlin做网络框架封装。 2.特点 3.说明 本篇博客主要浅显的讲讲如何使用kotlin+RxJava2++Retrofit2+MVP做网络框架...
  • 前言:此项目是一个适合刚入门安卓开发的且熟悉Kotlin的练手小项目,基于《Kotlin-Android开发之MVP模式+Retrofit2.0+RxJava1.0+Dagger2框架封装》开发而成,实现了新闻的分类浏览、历史记录、我的收藏以及小视频的...
  • Kotlin —— 这次入门就不用放弃了

    万次阅读 2018-05-09 23:30:56
    本文将展示在Android中会遇到的实际问题,并且使用Kotlin怎么去解决它们。一些Android开发者在处理异步、数据库或者处理Activity中非常冗长的listener时发现了很多的问题。通过一个个真实的场景,我们一边解决问题...
  • Kotlin 的?和!!

    千次阅读 2019-05-30 17:08:09
    Kotlin有针对Java的NullPointerException管理,kotlin开发中也会有很多?和!!出现,对于初学者来说肯定很糊涂。 ? 表示当前对象可以为空,即可以 = null !! 表示当前对象不为空的情况下执行 /** * name 可以为空...
  • android+kotlin开发笔记(一)

    千次阅读 2019-06-17 11:23:03
    唉,悲了个催的,我发现就是如果长时间不做android,就会忘了的,...1、搭建环境,有android studio环境的基础,处理kotlin的配置就很简单了,度娘有好多,比如:https://www.cnblogs.com/yunfang/p/7694118.html ...
  • 目录 介绍官网及文档中文社区教程 & 文章开源库和框架... 为了让大家更快了解和上手 Kotlin,这里为大家整理了这份 Kotlin 资源大全,希望可以帮助大家用最短时间学习 Kotlin. 官网及文档 Kotlin 官网Kotlin 文档
  • Kotlin(一)-->Android Studio配置Kotlin开发环境

    万次阅读 多人点赞 2016-10-27 09:47:42
    新的产品开发中Android端已经完全使用Kotlin作为开发语言了,目前为止还没有发现什么不适的地方。所以今天我们将讲解一下如何在Android Studio中配置Kotlin开发环境,算是为Kotlin讲解一下入门知识,后续会慢慢更新...
  • kotlin的三目运算

    万次阅读 2017-11-25 23:03:55
    kotlin的三目运算kotlin中不再有java中的三目运算格式,比如a>b?a:b 取而代之的是直接用if else模式格式:if(a>b) a else b
  • Java和Kotlin之间互相转换

    万次阅读 2018-04-02 18:10:36
    java转kotlin: 打开要转的java文件 快捷键 Ctrl+Shift+Alt+K 直接转换 快捷键 Ctrl+Shift+A在搜索框输入Convert Java File to Kotlin或者 Code -&gt;Convert Java File to Kotlin Kotlikn 转 Java ...
  • Kotlin学习(一)Kotlin插件安装

    万次阅读 2017-02-25 18:29:22
    什么是KotlinKotlin是针对JVM、Android 和浏览器的静态编程语言! 100% 与 Java™ 可互操作! Kotlin的插件安装
  • 其实就是互转,以下基于IDEA或Android StudioJava转Kotlin打开要转的文件方法1Ctrl+Shift+Alt+K方法2Code - Convert Java File To Kotlin FileKotlikn 转 Java Tools>Kotlin>Show Kotlin Bytecode Decompile
  • Java转Kotlin打开File –> Settings –> Plugins选项,输入“Kotlin”搜索插件,安装成功重启Android Studio 选中需要转换的文件或者包 Code –> Convert Java File To Kotlin File 注意:Convert Java File To ...
  • Could not find org.jetbrains.kotlin:kotlin-stdlib:1.1.3-2. Searched in the following locations: https://dl.google.com/dl/android/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.1.3-2/kotlin-stdl...
1 2 3 4 5 ... 20
收藏数 58,885
精华内容 23,554
关键字:

kotlin