图源:
为了实现多态,必然需要在运行时知晓当前对象的实际类型,Java通过在对象中记录一个Class
对象引用来解决这个问题。
所谓的Class
对象,实际上就是用于表示当前对象的类型,并作为当前对象创建时的”类型模板“。可以简单地看作是”对象化的类“。
通常我们所说的类(class)都是指源码中的静态类定义,而程序真正编译和运行的时候,编译器会读取源码并加载相应的类定义,在内存中创建相应的对象作为类定义的”动态实现“,并依赖这些对象完成相应类实例的创建和类属性的访问工作,这些对象就是Class
对象。
如果熟悉Python就不难理解,这里的
Class
对象实际上就是”元类“(meta class)实例。
可以通过两种途径获取Class
对象的引用。
获取Class对象引用
package ch12.get_cls;
class MyClass {
}
public class Main {
public static void main(String[] args) {
Class clsRef1 = null;
try {
clsRef1 = Class.forName("ch12.get_cls.MyClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Class clsRef2 = MyClass.class;
System.out.println(clsRef1 == clsRef2);
}
}
// true
有两种方式可以获取Class
对象引用:
-
使用静态方法
Class.forName
,需要注意的是传入的类名必须是完整类名(包含包名)。 -
使用类名+
.class
获取。
通过示例可以发现,用这两种方式尝试获取同一个类的Class
对象引用,最后指向的是同一个Class
对象。
类加载过程
实际上JVM在执行Java程序的时候,如果需要使用某个类,会执行以下操作:
-
加载,JVM的类加载器尝试从
.class
字节码文件加载类定义,并生成Class
对象。 -
链接,如果类存在父类,尝试加载父类,并执行父类的类初始化工作。
-
初始化,如果需要,完成当前类的初始化工作。
执行这些工作后,一个类就是”真正可用“的了,可以用这个类创建实例或者访问属性。
forName与.class的差异
clsName.class
与Class.forName
虽然都可以获取Class
对象引用,但它们是有区别的:
package ch12.cls_init2;
class MyCls1 {
static public int num = 47;
static {
System.out.println("MyCls1 is inited.");
}
}
class MyCls2 {
public static int num = 47;
static {
System.out.println("MyCls2 is inited.");
}
}
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class classRef1 = Class.forName("ch12.cls_init2.MyCls1");
System.out.println("class1 ref is get.");
Class classRef2 = MyCls2.class;
System.out.println("class2 ref is get.");
System.out.println(MyCls2.num);
}
}
// MyCls1 is inited.
// class1 ref is get.
// class2 ref is get.
// MyCls2 is inited.
// 47
示例中,先尝试用Class.forName
获取MyCls1
的Class
对象引用,这立即触发了类的初始化。而之后通过MyCls2.class
获取Class
对象引用却并没有触发相应的类初始化,只有在之后访问类成员后才触发了类初始化。
由此可以看出,.class
可以在不触发类初始化的情况下获取类的Class
对象引用。这样做可以将可能的类初始化延后,对性能优化是有帮助的。
此外还应当注意到,Class.forName
会产生一个”被检查异常“ClassNotFoundException
,而.class
则不会有类似的问题,这是因为后者可以在编译期完成检查,所以相比前者,使用起来更安全。
类成员访问与初始化
事实上访问类属性并不会一定触发类的初始化:
package ch12.cls_init;
import java.util.Random;
class MyCls1 {
static final int num = 47;
static {
System.out.println("MyCls1 is inited.");
}
}
class MyCls2 {
static final int num = (new Random()).nextInt(100);
static {
System.out.println("MyCls2 is inited.");
}
}
class MyCls3 {
public final int num = 47;
static {
System.out.println("MyCls3 is inited.");
}
}
public class Main {
public static void main(String[] args) {
Class MyCls1Class = MyCls1.class;
Class MyCls2Class = MyCls2.class;
Class MyCls3Class = MyCls3.class;
System.out.println("class ref is geted.");
System.out.println("access MyCls1's member.");
System.out.println(MyCls1.num);
System.out.println("access MyCls2's member.");
System.out.println(MyCls2.num);
System.out.println("access MyCls3's member.");
MyCls3 mc = new MyCls3();
System.out.println(mc.num);
}
}
// class ref is geted.
// access MyCls1's member.
// 47
// access MyCls2's member.
// MyCls2 is inited.
// 56
// access MyCls3's member.
// MyCls3 is inited.
// 47
这个示例检验了三种情况:
-
访问类的“编译时常量”属性
-
访问类的非“编译时常量”属性
-
访问对象属性
并非所有的
static final
声明的属性都是类的“编译时常量”属性,就像示例中MyCls2Class.num
一样,该变量只有在运行时才会被初始化。
因为类的”编译时常量“属性在编译时就是一个确定值,并不依赖于类的初始化,所以在访问时不会触发类的初始化动作,而其余的成员访问都会先执行类的初始化动作(如果还没有初始化的话),然后再执行相关的属性访问。至于对象属性,显然必须先初始化类才能创建对象。
使用Class对象
使用Class
对象可以打获取对应类的相关信息,比如类名:
package ch12.use_cls;
class MyCls {
}
public class Main {
private static void printCls(Class cls) {
System.out.println(cls.getName());
System.out.println(cls.getSimpleName());
System.out.println(cls.getCanonicalName());
}
public static void main(String[] args) {
printCls(MyCls.class);
}
}
// ch12.use_cls.MyCls
// MyCls
// ch12.use_cls.MyCls
更有用的是,可以通过Class
对象获取到类的构造器,并用类构造器创建实例:
...
class MyCls {
public MyCls() {
}
}
public class Main {
...
private static Object createInstance(Class cls) throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
Constructor c;
c = cls.getConstructor();
return c.newInstance();
}
public static void main(String[] args)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
NoSuchMethodException, SecurityException {
printCls(MyCls.class);
System.out.println(createInstance(MyCls.class));
}
}
// ch12.use_cls2.MyCls
// MyCls
// ch12.use_cls2.MyCls
// ch12.use_cls2.MyCls@24d46ca6
需要注意的是,使用Class.getConstructor
获取到的类构造器必须是类中确实定义了的构造器。如果开发者没有定义任何构造器,编译器会创建一个”默认构造器“,但这样的构造器无法通过Class.getConstructor
获取,会产生一个NoSuchMethodException
异常。
此外,还可以通过给getConstructor
指定构造器的参数类型来获取到带特定参数的构造器:
...
class MyCls {
public MyCls() {
}
public MyCls(int num) {
Fmt.printf("MyCls(%d) is called.\n", num);
}
}
public class Main {
...
private static MyCls createMyCls(int num) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Constructor<MyCls> c = MyCls.class.getConstructor(int.class);
return c.newInstance(num);
}
public static void main(String[] args)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
NoSuchMethodException, SecurityException {
printCls(MyCls.class);
System.out.println(createInstance(MyCls.class));
System.out.println(createMyCls(123));
}
}
// ch12.use_cls2.MyCls
// MyCls
// ch12.use_cls2.MyCls
// ch12.use_cls2.MyCls@24d46ca6
// MyCls(123) is called.
// ch12.use_cls3.MyCls@6d311334
Class
还有一个getConstructors
方法,可以获取所有的构造器组成的数组,并且按定义的顺序排列,同样可以利用该方法调用需要的构造器:
...
public class Main {
...
private static MyCls createMyCls2(int num) throws NoSuchMethodException, SecurityException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Constructor[] constructors = MyCls.class.getConstructors();
return (MyCls) constructors[1].newInstance(num);
}
public static void main(String[] args)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
NoSuchMethodException, SecurityException {
printCls(MyCls.class);
System.out.println(createInstance(MyCls.class));
System.out.println(createMyCls(123));
System.out.println(createMyCls2(333));
}
}
// ch12.use_cls2.MyCls
// MyCls
// ch12.use_cls2.MyCls
// ch12.use_cls2.MyCls@24d46ca6
// MyCls(123) is called.
// ch12.use_cls3.MyCls@6d311334
// MyCls(333) is called.
// ch12.use_cls4.MyCls@682a0b20
事实上还有一个
Class.newInstance
方法,可以简单地使用类的默认构造器创建实例并返回,但该方法已经被弃用。如果要使用默认构造器,应当使用getDeclaredConstructors
,该方法返回的构造器中包含默认构造器。
还可以使用Class
对象进行转型操作:
package ch12.use_cls5;
class MyCls{}
public class Main {
public static void main(String[] args) {
Object o = new MyCls();
MyCls myCls = MyCls.class.cast(o);
MyCls myCls2 = (MyCls)o;
}
}
其效果与强制类型转换是一致的。
泛型
Class
对象同样支持泛型,不使用泛型时,Class
对象可以代表任意类型的Class
对象,比如:
package ch12.generic;
class MyCls{}
public class Main {
public static void main(String[] args) {
Class cls = MyCls.class;
cls = Object.class;
cls = int.class;
}
}
普通的Class
对象实际上相当于Class<?>
,两者效果相同:
package ch12.generic2;
class MyCls{}
public class Main {
public static void main(String[] args) {
Class<?> cls = MyCls.class;
cls = Object.class;
cls = int.class;
}
}
但后者可以更明确地表示”你需要一个可以表示任意类型的Class对象“。
如果使用一个明确的类型作为泛型,可以让Class
对象具备相应的静态类型检查功能,比如:
package ch12.generic3;
class MyCls{}
public class Main {
public static void main(String[] args) {
Class<MyCls> cls = MyCls.class;
// cls = Object.class;
// cls = int.class;
}
}
// Type mismatch: cannot convert from Class<Object> to Class<MyCls>
// Type mismatch: cannot convert from Class<Integer> to Class<MyCls>
注释部分无法通过编译,使用泛型可以避免某些错误。
如果类涉及继承关系,同样可以用泛型进行表达:
package ch12.generic4;
class Parent {
}
class Child extends Parent {
}
public class Main {
public static void main(String[] args) {
Class<? extends Parent> cls;
cls = Parent.class;
cls = Child.class;
Class<? super Child> cls2;
cls2 = Parent.class;
cls2 = Child.class.getSuperclass();
cls2 = Child.class;
}
}
Class<? extends Parent>
表示这是一个Parent
或其子类的Class
对象。Class<? super Child>
表示这是一个Child
或其父类的Class
对象。
使用过容器类泛型的开发者,可能会尝试用
Class<Parent>
来承接Child.class
,但实际上这是无法通过编译的,原因是虽然Parent
是Child
的基类,但Class<Parent>
并非Class<Child>
的基类。因此必须使用Class<? extends Parent>
。
instanceof
很多编程语言都支持instanceof
或者类似的语法,以提供运行时类型检查。
下面通过一个例子来说明instanceof
在Java中的用法:
假设有一个基于Animal
的类继承层级:
package ch12.cls_counter;
public class Animal {
@Override
public String toString() {
return this.getClass().getCanonicalName();
}
}
class Cat extends Animal {
}
class Dog extends Animal {
}
class SportingDog extends Dog {
}
class WorkingDog extends Dog {
}
class HerdingDog extends Dog {
}
class PersianCat extends Cat {
}
class BirmanCat extends Cat {
}
设计一个类AnimalCreator
用于批量随机生成以上类的实例:
package ch12.cls_counter;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
abstract public class AnimalCreator {
private static Random rand = new Random();
abstract protected List<Class<? extends Animal>> getAnimalTypes();
private Animal randomAnimal() {
int total = getAnimalTypes().size();
if (total == 0) {
return null;
}
try {
return (Animal) getAnimalTypes().get(rand.nextInt(total)).getDeclaredConstructors()[0].newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (SecurityException e) {
throw new RuntimeException(e);
}
}
public List<Animal> randomAnimals(int num) {
List<Animal> animals = new ArrayList<>();
for (int i = 0; i < num; i++) {
animals.add(randomAnimal());
}
return animals;
}
}
这个类是一个抽象类,有一个抽象方法getAnimalTypes
提供用于生成Animal
实例的类型。这是继上是模板方法模式的应用,不同的子类可以通过实现该方法以实现不同的随机创建实现。
这里提供一个RealCreator
类实现:
package ch12.cls_counter;
import java.util.ArrayList;
import java.util.List;
public class RealCreator extends AnimalCreator {
private static List<Class<? extends Animal>> animalTypes;
private static String[] animalClsNames = new String[] {
"ch12.cls_counter.Cat",
"ch12.cls_counter.Dog",
"ch12.cls_counter.SportingDog",
"ch12.cls_counter.WorkingDog",
"ch12.cls_counter.HerdingDog",
"ch12.cls_counter.PersianCat",
"ch12.cls_counter.BirmanCat"
};
static {
loadAnimalTypes();
}
private static void loadAnimalTypes() {
animalTypes = new ArrayList<>();
for (String string : animalClsNames) {
try {
animalTypes.add((Class<? extends Animal>) Class.forName(string));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
@Override
protected List<Class<? extends Animal>> getAnimalTypes() {
return animalTypes;
}
}
该类使用一个类名字符串组成的类型列表,然后在初始化类时执行loadAnimalTypes
静态方法以创建相应的Class对象列表,并以直接返回该列表的形式实现父类的抽象方法getAnimalTypes
。
最后创建一个用于统计随机创建的Animal
实例的类:
package ch12.cls_counter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AnimalCounter {
private Map<String, Integer> counter = new HashMap<>();
private List<Animal> animals;
public AnimalCounter(List<Animal> animals) {
this.animals = animals;
}
private void countCls(String clsName) {
Object value = counter.get(clsName);
if (value == null) {
counter.put(clsName, 1);
} else {
counter.put(clsName, (int) value + 1);
}
}
public void count() {
for (Animal animal : animals) {
if (animal instanceof Animal) {
countCls("Animal");
}
if (animal instanceof Cat) {
countCls("Cat");
}
if (animal instanceof Dog) {
countCls("Dog");
}
if (animal instanceof SportingDog) {
countCls("SportingDog");
}
if (animal instanceof WorkingDog) {
countCls("WorkingDog");
}
if (animal instanceof HerdingDog) {
countCls("HerdingDog");
}
if (animal instanceof PersianCat) {
countCls("PersianCat");
}
if (animal instanceof BirmanCat) {
countCls("BirmanCat");
}
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (Map.Entry<String, Integer> entry : counter.entrySet()) {
sb.append(entry.getKey() + "=" + entry.getValue());
sb.append(",");
}
sb.deleteCharAt(sb.length() - 1);
sb.append("]");
return sb.toString();
}
}
因为Animal
及其子类有3个继承层次,所以子类实例可能同时满足多个instanceof
,这里用多个if
语句进行判断。当然这样的设计是不好的,不仅显得笨拙,而且扩展性很差,每添加一个类型这里就可能需要添加一个if
语句,稍后会改进这里的代码。
最后的测试语句很简单:
package ch12.cls_counter;
import java.util.List;
public class Main {
public static void main(String[] args) {
RealCreator rc = new RealCreator();
List<Animal> animals = rc.randomAnimals(10);
System.out.println(animals);
AnimalCounter ac = new AnimalCounter(animals);
ac.count();
System.out.println(ac);
}
}
// [ch12.cls_counter.WorkingDog, ch12.cls_counter.Dog, ch12.cls_counter.Dog,
// ch12.cls_counter.HerdingDog, ch12.cls_counter.BirmanCat,
// ch12.cls_counter.BirmanCat, ch12.cls_counter.BirmanCat,
// ch12.cls_counter.SportingDog, ch12.cls_counter.Dog, ch12.cls_counter.Cat]
// [Animal=10,WorkingDog=1,Cat=4,BirmanCat=3,SportingDog=1,Dog=6,HerdingDog=1]
改进后的代码
我们可以从两方面对代码进行改进:
-
直接使用
.class
,而不是Class.forName
,前者可以提供编译时检查,这样可以避免额外的异常处理语句。 -
直接使用
Class
对象检查Animal
实例的类型,以避免大量的if
语句。
对于第一点,这里使用一个新的ClassCreator
作为AnimalCreator
的新实现:
package ch12.cls_counter2;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ClassCreator extends AnimalCreator {
public static List<Class<? extends Animal>> animalTypes;
private static List<Class<? extends Animal>> subTypes;
static {
loadAnimalTypes();
}
private static void loadAnimalTypes() {
animalTypes = new ArrayList<>();
Collections.addAll(animalTypes, Animal.class, Dog.class, Cat.class, SportingDog.class, WorkingDog.class,
HerdingDog.class, PersianCat.class, BirmanCat.class);
subTypes = animalTypes.subList(3, animalTypes.size());
}
@Override
protected List<Class<? extends Animal>> getAnimalTypes() {
return subTypes;
}
}
这里animalTypes
是完整的Animal
及其子类Class
对象集合,用于进行最终的类型统计。subTypes
是前者的一个子集,用于随机生成Animal
实例。
关于第二点,这里用一个新的AnimalCounter2
来实现类型统计:
package ch12.cls_counter2;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AnimalCounter2 {
private Map<String, Integer> counter = new HashMap<>();
private List<Animal> animals;
private List<Class<? extends Animal>> animalTypes;
public AnimalCounter2(List<Animal> animals, List<Class<? extends Animal>> animalTypes) {
this.animals = animals;
this.animalTypes = animalTypes;
}
private void countCls(String clsName) {
Object value = counter.get(clsName);
if (value == null) {
counter.put(clsName, 1);
} else {
counter.put(clsName, (int) value + 1);
}
}
public void count() {
for (Animal animal : animals) {
for (Class<? extends Animal> animalType : animalTypes) {
if (animalType.isInstance(animal)){
countCls(animalType.getSimpleName());
}
}
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (Map.Entry<String, Integer> entry : counter.entrySet()) {
sb.append(entry.getKey() + "=" + entry.getValue());
sb.append(",");
}
sb.deleteCharAt(sb.length() - 1);
sb.append("]");
return sb.toString();
}
}
AnimalCounter2
直接接收一个Class对象集合作为统计依据,并在统计时依次调用Class
对象的isInstance
方法检查类型是否匹配,其作用与instanceof
相同,但是避免了大量if
语句的使用。
测试代码是相似的:
package ch12.cls_counter2;
import java.util.List;
class Animal{
@Override
public String toString() {
return this.getClass().getCanonicalName();
}
}
class Cat extends Animal{}
class Dog extends Animal{}
class SportingDog extends Dog{}
class WorkingDog extends Dog{}
class HerdingDog extends Dog{}
class PersianCat extends Cat{}
class BirmanCat extends Cat{}
public class Main {
public static void main(String[] args) {
AnimalCreator rc = new ClassCreator();
List<Animal> animals = rc.randomAnimals(10);
System.out.println(animals);
AnimalCounter2 ac = new AnimalCounter2(animals, ClassCreator.animalTypes);
ac.count();
System.out.println(ac);
}
}
使用递归进行类型统计
虽然上边的代码进行了一定程度的改进,但是依然需要维护统计所需的类型列表,并将其传递给统计代码。如果需要统计的类型列表发生变化,就要对列表进行相应的维护。
事实上可以给统计代码指定一个“基础的根类型”,然后在统计类型时依次检查当前类型和其父类型是否为“根类型”的子类型,如果是,就进行统计。这样做的好处是只要统计的类型都是根类型的子类型,即使我们添加了一些新类型,程序也无需做出任何改变。这无疑是在“可扩展性”上的一次进步。
package ch12.cls_counter3;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TypeCounter {
private Map<Class<?>, Integer> counter = new HashMap<>();
private Class<?> rootType;
public TypeCounter(Class<?> rootType) {
if (null == rootType) {
throw new RuntimeException("param must not null.");
}
this.rootType = rootType;
}
public void count(List<?> objs) {
for (Object object : objs) {
Class<?> type = object.getClass();
if (!this.rootType.isAssignableFrom(type)) {
throw new RuntimeException(type.toString() + " is not a count target type.");
}
countType(type);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (Map.Entry<Class<?>, Integer> entry : counter.entrySet()) {
sb.append(entry.getKey().getSimpleName());
sb.append("=");
sb.append(entry.getValue());
sb.append(", ");
}
sb.delete(sb.length() - 2, sb.length());
sb.append("]");
return sb.toString();
}
private void countType(Class<?> type) {
doCount(type);
Class<?> superCls = type.getSuperclass();
if (superCls != null && rootType.isAssignableFrom(superCls)) {
countType(superCls);
}
}
private void doCount(Class<?> type) {
int value = counter.getOrDefault(type, 0);
counter.put(type, value + 1);
}
}
测试:
...
public class Main {
public static void main(String[] args) {
AnimalCreator rc = new ClassCreator();
List<Animal> animals = rc.randomAnimals(10);
System.out.println(animals);
TypeCounter tc = new TypeCounter(Animal.class);
tc.count(animals);
System.out.println(tc);
}
}
// [ch12.cls_counter3.SportingDog, ch12.cls_counter3.SportingDog,
// ch12.cls_counter3.HerdingDog, ch12.cls_counter3.HerdingDog,
// ch12.cls_counter3.BirmanCat, ch12.cls_counter3.HerdingDog,
// ch12.cls_counter3.PersianCat, ch12.cls_counter3.SportingDog,
// ch12.cls_counter3.WorkingDog, ch12.cls_counter3.WorkingDog]
// [PersianCat=1, WorkingDog=2, Cat=2, SportingDog=3, Animal=10, HerdingDog=3,
// BirmanCat=1, Dog=8]
这样做还有一个好处:TypeCOunter
具备相当的通用性,完全可以用这个类来统计任意的“类簇”。
注册工厂
用Class
对象批量创建对象存在一些限制,比如只能通过相应的构造器创建,以及需要处理获取Class
对象或构造器时可能产生的异常等。
事实上,一般情况下创建对象的工作应当用“工厂模式”来实现,这里用“工厂模式”改写上边的例子:
package ch12.cls_counter4;
public interface AnimalFactory {
Animal create();
}
为了避免添加太多的类,以内部类的形式添加相应的工厂类:
...
class SportingDog extends Dog {
public static AnimalFactory factory = new AnimalFactory() {
public Animal create() {
return new SportingDog();
};
};
}
class WorkingDog extends Dog {
public static AnimalFactory factory = new AnimalFactory() {
public Animal create() {
return new WorkingDog();
};
};
}
class HerdingDog extends Dog {
public static AnimalFactory factory = new AnimalFactory() {
public Animal create() {
return new HerdingDog();
};
};
}
class PersianCat extends Cat {
public static AnimalFactory factory = new AnimalFactory() {
public Animal create(){
return new PersianCat();
}
};
}
class BirmanCat extends Cat {
public static AnimalFactory factory = new AnimalFactory() {
public Animal create() {
return new BirmanCat();
};
};
}
使用“注册工厂”实现Animal
对象批量生产的FactoryCreator
类:
package ch12.cls_counter4;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class FactoryCreator {
private static Random rand = new Random();
private List<AnimalFactory> factorys = new ArrayList<>();
public List<Animal> randomAnimals(int num) {
List<Animal> animals = new ArrayList<>();
for (int i = 0; i < num; i++) {
animals.add(randomAnimal());
}
return animals;
}
public void registeFactory(AnimalFactory factory) {
factorys.add(factory);
}
public void registeAllFactory() {
registeFactory(SportingDog.factory);
registeFactory(WorkingDog.factory);
registeFactory(HerdingDog.factory);
registeFactory(PersianCat.factory);
registeFactory(BirmanCat.factory);
}
private Animal randomAnimal() {
if (factorys.size() <= 0) {
return null;
}
int index = rand.nextInt(factorys.size());
return factorys.get(index).create();
}
}
测试:
package ch12.cls_counter4;
import java.util.List;
public class Main {
public static void main(String[] args) {
FactoryCreator fc = new FactoryCreator();
fc.registeAllFactory();
List<Animal> animals = fc.randomAnimals(10);
System.out.println(animals);
TypeCounter tc = new TypeCounter(Animal.class);
tc.count(animals);
System.out.println(tc);
}
}
// [ch12.cls_counter4.SportingDog, ch12.cls_counter4.WorkingDog,
// ch12.cls_counter4.WorkingDog, ch12.cls_counter4.HerdingDog,
// ch12.cls_counter4.PersianCat, ch12.cls_counter4.SportingDog,
// ch12.cls_counter4.HerdingDog, ch12.cls_counter4.SportingDog,
// ch12.cls_counter4.SportingDog, ch12.cls_counter4.HerdingDog]
// [Dog=9, Cat=1, PersianCat=1, Animal=10, HerdingDog=3, SportingDog=4,
// WorkingDog=2]
反射
反射(reflect)简单地讲,就是在程序运行时获取“组件”的“细节“,很多编程语言都支持反射。
一般情况下我们是用不到反射的,因为所有的代码都是在”本地“编译和加载,所有的类型结构都是已知的,但某些时候我们需要在运行时获取某个对象的类型以及内部结构,比如通过RMI(Remote Method Invocation,远程方法调用)运行的程序,其一部分代码是在另外的机器上编译和运行的,如果要获得通过这种方式获取的对象的类型和结构,就需要使用反射。
Java的反射主要使用java.lang.reflect
这个包,包括Constructor
、Method
等代表代码结构的类。
下面用一个示例说明反射的作用,在这个示例中,将通过命令行参数指定一个类名,将获取该类的构造器和方法,并打印:
package ch12.reflect1;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
if (args.length <= 0) {
System.out.println("Please enter params:[class_name <key_word>]");
}
String ClsName = args[0];
String KeyWord = "";
if (args.length >= 2) {
KeyWord = args[1];
}
Class<?> cls = Class.forName(ClsName);
Constructor[] constructors = cls.getConstructors();
Pattern prefixPattern = Pattern.compile("\\w+\\.");
for (Constructor constructor : constructors) {
String funcSig = constructor.toString();
funcSig = prefixPattern.matcher(funcSig).replaceAll("");
if (KeyWord.length() == 0) {
System.out.println(funcSig);
} else if (funcSig.indexOf(KeyWord) != -1) {
System.out.println(funcSig);
} else {
;
}
}
Method[] methods = cls.getMethods();
for (Method method : methods) {
String funcSig = method.toString();
funcSig = prefixPattern.matcher(funcSig).replaceAll("");
if (KeyWord.length() == 0) {
System.out.println(funcSig);
} else if (funcSig.indexOf(KeyWord) != -1) {
System.out.println(funcSig);
} else {
;
}
}
}
}
class Test {
public Test() {
}
public Test(int num) {
}
public void publicMethod() {
};
public static void publicStaticMethod() {
}
private void privateMethod() {
};
private static void privateStaticMethod() {
}
}
测试结果:
❯ java Main.java ch12.reflect1.Test
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
public Test()
public Test(int)
public void publicMethod()
public static void publicStaticMethod()
public final void wait(long,int) throws InterruptedException
public final void wait() throws InterruptedException
public final native void wait(long) throws InterruptedException
public boolean equals(Object)
public String toString()
public native int hashCode()
public final native Class getClass()
public final native void notify()
public final native void notifyAll()
可以看到反射依然遵循访问控制,我们通过反射获取到的方法只包含公共的构造器和方法。
此外,通过Consturcotr.toString
和Method.toString
获取到的是包含返回值类型的方法签名,其中方法名包含完整包名和类名,代码中通过正则来去除前缀。
通过反射获取到的方法包含了从Object
继承的方法。
除了通过反射打印代码结构,也可以编写程序读取源码,进行字符解析,但那样更为麻烦。反射相当于利用了语言本身的解析器。
动态代理
通过代理模式,可以以“不修改原有类型”为前提,给原类型添加一些额外特性。
关于代理模式的更多内容,可以阅读。
下面看一个简单示例:
package ch12.proxy;
import java.util.Random;
import util.Fmt;
class OriginalClass {
public void func1(int num) {
Fmt.printf("OriginalClass.func1(%d) is called.\n", num);
}
}
class OriginalClassProxy extends OriginalClass {
private OriginalClass oc;
public OriginalClassProxy(OriginalClass oc) {
this.oc = oc;
}
@Override
public void func1(int num) {
Fmt.printf("OriginalClassProxy.func1(%d) is called.\n", num);
oc.func1(num);
}
}
public class Main {
private static void testOriginalClass(OriginalClass oc, int num) {
oc.func1(num);
}
public static void main(String[] args) {
OriginalClass oc = new OriginalClass();
Random rand = new Random();
testOriginalClass(oc, rand.nextInt(100));
testOriginalClass(new OriginalClassProxy(oc), rand.nextInt(100));
}
}
// OriginalClass.func1(90) is called.
// OriginalClassProxy.func1(5) is called.
// OriginalClass.func1(5) is called.
利用反射机制和实现动态代理,这里用动态代理改写这个示例:
package ch12.proxy2;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Random;
import util.Fmt;
class OriginalClass {
public void func1(int num) {
Fmt.printf("OriginalClass.func1(%d) is called.\n", num);
}
public InvocationHandler getInvocationHandler() {
return new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
StringBuilder sb = new StringBuilder();
for (Object arg : args) {
sb.append(arg.toString());
sb.append(", ");
}
if (sb.length() >= 2) {
sb.delete(sb.length() - 2, sb.length());
}
Fmt.printf("DynamicProxy.%s(%s) is called.", method.getName(), sb.toString());
return method.invoke(OriginalClass.this, args);
}
};
}
}
public class Main {
private static void testOriginalClass(OriginalClass oc, int num) {
oc.func1(num);
}
public static void main(String[] args) {
OriginalClass oc = new OriginalClass();
Random rand = new Random();
testOriginalClass(oc, rand.nextInt(100));
OriginalClass dynamicProxy = (OriginalClass) Proxy.newProxyInstance(OriginalClass.class.getClassLoader(),
new Class<?>[] {},
oc.getInvocationHandler());
testOriginalClass(dynamicProxy, rand.nextInt(100));
}
}
最关键的是使用Proxy.newProxyInstance
在运行时创建动态代理,该方法接受三个参数:
-
类加载器(Class Loader),代表动态代理的基类,可以通过
Class.getClassLoader
获取。 -
动态代理需要实现的接口,用一个
Class
对象数组表示。 -
调用器(Invocation Handler),实际负责处理动态代理转发的方法调用。
调用器需要实现InvocationHandler
接口,这里通过一个匿名内部类来实现,这样做的好处是动态代理往往需要关联一个被代理的对象,而匿名内部类“天然”就关联其附属的类实例。
一切都看上去很棒,但实际上这段代码是无法正常运行的,如果尝试运行就会出现Exception in thread "main" java.lang.ClassCastException: class jdk.proxy1.$Proxy0 cannot be cast to class ch12.proxy2.OriginalClass
这样的错误提示,这是因为虽然我们使用了OriginalClass.class.getClassLoader()
作为类加载器,但可以看到,生成的动态代理实例的实际类型依然是jdk.proxy1.$Proxy0
,而非我们期望的OriginalClass
,所以是无法转换为我们期望的类型的。
要修复这个问题,就需要使用接口:
...
interface OriginalInterface {
void func1(int num);
}
class OriginalClass implements OriginalInterface {
...
}
public class Main {
...
public static void main(String[] args) {
...
OriginalInterface dynamicProxy = (OriginalInterface) Proxy.newProxyInstance(OriginalClass.class.getClassLoader(),
new Class<?>[] { OriginalInterface.class },
oc.getInvocationHandler());
testOriginalClass(dynamicProxy, rand.nextInt(100));
}
}
// OriginalClass.func1(64) is called.
// DynamicProxy.func1(15) is called.
// OriginalClass.func1(15) is called.
这样就可以正常运行了。
空对象
虽然可以在编程的时候用null
来表示一个“空对象”,但这意味着程序中需要频繁地通过==null
来判断对象是否为空,并作出相应处理。有时候我们可以用一个空对象(Null Object)来取代null
,这样做的好处是可以避免使用大量的==null
判断语句。
下面看一个简单示例:
package ch12.null_object;
class Room {
private String number = "";
private Student[] beds = new Student[4];
public Room(String number) {
this.number = number;
}
public boolean addStudent(Student student) {
for (int i = 0; i < beds.length; i++) {
if (beds[i] == null) {
beds[i] = student;
return true;
}
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(number);
sb.append("[");
String splite = ", ";
for (Student student : beds) {
if (student == null) {
sb.append("empty");
} else {
sb.append(student.getName());
}
sb.append(splite);
}
sb.delete(sb.length() - splite.length(), sb.length());
sb.append("]");
return sb.toString();
}
}
class Student {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class Main {
public static void main(String[] args) {
Room room = new Room("404");
room.addStudent(new Student("Li Lei"));
System.out.println(room);
}
}
// 404[Li Lei, empty, empty, empty]
这个示例中,Room
表示一个宿舍,Student
表示学生,Room
中的Student
数组beds
表示为学生分配的床位。因为一个宿舍中是固定的4个床位,所以初始状态用null
来占位。相应的,在toString
和addStudent
方法中,用检测是否床位为null
来处理床位为空的情况。
下面用“空对象”来改写这个示例:
package ch12.null_object2;
class Room {
private String number = "";
private Student[] beds = new Student[4];
{
for (int i = 0; i < beds.length; i++) {
beds[i] = Student.NULL;
}
}
public Room(String number) {
this.number = number;
}
public boolean addStudent(Student student) {
for (int i = 0; i < beds.length; i++) {
if (beds[i] == Student.NULL) {
beds[i] = student;
return true;
}
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(number);
sb.append("[");
String splite = ", ";
for (Student student : beds) {
sb.append(student.getName());
sb.append(splite);
}
sb.delete(sb.length() - splite.length(), sb.length());
sb.append("]");
return sb.toString();
}
}
class Student {
private String name;
public static final Student NULL = new Student("empty");
...
}
...
这里用一个Student
的类属性NULL
来表示Student
的空对象,因为空对象一般都是单例,所以用final
进行修饰。在Room
中,我们在beds
数组创建后在初始化块中用空对象Student.NULL
进行了初始化。因此初始数组中并不包含null
,所以在toString
方法中删除了==null
的相关语句。对于空对象,同样可以用.getName
获取相应的名称(empty)。
在addStudent
中,检查床位为空,只要使用==Student.NULL
进行判断就可以了,因为空对象是单例,这么检测是没有问题的。
实际上这里是有漏洞的,客户端程序是可以通过
.addStudent(null)
的方式添加null
到Room
对象中的,这样可能造成程序运行出错,可以通过检查参数和抛出异常的方式来修复这个问题。
因为示例代码规模很小,所以这里并不能显示出使用空对象的优势,但如果涉及复杂的持有对象的代码,空对象可能对降低代码复杂度有相当程度的好处。
此外,因为这里的空对象实现相当容易,所以用Student NULL = new Student("empty")
的方式实现,如果空对象实现比较复杂,可以单独用一个Student
的子类NullStudent
来实现。
使用动态代理实现空对象
如果有大量的类需要实现空对象,我们可以利用动态代理来简化代码:
package ch12.null_object3;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
class Room {
private String number = "";
private NameAble[] beds = new NameAble[4];
{
for (int i = 0; i < beds.length; i++) {
beds[i] = Student.NULL;
}
}
public Room(String number) {
this.number = number;
}
public boolean addStudent(Student student) {
for (int i = 0; i < beds.length; i++) {
if (beds[i] instanceof NULL) {
beds[i] = student;
return true;
}
}
return false;
}
public boolean setStudent(int index, NameAble student) {
NameAble bed = beds[index];
if (bed instanceof NULL) {
beds[index] = student;
return true;
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(number);
sb.append("[");
String splite = ", ";
for (NameAble bed : beds) {
sb.append(bed.getName());
sb.append(splite);
}
sb.delete(sb.length() - splite.length(), sb.length());
sb.append("]");
return sb.toString();
}
}
interface NameAble {
public String getName();
}
interface NULL {
}
class Student implements NameAble {
private String name;
public static final NameAble NULL = getNullStudent(Student.class);
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public static NameAble getNullStudent(Class<?> type) {
return (NameAble) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] { NameAble.class, NULL.class },
new StudentInvocationHandler(type));
}
}
class GoodStudent extends Student {
public static final NameAble NULL = getNullStudent(GoodStudent.class);
public GoodStudent(String name) {
super(name);
}
}
class BadStudent extends Student {
public static final NameAble NULL = getNullStudent(BadStudent.class);
public BadStudent(String name) {
super(name);
}
}
class NormalStudetn extends Student {
public static final NameAble NULL = getNullStudent(NormalStudetn.class);
public NormalStudetn(String name) {
super(name);
}
}
class StudentInvocationHandler implements InvocationHandler {
private Class<?> type;
public StudentInvocationHandler(Class<?> type) {
this.type = type;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName() == "getName") {
if (!Student.class.isAssignableFrom(type)) {
return "Unknow class";
} else if (BadStudent.class.isAssignableFrom(type)) {
return "BadStudetn emtpy";
} else if (GoodStudent.class.isAssignableFrom(type)) {
return "GoodStudent empty";
} else if (NormalStudetn.class.isAssignableFrom(type)) {
return "NormalStudent empty";
} else {
return "Studetn empty";
}
}
return null;
}
}
public class Main {
public static void main(String[] args) {
Room room = new Room("404");
room.addStudent(new Student("Li Lei"));
System.out.println(room);
room.setStudent(1, GoodStudent.NULL);
room.setStudent(2, BadStudent.NULL);
room.setStudent(3, NormalStudetn.NULL);
System.out.println(room);
room.addStudent(new Student("Han Meimei"));
System.out.println(room);
}
}
// 404[Li Lei, Studetn empty, Studetn empty, Studetn empty]
// 404[Li Lei, GoodStudent empty, BadStudetn emtpy, NormalStudent empty]
// 404[Li Lei, Han Meimei, BadStudetn emtpy, NormalStudent empty]
这里用动态代理,同时为Student
及其三个子类分别实现了空对象,这些空对象的行为差异由调用处理器StudentInvocationHandler
类来处理。
因为Room
需要同时处理多个类型的空对象,所以这里让所有空对象都实现NULL
接口,用这个接口表示这是一个空对象,需要检查对象是否为空对象的时候只要使用instanceof NULL
即可,这种方式比让空对象实现isNULL
方法更简单。
反射的隐患
反射无疑是一种强大和有用的特性,但同时也会带来一些隐患,下面用一些示例进行说明。
在Java中,接口(interface)用于代码解耦,我们通常期待客户端程序仅使用接口暴露的部分功能,但事实上客户端程序可以通过向下转型来获取额外功能:
package ch12.bad;
import util.Fmt;
interface NameAble {
String getName();
}
class Person implements NameAble {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
public void walk() {
Fmt.printf("%s is walking.", name);
}
}
class School {
public static NameAble getNameAble() {
return new Person("New Employee");
}
}
public class Main {
public static void main(String[] args) {
NameAble n = School.getNameAble();
Person p = (Person) n;
p.walk();
}
}
// New Employee is walking.
这个问题可以通过访问权限来解决,比如将Student
设置为包访问权限,这样包外就无法访问Student
,类似的,在同一个包内还可以将Student
设置为私有的内部类来限制访问:
package ch12.bad2;
import util.Fmt;
interface NameAble {
String getName();
}
class School {
public static NameAble getNameAble() {
return new Person("New Employee");
}
private static class Person implements NameAble {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
public void walk() {
Fmt.printf("%s is walking.", name);
}
}
}
public class Main {
public static void main(String[] args) {
NameAble n = School.getNameAble();
System.out.println(n.getName());
// Person p = (Person) n;
// p.walk();
// Person cannot be resolved to a type
}
}
// New Employee
注释部分的代码无法运行,因为Person
是School
的私有内嵌类,除了School
,其它作用域都无法访问。
这样看起来不错,很安全,但其实依然是一种假象,使用反射机制可以轻松突破这种限制:
...
public class Main {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
NameAble n = School.getNameAble();
System.out.println(n.getName());
Method method = n.getClass().getMethod("walk");
method.invoke(n);
Field name = n.getClass().getDeclaredField("name");
name.setAccessible(true);
name.set(n, "Li Lei");
method.invoke(n);
}
}
// New Employee
// New Employee is walking.
// Li Lei is walking.
使用反射,不仅可以调用School
的私有类型Person
的walk
方法,甚至可以通过getDeclaredField
获取到其私有属性name
,并在使用setAccessible
方法修改访问权限后使用set
方法修改其值。
类似的,可以使用
getDeclaredMethod
获取到私有方法并进行调用。
因此,在Java中,访问控制并不是绝对限制,事实上可以通过反射来突破这种限制。这和Python是类似的,在Python中,以__
或_
开头的属性和方法是私有或被保护的,但是实际上依然是可以通过某种方式来进行访问。
访问控制的意义在于,这是一种包开发者和客户端开发者的约定,包开发者会认为客户端开发者会遵守这种约定而不是通过反射来破坏,同样的,通过反射这种非正常方式使用包代码,不会享受到包开发者“安全升级”包代码的保护,和可能在包代码升级后而影响到现有代码的正常运行。
谢谢阅读。
文章评论