java编程思想--14类型信息

1.这一章主要讨论了运行时类型信息的获得与使用。
   (1)Class对象
    “在Java中,所有的类型转换都是在运行时进行正确性检查的。”“每个类都有一个Class对象(被保存在一个同名的.class文件中)”,因此可以通过Class类(所有Class对象都属于这个类)的一些方法来动态获得所需要的类的Class对象,如Class.forName()方法通过字符串形式的类名获得其class对象引用,进而获得该类的信息,或者可以通过newInstance()方法实现对象的构建;而这里对class对象的获得,使得该类被加载,同时static语句被执行,而单纯对类static final值(编译期常量)的引用不会引起static初始化,也可以说间接的说明了构造器隐式地是静态的。
    类字面常量:另一种生成class对象引用,是 类名.class 的形式,这种方式不会自动地初始化该class对象。“Class引用总是指向某个Class对象,它可以制造类的实例,并包含可作用于这些实例的所有方法代码”,为了更充分的利用Class,可以通过泛化的Class引用,使用“?”通配符和extends、super等关键字来无限或有限制的使用class类引用。
   (2)类型检查
    关键字instanceof,形如 x instanceof Dog 它返回一个布尔值,检测该对象是不是某个特定类型;另一种方式是Class.isInstance()可以动态的检测对象类型。这里需要注意的是使用instanceof 或者 isInstance()方法对对象进行检测时,保持了类型的概念,即检查的是“是这个类或者是这个类的派生类”,而通过使用“==”与获取的class对象引用对比则只是进行确切类型的比较。
    反射
    没有找到比较好的概念文字,但是这样理解还是可以的,JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。这里有会产生两个问题:一是运行中动态的获得类型信息,RTTI也是可以做到的,但是这个是在编译时打开和检查.class文件的,而反射机制是在运行时获得类型信息(这个还是有些不明白,mark);另一个问题是既然是获得类或者对象的所有属性和方法,那么private等修饰隐藏的私有信息也是可以获得的了,这里需要有一个前提就是知道这个类的方法或者属性名(仍然是可以通过java反编译工具获得的,因此确实是可以的,但是并不建议如此)。
    Class类和java.lang.reflect类库一起支持反射机制,包括了Field、Method、Constructor类。可以看一个书上的例子(我增加了属性获取):
package com.test.myjava;
/*
  * @author w7849516230
  */
import java.lang.reflect.*;
import java.util.regex.*;

/* 功能:可以查询任意类的所有public权限的方法、属性(可以查看所有),
* 当有第二个参数时,可以作为包含指定字符串的方法或属性过滤
* 注意:在eclipse中使用时,需要包含完整的包名;如果在console执行,需要注意包名
*/
public class ShowClassInformation {
private static String usage =
"usage:\n" +
"ShowMethods qualified.class.name\n" +
"To Show all methods in class or:\n" +
"ShowMethods qualified.class.name word\n" +
"To Search for methods involving 'word'";
public String str = "public string";
private static Pattern p = Pattern.compile("\\w+\\.");
//表示过滤掉前面的命名修饰,如果用下面的可以看到完整的命名
//private static Pattern p = Pattern.compile("\\W+\\.");

public static void main(String[] args){
if(args.length < 1){
System.out.print(usage);
System.exit(0);
}
int lines = 0;
try{
Class<?> c = Class.forName(args[0]);
//使用下面的类字面常量则不用try catch语句
//Class<?> c = ShowClassInformation.class
Method[] methods = c.getMethods();
Constructor[] ctors = c.getConstructors();
Field[] fields = c.getFields();

/* 如果需要设置属性值使用setXXX方法
* 调用方法可以使用Invoke方法
*/
if(args.length == 1){
System.out.println(args[0]+" Methods:");
for(Method method : methods)
System.out.println(p.matcher(method.toString()).replaceAll(""));
System.out.println();

System.out.println(args[0]+" Constructors:");
for(Constructor ctor : ctors)
System.out.println(p.matcher(ctor.toString()).replaceAll(""));
System.out.println();

System.out.println(args[0]+" Fields:");
for(Field field : fields)
System.out.println(p.matcher(field.toString()).replaceAll(""));
System.out.println();

lines = methods.length + ctors.length + fields.length;
}else {
for(Method method : methods)
if(method.toString().indexOf(args[1]) != -1){
System.out.println(p.matcher(method.toString()).replaceAll(""));
lines++;
}
for(Constructor ctor : ctors)
if(ctor.toString().indexOf(args[1]) != -1){
System.out.println(p.matcher(ctor.toString()).replaceAll(""));
lines++;
}
for(Field field : fields)
if(field.toString().indexOf(args[1]) != -1){
System.out.println(p.matcher(field.toString()).replaceAll(""));
}
}
}catch(ClassNotFoundException e){
System.out.println("No such class:" + e);
}
}
}
输入参数为:java.lang.reflect.Method
输出为:java.lang.reflect.Method Methods:
public int hashCode()
public int getModifiers()
public transient Object invoke(Object,Object[]) throws IllegalAccessException,IllegalArgumentException,InvocationTargetException
public boolean equals(Object)
public String getName()
public String toString()
public Annotation getAnnotation(Class)
public Annotation[] getDeclaredAnnotations()
public Class getDeclaringClass()
public Class[] getParameterTypes()
public Class getReturnType()
public TypeVariable[] getTypeParameters()
public boolean isSynthetic()
public String toGenericString()
public Object getDefaultValue()
public Class[] getExceptionTypes()
public Type[] getGenericExceptionTypes()
public Type[] getGenericParameterTypes()
public Type getGenericReturnType()
public Annotation[][] getParameterAnnotations()
public boolean isBridge()
public boolean isVarArgs()
public Annotation[] getAnnotations()
public boolean isAnnotationPresent(Class)
public boolean isAccessible()
public static void setAccessible(AccessibleObject[],boolean) throws SecurityException
public void setAccessible(boolean) throws SecurityException
public final native Class getClass()
public final void wait() throws InterruptedException
public final void wait(long,int) throws InterruptedException
public final native void wait(long) throws InterruptedException
public final native void notify()
public final native void notifyAll()


java.lang.reflect.Method Constructors:


java.lang.reflect.Method Fields:
public static final int PUBLIC
public static final int DECLARED
    反射机制又为动态代理创造了条件,可以在运行时,根据反射机制获得的类型信息,调用被代理对象的属性方法。用到Interface InvocationHandler和类Proxy。

运行时的类型信息使得你可以在程序运行时发现和使用类型信息
运行时识别对象和类的信息,主要有两种方式:
传统的RTTI,它假定我们在编译时已经知道了所有的类型
反射机制,允许我们在运行时发现和使用类的信息
在java中所有类型转换都是在运行时进行正确性检查的,这也就是在RTTI的含义:在运行时,识别一个对象的类型
Class对象相关知识:
java使用Class对象来执行其RTTI
类是程序的一部分,每个类都有一个Class对象,也就是说,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当的说,是被保存在一个同名的.class文件中),为了生成这个对象,运行这个程序的Java虚拟机将使用被称为“类加载器”的子系统
类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载的是所谓的可信类,包括java api类,它们通常是从本地盘加载的。如果需要特殊需求,可以挂接额外的类加载器。
所有的类都是在对其第一次使用时,动态加载到JVM中的,static初始化实在类加载时进行的,当程序创建第一个对类的静态成员的引用时,就会加载这个类,这也证明了构造器是类的静态方法
类加载器首先检查着各类的Class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找.class文件
Class类的static方法Class.forName():
取得Class对象的引用的一种方法
使用String作输入参数,返回的是一个Class对象的引用
如果找不到需要加载的类,会抛出异常ClassNotFoundException
传递给forName()的字符串中,必须使用全限定名
Object中有getClass()方法来获得Class引用,它将返回对象的实际类型的Class引用
Class中:
getInterfaces()方法返回的是Class对象的所包含的接口
getSuperclass()方法查询器直接基类
newInstance()来创建类的实例
Java还提供了另一种方法来生成对Class对象的引用,即使用类字面常量如:FancyToy.class
当使用.class来创建对象的引用时,不会自动地初始化该Class对象,为了使用类而做的准备工作实际包含三个步骤:
加载:这是由类加载器执行的,这里将查找字节码(通常在classpath所指定的路径中查找),并从这些字节码中创建一个Class对象
链接:在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用
初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块
Class引用总是执行某个Class对象,它可以制造类的实例,并包含可作用于这些实例的所有方法代码。它还包含该类的静态成员
如果需要使用泛化的Class引用时,可以使用Class<>
Java SE5中Class<?>匹配任意的类,Class<?>优于平凡的Class,即便他们是等价的
可以使用Class<? extends Number>来创建一个范围
Class引用添加泛型语法的原因仅仅是因为为了提供编译期类型检查
当使用泛型语法用于Class对象时,newInstance()将返回该对象的确切类型,而不仅仅是基本的Object
Class<? super FancyToy>允许声明超类引用是“某个类,它是FancyToy超类”
Java SE5中添加了用于Class引用的转型语法,cast()
cast()方法接收参数对象,并将其转型为Class引用的类型
使用方法   Class houseType;   House h = houseType.cast(b);  
已知的RTTI形式:
传统的类型转换,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCasetException异常
代表对象的类型Class对象,通过查询Class对象可以获取运行时所需的信息
使用instanceof,它将返回一个布尔值,告诉我们对象是不是某个特定类型的实例

