打赏

JAVA入门到精通-第25讲-泛型.异常

 泛型:安全简单;
  用set/get方法包起来;
  取出的时候有一个强转的动作:
 
   强转隐藏安全隐患:
  
  如果这样会报类型转换出错:
  编译的时候并不知道al里面放的是一只狗不报错;解决之道?
  泛型:
(1)安全 (2)代码的重用率;
  先打一对尖括号<Dog>
  
 自动转换过程,并没有强制转换。
 
 JAVA泛型的反射机制:
 定义一个T;
 传进去T是什么类型,T就是什么类型;
 
 
 
 
 
T可以表示任何拟定的类型;
反射的机制通过传进去的类型,知道T类型的所有的方法和返回值等。
 
 
 此时,Method数组包含函数的一些信息;

 
通过反射机制,可以得到一个类的好多信息; 

什么时候用?
一个类,但不知道这个类是什么类型;
提高代码的重用率;
 
 
   通过泛型,由于泛型的反射机制,get知道类型的相关信息,从        而, 不会报错
 

异常处理:
 代码健壮:异常的处理。代码根据用户的运用情况考虑各种情况。
 异常:用户的各种错误。
 java.lang.Exception
 java.lang.RuntimeException
 (1)程序在编译时就可探测可能有异常;
 (2)编译的时候发现不了的,数组从控制台输入;大小为5,用户8;
 (3)Error:
   Throwable类是异常的父类;
  
 
 
 
   1.检查性异常:(不是程序本身错误,可能是用户错误)
  netstat -an
  route print
  tracert
 
2.运行期异常:
排错比较难,往往是程序的逻辑错误;
 
  
 
 
 数组越界;

 3.ERROR:
 内存耗尽。由运行环境处理,外部环境造成的是最难调的。

 异常的处理:
 try...catch
 
  

Set结构的集合类

HashSet类,TreeSet

HashSet是基于HashMap实现的,HashSet底层采用HashMap来保存所有元素。

hashCodeequal()HashMap用的,因为无需排序所以只需要关注定位和唯一性即可

hashCode是用来计算hash值的,hash值是用来确定hash表索引的

hash表中的一个索引存放的是一张链表,所以还要通过equal方法循环比较链上的每一个对象才可以真正定位到键值对应的Entry

put时,如果hash表中没定定位到,就在链表前加一个Entry,如果定位到了,则更换Entry中的value()并返回旧value()

覆写keyhashCode()equal()时一定要注意,不要把它们和可变属性关联上,否则属性变了之后hashCode会变,equal也会为false,这样在Map中就找不到它了而且这样的对象因为找不到它所以得不到释放,这样就变成了一个无效引用(相当于内存泄漏)

 

//HashSet的使用方法[Demo145.java]

import java.util.*;

public class Demo145{

    public static void main(String []args){

        HashSet<Emp> hs=new HashSet<Emp>();

        Emp emp1=new Emp("s001","aa",1.2f);

        Emp emp2=new Emp("s002","bb",1.6f);

        Emp emp3=new Emp("s003","cc",1.8f);

        Emp emp4=new Emp("s001","aa",1.2f);

        hs.add(emp1);

        hs.add(emp2);

        hs.add(emp3);

        hs.add(emp4);

        hs.add(emp1);//重复的emp1,HashSet会自动去除

        System.out.println("HashSet_size="+hs.size());

        System.out.println();

        ArrayList<Emp> al=new ArrayList<Emp>();

        Emp emp5=new Emp("s004","dd",1.0f);

        Emp emp6=new Emp("s005","ee",2.5f);

        al.add(emp5);

        al.add(emp6);

        //al.add(emp1);

        hs.addAll(al);//al中的值加到hs中,并去除重复的emp1

        System.out.println("HashSet_ArrayList_size="+hs.size());

        System.out.println();

        //转换数组o[],遍历并输出HashSet中的无素

        Object o[]=hs.toArray();

        for(int i=0;i<o.length;i++){

            System.out.println("工号:"+((Emp)o[i]).getEmpNo()+"\t姓名:"+((Emp)o[i]).getName()+"\t工资:"+((Emp)o[i]).getSal());

        }

    }

}

class Emp{

    private String empNo;

    private String name;

    private float sal;

    public Emp(String empNo,String name,float sal){

        this.empNo=empNo;

        this.name=name;

        this.sal=sal;

    }

