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<Integer> TYPE = (Class<Integer>) 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<String> cls1 = String.class;//外部类
Class<Serializable> cls2 = Serializable.class;//接口
Class<Integer[]> cls3 = Integer[].class;//数组
Class<float[][]> cls4 = float[][].class;//二维数组
Class<Deprecated> cls5 = Deprecated.class;//注解
//枚举
Class<Thread.State> cls6 = Thread.State.class;
Class<Long> cls7 = int.class;//基本数据类型
Class<Void> cls8 = void.class;//void数据类型
Class<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程序代码,将主导权移交给应用程序。
执行类构造器<clinit>()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
虚拟机保证子类的<clinit>()方法执行前,父类的<clinit>()方法已经执行完毕(接口除外);【<clinit>()方法对于类或接口来说并非必需的】
虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
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;
}