语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家Peter.J.Landin发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。Java中最常用的语法糖主要有泛型、变长参数、条件编译、自动拆装箱、内部类等。虚拟机并不支持这些语法,它们在编译阶段就被还原回了简单的基础语法结构,这个过程成为解语法糖。
泛型的目的: Java 泛型就是把一种语法糖,通过泛型使得在编译阶段完成一些类型转换的工作,避免在运行时强制类型转换而出现ClassCastException,即类型转换异常。
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
泛型的本质是参数化类型,即将所操作数据类型指定为参数。实现数据类型的任意化。
在Java1.5之前(泛型 是JDK5中引入的一个新特性),在集合中存储对象时通过对类型Object的引用实现参数的任意化,需要在使用前进行强制类型转换。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处在于:
泛型是通过类型擦除来实现的。
类型擦除是编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如List在运行时仅用一个List来表示。
这是因为不管为泛型的类型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一类处理,在内存中也只占用一块内存空间。从Java泛型这一概念提出的目的来看,其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
这样做的目的,是确保能和Java 5之前的版本进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。
List<String> l1 = new List<String>();
List<Double> l2 = new List<Double>();
System.out.println(l1.getClass() == l2.getClass());
// 输出 true
// 由于类型擦除,l1.getClass() == l2.getClass() == java.util.List
public void testMethod(List<Integer> array) {}
public void testMethod(List<Double> array) {} // compile error
static <T> void genericMethod(T t) {
T newInstance = new T(); // compile errror
Class c = T.class; // compile errror
List<T> list = new ArrayList<T>(); // compile errror
if (list instance List<Integer>) {} // compile errror
}
// 这也是Gson.fromJson需要传入Class的原因
public <T> T fromJson(String json, Class<T> classOfT)
throws JsonSyntaxException {
Object object = fromJson(json, (Type)classOfT);
return Primitives.wrap(classOfT).cast(object);
}
class GenericClass<T> {
public static T max(T a, T b) {}
}
List<String> strList = new Array<>();
strList.add("Hallo");
String value = strList.get(0); // 返回Object,compile errror
String value = (String)strList.get(0);
public class Container<K, V> {
private K key;
private V value;
public Container(K k, V v) {
key = k;
value = v;
}
public K getkey() {
return key;
}
public V getValue() {
return value;
}
public void setKey() {
this.key = key;
}
public void setValue() {
this.value = value;
}
}
在使用Container类时,只需要指定K,V的具体类型即可,从而创建出逻辑上不同的Container实例,用来存放不同的数据类型。
public static void main(String[] args) {
Container<String,String> c1=new Container<String ,String>("name","hello");
Container<String,Integer> c2=new Container<String,Integer>("age",22);
Container<Double,Double> c3=new Container<>(1.1,1.3); // 泛型的“菱形”语法:Java允许在构造器后不需要带完成的泛型信息,只要给出一对尖括号(<>)即可,Java可以推断尖括号里应该是什么泛型信息。
}
修饰符<T, S> 返回值类型 方法名(形参列表){
方法体
}
class Demo{
public <T> T fun(T t){ // 可以接收任意类型的数据
return t ; // 直接把参数返回
}
};
// 当调用fun()方法时,根据传入的实际对象,编译器就会判断出类型形参T所代表的实际类型。
public class GenericsDemo26{
public static void main(String args[]){
Demo d = new Demo() ; // 实例化Demo对象
String str = d.fun("汤姆") ; // 传递字符串
int i = d.fun(30) ; // 传递数字,自动装箱
System.out.println(str) ; // 输出内容
System.out.println(i) ; // 输出内容
}
};
public class Person {
public <T> Person(T t) {
System.out.println(t);
}
}
public static void main(String[] args){
// 隐式推断泛型参数
new Person(22);
// 显示指定泛型参数
new<String> Person("hello");
}
使用通配符的目的是来限制泛型的类型参数的类型。有两种限定通配符:一种是 extends T>它通过确保类型必须是T的子类来设定类型的上界,另一种是 super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。
限定通配符 | 非限定通配符 | ||
表示 | extends T> 子类型通配符 |
super T> 父类型通配符 |
> |
功能 | 通过确保类型必须是T的子类来设定类型的上界 | 通过确保类型必须是T的父类来设定类型的下界 | 可以用任意类型来替代 |
举例 | List extends T>可以接受任何继承自T的类型的List 例如List extends Number>可以接受List< Integer >或List< Float > |
List super T> 可以接受任何T的父类构成的List |
|