前序:
运行时类型识别(RTTI, Run-Time Type Identification,在运行时识别一个对象的类型)是Java中非常有用的机制,在Java运行时,RTTI维护类的相关信息。
多态(polymorphism)是基于RTTI实现的。RTTI的功能主要是由Class类实现的。
1.类对象:类是程序的一部分,每个类都有一个Class对象,每当编写并且编译了一个新类的时候,就会产生一个Class对象,为了生成这个类对象,运行这个程序的JVM将使用“类加载器”系统。
2.所有的类都是在对其第一次使用的时候动态加载到JVM的。当程序创建第一个对类的静态成员的引用的时候,就会加载类。
所以构造器也是static方法,即使在构造器之间没有static关键字。因此,使用new操作符创建类的新对象也会被当作对类的静态成员的引用。
3.Class.forName():这个方法接受一个目标类的文本名的String作输入参数,返回一个Class对象的引用,如果类没加载,就加载它。(所抛出的异常是ClassNotFoundException)。
4.想要在运行时使用类型信息的前提条件是你必须获得恰当的Class对象的引用。
5.获得类对象引用:
当你没有具体的对象而想获得类的引用的时候:Class.forName();
当你有了感兴趣的对象(例如A a)而想获得类的引用的时候:a.getClass();
6.方法: Class cc;
cc.getName;
cc.getSimpleName();
cc.getCanonicalName();
cc.isInterface();
cc.getInterfaces();
cc.getSuperClass();
cc.newInstance();---------cc只是一个Class引用,在编译期不具备任何更进一步的类型信息。当你newInstacnce()的时候,得到的是一个Object的引用,但是这个引用指向了具体的对象。
作特定动作的时候需要进行转型。使用newInstance()来创建的类,必须有默认的构造器。
7.类字面常量(不仅仅可以应用于类,也可以应用于接口,数组,以及基本数据类型,对于基本数据类型的包装器类,用Integer.TYPE、Boolean.TYPE等等):
xxx.class,来生成Class对象的引用,它在编译期就会受到检查,根除了forName()方法的调用,更高效和安全。
注意,使用类字面常量来创建对类对象的引用的时候,不会自动的初始化该Class对象,而Class.forName()会自动的初始化Class对象。
8.初始化的惰性:
public class Test {
public static void main(String[]args) {
Class i = Init.class;
System.out.println(Init.value1);
System.out.println("--------------------------");
System.out.println(Init.value2);
}
}
class Init{
static Randomrand =new Random();
public static final int value1 = 1;
public static final int value2 =rand.nextInt(10);
static{
System.out.println("-----------------初始化:");
}
}
1
--------------------------
-----------------初始化:
7
a.)仅使用类字面常量.class来获得类的引用是不会引发初始化的,而使用Class.forName()则会立即就进行初始化。
b.)如果一个被staitc final修饰的词是“编译期常量”,如value1,那么这个值不需要被初始化就可以被读取。注意:如果一个域被设置成了static和 final的,但是它不是“编译期常量”,那么使用它的时候也会引发初始化。
c.)如果一个static域不是final的,那么对它访问时,总是要求在它被读取之前,进行链接和初始化。
b.c的解析:在你使用final修饰数据的时候,你的目的有2点:(1)编译期常量,(2)运行期初始化的时候,我们希望它不会被改变。
关于编译期常量:它在类“加载的时候”就已经完成了初始化,所以当加载完后,它是不可更改,可以在编译期中代入任何的计算式,编译期常量只能用于基本数据类型。
所以一个不是基本数据类型的static final域,它不是编译期常量,使用它的时候会触发初始化。
总结:初始化被延期到了对静态方法和非编译器常量进行首次引用的时候才执行。
9.使用类的准备工作:
a) 加载:由类加载器执行,查找字节码并在字节码中创建一个class对象。
b) 链接:为静态域分配存储空间,如果必要,将解析这个类创建的对其他类的引用。
c) 初始化.
10.Interger继承自Number类,但是Ingerger Class对象不是Number Class对象的子类。
11.泛化的Class引用:
Class引用其实总是在指向某个Class对象,它可以创造类的实例,并包含所有作用于这些实例的方法,他还包含该类的静态成员。因此,Class引用表示的就是它指向的对象的确切类型,而该对象就是Class类的一个对象。
向Class引用添加泛型语法的原因仅仅是因为为了提供”编译期”编译器检查。
比如FancyToy extends Toy{}
如果Class
这个时候表示的具体类型是FancyToy,此时fancytoy.newInstance返回的是FancyToy对象。
如果:Class Super Toy> aa,这个时候表现某个是Toy父类的类引用,因为其含糊不清,如果这时候 用newInstance()则返回Object对象。
如果不使用泛型,newInstance()返回的是Object对象。
如果泛型明确指定返回类的类型,则返回明确实例。
总结:
Class Extend Toy> aaa 当aaa.newInstance()的时候,此时表示的是任何是Toy的子类,因为向上转型不用强制转换,所以当你让此时的newInstance()生成的实例应用于List
Class Super Fancy>,这个时候表达的是任何是Fancy的父类,此时模糊不清,newInstance()返回Obejct,向下转型需要强制类型转换。
12.注册工厂:
正常的工厂模式:
interface IProduct {
public void productMethod();
}
class Product implements IProduct {
public void productMethod() {
System.out.println("产品");
}
}
interface IFactory {
public IProduct createProduct();
}
class Factory implements IFactory {
public IProduct createProduct() {
return new Product();
}
}
public class Client {
public static void main(String[] args) {
IFactory factory = new Factory();
IProduct prodect = factory.createProduct();
prodect.productMethod();
}
}
工厂方法模式:
通过工厂方法模式的类图可以看到,工厂方法模式有四个要素:
· 工厂接口。工厂接口是工厂方法模式的核心,与调用者直接交互用来提供产品)
· 工厂实现。在编程中,工厂实现决定如何实例化产品(如何实现产品),是实现扩展的途径,需要有多少种产品,就需要有多少个具体的工厂实现。
· 产品接口。产品接口的主要目的是定义产品的规范(产品规范),所有的产品实现都必须遵循产品接口定义的规范。产品接口是调用者最为关心的,产品接口定义的优劣直接决定了调用者代码的稳定性。同样,产品接口也可以用抽象类来代替,但要注意最好不要违反里氏替换原则。
· 产品实现。(产品具体行为)实现产品接口的具体类,决定了产品在客户端中的具体行为。
思路:有了泛型和内部类之后,工厂有了新的诠释方法。首先在父类生成一个静态list,里面存放的是不同工厂,这些不同的工厂是不同产品的“静态内部类”,不同的工厂里面有不同产品的不同的生成方式(即产品生成方式)。
产品父类有个static list(有了list,可以随机生成产品),通过泛型指定list持有父类工厂,通过泛型指定工厂里持有产品父类的子类。
正常方式:每个产品(A,B,C)都有一个工厂在外面。
目的:将创建对象的工作交给类自己完成。每个类持有自己的工厂实现,也就是产品实现和工厂实现都在一起。
public class Test {
public static void main(String[]args) {
for(int i=0;i<10;i++){
System.out.println(Big.createRandom());
}
}
}
class Big{
public static ArrayList<OutFactoryextends Big>>list =new ArrayList
static{
list.add(new A.Factory());
list.add(new B.Factory());
list.add(new C.Factory());
}
public static Big createRandom(){
Randomrandom =new Random();
int n =random.nextInt(list.size());
return list.get(n).create();
}
}
class Aextends Big{
public static class Factoryimplements OutFactory{
@Override
public A create() {
return new A();
}}
public StringtoString(){
return "A extends Big";
}
}
class Bextends Big{
public static class Factoryimplements OutFactory{
@Override
public B create() {
return new B();
}}
public String toString(){
return "B extends Big";
}
}
class Cextends Big{
public static class Factoryimplements OutFactory
@Override
public C create() {
return new C();
}}
public String toString(){
return "C extends Big";
}
}
13.instanceOf与”==”或者equals之于Class:
如果 B extends A{}
B b = new B();
B instanceOf A返回true,所以instanceOf考虑继承结构。
b.getClass() == A.class返回false 不考虑继承结构
b.getClass().equals(A.class)返回false 不考虑继承结构
所以==和equals比的是确切的类型,不考虑继承结构。
14.反射:
a).基础概念:
在Java中,万事万物皆对象(静态成员和基本数据类型不是对象)
类也是对象,类是java.lang.Class类的实例对象。
任何一个类都是Class类的实例对象。这个实例对象有3种表示方式:
Class Foo{}
Foo foo = new Foo();
第一种:
Class c1 = Foo.class;
这告诉我们任何一个类都有一个隐含的静态成员变量class。
第二种:
Class c2 = foo.getClass();
c1,c2代表的是类的类类型,类也是对象,是Class类的实例对象,这个对象我们称为该 类的类类型。
一个类只可能是类Class的一个实例对象。
第三种
Class c3 = Class.forName(“Foo”);
通过newInstance方法可以创建类类型的实例对象。
c1.newInstance();
基本数据类型也包含类类型 int.class (与包装类.class不同).
void也包含类类型。
b.)动态加载:
Class.forName(“类的全称”),不仅表示了类的类类型(Class Type),而且还动态加载类
编译时加载类是静态加载类,运行时加载类是动态加载类。
new对象是静态加载类,在编译时刻就加载所有可能需要的类。(但是new动作却发生在运行时? 这里理解不深,待我查资料。)
如果在你的程序当中有100个功能,当你是静态加载的时候,如果一个功能不好用,其他 所有的关联功能可能就会报错。
通过动态加载类可以解决该问题:
Class c = Class.forName(“XXX”);
XXX x = (XXX)c.newInstance();
c.)比较重要的方法:
getMethods()返回的是所有public方法,包括他的父类的public方法,c.getDeclaredMethods()返回的是它自己的方法,不包括它的父类。
class ClassUtil{
//打印某一对象的类类型:
public static void printMessage(Objectobj){
Classc =obj.getClass();
System.out.println("类的类型是"+c.getName());
Method[]methods = c.getMethods();
for(int i=0;i<methods.length;i++){
//获得方法的返回值类型,返回的也是类类型
ClassreturnType =methods[i].getReturnType();
System.out.print(returnType.getName()+" ");
System.out.print(methods[i].getName());
System.out.print(" (");
//方法参数类类型
Class[]paraType =methods[i].getParameterTypes();
for(Classtype :paraType){
System.out.print(type.getName()+",");
}
System.out.println(")");
}
}
}
public class Test {
public static void main(String[]args) {
Strings ="haha";
ClassUtil.printMessage(s);
}
}
输出:
类的类型是java.lang.String
boolean equals (java.lang.Object,)
java.lang.String toString ()
int hashCode ()
int compareTo (java.lang.String,)
int compareTo (java.lang.Object,)
int indexOf (java.lang.String,int,)
int indexOf (java.lang.String,)
int indexOf (int,int,)
int indexOf (int,)
java.lang.String valueOf (int,)
java.lang.String valueOf (long,)
java.lang.String valueOf (float,)
java.lang.String valueOf (boolean,)
java.lang.String valueOf ([C,)
java.lang.String valueOf ([C,int,int,)
java.lang.String valueOf (java.lang.Object,)
java.lang.String valueOf (char,)
java.lang.String valueOf (double,)
char charAt (int,)
int codePointAt (int,)
int codePointBefore (int,)
int codePointCount (int,int,)
int compareToIgnoreCase (java.lang.String,)
java.lang.String concat (java.lang.String,)
boolean contains (java.lang.CharSequence,)
boolean contentEquals (java.lang.CharSequence,)
boolean contentEquals (java.lang.StringBuffer,)
java.lang.String copyValueOf ([C,)
java.lang.String copyValueOf ([C,int,int,)
boolean endsWith (java.lang.String,)
boolean equalsIgnoreCase (java.lang.String,)
java.lang.String format (java.util.Locale,java.lang.String,[Ljava.lang.Object;,)
java.lang.String format (java.lang.String,[Ljava.lang.Object;,)
void getBytes (int,int,[B,int,)
[B getBytes (java.nio.charset.Charset,)
[B getBytes (java.lang.String,)
[B getBytes ()
void getChars (int,int,[C,int,)
java.lang.String intern ()
boolean isEmpty ()
java.lang.String join (java.lang.CharSequence,[Ljava.lang.CharSequence;,)
java.lang.String join (java.lang.CharSequence,java.lang.Iterable,)
int lastIndexOf (int,)
int lastIndexOf (java.lang.String,)
int lastIndexOf (java.lang.String,int,)
int lastIndexOf (int,int,)
int length ()
boolean matches (java.lang.String,)
int offsetByCodePoints (int,int,)
boolean regionMatches (int,java.lang.String,int,int,)
boolean regionMatches (boolean,int,java.lang.String,int,int,)
java.lang.String replace (char,char,)
java.lang.String replace (java.lang.CharSequence,java.lang.CharSequence,)
java.lang.String replaceAll (java.lang.String,java.lang.String,)
java.lang.String replaceFirst (java.lang.String,java.lang.String,)
[Ljava.lang.String; split (java.lang.String,)
[Ljava.lang.String; split (java.lang.String,int,)
boolean startsWith (java.lang.String,int,)
boolean startsWith (java.lang.String,)
java.lang.CharSequence subSequence (int,int,)
java.lang.String substring (int,)
java.lang.String substring (int,int,)
[C toCharArray ()
java.lang.String toLowerCase (java.util.Locale,)
java.lang.String toLowerCase ()
java.lang.String toUpperCase ()
java.lang.String toUpperCase (java.util.Locale,)
java.lang.String trim ()
void wait ()
void wait (long,int,)
void wait (long,)
java.lang.Class getClass ()
void notify ()
void notifyAll ()
java.util.stream.IntStream chars ()
java.util.stream.IntStream codePoints ()
d.)成员变量的反射:
c.getFields();
c.getDeclaredFields(); -----自己声明的成员变量。
返回Field[]
e.)构造函数的反射:
c.getConstructors();
c.getDeclaredlConstructors(); -----自己声明的构造器。
f.)方法的反射:
Methodm =c.getMethod("方法名","参数Type");
Methodm =c.getDeclaredMethod("方法名","参数Type");
正常的方法调用是对象调用方法:
A a = new A;
A.f();
反射的时候是在运行时发生的:
m.invoke(对象,参数);
m.invoeke(a,xxx);
g.)通过反射了解泛型本质:
public class Test {
public static void main(String[]args) {
ArrayList a =new ArrayList();
ArrayList
b.add("hello");
b.add("world");
System.out.println(a.getClass().equals(b.getClass()));
//返回为true,反射的操作都是编译之后的操作,即运行时的操作。说明编译后的集合是去泛型化的。
//Java中的泛型是编译期泛型,只在编译期有效,绕过编译就无效了。
//验证:我们可以通过反射操作来验证这个结论。
//b.add(1)错误;
Class c =b.getClass();
try {
Methodm =c.getMethod("add",Object.class);
m.invoke(b, 1);//反射操作绕过了编译,在运行期间,此时没有泛型控制,可以在ArrayList中加入任何类型。
System.out.println(b.size());
System.out.println(b);
}catch (Exceptione) {
//TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出:
true
3
[hello, world, 1]
结论:反射的操作在运行期,泛型是编译期泛型,绕过编译期,泛型无效。
15.几种RTTI的总结:
a.)传统的类型转换;
b.)代表对象类型的class对象;
c.)关键字instanceof,它告诉我们对象是不是某个特定类型的实例。
如果你不知道某个对象的确切”类型”,RTTI可以告诉你,但是这里有一个限制:那就是这个“类型”在编译期是必须已知的。 即Class的类类型必须是已知的。-------->也就是”编译期”,编译器必须知道所有要通过RTTI来处理的类。
在一些时刻,一些类可能你在运行时从网络中得到的,或者在运行时从磁盘得到的,此时已经错过了编译期,那么传统的RTTI将不能发挥作用,这时候我们就用到了反射。
要在运行时刻获得类的信息的动机:希望提供在跨网络的远程平台上创建和运行对象的 能力。比如一个Java程序分布在很多机器上,每个机器完成自己的子部分,一起协调工作。
16.反射和RTTI的区别:
首先要知道反射没有什么特殊的,当与一个未知类型的对象合作的时候,JVM只是简单的检 查这个对象属于那个特定的类。在用他做其他事情之前必须加载这个类的类对象。因此, 那个类的.class文件必须是可获得的,要么在本地机器上,要么通过网络。
所以反射和RTTI的真正区别是:RTTI在编译器在编译期打开和检查.class文件。而反射。Class文件在编译时是不可获得的,是在运行的时候打开和检查.class文件。
17.空对象:
空对象是一个没有对象信息的对象,它使得你减少了总是要检查null的精力。
通常空对象都是单例的,static final修饰。
18.反射破坏访问权限(所以反射不要乱用):
public class Test {
public static void main(String[]args) {
Aa =new B();
a.f();
//a.g();
Cc =new D();
c.c();
//c.d();
}
}
interface A{
public void f();
}
class Bimplements A{
@Override
public void f() {
System.out.println("B.f()");
}
public void g(){
System.out.println("B.g()");
}
}
class C {
public void c(){};
}
class Dextends C{
public void d(){};
}
可以看到利用多态的时候,父类或者接口引用无法使用子类的独有的方法,只能使用自己的方法,这是一种保护机制,是的客户端程序员与你所写的代码降低耦合性,但是这种耦合性是脆弱的,只要进行强制类型转换就可以使用我们子类的方法。((B)a).g();
一种解决方式是服务端声明你的实现方法是包访问权限或者private的,这样在包外部的客户端就不能看到它了。
但是如果人家知道你的方法名,可以通过反射来调用你的方法,甚至是private方法。
比如g()是private方法。
Methog g = a.getClass.getDeclaredMethoe(“g”);
g.setAccessiable(true);------setAccessible(true);可以强制使得java反射机制忽略访问权限控制
G.invoke(a);
当你知道方法名,借助于反射可以通过类对象获得方法,甚至可以设置private是可以“通行”的。
即使是私有内部类或者匿名内部类,也可以通过反射到达并使用那些非公共权限的方法。
对于域也是同理,即使是private域。
通常这些违反权限操作的事在解决某些特定问题的时候会发挥重要的作用,所以不必完全否定它。