数组可以在创建的时候就指定存放的数据类型,这样放入不同类型的时候就会发生编译错误。
而集合却可以存储多种不同类型,这样的话如果是遍历的时候在集合中装了好多不同的数据类型的时候,十分容易发生类型转换错误
集合也模仿数组的做法,在创建对象的时候明确数据的数据类型
这种技术被称为:泛型 泛型是一种把类型明确的工作推迟到创建对象或者调用方法的时候才明确的特殊的类型。参数化类型,把类型当做参数一样的传递。
格式:<数据类型> 此处的数据类型只能是引用数据类型 好处:
把运行期的问题提前到了编译期间
避免了强制类型转换
优化了程序设计,解决了黄色警告线 泛型的由来:
程序早期使用的是Object代替任意引用数据类型,但是在实际用用中会出现类型转换问题,索引JDK5引入了泛型来解决这个安全性问题
package com.Generic; import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
/*
* 定义了一个List集合,先后加入了两个字符串类型的值和一个Integer类型的值,这时候,此时list的默认类型为Object
* 在之后的循环中,由于忘记之前在list中也加入了Integer,引发类型转换异常
* 为了让集合能够记住集合内元素各种类型,且能达到只要编译期不出现问题,运行期间就不会出现类型转换异常,引入了
* 泛型
*
* 什么是泛型?
* 泛型,即“参数化类型”。一提到参数,最熟悉的就是已定义方法时有形参,然后调用此方法传递实参。那么参数化类型怎
* 么理解呢?顾名思义,就是将原来的类型由具体的类型参数化,类似于方法中的变量参数,此时类型也可以定义成参数形
* 式(可以称之为类型形参),然后在使用时传入具体的类型(类型实参)
*
*
* 采用泛型写法后,在test02中想加入Integer类型会出现编译错误,通过List<String>,直接限定了list集合中只能含有
* String类型的元素,从而在遍历的时候不需要进行强制类型转换,因为此时,集合能够记住元素的类型,编译期已经可以
* 确定它是String类型了
*
*结合上面的泛型定义,我们知道在List<String>中,String是形式参数,也就是说,相应的List接口中肯定含有类型的形参。
*而且get()方法的返回结果也是直接此形参类型(也就是对应的传入的类型实参)。
*
*可以看到,在List接口中采用泛型化定义后,<E>中的E表示类型参数,可以接受具体的类型实参,接口定义中,凡是出现E的
*地方均表示相同的接受外部的类型实参
*/
public class GenericTest {
public static void main(String[] args) {
List<String> list=new ArrayList<String>();
list.add("guo");
list.add("liu");
//list.add(100);这里就无法添加进入Integer类型的元素了 for(int i=0;i<list.size();i++){
String name=list.get(i);//这里不需要强制转换了
System.out.println("name:"+name);
}
} public void test01(){
//引出泛型
List list=new ArrayList();
list.add("guo");
list.add("liu");
list.add(100); for(int i=0;i<list.size();i++){
String name=(String) list.get(i);
System.out.println("name:"+name);
}
}
public void test02(){
//第一次使用泛型
List<String> list=new ArrayList<String>();
list.add("guo");
list.add("liu");
//list.add(100);这里就无法添加进入Integer类型的元素了 for(int i=0;i<list.size();i++){
String name=list.get(i);//这里不需要强制转换了
System.out.println("name:"+name);
}
}
} interface Listyuanma<E> extends Collection<E> { int size(); boolean isEmpty(); boolean contains(Object o); Iterator<E> iterator(); Object[] toArray(); <T> T[] toArray(T[] a); boolean add(E e); boolean remove(Object o); boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean addAll(int index, Collection<? extends E> c); boolean removeAll(Collection<?> c); boolean retainAll(Collection<?> c); void clear(); boolean equals(Object o); int hashCode(); E get(int index); E set(int index, E element); void add(int index, E element); E remove(int index); int indexOf(Object o); int lastIndexOf(Object o); ListIterator<E> listIterator(); ListIterator<E> listIterator(int index); List<E> subList(int fromIndex, int toIndex);
} package com.Generic;
/*
* 从前面了解了泛型的具体运作过程。也知道了接口、类、方法可以使用泛型去定义以及相关的使用。是的,在具体使用时候,可以分为泛型接口,泛型类,泛型方法
*
* 在泛型接口、泛型类和泛型方法的定义过程中,我们常用的如T K V E等形式的参数常常用于表示泛型形参,由于接受外部使用的时候传入的类型实参。那么对于传入的不同类型参数,
* 生成的相应对象实例的类型是不是不一样的呢?
*
* 下面的例子,我们发现,在使用泛型类的时候,虽然传入了不同的泛型实参,但是并没有真正意义上生成不同的类,传入不同泛型参数的泛型类在内存中只有一个,即还是原来的最基
* 本的类型,当然,在java中可以理解成多个不同的泛型类型
*
* 究其原因,在于java中泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确的泛型检查泛型结果后,会将泛型的相关信息擦除。也就是说,成功编译
* 后的class文件中是不包含任何泛型信息的,泛型信息不回进入到运行阶段。
*
* 对比总结成一句话:泛型类型在逻辑上看已堪称多个不同的类型,实际上都是相同的基本类型
*/
public class GenericTest02 {
public static void main(String[] args) {
Box<String> name=new Box<String>("corn");
Box<Integer> age=new Box<Integer>(712);
//System.out.println(name.getData()); System.out.println("name class:"+name.getClass());
System.out.println("age class:"+age.getClass());
System.out.println(name.getClass()==age.getClass());
}
} /*GenericTest02 的class文件反编译后
public class GenericTest02
{ public GenericTest02()
{
} public static void main(String args[])
{
Box name = new Box("corn");
Box age = new Box(Integer.valueOf(712));
System.out.println((new StringBuilder("name class:")).append(name.getClass()).toString());
System.out.println((new StringBuilder("age class:")).append(age.getClass()).toString());
System.out.println(name.getClass() == age.getClass());
}
} */ class Box<T>{
private T data; public Box(){ } public Box(T data){
this.data=data;
} public T getData(){
return data;
}
} package com.Generic;
/*
* 通过前面的结论我们知道,Box<Number>和Box<Intefer>实际上都是Box类型,现在需要探讨一个问题,那么在逻辑上
* ,类似于Box<Number>和Box<Integer>是否可以看成是具有父子关系的泛型呢?
*
* 我们发现,在age调用getData(Box<Number> data)会报错:The method getData(Box<Number>) in the type GenericTest03 is not applicable for the arguments (Box<Integer>)
* 显然,我们知道,Box<Number>在逻辑上不能视为Box<Integer>的父类,原因何在呢?
*
* 在这个例子中出现错误,我们可以使用反证法来说明。
*
* 假如Box<Number>在逻辑上可以视为Box<Integer>的父类,那么就不会报错了。那么问题来了,通过getDate取出来的 到底是什么类型呢?Integer、Number?且由于在编程过程中的顺序不可控,
* 导致在必要的时候必须要进行类型判断,然后进行强制类型转换,显然,这与泛型的理念相矛盾。因此,在逻辑上Box<Number>不能视为Box<Integer>的父类
*
* 解决问题:
* 定义一个新的函数,方法的重载?这与java中的多态理念是相违背的。因此我们需要一个用来表示同时是Box<Integer>和Box<Number>父类的一个引用类型,由此,类型通配符应运而生。
*
* 类型通配符一般使用?代替具体的参数类型。注意了,此处是类型实参,而不是类型形参!且Box<?>在逻辑上是Box<Integer>、Box<Integer>等所有Box<具体类型实参>的父类。由此,我们仍然
* 可以定义泛型方法来完成此需求。
*
* 有时候,我们可能对类型实参有进一步限制?此时可能用到通配符上限和通配符下限
* Box<? extends Number>代表Box的泛型只能是Number和Number的子类
* Box<? super Number>代表Box的泛型只能是Number和Number的父类
*
* java泛型主要是用在集合中,并没有泛型数组一说
*/
public class GenericTest03 {
public static void main(String[] args) {
Box<Number> name=new Box<Number>(99);
Box<Integer> age=new Box<Integer>(712);
Box<Double> h=new Box<Double>(0.2);
Box<String> s=new Box<String>("12s");
getData03(name);
getData03(age);
//getData03(h); 报错,类型
//getData03(s); 报错,类型
}
public void test01(){
Box<Number> name=new Box<Number>(99);
Box<Integer> age=new Box<Integer>(712);
Box<Double> h=new Box<Double>(0.2);
Box<String> s=new Box<String>("12s");
getData02(name);
getData02(age);
getData02(h);
//getData02(s); 这里会报错
} public void test02(){
Box<Number> name=new Box<Number>(99);
Box<Integer> age=new Box<Integer>(712);
Box<Double> h=new Box<Double>(0.2);
Box<String> s=new Box<String>("12s");
getData(name);
getData(age);
getData(h);
getData(s);
}
public static void getData00(Box<Number> data){
System.out.println("data:"+data.getData());
}
//Box的所有泛型
public static void getData(Box<?> data){
System.out.println("data:"+data.getData());
} //限制只能是泛型为Number和Number的子类泛型
public static void getData02(Box<? extends Number> data){
System.out.println("data:"+data.getData());
}
//限制只能是泛型为Integer和Integer的父类泛型
public static void getData03(Box<? super Integer> data){
System.out.println("data:"+data.getData());
}
}
package jdk5的新特性; /*
* 可变参数
* 举个例子:如果需要计算一组数的和,但不知道计算的数有多少个,那怎么办呢?
* 使用方法重载? 这是不现实的。
* 所以我们有了可变参数
* 形式: 数据类型...变量名 表示接受n个该类型的变量
* 可变参数的方式接受到了数据怎么使用呢?
* 通过查看源码,反编译的方式。我们发现,可变参数其实本质上是一个指定类型数组类型的参数,
* 在调用方法的时候构建固定长度的固定类型数组,然后传入方法
* 使用可变参数的时候需要注意:
* 可变参数一般放在参数列表的最后面。可变参数的形参名表示的是一个数组名
*/
public class ChangeParam {
public static void main(String[] args) {
System.out.println(getCount(1,2,3));
System.out.println(getCount(1,2));
System.out.println(getCount());
System.out.println(getCount(new int[]{1,2,3}));
}
/*public int getCount(int a,int b){
return a+b;
}
public int getCount(int a,int b,int c){
return a+b+c;
}*/
//...
public static int getCount(int...in){
int c=0;
for(int s:in){
c+=s;
}
//return 0;
return c;
}
} package jdk5的新特性; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; public class ArrayAsList {
//发现Arrays的asList使用的是可变参数
//public static <T> List<T> asList(T... a)
public static void main(String[] args) {
ArrayList<Integer> al=new ArrayList<Integer>();
List ss= Arrays.asList(12,24,12);
// ss.add(3);
//ss.remove(3); 通过它获得的实质上是一个数组,只能修改内容,不能操作长度
ss.set(2, 23);
System.out.println(ss); }
}
package jdk5的新特性; //import java.sql.Date; public class StaticImport {
public static void main(String[] args) {
//System.out.println(Date.valueOf("12"));
//System.out.println(java.sql.Date.valueOf("12"));
}
}
/*
* 原来的导入都是导入的是类。使用import 包名.类名
* 如果需要不导入类,就需要这样:java.sql.Date.valueOf("12")
* jdk5提供了静态导入的功能:
* 在类名的上面导入静态方法,暂时没找到例子
* import 类名.方法名格式。 注意方法必须是静态方法
* 当静态导入的方法与类中的方法重名的时候,必须使用这种形式java.sql.Date.valueOf("12")
*/