一、泛型:jdk1.5版本,出现的技术。是一个安全机制。
泛型技术的由来:
集合中可以存储任意类型对象,但是在取出时,如果要使用具体对象的特有方法时,需要进行向下转型,如果存储的对象类型不一致,在转型过程中就会出现ClassCastException异常。
这样就给程序带来了不安全性。
在jdk1.5以后就有了解决方案。就是泛型技术。
解决方案就是,在存储元素时,就不允许存储不同类型的元素。
存储了就编译失败。 所以就需要在存储元素时,在容器上明确具体的元素类型。这其实和数组定义很像。
好处:
1,将运行时期的ClassCastException异常转移到了编译事情,进行检 查,并以编译失败来体现。 这样有利于程序员尽早解决问题。
2,避免了向下转型(强转)的麻烦
什么时候写泛型呢?
先简单理解:只要在使用类或者接口时,该类或者接口在api文当描述时都带着<>就需要在使用时,定义泛型。
其实,泛型无非就是通过<>定义了一个形式参数。专门用于接收具体的引用类型。在使用时,一定要传递对应的实际参数类型。
集合中泛型的应用特别多见。
泛型的擦除:
泛型技术是用于编译时期的技术,编译器会按照<>中的指定类型对元素进行检查,检查不匹配,就编译失败,匹配,就编译通过,通过后,生产的class文件中是没有泛型的。这就成为泛型的擦除。
泛型的补偿:
运行时,可以根据具体的元素对象获取其具体的类型。并用该类型对元素进行自动转换。
二、泛型对对于程序的设计也有一定优化动作。例如下:
定义一个工具类对worker对象也可以对Student对象进行操作,设置和获取。甚至于任意对象。
之所以定义Object类型的对象是因为不能确定要操作什么类型的具体对象。
弊端是要使用对象的特有方法需要向下转型,并且问题发生,会出现的运行时期,而不是编译时期,不能解决。
class Tool{
private Object obj;
public void setObject(Object obj){
this.obj = obj;
}
public Object getObject(){
return obj;
}
主函数中对该工具类调用时
// Tool t = new Tool();
// t.setObject(new Student2());编译时竟没有报错而运行时报错
// Worker w = (Worker)t.getObject();需要类型强转,否则编译失败
怎么解决呢?泛型来了
class Util<QQ>{
private QQ obj;
public void setObject(QQ obj){
this.obj = obj;
}
public QQ getObject(){
return obj;
}
}
主函数中对泛型工具类的调用:
Util<Worker> u = new Util<Worker>();
// u.setObject(new Student2());//类型不匹配,直接编译失败。
// Worker w = u.getObject();//不需要向下转型了。
三、泛型类的诞生和使用条件
当一个类要操作的引用数据类型不确定的时候,可以将该类型定义一个形参。用到的这类时,有使用者来通过传递类型参数的形式,来确定要操作的具体的对象类型。
意味着在定义这个类时(自定义类),需要在类上定义形参。用于接收具体的类型实参。
这就是将泛型定义在类上。这就是泛型类。
什么时候使用泛型类呢?只要类中操作的引用数据类型不确定的时候,就可以定义泛型类。
有了泛型类,省去了曾经的强转和类型转换异常的麻烦。
泛型定义在方法上
这是一个泛型类。class Tool2<W>
class Tool2<W>{
public void show(W w){
System.out.println("show:"+w.toString());
}
public void myprint(W w){
System.out.println("myprint:"+a.toString());
}
主函数中创建该泛型类对象,并调用该泛型类中的方法
Tool2<String> t = new Tool2<String>();
t.show("abc");
t.show(new Integer(4));这儿会编译失败。因为只能是String类
t.myprint("haha");
t.myprint(new Integer(4));同上。
}而我对这个myprint方法,想要操作的类型是不确定的,但是不一定和调用该方法的对象指定的类型一致。
*这时 可以将泛型定义在方法上。
public <A> void myprint(A a){
System.out.println("myprint:"+a.toString());
}此时,上面的t.myprint(new Integer(4));就不会再编译报错了
另外,静态方法不能访问类上定义的泛型,如果需要泛型,该泛型只能定义在方法上。即如果void前没有 <Y> ,会编译失败的。
public static <Y> void method(Y w){
System.out.println("method:"+w);
}
泛型接口的应用
interface Inter<V>{
public abstract void show(V v);
}这是一个泛型接口
1、//实现接口时,明确具体的类型。
class InterImpl implements Inter<String>{
public void show(String s){
System.out.println("show :"+s);
}
}
2、/实现接口时,也不明确具体类型。
class InterImpl2<C> implements Inter<C>{
public void show(C c){
System.out.println("show :"+c);
}
}
主函数中分别对1 和 2 的情况
new InterImpl().show("abc");实现接口时明确了String类
new InterImpl2<Integer>().show(new Integer(5));
实现接口时没有明确具体类型。
四、泛型的通配符:?
ArrayList<Integer> al3 = new ArrayList<Object>();//错误
ArrayList<Object> al4 = new ArrayList<Integer>();//错误。
简单的定义方式就是保证两边的类型一致。但是也有其他情况。
所以通配符应运而生了。
当操作的不同容器中的类型都不确定的时候,而且使用的都是元素从Object类中继承的方法。
这时泛型就用通配符?来表示即可。
应用示例:创建一个用于迭代集合的功能。
public static void printColl(Collection<?> coll){
Iterator<?> it = coll.iterator();
while(it.hasNext()){
System.out.println(it.next().toString());
}
}
Collection<?> coll表示无论是ArrayList 集合还是HashSet集合,也无论他们的ArrayList<String>还是ArryList<Integer>都可以使用该功能。
五、泛型的限定
明确具体类型代表一个类型。
明确?代表所有类型。?相当于? extends Object
能不能对操作的类型限制在一个范围之内呢?
比如:定义一个功能,只操作Person类型或者Person的子类型。
这时可以用 ? extends E:接收E类型或者E的子类型。这就是上限。
下限:? super E: 接收E类型或者E的父类型。
六、foreach语句:增强for循环
jdk1.5以后 出现的新方式。这种升级就是简化书写。
Collection有了一个父接口,Iterable
该接口封装了iterator方法,同时提供了一个新的语句。foreach语句。
格式:
for(变量 : Collection集合or数组)
{
}
Iterator it = al.iterator();
while(it.hasNext()){
System.out.println(it.next());
}用foreach语句如下:
for(String s : al){
System.out.println(s);
}
int[] arr = {7,23,1,67,90,8};
for(int i : arr){//只为遍历元素,无法操作角标。
System.out.println("i="+i);
}
1,foreach循环简化了迭代器。迭代器还用吗?用,因为迭代过程中还可以remove().
一般只对基本遍历简化使用。
2,和传统for循环有什么区别呢?
foreach循环特点:必须明确被遍历的目标。没有目标没用。目标只能是数组或者Collection集合。
如果要对数组的中的元素进行特定操作时,建议传统for循环,通过角标完成。
七、TreeSet排序的两种方式
TreeSet:可以给其中的元素进行指定方式排序,使用的自然顺序。
自然顺序:就是元素自身的具备的比较性实现了Comparable接口的compareTo方法
TreeSet排序的方式一:让元素自身具备比较性,需要实现Comparable接口,覆盖compareTo方法。这种比较方式成为自然顺序排序。
如果元素自身不具备比较性或者具备的比较性(自然顺序)不是所需要的。这时只能用第二种方式 。
TreeSet排序的方式二:让容器自身具备比较性。容器一初始化就具备了比较功能。
因为容器时在对象构造时完成的。通过查阅,有一个构造方法TreeSet(Comparator).
在容器初始化时可以指定一个比较器。
需要实现Comparator接口,覆盖compare方法即可。
所以这种方式成为比较器排序。
TreeSet练习
练习:对字符串长度进行排序。
思路:
1,对字符串对象进行排序是很简单的。
因为字符串对象本身就具备着自然顺序(字典顺序),因为它实现 了Comparable接口。覆盖了compareTo方法。
2, 要求是对字符串进行长度的排序。
发现字符串具备的自然顺序不是所需要的。这时就只能使用比较 器的方式。
3, 需要定义个比较器。
TreeSet ts = new TreeSet(new ComparatorByLength());
ts.add("hahah");
ts.add("nba");
ts.add("aaaa");
ts.add("zzz");
ts.add("abc");
Iterator it = ts.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
定义比较器ComparatorByLength
public class ComparatorByLength implements Comparator {
public int compare(Object o1, Object o2) {
String s1 = (String)o1;
String s2 = (String)o2;
int temp = s1.length() - s2.length();
return temp==0?s1.compareTo(s2):temp;
}
}
八、有序集合LinkedHashSet
在Set集合中,其实有一个有序集合。就是HashSet的子类
顺便提一下LinkedList中特有的操作方法:
* addFirst()
* addLast();
* jdk1.6以后,变成。
* offerFirst();
* offerLast();
*
* getFirst():获取元素,但不删除,如果列表为空,抛出异常NoSuchElementException
* getLast();
* jdk1.6以后。
* peekFirst();获取元素,但不删除,如果列表为空,返回null。
* peekLast();
*
* removeFirst():获取元素,并会删除,如果列表为空,抛出异常NoSuchElementException
* removeLast();
* jdk1.6以后。
* pollFirst():获取元素,并会删除,如果列表为空,返回null。
* pollLast();
九、集合的技巧掌握
明确具体集合对象名称的后缀:
如果后缀是List,都所属于List体系。通常都是非同步的。
如果后缀是Set,都属于Set体系,通常也是非同步的。
这些体系中的其他子类对象,后缀不是所属接口名的,一般都 是同步的。比如Vector.
这在常用子类对象中通用。
明确数据结构:
对于jdk1.2版本的子类对象。
后缀名是所属的体系。
前缀名是就是数据结构的名称。
比如:
ArrayList: 看到Array,就要明确是数组结构。查询快。
LinkedList:看到Link,就要明确链表结构,就要想到 add get remove 和first last结合的方法.增删快。
HashSet:看到hash,就要明确是哈希表。查询巨快,而且唯 一性。
就要想到元素必须覆盖 hashCode方法和equals方法。
TreeSet:看到Tree,就要明确是二叉树,可以对元素排序。
就要想到两种排序方式:
自然顺序:Comparable接口,覆盖compareTo(一个参 数 )java.lang
比较器:Comparator接口,覆盖compare(两个参 数);java.util
判断元素唯一性的依据就是比较方法的返回结果return 0;
十、编程题训练
对一个字符串数组(其中包含着重复的元素)进行长度由小到大的排序。
想要按照字符串的长度排序。
说明字符串对象所具备的自然顺序不是所需要的。
只好使用另一种对象比较的方式。就是比较器。
在排序的时候传入一个指定的比较器进来。 按照指定的方式进行比较排序。
String[]arr= {"abcd","zz","hahaah","xixix","qq","mm","abcd","hahaah"};
sortStringArray2(arr,new ComparatorByStringLen());
String str = toString(arr);
System.out.println(str);
传进字符串数组,并对其排序。
public static void sortStringArray2(String[] arr,Comparator<String> comp){
for (int i = 0; i < arr.length-1; i++) {
for (int j = i+1; j < arr.length; j++) {
if(comp.compare(arr[i], arr[j])>0){
swap(arr,i,j);
}
}
}
将字符串数组通过字符串缓存区转成字符串并返回
private static String toString(String[] arr) {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < arr.length; i++) {
if(i!=arr.length-1)
sb.append(arr[i]+", ");
else
sb.append(arr[i]+"]");
}
return sb.toString();
}
创建比较器ComparatorByStringLen()
public class ComparatorByStringLen implements Comparator<String> {
public int compare(String o1, String o2) {
int temp = o1.length() - o2.length();
return temp==0?o1.compareTo(o2):temp;
}
}