黑马程序员技术交流社区

标题: 【上海校区】Java编程思想(二) [打印本页]

作者: 不二晨    时间: 2018-9-7 09:53
标题: 【上海校区】Java编程思想(二)
1. RTTI(Runtime Type Identification)运行阶段类型识别1.1 用途:
  为了确定基类指针实际指向的子类的具体类型。——《C++ Primer Plus》
1.2 工作原理:
  通过类型转换运算符回答“是否可以安全地将对象的地址赋给特定类型的指针”这样的问题。——《C++ Primer Plus》
1.3 Java中
  在Java中,所有的类型转换都是在运行时进行正确性检查的。这也是RTTI的含义:在运行时,识别一个对象的类型。
1.3.1 丢失具体类型信息的问题1.3.2 证实具体类型信息的丢失

  以下示例证实了上面描述的问题(具体类型信息的丢失):

package net.mrliuli.rtti;import java.util.Arrays;import java.util.List;/** * Created by leon on 2017/12/3. */abstract class Shape{    void draw(){        System.out.println(this + ".draw()");    }    abstract public String toString();  //要求子类需要实现 toString()}class Circle extends Shape{    @Override    public String toString() {        return "Circle";    }    public void drawCircle(){}}class Square extends Shape{    @Override    public String toString() {        return "Square";    }}class Triangle extends Shape{    @Override    public String toString() {        return "Triangle";    }}public class Shapes {    public static  void main(String[] args){        List<Shape> shapeList = Arrays.asList(                new Circle(), new Square(), new Triangle()  // 向上转型为 Shape,此处会丢失原来的具体类型信息!!对于数组而言,它们只是Shape类对象!        );        for(Shape shape : shapeList){            shape.draw();   // 数组实际上将所有事物都当作Object持有,在取用时会自动将结果转型回声明类型即Shape。        }        //shapeList.get(0).drawCircle(); //这里会编译错误:在Shape类中找不到符号drawCircle(),证实了具体类型信息的丢失!!    }}2 Class对象2.1 RTTI在Java中的工作原理

  要能够在运行时识别具体类型,说明必然有东西在运行时保存了具体类型信息,这个东西就是Class对象,一种特殊对象。即Class对象表示了运行时的类型信息,它包含了与类有关的信息。

2.2 Class对象用来生成对象(常规对象,非Class对象)

  运行程序的JVM使用所谓的“类加载器”的子系统(class loader subsystem)通过加载Class对象(或者说.class文件)来生成一个类的对象。

2.3 类加载器的工作(过程)

  以下程序证实上一点。

package net.mrliuli.rtti;/** * Created by leon on 2017/12/3. */class Candy{    static { System.out.println("Loading Candy"); }}class Gum{    static { System.out.println("Loading Gum"); }}class Cookie{    static { System.out.println("Loading Cookie"); }}public class SweetShop {    public static void main(String[] args){        System.out.println("inside main");        new Candy();        System.out.println("After creating Candy");        try{            Class.forName("net.mrliuli.rtti.Gum");        }catch (ClassNotFoundException e){            System.out.println("Couldn't find Gum");        }        System.out.println("After Class.forName(\"Gum\")");        new Cookie();        System.out.println("After creating Cookie");    }}

  总之,无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用

2.4 获得Class对象引用的方法2.5 Class包含的有用的方法

  以下程序展示Class包含的很多有用的方法:

package net.mrliuli.rtti;/** * Created by li.liu on 2017/12/4. */interface HasBatteries{}interface Waterproof{}interface Shoots{}class Toy{    Toy(){}    Toy(int i){}}class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots{    FancyToy(){ super(1); }}public class ToyTest {    static void printInfo(Class cc){        System.out.println("Class name: " + cc.getName() + " is interface? [" + cc.isInterface() + "]");        System.out.println("Simple name: " + cc.getSimpleName());        System.out.println("Canonical name: " + cc.getCanonicalName());    }    public static  void main(String[] args){        Class c = null;        try{            c = Class.forName("net.mrliuli.rtti.FancyToy");        }catch (ClassNotFoundException e){            System.out.println("Can't find FancyToy");            System.exit(1);        }        printInfo(c);        System.out.println("=============================");        for(Class face : c.getInterfaces()){            printInfo(face);        }        System.out.println("=============================");        Class up = c.getSuperclass();        Object obj = null;        try{            // Requires default constructor:            obj = up.newInstance();        }catch (InstantiationException e){            System.out.println("Cannot instantiate");            System.exit(1);        }catch (IllegalAccessException e){            System.out.println("Cannot access");            System.exit(1);        }        printInfo(obj.getClass());    }}2.6 类字面常量  2.6.1 使用类字面常量.class是获取Class对象引用的另一种方法。如 FancyToy.class。建议使用这种方法。  2.6.2 为了使用类而做的准备工作实际包含三个步骤:  2.6.3 初始化惰性

  初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行,即初始化有效地实现了尽可能 的“惰性”。
  以下程序证实了上述观点。注意,将一个域设置为static 和 final的,不足以成为“编译期常量”或“常数静态域”,如static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);就不是编译期常量,对它的引用将强制进行类的初始化。

package net.mrliuli.rtti;import java.util.Random;class Initable{    static final int staticFinal = 47;      // 常数静态域    static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);     // 非常数静态域(不是编译期常量)    static{        System.out.println("Initializing Initable");    }}class Initable2{    static int staticNonFinal = 147;    // 非常数静态域    static {        System.out.println("Initializing Initable2");    }}class Initable3{    static int staticNonFinal = 74;     // 非常数静态域    static {        System.out.println("Initializing Initable3");    }}public class ClassInitialization {    public static Random rand = new Random(47);    public static void main(String[] args) throws Exception {        Class initalbe = Initable.class;                // 使用类字面常量.class获取Class对象引用,不会初始化        System.out.println("After creating Initable ref");        System.out.println(Initable.staticFinal);       // 常数静态域首次引用,不会初始化        System.out.println(Initable.staticFinal2);      // 非常数静态域首次引用,会初始化        System.out.println(Initable2.staticNonFinal);   // 非常数静态域首次引用,会初始化        Class initable3 = Class.forName("net.mrliuli.rtti.Initable3");      // 使用Class.forName()获取Class对象引用,会初始化        System.out.println("After creating Initable3 ref");        System.out.println(Initable3.staticNonFinal);   // 已初始化过    }}2.7 泛化的Class引用2.7.1 Class对象类型限制

  Class引用总是指向某个Class对象,此时,这个Class对象可以是各种类型的,当使用泛型语法对Class引用所指向的Class对象的类型进行限定时,这就使得Class对象的类型变得具体,这样编译器编译时也会做一些额外的类型检查工作。如

package net.mrliuli.rtti;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, genericIntClass 限制为Integer 的Class对象    }}2.7.2 使用通配符?放松对Class对象类型的限制

  通配符?是Java泛型的一部分,?表示“任何事物”。以下程序中Class<?> intClass = int.class; 与 Class intClass = int.class; 是等价的,但使用Class<?>优于使用Class,因为它说明了你是明确要使用一个非具体的类引用,才选择了一个非具体的版本,而不是由于你的疏忽。

package net.mrliuli.rtti;/** * Created by li.liu on 2017/12/4. */public class WildcardClassReferences {    public static void main(String[] args){        Class<?> intClass = int.class;        intClass = double.class;    }}2.7.3 类型范围

  将通配符与extends关键字相结合如Class<? extends Number>,就创建了一个范围,使得这个Class引用被限定为Number类型或其子类型。

package net.mrliuli.rtti;/** * Created by li.liu on 2017/12/4. */public class BoundedClassReferences {    public static void main(String[] args){        Class<? extends Number> bounded = int.class;        bounded = double.class;        bounded = Number.class;        // Or anything derived from Number    }}

  泛型类语法示例:

package net.mrliuli.rtti;import java.util.ArrayList;import java.util.List;/** * Created by li.liu on 2017/12/4. */class CountedInteger{    private static long counter;    private final long id = counter++;    public String toString(){        return Long.toString(id);    }}public class FilledList<T> {    private Class<T> type;    public FilledList(Class<T> type){        this.type = type;    }    public List<T> create(int nElements){        List<T> result = new ArrayList<T>();        try{            for(int i = 0; i < nElements; i++){                result.add(type.newInstance());            }        }catch(Exception e){            throw new RuntimeException(e);        }        return result;    }    public static void main(String[] args){        FilledList<CountedInteger> fl = new FilledList<CountedInteger>(CountedInteger.class);   // 存储一个类引用        System.out.println(fl.create(15));      // 产生一个list    }}

  总结,使用泛型类后

2.7.4 Classpackage net.mrliuli.rtti;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:        // Toy toy = up.newInstance();        // Class<Toy> up2 = up.getSuperclass();     // 这里 getSuperclass() 已经知道结果是Toy.class了,却不能赋给 Class<Toy>,这就是所谓的含糊性(vagueness)        // Only produces Object:    (because of the vagueness)        Object obj = up.newInstance();    }}2.7.5 类型转换前先做检查

  RTTI形式包括:

2.7.6 isAssignableFrom()

  Class.isAssignableFrom() :调用类型可以被参数类型赋值,即判断传递进来的参数是否属于调用类型继承结构(是调用类型或调用类型的子类)。

3 注册工厂4 instanceof 与 Class 的等价性5 反射:运行时的类信息(Reflection: runtime class information)

  Class类与 java.lang.reflect类库一起对反射的概念进行了支持。

  RTTI与反射的真正区别在于:

6 动态代理6.1 动态代理的优点及美中不足7. 空对象7.1 YAGNI

   极限编程(XP)的原则之一,YAGNI(You Aren’t Going to Need It,你永不需要它),即“做可以工作的最简单的事情”。

7.2 模拟对象与桩(Mock Objects & Stubs)

  空对象的逻辑变体是模拟对象

8. 接口与类型信息
通过使用反射,可以到达并调用一个类的所有方法,包括私有方法!如果知道方法名,就可以在其Method对象上调用setAccessible(true),然后访问私有方法。

  以下命令显示类的所有成员,包括私有成员。-private标志表示所有成员都显示。

javap -private 类名

  因此任何人都可以获取你最私有的方法的名字和签名,即使这个类是私有内部类或是匿名内部类。

package net.mrliuli.rtti;/** * Created by li.liu on 2017/12/6. */import java.lang.reflect.Method;/** * 通过反射调用所有方法(包括私有的) */public class HiddenImplementation {    static void callHiddenMethod(Object obj, String methodName, Object[] args) throws Exception{        Method method = obj.getClass().getDeclaredMethod(methodName);        method.setAccessible(true);        method.invoke(obj, args);    }    public static void main(String[] args) throws Exception{        callHiddenMethod(new B(), "g", null);    }}interface A {    void f();}class B implements A{    @Override    public void f(){}    private void g(){        System.out.println("B.g()");    }}

作者: 不二晨    时间: 2018-9-13 16:09

很不错,受教了




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2