反射 和 IO流

三、反射

知识点总结:

  • 动态语言
    – 程序运行时,可以改变程序结构或变量类型。典型的语言:
    • Python、ruby、javascript等。
  • C, C++, JAVA不是动态语言,JAVA可以称之为“准动态语言”。但是JAVA有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
  • JAVA的动态性让编程的时候更加灵活!
  • 反射机制
    – 指的是可以于运行时加载、探知、使用编译期间完全未知的类。
    – 程序在运行状态中,可以动态加载一个只有名称的类,对于任意一个
    已加载的类,都能够知道这个类的所有属性和方法;对于任意一个对
    象,都能够调用它的任意一个方法和属性;
Class c = Class.forName("com.bjsxt.test.User");

– 加载完类之后,在堆内存中,就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

  • Class类介绍
    • java.lang.Class类十分特殊,用来表示java中类型(class/interface/enum/annotation/primitive type/void)本身。
    – Class类的对象包含了某个被加载类的结构。一个被加载的类对应一个
    Class对象。
    – 当一个class被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM 便自动产生一个Class 对象。
    • Class类是Reflection的根源。
    – 针对任何您想动态加载、运行的类,唯有先获得相应的Class 对象。

  • Class对象的获取方式:
    • 运用getClass()
    • 运用Class.forName()(最常被使用)
    • 运用.class 语法

  • 反射机制常见作用:
    •动态加载类、动态获取类的信息(属性、方法、构造器)
    • 动态构造对象
    • 动态调用类和对象的任意方法、构造器
    • 动态调用和处理属性
    • 获取泛型信息
    • 处理注解

  • 反射操作泛型:
    • Java采用泛型擦除的机制来引入泛型。Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦。但是,一旦编译完成,所有的和泛型有关的类型全部擦除。
    • 为了通过反射操作这些类型以迎合实际开发的需要,Java就新增了ParameterizedType,GenericArrayType,TypeVariable 和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
    • ParameterizedType: 表示一种参数化的类型,比如Collection
    • GenericArrayType: 表示一种元素类型是参数化类型或者类型变量的数组类型
    • TypeVariable: 是各种类型变量的公共父接口
    • WildcardType: 代表一种通配符类型表达式,比如?, ? extends Number, ? super Integer【wildcard是一个单词:就是“通配符”】

  • 反射操作注解:
    可以通过反射API:getAnnotations, getAnnotation获得相关的注解信息

//获得类的所有有效注解
Annotation[] annotations=clazz.getAnnotations();
for (Annotation a : annotations) {
System.out.println(a);
}
//获得类的指定的注解
SxtTable st = (SxtTable) clazz.getAnnotation(SxtTable.class);
System.out.println(st.value());
//获得类的属性的注解
Field f = clazz.getDeclaredField("studentName");
SxtField sxtField = f.getAnnotation(SxtField.class);
System.out.println(sxtField.columnName()+"-- "+sxtField.type()+"--"+sxtField.length());
  • 反射机制性能问题:
    setAccessible
    – 启用和禁用访问安全检查的开关,值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。并不是为true就能访问为false就不能访问。
    – 禁止安全检查,可以提高反射的运行速度。

  • 常见方法:

/**
 * 通过反射API动态的操作:构造器、方法、属性
 * 
 * @author moxi
 *
 */
public class Demo03 {
	public static void main(String[] args) {
		String path = "com.moxi.test.bean.User";

		try {
			Class<User> clazz = (Class<User>) Class.forName(path);
			// 通过反射API调用构造方法,构造对象
			User user = clazz.newInstance();// 其实是调用了User的无参构造方法
			System.out.println(user);

			Constructor<User> c = clazz.getDeclaredConstructor(String.class, int.class, int.class);
			User user2 = c.newInstance("moxi", 18, 1001);
			System.out.println(user2.getUname());

			// 通过反射API调用普通方法
			User user3 = clazz.newInstance();
			Method method = clazz.getDeclaredMethod("setUname", String.class);
			method.invoke(user3, "moxi2");// user3.setUname("moxi2");
			System.out.println(user3.getUname());

			// 通过反射API操作属性
			User user4 = clazz.newInstance();
			Field f = clazz.getDeclaredField("uname");
			f.setAccessible(true);// 这个属性不需要做安全检查了,可以直接访问
			f.set(user4, "moxi3");// 通过反射直接写属性
			System.out.println(user4.getUname());// 通过反射直接读属性的值
			System.out.println(f.get(user4));

		} catch (Exception e) {

			e.printStackTrace();
		}

	}
}

四、 IO流

简要补充

(1)

  • DataInputStream和DataOutputStream
    • 提供了可以存取所有Java基础类型数据(如:int,double 等)和String的方法。
    • 处理流,只针对字节流,二进制文件
  • 对象序列化(序列化机制可以用来“克隆“对象)
    • 构造器私有化,也可以通过序列化创造对象。
    •所有的单例类,枚举类在实现序列化时都应该提供readResolve()方法。
    • 对象序列化 (Serialization) • 将Java对象转换成字节序列(IO字节流)
    • 对象反序列化 (DeSerialization) • 从字节序列中恢复Java对象
  • 为什么序列化
    • 序列化以后的对象可以保存到磁盘上,也可以在网络上传输,使得不同的计算机可以共享对象.(序列化的字节序列是平台无关的)
  • 对象序列化的条件
    • 只有实现了Serializable接口的类的对象才可以被序列化。Serializable接口中没有任何的方法,实现该接口的类不需要实现额外的方法。
    • 如果对象的属性是对象,属性对应类也必须实现Serializable接口
  • 如何实现序列化
    • 创建ObjectOutputStream对象
    • 调用writeObject()输出对象
  • 如何实现反序列化
    • 创建ObjectInputStream对象
    • 调用readObject()读取对象
