Java的异常、多线程、集合类、泛型
异常
基本概念
- 异常
是在运行时期发生的不正常情况。
- 异常类
在java中用类的形式对不正常情况进行了描述和封装对象,描述不正常的情况的类。
异常就是java通过面向对象的思想将问题封装成了对象.用异常类对其进行描述.
- 异常体系
不同的问题用不同的类进行具体的描述。 比如角标越界。空指针等等。问题很多,意味着描述的类也很多,将其共性进行向上抽取
不正常情况就分为了两大类。
- Throwable: 无论是error,还是异常,问题,问题发生就应该可以抛出,让调用者知道并处理。
- throws throw ,凡是可以被这两个关键字所操作的类和对象都具备可抛性.
一般不可处理的. Error是由jvm抛出的严重性的问题
可以处理的.Exception
- 自定义异常:自定义的问题描述
异常的分类
- 编译时被检测异常:
- 只要是Exception和其子类都是(除了特殊子类RuntimeException体系)
- 这种问题一旦出现,希望在编译时就进行检测,让这种问题有对应的处理方式。
- 这样的问题都可以针对性的处理。
- 编译时不检测异常(运行时异常)
- 就是Exception中的RuntimeException和其子类。
- 这种问题的发生,无法让功能继续,运算无法进行,更多是因为调用者的原因导致的而或者引发了内部状态的改变导致的。
- 那么这种问题一般不处理,直接编译通过,在运行时,让调用者调用时的程序强制停止,让调用者对代码进行修正。
所以自定义异常时,要么继承Exception。要么继承RuntimeException。
throws 和throw的区别:
- throws使用在函数上.
throw使用在函数内.- throws抛出的是异常类,可以抛出多个,用逗号隔开。
throw抛出的是异常对象。
异常处理的捕捉形式
这是可以对异常进行针对性处理的方式。
//具体格式是:
try
{
//需要被检测异常的代码。
}
catch(异常类 变量)//该变量用于接收发生的异常对象
{
//处理异常的代码。
}
finally
{
//一定会被执行的代码。
}
异常的注意事项
- 子类在覆盖父类方法时,父类的方法如果抛出了异常, 那么子类的方法只能抛出父类的异常或者该异常的子类。
- 如果父类抛出多个异常,那么子类只能抛出父类异常的子集。
简单说:子类覆盖父类只能抛出父类的异常或者子类或者子集。
注意:如果父类的方法没有抛出异常,那么子类覆盖时绝对不能抛,就只能try .
包
对类文件进行分类管理;给类提供多层命名(名称)空间; 写在程序文件的第一行;
- 类名的全称是:
包名.类名
包也是一种封装形式;
//package
//protected必须是成为其子类,才能继承
//import导入指定包中的类用的
- 导包的原则:用到哪个导哪个;
- jar包:Java的压缩包;
多线程
- 进程和线程
- 进程:正在进行中的程序;
- 线程:就是进程中一个负责程序执行的控制单元(执行路径) 一个进程中可以多执行路径,称之为多线程;
- 一个进程中至少有一个线程;
- 开启多个线程是为了同时运行多个代码;
- 每一个线程都有运行的内容,这个内容称为这个线程执行的任务;
- 多线程好处:解决了多部分同时运行的问题;
- 多线程弊端:线程太多回到的效率降低;
其实应用的执行都是CPU进行着高速的转换,这个切换是随机的;
- 如:JVM启动时就启动了多个线程,至少有两个线程分析的出来;
- 执行main函数的线程; 该线程的代码都在主函数中;
- 负责垃圾回收的线程; 垃圾回收器中;
finalize清除垃圾,重新分配资源
System.gc()启动垃圾回收器
//主线程运行示例:
class Demo extends Thread
{
private String name;
Demo (String name)
{
this.name=name;
}
public void run()
{
for(int x=0;x<10;x++)
{
for(int y=-99999;y<999999999;y++){}
System.out.println(name+“....x”+x);
}
}
}
class Threaddemo
{
Demo d1=new Demo(“旺财”);
Demo d2=new Demo(“xiaoqiang”);
d1.start();
d2.start(); //开启线程,运行run方法
}
如何创建一个线程呢?
* 创建线程方式一:继承Thread类。
步骤:
- 定义一个类继承Thread类。
- 覆盖Thread类中的run方法。
- 直接创建Thread的子类对象创建线程。
- 调用start方法开启线程并调用线程的任务run方法执行。
可以通过Thread的getName获取线程的名称 Thread-编号(从0开始)
- 主线程的名字就是main
- 创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行。
- 而运行的指定代码就是这个执行路径的任务。
- jvm创建的主线程的任务都定义在了主函数中。
- 而自定义的线程它的任务在哪儿呢?
Thread类用于描述线程,线程是需要任务的。所以Thread类也对任务的描述。
这个任务就通过Thread类中的run方法来体现。也就是说,run方法就是封装自定义线程运行任务的函数。
run方法中定义就是线程要运行的任务代码。
开启线程是为了运行指定代码,所以只有继承Thread类,并复写run方法。
将运行的代码定义在run方法中即可。
- 通过Thread的getName()方法来获取该线程的名字
currentThread()正在运行的线程名字 - 调用start和run的区别
被创建
start() 运行
sleep(time)
wait() 冻结
Sleep(time)时间到
notify()
Run()方法结束,线程的任务结束
Stop()
消亡
- 线程的状态:
- CPU的执行资格:等待执行;
- CPU的执行权:正在执行
- 临时阻塞状态:具备执行资格,没有执行权,只有正在运行的有执行权;
- 只有一个有执行权;
- 创建线程的第二种方式:实现Runnable接口
- 定义类实现Runnable接口。
- 覆盖接口中的run方法,将线程的任务代码封装到run方法中。
- 通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
- 为什么?
- 因为线程的任务都封装在Runnable接口子类对象的run方法中。 所以要在线程对象创建时就必须明确要运行的任务。
- 调用线程对象的start方法开启线程。
- 实现Runnable接口的好处:
- 将线程的任务从线程的子类中分离出来,进行了单独的封装。 按照面向对象的思想将任务的封装成对象。
- 避免了java单继承的局限性。
所以,创建线程的第二种方式较为常用。
- 线程安全问题解决思路
将多条操作共享数据的线程代码封装起来,当有线程执行代码时,其他不能参与运算;
同步代码块 :synchronized(对象)
{
需要被同步的代码;
}
- 同步的好处:
解决了线程的安全问题;
- 同步的弊端:
相对降低了效率,因为同步外的线程都会判断同步锁
- 线程间通信(多个线程在处理同一资源,但是任务却不同):
- 这些方法存在于同步中;
- 使用这些方法时必须要标识所属的同步的锁;
- 等待、唤醒机制:
- 涉及的方法:
1. wait() //让线程处于冻结状态,被wait的线程会被存储线程池
2. notify() //唤醒线程池中任意一个线程
3. notifyAll() //唤醒线程池中所有线程
这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法
- 必须要明确到底操作的是哪个锁上的线程
- 为什么这些方法定义在object类中?
因为这些方法是监视器的方法,监视器本身就是一个锁
- Wait和sleep的方法:
- wait可以指定时间也可以不指定
Sleep必须指定时间- 在同步中,对CPU的执行权和锁的处理不同
Wait:释放执行权,释放锁
Sleep:释放执行权,不释放锁
- 停止线程
- stop方法(已过时)
- Run方法结束
- Interrupt()将线程从冻结状态恢复到运行状态,让线程具备CPU的执行资格
- 守护线程setDaemon(true)将该线程标记为守护线程,当所有线程被守护时,CPU退出
集合类
- 集合类的由来
对象用于封装特有数据,对象多了需要存储.
如果对象的个数不确定,就使用集合容器进行存储。
- 集合特点
- 用于存储对象的容器。
- 集合的长度是可变的。
- 集合中不可以存储基本数据类型值。
集合容器因为内部的数据结构不同,有多种具体容器。
Collection接口
- 不断的向上抽取,就形成了集合框架,框架的顶层Collection接口:
- Collection
- 添加
boolean add(Object obj):
boolean addAll(Collection coll):
- 删除
boolean remove(object obj):
boolean removeAll(Collection coll);
void clear();
- 判断
boolean contains(object obj):
boolean containsAll(Colllection coll);
boolean isEmpty():判断集合中是否有元素。
- 获取
int size():
Iterator iterator():
- 取出元素的方式:迭代器。 该对象必须依赖于具体容器,因为每一个容器的数据结构都不同。
- 所以该迭代器对象是在容器中进行内部实现的。
- 对于使用容器者而言,具体的实现不重要,只要通过容器获取到该实现的迭代器的对象即可,
- 也就是iterator方法。
- Iterator接口就是对所有的Collection容器进行元素取出的公共接口。
- 好比就是抓娃娃游戏机中的夹子!
- 其他
boolean retainAll(Collection coll) //取交集。
Object[] toArray() //将集合转成数组。
- Collection方法
- List:有序(存入和取出的顺序一致),元素都有索引(角标),元素可以重复。
- Set:元素不能重复,无序。
- List特有的常见方法:有一个共性特点就是都可以操作角标。
- 添加
void add(index,element)
void add(index,collection)
- 删除
Object remove(index):
- 修改
Object set(index,element)
- 获取
Object get(index);
int indexOf(object);
int lastIndexOf(object);
List subList(from,to);
//list集合是可以完成对元素的增删改查.
List:
- Vector:内部是数组数据结构,是同步的。增删,查询都很慢!
- ArrayList:内部是数组数据结构,是不同步的。替代了Vector。查询的速度快。
- LinkedList:内部是链表数据结构,是不同步的。增删元素的速度很快。
LinkedList:
addFirst();
addLast():
//jdk1.6
offerFirst();
offetLast();
getFirst();.//获取但不移除,如果链表为空,抛出NoSuchElementException.
getLast();
//jdk1.6
peekFirst();//获取但不移除,如果链表为空,返回null.
peekLast():
removeFirst();//获取并移除,如果链表为空,抛出NoSuchElementException.
removeLast();
//jdk1.6
pollFirst();//获取并移除,如果链表为空,返回null.
pollLast();
- Set:元素不可以重复,是无序。
Set接口中的方法和Collection一致。
- HashSet: 内部数据结构是哈希表 ,是不同步的。
- 如何保证该集合的元素唯一性呢?
- 是通过对象的hashCode和equals方法来完成对象唯一性的。
- 如果对象的hashCode值不同,那么不用判断equals方法,就直接存储到哈希表中。
- 如果对象的hashCode值相同,那么要再次判断对象的equals方法是否为true。
- 如果为true,视为相同元素,不存。如果为false,那么视为不同元素,就进行存储。
记住:如果元素要存储到HashSet集合中,必须覆盖hashCode方法和equals方法。
-
TreeSet:可以对Set集合中的元素进行排序。是不同步的。
-
判断元素唯一性的方式
就是根据比较方法的返回结果是否是0,是0,就是相同元素,不存。
- TreeSet对元素进行排序的方式一:
让元素自身具备比较功能,就需要实现Comparable接口。覆盖compareTo方法。
- 如果不要按照对象中具备的自然顺序进行排序。如果对象中不具备自然顺序。怎么办?
- 可以使用TreeSet集合第二种排序方式二:
让集合自身具备比较功能,定义一个类实现Comparator接口,覆盖compare方法。将该类对象作为参数传递给TreeSet集合的构造函数。
if(this.hashCode()== obj.hashCode() && this.equals(obj))
- 哈希表确定元素是否相同
- 判断的是两个元素的哈希值是否相同。 如果相同,在判断两个对象的内容是否相同。
- 判断哈希值相同,其实判断的是对象的hashCode的方法。判断内容相同,用的是equals方法。
注意:如果哈希值不同,是不需要判断equals。
集合的一些技巧
- 需要唯一吗?
需要:Set- 需要制定顺序:
需要: TreeSet
不需要:HashSet- 但是想要一个和存储一致的顺序(有序):
LinkedHashSet
不需要:List- 需要频繁增删吗?
需要:LinkedList
不需要:ArrayList- 如何记录每一个容器的结构和所属体系呢?
- List
|--ArrayList
|--LinkedList- Set
|--HashSet
|--TreeSet- 后缀名就是该集合所属的体系。
前缀名就是该集合的数据结构。- 看到array:就要想到数组,就要想到查询快,有角标.
- 看到link:就要想到链表,就要想到增删快,就要想要 add get remove+frist last的方法
- 看到hash:就要想到哈希表,就要想到唯一性,就要想到元素需要覆盖hashcode方法和equals方法。
- 看到tree:就要想到二叉树,就要想要排序,就要想到两个接口Comparable,Comparator 。
- 通常这些常用的集合容器都是不同步的。
Map
- Map:一次添加一对元素。Collection 一次添加一个元素。
- Map也称为双列集合,Collection集合称为单列集合。
- 其实map集合中存储的就是键值对。
- map集合中必须保证键的唯一性。
- 常用方法
- 添加
value put(key,value)
//返回前一个和key关联的值,如果没有返回null.
- 删除
void clear()
//清空map集合。
value remove(key)
//根据指定的key翻出这个键值对。
- 判断
boolean containsKey(key)
boolean containsValue(value)
boolean isEmpty()
- 获取
value get(key)
//通过键获取值,如果没有该键返回null。
//当然可以通过返回null,来判断是否包含指定键。
int size()
//获取键值对的个数。
- Map接口常用的子类
- Hashtable :内部结构是哈希表,是同步的。不允许null作为键,null作为值。
- Properties:用来存储键值对型的配置文件的信息,可以和IO技术相结合。
- HashMap : 内部结构是哈希表,不是同步的。允许null作为键,null作为值。
- TreeMap : 内部结构是二叉树,不是同步的。可以对Map集合中的键进行排序。
泛型
jdk1.5出现的安全机制。
泛型技术是给编译器使用的技术,用于编译时期。确保了类型的安全。
运行时,会将泛型去掉,生成的class文件中是不带泛型的,这个称为泛型的擦除。
为什么擦除呢?
因为为了兼容运行的类加载器。
- 好处
- 将运行时期的问题ClassCastException转到了编译时期。
- 避免了强制转换的麻烦。
- <>
- 当操作的引用数据类型不确定的时候。就使用<>。将要操作的引用数据类型传入即可.
- 其实<>就是一个用于接收具体引用数据类型的参数范围。
- 在程序中,只要用到了带有<>的类或者接口,就要明确传入的具体引用数据类型 。
- 泛型的补偿
在运行时,通过获取元素的类型进行转换动作。不用使用者再强制转换了。
- 泛型的通配符:
? 未知类型
- 泛型的限定:
- extends E: 接收E类型或者E的子类型对象。上限
一般存储对象的时候用。比如 添加元素 addAll.- super E: 接收E类型或者E的父类型对象。 下限。
一般取出对象的时候用。比如比较器。