Java 中的泛型

Java 中的泛型

  • 一、泛型的基础知识
  • 二、泛型的使用
    • 1.在类上定义泛型
    • 2.在静态方法上定义泛型
    • 3.在接口上定义泛型
  • 三、泛型通配符
  • 四、泛型的擦除与补偿


一、泛型的基础知识

  • ① 泛型是 Java5 的新特性,属于编译阶段的功能。
  • ② 泛型可以让开发者在编写代码时指定集合中存储的数据类型
  • ③ 泛型的作用:
    • 类型安全: 指定了集合中元素的类型之后,编译器会在编译时进行类型检查,如果尝试将错误类型的元素添加到集合中,就会在编译时报错,避免了在运行时出现类型错误的问题。
    • 代码简洁: 使用泛型可以简化代码,避免了繁琐的类型转换操作。比如,在没有泛型的时候,需要使用 Object 类型来保存集合中的元素,并在使用时强制类型转换成实际类型,而有了泛型之后,只需要在定义集合时指定类型即可,底层帮我们强制类型转换。
  • ④ 在集合中使用泛型
    Collection\<String> list = new ArrayList\<String> ();
    
    • 这就表示该集合只能存储字符串,存储其他类型时编译器报错。
    • 以上代码使用泛型后,避免了繁琐的类型转换,集合中的元素可以直接调用 String 类型特有的方法。
  • Java7 的新特性:砖石表达式
    Collection\<String> list = new ArrayList<>;
    

二、泛型的使用

1.在类上定义泛型

  • 基础语法:public class 类<泛型1, 泛型2, 泛型3 …>
  • 注意:类上声明的泛型只对非静态成员有效。
  • 泛型的名字是任意的只是一个标识符而已。
public class Generic<T1, T2> {
    private T1 id;
    private T2 username;

    public Generic(T1 id, T2 username) {
        this.id = id;
        this.username = username;
    }

    public T1 getId() {
        return id;
    }

    public void setId(T1 id) {
        this.id = id;
    }

    public T2 getUsername() {
        return username;
    }

    public void setUsername(T2 username) {
        this.username = username;
    }

    @Override
    public String toString() {
        return "Generic{" +
                "id=" + id +
                ", username=" + username +
                '}';
    }
}
class Test{
    public static void main(String[] args) {
        Generic<Integer, String> generic1 = new Generic<>(123, "jack");
        System.out.println(generic1);
    }
}

2.在静态方法上定义泛型

  • 在类上定义的泛型,在静态方法上无法使用。如果在静态方法中使用泛型,则需要在方法的返回值类型前进行泛型的声明。
  • 语法格式:public static <泛型1, 泛型2, 泛型3, …> 返回值类型 方法名(形式参数列表) {}
public class Generic {
    public static <T> String Print(List<T> list){
        return list.toString();
    }
}

class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("ddf");
        list.add("ed");
        list.add("dff");
        System.out.println(Generic.Print(list));
    }
}

3.在接口上定义泛型

  • 语法格式:public interface 接口名<泛型1, 泛型2, …> {}
  • 例如:public interface User {}
  • 实现接口时,如果知道具体的类型,则:public class MyClass implements User {}
  • 实现接口时,如果不知道具体的类型,则:public class MyClass implements User {}
//把泛型定义在接口上
public interface Inter<T> {
    void show(T t);
}

/**
 * 子类明确泛型类的类型参数变量:
 */
 class InterImpl implements Inter<String> {
    @Override
    public void show(String s) {
        System.out.println(s);
    }
}
/**
 * 子类不明确泛型类的类型参数变量:
 *      实现类也要定义出类型的
 */
class InterImpl2<T> implements Inter<T> {
    @Override
    public void show(T t) {
        System.out.println(t);
    }
}
class Test{
    public static void main(String[] args) {
        //测试第一种情况
        InterImpl i = new InterImpl();
        i.show("hello");
        //第二种情况测试
        Inter<String> ii = new InterImpl2<>();
        ii.show("100");
    }
}

