一、泛型
泛型概括:
泛型就是类型的参数化,就是可以把类型像方法的参数那样传递。泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行时由于对象类型不匹配引发的异常。在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。
也就是说在泛型的使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
- 泛型的应用场景:
- 集合
- 方法 (泛型方法)
- 类 (泛型类)
- 接口 (泛型接口)
当我们使用泛型来约束集合、方法、类、接口时,如果直接指定类型的话,那么在使用泛型约束的集合、方法、类、接口时便只能操作与泛型类型相同的数据。(强制绑定数据类型,比如给List集合定义上String类型的泛型,那么只能存储String类型的数据)
如果不定义泛型的类型而是使用类型变量的话,那么此时的泛型便可以认为是 Object 类型,可以对所有类型的数据进行操作。
注:泛型所指定的类型都是引用类型
1、泛型应用在集合上
众所周知,如果不定义集合的存储类型,那么集合的默认类型便是 Object 类型。这样的好处便是在创建集合对象,并向集合中存储元素时,不会有类型的限制,可以存储所有的数据类型。
我们平时在使用集合时,往往涉及到元素类型的转换。当集合中存在多种数据类型的元素时,我们便无法对集合整体做出类型转换的操作。虽然代码在编译期不会显示出异常,但是当对集合进行强制转换时,会将集合中的所有元素都进行类型转换,这时候如果其中某些元素与强转类型不符合,比如:不含数字的字符串类型元素转换成数字类型。对于这种不合理的类型转换,系统便会报出元素类型转换异常。
为了避免这种类型转换的异常,Java提供了一种数据类型定义规范 — 泛型。使用泛型可以在定义集合对象时候指定集合的类型,这样在向集合中存储元素时便只能存储泛型指定类型的元素数据。有效的避免了集合元素的类型转换出现异常的问题。
-
泛型应用在集合上的好处:
- 好处:避免了类型强制转化的麻烦,存的什么类型,取出来的也是什么类型;代码运行之后才会抛出异常,写代码时不会报错
-
泛型应用在集合上的弊端:
- 弊端:泛型是什么类型只能存储什么类型的数据。
集合转换异常, Demo 代码示例:
public class GenericDemo {
public static void main(String[] args) {
// 创建
List list = new ArrayList();
// 添加元素
//添加int类型的元素
list.add(10); // JDK5以后的自动装箱
// 等价于:array.add(Integer.valueOf(10));
list.add("String");
list.add(2.14);
list.add('i');
list.add(true);
list.add("hello");
// 遍历
Iterator it = list.iterator();
while (it.hasNext()) {
Integer integer =(Integer) it.next();
System.out.println(integer);
}
}
}
运行结果:
从运行结果可以看出,使用强制类型转换时,转换类型为 Integer ,只有第一个元素遍历出来了,因为第一个元素是整数类型,可以与 Integer 之间相互转换,其他的都不可以转换,所以报出类型转换异常。
使用泛型定义集合类型, Demo 代码示例:
public class GenericDemo {
public static void main(String[] args) {
// 创建
List<String> list = new ArrayList<String>();
//非字符串类型不可存储
//list.add(12.12);
list.add("String");
list.add("java");
/*list.add(2.14);
list.add('i');
list.add(true);
*/
// 遍历
Iterator it = list.iterator();
while (it.hasNext()) {
// Integer integer =(Integer) it.next();//字符串类型不可转换成数字类型
System.out.println(it.next());
}
}
}
运行结果:
2、泛型应用在方法上(泛型方法)
- 把泛型定义在方法上
- 定义格式:
修饰符 <泛型变量> 返回值的类型 方法名称(形参列表){ //方法体 }
- 定义格式:
Demo代码示例:
public class ObjectTool {
public static void main(String[] args) {
ObjectTool tool = new ObjectTool();
tool.information("姓名","姚青");
tool.information("年龄",22);
tool.information("性别","男");
tool.information("头发是否浓密?",true);
tool.information("是否是个憨批?",false);
}
public <M> void information(M m1,M m2){
System.out.println(m1+":"+m2);
}
运行结果:
当我们将方法定义成泛型方法,我们就可以向该方法中传递任意类型的参数了。
3、将泛型应用在类上(泛型类)
类结构是面向对象中最基本的元素,如果我们的类需要有很好的扩展性,那么我们可以将其设置成泛型类。
- 把泛型定义在类上,类便成为了泛型类
- 格式:public class 类名<泛型类型1,…>
- 注意:泛型类型必须是引用类型
泛型类Demo代码示例:
定义泛型实体类:
//定义泛型实体类,泛型中所指定的内容需要是引用类型
//此处O可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定O的具体类型
public class ObjectTool <O>{
//这个name成员变量的类型为O,O的类型由外部调用指定
private O name;
private O age;
//泛型方法getObj的返回值类型为O,O的类型由外部调用指定
public O getName() {
return name;
}
//泛型构造方法形参obj的类型也为O,O的类型由外部调用指定
public void setName(O name) {
this.name = name;
}
//泛型方法getObj的返回值类型为O,O的类型由外部调用指定
public O getAge() {
return age;
}
//泛型构造方法形参obj的类型也为O,O的类型由外部调用指定
public void setAge(O age) {
this.age = age;
}
}
实现测试类:
public class ObjectToolText {
public static void main(String[] args) {
ObjectTool<String> objectTool = new ObjectTool<String>();
objectTool.setName("李磊");
ObjectTool<Integer> objectTool1 = new ObjectTool<Integer>();
objectTool1.setAge(23);
System.out.println("姓名:"+objectTool.getName()+","+"年龄:"+objectTool1.getAge());
//可以看出实例化泛型类之后,会根据给对象定义的泛型来定义调用方法时参数的类型
}
}
运行结果:
4、将泛型应用在接口上(泛型接口)
- 定义格式:
- public interface 接口名<泛型类型> { }
我们使用泛型接口定义接口实现类时,通常会出现两个状况
- 第一种状况:我们知道接口的泛型是什么类型。
- 第二种状况:我们不知道接口的泛型是什么类型。
Demo代码示例:
/*
* 定义泛型接口:把泛型定义在接口上
*/
public interface Inter<T> {
public abstract void show(T t);
}
--------------------------------------------------
//实现类在实现接口的时候,我们会遇到两种情况
//第一种情况:已经知道是什么类型的了
public class InterImpl implements Inter<String> {
@Override
public void show(String t) {
System.out.println(t);
}
}
//第二种情况:还不知道是什么类型的
//这里需要注意,当实现泛型接口时,需要在类名和实现的接口名后面将泛型添上,
//实现类中添加的泛型不需要和接口的泛型一样,可以是自定义的随便搞,但是这两个泛型名需要是相同的
public class InterImpl<T> implements Inter<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
------------------------------------------
public class InterDemo {
public static void main(String[] args) {
// 第一种情况的测试
Inter<String> i = new InterImpl();
i.show("hello");
// 第二种情况的测试
Inter<String> i = new InterImpl<String>();
i.show("hello");
Inter<Integer> ii = new InterImpl<Integer>();
ii.show(100);
}
}
- 第一种情况,知道什么类型,并且指定了该类型,那么就只能操作指定类型的数据
- 第二种情况,不知道什么类型,使用类型变量,默认为 Object 类型,可以操作所有类型数据。
二,泛型进阶——通配符
1、泛型冷知识
-
常用的通配符有: T,E,K,V,?
- 其实也可以是A、B、C、D、E等的字母代替。使用 T,E,K,V,?只不过是约定俗成而已。
-
T,E,K,V,? 的约定如下:
-
T:(type) 表示具体的一个java类型。
-
E:代表Element。
-
K、V :分别代表java键值中的Key Value。
-
? :无界通配符,表示不确定的 java 类型
-
2、通配符
- 无界通配符:?
- 任意类型,如果没有明确,那么就是Object以及任意的Java类了
- 任意类型,如果没有明确,那么就是Object以及任意的Java类了
- 下边界限定通配符: < ? extends E>
- 下边界:用extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
- 下边界:用extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
- 上边界限定通配符: < ? super E>
- 上边界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object。
Demo代码示例:
public class GenericDemo {
public static void main(String[] args) {
// 泛型如果明确的写的时候,前后必须一致
Collection<Object> c1 = new ArrayList<Object>();
// Collection<Object> c2 = new ArrayList<Animal>();//报错
// Collection<Object> c3 = new ArrayList<Dog>();//报错
// Collection<Object> c4 = new ArrayList<Cat>();//报错
// ?表示任意的类型都是可以的,变相的等于 Object
Collection<?> c5 = new ArrayList<Object>();
Collection<?> c6 = new ArrayList<Animal>();
Collection<?> c7 = new ArrayList<Dog>();
Collection<?> c8 = new ArrayList<Cat>();
// ? extends E:向下限定,E及其子类
// Collection<? extends Animal> c9 = new ArrayList<Object>();//报错
Collection<? extends Animal> c10 = new ArrayList<Animal>();
Collection<? extends Animal> c11 = new ArrayList<Dog>();
Collection<? extends Animal> c12 = new ArrayList<Cat>();
// ? super E:向上限定,E极其父类
Collection<? super Animal> c13 = new ArrayList<Object>();
Collection<? super Animal> c14 = new ArrayList<Animal>();
// Collection<? super Animal> c15 = new ArrayList<Dog>();//报错
// Collection<? super Animal> c16 = new ArrayList<Cat>();//报错
}
}
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
通过代码可知:
集合前面的泛型类型指定为无界通配符时,后面的泛型指定可以为任意类型。
当在集合前面使用下边界限定通配符时,后面的泛型指定就只能是父类本身和继承该类的向下子类。
当在集合前面使用上边界限定通配符时,后面的泛型指定就只能是当前指定类本身和其父类,直至向上祖类。
通配符这可能有些不详细,这个好大哥写的挺好,可以看一看。