对instanceof 有比较严格的限制:只可将其与命名类型进行比较,而不能与Class对象作比较
可以使用obj.isInstance(Obj)来判定obj是否是Obj类型的实例
如果不知道某个对象的确切类型,RTTi可以告诉你,但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,并列用这些信息做一些有用的事,换句话说,在编译时,编译器必须知道所有要通过RTTI来处理的类
Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field,Method,以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员
可以使用Constructor创建新的对象
用get()和set()方法读取和修改与Field对相关连的字段
用invoke方法调用与Method对相关联的方法
调用getFields()、getMethods()和getConstructors()来获得字段,方法,构造器的数组
RTTI和反射之间的真正区别是:对于RTTI来说,编译器在编译时打开和检查.class文件,而对于反射机制来说,.class 文件在编译时是不可获取的,所以在运行时打开和检查.class方法
在使用内置的null表示缺少对象时,在每次使用引用都必须测试其是否为null。有时引用空对象的思想将会很有用,通过这种方式,可以假设所有的对象都是有效的
空对象定义在类呃内部,实现Null接口
空对象使用了单例模式,可以使用equals甚至==来比较Person.Null
interface关键字的一种重要目标就是允许程序员隔离构建,进而降低耦合性
通过使用反射,可以到达并调用所以方法,甚至是private方法,如果知道方法名,可以在Method对象上杜鳌用setAccessible(true)
对于final域,修改时是安全的,运行时系统会在不抛异常的情况下接收任何修改尝试,但是实际上不会发生任何修改

运行时识别对象和类信息,主要有两种方式:1.RTTI 2.反射机制
14.1为什么需要RTTI
RTTI(Run-Time Type Information,通过运行时类型信息)程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。
RTTI的表现形式有:
1.进行类型转换,这个主要是进行向下类型转换时使用,可以概括为:你是什么类型就可以从基类型转化什么类型 2 通过查询class对象可以获取运行时的所需的信息
Java中每个对象都有相应的Class类对象,因此,我们随时能通过Class对象知道某个对象“真正”所属的类。无论我们对引用进行怎样的类型转换,对象本身所对应的Class对象都是同一个。当我们通过某个引用调用方法时,Java总能找到正确的Class类中所定义的方法,并执行该Class类中的代码。由于Class对象的存在,Java不会因为类型的向上转换而迷失。这就是多态的原理。
14.2Class对象
forName()是获得Class引用的一种方法。它用一个目标类的文件名作为一个String参数,返回的事一个Class对象的引用。如果类未被加载,则加载。可以通过Class.forName('类全名').newInstance()来创建实例。
14.3类型转换之前先做检查
14.5instanceOf和class的等价性
c instanceof class class.isInstance(c)
instanceof和isInstance()生成的结果一样,指的是你是这个类吗或者你是这个类的继承类吗
用==和equals比较的是实际的class 对象。
14.6反射,运行时的类信息
RMI(远程方法调用)允许一个程序将对象分配到多台计算机上。
Class类和java.lang.reflect类库一起对反射的概念进行支持。
RTTI和反射机制的真正区别:RTTI,编译器在编译时打开和检查.class文件;对于反射机制,在编译期间.class文件是不可获取的,所以实在运行时打开和检查.class文件。
14.7动态代理
代理是基本的模式。
想要将额外的操作从实际对象中分离到不同的地方,特别是当你希望容易修改时。
java的动态代理比代理的思想更进一步,它可以动态的创建代理并动态的处理对所代理方法的调用。
通过静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要得到一个类加载器(通常从已加载类获取其加载器,然后传递给它),一个你希望该代理实现的接口类表,以及一个InvocationHandler实现。
动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造器传递一个实际对象的引用,从而使得调用处理器执行其中介任务时,可以将请求转发。
14.8空对象
通常空对象都是单例的。
14.9接口与类信息

.使用RTTI解决多态中的问题--知道某个泛化引用的确切类型 2.Java中的类加载是动态加载的,“当程序创建第一个对类的静态成员的引用时,就会加载这个类”,“使用new操作符创建类的新对象也会被当做对类的静态成员的引用”。类加载是就会执行static初始化,即为static变量赋值和执行static程序块。另,JDBC中用到的Class.forName('XXXX')就是为了加载类,使用.class不会引发初始化。
3.static final的编译期常量无需类初始化就可以读取,但如果不是常量,则需要类先初始化。
4.使用Class类的引用可以获取某个类的信息,其中需要注意的:
a)newInstance()方法实现了“虚拟构造器”,但类必须要有无参构造器(可以不是默认的)。
b)泛化Class引用中,Class<?>优于平凡的Class;Class<? extends XXXX>或Class<? super XXXX>可以强制类型检查;使用.getSuperclass()方法获取的超类后使用.newInstance()的返回值只是Object类型。
c)转型前的检查,可以使用关键字instanceof,如:if(x instanceof Dog) ...,Class的isInsance()方法有同样效果,但后者的Class类型可以使用变量,而前者只能写死。
d)类型检查时,使用==或.equals()方法只能判断类型是否相同,但使用instanceof或isInstance()则包含了继承关系。
5.动态代理,使用静态方法Proxy.newProxyInstance()可以创建动态代理的实例,三个参数分别为:类加载器、期望代理实现的接口列表、InvocationHandler接口的一个实现。在第三个参数中,可以实现方法过滤(使用代理的好处之一吧,感觉像是代理在操控情报。。。腹黑一下),也可以实现事务。
6.反射可以违反访问权限进行操作。


1、类型时类型识别(run-timetype identification,RTTI):当之有一个指向对象的引用时,RTTI可以让你找出这个对象的确切类型。
2、Java运行时识别对象和类的信息,主要有俩种方式:
1).一种是“传统“RTTI,它假定我们在运行时已经知道了所有的类型。
2).另一种是“放射“机制,它允许我们在运行时获得类的信息。
3、Class对象:每个类都有一个Class对象,保存在一个同名的.class文件中,它包含了与类相关的信息。
4、在运行时,当我们想生成这个类的对象时,运行这个程序的Java虚拟机(JVM)首先检查这个类的Class对象是否已经加载,就会根据类名查找.class文件,并将其载入。所以Java并非一开始执行就完全加载的,这一点与许多传统语言都不同。        
5、Class.forName(“类名”)是获得Class引用的一种方法,该方法返回一个Class对象的引用。例如:Class.forName(“MyClass”);
在调用该方法时,若MyClass类被还没加载,则加载MyClass类。
6、类字面常量:另一种获得Class引用的方法,MyClass.class(类名.class)。
此方法,更安全,更高效。
7、类字面常量不仅应用于普通的类,也可以应用于接口、数组以及基本数据类型。
8、RTTI的形式包括:
1).传统的类型转化。如Base类是Subclass类基类。
Base base = new Subclass();
Subclass sub= (Subclass)base;//(Subclass),由RTTI确定类型转化正确性。在C++中,经典的类型转换“(Subclass)”并不使用RTTI。
2).代表对象的类型的Class对象。同过查询Class对象可以获取运行时所需的信息。
3).RTTI的第三种形式,使用关键字“instanceof”。如:
boolean yesOrNot = (base instanceof Subclass);
若base是Subclass类的实例,yesOrNot就为true。
9、instanceof与Class的等价性:
class Base{}
class Subclass extends Base{};
public class Test{
   static void test(Object x){
      System.out.println("Testing x of type: " + x.getClass());
      System.out.println("x instanceof Base: " + (x instanceof Base));
      System.out.println("x instanceof Subclass: " + (x instanceof Subclass));
      System.out.println("Base.isInstance(x): " + (Base.class.isInstance(x)));
      System.out.println("Subclass.isInstance(x):" + (Subclass.class.isInstance(x)));
      System.out.println("x.getClass() == Base.class: " + (x.getClass() == Base.class));
      System.out.println("x.getClass() == Subclass.class: " + (x.getClass() == Subclass.class));
      System.out.println("x.getClass().equals(Base.class): " + (x.getClass().equals(Base.class)));
      System.out.println("x.getClass().equals(Subclass.class): " + (x.getClass().equals(Subclass.class)));
   }
   public static void main(String[] args){
      test(new Base());
   System.out.println("---------------------------------------------------");
      test(new Subclass());
   }
}
运行结果:

1).instanceof 与 isInstance生成的结果完全一样,equal与==也一样。
2). Instanceof与isInstance保持了类型的概念,它指得是“你是这个类,或者是这个类的派生类吗?”
3).equal与==比较实际的Class对象,没有考虑继承。
10、Java是通过Class对象来实现RTTI机制的,即使我们只是做些诸如类型转换这类的事情。Class类提供的一些方法:

11、RTTI的限制:编译器必须已经知道所有用RTTI来处理的类型。但是,当你从网络连接中获得一串字节,并被告知这些字节代表一个类。可是编译器并不知道这个类的信息。这时你要使用这个类,那么就得使用反射机制。
12、RTTI与反射之间的区别:对RTTI来书,编译器在编译时打开和检查.class文件,而对于反射机制,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件的。
13、获取关于类和对象的反射信息在java.lang.reflect库中,它包含了Field、Method以及Constructor类。
1).Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
2). Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。
3). Constructor 提供关于类的单个构造方法的信息以及对它的访问权限。 
Base类如下:
class Base{
private int number = 1;
public int otherNum = 100;
public Base(){

}
public Base(int number){
this.number = number;
}
public void func(){
number++;
}
public void otherFunc(){
otherNum--;
}
}
Test:
public class Test{
public static void main(String[] args){
Constructor[] constructors = null;
Method[] methods = null;
Field[] fields = null;
try {
Class c = Class.forName("Base");
constructors = c.getConstructors();//公有构造方法。
methods = c.getMethods();//公有方法。
fields = c.getFields();//公有字段。
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
System.out.println("Base含有的公有构造方法:");
for(int i = 0; i < constructors.length; i++){
System.out.println(constructors[i].getName());
}
System.out.println("Base含有的公有方法(有些是从Oject继承的):");
for(int i = 0; i < methods.length; i++){
System.out.println(methods[i].getName());
}
System.out.println("Base含有的公有字段:");
for(int i = 0; i < fields.length; i++){
System.out.println(fields[i].getName());
}
}
}
运行结果:

运行时类型信息(原来的翻译没有括号这里面的内容,Runtime type information,简称RTTI,个人觉得这样注释比较好)可以让你在程序运行的时候发现和使用类型信息。后面直接出现RTTI让人疑惑。

1)为什么需要RTTI
之前的多态的例子中:
public class EveryTV {
    public static void tvshow(TV tv){
        tv.show();
    }

