反射: 加载类,反射出类的各个组成部分 构造方法,属性(非静态属性和静态属性),方法(非静态方法,静态方法)
java 反射机制: 是在运行状态中(Class对象), 对于任何类,都能能够知道这个类的所有的属性和方法; 对于任意一个对象,能够调用它的任意属性和方法;这种动态获取信息的方式,就称之反射.
当程序要使用某个类时,如果这个类还没有加载内存中,则系统会通过加载,连接,初始化三个步骤来实现对这个类的初始化。
加载 :
将class 文件加载到内存中,并为之创建一个Class对象(将class文件的内容放到一个对象中,而对象的名字刚好就是Class)
任何类被使用时,系统都会建立一个Class对象.
连接
验证: 是否有正确的内部结构 ,并和其它协调一致
准备 : 负责为类的静态成员分配内存,并设置默认初始化值
解析: 将类的二进制数据中的符号引用替换成直接引用
初始化
就是之前的初始化步骤
创建类的实例
访问类的静态变量或给静态变量赋值
调用类的静态方法
初始化某个类的子类
java命令,运行某个类
使用反射方式强制创建某个类或接口对应的Class对象
负责将class 文件加载到内存中,并为之创建一个Class对象,如果了解类加载器的机制,可以的更好的理解程序的运行
类加载器的组成:
根类加载器: bootstrap classLoader
也被称为引导类加载类,负责Java核心类的加载
比如: System, String 等,在 JDK 中的JRE 中 lib 中的 rt.jar文件中
扩展类加载器: extension classLoader
负责jre的扩展目录中的jar的加载
系统类加载器: System classLoader
负责在JVM启动时加载来自java 命令的class文件
开发: 使用第三种.
为什么? 因为第三种是一个字符 串,而不是具体类名,这种的话就可以将这个值放到配置文件中,方便对它修改.
//方法1:
Person p = new Person();
Class c = p.getClass();
Person p1 = new Person();
Class c1 = p.getClass();
System.out.println(p==p1); //false
System.out.println(c==c1); //true
//方法2:
Class c2 = Person.class;
System.out.println(c == c2); //true
// 方法3
// Class对象中的静态方法
//ClassNotFoundException 必须写包名
Class c3 = Class.forName("com.demo1.Person");
System.out.println(c==c3);
//1. 如何获取 类的加载器
//1.1 首先得到Class对象
Class c = Person.class;
//1.2 获取类的加载器
ClassLoader classLoader = c.getClassLoader();
System.out.println(classLoader);
注:小心路径问题,最好是将方法放到 resource 目录下
https://blog.csdn.net/weixin_48052161/article/details/115151874
Class c = User.class;
//1.2 获取类的加载器
ClassLoader classLoader = c.getClassLoader();
System.out.println(classLoader);
// 类加载器加载其它的文件
InputStream in = classLoader.getResourceAsStream("jdbc.properties");
System.out.println(in);
类加载是Java虚拟机(JVM)对类文件进行加载、验证、准备、解析和初始化等一系列操作的过程。类加载的原理包括以下几个步骤:
1.加载:在类加载过程中,JVM会根据类的全限定名来获取类的字节码文件,并将其读取到内存中。
2.验证:在验证阶段,JVM会对字节码文件进行验证,确保其符合Java语言规范和安全要求。验证包括文件格式验证、元数据验证、字节码验证和符号引用验证等。
3.准备:在准备阶段,JVM会为类的静态变量分配内存,并设置默认初始值(例如0、null等)。
4.解析:在解析阶段,JVM会将类的符号引用转换为直接引用,以便于后续的内存访问。解析过程包括类、接口、字段和方法等的解析。
5.初始化:在初始化阶段,JVM会执行类的初始化代码,包括静态变量的赋值和静态代码块的执行。在这个阶段,类的初始化按照严格的顺序进行,并且只会执行一次。
类加载过程是按需进行的,即当程序需要使用某个类时,JVM才会进行相应的类加载操作。同时,JVM还采用了双亲委派模型来进行类加载,即先委派给父类加载器尝试加载,只有在父类加载器无法加载时,才由子类加载器尝试加载。
通过类加载的原理,Java实现了动态扩展和灵活的类加载机制,使得开发人员可以根据需要动态加载和使用类,实现了面向对象编程的核心特性之一:封装和复用
先做了解,第四阶段着重讲
反射构造器: 通过Class对象,获取构造方法
Class c = Person.class;
//获取构造方法
// Constructor>[] getConstructors() : 返回所有的构造方法 Constructor 类的对象 只能获取 public修饰的构造方法
//返回包含一个数组 Constructor对象反射由此表示的类的所有公共构造 类对象。
// Constructor[] constructors = c.getConstructors();
//c.getDeclaredConstructors(); 获取 所有构造方法
// Constructor[] constructors = c.getDeclaredConstructors();
// for(Constructor constructor:constructors){
// System.out.println(constructor);
// }
//获取单个构造方法
// getConstructor(类>... parameterTypes): 只能得到public修饰
// Constructor constructor = c.getConstructor(String.class);
Constructor constructor = c.getDeclaredConstructor(String.class);
System.out.println(constructor);
//Constructor getConstructor(类>... parameterTypes)
//返回一个 Constructor对象,该对象反映 Constructor对象表示的类的指定的公共 类函数。
// 根据构造方法,创建实例对象
//java.lang.IllegalAccessException 构造方法是私有的,创建实现,会报非法异常
// 解决方案:
constructor.setAccessible(true); //跳过java语法检查
Object obj = constructor.newInstance("张三");
System.out.println(obj);
反射构造器: 通过Class对象,获取成员属性
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class c = Person.class;
//public Field[] getFields() 返回包含一个数组Field对象反射由此表示的类或接口的所有可访问的公共字段类对象。
// Field[] fields = c.getFields();
// for(Field field : fields){
// System.out.println(field);
// }
//public Field[] getDeclaredFields() : 获取所有属性
// Field[] fields = c.getDeclaredFields();
// for(Field field : fields){
// System.out.println(field);
// }
//NoSuchFieldException
// Field field= c.getField("name");
// System.out.println(field);
// Field field= c.getDeclaredField("name");
// System.out.println(field);
// 没有对象就没有属性
// 要使用属性,必须 要有对象
Constructor constructor = c.getDeclaredConstructor();
Object obj = constructor.newInstance(); //创建对应的对象
Field namefield = c.getDeclaredField("name");
Field agefield = c.getDeclaredField("age");
Field addressfield = c.getDeclaredField("address");
//IllegalAccessException
namefield.setAccessible(true);
namefield.set(obj,"唐姐");
agefield.set(obj,18);
addressfield.set(obj,"成都");
System.out.println(obj);
}
反射构造器: 通过Class对象,获取成员方法
写一个setProperty(Object obj, String propertyName, object value){}
给ArrayList的一个对象,添加一个字符串
curd(增删改查)商品
商品表(goods)
g_id,g_name,g_price,g_num
1)注册数据库驱动。
2)建立数据库连接。
3)创建一个Statement。
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开始的序号来获取列数据。