    public String getEmpNo() {

        return empNo;

    }

    public void setEmpNo(String empNo) {

        this.empNo = empNo;

    }

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

    public float getSal() {

        return sal;

    }

    public void setSal(float sal) {

        this.sal = sal;

    }

}

-------------------------------------------------------------------------------

TreeSet集合类是一个有序集合,它的元素按照升序排序,默认是自然顺序排列,也就是说

TreeSet中的对象元素需要实现Comparable接口。TreeSetHashSet类一样没有get()方法来获取列表中的元素,所以也只能通过迭代器方法来获取。

由于TreeMap需要排序,所以需要一个Comparator为键值进行大小比较,当然也是用Comparator定位的Comparator可以在创建TreeMap时指定,这时排序时使用Comparator.compare

如果创建时没有指定Comparator那么就会使用key.compareTo()方法,这就要求key必须实现Comparable接口

TreeMap是使用Tree数据结构实现的,所以使用compare接口就可以完成定位了。

TreeSet是依靠TreeMap来实现的TreeSet是一个有序集合,它的元素按照升序排列,默认是按照自然顺序排列,也就是说TreeSet中的对象元素需要实现Comparable接口。

TreeSet类中跟HashSet类一要也没有get()方法来获取列表中的元素,所以也只能通过迭代器的方法来获取 。

 

//TreeSet集合类的使用[Demo146.java]

import java.util.*;

public class Demo146{

    public static void main(String[] args){

        //传递一个比较器来实现你自己的排序方式

        TreeSet tr =new TreeSet(new Student.StudentComparator());

        tr.add(new Student(301,"张三"));//将学生数据写入TreeSet集合类的tr

        tr.add(new Student(201,"李二"));

        tr.add(new Student(101,"王五"));

        tr.add(new Student(101,"穷一"));

        Iterator it=tr.iterator();//迭代器,遍历

        while(it.hasNext()){//判断是否有下一个元素

            System.out.println(it.next());

        }

    }

}

//创建Strudent学生类并实现ComparableComparator接口

class Student implements Comparable,Comparator{

    private int num;//定义学号

    private String name;//定义名字

    public Student(int num,String name){

        this.num=num;

        this.name=name;

    }

    public int compareTo(Object o){

        Student st=(Student)o;

        int result;

        result=num>st.num?1:(num==st.num?0:-1);//判断学号是否相同并返回result的值

        //如果学号相等,就按姓名排列

    /*  if(result==0){

            return name.compareTo(st.name);

        }*/

        return result;

    }

    //实现Comparator接口并实现它的抽象方法

    public int compare(Object o1,Object o2){

        Student st1 =(Student)o1;

        Student st2 =(Student)o2;

        return st1.name.compareTo(st2.name);//比较姓名是否相同

    }

    //重写toString()方法,因为如果不重写,打印出来的是16进制代码

    public String toString(){

        return "num="+num+"; name="+name;

    }

    public static class StudentComparator implements Comparator{//定义一个静态StudentComparator类并实现Comparator接口

        public int compare(Object o1,Object o2){

            Student st1 =(Student)o1;

            Student st2 =(Student)o2;

            int result;

            result=st1.num>st2.num?1:(st1.num==st2.num?0:-1);//判断学号是否相同进行排序

            if(result==0){//如果学号相等 就进行名字排序

                result=st1.name.compareTo(st2.name);

            }

            return result;

        }

    }

}

-------------------------------------------------------------------------------

HashSetTreeSet集合类的区别:

HashSet是基于hash算法实现的,性能优于TreeSet。通常使用HashSet,在我们需要对其中元素排序的时候才使用TreeSet

===============================================================================

Queue结构的集合

Queue接口

Queue接口与ListSet同一级别,都是继承了Collection接口。LinkedList实现了Queue接口。Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可以使用。BlockingQueue继承了Queue接口。

    队列是一种数据结构。它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素。就是说,队列以一种先进先出的方式管理数据,如果你试图向一个已经满了的阻塞队列中添加一个元素或者是从一个空的阻塞队列中移除一个元索,将导致线程阻塞。

    在多线程进行合作时,阻塞队列是很有用的工具。工作者线程可以定期地把中间结果存到阻塞队列中而其他工作者线线程把中间结果取出并在将来修改它们。队列会自动平衡负载。如果第一个线程集运行得比第二个慢,则第二个线程集在等待结果时就会阻塞。如果第一个线程集运行得快,那么它将等待第二个线程集赶上来。

