Java泛型设计原则:只要在编译时期没有出现警告,那么运行时期就不会出现ClassCastException异常javascript
泛型:把类型明确的工做推迟到建立对象或调用方法的时候才去明确的特殊的类型java
参数化类型:安全
相关术语:app
ArrayList<E>
中的E称为类型参数变量 ArrayList<Integer>
中的Integer称为实际类型参数 ArrayList<E>
泛型类型ArrayList<Integer>
称为参数化的类型ParameterizedType 早期Java是使用Object来表明任意类型的,可是向下转型有强转的问题,这样程序就不太安全ide
首先,咱们来试想一下:没有泛型,集合会怎么样学习
有了泛型之后:测试
在建立集合的时候,咱们明确了集合的类型了,因此咱们可使用加强for来遍历集合!this
//建立集合对象 ArrayList<String> list = new ArrayList<>(); list.add("hello"); list.add("world"); list.add("java"); //遍历,因为明确了类型.咱们能够加强for for (String s : list) { System.out.println(s); }基础
泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来….这样的话,用户明确了什么类型,该类就表明着什么类型…用户在使用的时候就不用担忧强转的问题,运行时转换异常的问题了。spa
/* 1:把泛型定义在类上 2:类型变量定义在类上,方法中也可使用 */ public class ObjectTool<T> { private T obj; public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } }
用户想要使用哪一种类型,就在建立的时候指定类型。使用的时候,该类就会自动转换成用户想要使用的类型了。设计
public static void main(String[] args) { //建立对象并指定元素类型 ObjectTool<String> tool = new ObjectTool<>(); tool.setObj(new String("钟福成")); String s = tool.getObj(); System.out.println(s); //建立对象并指定元素类型 ObjectTool<Integer> objectTool = new ObjectTool<>(); /** * 若是我在这个对象里传入的是String类型的,它在编译时期就经过不了了. */ objectTool.setObj(10); int i = objectTool.getObj(); System.out.println(i); }
前面已经介绍了泛型类了,在类上定义的泛型,在方法中也可使用…..
如今呢,咱们可能就仅仅在某一个方法上须要使用泛型….外界仅仅是关心该方法,不关心类其余的属性…这样的话,咱们在整个类上定义泛型,未免就有些大题小做了。
//定义泛型方法.. public <T> void show(T t) { System.out.println(t); }
用户传递进来的是什么类型,返回值就是什么类型了
public static void main(String[] args) { //建立对象 ObjectTool tool = new ObjectTool(); //调用方法,传入的参数是什么类型,返回值就是什么类型 tool.show("hello"); tool.show(12); tool.show(12.5); }
前面咱们已经定义了泛型类,泛型类是拥有泛型这个特性的类,它本质上仍是一个Java类,那么它就能够被继承
那它是怎么被继承的呢??这里分两种状况
/* 把泛型定义在接口上 */ public interface Inter<T> { public abstract void show(T t); }
/** * 子类明确泛型类的类型参数变量: */ public class InterImpl implements Inter<String> { @Override public void show(String s) { System.out.println(s); } }
/** * 子类不明确泛型类的类型参数变量: * 实现类也要定义出<T>类型的 * */ public class InterImpl<T> implements Inter<T> { @Override public void show(T t) { System.out.println(t); } }
public static void main(String[] args) { //测试第一种状况 //Inter<String> i = new InterImpl(); //i.show("hello"); //第二种状况测试 Inter<String> ii = new InterImpl<>(); ii.show("100"); }
值得注意的是:
为何须要类型通配符????咱们来看一个需求…….
如今有个需求:方法接收一个集合参数,遍历集合并把集合元素打印出来,怎么办?
public void test(List list){ for(int i=0;i<list.size();i++){ System.out.println(list.get(i)); } }
上面的代码是正确的,只不过在编译的时候会出现警告,说没有肯定集合元素的类型….这样是不优雅的…
public void test(List<Object> list){ for(int i=0;i<list.size();i++){ System.out.println(list.get(i)); } }
这样作语法是没毛病的,可是这里十分值得注意的是:该test()方法只能遍历装载着Object的集合!!!
强调:泛型中的<Object>
并非像之前那样有继承关系的,也就是说List<Object>
和List<String>
是毫无关系的!!!!
那如今咋办???咱们是不清楚List集合装载的元素是什么类型的,List<Objcet>
这样是行不通的……..因而Java泛型提供了类型通配符 ?
因此代码应该改为这样:
public void test(List<?> list){ for(int i=0;i<list.size();i++){ System.out.println(list.get(i)); } }
?号通配符表示能够匹配任意类型,任意的Java类均可以匹配…..
如今很是值得注意的是,当咱们使用?号通配符的时候:就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。
记住,只能调用与对象无关的方法,不能调用对象与类型有关的方法。由于直到外界使用才知道具体的类型是什么。也就是说,在上面的List集合,我是不能使用add()方法的。由于add()方法是把对象丢进集合中,而如今我是不知道对象的类型是什么。
首先,咱们来看一下设定通配符上限用在哪里….
如今,我想接收一个List集合,它只能操做数字类型的元素【Float、Integer、Double、Byte等数字类型都行】,怎么作???
咱们学习了通配符,可是若是直接使用通配符的话,该集合就不是只能操做数字了。所以咱们须要用到设定通配符上限
List<? extends Number>
上面的代码表示的是:List集合装载的元素只能是Number的子类或自身
public static void main(String[] args) { //List集合装载的是Integer,能够调用该方法 List<Integer> integer = new ArrayList<>(); test(integer); //List集合装载的是String,在编译时期就报错了 List<String> strings = new ArrayList<>(); test(strings); } public static void test(List<? extends Number> list) { }
既然上面咱们已经说了如何设定通配符的上限,那么设定通配符的下限也不是陌生的事了。直接来看语法吧
//传递进来的只能是Type或Type的父类 <? super Type>
设定通配符的下限这并很多见,在TreeSet集合中就有….咱们来看一下
public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); }
那它有什么用呢??咱们来想一下,当咱们想要建立一个TreeSet<String>
类型的变量的时候,并传入一个能够比较String大小的Comparator。
那么这个Comparator的选择就有不少了,它能够是Comparator<String>
,还能够是类型参数是String的父类,好比说Comparator<Objcet>
….
这样作,就很是灵活了。也就是说,只要它可以比较字符串大小,就好了
值得注意的是:不管是设定通配符上限仍是下限,都是不能操做与对象有关的方法,只要涉及到了通配符,它的类型都是不肯定的!
大多时候,咱们均可以使用泛型方法来代替通配符的…..
//使用通配符 public static void test(List<?> list) { } //使用泛型方法 public <T> void test2(List<T> t) { }
上面这两个方法都是能够的…..那么如今问题来了,咱们使用通配符仍是使用泛型方法呢??
原则:
泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将再也不带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。
JDK5提出了泛型这个概念,可是JDK5之前是没有泛型的。也就是泛型是须要兼容JDK5如下的集合的。
当把带有泛型特性的集合赋值给老版本的集合时候,会把泛型给擦除了。
值得注意的是:它保留的就类型参数的上限。
List<String> list = new ArrayList<>(); //类型被擦除了,保留的是类型的上限,String的上限就是Object List list1 = list;
若是我把没有类型参数的集合赋值给带有类型参数的集合赋值,这又会怎么样??
List list = new ArrayList(); List<String> list2 = list;
它也不会报错,仅仅是提示“未经检查的转换”