三、泛型通配符

  • ① 泛型是在限定数据类型,当在集合或其他地方使用到泛型后,那么这时一旦明确泛型的数据类型后,在使用的时候只能给其传递和数据类型匹配的类型,否则会报错。

  • ② 有的情况下,我们在定义方法时,根本无法确定集合中存储元素的类型是什么。为了解决这个 ”无法确定集合中存储元素类型“ 问题,那么 Java 语言就提供了泛型的通配符。

    public class Generic {
        public static void main(String[] args) {
            List<String> list1 = new ArrayList<>();
            list1.add("jack");
            // Generic.print(list1); 这个是非法的因为方法的泛型是 所以只能传一个NUmber数据类型的集合。
            List<Number> list2 = new LinkedList<>();
            list2.add(123);
            Generic.print(list2);
        }
        public static void print(Collection<Number> collection) {
            System.out.println(collection.toString());
        }
    }
    
  • ③ 通配符的几种形式:

    • 无限定通配符,,此处 “?” 可以为任意引用数据类型。

      public class Generic {
          public static void main(String[] args) {
              List<String> list1 = new ArrayList<>();
              list1.add("jack");
              Generic.print(list1); 
              List<Number> list2 = new LinkedList<>();
              list2.add(123);
              Generic.print(list2);
          }
          public static void print(Collection<?> collection) { //现在这里的问号表示一个占位符,想传什么类型就传递什么类型
              System.out.println(collection.toString());
          }
      }
      
    • 上界通配符,,此处 “?” 必须为 Number 及其子类。

      public class Generic {
          public static void main(String[] args) {
              List<String> list1 = new ArrayList<>();
              list1.add("jack");
              // Generic.print(list1); //非法的只能传子类或本身
              List<Number> list2 = new LinkedList<>();
              list2.add(123);
              Generic.print(list2);
              List<Integer> list3 = new ArrayList<>();
              list3.add(654);
              Generic.print(list3);
          }
          public static void print(Collection<? extends Number> collection) {
              System.out.println(collection.toString());
          }
      }
      
    • 下界通配符,,此处 “?”,必须为 Number 及其子类。

      public class Generic {
          public static void main(String[] args) {
              List<String> list1 = new ArrayList<>();
              list1.add("jack");
              // Generic.print(list1); //非法的只能传父类和本身
              List<Number> list2 = new LinkedList<>();
              list2.add(123);
              Generic.print(list2);
              List<Integer> list3 = new ArrayList<>();
              list3.add(654);
              //Generic.print(list3); //非法的只能传父类和本身
              List<Object> list4 = new ArrayList<>();
              list4.add("String");
              Generic.print(list4);
          }
          public static void print(Collection<? super Number> collection) {
              System.out.println(collection.toString());
          }
      }
      
  • ④ ?和 T 的区别

    • T:指定集合元素只能是T类型。
      List<T> list = new ArrayList<T>();
      
    • ?:集合元素可以是任意类型,没有任何意义,就是个占位符,一般方法中,只是为了说明用法。
      List<?> list = new ArrayList<?>();
      
    • ?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ?不行。
      // 可以
      T t = operate();
       
      // 不可以
      ?car = operate()
      

四、泛型的擦除与补偿

  • ① 泛型的出现提高了编译时的安全性,正因为编译时对添加的数据做了检查,则程序运行时才不会抛出类型转换异常。因此泛型本质上是编译时期的技术,是专门给编译器用的。加载类的时候,会将泛型擦除掉(擦除之后的类型为 Object 类型),这个称为泛型的擦除
  • ② 为什么要擦除类型?其本质是为了让 JDK1.5 之前的版本能够兼容同一个类加载器。在 JDK1.5 版本中,程序编译期会对集合添加的元素进行安全检查,如果检查是安全的、没有错误的,那么就意味着添加的元素都属于同一种数据类型,则加载类时就可以把这个泛型擦除掉,将泛型擦除后的类型就是 Object 类型,这样擦除之后的代码就与 JDK1.5 之前的代码一致了。
  • ③ 由于加载类的时候,会默认将类中的泛型擦除为 Object 类型,所以添加的元素就被转化为 Object 类型,同时取出来的元素默认也是 Object 类型。而我们获得集合中的元素时,按理说取出的元素应该是 Object 类型,为什么取出的元素却是实际添加的元素类型呢?
  • ④ 这里做了一个默认的操作,我们称为泛型的补偿。在程序运行时,通过获取元素的实际类型进行强转,这就叫做泛型补偿(不必手动实现强制转换)。获得集合中的元素时,虚拟机会根据获得元素的实际类型进行向下转型,也就是恢复获得元素的实际类型,因此我们就无需手动执行向下转型操作,从本质上避免了抛出类型转换异常。

你可能感兴趣的:(Java,Java)