第一讲 泛型(Generic)
一、概述
1、JDK1.5版本以后出现的新特性。用于解决安全问题,是一个类型安全机制。
2、JDK1.5的集合类希望在定义集合时,明确表明你要向集合中装入那种类型的数据,无法加入指定类型以外的数据。
3、泛型是提供给javac编译器使用的可以限定集合中的输入类型说明的集合时,会去掉“类型”信息,使程序运行效率不受影响,对参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。
4、由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,如用反射得到集合,再调用add方法即可。
5、没有使用泛型时,只要是对象,不管是什么类型的对象,都可以存储进同一个集合中。使用泛型集合,可以将一个集合中的元素限定为一个特定类型,集合中只能存储同一个类型的对象,这样更安全;并且当从集合获取一个对象时,编译器也可以知道这个对象的类型,不需要对对象进行强制类型转换,这样更方便。泛型就是把原来的类名进行了延长!在JDK 1.5中,你还可以按原来的方式将各种不同类型的数据装到一个集合中,但编译器会报告unchecked警告。
6、格式:
通过<>来定义要操作的引用数据类型
如:ArrayList<String> //定义要存入集合中的元素指定为String类型
7、好处
a、将运行时期出现的问题ClassCastException,转移到了编译时期。方便于程序员解决问题。让运行时期问题减少、安全。
b、避免了强制转换的麻烦。如在实现某一个接口时,指定传入接口方法的实参的类型的话,在复写该接口方法时就可以直接使用指定类型,而不需要强制转换。
泛型研究:
只有类被定义成了泛型,才可以对其进行参数化应用。
下面程序的main方法中的第二行代码和注释中的两行代码表达的意思完全相同,注释中的两行代码不能通过编译,而第二行(采用方法调用链)却可以顺利通过编译。
因为Generic,编译器可以在编译期获得类型信息所以可以编译这类代码。将下面那两行改成:
Class<?extends Test> c = ((Test)obj).getClass();
c.newInstance().func();
就能通过编译了。
在JDK 1.5中引入范型后,Object.getClass()方法的定义如下:
public final Class<? extends Object>getClass()
这说明((Test)obj).getClass()语句返回的对象类型为Class<? extendsTest>,而Class<T>的newInstance()方法的定义如下:
public T newInstance() throwsInstantiationException,IllegalAccessException
即对于编译器看来,Class<Test>的newInstance()方法的对象类型为Test,而((Test)obj).getClass()返回的为对象类型为Class<? extends Test>,所以,编译器认为((Test)obj).getClass().newInstance()返回的对象类型为Test。
下面这两行代码之所以无法通过编译
是因为((Test)obj).getClass()返回的为对象类型为Class<? extends Test>,但是我们在第一行将结果强制转换成了Class,然后再去调用Class的newInstance方法,而不是去调用Class<Test>的newInstance方法,编译器当然不再认为Class的newInstance方法返回的对象为Test了。
二、泛型定义中的术语:
如:ArrayList<E>类和ArrayList<Integer>
1、ArrayList<E>整个称为泛型类型
2、ArrayList<E>中的E称为类型变量或类型参数
3、整个ArrayList<Integer>称为参数化类型
4、ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
5、ArrayList<Integer>中的<>称为typeof
6、ArrayList称为原始类型
参数化:parametered,已经将参数变为实际类型的状态。
三、在使用java提供的对象时,什么时候写泛型?
通常在集合框架中很常见,只要见到<>就要定义泛型。
其实<>就是用来接收类型的。当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。
四、关于参数化类型的几点说明:
1、参数化类型与原始类型的兼容性
第一、参数化类型可引用一个原始类型的对象,编译只是报警告。
如:Collection<String>coll = new Vector();
第二、原始类型可引用一个参数化类型的对象,编译报告警告
如:Collectioncoll = new Vector<String>();
原来的方法接受一个集合参数,新类型也要能传进去。
2、参数的类型不考虑类型参数的继承关系:
Vector<String> v = newVector<Objec>();//错误的
不写Object没错,写了就是明知故犯
Vector<Objec> v = newVector<String>();//错误的
3、编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型
如:Vector<Integer>vectorList[] = new Vector<Integer>[10];//错误的
五、通配符
1、当传入的类型不确定时,可以使用通配符?。也可以理解为占位符。使用通配符的好处是可以不用明确传入的类型,这样在使用泛型类或者泛型方法时,提高了扩张性。
Collection<?> a可以与任意参数化的类型匹配,但到底匹配的是什么类型,只有以后才知道,所以:a=newArrayList<Integer>和a=new ArrayList<String>都可以,但a.add(new Date())或a.add(“abc”)都不行。
问题:
定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?
错误方式:
正确方式:
总结:
使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
2、泛型限定
对于一个范围内的一类事物,可以通过泛型限定的方式定义,有两种方式:
1)? extends E:可接收E类型或E类型的子类型;称之为上限。
如:ArrayList<? extends Number>x = new ArrayList<Integer>();
2)? super E:可接收E类型或E类型的父类型;称之为下限。
如:ArrayList<? super Integer>x = new ArrayList<Number>();
示例:
六、泛型类
1、若类实例对象中要使用到同一泛型参数,即这些地方引用类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型。
2、什么时候定义泛型类
当类中要操作的引用数据类型不确定的时候,早期定义Object来完成扩展。现在定义泛型来完成扩展。
3、泛型类定义的泛型,在整个类中有效。如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所以要操作的类型就已经固定了。
4、类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:
GenericDao<String>dao = null;
newgenericDao<String>();
5、语法格式:
class Utils<XX>
{
private XX s;
public void setxx(XX s)
{
this.s=s;
}
public XX getXX()
{
return s;
}
}
注意:
1、在对泛型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
2、当一个变量被声明为参数时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用,因为静态成员是被所有参数化的类共享的,所以静态成员不应该有类级别的类型参数。
七、泛型方法
为了让不同方法可以操作不同类型,而且类型还不确定。那么可以将泛型定义在方法上。
如:
其中方法中上的泛型可以不和类泛型相同。
特殊之处:
静态方法不可以访问类上定义的泛型。如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。
泛型方法的特点:
1、位置:用于放置泛型的类型参数的<>应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前,按照惯例,类型参数通常用单个大写字母表示。
2、只有引用类型才能作为泛型方法的实际参数。
3、除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符。例如,Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable& cloneable> void method(){}。
4、普通方法、构造函数和静态方法中都可以使用泛型。
5、可以用类型变量表示异常,称之为参数化的异常,可用于方法的throws列表中,但是不能用于catch子句中。
6、在泛型中可同时有多个类型参数,在定义它们的<>中用逗号分开。例如:
public static <K,V> V getValue(K key) { return map.get(key);}
八、看代码
1、
2、
上面两段代码中T和?有什么区别:
1、T限定了类型,传入什么类型即为什么类型,可以定义变量,接收赋值的内容。
2、?为通配符,也可以接收任意类型但是不可以定义变量。
但是这样定义,虽然提高了扩展性,可还是有一个局限性,就是不能使用其他类对象的特有方法。
通配符方案要比泛型方法更有效,当一个类型变量用来表达两个参数之间或参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用,而不是仅在签名的时候使用,才需要使用泛型方法。
小结:
对泛型的定义:
第一、定义泛型:当又不确定的类型需要传入到集合中,需要定义泛型。
第二、定义泛型类:如果类型确定后,所操作的方法都是属于此类型,则定义泛型类。
第三、定义泛型方法:如果定义的方法确定了,里面所操作的类型不确定,则定义泛型方法。
九、类型参数的类型推断
1、编译器判断范型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
2、根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
1)当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(new String[3],3,4)
——> static <E> voidswap(E[] a, int i, int j)
2)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:
add(3,5)
——> static<T> T add(T a, T b)
3)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3.5f)
——> static<T> void fill(T[] a, T v)
4)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
intx =(3,3.5f)
——> static<T> T add(T a, T b)
5)参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(newInteger[5],new String[5])
——> static<T> void copy(T[] a,T[] b);
copy(newVector<String>(), new Integer[5])
——> static<T> void copy(Collection<T> a , T[] b);
十、通过反射获得泛型的参数化类型
示例代码:
泛型DAO的应用:
第二讲 Collections
一、概述
Collections是对集合框架的一个工具类。它里边的方法都是静态的,不需要创建对象。并未封装特有数据。
在Collections工具类中大部分方法是用于对List集合进行操作的,如比较,二分查找,随机排序等。
二、常见操作
1、查找
Tmax(Collection<? extends T> coll);//根据集合的自然顺序,获取coll集合中的最大元素
Tmax(Collection<? extends T> coll,Comparator<? super T> comp);//根据指定比较器comp的顺序,获取coll集合中的最大元素
intbinarySearch(Lsit<? extends Comparable<? super T>> list,Tkey);//二分法搜索list集合中的指定对象
2、替换
voidfill(List<? super T> list, T obj);//将list集合中的全部元素替换成指定对象obj
booleanreplaceAll(List<T> lsit,T oldVal,T newVal);//用newVal替换集合中的oldVal值
void swap(Listlist,int i,int j);/在指定列表的指定位置处交换元素
3排序:
void shuffle(List<?> list);//使用默认随机源对list集合中的元素进行随机排序
void sort(Lsit<T> list);//根据自然顺序对list集合中的元素进行排序
voidsort(List<T> lsit,Comparator<? super T> c);//根据指定比较器c的排序方式对list集合进行排序
4、反转
reverse(List<?> list);//反转list集合中元素的顺序
Comparator reverseOrder();//返回一个比较器,强行逆转了实现Comparable接口的对象的自然顺序
ComparatorreverseOrder(Comparator<T> cmp);//返回一个比较器,强行逆转了指定比较器的顺序
5、同步的集合
List<T>synchronizedList(List<T> list);//返回支持的同步(线程安全的)List集合
Map<K,V>synchronizedList(Map<K,V> m);//返回支持的同步(线程安全的)Map集合
三、Collections和Collection的区别
Collection是集合框架中的一个顶层接口,它里面定义了单列集合的共性方法。
它有两个常用的子接口:
List:对元素都有定义索引。有序的。可以重复元素。
Set:不可以重复元素。无序
Collections是集合框架中的一个工具类。该类中的方法都是静态的。提供的方法中有可以对list集合进行排序,二分查找等方法
通常常用的集合都是线程不安全的。因为要提高效率。如果多线程操作这些集合时,可以通过该工具类中的同步方法,将线程不安全的集合,转换成安全的。
小练习:
第三讲 Arrays
一、概述
Arrays是用于操作数组的工具类。里边的方法也全是静态的。不需要创建对象。
把数组变成List集合的好处:可以使用集合的思想和方法来操作数组中的元素。如:contains,get,indexOf,subList等方法。
二、常见方法
1、Lsit<T> asList(T... a);//将数组转换为集合
注意:
a、将数组转换成集合,不可使用集合的增删方法,因为数组的长度是固定的。如果进行增删操作,则会产生UnsupportedOperationException的编译异常。
b、如果数组中的元素都是对象,则变成集合时,数组中的元素就直接转为集合中的元素。
c、如果数组中的元素都是基本数据类型,那么会将该数组作为集合中的元素存在。
2、binarySearch():二分查找方法,fill():替换方法,sort():排序方法等
特点:可对数组元素进行相应操作,可以接受除boolean之外的其他各种基本数据类型及有序的引用类型数组的参数,且还可以对指定元素的范围,并可根据指定比较器进行相应的操作。
如:sort(T[]a,Comparator<? super T> c)
fill(int[]a,int from,int to)等
3、String toString();//可以接收各种数组类型参数,并返回指定数组内容的字符串表现形式。
示例:
三、集合变数组
Collection接口中的toArray方法。
<T> T[]toArray(T[] a);将集合变为指定类型的数组。
1、指定类型的数组到底要定义多长呢?
当指定类型的数组长度小于了集合的size,那么该方法内部会创建一个新的数组。长度为集合的size。
当指定类型的数组长度大于了集合的size,就不会新创建了数组。而是使用传递进来的数组。
所以创建一个刚刚好的数组最优。
2、为什么要将集合变数组?
为了限定对元素的操作。不需要进行增删了。
示例:
第四讲 扩展知识---1.5版本新特性
一、高级for
1、格式:
for(数据类型变量名 :被遍历的集合(collection)或者数组) {执行语句}
2、说明
a、对集合进行遍历。只能获取集合元素。但是不能对集合进行操作。可以看作是迭代器的简写形式。
b、迭代器除了遍历,还可以进行remove集合中元素的动作。如果使用ListIterator,还可以在遍历过程中对集合进行增删改查的操作。
3、传统for和高级for的区别:
高级for有一个局限性。必须有被遍历的目标(集合或数组)。
传统for遍历数组时有索引。
建议在遍历数组的时候,还是希望使用传统for。因为传统for可以定义角标。
示例:
二、方法的可变参数
如果一个方法在参数列表中传入多个参数,个数不确定,那么每次都要复写该方法。这时可以用数组作为形式参数。但是在传入时,每次都需要定义一个数组对象,作为实际参数。在JDK1.5版本后,就提供了一个新特性:可变参数。
可变参数其实就是数组参数的简写形式。不用每一次都手动的建立数组对象。只要将要操作的元素作为参数传递即可。隐式将这些参数封装成了数组。
在使用时注意:可变参数一定要定义在参数列表的最后面。
示例:
三、静态导入
1、写法:
import staticjava.util.Arrays.*;//导入的是Arrays这个类中的所以静态成员。
import staticjava.lang.System.*//导入了Ssytem类中所以静态成员。
没加static导入的是类,加上static导入的全是某一个类中所以的静态成员。这样写在调用该类的静态方法时可以不用再写类名。如:Arrays.sort(数组);就可以直接写sort(数组);
2、注意:
当导入的两个类中有同名成员时,需要在成员前加上相应的类名。
当类名重名时,需要指定具体的包名。当方法重名时,指定具体所属的对象或者类。
示例: