反射(Reflection)
Java程序开发语言的特征之一,其允许运行时的程序获取自身的信息,并且可以操作类或对象的内部属性、方法,可能不是特别好理解,我们看下面来自百度百科的解释。
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。(百度百科:Java反射机制)
还是不怎么理解的话,这里举个用来帮助理解、但是不太全面的文字例子:写好程序之后,我们启动JVM,对程序代码进行编译,所写的类等都编译进去JVM了运行,程序运行完,JVM也就关闭了。但是墨菲定律告诉我们绝对不会这么简单,万一程序运行一半时,突然遇到问题需要用到某个类,而这个类我们事先并没有被编译进去,那么在运行的状态下去,可以通过反射机制去动态的获取一个类。(注意,运行状态和编译状态的区分)
可以看出反射机制的作用就是在运行状态下,将类读入JVM内存中,这里需要额外注意的是其实存在JVM内存中的类也是以对象形式存在(Class对象,注意这里Class指的是Class类(API文档:Class),而这个对象内部存着我们所读入的类的信息,且一个类在JVM内存中只能有一个Class对象,例如,我们通过反射将AA.class文件读入到内存中,实际是JVM创建一个Class对象,内部存放AA类的信息,再通过class对象去创建一个或多个AA对象,从而实现对AA类的使用。
获取Class对象三种方法:
1、直接通过某个类的对象调用class静态属性获取Class对象。(任意数据类型都具有一个class静态属性)
如:存在一个名叫AA的类。
AA aa = new AA();
Class AAclass = aa.class;
2、通过Object类的getClass()方法。(所有的类最终到继承于Object类)
如:存在一个名叫AA的类。
AA aa = new AA();
Class AAclass = aa.getclass;
3、通过Class类的静态方法:forName
Class类中有两个方法,forName (StringclassName)和forName (String className,boolean shouldInitialize,ClassLoader classLoader),className表示的是类的全名shouldInitialize表示是否初始化类,classLoader表示加载时使用的类加载器;一般来说forName (StringclassName)是最常用的shouldInitialize为true,classLoader为当前类加载器,其默认,用法:Class Class对象引用名= Class.forName("类的全名(路径)");
使用例子如下:
Class AAclass = Class.forName("包名.AA");
这个方法也是获取Class对象最常用的。
forName(StringclassName)和forName(String className,boolean shouldInitialize,ClassLoaderclassLoader)的区别:
forName(StringclassName,boolean shouldInitialize,ClassLoaderclassLoader)可以控制这个类加载后是不是被初始化,className参数同上,这里不重复介绍;shouldInitialize控制是否初始化,值为true时,会被初始化,为false时,不初始化;classLoader为类加载器。初始化和不初始化有什么区别呢,当类加载之后,如果没有初始化,那么类中的static块就不会被加载。
通过Class对象可以获取某个类中的:构造方法、成员方法、成员变量;
1.构造方法
A.获取所有构造方法:
getConstructors():获取所有"公有"的构造方法
getDeclaredConstructors():获取所有构造方法(不限权限)
B.获取单个的构造方法:
getConstructor(Class...parameterTypes):获取单个"公有"的构造方法
getDeclaredConstructor(Class...parameterTypes):获取单个构造方法(不限权限)
C.调用构造方法:
newInstance(Object...args)
A中返回的是Constructor[]数组,B中返回的是Constructor对象,方法参数是构造参数类型。
Class AAclass =Class.forName("包名.AA"); //假设AA类中有两个公有的构造函数AA( )和AA(char AA_char)
AA_Constructor1= AAclass. getConstructor (null); //调用构造函数AA( )
AA_Constructor2=AAclass. getConstructor (char.class); //调用构造函数AA(charAA_char)
getDeclaredConstructor方法用法也是一样
AA_Constructor3= AAclass. getDeclaredConstructor (null); //调用构造函数AA( )
AA_Constructor4=AAclass. getDeclaredConstructor (char.class); //调用构造函数AA(char AA_char)
newInstance()方法是Constructor类的方法,返回的是一个对象引用,用法如下:
Object AA_ Object= AA_Constructor2. newInstance('A');
2.获取成员方法并调用:
A.获取所有成员方法:
getMethods();获取所有"公有"的成员方法(包含继承的)
getDeclaredMethods();获取所有的成员方法(包括私有的但不包括继承的)
B.获取单个的成员方法:
getMethod(String name, Class ... <?>parameterTypes); 获取单个"公有"的成员方法
getDeclaredMethod(String name, Class...<?>parameterTypes); 获取单个不限权限的成员方法
C.调用方法:
invoke(Object receiver, Object... args)
A中返回的是Method [ ]数组,B中返回的是Method对象,方法参数中name是获取的方法名,Class... <?> parameterTypes是所获取方法的参数类型。
是不是和获取构造方法有异曲同工之妙,在使用方式上也差不多。
如下:
Method[] AAMethod1 = AAclass.getMethod();
Method AAMethod2 = AAclass.getMethod("AAmethod", String.class); //假设AA类下有一个公共方法AAmethod(String AAstring)
invoke ( )方法是Method类的方法。方法参数中,receiver是调用方法对象,args是调用时所传递的实际参数,用法如下:
AAMethod2. invoke(AA_ Object, 'String AAstring');
3.获取成员变量:
A. 获取所有成员变量
getFields():获取所有"公有"的变量
getDeclaredFields():获取所有变量(不限权限)
B.获取单个的成员变量:
getField(String Name):获取一个"公有"的变量
getDeclaredField(String Name):获取一个变量(可以是私有的)
C.设置变量的值:
set(Object object,Object value)
A中返回的是Field [ ]数组,B中返回的是Field对象,方法参数中Name是获取的变量名。
是不是和获取构造方法和成员方法也有异曲同工之妙,在使用方式上当然也是差不多。
如下:
Field[] AAField1 = AAclass.getFields();
Field AAField2 = AAclass. getField("AAvariable "); //假设AA类中有一个变量int AAvariable
set ( )方法是Field类的方法。方法参数中,object是变量所在对象,value是为变量设置的值,用法如下:
AAField2 . set (AA_Object, 2333);
由上和API文档:Class中的其他方法可以看出,反射主要提供以下功能:
1. 在运行时构造任意一个类的对象;
2. 在运行时调用任意一个对象的方法、变量;
3. 在运行时判断任意一个对象所属的类;
4. 在运行时判断任意一个类所具有的成员变量和方法;
尽管通过上文,明白反射使得Java程序在运行时提供了更多的灵活性,但看起来似乎作用和运用场景并不广泛,其实不然,接下来介绍一个最常用的场景,对配置文件的运用。下面是一个最简单的例程:
AA类
public class AA {
publicint AAvariable;
publicvoid AAmethod(String AAstring){
System.out.println(AAstring);
}
}
配置文件(以文本文件为例子):
AAconfigure.txt:
className = 包名.AA(AA类文件路径)
methodName = AAmethod
variableName = AAvariable
运用IO类加载文件
FileReader AAtxt = new FileReader("AAconfigure.txt");//获取输入流
Properties AAconfigure = new Properties();// Properties类,主要用于读取配置文件,下文简要说明
AAconfigure.load( AAtext );//加载配置文件
AAtxt.close( );
运用反射
//获取配置文件里的class对象
Class AAclass = Class.forName( AAconfigure.getProperty("className") );
//创建AAclass的对象
AA_Constructor = AAclass. getConstructor (null);
Object AA_ Object = AA_Constructor. newInstance();
//获取配置文件里的方法
Method AAmethod = AAclass.getMethod(AAconfigure.getProperty("methodName"));
AAMethod. invoke(AA_ Object, "String AAstring");
//获取配置文件里的变量
Field AAField = AAclass. getField( AAconfigure.getProperty("variableName ") );
AAField. set (AA_Object, 6666);
简要说明:Properties类(API文档:Properties):主要用于读取Java的配置文件,文件的内容的格式是“键=值”的格式
在这里看出,当我们要更改配置文件时,并不需要更改主要的源代码,只要更改配置文件和相应的类。当然反射的运用在这里的举例也只是冰山一角,比如代理模式中的动态代理、用XML文件配置等,更例如需要开发通用框架时,反射的作用显得更加明显,这里的基础篇就不再继续介绍。
再者,使用反射会消耗一定的系统资源,如果可以不需要动态创建就尽量不用反射;而且从上面可知反射机制调用时可忽略权限,所以可能存在安全问题。
泛型
先看看百度百科(百度百科:Java泛型)中的解释以及图示:泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。
上面对于泛型的文字解释已经很好的,但是泛型从纯文字解释太抽象太不好理解了,其实在以前的文章中,多少已经用到泛型了,因为泛型的运用太普遍了,对比下面三个非常简单而且被举了很多次的例程,就可以明白泛型作用了:
例程一:
class Text1_1 {
private int text1_int;
public Text1_1 (int num) { text1_int =num; }
public int getnum() { return text1_int; }
}
class Text1_2 {
private String text1_ string;
public Text1_2 (String num) { text1_ string= num; }
public String getstring() { return text1_ string; }
}
main:
Text1_1 textint= new Text1_1(233);
int i = textint.getnum();
Text1_2 textstring= new Text1_2("String:233");
String j = textstring.getstring();
例程二:
class Text2 {
private Object text2_object;
public Text2 (Object object) { text2_object= object; }
public Object getob() { return object_one; }
}
main:
Text2 object_int= new Text2 (new Integer(233));
int i =(Integer) object_int.getob();
Text2 object_str= new Text2(new String("String:233"));
String j = (String)object_str.getob();
例程三:
class Text3
{ private T text3_ gen;
public Text3 (T gen) { text3_ gen =gen; }
public T getgen() { return text3_ gen; }
}
main:
Text3 Text3_int= new Text3
(233); int i = Text3_int.getgen ();
Text3 Text3_string = new Text3< String> ("String:233");
String j = Text3_string.getgen ();
为了方便对比,上面三个例程都尽量把篇幅缩短。对比例程一和例程二,体现了对类型Object的引用来实现参数的“任意化”(参数化类型),并且提高了代码的重用率。而例程二和例程三例程是没使用泛型和使用了泛型的对比,可以明显看出所有的强制转换都是自动和隐式的,而且泛型作用主要在编译时会检查类型安全。
在上面例子中,我使用了T来代表泛型参数,常用还有E、K、V,当然实际上,这只是个标识,可以自定义,例如A、AB等等。正如Three例程中的,可以理解为,我只是做了个标识来表示所有的T为同一个类型,如Text3_int对象中的所有的T代表是Integer类型,Text3_string对象中的所有的T代表是String类型。
泛型有三种使用方式,分别为:泛型类、泛型方法、泛型接口
1.泛型类
泛型类型用于类的定义中,被称为泛型类,正如上文的例程three,那就是一个泛型类。最典型的泛型类就是各种容器类,如List、Set、Map,泛型类格式如下:
class 类名称 <泛型标识>{
private 泛型标识 var;
...
}
我们重新修改例程三的泛型类:
class Text3
{ private T text3_ gen;
private int text3_int;
private String text3_string;
public Text3 (T gen) { text3_ gen =gen; }
public text3_ method (T gen) { text3_gen = gen; }
public T getgen() { return text3_ gen; }
}
注意成员变量text3_ gen的类型声明是泛型标识,这说明了该成员变量的类型由外部决定。
2.泛型方法
我们知道,类通过创建对象来实例化,那么泛型类标识参数确定的时候是在实例化类的时候指明了泛型的具体类型;那么,成员方法需要对象来调用实现,所以由此看出,泛型方法是在调用方法的时候才指明泛型的具体类型。
那么如何去声明一个方法为泛型方法,也许这个时候会猜上文修改后的例程three的泛型类中text3_ method (T gen)方法就是泛型方法,那么,我只能遗憾的说猜错了,这只是个普通方法,在这个方法中只是运用了泛型类中的泛型标识所描述的类型参数,而且方法中的T在对象实例化的时候就已经确定了具体类型,这不符合刚刚所说的规则“泛型方法是在调用方法的时候才指明泛型的具体类型”,真正声明一个方法为泛型方法的格式如下:
权限声明 <泛型标识符(一个或者多个)> 方法返回值类型 方法名(方法参数){…}
例子:
public
void method (E num){…} public
void method ( ){…}
看下面例程,判断类里面的方法是不是泛型类:
class Method_text1
publicT num_T;
public method1(Tnum) { … }
public T method2() { return num_T; }
public
void method3(E num) { … } public
void method4(T num) { … } public
void method5(E num1,K num2) { … }
}
class Method_text2 {
public
void method6(T num) { … }
}
不是泛型方法:method1,method2
是泛型方法:method3,method4,method5,method6
解释:
method1在例程上文就有解释,这只是个普通方法;
method2中,T指的返回数据的数据类型,T没有<>,而且T是在方法返回值类型声明位置上;
method3算是最简单的泛型方法;
method4中,方法中的T和类中的T其实是没关系了,正如上面所说的规则“泛型方法是在调用方法的时候才指明泛型的具体类型”而泛型类的T在实例化过程就被确定了,而方法还没被调用,方法中的T还没被确定。
method5中,是拥有两个泛型类型参数声明的泛型方法
method6中,尽管类不是泛型类,同样可以存在泛型方法
现在,泛型方法的定义已经非常清楚了,那么就可以轻易的分析出静态方法如何使用泛型的规则了。
当静态方法加载的时候,类还没有被加载,所以类中的泛型类型(假如类为泛型类)是没被确定,自然而然的静态方法也没办法调用。所以静态方法使用到泛型的话,这个静态方法一定要被定义为泛型方法
泛型使用extends关键字,可以起到对泛型类型进行限制,格式如下:
public
void method(T num) { … }
这样的话,T的类型只能是Number类和其子类,这里的extends已经超过了一般继承(extends)的意义,后面可以是类也可以是接口,这里的extends可以理解为T泛型类型是实现的接口的类型或者继承的类的类型。当需要多实现的时候使用"&"号分开即可,如下:
public
void method (T num) { … }
3.泛型接口
泛型接口基本和泛型类差不多,使用将一个接口定义为泛型,如下:
public interface Inter_one
{ publicT Inter_ method( );
}
以上就是一个泛型接口,接口的实现也是交给实现此接口的类,而接口泛型类型的具体类型确认也是交给其接口的实现类,有两种方式。如下:
方式一:
class Inter_class
implements Inter_one { … } 其接口实现类也声明了泛型的声明,而泛型类型的具体类型确认则交给了此类的实例化对象。
方式二:
class Inter_classimplements Inter_one
{ … } 直接在实现接口上手动指定其泛型类型的具体类型,这时泛型类型已经确定,其接口实现类就不需要因为接口的泛型类型而声明为泛型类。
------------------------------------------------分割线----------------------------------------------------------
上文如有错误、写得不合理以及有疑惑的地方,希望您可以在评论区提出。
在下十分感谢。
如果只是想在评论区吐吐槽、聊聊心得、卖个萌,同样也十分欢迎。
祝大家生活美好,干杯!~( ゜▽゜)つロ
转载请注明原作者以及附上原文章地址,谢谢。