反射
能够分析类能力的程序称为反射
- 在运行中分析类的能力
- 在运行中查看对象, 例如, 编写一个toString方法供所有类使用
- 实现通用的数组操作代码
- 利用Method对象, 这个对象很像C++中的函数指针
反射是一种功能强大且复杂的机制. 使用它的主要人员是工具构造者, 而不是应用程序员.
一. 什么是类对象
- 类的对象: 基于某个类new出来的对象, 也称为实例对象
- 类对象: 类加载的产物, 封装了一个类的所有信息(类名, 父类, 接口, 属性, 方法, 构造方法)
二. 获取类对象的方法
- 通过类的对象, 获取类对象
- Student s = new Student();
- Class c = s.getClass();
- 通过类名获取类对象
- Class c = 类名.class;
- 通过静态方法获取类对象[推荐使用]
- Class = Class.forName("包名.类名");
//显示类的加载过程, 在Run下的Edit Configurations下配置 -verbose:class:
public class TestClass {
public static void main(String[] args) throws Exception{
getClazz();
}
public static void getClazz() throws Exception{
//1. 通过类的对象, 获取类对象
Person p1 = new Person();
Class<?> class1 = p1.getClass();
//System.out.println(class1.toString());
System.out.println(class1.hashCode());
//2. 通过类名获取类对象
Class<?> class2 = Person.class;
System.out.println(class2.hashCode());
//3. 通过静态方法获取类对象[推荐使用]
Class<?> class3 = Class.forName("com.reflex.demo01.Person");
System.out.println(class3.hashCode());
}
}
三. 常见操作
1. 常用方法
public String getName()
public Package getPackage()
public Class<? super T> getSuperclass()
public Class<?>[] getInterfaces()
public Constructor<?>[] getConstructors()
public T newInstance()
public Method[] getMethods()
public Field[] getField()
2. 常见操作
public class Demo01 {
public static void main(String[] args) throws Exception{
reflect1();
reflect2();
reflect3();
Properties properties = new Properties();
//properties.setProperty("name", "zhangsan");
//System.out.println(properties.toString());
invokeAny(properties, "setProperty", new Class[]{String.class, String.class}, "username", "张三");
System.out.println(properties.toString());
reflect4();
}
//1. 使用反射获取类的名字, 包名, 父类, 接口
public static void reflect1() throws Exception{
//(1) 获取类对象Person
Class<?> class1 = Class.forName("com.reflex.demo01.Person");
//getName() 获取类名
System.out.println(class1.getName());//com.reflex.demo01.Person
//getPackage() 获取包名
System.out.println(class1.getPackage().getName());//com.reflex.demo01
//getSuperClass() 获取父类
System.out.println(class1.getSuperclass().getName());//java.lang.Object
//getInterfaces() 获取接口
Class<?>[] classes = class1.getInterfaces();
System.out.println(Arrays.toString(classes));//[interface java.io.Serializable, interface java.lang.Cloneable]
System.out.println(class1.getSimpleName());//Person 只获取类名,不要前缀
System.out.println(class1.getTypeName());//com.reflex.demo01.Person 获取类名
}
//2. 使用反射获取类的构造方法, 创建对象
public static void reflect2() throws Exception{
//(1) 获取类的类对象
Class<?> class2 = Class.forName("com.reflex.demo01.Person");
//(2) 获取类的所有构造方法Constructor
// Constructor<?>[] cons = class2.getConstructors();
// for (Constructor<?> con : cons) {
// System.out.println(con.toString());
// }
//(3) 获取类中无参构造方法
Constructor<?> con1 = class2.getConstructor();
Person zhangsan = (Person)con1.newInstance();
System.out.println(zhangsan.toString());
Person lisi = (Person)con1.newInstance();
System.out.println(lisi.toString());
//简便方法: 类对象.newInstance
Person wangwu = (Person)class2.newInstance();
System.out.println(wangwu.toString());
//(4) 获取类中带参构造方法
Constructor<?> con2 = class2.getConstructor(String.class, int.class);
Person xiaoli = (Person)con2.newInstance("小李", 17);
System.out.println(xiaoli.toString());
}
//3. 使用反射获取类中的方法, 并调用方法
public static void reflect3() throws Exception{
//(1) 获取类对象
Class<?> class3 = Class.forName("com.reflex.demo01.Person");
//(2) 获取方法Method对象
//2.1 getMethods() 只能获取公开的方法, 包括从父类继承过来的方法
//Method[] methods = class3.getMethods();
//2.2 getDeclaredMethods() 获取类中所有的方法, 包括私有, 默认, 保护的, 不包含继承的方法
Method[] methods = class3.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.toString());
}
//(3) 获取单个方法
//3.1 run 无参方法
Method runMethod = class3.getMethod("run");
//调用方法
//正常调用 对象.run();
Person lily = (Person) class3.newInstance();
runMethod.invoke(lily);
System.out.println("--------------------------");
//3.2 toString 有返回值
Method toStringMethod = class3.getMethod("toString");
Object result = toStringMethod.invoke(lily);
System.out.println(result);
System.out.println("---------------------------");
//3.3 eat 带参方法
Method eatMethod = class3.getMethod("eat", String.class);
eatMethod.invoke(lily, "苹果");
System.out.println("--------------------------");
//3.4 私有方法
Method privateMethod = class3.getDeclaredMethod("privateMethod");
//设置访问权限无效
privateMethod.setAccessible(true);
privateMethod.invoke(lily);
System.out.println("--------------------------");
//3.5 静态方法
Method staticMethod = class3.getMethod("staticMethod");
//正常调用 Person.staticMethod
staticMethod.invoke(null);
}
//4. 使用反射实现一个可以调用任何对象方法的通用方法
public static Object invokeAny(Object obj, String methodName, Class<?>[] types, Object...args) throws Exception{
//1. 获取类对象
Class<?> classAll = obj.getClass();
//2. 获取方法
Method method = classAll.getMethod(methodName, types);
//3. 调用
return method.invoke(obj, args);
}
//5. 使用反射获取类中的属性
public static void reflect4() throws Exception{
//(1) 获取类对象
Class<?> class4 = Class.forName("com.reflex.demo01.Person");
//(2) 获取属性(字段) 公开的字段, 父类继承的字段
//Field[] fields = class4.getFields();//0 此方法无法获取字段
//getDeclaredFields() 获取所有的属性, 包括私有, 默认, 保护的, 不包含继承的属性
// Field[] fields = class4.getDeclaredFields();
// System.out.println(fields.length);
// for (Field field : fields) {
// System.out.println(field.toString());
// }
//(3) 获取name属性
Field nameField = class4.getDeclaredField("name");
nameField.setAccessible(true);
//(4) 赋值 正常调用是Person person = new Person(); person.name = "张三";
Person person = (Person)class4.newInstance();
nameField.set(person, "张三");// person.setName = "张三";
//(5) 获取值
System.out.println(nameField.get(person));// person.getName();
}
}
四. 设计模式介绍
- 什么是设计模式
- 一套被反复使用, 多数人知晓的, 经过分类编目的, 代码设计经验的总结. 简单理解: 特定问题的固定解决方法
- 好处:
- 使用设计模式是为了可重用代码, 让代码更容易被他人理解, 保证代码可靠性, 重用性
- 在Gof的<<设计模式>>书中描述了23种设计模式
五. 工厂设计模式
- 工厂模式主要负责对象创建的问题
- 开发中有一个非常重要的原则"开闭原则", 对拓展开放, 对修改关闭
- 可通过反射进行工厂模式的设计, 完成动态的对象创建
下面是工厂模式的示例
示例说明: 假设有一个机器上有很多Usb接口, 外部设备通过插入Usb接口就可以运行,比如, 鼠标键盘电风扇等等, 用反射实现添加新的产品无需修改工厂类
- 首先创建一个Usb接口
public interface Usb {
void service();
}
- 创建一个鼠标一个键盘类, 且都实现Usb接口
public class Mouse implements Usb{//鼠标类
@Override
public void service() {
System.out.println("鼠标开始工作了...");
}
}
public class KeyBoard implements Usb{//键盘类
@Override
public void service() {
System.out.println("键盘开始工作了");
}
}
- 创建工厂类
public class UsbFactory {
public static Usb creatUsb(String type){
Usb usb = null;
Class<?> class1;
try {
class1 = Class.forName(type);// 获取类对象
usb = (Usb)class1.newInstance();// 实例化类对象, 就可以调用接口内的方法了
} catch (Exception e) {
System.out.println(e.getMessage());
}
return usb;
}
}
- 创建usb.properties文件
1 = com.reflex.demo03.Mouse
2 = com.reflex.demo03.KeyBoard
- 创建测试文件
public class Demo {
public static void main(String[] args) throws Exception{
System.out.println("-----------请选择1鼠标, 2键盘--------");
//输入要使用的设备
Scanner input = new Scanner(System.in);
String choice = input.next();
//1 = com.reflex.demo03.Mouse
//2 = com.reflex.demo03.KeyBoard
// 我们需要把客户输入的数字1, 2转换为为其产品对应的类名
//创建Properties对象
Properties properties = new Properties();
//创建文件字节流
FileInputStream fis = new FileInputStream("D:\JavaSE\src\com\reflex\demo03\usb.properties");
//通过properties加载文件(键值对)
properties.load(fis);
fis.close();
//调用工厂类里的方法, 参数是properties通过键(1, 2)得到的值(com.reflex.demo03.Mouse, com.reflex.demo03.KeyBoard),也就是类名
Usb usb = UsbFactory.creatUsb(properties.getProperty(choice));
if (usb != null){
System.out.println("接入成功");
usb.service();// 调用方法
}else{
System.out.println("接入失败");
}
}
}
六. 单例设计模式
- 单例(Singleton): 只允许创建一个该类的对象
方式1: 饿汉式(类加载时创建, 天生线程安全)
- 优点: 线程安全
- 缺点: 生命周期太长, 浪费内存空间
public class SingleTon {
//1. 创建一个常量
private static final SingleTon instance = new SingleTon();
//2. 私有构造方法
private SingleTon() {
}
//3. 通过公开方法返回这个对象
public static SingleTon getInstance(){
return instance;
}
}
方式2: 懒汉式(使用时创建, 线程不安全, 加同步)
- 优点: 生命周期短, 节省内存空间
- 缺点: 有线程安全问题 解决办法: 使用同步方法或同步代码块
public class SingleTon2 {
//1. 创造一个对象, 赋值为null
private static SingleTon2 instance = null;
//2.私有构造方法
private SingleTon2() {}
//3. 通过一个公开的方法, 返回这个对象, 并加同步
public static SingleTon2 getInstance(){
if (instance == null){//提高执行效率
synchronized (SingleTon2.class){
if (instance == null){
instance = new SingleTon2();
}
}
}
return instance;
}
}
方式3: 懒汉式(使用时创建, 线程安全)
- 使用静态内部类的方式
public class SingleTon3 {
//1. 私有构造方法
private SingleTon3() {}
//2. 创建静态内部类(不使用时不执行)
private static class Holder {
static SingleTon3 instance = new SingleTon3();
}
//3.通过公开的方法, 返回静态内部类里的静态对象
public static SingleTon3 getInstance() {
return Holder.instance;
}
}
枚举
- 什么是枚举
- 枚举是一个引用类型, 枚举是一个规定了取值范围的数据类型
- 枚举变量不能使用其他的数据, 只能使用枚举中常量赋值, 提高程序安全性
- 定义枚举使用enum关键字
- 枚举的本质
- 枚举是一个终止类, 并继承Enum抽象类
- 枚举中常量是当前类型的静态常量
public enum Gender {//性别枚举
MALE, FEMALE;
}
public enum Season {//季节枚举
SPRING, SUMMER, AUTUMN, WINTER
}
public class TestGender {
public static void main(String[] args) {
Gender gender = Gender.MALE;
System.out.println(gender.toString());//MALE
Season season = Season.SPRING;
System.out.println(season.toString());//SPRING
}
}
枚举和switch语句的使用
public class demo01 {
public static void main(String[] args) {
//枚举和switch语句的使用
Season season = Season.WINTER;
switch (season) {// byte short int char string 枚举
case SPRING:
System.out.println("春天");
break;
case SUMMER:
System.out.println("夏天");
break;
case AUTUMN:
System.out.println("秋天");
break;
case WINTER:
System.out.println("冬天");
break;
default:
break;
}
}
}
注解
- 什么是注解
- 注解(Annotation)是代码里的特殊标记, 程序可以读取注解, 一般用于替代配置文件
- 开发人员可以通过注解告诉类如何运行
- 在Java技术里注解的典型应用是: 可以通过反射技术去得到类里面的注解, 以决定怎么去运行类
- 常见注解: @Override, @Deprecated
- 定义注解使用@interface关键字, 注解中只能包含属性
- 注解的本质是接口
创建注解类型
public @interface MyAnnotation {
//属性(类似方法)
String name() default "张三";//默认值
int age() default 20;//默认值
}
public class Person {
@MyAnnotation
public void show(){
}
}
- 注解属性类型
- String
- 基本数据类型
- Class类型
- 枚举类型
- 注解类型
- 以上类型的一维数组
public @interface MyAnnotation2 {
//属性
//字符串类型
String value();
//基本类型
int num();
//Class类型
Class<?> class1();
//枚举类型
Gender GENDER();
//注解类型
MyAnnotation ANNOTATION();
//ArrayList<String> list();
}
- 元注解: 用来描述注解的注解
- @Retention: 用于指定注解可以保留的域
- RetentionPolicy.CLASS: 注解记录在class文件中, 运行Java程序时, JVM不会保留.这是默认值
- RetentionPolicy.RUNTIME: 注解记录在class文件中, 运行Java程序时, JVM会保留, 程序可以通过反射获取该注解
- RetentionPolicy.SOURCE: 编译时直接丢弃这种策略的注释
- @Target: 指定注解用于修饰类的哪个成员
- 创建一个注解PersonInfo
@Retention(value = RetentionPolicy.RUNTIME)// 这样才能通过反射得到注解信息
@Target(value = {ElementType.METHOD})// 表示注解只能放在方法上面
public @interface PersonInfo {
String name();
int age();
String sex();
}
- 创建一个Person类, 里面写一个show()方法, 方法上使用注解PersonInfo
public class Person {
@PersonInfo(name = "张三", age = 19, sex = "男")
public void show(String name, int age, String sex){
System.out.println(name + "-----" + age + "-----" + sex);
}
}
- 使用反射得到注解信息method.getAnnotation
public class Demo {
public static void main(String[] args) throws Exception{
//1. 获取类对象
Class<?> class1 = Class.forName("com.reflex.demoAnnotation.Person");
//2. 获取方法
Method method = class1.getMethod("show", String.class, int.class, String.class);
//3. 获取方法上面得注解信息
PersonInfo personInfo = method.getAnnotation(PersonInfo.class);
//4. 打印注解信息
System.out.println(personInfo.name());// 张三
System.out.println(personInfo.age());// 19
System.out.println(personInfo.sex());// 男
//5. 调用方法
Person person = (Person)class1.newInstance();
method.invoke(person, personInfo.name(), personInfo.age(), personInfo.sex());// 张三-----19-----男
}
}