泛型:是一种未知的数据类型,当我们不知道使用什么数据类型的时候,可以使用泛型
泛型也可以看成是一个变量,用来接收数据类型
E e :Element元素
T t :Type类型
下面通过一张图更具体的解释一下泛型:
import java.util.ArrayList;
import java.util.Iterator;
public class Demo01Genneric{
public static void main(String[] args){
show02();
}
/*
创建集合对象,使用泛型
好处:
1.避免类型转换的麻烦,存储的是什么类型,取出的就是什么类型
2.把运行期异常(代码运行之后会抛出的异常),提升到了编译期(写代码的时候报错)
弊端:
泛型是什么类型,只能存储什么类型的数据
*/
private static void show02(){
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
//list.add(1);报错,因为创建对象时,已经确定只能添加String类型的数据
//使用迭代器遍历list集合
Iterator<String> it = list.iterator();//iterator()方法在Collection集合中,ArrayList集合实现了Collection集合,所以可以直接调用里面的方法
//使用迭代器中的方法hasNext和next遍历集合
while(it.hasNext()){
String s = it.next();
System.out.println(s+"-->"+s.length);//aaa-->3 *.length可获取字符串的长度*
}
}
/*
创建集合对象,不使用泛型
好处:
结合不使用泛型,默认的类型就是Object类型,可以存储任意类型的数据
弊端:
不安全,会引发异常
*/
private static void show01(){
ArrayList list = new ArrayList();//不使用泛型
list.add("abc");//存储字符串
list.add(1);//存储整数
//使用迭代器遍历list集合
Iterator it = list.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
//在此之前,代码都不会报错,
//但是想要使**用String类特有的方法,.length获取字符串的长度时**,不能使用 因为多态 Object obj = "abc"
//**需要向下转型**
String s = (String)obj;//但此时就会抛出ClassCastException类转换异常,因为集合中存在整数,不能把Integer类型转换为String类型
}
}
}
下面通过定义一个含有泛型的类,来模拟ArrayList集合
/*
泛型是一个未知的数据类型,当我们不能确定什么类型的时候,可以使用泛型
泛型可以接收任意的类型类型,可以使用Tnteger、String、Student...
创建对象的时候确定泛型的数据类型
*/
//创建一个泛型类
public class GenernicClass{
private E name;
public E getName(){
return name;
}
public void setName(E name){
this.name = name;
}
}
//主程序
public class Demo02GenernicClass{
public static void main(String[] args){
//**不写泛型默认为Object类型**
GenernicClass gc = new GenernicClass();
gc.setName("只能是字符串");
Object obj = gc.getName();
//创建GenernicClass对象,泛型使用Integer类型
GenernicClass<Integer> gc1 = new GenernicClass<>();
gc1.setName(1);
Integer name = gc1.getName();
System.out.println(name);//1
//创建GenernicClass对象,泛型使用String类型
GenernicClass<String> gc2 = new GenernicClass<>();
gc2.setName("小明");
String name = gc2.getName();
System.out.println(name);//小明
}
}
定义含有泛型的方法:
/*
定义含有泛型的方法:泛型定义在方法的*修饰符和返回值类型之间*
格式:
修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)){
方法体;
}
含有泛型的方法,在调用方法的时候确定泛型的数据类型
传递什么类型的参数,泛型就是什么类型
*/
public class GenernicMethod{
//定义一个含有泛型的方法
public <E> void method01(M m){
System.out.println(m);
}
//定义一个含有泛型的静态方法
public static <S> void method02(S s){
System.out.println(s);//表示泛型的字母,没有实际的区别
}
}
//测试含有泛型的方法
public class Demo03GenernicMethod{
public static void main(String[] args){
//创建GenernicMethod对象
GenernicMethod gm = new GenernicMethod();
/*
调用含有泛型的方法method01
**传递什么类型,泛型就是什么类型**
*/
gm.method01(10);
gm.method01("abc");
gm.method01(8.8);
gm.method01(true);
//使用静态方法
GenernicMethod.method02("静态方法");
GenernicMethod.method02(1);
}
}
定义含有泛型的接口
//定义格式:
修饰符 interface 接口名<代表泛型的变量> {}
//含有泛型的接口
public interface GenernicInterface<I>{
public abstract void method(I i);
}
/*
含有泛型的接口,第一种使用方式:定义接口的实现类,实现接口,指定接口的泛型
例如:1. public interface Iterator<E>{
E next();
}
2.Scanner类实现了Iterator接口,并指定接口的泛型为String,所以重写next方法泛型默认就是String
public final class Scanner implements Iterator<String>{
public String next() {}
}
*/
//用第一种方法创建实现类
public class GenernicInterfaceImpl1 implements GenernicInterface<String>{
@Override
public void method(String s){//String引用类型是跟着实现类确定的
System.out.println(s);
}
}
/*
含有泛型的接口第二种使用方式:接口使用什么泛型,实现类就使用什么泛型,类跟着接口走
就相当于定义了一个含有泛的类,创建对象的时候确定泛型的类型
//用第二种方法创建的实现类
*/
public class GenernicInterfaceImpl2<I> implements GenernicInterface<I>{
@Override
public void method(I i){
System.out.println(i);
}
}
下面讲讲泛型的通配符
/*
泛型的通配符:
? :代表任意的数据类型
使用方式:
不能在创建对象时使用
只能作为方法的参数使用
*/
public class Demo05Genernic{
public static void main(String[] args){
ArrayList<Integer> list01 = new ArrayList<>();
list01.add(1);
list01.add(2);
ArrayList<String> list02 = new ArrayList<>();
list02.add("a");
list02.add("b");
printArray(list01);
printArray(list02);
//ArrayList<?> list03 = new ArrayList<?>();错误写法,通配符 ?不能在创建对象时使用
}
/*
定义一个方法,能遍历所有类型的ArrayList集合
这时候我们不知道ArrayList集合使用什么数据类型,可以使用泛型的通配符 ? 来接受数据
*/
public static void printArray(ArrayList<?> list){
//使用迭代器遍历集合
Iterator<?> it = list.iterator();//这是调用方法获取实现类的对象,不算创建对象时,所以可以用通配符 ?。**此出不能用Iterator<Object> 使用则会报错,文章最会会讲解**
while(it.hasNext()){
//it.next()方法,取出的元素是Object,可以接受任何的数据类型
Object obj = it.next();
System.out.println(obj);
}
}
}
/*
泛型的上限限定:?extends E 代表使用的泛型只能是E类的子类/本身
泛型的下限限定:?super E 代表使用的泛型只能是E类型的父类/本身
*/
public class Demo06Genernic{
public static void main(String[] args){
Collection<Integer> coll =new ArrayList<>();
Collection<String> coll2 = new ArrayList<>();
Collection<Number> coll3 = new ArrayList<>();//Number类是Integer类的父类
Collection<Object> coll4 = new ArrayList<>();
getElement1(coll);
getElement1(coll2);//报错,String类不是Number类的子类
}
//泛型的上限:此时的泛型 ?,必须是Number类或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
//泛型的下限:此时的泛型 ?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
}
TIPS
这里补充两个个知识点:
1.、等与<?>之间的区别
2.泛型与的区别
、等与<?>之间的区别:
在刚接触到java泛型的学习的时候,分开来看,大家都容易能够知道它们的概念和使用方法,但是横向比较时,就会有点懵逼,感觉貌似都是一个意思啊,为啥要多此一举,下面就和详细讲一下它们之间的区别。
T、E等这些大写的字母,都没有实际的意义和区别,只是为了让程序的可读性更好,但它们与<?>之间就存在差别了,举个栗子:
【 ”“是类型参数】 【 ”<?>“是无界通配符】
类型参数是声明泛型类或泛型方法 ;无界通配符<?>是使用泛型类或泛型方法
声明泛型类的类型参数或声明泛型方法,声明泛型类不能用无界通配符<?>,通配符是拿来使用定义好的泛型的。
通俗的说,”T“是定义类或方法时声明的东西;”?“是调用时传入的东西
泛型与的区别:
在Java1.5 之前,没有泛型的情况下,通过对类型Object的引用来实现参数的”任意化“,”任意化“带来的缺点是要做显示的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处是数据类型,是由创建对象时传入什么样的类型参数,就只能接收什么样的数据类型参数,不需要强制转换