    public static void main(String[] args) {
        tvshow(new LeTV());
        tvshow(new MiTV());
        tvshow(new SanTV());
    }
}
将各种TV的子类转型为TV,这是RTTI的一种使用形式,运行时类型信息,所有类型的转换都是在运行时进行正确的检查。即在运行时,识别一个对象的类型。

2)Class对象
Java使用Class对象来执行其RTTI,类是程序的一部分,每个类都有一个Class对象,其实每编写和编译一个新类,就会产生一个Class对象,其实这个对象时被保存在同名的.class文件中的。生成这个类对象,其实是JVM(Java虚拟机)使用了“类加载器”的子系统。

补充一下百度的定义:
在Java中,每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。最后一句是重点。

所有的类都是在第一次使用时动态加载到JVM,当程序创建第一个对类的静态成员的引用时就会加载这个类,这样说的话,构造器是类的静态方法,虽然没有static修饰,因为new的新对象就是类的静态成员的引用。

动态加载使能行为(感觉好怪),英文原话:Dynamic loading enables behavior that is difficult or impossible toduplicate in a statically loaded language like C++.
动态加载允许的行为在C++这样的静态加载语言中是很难或者根本不可能复制的(这样翻译好一些)。

类加载器首先检查这个类的Class对象是否已经加载。未加载则根据类名查找.class文件。Class对象被载入内存,就被用来创建这个类的所有对象
package son;

class First{
    static{
        System.out.println("first load");
    }
}

class Second{
    static{
        System.out.println("second load");
    }
}
public class TestClass {
    public static void main(String[] args) {
        System.out.println("main");
        new First();
        System.out.println("first after");
        try {
            Class.forName("son.Second");
        } catch (ClassNotFoundException e) {
            System.out.println("not found");
        }
        System.out.println("second after");
        System.out.println("end");
    }
}
result:
main
first load
first after
second load
second after
end
这次特地加上包名,因为越发觉得奇怪,没有包名的时候class是找不到的。提前用了Second.class.getName(),类名为son.Second。果真一试可以了。

根据static方法里面的语句可以知道类的加载顺序,Class.forName("Second")
所有Class对象属于Class类。
static Class<?> forName(String className)
Returns the Class object associated with the class or interface with the given string name.
真阳就拿到提供名字的Class对象。由于包存在的缘故,没有写包名的时候找不到Second对象,所以是not found。

Class.forName()能获得对所需的Class对象的引用,而不需要我们去持有该类型对象。如果已经有了对象,可以通过getClass()获取Class引用。
书上例子有一个问题(main语句重复)。
package son;

public interface Fire {}
public interface Water {}

class Gun{
    Gun(){
        System.out.println("init");
    }
}

public class DeathGun extends Gun implements Water,Fire{
    static void printInfo(Class c){
        System.out.println("Class name: "+ c.getName()+
                                         "Interface? "+c.isInterface()+
                                         "\nsimplename "+c.getSimpleName()+
                                         "  canonicalname "+c.getCanonicalName());
    }
    public static void main(String[] args) {
        Class c = null;
        try {
            c = Class.forName("son.DeathGun");
        } catch (ClassNotFoundException e) {
            System.out.println("not found");
            System.exit(1);
        }
        printInfo(c);
        for(Class cc : c.getInterfaces()){
            printInfo(cc);
        }
       
        Class father = c.getSuperclass();
        Object o = null;
        try {
             o = father.newInstance();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        printInfo(o.getClass());
    }
}
forName正如前面所提到要有包名,整个称为canonicalname,规范类名,译者翻译为全限定名。getSimpleName()拿到的为类名,isIterface()是否为接口。getInterfaces返回Class对象。

先停一停,现在有人有点乱了,重理思路,Class,class,类,对象,会不会搞得乱七八糟了。class是关键字,定义一个类的,class就是类,对象则是一个类的实例,而Class是类中的一种,为什么要Class呢,因为他可以干一些厉害的工作——如反射。

(以下内容会有重复)前面提到的RTTI,其实是由叫做Class对象的特殊对象完成,因为Class是一个类,但又区别于整个的class,Class包含类的相关信息。class A,如果我们编写了并且编译了A,那么就会产生Class对象,存放在.class文件中。创建静态成员引用的时候会加载类,静态成员是类和多个对象拥有的属性或者方法,即可以用类名+静态成员名的方式调用,应为new对象的时候会加载类,这就说明了构造器也是静态方法。Class.forName会返回一个Class对象的引用,如果类未加载就进行加载。

继续,newInstance确实像网友所说的类似工厂模式,特地在构造器写了一个输出,newInstance会打印出来,father只是Class的引用,编译期不具备进一步的类型信息,new了之后的Object引用其实指的就是Gun对象。

3)类字面常量
呵呵,之前弄晕我了,你会发现class,getClass(),.class,Class这些看起来好像,看看之前重理的思路,会好理解。
这是生成对Class对象引用的另外一种方法:
A.class;
简单安全,编译器检查,可以应用于接口,数组和基本类型,基本类型的包装类还有一个标准字段TYPE,也是一个引用,指向对应的Class对象。
int.class 等价于 Integer.TYPE。
但是这个并不会初始化Class对象。所以不会像forName那样会打印类中的static方法。

使用类前的准备工作:
(1)加载,由类加载器,查找字节码,并从字节码中创建对象。
(2)链接,验证字节码,为静态域分配空间。
(3)初始化,具有超类的话对其初始化,执行静态初始化器和静态初始化块。

初始化被延迟了,延迟到了静态方法,自然包括之前说的构造器或者是非常数静态域进行首次引用才初始化。
package son;
class A{
    static final int show= 1;
    static{
        System.out.println("init a");
    }
}
class B{
    static int showb= 1;
    static{
        System.out.println("init b");
    }
}
class C{
    static int showc= 1;
    static{
        System.out.println("init c");
    }
}
public class ClassInitialization {
    public static void main(String[] args) {
        Class InitA = A.class;
        System.out.println("after a");
        System.out.println(A.show);
        Class InitB = B.class;
        System.out.println("after b");
        System.out.println(B.showb);
        try {
            Class InintC = Class.forName("son.C");
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
result:
after a
1
after b
init b
1
init c
之前没看到这里的时候,自己就先尝试Class InitA = A.class;没有打印static中的语句,就以为自己理解错了概念,原来真的没错,真的不会先初始化。

为了产生Class的引用,Class.forName()就立即初始化。但是.class的初始化是惰性的。static final是编译期常量,不需要A类进行初始化,而B类,没有final,读取域的时候,先进行链接,进行存储空间分配和初始化。所以B被初始化了,打印语句也打出来了。

运行时类型信息使得你可以在程序运行时发现和使用类型信息
RTTI  (Run -Time Type Information )通过类型时类型信息
         面向对象的基本目的是:让代码只操作对基类的引用,或者说体现在他的多态性质上,父类引用指向子类对象。所用的类型转换都在运行时进行正确性检查的,运行时,识别一个对象的类型。Class对象是RTTI的核心,Class的类的类,每个类都有一个class对象。每当编写并且编译一个新类,就会产生一个Class对象(被保存在同名的.class文件当中),为了生成这个类的对象,JVM将使用被称为“类加载器”的子系统。Class中拥有大量使用RTTI的其他方式,万物皆对象。
      注意:Class   与  class  的不同
      类加载器实际是一个可以包含一条类加载器链,只有一个原生类加载器,JVM的实现一部分,其加载的是可信类,包括java API类,通常从本地磁盘加载的。所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员引用时,就会加载这个类。这说明构造器是静态的。java程序是动态加载的,各个部分是在使用必需时才加载的。
     Class.forName("classname"),如果对象没有加载就加载对象(这将会触发类的静态初始化),forName()Class中的静态方法,可以获取对象的引用。参数是包含目标类的文本名的String输入参数。
      Class.newInstance()用来产生一个对象,虚拟构造器,
      newInstance使用的时候需要注意:
      a.类需要有默认的构造器才可以,不然则会出现java.lang.InstantiationException
      b.默认构造器必须能访问才行,不然会出现java.lang.IllegalAccessException
     Class.getInterfaces()  返回的是Class对象
     getClass(),属于根类Object的方法,可以获取已经拥有的类型对象的实际类型的Class引用。
类字面常量:
     生成Class对象的引用,简单安全,编译时就会受到检查
    boolean.class        Boolean.TYPE
    char.class              Character.TYPE
    byte.class              Byte.TYPE
    short.class             Short.TYPE
    int.class                 Integer.TYPE
    long.class              Long.TYPE
    float.class              Float.TYPE
    double.class          Double.TYPE
    void.class              Void.TYPE
当使用  .class  时,创建对Class的引用时,不会自动化初始化该Class对象
    1. 加载,类加载器执行,查找字节码(通常在classpath所指定的路径中查找,并非是必需的),并从这些字节码中创建一个Class对象。
    2. 链接,链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类创建的对其他类的所有引用。
    3.如果该类具有超类,则对其初始化,执行静态初始化器 和 静态初始化块。
泛化的Class引用
       Class< Number > genericNumberClass = int . class ;这句错了,
        因为 Integer Class 的对象并不是 Number  Class对象的子类 。
       通配符  ?表示 “ 任何事物 ”
       Class < ? >  intClass =int.class
       Class<?> 优于Class
       创建范围<? extends Number >  所有Number的子类型
       cast()引用转型
我们现在知道  ,一个是传统的类型转换,一个是代表对象的类型的Class对象,通过查询Class对象可以获取运行时所需的信息。还有一种是 关键字instanceof,返回boolean值,告诉我们对象是不是某个特定类型的实例。
       DEMO:
         if(x instanceof DogClass){
            ( (Dog) x ).bark(); 
         }


java 类型信息(RTTI) 中 Class对象的理解     
要理解RTTI 在java中的工作原理,首先必须知道类型信息在运行时是如何表示的,这项工作是由称为Class 对象的特殊对象完成的,它包含了与类有关的信息,事实上,Class对象就是用来创建类的所有的“常规”对象的的,JAVA使用Class对象来执行其RTTI,即使你正在执行的是类似转型的操作,Class类还拥有大量的使用RTTI的其他方式。
类是程序的一部分,每个类都有一个Class对象,换言之,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当的说,是被保存在一个同名的。class文件中)

为了生成这个类的对象,运行这个程序的java虚拟机(JVM)将使用被称为类加载器的子系统。

类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分,原声类加载器加载的是所谓的可信类,它包括JAVA API 类,他们通

常是从本地盘中加载的,这条不需要额外的类加载器d,除非有特殊需求。

所有的类都是在对其第一次使用的时候,动态的加载到JVM当中的,当程序创建第一个对类的静态成员引用的时候,就会加载这个类,这个证明构造器也是类的静态方法,即

使在构造器之前并没有使用static关键字,因此,使用New操作符创建类的新对象也会被当做对类的静态成员的引用。

因此,java程序在它开始运行的之前并非完全加载,其各个部分是在必须时才加载的,这个与许多的传统语言不

一样,动态加载使能的行为,在诸多的c++这样的静态加载语言中是很难或者根本不可能复制的

类加载器首先检查这个类的Class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找.class

文件。在这个类的字节码被加载时,他们会接受验证,以确保其没有被破坏。并且不包含不良代码

取得类引用的几种方式:

1)Class.forName(string s )  这个方法是Class类(所有的对象都属于这个类)的一个static成员,该方法返回的是一个Class对象的引用。使用这个方法会产生一个副作用:如果类没有被加载就加载它,在加载的过程中,类的static子句会被执行。(如果没有找到类会抛出一个异常ClassNotFoundException)
2)getClass 如果你已经拥有了一个类型的对象,那就可以通过调用.getClass()方法来获取Class引用了,这个方法属于根类Object的一部分,它将返回表示该对象的实际类型的Class引用,Class包含很多有用的方法
3)类字面常量
 类名.class 这样不简单,而且更安全,因为它在编译时就会受到检查,所以更高效不需要try.不仅仅可以应用于普通类,而且可以应用于接口数组,已经基本数据类型,另外,对于基本数据类型的包装类,还有一个标准字段TYPE。TYPE字段是一个引用,指向对应的基本数据类型的class

