详谈 java泛型(参数化类型)
一、泛型概念
1、什么是泛型?
泛型,即“参数化类型”。我们最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。
本质:其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
ArrayList<E> objectName =new ArrayList<>();
E: 泛型数据类型,用于设置 objectName 的数据类型,只能为引用数据类型。
2、作用
第一是泛化。可以用T代表任意类型Java语言中引入泛型是一个较大的功能增强不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了,这带来了很多好处。
第二是类型安全。泛型的一个主要目标就是提高Java程序的类型安全,使用泛型可以使编译器知道变量的类型限制,进而可以在更高程度上验证类型假设。如果不用泛型,则必须使用强制类型转换,而强制类型转换不安全,在运行期可能发生ClassCast Exception异常,如果使用泛型,则会在编译期就能发现该错误。
第三是消除强制类型转换。泛型可以消除源代码中的许多强制类型转换,这样可以使代码更加可读,并减少出错的机会。
第四是向后兼容。支持泛型的Java编译器(例如JDK1.5中的Javac)可以用来编译经过泛型扩充的Java程序(Generics Java程序),但是现有的没有使用泛型扩充的Java程序仍然可以用这些编译器来编译。
3、优点
1、类型安全
2、消除强制类型转换
3、更高的运行效率
4、潜在的性能收益
二、泛型分类 :泛型类、泛型接口、泛型方法
首先我们得先了解泛型字母的含义
字母 | 意义 |
---|---|
E-Element | 在集合中使用,因为集合中存放的是元素 |
T-Type | Java类 |
K - Key | 键 |
v - Value | 值 |
N- Number | 数值类型 |
首先我们来看看使用泛型跟不使用泛型的区别:
不使用泛型:
import java.util.ArrayList;
import java.util.Iterator;
/**
* @Description: $泛型
* @Author: dyq
* @Date: $
*/
public class GenericDemo {
public static void main(String[] args) {
show01();
}
/*创建集合对象,不使用泛型
好处:
集合不使用泛型,默认的类型就是object类型,可以存储任意类型的数据
弊端:
不安全,会引发异常
* */
private static void show01() {
ArrayList l = new ArrayList();
l.add("hi");
l.add(1122);
l.add(10f);
l.add('男');
//用迭代器遍历集合
Iterator it = l.iterator();
while (it.hasNext()){
/* Object next = it.next(); //输出无异常,以为ArrayList默认的类型就是object类型
System.out.println(next);*/
String str = (String) it.next(); //输出异常 ,数组中不单单只有String类型,转型报错
System.out.println(str);
}
}
}
运行输出报错:
使用泛型
当规定泛型的类型时,输入非规定类型数据会直接报错。
import java.util.ArrayList;
import java.util.Iterator;
/**
* @Description: $泛型
* @Author: dyq
* @Date: $
*/
public class GenericDemo {
public static void main(String[] args) {
show01();
}
/*创建集合对象,使用泛型
好处:
1 .避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型
2.把运行期异常(代码运行之后会批出的异常),提升到了编译期
弊端:
泛型是什么类型,只能存储什么类型的数据
*/
private static void show01() {
ArrayList<String> l = new ArrayList<>();
l.add("小马");
l.add("小明");
l.add("小华");
l.add("小红");
//用迭代器遍历集合
Iterator it = l.iterator();
while (it.hasNext()){
String str = (String) it.next();
System.out.println(str);
}
}
}
输出结果:
小马
小明
小华
小红
1、泛型类
定义格式:
修饰符 class类名<代表泛型的变量>{}
看一个指定泛型为String 的
public class GenericClass {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) {
GenericClass gc =new GenericClass();
gc.setName("我只能是字符串");
String name = gc.getName();
System.out.println(name);
}
}
输出
当我们使用不使用指定类型时
/**
* @Description: $定义一个含有泛型的类, 模拟Arraylist集合
* 泛型是一个未知的数据类型,当我们不确定什么什么数据类型的时候,可以使用泛型
* 泛型可以接牧任意的数据类型,可以使用Integer , String, student.. .
* 创建对象的时候确定泛型的数据类型
* @Author: dyq
* @Date: $
*/
public class GenericClass<E> {
private E name;
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
public static void main(String[] args) {
//不写泛型默认为Object
GenericClass gc =new GenericClass();
gc.setName("我是Object");
Object name = gc.getName();
System.out.println(name);
//可以通过创建对象时确定数据类型
//泛型使用Integer类型
GenericClass<Integer> Igc = new GenericClass<>();
Igc.setName(1);
Integer i = Igc.getName();
System.out.println("我是Interger类型:"+i);
//泛型使用String类型
GenericClass<String> sgc = new GenericClass<>();
sgc.setName("花花");
String s = sgc.getName();
System.out.println("我是String类型:"+s);
}
}
结果:
我是Object
我是Interger类型:1
我是String类型:花花
2、泛型方法
定义含有泛型的方法:泛型定义在方法的修饰符和返回值类型之间格式:
语法:
修饰符<泛型>返回值类型方法名(参数列表(使用泛型)){
方法体;
}
代码示例:
/**
* @Description: 泛型方法$ 含有泛型的方法,在调用方法的时候确定泛型的数据类型,
* 传递什么类型的参数,泛型是什么类型
* @Author: dyq
* @Date: $2021年2月5日
*/
public class GenericMethod {
//定义一个含有泛型的方法
public <M> void method01(M m){
System.out.println(m);
}
//定义一个含有泛型的静态方法
public static <S> void method02(S s){
System.out.println(s);
}
//测试含有泛型的主方法
public static void main(String[] args) {
GenericMethod m = new GenericMethod();
m.method01(10);
m.method01("你好");
m.method01('男');
m.method01(19);
System.out.println("===============");
m.method02("我是方法02");
m.method02("ok!");
m.method02(1);
m.method02('女');
}
}
结果:
10
你好
男
19
===============
我是方法02
ok!
1
女
3、泛型接口
定义格式∶
修饰符interface接口名<代表泛型的变量>{ }
代码演示:
新建一个接口
/**
* @Description: 泛型接口$
* @Author: dyq
* @Date: 2021年2月5日$
*/
public interface GenericInterface<E> {
public abstract void add(E e);
public abstract E getE();
}
有两种实现方法先创建一个GenericInterfaceImpl01类
/**
* @Description: $含有泛型的接口,第一种方式:定义接口的实现,实现接口,指定接口的泛型
* @Author: dyq
* @Date: $
*/
public class GenericInterfaceImpl01 implements GenericInterface<String>{
@Override
public void add(String s) {
System.out.println(s);
}
@Override
public String getE() {
return null;
}
}
再创建一个GenericInterfaceImpl02实现类
/**
* @Description: $含有泛型的接口第二种使用方式:接口使用什么泛型,
* 实现类就使用什么泛型,类跟着接口走就相当于定义了一个含有泛型的类,
* 创建对象的时候确定泛型的类型
* @Author: dyq
* @Date: $
* public interface List<E>{
* boolean add(E e);
* E get(int index);
* }
* public class ArrayList<E> impLements List<E>{
* }
*/
public class GenericInterfaceImpl02<I> implements GenericInterface<I> {
@Override
public void add(I i) {
System.out.println(i);
}
@Override
public I getE() {
return null;
}
}
最后新建一个测试类
/**
* @Description: $测试含有泛型的接口
* @Author: dyq
* @Date: $
*/
public class GenericInterfaceTest {
public static void main(String[] args) {
//创建GenericInterfaceImpl
GenericInterfaceImpl01 gil =new GenericInterfaceImpl01();
gil.add("哈哈哈~");
//创建GenericInterfaceImpl01 定义输出的数据类型
GenericInterfaceImpl02<Integer> gil2 = new GenericInterfaceImpl02<Integer>();
gil2.add(10);
GenericInterfaceImpl02<Double> gil3 = new GenericInterfaceImpl02<Double>();
gil3.add(8.88);
}
}
结果:
哈哈哈~
10
8.88
三、泛型的通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符< ?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
1、通配符基本使用
泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。此时只能接受数据,不能往该集合中存储数据。
代码示例:
import java.util.ArrayList;
import java.util.Iterator;
/**
* @Description: $类型通配符一般是使用?代替具体的类型参数
* @Author: dyq
* @Date: $
*/
public class GenericTest {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
ArrayList<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
printArray(list);
printArray(list1);
}
/*定义一个方法,能遍历所有类型的ArrayList集合
* 这时候我们知道ArrList集合使用说明数据,可以泛型的通配符?来接收数据类型*/
public static void printArray(ArrayList<?> list){
//使用迭代器遍历集合
Iterator<?> it =list.iterator();
while (it.hasNext()){
//it.next()方法。取出的元素是object,可以接收任意的数据类型
Object o = it.next();
System.out.println(o);
}
}
}
结果:
1
2
a
b
2、类型通配符上限通过形如List来定义,如此定义就是通配符泛型值接受Number及其下层子类类型。
3、类型通配符下限通过形如 List<? super Number>来定义,表示类型只能接受Number及其三层父类类型,如 Object 类型的实例。
代码示例:
import java.util.ArrayList;
import java.util.Collection;
/**
* @Description: $泛型的上线限定:? extends E 代表我使用的泛型只能是E类型/本身
* 泛型的下限定: ? super E 代表我使用的泛型只能是E类型/本身
* @Author: dyq
* @Date: $
*/
public class GenericTest01 {
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);
/*类与类之间的继承关系
Integer extends Number extents Object
STring extends Object
* */
}
//泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){
}
//泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> collection){}
}
会有报错
更多推荐
所有评论(0)