add

增加一个元索

如果队列已满,则抛出一个IIIegaISlabEepeplian异常

remove

移除并返回队列头部的元素

如果队列为空,则抛出一个NoSuchElementException异常

element

返回队列头部的元素

如果队列为空,则抛出一个NoSuchElementException异常

offer

添加一个元素并返回true

如果队列已满,则返回false

poll

移除并返问队列头部的元素

如果队列为空,则返回null

peek

返回队列头部的元素

如果队列为空,则返回null

put

添加一个元素

如果队列满,则阻塞

take

移除并返回队列头部的元素

如果队列为空,则阻塞

removeelementoffer pollpeek 其实是属于Queue接口。

    阻塞队列的操作可以根据它们的响应方式分为以下三类:addremoveelement操作在你试图为一个已满的队列增加元素或从空队列取得元素时 抛出异常。当然,在多线程程序中,队列在任何时间都可能变成满的或空的,所以你可能想使用offerpollpeek方法。这些方法在无法完成任务时 只是给出一个出错示而不会抛出异常。

注意:pollpeek方法出错进返回null。因此,向队列中插入null值是不合法的。

 

offeradd区别:

一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,多出的项就会被拒绝。这时新的 offer 方法就可以起作用了。它不是对调用 add() 方法抛出一个 unchecked 异常,而只是得到由 offer() 返回的 false

 

pollremove区别:

remove()poll()方法都是从队列中删除第一个元素(head)。remove()的行为与Collection 接口的版本相似,但是新的 poll()方法在用空集合调用时不是抛出异常,只是返回null。因此新的方法更适合容易出现异常条件的情况。

 

peekelement区别:

element()peek()用于在队列的头部查询元素。与 remove()方法类似,在队列为空时,element()抛出一个异常,而peek()返回null

 

五个队列所提供的各有不同:

* ArrayBlockingQueue

一个由数组支持的有界队列。基于数组的阻塞循环队列,先进先出原则对元素进行排序。

* LinkedBlockingQueue

一个由链接节点支持的可选有界队列。基于链表的队列,先进先出排序元素。

* PriorityBlockingQueue

一个由优先级堆支持的无界优先级队列。PriorityBlockingQueue是对PriorityQueue的再次包装,是基于堆数据结构的,而PriorityQueue是没有容量限制的,与ArrayList一样,所以在优先阻塞队列上put时是不会受阻的。但是由于资源被耗尽,所以试图执行添加操作可能会导致OutOfMemoryError),但是如果队列为空,那么取元素的操作take就会阻塞,所以它的检索操作take是受阻的。另外,往入该队列中的元素要具有比较能力。

* DelayQueue

