1、动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。
2、静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。
Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
Java 语言的反射机制:
反射的使用步骤:
反射相关的主要API
实现功能
优点:
缺点:
类名.class
、类型.getClass()
、Class.forName("类名")
等方法获取Class对象; private Class(ClassLoader loader) {
classLoader = loader;
}
说明:
所有 Java 类(包括Class类)均继承了 Object 类,在 Object 类中定义了一个 getClass() 方法,该方法返回同一个类型为 Class 的对象。
包装类源码:
public static final Class TYPE = (Class) Class.getPrimitiveClass("int");
toString()
public String toString() { return (isInterface() ? "interface " : (isPrimitive() ? "" : "class ")) + getName();}
toString()方法能够将对象转换为字符串,toString()首先判断Class类型是否是接口类型,也就是说普通类和接口都能用Class对象表示,然后在判断是否是基本数据类型,这里判断的都是基本数据类型和包装类,还有void类型。
getName()
获取类的全限定名称。(包括包名)即类的完整名称。
java.lang.String
byte
[Ljava.lang.Object;
getSimpleName()
获取类名(不包括包名)。
getCanonicalName()
获取全限定的类名(包括包名)。
toGenericString()
返回类的全限定名称,而且包括类的修饰符和类型参数信息。
forName()
根据类名获得一个Class对象的引用,这个方法会使类对象进行初始化。
例如:Class t = Class.forName("java.lang.Thread")
就能够初始化一个Thread线程对象。
在Java中,一共有三种获取类实例的方式:
Class.forName(java.lang.Thread)
Thread.class
thread.getClass()
newInstance()
创建一个类的实例,代表着这个类的对象。上面forName()方法对类进行初始化,newInstance方法对类进行实例化。使用该方法创建的类,必须带有无参的构造器。
getClassLoader()
获取类加载器对象。
getInterfaces()
获取当前类实现的类或是接口,可能是多个,所以返回的是Class数组。
isInterface()
判断Class对象是否是表示一个接口。
getFields()
获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods
和getConstructors
。
getDeclaredFields
获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods
和getDeclaredConstructors
。
import java.io.Serializable;
//哪些类型有Class对象
public class AllTypeClass {
public static void main(String[] args) {
Class cls1 = String.class;//外部类
Class cls2 = Serializable.class;//接口
Class cls3 = Integer[].class;//数组
Class cls4 = float[][].class;//二维数组
Class cls5 = Deprecated.class;//注解
//枚举
Class cls6 = Thread.State.class;
Class cls7 = int.class;//基本数据类型
Class cls8 = void.class;//void数据类型
Class cls9 = Class.class;//
System.out.println(cls1); //class java.lang.String
System.out.println(cls2); //interface java.io.Serializable
System.out.println(cls3); //class [Ljava.lang.Integer;
System.out.println(cls4); //class [[F
System.out.println(cls5); //interface java.lang.Deprecated
System.out.println(cls6); //class java.lang.Thread$State
System.out.println(cls7); //int
System.out.println(cls8); //void
System.out.println(cls9); //class java.lang.Class
}
}
Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法;
获取Constructor对象是通过Class类中的方法获取的,Class类与Constructor相关的主要方法如下:
关于Constructor类本身一些常用方法如下(仅部分,其他可查API):
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
//通过反射机制创建实例
public class ReflecCreateInstance {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//1. 先获取到User类的Class对象
Class> userClass = Class.forName("com.hspedu.reflection.User");
//2. 通过public的无参构造器创建实例
Object o = userClass.newInstance();
System.out.println(o);
//3. 通过public的有参构造器创建实例
/*
constructor 对象就是
public User(String name) {//public的有参构造器
this.name = name;
}
*/
//3.1 先得到对应构造器
Constructor> constructor = userClass.getConstructor(String.class);
//3.2 创建实例,并传入实参
Object hsp = constructor.newInstance("hsp");
System.out.println("hsp=" + hsp);
//4. 通过非public的有参构造器创建实例
//4.1 得到private的构造器对象
Constructor> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
//4.2 创建实例
//暴破【暴力破解】 , 使用反射可以访问private构造器/方法/属性
constructor1.setAccessible(true);
Object user2 = constructor1.newInstance(100, "张三丰");
System.out.println("user2=" + user2);
}
}
class User { //User类
private int age = 10;
private String name = "java教育";
public User() {//无参 public
}
public User(String name) {//public的有参构造器
this.name = name;
}
private User(int age, String name) {//private 有参构造器
this.age = age;
this.name = name;
}
public String toString() {
return "User [age=" + age + ", name=" + name + "]";
}
}
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
同样的道理,我们可以通过Class类的提供的方法来获取代表字段信息的Field对象,Class类与Field对象相关方法如下:
关于Field类本身一些常用方法如下(仅部分,其他可查API):
特别注意:被final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。
import java.lang.reflect.Field;
//反射操作属性
public class ReflecAccessProperty {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
//1. 得到Student类对应的 Class对象
Class> stuClass = Class.forName("com.hspedu.reflection.Student");
//2. 创建对象
Object o = stuClass.newInstance();//o 的运行类型就是Student
System.out.println(o.getClass());//Student
//3. 使用反射得到age 属性对象
Field age = stuClass.getField("age");
age.set(o, 88);//通过反射来操作属性
System.out.println(o);//
System.out.println(age.get(o));//返回age属性的值
//4. 使用反射操作name 属性
Field name = stuClass.getDeclaredField("name");
//对name 进行暴破, 可以操作private 属性
name.setAccessible(true);
//name.set(o, "jack");
name.set(null, "jack");//因为name是static属性,因此 o 也可以写出null
System.out.println(o);
System.out.println(name.get(o)); //获取属性值
System.out.println(name.get(null));//获取属性值, 要求name是static
//5.getDeclaredFields:获取本类中所有属性
//规定 说明: 默认修饰符 是0 , public 是1 ,private 是 2 ,protected 是 4 , static 是 8 ,final 是 16
Field[] declaredFields = stuClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类中所有属性=" + declaredField.getName()
+ " 该属性的修饰符值=" + declaredField.getModifiers()
+ " 该属性的类型=" + declaredField.getType());
}
}
}
class Student {//类
public int age;
private static String name;
public Student() {//构造器
}
public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
}
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。
下面是Class类获取Method对象相关的方法:
关于Method类本身一些常用方法如下(仅部分,其他可查API):
Method类的invoke(Object obj,Object… args) 方法:第一个参数代表调用的对象,第二个参数传递的调用方法的参数,从而实现类方法的动态调用。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
//通过反射调用方法
public class ReflecAccessMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
//1. 得到Boss类对应的Class对象
Class> bossCls = Class.forName("com.hspedu.reflection.Boss");
//2. 创建对象
Object o = bossCls.newInstance();
//3. 调用public的hi方法
//Method hi = bossCls.getMethod("hi", String.class);//OK
//3.1 得到hi方法对象
//获取Class对象中的hi方法
Method hi = bossCls.getDeclaredMethod("hi", String.class);//OK
//3.2 调用方法的invoke方法,这里的方法表示hi方法,相当于动态调用bossCls对象的hi方法并传入“韩顺平教育”参数;
hi.invoke(o, "韩顺平教育~");
//4. 调用private static 方法
//4.1 得到 say 方法对象
Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
//4.2 因为say方法是private, 所以需要暴破,原理和前面讲的构造器和属性一样
say.setAccessible(true);
System.out.println(say.invoke(o, 100, "张三", '男'));
//4.3 因为say方法是static的,还可以这样调用 ,可以传入null
System.out.println(say.invoke(null, 200, "李四", '女'));
//5. 在反射中,如果方法有返回值,统一返回Object , 但是他运行类型和方法定义的返回类型一致
Object reVal = say.invoke(null, 300, "王五", '男');
System.out.println("reVal 的运行类型=" + reVal.getClass());//String
//在演示一个返回的案例
Method m1 = bossCls.getDeclaredMethod("m1");
Object reVal2 = m1.invoke(o);
System.out.println("reVal2的运行类型=" + reVal2.getClass());//Monster
}
}
class Monster {}
class Boss {//类
public int age;
private static String name;
public Boss() {//构造器
}
public Monster m1() {
return new Monster();
}
private static String say(int n, String s, char c) {//静态方法
return n + " " + s + " " + c;
}
public void hi(String s) {//普通public方法
System.out.println("hi " + s);
}
}
在java语言中,类的加载、连接和初始化过程都是在程序运行期间完成。
由类加载器将.class文件(源于磁盘文件、网络、数据库、内存或动态产生等)中的二进制字节流读入到JVM中。其中类加载器由JVM提供,开发者也可以通过继承 classLoader基类来创建自己的类加载器。通过使用不同的类加载器可以从不同来源加载类的二进制数据。具体完成一下3项工作:
通过一个类的全限定名获取定义该类的二进制字节流;
将字节流所代表的静态存储结构转化为方法区的运行时数据结构。
在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。
链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
验证:确保加载的类信息符合JVM规范;具体校验为 文件格式验证;元数据验证(是否符合Java语言规范);字节码验证(确定程序语义合法,符合逻辑);符号引用验证(确保下一步的解析能正常执行)。例如:是否以0xCAFEBABE魔数开头等。
准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段。JDK 8及其以后类变量随着Class对象一起存在java堆中。此时初始值“通常情况”下是数据类型的零值。
解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
初始化:直到初始化阶段,java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。
执行类构造器
虚拟机保证子类的
虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
ClassLoader.loadClass(String name) 只会找对应的 class 字节码并加载(Loading)到JVM中,不会干其他的事,例如链接(Linking)、初始化(Initialization)等都不会再进一步处理。
Class.forName(String className) 不仅会找对应的 class 字节码并加载(Loading)到JVM中,还会进行链接(Linking)、初始化(Initialization),执行类的静态代码块和静态变量的初始化。
1)getName加载类
源码:
@CallerSensitive
public static Class> forName(String className)
throws ClassNotFoundException {
Class> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
(1)className:表示我们要加载的类名
(2)true:指Class被加载后是不是必须被初始化。 不初始化就是不执行static的代码即静态代码,在这里默认为true,也就是默认实现类的初始化。
(3)ClassLoader.getClassLoader(caller):表示类加载器;forNanme底层是使用ClassLoader类加载器加载。
(4)caller:指定类加载器。
2)classLoader加载类
流程是先判断class是否已经被加载,如果被加载了那就重新加载,如果没有加载那就使用双亲委派原则加载。加载的时候并没有指定是否要进行初始化;
源码:
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}