2022-10-18
泛型 类型
泛型由入门到精通
Hi,小伙伴你好~欢迎进入泛型的学习,在学习之前友情提醒一下:学习泛型需要小伙伴们具备一定的javaSE基础,如果之前小伙伴们没有接触过java,大家可以移步到千锋北京java好程序员的javaSE课程进行学习。
在正式开始学习之前,我们先来看一段经常书写的代码,分析一下代码存在那些问题?
代码如下:
public class GenericsDemo {
public static void main(String[] args) {
//1.创建一个List对象
List list = new ArrayList();
//2.向List中添加数据
list.add("python");
list.add("java");
list.add(66);
//3.遍历集合
for (int i = 0; i <list.size() ; i++) {
//4.把集合中的每个元素转成String类型
String ele = (String) list.get(i);
//5.打印-测试结果
System.out.println("元素的值:"+ele);
}
}
}
运行代码,会报如下图的异常:
那么小伙们,我们来分析一下原因,到底是因为什么报这个异常呢?
在代码中我们定义了一个List类型的集合,先向其中加入了两个String类型的值,随后加入一个Integer类型的值。这是完全允许的,因为此时list默认的类型为Object类型,所以在代码编译期间没有任何问题。
但是在运行代码时,由于list集合中既有String类型的值,又有Integer类型的值,致使list集合无法区分值是什么类型,很容易出现上图中的错误。因为编译阶段正常,而运行时会出现“java.lang.ClassCastException”异常。因此导致此类错误编码过程中不易发现。
分析完了,小伙们现在明白了吧,通过分析我们发现上述代码主要存在两个问题:
当我们将数据存入集合时,集合不会记住数据的类型,默认数据类型都是Object。
当我们遍历集合中的数据时,人为进行强制类型转换,很容易报“java.lang.ClassCastException”。强制类型转换异常
那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,运行时就
不会出现“java.lang.ClassCastException”异常呢?
答案就是使用泛型。
那么什么是泛型呢?
第一关 让我们一起走入泛型
1.泛型的概述
1.1 什么是泛型
泛型,泛指任意类型,可以应用在接口上,类上,变量上,方法上,以及方法的参数中。
百度百科介绍:
泛型是jdk1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
在jdk1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况 ,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处:使用泛型,首先可以通过IDE进行代码类型初步检查,然后在编译阶段进行编译类型检查,以保证类型转换的安全性;并且所有的强制转换都是自动和隐式的,可以提高代码的重用率。
个人理解:
简单来说:泛型,即“参数化类型”,那么类型“参数化”到底怎么理解呢?
顾名思义,类型“参数化”就是将类型由原来的具体类型,变成参数化的“类型”,有点类似于方法中的变量参数,不过此时是类型定义成参数形式(你可以理解为类型形参),然后在使用时传入具体的类型(也就是类型实参)。为什么这样操作呢?因为它能让类型"参数化",也就是在不创建新的类型的情况下,通过泛型可以指定不同类型来控制形参具体限制的类型。
总结:
泛型介绍完了, 小伙伴看完上述解释后,能理解泛型是什么了吗?我们可以用两句话来概括一下:
泛型在声明时,用标记符表示,仅仅作为“参数化的类型”,可以理解为形式参数。
比如:
//定义List集合时,用标记符E表示任意类型,E可以理解为形式参数,没有具体的类型值public interface Listextends Collection{ ----------}
泛型在使用时,需要指定具体的类型,也就是类型实际的参数。
比如:
//在使用List集合时,需要确定E的具体类型,String可以理解为具体的类型,也就是实际的参数值Listlist = new ArrayList();
1.2 常用的泛型标记符
E - Element (集合使用,因集合中存放元素)
T - Type(Java 类)
K - Key(键) V - Value(值)
N - Number(数值类型)
? - 表示不确定的java类型
S、U、V - 2nd、3rd、4th types
你可能会有疑问,弄这么多标识符干嘛,直接使用万能的Object难道不香么?我们知道Object是所有类的基类(任何类如果没有指明其继承类,都默认继承于Object类),因此任何类的对象都可以设置Object的引用,只不过在使用的时候可能要类型强制转换。但是如果设置了泛型E、T等这些标识符,那么在实际使用之前类型就已经确定,因此不再需要类型强制转换
2.泛型的语法使用
2.1 泛型在集合中的使用
单列集合中List中
public interface List<E> extends Collection<E> {
----
<T> T[] toArray(T[] a);
----
}
双列集合Map中
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
---
V get(Object key){---};
V put(K key, V value){---};
V remove(Object key){---};
void putAll(Map<? extends K, ? extends V> m){---};
-----
}
小伙们可以看到:List,HashMap的源码,在声明集合时或者定义方法时,使用采用尖括号内加占位符的形式 ,这里的占位符就是我们上面说的泛型标记符,泛型标记符号E,K,V,T等用来表示任意类型(E,K,V,T也就是“泛型形参”,在实例化集合对象时需要明确的具体的类型(也就是“泛型的实际参数”))。
通过观察集合的源码,那么我们自己也可以定义泛型接口,泛型类以及泛型方法,下面我们一起操作一下吧。
2.2 声明泛型接口
泛型应用于接口。例如生成器(GeneratorType),这是一种专门负责创建对象的类。当使用生成器创建新的对象时,它不需要任何参数,也就是说生成器无需额外的信息就知道如何创建新对象。
一般而言,一个生成器只定义一个方法,该方法用以产生新的对象。
/**
* 定义一个泛型接口:生成任意对象
* @param <T>: 泛型形式参数,可以是任意类型
*/
public interface GeneratorType<T> {
T create();
}
/**
* 测试泛型接口
*/
class DemoGeneratorType{
public static void main(String[] args) {
//1.使用生成器:创建random对象
GeneratorType<Random> gt= new GeneratorType<Random>() {
@Override
public Random create() {
return new Random();
}
};
//2.使用 GeneratorType:创建对象
Random random = gt.create();
}
}
来,小伙伴们,我们一起分析下上面的代码:
我们声明了一个泛型接口 GeneratorType,目的用来生成任意类型的对象,在这里T可以表示任意类型。
我们在测试类中,通过GeneratorType创建对象时,可以传递任意类型。
比如 GeneratorType,那么就可以生成Random对象了
注意: 在这里,我们通过匿名内部类的方式创建了Random对象,这种写法大家要慢慢熟悉喔。
2.3 声明泛型类
泛型应用于类上面。例如订单类(Order),这是一个专门负责封装订单里面商品的类,当我们购物生成订单时,订单里面可以包含任何商品信息。
请注意,在类上定义的泛型,在类的变量、方法的参数以及方法中同样也能使用(静态方法除外)。
/**
* 定义一个订单类:封装任意类型的商品信息
* @param <T>
*/
public class Order<T> {
private T t ;//在变量中使用: T表示任意商品类型
public T get(){//在普通方法中使用:T表示任意商品类型
return t;
}
public void set(T t){//在方法的参数使用: T表示任意类型
this.t = t;
}
//测试:
public static void main(String[] args) {
Order<Phone> order = new Order<Phone>();//创建订单对象:封装Phone商品
order.set(new Phone("华为Mate20",3899.0));
System.out.println("商品名称:"+order.get().getPhoneName());
}
}
//定义手机商品类
class Phone{
private String phoneName;
private Double phonePrice;
public Phone(String phoneName, Double phonePrice) {
this.phoneName = phoneName;
this.phonePrice = phonePrice;
}
public Phone() {
}
public String getPhoneName() {
return phoneName;
}
public void setPhoneName(String phoneName) {
this.phoneName = phoneName;
}
public Double getPhonePrice() {
return phonePrice;
}
public void setPhonePrice(Double phonePrice) {
this.phonePrice = phonePrice;
}
}
ok,泛型类我们声明完成了,大家看一下是不是和我们声明泛型接口很相似啊,确实是一样的。
声明的语法就是:类名,在这里T可以表示任意类型。
小伙伴也可以看到,我们定义了一个带泛型的Order类,在我们创建订单对象时,可以传入任意类型的商品对象,使我们的操作更加灵活
2.4 声明泛型方法
泛型应用于方法上面。前面说过在泛型类上定义的泛型,在类的方法中也能使用(静态方法除外)。但是有的时候我们只想在某个方法上使用泛型,而不是整个类,这也是被允许的,下面我和小伙们一起来体验一下。
比如FactoryBean工厂类,通过泛型方法,创建任意类型的对象。
package cn.qf;
/**
* 定义一个工厂Bean:
*/
public class FactoryBean {
/*
定义不带泛型的方法
*/
public static Object createObject0(String className) throws Exception{
return Class.forName(className).newInstance();
}
/*
定义一个普通的泛型方法:className表示类的全路径
*/
public <T> T createObject1(String className) throws Exception{
return (T) Class.forName(className).newInstance();
}
/*
定义一个静态的泛型方法:className表示类的全路径
*/
public static <T> T createObject2(String className)throws Exception{
return (E) Class.forName(className).newInstance();
}
//测试:
public static void main(String[] args) throws Exception {
//创建一个Phone对象 : 不使用泛型方法,需要类型强转
Phone p1 = (Phone) FactoryBean.createObject0("cn.qf.Phone");
//创建一个Phone对象 :泛型方法,不需要类型强转
Phone p2 = FactoryBean.createObject2("cn.qf.Phone");
}
}
class Phone{
private String phoneName;
private Double phonePrice;
----
}
在这里我们使用工厂模式来创建对象,为了在我们获取对象时,不用类型强转,我们也使用了泛型。小伙伴通过代码可以看到,不使用泛型的方法,在获取对象时,需要类型强转(可能会引起类型强转异常)。
在使用泛型方法获取对象时,不需要类型强转(可以避免引起类型强转异常)。
2.5 泛型方法、泛型接口、泛型类小结
从上面的介绍小伙伴也看到了,泛型类的好处就是在泛型类上定义的泛型,在类的方法中也能使用(普通静态方法除外)。而泛型方法的最大优点就是能独立于类,不受类是否是泛型类的限制。因此当你考虑使用泛型的时候,优先考虑定义泛型方法。如果非要定义泛型类,
个人建议通过使用泛型方法来将整个类泛型化,因为这样就不用担心静态方法的事,如果有静态方法那必然是泛型方法。这样就能避免普通静态方法无法获取泛型类泛型的尴尬局面。
你以为这就把泛型介绍完了吗?并没有,小伙伴们先休息片刻,稍后我们继续喔。
闯关练习
需求:
定义一个泛型类:
包含与类的泛型一样的变量,
包含与类的泛型一样的方法,参数也使用泛型
同时定义一个类的泛型不相同的泛型方法
答案:
/**
* 定义泛型类:
* @param <T>: 泛型T
*/
public class GenericDemo4<T> {
//1.定义一个与T 一样的变量
private T t;
//2.定义一个与T一样的方法
public T test1(T outer){
System.out.println(outer);
return outer;
}
//3.定义一个与T不一样的方法
public <E> E test2(E e){
System.out.println("自定义泛型的方法:"+e);
return e;
}
//测试:
public static void main(String[] args) {
//1.创建对象:指定T的泛型为 String
GenericDemo4<String> gt = new GenericDemo4<String>();
//2.调用 与T 一样的泛型方法
gt.test1("hello world");
//3.调用 与T 不一样的泛型方法
gt.test2(10);
}
}
开班时间:2021-04-12(深圳)
开班盛况开班时间:2021-05-17(北京)
开班盛况开班时间:2021-03-22(杭州)
开班盛况开班时间:2021-04-26(北京)
开班盛况开班时间:2021-05-10(北京)
开班盛况开班时间:2021-02-22(北京)
开班盛况开班时间:2021-07-12(北京)
预约报名开班时间:2020-09-21(上海)
开班盛况开班时间:2021-07-12(北京)
预约报名开班时间:2019-07-22(北京)
开班盛况Copyright 2011-2023 北京千锋互联科技有限公司 .All Right 京ICP备12003911号-5 京公网安备 11010802035720号