泛型与反射
泛型与反射
泛型
泛型说明
泛型,即“参数化类型”。简单的说 泛型就是函数的参数类型可以变化
接口、类和方法也都可以使用泛型去定义,以及相应的使用。在具体使用时,可以分为泛型接口、泛型类和泛型方法,
由于接收来自外部使用时候传入的类型实参。那么对于不同传入的类型实参,生成的相应对象实例的类型是不是一样的呢?参照如下范例:
public static void main(String[] args) {
// TODO Auto-generated method stub
Box<String> name = new Box<String>("corn");
Box<Integer> age = new Box<Integer>(712);
System.out.println("name class:" + name.getClass()); // com.bimowu.showstudy.java3.Step3fan1$Box
System.out.println("age class:" + age.getClass()); // com.bimowu.showstudy.java3.Step3fan1$Box
System.out.println(name.getClass() == age.getClass()); // true
}
static class Box<T> {
private T data;
public Box() {
}
public Box(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
输出为:
name class:class com.bimowu.showstudy.java3.Step3Fan1$Box
age class:class com.bimowu.showstudy.java3.Step3Fan1$Box
true
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段
类型通配符
在逻辑上Box不能视为Box的父类,如果我们需要一个在逻辑上可以用来表示同时是Box和Box的父类的一个引用类型,由此,类型通配符应运而生。
类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box在逻辑上是Box、Box…等所有Box<具体类型实参>的父类。
由此,我们依然可以定义泛型方法,来完成此类需求,如下:
`public static void getData(Box data)`
类型通配符上限与下限
类型通配符上限通过形如Box<? extends Number>形式定义,相对应的,类型通配符下限为Box<? super Number>形式,其含义与类型通配符上限正好相反,可以限定对应的实现类,如下:
public static void getUpperNumberData(Box<? extends Number> data){
System.out.println("data :" + data.getData());
}
泛型注意事项
Java中没有所谓的泛型数组一说,也就是:
List<String>[] ls = new ArrayList<String>[10];是不支持的
List<String>[] ls = new ArrayList[10] 是支持的
原因如下:
public static void main(String[] args) {
//下面的代码使用了泛型的数组,是无法通过编译的
GenTest<String>[] genArr = new GenTest<String>[2];
//创建泛型数组
Object[] test = genArr;
//类型向上转换
GenTest<StringBuffer> strBuf = new GenTest<StringBuffer>();
//创建GenTest<StringBuffer>
strBuf.setValue(new StringBuffer());
//设置值
test[0] = strBuf;
//数组首地址指向GenTest<StringBuffer>
GenTest<String> ref = genArr[0];
//使用数组移花接木,让Java编译器把GenTest<StringBuffer>当作了GenTest<String>
String value = ref.getValue();
/**
* 上面的代码中,最后一行是重点。根据本文第一部分的介绍,“String value = ref.getValue()”会被替换成“String value =(String)ref.getValue()”。
* 当然我们知道,ref实际上是指向一个存储着StringBuffer对象的GenTest对象。所以,编译器生成出来的代码是隐含着错误的,在运的时候就会抛出ClassCastException
*/
}
原因:
泛型是为了消灭ClassCastException而出现的,但是在这个时候它自己却引发了ClassCastException。所以是不是某个月黑风高的晚上,java语法中,决定不支持泛型的数组了
泛型举例
泛型无法向上转型:
class Info<T>{
private T var ;
}
public class GenericsDemo{
public static void main(String args[]){
Info<String> i1 = new Info<String>() ; // 泛型类型为String
Info<Object> i2 = null ;
i2 = i1 ; //这句会出错 incompatible types
}
};
泛型接口:
interface Info<T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
}
泛型方法:
class Demo{
public <T> T fun(T t){ // 可以接收任意类型的数据
return t ; // 直接把参数返回
}
};
public class GenericsDemo{
public static void main(String args[]){
Demo d = new Demo() ; // 实例化Demo对象
String str = d.fun("汤姆") ; // 传递字符串
int i = d.fun(30) ; // 传递数字,自动装箱
System.out.println(str) ; // 输出内容
System.out.println(i) ; // 输出内容
}
};
泛型方法返回泛型类型实例:
class Info<T extends Number>{ // 指定上限,只能是数字类型
private T var ; // 此类型由外部决定
}
public class GenericsDemo{
public static <T extends Number> Info<T> fun(T param){ //方法中传入或返回的泛型类型由调用方法时所设置的参数类型决定
Info<T> temp = new Info<T>() ; // 根据传入的数据类型实例化Info
temp.setVar(param) ; // 将传递的内容设置到Info对象的var属性之中
return temp ; // 返回实例化对象
}
};
泛型方法返回返回值:
public static <E> ArrayList<E> newArrayList() {
return new ArrayList<E>();
}
某个方法如下:
public List<PrepaidHistory> queryHistories(Long skyid,PrepaidHistoryType type, Date from, Date end) {
return Lists.newArrayList();
}
反射
反射机制说明
- 什么是反射机制:
反射机制指的是程序在运行时能够获取自身的信息。在java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。 - 什么时候用到反射机制?
用于某些模块集成场合。当你不能在开发时即得到其目标类完整接口定义,只能根据命名规则去进行集成时。并可以延伸到包装、动态代理等模式的应用中。有时候也干些hack的事情,比如绕过private保护机制啥的。
例如:Class.forName(“com.mysql.jdbc.Driver.class”).newInstance()就是反射。现在很多开发框架都用到反射机制,spring,struts,很多地方都是用反射机制实现的。
反射机制的优点和缺点:
- 反射机制的优点和缺点:
反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性。比如框架中,采用反射机制的话,只需要在运行时才动态的创建和编译,灵活性就表现的十分明显。
缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它 满足我们的要求。这类操作总是慢于只直接执行相同的操作。 - 利用反射机制能获得什么信息:
一句话,类中有什么信息,它就可以获得什么信息,不过前提是得知道类的名字,要不就没有后文了。可以如下:
在运行时判断任意一个对象所属的类;
在运行时构造任意一个类的对象;
在运行时判断任意一个类所具有的成员变量和方法;
在运行时调用任意一个对象的方法;
生成动态代理。
发射相关方法
反射机制相关方法:
-
首先得根据传入的类的全名来创建Class对象。
Class c=Class.forName("className");//注明:className必须为全类名 Object obj=c.newInstance();//创建对象的实例
OK,有了对象就什么都好办了,想要什么信息就有什么信息了
-
获得构造函数的方法:
Constructor getConstructor(Class[] params) //根据指定参数获得public构造器 Constructor[] getConstructors() //获得public的所有构造器 Constructor getDeclaredConstructor(Class[] params) //根据指定参数获得public和非public的构造器 Constructor[] getDeclaredConstructors() //获得所有构造器
-
获得类方法的方法 :
Method getMethod(String name, Class[] params) //根据方法名,参数类型获得方法 Method[] getMethods() //获得所有的public方法 Method getDeclaredMethod(String name, Class[] params) //根据方法名和参数类型,获得public和非public的方法 Method[] getDeclaredMethods() //获得所以的public和非public方法
-
获得类中属性的方法:
Field getField(String name) //根据变量名得到相应的public变量 Field[] getFields() //获得类中所以public的方法 Field getDeclaredField(String name) //根据方法名获得public和非public变量 Field[] getDeclaredFields() //获得类中所有的public和非public属性
反射相关案例
public class TestReflect {
public static final String KEY1 = "123";
private String priData;
public String pubData;
public static void main(String[] args) throws Exception{
//取得本类的全部属性
dealProperty();
//取得本类的全部属性
doMethod();
}
public static void doMethod() throws Exception{
System.out.println("-----------------------");
Class<?> clazz = Class.forName("com.bimowu.showstudy.java3.TestReflect");
// 调用TestReflect类中的reflect1方法
Method method = clazz.getMethod("reflect1");
method.invoke(clazz.newInstance());
// Java 反射机制 - 调用某个类的方法1.
// 调用TestReflect的reflect2方法
method = clazz.getMethod("reflect2", int.class, String.class);
method.invoke(clazz.newInstance(), 20, "张三");
// Java 反射机制 - 调用某个类的方法2.
// age -> 20. name -> 张三
//错误的调用方法reflect2
method = clazz.getMethod("reflect2");//此处会报java.lang.NoSuchMethodException: com.bimowu.showstudy.java3.TestReflect.reflect2()
method.invoke(clazz.newInstance(), 20, "张三");
System.out.println("-------------------------");
}
public static void dealProperty() throws Exception{
System.out.println("++++++++++++++++++++++++++");
Class<?> clazz = Class.forName("com.bimowu.showstudy.java3.TestReflect");
System.out.println("===============本类属性===============");
// 取得本类的全部属性
Field[] field = clazz.getDeclaredFields();
//Field[] filed1 = clazz.getFields(); 如果是类似的用法,显示的是public的属性
for (int i = 0; i < field.length; i++) {
// 权限修饰符
int mo = field[i].getModifiers();
String priv = Modifier.toString(mo);
// 属性类型
Class<?> type = field[i].getType();
System.out.println(priv + " : " + type.getName() + " : " + field[i].getName() + ";");
}
System.out.println("++++++++++++++++++++++++++");
}
public void reflect1() {
System.out.println("Java 反射机制 - 调用某个类的方法1.");
}
public void reflect2(int age, String name) {
System.out.println("Java 反射机制 - 调用某个类的方法2.");
System.out.println("age -> " + age + ". name -> " + name);
}
}
动态代理
- 反射机制的动态代理:
spring主要有两大思想,一个是IoC,另一个就是AOP,AOP的原理就是java的动态代理机制【不过java自带的动态代理机制只支持接口】。
在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。
InvocationHandler:
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
接下来我们来看看Proxy这个类:
Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
这个方法的作用就是得到一个动态的代理对象,其接收三个参数.
【详见如下代码】:
首先有接口和对应的实现类:
接口:
public interface MySubject {
public void rent();
public Integer hello(String str);
}
对应的实现类:
public class MyRealSubject implements MySubject {
@Override
public void rent() {
System.out.println("I want to rent my house");
}
@Override
public Integer hello(String str) {
System.out.println("hello: " + str);
return 0;
}
}
核心的代码,InvocationHandler和Proxy实现:
public class DynaClient {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
DynaClient client = new DynaClient();
client.main();
}
/**
* 方法的重载,可以有多个main方法,一般不建议这么写.
*/
public void main() throws Exception{
// 我们要代理的真实对象,再不修改类的情况下动态增加功能
// MySubject realSubject = new MyRealSubject();
// 另外一种写法,这种写法默认调用无参构造函数
Object realSubject = Class.forName("com.bimowu.showstudy.java3.MyRealSubject").newInstance();
//Constructor c = Inner.class.getDeclaredConstructor(Outer.class);
//c.newInstance(new Outer());如果此处的MyRealSubject是这个类的内部类的话,相当于没有无参构造函数了,需要显示调用有参构造函数
//我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler handler = new DynamicProxy(realSubject);
Class<?>[] interfaces = realSubject.getClass().getInterfaces(); //对应的实现接口
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
MySubject subject = (MySubject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), interfaces, handler);
/**
* 原因为:
* 可能我以为返回的这个代理对象会是Subject类型的对象,或者是InvocationHandler的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为Subject类型的对象?
* 原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,
* 这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。
*
* 同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,
* 它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,
* 并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。
*/
System.out.println(subject.getClass().getName()); //注意这行的输出:com.sun.proxy.$Proxy0
subject.rent();
Integer num = subject.hello("world");
System.out.println("the result of num is:=" + num);
}
/**
* InvocationHandler 最基础的handler的实现.
*/
public class DynamicProxy implements InvocationHandler {
// 这个就是我们要代理的真实对象
private Object subject;
// 构造方法,给我们要代理的真实对象赋初值
public DynamicProxy(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 在代理真实对象前我们可以添加一些自己的操作
System.out.println("");
System.out.println("------------------------------------------");
System.out.println("before rent house");
System.out.println("Method:" + method);
// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object obj = method.invoke(subject, args);
// 在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after rent house");
return obj;
}
}
}
【注意】JAVA的动态代理只支持实现了接口的类,如果某个类没有实现接口的话,Spring有CGLib代理实现方式