泛型的定义:
在定义类的时候并不会设置类的属性和方法的参数的具体类型,而是在类实际用的时候再定义。它存在的意义是帮我们在编译期间检查我们的类型是否正确。
//定义一个Point类
class Point{
private Object x;
private Object y;
public Object getX() {
return x;
}
public void setX(Object x) {
this.x = x;
}
public Object getY() {
return y;
}
public void setY(Object y) {
this.y = y;
}
}
public class Generic {
public static void main(String[] args) {
//创建Point类对象
Point point = new Point();
//定义X和Y
point.setX("东经80度");
point.setY("北纬20度");
//转成字符串类型
String x = (String) point.getX();
String y = (String) point.getY();
//打印
System.out.println("x = "+x);
System.out.println("y = "+y);
}
}
我们先定义一个Point类,其中的X和Y属性我们都用定义为Object类,在使用的时候转成字符串,再进行打印。

但是当我们setY的时候给了它一个整数,但是后面转换成字符串的时候,编译器并没有个我们提示类型错误,只有到运行的时候在产生报错。

告诉我们34行产生类型转化错误,所以就产生类泛型,目的是可以帮助我们在编译器期间就可以告诉我们类型错误,及时修改!
泛型类:
基本语法:
class exampleClass<T>{
}
这里的尖括号中的T表示类型参数,可以指代任何类型。
这里的T可以用任意符号代替,但是java中会给出了一些标准。
T:代表一般的类
E:代表Element元素和Exception异常
K:代表key
V:代表Value,通常会配合K一起使用(只能出现一个K,K有它对应的Value,键值对)
S:代表Subtype子类型
我们利用泛型类将上面的代码再写一遍
//定义一个泛型Point类
class Point<T>{
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
public class Generic {
public static void main(String[] args) {
//创建Point类对象
Point<String> point = new Point<String>();
//定义X和Y
point.setX("东经80度");
point.setY("北纬30度");
//转成字符串类型
String x = (String) point.getX();
String y = (String) point.getY();
//打印
System.out.println("x = "+x);
System.out.println("y = "+y);
}
}
和之前的代码没有太多的差距,但是我们再将Y定义成一个int类型的数据看一下效果。

编译器提醒我们在32行出现了错误需要我们进行修改!这就是泛型存在的意义。
泛型方法:
基本语句:
class exampleClass<T>{
public <E> E exampleMethod(E e){
System.out.println(e);
return e;
}
}
第一个<E>表示这个方法是一个泛型方法;
第二个E表示方法的返回值类型是E;
括号中的E表示传入的参数的类型是E,这个E被称为是参数化类型。
泛型类和泛型方法是可以共存的,在泛型类中有泛型方法,他们俩尖括号中的内容不是一定要相同的,泛型方法的参数类型是单独的 ,与泛型类无关!
泛型接口:
基本语法:
interface IMessage<T>{
public void print(T t);
}
子类实现这个接口:
(1)子类仍然是泛型类:
//定义一个泛型接口
interface IMessage<T>{
//抽象方法
public void print(T t);
}
//定义一个类实现泛型接口
class MessageImpl1<T> implements IMessage<T>{
//覆写泛型接口的方法
@Override
public void print(T t) {
System.out.println(t);
}
}
(2)子类给出具体类型(子类就不是泛型类了):
//给出具体类型的类就不是泛型类了
class MessageImpl2 implements IMessage<Integer>{
//覆写接口方法
@Override
public void print(Integer integer) {
}
}
通配符:
问号通配符:<?>
当我们定义了一个泛型类,在写一个方法的时候,要将这个泛型类当做参数传入,但是我们这是不能确定这个泛型类的具体类型,所以就有了问号通配符,帮我们接受这个泛型类包含的所有数据类型。
//泛型类
class exampleClass2<T>{
T t;
//定义fun方法
//利用问号通配符,接收泛型类所包含的所有数据类型
public static void fun(exampleClass2<?> exampleClass){
System.out.println(exampleClass);
}
}
但是用问号通配符的方法不可以修改泛型类对象的值,因为我们在这是不知道该属性的类型,所有不能进行修改。我们只能获取它的值,不能修改!

这里77行我们试图修改,编译器就会提醒有错误。
上限通配符:<? extends A>
//泛型类
class exampleClass3<T>{
T t;
//上限通配符
public static void fun(exampleClass3<? extends Number> exampleClass3){
System.out.println(exampleClass3);
}
}
上限通配符与问号通配符的不同就是让问号继承了一个父类,这个这个方法只会接受这个父类及其子类的类型。
这里也是不清楚该方法具体传入的参数的类型,所以也是只能获取不能修改。

95行修改时报错。
下限通配符:<? super B>
//泛型类
class exampleClass4<T>{
T t;
//下限通配符
public static void fun(exampleClass4<? super Number> exampleClass4){
System.out.println(exampleClass4);
}
}
下限通配符表示方法只能接收该类及其所有的父类的类型。
这里我们清楚所有的传入数据不是当下这个就是它的父类,所以可以通过向上转型进行修改,这里是一个天然的向上转型。

这里我们在修改的时候就不会报错。
三种通配符的使用特点:
1、三种通配符都可以用在方法上
2、只有上限通配符还可以用在泛型类和泛型接口的声明上
3、问号通配符和上限通配符只能获取数据不能修改
4、只有下限通配符可以对元素进行修改
类型擦除:
泛型只存在于编译阶段,当进入JVM中所有的泛型内容都会被擦除。普通泛型擦除为Object;有泛型上限的擦除为相应的泛型上限;有泛型下限的,擦除为相应的反省下限。反省的存在就只是为了在编译阶段帮助程序员检查类型错误的,所以在运行是它的意义就失去了。
本文详细介绍了Java泛型的定义、泛型类、泛型方法、泛型接口及通配符的使用。泛型帮助我们在编译阶段检查类型错误,例如通过泛型类避免类型转换异常。此外,文章还讲解了通配符的三种形式:问号通配符<?>、上限通配符<? extends A>和下限通配符<? super B>,以及它们在读取和修改数据时的限制。最后,解释了类型擦除的概念,即泛型在运行时会被擦除,以确保泛型的类型安全。
详解&spm=1001.2101.3001.5002&articleId=121578091&d=1&t=3&u=6695bb7d78274212b59f141a7ff12e23)
1088

被折叠的 条评论
为什么被折叠?



