1.RTTI作用
有这样的一个问题,如果想知道某个泛化引用的确切类型,怎么做?比如假设允许用户将某一具体类型的几个形状都变成某种特殊的颜色,通过这种方式,用户就能找出屏幕上突出显示的三角形。使用RTTI就可以找到确切的类型,选择或者剔除他们。
2.Class对象
类型的信息在运行时的表示是有Class对象完成的,他包含了与类有关的信息,他包含了与类有关的信息,他被保存在一个同名的.class文件中,运行这个程序的JVM将使用被称为类加载器的子系统。所有的类都是在第一次使用时动态加载到JVM,当程序创建第一个对类的静态成员的引用时,就会加载这个类,这证明了构造器是静态方法,java程序在开始运行之前并非完全加载,其各个部分是在必须时才加载的。
public class Test1 {
public static void main(String[] args) {
System.out.println("inside main");
new Candy();
try {
Class.forName("com.wx.test6.Gum");
Class.forName("com.wx.test6.Gum");
Class.forName("com.wx.test6.Pencil");
} catch (Exception e) {
e.printStackTrace();
System.out.println("Cound not load Gum");
}
new Cookie();
Class<?> forName = null;
try {
forName = Class.forName("com.wx.test6.Pencil");
Pencil pencil = (Pencil)forName.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Candy{
static {
System.out.println("Loading Candy");
}
}
class Gum{
static {
System.out.println("Loading Gum");
}
}
class Cookie{
static{
System.out.println("Loading Cookie");
}
}
class Pencil{
public Pencil(){
System.out.println("Loading Pencil");
}
}
static字句会在类第一次加载的时候执行,而创建对象和类加载是不同的两个步骤,构造方法是要在创建对象的时候才会执行。类加载的时候是不会执行的。
Class.forName("com.wx.test6.Gum"); forName是取得Class对象的引用的一种方法。返回的是一个Class对象的引用。无论何时只要你想要在运行时使用类型的信息,就必须首先获得对恰当的Class对象的引用。Class.forName()就是实现此功能的便捷途径。
public class Test2 {
public static void main(String[] args) {
Class<?> name=null;
try {
name = Class.forName("com.wx.test6.FancyToy");
} catch (Exception e) {
e.printStackTrace();
System.out.println("Can not find FancyToy");
}
printinfo(name);
for (Class<?> aClass : name.getInterfaces()) {
System.out.println(aClass);
}
Class<?> superclass = name.getSuperclass();
try {
Object o = superclass.newInstance();
System.out.println(o.getClass());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
static void printinfo(Class cc){
System.out.println("Class name:"+cc.getName());
System.out.println("interface:"+cc.getInterfaces());
System.out.println("Simple name:"+cc.getSimpleName());
System.out.println("Canonical name:"+cc.getCanonicalName());
}
}
interface HasBatteries{}
interface WaterPfoof{}
interface Shoots{}
class Toy{
Toy(){};
Toy(int i){};
}
class FancyToy extends Toy implements HasBatteries,WaterPfoof,Shoots{
FancyToy(){
super(1);
}
}
cc.getName():产生全限定的类名
cc.getSimpleName():产生不含包名的类名
cc.getCanonicalName():产生不含包名的全限定名
还有很多,比如判断当前的Class对象是否是接口,查询Class对象的直接基类,查询Class对象实现了哪些的接口,ok,通过Class对象,你可以发现你想要的了解的类型的所有信息,有了这些信息,当然可以构建出这个类的实例来了。
newInstance()方法是实现虚拟构造器的一种途径,使用它来创建的类。必须带有默认的构造器。
类字面常量
java 提供FancyToy.class的方法来生成Class对象,这样做简单,安全。因为他在编译的时候就会受到检查,根除了对forName方法的调用。
类字面常量适用于普通类,接口,数组,以及基本的数据类型,对于基本数据类型的包装类型有一个TYPE字段,TYPE字段就是一个引用,指向基本数据类型的Class对象,比如:boolean.class等价于Boolean.TYPE,使用.class的方法来创建Class对象。不会自动的初始化Class对象,初始化德邦操作被延迟到了对静态方法或者非常数静态域进行首次引用才执行,为了使用类而做的准备工作实际上有三步:
1.加载:由类加载器执行,查找字节码,从字节码中创建Class对象。
2.链接:验证类中的字节码,为静态域分配存储空间,如果有必须的话会解析这个类创建的对其他类的引用。
3.初始化:父类优先,静态优先
public class Test1 {
public static Random random=new Random(47);
public static void main(String[] args) {
Class inittableClass = Inittable.class;
System.out.println("After Creating Inittable");
System.out.println(Inittable.staticFinal);
System.out.println(Inittable.staticFinal2);
System.out.println(Inittable2.staticFinal);
try {
Class<?> aClass = Class.forName("com.wx.test7.Inittable3");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(Inittable3.staticFinal);
}
}
class Inittable{
public static final int staticFinal=47;
static final int staticFinal2=Test1.random.nextInt(1000);
static {
System.out.println("Init Inittable");
}
}
class Inittable2{
static int staticFinal=147;
static {
System.out.println("Init Inittable2");
}
}
class Inittable3{
static int staticFinal=74;
static {
System.out.println("Init Inittable3");
}
}
可以看到仅使用.class语法来获得对类的引用不会引发初始化。但是为了产生Class引用,Class.forName()就立即进行了初始化。如果一个static final 的值,这是一个编译期的常量,不需要对类进行初始化就可以读取,如Inittable.staticFinal,如下面,看到没有,并没有初始化就拿到值了。
因为.class创建的Class引用会把类的初始化延迟。但是仅仅static final还不够确保这种行为,如果不是编译期的常量那么就会在访问他之前强制初始化。就Inittable.staticFinal2,是先初始化打印了再打印值的。
如果他只是一个static域,那么在读取他的时候还是要求先进行初始化,如Inittable2.staticFinal
3.泛化的Class
对于上面的这种情况,虽然Integer继承于Number,但是Integer Class不是Number Class的子对象,所以这个时候就需要泛化Class了。Class<?>优于平凡的Class。为了将Class引用限定为某种类型,通配符需要与extend关键字相结合来使用,创建一个范围。
如果手头上的是超类:
newInstance()返回的就不是确切的类型。
3.类型转换前先做检查
ok ,现在我们知道RTTI的形式包括两个,传统的类型转换和Class对象来查询类型的信息。RTTI在java中还有第三种形式就是关键字instanceof,他返回一个布尔值,告诉我们对象是不是某个特定类型的实例,使用它是非常重要的否则就会报类型转换异常的信息。
ok,接下来有这样的一个需求,有一些类的继承关系如下,现在我随机创建20个不同的类的实例,最后分别统计每个类型的实例共创建了多少个?
package com.wx.test11;
/**
* Created by IntelliJ IDEA.
*/
public class Animal {
}
/**
* 5层类继承的基类,第一层
*/
class Individual {
private String name;
public Individual() {
String name;
}
public Individual(String name) {
this.name = name;
}
}
/**
* 第二层
*/
class Person extends Individual {
public Person(String name) {
super(name);
}
}
class Pet extends Individual {
public Pet() {
super();
}
public Pet(String name) {
super(name);
}
}
/**
* 第三层
*/
class Dog extends Pet {
public Dog() {
super();
}
public Dog(String name) {
super(name);
}
}
class Cat extends Pet {
public Cat() {
super();
}
public Cat(String name) {
super(name);
}
}
class Rodent extends Pet {
public Rodent() {
super();
}
public Rodent(String name) {
super(name);
}
}
/**
* 第四层
*/
class Mutt extends Dog {
public Mutt() {
super();
}
public Mutt(String name) {
super(name);
}
}
class Pug extends Dog {
public Pug() {
super();
}
public Pug(String name) {
super(name);
}
}
class EgyptianMau extends Cat {
public EgyptianMau() {
super();
}
public EgyptianMau(String name) {
super(name);
}
}
class Manx extends Cat {
public Manx() {
super();
}
public Manx(String name) {
super(name);
}
}
class Rat extends Rodent {
public Rat() {
super();
}
public Rat(String name) {
super(name);
}
}
class Mouse extends Rodent {
public Mouse() {
super();
}
public Mouse(String name) {
super(name);
}
}
class Hamster extends Rodent {
public Hamster() {
super();
}
public Hamster(String name) {
super(name);
}
}
/**
* 第五层
*/
class Cymric extends Manx {
public Cymric() {
super();
}
public Cymric(String name) {
super(name);
}
}
第一步需要一个方法,他可以随机的创建不同类型的宠物,为了方便起见,创建宠物数组和List.这是一个抽象类,方便继承用不同的方式来创建Class对象。
public abstract class PetCreator {
private Random random = new Random(47);
/**
* 需要有一个字段装的是不同宠物的Class对象
*/
public abstract List<Class<? extends Pet>> types();
/**
* 其次能随机的从集合的Class对象拿到一个出来创建Pet
*/
public Pet createPet() {
int i = random.nextInt(types().size());
Pet pet = null;
try {
pet = types().get(i).newInstance();
} catch (InstantiationException e) {
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return pet;
}
/**
* 随机生成个宠物
*/
public Pet[] petArray(int size) {
Pet[] pets = new Pet[size];
for (int i = 0; i < size; i++) {
pets[i] = createPet();
}
return pets;
}
}
抽象类中有一个抽象的方法,这个方法的返回值是一个Class集合,如何获得这个集合就是子类关心的事情,比如下面这种方式是通过forName的方式来获得,使用静态代码块来保证类加载的时候,Class对象的集合里就有值了,OK,这个非常的棒,充分的利用了编译器来给我们做了优化,运行阶段只需要拿Class生成对象再分类计数即可。
public class ForNameCreater extends PetCreator {
private static List<Class<? extends Pet>> list = new ArrayList<>();
private static String[] typyeName = {
"com.wx.test11.Mutt",
"com.wx.test11.Pug",
"com.wx.test11.EgyptianMau",
"com.wx.test11.Manx",
"com.wx.test11.Cymric",
"com.wx.test11.Rat",
"com.wx.test11.Mouse",
"com.wx.test11.Hamster"
};
public static List<Class<? extends Pet>> list() {
for (int i = 0; i < typyeName.length; i++) {
try {
list.add((Class<? extends Pet>) Class.forName(typyeName[i]));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return list;
}
static {
list();
}
@Override
public List<Class<? extends Pet>> types() {
return list;
}
}
接下来就是生成对象计数了,随机生成二十个对象再计数,使用内部类的方式来封装一个HashMap的功能。使用期get,put放来存取。
public class PetCount {
public static void main(String[] args) {
ForNameCreater forNameCreater = new ForNameCreater();
createPet(new ForNameCreater());
}
static class PetConter extends HashMap<String, Integer> {
public void count(String type) {
Integer integer = get(type);
if (integer == null) {
put(type, 1);
} else {
put(type, integer + 1);
}
}
}
public static void createPet(PetCreator petCreator) {
PetConter petCounter = new PetConter();
Pet[] pets = petCreator.petArray(20);
for (Pet pet : pets) {
if (pet instanceof Pet) {
petCounter.count("Pet");
}
if (pet instanceof Dog) {
petCounter.count("Dog");
}
if (pet instanceof Mutt) {
petCounter.count("Mutt");
}
if (pet instanceof Pug) {
petCounter.count("Pug");
}
if (pet instanceof Cat) {
petCounter.count("Cat");
}
if (pet instanceof Manx) {
petCounter.count("Manx");
}
if (pet instanceof Cymric) {
petCounter.count("Cymric");
}
if (pet instanceof Rodent) {
petCounter.count("Rodent");
}
if (pet instanceof Rat) {
petCounter.count("Rat");
}
if (pet instanceof Mouse) {
petCounter.count("Mouse");
}
if (pet instanceof Hamster) {
petCounter.count("Hamster");
}
}
System.out.println(petCounter);
}
}
ok,接下来就是运行,输入的结果是直接打印,说明HashMap重写了toString方法。
接下来使用类字面常量来重新常见Class对象的集合。
public class LiteralPetCount extends PetCreator {
public static final List<Class<? extends Pet>> allTypes =
Collections.unmodifiableList(Arrays.asList(
Pet.class, Dog.class, Cat.class, Rodent.class,
Mutt.class, Pug.class, EgyptianMau.class,
Manx.class, Cymric.class, Rat.class, Mouse.class,
Hamster.class
));
private static final List<Class<? extends Pet>> types =
allTypes.subList(allTypes.indexOf(Mutt.class), allTypes.size());
@Override
public List<Class<? extends Pet>> types() {
return types;
}
}
结果还是一样的
动态instanceof,Class.isInstance提供一种动态测试对象的路径,所有的单调的instanceof语句都可以从PetCount中移除。
class.inInstance(obj) 这个对象能不能被转化为这个类
3.反射
关于反射的文章:https://www.jianshu.com/p/9be58ee20dee
RTTI和反射的真正区别在于,对于RTTI来说,编译器再编译时打开和检查.class文件,而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。
通常不需要直接使用反射工具,但是他们在你需要创建更加动态的代码时会很有用。反射在java中是用来支持其他特性的。
Class类: