JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取类的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类, 必须先要获取到该类的字节码文件对象。
而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象
反射概述以及获取class文件对象的三种方式
获取一个类对应的字节码文件对象:
package com.itheima.demo1.demo7;
public class Student {
public String name;
protected int age;
char sex;
private double height;
}
package com.itheima.demo1.demo7;
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {
//要使用反射机制,你先要获取到该类的字节码文件对象,然后通过该类的字节码文件对象,去剖析类的构成,
//以及通过反射,去调用类中的属性和功能。
//获取一个类的的字节码文件对象的三种方式。
// //方式1.getClass();
Student student = new Student();
//Student.class---->会为这个Student.class文件来创建一个Class对象。
Class<? extends Student> aClass = student.getClass();
//方式2 每一个类,都一个 .class 属性,就可以获取该类的字节码文件对象。
Class<Student> aClass1 = Student.class;
//方式3:通过Class 类中的静态方法 forName();
//全限定名:包名+类名 org.westos.demo.Student
Class<?> aClass2 = Class.forName("com.itheima.demo1.demo7.Student");
//System.out.println(aClass==aClass1);//true
// System.out.println(aClass1==aClass2);//true
//获取指定的字段对象
//获取公共的
Field name = aClass2.getField("name");
System.out.println(name);//public java.lang.String com.itheima.demo1.demo7.Student.name
//过去各种类型的都可
Field height = aClass2.getDeclaredField("height");
System.out.println(height);
//通过反射给字段设置值
Object o = aClass2.newInstance();//创建一个设置值的对象
name.set(o,"cherry");//cherry
System.out.println(name.get(o));
//给私有字段设置值,取消私有的语法检查
height.setAccessible(true);
height.setDouble(o,1.68);
System.out.println(height.getDouble(o));//1.68
}
}
package com.itheima.demo1.demo8;
public class Student {
private Student() {
}
public void show()
{
System.out.println("show方法调用了");
}
protected void show2(String name)
{
System.out.println("show2方法调用了" + name);
}
void show3(String name, int age) {
System.out.println("show3方法调用了" + name + "===" + age);
}
String show4(String name, int age) {
System.out.println("show4方法调用了" + name + "===" + age);
return "返回值";
}
private void test(String name, int age) {
System.out.println("私有的方法调用了" + name + "==" + age);
}
}
通过反射越过泛型检查
package com.itheima.demo1.demo8;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class<?> aClass = Class.forName("com.itheima.demo1.demo8.Student");
//getMethods(); 获取所有的公共的方法对象,包括父类的公共方法。
Method[] methods = aClass.getMethods();
for (Method method : methods) {
// System.out.println(method);//打印出公共类的地址和原始父类的地址值
}
//getDeclaredMethods(); 获取所有的方法对象,包括私有的方法。不获取父类的方法对象。
Method[] methods1 = aClass.getDeclaredMethods();
for (Method method : methods1) {
//System.out.println(method);
}
//获取指定的方法对象的地址
Method show = aClass.getMethod("show");
// System.out.println(show);
Method show2 = aClass.getDeclaredMethod("show2", String.class);
// System.out.println(show2);
}
}
反射是在运行期反射
泛型在编译器有效,在运行期就被擦出了。
//需求:我这个集合的泛型声明为了String类型,但是我想在集合中添加Integer类型的元素
//通过反射机制,越过了泛型检测。
ArrayList<String> list = new ArrayList<>();
list.add("aaaa");
list.add("bbbb");
//泛型只在编译期有效,在运行期就擦除了.
//list.add(100);
Class<? extends ArrayList> aClass = list.getClass();
Method add = aClass.getMethod("add", Object.class);
add.invoke(list, 100);
System.out.println(list);
//public void setProperty(Object obj, String propertyName, Object value){},
// 此方法可将obj对象中名为propertyName的属性的值设置为value。
Student student = new Student();
MyUtils.setProperty(student, "name", "张三");
Object name = MyUtils.getProperty(student, "name");
System.out.println(name);
public static void setProperty(Object obj, String propertyName, Object value) throws NoSuchFieldException, IllegalAccessException {
Class<?> aClass = obj.getClass();
Field declaredField = aClass.getDeclaredField(propertyName);//获取指定的字段对象
declaredField.setAccessible(true);// 值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
declaredField.set(obj, value);
}
public static Object getProperty(Object obj, String propertyName) throws NoSuchFieldException, IllegalAccessException {
Class<?> aClass = obj.getClass();
Field declaredField = aClass.getDeclaredField(propertyName);
declaredField.setAccessible(true);
Object o = declaredField.get(obj);
return o;
}
//类 Proxy 代理类,可以通过反射机制,帮你创建换一个代理对象。
/* newProxyInstance(ClassLoader loader, Class < ? >[]interfaces, InvocationHandler h)
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。*/
UserDao userDao = new UserDaoImpl();
//userDao.insert();
//userDao.update();
//userDao = new UserDaoStaticClass();
//userDao.insert();
UserDao proxy = ProxyUtils.getProxy(userDao);
proxy.insert();
// proxy.update();
// proxy.delete();
// proxy.query();
System.out.println("===================");
proxy.update();
//JDK动态代理:要求:必须要有接口。
//CGLIB动态代理:不需要有接口。
动态代理
-
在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口, 通过使用这个类和接口就可以生成动态代理对象。JDK提供的代理只能针对接口做代理。 我们有更强大的代理cglib,Proxy类中的方法创建动态代理类对象 public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 最终会调用InvocationHandler的方法 InvocationHandler Object invoke(Object proxy,Method method,Object[] args) B:案例演示: 动态代理的实现 我们可以通过Proxy类中的静态方法获取一个代理对象: - - public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
loader: 类加载器
interfaces: 接口对应的一个Class数组
InvocationHandler: 这个其实就是要代理对象所做的事情的一个类的封装
动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口的动态代理:
* 涉及的类:Proxy
* 提供者:JDK官方
* 如何创建代理对象:
* 使用Proxy类中的newProxyInstance方法
* 创建代理对象的要求:
* 被代理类最少实现一个接口,如果没有则不能使用
* newProxyInstance方法的参数:
* ClassLoader:类加载器
* 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
* Class[]:字节码数组
* 它是用于让代理对象和被代理对象有相同方法。固定写法。
* InvocationHandler:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* 方法参数的含义
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
}
}
//产生代理对象
/**
*newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
* loader 类加载器,用的是和被代理对象同一个加载器 固定写法
* interfaces 代理对象所实现的所有接口的Class类型的数组 固定写法
*
* InvocationHandler 接口,让你用来做增强的
*/
UserDao proxy = (UserDao) Proxy.newProxyInstance(dao.getClass().getClassLoader(), dao.getClass().getInterfaces(), new InvocationHandler() {
@Override
/**
* proxy 代理对象
* method 方法对象
* args 方法对象的参数的数组
*/
//方法调用会被转发到该类的invoke()方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//System.out.println("invoke 方法执行了");
Object invoke = null;
if (method.getName().equals("insert") || method.getName().equals("update")) {
System.out.println("权限校验的功能");
invoke = method.invoke(dao);
System.out.println("日志记录");
}
return invoke;
}
});
return proxy; //返回代理对象
枚举
JDK1.5的新特性: 自动拆装箱 , 泛型 , 增强for , 可变参数 , 枚举
枚举概述: 就是一个类只能存在几个固定的对象,那么这个就是枚举.我们就可以使用这些对象可以表示一些固定的值.
//方向
Direction front = Direction.FRONT;
Direction behind = Direction.BEHIND;
Direction left = Direction.LEFT;
Direction right = Direction.RIGHT;
System.out.println(front);
System.out.println(behind);
System.out.println(left);
System.out.println(right);
//Ctrl+Shift+U 转换大小写
public static final Direction FRONT = new Direction();
public static final Direction BEHIND = new Direction();
public static final Direction LEFT = new Direction();
public static final Direction RIGHT = new Direction();
private Direction() {
}
//方向
Direction front = Direction.FRONT;
Direction behind = Direction.BEHIND;
Direction left = Direction.LEFT;
Direction right = Direction.RIGHT;
System.out.println(front.name);
System.out.println(behind.name);
System.out.println(left.name);
System.out.println(right.name);
System.out.println("===============================");
front.show("前");
behind.show("后");
left.show("左");
right.show("右");
public static final Direction FRONT = new Direction("前") {
@Override
public void show(String name) {
System.out.println(name);
}
};
public static final Direction BEHIND = new Direction("后") {
@Override
public void show(String name) {
System.out.println(name);
}
};
public static final Direction LEFT = new Direction("左") {
@Override
public void show(String name) {
System.out.println(name);
}
};
public static final Direction RIGHT = new Direction("右") {
@Override
public void show(String name)
{
System.out.println(name);
}
};
String name;
private Direction(String name) {
this.name = name;
}
public abstract void show(String name);
/*枚举项,多个枚举项用逗号隔开,最后一个枚举项,下面没有内容了分号可以省略不写
* 枚举项要位于第一行
* */
FRONT, BEHIND, LEFT, RIGHT;
int num = 0;
private Direction() {
}
private Direction(String name) {
}
一个人枚举类有一个枚举项,其实就是一单例模式的饿汉式
FRONT("前"), BEHIND("后"), LEFT("左"), RIGHT("右");
String name;
枚举的注意事项
定义枚举类要用关键字enum
所有枚举类都是Enum的子类,但是不要显式的写出来
枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的东西,这个分号就不能省略。建议不要省略
枚举类可以有构造器,但必须是private的,它默认的也是private的。枚举项的用法比较特殊:枚举(“”);
枚举类也可以有抽象方法,但是枚举项必须重写该方法
枚举在switch语句中的使用
枚举类的常见方法
int ordinal() 返回枚举项的序号
int compareTo(E o) 比较两个枚举项的 返回的是两个枚举项序号的 差值
String name() 获取枚举项的名称
String toString()获取枚举项的名称
<T> T valueOf(Class<T> type,String name) 用来获取指定的枚举项 参数1:枚举类对应的字节码对象 参数2 枚举项的名称
values() 获取所有的枚举项
此方法虽然在JDK文档中查找不到,但每个枚举类都具有该方法,它遍历枚举类的所有枚举值非常方便
B:案例演示: 枚举类的常见方法
public static void main(String[] args) {
// 测试
Direction front = Direction.FRONT ;
Direction behind = Direction.BEHIND;
Direction left = Direction.LEFT ;
Direction right = Direction.RIGHT ;
System.out.println(front.ordinal());
System.out.println(behind.ordinal());
System.out.println(left.ordinal());
System.out.println(right.ordinal());
System.out.println("----------------------------------");
System.out.println(front.compareTo(right));
System.out.println("----------------------------------");
System.out.println(front.name());
System.out.println("----------------------------------");
System.out.println(front.toString());
System.out.println(front);
System.out.println("----------------------------------");
// <T> T valueOf(Class<T> type,String name): 用来获取指定的枚举项
// type: 表示的是对应的枚举的字节码文件对象
// name: 就是枚举项的名称
Direction direction = Direction.valueOf(Direction.class, "RIGHT") ;
System.out.println(direction);
System.out.println("----------------------------------");
Direction[] directions = Direction.values() ;
for(Direction d : directions){
System.out.println(d);
}
}
如果你的枚举类,只有一个枚举项,那其实就是个单列模式
public enum Student {
S
}
public static void main(String[] args) {
Student s = Student.S;
Student s2 = Student.S;
System.out.println(s == s2);
}
public static void main(String[] args) {
//_ 千位分隔符
//int money=10_000_000;
}