Java基础-异常、集合

一、Exception (例外/ 异常)

对于程序可能出现的错误应该做出预案。

例外是程序中所有出乎意料的结果。(关系到系统的健壮性)

JAVA 会将所有的错误封装成为一个对象,其根本父类为 Throwable 。

Throwable 有两个子类:Error 和 Exception 。

一个 Error 对象表示一个程序错误,指的是底层的、低级的、不可恢复的严重错误。此时程序一定会退出,因为已经失去了运行所必须的物理环境。

对于 Error 错误我们无法进行处理,因为我们是通过程序来应对错误,可是程序已经退出了。

我们可以处理的 Throwable 对象中只有 Exception 对象(例外/ 异常)。

Exception 有两个子类:
Runtime exception (未检查异常)
非 Runtime exception(已检查异常)

(注意:无论是未检查异常还是已检查异常在编译的时候都不会被发现,在编译的过程中检查的是程序的语法错误,而异常是一个运行时程序出错的概念。)

在 Exception 中,所有的非未检查异常都是已检查异常,没有另外的异常!!

未检查异常是因为程序员没有进行必要的检查,因为他的疏忽和错误而引起的异常。一定是属于虚拟机 内部的异常(比如空指针)。

应对未检查异常就是养成良好的检查习惯。
已检查异常是不可避免的,对于已检查异常必须实现定义好应对的方法。
已检查异常肯定跨越出了虚拟机的范围。(比如“未找到文件”)

如何处理已检查异常(对于所有的已检查异常都要进行处理):

1、异常形成的机制
当一个方法中有一条语句出现了异常,它就会 throw(抛出)一个例外对象,然后后面的语句不会执行,返回上一级方法,其上一级方法接受到了例外对象之后,有可能对这个异常进行处理,也可能将这个异常转到它的上一级。

2、对于接收到的已检查异常有两种处理方式:throws 和 和 try 方法。

① 注意:出错的方法有可能是 JDK ,也可能是程序,无论谁写的,抛出一定用 throw 。
例:public void print() throws Exception.

② 对于方法 a ,如果它定义了 throws Exception 。那么当它调用的方法 b 返回异常对象时,方法 a 并不处理,而将这个异常对象向上一级返回,如果所有的方法均不进行处理,返回到主方法,程序中止。(要避免所有的方法都返回的使用方法,因为这样出现一个很小的异常就会令程序中止)。

如果在方法的程序中有一行 throw new Exception(),返回错误,那么其后的程序不执行。因为错误返回后,后面的程序肯定没有机会执行,那么 JAVA 认为以后的程序没有存在的必要。

③ 对于 try ……catch 格式:
try { 可能出现错误的代码块} catch(exception e){ 进行处理的代码 } ;

对象变量的声明
用这种方法,如果代码正确,那么程序不经过 catch 语句直接向下运行;
如果代码不正确,则将返回的异常对象和 e 进行匹配,如果匹配成功,则处理其后面的异常处理代码。
(如果用 exception 来声明 e 的话,因为 exception 为所有 exception 对象的父类,所有肯定匹配功)。 处理完代码后这个例外就完全处理完毕,程序会接着从出现异常的地方向下执行(是从出现异常的地方还是在 catch 后面呢?利用程序进行验证)。最后程序正常退出。

1、Try 中如果发现错误,即跳出 try 去匹配 catch ,那么 try 后面的语句就不会被执行。

2、一个 try 可以跟进多个 catch 语句,用于处理不同情况。当一个 try 只能匹配一个 catch
我们可以写多个 catch 语句,但是不能将父类型的 exception 的位置写在子类型的 excepiton 之前,因为这样父类型肯定先于子类型被匹配,所有子类型就成为废话。JAVA 编译出错。

3、在 try ,catch 后还可以再跟一子句 finally 。其中的代码语句无论如何都会被执行(因为 finally 子句的这个特性,所以一般将释放资源,关闭连接的语句写在里面)。

4、如果在程序中书写了检查(抛出)exception 但是没有对这个可能出现的检查结果进行处理,那么程序就会报错。

5、而如果只有处理情况(try )而没有相应的 catch 子句,则编译还是通不过。

如何知道在编写的程序中会出现例外呢
1、调用方法,查看 API 中查看方法中是否有已检查错误。

2、在编译的过程中看提示信息,然后加上相应的处理。

Exception 有一个 message 属性。在使用 catch 的时候可以调用:
Catch(IOException e){System.out.println(e.message())};
Catch(IOException e){e.printStackTrace()};
上面这条语句会告诉我们出错类型所历经的过程,在调试的中非常有用。

