反射:加载类,反射出类的各个组成部分(类的成员:构造方法,属性,方法)
java反射机制:在运行状态中,对于任何一个类都能够知道这个类的所有属性和方法;对于任意一个对象,能够调用它的任意属性和方法;这种动态获取信息的方式就称为反射。
当程序要使用某个类时,如果这个类没有加载到内存中,则系统会通过加载,连接,初始化三个步骤来实现对这个类的初始化。
将class(字节码)文件读取到内存中,并为之创建一个Class对象
任何类被使用时都会被创建一个Class对象(注:一个类只有一个Class对象)
该阶段主要是为类的类变量初始化值的,初始化有两种方式:
负责将class 文件加载到内存中,并为之创建一个Class对象,如果了解类加载器的机制,可以的更好的理解程序的运行
类加载器的组成:
根类加载器: bootstrap classLoader
也被称为引导类加载类,负责Java核心类的加载
比如: System, String 等,在 JDK 中的JRE 中 lib 中的 rt.jar文件中
扩展类加载器: extension classLoader
负责jre的扩展目录中的jar的加载
系统类加载器: System classLoader
负责在JVM启动时加载来自java命令的class文件
// 先创建了一个Person类
public class Person {
private String name;
int age;
public String address;
public Person() {}
private Person(String name){
this.name = name;
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public void show(){
System.out.println("show");
}
public void method(String s){
System.out.println("method"+s);
}
public String getString(String s,String m){
return s+"----"+m;
}
private void function(){
System.out.println("function");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
// 获取Person类的Class对象
// 方法1:
Person person = new Person();
Class c = person.getClass();
// 方法2:
Class c2 = Person.class;
// 方法3:
Class c3 = Class.forName("com.gxa.demo1.Person");
// c=c2=c3:因为一个类的Class对象只有一个
注:在开发中我们常使用第三种方式去做反射,因为第三种传入的是个字符串而不是具体的类名,这样的话就可以把这个值方法配置文件中去,方便修改。
a: 得到类的Class对象
Class c = Class.forName("com.gxa.demo1.Person");
b: 获取类的加载器
ClassLoader classLoader = c.getClassLoader();
// 得到Class对象
Class c = Class.forName("com.gxa.demo1.Person");
// 获取类加载器对象
ClassLoader classLoader = c.getClassLoader();
// 类加载器加载其它文件(加入我的src目录下面有一个jdbc.properties文件)
InputStream in = classLoader.getResourceAsStream("jdbc.properties");
注:如果找不到文件则会返回null给输入流
public Constructor<T> getConstructor(类<?>... parameterTypes)
:返回一个Constructor
对象,返回的是public修饰的构造方法;如果不写参数则返回无参的构造函数对象;如果构造方法不是公共的将会报异常:NoSuchMethodException
Class c = Class.forName("com.gxa.demo1.Person");
// 返回公共的无参构造,如果无参构造不是public修饰的将会报错
Constructor constructor = c.getConstructor();
System.out.println(constructor);
// 有参数传入:传入的参数是数据类型类的Class对象;下面是获取一个参数为String类型的构造方法对象
Constructor constructor = c.getConstructor(String.class);
System.out.println(constructor);
public Constructor<?>[] getConstructors()
:返回一个public修饰的构造方法的Constructor对象的数组
Constructor[] constructors = c.getConstructors();
// 返回的构造方法都是public修饰的
System.out.println(constructors);
public Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
:返回一个Constructor
对象,不传参数返回无参的构造方法(没有修饰符的限制),如果构造方法不存在将报异常:NoSuchMethodException
// 获取无参的构造方法
Constructor declaredConstructor = c.getDeclaredConstructor();
System.out.println(declaredConstructor);
public Constructor<?>[] getDeclaredConstructors()
:返回所有构造方法的Constructor对象的数组
// 获取所有方法
Constructor[] declaredConstructors = c.getDeclaredConstructors();
System.out.println(declaredConstructors);
// 获取Class对象
Class c = Person.class;
// 获取Person类的无参构造
Constructor declaredConstructor = c.getDeclaredConstructor();
// 通过构造方法对象创建一个Person类的实例对象
Object obj = declaredConstructor.newInstance();
System.out.println(obj);
注:如果获取的构造方法对象是一个非私有的,那么将会报IllegalAccessException
异常;
解决方案:
在Constructor类里面有一个方法叫作:public void setAccessible(boolean flag)
的方法,将此对象的accessible
标志设置为指示的布尔值。 true
的值表示反射对象应该在使用时跳过java的语法检查。
// 获取Class对象
Class c = Person.class;
// 获取Person类的无参构造
Constructor declaredConstructor = c.getDeclaredConstructor();
// 设置constructor对象的访问权限
declaredConstructor.setAccessible(true);
// 通过构造方法对象创建一个Person类的实例对象
Object obj = declaredConstructor.newInstance();
System.out.println(obj);
// 通过有参构造方法创建类的实例
Class c = Person.class;
Constructor declaredConstructor = c.getDeclaredConstructor(String.class);
declaredConstructor.setAccessible(true);
Object obj = declaredConstructor.newInstance("张三");
System.out.println(obj);
与获取构造方法对象的获取方法相似:
public Field getField(String name)
:返回一个Field
对象,它反映此表示的类或接口的指定公共成员字段类对象。
Class c = Person.class;
Field addressField = c.getField("address");
System.out.println(addressField);
注:在使用getField方法是如果传的name为不存在的属性或非公共属性将会报异常:NoSuchFieldException
public Field[] getFields()
:返回包含一个数组Field
对象反射由此表示的类或接口的所有可访问的公共字段类对象。
// 获取该类公共属性的field对象的数组
Field[] fields = c.getFields();
public Field getDeclaredField(String name)
:返回一个Field
对象,它反映此表示的类或接口的指定字段类对象。
Field name = c.getDeclaredField("name");
public Field[] getDeclaredFields()
:返回的数组Field
对象反映此表示的类或接口声明的所有字段类对象。
获取所有属性的field对象集合
Field[] fields = c.getDeclaredFields();
// 没有对象就没有属性
// 要使用属性,必须 要有对象
// 1、反射一个无参的构造方法的Constructor对象
Constructor constructor = c.getDeclaredConstructor();
// 2、创建对应的对象
Object obj = constructor.newInstance();
// 3、反射Person类的属性对象
Field namefield = c.getDeclaredField("name");
Field agefield = c.getDeclaredField("age");
Field addressfield = c.getDeclaredField("address");
// 4、给对象实例赋值
namefield.setAccessible(true); // 设置私有属性的访问权限
namefield.set(obj,"张三");
agefield.set(obj,18);
addressfield.set(obj,"成都");
System.out.println(obj);
public 方法 getDeclaredMethod(String name,类<?>... parameterTypes)
:返回一个方法
对象,它反映此表示的类或接口的指定声明的方法类对象。
public 方法[] getDeclaredMethods()
:返回包含一个数组方法
对象反射的类或接口的所有声明的方法,通过此表示类
对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法。
public 方法 getMethod(String name,类<?>... parameterTypes)
:返回一个方法
对象,它反映此表示的类或接口的指定公共成员方法类
对象。
public 方法[] getMethods()
:返回包含一个数组方法
对象反射由此表示的类或接口的所有公共方法类
对象,包括那些由类或接口和那些从超类和超接口继承的声明。
// 1、获取Class对象
Person person = new Person();
Class aClass = person.getClass();
// 2、获取Person的方法Method对象
Class对象.getDeclaredMethod("方法名",数据类型的Class对象);
Method method = aClass.getDeclaredMethod("method", String.class);
// invoke是Method类中的方法:表示调用某个具体对象的方法
Method对象.invoke(类的实例对象,参数...);
// 通过Method对象调用person对象的method方法,为了防止权限不够(方法为私有的)最好使用setAccessible(true)来跳过java的代码检查
method.setAccessible(true);
method.invoke(person,"反射调用了method方法");
案例:
在ArrayList 对象中添加一个字符串;
我们知道指定了ArrayList中参数的类型再传入其他类型的话java会报错,那么这个时候我们就可以通过反射的方式去获取ArrayList类中的add方法给ArrayList的对象赋值,因为ArrayList类的add方法传入的参数是一个泛型而不是具体的类型,所以我们可以添加任意数据:
// 1、创建一个ArrayList<Integer>对象 ArrayList<Integer> list = new ArrayList<>(); // 2、通过反射的方式获取ArrayList的Class对象 Class listClass = list.getClass(); // 3、通过Class对象反射add方法 Method listAdd = listClass.getDeclaredMethod("add",Object.class); // 4、通过ArrayList类的add方法给ArrayList<Integer>实例对象添加值 // 4.1、为了防止访问的权限不够加上setAccessible方法跳过java代码检查 listAdd.setAccessible(true); // 4.2、添加值 listAdd.invoke(list,"我在Integer类型的集合里添加了一个字符串"); // 5、打印输出list集合 System.out.println(list);
1)注册数据库驱动。
2)建立数据库连接。
3)获取statemen对象。
4)执行SQL语句。
5)返回结果(如果是查询操作有结果集:处理结果集)。
6)(释放资源)关闭数据库连接
1)PreparedStatement是预编译的SQL语句,效率高于Statement。
2)PreparedStatement支持“?”操作符,相对于Statement更加灵活。
3)PreparedStatement可以防止SQL注入,安全性高于Statement。
4)CallableStatement适用于执行存储过程。
· Statement的execute(String query)方法用来执行任意的SQL查询,如果查询的结果是一个ResultSet,这个方法就返回true。如果结果不是ResultSet,比如insert或者update查询,它就会返回false。我们可以通过它的getResultSet方法来获取ResultSet,或者通过getUpdateCount()方法来获取更新的记录条数。
· Statement的executeQuery(String query)接口用来执行select查询,并且返回ResultSet。即使查询不到记录返回的ResultSet也不会为null。我们通常使用executeQuery来执行查询语句,这样的话如果传进来的是insert或者update语句的话,它会抛出错误信息为 “executeQuery method can not be used for update”的java.util.SQLException。
· Statement的executeUpdate(String query)方法用来执行insert或者update/delete(DML)语句,或者 什么也不返回DDL语句。返回值是int类型,如果是DML语句的话,它就是更新的条数,如果是DDL的话,就返回0。
· 只有当你不确定是什么语句的时候才应该使用execute()方法,否则应该使用executeQuery或者executeUpdate方法。
PreparedStatement的一个缺点是,我们不能直接用它来执行in条件语句;需要执行IN条件语句的话,下面有一些解决方案:
1)分别进行单条查询——这样做性能很差,不推荐。
2)使用存储过程——这取决于数据库的实现,不是所有数据库都支持。
3)动态生成PreparedStatement——这是个好办法,但是不能享受PreparedStatement的缓存带来的好处了。
4)在PreparedStatement查询中使用NULL值——如果你知道输入变量的最大个数的话,这是个不错的办法,扩展一下还可以支持无限参数。
在查询数据库后会返回一个ResultSet,它就像是查询结果集的一张数据表。
ResultSet对象维护了一个游标,指向当前的数据行。开始的时候这个游标指向的是第一行。如果调用了ResultSet的next()方法游标会下移一行,如果没有更多的数据了,next()方法会返回false。可以在for循环中用它来遍历数据集。
默认的ResultSet是不能更新的,游标也只能往下移。也就是说你只能从第一行到最后一行遍历一遍。不过也可以创建可以回滚或者可更新的ResultSet。
当生成ResultSet的Statement对象要关闭或者重新执行或是获取下一个ResultSet的时候,ResultSet对象也会自动关闭。
可以通过ResultSet的getter方法,传入列名或者从1开始的序号来获取列数据。