零基础学习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)以上异常指的都是编译时异常 

 

posted @ 2019-08-21 11:43  一y样  阅读(311)  评论(0编辑  收藏  举报