目录
2.1那么为什么要有泛型呢,直接Object不是也可以存储数据吗?
1.为什么要有泛型(Generic)?
泛型:标签
举几个例子:
1 中药店,每个抽屉外面贴着标签
2.超市购物架上很多瓶子,每个瓶子装的是什么,有标签
3.垃圾箱分类,在垃圾箱上标明垃圾的分类形式。
实际上就是说,你具体要使用什么,根据标签提示,放进去,如果不对应是放不进去的。相当于加了一个限制的条件。这是在编译过程中所表现出来的,如果不对就会报错。
2.泛型的设计背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的 对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来 解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于 这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个 参数,这个类型参数叫做泛型。Collection<E>,List<E>,ArrayList<E> 这个就 是类型参数,即泛型。
2.1那么为什么要有泛型呢,直接Object不是也可以存储数据吗?
1. 解决元素存储的安全性问题,好比商品、药品标签,不会弄错。
2. 解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药 品都要辨别。
我们之前用的数组定义的类型只能是相同数据类型,就是单一的数据类型,集合(Object obj)类型,各种类型都能存入到里边,不够严格,引入泛型后表明是单一的数据类型。
举例说明一下:
在没有使用泛型之前重写compareto()方法需要: if (o instanceof Employee)判断是不是Employee数据类型。
@Override
public int compareTo(Object o) {
if (o instanceof Employee){
Employee e= (Employee) o;
return this.name.compareTo(e.name);//从小到大
}
throw new RuntimeException("传入数据不一致");
}
如果我们直接就知道这个数据类型相同指定泛型直接这样写:
使用泛型
@Override
public int compareTo(Employee o) {
//不用判断数据类型,直接都是Employee类型的
return this.name.compareTo(o.name);//从小到大
}
3.在集合中使用泛型
泛型的使用 * 一.jdk5.0之后增加的特性 * 二.在集合中使用泛型 * 总结: * 1.集合接口 或集合类 jdk5.0后都修改为带泛型的结构 * 2.在实例化集合类时,可以指明具体的泛型类型 * 3.指明完后,在集合类或接口中,凡是定义类和接口时, * 内部结构使用到类的泛型的位置,都指定为实例化时的泛型类型 * 比如:add(E e)---->实例化后:add(Integer e) 看集合上边实例化指明的数据类型 *4.注意:我们泛型类型必须是类,不能是基本数据类型,需要用到包装类的位置,就拿包装类来进行替换 *5.如果实例化时,没有用泛型,默认类型为Object 的类型 * 三.如何自定义泛型类,泛型结构:泛型类,泛型接口,泛型方法 举例说明一下:
1.类型不安全性,我们有时候需要用某个具体的类型就容易出错。我也想到了可以用数组解决,但是数组的方法操作比较少,相对于集合用泛型解决了这个问题。
定义一个集合用来存放学生成绩,最终我们要的数据类型应该都是相同的,那么问题来了,在集合中我们可以存放各种数据类型即Object类型,我们在遍历时想要统一类型,进行强转,就出错。
@Test
public void test1(){
ArrayList list = new ArrayList();
//存放学生成绩 存放一个别的数据类型
list.add(78);
list.add(88);
list.add(98);
list.add(100);
//问题1:类型不安全,没有限制 都是Object 类型数据
//list.add("aa");//不能转为int型,报异常 所以说不可以在list中不能添加别的数据类型的数据
for (Object score:list){
//向下强转时有可能出现ClassCastExceprion
int stuscore= (Integer) score;
System.out.println(stuscore);
}
}
2.解决类型不安全性,引入泛型之后在编译的时候直接爆红给出警告,添加不进去,就避免了各种类型鱼龙混杂的问题,也避免了要去来回的进行数据的强转问题:
//在集合中使用泛型
//以ArrayList举例
@Test
public void test2(){
ArrayList<Integer> list = new ArrayList<Integer>();//泛型不能使用基本的数据类型,只能使用包装类的形式
//这就限制了数据类型
list.add(78);
list.add(88);
list.add(98);
list.add(100);
//解决问题一:编译时进行类型检查,保证数据的安全
//list.add("aa");//这时候就报异常了编译的时候报异常
//遍历方式一
for (Integer score:list){
//避免了强转操作,强转操作容易出现问题。 其实还是用了泛型的优势
int stuScore=score;
System.out.println(stuScore);
}
//方式二 //定义结构的时候就使用了泛型
//public interface Iterator<E>
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
Integer stuScore = iterator.next();
System.out.println(stuScore);
}
}
//集合中使用泛型之前情况 HashMap()为例
@Test
public void test3(){
//jdk新特性 后边这个new hashmap<>泛型可省略 默认推断
//Map<String, Integer> map = new HashMap<String, Integer>();
Map<String, Integer> map = new HashMap<>();
map.put("Tom",89);
map.put("meme",87);
map.put("didi",69);
map.put("Jack",100);
// map.put(hhd,12);//有泛型报错 不满足形参的要求
Set<Map.Entry<String,Integer>>entry=map.entrySet();// Set<E> Entry<K,V>嵌套一块
Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();
while (iterator.hasNext()){
Map.Entry<String, Integer> e = iterator.next();
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key+"-->"+value);
}
}
4.自定义泛型结构
1. 自定义泛型类
2. 自定义泛型接口
3. 自定义泛型方法
如何自定义泛型类,泛型结构:泛型类,泛型接口,泛型方法 * 1.关于自定义泛型类 接口这两个基本是一会事,只是接口和类的区别 * 2.泛型要么用,要么别用 一般都要用 * 如果泛型结构是一个接口或抽象类 ,测不可以创建对象 * 测试
4.1自定义泛型结构:泛型类、泛型接口
1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
可行的
2. 泛型类的构造器如下:public GenericClass(){}。
而下面是错误的:public GenericClass<E>(){}
爆红
3. 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
4. 泛型不同的引用不能相互赋值。 尽管在编译时ArrayLis<String>t和ArrayList<Inetger>是两种类型,但是,在运行时只有 一个ArrayList会被加载到JVM中。
5. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验:泛型要使用一路都用。要不用,一路都不要用。
@Test
public void test1(){
//如果定义了泛型,实例化时没有指明类的泛型,则认为次泛型类型为object类型的
//如果定义类是泛型的,那么建议实例化时要指明类的泛型
Order order = new Order();
order.setOrderT(123);
order.setOrderT("sdf");
//建议带上泛型在实例化时指明泛型类型
Order<String> order1 = new Order<String>("tom",1001,"aa");
order1.setOrderT("tom:hello");
}
6. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
7. jdk1.7,泛型的简化操作:ArrayList <String>list = new ArrayList<>();
8. 泛型的指定中不能使用基本数据类型,如果要使用基本数据类型可以使用包装类替换。
//使用基本数据类型,必须使用包装类来替换,因为泛型也是一个类的对象
9. 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态 属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
原因://静态方法中不能使用类的泛型 //因为泛型时在创建对象实例化之前创建的 //而静态方法是在创建对象之前就已经执行了
10. 异常类不能是泛型的
异常类不能使用泛型:因为异常类 Exception这个API中没有使用泛型
11. 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity]; 参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
12.父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
1. 子类不保留父类的泛型:按需实现 没有类型 擦除 具体类型
2.子类保留父类的泛型:泛型子类 全部保留 部分保留
结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型
4.2自定义泛型结构:泛型方法
方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
泛型方法的格式: [访问权限] 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
//泛型方法 在方法中出现了泛型的结构,泛型的参数与类的泛型参数没有任何关系 //换句话说,泛型方法所属的类是不是泛型类都没有关系。 //认为这个类是E 而这个E是一个变量 //而在前边加个<E>这时候它知道了这是个变量,如果不在访问权限 前加<E>,系统默认是一个名字为E的一个类 //这个方法是泛型方法 //这个E是在调用的时候用的 //泛型方法可以声明为静态的,主要原因是泛型参数是在调用的时候确定的,并非在实例化才可以调用
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for (E e:arr){
list.add(e);
}
return list;
}
测试://泛型方法在调用时,指明泛型参数的类型
@Test
public void test4(){
Order<String > order=new Order<>();
Integer[] arr=new Integer[]{1,2,3,3};
//泛型方法在调用时,指明泛型参数的类型
List<Integer> list = order.copyFromArrayToList(arr);
System.out.println(list);
}
结果:
5.泛型在继承上的体现
如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的 类或接口,G并不是G的子类型。
比如:String是Object的子类,但是List<String>并不是List<Object>的子类。
反证法: 假设 list1=list2, list1.add(123); 导致list2中混入非string中的数据,这个泛型时不对的,编译不通过 不具备并列关系,
/*
1.泛型在继承方面的体现
虽然类A是类B的父类,但是G<A> 与G<B>没有父子类关系,二者是并列的关系
补充:类A是类B的父类 A<G> B<G> 他们之间是什么关系
*/
@Test
public void test1(){
Object obj=null;
String str=null;
obj=str;
Object[] arr=null;
String[] arr2=null;
arr=arr2;//多态的 对象与对象之间的使用
List<Object>list1=new ArrayList<Object>();
List<String>list2=new ArrayList<String>();
//此时的list1与list2不具有子分类关系。
//编译不通过
// list1=list2;
/*
反证法:
假设list1=list2,
list1.add(123);
导致list2中混入非string中的数据,这个泛型时不对的,编译不通过 不具备并列关系
*/
}
6.通配符的使用
6.1使用类型通配符:?
比如:List <?>,Map<?,?>
List<?>是List<String>、List<Integer>等各种泛型List的父类。
2.读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型 是什么,它包含的都是Object。
3.写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中添加对象。
唯一的例外是null,它是所有类型的成员.
//写入的过程 //添加 对于list用了通配符之后就不可以加数据了 //限制添加数据,除了添加null之外。 //所有类类对象都是可以加null的
将任意元素加入到其中不是类型安全的:这样不又回到了之前,那还要泛型干什么!
Collection c = new ArrayList();
c.add(new Object()); // 编译时错误
因为我们不知道c的元素类型,我们不能向其中添加对象。add方法有类型参数E作为集 合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知 道那是什么类型,所以我们无法传任何东西进去。
唯一的例外的是null,它是所有类型的成员。
另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object。
@Test
public void test3(){
List<Object> list1=null;
List<String> list2=null;
List<?> list=null;
list=list1;
list=list2;//表现为list<?>是父类
//print(list);
// print(list2);
//
ArrayList<String> list3 = new ArrayList<>();
list3.add("ss");
list3.add("bb");
list3.add("cc");
list3.add("dd");
list=list3;
//写入的过程
//添加 对于list用了通配符之后就不可以加数据了
//限制添加数据,除了添加null之外。
//所有类类对象都是可以加null的
list.add(null);
//获取 允许读取数据,读取的数据类型是object的类型。以多态的方式进行读的
Object o = list.get(0);
System.out.println(o);
}
使用通配符要注意以下几点:
6.2有限制的通配符:
3.有限制条件的统配符的使用
* ?extends A
* G<? ectends A> 可以作为G<A> 和G<B>的父类,其中B是A的子类
* ?super A
* G<? ectends A> 可以作为G<A> 和G<B>的父类,其中B是A的父类
代码中有详细注释:
@Test
public void test4(){
List<? extends Person>list1=null;//把extends看成小于等于 负无穷到person有上界
List<? super Person> list2=null;//把super看成大于等于,有下届没有上界
List<Student>list3=new ArrayList<Student>();
List<Person>list4=new ArrayList<Person>();
List<Object>list5=new ArrayList<Object>();
list1=list3;
list1=list4;
//list1=list5;
//list2=list3;
list2=list4;
list2=list5;
//读数据
list1=list4;
Person person = list1.get(0);
list1=list3;
Person person1 = list1.get(0);
list2=list4;
Object object = list2.get(0);
//写入数据
//编译不通过
// list1.add(new Student())
//编译通过。
list2.add(new Person());
list2.add(new Student());
}