一、泛型介绍:
JDK5除了推出foreach新循环,还推出了一个新特性:泛型
泛型作用:在一个类或接口的声明处指定该类中某个属性的类型。或声明方法返回值的类型或方法参数的类型
- 泛型也称为参数化类型。 它允许我们在一个类或接口的声明处指定该类中某个属性的类型或
方法返回值的类型或方法参数的类型,使得我们使用这个类时更方便更灵活。
- 使用了泛型的类叫泛型类、 使用了泛型的接口叫泛型接口、 使用了泛型的方法叫泛型方法
泛型在集合中广泛使用,用于指定该集合中的元素类型。
注意:当没有指定泛型时,默认类型为Object类型。
用泛型的好处: 避免了类型强转的麻烦, 在编译期就做了类型检查,避免了在运行时出现ClassCastException。
注意: 泛型具体的类型不能为8种基本类型(可以为8种包装类型)
注意:泛型只在编译阶段有效(检查数据类型)正确检验泛型后,编译后会将泛型的相关信息擦除。
上述结论可通过下面反射的例子来印证: (因为绕过了编译阶段也就绕过了泛型,输出结果为:[zyq, 100])
import java.lang.reflect.Method; import java.util.ArrayList; public class Main{ public static void main(String[] args){ ArrayList<String> a = new ArrayList<String>(); a.add("zyq");//1.集合中添加一个String字符串 Class c = a.getClass(); try{ Method method = c.getMethod("add",Object.class);//获取add方法对象 method.invoke(a,100);//2.执行add方法并给add方法传入参数100 }catch(Exception e){ e.printStackTrace(); } System.out.println(a);//打印集合中元素,可以看到集合中存入了 "zyq" 和 100 }//100不满足泛型的要求, 但是这是用反射的方式存入的(用字节码方式操作的,不是用编译的方式) }
二、泛型用在集合中:
package com.zyq.se.javaGenerics; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; /** * @author zhaoYQ * JDK5除了推出foreach新循环,还推出了一个新特性:泛型 * * 泛型也称为参数化类型,它允许我们在一个类或接口的声明处指定该类中某个属性的类型或 * * 方法返回值的类型或方法参数的类型,使得我们使用这个类时更方便更灵活。 * 使用了泛型的类叫泛型类、 使用了泛型的接口叫泛型接口、 使用了泛型的方法叫泛型方法 * * 泛型在集合中广泛使用,用于指定该集合中的元素类型。 * 注意:一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。 * 用泛型的好处: 避免了类型强转的麻烦, 在编译期就做了类型检查,避免了在运行时出现ClassCastException。 * * 注意: 泛型只在编译阶段有效(检查数据类型)正确检验泛型后,会将泛型的相关信息擦除。 * 上述结论可通过下面反射的例子来印证: (因为绕过了编译阶段也就绕过了泛型,输出结果为:[zyq, 100]) * ArrayList<String> a = new ArrayList<String>(); * a.add("zyq"); * Class c = a.getClass(); * try{ * Method method = c.getMethod("add",Object.class); * method.invoke(a,100); * * }catch(Exception e){ * e.printStackTrace(); * }System.out.println(a); * 注意: 泛型具体的类型不能为8种基本类型 */ public class Demo1_CollectionGenericity { public static void main(String[] args) { /* 1.interface Collection<E>接口在定义处,指定了一个泛型E。 我们在实际使用集合时可以指定E的实际类型。 这样一来,编译器会检查集合中的元素和泛型规定的类型是否匹配。 例如,集合的方法: boolean add(E e) 编译器会检查我们调用add方法向集合中添加元素时,元素的类型是否为E指定的 类型,不符合编译不通过。 */ //集合创建的new后边的泛型可以省略 (但是尖括号不能省略) Collection<String> c = new ArrayList<>(); c.add("one"); c.add("two"); c.add("three"); c.add("four"); c.add("five"); //c.add(123);//不符合E在这里的实际类型String,因此编译不通过。 /* * 2.使用新循环遍历时,如果集合指定了泛型,那么接收元素时 * 可以直接用对应的类型接收元素。 */ //新循环遍历集合时,底层会自动改回成迭代器遍历 for(String s : c){ String x=s+"。"; System.out.println(x); } //3.迭代器也支持泛型,指定时要与其遍历的集合指定的泛型一致。 Iterator<String> e = c.iterator(); while(e.hasNext()){ String str = e.next(); System.out.println(str); } } }
三、泛型用在类上
- 泛型类可以用于:规定成员变量的类型, 方法参数类型 , 返回值类型
- 在定义类时使用了泛型, 需要在创建对象的时候确定泛型:
- (jdk1.7后右边的泛型可以省略<第二个泛型括号内容可以省略new对象时第二个泛型具体的类型>)
泛型在定义的时候不具体,使用的时候才变得具体。在使用的时候确定泛型的具体数据类型。
- 案例:
package com.zyq.se.javaGenerics; /** * @author zhaoYQ * 泛型类的演示 * 使用泛型类定义的类型 : 规定成员变量的类型, 方法参数类型 , 返回值类型 */ public class Demo2_GenericityClass { public static void main(String[] args) { //1.在创建对象的时候确定泛型: 创建Person对象时需要确定R的类型 // (jdk1.7后右边的泛型可以省略<第二个泛型括号内容可以省略>) //泛型在定义的时候不具体,使用的时候才变得具体。在使用的时候确定泛型的具体数据类型。 Person<String> p=new Person<String>(); //这里传入<String>的意思相当于将字符R替换为了String。 效果如下: /* class Person<String>{ private String gender; public void setGender(String e){ this.gender=e; } //泛型方法getE的返回值类型为R, // R的类型由外部指定(当创建对象时确定R的类型) public String getGender(){ return gender; } } */ //调用泛型方法setE()给p对象的gender属性赋值 p.setGender("男"); //测试获取gender属性值 System.out.println(p.getGender()); //调用泛型方法getE()获取p对象的gender属性值 String rs=p.getGender(); //测试获取gender属性值 System.out.println("rs = " + rs); //2.泛型类中表示类型的R可以不传入实际的类型(泛型实参) //在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制, // 此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话, // 在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。 //(但是在编程时容易出现类型转化异常) Person pe=new Person(); pe.setGender(444); } } /** * @author zhaoYQ * 类定义上加泛型,可以用任意一个字母代表类型,当 * 创建此类对象 时需要给此字母传入具体的类型 * (此字母一般习惯上大家都会用T或者E<一般不会随便写>) * @param <R> */ class Person<R>{ private R gender; //泛型方法形参t的类型也为R, // R的类型由外部指定(用对象调用此方法传参时确定) public void setGender(R e){ this.gender=e; } //泛型方法getE的返回值类型为R, // R的类型由外部指定(当创建对象时确定R的类型) public R getGender(){ return gender; } }
四、泛型方法:
- 使用泛型方法声明的类型 : 方法参数类型 , 返回值类型
- 案例:
package com.zyq.se.javaGenerics; /** * @author zhaoYQ * 泛型方法的演示 * 使用泛型方法声明的类型 : 方法参数类型 , 返回值类型 */ public class Demo3_GenericityMethod { /** * <T> 用来规定泛型类型(固定一个类型)在调用方法时确定具体的类型 * <T>后的T表示方法的返回值类型(为方法调用时规定的类型T) * T parameter 表示方法的参数类型也采用了T类型(具体T的类型在调用方法时确定) * @param parameter 方法参数 * @param <T> 用来规定泛型类型(固定一个类型) * @return 一个T类型的数据 */ public <T>T getData(T parameter){ return parameter; } public <T>String getData2(T parameter){ return parameter.toString(); } public static void main(String[] args) { Demo3_GenericityMethod generiMethodDemo=new Demo3_GenericityMethod(); //这里就在调用方法时,在getData()括号中传入了参数,同时据此可以确定T的类型为String类型 //(因为getData方法传入的数据的类型为String类型的对象,所以T就是String类型了) //因为知道方法的返回值类型为String(和方法参数类型相同), 所以可以用String变量接收结果 String rs=generiMethodDemo.getData(new String("hello")); //注意泛型方法一般方法返回值类型为T,方法参数类型也会是T(因为要根据方法参数确定返回值类型) } }
五、泛型接口
- 接口在定义时可以使用泛型
- 实现一个使用了泛型接口 : 需要在子类定义处声明类型, 或者在对象创建时声明类型
案例:
package com.zyq.se.javaGenerics; /** * @author zhaoYQ * 泛型接口的演示 * 实现一个使用了泛型接口 : * 在子类定义处声明类型, 或者在对象创建时声明类型 */ public class Demo4_GenericityInterface { public static void main(String[] args) { //1.实现接口时确定T的类型 ToyCar car=new ToyCar(); String carRs=car.play("'金色童年卡丁车场地赛车场'"); System.out.println("carRs = " + carRs); //2.实现接口时不确定T的类型(需要在创建对象时确定类型) ToyBarbie<Character> Barbie=new ToyBarbie<Character>(); Barbie.play('红'); } } /** * 玩具接口 * @param <T> */ interface Toy<T>{ T play(T msg); } /** * 玩具汽车实现玩具接口 (实现接口时确定T的类型) * 玩具汽车ToyCar在实现接口时确定T的类型为String * 则play方法的参数类型和返回值类型也就会为String */ class ToyCar implements Toy<String>{ /** * 玩玩具汽车的方法 * @param place 地点 * @return 一段文字 */ public String play(String place) { return "在"+place+"玩具汽车"; } } /** * 玩具芭比娃娃实现玩具接口 * 玩具汽车ToyCar在实现接口时确定T的类型为String * 则play方法的参数类型和返回值类型也就会为String */ class ToyBarbie<T> implements Toy<T>{ /** * 玩玩具汽车的方法 * @param place 地点 * @return 一段文字 */ public T play(T place) { System.out.println("Barbie= 妍妍在玩'"+place+"'色的芭比娃娃"); return place; } }
六、泛型通配符:
泛型通配符:不知道使用什么类型来接收的时候,此时可以使用?, ?表示未知通配符。
集合中使用 ? 作为泛型时不能添加元素(只能从集合中取元素)(所以集合基本不用?)
package com.zyq.se.javaGenerics; import java.util.ArrayList; /** * 泛型通配符: * 不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。 */ public class Demo5_Genericity { public static void main(String[] args) { //1.集合中使用 ? 作为泛型时不能添加元素(只能从集合中取元素)(所以集合基本不用?) ArrayList<?> list1 = new ArrayList<Object>(); //list1.add(1); //list1.add("r"); //2.泛型通配符?主要应用在方法参数传递方面 ArrayList<String> list2 = new ArrayList<String>(); list2.add("张"); list2.add("王"); testAdd(list2); } /** * 2.泛型通配符?主要应用在参数传递方面 */ public static void testAdd(ArrayList<?> eles) { for (Object o : eles) { System.out.println(o); }//在方法中是不能操作集合元素的,因为集合元素类型是?类型(未知类型<不是Object类型>) } } 点击并拖拽以移动
七、泛型高级用法:
- 泛型高级用法: 用?规定一个泛型的上限和下限。
1.泛型的上限:
格式: 类型名称 <? extends 类Cls > 对象名称
意义: 只能接收Cls类型及其子类型 (?是Cls的子类型, 或者?和Cls类型相同)
当从一个结构中读取已知是某类型或某类型的子类型时,适合用泛型的上限来规定数据(泛型的上限适合读)。
2.泛型的下限:
式:类型名称 <? super 类Cls > 对象名称
解释:?是Cls的父类型(或者?和Cls类型相同) (规定了子类型为Cls,父类型无上限<没规定父类型>)
意义: 只能接收该类型及其父类型
比如: < T super Integer > 这个泛型中的T表示: T只能是Integer的父类。
当向一个结构中存入已知是某类型或某类型的父类型时,适合用泛型的下限来规定数据(因为读取时的元素类型不确定,只是知道读取的元素类型是Cls的父类型)(泛型的下限适合写)。
案例:
package com.zyq.se.javaGenerics; import java.util.ArrayList; import java.util.Collection; /** * author: zhaoYQ * 泛型高级用法 * 用?规定一个泛型的上限和下限。 * 1.泛型的上限: * 格式: 类型名称 <? extends 类 > 对象名称 * 意义: 只能接收该类型及其子类 * <p> * 2.泛型的下限: * 格式: 类型名称 <? super 类 > 对象名称 * 意义: 只能接收该类型及其父类型 */ public class Demo6_GenericityAnvanced { public static void main(String[] args) { //1.泛型的上限<? extends 类1 >: 泛型的最顶级是类1(不能是类1的父类<可以是类1或类1的子类>) //ArrayList<? extends Animal> list = new ArrayList<Object>();//报错 //上边报错原因是: 泛型的最顶级是Animal(泛型需要是Animal或者是Animal的子类)(不能是Animal的父类) ArrayList<? extends Animal> list2 = new ArrayList<Animal>();//泛型可以是Animal ArrayList<? extends Animal> list3 = new ArrayList<Dog>();//泛型可以是Animal的子类 ArrayList<? extends Animal> list4 = new ArrayList<Cat>();//泛型可以是Animal的子类 //2.泛型的下限<? super 类2 >: 泛型的最底层是类2(不能是类2的子类<可以是类2或类2的父类>) ArrayList<? super Animal> list5 = new ArrayList<Object>();//泛型可以是Animal的子类 ArrayList<? super Animal> list6 = new ArrayList<Animal>();//泛型可以是Animal类 //ArrayList<? super Animal> list7 = new ArrayList<Dog>();//报错 //上边报错原因是: 泛型的最底层是Animal(泛型需要是Animal或者是Animal的父类) //ArrayList<? super Animal> list8 = new ArrayList<Cat>();//报错 //上边报错原因是: 泛型的最底层是Animal(泛型需要是Animal或者是Animal的父类) //3.一般泛型的上限和下限也是用来进行方法参数传递的(具体看) Collection<Integer> lis1 = new ArrayList<Integer>(); Collection<String> lis2 = new ArrayList<String>(); Collection<Number> lis3 = new ArrayList<Number>(); Collection<Object> lis4 = new ArrayList<Object>(); System.out.println(getElement1(lis1)); //报错(因为方法中规定了传入的集合元素类型必须是Number类或是Number的子类<String不是>) //System.out.println(getElement1(lis2));//报错 System.out.println(getElement1(lis3)); //报错(因为方法中规定了传入的集合元素类型必须是Number类或是Number的子类<Object不是>) //System.out.println(getElement1(lis4));//报错 //报错(因为方法中规定了传入的集合元素类型必须是Number类或是Number的父类<Integer不是>) //System.out.println(getElement2(lis1));//报错 //报错(因为方法中规定了传入的集合元素类型必须是Number类或是Number的父类<String不是>) //System.out.println(getElement2(lis2));//报错 getElement2(lis3); getElement2(lis4); } // 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类 public static Object getElement1(Collection<? extends Number> list) { return list.toArray()[0]; } // 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类 public static Object getElement2(Collection<? super Number> list) { return list.toArray()[0]; } } class Animal { }//父类 class Dog extends Animal { }//子类 class Cat extends Animal { }//子类
八、泛型擦除:
简单理解:在编译期间,所有的泛型信息都会被擦除掉。例如代码中定义的List<Object>和List<String>等类型,在编译后都会变成List。JVM看到的只是List,JVM虚拟机运行字节码文件时是不能获取用泛型规定的类型信息的。
泛型的擦除原则,特别是在Java中,指的是在编译时泛型类型参数会被替换或“擦除”为其上限类型或Object类型,以便在运行时能够正确地执行代码。以下是关于Java泛型擦除原则的详细解释:
1.所有参数化容器类都被擦除成非参数化的(raw type):
例如,List<E>、List<List<E>>等参数化的容器类在编译后都会被擦除成非参数化的List。
2.所有参数化数组都被擦除成非参数化的数组:
例如,List<E>[]这样的参数化数组会被擦除成List[]。
3.Raw type的容器类,被擦除成其自身:
如果一个容器类本身就是raw type(非参数化的),那么它会被擦除成它自身。例如,List(raw type)在擦除后仍然是List。 List<泛型>编译后会成为List
4.原生类型(int, String以及它们的包装类)都擦除成他们的自身:
原生类型在Java中不是泛型类型,因此它们不会被擦除。它们保持原样。
例如: Integer n1;编译后还是Integer n1; String s编译后还是String s
5.参数类型E,如果没有上限,则被擦除成Object:
在Java中,如果没有明确指定泛型参数的上限(例如,List<T>中的T),那么它会被默认擦除成Object。
6.所有约束参数如<? extends E>、<X extends E>都被擦除成E:
对于有约束的泛型参数,如List<? extends Number>,在擦除时会被处理为其约束的上限类型Number。
7.如果有多个约束,擦除成第一个:
对于多个约束的情况,如List<T extends Serializable & Cloneable>,泛型参数T会被擦除成第一个约束类型Serializable。
8.擦除导致强制类型转换变为自动和隐式的:
由于泛型擦除,在运行时所有的泛型类型信息都丢失了。因此,在使用泛型集合时,如果要将元素放入集合或从集合中取出元素,可能需要进行强制类型转换。但由于擦除机制,这些转换在Java中通常是自动和隐式的。
9.类型通配符和上限限定符用于编译时类型检查:
为了在编译时捕获类型错误,Java提供了类型通配符(如?)和上限限定符(如extends)。这些工具可以帮助程序员在编译阶段就进行类型检查,从而避免在运行时出现ClassCastException等异常。
这些擦除原则是Java泛型机制的基础,理解它们对于正确编写和使用泛型代码至关重要。