java范型
一般都是java中的范型在编译的时候,都会进行类型擦除,那么在真正运行的时候,我们是获取不到范型信息的,因为编译器在编译的时候,会把对应的范型用对应的限定类型给替换掉(如果没有限定,则是object),如下:
public class Generics<T> {
private T value;
public T getValue() {
return value;
}
}
||
\/
public class Generics<Object> {
private Object value;
public Object getValue() {
return value;
}
}
这是正确的,但是我们运行如下代码(会发现不一样的地方):
public class AATest {
public <T> T get(T a){
return a;
}
public static void main(String[] args) {
AATest a = new AATest();
String b = a.get("aaa");
}
}
String b = a.get("aaa");
看到我们居然把可以范型为T(也就是object)的类型的值,赋值给String类型的变量,这难道是编译器没有进行类型擦除么?
我们可以用命令javap -c AATest.class
反编译下字节码,如下:
public class AATest {
public AATest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public <T> T get(T);
Code:
0: aload_1
1: areturn
public static void main(java.lang.String[]);
Code:
0: new #2 // class AATest
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String aaa
11: invokevirtual #5 // Method get:(Ljava/lang/Object;)Ljava/lang/Object;
14: checkcast #6 // class java/lang/String
17: astore_2
18: return
可以看到有这么两行字节码:
11: invokevirtual #5 // Method get:(Ljava/lang/Object;)Ljava/lang/Object;
14: checkcast #6 // class java/lang/String
invokevirtual
这一行表明编译器确实进行了类型擦数,因为能偶看到调用的是object类型的方法
checkcast
这一行表明,jvm字节码进行了String的类型转换
原来是因为我们调用的是a.get("aaa")
,其中aaa是String类型的变量,这样编译器在编译的时候,就会做一层强制转换。所以这和范型擦除不冲突。
可以参考Java泛型类型擦除以及类型擦除带来的问题
scala classTag[T] typeTag[T]
在scala代码中我们会经常看到类似与这样的代码:
def max[T : Comparator] (a:T, b:T) = { … }
其实这种与以下代码是等同的:
def max[T](a:T, b:T) (implicit cp : Comparator[T]) = {
}
[T: Comparator],它主要做了两件事:第一,引入了一个参数类型 T;第二,增加了一个隐式参数 Comparator[T]。
如果在调用该方法的时候没有显式得传入 Comparator[T]
类型的参数,则编译器会自动会从上下问中去查找。所以以下两种方式调用都是可以的:
// 这种方式说明上下文中有隐式参数
max(a,b)
// 这种是显式的传入对应类型的参数
max(a,b)(cp)
在上面说到对于java语言来说,会在编译器期间擦除掉类型,但是对于scala来说,我们在编译器阶段是可以保存类型T的,这就是接下来说的classTag[T] typeTag[T]
:
def max[T : classTag] (a:T, b:T) = { … }
def max[T : typeTag] (a:T, b:T) = { … }
与上面代码不同的是,该隐式参数classTag[T],typeTag[T]
是由编译器给传递进去的,所以我们不需要显式传入。
关于这两者的区别,可以参考:谈谈 Scala 的运行时反射,
对于怎么在运行期间或得对应的T的具体类型,可以参考SPARK中的ExpressionEncoder:
val mirror = ScalaReflection.mirror
val tpe = typeTag[T].in(mirror).tpe
val cls = mirror.runtimeClass(tpe)
而对于classTag获取运行时的T类型,如下代码:
val cl = classTag[T].runtimeClass
隐式解析的搜索范围
参考 Scala 隐式转换
隐式转换本身是一种代码查找机制,所以下面会介绍隐式转换的查找范围:
- 当前代码作用域。最直接的就是隐式定义和当前代码处在同一作用域中。
- 当第一种解析方式没有找到合适的隐式转换时,编译器会继续在隐式参数类型的隐式作用域里查找。一个类型的隐式作用域指的是与该类型相关联的所有的伴生对象。
对于一个类型T它的隐式搜索区域包括如下:
- 假如T是这样定义的:T with A with B with C,那么A, B, C的伴生对象都是T的搜索区域。
- 如果T是类型参数,那么参数类型和基础类型都是T的搜索部分。比如对于类型List[Foo],List和Foo都是搜索区域
- 如果T是一个单例类型p.T,那么p和T都是搜索区域。
- 如果T是类型注入p#T,那么p和T都是搜索区域。
所以,只要在上述的任何一个区域中搜索到合适的隐式转换,编译器都可以使编译通过。