泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参列表,普通方法的形参列表中,每个形参的数据类型是确定的,而变量是一个参数。在调用普通方法时需要传入对应形参数据类型的变量(实参),若传入的实参与形参定义的数据类型不匹配,则会报错。
那参数化类型是什么?以方法的定义为例,在方法定义时,将方法签名中的形参的数据类型也设置为参数(也可称之为类型参数),在调用该方法时再从外部传入一个具体的数据类型和变量。
泛型的本质是为了将类型参数化, 也就是说在泛型使用过程中,数据类型被设置为一个参数,在使用时再从外部传入一个数据类型;而一旦传入了具体的数据类型后,传入变量(实参)的数据类型如果不匹配,编译器就会直接报错。这种参数化类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型允许在编译时指定数据类型。它可以提高代码的可读性和安全性,并减少强制类型转换的需要。例如,List 表示一个字符串类型的列表。
泛型是java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个类型。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
举个简单的例子,咱们创建一个值是Long型的map,那么Map
泛型就是将类型变成参数传入,使得可以使用的类型多样化,从而实现解耦。Java 泛型是在Java1.5 以后出现的,为保持对以前版本的兼容,使用了擦除的方法实现泛型。擦除是指在一定程度无视类型参数 T,直接从 T 所在的类开始向上 T 的父类去擦除,如调用泛型方法,传入类型参数 T 进入方法内部,若没在声明时做类似 public T methodName(T extends Fathert){},Java 就进行了向上类型的擦除,直接把参数 t 当做 Object 类来处理,而不是传进去的 T。
即在有泛型的任何类和方法内部,它都无法知道自己的泛型参数,擦除和转型都是在边界上发生,即传进去的参在进入类或方法时被擦除掉,但传出来的时候又被转成了我们设置的 T。
在泛型类或方法内,任何涉及到具体类型(即擦除后的类型的子类)操作都不能进行,如new T(),或者 T.play()(play 为某子类的方法而不是擦除后的类的方法)
泛型的定义
泛型可以定义在类、接口、方法中,编译器通过识别尖括号和尖括号内的字符来解析泛型。
其中约定俗成的字符包括:
E:Element,用于集合中的元素,如List
K、V:Key和Value,如Map
T:the Type of object
1、尖括号里的每个元素都代表一种未知类型。
2、尖括号的位置必须在类名之后或方法返回值之前。
3、泛型在定义处只具备执行Object方法的能力。
4、泛型本质是一种语法糖,成功编译后会被类型擦除,可看成是一种编写代码的语法检查。
泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。比如我们要写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,我们就可以使用 Java 泛型。java泛型,其实就是数据类型参数化。使用的时候就是把数据类型当参数。根据这个参数进行指定的操作。java中泛型是在jdk1.5之后出了。所以为了兼容低版本,所以这个泛型是假的,意思是底层实现的时候,泛型会被擦除,替换成object或者做了限制的数据类型。
提高复用性。适用于多种数据类型,执行相同的代码
提前编译错误报错。在编码的时候可以指定数据类型,方便后面使用,不需要强制类型转换,提前把错误报出来。
搭建框架做限制使用。如T extends XXX;这种形式
泛型的使用:
泛型类、泛型接口、泛型方法。
//泛型类
public class Bean<T>{}
//泛型接口
interface IBean<T>{}
//泛型方法
public <T> void methodText(T t){}
泛型上界和下界(extends super)
//泛型类
public class Bean<T extends ArrayList>{}
//泛型接口
interface IBean<T extends ArrayList>{}
//泛型方法
public <T extends ArrayList> void methodText(T t){}
//泛型类
public class Bean<? super ArrayList>{}
//泛型接口
interface IBean<? super ArrayList>{}
//通配符下限
private static void grenericitySuper(List<? super Integer> t){
}
这个界限不只是界限,还有一些副作用。使用extends的不能set对象,使用super的不能get对象。
一般在写框架的时候用到的比较多
java 泛型是java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。
可以在集合框架(Collection framework)中看到泛型的动机。例如,Map 类允许您向一个 Map 添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String)的对象。
因为 Map.get() 被定义为返回 Object,所以一般必须将 Map.get() 的结果强制类型转换为期望的类型,如下面的代码所示:
Map m = new HashMap();
m.put("key", "blarg");
String s = (String) m.get("key");
要让程序通过编译,必须将 get() 的结果强制类型转换为 String,并且希望结果真的是一个 String。但是有可能某人已经在该映射中保存了不是 String 的东西,这样的话,上面的代码将会抛出 ClassCastException。
理想情况下,您可能会得出这样一个观点,即 m 是一个 Map,它将 String 键映射到 String 值。这可以让您消除代码中的强制类型转换,同时获得一个附加的类型检查层,该检查层可以防止有人将错误类型的键或值保存在集合中。这就是泛型所做的工作
Java 语言中引入泛型是一个较大的功能增强。不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了。
这带来了很多好处:
1,类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
2,消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
3,潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。
java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
更好的代码复用性,比如实现泛型算法
在框架设计时候,BaseDao、BaseService、BaseDaoImpl、BaseServiceImpl;通过继承,实现抽象了所有公共方法,避免了每次都要写相同的代码。
泛型在使用中还有一些规则和限制:
1、泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
2、同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
3、泛型的类型参数可以有多个。
4、泛型的参数类型可以使用extends语句,例如。习惯上成为“有界类型”。
5、泛型的参数类型还可以是通配符类型。例如Class> classType = Class.forName(Java.lang.String);
泛 型还有接口、方法等等,内容很多,需要花费一番功夫才能理解掌握并熟练应用。在此给出我曾经了解泛型时候写出的两个例子(根据看的印象写的),实现同样的 功能,一个使用了泛型,一个没有使用,通过对比,可以很快学会泛型的应用,学会这个基本上学会了泛型70%的内容。
//未使用泛型
Map map1 = new HashMap();
map1.put("key", "values");
String str1 = (String) map1.get("key");
//使用泛型
Map<String,String> map2 = new HashMap();
map2.put("key", "values");
String str2 = map2.get("key");
1、类型安全。放置的是什么类型,取出来的就是什么类型,不用担心会抛出ClassCastException异常。
2、提升可读性。从编码阶段就显示的知道泛型集合、泛型方法等处理的对象类型是什么。
3、代码重用。泛型合并了同类型的处理代码,使代码重用度变高。
public class FruitKnife {
public static Object cut(Object fruit) {
System.out.println("cut" + fruit);
return fruit;
}
public static void main(String[] args) {
// 切苹果
Apple apple = new Apple();
apple = (Apple) FruitKnife.cut(apple);
// 切橙子
Orange orange = new Orange();
orange = (Orange) FruitKnife.cut(orange);
// 切梨
Pear pear = new Pear();
pear = (Pear) FruitKnife.cut(pear);
}
}
class Apple {}
class Orange {}
class Pear {}
cut方法为了能够适应所有类型的水果,定义成了Object类型,所以再拿到结果之后需要强制向下转型才能得到我们真正想要的结果,这显然存在类型转换异常的风险。
public class FruitKnife {
public static <T> T cut(T fruit) {
System.out.println("cut" + fruit);
return fruit;
}
public static void main(String[] args) {
// 切苹果
Apple apple = new Apple();
apple = FruitKnife.cut(apple);
// 切橙子
Orange orange = new Orange();
orange = FruitKnife.cut(orange);
// 切梨
Pear pear = new Pear();
pear = FruitKnife.cut(pear);
}
}
class Apple {
}
class Orange {
}
class Pear {
}
可以看到,通过使用泛型cut方法即兼容所有不同类型的水果,又避免了类型转换的风险。
1:类型安全,例如上边的map例子,能够控制咱们需要的类型
2:消除强制类型转换,这个意思是指,继续用map做例子:map不指定泛型具体类型,键值对中的值默认是Object类型的,无论你取到什么类型的值,都要做类型转化
总结好处就是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率和性能。
List<User> userList =new ArrayList<>();
//TODO,要判断list是否为null
for(User user:userList){
String userName =user.getUserName();
Integer age =user.getAge();
//…………
}
注意事项:
1、泛型的类型参数只能是对象类型,不能是基本数据类型。
2、同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
3、泛型的类型参数可以有多个。
5、泛型的参数类型还可以是通配符类型。例如Class> classType = Class.forName(Java.lang.String);
泛型还有接口、方法等等
在Java中,泛型为Java对象在编译期增加一道类型检查,促使开发人员在使用泛型时能够安全地放置和使用数据。
Java 泛型(Generics)是一种在编译时提供类型安全的编程机制。泛型允许你在类、接口和方法中使用类型参数,从而使得代码更加灵活、可复用和易于维护。
泛型的主要优点是提高代码的可读性和可维护性,减少类型转换的错误,同时提高代码的性能。
泛型类是在类声明中使用类型参数的类。例如,Java 集合框架中的 List 就是一个泛型类,其中 T 是一个类型参数。
public class MyGenericClass<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
在上面的示例中,MyGenericClass 是一个泛型类,它具有一个类型参数 T。你可以通过指定类型参数来实例化泛型类,例如:
MyGenericClass<String> stringClass = new MyGenericClass<>();
MyGenericClass<Integer> integerClass = new MyGenericClass<>();
泛型接口是在接口声明中使用类型参数的接口。例如,Java 集合框架中的 Comparable 接口就是一个泛型接口,其中 T 是一个类型参数。
public interface MyGenericInterface<T> {
void doSomething(T value);
}
在上面的示例中,MyGenericInterface 是一个泛型接口,它具有一个类型参数 T。你可以通过指定类型参数来实现泛型接口,例如:
public class MyClass implements MyGenericInterface<String> {
@Override
public void doSomething(String value) {
System.out.println("Doing something with: " + value);
}
}
泛型方法是在方法声明中使用类型参数的方法。例如:
public class MyGenericUtils {
public static <T> void printList(List<T> list) {
for (T item : list) {
System.out.println(item);
}
}
}
在上面的示例中,printList 是一个泛型方法,它具有一个类型参数 T。你可以通过指定类型参数来调用泛型方法,例如:
List<String> stringList = Arrays.asList("A", "B", "C");
MyGenericUtils<String>printList(stringList);
在 Java 中,泛型是一种参数化类型的机制,它使我们能够定义可以在编译时指定数据类型的类、接口和方法。
在泛型中,常见的使用方式是通过使用类型参数来代表具体的数据类型。通常情况下,我们使用以下约定的类型参数名称:
● E:表示元素类型,在集合类中经常使用,比如 List 表示存储元素类型为 E 的列表。
● K:表示键的类型,在与键值对相关的数据结构中经常使用,比如 HashMap
● V:表示值的类型,在与键值对相关的数据结构中经常使用,比如 HashMap
● T:表示任意类型,在需要使用泛型但不关心具体类型时使用,比如 class MyClass 表示一个类,其类型参数为 T。
这些类型参数只是一种约定,并没有硬性规定,你可以使用其他的字母或单词作为类型参数名称。但为了保持代码的可读性和一致性,建议按照约定来使用类型参数。
下面是一个简单的示例,演示了如何在使用泛型时使用这些常见的类型参数名称:
class MyGenericClass<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class GenericExample {
public static void main(String[] args) {
MyGenericClass<Integer> integerClass = new MyGenericClass<>();
integerClass.setValue(42);
System.out.println(integerClass.getValue()); // 输出: 42
MyGenericClass<String> stringClass = new MyGenericClass<>();
stringClass.setValue("Hello, World!");
System.out.println(stringClass.getValue()); // 输出: Hello, World!
}
}
在上述示例中,我们定义了一个名为 MyGenericClass 的泛型类,其类型参数为 T。该类有一个值为类型参数 T 的成员变量 value,以及用于设置和获取该值的方法。
然后,在 main 方法中,我们实例化了两个 MyGenericClass 对象,一个使用 Integer 类型作为类型参数,另一个使用 String 类型作为类型参数。通过调用 setValue 方法设置值,并通过调用 getValue 方法获取值,我们可以看到分别输出了整数和字符串的值。
总结一下,泛型的使用使我们能够编写更加通用、类型安全的代码,而常见的泛型类型参数名称包括 E、K、V 和 T。
类型参数名称E K V T ?
上面的 T 仅仅类似一个形参的作用,名字实际上是可以任意起的,但是我们写代码总该是要讲究可读性的。常见的参数通常有 :
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(表示Java 类,包括基本的类和我们自定义的类)
K - Key(表示键,比如Map中的key)
V - Value(表示值)
? - (表示不确定的java类型)
在 Java 的泛型中,? 是通配符类型参数,称为无界通配符或问号通配符。
使用 ? 作为类型参数表示我们对具体的类型不关心,或者无法确定类型。这可以使泛型类、接口或方法更加灵活,允许处理不特定类型的数据。
通配符类型参数 ? 在以下三种情况下常见:
import java.util.ArrayList;
import java.util.List;
public class GenericWildcardExample {
public static void main(String[] args) {
List<?> list = new ArrayList<>();
list.add(null); // 只能添加 null
List<? extends Number> numbers = new ArrayList<>();
// numbers.add(10); // 错误,无法添加元素
Number number = numbers.get(0);
List<? super Integer> integers = new ArrayList<>();
integers.add(10); // 可以添加 Integer 或其子类的元素
Object obj = integers.get(0);
}
}
在上述示例中,我们创建了三个包含通配符类型参数的泛型列表。第一个列表 List> 是未限制的通配符类型,可以接受任何类型(包括 null)作为元素。
第二个列表 List extends Number> 使用通配符 ? 限制了元素类型的上边界为 Number 或其子类型。这意味着我们只能获取(使用 get 方法)元素,而不能添加元素。
第三个列表 List super Integer> 使用通配符 ? 限制了元素类型的下边界为 Integer 或其父类型。这意味着我们可以添加 Integer 类型或其子类作为元素,而获取元素时会得到 Object 类型。
总结一下,? 是 Java 泛型中的通配符,用于表示无界通配符类型参数。它可以用于限制类型的上边界、下边界或表示未限制的通配符类型。
List list = new ArrayList();
list.add(10);
list.add("Hello");
Integer number = (Integer) list.get(0); // 运行时抛出ClassCastException
List<Object> list = new ArrayList<>();
list.add(10);
list.add("Hello");
Integer number = (Integer) list.get(0); // 编译时类型检查通过,安全获取对象
1)集合
2)泛型类 返回参数封装类,如下代码。
3)泛型接口
4)泛型方法
泛型方法()
你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数
类型,编译器适当地处理每一个方法调用。
// 泛型方法 printArray
public static < E > void printArray( E[] inputArray )
{
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
}
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。和泛型方法一
样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,
也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,
这些类被称为参数化的类或参数化的类型。
public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
类型通配符一般是使用 ? 代 替 具 体 的 类 型 参 数 。 例 如 List> 在逻辑上是
List,List 等所有 List<具体类型实参>的父类。
Java 中的泛型基本上都是在编译器这个层次来实现的。在生成的 Java 字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的 List和 List等类型,在编译之后都会变成 List。JVM 看到的只是 List,而由泛型附加的类型信息对 JVM 来说是不可见的。类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是 Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。