類型信息
本章將討論java是如何讓我們在運行時識別對象和類的信息的.主要有兩種方式:一種是"傳統(tǒng)的"RTTI,它假定我們在編譯時已經知道了所有的類型,另一種是"反射"機制,它允許我們在運行時發(fā)現(xiàn)和使用類的信息.
14.1為什么需要RTTI(Run-Time Type identification)
- 如果不知道某個對象的確切類型,RTTI可以告訴你.但是有一個限制:這個類型在編譯時必須已知,這樣才能使用RTTI識別它.例如多態(tài)
14.2Class對象
- 要理解RTTI在java中的工作原理,首先必須知道類型信息在運行時是如何表示的.這項工作是由稱為Class對象的特殊對象完成的,包含了與類有關的信息.事實上,Class對象就是用來創(chuàng)建類的所有的"常規(guī)"對象的.java使用Class對象來執(zhí)行其RTTI,即使你正在執(zhí)行的時類似轉型這樣的操作.
- 類是程序的一部分,每個類都有一個Class對象.換言之,每當編寫并且編譯了一個新類就會產生一個Class對象(被保存在一個同名的.class文件中).為了生成這個類的對象,運行這個程序的Java虛擬機JVM將使用被稱為"類加載器"的子系統(tǒng).
- 所有的類都是在對其第一次使用時,動態(tài)加載到JVM中的.當程序創(chuàng)建第一個對類的靜態(tài)成員的引用時,就會加載這個類.因此證明構造器也是類的靜態(tài)方法,即使在構造器之前并沒有使用static關鍵字.
- 因此,java程序在它開始運行之前并非完全加載,其各個部分是在必需時才加載的.
- 類加載器首先檢查這個類的Class對象是否已經加載.如果沒加載,默認的類加載器就會根據雷鳴查找.class文件.
6. 幾個方法
- Class的newInstance()
public T newInstance()
throws InstantiationException,IllegalAccessException
創(chuàng)建由此類對象表示的類的新實例。 該類被實例化為一個具有空參數列表的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
返回與給定字符串名稱的類或接口相關聯(lián)的類對象。
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"來創(chuàng)建對Class對象的引用時,不會自動地初始化該Class對象.為了使用類而做的準備工作實際包含三個步驟:
- 加載,這是由類加載器執(zhí)行的.該步驟將查找字節(jié)碼,并從這些字節(jié)碼中創(chuàng)建一個Class對象
- 鏈接,在鏈接階段將驗證類中的字節(jié)碼,為靜態(tài)域分配存儲空間,并且如果必須的話,將解析這個類創(chuàng)建的對其他類的所有引用.
- 初始化.如果該類具有超類,則對其初始化,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化塊
14.3類型轉換前先做檢查
14.4注冊工廠
里面含有設計模式一些泛型,現(xiàn)在看不太懂等到看完有一定的理解之后再回來看這兩節(jié)
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作為參數來執(zhí)行類型檢查.然后獲取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類(每個類都實現(xiàn)了Member接口).這些類型的對象是由JVM在運行時創(chuàng)建的,用以表示未知類里對應的成員.這樣你就可以使用Constructor創(chuàng)建新的對象,用get()和set()方法讀取和修改與Field對象關聯(lián)的字段,用invoke()方法調用與Method對象關聯(lián)的方法.另外,還可以調用getFields(),getMethods()和getConstructors()等很便利的方法,以返回表示字段.方法以及構造器的對象的數組.這樣匿名對象的類信息就能在運行時被完全確定下來,而在編譯時不需要知道任何事情.
- RTTI與反射之間真正的區(qū)別只在于,對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
//空對象都是單例,因此這里將其作為靜態(tài)final實例創(chuàng)建
//只能寫到這里了感覺 還是有點不太懂這個設計
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<Position> {
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域實際上在遭遇修改時是安全的.