美文网首页
java 反射学习(上)

java 反射学习(上)

作者: tingtingtina | 来源:发表于2020-04-15 13:43 被阅读0次

    Class 类

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


    知识导图

    获取类对象

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

    • 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 文件,或者其他读取文件配置的方式来改善。

    相关文章

      网友评论

          本文标题:java 反射学习(上)

          本文链接:https://www.haomeiwen.com/subject/mnkcvhtx.html