kotlin与Java在Android开发中的简单对比

by admin on 2019年9月8日

自从谷歌宣布,kotlin为Android官方的开发语言,它确实火了,因为他有强大的背景,更因为它的安全,简洁大部分的人会说,我的项目是java,换成kotlin那成本太大了.其实kotlin的开发者早就想到这一点,kotlin官网强调:与java100%的交互.这让你想到什么?没错,这意味着你不用用kotlin重构整个项目,可以立刻马上现在就kotlin写Android了.接下来我们来看看kotlin…

  • 背景
    开发过程中,我们经常会遇见使用日历组件,有时候需要我们自己做高度自定义。因此下面我们来讲一下如何使用合适的算法来绘制我们自己的日历组件。

  • 需求分析
    要显示一个日历组件,我们需要知道日历的大致外观是怎么样的?

    图片 1
    日历图片

泛型这个东西,总让人感觉高大上,具体介绍就不说了,网上一大把,今天就来看看在Android开发中的日常是怎么使用的:

很久之前在盆友圈发了一张照片

前言:

  Java
8推出已经将近2年多了,引入很多革命性变化,加入了函数式编程的特征,使基于行为的编程成为可能,同时减化了各种设计模式的实现方式,是Java有史以来最重要的更新。但是Android上,一直没有看到支持Java8的消息。Android到底会不会支持Java8呢?答案是肯定的,Android
N已经开始支持Java 8 了。

//Student,Teacher的实体类声明data class Student(val name: String, val age: Int, val sex: String)data class Teacher(val name: String, val age: Int, val sex: String)
从上图中看,  
a. 日历大致有表头,星期显示,日期显示以及农历(此处暂不讨论)显示。  
b. 日期格子占有的数量是:6 × 7 = 42
个,其中包含有:上月的部分日期,本月的全部日期以及下月的部分日期。明白一点说就是,如果我们想计算某个月的日历,那么我们需要知道本月的一号是星期几,有多少天,上个月在这个月的日历中占有几天以及下个月在这个月的日历中占有几天,那么我们就可以计算出当月的日历数据了。
    public class MainActivity extends Activity {

        private TextView textView;
        private Button button;
        private ImageView imageView;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            textView = getView(R.id.text_view);
            button = getView(R.id.button);
            imageView = getView(R.id.image_view);

            }

        public <T extends View> T getView(int id) {
            return (T) findViewById(id);
        }
    }

图片 2

关于Java 8

没错,你再也不用写枯燥无味的get/set方法了,我们在看一段RxJava代码

  • 具体实现

是的你没看错,就是那些烦人的findViewById!虽然现在黄油刀流行,但是作为最基础的写法,还是在很多场合不可或缺。几行代码,虽然简单,却给人启发。

lambda_example

(1.)使用Lambda表达式

   Java
8的一大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。当开发者在编写Lambda表达式时,也会随之被编译成一个函数式接口。

 //java写法, Observable.just .map(new Function<String, Integer>() { @Override public Integer apply throws Exception { return Integer.parseInt + 1; //将"1"转成Integer,并加1 } }) .subscribe(new Consumer<Integer>() { @Override public void accept(Integer integer) throws Exception { System.out.println; //打印2 } });

 //kotlin写法 Observable.just .map { it.toInt() + 1 } //String转Int,并+1 .subscribe { println } //打印结果:2

由于年份中存在闰年和平年的概念,并且影响2月的天数,因此我们需要一个判断闰年和平年的方法,如下:

参考资料
https://www.zhihu.com/question/20400700
http://www.cnblogs.com/absfree/p/5270883.html

上面的两段代码是完全等效的,但是代码行数从11行降低到了一行,更不用说在第一段代码里面,我在run方法的前后以及内部都没有加入任何的空行。由此可以看出,使用lambda可以让你的Java代码在某些情况下达到何等的简洁。
那么问题来了。。。

(2.)引入函数式接口

   Java 8
引入的一个核心概念是函数式接口。如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。

从上面可以看出kotlin支持lambda表达式,而java是java8才开始支持,Android是不支持java8的.细心的同学可能已经发现了,上面kotlin将String转成Int,只需要”1″.toInt,写起来明显更符合面向对象的逻辑,不像java写起来有点繁琐.那么.toInt()的背后是什么呢?

/**
 * 判断某一年是否是闰年
 * @param year
 */
@JvmStatic
fun isLeapYear(year: Int): Boolean = year % 4 == 0 && year % 100 != 0 || year % 400 == 0

什么叫lambda呢?