Class引用包含很多有用的方法,以下是其中的一部分。
.getName()    isInterface()   getSimpleName  getCannonicalName()(获得完整名字)
getSupperClass() (获得父类的class引用)  getInterfaces()获得接口引用)

class 的newInstance()方法是实现虚拟构造器的一种途径,虚拟构造器允许你声明,我不知道你的确切类型,但是无论如何要正确的创建你自己,

为了使用类Class而做的工作包含三个步骤:
1。加载。这是由类加载器执行的,该步骤将查找字节码并从这些字节码中生成一个Class对象。
2。 连接。在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用。
3。初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。


(1)RTTI
RTTI是Run-Time Type Information的缩写,指运行时类型信息可以在程序运行时发现和使用。
要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作是由称为Class对象的特殊对象完成的,它包含了与类有关的信息。类是程序的一部分,每个类都有一个Class对象。每当编写并且编译了一个新类,就会产生一个Class对象。为了生成这个类的对象,运行这个程序的JAVA虚拟机(JVM)将使用被称为类加载器的子系统。
类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它 是JVM实现的一部分。原生类加载器加载的是所谓的可信类,包括Java API类。
Java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载的。这一点与许多传统语言都不同。
import java.util.*;
class Initable{
static final int staticFinal=47;
static final int staticFinal2=ClassInitialization.rand.nextInt(1000);
static {
System.out.println("Initializing Initable1");
}
}
class Initable2{
static int staticNonfinal=147;
static {
System.out.println("Initializing Initable2");
}
}
class Initable3{
static int staticNonfinal=74;
static{
System.out.println("Initialzing Initable3");
}
}
public class ClassInitialization {
public static Random rand =new Random(47);
public static void main(String[] args)throws Exception{
Class initable =Initable.class;
System.out.println("After creation Initable ref");
//Does not trigger initialization;
System.out.println(Initable.staticFinal);
//Does trigger initialization;
System.out.println(Initable.staticFinal2);
//Does trigger initialization;
System.out.println(Initable2.staticNonfinal);
Class initable3=Class.forName("typeinfo.Initable3");
//Class initable3=Class.forName("Initable");//书中该处forName的参数仅有类名,但直接运行报异常,实际该处参数应在类名前添加包名。
System.out.println("After creation Initable3 ref");
System.out.println(Initable3.staticNonfinal);
}
}
运行结果:
After creation Initable ref

47

Initializing Initable1

258

Initializing Initable2

147

Initialzing Initable3

After creation Initable3 ref

74
初始化有效地实现了尽可能的惰性。从对initable引用的创建中可以看到,仅使用.class语法来获得对类的引用不会引发初始化。但是,为了产生Class引用,Class.forName()立即就进行了初始化。

Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。可以将类型变得更具体一些,这是通过允许对Class引用所指向的Class对象的类型进行限定而实现的。
public class GenericClassReferences {
public static void main(String[] args){
Class intClass=int.class;
Class<Integer>genericIntClass=int.class;
genericIntClass=Integer.class;//Same thing
intClass=double.class;
//genericIntClass=double.class;//Illegal
//Class<Number> genericNumberClass=int.class;//Illegal
Class<?> intClass2=int.class;
intClass2 =double.class;
Class<? extends Number> bounded=int.class;
bounded=double.class;
bounded=Number.class;
// or anything else derived from Number.
}
}
普通的类引用不会产生警告信息,你可以看到,尽管泛型类引用只能赋值为指向其声明的类型,但是普通的类引用可以被重新赋值为指向任何其他的class对象。通过使用泛型语法,可以让编译器强制执行额外的类型检查。

当将泛型语法用于Class对象会发生一件很有趣的事情:newInstance()将返回该对象的确切类型,而不仅仅只是在ToyTest.java中看到的基本的Object。
interface HasBatteries{}
interface Waterproof{}
interface Shoots{}
class Toy{
//Comment out the following default constructor
//to see NoSuchMethodErroe from(*1*)
Toy(){}
Toy(int i){}
}
class FancyToy extends Toy implements HasBatteries,Waterproof,Shoots{
FancyToy(){super(1);}
}
public class GenericToyTest {
public static void main(String[] args)throws Exception{
Class<FancyToy> ftClass=FancyToy.class;
//Produces exact type;
FancyToy fancyToy=ftClass.newInstance();
Class<? super FancyToy> up=ftClass.getSuperclass();
//this Won't compile;
// Class<toy> up2=ftClass.getSuperclass();
// Only produces Object;
Object obj=up.newInstance();
}
}
如果手头的是超类,那编译器只允许声明超类引用是某个类,它是FancyToy超类。正是由于这种含糊性,up.newInstance()的返回值不是精确类型,而只是Object。
(2)类型转换
1)传统的类型转换,利用()进行强制类型转换。
2)代表对象类型的Class对象。
3)关键字instanceof。
实现了书中P323-329的代码,因为代码过长,不再粘贴。
(3)又一个工厂方法实例
import java.util.*;
interface Factory<T>{T create();}
class Part{
public String toString(){
return getClass().getSimpleName();
}
static List<Factory<? extends Part>> partFactories=new ArrayList<Factory<? extends Part>>();
static{
//Collections.addAll() gives an "unchecked generic"
//array creation... for varargs parameter" warning;
partFactories.add(new FuelFilter.Factory());
partFactories.add(new AirFilter.Factory());
partFactories.add(new CabinAirFilter.Factory());
partFactories.add(new OilFilter.Factory());
partFactories.add(new FanBelt.Factory());
partFactories.add(new PowerSteeringBelt.Factory());
partFactories.add(new GeneratorBelt.Factory());
}
private static Random rand=new Random(47);
public static Part createRandom(){
int n= rand.nextInt(partFactories.size());
return partFactories.get(n).create();
}
}
class Filter extends Part{}
class FuelFilter extends Filter{
// Create a Class Factory for each specific type;
public static class Factory implements typeinfo.Factory<FuelFilter>{
public FuelFilter create() {return new FuelFilter();}
}
}
class AirFilter extends Filter{
public static class Factory implements typeinfo.Factory<AirFilter>{
public AirFilter create(){return new AirFilter();}
}
}
class GeneratorBelt extends Belt{
public static class Factory implements typeinfo.Factory<GeneratorBelt>{
public GeneratorBelt create(){return new GeneratorBelt();}
}
}
class PowerSteeringBelt extends Belt{
public static class Factory implements typeinfo.Factory<PowerSteeringBelt>{
public PowerSteeringBelt create(){return new PowerSteeringBelt();}
}
}
class FanBelt extends Belt{
public static class Factory implements typeinfo.Factory<FanBelt>{
public FanBelt create(){return new FanBelt();}
}
}
class Belt extends Part{};
class OilFilter extends Filter{
public static class Factory implements typeinfo.Factory<OilFilter>{
public OilFilter create(){return new OilFilter();}
}
}
class CabinAirFilter extends Filter{
public static class Factory implements typeinfo.Factory<CabinAirFilter>{
public CabinAirFilter create(){return new CabinAirFilter();}
}
}
public class RegisteredFactories {
public static void main(String[] args){
for(int i=0;i<10;i++)
System.out.println(Part.createRandom());
}
}
(4)反射
反射是JAVA中用来解决某一些特殊问题的方法。可以下个阶段再做详细了解。
代理是基本设计模式之一,它是你为了提供额外的或者不同的操作,而插入的用来代替实际对象的对象。这些操作通常涉及与实际对象的通信,因此代理通常弃当着中间人的角色。
import java.lang.reflect.*;
interface Interface{
void doSomething();
void somethingElse(String arg);
}
class RealObject implements Interface{
public void doSomething(){System.out.println("doSomething");}
public void somethingElse(String arg){
System.out.println("SomegthingElse "+arg);
}
}
class DynamicProxyHandler implements InvocationHandler{
private Object proxied;
public DynamicProxyHandler(Object proxied){
this.proxied=proxied;
}
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
System.out.println("**** proxy: "+ proxy.getClass()+",method: "+ method +",args : "+ args);
if(args!=null)
for(Object arg:args) System.out.println("   " +arg);
return method.invoke(proxied, args);
}
}
public class SimpleDynamicProxy {
public static void consumer(Interface iface){
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args){
RealObject real=new RealObject();
consumer(real);
//Insert a proxy and call again;
Interface proxy=(Interface)Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[]{Interface.class},
new DynamicProxyHandler(real));
consumer(proxy);
}
}

