目录
第一节:异常
异常的分类
• Throwable有两个重要的子类:Exception 和 Error
Error
• Error是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java虚拟机)出现的问题。
• 例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError
Exception
• Exception是程序本身可以处理的异常。异常处理通常指针对这种类型异常的处理。
• Exception类的异常包括 checked exception 和 uncheckedexception。
unchecked exception
• unchecked exception:编译器不要求强制处置的异常。
• 包含RuntimeException类及其子类异常。
• 如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是unchecked exception 。
• Java编译器不会检查这些异常,在程序中可以选择捕获处理,也可以不处理,照样正常编译通过。
checked exception
• checked exception:编译器要求必须处置的异常。
• 是RuntimeException及其子类以外,其他的Exception类的子类。 如IOException、SQLException等
• Java编译器会检查这些异常,当程序中可能出现这类异常时,要求必须进行异常处理,否则编译不会通过。
异常处理
多重catch块
• 一旦某个catch捕获到匹配的异常类型,将进入异常处理代码。一经处理结束,就意味着整个try-catch语句结束。其他的catch子句不再有匹配和捕获异常类型的机会。
• 对于有多个catch子句的异常程序而言,应该尽量将捕获底层异常类的catch子 句放在前面,同时尽量将捕获相对高层的异常类的catch子句放在后面。否则,捕获底层异常类的catch子句将可能会被屏蔽。
try-catch-finally
• try块后可以接零个或多个catch块
• 如果没有catch,则必须跟一个finally块
• finally无论是否发生异常都执行(唯一不执行的情况,catch中包含System.exit(1)语句)。
常见的异常类型
throws
• 如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常。
• 例如:汽车在运行时可能会出现故障,汽车本身没办法处理这个故障,那就让开车的人来处理。
• throws语句用在方法定义时声明该方法要抛出的异常类型。
• 当方法抛出异常列表中的异常时,方法将不对这些类型及其子类类型的异常作处理,而抛向调用该方法的方法,由他去处理。
throw
• throw用来抛出一个异常。
• 例如:throw new IOException();
• throw 抛出的只能够是可抛出类Throwable 或者其子类的实例对象。
• 例如:throw new String(“出错啦”); 是错误的
自定义异常
• 使用Java内置的异常类可以描述在编程时出现的大部分异常情况。
• 也可以通过自定义异常描述特定业务产生的异常类型。
• 所谓自定义异常,就是定义一个类,去继承Throwable类或者它的子类。
public class HotelAgeException extends Exception {
public HotelAgeException() {
super("18岁以下,80岁以上的住客必须有亲友陪同!");
}
}
异常链
•有时候我们会捕获一个异常后再抛出另一个异常
•顾名思义就是:将异常发生的原因一个传一个串起来,即把底层的异常信息传给上层,这样逐层抛出
public class TryDemoFive {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
testThree();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
//获取消息
StringBuilder builder = new StringBuilder();
builder.append(e.getMessage());
Throwable causeTwo = e.getCause();
if (causeTwo != null) {
builder.append("\n"+causeTwo.getMessage());
Throwable causeOne = causeTwo.getCause();
if (causeOne != null) {
builder.append("\n"+causeOne.getMessage());
}
}
txtInfo.setText(builder.toString());
}
}
public static void testOne() throws HotelAgeException {
throw new HotelAgeException();
}
public static void testTwo() throws Exception {
try {
testOne();
} catch (HotelAgeException e) {
throw new Exception("我是新产生的异常1",e);
}
}
public static void testThree() throws Exception {
try {
testTwo();
} catch (Exception e) {
Exception e1=new Exception("我是新产生的异常2");
e1.initCause(e);
throw e1;
// throw new Exception("我是新产生的异常2",e);
}
}
}
第二节:包装类
包装类与基本数据类型
• 装箱:把基本数据类型转换成包装类
– 自动装箱
– 手动装箱
• 拆箱:把包装类转换成基本数据类型
– 自动拆箱
– 手动拆箱
//自动装箱
int t1=1;
Integer t2=t1;
//手动装箱
Integer t3=new Integer(t1);
//自动拆箱
int t4=t2;
//手动拆箱
int t5=t3.intValue();
double t6=t3.doubleValue();
整型和字符串转换
//基本数据类型转换为字符串
int t1=2;
String s1=Integer.toString(t1);
//字符串转换为基本数据类型
//1、包装类的parse
int t2=Integer.parseInt(s1);
//2、包装类的valueOf将字符串转换为包装类,再通过自动拆箱完成基本数据类型转换
int t3=Integer.valueOf(s1);
常量池
java为了提高运行效率,提供了一个缓冲区(对象池、常量池),当使用valueOf创建包装类时,值的范围在-128-127时,会从常量池查找是否存在这样的对象,如果有将会直接产生,如果没有就会实例化一个对象。
除float、double以外,其他基本类型都可以应用常量池概念。
Integer one=new Integer(100);
Integer two=new Integer(100);
System.out.println("one==two的结果:"+(one==two));//1
//结果是false。因为使用new关键字创建的两个对象在内存中是两个不同的空间,而==比较的是两个引用对象地址是否相同,所以结果是false。
Integer three=100;//自动装箱
//相当于Integer three=Integer.valueOf(100);
System.out.println("three==100的结果:"+(three==100));//2 自动拆箱
//结果为true。引用类型和基本类型比较,先拆箱后比较,所以结果为false
//Integer four=100;
Integer four=Integer.valueOf(100);
System.out.println("three==four的结果:"+(three==four));//3
//结果为true。因为three和four同时指向常量池中同一个位置。
Integer five=200;
System.out.println("five==200的结果:"+(five==200));//4
//结果为true。原因同2
Integer six=200;
System.out.println("five==six的结果:"+(five==six));//5
//结果为false。数值200超出常量池范围,会通过new创建新的对象,所以结果为false。
第三节:字符串
一、String的常用方法
二、String与byte数组间的转换
String str="Java 编程 基础";
//默认utf-8编码
byte[] bytes=str.getBytes();
String strNew=new String(bytes);
//使用GBK编码,保持字符集一致,否则会出现乱码
byte[] bytes1=str.getBytes("GBK);
String strNew1=new String(bytes,"GBK");
三、等于运算符与equals的区别
==比较地址;equals比较内容
四、StringBuilder类
String类具有不可变性,一旦创建长度不可以再变更。
StringBuilder类是可变长度的字符串。
StringBuilder类常用方法:
第四节:集合
Java中的集合是工具类,可以存储任意数量的具有共同属性的对象。
应用场景
• 无法预测存储数据的数量
• 同时存储具有一对一关系的数据
• 需要进行数据的增删改查
• 数据重复问题
集合框架的体系结构
List(列表)
• List是元素有序并且可以重复的集合,称为序列
• List可以精确的控制每个元素的插入位置,或删除某个位置的元素
• List接口的两个主要实现类是ArrayList和LinkedList
ArrayList
• ArrayList底层是由数组实现的
• 动态增长,以满足应用程序的需求
• 在列表尾部插入或删除非常有效
• 更适合查找和更新元素
• ArrayList中的元素可以为null
Set(集)
• Set是元素无序并且不可以重复的集合,被称为集
Set接口常用方法
add(E e)
clear()
contains(Object o)
equals(Object o)
hashCode()
isEmpty()
iterator()
remove(Object o)
size()
toArray()
Hash Set
• Hash Set是Set的一个重要实现类,称为哈希集
• Hash Set中的元素无序并且不可以重复
• Hash Set中只允许一个null元素
• 具有良好的存取和查找性
//构造Set集
Set set=new HashSet();
//添加数据
set.add("Yellow");
set.add("Red");
set.add("Blue");
set.add("Black");
set.add("White");
//输出数据
Iterator it=set.iterator();
while(it.hasNext()){
System.out.print(it.next()+" ");
}
//插入新的元素
set.add("green");//无序,显示顺序与插入顺序无关
//插入重复数据
set.add("White");//插入失败不报错
Map(集合)
• Map中的数据是以键值对(key--value)的形式存储的
• key--value以Entry类型的对象实例存在
• 可以通过key值快速地查找value
• 一个映射不能包含重复的键
• 每个键最多只能映射到一个值
Map接口常用方法
clear()
entrySet() 获取键值对的所有的内容,返回类型Set<Map.Entry<K,V>>
get(Object key) 获取value的值
keySet()获取所有key的值,返回类型Set<K>
put(K key,V value) 添加元素
remove(Object key)
values() 获取values的集合,返回类型Conllection<V>
size()
HashMap
• 基于哈希表的Map接口的实现
• 允许使用null值和null键
• key值不允许重复
• HashMap中的Entry对象是无序排列
HashMap案例
public class Goods {
private String id;//商品编号
private String name;//商品名称
private double price;//商品价格
//构造方法
public Goods(String id,String name,double price){
this.id=id;
this.name=name;
this.price=price;
}
//getter和setter方法
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String toString(){
return "商品编号:"+id+",商品名称:"+name+",商品价格:"+price;
}
}
public class GoodsTest {
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
// 定义HashMap对象
Map<String, Goods> goodsMap = new HashMap<String, Goods>();
System.out.println("请输入三条商品信息:");
int i = 0;
while (i < 3) {
System.out.println("请输入第" + (i + 1) + "条商品信息:");
System.out.println("请输入商品编号:");
String goodsId = console.next();
// 判断商品编号id是否存在
if (goodsMap.containsKey(goodsId)) {
System.out.println("该商品编号已经存在!请重新输入!");
continue;
}
System.out.println("请输入商品名称:");
String goodsName = console.next();
System.out.println("请输入商品价格:");
double goodsPrice = 0;
try {
goodsPrice = console.nextDouble();
} catch (java.util.InputMismatchException e) {
System.out.println("商品价格的格式不正确,请输入数值型数据!");
console.next();
continue;
}
Goods goods = new Goods(goodsId, goodsName, goodsPrice);
// 将商品信息添加到HashMap中
goodsMap.put(goodsId, goods);
i++;
}
// 遍历Map,输出商品信息
System.out.println("商品的全部信息为:");
Iterator<Goods> itGoods = goodsMap.values().iterator();
while (itGoods.hasNext()) {
System.out.println(itGoods.next());
}
}
}
第五节:多线程
一、线程vs进程
1、一般每个应用对应一个进程(不重复运行程序)。
2、一个进程包含多个线程。
3、进程独占内存,线程共享内存。
二、线程的创建
• 创建一个Thread类,或者一个Thread子类的对象
• 创建一个实现Runnable接口的类的对象
三、Thread类
• Thread是一个线程类,位于java.lang包下
• Thread类的常用方法
四、Runnable接口
• 只有一个方法run();
• Runnable是Java中用以实现线程的接口,
• 任何实现线程功能的类都必须实现该接口
• 为什么要实现Runnable接口?
- Java不支持多继承
-不打算重写Thread类的其他方法
五、线程的状态
• 新建(New)
• 可运行(Runnable)
• 正在运行(Running)
• 阻塞(Blocked)
• 终止(Dead)
• Thread类的方法
public static void sleep(long millis)
• 作用:在指定的毫秒数内让正在执行的线程休眠(暂停执行)
• 参数为休眠的时间,单位是毫秒
public final void join()
• 作用:等待调用该方法的线程结束后才能执行其他线程(霸占cpu资源,线程调用join方法后,会一直占用cpu直到该线程结束)。
public final void join(long millis)
• 作用:等待等待该线程终止的最长时间为millis毫秒,如果millis为0则一直等下去。(线程线程抢占cpumillis毫秒后释放,或线程结束提前释放)。
五、线程优先级
• Java为线程类提供了10个优先级
• 优先级可以用整数1-10表示,超过范围会抛出异常
• 主线程默认优先级为5
• 优先级常量
-MAX_PRIORITY:线程的最高优先级10
- MIN_PRIORITY:线程的最低优先级1
- NORM_PRIORITY:线程的默认优先级5
•优先级相关的方法
private static final String TAG = "MainActivity-priority";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int mainPriority = Thread.currentThread().getPriority();
Log.d(TAG, "onCreate: 主线程的优先级为:" + mainPriority);
Thread thread1 = new MyThread("线程1");
Thread thread2 = new MyThread("线程2");
thread1.setPriority(Thread.MAX_PRIORITY);
thread2.setPriority(Thread.MIN_PRIORITY);
thread1.start();
thread2.start();
Log.d(TAG, "onCreate: 线程1的优先级为:" + thread1.getPriority());
Log.d(TAG, "onCreate: 线程2的优先级为:" + thread2.getPriority());
}
class MyThread extends Thread {
private String mName;
public MyThread(String name) {
this.mName = name;
}
@Override
public void run() {
super.run();
try {
for (int i = 0; i < 50; i++) {
sleep(100);
Log.d(TAG, "run: 线程" + mName + "正在运行" + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
备注:优先级高不一定就先运行!
六、线程同步
线程同步:多个线程对同一个对象操作可能导致数据混乱。
解决:使用关键字synchronized将对象进行锁定。
• synchronized关键字用在
- 成员方法
- 静态方法
- 语句块
public synchronized void saveAccount(){}
public static synchronized void saveAccount(){}
synchronized (obj){……}
理解:
1、多个线程访问一个对象的synchronized方法(或代码块),同一时间只能有一个线程执行,其他等待。
2、对象的synchronized方法(或代码块)被一个线程执行时,其他线程仍可以访问对象的非synchronized方法(或代码块)。
3、对象有多个synchronized方法(或代码块)时,一个synchronized方法(或代码块)被执行其他都阻塞。
4、对象P1的synchronized方法(或代码块)被一个线程执行时,同属一个类的另一个对象P2的synchronized方法(或代码块)的访问不受影响。
举例:
男人类:方法1按摩(synchronized);方法2洗脚(synchronized);方法3说话。
线程1:老婆的命令。
线程2:老妈的命令。
//伪代码
public class Man {
//体力,只能一点点消耗,防止多个方法同时存取,需要同步锁。
private int mStrength=100;
//按摩方法:同一时间只能给一个人按摩
public synchronized void massage(){
for(int i=0;i<10;i++){
System.out.println("按呀按!");
mStrength--;
sleep(1000);
}
}
//洗脚方法:同一时间只能给一个人洗脚
public synchronized void footBath(){
for(int i=0;i<10;i++){
System.out.println("洗呀洗!");
mStrength--;
sleep(1000);
}
}
//说话方法:可以同时和多个人说话
public void talk(){
for(int i=0;i<10;i++){
System.out.println("说呀说!");
sleep(1000);
}
}
}
public static void main(String[] args){
Man zhangSan=new Man();
Man geBiLaoWang=new Man();
//老婆的命令
Thread wifeOrder=new Thread(new Runnable(){
@Override
public void run(){
zhangSan.massage();
zhangSan.footBath();
zhangSan.talk();
geBiLaoWang.massage();
}
},"老婆的命令");
//老妈的命令
Thread momOrder=new Thread(new Runnable(){
@Override
public void run(){
zhangSan.massage();
zhangSan.footBath();
zhangSan.talk();
}
},"老妈的命令");
wifeOrder.start();
momOrder.start();
}
synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。
七、线程间通信
• wait()方法:中断方法的执行,使线程等待
• notify()方法:唤醒处于等待的某一个线程,使其结束等待
• nofifyAll()方法:唤醒所有处于等待的线程,使它们结束等待
案例:生产者消费者问题。
产品队列:
public class Queue {
private int n;
boolean flag=false;
public synchronized int get() {
if(!flag){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("消费:"+n);
flag=false;//消费完毕,容器中没有数据
notifyAll();
return n;
}
public synchronized void set(int n) {
if(flag){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("生产:"+n);
this.n = n;
flag=true;//生产完毕,容器中已经有数据
notifyAll();
}
}
生产者:
public class Producer implements Runnable{
Queue queue;
Producer(Queue queue){
this.queue=queue;
}
@Override
public void run() {
int i=0;
while(true){
queue.set(i++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
消费者:
public class Consumer implements Runnable{
Queue queue;
Consumer(Queue queue){
this.queue=queue;
}
@Override
public void run() {
while(true){
queue.get();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
测试:
public class Test {
public static void main(String[] args) {
Queue queue=new Queue();
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}
Queue队列的get和set方法添加synchronized 是为了防止多个线程同时读写n,导致错误。
为了保证生产消费一一对应,不会出现生产一次消费多次或者生产多次消费一次的情况,加入flag标记和wait()方法。
加入notifyAll()方法防止线程锁死。
补充线程池内容,参考:https://www.cnblogs.com/cnmenglang/p/6273761.html
第六节:输入输出流
一、文件
常用方法:
canRead、canWrite、isExists、getName、isDirectory、isFile、mkDir、mkDirs、lastModified、isHidden/
二、字节流
字节输入流InputStream
字节输出流OutputStream
三、字符流
字符输入流Reader
字符输出流Writer
备注:字符流操作时使用缓冲区,在关闭字符流时会强制性地将缓冲区中的内容进行输出,但是如果程序没有关闭,则缓冲区中的内容是不输出的,强制输出需要时用flush方法。
字节流与字符流主要的区别:
流分类:
1、字节流:InputStream是所有字节输入流的祖先,而OutputStream是所有字节输出流的祖先。
2、字符流:Reader是所有读取字符串输入流的祖先,而writer是所有输出字符串的祖先。
InputStream,OutputStream,Reader,writer都是抽象类。
字节流是最基本的,要用在处理二进制数据,它是按字节来处理的,但实际中很多的数据是文本,又提出了字符流的概念,它是按虚拟机的Encode来处理,也就是要进行字符集的转化。这两个之间通过 InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联。在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的。
在从字节流转化为字符流时,实际上就是byte[]转化为String时,
public String(byte bytes[], String charsetName)
有一个关键的参数字符集编码,通常我们都省略了,那系统就用操作系统的lang
而在字符流转化为字节流时,实际上是String转化为byte[]时,
byte[] String.getBytes(String charsetName)
也是一样的道理
至于java.io中还出现了许多其他的流,按主要是为了提高性能和使用方便,
如BufferedInputStream,PipedInputStream等
四、对象的序列化和反序列化
• 步骤:
-创建一个类,继承Serializable接口
-创建对象
-将对象写入文件(网络)
-从文件(网络)读取对象信息
• 对象输入流ObjectInputStream
• 对象输出流ObjectOutputStream
序列化Goods类:
public class Goods implements Serializable{
private String goodsId;
private String goodsName;
private double price;
public Goods(String goodsId,String goodsName,double price){
this.goodsId=goodsId;
this.goodsName=goodsName;
this.price=price;
}
public String getGoodsId() {
return goodsId;
}
public void setGoodsId(String goodsId) {
this.goodsId = goodsId;
}
public String getGoodsName() {
return goodsName;
}
public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "商品信息 [编号:" + goodsId + ", 名称:" + goodsName
+ ", 价格:" + price + "]";
}
}
测试类:
public class GoodsTest {
public static void main(String[] args) {
// 定义Goods类的对象
Goods goods1 = new Goods("gd001", "电脑", 3000);
try {
FileOutputStream fos = new FileOutputStream("imooc.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
FileInputStream fis = new FileInputStream("imooc.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
// 将Goods对象信息写入文件
oos.writeObject(goods1);
oos.writeBoolean(true);
oos.flush();
// 读对象信息
try {
//写入多个对象时读取顺序要相同,否则会报错
Goods goods = (Goods) ois.readObject();
System.out.println(goods);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(ois.readBoolean());
fos.close();
oos.close();
fis.close();
ois.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
序列化:把Java对象转换为字节序列的过程。
反序列化:把字节序列恢复为Java对象的过程。