一个由优先级堆支持的、基于时间的调度队列。(基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用null元素。

* SynchronousQueue

一个利用 BlockingQueue接口的简单聚集(rendezvous)机制。

SynchronousQueue 类是最简单的。它没有内部容量。它就像线程之间的手递手机制。在队列中加入一个元素的生产者会等待另一个线程的消费者。当这个消费者出现时,这个元素就直接在消费者和生产者之间传递,永远不会加入到阻塞队列中。

 

注意:Queue队列是不能直接实例化的。

原先在java编程中,Queue的实现都是用LinkedList

如:Queue qe=new LinkedList();

注意:此实现不是同步的。如果多个线程同时访问一个链接列表,而其中至少一个线程结构上修改了该列表,则它必须保质外部同步。(结构修改指添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。

JDK1.6中:

接口Queue<E>

类型参数:E - collection 中所保存元素的类型。

所有超级接口:Collection<E>, Iterable<E>

所有已知子接口:BlockingDeque<E>, BlockingQueue<E>, Deque<E>

所有已知实现类:AbstractQueue, ArrayBlockingQueue, ArrayDeque,

ConcurrentLinkedQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, LinkedList, PriorityBlockingQueue, PriorityQueue, SynchronousQueue

 

JDK1.7中:

接口Queue<E>

类型参数:E - collection 中所保存元素的类型。

所有超级接口:Collection<E>, Iterable<E>

所有已知子接口:BlockingDeque<E>, BlockingQueue<E>, Deque<E>, TransferQueue<E>

所有已知实现类:AbstractQueue, ArrayBlockingQueue, ArrayDeque,

ConcurrentLinkedQueue, ConcurrentLinkedQueue, DelayQueue, LinkedBlockingDeque,

LinkedBlockingQueue, LinkedList, LinkedTransferQueue, PriorityBlockingQueue,

PriorityQueue, SynchronousQueue

===============================================================================

Java中的List/SetMap的区别

    List按对象进入的顺序保存对象,不做排序和编辑操作。

    Set对每个对象只接受一次,并使用自己内部的排序方法(通常,你只关心某个元素是否属于Set而不关心它的顺序--否则使用List)

    Map同样对每个元素保存一份,但这是基于""(key)的,Map也有内置的排序,因而不关心元素添加的顺序。

如果添加元素的顺序对程序设计很重要,应该使用LinkedHashSet或者LinkedHashMap

 

List的功能方法

    实际上有两种List:一种是基本的ArrayList其优点在于随机访问元素,另一种是更强大的LinkedList它并不是为快速随机访问设计的,而是具有一套更通用的方法。

List:次序是List最重要的特点:它保证维护元素特定的顺序。ListCollection添加了许多方法,使得能够向List中间插入与移除元素(这只推荐LinkedList使用)一个List可以生成Listlterator,使用它可以从两个方向遍历List,也可以从List中间插入和移除元素。

ArrayList:由数组实现的List允许对元素进行快速随机访问,但是向List中间插入与移除元素的速率很慢。Listlterator只应该用来由后向前遍历ArrayList。而不是用来插入和移除元素。因为那比LinkedList开销要大很多。

LinkedList:对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。(使用ArrayList代替)还具有下列方法:addFirst()addLast()getFirst()getLast()removeFirst()removeLast()这些方法(没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。

 

Set的功能方法

    Set具有与Collection完全一样的接口,因此没有任何额外的功能,不象前面有两个不同的List。实际上Set就是Collection,只是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。)Set不保存重复的元素(至于如何判断元素相同则较为负责)

    Set:存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必需定义equals()方法以确保对象的唯一性。SetCollection有完全一样的接口。Set接口不保证维护元素的次序。

HashSet:为快速查找设计的Set。存入HashSet的对象必须定义hashCode()

TreeSet:保存次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。

LinkedHashSet:具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。

 

Map的功能方法

    方法put(Object key,Object value)添加一个""(想要得东西)和与""相关的""(key)(使用它来查找)。方法get(Object key)返回与给定""相关联的""可以用containsKey()containsValue()测试Map中是否包含某个""""。标准的java类库中包含了几种不同的MapHashMapTreeMapLinkedHashMapWeakHashMapldentityHashMap。它们都有同样的基本接口Map,但是行为、效率、排序策略、保存对象的生命周期和判定""等价的策略等各不相同。

    执行效率是Map的一个大问题。看看get()要做哪些事,就会明白为什么在ArrayList中搜索""是相当慢的。这正是HashMap提高速度的地方。HashMap使用了特殊的值,称为"散列码"(hash code),来取代对键的缓慢搜索。"散列码""相对唯一"用以代表对象的int值,它是通过将该对象的某些信息进行转换而生成的。所有java对象都能产生散列码,因为hashCode()是定义在基类Object中的方法。

HashMap就是使用对象的hashCode()进行快速查询的。此方法能够显著提高性能。

    Map:维护"键值对"的关联性,使你可通过""查找""

HashMapMap基于散列表的实现。插入和查询"键值对"的开销是固定的。可以通过构造器设置容量capacity和负载因子load factor,以调整容器的性能。

LinkedHashMap:类似于HashMap,但是迭代遍历它时,取得"键值对"的顺序是其插入次序,或者是最近最少使(LRU)的次序。只能HashMap慢一点。而在迭代访问时发而更快,因为它使用键表维护内部次序。

TreeMap:基于红黑树数据结果的实现。查看"""键值对"时,它们会被排序(次序由ComparabelComparator决定)TreeMap的特点在于,你得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。

WeakHashMap:旨键(weak key)MapMap中使用的对象也被允许释放:这是为解决特殊问题设计的。如果没有map之外的引用指向某个"",则此""可以被垃圾收集器回收。

ldentifyHashMap:使用==代替equals()""作比较的hash map。专为解决特殊问题而设计。

-------------------------------------------------------------------------------

Java中的Iterator(迭代器)的用法

java.util包中包含了一系列重要的集合类,集合类的根接口Collection

Collection接口是所有集合类的根类型。它的一个主要的接口方法是:

boolean add(Object c)添加数据

add()方法将添加一个新元素。注意这个方法会返回一个boolean,但是返回值不是表示添加成功与否。Collection规定:如果一个集合拒绝添加这个元素,无论任何原因,都必须抛出异常。这个返回值表示的意义是add()方法执行后,集合的内容是否改变了(就是元素有无数量,位置等变化),这是由具体类实现的。即:如果方法出错,总会抛出异常;返回值仅仅表示该方法执行后这个Collection的内容有无变化。

类似还有:

boolean addall(Collection c);添加所有数据

boolean remove(Object o);删除数据

boolean removeall(Collection c);删除所有数据

boolean remainall(Collection c);保持所有数据

Object[]toArray()方法很简单,把集合转换成数组返回。Object[]toArray(Object[] a)方法就有点复杂了,首先,返回的Object[]仍然是把集合的所有元素变成数组,但是类型和参数a的类型是相同的。

如:String[] o=(String)c.toArray(new String[0]);

得到的o实际类型是String[]数组。

    其次,如果参数a的大小装不下集合的所有元素,返回的将是一个新的数组。如果参数a的大小能装下集合的所有元素,则返回的还是a,但a的内容用集合的元素来填充。尤其要注意的是,如果a的大小比集合元素的个数还多,a后面的部分全部被置为null()

    最后一个最重要的方法是Iterator(),返回一个Iterator(迭代子),用于遍历集合的所有元素。

 

Iterator模式实现遍历集合

Iterator模式 是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。

例如,如果没有使用Iterator,遍历一个数组的方法是使用索引:

    for(int i=0;i<array.size();i++){..get(i)...}

而访问一个链表(LinkedList)又必须使用while循环:

    while((e=e.next())!=null){...e.data()...}

    以上两种方法客户端都必须事先知道集合的内部结构,访问代码和集合本身是紧耦合,无法将访问逻辑从集合类和客户端代码中分离出来,每一种集合对应一种遍历方法,客户端代码无法复用。

    更恐怖的是,如果以后需要把ArrayList更换为LinkedList,则原来的客户端代码必须全部重写。

    为解决以上问题,Iterator模式总是用同一种逻辑来遍历集合:

    for(Iterator it=c.iterater();it.hasNext();){ ...}

    奥秘在于客户端自身不维护遍历集合的"指针",所有的内部状态(如当前元素位置,是否有下一个元素)都由Iterator来维护,而这个Iterator由集合类通过工厂方法生成,因此,它知道如何遍历整个集合。

    客户端从不直接和集合类打交道,它总是控制Iterator,向它发送"向前""向后""取当前元素"的命令,就可以间接遍历整个集合。

 

首先看看java.util.Iterator接口的定义:

public interface Iterator {

  boolean hasNext();

  Object next();

  void remove();

}

    依赖前两个方法就能完成遍历,典型的代码如下:

for(Iterator it=c.iterator();it.hasNext();){

  Object o=it.next();

  // o的操作...

}

    JDK1.5中,还对上面的代码在语法上作了简化:

// Type是具体的类型,如String

for(Type t:c){

// t的操作...

}

    每一种集合类返回的Iterator具体类型可能不同,Array可能返回ArrayIteratorSet可能返回SetIteratorTree 可能返回TreeIterator,但是它们都实现了Iterator接口,因此,客户端不关心到底是哪种Iterator,它只需要获得这个 Iterator接口即可,这就是面向对象的威力。

    要确保遍历过程顺利完成,必须保证遍历过程中不更改集合的内容(Iteratorremove()方法除外),因此,确保遍历可靠的原则是只在一个线程中使用这个集合,或者在多线程中对遍历代码进行同步。

Iterator示例:

    Collection c=new ArrayList();

    c.add("abc");

    c.add("xyz");

    for(Iterator it=c.iterator();it.hasNext();){

        String s=(String)it.next();

        System.out.println(s);

    }

如果你把第一行代码的ArrayList换成LinkedListVector,剩下的代码不用改动一行就能编译,而且功能不变,这就是针对抽象编程的原则:对具体类的依赖性最小。

 

 


  




posted on 2018-11-30 15:11  XuCodeX  阅读(309)  评论(0编辑  收藏  举报

导航