1. RTTI(런타임 유형 식별) 런타임 유형 식별
1.1 목적:
기본 클래스 포인터가 실제로 가리키는 하위 클래스의 특정 유형을 결정합니다. ——"C++ Primer Plus"
1.2 작동 방식:
형 변환 연산자를 통해 "객체의 주소를 특정 유형의 포인터에 할당하는 것이 안전한가요?"라는 질문에 답하세요. ——"C++ Primer Plus"
1.3 Java에서
Java에서는 모든 유형 변환이 런타임 시 정확성을 검사합니다. 이는 RTTI의 의미이기도 합니다: 런타임에 객체의 유형을 식별합니다.
1.3.1 특정 타입 정보가 손실되는 문제
- 다형성으로 표현된 타입 변환은 RTTI의 가장 기본적인 사용 형태이지만, 이 변환은
-
완전이 아닙니다. 예를 들어, 배열 컨테이너는 실제로 모든 요소를 Object로 보유하고 액세스 시 결과를 자동으로 선언된 유형으로 다시 변환합니다. 배열이 개체를 채울 때(보유) 특정 유형은 선언된 유형의 하위 클래스일 수 있습니다. 배열에 배치되면 선언된 유형으로 업캐스트되고 보류된 개체는 해당 클래스를 잃게 됩니다. 특이성 을 입력하세요. 액세스하면 Object는 특정 하위 클래스 유형이 아닌 선언된 유형으로만 다시 변환되므로 이 변환은 완료되지 않습니다. 다형성은 특정 유형의 동작을 표현하지만 이는 단지 "다형성 메커니즘"의 문제일 뿐이며 참조에서 가리키는
특정 object에 의해 결정되며 런타임과 동일하지 않습니다. 콘크리트 유형 식별. 위의 내용은 특정 유형 정보가 손실된다는 문제를 드러냅니다!
문제가 있을 경우 이를 해결하는 것이 RTTI, 필요합니다. 런타임에 객체의 특정 유형 . 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 클래스 객체
2.1 Java에서 RTTI가 작동하는 방식
To Being 런타임에 특정 유형을 식별할 수 있다는 것은 런타임에 특정 유형 정보를 저장하는 것이 있어야 한다는 것을 의미합니다. 이것이 바로 특수 객체인
Class 객체
입니다. 즉, Class 객체는 클래스 관련 정보를 담고 있는 런타임 타입 정보를 나타냅니다. 사실
Class- 개체는 클래스의 모든 "일반" 개체를
- 생성
하는 데 사용됩니다. 모든 클래스에는
Class 개체가 있습니다. 즉, 새 클래스가 작성되고 컴파일할 때마다 Class 개체 가 생성됩니다(더 적절하게는 동일한 이름의 .class 파일 에 저장 ). 즉,
Class 객체- 는 .java 파일이 .class 파일로 컴파일될 때 생성되고, 이 .
class 파일에 저장됩니다. 2.2 클래스 객체는 객체(일반 객체, 비클래스 객체)를 생성하는 데 사용됩니다.
프로그램을 실행하는 JVM은 소위 "
클래스 로더
" 하위 시스템(클래스 로더 하위 시스템)을 사용하여 클래스 객체 (또는 .class 파일)를 사용하여 클래스 객체를 생성합니다. 모든 클래스는
처음으로- 사용될 때 JVM에 동적으로 로드됩니다. 프로그램이 처음으로 클래스의 정적 멤버를 사용하면 클래스가 로드됩니다. 이는 생성자 앞에 static 키워드가 추가되지 않은 경우에도 생성자도 정적 메서드임을 의미합니다.
-
따라서 Java 프로그램은 실행을 시작하기 전에 완전히 로드되지 않고, 다양한 부분이
로드되어야 하는 경우에만 로드됩니다. (C++와 같은 언어는 정적으로 로드하기 어렵습니다.) 2.3 클래스 로더의 작업(프로세스)
먼저
- check
a 클래스의 Class 객체 (또는 .class 파일 이해) 로드되지 않은 경우 기본 클래스 로더는 클래스 이름을 기반으로 .class 파일을 찾습니다
클래스 객체(.class 파일)가 로드(메모리에 로드)되면 이 클래스의 모든 객체를 create하는데 사용됩니다.
以下程序证实上一点。
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"); } }
以上程序每个类都有一个static子句,static子句在类第一次被加载时执行。
从输出中可以看出,
Class对象仅在需要时才被加载,
static初始化是在类加载时进行的。
Class.forName(net.mrliuli.rtti.Gum)
是Class类的一个静态成员,用来返回一个Class对象的引用(Class对象和其他对象一样,我们可以获取并操作它的引用(这也就是类加载器的工作))。使用这个方法时,如果net.mrliuli.rtti.Gum
还没有被加载就加载它。在加载过程中,Gum的static子句被执行。
总之,无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。
2.4 获得Class对象引用的方法
通过
Class.forName()
,就是一个便捷途径,这种方式不需要为了获得Class对象引用而持有该类型的对象。(即没有创建过或没有这个类型的对象的时候就可以获得Class对象引用。)如果已经有一个类型的对象,那就可以通过调用这个对象的
getClass()
方法来获取它的Class对象引用了。这个方法属于Object,返回表示该对象的实际类型的Class对象引用。
2.5 Class包含的有用的方法
以下程序展示Class包含的很多有用的方法:
getName()
获取类的全限定名称getSimpleName()
获取不含包名的类名getCanonicalName()
获取全限定的类名isInterface()
判断某个Class对象是否是接口getInterfaces()
返回Class对象实现的接口数组getSuperClass()
返回Class对象的直接基类newInstance()
创建一个这个Class对象所代表的类的一个实例对象。Class引用在编译期不具备任何更进一步的类型信息,所以它返回的只是一个Object引用,但是这个Object引用指向的是这个Class引用所代表的具体类型。即需要转型到具体类型才能给它发送Object以外的消息
newInstance()
这个方法依赖于Class对象所代表的类必须具有可访问的默认的构造函数(Nullary constructor,即无参的构造器),否则会抛出InstantiationException
或IllegalAccessException
异常
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
。建议使用这种方法。
编译时就会受到检查(因此不需要放到try语句块中),所以既简单又安全。根除了对
forName()
的调用,所以也更高效。类字面常量
.class
不仅适用于普通的类,也适用于接口、数组和基本类型。基本类型的包装器类有一个标准字段
TYPE
,它是一个引用,指向对应的基本数据类型的Class引用,即有boolean.class
等价于Boolean.TYPE
,int.class
等价于Integer.TYPE
…注意,使用
.class
来创建Class对象的引用时,不会自动地初始化该Class对象。
2.6.2 为了使用类而做的准备工作实际包含三个步骤:
加载,这是由类加载器执行的。该步骤将查找字节码(通常在CLASSPATH所指定的路径中查找),并从这些字节码中创建一个Class对象。
链接。在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用。
初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始块。
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 } }
总结,使用泛型类后
使得编译期进行类型检查
.newInstance()
将返回确切类型的对象,而不是Object
对象
2.7.4 Class
package 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形式包括:
传统类型转换,如
(Shape)
代表对象的类型的Class对象
每三种形式,就是关键字
instanceof
。它返回一个布尔值,告诉我们对象是不是某个特定类型或其子类。如if(x instanceof Dog)
语句会检查对象x
是否从属于Dog
类。还一种形式是动态的instanceof:
Class.isInstance()
方法提供了一种动态地测试对象的途径。Class.isInstance()
方法使我们不再需要instanceof
表达式。
2.7.6 isAssignableFrom()
Class.isAssignableFrom()
:调用类型可以被参数类型赋值,即判断传递进来的参数是否属于调用类型继承结构(是调用类型或调用类型的子类)。
3 注册工厂
4 instanceof 与 Class 的等价性
instanceof
和isInstance()
保持了类型的概念,它指的是“你是这个类吗,或者你是这个类的派生类吗?”==
和equals()
没有考虑继承——它要么是这个确切的类型,要么不是。
5 反射:运行时的类信息(Reflection: runtime class information)
Class
类与 java.lang.reflect
类库一起对反射的概念进行了支持。
RTTI与反射的真正区别在于:
对于RTTI来说,是编译时打开和检查.class文件。(换句话说,我们可以用“普通”方式调用对象的所有方法。)
对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。
6 动态代理
Java的动态代理比代理的思想更向前迈进了一步,因为它可以动态地创建代理并动态地处理对所代理方法的调用。
在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。
通过调用静态方法
Proxy.newProxyInstance()
可以创建动态代理,需要三个参数:ClassLoader loader
一个类加载器,通常可以从已经被加载的对象中获取其类加载器Class>[] interfaces
一个希望代理要实现的接口列表(不是类或抽象类)InvocationHandler h
一个调用处理器接口的实现动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器传递一个“实际”对象(即被代理的对象)的引用,从而使得调用处理器在执行其中介任务时,可以将请求转发(即去调用实际对象)。
6.1 动态代理的优点及美中不足
优点:动态代理与静态代理相较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法(
InvocationHandler.invoke
)中处理。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。美中不足:它始终无法摆脱仅支持
interface
代理的桎梏,因为它的设计注定了这个遗憾。
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()"); } }
相关文章:
Java 프로그래밍 사고 학습 수업(3) 15장 - Generics
위 내용은 자바 프로그래밍 생각 학습 수업(2) 14장 유형 정보의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于结构化数据处理开源库SPL的相关问题,下面就一起来看一下java下理想的结构化数据处理类库,希望对大家有帮助。

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于PriorityQueue优先级队列的相关知识,Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,下面一起来看一下,希望对大家有帮助。

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于java锁的相关问题,包括了独占锁、悲观锁、乐观锁、共享锁等等内容,下面一起来看一下,希望对大家有帮助。

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于多线程的相关问题,包括了线程安装、线程加锁与线程不安全的原因、线程安全的标准类等等内容,希望对大家有帮助。