OutputStream fos = new FileOutputStream(new File("d:/java6.txt"));
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(stu);
oos.close();


InputStream fis = new FileInputStream(new File("d:/java6.txt"));
ObjectInputStream ois = new ObjectInputStream(fis);
Student stu = (Student)ois.readObject();
System.out.println(stu.getAge()+" "+stu.getScore());

  • 序列化能保存的元素
    • 只能保存对象的非静态成员变量
    • 不能保存任何成员方法和静态的成员变量
    • 不保存transient成员变量
    • 如果一个对象的成员变量是一个对象,这个对象的成员变量也会保存
    • 串行化保存的只是变量的值,对于变量的任何修饰符,都不能保存
    • 使用对象流把一个对象写到文件时不仅保证该对象是序列化的,而且该对象的成员对象也必须是可序列化的。
    • 如果一个可序列化的对象包含对某个不可序列化的对象的引用,那么整个序列化操作将会失败,并且会抛出一个NotSerializableException。我们可以将这个引用标记为transient,那么对象仍然可以序列化。
  • 同一个对象多次序列化的处理
    • 所有保存到磁盘中的对象都有一个序列化编号, 序列化一个对象中,首先检查该对象是否已经序列化过。
    • 如果没有,进行序列化
    • 如果已经序列化,将不再重新序列化,而是输出编号即可
    • 如果不希望某些属性(敏感)序列化,或不希望出现递归序列, 为属性添加transient关键字(完成排除在序列化之外)。
    • 自定义序列化(不仅可以决定哪些属性不参与序列化,还可以定义属性具体如何序列化)
  • 序列化版本不兼容
    • 修改了实例属性后,会影响版本号,从而导致反序列化不成功
    • 解决方案:为Java对象指定序列化版本号serialVersionUID

(2)Java新IO概述(java.nio包下)

  • 新IO采用内存映射文件的方式来处理输入、输出,将文件或文件的一段区域映射到内存中,就可以像访问内存一样访问文件了(模拟了操作系统上的虚拟内存的概念),速度更快。
  • 新IO系统中所有的数据都需要通过通道传输:Channel 提供了一个map()方法,可以直接将“一块数据”映射到内存中。是面向块的处理。 Buffer本质上是一个数组,输入输出的数据必须进入Buffer中。Buffer类没有提供构造器,使用alllocate()方法创建一个一定容量的XxxBuffer对象。
  • Buffer中flip()方法将limit设置为position所在的位置,clear()方式将limit置为 capacity。
  • 直接Buffer创建的成本高,只适用生存期长的Buffer,读效率高。
  • Channel中最常见的三种方法:map() , read(), write().如下:
public class FileChannelTest {
	public static void main(String[] args) throws FileNotFoundException, IOException {
		File f = new File("src/others/FileChannelTest.java");
		try (FileChannel inChannel = new FileInputStream(f).getChannel();
				FileChannel outChannel = new FileOutputStream("a.txt").getChannel();) {
			MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
			Charset charset = Charset.forName("GBK");
			outChannel.write(buffer);
			buffer.clear();
			CharsetDecoder decoder = charset.newDecoder();
			CharBuffer charBuffer = decoder.decode(buffer);
			System.out.println(charBuffer);

		}

	}
}

  • 文件锁:程序之间需要某种机制进行通信,使用文件锁可以有效地组织多个进程并发修改同一个文件。lock()试图锁定文件时,如果无法得到文件锁,程序将一直阻塞。tryLock()尝试锁定文件,它将直接返回而不是阻塞,如果获得返回文件锁,否则返回null。
  • 直接使用lock()或tryLock()方法获取的文件锁是排他锁—锁住对该文件的读写。
public class FileLockTest {
	public static void main(String[] args) throws FileNotFoundException, IOException, InterruptedException {
		try (FileChannel channel = new FileOutputStream("a.txt").getChannel();) {
			FileLock lock = channel.tryLock();
			Thread.sleep(10000);// 10秒内无法对a.txt文件进行修改
			lock.release();
		}
	}
}
  • Java7的 NIO.2提供了全面的文件IO和文件系统访问支持。引入一个Path接口,代表一个平台无关的平台路径。还提供了Files和Paths两个工厂类。如下:展示了Files类得到用法。
public class FilesTest {
	public static void main(String[] args) throws FileNotFoundException, IOException {
		// 复制文件
		Files.copy(Paths.get("src/others/FilesTest.java"), new FileOutputStream("b.txt"));
		// 判断FilesTest是否是隐藏文件
		System.out.println(Files.isHidden(Paths.get("src/others/FilesTest.java")));
		// 一次性读取FilesTest的所有行
		List<String> lines = Files.readAllLines(Paths.get("src/others/FilesTest.java"), Charset.forName("gbk"));
		System.out.println(lines);
		// 判断指定文件的大小
		System.out.println(Files.size(Paths.get("src/others/FilesTest.java")));
		List<String> poem = new ArrayList<>();
		poem.add("相看两不厌");
		poem.add("唯有敬亭山");
		// 直接将多个字符串内容写入到指定文件中
		Files.write(Paths.get("poem.txt"), poem, Charset.forName("gbk"));
		// 使用Java8新增的Stream API 列出当前目录下的所有文件和目录
		Files.list(Paths.get(".")).forEach(path -> System.out.println(path));

	}
}

猜你喜欢

转载自blog.csdn.net/qq_42349617/article/details/88139827