1:反射
就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个 类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可 以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看 到类的结构,所以,我们形象的称之为:反射。
基本反射技术:
ClassLoader.loadClass() 与 Class.forName()的区别?
Class.forName 内部调用方法 forName(className,true,classloader);,第二个参数为 true,表示类需要初始化,一旦初始化就会触发目标对象的 static 方法。但 loadClass 不会。
LoadClass 内部调用的是 loadClass(className,false);第二个参数为 false,表示目标对象被装载后不进行链接,不链接就不进行初始化操作,目标对象静态块和静态对象就不会执行。
根据一个字符串得到一个类
getClass 方法
String name = “Huanglinqing”;
Class c1 = name.getClass();
System.out.println(c1.getName());
//输出
//java.lang.String
Class.forName
String name = “java.lang.String”;
Class c1 = null;
try {
c1 = Class.forName(name);
System.out.println(c1.getName());
} catch (ClassNotFoundException e) {
}
//输出
//java.lang.String
从这个简单的例子可以看出,一般情况下我们使用反射获取一个对象的步骤:
- 获取类的 Class 对象实例
Class clz = Class.forName(“com.zhenai.api.Apple”);
- 根据 Class 对象实例获取 Constructor 对象
Constructor appleConstructor = clz.getConstructor();
- 使用 Constructor 对象的 newInstance 方法获取反射类对象
Object appleObj = appleConstructor.newInstance();
而如果要调用某一个方法,则需要经过下面的步骤:
- 获取方法的 Method 对象
Method setPriceMethod = clz.getMethod(“setPrice”, int.class);
- 利用 invoke 方法调用方法
setPriceMethod.invoke(appleObj, 14);
获取类的成员
当类中方法定义为私有的时候我们能调用?不能!当变量是私有的时候我们能获取吗?不能!但是反射可以。比如源码中有你需要用到的方法,但是那个方法是私有的,这个时候你就可以通过反射去执行这个私有方法,并且获取私有变量。
获取类的构造函数
反射 API
获取反射中的 Class 对象
在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象。
在 Java API 中,获取 Class 类对象有三种方法:
第一种,使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。
Class clz = Class.forName(“java.lang.String”);
第二种,使用 .class 方法。
这种方法只适合在编译前就知道操作的 Class。
Class clz = String.class;
第三种,使用类对象的 getClass() 方法。
String str = new String(“Hello”);
Class clz = str.getClass();
通过反射创建类对象
通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。
第一种:通过 Class 对象的 newInstance() 方法。
Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance();
第二种:通过 Constructor 对象的 newInstance() 方法
Class clz = Apple.class;
Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();
通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。
Class clz = Apple.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Apple apple = (Apple)constructor.newInstance(“红富士”, 15);
通过反射获取类属性、方法、构造器
我们通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性。
Class clz = Apple.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
输出结果是:
price
而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性:
Class clz = Apple.class;
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
输出结果是:
name
price
与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。
源码解析
异常报错时的输出
可以看到异常堆栈指出了异常在 Method 的第 497 的 invoke 方法中,其实这里指的 invoke 方法就是我们反射调用方法中的 invoke。
Method method = clz.getMethod(“setPrice”, int.class);
method.invoke(object, 4); //就是这里的invoke方法
允许程序在执行期借助于反射 API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类后,在堆内存的方法区就产生一个 Class 类型的对象(一个类只有一个 class 对象)
1:Class 实例
在 Object 类中定义了方法
Public final Class getClass()
返回一个 Class 类,它是 Java 反射的源头
Class 的常用方法
2:类的加载与 ClassLoader
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤对该类进行初始化
类加载器的作用:
类加载的作用:将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的 java.lang.Class 对象,作为方法区中类数据的访问入口。
类缓存:标准的 JavaSE 类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过 JVM 垃圾回收机制可以回收这些 Class 对象。
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
ClassLoader 与 Class.forName 的区别?
(1)class.forName()除了将类的.class 文件加载到 jvm 中之外,还会对类进行解释,执行类中的 static 块。当然还可以指定是否执行静态块。
(2)classLoader 只干一件事情,就是将.class 文件加载到 jvm 中,不会执行 static 中的内容,只有在 newInstance 才会去执行 static 块。
反射的应用:动态代理
反射机制:
1:代理模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及何时将方法转移到原始对象
与静态代理的区别?
在程序运行前,代理类的.class 文件就已经存在了,代理类和委托类的关系在运行前就已经确定了。
缺点:代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,撕逼每个方法都要进行代理,静态代理的程序规模稍大时就不行了。
2:静态代理:
2.1 举例:实现 Runnable 接口的方法创建多线程
Class MyThread implements Runnable{};//想当与被代理类
Class Thread implements Runnable{}//相当于代理类
Main(){
Mythread t = new MyThread();
Thread thread = new Thread(t);
Thread.start();//启动线程,调用线程的 run();
}
3:枚举
枚举类对象的属性不允许被改动,应该使用 private final 修饰,且应在构造器中为其赋值。私有化构造器,保证不能在类外部创建对象
方法:
4:注解 Annotation
类型
内置注解:JDK 内置的三个基本注解)
- @Override: 限定重写父类方法, 该注解只能用于方法
- @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
- @SuppressWarnings: 抑制编译器警告
元注解:负责解释其他注解
- @Target:用于描述注解的使用范围,即:被描述的注解可以在什么地方使用
JDK1.8 以后参数 ElementType 多了两个枚举值为 TYPE_PARAMETER,USE
- @Retention:表示需要什么保存该注释信息,用于描述注解的生命周期
- 级别范围:Source < Class < Runtime
- @Document:说明该注解被包含在 java doc 中
- @Inherited:说明子类可以集成父类中的注解
/
定义一个注解
/
@Target(value={ElementType.METHOD, ElementType.TYPE}) // target表示我们注解应用的范围,在方法上,和类上有效
@Retention(RetentionPolicy.RUNTIME) // Retention:表示我们的注解在什么时候还有效,运行时候有效
@Documented // 表示说我们的注解是否生成在java doc中
@Inherited // 表示子类可以继承父类的注解
@interface MyAnnotation {
}
自定义注解:使用 @interface自定义注解
/
定义一个注解
/
@Target(value={ElementType.METHOD, ElementType.TYPE}) // target表示我们注解应用的范围,在方法上,和类上有效
@Retention(RetentionPolicy.RUNTIME) // Retention:表示我们的注解在什么时候还有效,运行时候有效
@Documented // 表示说我们的注解是否生成在java doc中
@Inherited // 表示子类可以继承父类的注解
@interface MyAnnotation {
// 注解的参数:参数类型 + 参数名()<br /> // 通过default来申明参数的默认值<br /> String name() default "";
int age() default 0;
// 如果默认值为-1,代表不存在<br /> int id() default -1;
String[] schools();<br />}
5:泛型
1.5 以后,把集合中的内容限定为一个特定的数据类型
如果 Foo 是 Bar 的一个子类型(子类或者子接口),而 G 是某种泛型声明,那么 G
使用情景,集合,类,方法
List
Person
show(T[] arr)
泛型通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用 Object 类中的共性方法,集合中元素自身方法无法使用。
受限泛型
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在 JAVA 的泛型中可以指定一个泛型的上限和下限。
泛型的上限:
- 格式: 类型名称 <? extends 类 > 对象名称
- 意义: 只能接收该类型及其子类
泛型的下限:
- 格式: 类型名称 <? super 类 > 对象名称
- 意义: 只能接收该类型及其父类型
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
自定义泛型
泛型方法的格式:
[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
1.使用类型通配符:? 比如:List ,Map List 是 List、List 等各种泛型 List 的父类。
2.读取 List 的对象 list 中的元素时,永远是安全的,因为不管 list 的真实类型 是什么,它包含的都是 Object。
3.写入 list 中的元素时,不行。因为我们不知道 c 的元素类型,我们不能向其中 添加对象。 唯一的例外是 null,它是所有类型的成员
问题 1:List<>、List、List<?>、List 泛型的区别?
List、List,最后选择 List
泛型之间只有同类型才能相互赋值。
List表示的是 List 集合中的元素都为 T 类型,具体类型在运行期决定;
List读取出的元素都是 Object 类 型的,需要主动转型,所以它经常用于泛型方法的返回值。注意,List<?>虽然无法增加、修 改元素,但是却可以删除元素,比如执行 remove、clear 等方法,那是因为它的删除动作与泛型类型无关。
List也可以读写操作,但是它执行写入操作时需要向上转型(Upcast),在读 取数据后需要向下转型(Downcast)