反射机制和工厂设计模式学习笔记
程序员文章站
2022-03-09 21:52:32
...
工厂模式专门负责将大量有共同接口的类实例化。工厂模式可以动态的决定将哪个类实例化。工厂模式有以下几种形态:
A.简单工厂模式(Simple Factory):又称静态工厂方法(Static Factory Method)模式;
B.工厂方法模式(Factory Method):又称多态性方法(PolyMorphic Factory)模式,或虚拟 构造方法(Viutual Constructor)模式;
C.抽象工厂模式(Abstract Factory):又称工具箱(ToolKit)模式
设计一个简单的工厂类如下:
运行结果:
[img]http://dl.iteye.com/upload/attachment/181727/960fb6ca-6e7c-36f1-acb7-f1a463f4bb80.jpg[/img]
客户端只与工厂和直接的接口有关了,而与其他的无关,但是有一个问题,如果现在要扩展多个子类,那么工厂也必须同时进行修改那么有没有一种方法可以让子类扩展后而不用去修改工厂呢?我们可以通过Class.forName()来完成。
看如下代码:
运行结果:
[img]http://dl.iteye.com/upload/attachment/181734/10234893-2155-338d-af33-b9e6c728e63d.jpg[/img]
上面程序中的“f = (Fruit)Class.forName(className).newInstance()”就是用的反射机制。关于反射机制,我们从Class类开始说起。
从API文档中查阅Class类定义如下:
[quote] Instances of the class Class represent classes and interfaces in a running Java application. An enum is a kind of class and an annotation is a kind of interface. Every array also belongs to a class that is reflected as a Class object that is shared by all arrays with the same element type and number of dimensions. The primitive Java types (boolean, byte, char, short, int, long, float, and double), and the keyword void are also represented as Class objects.
Class has no public constructor. Instead Class objects are constructed automatically by the Java Virtual Machine as classes are loaded and by calls to the defineClass method in the class loader.[/quote]
由以上API文档来理解,Class它和一般的classes一样都是继承自Object。它的实体(instance)用以表达Java程序运行时的classes和interface,也用来表达enum,array,primitive Java types(boolean,byte,char,short,int ,long.float,double)以及关键词void。Class 没有公共构造函数。当一个class被加载时,或当加载器(class loader)的defineClass()被JVM(Java 虚拟机)调用,JVM自动产生一个Class Object。
[quote]Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识。这项信息记录了每个对象所属的类。虚拟机通常使用运行时类型信息选准放正确方法去执行。用来保存这些类型信息的类就是Class类。Class类封装了一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建。
虚拟机为每种类型管理一个独一无二的class对象。也就是说每个类型都有一个class对象。运行程序时,Java虚拟机首先检查所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。
--选自51CTO网站网友熔岩的博客文章《深入研究Java.lang.class类》[/quote]
那么如何获得Class对象呢?有一下三种方法:
[b]A.调用Object类的getClass()方法得到Class对象。[/b]
运行结果:
[table]
| Person|
[/table]
[b]2.使用Class类的静态方法forName(String str)方法获得与字符串对应的Class对象。[/b]
运行结果:
[img]http://dl.iteye.com/upload/attachment/181755/21e62477-33bf-3658-bc76-8480cafd9f3b.jpg[/img]
以上程序中的newInstance()方法,API中定义如下:
[quote]Creates a new instance of the class represented by this Class object.[/quote]
也就是创建了一个这个Class对象所代表的类的实例。注意:如果要用以上代码来实例化一个对象,则必须有一个前提条件:在对象所在的类中必须要有一个无参的构造方法,如果没有此无参构造方法,则会出现错误。就是说如果将以上程序中的Person类的构造方法去掉了注释符号,此时Person类就失去了它的默认的无参构造方法,那么即使编译通过,最终运行还是发生以下错误:
[img]http://dl.iteye.com/upload/attachment/181761/788dc988-0af9-30ee-8bc1-e33ba6e39b39.jpg[/img]
关于newInstance方法实例对象与用new关键字来实例化对象的区别,参考网上的一篇文章(我发现网上大部分对这个问题的回答都是Ctrl+C Ctrl+V了这篇文章,至于原创者是谁也无从得知了。):
[quote]在初始化一个类,生成一个实例的时候;newInstance() 和 new 有什么区别?
用newInstance与用new是区别的,区别在于创建对象的方式不一样,前者是使用类加载机制,那么为什么会有两种创建对象方式?这个就要从可伸缩、可扩展,可重用等软件思想上解释了。
Java中工厂模式经常使用newInstance来创建对象,因此从为什么要使用工厂模式上也可以找到具体答案。
例如:
Class c = Class.forName(“A”);factory = (AInterface)c.newInstance();
其中AInterface是A的接口,如果下面这样写,你可能会理解:
String className = "A";Class c = Class.forName(className);factory = (AInterface)c.newInstance();
进一步,如果下面写,你可能会理解:
String className = readfromXMlConfig;//从xml 配置文件中获得字符串Class c = Class.forName(className);factory = (AInterface)c.newInstance();
上面代码就消灭了A类名称,优点:无论A类怎么变化,上述代码不变,甚至可以更换A的兄弟类B , C , D....等,只要他们继承Ainterface就可以。
从jvm的角度看,我们使用new的时候,这个要new的类可以没有加载;
但是使用newInstance时候,就必须保证:1、这个类已经加载;2、这个类已经连接了。而完成上面两个步骤的正是class的静态方法forName()方法,这个静态方法调用了启动类加载器(就是加载java API的那个加载器)。
有了上面jvm上的理解,那么我们可以这样说,newInstance实际上是把new这个方式分解为两步,即,首先调用class的加载方法加载某个类,然后实例化。
这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了我们降耦的手段。
[补充:]
newInstance: 弱类型。低效率。只能调用无参构造。
new: 强类型。相对高效。能调用任何public构造。
newInstance()是实现IOC、反射、面对接口编程 和 依赖倒置 等技术方法的必然选择,new 只能实现具体类的实例化,不适合于接口编程。
里面就是通过这个类的默认构造函数构建了一个对象,如果没有默认构造函数就抛出InstantiationException, 如果没有访问默认构造函数的权限就抛出IllegalAccessException [/quote]
[b]3.第三种方法很简单,如果T是一个Java类型,那么T.class就代表了匹配的类对象。[/b]
如:
Class c3 = Manager.class;
Class c4 = int.class;
Class c5 = Double[].class;
A.简单工厂模式(Simple Factory):又称静态工厂方法(Static Factory Method)模式;
B.工厂方法模式(Factory Method):又称多态性方法(PolyMorphic Factory)模式,或虚拟 构造方法(Viutual Constructor)模式;
C.抽象工厂模式(Abstract Factory):又称工具箱(ToolKit)模式
设计一个简单的工厂类如下:
interface Fruit{
public void grow();
public void eat();
}
class Apple implements Fruit {
public void grow() {
System.out.println("苹果在生长");
}
public void eat() {
System.out.println("吃苹果");
}
}
class Orange implements Fruit {
public void grow() {
System.out.println("橘子在生长");
}
public void eat() {
System.out.println("吃橘子");
}
}
class Factory {
public static Fruit getFruit(int i) {
Fruit f = null;
if(i == 1) {
f = new Apple();
}
if(i == 2) {
f = new Orange();
}
return f;
}
}
public class TestFactory {
public static void main(String[] args){
Fruit f = Factory.getFruit(1);
f.grow();
}
}
运行结果:
[img]http://dl.iteye.com/upload/attachment/181727/960fb6ca-6e7c-36f1-acb7-f1a463f4bb80.jpg[/img]
客户端只与工厂和直接的接口有关了,而与其他的无关,但是有一个问题,如果现在要扩展多个子类,那么工厂也必须同时进行修改那么有没有一种方法可以让子类扩展后而不用去修改工厂呢?我们可以通过Class.forName()来完成。
看如下代码:
interface Fruit {
public void grow();
public void eat();
}
class Apple implements Fruit {
public void grow() {
System.out.println("苹果在生长");
}
public void eat() {
System.out.println("吃苹果");
}
}
class Orange implements Fruit {
public void grow() {
System.out.println("橘子在生长");
}
public void eat() {
System.out.println("吃橘子");
}
}
class Banana implements Fruit {
public void grow() {
System.out.println("香蕉在生长");
}
public void eat() {
System.out.println("吃香蕉");
}
}
class Factory {
public static Fruit getFruit(String className) {
Fruit f = null;
try {
f = (Fruit)Class.forName(className).newInstance();
} catch(Exception e) {
e.printStackTrace();
}
return f;
}
}
public class TestFactory2 {
public static void main(String[] args) {
Fruit f = Factory.getFruit("Banana");
f.grow();
}
}
运行结果:
[img]http://dl.iteye.com/upload/attachment/181734/10234893-2155-338d-af33-b9e6c728e63d.jpg[/img]
上面程序中的“f = (Fruit)Class.forName(className).newInstance()”就是用的反射机制。关于反射机制,我们从Class类开始说起。
从API文档中查阅Class类定义如下:
[quote] Instances of the class Class represent classes and interfaces in a running Java application. An enum is a kind of class and an annotation is a kind of interface. Every array also belongs to a class that is reflected as a Class object that is shared by all arrays with the same element type and number of dimensions. The primitive Java types (boolean, byte, char, short, int, long, float, and double), and the keyword void are also represented as Class objects.
Class has no public constructor. Instead Class objects are constructed automatically by the Java Virtual Machine as classes are loaded and by calls to the defineClass method in the class loader.[/quote]
由以上API文档来理解,Class它和一般的classes一样都是继承自Object。它的实体(instance)用以表达Java程序运行时的classes和interface,也用来表达enum,array,primitive Java types(boolean,byte,char,short,int ,long.float,double)以及关键词void。Class 没有公共构造函数。当一个class被加载时,或当加载器(class loader)的defineClass()被JVM(Java 虚拟机)调用,JVM自动产生一个Class Object。
[quote]Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识。这项信息记录了每个对象所属的类。虚拟机通常使用运行时类型信息选准放正确方法去执行。用来保存这些类型信息的类就是Class类。Class类封装了一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建。
虚拟机为每种类型管理一个独一无二的class对象。也就是说每个类型都有一个class对象。运行程序时,Java虚拟机首先检查所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。
--选自51CTO网站网友熔岩的博客文章《深入研究Java.lang.class类》[/quote]
那么如何获得Class对象呢?有一下三种方法:
[b]A.调用Object类的getClass()方法得到Class对象。[/b]
class Person {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
public class TestReflect {
public static void main(String[] args) {
Person p = new Person();
Class c = null;
c = p.getClass();
System.out.println(c.getName());
}
}
运行结果:
[table]
| Person|
[/table]
[b]2.使用Class类的静态方法forName(String str)方法获得与字符串对应的Class对象。[/b]
import java.lang.reflect.*;
class Person {
private String name;
private int age;
/*public Person(String name) {
this.name = name;
}*/
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
public class TestReflect {
public static void main(String[] args) {
Person p = null;
Class c = null;
try {
c = Class.forName("Person");
} catch(ClassNotFoundException e) {
e.printStackTrace();
}
try{
p = (Person)c.newInstance();
} catch(Exception e) {
e.printStackTrace();
}
p.setName("Heaven");
p.setAge(24);
System.out.println(p.getName() + "---->" + p.getAge());
}
}
运行结果:
[img]http://dl.iteye.com/upload/attachment/181755/21e62477-33bf-3658-bc76-8480cafd9f3b.jpg[/img]
以上程序中的newInstance()方法,API中定义如下:
[quote]Creates a new instance of the class represented by this Class object.[/quote]
也就是创建了一个这个Class对象所代表的类的实例。注意:如果要用以上代码来实例化一个对象,则必须有一个前提条件:在对象所在的类中必须要有一个无参的构造方法,如果没有此无参构造方法,则会出现错误。就是说如果将以上程序中的Person类的构造方法去掉了注释符号,此时Person类就失去了它的默认的无参构造方法,那么即使编译通过,最终运行还是发生以下错误:
[img]http://dl.iteye.com/upload/attachment/181761/788dc988-0af9-30ee-8bc1-e33ba6e39b39.jpg[/img]
关于newInstance方法实例对象与用new关键字来实例化对象的区别,参考网上的一篇文章(我发现网上大部分对这个问题的回答都是Ctrl+C Ctrl+V了这篇文章,至于原创者是谁也无从得知了。):
[quote]在初始化一个类,生成一个实例的时候;newInstance() 和 new 有什么区别?
用newInstance与用new是区别的,区别在于创建对象的方式不一样,前者是使用类加载机制,那么为什么会有两种创建对象方式?这个就要从可伸缩、可扩展,可重用等软件思想上解释了。
Java中工厂模式经常使用newInstance来创建对象,因此从为什么要使用工厂模式上也可以找到具体答案。
例如:
Class c = Class.forName(“A”);factory = (AInterface)c.newInstance();
其中AInterface是A的接口,如果下面这样写,你可能会理解:
String className = "A";Class c = Class.forName(className);factory = (AInterface)c.newInstance();
进一步,如果下面写,你可能会理解:
String className = readfromXMlConfig;//从xml 配置文件中获得字符串Class c = Class.forName(className);factory = (AInterface)c.newInstance();
上面代码就消灭了A类名称,优点:无论A类怎么变化,上述代码不变,甚至可以更换A的兄弟类B , C , D....等,只要他们继承Ainterface就可以。
从jvm的角度看,我们使用new的时候,这个要new的类可以没有加载;
但是使用newInstance时候,就必须保证:1、这个类已经加载;2、这个类已经连接了。而完成上面两个步骤的正是class的静态方法forName()方法,这个静态方法调用了启动类加载器(就是加载java API的那个加载器)。
有了上面jvm上的理解,那么我们可以这样说,newInstance实际上是把new这个方式分解为两步,即,首先调用class的加载方法加载某个类,然后实例化。
这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了我们降耦的手段。
[补充:]
newInstance: 弱类型。低效率。只能调用无参构造。
new: 强类型。相对高效。能调用任何public构造。
newInstance()是实现IOC、反射、面对接口编程 和 依赖倒置 等技术方法的必然选择,new 只能实现具体类的实例化,不适合于接口编程。
里面就是通过这个类的默认构造函数构建了一个对象,如果没有默认构造函数就抛出InstantiationException, 如果没有访问默认构造函数的权限就抛出IllegalAccessException [/quote]
[b]3.第三种方法很简单,如果T是一个Java类型,那么T.class就代表了匹配的类对象。[/b]
如:
Class c3 = Manager.class;
Class c4 = int.class;
Class c5 = Double[].class;
上一篇: 反射实现动态工厂设计模式
下一篇: vue-cli项目设置全局变量