目录
- 一、基本介绍
- 1、Java泛型的基本语法格式为:
- 2、在使用泛型时,还需要注意以下几点:
- 二、泛型的优点
- 1、类型安全
- 2、消除强制类型转换
- 3、更高的效率
- 4、潜在的性能收益
- 三、常见泛型字母含义
- 四、使用泛型时的注意事项
- 五、泛型的使用
- 1、泛型类
- 2、泛型接口
- 3、泛型通配符
- 4、泛型方法
- 六、Java泛型上下边界
- 1、上边界
- 2、下边界
- 七、泛型擦除
大家好,我是哪吒。
一、基本介绍
Java泛型(Generics)是一种强类型约束机制,用于在编译时检查代码的类型安全性。它可以让程序员定义一些具有通用性的类、接口和方法,以便在后续使用中可以适用于不同的数据类型。
1、Java泛型的基本语法格式为:
public class Student<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
在上面的示例代码中,我们定义了一个泛型类MyClass,其中T表示类型参数(Type Parameter)。在类的成员变量和方法中,我们都可以使用T来代替实际的数据类型,从而实现对不同数据类型的支持。
通过使用泛型,可以大大提高代码的可读性和类型安全性,并且避免了需要进行强制类型转换的繁琐操作。同时,泛型还可以提高代码的重复利用率,减少代码量,提高开发效率。
2、在使用泛型时,还需要注意以下几点:
- 泛型只能使用引用类型,不能使用基本类型;
- 不能创建泛型数组,但可以使用泛型集合;
- Java的泛型是通过类型擦除(type erasure)实现的,在编译期间会被转换为原始类型,因此无法在运行时获取泛型类型的具体信息;
- Java中的泛型不支持协变和逆变,但Java SE 8及以上版本引入了一些新特性来支持函数式编程中的协变和逆变。
二、泛型的优点
1、类型安全
泛型的主要目的是提高Java程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在非常高的层次上验证类型假设。没有泛型,这些假设就只能存在于系统开发人员的头脑中。
通过在变量声明中捕获这一附加的类型信息,泛型允许编译器实施这些附加的类型约束。类型错误就可以在编译时被捕获了,而不是在运行时当作ClassCastException展示出来。将类型检查从运行时挪到编译时有助于Java开发人员更早、更容易地找到错误,并可提高程序的可靠性。
2、消除强制类型转换
泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。尽管减少强制类型转换可以提高使用泛型类的代码的累赞程度,但是声明泛型变量时却会带来相应的累赞程度。在简单的程序中使用一次泛型变量不会降低代码累赞程度。但是对于多次使用泛型变量的大型程序来说,则可以累积起来降低累赞程度。所以泛型消除了强制类型转换之后,会使得代码加清晰和筒洁。
3、更高的效率
在非泛型编程中,将筒单类型作为Object传递时会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显。
4、潜在的性能收益
泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,Java系统开发人员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的JVM的优化带来可能。
三、常见泛型字母含义
格式: 类名<字母列表>
- T Type表示类型
- K V 分辨表示键值对中的key value
- E 代表Element
- ?表示不确定的类型
四、使用泛型时的注意事项
1、在定义一个泛型类时,在“<>”之间定义形式类型参数,例如:“class TestGen<K,V>”,其中“K”,“V”不代表值,而是表示类型。
2、实例化泛型对象时,一定要在类名后面指定类型参数的值(类型),一共要有两次书写。
3、使用泛型时,泛型类型必须为引用数据类型,不能为基本数据类型,Java中的普通方法,构造方法,静态方法中都可以使用泛型,方法使用泛型之前必须先对泛型进行声明,可以使用任意字母,一般都要大写。
4、不可以定义泛型数组。
5、在static方法中不可以使用泛型,泛型变量也不可以用static关键字来修饰。
6、根据同一个泛型类衍生出来的多个类之间没有任何关系,不可以互相赋值。
7、泛型只在编译器有效
五、泛型的使用
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法
1、泛型类
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Student<T> {
//key这个成员变量的类型为T,T的类型由外部指定
private T key;
//泛型构造方法形参key的类型也为T,T的类型由外部指定
public Student(T key){
this.key = key;
}
//泛型方法getKey的返回值类型为T,T的类型由外部指定
public T getKey(){
return key;
}
public static void main(String[] args) {
//泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
//传入的实参类型需与泛型的类型参数类型相同,即为Integer.
Student<Integer> student = new Student<Integer>(123456);
//传入的实参类型需与泛型的类型参数类型相同,即为String.
Student<String> str= new Student<String>("哪吒编程");
System.out.println("泛型测试,key is "+student .getKey());
System.out.println("泛型测试,key is "+str.getKey());
}
}
泛型参数就是随便传的意思!
Student student1 = new Student("哪吒编程");
Student student2 = new Student(4444);
Student gstudent3 = new Student(55.55);
Student student4 = new Student(false);
2、泛型接口
泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:
//定义一个泛型接口
public interface Generator<T> {
public T next();
}
当实现泛型接口的类,未传入泛型实参时:
/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:class FruitGenerator<T> implements Generator<T>{
* 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
*/
class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}
当实现泛型接口的类,传入泛型实参时:
package javase.genericity;
import java.util.Random;
public class FruitGenerator implements Generator<String>{
String[] fruits = new String[]{"apple","banana","Pear"};
@Override
public String next() {
Random random = new Random();
System.out.println(fruits[random.nextInt(3)]);
return fruits[random.nextInt(3)];
}
public static void main(String[] args) {
FruitGenerator ff = new FruitGenerator();
ff.next();
}
}
3、泛型通配符
Java泛型通配符(wildcard)用于表示某个类型参数可以接受任意类型的实参,采用“?”作为通配符。
在使用泛型时,有时候需要定义一个方法或类,能够接受多种类型的参数。这时就可以使用泛型通配符来实现。Java中有三种泛型通配符:extends、super和无限制通配符。
(1)extends通配符
表示该类型参数是某个特定类型的子类或本身,可以接受该类型及其子类的实参。
例如:
public void printList(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n);
}
}
(2)super通配符
表示该类型参数是某个特定类型的超类或本身,可以接受该类型及其父类的实参。
例如:
public void addList(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
(3)无限制通配符
表示该类型参数可以接受任意类型的实参。
例如:
public void showList(List<?> list) {
for (Object o : list) {
System.out.println(o);
}
}
泛型通配符只能用于方法参数、返回值和局部变量等地方,不能用于类的定义中。同时,在使用泛型通配符时应该遵循PECS原则(Producer Extends, Consumer Super),即生产者使用extends通配符,消费者使用super通配符,以保证类型安全。
4、泛型方法
Java中的泛型方法是指在方法名称前加上的方法,它可以接受任意类型的参数。
下面是一个简单的示例:
public class GenericExample {
public <T> T[] createArray(T[] array) {
return array;
}
}
在上面的示例中,createArray方法接受一个T[]类型的参数,并返回一个T[]类型的数组。在方法体中,我们可以使用array参数来创建一个T[]类型的数组。
需要注意的是,泛型方法的参数类型必须与方法体中的参数类型相同。如果方法体中的参数类型与方法名称前加上的参数类型不同,则编译器会报错。
六、Java泛型上下边界
Java泛型上下边界是指在定义泛型时,我们可以通过指定类型参数的上限或下限来限制实际参数的类型范围。
1、上边界
使用extends关键字来限制类型参数的上限,表示该类型参数必须是指定类型的子类或本身。
public class MyClass<T extends Number> {
// 类定义
}
在上面的示例中,我们使用extends关键字将类型参数T的上限指定为Number类,表示只有Number及其子类才能作为实参传递进来。
2、下边界
使用super关键字来限制类型参数的下限,表示该类型参数必须是指定类型的超类或本身。
public class MyClass<T super Integer> {
// 类定义
}
在上面的示例中,我们使用super关键字将类型参数T的下限指定为Integer类,表示只有Integer及其父类才能作为实参传递进来。
需要注意的是,在使用上下边界时,如果同时指定了上下边界,则应该先指定下边界再指定上边界。同时,在使用上下边界时也需要遵循PECS原则(Producer Extends, Consumer Super),即生产者使用extends通配符,消费者使用super通配符,以保证类型安全。
七、泛型擦除
Java泛型擦除是指在Java编译器将泛型类型的代码编译成字节码时,会将所有泛型类型的信息删除,以避免运行时带来的额外开销。
在Java中,泛型只是一种编译时检查机制,并不会影响程序的运行时行为。在编译期间,Java编译器会将泛型类型擦除为原始类型,并在必要时插入强制类型转换语句。
List<String> list = new ArrayList<>();
list.add("哪吒编程");
String s = list.get(0);
然我们使用了泛型类型List,但在编译后,它会被擦除为List,因此get()方法返回的实际类型为Object,并且需要进行强制类型转换才能得到String类型的值。
由于泛型擦除的存在,导致在运行时无法获取泛型类型的具体信息,因此在使用反射和类型转换等相关操作时需要格外小心,以避免产生类型安全问题。
需要注意的是,在Java SE 8及以上版本中,引入了新的特性来支持对函数式接口中的泛型类型进行推断和获取,以及在局部变量初始化时使用类型推断,从而进一步提高了泛型的灵活性和可读性。
🏆本文收录于,49天精通Java从入门到就业。
全网最细Java零基础手把手入门教程,系列课程包括:基础篇、集合篇、Java8新特性、多线程、代码实战,持续更新中(每周1-2篇),适合零基础和进阶提升的同学。
🏆哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师。