零基础Java学习之泛型
-
- 概述
- 泛型类
-
- 语法格式
- 泛型类的使用
- 代码示例
- 泛型接口
-
- 语法格式
- 泛型接口的使用
- 代码示例
- 类型变量的上限和下限
-
- 类型变量的上限
-
- 语法格式
- 代码示例
- 类型变量的下限
-
- 语法格式
- 代码示例
- 泛型方法
-
- 语法格式
- 代码示例
- 泛型擦除
- 类型通配符
-
- <?> 任意类型
-
- 代码示例
- <? extends 上限>
-
- 代码示例
- <? super E>
-
- 代码示例
概述
作为一个面向对象的编程语言,Java可以通过实现一些类,作为我们各种需求的一个模板,方便我们的使用。但有时候,这个类的范围可能比我们想要的范围要大,我们只想限定于满足类的某些对象,那这样的情况下,泛型的概念就被提出来了(非官方解释,方便理解)。
举个例子:比如我们我们生活中的车,它可以作为一个类,但是车其实又有很多种,包括货车,轿车,大巴车等等,而其中的轿车外观差不多,但是又属于不同的品牌,这些品牌有很多不一样的地方,这里我们可以把轿车的品牌看作是泛型(类似于标签)
通过上面的解释,泛型的概念就比较清晰了,就是一种“类型参数”,所谓类型参数可以理解为将类型由原来的具体的类型进行参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
泛型的优点,不仅仅是上面提到的,其还有下面的优点::
- 类型安全: 提高Java 程序的类型安全(泛型的主要目标)。
通过知道使用泛型定义的变量的类型限制,编译器可以验证类型假设。 - 消除强制类型转换:消除源代码中的许多强制类型转换。
这使得代码的可读性更高了,并且还减少了错误
上面说到了泛型在类中的使用,其实泛型的使用远不止于此,其还可以在在接口、方法中使用。下面就对这些分别进行介绍
泛型类
所谓泛型类就是把当我们在声明类时,类中的有些成员的类型并不是确定,然后我们可以把泛型定义在类上,当使用该类的时候,再把不确定成员的类型明确下来。
语法格式
【修饰符】 class 类名<类型变量列表>{
//类体
}
注: <类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示。例如:、<K,V>等。
<类型变量列表>中的类型变量不能用于静态成员上。
泛型类的使用
使用这种类似于参数化类型的类时,在创建类的对象时候,我们需要注意:
- 指定类型变量对应的实际类型参数
- 实际类型参数必须是引用数据类型,不能是基本数据类型
注:指定泛型实参时,必须左右两边一致,不存在多态现象(右边的可以省略不写)
代码示例
/*
泛型类的声明与使用
*/
public class Demo1 {
public static void main(String[] args) {
//泛型类的使用(<T>里面只能是引用类型)
Student<Double> student1 = new Student<>("学生1",99.5);
Student<String> student2 = new Student<>("学生2","优秀");
Student<Character> student3 = new Student<>("学生3",'A');
//输出结果
System.out.println(student1);
System.out.println(student2);
System.out.println(student3);
}
}
//泛型类的声明
class Student<T> { //<T>这个就是泛型类的类型参数
private String name;
private T score; //使用泛型,定义分数(分数可能有double类型(99.5)、字符串类型(优秀)、字符类型(‘A’)等)
//构造方法
public Student() {
}
public Student(String name, T score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
泛型接口
泛型接口和泛型类关系,就像接口和类的关系一样。 这里不多说。
语法格式
【修饰符】 interface 接口名<类型变量列表>{
}
注: <类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示。例如:、<K,V>等。
<类型变量列表>中的类型变量不能用于静态成员上。
泛型接口的使用
使用这种类似于参数化类型的接口时,我们需要注意:
- 指定类型变量对应的实际类型参数
- 实际类型参数必须是引用数据类型,不能是基本数据类型
代码示例
/*
泛型接口的声明与使用
*/
public class Demo1 {
public static void main(String[] args) {
//泛型类的使用(<T>里面只能是引用类型)
Student<Double> student1 = new Student<>("学生1",99.5);
//使用泛型接口
student1.print("学生1",99.5);
}
}
//泛型类的声明
class Student<T> implements Print<String,T>{ //<T>这个就是泛型类的,后面<String,T>是接口,多个类型变量
private String name;
private T score; //使用泛型
//构造方法
public Student() {
}
public Student(String name, T score) {
this.name = name;
this.score = score;
}
//重写接口的方法
@Override
public void print(String s, T t) {
System.out.println("学生姓名:"+ this.name);
System.out.println("学生成绩:"+ this.score);
}
}
//泛型接口的声明
interface Print <T,V>{
//定义一个打印函数,可以打印学生姓名和成绩
public void print(T t, V v);
}
类型变量的上限和下限
前面说到,我们可以使用泛型类型参数,这样等我们进行实际使用的时候,我们可以任意使用类型,但如果想只使用某一系列的类型,泛型也是可以实现的。这就是我们说的类型变量的上限和类型变量的下限。下面进行分别介绍。
类型变量的上限
如果泛型类定义了类型变量的上限,那么该泛型类实际的类型只能是该上限类型或者其子类类型。
语法格式
泛型类和泛型方法的用法是一样的,后面都不再做区分。
<类型变量 extends 上限1 & 上限2> //上限可以有多个
注:如果多个上限中有类有接口,那么只能有一个类,而且必须写在最左边。接口的话,可以多个。
如果在声明<类型变量>时没有指定上限,默认上限是java.lang.Object。
代码示例
/*
类型变量的上限
*/
public class Demo2 {
public static void main(String[] args) {
Test<Double> test1 = new Test<>(77.5); //double类
// Test<String> test2 = new Test<String>(); 不是数字类的子类
Test<Integer> test3 = new Test<>(18);
test1.print(77.5);
test3.print(18);
}
}
class Test<T extends Number >{ //数字类上限,只能使用数字类及其子类
private T num;
public Test() {
}
public Test(T num) {
this.num = num;
}
public void print(T num){ //测试方法
System.out.println(num);
}
}
类型变量的下限
如果泛型类定义了类型变量的下限,那么该泛型类实际的类型只能是该下限类型或者其父类类型。
语法格式
<? super E > // ? 代表接收E类型或者E的父类型的元素
? 是泛型类中的通配符(下面会讲到,可以先看下面的再回来看这个)
代码示例
/*
<? super 下限>
*/
public class Demo5 {
public static void main(String[] args){
C<String> c=new C<>();
c.setT("<? super 下限>");
fun1(c);
}
//测试函数,泛型类使用了下限
public static void fun1(C<? super String> c){ //接受的数据类型只能为String、Object
System.out.println(c.getT()); //输入测试
}
}
class C<T>{
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
泛型方法
鉴于某个方法定义时,想要自己定义类型变量或者在某个静态方法中定义类型变量的需求,JDK还提供了泛型方法的支持。即可以在某个方法定义时,自定以<类型变量>
注:前面说到类和接口上的类型形参是不能用于静态方法
语法格式
【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)【throws 异常列表】{
//方法体
}
注:- <类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示。例如: < T >、<K,V>等。
<类型变量>同样也可以指定上限
代码示例
/*
泛型方法
*/
public class Demo3 {
public static void main(String[] args) {
Test1 test = new Test1(); //创建测试对象
test.print(12); //测试
test.print(12.5); //测试
}
}
class Test1{
public <T extends Number> void print(T t){ //泛型方法,可以设置上限
System.out.println("这是一个泛型方法,测试类型:" + t);
}
}
泛型擦除
泛型擦除只是在编译阶段才会有的,在实际运行阶段类型已经确定了,这个时候就没有泛型的概念了(JVM并不知道泛型的存在)。这个从有泛型信息到没有泛型信息的过程称之为“泛型擦除”。
其擦除规则如下:
- 若泛型类型没有指定具体类型,用Object作为原始类型;
- 若有限定类型< T exnteds XClass >,使用XClass作为原始类型;
- 若有多个限定< T exnteds XClass1 & XClass2 >,使用第一个边界类型XClass1作为原始类型;
类型通配符
通配符的意思是可以指代很多类型。这个主要使用在当我们在声明方法时,不确定该泛型实际类型的情况。类型通配符有三种:
- <?> 任意类型
- <? extends 上限>
- <? super E>
下面对这三种通配符分别进行介绍
<?> 任意类型
当泛型使用这种 类型通配符的时候,表示可以使用任意类型
代码示例
/*
类型通配符
*/
public class Demo4 {
public static void main(String[] args) {
// 语文老师使用时:
StudentInfo<String> stu1 = new StudentInfo<String>("张三", "良好");
// 数学老师使用时:
StudentInfo<Double> stu2 = new StudentInfo<Double>("张三", 90.5);
// 英语老师使用时:
StudentInfo<Character> stu3 = new StudentInfo<Character>("张三", 'C');
StudentInfo<?>[] arr = new StudentInfo[3]; //使用通配符
arr[0] = stu1;
arr[1] = stu2;
arr[2] = stu3;
StudentInfoPrint.print(arr); //打印输出结果
}
}
//学生类是一个参数化的泛型类
class StudentInfo<T>{
private String name;
private T score;
public StudentInfo() {
super();
}
public StudentInfo(String name, T score) {
super();
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "姓名:" + name + ", 成绩:" + score;
}
}
//学生信息打印类
class StudentInfoPrint {
//泛型方法,使用通配符
public static void print(StudentInfo<?>[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
<? extends 上限>
? 代表接收E类型或者E的子类型的元素
代码示例
可参考上面的类型变量的上限代码
<? super E>
? 代表接收E类型或者E的父类型的元素
代码示例
可参考上面的类型变量的下限代码