Java编程思想笔记14——第14章:类型信息

14 类型信息

​ 本章讨论Java如何在运行时识别对象和类的信息。

​ 主要有两种方式

​ (1)传统的”RTTI“,在编译时已经知道所有类型

​ (2)”反射“机制,允许在运行时发现和使用类的信息

14.1 为什么需要RTTI

interface Animal{
	void bark();
}

class Dog implements Animal{
	@Override
	public void bark() {
		System.out.println("汪汪汪");		
	}
}
class Cat implements Animal{

	@Override
	public void bark() {
		System.out.println("喵喵喵");
	}
}

​ 这是RTTI最基本的使用形式。在Java中,所有的类型转换都是在运行时进行正确性检查的。即RTTI的含义:在运行时,识别一个对象的类型。

​ 但是RTTI类型的转换并不彻底:Object被转型为Animal,而不是转型为Dog、Cat。

​ 但是,加入你要知道某个类的确切类型,如Dog或者Cat,那么用多态的方式就无法解决了。

​ 使用RTTI,就可以查询Animal引用所指的确切对象,从而实现特定的操作。

14.2 Class对象

​ 要了解RTTI的工作原理,必须先知道类型信息在运行时是如何表示的。

​ 该工作由Class对象的特殊对象来完成,它包含了与类有关的信息。

Class类中包含了该具体类的所有方法。但是使用多态机制只展示了多态中的方法,还有其他的方法在Class类中,没有被表现。

​ 每个类都会有一个Class对象

​ Java的各个部分一开始并没有被加载,而是在需要的时候被加载,这和传统语言不同。

​ 一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。

package com.test;
class Keji{
	static {
		System.out.println("keji,类被加载时才调用");
	}
}

class Hasiqi{
	static {
		System.out.println("hasiqi,类别加载时才调用");
	}
}

public class Test {
	public static void main(String[] args) {	
		System.out.println("开始");
		new Keji();

		try {
			Class.forName("Hasiqi"); // 加载Hasiqi这个类
			// 加载不出来,因为没有全限定类名
		}catch(ClassNotFoundException e) {
			System.out.println("没有找到Hasiqi这个类");
		}
		
		try {
			Class.forName("com.test.Hasiqi");
		}catch(ClassNotFoundException e) {
			System.out.println("没有找到Hasiqi这个类");
		}
		
	}
}
// 输出结果
// 开始
// keji,类被加载时才调用
// 没有找到Hasiqi这个类
// hasiqi,类别加载时才调用

Class.forName("全限定类名")会根据字符串,返回一个Class类型的对象

根据Class对象就可以获得对象的接口,基类等信息。还可以new一个新的对象

interface Animal{}

class Dog implements Animal{}

public class Test {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException {	
		try {
			Class dog = Class.forName("com.test.Dog");
			

			for (Class cls : dog.getInterfaces()) {//获得接口的信息
				System.out.println(cls);
			}
			Class superclass = dog.getSuperclass();//获得基类(父类)的信息
			System.out.println(superclass);
			
			// 根据类,new一个新的对象
			Object obj = dog.newInstance();
			System.out.println(obj.getClass());//obj的所属类别
			
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		
	}
}

14.2.1 类字面常量

​ Java还提高类字面常量来生成对Class对象的引用

class Dog {}

public class Test {
	public static void main(String[] args){	
		
		Class Dog = Dog.class;
	}
}

​ 类字面常量更加的安全。

​ 类字面常量不仅可以用于普通的类,还可以用于接口、数组以及基本数据类型。

public class Test {
	public static void main(String[] args){	
		
		Class int_ = int.class;
		Class int_2 = Integer.TYPE; //这两个是等价的
		
		Class b = boolean.class; //基本类型的类字面常量
		Class b_2 = Boolean.TYPE;
		
		Class void_ = void.class;
		Class void_2 = Void.class;
		
	}
}

当使用.class的时候,不会初始化对象,而是做如下三个步骤

(1)加载:由类加载器执行的 。从字节码创建一个Class对象

(2)链接:验证类中的字节码。

(3)初始化:初始化其超类(基类,父类),执行静态初始化器和静态初始化块。

14.2.2 泛化的Class引用

​ Class引用指向某个Class对象,它可以创造类的实例

​ 通过泛型语法,限制Class指向的具体类

public class Test {
	public static void main(String[] args){	
		
		
		Class in = int.class;
		in = double.class; //不会报错
		
		Class<Integer> i = int.class;
		//i = double.class; //报错,语法错误	
        
        // 使用通配符就可以解决这个问题
        Class<?> ii = int.class;
		ii = double.class; //不会报错
		
		Class<? extends Number> iii = int.class;
		iii = double.class;//不会报错,因为int和double都继承于Number
	}
}

14.2.3 新的转型语法

​ Java SE5 添加了用于Class引用的转型语法,即cast()方法

​ Class对象中的cast方法,将其他转型转化为当前对象

class Animal {}
class Dog extends Animal{}


public class Test {
	public static void main(String[] args){	
		
		Animal a = new Dog();
		
		Class<Dog> dogType = Dog.class;
		Dog d = dogType.cast(a); //把a转换为Dog
		
		// 等价于
		d = (Dog)a;	
	}
}

14.3 类型转换前先做检查

​ 截至目前,我们已知的RTTI形式包括

​ (1)传统的类型转换。将Animal类转换为Cat类

​ (2)代表对象的类型的Class对象。通过Class对象获取运行时所需要的信息。

​ (3)使用instanceof 进行转换

if (x instanceof Dog)
    ((Dog)x).speak();//如果是Dog类型,则将其强制转换为Dog类型
class Animal {}
class Dog extends Animal{}
class Cat extends Animal{}

public class Test {
	public static void main(String[] args){	
		
		Animal d = new Dog();
		Animal c = new Cat();
		
		if(d instanceof Dog) {
			Dog dog = (Dog)d;
		}else if(d instanceof Cat) {
			Cat cat = (Cat)d;
		}		
		
	}
}

instance需要大量的if进行判断,这对设计者来说并不好。

14.3.1 使用类字面常量

​ 通过类字面常量,将这些加入数组或者容器中,通过for循环进行比较,使代码稍微清晰一点

import java.util.ArrayList;
import java.util.List;

class Animal {}
class Dog extends Animal{}
class Cat extends Animal{}

public class Test {
	
	
	