一、概念
编译时已知的到所有的类型:就是在写代码阶段就确定是这个类型了,当运行程序的时候,类型是不可改变的

举例:List<String> str = new ArrayList();  //运行时就无法改变其类型

运行时使用其他类型:就是运行程序的时候,可以根据代码改变其类型

Class c = Class.fromName(String className);//传入不同的className获取不同的对象

二、RTTI
定义:

 RTTI(Run-Time Type Identification,通过运行时类型识别)的含义

就是在运行时识别一个对象的类型,其对应的类是Class对象,每个java里面的类都对应一个Class对象(在编写并且编译后),这个对象被保存在这个类的同名class文件里。

2、支持向上转型和向下转型:如“(Apple)Fruit”,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个 ClassCastException异常。

3、判定是否为同一类别:通过关键字instanceof。

所以说:在编译时必须知道一个非常重要的东西:类名(甚至是全类名)

举例:


//假设Shape类,含有子类Circle类、Rectange类
List<Shape> list = new ArrayList();
list.add(new Circle());
list.add(new Rectange());

//根据RTTI会先识别Circle类,然后寻找对应的Class,进行编译
//因为容器都是将类型当做Object类持有,当取出对象的时候RTTI会将Object转换为泛型的类,也就是Shape,而不是转换为更彻底的Cirlcle类

类加载器在类被第一次static调用(比如一个静态方法,一个静态代码块或者new关键字调用构造器,注意构造器contructors其实都是静态的)时会把那个对应的Class对象加载到内存中。(运行时创建对象,而不是在编译时创建对象,这是和其他语言不一样的地方——比如说PHP就是先将类创建完成之后再运行的,所以类是先创建还是后创建的不影响逻辑顺序)

三、Class对象
JAVA可以使用Class对象执行RTTI,Class拥有大量使用RTTI的其他方法:

通过Class对象来获取对象的类型。如

Class c = Class.forName(“Apple”);
Object o = c.newInstance();
3.通过关键字instanceof或Class.isInstance()方法来确定对象是否属于某个特定类型的实例

1、java编译顺序详解

①、当编译了一个新类的时候,就会创建.class文件,为了生成这个类的对象,就运行程序的“JVM”(Java虚拟机)称为类加载器的子系统

②、程序中那么如何生成这个类的对象:

所有的类都是第一次被使用的时候,就会动态加载到JVM上,类加载器在类被第一次static调用的时候就会被加载(构造方法也是一个静态方法 所以 new A()就是调用static)

所以说,java程序是在需要的时候,才会加载,而不是在运行前完全加载。

③、类加载器的操作:类加载器首先会检查这个类是否被加载,如果未被加载就根据类名查找.class文件,然后经Class对象载入内存,之后就创建这个类中的所有对象。

注:


public class A{
  static {
      //static 初始化   是在类加载时进行的
  }
}
2、Class类的使用

主要类:



public class UseClass {

    static void printfIn(Class cc){
        String name = cc.getName();//获取Class类加包的名字
        Boolean isInterface = cc.isInterface();
        String simplyName = cc.getSimpleName();//获取类的名字
    }
   
    public static void main(String [] args){
        try {
            Class newClass = Class.forName("ClassLoaderTest");//获取相应对象
            //获取该类继承的接口
            for(Class face : newClass.getInterfaces()){
                System.out.println(face.getName());
            }
            Class classSuper = newClass.getSuperclass();//获取该类父类
            Object object = newClass.newInstance();
            //将Class对象创建为其所对应的对象(这里为ClassLoaderTest),不过返回的是Object类型,需要向下转型为ClassLoaderTest类型
            //原理:调用ClassLoader的默认的构造器,创建ClassLoaderTest对象。
            printfIn(newClass);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }//加载类
        catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
       
    }
}

创建类:


public class ClassLoaderTest {
    static {
        //当被加载的时候调用
        System.out.println("My name is ClassLoader");
    }
}   
 根据得到的结果,当调用Class.forName("ClassLoaderTest");,会调用ClassLoaderTest的static{}域。

四、类字面常量
第三种生成Class对象的方法,举例:Class c = ClassLoaderTest.class;

与其他生成方法的区别:不会自动初始化该class对象。

延生(使用类而做的准备工作):

①、加载。类加载器创建Class对象      ②、链接。分配存储空间    ③、初始化:初始化其父类,静态初始化块。

所以说:不会自动初始化意思就是,不会执行上诉的初始化工作,当只有第一次调用该类的静态域的时候才会被调用。

 执行类:



