Overview
本节主要介绍一下四门语言的数据类型
Java 篇
Java 的数据类型主要分为基本数据类型和引用数据类型
基本类型
Java 的基本类型有 int, long, float, double, char, boolean,即以小写字母开头的数据类型。
例
int x = 2;
float y = 0.1f;
boolean flag = false;
引用数据类型
Java 中除基本数据类型之外的所有数据都属于引用数据类型,并且所有引用数据类型都是 Object 类的子类。
BigDecimal
Java 的基本数值类型及其包装类在进行浮点操作时,很有可能会损失精度。为此,Java 引进了 BigDecimal 类,可以在不损失精度的情况下进行计算。但是不幸的是 Java 的 BigDeciaml 的构造器设计得比较反人类。
System.out.println(2.0 - 1.8); //0.19999999999999996
System.out.println(new BigDecimal(2.0).subtract(new BigDecimal(1.8))); //0.1999999999999999555910790149937383830547332763671875
System.out.println(new BigDecimal("2.0").subtract(new BigDecimal("1.8"))); //0.2
从上面的例子可以看到,BigDecimal 有字符串和数值两种构造器,字符串构造器构造的 BigDecimal 才能满足我们的需求,而不是直接使用数值进行构造,基本上用过这个类的人都会踩过这个坑。
类型转换
隐式转换
所谓的隐式转换即无需显示指明需要进行类型转换,由编译器根据变量的类型自动推断。
Java 的隐式转换只发生在使用字面值给变量赋值或者向上转型时
例:
byte b = 1;
int i = b; // byte 类型向上转型为 int 类型
char c = 1; // 使用 int 型的字面值 1 为 char 变量赋值
在以上的例子,Java 默认数值字面值为 int 型,只要该数值在 byte 取值范围内,字面值就可以直接赋值给 byte 类型变量。此外由于 int 类型取值范围比 byte 大,所以 byte 变量可以直接赋值给 int 变量,即向上转型。
显式转换
无论是基本类型还是引用类型都是使用 (dataType)value
的语法进行转换。显示转换又称作强制转换,且向下转型时很可能会丢失精度。
int i = (int) 99.98
类型推断
Java 使用关键字 isInstanceof
来判断变量的类型
例:
private static void bar(Object foo) {
if (foo instanceof String) {
String fooString = (String) foo;
System.out.println(fooString.toUpperCase());
}
}
Object foo = "foo";
bar(foo);
从以上例子可以看到,第二行通过 instanceof
已经知道 foo
是 String
类型了,但是还需要声明一个变量进行强制类型转换,这一做法稍显多余。稍后可以看到 Groovy 的做法要优雅得多。
Groovy 篇
由于 Groovy 就是 Java,所以 Groovy 的数据类型也与 Java 完全一样。但是 Groovy 根据上下文的不同会对自动数据进行包装。
int i = 1
1.toString()
编译以上代码后可以看到,第一行编译后的结果与 Java 完全一样,而第二行由于调用了方法,所以 Groovy 会将其转换为 Integer 类型。这一自动化操作比 Java 的装箱,拆箱操作要更加先进。
由于以上的原因,我们可以认为 Groovy 中一切皆对象。通常来说 Groovy 将数据类型分为静态类型和动态类型。
静态类型
静态类型即声明变量时指定了数据类型,该种变量声明后就不可以再改变数据类型。
例
int x = 2
float y = 0.1f
boolean flag = false
动态类型
动态类型即使用关键字 def
声明的变量。与静态类型变量不同,动态类型变量在定义后可以任意改变类型。
例:
定义几个动态类型变量
def dx = 2
def dy = 0.1
def dflag = false
改变以上数据的类型
Date staticDate = new Date()
// 静态类型变量
// 无法改变数据类型,会报 'java.lang.Integer' to class 'java.util.Date'
// staticDate = 2
// 动态类型变量
// 变量可以由 Date 类型转换为 Integer 类型
def dynamicDate = new Date()
dynamicDate = 2
BigDecimal
与 Java 不同,Groovy 中定义一个浮点类型数据实际就是使用 BigDecimal 的字符串构造器定义一个 BigDecimal,所以 Groovy 中不需要做任何额外操作直接就可以做精确的浮点运算。
println(2.0 - 1.8) //0.2
类型转换
隐式转换
同 Java
显式转换
Groovy 通常使用 toXXX()
这样的方法来实现基本类型的显式转换,语法看起来更加优雅
int i = 99.98.toInteger()
double d = "99.12".toDouble()
需要注意的是 Groovy 并没有提供到 char
类型的转换方法,所以 char
类型还是需要使用 Java 样式的转换方式或者使用以下方式的类型转换:
char c = 99 as char
而对于引用类型的话则转换方式则类似 Java。
类型推断
对于引用类型而言,Groovy 也使用关键字 instanceof
做类型推断,但是 Groovy 也提供了 isXXX()
的语法糖来进行基本类型的推断。
例:
def static bar(foo) {
if (foo instanceof String) {
println(foo.toUpperCase())
}
if (foo instanceof Double) {
println(foo.intValue())
}
if (foo.isDouble()) {
println(foo.toDouble())
}
}
def foo = "foo"
bar(foo)
从以上例子中可以看到,通过 instanceof
进行类型判断以后,变量就可以直接调用其真实类型的方法,无需再声明另一个变量并进行强制类型转换,这一做法相比 Java 方便很多。但是如果使用的是 isXXX()
的语法糖进行类型判断的话则不支持此特性。
Scala 篇
Scala 中并不存在基本类型,所有数据都是对象,且都继承自 Any
类。Scala 数据类型主要分为 AnyVal
和 AnyRef
,两者都是 Any
的子类。
AnyVal
AnyVal 分为 Int, Long, Float, Double, Boolean, Char, Byte,Unit 几种类型。除了最后一种,其它都可以看做是 Java 上的基本类型的包装类。而 Unit 则相对于 Java 平台上的 Void
,即表示没有返回值。
var x: Int = 2
var y: Float = 0.1f
var flag: Boolean = false
AnyRef
AnyRef 是 Scala 中所有引用类的基类,本质上就是 Java 中的 Object 类。
BigDecimal
Scala 中的 BigDecimal 虽然没有 Groovy 那么方便,但是表现也与预期一样
println(BigDecimal(2) - BigDecimal(1.8))
类型转换
隐式转换
类似 Java
显式转换
对于 AnyVal 类型,Scala 科研使用方法进行类型转换
例:
var i:Int = 99.98.toInt
var d:Double = "99.12".toDouble
对于 AnyRef类型,Scala 则使用方法 asInstanceOf[type]
类进行类型转换
例:
val fooString: String = foo.asInstanceOf[String]
类型推断
Scala 使用 isInstanceOf
来进行类型推断,整个过程与 Java 基本一样,需要额外定义变量并显式进行类型转换。
def bar(foo: Any): Unit = {
if (foo.isInstanceOf[String]) {
val fooString: String = foo.asInstanceOf[String]
println(fooString.toUpperCase)
}
}
Kotlin 篇
Kotlin 中一切皆对象,Any
为所有类的基类。
数据类型
Kotlin 中的所有类型都是引用类型,但是与其它几门语言不一样,Kotlin 中的 Char 类型只属于字符类型,而不属于数值类型。
var x: Int = 2
var y: Float = 0.1f
var flag: Boolean = false
val c: Char = 1 // 错误,Char 不是数值类型
BigDecimal
Kotlin 并没有专门的 BigDecimal 类,需要调用 Java 代码来完成计算
println(BigDecimal("2").subtract(BigDecimal("1.8")));
类型转换
隐式转换
Kotlin 只有使用字面值给变量赋值时的支持自动隐式转换,并且 Kotlin 不支持自动向上转型,这点上比其它几门语言都要严格不少。
var b: Byte = 1
// 错误,不支持自动向上转型
val i: Int = b
显式转换
Kotlin 也使用方法进行内置类型的转换
val i:Int = 99.98.toInt()
"99.12".toDouble()
对于其它类型的数据,Kotlin 使用 as
关键字进行类型转换。
val fooString: String = foo as String
类型推断
Kotlin 使用关键字 is
来进行类型判断,在类型判断后数据会被自动隐式转换为真正的类型,所以可以直接调用该类型的所有方法。在 Kotlin 的文档中称这一特性为 "Smart Cast"。
fun bar(foo: Any) {
if (foo is String) {
// 该处发生了 Smart Cast, Any 类型的 foo 在该处被自动转换成了 String 类型
println(foo.toUpperCase())
}
}
val foo = "foo"
bar(foo)
Summary
- 除了 Java 之外,可以认为其它三门语言中一切都是对象
- 每种语言的数据类型的基类的叫法都有差别:Java(Object), Grooy(Object), Scala(Any), Kotlin(Any)
- 除了 Java 之外,其它语言都可以使用更优雅的方法调用来完成内置类型的强制类型转换
文章源码见 https://github.com/SidneyXu/JGSK 仓库的 _05_datatype
小节