学习java开始接触到泛型是在容器的时候,如没有使用泛型
List list = new ArrayList(); list.add(1); list.add("1"); list.forEach(x-> System.out.println(x));//编译器不会报错,但是在输出list的时候要注意类型检查。
使用泛型
List<String> list = new ArrayList<String>(); list.add(1); list.add("1");//编译器会报参数类型不匹配的错误。
这里使用泛型的最大好处就是检查了容器安全,将运行期可能出现的类型转换异常ClassCastException转移到编译期。并且省去了类型的强制转换。
- 什么是泛型
1.泛型的本质是参数化类型,将数据类型(该数据类型只能是引用类型,不包括基础的数据类型)作为参数传递(类,方法,接口)
gpublic class Generics<T> { private T obj; public Generics(T obj){ this.obj = obj; } } Generics<Integer> generics = new Generics(11);
这里将Integer作为参数传递给了Generics的成员变量obj。
2.泛型作用只在编译期,在编译过程中,正确校验泛型结果后会将泛型信息擦除,因此泛型信息不会进入运行阶段。
泛型的擦除保证了有泛型和没有泛型产生的代码(class文件)是一致的。
List<String> list1 = new ArrayList<>(); List<Integer> list2 = new ArrayList<>(); System.out.println(list1.getClass().equals(list2.getClass())); //true
说明list1,list2是同一种数据类型(List)
void m1(List<String> strList){}; void m1(List<Intger> strlist11){}
编译期会报错:‘m1(List<String>)‘ clashes with ‘m1(List<Intger>)‘; both methods have same erasure 擦除后参数都是List类型,因此不构成方法重载的条件。
3.泛型具有不可变性:不论 A B 有什么关系,A 的容器和 B 的容器都没有父子关系,称之为不可变
List<String> str = null; m2(str); static void m2(List<Object> obj){}
编译期会报错:‘m2(java.util.List<java.lang.Object>)‘ in ‘com.generics.Generics‘ cannot be applied to ‘(java.util.List<java.lang.String>)‘
即List<String> 和 List<Object> 没有任何关系,哪怕String是Object的子类。
协变性:如果 A 是 B 的父类,并且 A 的容器(比如 List< A>) 也是 B 的容器(List< B>)的父类,则称之为协变的(父子关系保持一致)
如果想要让某个泛型类具有协变性,就需要用到边界。这就是后面介绍的extends,super
4.List<object> 和 原始类型List的区别
4.1 编译器不会对原始类型进行类型检查,会对于带泛型的进行类型检查
4.2 你可以把任何带参数的类型传递给原始类型 List,但却不能把 List< String> 传递给接受 List< Object> 的方法,因为泛型的不可变性,会产生编译错误。
- 泛型的使用方式
泛型的使用方式有三种:泛型类,泛型方法,泛型接口
泛型类的基础语法:class 类名 <泛型标识>
{
public 泛型标识 var //成员变量
public 泛型标识 getSomething(泛型标识 var){ //泛型方法,方法的头部使用<T>声明
................................
}
}
public class Generics<T> { private T obj; public Generics(T obj){ this.obj = obj; } public <T> void getObj(T obj){ } public <T> T getObj1(T obj){ return obj; } }
泛型接口的语法和泛型类类似
public interface IGenerics<T> { public <T> T next(); public <T> void next1(); } public class GenericsImpl<String> implements IGenerics { @Override public String next() { return null; }
@Override public void next1() { }
}
- 泛型通配符(?)
1.使用通配符可以引用包含多种类型的泛型
List<?> nlist1 = new ArrayList<String>(); nlist1 = new ArrayList<Integer>(); nlist1 = new ArrayList<Double>(); // nlist1.add("www"); // nlist1.add(10); nlist1.add(null); System.out.println(nlist1.get(0));
注意?表示未知的类型,因此只能获取容器里面的元素,不能添加元素(null例外),因为编译器不能保证添加的元素类型和容器的类型是否兼容。
2. ? + extends obj: 表示该泛型的类型虽然是未知的,但是必须是obj或者obj的子类
List<? extends Number> nlist2 = new ArrayList<Integer>(); // nlist2 = new ArrayList<String>(); nlist2 = new ArrayList<Float>(); nlist2 = new ArrayList<Double>(); nlist2.add(null); System.out.println(nlist2.get(0));
public static void add1(List<? extends Number> list){ Number number = list.get(0); Object obj = list.get(0); System.out.println(number+" "+obj); }
说明: 方法add1中,list里面的元素类型一定可以向上转型成Number类型,因此我们可以用Number类型来接收list (Object当然可以,这里我们不考虑),但是我们不能往list里面添加元素(null除外)
3.? + super obj : 表示该泛型的类型虽然是未知的,但是必须是obj或者obj的父类
List<? super Long> nlist3 = new ArrayList<Long>(); nlist3 = new ArrayList<Number>(); // nlist3 = new ArrayList<Integer>(); nlist3.add(null); System.out.println(nlist3);
public static void add2(List<? super Long> list){ list.add(10L); }
说明:add2方法中,list里面的元素一定可以向下转型成Long类型,因此我们可以往list里面添加Long类型的元素,但是我们不能用Long类型来接收list。
- 泛型和数组
Object[] obj = new Integer[3]; obj[0] = 1.2; obj[1] = "www"; obj[2] = Boolean.FALSE;
上面的代码编译期间没有问题,运行期汇报java.lang.ArrayStoreException: java.lang.Double ,因为obj编译期是Object[]类型,可以接受任何类型的参数;运行期是Integer[]类型,因此报了异常
static <T> T[] toArray(T... args) { return args; } static <T> T[] pickTwo(T a, T b, T c) { switch(ThreadLocalRandom.current().nextInt(3)) { case 0: return toArray(a, b); case 1: return toArray(a, c); case 2: return toArray(b, c); } throw new AssertionError(); // Can‘t get here } public static void main(String[] args) { String[] attributes = pickTwo("Good", "Fast", "Cheap"); }
这是《Effective Java》中看到的例子,编译此代码没有问题,但是运行的时候却会类型转换错误:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
分析:可变参数 T... args 的本质是数组,由于泛型擦除,
pickTwo(T a, T b, T c) == pickTwo(Oject a, Object b, Object c)
toArray(T...) == toArray(Object [])
数组具有协变性,因此在编译期间能够接受String[]。能够通过编译。运行期间的时候 toArray()方法返回的是String[] ,因此报了类型转换错误。
原文地址:https://www.cnblogs.com/chenzhubing/p/11196405.html