	private static List<Class<? extends Animal>> list = new ArrayList<Class<? extends Animal>>();
	public static void main(String[] args){	
		list.add(Dog.class);
		list.add(Cat.class);
		
	}
}

14.3.2 动态instanceof

​ Class.isInstance 方法提供了一种动态地测试对象的途径。

class Animal {}
class Dog extends Animal{}
class Cat extends Animal{}

public class Test {
	public static void main(String[] args){	
		Animal a = new Dog();
		Class t = a.getClass();
		
		if (t.isInstance(Dog.class)) {
			Dog d = (Dog)a;
		}else if(t.isInstance(Cat.class)) {
			Cat c = (Cat)a;
		}
		
	}
}

14.3.3 递归计数

​ 。。。

14.4 注册工厂

​ 如果要在每个子类中添加静态初始化器,以使得初始化器可以将她的类添加到某个List中。

​ 但是静态初始化器只能在类加载时才能调用,这就变成了“先有鸡还是先有蛋”的问题。

​ 因此最佳的做法是,将这个列表置于一个显眼的位置,将需要的类继承于该类。

package com.test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

// 工厂接口
interface Factory<T> {
	T create();
}

class Animals{
	
	// 该list中,放的Factory的子类, 
	// 且Factory的泛型是Animals的子类
	static List<Factory<? extends Animals>> animalFactory = 
			new ArrayList<Factory<? extends Animals>>();
	
	static {
		animalFactory.add(new Dog.Factory());
		animalFactory.add(new Cat.Factory());
		animalFactory.add(new Bird.Factory());
	}
	
	public static Animals createRandomAnimal() {
		Random rand = new Random();
		int n = rand.nextInt(animalFactory.size());
		return animalFactory.get(n).create(); // 构造一个新的对象
	}
}



class Dog extends Animals {
	// 内部类,该内部类继承Factory接口
	public static class Factory implements com.test.Factory<Dog>{
		@Override
		public Dog create() {
			return new Dog();
		}
	}
}

class Cat extends Animals {
	// 内部类,该内部类继承Factory接口
	public static class Factory implements com.test.Factory<Cat>{
		@Override
		public Cat create() {
			return new Cat();
		}
	}
}

class Bird extends Animals {
	// 内部类,该内部类继承Factory接口
	public static class Factory implements com.test.Factory<Bird>{
		@Override
		public Bird create() {
			return new Bird();
		}
	}
}


public class Test {
	
	// Dog,Cat,Bird必须通过Factory才可以创建
	// 且Dog,Cat,Bird都是Animals的子类
	
	public static void main(String[] args){	
		System.out.println(Animals.createRandomAnimal());
		
	}
}

14.5 instanceof 与 Class的等价性

class Father{}
class Son extends Father{ }

public class Test2 {
	public static void main(String[] args) {
		
		
		Father f = new Son();
		// 必须是确切的类
		System.out.println(f.getClass() == Father.class); //false
		System.out.println(f.getClass().equals(Father.class)); //false
		// 只要是派生类就可以
         System.out.println(f instanceof Father); //true
		System.out.println(Father.class.isInstance(f)); //true	
	}
}

​ instanceof 与 isInstance是等价的,都是判断是否为父类的子类

​ 而==,equals 则是判断类是否完全相等。

14.6 反射:运行时的类信息

​ 加入你获得了一个不在程序空间中的对象的引用。编译时无法知道该对象所属的类。比如说你从磁盘空间读取一串字节码,怎样才能使用这个类?

​ Class类 和 java.lang.reflect类对反射进行了支持,该类库包括Field、Method以及Constructor类。

​ (1)可以使用Constructor创建新的对象

​ (2)用get()和set()方法读取和修改Field对象关联的字段

​ (3)用invoke()方法调用与Method对象关联的方法

​ (4)还可以调用getFields(),getMethods()和getConstructors()等方法

反射与RTTI的区别之处

​ RTTI在编译时时会打开和检查.class文件

​ 而反射机制,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件

14.6.1 类方法提取器

​ 发射在Java中用来支持其他特性的,例如对象序列化和JavaBean

​ 反射机制还可以展示类中的方法、字段等信息。

package com.test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
class Dog2 {
	String color;
	public Dog2(){color = "white";}
	public Dog2(String color) {this.color = color;}
	
