本节讨论的是Java如何让我们知道如何在运行时知道对象和类的信息。只要方式有两种,一种是传统的RTTI(在运行时,识别一个对象的类型信息),它假定我们在编译时已经知道了所有的类型,另一种是反射机制,它允许我们在运行时发现和使用类型信息。
1Class相当于类的类型一样,无论何时,如果想在运行时使用类型信息,就必须先获得恰当对象的引用(参照第三条String 例子,也是RTTI的例子)。如果你有一个Class对象,还可以使用getSuperclass()方法来查询其直接基类,将返回你可以用来直接查询的Class对象(类),这样就能在运行时发现一个完整的类继承结构,还有可以调用getSimpleName()返回类名,getCanonicalName()返回一个完整的名称(含有包名),getInterfaces()返回一个Class类型的数组。getClass是获得Class对象,也就是一个类。
Class对象就是用来创建所有常规对象的,类是程序的一部分,每个类都有一个Class对象,(更确切的说是被保存在一个同名的.class文件中)。为了运行程序,JVM虚拟机会使用“类加载器”的子系统。原生的类加载器加载的是所谓的 可信类,包括JAVA API类,他们通常是从本地加载的。如果你有特殊的需求,需要以某种特殊的方式加载类,那么你有一种特殊的方式可以加载额外的类加载器。 构造器也是类的静态方法,虽然没有static修饰。
2 Java程序在它开始运行之前不是被全部加载的,它在必需是才会被加载,动态加载的使能行为,在静态加载语言c++是根本不可能做到的。类加载器首先会检查这个类的Class对象是否加载,如果没有,默认的类加载器会根据类名查找.class文件。某个附加类加载器可能会从数据库中查找字节码。一旦某个类的Class对象被载入类存,它就被用来创建这个类的所有对象。
3 jvm会执行静态代码段,你要记住一个概念,静态代码是和class绑定的,class装载成功就表示执行了你的静态代码了。而且以后不会再走这段静态代码了。
Class.forName(xxx.xx.xx) 返回的是一个类 注意类名要完整(包含包名.类名),加载时会加载静态初始化块和静态初始化器
Class.forName(xxx.xx.xx);的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段
动态加载和创建Class 对象,比如想根据用户输入的字符串来创建对象
String str = 用户输入的字符串
Class t = Class.forName(str);
t.newInstance();
4 使用newInstance()方法时Class并不知道你确切的类型,但是无论如何都会正确的创建,这是实现虚拟构造器的一种途径。注意:使用newInstance创建的类必须拥有默认的构造器。
5 java还提供了一个方法来生成对Class对象的引用(即类字面常量) 例如FancyToy.class 这样做不仅简单而且更安全,因为它在编译时就受到检查(因此不需要放在try语句块中),并且根除了它对forName()方法的调用,所以也更高效。
类字面常量不仅可以用于普通类, 还可以用于数组 接口 基本类型 以及包装类
boolean.class等价于Boolean.TYPE
char.class等价于Character.TYPE
byte.class等价于Byte.TYPE
int.class等价于Integer.TYPE
short.class等价于Short.TYPE
long.class等价于Long.TYPE
float.class等价于Float.TYPE
double.class等价于Double.TYPE
void.class等价于boolean.class等价于Void.TYPE
有一点注意:当使用.class来创建class对象时,不会自动的初始化该class对象 注意是类.class 普通对象是 对象.getClass 比如 int.class User.class u.getClass
6 为了在Class中使用泛型时放松限制,使用了通配符?,它也是泛型的一部分,表示 任何事物 例如:Class<?>=int.class 注意:Class<?>优于普通不加泛型的class 即便他们是等价的。为了创建一个Class引用,被限定在某种范围,或该类型的任何子类,需要将通配符和extends结合,创建一个范围。例如 Class<? extends User>=user.class
7对于newInstance()方法 加了泛型之后可能返回的不是确定类型 也可能返回的是确定类型
例如:
Class<FancyToy> ftClass=FancyToy.class;
FancyToy fancyToy=ftClass.newInstance;//这里newInstance出来的就是确切的类型
Class<? extends FancyToy> up=ftClass.getSuperclass;
这里泛型不能直接写FancyToy的父类 要模糊的写 这样也导致了newInstance出来不知道是一个什么类型 不是精确地
Object obj=up.newInstance();
8 反射机制在Java中是用来支持其他跳特性的,例如对象序列化和JavaBean(MVC中的数据库层model)。
javaBean在MVC设计模型中是model,又称模型层,在一般的程序中,我们称它为数据层,就是用来设置数据的属性和一些行为, 然后我会提供获取属性和设置属性的get/set方法9重要P334thinking in java RTTI与反射的真正区别在于:对RTTI来说,编译器在编译时打开和检查.class文件(
当你创建好一个java类的时候,他会编译一次,之后就是在你每次修改这个文件的时候,单击保存或是CTR+S的时候都会编译一次,这是eclipse为自动编译的),换句话说我们可以用普通的方式调用对象的所有方法;而对于反射的方式, .class文件在编译的时候是不可以获取的,所以是在运行时打开和检查.class文件。(对运行时的理解看第三条的代码String例子)。对于想要运行时运算的动机看第三条的代码String例子和334页的分布式计算,把许多Java对象分布在各处,分为许多的小的计算单元,分布到空闲的机器上运行。
10通常不会直接使用反射工具,但是反射在你要创建更加动态的代码的时候会很有用。
11 一个类,你不写任何构造方法时,会自动存在一个无参构造方法
但是如果你已经写了有参的构造方法了,那么那个默认存在的无参构造方法就不存在了
你如果还需要一个无参构造方法,那就要自己写一个
那为什么一般都需要一个无参构造方法呢?
1)、子类构造方法需要调用父类构造方法,而默认情况下是隐式调用父类无参构造方法,如果父类没有无参构造方法,那就要显式调用一个有参构造方法
2)、反射需要它,反射生成对象时,是调用的无参构造方法,如果没有无参构造方法,就不能反射 newInstance 生成一个该类的对象
10 如果在多态中,B extends A ,A a=new B() 其中B实现A a中的方法只有实现的也就是公共的方法,a是一个B.class 也就是B的类型,但是如果想要调用B中特有的方法,必须对a向下转型(强转)才可以调用B特有的方法,
注意:default和private的类及时在import导入包的时候也不能在另外一个包中命名,假设C设一个default的类,在另一个包中C c=...这样是不对的,即使import了,所以此时没办法强制转换,因为连(C)都写不出来(提示不可见),这时候把对应的类改为public就可以或者还有另外一种方法就是通过反射,压制java的安全性检查,在另一个包中来调用C(即使C不是公开的类)中的任何方法,包括private方法。 即使import了也不能使得default的类在其包中被使用,包可以规范命名,还可以使得在不用包使用相同的名字,import进来只是使用其public和protected的部分类,相当于把一系列的类给导入进来的作用。
下面举例:
结构截图:
package typeinfo.interfacea;
public interface A
{
/*
* thinking in java page346
*/
void f();
}
package typeinfo.packageaccess;
import typeinfo.interfacea.*;
class C implements A
{
@Override
public void f()
{
System.out.println("public C.f()");
}
public void g()
{
System.out.println("public C.g()");
}
void u()
{
System.out.println("package C.u()");
}
protected void v()
{
System.out.println("protected C.v()");
}
private void w()
{
System.out.println("private C.w()");
}
}
package typeinfo.packageaccess;
import typeinfo.interfacea.*;
public class HiddenC
{
public static A makeA()
{
return new C();
}
}
package type;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import typeinfo.interfacea.A;
import typeinfo.packageaccess.HiddenC;
public class Test
{
public static void main(String[] args)
{
//这是在typeinfo.packageaccess.HiddenC里面调用new C()
A a=HiddenC.makeA();
a.f();
/*
这个能成功是因为A时public ,C重写了f方法 动态链接到C的public方法
a中只有f方法,还没有向下转型,转型之后才有的其他方法
*/
System.out.println(a.getClass().getName());//a是C类型 但是此时a自己不能C自己特有的方法 只能调用
//必须向下转型(此时不允许 因为不能在包的外部命名C,default), 只能调用父接口共有的方法(可用)
/*
* 导包之后即使原本是default的类仍然是无法可见的
* 只有public protected才行 包作用一是规范,方便整合 二是在不同包内部可以有相同的类的名字、
* 此处C是default 在包外是不可见 即使导入包了之后也是,不能在package typeinfo.packageaccess
* 不能在包的外部命名C
*/
// if(a instanceof C)
//
// {
// C c=(C)a;
// c.g();
// }
//此时可以使用反射解决这个不能向下转型从而调用C自己方法的问题,我们可以通过反射来压制java的安全性检查
callHiddenMethods(a,"g");
callHiddenMethods(a,"f");
callHiddenMethods(a,"v");
callHiddenMethods(a,"w");
}
static void callHiddenMethods(Object object,String methodName)
{
try
{
Method m=object.getClass().getDeclaredMethod(methodName);
m.setAccessible(true);
m.invoke(object);
}
catch (IllegalAccessException e)
{
e.printStackTrace();
}
catch (IllegalArgumentException e)
{
e.printStackTrace();
}
catch (InvocationTargetException e)
{
e.printStackTrace();
}
catch (NoSuchMethodException e)
{
e.printStackTrace();
}
catch (SecurityException e)
{
e.printStackTrace();
}
}
输出:
public C.f()
typeinfo.packageaccess.C
public C.g()
public C.f()
protected C.v()
private C.w()