开发中的两个道理:
① 如何控制 try 的范围:根据操作的连动性和相关性,如果前面的程序代码块抛出的错误影响了后面程序代码的运行,那么这个我们就说这两个程序代码存在关联,应该放在同一个 try 中。

② 对已经查出来的例外,有 throw(积极)和 try catch(消极)两种处理方法。
对于 try catch 放在能够很好地处理例外的位置(即放在具备对例外进行处理的能力的位置)。如果没有处理能力就继续上抛。

当我们自己定义一个例外类的时候必须使其继承 excepiton 或者 RuntimeException。

Throw 是一个语句,用来做抛出例外的功能,而 throws 是表示如果下级方法中如果有例外抛出,那么本方法不做处理,继续向上抛出。Throws 后跟的是例外类型。

断言是一种调试工具(assert)
其后跟的是布尔类型的表达式,如果表达式结果为真不影响程序运行。如果为假系统出现低级错误,在屏幕上出现 assert 信息。

Assert 只是用于调试。在产品编译完成后上线 assert 代码就被删除了。

 方法的覆盖中,如果子类的方法抛出的例外是父类方法抛出的例外的父类型,那么编译就会出错:子类无法覆盖父类。

结论:子类方法不可比父类方法抛出更多的例外。子类抛出的例外或者与父类抛出的例外一致,或者是父类抛出例外的子类型。或者子类型不抛出例外。 如果父类型无 throws 时,子类型也不允许出现 throws 。此时只能使用 try catch

二、集合

集合是指一个对象容纳了多个对象,这个集合对象主要用来管理维护一系列相似的对象。

数组就是一种对象。 (练习:如何编写一个数组程序,并进行遍历。)

java.util.* 定义了一系列的接口和类,告诉我们用什么类 NEW 出一个对象,可以进行超越数组的操作。
(注:JAVA1.5 对 对 JAVA1.4 的最大改进就是增加了对范型的支持)

集合框架接口的分类:(分 collection 接口 和 map 接口)
Collection 接口:List、Set——>SortedSet
Map 接口:SortedMap 接口

JAVA 中所有与集合有关的实现类都是这六个接口的实现类。

1、Collection 接口:集合中每一个元素为一个对象,这个接口将这些对象组织在一起,形成一维结构。

2、List 接口代表按照元素一定的相关顺序来组织(在这个序列中顺序是主要的),List 接口中数据可重复。

3、Set 接口是数学中集合的概念:其元素无序,且不可重复。(正好与 List 对应)

4、SortedSet 接口会按照数字将元素排列,为“可排序集合”。

5、Map 接口中每一个元素不是一个对象,而是一个键对象和值对象组成的键值对(Key-Value )。

Key-Value 是用一个不可重复的 key 集合对应可重复的 value 的 集合。(典型的例子是字典:通过页码的 key 的值找字的 value 值)。
例子:
key1 —value1;
key2 —value2;
key3 —value3.

6、SortedMap 接口 :如果一个 Map 可以根据 key 值排序,则称其为 SortedMap 。(如字典)

!! 注意数组和集合的区别:数组中能存基本数据类型。Collection 接口和 Map 接口只能存对象。

以下介绍接口:

List 接口:(介绍其下的两个实现类:ArrayList 和 和 LinkedList )

ArrayList 和数组非常类似,其底层①也用数组组织数据,ArrayList 是动态可变数组。

① 底层:指存储格式。说明 ArrayList 对象都是存在于数组中。
注:数组和集合都是从下标 0 开始。

ArrayList 有一个 add(Object o) 方法用于插入数组。
ArrayList 的使用:(完成这个程序)
先 import java.util.* ;
用 ArrayList 在一个数组中添加数据,并遍历。

ArrayList 中数组的顺序与添加顺序一致。

只有 List 可用 get 和 和 size 。而 Set 则不可用(因其无序)。

迭代
1、Collection 接口都是通过 Iterator() (即迭代器)来对 Set 和 和 List 遍历。

2、通过语句:Iterator it=c.iterator(); 得到一个迭代器,将集合中所有元素顺序排列。然后可以通过 interator 方法进行遍历,迭代器有一个游标(指针)指向首位置。

3、Interator 有 hasNext() ,用于判断元素右边是否还有数据,返回 True 说明有。然后就可以调用 next 动作。Next()会将游标移到下一个元素,并把它所跨过的元素返回。(这样就可以对元素进行遍历)。

集合中每一个元素都有对象,如有字符串要经过强制类型转换。

Collections
1、Collections 是工具类,所有方法均为有用方法,且方法为 static 。有 Sort 方法用于给 List 排序。

2、Collections.Sort() 分为两部分,一部分为排序规则;一部分为排序算法。

