类型信息
本章将讨论java是如何让我们在运行时识别对象和类的信息的.主要有两种方式:一种是"传统的"RTTI,它假定我们在编译时已经知道了所有的类型,另一种是"反射"机制,它允许我们在运行时发现和使用类的信息.
14.1为什么需要RTTI(Run-Time Type identification)
- 如果不知道某个对象的确切类型,RTTI可以告诉你.但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它.例如多态
14.2Class对象
- 要理解RTTI在java中的工作原理,首先必须知道类型信息在运行时是如何表示的.这项工作是由称为Class对象的特殊对象完成的,包含了与类有关的信息.事实上,Class对象就是用来创建类的所有的"常规"对象的.java使用Class对象来执行其RTTI,即使你正在执行的时类似转型这样的操作.
- 类是程序的一部分,每个类都有一个Class对象.换言之,每当编写并且编译了一个新类就会产生一个Class对象(被保存在一个同名的.class文件中).为了生成这个类的对象,运行这个程序的Java虚拟机JVM将使用被称为"类加载器"的子系统.
- 所有的类都是在对其第一次使用时,动态加载到JVM中的.当程序创建第一个对类的静态成员的引用时,就会加载这个类.因此证明构造器也是类的静态方法,即使在构造器之前并没有使用static关键字.
- 因此,java程序在它开始运行之前并非完全加载,其各个部分是在必需时才加载的.
- 类加载器首先检查这个类的Class对象是否已经加载.如果没加载,默认的类加载器就会根据雷鸣查找.class文件.
6. 几个方法
- Class的newInstance()
public T newInstance()
throws InstantiationException,IllegalAccessException
创建由此类对象表示的类的新实例。 该类被实例化为一个具有空参数列表的new表达式。 如果类尚未初始化,则初始化该类.
Creates a new instance of the class represented by this Class object. The class is instantiated as if by a new expression with an empty argument list. The class is initialized if it has not already been initialized.
- Class的getName()
- Class的forName()
public static 类> forName(String className)throws ClassNotFoundException
返回与给定字符串名称的类或接口相关联的类对象。
Returns the Class object associated with the class or interface with the given string name. Invoking this method is equivalent to: Class.forName(className, true, currentLoader)
- Java还提供了另一种方法来生成对Class对象的引用,即使用类字面常量.
- 注意,有一点很有趣,当使用".class"来创建对Class对象的引用时,不会自动地初始化该Class对象.为了使用类而做的准备工作实际包含三个步骤:
- 加载,这是由类加载器执行的.该步骤将查找字节码,并从这些字节码中创建一个Class对象
- 链接,在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用.
- 初始化.如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块
14.3类型转换前先做检查
14.4注册工厂
里面含有设计模式一些泛型,现在看不太懂等到看完有一定的理解之后再回来看这两节
14.5 instanceof与Class的等价性
class Base{}
class Derived extends Base{}
public class FamilyVsExactType {
static void test(Object x){
print("Testing x of type" + x.getClass());
print("x instanceof Base " + (x instanceof Base));
print("x instanceof Derived" + (x instanceof Derived));
print("Base.isInstance(x) " + Base.class.isInstance(x));
print("Derived.isInstance(x) " + Derived.class.isInstance(x));
print("x.getClass()==Base.class " + (x.getClass() == Base.class));
print("x.getClass() == Derived.class" + (x.getClass() == Derived.class));
print("x.getClass().equals(Base.class))" + (x.getClass().equals(Base.class)));
print("x.getClass().equals(Derived.class))" + (x.getClass().equals(Derived.class)));
}
public static void main(String[] args) {
test(new Base());
test(new Derived());
}
}
/*Output
Testing x of typeclass cn.itcast.zhuofai803.demo14_5.Base
x instanceof Base true
x instanceof Derivedfalse
Base.isInstance(x) true
Derived.isInstance(x) false
x.getClass()==Base.class true
x.getClass() == Derived.classfalse
x.getClass().equals(Base.class))true
x.getClass().equals(Derived.class))false
Testing x of typeclass cn.itcast.zhuofai803.demo14_5.Derived
x instanceof Base true
x instanceof Derivedtrue
Base.isInstance(x) true
Derived.isInstance(x) true
x.getClass()==Base.class false
x.getClass() == Derived.classtrue
x.getClass().equals(Base.class))false
x.getClass().equals(Derived.class))true
*///:~
- test()方法使用了两种形形式的instanceof作为参数来执行类型检查.然后获取Class引用,并用==和equals()来检查Class对象是否相等.instanceof和isInstance()生成的结果完全一样,equals()和==也一样.但是两组测试得出的结果不同.instanceof保持了类型的概念,而如果用==比较实际的Class对象,就没有考虑继承--他或者时这个确切的类型,或者不是.
- 使用到的方法解释
- getClass()方法
public final 类> getClass()返回此Object的运行时类。 返回的类对象是被表示类的static synchronized方法锁定的对象. Returns the runtime class of this Object. The returned Class object is the object that is locked by static synchronized methods of the represented class.
14.6反射:运行时的类信息
- Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field.Method以及Construcotr类(每个类都实现了Member接口).这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员.这样你就可以使用Constructor创建新的对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法.另外,还可以调用getFields(),getMethods()和getConstructors()等很便利的方法,以返回表示字段.方法以及构造器的对象的数组.这样匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情.
- RTTI与反射之间真正的区别只在于,对RTTI来说,编译器在编译时打开和检查.class文件.(换句话说,我们可以用"普通"方式调用对象和所有方法.)而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件.
14.7 也是这样看不太懂讲的反射机制
14.8空对象
- Sometimes it is useful to introduce the idea of a Null Object3 that will accept messages for the object that it’s "standing in" for, but will return values indicating that no "real" object is actually there. This way, you can assume that all objects are valid and you don’t have to waste programming time checking for null (and reading the resulting code).
- The place where Null Objects seem to be most useful is "closer to the data," with objects that represent entities in the problem space. As a simple example, many systems will have a Person class, and there are situations in the code where you don’t have an actual person (or you do, but you don’t have all the information about that person yet), so traditionally you’d use a null reference and test for it. Instead, we can make a Null Object. But even though the Null Object will respond to all messages that the "real" object will respond to, you still need a way to test for nullness. The simplest way to do this is to create a tagging interface
//空对象都是单例,因此这里将其作为静态final实例创建
//只能写到这里了感觉 还是有点不太懂这个设计
public interface Null {
}
class Person{
public final String first;
public final String last;
public final String address;
Person(String first, String last, String address) {
this.first = first;
this.last = last;
this.address = address;
}
public String toString(){
return "Person: " + first + " " + last + " " + address;
}
public static class NullPerson extends Person implements Null{
NullPerson() {
super("None", "None", "None");
}
public String toString(){
return "NullPerson";
}
}
public static final Person NULL = new NullPerson();
}
public class Position {
private String title;
private Person person;
public Position(String jobTitle,Person employee){
title = jobTitle;
person = employee;
if (person == null){
person = Person.NULL;
}
}
public Position(String jobTitle){
title = jobTitle;
person = Person.NULL;
}
public String getTitle(){
return title;
}
public void setTitle(String newTitle){
title = newTitle;
}
public Person getPerson(){
return person;
}
public void setPerson(Person newPerson){
person = newPerson;
if (person==null)
person = Person.NULL;
}
public String toString(){
return "Position: " + title + " " + person;
}
}
public class Staff extends ArrayList {
public void add(String title,Person person){
add(new Position(title,person));
}
public void add(String ...titles){
for (String title :
titles) {
add(new Position(title));
}
}
public Staff(String ...titles){
add(titles);
}
public boolean positionAvailable(String title){
for (Position position :
this) {
if (position.getTitle().equals(title) && position.getPerson()==Person.NULL){
return true;
}
}
return false;
}
public void fillPosition(String title ,Person hire){
for (Position position :
this) {
if (position.getTitle().equals(title) && position.getPerson() == Person.NULL)
{
position.setPerson(hire);
}
return;
}
throw new RuntimeException("Position " + title + " not available");
}
public static void main(String[] args) {
Staff staff = new Staff("Persident","CTO","Marketing Manager","Product Manager"
,"Project Lead","Software Engineer",
"soft Enginner","Test Enginner",
"Technical Writer");
staff.fillPosition("Persident",new Person("Me","Last","The Top, Lonely At"));
staff.fillPosition("Project Lead",new Person("Janet","Planner","The Burbs"));
if (staff.positionAvailable("software Engineer")){
staff.fillPosition("software Engineer",new Person("Bob","Coder","Bright Light City"));
}
System.out.println(staff);
}
}
14.9接口与类型信息
public interface A {
void f();
}
class B implements A {
public void f(){}
public void g(){}
}
public class InterfaceViolation {
public static void main(String[] args) {
A a = new B();
a.f();
//a.g();
System.out.println(a.getClass().getName());
if (a instanceof B)
{
B b = (B)a;
b.g();
}else{
System.out.println("nbo jflwkjf ");
}
}
}
class C implements A{
@Override
public void f() {
print("public C.f()");
}
public void g(){
print("public C.g()") ;
}
void u(){
print("package C.u()");
}
protected void v(){
print("protected C.v()");
}
}
public class HiddenC {
public static A makeA(){
return new C();
}
}
//同上面程序包不一样导致使用不了C从而不能向下转型
public class HiddenImplementation {
public static void main(String[] args) throws Exception {
A a = HiddenC.makeA();
a.f();
System.out.println(a.getClass().getName());
//Compile error :cannot find symbol 'c';
/*
if (a instanceof C){
C c = (C)a;
c.g();
}
*/
//Oops ! Reflection still allows us to call g();
callHiddentMethod(a,"g");
//And even methods that are less accessible!
callHiddentMethod(a,"v");
callHiddentMethod(a,"w");
}
static void callHiddentMethod(Object a,String methodName)throws Exception{
Method g = a.getClass().getDeclaredMethod(methodName);
g.setAccessible(true);
g.invoke(a);
}
}
- 循序渐进后面更进一步的程序就不列举了,直接说结论好啦
- 前两个程序说明借口并非是对解耦的一中无懈可击的保障,最简单的解决的方式是使用包访问权限.
- 通过反射,仍旧可以调用子接口的所有方法甚至是private.如果知道方法名,你就可以在其Method对象上调用setAccessible(true),就像在callHiddenMthod()中看到那样
- 也可以使用反编译文件C.class 即javap -private C
- 同样的私有内部类和匿名类也不能阻止反射大道并调用那些非公共访问权限的方法
- 但是final域实际上在遭遇修改时是安全的.