Java 8
给我们带来了lambda,然而在Oracle的文档中,我没有找到lambda的定义,wikipedia里面也没有找到适合Java中的lambda的定义。写这篇文章的时候,我在这里
看到一篇很好的介绍lambda的文章,它里面给了一个定义,我觉得还挺合适的。

A lambda expression is a block of code with parameters.

(3.)泛型接口改进

 
这是一个以前不能做到的,对编译器判定泛型能力的努力改进。在以前版本的Java中有许多情形编译器不能给某个方法计算出泛型,当方法处于嵌套的或串联方法调用这样的上下文的时候,即使有时候对程序员来说它看起来“很明显”。

//点进kotlin的源码一看,这货其实还是调用java的,并用kotlin的内联函数,保证不会因为函数入栈,出栈而带来性能损失,额...说得有点多public inline fun String.toInt(): Int = java.lang.Integer.parseInt

计算每个月的1号是星期几?查看网上资料
关于这个星期几的计算,有多种计算公式。
①. 常用公式
W = [Y-1] + [(Y-1)/4] – [(Y-1)/100] + [(Y-1)/400] + D
Y是年份数,D是这一天在这一年中的累积天数,也就是这一天在这一年中是第几天。

lambda的写法

首先列举一个完整的lambda expression:

(int a, int b) -> {
    System.out.println("Performing add operation...");
    return a+b;
}

一个lambda expression由三部分组成:

  • 参数:(int a, int b)是这个lambda
    expression的参数部分,包括参数类型和参数名
  • 箭头:->
  • 代码块:就是用”{}”包含着的那两句代码。

上面说的是一个完整的lambda表达式,在很多情况下,很多东西是可以省略的。比如说,当系统可以根据context自动推断出参数的类型的时候,参数类型是可以神略的。这样的话就可以写成:

(a, b) -> {
    System.out.println("Performing add operation...");
    return a+b;
}

系统怎么自动推断出参数类型的呢? 在下面我们就可以看到。
再比如,如果只有一个参数,而参数的类型有可以自动判断,那么连“()”也是可以省略的,那么久写成了:

a -> {
    System.out.println("Performing add operation...");
    return a+a;
}

再再比如,如果代码块里面只有一行代码,那么“{}”也是可以省略的,那么久写成了:

a ->
    return a+a;

是的,可以写在同一行

a -> return a+a;

让我们更进一步,在这里,“return”其实也是没必要的。

a -> a+a;

Great, 如果没有参数的话,是不是就可以写成:

-> a+a

呢?
很可惜,如果没有参数,那么前面的”()”是必须存在的。也就是说,必须写成:

()-> a+a

(4.)Streams编程

  集合(Collections)的改进也是Java
8的一大亮点,而让集合越来越好的核心组件则是“Stream”。它与java.io包里的InputStream和OutputStream是完全不同的概念,它是一个全新的概念,大家不要混淆。此外,Stream的出现也并不是要取代ArrayLists或其他集合,它提供了一种操作大数据接口,让数据操作更容易和更快。

解说:不过上面的几大改变仅仅只能在Android
N及之后的版本才能使用,这也让Android 23及以下还在使用Java
7开发的小伙伴们望尘莫及,不过Java
8的lambda表达式还是可以支持的,接下来我们看下如何使用Java 8.

 

类型自动推导

②. 蔡勒(Zeller)公式
w=y+[y/4]+[c/4]-2c+[26(m+1)/10]+d-1

lambda的用法

实际上,如果你直接把上面的代码放到你的编辑器里面,你的IDE是会报错的,因为lambda是不能这样使用的。lambda的使用永远要跟一个叫做Functional Interface的东西绑定在一起。什么叫Functional Interface呢?Functional Interface也是Java8
中引入的概念,是的,是为了lambda。我们知道java中的interface,而Functional Interface就是一个“只有一个抽象方法”的interface。比如Runnable
这个interface就只有一个run方法,那么它就是一个Functional Interface
那么或许你要问了,什么叫只有一个抽象方法的interface?interface中的方法不都是抽象的吗?Well,在java8以前是这样的,而在java8中,Java引进了default
method
的概念,就是带有具体实现的方法:

public interface DuckInterface {
    public void walksLikeADuck();
    public default void talksLikeADuck() {
        System.out.println("Quack!");
    }
}

在上面的例子中,talksLikeADuck就是一个default
method,注意到这个方法的定义中有一个“default”修饰符。相信很多人会觉得这是个非常有用的feature,我也是这样觉得的。
再说回Functional Interfacel,lambda,刚刚说到,lambda必须和Functional
Interface配套使用,那怎么配套使用呢?
以安卓里面的View.OnClickListener为例(它也是个Functional
Interface)如果没有lambda,我们经常会这样使用的。

