java反射

14

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中创建对象的方式

  1. new关键字 调用构造方法
  2. 反序列化 自动不调用构造方法,手动调用
  3. 反射 调用构造方法
  4. 克隆 不调用构造方法(注意事项:类必须实现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]