文章目录
- 第三章、常用工具类
- 一、Java异常
- 1、什么是异常
- 2、异常处理
- 3、常见的异常类型
- 4、throws
- 5、throw
- 6、自定义异常
- 7、异常链
- 二、包装类
- 1、包装类
- 2、字符串与基本数据类型转换
- 3、包装类的比较
- 三、String 类
- 1、创建String对象的方法
- 2、String的常用方法
- 3、字符串的存储
- 4、字符串StringBuilder
- 1)String和StringBuilder的区别:
- 2)StringBuilder和StringBuffer
- 3)StringBuilder类的常用方法
- 四、集合
- 1、List(列表)
- 2、Set
- 3、Map
- 4、集合排序
- 1)Comparator接口
- 2)Comparable接口
- 3)Comparable和Comparator的区别
- 五、泛型
- 1、为什么使用泛型
- 2、泛型的使用
- 3、自定义泛型类
- 4、自定义泛型方法
- 六、Java输入输出流
- 1、File类
- 2、字节流
- 1)FileInputStream
- 2)FileOutputStream
- 3)缓冲流
- 3、字符流
- 4、对象序列化
- 七、多线程
- 1、什么是线程
- 2、线程的创建
- 3、线程的生命周期
- 4、线程优先级
- 5、线程同步
- 6、线程间通信
- 7、创建多线程的第三种方式
第三章、常用工具类
一、Java异常
1、什么是异常
在程序运行过程中,意外发生的情况,背离我们程序本身的意图的表现,都可以理解为异常。在Java中,通过Throwable及其子类描述各种不同的异常类型。
Throwable有两个重要的子类:Exception和Error。
Error
Error是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,表示代码运行时JVM出现的问题。
这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。因此我们编写程序时不需要关心这类异常。
Exception
Exception是程序本身可以处理的异常。
Exception类的异常包括checked exception 和 unchecked exception。
unchecked exception
编译器不要求强制处理的异常。Java编译器不会检查这些异常,在程序中可以选择捕获处理,也可以不处理。包含RuntimeException类及其子类异常。如:NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)。
checked exception
编译器要求必须处置的异常。Java编译器会检查这些异常,当程序中可能出现这类异常时,要求必须进行异常处理,否则编译不通过。RuntimeException类及其子类以外其他Exception类的子类。如IOException、SQLException。
2、异常处理
抛出异常:
当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统处理。异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
捕获异常:
在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。若未找到合适的异常处理器,则运行时系统终止,即Java程序终止。
简单地说,异常总是先被抛出,后被捕获的。
try-catch-finally
public void method(){try{//可能产生异常的代码段}catch(异常类型1 ex){//对异常进行处理的代码段1}catch(异常类型2 ex){//对异常进行处理的代码段2}finally{//无论是否发生异常都会执行的代码段,一般释放资源的代码}
}
try块后可以接零个或多个catch块,如果没有catch,则必须跟一个finally块。
catch语句的顺序:先子类,后父类
发生异常时按顺序逐个匹配,只执行第一个与异常类型匹配的catch语句。
注:
1.不执行finally块的唯一情况:在finally之前中断程序,如用System.exit(1)
2.在catch 中有return关键字时先执行finally语句,再执行return语句。
3、常见的异常类型
Exception 异常层次结构的父类
ArithmeticException 算术异常
ArrayIndexOutOfBoundsException 数组下标越界异常
NullPointerException 空指针异常
ClassNotFoundException 不能加载所需的类
IIIegalArgumentException 方法接受到非法参数
ClassCastException 类型转换异常
NumberFormatException 数字格式转换异常
4、throws
如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明出用throws子句来声明抛出异常。throws语句用在定义方法时声明该方法要抛出的异常类型,该方法将不对这些类型及其子类类型的异常作处理,而抛向调用该方法的方法。
public void method() throws Exception1,Exception2,...ExceptionN{//可能产生异常的代码
}
throws的使用规则:
1、如果是不可检查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能通过,但是运行时会被系统抛出。
2、如果一个方法中可能出现可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则编译错误。
3、当抛出了异常,则该方法的调用者必须处理货重新抛出该异常。
4、当子类重写父类抛出异常的方法时,声明的异常必须是父类方法所声明异常的同类或子类。
5、throw
throw用来抛出一个异常。
例如:throw new IOException();
throw抛出的只能是Throwable或者其子类的实例对象。抛出异常后,可以用try-catch语句捕获,也可以用throws子句声明将它抛出。
6、自定义异常
所谓自定义异常,就是定义一个类,去继承Throwable类或者它的子类。
自定义异常的常见问题:
1、自定义异常属于检查异常还是非检查异常,要看继承的父类。
2、几个异常处理的方法:
e.toString(); 获得异常类型和描述信息,当直接输出对象e时,默认调用。
e.getMessage(); 获得异常描述信息。
e.printStackTrace(); 打印出异常产生的堆栈信息,包括种类、描述信息、出错位置等。
3、自定义异常需要先经过throw抛出,才能被catch捕获,是无法自动被程序捕获并处理的。
7、异常链
捕获一个异常后再抛出另一个异常,将异常发生的原因一个传一个串起来,即把底层的异常信息传给上层,这样逐层抛出。
可以借用在异常的根类Throwable中提供的带参构造方法Throwable(String message, Throwable couse)以及初始化方法initCause(Throwable couse)实现异常链信息的传递。
二、包装类
1、包装类
基本类型 | 对应的包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
装箱 =》基本数据类型 转换成 包装类
1、自动装箱
int t1 = 2;
Integer t2 = t1;
2、手动装箱
Integer t3 = new Integer(t1);
Integer t6 = Integer.valueOf(t1);
拆箱 =》 包装类 转换成 基本数据类型
1、自动拆箱
int t4 = t2;
2、手动拆箱
Integer t5 = t2.intValue();
注:
1、类型特点:包装类是引用类型,拥有方法和属性;基本数据类型只包含数值信息。
2、存储方式:包装类对象实例化,借由new在堆空间里进行空间分配,对象栈空间中存储地址引用;基本数据类型变量对应栈空间中存储的是具体数据值。通常,包装类的效率会比基本数据类型的效率低,空间占用大。
int one = 12;
Integer two = new Integer(20);
3、初始值:基本数据类型有各自默认初始值,包装类的对象未初始化时,初始值均为null。
2、字符串与基本数据类型转换
基本数据类型转字符串:
使用包装类的toString()方法
字符串转换为基本数据类型:
自动拆箱调用包装类的parseXxx()静态方法
调用包装类的valueOf()方法转换为基本类型的包装类,自动拆箱
3、包装类的比较
1、拆箱后的数据是基础数据类型。用==判断相等性,比较的都是数值,如果是字符,比较的是ASCII值。
2、装箱后如果用==比较对象的内存地址,除Double、Float外,如数据值在缓存区范围内(-128~127),则相同;反之会重新生成对象,为不同。
3、调用equals方法时,当类型相同,且数值相同时,返回true;反之,返回false。当比对方为基本数据类型时,会进行自动装箱操作,后进行比较。
三、String 类
1、创建String对象的方法
String s1 = "hello";//创建一个字符串对象hello,名为s1
String s2 = new String();//创建一个空字符串对象,名为s2
String s3 = new String("hello");//创建一个字符串对象hello,名为s3
char[] ch = {'a','b','c'};
String s4 = new String(ch);//abc
String s5 = new String(ch,1,2);//bc 1表示从第几位开始,2表示几个元素
byte[] b = [54,69,70,71,72];
String s6 = new String(b,"utf-8");
2、String的常用方法
注:字符串索引是从0开始的。
3、字符串的存储
4、字符串StringBuilder
1)String和StringBuilder的区别:
String具有不可变性,而StringBuilder不具备。
建议:
当频繁操作字符串时,使用StringBuilder。
2)StringBuilder和StringBuffer
二者基本相似
StringBuffer是线程安全的,StringBuilder则没有,所以性能略高
在执行速度方面的比较:StringBuilder > StringBuffer
3)StringBuilder类的常用方法
四、集合
集合框架的体系结构
集合和数组的区别
1、数组的长度是固定的,集合的长度可以动态扩展。
2、数组只能存储相同数据类型的数据,而集合可以存储不同数据类型的数据。
3、数组可以存储基本数据类型数据,也可以是引用类型,而集合只能是引用类型。
1、List(列表)
List是元素有序并且可以重复的集合,称为序列
List可以精确的控制每个元素的插入位置,或删除某个位置的元素
List的两个主要实现类是ArrayList和LinkedList
ArrayList
ArrayList底层是由数组实现的
动态增长,以满足应用程序的需求
在列表尾部插入或删除非常有效
更适合查找和更新元素
ArrayList中的元素可以为null
LinkedList
与 ArrayList 一样,LinkedList 也按照索引位置排序,但它的元素之间是双向链接的
适合快速地插入和删除元素
LinkedList 实现 List 和 Queue 两个接口
2、Set
Set是元素无序并且不可以重复的集合,被称为集。
HashSet
HashSet是Set的一个重要实现类,称为哈希集(底层数组+链表实现)
HashSet中的元素无序并且不可以重复
HashSet中只允许一个null元素
具有良好的存取和查找性能
Iterator(迭代器)
Iterator接口可以以统一的方式对各种集合元素进行遍历
hasNext()方法检测集合中是否还有下一个元素
next()方法返回集合中的下一个元素、
hashCode和equals方法的作用
hashCode()方法用于给对象返回hash code值,equals()方法用于判断其他对象与该对象是否相等。为什么需要这两个方法呢?我们知道HashSet中是不允许添加重复元素的,那么当调用add()方法向HashSet中添加元素时,是如何判断两个元素是不同的。这就用到了hashCode()和equals()方法。在添加数据时,会调用hashCode()方法得到hash code值,通过这个值可以找到数据存储位置,该位置可以理解成一片区域,在该区域存储的数据的hashCode值都是相等的。如果该区域已经有数据了,就继续调用equals()方法判断数据是否相等,如果相等就说明数据重复了,就不能再添加了。如果不相等,就找到一个位置进行存储。
这些是基于哈希算法完成的,它使得添加数据的效率得到了提升。假设此时Set集合中已经有100个元素,那么如果想添加第101个元素,如果此时没有使用哈希算法,就需要调用equals()方法将第101个元素与前100个元素依次进行比较,如果元素更多,比较所耗费的时间就越长。
如果两个对象相等,那么他们的hashCode值一定相等。反之,如果两个对象的hashCode值相等,那么这两个对象不一定相等,还需要使用equals()方法进行判断。
如果不重写hashCode()方法,默认每个对象的hashCode()值都不一样,所以该类的每个对象都不会相等。
3、Map
Map中的数据是以键值对(key-value)的形式存储的
key-value以Entry类型的对象实例存在
可以通过key值快速地查找value
一个映射不能包含重复的键
每个键最多只能映射到一个值
HashMap
基于哈希表的Map接口的实现
允许使用null值和null键
key值不允许重复
HashMap中的Entry对象是无序排列的
4、集合排序
使用Collections类的sort()方法
sort(List list) -根据元素的自然顺序对指定列表按升序进行排序。
1)Comparator接口
强行对某个对象进行整体排序的比较函数。
可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort)
int compare(T o1, T o2) 比较用来排序的两个参数。
– 如果o1<o2,返回负整数
– 如果o1==o2,返回0
– 如果o1>o2,返回正整数
//1.先定义比较器
public class NameComparator implements Comparator<Cat>{@Overridepublic int compara(Cat o1, Cat o2){//按名字升序排序String name1=o1.getName();String name2=o2.getName();int n= name1.comparaTo(name2);return n;}
}
//2.调用排序
Collections.sort(catList, new NameComparator());
2)Comparable接口
此接口强行对实现它的每个类的对象进行整体排序。
这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。
对于集合,通过调用Collections. sort方法进行排序。
对于数组,通过调用Arays.sort方法进行排序。
int compareTo(T o)方法
该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
//1.定义类的时候实现Comparable接口
public class Dog implements Comparable<Dog>{private int age;...@Overridepublic int comparaTo(Dog o){//按名字升序排序int age1=this.getAge();int age2=o.getAge();int n= age2 - age1;return n;}
}
//2.直接调用sort方法排序
Collections.sort(dogList);
注:包装类和String都有自己的comparaTo方法
3)Comparable和Comparator的区别
1、Comparator接口位于java.util包下,而Comparable接口位于java.lang包下。
2、对于Comparator接口,可以看到它的compare()方法的参数是两个对象,所以可以有一个单独的类实现Comparator。
对于Comparable接口,它的方法只有一个对象作为参数,所以要比较的类需要实现Comparable接口,将当前对象与方法参数中的对象进行比较。
因此,如果使用Comparator接口,那么要比较的类和实现Comparator接口的类可以分开,如果使用Comparable接口,那么要比较的类就要实现Comparable接口才可以。
3、关于应用场景
一般情况下如果对某个类进行排序,比如Cat类,如果使用Comparable接口的方式,那么Cat类需要实现Comparable接口。
如果Cat类通过Comparable接口的方式实现排序,比如通过name排序了。那么我们还希望通过age进行排序,这时不希望修改Cat类,那此时就需要使用Comparator接口了。
因此,Comparable接口可以作为实现类的默认排序算法,Comparator接口则用于一个类的扩展排序。
五、泛型
1、为什么使用泛型
在Java中增加泛型之前,泛型程序设计使用继承来实现的
坏处:
-需要强制转换
-可向集合中添加任意类型的对象,存在风险。
2、泛型的使用
List list = new ArrayList();
Java SE7及以后的版本中,构造方法中可以省略泛型类型。
List list=new ArrayList<>();
注: 变量声明的类型必须匹配传递给实际对象的类型
其他的错误例子:
List list=new ArrayList();
List numbers=new ArrayList();
泛型作为方法参数
<? extends Goods>
表示添加的类型是Goods或子类都是允许的,extends后面的内容也可以是接口
<? super Goods>
表示添加的类型是Goods或超类都是允许的
3、自定义泛型类
public class NumGeneric<T>{private T num;//get、set...
}
NumGeneric<Integer> i = new NumGeneric<>();
i.setNum(10);
NumGeneric<Float> i = new NumGeneric<>();
i.setNum(10.5f);
4、自定义泛型方法
泛型方法不一定写在泛型类里面
public class One{public <T extends Number> void printV(T t){sout(t);}
}
//调用
One one = new One();
one.printV(10);
one.printV(10.5f);
六、Java输入输出流
流是指一连串流动的字符,以先进先出的方式发送信息的通道。
文件输入————读
文件输出————写
1、File类
什么是文件?
文件可认为是相关记录或放在一起的数据的集合。
在Java中,使用java.io.File类对文件进行操作。
注:
Windows中的目录分隔符为""
Linux中的目录分隔符为"/"
绝对路径:是从盘符开始的路径
相对路径:是从当前路径开始的路径
//创建file对象
File file1 = new File("c:\\abc\\a.txt");
File file2 = new File("c:\\abc","a.txt");
File file3 = new File("c:\\abc\\bcd");
File file4 = new File(file3,"a.txt");
file1.isDirectory();//是否是目录
file1.isFile();//是否是文件
file3.exists();//判断是否存在
file3.mkdir();//创建一个目录
file3.mkdirs();//创建多个目录
file4.createNewFile();//创建文件
file3.isAbsolute();//是否绝对路径
file3.getPath()//获取路径(构造方法的路径)
file3.getAbsolutePath();//获取绝对路径
file4.getName();//获取文件名
2、字节流
字节输入流InputStream
字节输出流OutputStream
1)FileInputStream
从文件系统中的某个文件中获得输入字节。
用于读取诸如图像数据之类的原始字节流。
FileInputStream常用方法
注:如果返回值为-1,则表示已经达到文件末尾!
2)FileOutputStream
FileOutputStream常用方法
//**文件拷贝小案例:**
try{FileInputStream fit = new FileInputStream("happy.gif");FileOutputStream fos = new FileOutputStream("happycopy.gif");int n=0;byte[] b = new byte[1024];while((n=fit.read(b))!=-1){fos.write(b,0,n);}fit.close();fos.close();
}catch(FileNotFountException e){e.printStackTrace();
}catch(IOException e){e.printStackTrace();
}
3)缓冲流
缓冲输入流BufferedInputStream
缓冲输出流BufferedOutputStream
//**文件拷贝小案例:**
try{FileInputStream fis = new FileInputStream("happy.gif");BufferedInputStream bis = new BufferedInputSream(fis);FileOutputStream fos = new FileOutputStream("happycopy.gif");BufferedOutputStream bos = new BufferedOutputSream(fos);int n=0;byte[] b = new byte[1024];while((n=bis.read(b))!=-1){bos.write(b,0,n);bos.flush();}fis.close();bis.close();fos.close();bos.close();
}catch(FileNotFountException e){e.printStackTrace();
}catch(IOException e){e.printStackTrace();
}
3、字符流
字符输入流Reader
字符输出流Writer
字节字符转换流
InputStreamReader
OutputStreamWriter
//**文件拷贝小案例:**
try{FileInputStream fis = new FileInputStream("happy.txt");InputStreamReader bis = new InputStreamReader(fis,"GBK");FileOutputStream fos = new FileOutputStream("happycopy.txt");OutputStreamWriterbos = new OutputStreamWriter(fos,"GBK");int n=0;char[] b = new char[10];while((n=bis.read(b))!=-1){bos.write(b,0,n);bos.flush();}fis.close();bis.close();fos.close();bos.close();
}catch(FileNotFountException e){e.printStackTrace();
}catch(IOException e){e.printStackTrace();
}
4、对象序列化
步骤:
-创建一个类,继承Serializable接口
-创建对象
-将对象写入文件
-从文件读取对象信息
对象输入流ObjectInputStream
对象输出流ObjectOutputStream
public class Goods implement Serializable{...
}
Goods goods1 = new Goods("gd001","电脑",3000);
try{FileOutputStream fos = new FileOutputStream("a.txt");ObjectOutputStream oos = new ObjectOutputStream(fos);FileInputStream fis = new FileInputStream("a.txt");ObjectInputStream ois = new ObjectInputStream(fis);//将Goods对象信息写入文件oos.writeObject(goods1);oos.writeBoolean(true);oos.flush();//读对象数据try{Goods goods = (Goods)ois.readObject();sout(goods);//输出:商品信息[编号:gd001,名称:电脑,价格:3000]}catch(ClassNotFoundException e){e.printStackTrace();} sout(ois.readBoolean());//输出:truefos.close();oos.close();fis.close();ois.close();
}catch(FileNotFountException e){e.printStackTrace();
}catch(IOException e){e.printStackTrace();
}
七、多线程
1、什么是线程
进程:进程是指可执行程序并存放在计算机存储器的一个指令序列,它是一个动态执行的过程
线程:是比进程还要小的运行单位,一个进程包含多个线程
2、线程的创建
创建一个Thread 类,或者一个Thread 子类的对象
创建一个实现Runnable接口的类的对象
Thread类
Thread 是一个线程类,位于 java.lang 包下
构造方法:
Thread() 创建一个线程对象
Thread(String name) 创建一个具有指定名称的线程对象
Thread(Runnable target) 创建一个基于Runnable接口实现类的线程对象
Thread(Runnable target,String name) 创建一个基于Runnable接口实现类,并且具有指定名称的线程对象。
Thread类的常用方法:
public void run() 线程相关的代码写在该方法中,一般需要重写
public void start() 启动线程的方法,一个线程实例只能启动一次
public static void sleep(long m) 线程休眠m毫秒的方法
public void join() 优先执行调用join()方法的线程
class MyThread extends Thread{public MyThread(String name){super(name);}public void run(){sout(getName()+"该线程正常执行");}
}
MyThread mt1 = new MyThread("线程1");
mt1.start();
MyThread mt2 = new MyThread("线程2");
mt2.start();
Runnable接口
只有一个方法run();
Runnable 是 Java 中用以实现线程的接口
任何实现线程功能的类都必须实现该接口
为什么要实现Runnable接口?
– Java不支持多继承
– 不打算重写Thread类的其他方法
class MyRunnable implements Runnable{int i = 1;@Overridepublic void run(){while(i<=10){sout(Thread.currentThread().getName()+ "正在运行"+(i++));}}
}
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(pr);
t1.start();
//MyRunnable mr1 = new MyRunnable();
Thread t2 = new Thread(pr);
t2.start();
//两个线程共享一个资源,总共打印10次
3、线程的生命周期
sleep方法应用
public static void sleep(long millis)
作用:在指定的毫秒数内让正在执行的线程休眠(暂停执行)
参数为休眠的时间,单位是毫秒
//在run方法里写,需要捕获异常
try{Thread.sleep(1000);
}catch(InterruptedException e){e.printStackTrace();
}
join方法应用
public final void join()
作用:等待调用该方法的线程结束后才能执行
//调用线程时执行
MyThread mt = new MyThread();
mt.start();
try{mt.join();
}catch(InterruptedException e){e.printStackTrace();
}
public final void join(long millis)
作用:等待该线程终止的最长时间为millis毫秒。
如果millis 0 为 则意味着要一直等下去。
4、线程优先级
Java为线程类提供了10个优先级
优先级可以用整数 1-10 表示,超过范围会抛出异常
主线程默认优先级为5
优先级常量
MAX_PRIORITY :线程的最高优先级 10
MIN_PRIORITY :线程的最低优先级 1
NORM_PRIORITY :线程的默认优先级 5
优先级相关的方法
public void setPriority(int newPriority) 设置线程优先级的方法,在启动线程之前执行
public int getPriority () 获取线程优先级的方法,启动线程之后执行
5、线程同步
各个线程是通过竞争 CPU 时间而获得运行机会的
各线程什么时候得到 CPU 时间,占用多久,是不可预测的
一个正在运行着的线程在什么地方被暂停是不确定的
使用关键字synchronized实现同步锁
synchronized关键字用在
– 成员方法 public synchronized void saveAccount(){}
– 静态方法 public static void saveAccount synchronized (){}
– 语句块 synchronized (obj){……}
6、线程间通信
wait()方法:中断方法的执行,使线程等待
notify()方法:唤醒处于等待的某一个线程,使其结束等待
notifyAll()方法:唤醒所有处于等待的线程,使它们结束等待
//在普通方法里写
//需要中断的时候
try{wait();
}catch(InterruptedException e){e.printStackTrace();
}
//需要唤醒的时候
notifyAll();
7、创建多线程的第三种方式
实现Calleble接口,重写call()方法,call()作为线程的执行体,具有返回值,并且可以对异常进行声明和抛出;使用start()方法来启动线程。
public class ThirdThread implements Callable<String>{@Overridepublic String call() throws Exception {//方法类型可以根据要返回值的类型进行确认String str = "多线程的第三种创建方法";return str;}
}
Callable<String> call = new ThirdThread();
FutureTask<String> ft = new FutureTask<>(call);
Thread t3 = new Thread(ft);
t3.start();
//获取call方法的返回值:先启动线程才可以获取到Call的返回值
try{sout(ft.get());
}catch (InterruptedException | ExecutionException e){e.printStackTrace();
}