ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

java基础-泛型

2022-02-25 20:34:10  阅读:138  来源: 互联网

标签:java List ArrayList 基础 Son 类型 泛型 new


泛型的由来

什么是泛型

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

泛型的好处

  • 好处一:编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。
    • 在没有泛型的情况的下,通过对类型 Object 的引用来实现参数的“任意化”;
    • “任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的;
    • 对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是本身就是一个安全隐患。

自定义泛型结构

泛型结构包括泛型类、泛型接口和泛型方法

泛型类、泛型接口

泛型类与泛型接口的区别其实就是类与接口的区别,这里以泛型类为例:

  1. 类中声明的泛型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型,但是在静态方法中不能使用类的泛型。因为泛型类型是在创建类的时候指定的,而静态方法加载的时候,类的实例并没有创建。
  2. 异常类不能是泛型的。

泛型方法

以JDK中的Collection接口为例,区分普通方法和泛型方法:

public interface Collection<E> extends Iterable<E> {
	//普通方法
	boolean add(E data);
	//泛型方法,泛型方法的类型区别于类或接口中的类型,普通类中也可以含有泛型方法
	<T> T[] toArray(T[] a);
}

注意:区别于泛型类中带有类型符号的普通方法的是,泛型方法可以是静态的。具体说来就是,上面的add方法不能是静态的,因为E是在类创建时类型才确定下来;而toArray方法可以是静态的,因为T是在方法调用时就确定下来了。

泛型与面向对象

泛型与继承

  • 在此定义三个类,Father、Son和Grandson
  • Grandson extends Son
  • Son extends Father

类的继承

//Son不是泛型类,它继承了Father中的Integer
public class Son extends Father<Integer> {

}
//Son<T> 仍然是泛型类
public class Son<T> extends Father<T> {

}

参数类型的继承

java中的向上转型有如下几种形式

//1.对象,编译通过
Object abj = null;
String str = null;
abj = str;
//2.数组,编译通过
Object[] objArr = null;
String[] strArr = null;
objArr = strArr
//3.泛型1,编译通过,其实就是平常写的List<String> list = new ArrayList<>(); 
List<String> list1 = null;
ArrayList<String> list2 = null;
list1 = list2
//4.泛型2,编译不通过,此时的objList和strList的类型不具有子父类关系
List<Object> objList = null;
List<String> strList = null;
objList = strList;

## 第四种情况会导致一些问题,比如如下方法,遍历集合中的元素
public void foreach(List<Object> list) {

}
## List<String>类型的变量就无法用这个方法遍历,因为List<Object>类型和List<String>类型
不具备子父类关系,无法使用多态,这样就需要重载foreach方法

总结:List<Object>ArrayList<Object>的父类;List<Object>不是List<String>的父类。针对上述的第四种情况,泛型通配符可以解决。

泛型通配符

示例

泛型通配符用来表示,在泛型中,List<?>List<String>List<Object>的父类,可以声明一下方法解决上述第四种情况

public void foreach(List<?> list) {
	Iterator<?> iterator = list.iterator();
	while (iterator.hasNext()) {
		Object obj = iterator.next();
		System.out.println(obj);
	}
}

通配符上下限

  • ? extends Son,其中Son是通配符?的上限,可以理解为?的类只能是Son及其子类,即小于等于
  • ? super Son,其中Son是通配符?的下限,可以理解为?的类只能是Son及其父类,即大于等于
  • 对于通配符上下限,如List<? extends Son>List<? super Son>,声明以下对象做测试
List<? extends Son> list1 = new ArrayList<>();
List<? super Son> list2 = new ArrayList<>();
List<Grandson> list3 = new ArrayList<>();
List<Son> list4 = new ArrayList<>();
List<Father> list5 = new ArrayList<>();
//测试一
list1 = list3;//(编译成功)
list1 = list4;//(编译成功)
list1 = list5;//(编译失败)
//结论:G<? extends A>可以作为G<A>和G<B>的父类,其中B是A的子类
//测试二
list2 = list3;//(编译失败)
list2 = list4;///(编译成功)
list2 = list5;///(编译成功)
//结论:G<? super A>可以作为G<A>和G<B>的父类,其中B是A的父类

泛型通配符读写要求

  • 对于普通的通配符List<?>
List<String> listStr = new ArrayList<>();
List<Integer> listInteger = new ArrayList<>();
List<?> list = null;
list = listStr ;
list = listInteger ;
//不能添加(只能添加null),编译失败
list.add("1");
list.add(1);
//只能读取,读取的是object类型的对象
Object o = list.get(0);
  • 对于通配符上限List<? extends Son>
