java反射
1.什么是类对象?
反射
:把Java类中的各种成分映射成单独的Java对象进行操作。
类的对象
:基于某个类new 出来的对象,也称为实例对象。
类对象
:类加载的产物,封装了一个类的所有信息(类名、父类、接口、属性、方法、构造方法)
2.反射相关类
- Class类—可获取类和类的成员信息
- Field类—可访问类的属性
- Method类—可调用类的方法
- Constructor类—可调用类的构造方法
创建一个测试类
public class Student implements Serializable, Cloneable{
private String name;
private int age;
public Student() {
System.out.println("无参构造执行了");
}
public Student(String name, int age) {
this.name = name;
this.age = age;
System.out.println("带参构造执行了");
}
private Student(String name) {
this.name = name;
}
//1.无参无返回值的方法
public void show() {
System.out.println(this.name + " " + this.age);
}
//2.有参无返回值的方法
public void show(String addr) {
System.out.println("地址:" + addr);
}
//3.无参带返回值的方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//4.静态方法
public static void calcount() {
System.out.println("计算学生数量");
}
//5.私有方法
private void study() {
System.out.println("好好学习!");
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
3.获取类对象
- 实例对象.getClass()
- 类名.calss()
- Class.forName("类的全名称带包名")
[wppay]
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
reflectOpe1();
}
//1.反射获取类对象
public static void reflectOpe1() throws Exception {
//1.创建一个实例对象,调用实例对象的getClass()方法
Student student = new Student("a", 22);
Class<?> aClass = student.getClass();
System.out.println(aClass.hashCode());
//2.使用类名.class属性
Class<Student> aClass1 = Student.class;
System.out.println(aClass1.hashCode());
//3.Class.forName() 方法获取,类的全名称带包名
//推荐:编译时不依赖于目标类,耦合性低,灵活,扩展性强
Class<?> aClass2 = Class.forName("com.fq.lz.Day24.Reflection_4.Student");
System.out.println(aClass1.hashCode());
}
}
4.获取构造方法
首先要获取类对象
- 获取所有的公开的构造方法
- 类对象.getConstructors()
- 获取私有的,保护的,公有的所有构造方法
- 类对象.getDeclaredConstructors()
- 获取一个无参构造方法
- 类对象.getConstructor()
- 使用构造方法创建对象
- 无参构造对象.newInstance("as", 12);
- 简写创建对象的方式调用无参构造
- 类对象.newInstance();
//2.获取构造方法
public static void reflectOpe2() throws Exception {
//1.获取类对象
Class<?> aClass2 = Class.forName("com.fq.lz.Day24.Reflection_4.Student");
//2.获取构造方法
//获取所有的公开的构造方法
Constructor<?>[] constructors = aClass2.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
System.out.println("----------------------------");
//获取私有的,保护的,公有的所有构造方法
Constructor<?>[] constructors1 = aClass2.getDeclaredConstructors();
for (Constructor<?> constructor : constructors1) {
System.out.println(constructor);
}
System.out.println("----------------------------");
//3.获取一个无参构造方法
Constructor<?> constructor = aClass2.getConstructor();
//创建一个学生对象
Student instance = (Student) constructor.newInstance();
System.out.println(instance.toString());
System.out.println("----------------------------");
//4.获取一个带参的构造方法
Constructor<?> constructor1 = aClass2.getConstructor(String.class, int.class);
//创建一个学生对象
Student instance1 = (Student) constructor1.newInstance("as", 12);
System.out.println(instance1.toString());
//5.简写创建对象的方式 - 无参构造
Student o = (Student) aClass2.newInstance();
}
5.获取方法
首先要获取类对象
- 获取所有的公开方法,包括继承的
- 类对象.getMethods();
- 获取一个无参无返回值的方法
- 类对象.getMethods("方法名");
- 调用此方法
- 1.创建实例对象:类对象.newInstance();
- 2.无参无返回值对象.invoke(实例对象);
- 获取一个有参无返回值的
- 类对象.getMethod("方法名", 参数类型.class);
- 获取无参带返回值的
- 类对象.getMethod("方法名");
- 获取返回值:无参带返回值对象.nvoke(实例对象);
- 获取静态方法
- 类对象.getMethod("方法名");
- 调用,静态方法对象.invoke(null);
- 获取私有方法
- 类对象.getDeclaredMethod("方法名");
- 私有方法对象.setAccessible(true); 把私有的访问权限设置为无效
- 私有方法对象.invoke(实例对象);
//1.首先拿到类对象
Class<?> aClass2 = Class.forName("com.fq.lz.Day24.Reflection_4.Student");
//2.获取所有的公开方法,包括继承的
Method[] methods = aClass2.getMethods();
for (Method method : methods) {
System.out.println(method.toString());
}
//3.获取类中所有的方法,包括私有保护默认,不包括继承
Method[] methods1 = aClass2.getDeclaredMethods();
for (Method method : methods1) {
System.out.println(method);
}
//4.1获取一个无参无返回值的方法
Method show = aClass2.getMethod("show");
//要调用方法要创建对象
Student instance = (Student) aClass2.newInstance();
System.out.println(show.invoke(instance));
//4.2 获取一个有参无返回值的
Method show2 = aClass2.getMethod("show", String.class);
show2.invoke(instance, "北京");
//4.3 获取无参带返回值的
Method toString = aClass2.getMethod("toString");
String invoke = (String) toString.invoke(instance);
System.out.println(invoke);
//5.获取静态方法
Method calcount = aClass2.getMethod("calcount");
calcount.invoke(null);
//6.获取私有方法
Method study = aClass2.getDeclaredMethod("study");
//把私有的访问权限设置为无效
study.setAccessible(true);
study.invoke(instance);
6.获取类的属性
首先拿到类对象
- 获取所有的属性
- 类对象.getDeclaredFields();
- 获取单个属性(私有的)
- 类对象.getDeclaredField("属性名");
- 属性对象.setAccessible(true);
- 属性对象.set(实例对象, 值);
//4.获取类的属性
public static void reflectOpe4() throws Exception {
//1.首先拿到类对象
Class<?> aClass2 = Class.forName("com.fq.lz.Day24.Reflection_4.Student");
//2.获取所有的属性
Field[] declaredFields = aClass2.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field);
}
//3.获取单个属性(私有的)
Field name = aClass2.getDeclaredField("name");
Field age = aClass2.getDeclaredField("age");
Student student = (Student) aClass2.newInstance();
name.setAccessible(true);
name.set(student, "asd");
age.setAccessible(true);
age.set(student, 20);
System.out.println(student.toString());
}
7.获取这个类的名字,包名,父类,实现接口,继承的类
//5.获取这个类的名字,包名,父类,实现接口,继承的类
public static void reflectOpe5() throws Exception {
//1.首先拿到类对象
Class<?> aClass2 = Class.forName("com.fq.lz.Day24.Reflection_4.Student");
//全名称
System.out.println(aClass2.getName());
//简单名称
System.out.println(aClass2.getSimpleName());
//包名
System.out.println(aClass2.getPackage().getName());
//类的父类
System.out.println(aClass2.getSuperclass().getName());
//类实现的接口
Class<?>[] interfaces = aClass2.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println(anInterface);
}
}
8.面试题:java中创建对象的方式
- new关键字 调用构造方法
- 反序列化 自动不调用构造方法,手动调用
- 反射 调用构造方法
克隆
不调用构造方法(注意事项:类必须实现Cloneable接口,重写clone方法)
- 浅克隆(浅拷贝)
- 深克隆(深拷贝)
9.反射优点和缺点
优点:
- 提高了Java程序的灵活性和扩展性,降低了耦合性,提高自适应能力
- 允许程序创建和控制任何类的对象,无需提前硬编码目标类
缺点:
- 性能问题
- 代码维护问题
10.案例:使用反射实现插件开发
步骤:先编写接口,创建一个类实现接口,编译这个实现类,拿出来这个类的class文件,删除这个文件(class文件也被删除类),将保存的class文件重新加入out目录下。创建一个txt文本,保存这个实现类的包名。
接口
public interface CarService {
void run();
void turn();
}
实现类
public class BMW implements CarService{
@Override
public void run() {
System.out.println("1");
}
@Override
public void turn() {
System.out.println("2");
}
}
测试类
public class Test {
public static void main(String[] args) throws Exception {
FileReader fr = new FileReader("cars.txt");
BufferedReader br = new BufferedReader(fr);
String data;
while ((data = br.readLine())!=null){
Class<?> aClass = Class.forName(data);
CarService carService = (CarService) aClass.newInstance();
carService.run();
carService.turn();
}
br.close();
}
}
txt文件
com.fq.lz.Day25.Demo1.BMW
11.内省设置属性
通过反射获取类的getter和setter方法,对属性进行设置。
- PropertyDescriptor:属性描述符,代表一个属性
- BeanInfo:实体类信息,包含类的信息
- Introspector:工具类
public class TestIntrospector {
public static void main(String[] args) throws Exception {
//使用反射创建学生
Class<?> aClass = Class.forName("com.fq.lz.Day25.Demo2.Student");
Student student = (Student) aClass.newInstance();
//1.使用内省技术对属性赋值
PropertyDescriptor pd1 = new PropertyDescriptor("name", aClass);
pd1.getWriteMethod().invoke(student, "test");//setName
//descriptor.getReadMethod();//getName
PropertyDescriptor pd2 = new PropertyDescriptor("age", aClass);
pd2.getWriteMethod().invoke(student, 20);
System.out.println(student.toString());
System.out.println("----------------------------------------");
//2.BeanInfo : 获取类的信息
//Introspector : 内省的工具类
BeanInfo beanInfo = Introspector.getBeanInfo(aClass);
//属性:
// * 狭义的属性:类中的字段
// * 广义的属性:类中所包含的以getXxx(有返回值)和setXxx(有参数),isXxx(返回值boolean值)表示这个类中有Xxx属性
//age int
//class class java.lang.Class : Object类中
//name class java.lang.String
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
System.out.println(pd.getName() + " " + pd.getPropertyType());
}
}
}
12.设计模式
什么是设计模式:
- 一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
- 可以简单理解为特定问题的固定解决方法。
好处:
- 使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、重用性。
12.1简单工厂模式
- 定义一个工厂类,他可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类,简单工厂模式主要负责对象创建的问题。
- 开发中有一个非常重要的原则“开i闭原则”,对拓展开放、对修改关闭。
- 可通过反射进行工厂模式的设计,完成动态的对象创建。
简单工厂四个角色:
- 工厂角色:负责创建具体的产品。
- 父类产品:作为所有产品的父类,使用抽象类、接口表示·
- 子类产品:具体的产品
- 客户程序:使用工厂和产品的程序
//服装类
public abstract class Clothes {
abstract void prepare();
abstract void make();
abstract void sell();
}
public class Trousers extends Clothes {
@Override
void prepare() {
System.out.println("准备Trousers制作的材料");
}
@Override
void make() {
System.out.println("开始制作");
}
@Override
void sell() {
System.out.println("销售裤子");
}
}
public class TShirt extends Clothes{
@Override
void prepare() {
System.out.println("准备TShirt制作的材料");
}
@Override
void make() {
System.out.println("开始制作");
}
@Override
void sell() {
System.out.println("销售裤子");
}
}
//衣服工厂
public class ClothesFactory {
public static Clothes create(int type) {
Clothes clothes = null;
switch (type) {
case 1:
clothes = new Trousers();
break;
case 2:
clothes = new TShirt();
break;
default:
System.out.println("类型错误");
break;
}
if (clothes!=null){
clothes.prepare();
clothes.make();
clothes.sell();
}
return clothes;
}
}
public class TestFactory {
public static void main(String[] args) {
System.out.println("请输入:1.裤子,2.T恤");
Scanner scanner = new Scanner(System.in);
int i = scanner.nextInt();
switch (i){
case 1:
ClothesFactory.create(1);
break;
case 2:
ClothesFactory.create(2);
break;
default:
System.out.println("输入错误");
break;
}
}
}
存在的问题:再添加一个衣服时,衣服工厂类需要修改,不符合开闭原则
使用反射和配置文件对衣服工厂类进行优化
clothes.propertices
1=com.fq.lz.Day25.Factory.Trousers
2=com.fq.lz.Day25.Factory.TShirt
衣服工厂类
//衣服工厂
public class ClothesFactory {
private static Properties properties = new Properties();
static {
try {
FileInputStream fis = new FileInputStream("src\\clothes.propertices");
properties.load(fis);
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static Clothes create(int type) {
Clothes clothes = null;
if (properties.containsKey(String.valueOf(type))) {
String className = properties.getProperty(String.valueOf(type));
try {
clothes = (Clothes) Class.forName(className).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
/*switch (type) {
case 1:
clothes = new Trousers();
break;
case 2:
clothes = new TShirt();
break;
default:
System.out.println("类型错误");
break;
}*/
if (clothes != null) {
clothes.prepare();
clothes.make();
clothes.sell();
}
return clothes;
}
}
12.2 单例模式
只允许创建一个该类的对象。
实现步骤:
- 1.私有化构造方法
- 2.在类内部创建一个该类的对象
- 3.在类中添加一个公开的静态方法,返回单例对象
饿汉式
方式1:饿汉式(类加载时创建,天生线程安全)
//饿汉式:类加载时对象实例化
// * 优点:线程安全
// * 缺点:生命周期长,占用空间时间长
public class SingleTon {
//1.私有化构造方法
private SingleTon() {
}
//2.在单例类的内部创建一个对象
private static final SingleTon SINGLE_TON = new SingleTon();
//3.添加公开的静态方法
public static SingleTon getInstance() {
return SINGLE_TON;
}
//测试方法
public void show() {
System.out.println("测试方法");
}
}
class Test {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
SingleTon instance = SingleTon.getInstance();
System.out.println(instance.hashCode());
}
}).start();
}
}
}
懒汉式
//懒汉式:使用实例对象时初始化单例对象
// * 优点:节省空间
// * 缺点:解决线程安全问题
public class LazySingleton {
private LazySingleton() {
}
//保证内存可见性和禁止指令重排序
private volatile static LazySingleton singleTon2 = null;
public static LazySingleton getInstance() {
//DCL Double Check Lock 双重检查锁
//多线程情况下也只需要一次实例化,提高效率
if (singleTon2 == null) {
//同步代码块 保证多线程情况下线程同步
synchronized (LazySingleton.class) {
//当singleTon2为空时才创建对象,但是多线程情况下,不能保证创建唯一对象
if (singleTon2 == null) {
singleTon2 = new LazySingleton();
//1.在堆中开辟空间,给属性赋值默认值 new dup
//2.执行构造方法 invokespecial
//3.局部变量赋值 a_store
//可能执行的顺序会改变
}
}
}
return singleTon2;
}
public void show() {
System.out.println("显示");
}
}
class Test2 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance.hashCode());
}
}).start();
}
}
}
静态内部类写法
使用时创建,线程安全
//静态内部类写法
public class King {
//1.私有化构造方法
private King() {
}
//2.在静态内部类中创建对象(不使用静态内部类,不会加载)
private static class Holder {
private static final King KING = new King();
}
//3.添加公开的静态方法返回对象
public static King getInstance() {
return Holder.KING;
}
}
13.枚举
什么是枚举:
- 枚举是一个引用类型,枚举是一个规定了取值范围的数据类型。
- 枚举变量不能使用其他的数据,只能使用枚举中常量赋值,提高程序安全性。
- 定义枚举使用enum关键字。
注意事项
- 1.枚举中可以包含public static final
静态常量(只写常量名)
,常量之间用逗号隔开,后面的分号可写可不写
- 2.
枚举还可以包含属性和方法,必须在常量的后面,常量后面的分号必须写
- 3.
也可以写构造方法,但是必须是私有的,不写也是私有的
public enum Gender {
ONE,
TWO;
private Gender() {
}
private int value;
public void show() {
System.out.println(value);
}
}
class Person{
private int age;
private Gender gender;
public Person() {
}
public Person(int age, Gender gender) {
this.age = age;
this.gender = gender;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", gender=" + gender +
'}';
}
}
class Test{
public static void main(String[] args) {
Person person = new Person(10, Gender.ONE);
System.out.println(person.toString());
}
}
枚举的本质
本质上说java引用数据类型只有数组、类、接口
- 枚举是一个终止类,并继承Enum抽象类。
- 枚举中常量是当前类型的静态常量。
package com.fq.lz.Day25.Enum5;
import java.io.PrintStream;
public final class Gender extends Enum
{
public static final Gender ONE;
public static final Gender TWO;
private int value;
private static final Gender $VALUES[];
public static Gender[] values()
{
return (Gender[])$VALUES.clone();
}
public static Gender valueOf(String name)
{
return (Gender)Enum.valueOf(com/fq/lz/Day25/Enum5/Gender, name);
}
private Gender(String s, int i)
{
super(s, i);
}
public void show()
{
System.out.println(value);
}
static
{
ONE = new Gender("ONE", 0);
TWO = new Gender("TWO", 1);
$VALUES = (new Gender[] {
ONE, TWO
});
}
}
枚举实现单例
//枚举单例:只添加一个常量,类加载时就实例化,没有线程安全问题,
// 防止反射破解
public enum SingleTon {
INSTANCE;
public void show() {
System.out.println("show");
}
}
class Test3{
public static void main(String[] args) {
SingleTon instance = SingleTon.INSTANCE;
SingleTon instance2 = SingleTon.INSTANCE;
SingleTon instance3 = SingleTon.INSTANCE;
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
System.out.println(instance3.hashCode());
}
}
14.注解
什么是注解:
- 注解(Annotation)是代码里的特殊标记, 程序可以读取注解,一般用于替代配置文件。
开发人员可以通过注解告诉类如何运行
- 在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。
常见注解:@Override、@Deprecated
定义一个注解:使用@interface 使用注意事项:
- (1) 注解中只能包含属性,属性有括号
- (2) 注解中属性可以写默认值,使用default关键字添加默认值
- (3) 注解中属性的类型只能是5种:
- 基本类型,字符串类型,class类型,枚举类型,注解类型, 以及这些类型的一维数组
- (4) 如果注解的属性名是value的可以省略
- @Target(value = ElementType.METHOD)
- @Target(ElementType.METHOD)
元注解:用来描述注解的注解。
- @Retention 设置注解保留的范围
- 默认CLASS 字节码文件有,运行时消失
- RUNTIME 字节码有,运行时有
- SOURCE 源文件有,字节码没有
- @Target
- 指定注解用于修饰类的哪个成员。
//定义一个注解:使用@interface
//使用注意事项:
// (1) 注解中只能包含属性,属性有括号
// (2) 注解中属性可以写默认值,使用default关键字添加默认值
// (3) 注解中属性的类型只能是5种:
// 基本类型,字符串类型,class类型,枚举类型,注解类型,
// 以及这些类型的一维数组
// (4) 如果注解的属性名是value的可以省略
// @Target(value = ElementType.METHOD)
// @Target(ElementType.METHOD)
//@Retention 设置注解保留的范围
// 默认CLASS 字节码文件有,运行时消失
// RUNTIME 字节码有,运行时有
// SOURCE 源文件有,字节码没有
//@Target:
// * 指定注解用于修饰类的哪个成员。
//6.修改保留级别
@Retention(value = RetentionPolicy.RUNTIME)
//7. 指定注解用于修饰类的哪个成员。一般不改
@Target(value = ElementType.METHOD)
public @interface MyAnnotation {
//属性写括号,只能是public,不能是静态的
public String name() default "test";
//设置默认值
public int age() default 20;
// Class<?> CLASS();
//
// Gender GENDER();
//
// Test TEST();
}
@interface Test {
}
class Person {
//5.但是运行的时候就没了
@MyAnnotation(name = "test", age = 30)
public void show() throws NoSuchMethodException {
//使用反射获取注解的信息
//1.获取类对象
Class<Person> personClass = Person.class;
//2.获取方法,由方法获得注解
Method show = personClass.getMethod("show");
//3.获取注解信息
MyAnnotation annotation1 = show.getAnnotation(MyAnnotation.class);
//4.打印注解的属性
System.out.println(annotation1.age());
System.out.println(annotation1.name());
}
}
class Test2 {
public static void main(String[] args) throws NoSuchMethodException {
Person person = new Person();
//运行错误 因为5,但是元注解可以解决问题见6
person.show();
}
}
注解的本质
注解的本质是接口
package com.fq.lz.Day25.Annotation6;
import java.lang.annotation.Annotation;
public interface MyAnnotation
extends Annotation
{
public abstract String name();
public abstract int age();
}
[/wppay]