IO與反射機制總結
一.file類:屬於java.io包中kkb
作用:操作文件或目錄
file既可以表示文件,也可以表示目錄,也可以表示盤符。利用他可以用來對文件進行操作。
file中常用的構造方法
方法 | 說明 |
---|---|
File(String pathname) | 在指定的目錄下創建指定文件名的名稱 |
File(String parent,String child) | parent參數指定目錄,child參數指定文件名 |
File(File parent,String child) | parent參數是目錄對象,child參數指定文件名 |
file中的常用方法:
方法名稱 | 參數 | 作用 | 返回值 |
---|---|---|---|
delete() | 無 | 刪除目錄或文件 | boolean:操作結果 |
exists() | 無 | 判斷目錄或文件是否存在 | boolean:是否存在 |
getAbsolutePath() | 無 | 獲取絕對路徑 | String:絕對路徑 |
getName() | 無 | 獲取文件或目錄名稱 | String:文件或目錄名稱 |
isDirectory() | 無 | 判斷是否為目錄 | boolean:是否為目錄 |
isFile() | 無 | 判斷是否為文件 | boolean:是否為文件 |
length() | 無 | 獲取文件長度 | long:位元組數量 |
mkdirs() | 無 | 創建多級目錄 | boolean:是否創建成功 |
file類操作文件和目錄屬性的步驟如下:
-
導入file類
-
構造一個file對象
-
利用file類的方法訪問文件或目錄的屬性
二.認識java流
流是是計算機輸入輸出操作中流動的數據序列,java對數據的操作都通過流的方式,java用於操作流的對象都在IO包中,IO流是用來處理設備之間的數據傳輸
java的輸入輸出流主要是由OutputStream和Writer類作為基類,而輸入流則主要是由InputStream和Reader作為基類
按流向分類可以分為:
-
輸入流:OutputStream和Writer作為基類
-
輸出流:InputStream和Reader作為基類
按照處理數據單元來分劃分:
-
位元組流:位元組輸入流InputStream和輸出流OutputStream作為基類
-
字符流:字符輸入流Reader和字符輸出流Writer
位元組流是8位通用位元組流,其基本單位是位元組
字符流是16位Unicode字符流,基本單位Unicode字符,字符流最適合用來處理字符串和文本,應為他們支持世界上大多數的字符集和語言
四大基類都是抽象類
InputStream類的常用子類有FileInputStream,用於從文件中讀取數據
InputStream類的常用方法
方法 | 說明 |
---|---|
int read() | 從輸入流中讀取下一個位元組數據 |
int read(byte[] b) | 從輸入流中讀取數據,並將數據存儲在緩衝區b數組中,返回實際讀取的位元組數 |
int read(byte[] b,int offset,int length) | 從輸入流中讀取最長length個位元組,保存到數組b中,保存位置從offset開始 |
void close() | 關閉流 |
OutputStream類的常用子類有FileOutputStream類,用於向文件中寫數據
方法 | 說明 |
---|---|
void write(int c) | 將指定的位元組數據寫入此輸入流中 |
void wirte(byte[] b) | 將數組b 中的所有位元組全部寫入此輸入流中 |
void write(byte[] b,int offset ,int length) | 將位元組數組中從偏移量offset開始的長度為lenth的位元組數據輸出到輸出流中 |
void close() | 關閉流 |
Reader類的常用子類為BufferedReader,接受Reader對象作為參數,並對其添加字符緩衝器
方法 | 說明 |
---|---|
int read() | 從輸入流中讀取單個字符,返回所讀取的字符數據 |
int read(char[] c) | 從輸入流中讀取最多c.length個字符,保存到字符數組c中,返回實際讀取的字符數 |
int read(char[] c,int offset,int len) | 從輸入流中最多讀取len個字符,保存到字符數組中,保存的位置從offset位置開始,返回實際讀取的字符數 |
void close() | 關閉輸入流 |
Writer類的常用子類為BufferedWriter,用於將數據緩衝到字符輸出流
方法 | 說明 |
---|---|
void write(String str) | 將str字符串里包含的字符輸出到指定的輸出流中 |
void write(String str,int offset,int len) | 將str字符串里從offset位置開始,長度為len的多個字符輸出到輸出流中 |
void close() | 關閉流 |
void flush() | 刷新輸出流 |
注意:所有的與流相關的方法在出現錯誤時都會拋出IOException異常
三.使用對象流讀寫對象信息
步驟:
-
使用序列化將對象保存到文件中
-
使用反序列化從文件中讀取對象信息
1.認識序列化
序列化:就是將對象的狀態(對象的屬性)存儲到特定存儲介質中的過程,也就是將對象狀態轉化為可保存或可傳輸格式的過程
序列化的核心:(1)保存對象的狀態(2)對象狀態可儲存
使用序列化的意思是將java對象序列化後,可以將其轉換為位元組序列,這些位元組序列可以被保存在磁盤上,也可以通過網絡進行傳輸,實現了
平台無關性
2.序列化保存對象信息
序列化機制允許將實現了序列化的java對象轉換為位元組序列,這個過程需要藉助io流來實現
java中只有實現了java.io.Serializable接口的類的對象才能被序列化,Serializable表示可串行化、可序列化,所以,對象序列化在某些文獻上也被稱為串行化。JDK類庫中有些類,如String類、包裝類和Date類都實現了Serializable接口
對象序列化的步驟:
-
創建一個對象輸出流(ObjectOutputStream),它可以包裝一個其他類型的輸出流
如文件輸出流FileOutputStream。代碼如下:
ObjectOutputStream oos=new ObjectOutputStraem("C:/docStudetnt.txt")
創建了對象
輸出流oos,包裝了一個文件輸出流,即參數
2.通過對象輸出流的writerObject(obj)方法寫對象,也就是輸出可序列化對象
例如將學生對象保存到文件中,其實現步驟如下:
-
引入相關類
-
創建學生類,實現serializable接口
-
創建對象輸出流
-
調用writeObject()方法將對象寫入文件
-
關閉流對象
代碼:
package com.gcy.test;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerilaizableDemo {
public static void main(String[] args) {
ObjectOutputStream oos = null;
try {
// 創建序列化對象
oos = new ObjectOutputStream(new FileOutputStream("D:/student"));
// 創建被序列化的對象
Student student = new Student("張三", 100);
// 開始序列化
oos.writeObject(student);
System.out.println("序列化完成");
} catch (Exception e) {
System.out.println("文件操作失敗");
} finally {
// 關閉流
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3.反序列化獲取對象信息
-
反序列化:是從特定存儲介質中讀取數據並重新構建成對象的過程。通過反序列化,可以將存儲在文件中的對象信息讀取出來,然後重新構建成對象
-
反序列化的步驟大致分為兩步:
-
創建一個對象流(ObjectInputStream),它可以包裝一個其他類型的輸入流,如文件輸入流FileInputStream
-
通過對象輸入流的readObject()方法讀取對象,該方法返回一個Object類型的對象,如果程序知道該java的類型,則可以將該對象強制轉換成其真實的類型
-
使用反序列化讀取文件中的學生對象,其實現步驟如下:
-
引入相關類
-
創建對象輸入流
-
調用readObject()方法讀取對象
-
關閉對象輸入流
-
-
代碼:
package com.gcy.test;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class ReverseSerilalizableDemo {
public static void main(String[] args) {
//創建反序列化對象ois
ObjectInputStream ois=null;
try {
ois=new ObjectInputStream(new FileInputStream("d:/student"));
//開始反序列化
Student s = (Student)ois.readObject();
//輸出反序列化後的學生狀態
System.out.println("學生姓名為:"+s.getName()+",成績為"+s.getScore());
System.out.println("反序列化完成");
} catch (Exception e) {
System.out.println("操作文件失敗");
}finally {
//關閉流
if(ois!=null) {
try {
ois.close();
} catch (Exception e2) {
e2.getStackTrace();
}
}
}
}
}
4.對象引用的序列化
-
如果一個類的成員包含其他類的對象,如班級類中包含學生類的對象,那麼當序列化班級對象時,則必須保證班級類和學生類都是可序列化的
-
序列化的算法規則如下:
-
所有的保存到磁盤的對象都有一個序列號
-
當程序試圖序列化一個對象時,將會檢查是否已經被序列化,只有序列化後的對象才能被成位元組序列輸出
-
如果對象已經被序列化,則程序直接輸出一個序列化的編號,而不再重新進行序列化
-
-
如果向文件中使用序列化機制寫入多個對象,那麼反序列化恢復對象時,必須按照寫入的對象讀取
-
如果一個可序列化的類,有多個父類(包括直接父類和間接父類),則這些父類的要麼是可序列化的,要麼有無參數的構造器,否則會拋出異常
-
在進行對象的序列化時,一般所有的屬性都會被序列化,但是對於一些比較敏感的信息,一旦被序列化後,人們完全可以通過讀取文件或攔截網絡傳輸數據的方式獲得這些信息。因此,出於安全考慮,某些屬性被序列化。解決的辦法是使用transient來修飾,例如:private trabsient String userpass;
四.反射機制
1.功能:
-
使用反射獲取類的信息
-
使用反射創建對象
-
使用反射訪問屬性和方法
-
使用Array動態創建和訪問數組
2.認識反射
2.1反射機制
java的反射機制是java的特性之一,反射機制是構建框架技術的基礎所在
java反射機制是指在運行狀態中,動態獲取類的信息(類的屬性或類的方法)以及動態調用類的方法的功能
java反射有三個動態性質:
-
運行時生成對象實例
-
運行期間調用方法
-
運行時更改屬性
java反射機制能夠知道類的基本結構,這種對java類結構探知的能力,成為java的「自審」。使用eclipse時,java代碼的自動提示功能就是利用java的反射原理,是對所創建對象的探知和自審。
2.2java反射常用API
使用java反射技術常用的類如下:
-
Class類:反射的核心類,反射所有的操作都是圍繞該類來生成的。通過Class類,可以獲取類的屬性、方法等內容信息
-
Field類:表示類的屬性,可以獲取和設置類中屬性的值
-
Method類:表示類的方法,可以用來獲取類中方法的信息,或者執行方法
-
Constructor類:表示類的構造方法
2.3java程序中使用反射的基本步驟:
-
導入java.lang.reflect.*;
-
獲取需要操作的類的java.lang.Class對象
-
調用Class的方法獲取Field、Method等對象
-
使用反射API進行操作
3.反射的應用
3.1獲取類的信息
通過反射獲取類的對象分為兩步,首先獲取Class對象,然後通過Class對象獲取信息
-
獲取Class對象
每個類加載後,系統都會為該類生成一個對應的Class對象,通過該對象就可以訪問java虛擬機中的這個類
獲取類對象的三種方式:
-
調用對象的getClass()方法,該方法是java.lang.Object類中的一個方法,所有對象都可以調用該方法,該方法會返回該對象所屬的類對應的Class的對象
Student s=new Student();
Class clazz=s.getClass();//clazz為Class對象 -
調用類的class屬性
調用某個類的class屬性可以獲取該類對應的Class對象,這種方式需要在編譯期間知道類的名稱
Class clazz=Student.class;
-
使用Class類的forName()靜態方法
在使用該方法時需要傳入字符串參數,該字符串的值是一個類的全名,即要在類前加上其所屬哪個包
Class clazz=Class.forName("java.lang.String")//正確
Class clazz=Class.forName("String")//錯誤
//如果輸入的字符的值不是類的全名,則會拋出一個ClassNotFoundException異常
-
總結:後兩種方式都是直接根據類的屬來獲取類的對象,相比之下調用某個類的class屬性來獲取該類對應的Class對象這種方式更有優勢,其原因如下:
-
代碼更安全,程序在編譯階段就可以檢查需要訪問的Class對象是否存在
-
程序性能更高,因為這種方式無需調用方法 ,所以性能更好
3.2從Class對象獲取信息
在獲取某個對象所對應的Class對象之後,程序就可以調用Class對象的方法來獲取該類的詳細信息
-
訪問Class對應的類所包含的構造方法,參考java高級特性P99
-
訪問Class對應的類所包含的方法P99
-
訪問Class對應的類所包含的屬性P99
3.3創建對象
通過反射來創建對象有以下兩種方式:
-
使用Class對象的new Instance()方法創建對象,要求對應類有默認的構造方法,而執行該方法其實就是利用默認的構造方法來創建該類的實例
-
使用Constructor對象創建對象
使用Comstructor對象創建對象,要先使用Class對象獲取指定的Constructor對象
代碼:
package com.gcy.test;
import java.util.Date;
public class ReflectTest {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
//利用newInstance()進行對象的創建
Class clazz=Date.class;
Date date=(Date) clazz.newInstance();
System.out.println(date);
}
}
如果創建java對象時不是利用默認的構造方法,而是使用指定的構造方法,則可以利用Constructor對象,每個Constructor對應一個構造方法。指定的構造方法創建java對象需要以下三個步驟:
-
獲取該類的Class對象
-
利用Constructor()方法啊來獲取指定的構造方法
-
調用Constructord newInstance()方法創建java對象
代碼:
package com.gcy.test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
public class ReflectTest {
public static void main(String[] args) throws Exception {
//利用Constructor對象指定的構造方法創建對象
//1.獲取Date類指定的Class對象
Class clazz=Date.class;
//獲取Date類中帶有一個長整型參數的構造方法
Constructor cu=clazz.getConstructor(long.class);
//3.調用Constructor中的newInstance()方法
Date date=(Date)cu.newInstance(2000);
System.out.println(date);
}
}
3.4訪問類的屬性
使用Field對象可以獲取對象的屬性。
方法 | 說明 |
---|---|
Xxx getXxx(Object obj) | 該方法中對應8中基本數據類型 |
Object get(Object obj) | 得到應用類型的屬性值,例如Student s=new Student();nameField.get(s);//nameField為Field對象 |
void setXxx(Object obj,Xxx val) | 將對象的屬性設置成val值。此處Xxx對應的是8中基本數據類型 |
void set(Object obj,Object val) | 將obj對象的該屬性設置成為val值。針對;引用類賦值 |
void setAccessible(bool flag) | 對獲取的屬性設置訪問權限。參數為true,可以對私有屬性取值和賦值 |
代碼:
package com.gcy.test;
import java.lang.reflect.Field;
public class StudentTest {
public static void main(String[] args) throws Exception {
//1.創建Student對象
Student s=new Student();
//2.獲取Student對應的Class對象
Class clazz=Student.class;
//3.獲取Student類的name屬性,使用getDeclaredField()
Field field=clazz.getDeclaredField("name");
//4.設置通過反射訪問該Field是取消權限檢查
field.setAccessible(true);
//5.調用set()方法為s對象指定Field設置值
field.set(s, "孫利");
System.out.println(s.getName());
//相同的步驟為age賦值
Field field1=clazz.getDeclaredField("score");
field1.setAccessible(true);
field1.setInt(s, 98);
System.out.println(s.getScore());
}
}
3.5訪問類的方法
Object invoke(Object obj,Object args);其中obj是執行該方法的對象,args是執行該方法是傳入的參數
代碼:
package com.gcy.test;
import java.lang.reflect.Field;
public class StudentTest {
public static void main(String[] args) throws Exception {
//1.創建Student對象
Student s=new Student();
//2.獲取Student對應的Class對象
Class clazz=Student.class;
//3.獲取Student類的name屬性,使用getDeclaredField()
Field field=clazz.getDeclaredField("name");
//4.設置通過反射訪問該Field是取消權限檢查
field.setAccessible(true);
//5.調用set()方法為s對象指定Field設置值
field.set(s, "孫利");
System.out.println(s.getName());
//相同的步驟為age賦值o
五總結
使用反射雖然會很大程度上提高代碼的靈活性,但是不能濫用反射,因為通過反射創建對象時性能要稍微低一些。實際上,只有當程序需要動態的創建某個類的對象時才會考慮使用反射,通常在開發通用性比較廣的框架、基礎平台時可能會大量使用反射。因為在很多java框架中都需要根據配置文件信息來創建java對象,從配置文件讀取的只是某個類的字符串類名,程序需要根據字符串來創建對象的實例,就必須使用反射。