目录
[TOC]
参考资料:
Why does Scala choose to have the types after the variable names?
Why does Kotlin require type after variable, rather than before?
Eric Lippert's description of why using the C style syntax in C# was probably a mistake.
正文
之前一直使用 Java 作为主要语言,在学习 Kotlin 的时候,突然发现 Kotlin 定义一个变量的语法与之前不太一样。
int a = 5; // java way
var a: Int = 5; // kotlin way
var a = 5 // also kotlin way
为何 Kotlin 在全面兼容 Java 的同时会选择 Scala 的这种语法呢?
为什么很多现代语言(21世纪之后出现)越来越青睐于类型放在变量名后面这种方式呢?
也就是 var + name + :Type 这种形式呢?
大概有这样几个好处:
1、强调命名的重要性
2、强迫对变量进行初始化(减少 NPE)
3、Type Inference(类型推断)
4、规避 Java 数组的一些困惑
JEP 286 :Java 10 也将 var 类型推导这种特性加入到 局部变量 中
强调命名的重要性
我们阅读代码的时候,都是从左到右读下来的。
假如我们省略变量类型的话,就像 动态类型语言一样了,所以如果变量的命名不能暗示它的类型信息,那么可能
我们还需要看一下这个变量属于什么类型的(当然,IDE can help us)
var testData = getTestList()
var testDataList = getTestList() // better
强迫对变量进行初始化
Kotlin 相比与 Java 中最大的改进应该是利用语法尽可能去减少 NPE :
class MainActivity : Activity() {
var mButton : Button // error : Property must be initialized or be abstract
var mButton : Button? = null //valid , instantiate with null
lateinit var mButton : Button // or we can use lateinit
}
假设是 Java 的话,虽然有隐式初始化,但你在使用成员变量的时候,你还是不知道这个成员变量是否是赋值了,所以你需要防御性编程:
class MainActivity extends Activity {
Button mBtn;
if (mBtn != null){
// do something
}
}
为类型推断(Type Inference)做准备
Larry Wall 曾在 《Programming Perl》中说过 "程序员的3个美德" : 懒惰、不耐烦、骄傲。
类型推断其实就体现程序员的"懒惰",其实我们在 Java 中就曾经见到过这种特性:
HashMap<String,String> map = new HashMap<String,String>();
// IDE can inference the generic
HashMap<String,String> map = new HashMap<>();
// lambda
IntStream.of(1,2,3,4,5)
.forEach(i -> System.out.println(i)); // (int i) -> sout(i)
我们知道 Kotlin 中可以省略类型,IDE 会自动从赋值语句中推断出类型信息:
var str: String = "" // same as Java: String str = "";
var string = "" // you can skip the type
var char = 'a' // char
var int = 0 // int
var long = 0L
var float = 0F
var double = 0.0
var numArray = IntArray(3)
// 特殊情况,如果赋值是 null 的话,它的类型并不是 Any
var a = null // a isn't Any,we can't use hashcode or toString
但为何说这种 var + name + :Type 这种写法有助于类型推导呢?
我们看下 C++ 的类型推导 auto,可以简化很多代码 :
string search_item("Barth John");
for(std::pair<multimap<string,string>::iterator,multimap<string,string>::iterator> pos=authors.equal_range(search_item);pos.first!=pos.second;++pos.first)
cout<<pos.first->second<<endl;
// use auto
for(auto pos=authors.equal_range(search_item);pos.first!=pos.second;++pos.first)
但 Kotlin 中的 var 和 C# 中的 var (类似于 C++ 的 auto ) 又有什么区别呢?
先看下 C# 的例子,出自于: Eric Lippert's description of why using the C style syntax in C# was probably a mistake.
C# 中的格式是:type identifier
Kotlin 中的格式是: var identifier : type
因此 C# 的设计者也很后悔没有在 C# 1.0 的时候就使用 var identifier : type 的格式,导致编程语言中有很多 不一致。
因为 var identifier : type 这种形式,在隐式推导流行起来的时候可以非常自然地进行过度:
var identifier : type ---> var identifier
而 C++ 和 C# 中,假设按照一样的路数来,就变成了:
int a = 5;
b = 5; // skip type,but it isn't a declaration
这显然是不行的,因此需要 fake 出一种万能类型来填充之前 type 所在的地方。
于是 C++ 有了 auto,C# 有了 var。
但实际上这又是非常别扭的,明明你不想写类型信息,但又自欺欺人一样写上了一个 万能类型。
所以 Go 语言在这里处理的就好很多:
var name string = "tom"
var name = "tom"
name := "tom"
Java 现在怎么使用这种形式呢? 其实也不是不行。
Lombok 是一个很好的项目,其中它就提供了这样的类型推导
val example = new ArrayList<String>(); example.add("Hello, World!"); val map = new HashMap<Integer, String>(); map.put(0, "zero"); map.put(5, "five"); var x = "Hello";
规避已有的一些语法问题
先看下 Java 的数组定义:
int[] a; // valid suffix on type
int b[]; // C++ way , valid suffix on name
好像还行,但如果我们把情况弄复杂一点:
int[] a,b; // all int[]
int c[],d; // c is int[],d is int
int[] e,f[]; // e is int[], f is int[][]
Java 这种形式应该来说是为了兼容 C++ 的语法(谁叫当初 Java 的目的就是取代 C++ 呢),但是这种
对于初学者来说其实是比较困惑的。
所以 Kotlin 一刀切,所有的数组都变成了这样:
| Kotlin | Java |
|---|---|
| IntArray | int[] |
| FloatArray | float[] |
| Array (不支持协变) | T[](支持协变) |
但这样也有不足的地方,就是在表达多维数组的时候比较复杂:
val array3d = Array<Array<Array<Int>>>(3){ Array<Array<Int>>(3){ Array<Int>(3){ it -> it} } }
// 但幸运的是我们可以缩写
val array3d = Array(3){ Array(3){ Array(3){ it -> it} } }
关于可读性上的一些争议
在 C# 引入 var 之后,就一直争论不休,习惯于 C 语言式命名方式的编程玩家觉得 var 反而会降低代码的可读性。
但在 Kotlin 的官方 FAQ 说了:
Why have type declarations on the right?
We believe it makes the code more readable. Besides, it enables some nice syntactic features. For instance, it is easy to leave type annotations out. Scala has also proven pretty well this is not a problem.
对于可读性上的争论,个人觉得见仁见智吧,如果真的完全滥用了 var 且忽略掉所有类型,加上如果代码写的烂
一点,看上去基本上和动态语言貌似没有什么区别了(但 IDE 还是能为静态语言提供很好的支持,比如说有哪些
函数、成员变量等)。
总结
Kotlin 作为一门 2010 年才诞生的语言,站在很多巨人的肩膀上去发展,吸收了 C#、Scala、Java等 的各种精华,也摒弃了这些语言的很多缺点。
variable: Type 这种形式最早可以追溯到 Pascal,主要目的是用于教学(然而国内基本都是C、C++、Java)。
而 C系列的 Type variable ,已经是工业界养成的习惯了,毕竟这种习惯上的哲学就类似于
字节序上面的 大端和小端,不同习惯的人有不同的看法,这里我们只能自己去适应语言。
但从整个编程语言的趋势上来看,现代化的编程语言(Go、Rust、Kotlin、Swift)基本都选择了 Pascal 的 variable: Type 形式,工业界也越来越愿意接受 type inference ,作为习惯于C系列语言的程序员们,也要慢慢适应时代了。
最后再来看个 Scala 的代码:
val shapeInfo: HashMap[Shape, (String, String)] = makeInfo()
- We define a value here, not a variable or method (val)
- The name of the thing we define is shapeInfo
- If you care about it, here's the type (HashMap[...])