public static void main(String [] args){
       
        System.out.println(ClassLoaderTest.DATA+"");//没有进行初始化
        System.out.println(ClassLoaderTest.TEST+"");//强制进行了初始化
        //说明了加上了final表示,直接调用不会进行初始化。但也有例外比如说
        System.out.println(ClassLoaderTest.DATA_ONE+"");//强制初始化,因为值不是编译期常量(是运行时的)
       
        try {
            Class c = ClassLoaderTest.class;//没有进行初始化
            Class c1 = Class.forName("ClassLoaderTest");//强制进行初始化
            new ClassLoaderTest();//调用默认构造器,强制进行初始化
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

辅助类:



public class ClassLoaderTest {
    static final int DATA= 333;
    static final int DATA_ONE = new Random().nextInt(3);
    static int TEST = 333;
    static {
        //当被加载的时候调用
        System.out.println("My name is ClassLoader");
    }
}   

五、泛型的Class引用
使用:Class<?> class = int.class;

作用:在编译器进行类型检查。

注:?代表通配符,表示使用一个非具体的类型,Class<?>等价于Class,那么他的作用在哪里呢,需要加上extends才能体现的出来



public class ClassReferences {
    public static void main(String[]args){
        //Integer继承自Number
        Class<? extends Number> bound = int.class;//这样就能够声明放入的是Number的子类
        //但是错误的是:该泛型不支持向上转型,不像List那样。
        Class<Number> intClass = int.class;//这种方式是会报错的
    }
}

第二个作用:class.newInstance()返回的是具体的类型,而不是Object类



    public static void main(String[]args){
        Class<ClassLoader> loader = ClassLoader.class;//类字面常量才能这么用
        try {
            ClassLoader cl = loader.newInstance();//返回的是具体类型,而不是Object
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
       
    }

六、RTTI的第三种用法:instanceof
使用 :if (xx instanceof Dog){

       ((Dog)x).bark();

        } 

实例,动态创建不同类型的宠物

假设:所有父类为Pet,所有动物为其子类

步骤:①、获取所有子类的Class对象放在List数组中,注意最好使用泛型,判定该动物是否继承Pet类   ②、通过Random随机获取List中的Class对象,然后通过newInstance()方法生成具体动物的对象。  ③、再放入新的List数组中。

实例:

模型类:



ublic abstract class PetCreator {
    //这里用到的模型模式,利用重写抽象方法获取种类的类型
    public abstract List<Class<? extends Pet >> type () throws ClassNotFoundException;
   
    //随机取出List中的Class对象,初始化成具体类
    public Pet randomPet() throws InstantiationException, IllegalAccessException, ClassNotFoundException{
        Random random = new Random();
        int index = random.nextInt(type().size());
        return type().get(index).newInstance();
    }
   
    //输入具体生成多少个动物,然后将random生成的对象,装入数组中
    public Pet[] createPets(int size){
        Pet[] pets = new Pet[size];
        try {
            for (int i=0; i<size; ++i){
                pets[i] = randomPet();
            }
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return pets;
    }
    //将数组中的东西,转换成List
    public ArrayList<Pet> getPetsList(int size){
        ArrayList<Pet> arrList = new ArrayList();
        Collections.addAll(arrList, createPets(size));
        return arrList;
    }
}

实现类:



public class ForNameCreator extends PetCreator{
    private String[] forName = {"1","2","3","4"};
    private  List<Class<? extends Pet>> types;
    @Override
    public List<Class<? extends Pet>> type() throws ClassNotFoundException {
        // TODO Auto-generated method stub
        types = new ArrayList();
        for (int i=0; i<forName.length; ++i){
            types.add((Class<? extends Pet>)Class.forName(forName[i]));
        }
        return types;
    }
   
}

创建计数器,计算生成的Pet的具体种类多少

步骤:①、获取装有Pet对象的容器 ②、获取容器中的数据,并使用instanceof判断其属于的具体类型  ③、如果匹配,创建Map,将该类型的名字作为键,每当有一个匹配,就让该键所对应的值加一。



public class PetCount {
    private Map<String,Integer> petCounts;
    private List<Pet> petsList;
   
    public PetCount(List<Pet> list){
        petCounts = new HashMap();
        petsList = list;
    }
    //将动物分类
    private void classify(){
        for(int i=0; i<petsList.size();++i){
            Pet pet = petsList.get(i);
            if (pet instanceof Dog){
                count("Dog");
            }
            else if (pet instanceof Cat){
                count("Cat");
            }
            else if (pet instanceof Bird){
                count("Bird");
            }
            else if (pet instanceof Chicken){
                count("Chicken");
            }
        }
    }
    //计数
    private void count(String className){
        //应该使用Integer,不应该使用int类型
        Integer count = petCounts.get(className);
        if(count != null){
            petCounts.replace(className, count+1);
        }
        else{
            petCounts.put(className,1);
        }
    }
}

动态的instanceof重写计数器:

步骤:①、创建PetCount1类,在初始化前获取全部子类的对象,存入Map<Pet,Integer>中(与静态的instanceof相比,将所有的子类不用数组形式展现,以放入map的形式出现) ②、获取Pet对象的容器    ③、获取map中的对象,调用isInstance()与pet容器中的对象进行比较,一致则加一。



public class PetCount1 {
   
    private Map<Pet,Integer> allType;//获取所有Pet的子类并初始化。
   
    public void count(Pet pet){
        for (Map.Entry<Pet, Integer> map:allType.entrySet()){
            Pet newPet = map.getKey();//获取键
            Class c = newPet.getClass();//获取class
            //动态使用instanceof
            if (c.isInstance(pet)){
                map.setValue(map.getValue()+1);//如果相等就坐加法
            }
        }
    }
}

小知识:Class.isAssignableFrom()是用来判断一个类Class1和另一个类Class2是否相同或是另一个类的子类或接口。 

获取该对象的继承结构(核心 Class.getSuperClass(),递归)



public class PetCount3 {
   
    public static void main(String[]args){
        Dog dog = new Dog();
        Class c = dog.getClass();
        getConstruct(c);
    }
   
    public static void getConstruct(Class c){
        Class superClass = c.getSuperclass();//获取该类的父类
        if (superClass != null){
            System.out.println(superClass.toString());
            getConstruct(superClass);//进行递归
            superClass = null;
        }
    }
}

七、注册工厂
在继承结构中的问题是:当我向Pet结构中添加了一个新的类的时候,就需要需改ForNameCreator.java中的项,那么这样就很可能出问题。

所以采用工厂方法,使用一个接口,继承这个接口就表示成为了Pet的一个结构,然后由工厂创建该类。

八、instanceof与Class的关系
1、instanceof与isInstanceof()生成的结果完全一样

2、equal与==结果也一样。

3、但是意义不同:instanceof表示:你是这个类型的吗,或者你是这个类的派生类吗

equal只是表示,你是这个了性吗,不考虑继承。

一、反射与RTTI
RTTI:这个类型必须在编译的时候已知或者存在,如果不知道对象的确切类型,RTTI可以告诉你。

反射(个人认为就是能够利用Class获取或者调用.class这个文件中的数据):当我们从程序外(网络,磁盘中)在程序运行的时候获取这些数据,发现这些数据是个类,并且不知道该类的类型,那么我们怎么使用这些数据呢。

所以说根本区别是:RTTI在编译的时候会检查和打开.class文件,但是在反射中.class文件是不可获取的,所以在运行时打开和检查.class文件

二、使用
利用反射获取类的方法和构造方法



ublic class UseReflection {
    public static void main(String[]args){
        try {
            Class c = Class.forName("String");
            Method[]methods = c.getMethods();//获取c这个对象的所有方法
            Constructor[]constructors = c.getConstructors();//获取c这个对象的所有构造方法
            for (Method method:methods){
                method.toString();
                constructors.toString();
            }
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
       
    }
}

三、动态代理
①、代理

作用:将额外的操作从“实际”对象中分离到不同的地方。(特别是当有时候准备使用这个功能,有时候又不需要使用这个功能的时候,这就很容易修改)。

(感觉就是扩展了这个方法,但是又不是直接在方法内增加,并且还能够选择用或者不用)

步骤:1、将需要的操作封装为一个接口 2、主类继承这个接口,然后实现 3、创建代理继承接口,然后获取主类,在实现方法的时候,调用新方法然后再调用主类的同样的方法。


public interface Proxy {
    void doSomething();
    void somethingElse(String make);
}


//主类继承了代理类,并重写了该方法
public class Child implements Proxy{

    @Override
    public void doSomething() {
        // TODO Auto-generated method stub
        System.out.println("Play");
    }

    @Override
    public void somethingElse(String make) {
        // TODO Auto-generated method stub
        System.out.println("eat fruit");
    }

}



//代理类,继承了代理接口,并获取主类对象向上转型为代理,然后在相应方法内,重新调用
public class SimpeProxy implements Proxy{
    private Proxy mProxy;
   
    public SimpeProxy(Proxy proxy){
        mProxy = proxy;
    }
   
    @Override
    public void doSomething() {
        // TODO Auto-generated method stub
        System.out.println("Footboll");
        //调用主类的该方法
        mProxy.doSomething();
    }

    @Override
    public void somethingElse(String make) {
        // TODO Auto-generated method stub
        System.out.println("Footboll"+make);
        mProxy.somethingElse(make);
    }
   
}



public class AchieveProxy {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        complete(new Child());//未使用代理
        complete(new SimpeProxy(new Child()));//使用代理
    }

    public static void complete(Proxy proxy){
        proxy.doSomething();
        proxy.somethingElse("make cake");
    }
}

②、动态代理

使用:1、用到的类:InvocationHandler接口,和Proxy类

步骤:1、同样将需要的操作封装成一个接口 2、创建相关类并继承接口   3、创建代理类,该类继承InvocationHandler接口,实现invoke()方法,该方法实现代理的逻辑。

4、在主线程中调用代理

示例:

代理接口和主类不变就不写了。



//继承接口
public class DynamicProxy implements InvocationHandler {
    private Object mChild;
    public DanamicProxy(Object child){
        mChild = child;
    }
//实现接口的方法。
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // TODO Auto-generated method stub
       
        System.out.println("我是代理,大家好"+"proxy   "+proxy.getClass().getSimpleName());
               //绑定该方法,参数1、是被被代理对象  参数2、是传送到该方法的值
        return method.invoke(mChild, args);
    }
}
/*
*方法的使用原理:参数1、当前的代理对象   参数2、正在被使用的方法  参数3、对于该方法传入的参数。    
*
*/



//创建被代理对象       
Child child = new Child();
//创建代理对象
DanamicProxy dana = new DanamicProxy(child);
//连接代理与被代理对象    参数1、获取类加载器   参数2、获取代理的接口  参数3、获取代理对象
Proxy proxy = (Proxy)java.lang.reflect.Proxy.newProxyInstance(child.getClass().getClassLoader(), new Class[]{Proxy.class}, dana);
//调用代理对象的方法
complete(proxy);

public static void complete(Proxy proxy){
    proxy.doSomething();
    proxy.somethingElse("make cake");
}

原理:Proxy类通过反射获取接口的方法,在继承InvcationHandler的类中,当代理调用接口的方法的时候,就会触发invoke()方法,并将用到的方法和参数传入,然后再用用到的方法调用invoke()调用被代理类的相应方法。

动态代理的优点:①、只需要在invoke()中实现逻辑就可以了,不用因为有多个方法需要代理,就需要重写一堆方法。  ②、代理可以被多个类使用,而不是继承特定接口的类。

最近用到的Retrofit应该就是用到了动态代理。

 

RTTI的新发现:


Child child = new Child();
Proxy proxy = (Proxy)child;
System.out.println(proxy.getClass().getName());
//答案居然是Child。 说明反射是根据指针来的,向上转型无法改变其name
 
java是如何让我们在运行时候识别对象和类的信息的?
1)传统的RTTI:编译时候就已经知道了所有的类型
2)反射机制:允许我们在寻星的时候发现和使用类的信息

一、为什么需要RTTI

RTTI名字的含义:在运行时,识别一个对象的类型。

二、Class对象

所有的类都是在对其第一次使用的时候,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,即使在构造器之前并没有使用static关键字。





forName()的调用是为了它产生的“副作用”:如果类Gum还没有被加载就加载它。在加载的过程中,Gum的static子句被执行。

无论何时,只要你想在运行时候使用类型信息,就必须首先获得对恰当的Class对象的引用。Class.forName()就是实现此功能的便捷途径,因为你不需要为了获得Class引用而持有该类型的对象。




1)在传递给forName()的字符串中,你必须使用全限定名(包含包名)
2)up仅仅只是一个Class引用,在编译期不具备任何进一步的类型信息。当你创建新实例的时候,会得到Object引用,但是这个引用指向的是Toy对象。
3)使用newInstance()来创建的类,必须带有默认的构造器。

1、类字面常量

java还提供了另一种方法来生成对Class对象的引用,即使用类字面常量。如:
FancyToy.class;   

java取得Class对象引用:1)forName()方法  2)类字面常量

这样做不仅简单,而且更安全,因为它是在编译时候就会受到检查(因此不需要置于try语句块中),并且它根除了对forName()方法的调用,所以也更高效

类字面常量不仅可以应用于普通的类,也可以应用于接口、数组、以及基本数据类型。另外,对于基本数据类型的包装类,还有一个标准的TYPE。TYPE字段是一个引用,指向对应的基本数据类型的Class对象。


当使用.class来创建对Class对象的引用的时候,不会自动初始化该Class对象,为了使用类而做的准备工作实际包含三个步骤:
1)加载
2)链接
3)初始化

初始化被延迟到了对静态方法(构造器隐式的是静态的)或者非常数静态域进行首次引用的时候来执行:




2、泛化的Class引用

Class引用总是指向某个Class对象,它可以制造类的实例,并包含可以作用域这些实例的所有方法代码。它还包含该类的静态成员,因此,Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。


