String字符串:
字符串是不可变的(immutable), String类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象。每当把String对象作为方法的参数时,都会复制一份引用,而该引用所指的对象其实一直待在单一的物理位置上,从未动过。对于一个方法而言,参数是为该方法提供信息的,而不是想让该方法改变自己的?
“+”操作符重载会带来一定的效率问题,编译器自动引入了更高效的java.lang.StringBuilder。 java -c Concatenation反编译如下:
StringBuffer 是线程安全的,StringBuilder是单线程使用的。如果已经知道最终的字符串大概有多长,那预先指定StringBuilder的大小可以避免多次重新分配缓冲。
String 的Intern()方法是为每一个唯一的字符序列生成一个且仅生成一个String引用。
类型信息:RTTI---在运行时,识别一个对象的类型。
Java是如何让我们在运行时识别对象和类的信息的?
1.“传统的”RTTI,它假定我们在编译时已经知道了所有的类型。
2.“反射”机制,它允许我们在运行时发现和使用类的信息。
面向对象编程中基本的目的是:让代码只操纵基类的引用。这样如果添加一个新类来扩展程序,就不会影响原来的代码。在编译时,将由容器和Java的泛型系统来强制确保这一点;而在运行时,由类型转换操作来确保这一点。
Class对象:用来创建类的所有“常规”的对象的。Java使用Class对象来执行其RTTI,即使你正在执行的是类似转型这样的操作。Class类还拥有大量的使用RTTI的其他方式。类是程序的一部分,每个类都有一个Class对象(每当编写并且编译了一个新类,就会产生一个对象的Class对象/.class文件),为了生成这个类的对象,运行这个程序的Java虚拟机/JVM将使用被称为“类加载器”的子系统。Class对象仅在需要的时候才被加载,static初始化是在类加载时进行的。
类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器(root加载器),它是JVM实现的一部分。Root根加载器/原生加载器从本地盘加载的。所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员引用时,就会加载这个类。这个证明构造器默认也是类的静态方法。因此使用new操作符创建类的新对象也会被当作对类的静态成员的引用。
Class.forName(“Fruit”)------这个方法是Class类(所有Class对象都属于这个类)的一个static成员。Class对象就和其他对象一样,可以获取并操作它的引用。forName( )是取得Class对象的引用的一种方法。它是用一个包含目标类的文本名(注意拼写和大小写)的String作输入参数,返回的是一个Class对象的引用。
Class的newInstance( )方法是实现“虚拟构造器”的一种途径,虚拟构造器允许你声明:“我不知道你的确切类型,但是无论如何要正确地创建你自己!”,使用newInstance( )来创建的类,必须带有默认的构造器。
类字面常量(literal):生成对Class对象的引用,如Fruit.class。 类字面常量不仅可以应用于普通的类,也可以应用接口、数组以及基本数据类型。另外,对于基本数据类型的包装器类,还有一个标准字段TYPE(是一个引用,指向对应的基本类型的Class对象)。
==========================================================================
当使用“.class”来创建对Class对象的引用时,不会自动地初始化该Class对象,为了使用类而做的准备工作实际包含3个步骤:
- 加载,这是由类加载器执行的。该步骤将查找字节码(通常在classpath所指定的路径查找,但这并非是必须的),并从这些字节码中创建一个Class对象。
- 链接。在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用。
- 初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。初始化被延迟到了对静态方法(构造器隐式的是静态的)或者非常数静态域进行首次引用时才执行。
Tips:
- 仅使用.class语法来获得对类的引用不会引发初始化。但是为了产生Class引用,Class.forName()立即就进行了初始化。
- 如果一个static final值是“编译期常量”,不需要对类进行初始化就可以被读取。
- 如果只是将一个域设置为static和final的,则必须强制进行类的初始化后才能访问该变量。
- 如果一个static域不是final的,那么在对它访问是,总是要求在它被读取之前,要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间)
==========================================================================
普通的类引用不会产生警告信息,泛型类引用只能赋值为指向其声明的类型,但是普通的类引用可以被重新赋值为指向任何其他的Class对象。通过使用泛型语法,可以让编译器强制执行额外的类型检查。
- Class<Number> genericNumberClass = int.class; // not work, 因为Interger Class 对象不是Number Class对象的子类
- Class<?> intClass = int.class; // 通配符的好处是它表示你并非是碰巧或者由于疏忽,而使用一个非具体的类引用,就是选择了非具体的版本。
- 为了创建一个被限定为某种类型的Class引用或该类型的任何子类型,将通配符与extends结合:Class<? extends Number> bounded = int.class;
- 向Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查!将泛型语法用于Class对象时,newInstance() 将返回该对象的确切类型。
- getSuperClass( ) 方法返回的是基类(不是接口),并且编译器在编译期就知道它是什么类型了。
==========================================================================
类型转换前先做检查:
- 传统的类型转换,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException异常。
- 代表对象的类型的Class对象,通过查询Class对可以获取运行时所需的信息。
- Instanceof关键字,返回一个布尔值。判断对象是不是某个特定类型的实例?只可将其与命名类型进行比较,而不能与Class对象作比较。
- Equals() 和 == ,instanceof和isInstance()生成结果一样。Instanceof保持了类型的概念:“你是这个类吗,或者你是这个类的派生类吗?”;而用==比较实际的Class对象,就没有考虑继承—它或者是这个确切的类型,或者不是。
=========================================================================
反射:运行时的类信息---提供了一种用来检查可用的方法,并返回方法名的机制。
如果不知道某个对象的确切类型,RTTI可以告诉你。但是有一个限制:这个类型在编译期必须已知,这样才能使用RTTI识别它,并利用这些信息做一些有用的事。换句话说:在编译时,编译器必须知道所有要通过RTTI来处理的类。如假设你获取了一个指向某个并不在你的程序空间中的对象的引用;事实上,在编译时你的程序根本没法获知这个对象所属的类。RAD—快速应用开发,IDE---集成开发环境
人们想要在运行时获取类的信息的另一个动机,便是希望提供在跨网络的远程平台上创建和运行对象的能力—RMI(远程方法调用),它允许一个Java程序将对象分布到多台机器上。
Class类与java.lang.reflect 类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类(每个类都实现了Member接口),这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。
当通过反射与一个未知类型的对象通信时,JVM只是简单地检查这个对象,看它属于哪个特定的类(就像RTTI那样)。在用它做其他事情之前必须先加载那个类的Class对象。因此那个类的.class对象文件对于JVM来说必须是可获取的:要么在本地机器上,要么可以通过网络取得。所以RTTI 和反射之间真正的区别是:
- 对于RTTI来说,编译器在编译时打开和检查.class文件。即可以用“普通”方式调用对象的所有方法
- 对反射机制而言,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。
- Class.forName()生成的结果在编译时是不可知的,因此所有的方法特征签名信息都是在执行时被提取出来的。
===========================================================================
Java动态代理可以动态地创建代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。
通过调用静态方法Proxy.newProxyInstance() 可以创建动态代理,这个方法需要得到一个类加载器(可以从已经被加载的对象中获取其类加载器,然后传递给它),一个希望该代理实现的接口列表(不是类或抽象类),以及InvocaHandler接口的一个实现。动态代理可以将所有调用重定向到调用处理器,因此通常会调用处理器的构造器传递给一个“实际”对象的引用,从而得到调用处理器在执行其中介任务时,可以将请求转发。
Invoke()方法中传递进来了代理对象,以防需要区分请求的来源,但是在很多情况下,并不关心这一点。然而,在invoke方法内部,在代理你上调用方法时需要格外当心,因为对接口的调用将被重定向为对代理的调用。
===========================================================================
空对象:可以接受传递给它的所代表的对象的消息,但是将返回表示为实际上并不存在任何“真实”对象的值。空对象最有用之处在于它更靠近数据,因为对象表示的是问题空间内的实体。
你可以选择使用instanceof来探测泛化的Null还是更具体的NullPerson, 但是由于使用了单例方式,所以还可以只使用equals()甚至==来与Person.Null 比较。
空对象的逻辑变体是模拟对象和桩(DTO), 模拟对象和桩都只是假扮可以传递实际信息的存活对象,而不是像空对象那样可以成为null的更加智能化的替代。区别:
- 模拟对象往往是轻量级和自测试的,通常很多模拟对象被创建出来是为了出来各种不通的测试情况。可以创建大量而简单的模拟对象来做很多事情。
- 桩只是返回桩数据,通常是重量级的,并且经常在测试之间被复用。可以根据它们被调用的方式,通过配置进行修改,因此桩是一种复杂对象。
===========================================================================
*没有任何方法可以阻止反射到达并调用那些非公共访问权限的方法以及private域*
==========================================================================
面向对象编程的语言目的是让凡是可以使用的地方都使用多态机制,只在必需的时使用RTTI。多态机制的方法调用要求拥有基类定义的控制权,如果基类来自别人的类或者由别人控制,这时RTTI便是一种解决之道:可继承一个新类,然后添加你需要的方法。在代码的其他地方,可以检查你自己特定的类型,并调用自己的方法。