	public void bark() {System.out.println("汪汪汪");}
	public void sleep() {System.out.println("zzzzzzzzz");}
	public int eat() {return 1;}
}


public class Test2 {
	public static void main(String[] args) throws ClassNotFoundException {
		Class<?> dogType = Class.forName("com.test.Dog2");
		
		Method[] methods = dogType.getMethods();//获得所有的方法
		for (Method method : methods) {
			System.out.println(method);
		}
		System.out.println("==============");
		Constructor<?>[] constructors = dogType.getConstructors();//获得所有构造方法
		for (Constructor<?> constructor : constructors) {
			System.out.println(constructor);
		}

	}
}
// public void com.test.Dog2.sleep()
// public void com.test.Dog2.bark()
// public int com.test.Dog2.eat()
// public final void java.lang.Object.wait() throws java.lang.InterruptedException
// public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
// public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
// public boolean java.lang.Object.equals(java.lang.Object)
// public java.lang.String java.lang.Object.toString()
// public native int java.lang.Object.hashCode()
// public final native java.lang.Class java.lang.Object.getClass()
// public final native void java.lang.Object.notify()
// public final native void java.lang.Object.notifyAll()
// ==============
// public com.test.Dog2()
// public com.test.Dog2(java.lang.String)

Class.forName()生成的结果在编译时是不可知的,所有的方法都是在执行时被提取出来的。

14.7 动态代理

​ 代理是基本的设计模式之一,相当于一个中间人的角色,通过中间人进行沟通。

interface Person{
	void sing();//唱歌
	void talk();//访谈
}

class Singer implements Person{// 歌手

	@Override
	public void sing() { // 歌手唱歌
		System.out.println("大河向东流。。");
	}

	@Override
	public void talk() { // 访谈唱歌
		System.out.println("进行访谈");
	}
}

class Proxy implements Person{ //经纪人
	
	private Person singer;
	public Proxy(Person singer) {
		this.singer = singer;
	}
	@Override
	public void sing() { 
		System.out.println("先签合同");
		singer.sing();
	}

	@Override
	public void talk() {
		System.out.println("先对台本");//先对台本,再进行访谈。
		singer.talk();
	}
}

public class Test2 {
	public static void main(String[] args){
		Person p = new Proxy(new Singer());
		p.sing();
		p.talk();
	}
}

Java的动态代理比代理的思想更进一步,它可以动态地创建代理并动态地处理对所代理方法的调用。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Person{
	void sing();//歌手唱歌
	void talk();//歌手访谈
}

class Singer implements Person{// 歌手

	@Override
	public void sing() {
		System.out.println("大河向东流。。");
	}

	@Override
	public void talk() {
		System.out.println("进行访谈");
	}
}


class DynamicProxy implements InvocationHandler{
	
	private Object singer;
	public  DynamicProxy(Object singer) {//构造方法
		this.singer = singer;
	}
	
	//代理
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("开始执行代理");
		if(args != null ) {
			for (Object arg : args) {
				System.out.println("参数:"+arg);
			}
		}
		return method.invoke(singer, args); //执行代理的方法
	}
	
}


public class Test2 {
	public static void main(String[] args){
		
		Singer singer = new Singer();
		
		Person proxy = (Person)Proxy.newProxyInstance(
				Person.class.getClassLoader(),
				new Class[] {Person.class}, 
				new DynamicProxy(singer));//获得动态代理
		proxy.sing();
		proxy.talk();
	}
}
// 输出结果
// 开始执行代理
// 大河向东流。。
// 开始执行代理
// 进行访谈

14.8 空对象

​ 在每次使用引用时必须测试其是否为null,这显得很麻烦

​ 所有Java引入的空对象,该对象可以返回一个有效的值,这样就不需要每次进行判断了。

​ 只需要自己定义一个名字为Null的接口,认为其为空对象。

interface Null{}// 定义一个NULL接口

class Person{
	String name;
	int age;
	
	// 定义一个空对象的内部类
	public static class NullPerson extends Person implements Null{
		private NullPerson() {
			super();
		}
	}
	public static final Person NULL = new NullPerson();
	
}

14.9 接口与类型信息

​ interface允许程序员隔离构建,降低耦合性。

​ 但是接口对于解耦还是有一些缺陷。

interface A{
	void f();
}

class C implements A{
	@Override
	public void f() {}
	public void g() {}
}

public class Test3 {
	public static void main(String[] args) {
		A a = new C();
		a.f();
		//a.g();//编译错误
	}
}

虽然a是按照C这个对象进行创建的,但是却用不了g()方法。

14.10 总结

​ 面向对象编程语言的目的是尽量使用多态,只在必需的时候使用RTTI

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值