Java——反射:运行时的类信息
- RTTI的使用
如果不知道某个对象的确切类型,RTTI会告诉我们,但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,并利用这些信息做一些有用的事情。
2.什么情况下需要反射
假设你获取了一个指向某个并不在你的程序空间的对象的引用;或者,你从磁盘文件,或者网络连接中获取了一串字节,并且你被告知这些字节代表一个类,想要使用这些类就需要反射。
3.反射和RTTI的区别
当通过反射与一个未知类型的对象打交道的时候,JVM只是简单的检查这个对象,看它属于那个特定的类。然后在使用这个对象之前先加载那个类的class对象,这个.class要么从本地机器获得,要么从网络中获得。RTTI和反射的区别就是,对RTTI来说,编译器在编译时打开和检查.class文件,而对于反射来说,.class在编译时是不可获取的,是在运行的时候打开和检查.class文件。
Class类和java.lang.reflect类库一起对反射的概念进行了支持,该类库包括Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。
一、类方法提取器
import java.lang.reflect.*;
import java.util.*;
import java.util.regex.*;
class Ex1{
public Ex1(){
}
public void fun() {
System.out.println("fun()");
}
}
public class Ex {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String name = in.nextLine();
try {
Class<?> c = Class.forName(name);
Method[] methods = c.getMethods();
Constructor[] ctors = c.getConstructors();
System.out.println("methods:");
for(Method method: methods) {
System.out.println(method.toString());
}
System.out.println();
System.out.println("Constructor:");
for(Constructor ctor:ctors) {
System.out.println(ctor.toString());
}
}catch(ClassNotFoundException e) {
System.out.println("ClassNotfound");
}
}
}
输出:
Ex1
methods:
public void Ex1.fun()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll() Constructor:
public Ex1()
Class.forName()生成的结果是编译时未知的,然后Class的getMethods()和getConstructors()分别返回Method对象的数组和Constructors对象的数组。Constructors就是构造器。
2、动态代理
代理: 代理模式(Proxy)就是为一个对象创建一个替身,用来控制对当前对象的访问。目的就是为了在不直接操作对象的前提下对对象进行访问。
interface Interface{
void doSomething();
void SomethingElse(String arg);
}
class RealObject implements Interface{
public void doSomething() {
System.out.println("doSomething1");
}
public void SomethingElse(String arg) {
System.out.println("SomethingElse1" + arg);
}
}
class SimpleProxy implements Interface{
private Interface proxied;
public SimpleProxy(Interface proxied) {
this.proxied = proxied;
}
public void doSomething() {
System.out.println("doSomething2");
proxied.doSomething();
}
public void SomethingElse(String arg) {
System.out.println("SomethingElse2" + arg);
proxied.SomethingElse(arg);
}
}
public class Ex2 {
public static void consumer(Interface iface) {
iface.doSomething();
iface.SomethingElse("xxxx");
}
public static void main(String[] args) {
consumer(new RealObject());
consumer(new SimpleProxy(new RealObject()));
}
}
输出:
doSomething1
SomethingElse1xxxx
doSomething2
doSomething1
SomethingElse2xxxx
SomethingElse1xxxx
这里的SimpleProxy和RealObject都实现了Interface,然后在SimpleProxy中创建一个RealObject的替身,用来控制对RealObject对象的访问,这样的使用称为代理模式。
代理模式的好处就是在某些情况下,一个客户不想或者不能直接引用一个对象,代理对象就再客户端和被代理对象之间起到中介的作用。就好比你在北京租房,初来乍到,人生地不熟,找房子遍地都是中介,想找房东可没那么容易(基本算得上是找不到房东直租的)。问题来了,找不到房东直租,但房子又是一切的基础,so....走中介,房东能租房,中介也能租房,不过是你通过中介去将房东的房子租给自己。OK,这就是一个活生生的代理模式的例子,相必在外漂泊的小年轻们都感同身受吧。
这样的代理在运行之前,就确定好代理类、被代理类之间的关系,称之为静态代理。像上面的代码就是一个中介只代理一个房东,如果想增加中介代理的房东就需要额外的编写代码所以出现了动态代理
Java的动态代理可以动态地创建代理并动态地处理对代理方法的调用。动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作就是揭示调用的类型并确定相应的对策。
import java.lang.reflect.*;
class DynamicProxyHandler implements InvocationHandler{
private Object proxied;
public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(proxied, args);
return null;
}
}
public class Ex3 {
public static void consumer(Interface iface) {
iface.doSomething();
iface.SomethingElse("xxxx");
}
public static void main(String[] args) {
RealObject real = new RealObject();
SimpleProxy por = new SimpleProxy(real);
Interface proxy1 = (Interface)Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[] { Interface.class },
new DynamicProxyHandler(real));
consumer(proxy1);
System.out.println();
Interface proxy2 = (Interface)Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[] { Interface.class },
new DynamicProxyHandler(por));
consumer(proxy2);
}
}
首先通过实现InvocationHandler接口创建动态代理类,实现代码运行过程中对各种真实对象的动态代理,构造器中将传入一个想要代理的类对象,在应该被实现的invoke方法中通过method.invoke(proxied, args)实现代理的类的方法
然后在main中调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要得到一个类加载器,一个希望该代理实现的接口列表,以及一个InvocationHandler接口的一个实现。
输出:
doSomething1
SomethingElse1xxxx doSomething2
doSomething1
SomethingElse2xxxx
SomethingElse1xxxx
3、空对象
有时引入空对象的思想会很有用,它可以接受传递给它的所代表的对象的信息,但是返回值表示为实际上并不存在的任何“真实”对象的值,假如我们需要查询某个学生的信息,我们输入学号来进行查询,如果没有这个学生的话,我们就可以返回一个空对象。
interface Null{}
class Person{
public final String name;
public Person(String name){
this.name = name;
}
public String toString() {
return "the person is" + name;
}
public static class NullPerson extends Person implements Null{
private NullPerson() {
super("None");
}
public String toString() {
return "NullPerson";
}
}
public static final Person NULL = new NullPerson();
}
先创建一个标志接口,然后实现这个接口,并继承Person类,将name设为None,最后在创建一个静态final的NULL的Person对象。这个NULL就是一个空对象,使用是可以直接使用equals或者是==来与Person.NULL进行比较,还可以选择使用instanceof来探测泛化的NULL还是更具体的NullPerson。
5、接口与类型信息
interface A{
public void f();
}
class B implements A{
public void f() {
System.out.println("f()");
}
public void g() {
System.out.println("g()");
}
}
public class Ex5 {
public static void main(String[] args) {
A a = new B();
System.out.println(a.getClass().getName());
if(a instanceof B) {
B b = (B) a;
b.g();
}
}
}
输出:
B
g()
这里B是一个实现A接口的类,在main中我们可以发现a是被当作B实现的,然后我们还可以通过转型为B从而调用g()方法。这样会试代码的耦合程度超过你的期望,最简单的方法是实现使用包访问权限,在包外部的就不能使用它。
interface A{
public void f();
}
class B implements A{
public void f() {
System.out.println("f()");
}
public void g() {
System.out.println("g()");
}
}
public class Ex5 {
public static A makeA() {
return new B();
}
}
这个class中只有Ex5部分是public的,这个方法将返回一个当作B实现的A,
public class Ex6 {
public static void main(String[] args) throws Exception {
A a = Ex5.makeA();
a.f();
System.out.println(a.getClass().getName());
/*
* B = (B) a
* 当试图将其向下转型为B时,则将会被禁止,因为没有任何的B类型可以用
*/
}
}
但其实通过反射还是能够调用所有的方法的:
import java.lang.reflect.Method;
public class Ex6 {
public static void main(String[] args) throws Exception {
A a = Ex5.makeA();
a.f();
System.out.println(a.getClass().getName());
callEx5Method(a,"g");
}
static void callEx5Method(Object a, String MethodName) throws Exception {
Method g = a.getClass().getDeclaredMethod(MethodName);
g.setAccessible(true);
g.invoke(a);
}
}
callEx5Method方法传入一个对象,和一个String类型的MethodName(方法名),通过调用a.getClass().getDeclaredMethod(MethodName)传入方法名获得想要调用的方法,然后调用setAccessible()并传入true这样就可以通过反射访问私有方法,最后使用invoke()运行该方法。
输出:
f()
B
g()
Java——反射:运行时的类信息的更多相关文章
- Java 反射 —— 运行时的类型信息
1. 反射机制的由来 RTTI 机制可以告知某个对象的确切类型,但有一个前提,该类型在编译时必须已知(编译器在编译时打开和检查 .class 文件以获取类型信息).似乎是个很宽松的限制,但假如你获取了 ...
- java 查看运行时某个类文件所在jar的位置
在一些大型项目中,项目所依赖的库可能比较到,有时候也会出现库冲突的情况,曾经遇到过一种情况:一个第三方云存储提供了一个sdk,这个sdk本身依赖httpclient相关的包,然而对方却把httpcli ...
- Java如何在运行时识别类型信息?
在日常的学习工作当中,有一些知识是我们在读书的时候就能够习得:但有一些知识不是的,需要在实践的时候才能得到真知——这或许就是王阳明提倡的“知行合一”. 在Java中,并不是所有的类型信息都能在编译阶段 ...
- Java虚拟机运行时栈帧结构--《深入理解Java虚拟机》学习笔记及个人理解(二)
Java虚拟机运行时栈帧结构(周志明书上P237页) 栈帧是什么? 栈帧是一种数据结构,用于虚拟机进行方法的调用和执行. 栈帧是虚拟机栈的栈元素,也就是入栈和出栈的一个单元. 2018.1.2更新(在 ...
- 《深入理解Java虚拟机》(二)Java虚拟机运行时数据区
Java虚拟机运行时数据区 详解 2.1 概述 本文参考的是周志明的 <深入理解Java虚拟机>第二章 ,为了整理思路,简单记录一下,方便后期查阅. 2.2 运行时数据区域 Java虚拟机 ...
- Java虚拟机运行时数据区
运行时数据区程序计数器Java虚拟机栈本地方法栈Java堆(GC堆)方法区运行时常量池 运行时数据区 Java虚拟机在运行Java程序时,会将它所管理的内存划分为若干个内存区域.这些数据区域有各自的用 ...
- java程序运行时内存分配详解
java程序运行时内存分配详解 这篇文章主要介绍了java程序运行时内存分配详解 ,需要的朋友可以参考下 一. 基本概念 每运行一个java程序会产生一个java进程,每个java进程可能包含一个 ...
- Java程序运行时内存划分
1.Java程序跨平台运行的原因 主要原因是:各种平台的JVM和字节码文件 Java源程序--具体平台的机器代码文件---被编译器翻译成平台无关的Class文件,又用特定JVM运行字节码文件,JVM在 ...
- 关于Java虚拟机运行时数据区域的总结
Java虚拟机运行时数据区域 程序计数器(Program Counter) 程序计数器作为一个概念模型,这个是用来指示下一条需要执行的字节码指令在哪. Java的多线程实际上是通过线程轮转做到的,如果 ...
随机推荐
- 未能加载文件或程序集“Seagull.BarTender.Print, Version=11.0.8.1, Culture=neutral, PublicKeyToken=109ff779a1b4cbc7
这2天项目上需要使用BarTender打印软件,使用BarTender的库的时候时候发现一个特别的问题: 未能加载文件或程序集“Seagull.BarTender.Print, Version=11. ...
- 简书全站爬取 mysql异步保存
# 简书网 # 数据保存在mysql中; 将selenium+chromedriver集成到scrapy; 整个网站数据爬取 # 抓取ajax数据 #爬虫文件 # -*- coding: utf-8 ...
- c++ 逆序对
c++ 求逆序对 例如数组(3,1,4,5,2)的逆序对有(3,1)(3,2)(4,2)(5,2)共4个 逆序对就是左边的元素比右边的大,那么左边的元素和右边的元素就能产生逆序对 代码跟归并排序差不多 ...
- kuangbin专题 专题一 简单搜索 Prime Path POJ - 3126
题目链接:https://vjudge.net/problem/POJ-3126 题意:给你两个四位的素数N,M,每次改变N四位数中的其中一位,如果能经过有限次数的替换变成四位数M,那么求出最少替换次 ...
- 关系型数据库MySql 数据类型与约束
MySql数据库 :数据类型与约束 注意 : 在创建数据表的时候,需要对数据表中的字段设置 数据类型和约束, 便于检测用户输入的数据是否正确有效. 1 数据类型 数据类型的选用原则 : 够用就行,尽 ...
- I/O:ByteBuffer
ByteBuffer: static ByteBuffer allocate(int capacity) :分配一个新的字节缓冲区. static ByteBuffer allocateDirect( ...
- ElasticSearch7.2安装
1.环境 Java -version:java11 centos: 7.2 elasticsearch: 7.2 2.获取压缩包 wget https://artifacts.elastic.co/d ...
- redis集群(单机6节点实现)
Redis集群搭建与简单使用 1.介绍安装环境与版本: 1)Redis使用的是Redis-3.2.8版本. 2)用一台虚拟机模拟6个节点,三个master节点,三个slave节点.虚拟机使用CentO ...
- YuniKorn 介绍
一.YuniKorn 简介 YuniKorn 是一种轻量级的通用资源调度程序,适用于容器编排系统.它的创建是为了一方面在大规模,多租户环境中有效地实现各种工作负载的细粒度资源共享,另一方面可以动态地创 ...
- ForkJoinPool分支/合并框架工程使用的工作窃取
ForkJoinPool分支/合并框架 在必要的情况下,讲一个大任务,进行拆分(fork)成若干个小任务(拆到不可拆为止),再将一个个小的任务运算的结果进行join汇总. 工作窃取的背景 分支/合并框 ...