java基础知识(六)——异常、IO流、多线程、反射、集合
一、异常
java程序中会出现不正常的情况,而这些情况我们统称为异常。
《java编程思想》中定义异常:阻止当前方法或作用域继续执行的问题。
1、异常体系
在java程序中会出现不正常的情况,java是面向对象的语言,任何的事物都可以使用类进行描述,那么这时候sun公司就是用了很多的类描述了java程序中各种不正常的情况,而用于描述程序不正常的情况的类被称为异常类,很多异常类堆积起来就形成了java中的异常体系。
等级一:Throwable(所有异常或者错误类的超类)
等级二:Error(错误-------错误一般是用于jvm或者是硬件引发的问题,所以我们一般不会通过代码去处理错误的。)
Exception(异常-------是需要通过代码去处理的):
编译时异常(异常较严重)
运行时异常(异常较轻量)
2、Throwable类常用的方法:
toString():返回当前异常对象的完整类名+异常信息。
getMessage():返回此 throwable 的详细消息字符串。
printStackTrace():打印异常的栈信息。
3、如何区分错误与异常?
程序出现了不正常的信息
如果不正常的信息的类名是以Error结尾的,那么肯定是一个错误。(ThreadDeath)
如果是以Exception结尾的,那么肯定就是一个异常。
4、异常的处理:
方式一:捕获处理
捕获处理的格式:
try{
可能发生异常的代码;
}catch(捕获的异常类型 变量名){
处理异常的代码....
}
捕获处理要注意的细节:
a. 如果try块中代码出了异常经过了处理之后,那么try-catch块外面的代码可以正常执行。
b. 如果try块中出了异常的代码,那么在try块中出现异常代码后面的代码是不会执行了。
c. 一个try块后面是可以跟有多个catch块的,也就是一个try块可以捕获多种异常的类型。
d. 一个try块可以捕获多种异常的类型,但是捕获的异常类型必须从小到大进行捕获,否则编译报错。
实例:
public class TestException01 {
public static void main(String[] args) {
int[] arr = null;
div(4,0,null);
}
public static void div(int a,int b,int[] arr){
int c = 0;
try{
c = a/b;
System.out.println(arr.length);
}catch(ArithmeticException e){
e.printStackTrace();
System.out.println("nisb ");
}catch(NullPointerException e){
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}
System.out.println("c="+c);
}
}
抛出处理(throw throws)关键字
抛出处理要注意的细节:
a. 如果一个方法的内部抛出了一个 编译时异常对象,那么必须要在方法上声明抛出。
b. 如果调用一个声明抛出 编译时异常的方法,那么调用者必须要处理异常。
c. 如果一个方法内部抛出了一个异常对象,那么throw语句后面的代码都不会再执行了(一个方法遇到了throw关键字,该方法也会马上停止执行的)。
d. 在一种情况下,只能抛出一种类型异常对象。
throw 与throws两个关键字的区别:
1. throw关键字是用于方法内部的,
throws是用于方法声声明上的。
2. throw关键字是用于方法内部抛出一个异常对象的,
throws关键字是用于在方法声明上声明抛出异常类型的。
3. throw关键字后面只能有一个异常对象,
throws后面一次可以声明抛出多种类型的 异常。
二、集合
集合的体系:
1、Collection 单例集合的根接口
1.1---List:如果是实现了List接口的集合类, 该集合类具备的特点:有序(添加进去的顺序与元素出来的顺序是一致的。),可重复。
1.1.1---ArrayListArrayList:底层是维护了一个线性结构的Object数组实现的,特点: 查询速度快,增删慢。
1.1.2---LinkedList:LinkedList底层是使用了链表数据结构实现的,特点: 查询速度慢,增删快。
1.2---Set:如果是实现了Set接口的集合类, 该集合类具备的特点:无序(添加进去的顺序与元素出来的顺序不一致的。),不重复。
1.2.1---HashSet:底层是使用了哈希表来支持的,特点: 存取速度快.。
1.2.2---TreeSet:如果元素具备自然顺序的特性,那么就按照元素自然顺序的特性进行排序存储。
2、Map 双例集合的根接口
具备的特点: 存储的数据都是以键值对的形式存在的,键不可重复,值可以重复。
2.1---HashMap(线程不安全)
HashMap的存储原理:
往HashMap添加元素的时候,首先会调用键的hashCode方法得到元素的哈希码值,然后经过运算就可以算出该元素在哈希表中的存储位置。
------ 情况1:如果算出的位置目前没有任何元素存储,那么该元素可以直接添加到哈希表中。
------ 情况2:如果算出的位置目前已经存在其他的元素,那么还会调用该元素的equals方法与这个位置上的元素进行比较,如果equals方法返回的是 false,那么该元素允许被存储,如果equals方法返回的是true,那么该元素被视为重复元素,不允存储。
2.2---TreeMap
特点:会对元素的键进行排序存储。
TreeMap 要注意的事项:
1. 往TreeMap添加元素的时候,如果元素的键具备自然顺序,那么就会按照键的自然顺序特性进行排序存储。
2. 往TreeMap添加元素的时候,如果元素的键不具备自然顺序特性,那么键所属的类必须要实现Comparable接口,把键的比较规则定义在CompareTo 方法上。
3. 往TreeMap添加元素的时候,如果元素的键不具备自然顺序特性,而且键所属的类也没有实现Comparable接口,那么就必须在创建TreeMap对象的时 候传入比较器。
2.3---HashTable(线程安全)
3、集合使用迭代器遍历:
public static void main(String[] args) {
Iterator iter = list.iterator();
while(iter.hasNext()){
//遍历list集合里的所有元素
System.out.println(iter.next());
}
}
三、IO流
IO技术主要的作用是解决设备与设备之间的数据传输问题。
1、输入流
------输入字节流:使用缓冲数组配合循环一起读取
--------| InputStream 所有输入字节流的基类 抽象类
--------| FileInputStream 读取文件数据的输入字节流 :
使用FileInputStream读取文件数据的步骤:
1. 找到目标文件
2. 建立数据的输入通道。
3. 读取文件中的数据。
4. 关闭资源。
实例代码:
public static void readTest4() throws IOException{
long startTime = System.currentTimeMillis();
//找到目标文件
File file = new File("D:\\a.txt");
//建立数据的输入通道
FileInputStream fis = new FileInputStream(file);
//建立缓冲数组配合循环读取文件的数据。
int length = 0; //保存每次读取到的字节个数。
byte[] buf = new byte[1024];
//存储读取到的数据 缓冲数组的长度一般是1024的倍数,
因为与计算机的处理单位。理论上缓冲数组越大,效率越高
while((length = fis.read(buf))!=-1){
// read方法如果读取到了文件的末尾,那么会返回-1表示。
System.out.print(new String(buf,0,length));
}
//关闭资源
fis.close();
}
------输入字符流:使用缓冲字符数组读取文件。
--------FileReader:
示例代码:
public static void readTest2() throws IOException{
//找到目标文件
File file = new File("D:\\Demo1.java");
// 建立数据的输入通道
FileReader fr = new FileReader(file);
//建立缓冲字符数组读取文件数据
char[] buf = new char[1024];
int length = 0 ;
while((length = fr.read(buf))!=-1){
System.out.print(new String(buf,0,length));
}
//关闭资源
fr.close();
}
---------BufferedReader:缓冲输入字符流
2、输出流
------输出字节流:使用字节数组把数据写出。
--------| OutputStream 是所有输出字节流 的父类。 抽象类
--------| FileOutStream 向文件输出数据的输出字节流。
使用FileOutStream输出文件数据的步骤:
1. 找到目标文件
2. 建立数据的输出通道。
3. 把数据转换成字节数组写出。
4. 关闭资源
实例代码:
public static void writeTest3() throws IOException{
//找到目标文件
File file = new File("D:\\b.txt");
//建立数据输出通道
FileOutputStream fos = new FileOutputStream(file);
//把数据写出。
String data = "abc";
byte[] buf = data.getBytes();
// 0 从字节数组的指定索引值开始写, 2:写出两个字节。
fos.write(buf, 0, 3);
//关闭资源
fos.close();
}
------输出字符流
-----FileWriter:
实例代码:
public static void writeTest1() throws IOException{
//找到目标文件
File file = new File("D:\\a.txt");
//建立数据输出通道
FileWriter fw = new FileWriter(file,true);
//准备数据,把数据写出
String data = "今天天气非常好!!";
fw.write(data); //字符流具备解码的功能。
//刷新字符流
//fw.flush();
//关闭资源
fw.close();
}
--------BufferedWriter:缓冲输出字符流
四、反射
Java反射机制:
程序在运行状态中,对于任意一个类(class文件),都能知道这个类的所有属性和方法;对于任意一个对象, 都能够调用它的任意一个方法和属性;这种动态获取 信息以及动态调用对象方法或属性的功能称为Java语言的反射机制。
在反射技术中一个雷的任何成员都有对于的类进行描述:构造器——》Constructor类;成员变量——》Field类;方法——》Method类;
动态代理:
在java的动态代理机制中,有两个重要的类或接口。一个是 ImvocationHandler(Interface)、另一个则是Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每一个代理类的实例都关联到了一个handler,让我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发由InvocationHandler这个接口的invoke方法来进行调用。
实例:
public interface Subject
{
public void rent();
public void hello(String str);
}
public class RealSubject implements Subject
{
@Override
public void rent()
{
System.out.println("I want to rent my house");
}
@Override
public void hello(String str)
{
System.out.println("hello: " + str);
}
}
public class DynamicProxy implements InvocationHandler
{
//这个就是我们要代理的真实对象
private Object subject;
//构造方法,给我们要代理的真实对象赋初值
public DynamicProxy(Object subject)
{
this.subject = subject;
}
@Override
public Object invoke(Object object, Method method, Object[] args)
throws Throwable
{
//在代理真实对象前我们可以添加一些自己的操作
System.out.println("before rent house");
System.out.println("Method:" + method);
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象
//关联的handler对象的invoke方法来进行调用
Object result = method.invoke(subject, args);
//在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after rent house");
return result;
}
}
public class Client
{
public static void main(String[] args)
{
//我们要代理的真实对象
Subject realSubject = new RealSubject();
//我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler handler = new DynamicProxy(realSubject);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,
* 我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),
* 我们这里为代理对象提供的接口是真实对象所实行的接口,
* 表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler,我们这里将这个代理对象关联到了
* 上方的 InvocationHandler 这个对象上
*/
Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
.getClass().getInterfaces(), handler);
System.out.println(subject.getClass().getName());
subject.rent();
subject.hello("world");
}
}
五、多线程
进程 :正在执行的程序称作为一个进程。进程负责了内存空间的划分。
线程:线程在一个进程中负责了代码的执行,就是进程中一个执行路径
多线程:在一个进程中有多个线程同时在执行不同的任务。
一个java应用程序至少有几个线程?
答:至少有两个线程,一个是主线程负责main方法代码的执行,一个是垃圾回收器线程,负责了回收垃圾。
多线程的好处与弊端
好处:
1. 解决了一个进程能同时执行多个任务的问题。
2. 提高了资源的利用率。
弊端:
1. 增加cpu的负担。
2. 降低了一个进程中线程的执行概率。
3. 引发了线程安全问题。
4. 出现了死锁现象
创建线程的方式:
方式一:
1. 自定义一个类继承Thread类。
2. 重写Thread类的run方法 , 把自定义线程的任务代码写在run方法中
疑问: 重写run方法的目的是什么?
每个线程都有自己的任务代码,jvm创建的主线程的任务代码
就是main方法中的所有代码, 自定义线程的任务代码就
写在run方法中,自定义线程负责了run方法中代码。
3. 创建Thread的子类对象,并且调用start方法开启线程。
线程的生命周期:
创建:新创建了一个线程对象。
可运行:线程对象创建后,其他线程调用了该对象的start()方法。 该状态的线程位于可运行线程池中,变得可运行,等待获取cpu的执行权。
运行:就绪状态的线程获取了CPU执行权,执行程序代码。
阻临时塞: 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
死亡:线程执行完它的任务时。