1 泛型引入
我们来引入一个需求,在ArrayList集合中添加三个Dog对象,并遍历该集合,输出Dog对象的姓名。这点我们很容易做到,只需要在遍历的时候将Object对象向下转型为Dog就可以正常遍历了。但是,假如程序员在添加Dog对象时不小心添加了一只猫呢? 来看下面的代码:
import java.util.ArrayList; /** * @author 兴趣使然黄小黄 * @version 1.0 */ public class Generic0 { public static void main(String[] args) { ArrayList arrayList = new ArrayList(); Dog dog1 = new Dog("小黄"); Dog dog2 = new Dog("小花"); Cat cat = new Cat("猫猫"); arrayList.add(dog1); arrayList.add(dog2); arrayList.add(cat); // 不小心添加了一只猫 // 遍历 for (Object o : arrayList) { // 向下转型 Dog dog = (Dog) o; System.out.println(dog.name); } } } class Dog{ public String name; public Dog(String name) { this.name = name; } } class Cat{ public String name; public Cat(String name) { this.name = name; } }
🐱 传统方法可能遇到的问题:
不能对加入到集合的ArrayList中的数据类型进行约束(不安全);
遍历的时候,需要进行类型转换,在数据量大的情况下,对效率有影响。
而通过使用泛型,则可以很好的解决该类问题。
在创建 ArrayList 的时候,通过下面的语句使用泛型,限制元素的类型:
ArrayList<Dog> arrayList = new ArrayList<>(); 1
则编译器就会对类型进行检查,避免了将Cat加入ArrayList的错误情况:
🐰 泛型的好处:
编译时,检查添加的元素类型,提高了安全性;
减少了类型转换的次数,提高了效率;
不再提示编译警告。
2 泛型讲解
2.1 泛型的介绍
🐱 简介:
泛型又称参数化类型,是jdk1.5出现的新特性,解决数据类型的安全性问题;
在类声明或实例化时只要指定好具体的类型即可;
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生 ClassCastException 异常。同时,代码更加简洁、健壮;
泛型的作用:在类声明时通过一个标识表示类中某个属性的类型,或者某个方法的返回值类型,或者参数类型。
class Person<E>{ E element; // E 表示 element 的数据类型,该数据类型在定义 Person 对象的时候指定,即在,编译期间,就确定E的类型 public Person(E element){ // E 也可以是参数类型 this.element = element; } public E method(){ // 返回类型为E return element; } }
2.2 泛型实例
1.创建 3 个学生对象;
2.放入HashSet中,元素为Student;
3.通过遍历HashSet,将值放入到 HashMap 中,Key 为 String name(HashSet中的Student), Value 为学生对象;
4.使用两种方式遍历,HashMap。
import java.util.*; /** * @author 兴趣使然黄小黄 * @version 1.0 */ public class Generic2 { public static void main(String[] args) { HashSet<Student> students = new HashSet<>(); students.add(new Student("黄小黄", 20)); students.add(new Student("祢豆子", 7)); students.add(new Student("春卷儿", 19)); HashMap<String, Student> hashMap = new HashMap<>(); // 遍历 HashSet,并将值传入 HashMap Iterator<Student> studentIterator = students.iterator(); while (studentIterator.hasNext()){ Student s = studentIterator.next(); hashMap.put(s.name, s); } // 遍历 HashMap System.out.println("=================== 方式一 =================="); Set<String> keySet = hashMap.keySet(); for (String s : keySet) { System.out.println(s + "--->" + hashMap.get(s)); } System.out.println("=================== 方式二 =================="); Set<Map.Entry<String, Student>> entrySet = hashMap.entrySet(); for (Map.Entry es : entrySet) { System.out.println(es.getKey() + "--->" + es.getValue()); } } } // 学生类 class Student{ public String name; public int age; public Student(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
2.3 泛型使用细节
给泛型指定数据类型时,必须是引用类型,不能是基本数据类型;
给泛型指定具体类型后,可以传入该类型或者其子类类型;
泛型的使用形式如下:
如果不使用泛型,默认是使用 Object类型。
3 自定义泛型类
🆔 基本语法:
class 类名<T, R...>{ ... }
普通成员可以使用泛型(属性方法);
使用泛型的数组,不能初始化, 因为数组在 new 时不能确定泛型的类型,无法确定开辟的空间;
静态方法中不能使用类的泛型, 因为静态是和类相关的,在类加载的时候,对象还没创建,如果静态方法和静态属性使用了泛型,JVM就无法完成初始化;
泛型类的类型,是在创建对象时确定的;
如果在创建对象时没有指定类型,默认为Object。
4 自定义泛型接口
🆔 基本语法:
interface 接口名<T, R...>{ ... }
- 接口中,静态成员也不能使用泛型;
- 泛型接口的类型,在继承接口或者实现接口的时候确定;
- 没有指定类型,依然为Object;
- 在 jdk8 中,可以在接口中使用默认方法,该方法可以使用泛型。
5 自定义泛型方法
🆔 基本语法:
修饰符 <T, R...>返回类型 方法名(参数列表){ ... }
泛型方法,可以定义在普通类中,也可以定义在泛型类中;
当泛型方法被调用时,类型就会确定;
public void f(E e){},修饰符后面没有 ,则 f 不是泛型方法,而是使用了泛型;
泛型方法,可以使用类声明的泛型,也可以使用自己声明的泛型。
6 泛型的继承与通配
泛型不具有继承性;
- :支持任意泛型类型;
- :支持A类以及A类的子类,规定了泛型的上限;
- :支持A类以及A类的父类,不限于直接父类,规定了泛型的下限。
🐰 通配示例如下,具体见注释:
import java.util.List; /** * @author 兴趣使然黄小黄 * @version 1.0 */ public class Generic3 { // <?> 任意泛型 public static void printCollection01(List<?> c){ for (Object object: c) { System.out.println(object); } } // <? extends A> A表示上限,可以接受A或者A的子类 public static void printCollection02(List<? extends A> c){ for (Object object: c) { System.out.println(object); } } // <? super A> A表示下限,可以接受A以及A的父类 public static void printCollection03(List<? super A> c){ for (Object object: c) { System.out.println(object); } } } class C{} class A extends C{} class B extends A{}