//读取数据
list1 = list4;//用list1 = list3同理
//由上面的测试可知,list1中的类型最大为Son,这里用了最大的类型,保证了多态的特性(父类引用指向子类对象)
Son s = list1.get(0);

//写入数据
//无法确定list1的下限,传入的Son类型不能确定是?的子类还是父类,不满足多态特性
list1.add(new Son());//(编译失败)

无法写入数据,那具体怎么使用呢?通配符在声明局部变量时没什么意义,但是作为形参,限制传入的参数类型时,它非常重要

  • 对于通配符下限List<? super Son>
//读取数据
list2 = list4;
//只能返回Object,因为list2中的对象最小类型是Son,但是上限没有定,只能用顶级父类Object才能保证多态特性
Object o = list2.get(0);

//写入数据
//list2的下限是Son,可以添加Son及其子类,以满足多态特性
list2.add(new Son());
list2.add(new Grandson());//(编译成功)

使用案例:TreeSet的构造器中传入的比较器使用了Comparator<? super E>

类型擦除

定义

  • 泛型是java1.5版本才引入的概念,在这之前没有泛型那个。但是,泛型代码可以很好的兼容以前的版本,那是因为泛型信息只存在于代码编译阶段。在进入JVM之前,与泛型相关的信息会被擦除,我们称之为类型擦除

演示

ArrayList<String> strList = new ArrayList<>();
ArrayList<Integer> intList = new ArrayList<>();
System.out.println(strList.getClass().getSimpleName());//ArrayList
System.out.println(intList.getClass().getSimpleName());//ArrayList
System.out.println(strList.getClass() == intList.getClass());//true

种类

  • 无限制的类型擦除
    在这里插入图片描述
  • 有限制的类型擦除
    • 类的类型擦除
      在这里插入图片描述
    • 方法的类型擦除
      在这里插入图片描述
  • 桥接方法
    在这里插入图片描述
    在jvm中,对接口进行类型擦除后,生成的info方法,在实现类中为了保持接口和类的实现关系,也需要有这个方法

类型擦除中的一些坑

//todo

泛型与反射

一些区别

?和T的区别

  • ?是一个不确定的类型,通常用于接收返回值或者作为形参
  • T是一个确定的类型,通常用于泛型类和泛型方法的定义

class<?>和class<T>的区别

  • class<?>作为通配泛型,当我们不知道确切类型的时候,可以做以下声明:
public class<?> clazz;
  • class<T>List<T>类似,一般用在反射中,具体用法如下:
// 通过反射的方式生成  Son
// 对象,这里比较明显的是,我们需要使用强制类型转换,强制类型转换可能在运行期报ClassCastException
Son son= (Son) Class.forName("com.test.model.Son").newInstance();

//使用class<T>优化
//当传入的类型不是T时,无法创建T类型的对象,且在编译期就会报错
public static <T> T createInstance(class<T> clazz) throw Exception {
	return clazz.newInstance();
}

泛型数组

创建泛型数组

  • 可以声明带泛型数组的应用,但是不能直接创建带泛型的数组对象
//编译失败
new ArrayList<String>[5];

//编译成功
ArrayList[] list = new ArrayList[5];
ArrayList<String>[] listArr = list;
  • 不能使用new E[],但是可以E[] elements = (E[]) new Object[capacity],参考ArrayList源码中声明Object[] elementData,而非泛型数组。
public class Fruit<T> {
	//编译成功, 可以声明T[]
	private T[] array;
	
	//public Fruit(int length) {
		//编译失败
		//array = new T[length];
	//}

	//可以使用反射加类型强转
	public Fruit(Class<T> clazz, int length) {
		array = (T[]) Array.newInstance(clazz, length);
	}

	public void put(int index, T item) {
		array[index] = item;
	}
}

泛型的使用场景

DAO

数据库中的一张表对应java中的一个类型,对数据库数据的增删改查,都可以归结为操作这个类,针对多张不同的表,映射到多个类,所要进行的操作都相同(CRUD),因此可以使用泛型。具体用法体现在:

public class BaseDAO<T> {
	//带有类型参数T的增删改查
	public T get(Query query) {
		...
	}
	...
}

public class XXXDAO extends BaseDAO<XXX> {
	//直接继承增删改查方法
}

标签:java,List,ArrayList,基础,Son,类型,泛型,new
来源: https://www.cnblogs.com/xianwenyang/p/15937648.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有