View.OnClickListener onClickListener = new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        handleClick();
    }
});
findViewById(R.id.someView).setOnClickListener(onClickListener);

或者直接使用匿名内部类:

findViewById(R.id.someView).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        handleClick();
    }
});

在上面的5行代码中,有用的其实只有handleClick(),而我们却必须用5行代码去处理,这是非常繁琐的。在Java
8以前的世界,这样的代码非常多。有一个专门的名词来称呼这种性质的代码,叫“boilerplate
code”,我不知到中文叫什么。。。
现在好了,有了lambda,我们可以这样写:

View.OnClickListener onClickListener = view -> handleClick();
findViewById(R.id.someView).setOnClickListener(onClickListener);

匿名内部类的版本:

findViewById(R.id.someView).setOnClickListener(view -> handleClick());

是不是瞬间觉得简洁优雅了?
从上面的例子可以看到,lambda其实就相当于简化了Functional
Interface的实例的创建。当然,从真正意义上来讲,lambda的意义不止这么一点点,只不过从使用的角度来看,你可以这样看待。

如何在Android 开发中使用Java 8?

 //你可以不用生声明类型 private val age = 1 //用val声明一个不可变的字段 private var name = "lin" //用var声明一个变量
   公式中的符号含义如下,w:星期;c:世纪;y:年(两位数);m:月(m大于等于3,小于等于14,即在蔡勒公式中,某年的1、2月要看作上一年的13、14月来计   算,比如2003年1月1日要看作2002年的13月1日来计算);d:日;[ ]代表取整,即只要整数部分。相比于通用通用计算公式而言,蔡勒(Zeller)公式大大降低了计算的复杂度。

从lambda到Functional Programming

这里稍微讨论一下关于lambda的其他一些特性。
在上面的例子中

View.OnClickListener onClickListener = view -> handleClick();
findViewById(R.id.someView).setOnClickListener(onClickListener);

这里,你既可以吧onClickListener看作是OnClickListener的一个instance,也可以把它看做一个代码块,后面的那句findViewById(R.id.someView).setOnClickListener(onClickListener);就相当于是吧这个代码块传给了view.setOnClickListener()这个函数。也就是说,从某种意义上来讲,你可以把lambda看作是可以相互传递的代码块。而传递代码块,是Functional
Programming(一下简称FP)非常重要的一个特征,虽然说这两者其实没有什么对等关系。因为FP的本质特征是,运行一段代码并不会改变事物的状态,也就是说,没有side-effect。而lambda里面是可以调用所在的类的成员方法的、也可以访问和修改所在类的成员变量的。
话说回来,关于FP我也不是了解的很多,我本身并没有多少FP的经验,虽然对Ruby有一定了解,但Ruby也只是“可以比较好的进行”FP而已,也不是纯粹的FP语言。纯粹的FP语言是List(包括Scheme,Clojure)、Haskell、ML等等这些。关于FP,Robert
Fowler(就是《Clean code》和《The Clean
Coder》的作者)有一个讲得很好的视频在这里。
刚刚讲到,lambda的代码块可以访问所在类的成员变量和成员方法,那对于局部变量?
我们知道,方法内部定义的匿名类是可以访问所在方法的final局部变量的,作为Functional
Interface的简写方式,lambda在这点上面跟匿名类保持了一致。也就是说,lambda可以访问定义它的那个方法的final局部变量。而在Java8里面,lambda还可以访问所谓“Effectively
final”的局部变量。所谓“Effectively
final”的局部变量,就是说除了在定义的时候给了一个初始值以为,在没有改变过她的值的那些局部变量:

int age = 26;   //在这里,age就是Effectively final的局部变量
Runnable r = () -> System.out.println("My age is "+age);
new Thread(r).start();

1.)第一步,安装Java8,并指定项目使用Java8,minSdkVersion <24 只能使用Java8 的 Lambda特性,minSdkVersion>= 24可以使用Java8特性,由此可见全部普及Java8还是需要一定时间的

图片 3

事实上,值不可变的字段,你应该用val声明.在java中,你应该用final关键字修饰.这样你维护代码的时候,一眼看到这个字段就知道它不可能在其他地方被赋值了,如果你不是这样做,修bug,维护这个字段的时候你得小心翼翼.这就是val声明的好处

③. 对蔡勒(Zeller)公式的改进

在Android开发中的应用

