2.泛型
2.1.背景
- 泛型:可以在类或方法中预支地使用未知的类型。
一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。
2.2.泛型的优点
- 将运行时期的ClassCastException,转移到了编译时期变成了编译失败。
- 避免了类型强转的麻烦。
public class Generic {
public static void main(String[] args) {
Collection<String> list = new ArrayList<String>();
list.add("abc");
list.add("itcast");
// list.add(5);//当集合明确类型后,存放类型不一致就会编译报错
// 集合已经明确具体存放的元素类型,那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型
Iterator<String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
//当使用Iterator<String>控制元素类型后,就不需要强转了。获取到的元素直接就是
String类型
System.out.println(str.length());
}
}
}
2.3.定义泛型
- 泛型类
public class MyGenericClass<MVP> {
//没有MVP类型,在这里代表 未知的一种数据类型 未来传递什么就是什么类型
private MVP mvp;
public void setMVP(MVP mvp) {
this.mvp = mvp;
}
//使用了泛型作为返回值的方法,因为在类声明中已经声明过了,所以可以直接使用。
//但是该方法不能泛型方法,只能称为使用了泛型的方法,因为是在类中声明过的。
public MVP getMVP() {
return mvp;
}
}
public class Class_Generic {
public static void main(String[] args) {
// 创建一个泛型为String的类
MyGenericClass<String> my = new MyGenericClass<String>();
// 调用setMVP
my.setMVP("大胡子登登");
// 调用getMVP
String mvp = my.getMVP();
System.out.println(mvp);
//创建一个泛型为Integer的类
MyGenericClass<Integer> my2 = new MyGenericClass<Integer>();
my2.setMVP(123);
Integer mvp2 = my2.getMVP();
}
}
- 泛型方法
//在这里,<>仅仅作为一种泛型声明,意思是,这个方法要用到泛型,在作用域中用到尖括号里的内容的时候,编译器会将其识别为一种数据类型。
//如果在方法声明中不进行<>尖括号的泛型声明,编译器无法识别<>中的内容是一种类型
public class MyGenericMethod {
//方法中用到了泛型,所以提前声明
public <MVP> void show(MVP mvp) {
System.out.println(mvp.getClass());
}
//方法中用到了泛型作为返回值,也要提前声明,告诉编译器,尖括号里的内容是一种引用类型
//因为已经超过了上一个<>的作用域范围,所以要重新声明一下<>的内容是一种引用类型的代指
public <MVP> MVP show2(MVP mvp) {
return mvp;
}
//含有两个泛型的泛型方法
//对于一个泛型方法作为返回值类型,编译器会自动识别返回的真正类型,比如传入一个数字,编译器会自动返回为Integer类型。
public <MVP,MVP1> MVP show3(MVP mvp,MVP1 mvp1) {
return mvp;
}
//show3(1,"str"); //返回值为Integer类型的数字1。
}
//对于泛型类中的使用了泛型的普通方法,和泛型方法,有以下两种情况
// 1.对于show4方法,是一个使用了泛型的普通方法,返回值为类中的泛型,因为名称是一样的。
// 2.对于show5方法,泛型类中的泛型虽然与泛型方法中的名称一样,但是,它的本质是两种泛型,<>在方法体中重新声明了,类似于变量隐藏。
class MyGenericMethod1<MVP> {
public MVP show4(MVP mvp) {
return mvp;
}
public<MVP> MVP show5(MVP mvp) {
return mvp;
}
}
//对于上边的泛型类中的泛型方法,其实可以这样理解,是等价的
class MyGenericMethod2<MVP> {
public MVP show6(MVP mvp) {
return mvp;
}
public<MVP1> MVP1 show7(MVP1 mvp1) {
return mvp1;//返回的就是<MVP1>类型的,而不是类中的<MVP>
}
//当然,这个泛型方法的参数,就和类中的一样了,因为返回值是类中的<MVP>,而该方法体中只是重新定义了<MVP1>为一个全新的泛型
public<MVP1> MVP show8(MVP mvp) {
return mvp;
}
}
public class GenericMethodDemo {
public static void main(String[] args) {
// 创建对象
MyGenericMethod mm = new MyGenericMethod();
// 演示看方法提示
mm.show("aaa");
mm.show(123);
mm.show(12.45);
}
}
- 泛型接口
public interface MyGenericInterface<E>{
public abstract void add(E e);
public abstract E getE();
}
//定义类时确定泛型的类型
//此时,泛型E的值就是String类型。
public class MyImp1 implements MyGenericInterface<String> {
@Override
public void add(String e) {
// 省略...
}
@Override
public String getE() {
return null;
}
}
//始终不确定泛型的类型,直到创建对象的时候,确定泛型的类型
public class MyImp2<E> implements MyGenericInterface<E> {
@Override
public void add(E e) {
// 省略...
}
@Override
public E getE() {
return null;
}
}
/*
* 使用
*/
public class GenericInterface {
public static void main(String[] args) {
MyImp2<String> my = new MyImp2<String>();
my.add("aa");
}
}
- 详解关于泛型类、泛型接口的继承与实现
//泛型接口的实现
public interface InGeneric1<T> {
// 泛型接口:把泛型定义在接口上
// 格式:public interface 接口名<泛型类型1…>
public T getAge();
}
//不传入类型,默认Object类型
class ClassGeneric1 implements InGeneric1{
@Override
public Object getAge() {
return null;
}
}
//在类的实现的时候确定了为String类型
class ClassGeneric2 implements InGeneric1<String>{
@Override
public String getAge() {
return null;
}
}
//用一个泛型类去实现接口,实现的时候确定了接口类型为String,但是泛型类的类型仍然不确定
class ClassGeneric3<E> implements InGeneric1<String>{
@Override
public String getAge() {
return null;
}
}
//泛型类实现该接口的时候仍然不确定类型,创建对象的时候会确定。
class ClassGeneric4<E> implements InGeneric1<E>{
@Override
public E getAge(){
return null;
}
}
//泛型类的继承
public class ClassGeneric01<T> {}
//与上一个类没有任何关系,只是两个类而已,T只是一个标记
class ClassGeneric02<T> {
T t;
}
class ClassGeneric03<T> extends ClassGeneric12<T> {}
//报错
//class ClassGeneric04<E> extends ClassGeneric12<T> {}
/*
报错原因:
在类的声明中,我们只声明了一个<E>是一个确定的类型,代表任何引用类型,而在后边,对<>内容的声明已经完毕了,编译器无法识别。
编译器不会识别<T>是什么,只能识别到声明里<E>是一种固定类型,或者说是某种自定义类型,此时对于编译器来说,<T>是一种语法错误。
*/
个人的一些理解:
- 对于类中声明的<>,<>内可以是任意内容,它的有效范围只有该类的{}范围加上声明时尖括号后边的继承语句或实现语句,超出这个范围需要重新声明。
- 同理,对于方法体中声明的<>,<>内可以是任意内容,它的有效范围只有该方法的{}内,否则编译器仍然会作为语法错误而编译异常。
- 可以理解为,声明的位置,只有固定的位置,只有这么一个位置可以告诉编译器,<>里的某类型是一个我们自己定义的类型的代表,编译器就可以识别。
2.4.泛型擦除
- 对于这样一种情形:定义一个泛型类,创建该类的不同类型的对象,例如一个是String类型,另一个是Integer类型,调用两个线程分别控制它们运行,这个代码依旧编译成一份,所有带泛型的代码, 在编译完成之后, 泛型完全就不存在了, 全部替换成Object,底层会自动进行类型强转。泛型仅存在于编译之前。
2.5.泛型通配符
2.5.1.背景及使用方法
?
代表任意的数据类型- 泛型没有集成概念,不能创建对象使用,只能作为参数传递
2.5.2.高级使用
- 泛型的上限限定:? extends E 代表使用的泛型只能是E类型的子类/本身
- 泛型的下限限定:? super E 代表使用的泛型只能是E类型的父类/本身
//已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类,Object是根类
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement1(list1);
getElement1(list2);//报错
getElement1(list3);
getElement1(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
}
// 泛型的上限:此时的泛型<>,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型<>,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
2.6.泛型协变
2.6.1.数组协变
- Java对于数组进行了很多特殊处理,比如foreach循环,其底层其实是fori循环,因为其并没有实现Iterator接口,依旧能进行foreach循环。
- 关于数组的协变处理,例子如下:
Integer[] arrs = new Integer[10];
Object[] objs = new Object[10];
objs = arrs; //可以指向
objs[0] = 1; //可以存
objs[1] = "1"; //类型不匹配,报错
- 对于多态,应该是父类的引用指向子类,但是,对于数组的引用,数组并不存在父子关系,因此并没有多态的情形。
- 对于数组,JVM单独处理,数组的类型Integer是Object的子类,所以可以指向,称之为协变。
2.6.2.集合协变
- 对于集合类型,JVM并没有像数组那样进行特殊处理,所以并不存在一般情形下的协变。
List<String> listStr = new ArrayList<>();
List<Object> listObj = new ArrayList<>();
// Java中的集合类: 不允许协变
listObj = listStr; //报错
- 特殊情形下的协变:通配符
//泛型通配符<?> 可以代指任意类型(除了基本类型)
//为了模拟协变 , Java提供了泛型的通配
//个人理解,?这个通配符,可以理解为植物大战僵尸里的模仿者,指向的引用是什么类型,就化身为什么类型
List<String> arrayList = new ArrayList<String>();
List<?> listObj1 = arrayList; //可以指向
List<Object> listObjs = new ArrayList<>();
List<?> listObj2 = listObjs; //可以指向
//为了解决数组协变的存储元素的问题,所以直接暴力解决,不允许添加元素
//即使底层的数据存储类型一样,也不能存储,如,listObj1指向的类型是String,也不允许存储String类型的元素
listObj1.add("1"); //不可以,报错
listObj2.add(new Object()); //不可以,报错
//? extends E
public class DemoGeneric {
public static void main(String[] args) {
//? extends E : 可以通配E类型或者E类型的子类
//向下限定,E及其子类
List<B> list = new ArrayList<>();
List<A> list1 = new ArrayList<>();
List<Object> list2 = new ArrayList<>();
List<? extends A> listAll = list;
listAll = list1;
// listAll = list2; //报错,因为Object是A的父类
// 因为编程人员可能并不确定底层到底是A还是A的哪一个子类,所以暴力解决,全部不允许存储,报错
// listAll.add(new A());
// listAll.add(new C());
// listAll.add(new Object());
}
}
class A{}
class B extends A{}
class C extends A{}
//? super E
public class DemoGeneric {
public static void main(String[] args) {
// ? super E : 仅能代指E类型, 或者E类型的父类型: 代表可以存储E及E的子类型
// 向上限定,E及其父类
// 这个List: 里面存储的是F类型的数据
List<F> list1 = new ArrayList<>();
List<? super F> listObj = list1;
// 可以存储F的对象,所以F的子类也必定可以存储,这个是必定的,因为list1按子父类继承关系,最低也是F类型
// 对于? super E这种通配符,可以指向E或E的父类型,但是只能存储E或E的子类型。
listObj.add(new F());
listObj.add(new S1());
listObj.add(new S2());
//错误
// List<S1> list1 = new ArrayList<>();
// List<? super F> listObj = list1;
}
}
class F{}
class S1 extends F{}
class S2 extends F{}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/181079.html