三、反射
知识点总结:
- 动态语言
– 程序运行时,可以改变程序结构或变量类型。典型的语言:
• 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));
}
}