泛型
传统方法的问题
- 不能对加入集合的元素进行数据类型的约束
- 遍历的时候需要进行类型转换,如果集合的数据量太大,会对效率有影响
@Data
class Dog{
private String name;
private int age;
}
@Date
class Cat{
private String name;
private int age;
}
import java.util.ArrayList;
import java.util.List;
public class Introduce {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add(new Dog("旺财",20));
arrayList.add(new Dog("小航",10));
arrayList.add(new Dog("小驴",1));
arrayList.add(new Cat("小驴",1));
for (Object o : arrayList) {
Dog dog = (Dog) o;
System.out.println(dog.getName()+"-"+dog.getAge());
}
}
}
泛型的好处
- 编译时检查添加元素的类型 提高了安全性
- 减少了类型转换的次数
- 如果没有泛型,要实现不同类型的加法,每种类型都需要重载一个add方法;通过泛型,我们可以复用为一个方法:java
private static <T extends Number> double add(T a, T b) { System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue())); return a.doubleValue() + b.doubleValue(); }
public class Generic{
public static void main(String[] args) {
ArrayList<Dog> arrayList = new ArrayList<Dog>();
arrayList.add(new Dog("旺财",20));
arrayList.add(new Dog("小航",10));
arrayList.add(new Dog("小驴",1));
// arrayList.add(new Cat("小驴",1)); 不能加入
for (Dog dog : arrayList) {
System.out.println(dog.getName()+"-"+dog.getAge());
}
}
}
Generic
- 泛型 又称 参数化类型 是jdk5以后出现的特性,解决了数据类型的安全问题
- 在类声明或者实例化的时候只需要指定具体的类型即可
- java泛型可以保证在编译时没有发出警告,在运行时就不会有ClassCastException
- 可以在类声明时通过一个标识来表示 类中某个属性的类型
- 方法的返回值类型
- 参数类型
class Person<E>{
E s; //在定义person对象时指定,在编译期间就确定了e的类型
public Person(E s) {
this.s= s;
}
public E method(){
return s;
}
public void show(){
System.out.println(s.getClass());
}
}
public class Introduce{
public static void main(String[] args) {
Person<String> person1 = new Person<String>("hh");
person1.show();
Person<Integer> person2 = new Person<Integer>(100);
person2.show();
}
}
run:
class java.lang.String
class java.lang.Integer
泛型的声明
TKV 表示类型 可以有多个
TKV只能是引用类型
指定泛型具体类型后,可以传入其子类型
class Animal{
}
class Dog extends Animal{
}
public class Introduce{
public static void main(String[] args) {
Person<Animal> person1 = new Person<>(new Animal());
Person<Animal> person2 = new Person<>(new Dog());
//后面<>可以不写,编译器会进行类型推断
}
}
ArrayList arrayList = new ArrayList()
//ArrayList<Object> arrayList = new ArrayList()
//默认泛型是Object
泛型的实例化
在类名后面指定参数的类型
自定义泛型类 class Person
- 普通成员可以使用泛型(
- 某个属性的类型
- 方法的返回值类型
- 参数类型)
- 使用泛型的数组不能初始化 数组在new不能确定T的类型,就无法在内存中开辟空间
T array = new T[8];//错误
- 静态方法中不能使用泛型 静态是和类相关的,静态方法在类加载(在类new 之前,对象还没有创建)的时候就会被调用,不能确定 T的类型
static R r ; //错
静态成员也不能使用泛型泛型接口的类型
继承和实现接口时确定没有指定类型,默认是object
自定义泛型接口
public interface Generator<T> {
public T method();
}
自定义泛型方法
修饰符 <T,R> 返回类型 方法名 (参数)
- 可以在普通类 也可以在泛型类
- 方法被调用时 类型会确定
//自定义泛型类
class car<T,E>{
// 自定义泛型方法 U M 提供给方法使用的
public <U,M> void run(U u,M m){
}
}
public static void main[String[] args]{
Car car = new Car();
car.fly("宝马",20);//在调用的时候确定类型 U M
//传入参数编译器会自动确定什么类型
}
public static < E > void printArray( E[] inputArray )
{
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
public static < E > void printArray( E[] inputArray )
{
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
由于静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数,静态的方法的加载就已经完成了,所以静态泛型方法是没有办法使用类上声明的泛型的。只能使用自己声明的 <E>
泛型的继承性和通配符
泛型没有继承性
<?>
支持任意泛型
<?
extends A> 支持A及A的子类 规定了泛型的上限
<?
super A> 支持A已及A的父类 规定了泛型的下限
语法糖
Java 虚拟机并不支持语法糖。语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖
Map<String, String> map = new HashMap<String, String>();
map.put("name", "hollis");
map.put("wechat", "Hollis");
map.put("blog", "www.hollischuang.com");
解语法糖
Map map = new HashMap();
map.put("name", "hollis");
map.put("wechat", "Hollis");
map.put("blog", "www.hollischuang.com");
泛型擦除
在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型)
在运行时期间,JVM是不知道泛型信息的,所有的泛型类和方法都会被当作它们的原生类型(Raw Type)来处理
package com.jasper.generic;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class Demo1 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
Method add = list.getClass().getDeclaredMethod("add", Object.class);
add.invoke(list,"jasper");
System.out.println(list);
}
}
[1, jasper]
- 与使用Object相比,提供了一种扩展能力,参数可以由外部传递进来
- 安全检测机制,确定类型后传值不匹配会报错
- 增加了代码的可读性
一、当泛型遇到重载
public class GenericTypes {
public static void method(List<String> list) {
System.out.println("invoke method(List<String> list)");
}
public static void method(List<Integer> list) {
System.out.println("invoke method(List<Integer> list)");
}
}
上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是List<String>
另一个是List<Integer>
,但是,这段代码是编译通不过的。因为我们前面讲过,参数List<Integer>
和List<String>
编译之后都被擦除了,变成了一样的原生类型 List,擦除动作导致这两个方法的特征签名变得一模一样。
如何理解基本类型不能作为泛型类型?
比如,我们没有
ArrayList<int>
,只有ArrayList<Integer>
, 为何?
因为当类型擦除后,ArrayList的原始类型变为Object,但是Object类型不能存储int值,只能引用Integer的值。
另外需要注意,我们能够使用list.add(1)
是因为Java基础类型的自动装箱拆箱操作。
泛型擦除和多态的冲突
虚拟机巧妙的使用了桥方法,来解决了类型擦除和多态的冲突。
todo