零基础学习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] } }
此时若将集合改为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("您的年龄有问题"); } } }
运行结果
(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)以上异常指的都是编译时异常