学新通技术网

java 反射(一)

juejin 7 1
java 反射(一)

Class 类

文本通过李兴华 java 视频教学中跟进学习,做以下知识总结。分为上下两部分,本篇介绍获取类对象以及类的实例化。

知识导图

获取类对象

整个反射的开发模式之中,有一个最为重要的组成就是 java.lang.Class 类,但是如果想要取得这个类的实例化对象,有三种方式可以完成:

  • Object 类之中存在一个 getClass() 方法:public final Class<?> getClass()

    • 此方法不能被子类覆写,而且所有类的实例化对象都可以调用。
  • 利用 包.类.class 的形式实例化 Class 类对象:

    • 例如:java.util.Date.class,在一些开源框架中大量使用到。
  • 利用 Class 类中提供的 forName() :

    public static Class<?> forName(String class) throws ClassNotFoundException
    
    • 主要可用用在工厂类上。

范例:验证第一种方式

public class TestDemo {
    public static void main(String[] args) {
        String str = "Hello";// 是 String 类的实例化对象
        Class<?> cls = str.getClass(); // 只要是实例化对象都具有
        System.out.println(cls);
    }
}

// 输出结果
class java.lang.String

以上的方式是不会使用到的,在所有的开发环境里面这种方式可以使用的几率是很低的。

范例:验证第二种方式

public class TestDemo {
    public static void main(String[] args) {
        Class<?> cls = java.lang.String.class;
        System.out.println(cls);
    }
}

此操作方式不需要得到指定操作类的实例化对象,而是通过类的名称就可以完成了,少了一个对象的空间。这种方式虽然严谨,那么却需要明确的结构,即:定义的时候类必须存在。

范例:验证第三种方式

public class TestDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> cls = Class.forName("java.lang.String");
        System.out.println(cls);
    }
}

使用此方式最大特点是:程序没有很强的严谨性,操作的类可以不存在,只要你程序不运行,那么就不会出现任何的语法错误,如果要运行,那么就把指定的类设置上。

类的实例化

取得 Class 类对象最后,就可以实例化类对象了。在 Class 类里面提供了一个实例化对象的操作方法。

public T newInstance() throws 
    InstantiationException, // 没有无参构造,类名错误
	IlleageAccessException  // 构造方法私有化

范例:利用反射实例化对象

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.tina.moduler.Book");
        Object obj = (Book)cls.newInstance(); // 表示实例化对象
        System.out.println(obj); // 输出对象调用 toString()
    }
}

public class Book {
    public Book() {
        System.out.println("Book 的无参构造方法");
    }

    @Override
    public String toString() {
        return "this is a book";
    }
}

// 输出结果
Book 的无参构造方法
this is a book

需要注意的是,使用 newInstance() 方法只能调用类中的无参构造方法,这个时候的功能相当于使用关键字 new 进行对象的实例化操作。

Q:使用关键字 new 实例化对象,以及使用反射实例化对象有什么区别?

如果只是单纯的对类进行对象的实例化,那么两者的区别不大,如果非要强调一个区别,那么就是反射实例化对象,它的灵活度更高,因为只需要传入包.类 名称的字符串就可以取得实例化对象,比使用 new 要宽松许多。

如果现在是一个子类需要为父接口进行对象实例化,那么如果使用了关键字 new,会造成接口对象的耦合性增加的问题,因为一个接口在使用中就与固定的一个子类进行绑定了,而最早的解耦合的方式是利用工厂设计模式,但是为了让一个类可以使用所有接口子类的扩展要求,则可以利用反射完成。

范例:传统方式取得接口对象

public class TestDemo {
    public static void main(String[] args) throws Exception {
      Message msg = new Email();
      msg.print("You have a new email message");
    }
}

public interface Message {
    public void print(String str);
}
class News implements Message {
    @Override
    public void print(String str) {
        System.out.println("新闻消息: " + str);
    }
}
class Email implements Message {
    @Override
    public void print(String str) {
        System.out.println("邮件消息: " + str);
    }
}

// 输出结果
邮件消息: You have a new email message

从上面的代码段可以看出,客户端代码需要负责具体的子类操作。

范例:利用工厂类

public class TestDemo {
    public static void main(String[] args) throws Exception {
      Message msg = Factory.getInstance("news");
      msg.print("You have a new email message");
    }
}

class Factory {
    public static Message getInstance(String className){
        if ("news".equalsIgnoreCase(className)){
            return new News();
        }else if ("email".equalsIgnoreCase(className)){
            return new Email();
        }else {
            return null;
        }
    }
}

// 输出结果
新闻消息: You have a new email message

虽然此时实现了工厂设计模式,但是本代码却具备一个非常大缺点(根据子类口扩充来决定的):每当接口扩充新的子类的时候,都需要去修改工厂类,这有背于开闭原则。优秀的编码:一个类完成之后是可以适应情况变化的,所以这种严谨性的代码,就不适合于实际的编写。

范例:利用反射优化工厂模式

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Message msg = Factory.getInstance("cn.tina.moduler.News");
        msg.print("You have a new email message");
    }

static class Factory {
    public static Message getInstance(String className){
        try {
            return (Message) Class.forName(className).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

这个时候虽然解决了工厂类的固化问题,但是有个新的问题产生了,客户端代码还是固定的。客户端必须明确写出要操作的类名称,这样一来就会造成新的耦合问题。

最好的设计方案永远不是点对点直达(最快),通过 key 找到 value。如果一个普通用户,那么一定不能修改程序的逻辑代码,但是可以修改文件的内容,如果要解决这种耦合问题,可以定义文件来存储属性,在 java.utils 包中有一个 Properties 类,是 Hashtable 的子类,这个类可以定义key = value 的文件。在 Android 中也有默认的 gradle.properties 文件,或者其他读取文件配置的方式来改善。

本文出至:学新通技术网

标签: