IO与反射机制总结

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接口

对象序列化的步骤:

  1. 创建一个对象输出流(ObjectOutputStream),它可以包装一个其他类型的输出流

    如文件输出流FileOutputStream。代码如下:

    ObjectOutputStream oos=new ObjectOutputStraem("C:/docStudetnt.txt")

    创建了对象

输出流oos,包装了一个文件输出流,即参数

2.通过对象输出流的writerObject(obj)方法写对象,也就是输出可序列化对象

例如将学生对象保存到文件中,其实现步骤如下:

  1. 引入相关类

  2. 创建学生类,实现serializable接口

  3. 创建对象输出流

  4. 调用writeObject()方法将对象写入文件

  5. 关闭流对象

     

代码:

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.反序列化获取对象信息

  • 反序列化:是从特定存储介质中读取数据并重新构建成对象的过程。通过反序列化,可以将存储在文件中的对象信息读取出来,然后重新构建成对象

  • 反序列化的步骤大致分为两步:

  1. 创建一个对象流(ObjectInputStream),它可以包装一个其他类型的输入流,如文件输入流FileInputStream

  2. 通过对象输入流的readObject()方法读取对象,该方法返回一个Object类型的对象,如果程序知道该java的类型,则可以将该对象强制转换成其真实的类型

  • 使用反序列化读取文件中的学生对象,其实现步骤如下:

    1. 引入相关类

    2. 创建对象输入流

    3. 调用readObject()方法读取对象

    4. 关闭对象输入流

       

  • 代码:

    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.对象引用的序列化

  • 如果一个类的成员包含其他类的对象,如班级类中包含学生类的对象,那么当序列化班级对象时,则必须保证班级类和学生类都是可序列化的

  • 序列化的算法规则如下:

    1. 所有的保存到磁盘的对象都有一个序列号

    2. 当程序试图序列化一个对象时,将会检查是否已经被序列化,只有序列化后的对象才能被成字节序列输出

    3. 如果对象已经被序列化,则程序直接输出一个序列化的编号,而不再重新进行序列化

  • 如果向文件中使用序列化机制写入多个对象,那么反序列化恢复对象时,必须按照写入的对象读取

  • 如果一个可序列化的类,有多个父类(包括直接父类和间接父类),则这些父类的要么是可序列化的,要么有无参数的构造器,否则会抛出异常

  • 在进行对象的序列化时,一般所有的属性都会被序列化,但是对于一些比较敏感的信息,一旦被序列化后,人们完全可以通过读取文件或拦截网络传输数据的方式获得这些信息。因此,出于安全考虑,某些属性被序列化。解决的办法是使用transient来修饰,例如:private trabsient String userpass;

四.反射机制

1.功能:

  1. 使用反射获取类的信息

  2. 使用反射创建对象

  3. 使用反射访问属性和方法

  4. 使用Array动态创建和访问数组

2.认识反射

2.1反射机制

java的反射机制是java的特性之一,反射机制是构建框架技术的基础所在

java反射机制是指在运行状态中,动态获取类的信息(类的属性或类的方法)以及动态调用类的方法的功能

java反射有三个动态性质:

  1. 运行时生成对象实例

  2. 运行期间调用方法

  3. 运行时更改属性

java反射机制能够知道类的基本结构,这种对java类结构探知的能力,成为java的“自审”。使用eclipse时,java代码的自动提示功能就是利用java的反射原理,是对所创建对象的探知和自审。

2.2java反射常用API

使用java反射技术常用的类如下:

  1. Class类:反射的核心类,反射所有的操作都是围绕该类来生成的。通过Class类,可以获取类的属性、方法等内容信息

  2. Field类:表示类的属性,可以获取和设置类中属性的值

  3. Method类:表示类的方法,可以用来获取类中方法的信息,或者执行方法

  4. Constructor类:表示类的构造方法

2.3java程序中使用反射的基本步骤:

  1. 导入java.lang.reflect.*;

  2. 获取需要操作的类的java.lang.Class对象

  3. 调用Class的方法获取Field、Method等对象

  4. 使用反射API进行操作

     

3.反射的应用

3.1获取类的信息

通过反射获取类的对象分为两步,首先获取Class对象,然后通过Class对象获取信息

  • 获取Class对象

    每个类加载后,系统都会为该类生成一个对应的Class对象,通过该对象就可以访问java虚拟机中的这个类

    获取类对象的三种方式:

    1. 调用对象的getClass()方法,该方法是java.lang.Object类中的一个方法,所有对象都可以调用该方法,该方法会返回该对象所属的类对应的Class的对象

      Student s=new Student();
      Class clazz=s.getClass();//clazz为Class对象
    2. 调用类的class属性

      调用某个类的class属性可以获取该类对应的Class对象,这种方式需要在编译期间知道类的名称

      Class clazz=Student.class;

       

    3. 使用Class类的forName()静态方法

      在使用该方法时需要传入字符串参数,该字符串的值是一个类的全名,即要在类前加上其所属哪个包

      Class clazz=Class.forName("java.lang.String")//正确
      Class clazz=Class.forName("String")//错误
      //如果输入的字符的值不是类的全名,则会抛出一个ClassNotFoundException异常

       

总结:后两种方式都是直接根据类的属来获取类的对象,相比之下调用某个类的class属性来获取该类对应的Class对象这种方式更有优势,其原因如下:

  1. 代码更安全,程序在编译阶段就可以检查需要访问的Class对象是否存在

  2. 程序性能更高,因为这种方式无需调用方法 ,所以性能更好

3.2从Class对象获取信息

在获取某个对象所对应的Class对象之后,程序就可以调用Class对象的方法来获取该类的详细信息

  1. 访问Class对应的类所包含的构造方法,参考java高级特性P99

  2. 访问Class对应的类所包含的方法P99

  3. 访问Class对应的类所包含的属性P99

3.3创建对象

通过反射来创建对象有以下两种方式:

  1. 使用Class对象的new Instance()方法创建对象,要求对应类有默认的构造方法,而执行该方法其实就是利用默认的构造方法来创建该类的实例

  2. 使用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对象需要以下三个步骤:

  1. 获取该类的Class对象

  2. 利用Constructor()方法啊来获取指定的构造方法

  3. 调用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访问类的方法

使用Method对象可以调用对象的方法。在Method类中包含一个invoke()方法,定义方法如下:

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对象,从配置文件读取的只是某个类的字符串类名,程序需要根据字符串来创建对象的实例,就必须使用反射。