zoukankan      html  css  js  c++  java
  • 零基础学习java------day15--------collections用法,比较器,Set(TreeSet,TreeMap),异常

    1. Collections用法

    Collections: 集合的工具类
    public static <T> void sort(List<T> list) 排序,升序
    public static <T> int binarySearch(List<?> list,T key) 二分查找,不存在返回负数,只能针对升序集合
    public static <T> T max(Collection<?> coll) 最大值
    public static void reverse(List<?> list) 反转

    public static void shuffle(List<?> list) 随机打乱
    public static <T> void sort(List<T> list, Comparator<? super T> c)

    Collection 和Collections 区别:
      Collection: 是单列集合的根接口
      Collections: 集合的工具类

    2. 比较器***

    2.0 前提

    通过Collections.sort()方法已经可以对一些集合实现排序功能,如下对List<Integer> list进行了排序

    public class ComparableDemo {
        public static void main(String[] args) {
            List<Integer> list = new ArrayList<Integer>();
            list.add(100);
            list.add(124);
            list.add(99);
            list.add(300);
            Collections.sort(list);
            System.out.println(list);//[99, 100, 124, 300]
        }
    }
    View Code

    此时若将集合改为List<Person>会直接报错,如下(The method sort(List<T>) in the type Collections is not applicable for the arguments (List<Person>))

    排序是通过Collections.sort()方法,那么,为什么List<Integer>可以进行排序呢?我们可以去看下Collections下sort方法以及Integer源码

    sort()源码部分

               

    由此处可看出,sort内的参数类型必须是Comparable<? super T>本身或者其父类,接下来看下Integer的源码部分

     

    可见其继承类Comparable<Integer>接口,那么如何让List<Person>类型的list也为comparable类型呢,首先想到的是继承,让Person类继承Integer类,Person就自然符合sort()方法中的参数类型了,但是有上面Integer源码可知,Integer有final修饰,其是不能被继承的,没办法,只能自己去继承并实现Comparable接口了。查看Comparable接口的源码,发现发现接口内部有个抽象方法待实现(compareTo()),而这个方法便是定义排序规则的,以便sort可以按此规则对list进行排序。这里我们先看下Integer类中是怎么实现comparable接口的,查看起源码,如下:

    可见实现compareTo的方法最终返回值有三种(0,-1,1),但sort()是怎么实现这个排序的呢?(以后再去看详细的源码)

     大致调用过程如下:

     Collections.sort()----->list.sort()------->Arrays.sort()------>Arrays.legacyMergeSort()----->Arrays.mergeSort()   ,可见,最终是用归并排序来实现的

    所以,要利用Collections.sort()对List<Person>集合进行排序,只需让Person实现Comparable接口即可(可参照Integer类)

    2.1 Comparable用法

    (1)

      要想让一个List可以使用Collections.sort进行排序,需要要求集合中的元素所在的类实现Comparable(java.lang)接口,实现了该接口就具备了排序的能力,才能进行排序,实现该接口我们需要重写里面的compareTo方法,该方法的主要目的就是定义排序的规则(即告诉接口按照上面规则比较大小),重写该方法时要注意,该方法返回值是int类型

      返回值为正数,表明this中的数据大于参数中的数据,排序时将大的数移至后面

      返回值为0,表明this中的数据等于参数中的数据

      返回值为负数,表明this中的数据小于参数中的id

    其实就是this对象和参数对象做一个比较,this对象在前,就是升序,参数对象在前就是降序。

    以下将Person去实现Comparable接口(只写出重写方法部分代码):

    @Override
    public int compareTo(Person o) {
       return (this.age<o.age)?-1:(this.age==o.age)?0:1;
    }

    这样的话Collections.sort()就可以对List<Person>集合进行排序,具体如下(此处是按年龄排序)

    public class ComparableDemo {
        public static void main(String[] args) {
            List<Person> person = new ArrayList<Person>();
            person.add(new Person("zs",26,''));
            person.add(new Person("ls",36,''));
            person.add(new Person("xh",26,''));
            person.add(new Person("mz",16,''));
            Collections.sort(person);
            System.out.println(person);
        }
    }
    // 运行结果:[Person [name=mz, age=16, gender=女], Person [name=zs, age=26, gender=男], Person [name=xh, age=26, gender=女], Person [name=ls, age=36, gender=男]]

     若要按姓名来排序,并且降序则compareTo方法如下:

    public int compareTo(Person o) {
        return o.name.compareTo(this.name);//  此处的compareTo为字符串内实现的compareTo方法
    }

    同样是上面的 ComparableDemo测试类,得到的结果为

    [Person [name=zs, age=26, gender=男], Person [name=xh, age=26, gender=女], Person [name=mz, age=16, gender=女], Person [name=ls, age=36, gender=男]]

    练习:创建一个Student类型的List:姓名,年龄,分数,排序要求:按照年龄做升序,如果年龄相同,则按照分数做降序

    public class Student implements Comparable<Student>{
      String name;
      int age;
      double score;
      public Student() {
      }   
    public Student(String name, int age, double score) {     super();     this.name = name;     this.age = age;     this.score = score;   }   @Override   public String toString() {     return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";   }   //需求: 默认按照年龄的升序,如果年龄相同,按照分数的降序   @Override   public int compareTo(Student o) {     if(this.age == o.age) {   //return (int)(o.score - this.score);//这么写不好,98.6 98.7得到的结果是0
        //  会认为分数相同,就会按照添加的顺序排序,和预期不符
       return o.score - this.score>0?1:-1; } return this.age - o.age; } }

    2.2  comparator用法

      1.  好了,目前我们已经掌握了如何对一个List<Person>做排序,那么我们知道了为什么List<Integer>默认实现的是升序,就是因为Integer在实现Comparable接口的时候用的就是升序的方式,如果我们想要实现降序应该怎么做呢?其实只需要将Integer中的compareTo方法中的两个参数调换一下位置就可以了,但是由于是class文件,我们是不能修改的,所以java为我们提供了一个外部比较器Comparator(java.util),这是个接口

    public class ComparableDemo {
        public static void main(String[] args) {
            List<Integer> list = new ArrayList<Integer>();
            list.add(100);
            list.add(124);
            list.add(99);
            list.add(300);
            Collections.sort(list,new IntegerComparator());//注意此处的sort方法跟升序不是一个方法,这是提供给 comparator的重载方法,接收2个参数
            System.out.println(list);//[99, 100, 124, 300]
        }
    }
    class IntegerComparator implements Comparator<Integer>{
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2.compareTo(o1);
        }
    }
    // 运行结果:[300, 124, 100, 99]

    2. 为了简便,可进行如下改写:

      (1)使用匿名函数来实现comparator接口

    Collections.sort(list, new Comparator<Integer>() {
        public int compare(Integer o1, Integer o2) {
             return o2-o1;
        }    
    });

      (2)在java1.8后,可以直接调用list.sort(),不需要Collections(更加方便),如下

    list.sort(new Comparator<Integer>() {
                public int compare(Integer o1, Integer o2) {
                    return o2-o1;
                }    
            });

    (3)使用lambda方法

    list.sort((a,b)->b-a);

     3. 此处可以不用Collections.sort(),直接使用带参数的构造方法,具体如下

    2  总结

     Comparator:用法:

        Collections.sort(List<List>, Comparator<>); 

     由于Comparator是一个接口,需要传入Comparator子类的对象(匿名内部类),需要重写compare,从而定义新的排序规则

        用前面的和后面的比较:升序

        用后面的和前面的比较:降序

     Comparable  和 Comparator的区别:

        Comparable:内部比较器,位于java.lang,如果一个集合想要使用Collections.sort进行排序,需要里面的元素所在类实现Comparable接口,并且重写compareTo方法实现排序

    3.  TreeSet和TreeMap的用法

    3.1TreeSet

      Collections.sort()只能作用于list,并不能作用于Set。Set要排序的话可以通过其子类TreeSet,TreeSet会自动对集合中的元素进行排序,同List一样,其对Person这种类型的元素做排序时,需要在这种类中实现Comparable接口,否则会出现转型异常:com._51doit.javase04.day15.tree.Person cannot be cast to java.lang.Comparable。

    案例1

    public class TreeSetDemo {
        public static void main(String[] args) {
            Set<Integer> set = new TreeSet<Integer>();
            set.add(12);
            set.add(15);
            set.add(20);
            set.add(9);
            System.out.println(set);
        }
    }
    // 运行结果:[9, 12, 15, 20]

    由结果可知,TreeSet可实现自动排序

    Set实现comparable接口的方式就是直接重载compareTo方法就行了,其内部是自动调用了排序方法的,但comparator则有点不一样,重写完comparator中的compare方法,直接将这个子类对象传进集合构造方法,具体如下:

    public class TreeSetDemo1 {
        public static void main(String[] args) {
            //实现TreeSet中Integer降序
            Set<Integer> set3 = new TreeSet<>(new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return o2 - o1;
                }
            });
            set3.add(123);
            set3.add(45);
            set3.add(1);
            set3.add(100);
            System.out.println(set3);
       }
    }
    // 运行结果:[123, 100, 45, 1]

    注意  Set里面的元素不能重复,其判断元素是否重复的原理是:根据compareTo(对comparable)/compare(对comparator)方法来验证元素是否重复,compareTo/compare返回结果是0,就认为是相同的元素,否则就不同。我们在重写compareTo/compare方法的时候,尽量不要只比较一个属性

    案例2

    public class TreeSetDemo {
        public static void main(String[] args) {
    
            Set<Teacher> teacher = new TreeSet<Teacher>();
            teacher.add(new Teacher("zs",26));
            teacher.add(new Teacher("ls",36));
            teacher.add(new Teacher("xh",26));
            teacher.add(new Teacher("xf",26));
            teacher.add(new Teacher("mz",16));
            System.out.println(teacher);  //并没有调用排序方法,重写compareTo方法直接打印此set集合,就会按照compareTo定义的规则排序,其内部怎么实现排序的暂不清楚
        }
    }
    class Teacher implements Comparable<Teacher>{
        String name;
        int age;
        public Teacher(String name, int age) {
            super();
            this.name = name;
            this.age = age;
        }
        @Override
        public int compareTo(Teacher o) {
            return o.age - this.age;  //按年龄降序
        }
        @Override
        public String toString() {
            return "Teacher [name=" + name + ", age=" + age + "]";
        }
    }
    // 运行结果:[Teacher [name=ls, age=36], Teacher [name=zs, age=26], Teacher [name=mz, age=16]]

    有结果可知,TreeSet直接按年龄是否相同去重了,若要不被去重,可以在compareTo中多写几个属性,如下

    @Override
    public int compareTo(Teacher o) {
       if(o.age == this.age) {
           return o.name.compareTo(this.name);//降序
       }
       else {
           return o.age - this.age;// 降序
       }
    }
    // 运行结果: [Teacher [name=ls, age=36], Teacher [name=zs, age=26], Teacher [name=xh, age=26], Teacher [name=xf, age=26], Teacher [name=mz, age=16]]

    3.2 TreeMap

    1. 概述:

    TreeMap:可以对key这一列实现排序,key不能为nul(key会调用compare/compareTo方法进行排序,若为null,则会出现空指针异常),TreeMap中的key所在的类必须要实现Comparable接口,如果不想实现,则必须在构造方法中传入一个外部比较器。

    key:   不能重复,其原理也是由compare/compareTo方法决定的

    public class TreeMapDemo {
        public static void main(String[] args) {
            TreeMap<Integer,String> map = new TreeMap<>();
            map.put(1,"热河");
            map.put(2,"你好");
            map.put(3,"再见");
            //map.put(null,"热河"); 报空指针异常
            System.out.println(map);
        }
    }
    运行结果:{1=热河, 2=你好, 3=再见}

    2.TreeMap实现降序

      使用外部选择器:Comparator(此处能不能通过重写comparable中的compareTo方法来实现降序呢?

    public class TreeMapDemo {
        public static void main(String[] args) {
            TreeMap<Integer,String> map = new TreeMap<>(new Comparator<>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return o2-o1;
                }
            });
            map.put(1,"热河");
            map.put(2,"你好");
            map.put(3,"再见");
            System.out.println(map);
        }
    }

    运行结果:{3=再见, 2=你好, 1=热河}

      

    4. 异常

    4.0 概述:  

      异常就是java程序在运行过程中出现的错误,现实生活中遇到的问题也是一个具体的事务,可以通过java类的形式进行描述,并封装成对象。其实就是java对不正常情况进行描述后的对象体现。前面见过的异常有角标越界异常,空指针异常等。

    4.1 异常的层次结构:

    Throwable:

      Error:程序出现的错误,不能使用程序处理,一般无需程序员关注,如内存溢出,网络连接异常

      Exception(异常):

            运行时异常:RuntimeException及其子类,可以对其进行处理,也可以不处理,往往是由于代码书写不正确或不规范造成,程序员自己去检查问题,并修改代码。

            编译时异常:除了运行时异常以外的所有异常,必须对异常进行处理,否则无法通过编译,程序无法执行往往是由于无法预估的用户操作造成的

    产生异常后:代码无法向下执行

    我们处理异常的目的是什么?   为了让程序能够执行下去

     下面是编译时异常

    4.2 异常的处理方式:

    JVN处理异常的方式:

        打印异常信息并终止程序,其并不会对异常进行处理

    当出现异常时,在eclipse找异常的顺序是由上往下,上面的异常一般为异常产生处,如下(先是test出现异常,再是main,而main方法出现异常是因为调用了test方法)

    4.2.1  捕获:

      1.try...catch( ){  }

        格式:

           try{

             //可能出现的异常

              }catch(异常类名   对象名){

               //对异常的处理

           }

      2.try...catch...finally

        格式:   

          try{

             //可能出现的异常

              }catch(异常类名   对象名){

               //对异常的处理

           }finally{

               //一定会执行的代码

           }

    注意事项:

    1. catch 块中可以存在多个,并且可以有子父类的关系:
       一旦存在子父类的关系,要把子类异常放上面,因为放下面,就再也执行不到了
          catch 块中应该写的是一种备选方案
    2. jdk7:一个catch 块中可以处理多个异常,多个异常之间使用 | 隔开
    3. try 块中的局部变量和catch 块中的局部变量(包括异常变量),以及finally 中的局部变量,他们之间不可共享使用。
    4. 每一个catch 块用于处理一个异常。异常匹配是按照catch 块的顺序从上往下寻找的,只有第一个匹配的catch 会得到执行。匹配时,不仅运行精确匹配,也支持父类匹配,因此,如果同一个try 块下的多个catch 异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,这样保证每个catch 块都有存在的意义。
    5. 当一个函数的某条语句发生异常时,这条语句的后面的语句不会再执行,它失去了焦点。执行流跳转到最近的匹配的异常处理catch 代码块去执行,异常被处理完后,执行流会接着在“处理了这个异常的catch 代码块”后面接着执行。
    6. finally 块不管异常是否发生,只要对应的try 执行了,则它一定也执行。只有一种方法让finally 块不执行:System.exit()。因此finally 块通常用来做资源释放操作:关闭文件,关闭数据库连接等等

    public class ExceptionDemo {
        public static void main(String[] args) {
            Date d = null;
            try {
            System.out.println(1/0); // 运行时异常
            }catch(Exception e1){
                System.out.println("哈哈");
            }
            try {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                d = sdf.parse("1232012-09-18"); // 编译时异常
                String s = null;
                System.out.println(s.length()); // 空指针异常
                System.out.println(1/0);
            }catch(ArithmeticException | NullPointerException e) { // 1个catch块处理多个异常
                System.out.println("1");
            }catch(Exception e) {  //此异常不能放前面,不然前面catch就不能执行了
                d = new Date();
            }
        }
    }

    面试题:

      final           finalize            finally:区别:  

    final:     修饰类、方法、变量,被修饰的类不能被继承,被修饰的方法不能重写,被修饰的变量值不变
    finalize: 用于垃圾回收

    finally:   用于异常处理,代表一定会执行的代码

    4.2.2  抛出(throws)

    格式:

     在方法头上:throws  异常类名{  // 类名可以有多个,多个类之间使用“,”隔开
    
      } 

    注意:异常最终被抛给了异常的调用者

    public class ExceptionDemo1 {
        public static void main(String[] args) throws ParseException {// 抛给其调用者JVM
            test();
        }
        public static void test() throws ParseException {  // 抛给其调用者main方法
            new SimpleDateFormat().parse("");
        }
    }

    一般内层抛出,在最外层做统一处理(不然每调用一次出现异常的方法就要处理一次异常)

     4.3  异常中常用的方法

     4.3.1  Trowable中的方法

    (1)getMessage(): 获取异常信息,返回字符串。

    public class ExceptionDemo2 {
        public static void main(String[] args) {
            try {
                new SimpleDateFormat().parse("");
            }catch(ParseException e){
                System.out.println(e.getMessage()); 
            }
        }    
    }
    // 运行结果:Unparseable date: ""

    (2)toString():获取异常类名和异常信息,返回字符串

    System.out.println(e.toString());  // java.text.ParseException: Unparseable date: ""

    (3)printStackTrace():获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void(不需要System.out.print(),自身就有打印功能)

    e.printStackTrace(); // 打印异常信息,打印到控制台

    运行结果:如下图

    4)printStackTrace(PrintStream s):通常用该方法将异常内容保存在日志文件中,以便查阅

    public class ExceptionDemo2 {
        public static void main(String[] args) {
            try {
                new SimpleDateFormat().parse("");
            }catch(ParseException e){
            e.printStackTrace();
            try {
                e.printStackTrace(new PrintStream("f:/a/error.txt"));
            }catch(FileNotFoundException e1) {
                e1.printStackTrace();
                }    
            }    
        }
    }

    4.4 throw用法

     throw 不是用来处理异常的,相反是触发一个异常,或者理解成一个异常

    格式:

      用在方法体中,throw异常的对象,这个对象只能有一个

    throw的异常一定会发生

    public class ExceptionDemo3 {
        public static void main(String[] args) {
            test();
        }
        public static void test() {
            throw new NullPointerException(""); // 若加参数,在报错异常类后会出现该参数
        }
    }

    运行结果:

    若创建的是编译异常,则要抛出或者处理异常

    public class ExceptionDemo3 {
        public static void main(String[] args) {
            try {
                test();
            } catch (ParseException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        public static void test() throws ParseException {
            throw new ParseException("天空之城",30);  //此处的inr参数表示当编译时,错误会在那个位置发生(不大理解)
        }
    }

     4.5 throws和throw区别

    throws:

       用在方法声明后面,跟的是异常类名;可以跟多个异常类名,用逗号隔开,表示抛出异常,由该方法的调用者来处理;throws表示出现异常的一种可能性,并不一定会发生这些异常

    throw:

      用在方法体内,跟的是异常对象名;只能抛出一个异常对象名;表示抛出异常,由方法体内的语句处理;throw则是抛出了异常,执行throw则一定抛出了某种异常

     4.6 自定义异常

      定义编译时异常:继承Exception

      定义运行时异常:继承RunException

     案例:人的年龄必须在1-260,显然java没有对应的异常,需要我们自己来定义一个异常,如下:

    (1)定义运行时异常: 

     定义一个继承自RuntimeException的异常,即运行时异常

    public class MyException extends RuntimeException {
        public MyException() {};
        public MyException(String desc) {
                super(desc);
        }
    }

    定义测试类,以及抛出异常

    public class AgeTest {
        public static void main(String[] args) {
            AgeException ae = new AgeException();
            ae.setAge(300);
        }
    }
    class AgeException {
        int age;
    
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            if(age>=1 && age<=260) {
                this.age = age;
            }else {
                throw new MyException("您的年龄有问题");
            }
        }        
    }
    View Code

    运行结果

     

    (2)定义编译时异常:

     将MyException继承自Exception即可,改完后,测试类中的年龄部分就要抛出异常或者处理异常了

     4.7  异常的注意事项

    (1)子类重写父类方法时,子类的方法必须要抛出相同的异常或父类异常的子类

    class FatherClass{
        public void test() throws Exception{
            
        }
    }
    class SonClass extends FatherClass{
        public void test() throws ParseException{  
            
        }
    }
    若将ParseException与Exception调换则会报错:Exception Exception is not compatible with throws clause in FatherClass.test()

    (2)如果父类抛出了多个异常,子类重写父类时,只能抛出相同的异常或者是它的子集,子类不能抛出父类没有的异常

    (3)如果被重写的方法没有异常抛出,那么子类方法绝对不可以抛出异常,如果子类方法内有异常发生,那么子类只能try,不能throws

    (4)以上异常指的都是编译时异常 

  • 相关阅读:
    4-8 求二叉树高度 (20分)
    汉诺塔的递归和非递归实现
    5-18 银行业务队列简单模拟 (25分)
    ACM 刷题小技巧【转】
    5-21 求前缀表达式的值(25分)
    5-20 表达式转换 (25分)
    约瑟夫环----循环链表问题
    关于埃拉托色尼筛选法的整理(质数问题)
    编码---隐藏在计算机软硬件背后的语言
    内排序和外排序扫盲
  • 原文地址:https://www.cnblogs.com/jj1106/p/11387873.html
Copyright © 2011-2022 走看看