文章目录
- 1. 泛型简介
- 2. 使用泛型的好处
- 3.使用泛型
- 3.1 泛型类
- 3.2 泛型接口
- 3.3 泛型方法
- 4 泛型的通配符
- 4.1 <?>:无边界的通配符
- 4.2 <? extends E>:固定上边界的通配符
- 4.3 <? super E>:固定下边界的通配符
- 5.总结
今天主要学习泛型
1. 泛型简介
在我们自定义的集合类中,底层是Object类型的数组,在设计和声明时,不能确定这个容器里到底要存储什么类型的数据。从JDK5版本之后,引入一个新的特性----泛型,提供了编译时的类型安全检测机制,该机制允许程序员在编译时检测到非法的数据类型。
泛型允许在定义类、接口时通过一个标识来表示其中某个属性的类型或者某个方法的返回值及参数类型。泛型的本质是参数化类型,给类型指定一个参数,然后在使用时再指定参数具体的值,这样类型可以在使用时决定了。
这种参数类型可以用在类、接口、方法中,分别称为泛型类、泛型接口和泛型方法。
2. 使用泛型的好处
- 保证了类型的安全性
如果没有使用泛型,在集合中存储和读取数据,都是Object类型。如果要将数据读取成特定类型,需要对每一个对象进行强制转换,如果存储的对象数据类型错误,在转换时会报异常。
- 消除强制转换
使代码可读性更强,减少出错机会
- 避免了不必要的拆箱封箱操作
- 提高了代码的重用性
3.使用泛型
3.1 泛型类
把泛型定义在类上,格式:
public class 类名<泛型>
例如:
public class MyArray<E>{}
-
泛型类型必须是引用类型,基本数据类型不可以
-
定义泛型类,在类后添加一对尖括号,在尖括号中填类型参数,参数可以有多个,多个参数用逗号分隔。
规范泛型使用字母的表示信息:
-
T:Type(java类)
-
E:Element(在集合中使用,指集合中存放的元素)
-
K:Key(键)
-
V:Value(值)
-
N:Number(数值类型)
-
?:表示不确定的java类型
public class GenericClass<T> {
private T value;
public GenericClass() {
}
public GenericClass(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
public class TestGenericClass {
public static void main(String[] args) {
//钻石符号
GenericClass<Integer> c=new GenericClass<>(123);
Integer x = c.getValue();
System.out.println(x);//123
c.setValue(456);
System.out.println(c.getValue());//456
GenericClass<String> c1=new GenericClass<>("这是一个字符串");
String s = c1.getValue();
System.out.println(s);//这是一个字符串
}
}
3.2 泛型接口
定义方式:
public interface 接口名<泛型>
public interface GenericInterface<T> {
void showValue(T value);
}
class imple implements GenericInterface<String >{
@Override
public void showValue(String value) {
System.out.println(value);
}
}
class imple1<T> implements GenericInterface<T>{
@Override
public void showValue(T value) {
System.out.println(value);
}
}
public class TestGenericInterface {
public static void main(String[] args) {
GenericInterface<String> c=new imple();
c.showValue("hello");
GenericInterface<Integer> x=new imple1<>();
x.showValue(123);
GenericInterface<String > y=new imple1<>();
y.showValue("hello world");
}
}
3.3 泛型方法
在调用方法时指明泛型的具体类型(参数和返回值)
定义格式:
1.没有返回值类型,有参数
public < T> void 方法名(T t){}
2.有返回值类型,可以有参数,也可以没有参数
public < T> T 方法名([T t]){}
public class GenericMethod {
/**
* 没有返回值类型,有参数
*public < T> void 方法名(T t){}
*/
public <T> void method1(T t){
System.out.println(t.toString());
}
/**
* 有返回值类型,可以有参数,也可以没有参数
*public < T> T 方法名([T t]){}
*/
public <T> T method2(T t){
System.out.println(t.getClass().getName());
return t;
}
}
class Student{
private String name;
private String gender;
//此处省略相关的Getter()和Setter()方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
'}';
}
}
class Teacher{
private String name;
private Integer age;
//此处省略相关的Getter()和Setter()方法
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class TestGenericMethod {
public static void main(String[] args) {
Student student=new Student("李白","男");
GenericMethod c=new GenericMethod();
c.method1(student);
Teacher teacher=new Teacher("李清照",25);
c.method1(teacher);
System.out.println("------------------------");
c.method2(student);
c.method2(teacher);
}
}
4 泛型的通配符
用于解决泛型之间引用传递问题的特殊用法,主要分成三种情况:
4.1 <?>:无边界的通配符
主要作用是让泛型能够接受未知类型的数据。在没有赋值前,表示可以接受任何的数据类型,赋值之后不能往里面随便添加元素,因为不知道集合的数据类型,只能做读取操作,并且读到元素当成Object实例操作,但是可以去执行remove移除和clear清空操作。
使用情况:
- 用于编写可使用Object类中提供的功能方法时
- 代码使用不依赖于类型参数的泛型类中的方法时
public class TestGenericWildCard {
public static void main(String[] args) {
MyArray<Integer> myArray=new MyArray<>();
myArray.add(123);
myArray.add(456);
myArray.add(789);
test(myArray);
}
//不知道集合是哪种类型,只能进行读取操作,读取到的元素都当成Object对象来处理
public static void test(MyArray<?> array){
//在没有赋值前,表示可以接受任何的数据类型,赋值之后不能往里面随便添加元素
//array.add(100);//报错
/*//可以执行删除操作
array.remove(0);
//不知道集合是哪种类型,只能进行读取操作,读取到的元素都当成Object对象来处理
for(int i=0;i<array.size();i++){
System.out.println(array.get(i));
}*/
//可以做清除操作
array.clear();
System.out.println(array.size());//0
}
}
4.2 <? extends E>:固定上边界的通配符
协变:在使用父类类型场景的地方可以改用子类类型(也就是说,父类出现的地方子类一定可以出现)
逆变:在使用子类类型场景的地方可以改用父类类型
不变:不能做到以上两点
数组是可以协变的。泛型不是协变的。这种设计降低了程序的灵活性,为了解决这个问题,设计出固定上边界的通配符。能够接受指定类及其子类类型的数据。
虽然用的是extends关键字,但不限于继承了父类的子类,也可以使用接口的实现类
使用上限通配符只能从集合中获取值,而不能将值放入集合中。
public class TestArray {
public static void main(String[] args) {
//数组是可以协变的
//在X数组中,0的位置存储的是Y,1的位置存储的是Z,
//在转换的过程中会报错,会产生ArrayStoreException异常
X[] x=new X[2];
x[0]=new Y();
x[1]=new Z();//抛异常:ArrayStoreException
//为了解决这个问题,就提出了泛型的概念
}
}
class X{}
class Y extends X{}
class Z extends X{}
public class TestArray {
public static void main(String[] args) {
//泛型不是协变的
//虽然Y是继承自X的,但是把Y放进X中时会报错,所以泛型不是协变的
MyArray<Y> array=new MyArray<>();
test(array);//报错
}
public static void test(MyArray<X> array){
}
}
class X{}
class Y extends X{}
class Z extends X{}
报错信息:
上面的程序使用固定上边界的通配符<? extends E>就可以了
public class TestArray {
public static void main(String[] args) {
//使用固定上边界的通配符<? extends E>就可以了
MyArray<Y> array=new MyArray<>();
test(array);
MyArray<Z> array1=new MyArray<>();
test(array1);
}
public static void test(MyArray<? extends X> array){
}
}
class X{}
class Y extends X{}
class Z extends X{}
虽然用的是extends关键字,但不限于继承了父类的子类,也可以使用接口的实现类。
public class TestArray {
public static void main(String[] args) {
//虽然用的是extends关键字,但不限于继承了父类的子类,也可以使用接口的实现类
MyArray<MyImpl1> inter=new MyArray<>();
test(inter);
}
public static void test(MyArray<? extends MyInter1> inter){
}
}
interface MyInter1{}
class MyImpl1 implements MyInter1{}
class MyImpl2 implements MyInter1{}
public class TestArray {
public static void main(String[] args) {
//虽然用的是extends关键字,但不限于继承了父类的子类,也可以使用接口的实现类
MyArray<MyImpl1> inter=new MyArray<>();
test(inter);
}
public static void test(MyArray<? extends MyInter1> inter){
inter.add(new MyImpl1());//报错 原因是:使用上限通配符只能从集合中获取值,而不能将值放入集合中
}
}
interface MyInter1{}
class MyImpl1 implements MyInter1{}
class MyImpl2 implements MyInter1{}
程序报错信息:此错误出现的原因是:使用上限通配符只能从集合中获取值,而不能将值放入集合中。如下图所示:
做读取操作是可以的:
public class TestArray {
public static void main(String[] args) {
//虽然用的是extends关键字,但不限于继承了父类的子类,也可以使用接口的实现类
MyArray<MyImpl1> inter=new MyArray<>();
inter.add(new MyImpl1());
inter.add(new MyImpl1());
inter.add(new MyImpl1());
inter.add(new MyImpl1());
test(inter);
}
public static void test(MyArray<? extends MyInter1> inter){
//inter.add(new MyImpl1());
//做读取操作是可以的
for (int i=0;i<inter.size();i++){
System.out.println(inter.get(i));
}
}
}
interface MyInter1{}
class MyImpl1 implements MyInter1{}
class MyImpl2 implements MyInter1{}
4.3 <? super E>:固定下边界的通配符
接受指定类及其父类类型(或接口)的数据
可以读取到集合的数据,按照Object类型处理
可以向方法中添加元素,添加的只能是指定类或其子类类型的对象,不能添加父类或接口类型的对象
public class TestArray1 {
public static void main(String[] args) {
MyArray<MyInter> array=new MyArray<>();
array.add(new MyImple2());
array.add(new MyImple2());
test1(array);
}
public static void test1(MyArray<? super MyImple1> array){
// MyImple1 object= (MyImple1) array.get(0);//报错java.lang.ClassCastException类转换异常
//可以读取到集合的数据,按照Object类型处理
Object object=array.get(0);
System.out.println(object);
}
}
interface MyInter{}
class MyImple1 implements MyInter{}
class MyImple2 implements MyInter{}
//可以向方法中添加元素,添加的只能是指定类或其子类类型的对象,不能添加父类或接口类型的对象
public class TestArray1 {
public static void main(String[] args) {
MyArray<MyInter> array=new MyArray<>();
array.add(new MyImple2());
array.add(new MyImple2());
test1(array);
}
public static void test1(MyArray<? super MyImple1> array){
//消费是可以的
//添加元素时,不能加指定类的父类对象
//添加的只能是指定类或其子类类型的对象
array.add(new MyImple1());//添加当前指定类对象
array.add(new SubImpl());//添加当期那指定类的子类对象
//array.add(new MyImple2());//报错
//array.add(new MyInter());//报错
}
}
interface MyInter{}
class MyImple1 implements MyInter{}
class MyImple2 implements MyInter{}
class SubImpl extends MyImple1{}
5.总结
-
1.如果要从集合中获取值,使用上限通配符
-
2.如果要向集合中放入数据值,使用下限通配符
-
3.可以为通配符指定上限,也可以指定下限,但不能同时指定两者