3、规则用来判断对象;算法是考虑如何排序。

4、对于自定义对象,Sort 不知道规则,所以无法比较。这种情况下一定要定义排序规则。方式有两种:
① java.lang 下面有一个接口:Comparable(可比较的)
可以让自定义对象实现一个接口,这个接口只有一个方法 comparableTo(Object o)
其规则是当前对象与 o 对象进行比较,其返回一个 int 值,系统根据此值来进行排序。
如 当前对象>o 对象,则返回值>0;(可将返回值定义为 1)
如 当前对象=o 对象,则返回值=0;
如 当前对象<o 对象,则返回值〈0。(可将返回值定义为-1)
看 TestArraylist 的 java 代码。
我们通过返回值 1 和-1 位置的调换来实现升序和降序排列的转换。

② java.util 下有一个 Comparator(比较器)
它拥有 compare(),用来比较两个方法。
要生成比较器,则用 Sort 中 Sort(List,List(Compate))
第二种方法更灵活,且在运行的时候不用编译。

注意:要想实现 comparTo()就必须在主方法中写上 implement comparable.

练习:生成一个 EMPLOYEE 类,然后将一系列对象放入到 ArrayList。用 Iterator 遍历,排序之后,再进行遍历。

集合的最大缺点是无法进行类型判定(这个缺点在 JAVA1.5 中已经解决),这样就可能出现因为类型不同而出现类型错误。解决的方法是添加类型的判断。

LinkedList 接口(在代码的使用过程中和 ArrayList 没有什么区别)
1、ArrayList 底层是 object 数组,所以 ArrayList 具有数组的查询速度快的优点以及增删速度慢的缺点。

2、而在 LinkedList 的底层是一种双向循环链表。在此链表上每一个数据节点都由三部分组成:前指针(指向前面的节点的位置),数据,后指针(指向后面的节点的位置)。最后一个节点的后指针指向第一个节点的前指针,形成一个循环。

3、双向循环链表的查询效率低但是增删效率高。所以 LinkedList 具有查询效率低但增删效率高的特点。

4、ArrayList 和 和 LinkedList 在用法上没有区别,但是在功能上还是有区别的。
LinkedList 经常用在增删操作较多而查询操作很少的情况下:队列和堆栈。
队列:先进先出的数据结构。
堆栈:后进先出的数据结构。

注意:使用堆栈的时候一定不能提供方法让不是最后一个元素的元素获得出栈的机会。
LinkedList 提供以下方法:(ArrayList 无此类方法)
addFirst();
removeFirst();
addLast();
removeLast();

在堆栈中,push 为入栈操作,pop 为出栈操作。
push 用 addFirst() ;pop 用 removeFirst() ,实现后进先出。
用 isEmpty()-- 其父类的方法,来判断栈是否为空。

在队列中,put 为入队列操作,get 为出队列操作。
Put 用 addFirst() ,get 用 removeLast() 实现队列。

List 接口的实现类(Vector )(与 ArrayList 相似,区别是 Vector 是重量级的组件,使用使消耗的资源比较多。)

结论:在考虑并发的情况下用 Vector (保证线程的安全)。
在不考虑并发的情况下用 ArrayList (不能保证线程的安全)。

面试经验
java.util.stack (stack 即为堆栈)的父类为 Vector 。可是 stack 的父类是最不应该为 Vector 的。因为 Vector 的底层是数组,且 Vector 有 get 方法(意味着它可能访问到并不属于最后一个位置元素的其他元素,很不安全)。

对于堆栈和队列只能用 push 类和 get 类。
Stack 类以后不要轻易使用。

!!!实现堆栈一定要用 LinkedList 。
(在 JAVA1.5 中,collection 有 有 queue 来实现队列。)

Set-HashSet 实现类:
遍历一个 Set 的方法只有一个:迭代器(interator )。
HashSet 。 中元素是无序的(这个无序指的是数据的添加顺序和后来的排列顺序不同),而且元素不可重复。 在 Object 中除了有 final() ,toString() ,equals() ,还有 hashCode()。

HashSet 底层用的也是数组。
当向数组中利用 add(Object o) 添加对象的时候,系统先找对象的 hashCode :
int hc=o.hashCode(); 返回的 hashCode 为整数值。

int I=hc%n; (n 为数组的长度),取得余数后,利用余数向数组中相应的位置添加数据,以 n 为 为 6 为例,如果 I=0 则放在数组 a[0] 位置,如果 I=1, 则放在数组 a[1] 位置。如果 equals() 返回的值为 true,则说明数据重复。如果 equals() 返回的值为 false,则再找其他的位置进行比较。

