第八章 泛型

文章目录

  • 语法糖
  • 什么是泛型?能解决什么问题?
  • 说一下Java泛型工作机制?什么是类型擦除?
  • 泛型的使用
  • 什么是泛型中的限定通配符和非限定通配符?

语法糖

语法糖(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的引用实现参数的任意化,需要在使用前进行强制类型转换。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处在于:

  • 提供了编译期的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException。
  • 消除了代码中许多的强制类型转换,增强了代码的可读性。

说一下Java泛型工作机制?什么是类型擦除?

泛型是通过类型擦除来实现的。
类型擦除是编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如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

第八章 泛型_第1张图片

  1. 优点
  • 向前兼容性好
    是确保能和Java 5之前的版本进行兼容
  • 减小运行时负担
    在Java中,是不存在类似List,List等等这样的类型,真正被加载进方法区存储的只有List类型
  1. 缺点
  • 基本类型无法作为泛型实参
  • 泛型类型无法用做方法重载
    类型擦除意味着List和List编译后其类型都是List,也就是属于同个方法:
  public void testMethod(List<Integer> array) {}
  public void testMethod(List<Double> array) {}    // compile error
  • 泛型类型无法当做真实类型使用
    由于类型擦除后像List这样的类型是不存在的,所以也就无法直接当成真实类型使用:
  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);

泛型的使用

  1. 泛型类和泛型接口
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可以推断尖括号里应该是什么泛型信息。
 }
  1. 泛型的方法
    所谓泛型方法,就是在声明方法时定义一个或多个类型形参。 泛型方法的用法格式如下:
修饰符<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) ;  // 输出内容  
  }  
};
  1. 泛型构造器
    正如泛型方法允许在方法签名中声明类型形参一样,Java也允许在构造器签名中声明类型形参,这样就产生了所谓的泛型构造器。
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");
}

什么是泛型中的限定通配符和非限定通配符?

表示了非限定通配符,因为可以用任意类型来替代。

使用通配符的目的是来限制泛型的类型参数的类型。有两种限定通配符:一种是它通过确保类型必须是T的子类来设定类型的上界,另一种是它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。

限定通配符 非限定通配符
表示
子类型通配符

父类型通配符
功能 通过确保类型必须是T的子类来设定类型的上界 通过确保类型必须是T的父类来设定类型的下界 可以用任意类型来替代
举例 List可以接受任何继承自T的类型的List
例如List可以接受List< Integer >或List< Float >
List
可以接受任何T的父类构成的List

你可能感兴趣的:(Java面试之旅,泛型)