普通的类引用不会产生警告信息,你可以看到,尽管泛型类引用只能赋值为指向其声明的类型,但是普通的类引用可以被重新赋值为指向任何其他的Class对象。通过使用泛型语法,可以让编译器强制执行额外的类型检查。

在Java SE5中,Class<?>优于平凡的Class,即便它们是等价的,并且平凡的Class如你所见,不会产生编译器警告信息。Class<?>的好处是它表示你并非碰巧或者由于疏忽,而使用了一个非具体的类引用,你就是选择了非具体的版本。



向Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查,因此如果你操作有误,稍后立即就会发现这一点。

当你将泛型语法用于Class对象的时候,会发生一件很有趣的事情:newInstance()将返回该对象的确切类型,而不仅仅只是在ToyTest。java中看到的基本的Object:


3、新的转型语法

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




三、类型转换前先做检查

1)instance of
2)Class.isInstance

四、注册工厂

五、instanceof与Class的等价性




1)instanceof保持了类型的概念,它指的是“你是这个类吗?或者你是这个类的派生类吗?”
2)如果用==比较实际的Class对象,就没有考虑继承---它或者是这个确切的类型,或者不是。

六、反射:运行时候的类信息

七、动态代理



通过调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要得到一个类加载器(你通常可以从已经被加载的对象中获取其类加载器,然后传递给它),一个你希望该代理实现的接口列表(不是类或者抽象类),以及InvocationHandle接口的一个实现。

八、空对象

九、接口与类型信息


1. Class.forName(className)用来加载指定类名,并返回Class对象,一般可忽略返回值。如果已有一个对象,可以通过getClass方法来获得Class对象。或者用ClassName.class也可以获得。
2. 通过Class对象,可以用getInterfaces方法getSuperClass方法获得其实现的接口及父类名,而且Class对象的newInstance方法可以创建该类的一个对象(必须要有无参构造函数)。
3. Class.forName方法加载时会进行静态代码的初始化(如jdbcDriver就必须用这种形式),而类名+.class的方式并不会立刻执行静态代码的初始化,而等第一次用该类时,才会进行。
4. Class<Number> cc=Integer.class;//错误,因为虽然Number是Integer的父类,但其Class对象并无父子关系。正确形式应该是:
Class <? extends Number> cc=Integer.class;
5. Class对象的isAssignableFrom()是用来判断一个类型是否是另一个类型的父类或接口,用法是:baseType.isAssignableFrom(sonType);
6. 反射。Class类与java.lang.reflect包一起对反射的概念进行支持。其中包含Field,Method,Constructor等对象也用Class对象的getFields等方法得到,并可用invoke方法调用类的方法。类方法观察器,RMI,及Java动态代理等都需要反射的支持。
7. 空对象模式。定义一个接口实现,NullInterface implements Interface{...}即空对象类。在定义一个对象时,可以使之默认初始化为空对象类,这样就可以省去许多的if(obj==null)语句了。
8. Tomcat Server在启动的时候将构造一个ClassLoader树,以保证模块的类库是私有的 
Tomcat Server的ClassLoader结构如下: 
    Bootstrap  
       |            
     System   
       |  
     Common  
    /      /  
Catalina  Shared  
          /    /  
       WebApp1  WebApp2  

