JAVA入门基础_JAVA中的泛型 Generic(六)
泛型Generic
什么是泛型、为什么需要使用泛型
-
泛型:泛型是JDK1.5引入的一个新特性,提供了编译时类型安全的监测极致,该极致允许我们在编译时期监测到非法的类型数据结构。本质上就是一个是一个参数化类型,理解为要把所操作的类型当做参数来使用。
-
为何要使用: 在JDK1.5没有推出泛型以前,我们如果一个方法想要接收多种不同类型的参数,就只能使用Object来作为参数类型。也就导致了频繁的强制转换,容易发生类型转换异常。
-
因此使用泛型有保证数据安全的作用
-
消除了强制类型的转换(生成的字节码文件会自动进行强制转换)
-
泛型的常用标识(T、E、K、V)
- T 代表type类型,一般是指JAVA类
- E 代表element元素,一般是指的集合中的元素类型
- K 理解为map中的key,键
- V 理解为map中的value,值
泛型的知识点(未指定时、泛型类型)
-
当一个泛型类或泛型方法的泛型未被指定时,会直接使用Object类型
-
泛型的参数类型只能是类类型,不能是基本数据类型(基本数据类型不继承Object)
-
泛型类型从逻辑上可以看成是多个不同的类型,但实际上都是相同类型(可以看看如下的示例)
public class Test {
public static void main(String[] args) {
Generic<Integer,Integer> generic1 = new Generic<>();
Generic<String,String> generic2 = new Generic<>();
// 可以看出,泛型类型实际上都是相同类型
System.out.println(generic1.getClass()); // Generic
System.out.println(generic2.getClass()); // Generic
System.out.println(generic2.getClass() == generic1.getClass()); // true
}
}
/**
* 定义一个泛型类
*/
class Generic<T,E> {
}
泛型类
泛型类的语法
class 类名称 <泛型标识, 泛型标识> {
private 泛型标识 变量名;
private 泛型标志 show(泛型标识 变量名) {...}
}
泛型类的定义与使用
- 创建该类的对象即可使用泛型类了,示例如下:
/**
* 定义一个泛型类
* @param <T> 一个泛型标识
* @param <E> 一个泛型标识
*/
class Generic<T,E> {
private T t;
private E e;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
}
public class Test {
public static void main(String[] args) {
Generic<String,Integer> generic = new Generic<>();
generic.setT("张三");
generic.setE(159);
System.out.println(generic.getT()); // 张三
System.out.println(generic.getE()); // 159
}
}
泛型类的子类是泛型类时
- 先定义一个泛型类
class GenericFather<T> {
}
- 定义泛型类型的子类
// 如果需要标识父类的泛型类型,则**子类的泛型标识列表**中必须含有为其父类标识的泛型
class GenericSon<T, E> extends GenericFather<T>{
}
或者也是可以正确编译的
class GenericSon<T, E> extends GenericFather<E>{
}
泛型类的子类不是泛型类时(那么这就可以当成是一个普通的类了)
// 为继承的父类传入泛型的参数类型,不传则默认为Object类型
class GenericSon extends GenericFather<String>{
}
泛型接口(与泛型类使用方法一致)
泛型接口的定义语法
注意:接口中无法定义带泛型的变量,例如: T name;
interface 接口名称 <泛型标识, 泛型标识> {
泛型标志 show(泛型标识 变量名);
}
/**
* 一个泛型接口
* @param <T> 泛型标识
*/
interface GenericInterface<T> {
void show(T t);
}
泛型接口的使用(这里使用了匿名内部类,搞个子类实现一下也可以的)
public class Test {
public static void main(String[] args) {
GenericInterface<String> genericInterface = new GenericInterface<String>() {
@Override
public void show(String s) {
System.out.println(s);
}
};
genericInterface.show("123");
}
}
/**
* 一个泛型接口
* @param <T> 泛型标识
*/
interface GenericInterface<T> {
void show(T t);
}
泛型接口的实现类为泛型时
// 如果需要标识实现类的泛型类型,则**实现类的泛型标识列表**中必须含有为其实现接口标识的泛型
class GenericSon<T> implements GenericInterface<T>{
@Override
public void show(T t) {
System.out.println(t);
}
}
interface GenericInterface<T> {
void show(T t);
}
泛型接口的实现类不为泛型时
// 可以为接口的泛型传入参数类型,不传则默认为Object
class GenericSon implements GenericInterface<String>{
@Override
public void show(String s) {
System.out.println(s);
}
}
interface GenericInterface<T> {
void show(T t);
}
泛型方法
泛型方法的定义语法
修饰符 <泛型标识, 泛型标识, ...> 返回值类型 方法名(形参列表){
方法体...
}
泛型方法的使用
public class Test {
public static void main(String[] args) {
Generic generic = new Generic();
// 1. 调用一下方法,随便传入2个类型的值
Integer number = generic.show("abcde", 123);
System.out.println(number); // 123
}
}
class Generic {
/**
*
* @param k 调用时传入的类型,会直接匹配泛型标识列表中的K
* @param v 调用时传入的类型,会直接匹配泛型标识列表中的V
* @return 返回一个V类型的
*/
<K,V> V show(K k,V v){
System.out.println(k); // abcde
return v;
}
}
静态泛型方法(可以通过类名点的方式调用)
static <T> void staticGenericMethod(T t) {
System.out.println(t);
}
泛型方法的使用细节(泛型、非泛型方法、独立于类、静态方法如何获取泛型能力、优先原则)
-
并不是使用了泛型作为返回值类型或形参的类型就称之为泛型方法(此时只能称为成员方法)
-
返回值类型前加了泛型标识的列表就称之为泛型方法
-
泛型方法能够独立于类而发生变化(泛型类定义的泛型标识跟泛型方法定义的泛型标识是独立分开的,即便标识名一致)
-
如果静态方法需要具备泛型能力,则需要成为一个泛型方法(静态方法无法使用当前类所标识的泛型)
-
如果一个泛型方法定义的泛型标识与类一致,会优先使用泛型方法上定义的泛型类型。
类型通配符、上限、下限
类型通配符的使用(相当于是一个实参,可以将原本要传入的泛型实参代替)
-
类型通配符为一个问号: ?,可以表示任意泛型,相当于所有泛型的父类,作为实参使用
-
先看如下代码,method1(box2);这一行代码编译错误无法使用。
public class Test {
public static void main(String[] args) {
Box<Number> box1 = new Box<>();
Box<Double> box2 = new Box<>();
// 可以通过传入box1来调用method1方法
method1(box1);
// 无法通过传入box2来调用method1方法
method1(box2);
}
public static void method1(Box<Number> box){
box.get();
}
}
class Box<T> {
void get() {
System.out.println("哈哈");
}
}
- 因此可以将mehtod1代码改成如下
public static void method1(Box<?> box){
box.get();
}
指定所传入泛型类型的范围下限<? extends 类型>
/**
* 只能传入存储类型为Cat或者Cat的子类的ArrayList
* @param list
*/
public static void get(ArrayList<? extends Cat> list) {
}
指定所传入泛型类型的范围上限<? super 类型>
/**
* 只能传入存储类型为Cat或者Cat的父类的ArrayList
* @param list
*/
public static void get(ArrayList<? super Cat> list) {
}
类型擦除(泛型能够很好的兼容JDK1.5之后版本的原因)
无限制的代码擦除(泛型类、泛型方法):(底层会帮助进行强制类型转换)
泛型类型都会被转换为Objcet类
有限制的代码擦除(泛型类、泛型方法),也就是加了下限时
泛型类型都会被转换为下限的类型,例如<? extends Number>,那么泛型都会转换为Number类型。
桥接方法
Test测试代码
public class Test {
public static void main(String[] args) {
Son<String> son = new Son<>();
son.show("张三");
}
}
编译前的实现类与接口
class Son<T> implements Father<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
interface Father<T> {
void show(T t);
}
编译后的实现类与接口(实现类增加方法)
class Son implements Father {
// 转换为Object类型,达到与父类一致,主要是为了重写父类方法防止报错。
@Override
public void show(Object t) {
System.out.println(t);
}
// 会添加一个方法,类型为String类型
public void show(String t) {
System.out.println(t);
}
}
interface Father {
void show(Object t);
}
创建一个泛型数组(不建议使用)
public class Test {
public static void main(String[] args) {
Generic<String> generic = new Generic<>();
String[] strings = generic.get(String.class, 5);
strings[0] = "张三";
System.out.println(strings[0]);
}
}
class Generic<T> {
private T[] arr;
public T[] get(Class<T> clazz, int length) {
arr = (T[]) Array.newInstance(clazz, length);
return arr;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下