对象有可能重复地添加到数组中,因为他们的 hashCode 不同。
如果我们能够使两个相同的对象具有相同 hashcode ,才能在 equals() 返回为真。

在实例中,定义 student 对象时覆盖它的 hashcode 。
因为 String 类是自动覆盖的,所以当比较 String 类的对象的时候,就不会出现有两个相同的 string 对象 的情况。

现在,在大部分的 JDK 中,都已经要求覆盖了 hashCode 。

结论:如将自定义类用 hashSet 来添加对象,一定要覆盖 hashcode()和 和 equals(),覆盖的原则是保证当两 个对象 hashcode 返回相同的整数,而且 equals() 返回值为 True 。

如果偷懒,没有设定 equals() ,就会造成返回 hashCode 虽然结果相同,但在程序执行的过程中会多次地调用 equals(),从而影响程序执行的效率。

要保证相同对象的返回的 hashCode 一定相同,也要保证不相同的对象的 hashCode 尽可能不同(因为数组的边界性,hashCode 还是可能相同的)。例子:

public int hashCode(){
     return name.hashcode()+age;
}

这个例子保证了相同姓名和年龄的记录返回的 hashCode 是相同的。

使用 hashSet 的优点:
1、hashSet 的底层是数组,其查询效率非常高。而且在增加和删除的时候由于运用的 hashCode 的比较开确定添加元素的位置,所以不存在元素的偏移,所以效率也非常高。因为 hashSet 查询和删除和增加元素的效率都非常高。

2、但是 hashSet 增删的高效率是通过花费大量的空间换来的:因为空间越大,取余数相同的情况就越小。
HashSet 这种算法会建立许多无用的空间。

练习:new 一个 hashset ,插入 employee 对象,不允许重复,并且遍历出来。
添加知识点:集合对象存放的是一系列对象的引用。
例:

Student S
Al.add(s);
s.setName(“lucy”);
Student s2=(Student)(al.get(o1));

可知 s2 也是 s 。

SortedSet 可自动为元素排序。
SortedSet 的实现类是 TreeSet: 它的作用是字为添加到 TreeSet 中的元素排序。

如果要查询集合中的数据,使用 Set 必须全部遍历,所以查询的效率低。使用 Map ,可通过查找 key 得到 value ,查询效率高。
集合中常用的是:ArrayList ,HashSet ,HashMap 。其中 ArrayList 和 和 HashMap 使用最为广泛。

使用 HashMap ,put() 表示放置元素,get()。

遍历 Map ,使用 keySet() 可以返回 set 值,用 keySet() 得到 key 值,使用迭代器遍历,然后使用 put()得到 value 值。

上面这个算法的关键语句:

Set s=m.keySet();
Interator it=new interator();
Object key=it.next();
Object value=m.get(key);

注意:HashMap 与 与 HashCode 有关,用 Sort 对象排序。
在 如果在 HashMap 中有 key 值重复,那么后面一条记录的 value 覆盖前面一条记录。

Key 值既然可以作为对象,那么也可以用一个自定义的类。比如:
m.put(new sutdent(“Liucy”,30),”boss”)
如果没有语句来判定 Student 类对象是否相同,则会全部打印出来。

当我们用自定义的类对象作为 key 时,我们必须在程序中覆盖 HashCode()和 和 equals() 。

注:HashMap , 底层也是用数组,HashSet 底层实际上也是 HashMap, ,HashSet 类中有 HashMap 属性(我 们如何在 API 中查属性)。HashSet 实际上为(key.null) 类型的 HashMap 。有 key 值而没有 value 值。

正因为以上的原因,TreeSet 和 和 TreeMap 的实现也有些类似的关系。
注意:TreeSet 和 和 TreeMap 非常的消耗时间,因此很少使用。
我们应该熟悉各种实现类的选择——非常体现你的功底。

HashSet VS TreeSet :HashSet 非常的消耗空间,TreeSet 因为有排序功能,因此资源消耗非常的高,我们应该尽量少使用,而且最好不要重复使用。

基于以上原因,我们尽可能的运用 HashSet 而不用 TreeSet ,除非必须排序。
同理:HashMap VS TreeMap: 一般使用 HashMap ,排序的时候使用 TreeMap

HashMap VS Hashtable (注意在这里 table 的第一个字母小写)之间的区别有些类似于 ArrayList 和 Vector ,Hashtable 是重量级的组件,在考虑并发的情况,对安全性要求比较高的时候使用。

Map 的运用非常的多。

使用 HashMap() ,如果使用自定义类,一定要覆盖 HashCode()和 和 equals() 。

重点掌握集合的四种操作:增加、删除、遍历、排序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值