其中: 
- Bootstrap - 载入JVM自带的类和$JAVA_HOME/jre/lib/ext/*.jar 下JVM需要用到的类
- System - 载入$CLASSPATH/*.class Tomcat5之后不从CLASSPATH加载,而从tomcat-home/bin及tomcat-home/lib目录加载。
- Common - 载入$CATALINA_HOME/common/...,它们对TOMCAT和所有的WEB APP都可见 
- Catalina - 载入$CATALINA_HOME/server/...,它们仅对TOMCAT可见,对所有的WEB APP都不可见 
- Shared - 载入$CATALINA_HOME/shared/...,它们仅对所有WEB APP可见,对TOMCAT不可见(也不必见) 
- WebApp - 载入ContextBase?/WEB-INF/...,它们仅对该WEB APP可见 
同时,classes目录比同级的lib目录有优先权,如有同名者,用前者。

每个运行中的线程都有一个成员contextClassLoader,用来在运行时动态地载入其它类,系统默认的contextClassLoader是systemClassLoader,所以一般而言java程序在执行时可以使用JVM自带的类、$JAVA_HOME/jre/lib/ext/中的类和$CLASSPATH/中的类,可以使用Thread.currentThread().setContextClassLoader(...);更改当前线程的contextClassLoader,来改变其载入类的行为 

ClassLoader被组织成树形,一般的工作原理是: 
1) 线程需要用到某个类,于是contextClassLoader被请求来载入该类 
2) contextClassLoader请求它的父ClassLoader来完成该载入请求 
3) 如果父ClassLoader无法载入类,则contextClassLoader试图自己来载入 
注意:WebApp?ClassLoader的工作原理和上述有少许不同: 
它先试图自己载入类(在ContextBase?/WEB-INF/...中载入类),如果无法载入,再请求父ClassLoader完成 

由此可得: 
- 对于WEB APP线程,它的contextClassLoader是WebApp?ClassLoader 
- 对于Tomcat Server线程,它的contextClassLoader是CatalinaClassLoader

运行时的类型信息使得你可以在程序运行时发现和使用类型信息
运行时识别对象和类的信息,主要有两种方式:
传统的RTTI,它假定我们在编译时已经知道了所有的类型
反射机制,允许我们在运行时发现和使用类的信息
在java中所有类型转换都是在运行时进行正确性检查的,这也就是在RTTI的含义:在运行时,识别一个对象的类型
Class对象相关知识:
java使用Class对象来执行其RTTI
类是程序的一部分,每个类都有一个Class对象,也就是说,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当的说,是被保存在一个同名的.class文件中),为了生成这个对象,运行这个程序的Java虚拟机将使用被称为“类加载器”的子系统
类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载的是所谓的可信类,包括java api类,它们通常是从本地盘加载的。如果需要特殊需求,可以挂接额外的类加载器。
所有的类都是在对其第一次使用时,动态加载到JVM中的,static初始化实在类加载时进行的,当程序创建第一个对类的静态成员的引用时,就会加载这个类,这也证明了构造器是类的静态方法
类加载器首先检查着各类的Class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找.class文件
Class类的static方法Class.forName():
取得Class对象的引用的一种方法
使用String作输入参数,返回的是一个Class对象的引用
如果找不到需要加载的类,会抛出异常ClassNotFoundException
传递给forName()的字符串中,必须使用全限定名
Object中有getClass()方法来获得Class引用,它将返回对象的实际类型的Class引用
Class中:
getInterfaces()方法返回的是Class对象的所包含的接口
getSuperclass()方法查询器直接基类
newInstance()来创建类的实例
Java还提供了另一种方法来生成对Class对象的引用,即使用类字面常量如:FancyToy.class
当使用.class来创建对象的引用时,不会自动地初始化该Class对象,为了使用类而做的准备工作实际包含三个步骤:
加载:这是由类加载器执行的,这里将查找字节码(通常在classpath所指定的路径中查找),并从这些字节码中创建一个Class对象
链接:在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用
初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块
Class引用总是执行某个Class对象,它可以制造类的实例,并包含可作用于这些实例的所有方法代码。它还包含该类的静态成员
如果需要使用泛化的Class引用时,可以使用Class<>
Java SE5中Class<?>匹配任意的类,Class<?>优于平凡的Class,即便他们是等价的
可以使用Class<? extends Number>来创建一个范围
Class引用添加泛型语法的原因仅仅是因为为了提供编译期类型检查
当使用泛型语法用于Class对象时,newInstance()将返回该对象的确切类型,而不仅仅是基本的Object
Class<? super FancyToy>允许声明超类引用是“某个类,它是FancyToy超类”
Java SE5中添加了用于Class引用的转型语法,cast()
cast()方法接收参数对象,并将其转型为Class引用的类型
使用方法   Class houseType;   House h = houseType.cast(b);  
已知的RTTI形式:
传统的类型转换,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCasetException异常
代表对象的类型Class对象,通过查询Class对象可以获取运行时所需的信息
使用instanceof,它将返回一个布尔值,告诉我们对象是不是某个特定类型的实例

对instanceof 有比较严格的限制:只可将其与命名类型进行比较,而不能与Class对象作比较
可以使用obj.isInstance(Obj)来判定obj是否是Obj类型的实例
如果不知道某个对象的确切类型,RTTi可以告诉你,但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,并列用这些信息做一些有用的事,换句话说,在编译时,编译器必须知道所有要通过RTTI来处理的类
Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field,Method,以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员
可以使用Constructor创建新的对象
用get()和set()方法读取和修改与Field对相关连的字段
用invoke方法调用与Method对相关联的方法
调用getFields()、getMethods()和getConstructors()来获得字段,方法,构造器的数组
RTTI和反射之间的真正区别是:对于RTTI来说,编译器在编译时打开和检查.class文件,而对于反射机制来说,.class 文件在编译时是不可获取的,所以在运行时打开和检查.class方法
在使用内置的null表示缺少对象时,在每次使用引用都必须测试其是否为null。有时引用空对象的思想将会很有用,通过这种方式,可以假设所有的对象都是有效的
空对象定义在类呃内部,实现Null接口
空对象使用了单例模式,可以使用equals甚至==来比较Person.Null
interface关键字的一种重要目标就是允许程序员隔离构建,进而降低耦合性
通过使用反射,可以到达并调用所以方法,甚至是private方法,如果知道方法名,可以在Method对象上杜鳌用setAccessible(true)
对于final域,修改时是安全的,运行时系统会在不抛异常的情况下接收任何修改尝试,但是实际上不会发生任何修改

)泛化的Class引用
Class也可以加入泛型,加入之后会进行类型检查。

贴一下书上原话,Class<?>优于Class,虽然他们是等价的,Class<?>的好处是碰巧或疏忽使用了一个非具体的类引用。我搞不懂这个所谓非具体是什么?
后面弄懂了,其实<?>作为通配符,就是未知的,直接写结论的话不能写个具体类型吧,作者的意思其实就是说加了泛型的Class就是选择了非具体的版本。

加入泛型的原因是提供编译期间的类型检查,操作失误的话便会显示错误,但是使用普通的Class的时候,就要到等到运行的时候。

还有这个Class<T>,JDK文档中:
T - the type of the class modeled by thisClass object. For example, the type ofString.class isClass<String>. UseClass<?> if the class being modeled is unknown.
<T>是使用泛型类的声明。包括:
Interface List<E>
Type Parameters:
E - the type of elements in this list
一样也是声明。

2)转型语法
SE5添加的用于Class引用转型的语法。
class Gun{
    int price = 1;
}
class DeathGun extends Gun{
    int price = 2;
}
public class TestCast {
    public static void main(String[] args) {
        Gun g = new DeathGun();
        Class<DeathGun> d = DeathGun.class;
        //泛型的用处 类型不匹配
        //Class<DeathGun> dd = Gun.class;  
        DeathGun gg = d.cast(g);
        System.out.println(gg.price);
    }
}

cast源码:
public T cast(Object obj) {
    if (obj != null && !isInstance(obj))
       throw new ClassCastException(cannotCastMsg(obj));
    return (T) obj;
}

3)类型转换前的检查
if(x instanceof TV){
  ((TV)x).show();
}
检查对象类型,向下转型时如果有错,则抛出ClassCastException,所以要先使用instanceOf。

instaceOf与Class的等价性。
class Father{}
class Son extends Father{}
public class Test {
    static void  test(Object o ){
        System.out.println("type: "+o.getClass());
        System.out.println("o instanceof Father: "+(o instanceof Father));
        System.out.println("o instanceof Son: "+(o instanceof Son));
        System.out.println("Father.isInstanceOf(o)"+Father.class.isInstance(o));
        System.out.println("Son.isInstanceOf(o)"+Son.class.isInstance(o));
        System.out.println("o.getClass()==Father.class:"+(o.getClass()==Father.class));
        System.out.println("o.getClass()==Son.class:"+(o.getClass()==Son.class));
        System.out.println("o.getClass().equals(Father.class):"+(o.getClass().equals(Father.class)));
        System.out.println("o.getClass().equals(Son.class):"+(o.getClass().equals(Son.class)));
    }
   
    public static void main(String[] args) {
        test(new Father());
        test(new Son());
    }
}

result:
type: class son.Father
o instanceof Father: true
o instanceof Son: false
Father.isInstanceOf(o)true
Son.isInstanceOf(o)false
o.getClass()==Father.class:true
o.getClass()==Son.class:false
o.getClass().equals(Father.class):true
o.getClass().equals(Son.class):false

type: class son.Son
o instanceof Father: true
o instanceof Son: true
Father.isInstanceOf(o)true
Son.isInstanceOf(o)true
o.getClass()==Father.class:false
o.getClass()==Son.class:true
o.getClass().equals(Father.class):false
o.getClass().equals(Son.class):true

instanceof指的是你是这个类吗?你是这个类的派生类吗?

4)反射
书上讲得好简单,3页左右。
RTTI,运行时类型信息可以告诉你对象的具体类型,但是,这个类型必须在编译时必须已知,这样RTTI才能识别。即在编译时,编译器必须知道要通过RTTI来处理的类。
这个看起来不是限制,但是置身于大规模的编程中,可能编译时程序无法获知这个对象所属的类。
RTTI和反射真正的区别只有:RTTI,编译器在编译时检查和打开.class文件,对于反射,.class文件编译时不可获取,是在运行时打开和检查.class文件。

直接用书上的例子,贴个代码:
public class ShowMethods {
  private static String usage =
    "usage:\n" +
    "ShowMethods qualified.class.name\n" +
    "To show all methods in class or:\n" +
    "ShowMethods qualified.class.name word\n" +
    "To search for methods involving 'word'";
  private static Pattern p = Pattern.compile("\\w+\\.");
  public static void main(String[] args) {
    if(args.length < 1) {
      System.out.println(usage);
      System.exit(0);
    }
    int lines = 0;
    try {
      Class<?> c = Class.forName(args[0]);
      Method[] methods = c.getMethods();
      Constructor[] ctors = c.getConstructors();
      if(args.length == 1) {
        for(Method method : methods)
          System.out.println(
            p.matcher(method.toString()).replaceAll(""));
        for(Constructor ctor : ctors)
          System.out.println(p.matcher(ctor.toString()).replaceAll(""));
        lines = methods.length + ctors.length;
      } else {
//其实作者这样写是,method匹配会匹配,如果我java ShowMethods ShowMethods ShowMethods
//本来的话是提取其中的一个,但是args[1]为ShowMethods,刚刚好匹配到的就是ShowMethods本身自带的方法,也就是main方法。
        for(Method method : methods)
          if(method.toString().indexOf(args[1]) != -1) {
            System.out.println(
              p.matcher(method.toString()).replaceAll(""));
            lines++;
          }
        for(Constructor ctor : ctors)
          if(ctor.toString().indexOf(args[1]) != -1) {
            System.out.println(p.matcher(
              ctor.toString()).replaceAll(""));
            lines++;
          }
      }
    } catch(ClassNotFoundException e) {
      System.out.println("No such class: " + e);
    }
  }
}


F:\>java ShowMethods ShowMethods
public static void main(String[])
public final native Class getClass()
public native int hashCode()
public boolean equals(Object)
public String toString()
public final native void notify()
public final native void notifyAll()
public final native void wait(long) throws InterruptedException
public final void wait(long,int) throws InterruptedException
public final void wait() throws InterruptedException
public ShowMethods()


慢慢看Matcher的replaceAll方法,不同于String的replaceAll,后者有两个参数,一个替换内容,一个是要被替换的内容。而前者只有一个参数。
public String replaceAll(String replacement)
Replaces every subsequence of the input sequence that matches the pattern with the given replacement string.
public class TestString { 
    public static void main(String[] args) { 
        String s = "0898((*(*))(";
        //正则表达式编译进Pattern
        Pattern p = Pattern.compile("\\W");
        //p.matcher返回的是Matcher对象,Matcher的replaceAll方法是可以将匹配的字符串
        //替换成方法参数中的内容。
        System.out.println(p.matcher(s).replaceAll("-"));
    } 

result:0898--------
如果没有正则表达式的替换,那么结果是:
public static void ShowMethods.main(java.lang.String[])
public final native java.lang.Class java.lang.Object.getClass()
public native int java.lang.Object.hashCode()
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
public final native void java.lang.Object.wait(long) throws java.lang.Interrupte
dException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedEx
ception
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public ShowMethods()
其实就是替换掉类前面的包名。
有没有发现,forName的结果在编译时不可知,我们是在运行的时候传进Class参数的。虽然刚开始的时候没用到这么高级的东西,但是后面学得深入了之后就发现有些东西在使用,如Spring。

getMethods拿到的是类的所有共有方法,包括自身,从父类继承,从接口实现的public方法。
还有一个是getDeclaredMethods拿到的是类自身声明的方法,不止是public方法,protected也能拿到,甚至是private。(刚开始我也是很疑惑,不是经常强调封装,私有不让人家看到吗,怎么可以直接拿到private的东西的,其实可以这样看,什么东西有个度,但是这个度并不是一尘不破,有一定的后门)。

getConstructors返回的是public的构造器。试了一下,私有的构造器不会显示。书上讲到这里就停了,继续扩展:
以前看过Java反射教程里面介绍的很好。

一、未知的方法调用
public class TestRefl {
    public static void main(String[] args) {
        UnKnown uk = new UnKnown();
        Method m = null;
        try {
            m = uk.getClass().getMethod("print", new Class<?>[0]);
                m.invoke(uk);
        } catch (NoSuchMethodException | InvocationTargetException| SecurityException  |IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

class UnKnown{
    public void print(){
        System.out.println("haha");
    }
}
之前我特地将方法改成private,发现是找不到方法的,只有改为public。

二、对象的创建
public class TestRefl {
    public static void main(String[] args) {
       Class c = null;
       try {
        c = c.forName("son.UnKnown");
        System.out.println(c.getName());
    } catch (ClassNotFoundException e) {
        System.out.println("not found");
    }
       try {
        UnKnown u = (UnKnown) c.newInstance();
        u.print();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    }
}

class UnKnown{
   static  void print(){
        System.out.println("haha");
    }
}

通过构造函数获取对象。
public class TestRefl {
    public static void main(String[] args) {
        Class c = null;
        try {
            c = c.forName("son.UnKnown");
        } catch (ClassNotFoundException e) {
            System.out.println("not found");
        }
        Constructor<?> ct[] = c.getConstructors();
        try {
            UnKnown u = (UnKnown) ct[0].newInstance();
            UnKnown u2 = (UnKnown) ct[1].newInstance(1);
            u.print();
            u2.print();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

class UnKnown {
    public UnKnown() {
    }

    public UnKnown(int i) {
    }

    static void print() {
        System.out.println("haha");
    }
}
构造函数数组有顺序,0是无参的那个构造函数,1为传参,在方法中直接传参即可。当然私有的构造方法是拿不到的。

解释到RTTI和反射的不同的时候,反过来可以说明反射的作用,就是运行时检查对象的类型,任意调用对象的方法(Spring和servlet中都用到了,注意到没有,我们在xml配置,然后属性会根据xml注入),同时可以知道方法参数和属性。

反射还是很强大,接下来会介绍动态代理,以的一个设计模式。






















猜你喜欢

转载自zhyp29.iteye.com/blog/2307090