Java数据结构基础——泛型通配符
Posted YoLo♪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java数据结构基础——泛型通配符相关的知识,希望对你有一定的参考价值。
作者:敲代码の流川枫
博客主页:流川枫的博客
专栏:和我一起学java
语录:Stay hungry stay foolish
工欲善其事必先利其器,给大家介绍一款超牛的斩获大厂offer利器——牛客网
文章目录
1. 泛型概念
泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化
一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。 ——《Java编程思想》
泛型的引出
实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值
可以将数组定义为Object类,因为所有类默认继承于这个类
public class Test
public static void main(String[] args)
MyArray myArray = new MyArray();
myArray.setVal(0,10);
myArray.setVal(1, "s");//字符串也可以存放
myArray.setVal(2,10.3);
//String ret = myArray.getPos(1);//编译报错
String ret = (String) myArray.getPos(1);
System.out.println(ret);
class MyArray
public Object[] array = new Object[10];
public Object getPos(int pos)
return this.array[pos];
public void setVal(int pos,Object val)
this.array[pos] = val;
//String ret = myArray.getPos(1);//编译报错
String ret = (String) myArray.getPos(1);
System.out.println(ret);
这里我们看到发生了向下转型,需要手动强制类型转换才可以,这样数据很多的时候就会很麻烦
使用Object数组的缺点有二,存放元素时可以存放任何类型的元素,再者,取出元素的时候需要手动强转
此时泛型就解决了这些问题,它将类型参数化了
2. 泛型语法
class 泛型类名称<类型形参列表>
// 这里可以使用类型参数
class ClassName<T1, T2, ..., Tn>
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */
// 这里可以使用类型参数
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1>
// 可以只使用部分类型参数
注意:
1. 类名后的 <T> 代表占位符,表示当前类是一个泛型类
类型形参一般使用一个大写字母表示,常用的名称有:
E 表示 Element
K 表示 Key
V 表示 Value
N 表示 Number
T 表示 Type
S, U, V 等等 - 第二、第三、第四个类型2. 不能new 泛型类型的数组
T[] ts = new T[5];//是不对的
3.<>里必须是类类型,不能是简单类型
我们将上述代码改写:
class MyArray<T>
public T[] array = (T[]) new Object[10];
public T getPos(int pos)
return this.array[pos];
public void setVal(int pos,T val)
this.array[pos] = val;
public class Test
public static void main(String[] args)
MyArray<Integer> myArray = new MyArray<Integer>();
myArray.setVal(0,10);
myArray.setVal(1, "s");//报错
myArray.setVal(2,10.3);//报错
int a = myArray.getPos(0);//不用强转
System.out.println(a);
MyArray<String> myArray1 = new MyArray<String>();
myArray1.setVal(0,"hello");
myArray1.setVal(1,"world");
String b = myArray1.getPos(0);//不用强转
System.out.println(b);
泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查。此时,就需要把类型作为参数传递。需要什么类型,就传入什么类型
泛型存在的意义:
1.在存放类型的时候会进行类型的检查
2.取出元素的时候会自动强制类型转换
注意:泛型时在编译的时候的一种机制,在运行时是没有泛型的概念的
3. 泛型的使用
泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
MyArray<Integer> list = new MyArray<Integer>();
类型推导(Type Inference)
public class Test
public static void main(String[] args)
MyArray<Integer> myArray = new MyArray<>();
myArray.setVal(0,10);
int a = myArray.getPos(0);//不用强转
System.out.println(a);
MyArray<String> myArray1 = new MyArray<>();
myArray1.setVal(0,"hello");
myArray1.setVal(1,"world");
String b = myArray1.getPos(0);//不用强转
System.out.println(b);
编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写
MyArray<Integer> myArray = new MyArray<>();
MyArray<String> myArray1 = new MyArray<>();
裸类型(Raw Type)
裸类型是一个泛型类型,但是没有实参,取值是还是要强转
注意: 我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制
4. 泛型的擦除机制
编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制
getPos中返回值为T,被替换为Object,setVal中返回值为空,即V,val参数类型被替换为Object
先根据你制定了类型进行检查和转换,在编译的时候把T都擦除成了Object
因此,Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息
5. 泛型的上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束
语法
class 泛型类名称<类型形参 extends 类型边界>
//...
示例
public class MyArray<E extends Number>
//...
只接受 Number 的子类型作为 E 的类型,实参没有指定类型边界 E,可以视为 E extends Object
public class Test
public static void main(String[] args)
MyArray<Integer> myArray = new MyArray<>();
MyArray<Float> myArray1 = new MyArray<>();
MyArray<Double> myArray2 = new MyArray<>();
class MyArray<E extends Number>
6. 泛型方法
静态的泛型方法 需要在static后用<>声明泛型类型参数
class Util
public static <E> void swap(E[] array, int i, int j)
E t = array[i];
array[i] = array[j];
array[j] = t;
<E>是泛型的形参,void是返回值,可以直接通过类名.来调用,不用new
非静态
7. 通配符
看一个例子:
class Message<T>
private T message ;
public T getMessage()
return message;
public void setMessage(T message)
this.message = message;
public class TestDemo
public static void main(String[] args)
Message<String> message = new Message() ;
message.setMessage("hello");
fun(message);
Message<Integer> message1 = new Message() ;
message1.setMessage(10);
fun(message1);//报错
public static void fun(Message<String> temp)
System.out.println(temp.getMessage());
报错原因是fun方法只能接收Message<String>类型的参数,而传入的是Message<Integer>类型的参数
接下来我们体会通配符的作用
public static void fun(Message<?> temp)
System.out.println(temp.getMessage());
此时不再报错
通配符可以接收所有的泛型类型,但是又不能够让用户随意修改
public class TestDemo
public static void main(String[] args)
Message<Integer> message = new Message() ;
message.setMessage(55);
fun(message);
// 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
public static void fun(Message<?> temp)
temp.setMessage(100); //无法修改!
System.out.println(temp.getMessage());
通配符上界
<? extends 上界>
<? extends Number>//可以传入的实参类型是Number或者Number的子类
class Food
class Fruit extends Food
class Apple extends Fruit
class Banana extends Fruit
class Message<T> // 设置泛型上限
private T message ;
public T getMessage()
return message;
public void setMessage(T message)
this.message = message;
public class TestDemo
public static void main(String[] args)
Message<Apple> message = new Message<>() ;
message.setMessage(new Apple());
fun(message);
Message<Banana> message2 = new Message<>() ;
message2.setMessage(new Banana());
fun(message2);
// 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
public static void fun(Message<? extends Fruit> temp)
//temp.setMessage(new Banana()); //无法修改!
//temp.setMessage(new Apple()); //无法修改!
System.out.println(temp.getMessage());
此时无法在fun函数中对temp进行添加元素,因为temp接收的是Fruit和他的子类,此时存储的元素应该是哪个子类无法确定。所以添加会报错,但是可以获取元素
public static void fun(Message<? extends Fruit> temp)
//temp.setMessage(new Banana()); //无法修改!
//temp.setMessage(new Apple()); //无法修改!
Fruit b = temp.getMessage();
System.out.println(b);
通配符的上界,不能进行写入数据,只能进行读取数据
通配符下界
<? super 下界>
<? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型
看一个例子
class Food
class Fruit extends Food
class Apple extends Fruit
class Message<T>
private T message ;
public T getMessage()
return message;
public void setMessage(T message)
this.message = message;
public class TestDemo
public static void main(String[] args)
Message<Fruit> message = new Message<>() ;
message.setMessage(new Fruit());
fun(message);
Message<Food> message2 = new Message<>() ;
message2.setMessage(new Food());
fun(message2);
Message<Apple> message1 = new Message<>();
fun(message1);//报错
//temp 接收Fruit及其子类的一个Message
public static void fun(Message<? super Fruit> temp)
// 此时可以修改 添加的是Fruit 或者Fruit的子类
temp.setMessage(new Apple());//这个是Fruit的子类
temp.setMessage(new Fruit());//这个是Fruit的本身
//Fruit fruit = temp.getMessage(); 不能接收,这里无法确定是哪个父类
System.out.println(temp.getMessage());//只能直接输出
通配符的下界,不能进行读取数据,只能写入数据
因为无法确定是哪个父类
说明可以传入的实参的类型必须是Fruit或者Fruit的父类类型
“ 本期的分享就到这里了, 记得给博主一个三连哈,你的支持是我创作的最大动力!
大数据必学Java基础(五十五):泛型深入了解
文章目录
泛型深入了解
一、引入
1、什么是泛型(Generic)
泛型就相当于标签
形式:<>
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,
JDK1.5之 后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。
Collection<E>, List<E>, ArrayList<E> 这个<E>就是类型参数,即泛型。
2、没有泛型的时候使用集合
package com.lanson.test01;
import java.util.ArrayList;
/**
* @author : lanson
*/
public class Test01
//这是main方法,程序的入口
public static void main(String[] args)
//创建一个ArrayList集合,向这个集合中存入学生的成绩:
ArrayList al = new ArrayList();
al.add(98);
al.add(18);
al.add(39);
al.add(60);
al.add(83);
al.add("lili");
//对集合遍历查看:
for(Object obj:al)
System.out.println(obj);
如果不使用泛型的话,有缺点
一般我们在使用的时候基本上往集合中存入的都是相同类型的数据 --》便于管理,所以现在什么引用数据类型都可以存入集合,不方便!
3、JDK1.5以后开始使用泛型,集合中使用泛型
package com.lanson.test01;
import java.util.ArrayList;
/**
* @author : lanson
*/
public class Test01
//这是main方法,程序的入口
public static void main(String[] args)
//创建一个ArrayList集合,向这个集合中存入学生的成绩:
//加入泛型的优点:在编译时期就会对类型进行检查,不是泛型对应的类型就不可以添加入这个集合。
ArrayList<Integer> al = new ArrayList<Integer>();
al.add(98);
al.add(18);
al.add(39);
al.add(60);
al.add(83);
/*al.add("lili");
al.add(9.8);*/
//对集合遍历查看:
/*for(Object obj:al)
System.out.println(obj);
*/
for(Integer i:al)
System.out.println(i);
4、泛型总结
1、JDK1.5以后泛型实际就是 一个<>引起来的 参数类型,这个参数类型具体在使用的时候才会确定具体的类型。
2、使用了泛型以后,可以确定集合中存放数据的类型,在编译时期就可以检查出来。
3、使用泛型你可能觉得麻烦,实际使用了泛型才会简单,后续的遍历等操作简单。
4、泛型的类型:都是引用数据类型,不能是基本数据类型。
5、ArrayList<Integer> al = new ArrayList<Integer>();在JDK1.7以后可以写为:
ArrayList<Integer> al = new ArrayList<>(); --<> ---钻石运算符
二、自定义泛型结构
1、泛型类,泛型接口
1.1、泛型类的定义和实例化
package com.lanson.test02;
/**
* @author : lanson
* GenericTes就是一个普通的类
* GenericTest<E> 就是一个泛型类
* <>里面就是一个参数类型,但是这个类型是什么呢?这个类型现在是不确定的,相当于一个占位
* 但是现在确定的是这个类型一定是一个引用数据类型,而不是基本数据类型
*/
public class GenericTest<E>
int age;
String name;
E sex;
public void a(E n)
public void b(E[] m)
class Test
//这是main方法,程序的入口
public static void main(String[] args)
//GenericTest进行实例化:
//(1)实例化的时候不指定泛型:如果实例化的时候不明确的指定类的泛型,那么认为此泛型为Object类型
GenericTest gt1 = new GenericTest();
gt1.a("abc");
gt1.a(17);
gt1.a(9.8);
gt1.b(new String[]"a","b","c");
//(2)实例化的时候指定泛型:---》推荐方式
GenericTest<String> gt2 = new GenericTest<>();
gt2.sex = "男";
gt2.a("abc");
gt2.b(new String[]"a","b","c");
1.2、继承情况
1.2.1、父类指定泛型
class SubGenericTest extends GenericTest<Integer>
class Demo
//这是main方法,程序的入口
public static void main(String[] args)
//指定父类泛型,那么子类就不需要再指定泛型了,可以直接使用
SubGenericTest sgt = new SubGenericTest();
sgt.a(19);
1.2.2、父类不指定泛型
如果父类不指定泛型,那么子类也会变成一个泛型类,那这个E的类型可以在创建子类对象的时候确定:
class SubGenericTest2<E> extends GenericTest<E>
class Demo2
//这是main方法,程序的入口
public static void main(String[] args)
SubGenericTest2<String> s = new SubGenericTest2<>();
s.a("abc");
s.sex = "女";
1.3、应用场合
1.4、细节
1.4.1、泛型类可以定义多个参数类型
1.4.2、泛型类的构造器的写法
1.4.3、不同的泛型的引用类型不可以相互赋值
1.4.4、泛型如果不指定,那么就会被擦除,反应对应的类型为Object类型
1.4.5、反省类中的静态方法不能使用类的泛型
1.4.6、不能直接使用E[]的创建
2、泛型方法
package com.lanson.test04;
/**
* @author : lanson
* 1.什么是泛型方法:
* 不是带泛型的方法就是泛型方法
* 泛型方法有要求:这个方法的泛型的参数类型要和当前的类的泛型无关
* 换个角度:
* 泛型方法对应的那个泛型参数类型 和 当前所在的这个类 是否是泛型类,泛型是啥 无关
* 2.泛型方法定义的时候,前面要加上<T>
* 原因:如果不加的话,会把T当做一种数据类型,然而代码中没有T类型那么就会报错
* 3.T的类型是在调用方法的时候确定的
* 4.泛型方法可否是静态方法?可以是静态方法
*/
public class TestGeneric<E>
//不是泛型方法 (不能是静态方法)
public static void a(E e)
//是泛型方法
public static <T> void b(T t)
class Demo
//这是main方法,程序的入口
public static void main(String[] args)
TestGeneric<String> tg = new TestGeneric<>();
tg.a("abc");
tg.b("abc");
tg.b(19);
tg.b(true);
3、泛型参数存在继承关系的情况
4、通配符
4.1、在没有通配符的时候
下面的a方法,相当于方法的重复定义,报错
public class Test
/*public void a(List<Object> list)
public void a(List<String> list)
public void a(List<Integer> list)
*/
4.2、引入通配符
public class Demo
//这是main方法,程序的入口
public static void main(String[] args)
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<Integer> list3 = new ArrayList<>();
List<?> list = null;
list = list1;
list = list2;
list = list3;
发现: A 和 B是子类父类的关系,G<A>和G<B>不存在子类父类关系,是并列的
加入通配符?后,G<?>就变成了 G<A>和G<B>的父类
4.3、使用通配符
package com.lanson.test06;
import java.util.ArrayList;
import java.util.List;
/**
* @author : lanson
*/
public class Test
/*public void a(List<Object> list)
public void a(List<String> list)
public void a(List<Integer> list)
*/
public void a(List<?> list)
//内部遍历的时候用Object即可,不用?
for(Object a:list)
System.out.println(a);
class T
//这是main方法,程序的入口
public static void main(String[] args)
Test t = new Test();
t.a(new ArrayList<Integer>());
t.a(new ArrayList<String>());
t.a(new ArrayList<Object>());
4.4、查看API中应用位置
5、使用通配符后的细节
public class Test
public void a(List<?> list)
//1.遍历:
for(Object a:list)
System.out.println(a);
//2.数据的写入操作 :
//list.add("abc");-->出错,不能随意的添加数据
list.add(null);
//3.数据的读取操作:
Object s = list.get(0);
class T
//这是main方法,程序的入口
public static void main(String[] args)
Test t = new Test();
t.a(new ArrayList<Integer>());
t.a(new ArrayList<String>());
t.a(new ArrayList<Object>());
6、泛型受限
package com.lanson.test07;
import java.util.ArrayList;
import java.util.List;
/**
* @author : lanson
*/
public class Test
//这是main方法,程序的入口
public static void main(String[] args)
//a,b,c三个集合是并列的关系:
List<Object> a = new ArrayList<>();
List<Person> b = new ArrayList<>();
List<Student> c = new ArrayList<>();
/*开始使用泛型受限:泛型的上限
List<? extends Person>:
就相当于:
List<? extends Person>是List<Person>的父类,是List<Person的子类>的父类
*/
List<? extends Person> list1 = null;
/*list1 = a;
list1 = b;
list1 = c;*/
/*开始使用泛型受限:泛型的下限
List<? super Person>
就相当于:
List<? super Person>是List<Person>的父类,是List<Person的父类>的父类
*/
List<? super Person> list2 = null;
list2 = a;
list2 = b;
list3 = c;
- 📢博客主页:https://lansonli.blog.csdn.net
- 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
- 📢本文由 Lansonli 原创,首发于 CSDN博客🙉
- 📢停下休息的时候不要忘了别人还在奔跑,希望大家抓紧时间学习,全力奔赴更美好的生活✨
以上是关于Java数据结构基础——泛型通配符的主要内容,如果未能解决你的问题,请参考以下文章