三、java反射
1、反射概述
类的加载机制中提到过,java虚拟机有一个运行时数据区,这个数据区又被分为方法区、堆和栈等。方法区主要的作用就是存储被装载的类的类型信息,当虚拟机装载某个类型的时候,需要装载器定位相应的class文件,然后将其读入到java虚拟机中,紧接着虚拟机提取class中的类型信息,将这些信息存储到方法区中。[1、这个类的全限定名......11、指向class类的引用]
Class类是一个非常重要的java基础类,每当装载一个新的类型的时候,java虚拟机都会在java堆中创建一个对应于新类型的Class实例[Class类没有公有的构造方法,它由JVM自动调用],该实例就代表此类型,通过该Class实例我们就可以访问存储在方法区中该类型的基本信息。同一个类型的Class对象全局只有一个,即如果某个类型在内存中已经加载了,就不会重复创建Class对象。
反射机制的概念:所谓反射机制就是在运行状态中,对于任意一个类(class文件),都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能成为java语言的反射机制。通俗的讲:反射使我们可以得到装载到JVM中的类的内部信息,允许我们在执行程序时得到,而不是在编写代码的时候就必须知道所需类的内部信息。我们可以根据类的部分已知信息,来还原类的全部信息。[动态获取类中的信息,就是java反射]
例如:假设对于类ReflectionTest.java,我们知道的唯一信息是它的类名是“com.cn.ReflectionTest”。这时,我们想要知道ReflectionTest.java的其它信息(比如它的构造函数,它的成员变量等等),要怎么办呢?
这就需要用到“反射”。通过反射,我们可以解析出ReflectionTest.java的完整信息,包括它的构造函数,成员变量,继承关系等等。
在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中:
Class类:代表一个类。
Field 类:代表类的成员变量(成员变量也称为类的属性)。
Method类:代表类的方法。
Constructor 类:代表类的构造方法。
Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
2、反射获取对象的三种方式
2.1、Class类中的方法:Class.forName("类名字符串")
/**
* (注:类名字符串必须是全称,包名+类名)这种方式装入类,会做类的静态初始化
*/
Class<?> cls1 = Class.forName("java.lang.String");
System.out.println(cls1);//class java.lang.String
2.2、表达式方式获取[类型.class]
类型.class:class字面值是一个表达式,其值等于指定类型的Class对象。表达式包括类的名字、接口、数组或基本类型或伪类型void。X.class(X是类、接口、数组或基本类型或伪类型)class字面值的类型是Class<X>。如void.class的类型是Class<void>
所有的引用数据类型(类-类型)的类名、基本数据类型都可以通过.class方式获取其 Class对象。
/**
* 对于基本数据类型的封装类还可以通过.TYPE 的方式获取其 Class 对象,但要注
* 意。TYPE 实际上获取的封装类对应的基本类型的 Class 对象的引用。
* 通过这种方式不会初始化静态域,clazz和clazz1引用地址同
*/
System.out.println(int.class == Integer.class);//false
System.out.println(int.class == Integer.TYPE); //true
Class clazz = Person.class;
Class clazz1 = Person.class;
System.out.println(clazz == clazz1);//true
System.out.println("是否原始类型:"+int.class.isPrimitive());//true
System.out.println("是否原始类型:"+void.class.isPrimitive());//true
//注:基本数据类型,就没有类的全限定名,也没有getClass方法.一般可以用在反射查找具有int参数的方法中
2.3、Object类中的方法:obj.getClass()
/* *
* 想要用这种方式,必须要明确具体的类,并创建对象
* 说明:对类进行静态初始化、非静态初始化;返回引用o运行时真正所指的对象
* (因为:子对象的引用可能会赋给父对象的引用变量中)所属的类的Class的对象
*/
String str1 = new String();
String str2 = new String();
Class cls3 = str1.getClass();
Class cls4 = str1.getClass();
System.out.println(cls3 == cls4);//true
注:三者的区别案例
public class ReflectionTest {
public static void main(String[] args) {
try {
// 测试.class
Class testTypeClass = TestClassType.class;
System.out.println("testTypeClass---" + testTypeClass);
// 测试Class.forName()
Class testTypeForName = Class.forName("hls.com.cn.TestClassType");
System.out.println("testTypeForName---" + testTypeForName);
// 测试Object.getClass()
TestClassType testTypeGetClass = new TestClassType();
System.out.println("testTypeGetClass---"+ testTypeGetClass.getClass());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class TestClassType {
// 构造函数
public TestClassType() {
System.out.println("----构造函数---");
}
// 静态的参数初始化
static {
System.out.println("---静态的参数初始化---");
}
// 非静态的参数初始化
{
System.out.println("----非静态的参数初始化---");
}
}
/*------------------------输出结果:--------------------
* testTypeClass---class hls.com.cn.TestClassType
* ---静态的参数初始化---
* testTypeForName---class hls.com.cn.TestClassType
* ----非静态的参数初始化---
* ----构造函数---
* testTypeGetClass---class hls.com.cn.TestClassType
*------------------------------------------------------
*/
根据结果可以发现,三种生成的Class对象一样的。并且程序只打印一次“静态的参数初始化”。
我们知道,静态的方法属性初始化,是在加载类的时候初始化。而非静态方法属性初始化,是new类实例对象的时候加载。
因此,这段程序说明,三种方式生成Class对象,其实只有一个Class对象。在生成Class对象的时候,首先判断内存中是否已经加载。
所以,生成Class对象的过程其实是如此的:
在运行时,当需要生成这个类的对象,JVM就会检查此类是否已经装载内存中。若是没有装载,类加载器,就会定位到对应的.class文件,然后载入.class文件,并生成一个Class类型的对象。若是装载了,则根据class文件生成实例对象。
总结:
- 类型.class
JVM将使用类装载器, 将类装入内存(前提是:类还没有装入内存),不做类的初始化工作.返回Class的对象
b)Class.forName(“类全限定名”):
装入类,并做类的静态初始化,返回Class的对象
c)实例对象.getClass()
对类进行静态初始化、非静态初始化;返回引用o运行时真正所指的对象(因为:子对象的引用可能会赋给父对象的引用变量中)所属的类的Class的对象
3.获取Class中的构造函数
3.1、构造函数相关API
// 获取“参数是parameterTypes”的public的构造函数
public Constructor getConstructor(Class[] parameterTypes)
// 获取全部的public的构造函数
public Constructor[] getConstructors()
// 获取“参数是parameterTypes”的,并且是类自身声明的构造函数,包含public、protected和private方法。
public Constructor getDeclaredConstructor(Class[] parameterTypes)
// 获取类自身声明的全部的构造函数,包含public、protected和private方法。
public Constructor[] getDeclaredConstructors()
// 如果这个类是“其它类的构造函数中的内部类”,调用getEnclosingConstructor()就是这个类所在的构造函数;若不存在,返回null。意思就是说如果一个类A的构造函数中定义了一个内部类InnerA,则通过InnerA的Class对象调用getEnclosingConstructor()方法,可以获取类A的这个构造函数
public Constructor getEnclosingConstructor()
//案例:
package hls.com.cn;
import java.lang.reflect.Constructor;
public class Person {
Class cls;
private Gender gender; //性别
private int age;
private String name;
public Gender getGender() {
return gender;
}
public void setGender(Gender gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private Person(){
this.name = "unknow";
this.age = 0;
this.gender= gender.Female;
System.out.println("这是private Person()");
}
protected Person(String name){
this.name = name;
this.age = 0;
this.gender= gender.Female;
System.out.println("这是protected Person(String name)");
}
public Person(String name,int age,Gender gender){
this.name = name;
this.age = age;
this.gender= gender;
System.out.println("这是 public Person(String name,int age,Gender gender)");
}
public Person(String name, int age){
this.name = name;
this.age = age;
this.gender = Gender.Female;
//内部类在构造方法中
class InnerA{
}
// 获取InnerA的Class对象
cls = InnerA.class;
}
private void privateMethod(){
System.out.println("我是私有方法");
}
class InnerB{
}
@Override
public String toString() {
return "("+name+", "+age+", "+gender+")";
}
}
//枚举类型,性别
enum Gender{
Male,Female
}
public class TestReflect {
public static void main(String[] args) {
Class c;
try {
c = Class.forName("hls.com.cn.Person");
//getDeclaredConstructor
Constructor[] allCon = c.getDeclaredConstructors();//获取全部构造包括私有
Constructor ryCon = c.getDeclaredConstructor();//获取无参构造不管什么权限
Class cn = Class.forName("hls.com.cn.Person$InnerB");//内部类的全限定名
Constructor cs2 = cn.getDeclaredConstructor(hls.com.cn.Person.class);//hls.com.cn.Person$InnerB(hls.com.cn.Person)
//这里的可变参数:可以 new Class[]{String.class} 也可以直接写 String.class
Constructor cst2 = c.getDeclaredConstructor(new Class[]{String.class});//获取带参构造
/**
* newInstance() 默认返回的是Object对象,而向下转型,可以以实际类型转
*/
Person p2 = (Person) cst2.newInstance("zly");
System.out.println(p2.getName()); //zly
/** setAccessible(true) 并不是将方法的访问权限改成了public,而是取消java
* 的权限控制检查。所以即使是public方法其accessible 属性只是默认false
* Field Method Constructor在使用构造器的时候,是会执行检查的(private:
* 只能在同类中访问)
* 设置为true,则不进行检查,否则无权限访问,则会报:
* java.lang.IllegalAccessException
* not access a member ... modifiers "private"
* 不管是方法(包括构造方法)、属性跨越访问权限都要设置不执行检查
*/
ryCon.setAccessible(true);
Object p1 = ryCon.newInstance(); //创建对象 ryCon 的访问限定符是private
//getConstructor
Constructor[] pubCon = c.getConstructors(); //获取全部public构造
Constructor onePubCon1 = c.getConstructor(String.class,int.class);
// 根据class,调用Person类中有内部类InnerA的构造函数
Class cn1 = new Person("zly",25).cls; //获取内部类的Class对象
//取得的hls.com.cn.Person(java.lang.String,int)
Constructor cst1 = cn1.getEnclosingConstructor();
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.2、成员方法相关API
// 获取“名称是name,参数是parameterTypes”的public的函数(包括从基类继承的、从接口实现的所有public函数)
public Method getMethod(String name, Class[] parameterTypes)
// 获取全部的public的函数(包括从基类继承的、从接口实现的所有public函数)
public Method[] getMethods()
// 获取“名称是name,参数是parameterTypes”,并且是类自身声明的函数,包含public、protected和private方法。
public Method getDeclaredMethod(String name, Class[] parameterTypes)
// 获取全部的类自身声明的函数,包含public、protected和private方法。
public Method[] getDeclaredMethods()
// 如果这个类是“其它类中某个方法的内部类”,调用getEnclosingMethod()就是这个类所在的方法;若不存在,返回null。
public Method getEnclosingMethod()
//案例:
public class ReflectTest2 {
public static void main(String[] args) {
try {
Class c = Class.forName("hls.com.cn.Person");
Constructor con = c.getDeclaredConstructor(String.class);
Person p = (Person) con.newInstance("赵丽颖");
Method[] m1 = c.getMethods(); //获取所的公共成员方法,包括继承的
Method m2 = c.getMethod("setName",String.class); //获取指定名字参数的类型的方法
Method m3 = c.getDeclaredMethod("privateMethod");//获取类声明的方法(包括私有)
m3.setAccessible(true);
m3.invoke(p);//我是私有方法
Method[] m4 = c.getDeclaredMethods();
for (Method m: m4) {
System.out.println(m);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.3、成员变量相关API
// 获取“名称是name”的public的成员变量(包括从基类继承的、从接口实现的所有public成员变量)
public Field getField(String name)
// 获取全部的public成员变量(包括从基类继承的、从接口实现的所有public成员变量)
public Field[] getFields()
// 获取“名称是name”,并且是类自身声明的成员变量,包含public、protected和private成员变量。
public Field getDeclaredField(String name)
// 获取全部的类自身声明的成员变量,包含public、protected和private成员变量。
public Field[] getDeclaredFields()
案例:
public class ReflectTest3 {
public static void main(String[] args) {
try {
Class c = Class.forName("hls.com.cn.Person");
Constructor con = c.getDeclaredConstructor(String.class);
Person p = (Person) con.newInstance("赵丽颖");
System.out.println(p);//(赵丽颖,0,Female)
Field f1 = c.getDeclaredField("name");//private ...Person.name
f1.setAccessible(true);
f1.set(p, "zly");
System.out.println(p);//(zly,0,Female)
Field[] f2 = c.getDeclaredFields();
for(Field f: f2){
System.out.println(f);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
4、静态代理和动态代理
静态代理和动态代理概述
代理: 为被代理对象产生一个代理对象。
静态代理:被代理类的数量有限,这个代理类只能代理这些已知的被代理类
动态代理:被代理的类的数量没有限制,可以代理无限多个类
动态代理又分为JDK动态代理和CGLib动态代理
JDK动态代理:使用JDK动态代理产生的代理对象只能转换成被代理对象的接口的对象
代理类的创建:
①实现InvocationHandler接口
②实现invoke方法
③定义一个Object属性
④定义一个带有Object类型的参数的构造器:用于将被代理对象传递进来赋值给obj
⑤定义一个方法,用来产生obj的代理对象
⑥完善invoke方法
CGLib动态代理:通过创建被代理类的子类来构造一个代理对象。因为CGLib动态代理,依赖于第三方Jar包
①创建一个类实现MethodInterceptor接口
②实现intercept方法
③定义一个Object属性,用来表示被代理对象
④定义一个带有object类型参数的构造器,用于将被代理对象传递进来赋值给obj
⑤创建一个方法,用于产生代理对象
|
JDK |
CGLib |
作用 |
都是用来产生被代理对象的代理对象 |
|
实现接口 |
InvocationHandler |
MethodInterceptor |
实现原理 |
通过被代理类实现的接口来创建代理对象 |
通过构造被代理类的子类来创建代理对象 |
适用的被代理类 |
可以为实现了接口的类创建代理对象 |
可以为没被final修饰的类创建代理对象 |
|
JDKProxy dp2 = new JDKProxy(new BaoLiHouse()); //产生的代理对象只能转换成被代理对象实现的接口 ZhongJie zj = (ZhongJie)dp2.getProxy(); yk.buy(); |
CGLibDynamicProxy cdp = new CGLibDynamicProxy(new User()); //通过CGLib动态代理产生的代理对象可以强转成被代理对象本身 User pobj = (User)cdp.getProxy(); pobj.print(); |
【JDK动态代理类】 public class JDKDynamicProxy implements InvocationHandler { private Object obj;// 表示被代理对象 public JDKDynamicProxy(Object o) { this.obj = o; }
public Object getProxy() { //第一个参数:被代理对象的类的加载器 //第二个参数:被代理对象实现的所有接口 return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj .getClass().getInterfaces(), this); }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 代码1 Object rv = method.invoke(obj, args); // 代码2 return rv; } } |
||
【CGLib动态代理】 public class CGLibDynamicProxy implements MethodInterceptor { private Object obj; public CGLibDynamicProxy(Object obj){ this.obj = obj; }
public Object getProxy(){ Enhancer en = new Enhancer(); en.setSuperclass(obj.getClass()); en.setCallback(this); return en.create(); }
public Object intercept(Object obl, Method method, Object[] args, MethodProxy mp) throws Throwable { //代码1 System.out.println("before"); Object rv = method.invoke(obj, args); System.out.println("after"); //代码2 return rv; } } |
四、IO流
1、File类
尽管java.io定义的大多数类是实行流式操作的,File类不是。它直接处理文件和文件系统。也就是说,File类没有指定信息怎样从文件读取或向文件存储;它描述了文件本身的属性。File对象用来获取或处理与磁盘文件相关的信息,例如权限,时间,日期和目录路径。此外,File还浏览子目录层次结构。很多程序中文件是数据的根源和目标。
a)File类主要属性
static String |
separator 与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。 |
static String |
pathSeparator与系统有关的路径分隔符,为了方便,它被表示为一个字符串。 |
b)File类的构造方法
File(File parent, String child) |
File(String pathname) |
File(String parent, String child) |
c)常用方法
获取:
String |
getName() 获取文件或目录的名称。 |
File |
getAbsoluteFile() 获取绝对路径名形式。 |
String |
getAbsolutePath() 获取绝对路径名字符串。 |
long |
length() 返回文件的大小。 |
long |
lastModified() 返回文件最后一次被修改的时间。 |
创建:
boolean |
createNewFile() 和输出流不同,如果文件不存在,则创建,如果文件存在,则不创建。 |
boolean |
delete() 删除此抽象路径名表示的文件或目录(如果删除文件夹,此文件夹必须是空内容)。 |
void |
deleteOnExit() 在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。 |
boolean |
mkdir() 创建此抽象路径名指定的目录。 |
boolean |
mkdirs() 创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。 |
判断:
boolean |
exists() 测试此抽象路径名表示的文件或目录是否存在。 |
boolean |
isDirectory() 测试此抽象路径名表示的文件是否是一个目录。 |
boolean |
isFile() 测试此抽象路径名表示的文件是否是一个标准文件。 |
boolean |
isHidden() 测试此抽象路径名指定的文件是否是一个隐藏文件。 |
boolean |
canRead() 测试应用程序是否可以读取此抽象路径名表示的文件。 |
boolean |
canWrite() 测试应用程序是否可以修改此抽象路径名表示的文件。 |
重命名:
boolean |
renameTo(File dest) |
根目录和容量获取:
static File[] |
listRoots() 列出可用的文件系统根(分区)。 |
long |
getFreeSpace() |
long |
getTotalSpace() |
long |
getUsableSpace() |
获取目录内容:
String[] |
list() 获取当前目录下的文件以及文件夹名,包含隐藏文件。调用list方法的File对象中封转的必须是目录,否则会发生NullPointerException异常,如果访问的系统级目录,也会发生空指针异常。如果目录存在但是没有内容,会返回一个数组,但长度为0. |
//获取:
public class TestFile {
public static void main(String[] args) {
System.out.println(File.separator); //--> \
System.out.println(File.pathSeparator);//--> ;
/**
* 第一个File对象是由仅有一个目录路径参数的构造函数生成的。
* 第二个对象有两个参数——路径和文件名。
* 第三个File对象的参数包括指向f1文件的路径及文件名。
* f3和f2指向相同的文件。
*/
File f1 =new File("/");
File f2 = new File("/"+"autoexe.bat");
File f3 =new File(f1,"autoexe.bat");
System.out.println(f3.getPath()); //相对路径 \autoexe.bat
System.out.println(f3.getAbsolutePath());//绝对路径 E:\autoexe.bat
File f4 = new File("E:\\text\\a.txt");
System.out.println("最后修改时间"+f4.lastModified()+" 大小:"+f4.length());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
date.setTime(f4.lastModified());
System.out.println(sdf.format(date));
}
}
public class TestFile {
public static void main(String[] args) throws IOException {
//---------创建和删除----------//
File f1 = new File("E:/text/ts"); //只有text目录,无ts目录
boolean b =f1.mkdirs();
System.out.println(b); //true
//System.out.println(f1.delete()); //true ts目录被删除,delete只能删除空内容的文件夹
File f2 = new File("E:/text/a.txt");
boolean b1 = f2.createNewFile(); //没有就会创建一个新的文件,有就不会创建
//System.out.println(f2.delete());
//---------判断----------//
System.out.println("文件或目录是否存在:"+f2.exists());
System.out.println("是否是目录:"+f1.isDirectory());
System.out.println("是否是文件:"+f2.isFile());
System.out.println("是否隐藏:"+f2.isHidden());
//---------重命名---------//
File f3 = new File("E:/text/b.txt");
System.out.println(f2.renameTo(f3));
File file = new File("E:\\");
String names[] = file.list();//列出路径中的目录和文件
for (String name : names) {
System.out.println(name);
}
}
}
扩展:java中的转义字符“\”
Java中为什么需要转义字符:转义字符的意义就是避免出现二义性。
例如:
- 在路径中“com\cn\hls”,这里面的\你不清楚这是路径的下一层,还是就是指字符反斜杠。
- 在“\””中,你不知道这个引号是要作为字符还是作为定义字符串的格式。
Java中的常见转义字符:
符号 |
意义 |
\n |
回车 |
\t |
水平制表符 |
\r |
换行 |
\f |
换页 |
\’ |
单引号 |
\” |
双引号 |
\\ |
反斜杠 |
注:java中斜杠和反斜杠的区别。
在Unix/Linux中的路径分隔符采用斜杠“/”,而反斜杠“\”表示跳脱字符,将特殊字符变成一般字符。
而在Windows中,路径分隔符采用反斜杠“\”,而斜杠“\”表示除法。比如:C:\Windows\System。
但是在编程中,无法直接这么使用,在java语言中一个反斜杠是转义字符,而斜杠就是一个字符,表示地址路径下一级目录,也表示除号。但是也可以这么写:C:/Windows/System。
String[] |
list(FilenameFilter filter) |