黑马程序员java学习—泛型

一、泛型: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;

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值