可是!!!Android只支持Java
7啊?怎么办?莫急,要相信网友的力量,已经有人开发了gradle的插件,可以将java
8中的labmda表达式在编译出来的bytecode里面给它转化成Java
7兼容的代码。猛戳这里,使用方法那个页面都用,在这里就不赘述了。

关注公众号“小创作” 及时获取最新文章

图片 4

2.)第二步在module的build.gradle中添加相关配置

android {
  ...
  defaultConfig {
    ...
    jackOptions {
      enabled true
    }
  }
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

我这里使用的Android studio 2.2.2版本
Android版本为24,如果是低于24版本的话是无法使用Java
8特性的。如果仅仅只想使用lambda表达式的话完全够用了,我们只需把minSdkVersion设为14即可。

以下是我自己的设置范例

android {
    compileSdkVersion 24
    buildToolsVersion '24.0.2'
    defaultConfig {
        applicationId "com.whoislcj.jsbridge"
        minSdkVersion 14
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        jackOptions {
            enabled true
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    dexOptions {
        incremental true
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    productFlavors {
    }
}

java的switch 与 kotlin的when

   相比于另外一个通用通用计算公式而言,蔡勒(Zeller)公式大大降低了计算的复杂度。不过,笔者给出的通用计算公式似乎更加简洁(包括运算过程)。现将公式列于其下:

关于Lambda

   “Lambda 表达式”(lambda
expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda
abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。

//java代码 switch  { case 1: System.out.println; break; case 2: case 3: System.out.println; break; default: System.out.println("翅膀硬了..."); }

//kotlin代码 when  { 1 -> println 2, 3 -> println else -> print("翅膀硬了...") }
    ***W=[y/4]+r (y/7)-2r(c/4)+m’+d***
    公式中的符号含义如下,r ( )代表取余,即只要余数部分;m’是m的修正数,现给出1至12月的修正数1’至12’如下:(1’,10’)=6;(2’,3’,11’)=2;(4’,7’)=5;5’=0;6’=3;8’=1;(9’,12’)=4(注意:在笔者给出的公式中,y为润年时1’=5;2’=1)。其他符号与蔡勒(Zeller)公式中的含义相同。

如何使用Java 8 Lambda表达式?

你还记得你上一次在java中少写了一个break,而导致的bug吗?
当然kotlin的when还有更强大的操作.

④. 基姆拉尔森计算公式
W= (d+2m+3(m+1)/5+y+y/4-y/100+y/400) mod 7
在公式中d表示日期中的日数,m表示月份数,y表示年数。
注意:在公式中有个与其他公式不同的地方:
把一月和二月看成是上一年的十三月和十四月,例:如果是2004-1-10则换算成:2003-13-10来代入公式计算。

1.)使用Lambda表达式示例

这里以常见的Java匿名内部类为例

(1.)未使用lambda表达式之前

1     findViewById(R.id.to_web).setOnClickListener(new View.OnClickListener() {
2             @Override
3             public void onClick(View v) {
4                 Toast.makeText(MainActivity.this, "终于等到你", Toast.LENGTH_LONG).show();
5             }
6         });

(2.)使用lambda表达式

1      findViewById(R.id.to_web).setOnClickListener((View v) -> {
2             Toast.makeText(this, "终于等到你", Toast.LENGTH_LONG).show();
3         });

(3.)进一步简化

1 findViewById(R.id.to_web).setOnClickListener(v -> Toast.makeText(this, "终于等到你", Toast.LENGTH_LONG).show());

集合操作符

   此处,我们取第④种公式,因此有如下代码:

 2.)扩展知识

  在Android studio 2.1 版本要想实现Lambda表达式的话可以采用第三方AS
retrolambda插件来实现,github地址:

//kotlin写法 //声明一个Student集合 val list = listOf(Student("lin", 1, "男"), Student("zhang", 2, "女"), Student("san", 3, "妖怪"), Student("四", 4, "未知")) //过滤出所有年龄大于2的学生 val filterStudents = list.filter { age > 2 } //判断是否有一个名字叫"lin"的学生,如果有返回true,反之false val hasStudentLin = list.any { it.name == "lin" } //计算出男生的个数 val count = list.count { it.sex == "男" }

//java写法你可能会这样写private boolean hasStudentLin(ArrayList<Student> students) { for (int i = 0; i < students.size { Student student = students.get; if (student.getName().equals { return true; } } return false; }//你还可能这样写boolean hasStudentLin; for (int i = 0; i < students.size { Student student = students.get; if (student.getName().equals { hasStudentLin = true; break; //是不是有可能你一不小心把break漏掉了,导致多循环n次 } }
/**
* 计算星期几? 取值范围从 1 - 7
*
* @param year 当前年份
* @param month 当前月份
* @param date 当前日期
* @return
*/
@JvmStatic
fun computeWeekNum(year: Int, month: Int, date: Int): Int {
     val isMonth1Or2 = month == 1 || month == 2
     val month1 = if (isMonth1Or2) month + 12 else month
     val year1 = if (isMonth1Or2) year - 1 else year
     val weekNum = (date + 2 * month1 + 3 * (month1 + 1) / 5 + year1 - year1 / 100 + year1 / 4 + year1 / 400) % 7
     return weekNum + 1
}

目前遇见的问题

 1.)中文乱码问题

这个问题我一直以为是编码问题,后来才发现是jackOptions {enabled
true}引起的中文乱码,解决办法在project的gradle.properties中添加如下代码

org.gradle.jvmargs=-Dfile.encoding=UTF-8

 2.)Instant Run 目前不能用于 Jack,目前建议暂时关闭Instant Run使用。

kotlin用简单的操作符any,filter
,count就清楚了表达了你想要的逻辑,代码的可读性大大提高,健壮性在某种程度上来说,也算提高了,比如上面student.getName()为null的时候(其实你应该用”lin”来调equals),java的写法就挂掉了,而kotlin则不会。事实上,kotlin还有很多操作符,他们都很实用。

在日历计算中,我们还需要知道当月天数,前一个月的天数以及后一个月的天数,所以:

总结:

   本文仅仅是学习了如何使用在Android
开发中使用Java8的lambda表达式来使代码变得简洁,借来会逐步对Java的一些特性进行学习。

 

以上的对比只是冰山一角,还有很多没提及,比如kotlin最重要的空安全…后面再慢慢补充…

/**
* 计算月份的天数
*
* @param year 当前年份
* @param month 当前月份
* @return
*/
@JvmStatic
fun computeMonthDayCount(year: Int, month: Int): Int {
     when (month) {
         2 -> return if(isLeapYear(year)) 29 else 28
         1, 3, 5, 7, 8, 10, 12 -> return 31
         4, 6, 9, 11 -> return 30
     }
     return -1
}

磨刀不误砍柴工,现在我们的准备工作都已经完成,现在就要开始日历的计算工作了。

/**
 * 计算日历中的日期数据,其中涉及到的数字42就是6×7的布局。
 * @param year 年份
 * @param month 月份
 */
@JvmStatic
fun computeDatesInCalendar(year: Int, month: Int): MutableList<Int> {
     val dates: MutableList<Int> = mutableListOf()
     val monthDayCount = computeMonthDayCount(year, month) //计算出当前这个月有多少天
     val preMonthDayNum = computeWeekNum(year, month, 1) //先获得该日期下是周几?然后计算出上个月有几天要在日期中显示
     val preMonthDayCount = computeMonthDayCount(if (month == 1) year - 1 else year, if (month == 1) 12 else month - 1)
     val nextMonthDayNum = 42 - preMonthDayNum - monthDayCount //计算出下一个月要显示几天
     IntRange(0, preMonthDayNum - 1).forEach { dates.add(preMonthDayCount - (preMonthDayNum - 1 - it)) } //填充上个月要显示的天数
     IntRange(0, monthDayCount - 1).forEach { dates.add(it + 1) } //填充本月要显示的天数
     IntRange(0, nextMonthDayNum - 1).forEach { dates.add(it + 1) } //填充下一月要显示的天数
    return dates
}

现在我们来测试一下结果,如下:

public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() throws Exception {
         List<Integer> data = DateUtil.computeDatesInCalendar(2017, 1);
         for (int i = 0; i < data.size(); i++) {
             System.out.print(data.get(i) + "\t");
             if ((i + 1) % 7 == 0) {
                System.out.print("\n");
             }
         }
         assertEquals(4, 2 + 2);
     }
}

结果输出:

25  26  27  28  29  30  31  
1   2   3   4   5   6   7   
8   9   10  11  12  13  14  
15  16  17  18  19  20  21  
22  23  24  25  26  27  28  
29  30  31  1   2   3   4   

结果我们已经算出来了,现在造UI轮子还难吗?下面是一个Git地址,存了使用该算法的一个demo,只是其中涉及到的公式选用的蔡勒(Zeller)公式
Git项目地址(项目使用java代码编写):
http://git.oschina.net/yugecse/CalendarWidget

  • 项目总结

工作中我们避免不了跟一定的数学公式打交道,尤其是一些UI定制,特殊计算等。因此熟悉一些公式的使用是很有必要的。还有就是注意观察我们需要实现的东西的细节,选择合适的方法进行分步骤解决。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图