本篇文章给大家带来了关于Java的相关知识,其中主要介绍了关于关键字中this和super的相关问题,以及他们的一些区别,下面一起来看一下,希望对大家有帮助。

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于枚举的相关问题,包括了枚举的基本操作、集合类对枚举的支持等等内容,下面一起来看一下,希望对大家有帮助。

封装是一种信息隐藏技术,是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法;封装可以被认为是一个保护屏障,防止指定类的代码和数据被外部类定义的代码随机访问。封装可以通过关键字private,protected和public实现。

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于设计模式的相关问题,主要将装饰器模式的相关内容,指在不改变现有对象结构的情况下,动态地给该对象增加一些职责的模式,希望对大家有帮助。


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

SublimeText3 Linux 새 버전
SublimeText3 Linux 최신 버전

SecList
SecLists는 최고의 보안 테스터의 동반자입니다. 보안 평가 시 자주 사용되는 다양한 유형의 목록을 한 곳에 모아 놓은 것입니다. SecLists는 보안 테스터에게 필요할 수 있는 모든 목록을 편리하게 제공하여 보안 테스트를 더욱 효율적이고 생산적으로 만드는 데 도움이 됩니다. 목록 유형에는 사용자 이름, 비밀번호, URL, 퍼징 페이로드, 민감한 데이터 패턴, 웹 셸 등이 포함됩니다. 테스터는 이 저장소를 새로운 테스트 시스템으로 간단히 가져올 수 있으며 필요한 모든 유형의 목록에 액세스할 수 있습니다.

WebStorm Mac 버전
유용한 JavaScript 개발 도구

SublimeText3 영어 버전
권장 사항: Win 버전, 코드 프롬프트 지원!
