来源https://course.tianmaying.com/java-basic+java-environment
十四、异常处理
异常定义了程序中遇到的非致命的错误,比如如程序要打开一个不存的文件、网络连接中断、除零操作、操作数越界、装载一个不存在的类等情况。
先来看看下面的程序代码:
package com.tianmaying;
public class HelloWorld {
public static void main(String[] args) {
int x = 5 / 0;
System.out.println(x);
}
}
编译运行上面的程序,将出现如下错误:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.tianmaying.HelloWorld.main(HelloWorld.java:5)
上面的程序运行的结果报告发生了算术异常(
ArithMethicException
),应用执行提前结束,这种情况就是我们所说的异常。
Java异常强制我们去考虑程序的强健性和安全性,其在设计时就考虑到这些问题,提出了一套异常处理的解决方案。将上面的程序代码进行如下修改:
package com.tianmaying;
public class HelloWorld {
public static void main(String[] args) {
try {
int x = 5 / 0;
System.out.println(x);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("program is still running here!");
}
}
程序运行结果如下:
java.lang.ArithmeticException: / by zero
at com.tianmaying.HelloWorld.main(HelloWorld.java:6)
program is still running here!
我们将可能出现异常的代码通过try/catch
代码进行了处理,当异常发生时,系统能够继续运行,而没有意外终止。
当try
代码块中的语句发生了异常,程序就会跳转到catch
代码块中执行,执行完catch代码块中的程序代码后,系统会继续执行catch
代码块之后的代码,try代码块中发生异常语句后的代码则不会再执行。比如如程序中的System.out.println(x);
不会再被执行。
异常发生时,系统会将代码行号,异常类别等信息封装到一个对象中,并将这个对象传递给catch
代码块,catch
代码块是以下面的格式出现的。
catch
关键字后跟有一个用括号括起来的Exception
类型的参数e
,这跟我们经常用到的如何定义一个函数接收的参数格式是一样的。
括号中的Exception
就是try
代码块传递给catch
代码块的变量类型,e
就是变量名,所以我们也可以将e
改用成别的名称(如ex
),如下所
每个try语句必须有一个或多个catch语句对应,try代码块与catch代码块代码块之间不能有其他语句。
十五、Java IO
Java IO 是一套Java用来读写数据(输入和输出)的API。大部分程序都要处理一些输入,并由输入产生一些输出。Java为此提供了java.io
包,主要涉及文件,网络数据流,内存缓冲等的输入输出。
Java IO中的一个核心概念是流(Stream),从外部(包括磁盘文件、键盘、网络套接字)读入到内存中的数据序列称为输入流,从内存写入到外部设备的数据序列称为输出流。
流中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因此Java中的流分为两种:
- 字节流:数据流中最小的数据单元是字节,多用于读取或书写二进制数据
- 字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节
字节流:
字节流的最顶层是两个抽象类:InputStream
和
OutputStream
,其他关于处理字节的类都是它们的子类,这些子类对不同的外设进行处理,例如磁盘文件,网络连接,甚至是内存缓冲区。
抽象类InputStream
和 OutputStream
中定义两个关键的抽象方法read()
和write()
,它们分别对数据的字节进行读写,其子类重载完成特定输入输出方式的对应实现。
下面的代码通过FileInoutStream
和FileOutputStream
来完成文件内容的拷贝:
package com.tianmaying;
import java.io.*;
public class CopyFileByte {
public static void main(String args[]) throws IOException
{
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("input.txt");
out = new FileOutputStream("output.txt");
int c;
while ((c = in.read()) != -1) { // 返回-1 表示达到文件结尾
out.write(c);
}
}finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
字节流的每次操作都是一个数据单位——
字节。假如input.txt文件中包含“Hello world”,那么它将复制完'H'之后,再复制'e',接下来复制'l',直到其结束。
in.read()
每次从输入流中读取一个字节,如果达到文件末尾就返回
-1
。注意使用完输入输出流,一定调用
close()
方法将其关闭。
字符流
Java 字符流用于处理16位 unicode 的输入和输出。字符流的两个顶层抽象类是Reader
和Writer
,分别定义了关键方法read()
和write()
,表示对字符的读写。
用字符流的方式进行文件内容拷贝的代码如下:
package com.tianmaying;
import java.io.*;
public class CopyFileCharacter {
public static void main(String args[]) throws IOException
{
FileReader in = null;
FileWriter out = null;
try {
in = new FileReader("input.txt");
out = new FileWriter("output.txt");
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
}finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
这里使用的
FileReader
和
FileWriter
,它们操作的最小单位是一个字符16 bits,而
FileInputStream
和
FileOutputStream
最小单位则是一个字节8 bits.
所有的编程语言都提供了对标准 I/O 流的支持,即用户可以从键盘上进行输入,并且从控制台屏幕上输出。Java提供了以下的三种标准流:
- Standard Input: 用以将数据输入给用户的程序,通常键盘作为标准输入流,表示为
System.in
,其类型是InputStream
- Standard Output:用以输出用户程序产生的数据,通常控制台屏幕作为标准输出流,表示为
System.out
,其类型是PrintStream
- Standard Error: 这是用来输出用户产生的错误数据,通常控制台屏幕作为标准错误流,表示为
System.err
,类型和System.out
相同是PrintStream
InputStreamReader
来读标准输入流直到用户输入字符 "q":
package com.tianmaying;
import java.io.*;
class ConsoleInOut {
public static void main(String args[])throws IOException{
InputStreamReader isr = null;
try {
isr = new InputStreamReader(System.in);
System.out.println("Enter characters, 'q' to quit.");
char c;
do {
c = (char) isr.read();
System.out.println(c);
} while(c != 'q');
}finally {
if (isr != null) {
isr.close();
}
}
}
}
如果我们在控制台中输入
tianmaying!
,则控制台的输入如下
Enter characters, 'q' to quit.
tianmaying!
t
i
a
n
m
a
y
i
n
g
!
q
从控制台读入单个字符我们用了
read()
方法,读入字符串则可以使用
readLine()
方法,该方法返回的是
String
类型的字符串对象。
回顾我们已经学习过的Scanner
,创建Scanner
对象时需要用System.in
作为它的参数, Scanner
从System.in
取得用户输入的内容后,进一步提供了更易于我们调用的方法,如:
next()
:取得一个字符串hasNext()
:是否还有输入nextInt()
:将取得的字符串转换成int
类型的整数nextFloat()
:将取得的字符串转换成float
型nextBoolean()
:将取得的字符串转换成boolean
型
使用Scanner
非常方便,但也有不足。Scanner取得输入的依据是空格符,包括空格键、Tab键和回车键等。当按下这些键时,Scanner
就会返回下一个输入。因此当你输入的内容中间包括空格时,显然使用Scanner
不能完整的获得你输入的字符串。
文件处理最常用的两个流是FileInputStream
和FileOutputStream
。
对于FileInputStream
,可以以文件名的字符串为参数来创建一个输入流对象去读文件:
InputStream f = new FileInputStream("D:/java");
也可以以一个文件对象作为参数来去创建一个输入流对象去读文件,首先我们需要创建一个
File
文件对象。
File f = new File("D:/java");
InputStream f = new FileInputStream(f);
对于
FileOutputStream
类似。
package com.tianmaying;
import java.io.*;
public class fileStreamTest{
public static void main(String args[]){
try{
byte bWrite [] = {10,20,30,40,50};
OutputStream os = new FileOutputStream("test.txt");
for(int x = 0; x < bWrite.length ; x++){
os.write( bWrite[x] ); // writes the bytes
}
os.close();
InputStream is = new FileInputStream("test.txt");
int size = is.available();
for(int i = 0; i< size; i++){
System.out.print((char)is.read() + " ");
}
is.close();
} catch(IOException e){
System.out.print("IOException");
}
}
}
上一节中创建文件输入输出流时,我们用到了
File
类。
File
类用于进行文件(以及目录)的相关操作,位于
java.io
包中。对一个文件进行操作,可以创建一个
File
对象:
File file = new File("input.txt"); //文件位于当前目录下
File file = new File("D:/java","input.txt"); //文件位于/home/user目录下
package com.tianmaying;
import java.io.*;
public class TestAbstract {
public static void main(String args[]) throws IOException {
File dir = new File("D:/java");
File file1 = new File(dir, "fileTest001.txt");
File file2 = new File(dir, "fileTest002.java");
if (!dir.exists())
dir.mkdir();
if (!file1.exists())
file1.createNewFile();
if (!file2.exists())
file2.createNewFile();
System.out.println("file1's AbsolutePath= " + file1.getAbsolutePath());
18 System.out.println("file2's AbsolutePath= " + file2.getAbsolutePath());
System.out.println("file1 Canread=" + file1.canRead());
System.out.println("file1's len= " + file1.length());
String[] fileList = dir.list();
System.out.println("there are " + fileList.length + " file(s) in D:/java");
}
}
输出如下:
1file1's AbsolutePath= D:\java\fileTest001.txt
file2's AbsolutePath= D:\java\fileTest002.java
file1 Canread=true
file1's len= 0
there are 12 file(s) in D:/java
十二、反射
反射是一种动态获取信息以及动态调用对象方法的机制。在程序运行状态中,通过反射能够知道某个类具有哪些属性和方法;能够访问某一个对象的方法和属性。
具体来说,反射机制主要提供了以下功能:
•在运行时判断任意一个对象所属的类;
•在运行时构造任意一个类的对象;
•在运行时判断任意一个类所具有的成员变量和方法;
•在运行时调用任意一个对象的方法;
•生成动态代理。
任何一个类都有getClass()方法,调用该方法即可获得一个表示类信息的对象Class。
当然,我们获取Class并不仅仅是希望做类似getName()这样简单的操作,一般会进一步创建实例和访问方法属性等操作
通过反射可以获取一个类的父类以及它实现的接口:
你可能会说直接使用new
操作创建对象不就行了吗? 试想如果你需要从一个文件里读入类的名字,然后根据这个名字来创建类,这个时候new
操作就无能为力了,而上面代码中的方式就有用武之地了。
通过反射,不仅可以获取属性的名称,还能获取属性类型和修饰符,这些信息存储在Field
对象中。
通过Field
对象,可以改变对应属性的可见性,也可以通过set()
方法进行属性的赋值:
通过反射,可以获取方法的整个签名信息,这些信息存储在Method
对象中:
通过Method
对象,也可以通过invoke()
方法进行方法调用:
十七、日期处理
Java 8在java.time
包中包含了一组全新的时间日期API
Clock
类提供了访问当前日期和时间的方法。Clock
是时区敏感的,可以用来取代System.cur
rentTimeMillis()
来获取代表当前时间的微秒数。某一个特定的时间点也可以使用Instant
类来表示,Instant
也可以用来创建老的java.util.Date
对象。
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant
新API中时区使用ZoneId
来表示。时区可以很方便的使用静态方法of
来获取到。
时区定义了到UTS时间的时间差,在Instant
时间点对象到本地日期对象之间转换的时候是极其重要的。
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());
LocalTime
定义了一个没有时区信息的时间,例如晚上7点。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2)); // false
long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
LocalDate
表示了一个确切的日期,比如
2015-04-10。该对象值是不可变的,用起来和
LocalTime
基本一致。下面的例子展示了如何给
Date
对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
LocalDateTime
同时表示了时间和日期,相当于前两节内容合并到一个对象上了。
LocalDateTime
、
LocalTime
和
LocalDate
都是不可变的。
LocalDateTime
提供了一些能访问具体字段的方法。
LocalDateTime time = LocalDateTime.of(2015, Month.April, 10, 4, 10, 1);
DayOfWeek dayOfWeek = time.getDayOfWeek();
附加上时区信息,就可以将其转换为一个时间点
Instant
对象,
Instant
时间点对象可以很容易的转换为老式的
java.util.Date
:
在实际项目中,我们一般会通过Date
对象来对时间进行存储,但是Date
对象并没有开放相关的API对时间进行操作(比如修改当前hour的值)。此时,我们可以通过下列方式来在LocalDateTime
与Date
之间进行互相转换,将Date
转换为LocalDateTime
后再对时间进行操作
Date
转换为LocalDateTime
Date date = new Date();
Instant instant = Instant.ofEpochMilli(date.getTime());
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
LocalDateTime
转换为Date
LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
Date date = Date.from(zdt.toInstant());
十八、第三方库
Java开发中最经常做的一件事情就是管理项目的依赖,所谓的依赖其实就是第三方的库,以JAR包的形式存在。JAR包就是把编译好的类用Zip格式压缩存储在一个文件中。引入JAR包,我们就能够使用JAR包公共的类及其方法和属性。
Java开发中大多数常用的功能其实都有非常优秀的第三方库实现了,给我们提供了很多“轮子”,我们造汽车的时候直接拿来就可以了。
我们现在需要进行文件操作,这里我们准备使用Apache Commons IO提供的一个文件操作库来实现博客信息的加载和存储。
当然我们也可以使用JDK自带的IO功能来实现,不过你需要学习Java IO相关的类和接口。
事实上了解API可能是成为一个编程高手花时间最多的一个地方。当然你也不可能把所有API完全记下来。
在软件开发中也有二八原则,少数的API能够胜任开发中的大部分场景。所以你只需要掌握这些关键部分,当在新场景下遇到不能解决的新问题时,再去查看帮助文档。所以帮助文档的阅读也是技术学习的一个关键因素。了解了基础原理之后一般就能上手开发了,帮助文档可以帮助你在实践中学习。
回到我们的项目,要使用第三方的库,需要将其加入到类路径当中。
- 到Apache Commmons IO官方网站下载最新的库,现在的版本是2.4。
- 解压之后,找到commons-io-2.4.jar,在项目中新建一个文件夹,命名为lib,将该文件拷贝进入lib文件夹。
- 在Eclipse中右键点击该文件,【Build Path】->【Add to Build Path】就将这个JAR包加入到类路径当中了。只需要在代码中,进行
import
就能使用其中的类了。
将来我们引入其他JAR包也可以按照相同的方式来进行操作。
让一个类只能创建一个实例的做法,是一种典型的设计模式,成为 Singleton模式。设计模式你可能还是第一次听说,你现在只要知道设计模式是一种常用的定义类的方式就好了,这些方式能更好地适应某些特定的应用场景。