-------------android培训、java培训、期待与您交流! -----------------
Class类:反射的基石
1,Class是Java程序中各个Java类的总称;它是反射的基石,通过Class类来使用反射。
2,class和Class的区别
1)Class:指的是Java程序中的各个Java类是属于同一类事物,都是Java程序的类,这些类称为Class。
例如人对应的是Person类,Java类对应的就是Class。它们在内存中对应的是字节码,如Person类的字节码。
2)class:Java中的类用于描述一类事物的共性,该类事物有什么属性以及属性值是什么是由此类的实例对象
确定,不同的实例对象有不同的属性值。
3,Class不可以new对象,因为Class里没有构造方法。
它是通过字节码对象获得Class对象的。如Person类,它的字节码对象就是Class cls = Person.class ;
4,得到各个字节码对应的实例对象的三种方式( Class类型)
1)类名.class,例如,Person.class
2)对象.getClass(),例如,new Date().getClass()
3)Class.forName("类名"),例如,Class.forName("java.util.Date");
同一类型用三种方式得到的字节码都是同一字节码。
得到Class.forName("类名")这个字节码对象有两种情况:
1)此类已经加载进内存:若要得到此类字节码,不需要再加载。
2)此类还未加载进内存:类加载器加载此类后,将字节码缓存起来,forName()方法返回加载进来的字节码。
九个预定义Class实例对象:
1)包括八种基本类型(byte、short、int、long、float、double、char、boolean)的字节码对象和一种返回值为void类型的void.class。
2)Integer.TYPE是Integer类的一个常量,它代表此包装类型包装的基本类型的字节码,所以它和int.class是相等的。
基本数据类型的字节码都可以用与之对应的包装类中的TYPE常量表示
数组类型的Class实例对象,可以用Class.isArray()方法判断是否为数组类型的。
3)每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class
对象。
class类的常见方法:
1,Class getClass() : 返回的是Object运行时的类,即返回Class对象即字节码对象
2,String getName() : 以String形式返回此Class对象所表示的实体名称。
3,static Class forName(String className) :返回与给定字符串名的类或接口的相关联的Class对象。
4,boolean isPrimitive() : 判断指定的Class对象是否是一个基本类型。
5,boolean isArray() : 判定此Class对象是否表示一个数组。
代码演示:
public class ReflectTest {
public static void main(String[] args) throws ClassNotFoundException {
String str = "abc";
Class cls1 = str.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");
System.out.println(cls1==cls2);
System.out.println(cls1==cls3);
//上述代码得到的结果是用三种方式得到的都是同一份字节码
System.out.println(cls1.isPrimitive());//判断String是否是基本类型
System.out.println(int.class.isPrimitive());
System.out.println(int.class==Integer.class);
System.out.println(int.class==Integer.TYPE);
System.out.println(int[].class.isPrimitive());
}
}
反射:
1,为什么要使用反射?
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,
都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。简单一句话:反射技术可以对类进行解剖。
2,反射就是把Java类中的各种成分映射成相应的java类。
例如,一个Java类中用一个Class类的对象来表示。
3,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。
4,表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。
5,一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对。
Constructor类:
1,Constructor类代表某个类中的一个构造方法
2,获取构造方法:
1)到某个类的所有构造方法:如得到String类的所有构造方法:
Constructor[] cons = Class.forName(“java.lang.String”).getConstructors();
2)获取某一个构造方法:
Constructor con =String.class.getConstructor(StringBuffer.class);//获得方法时要用到类型
3、创建实例对象:
1) 通常方式:String str = new String(new StringBuffer (”abc”));
2) 反射方式:String str = (String)con.newInstance(new StringBuffer(“abc”));
//调用获得的方法时要用到上面相同类型的实例对象,即两个StringBuffer()要对应相等。
注意:获取构造方法和创建实例对象都要用到StringBuffer,这必须是一致的。
获取构造方法中的StringBuffer是指需使用的是含StringBuffer类型的构造方法。
创建实例对象中的StringBuffer是指用这个构造方法创建对象,还要传入StringBuffer的对象进去。
例如,
public static void reflectMethod() throws Exception {
//用反射的方法实现 String str = new Strig(new StringBuffer("abc"));
Constructor constructor =
String.class.getConstructor(StringBuffer.class);//这里的StringBuffer是指需使用的是含StringBuffer类型的构造方法。
String str =
(String)constructor.newInstance(new StringBuffer("abc"));//这里的StringBuffer是指用这个构造方法创建对象,还要传入StringBuffer的对象进去。这里需要强转因为程序知道它是一个构造方法,但是不知道是那个类的构造方法
System.out.println(str.charAt(2));
}
Field类:
1,它代表的是某个类中的一个成员变量。
2,获取成员变量:
1)获取公有的成员变量:getField(String name)和get(变量)
2)获取私有的成员变量:暴力反射
getDeclared(String name):只要是类中的成员都可以访问
setAccessible(boolean b),将b设为true,表示取消访问检查。
例如:
public static void reflectMethod_2() throws Exception{
ReflectPoint pt = new ReflectPoint(3, 9);
Field fieldY = pt.getClass().getField("y");//通过pt的字节码获取y的字段。fieldY不代表具体的值,只代表一个变量。
//fieldY不是对象上的变量,而是类上的,要用它去取某个对象上对应的值
//获取pt上fieldY对应的值
System.out.println(fieldY.get(pt));
//获取私有的变量,要用到暴力反射
Field fieldX = pt.getClass().getDeclaredField("x");
fieldX.setAccessible(true);
System.out.println(fieldX.get(pt));
}
Method类:
1,Method类代表某个类中的一个成员方法。
2,得到类中的某一个方法:
例如: Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
3,获取某个类中的某个方法:(如String str = ”abc”)
1)通常方式:str.charAt(1)
2)反射方式:Method charAtMethod =Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);
charAtMethod.invoke(str,1);
说明:如果传递给Method对象的invoke()方法的第一个参数为null,说明Method对象对应的是一个静态方法。
用反射方式执行某个类中的main方法:
使用反射的原因:在编写程序时,并不知道使用者传入的类名是什么,虽然传入的类名不知道,但是知道的是这个类中的方法有main这个方法,所以可以用反射的方式,通过调用者传入的类名(可定义字符串型变量作为传入类名的入口,通过这个变量代表类名),内部通过传入的类名获取其main方法,然后执行相应的内容。
将类名作为参数传递给调用者的方法:右键-->Run As-->Run Configuration-->arguments-->Programe arguments中写入完整类名即可(Name就是调用者名)。
代码演示:
class TestArguments{//这里完整的类名是cn.itcast.day01.TestArguments
public static void main(String[] args){
for(String arg : args){
System.out.println(arg);
}
}
}
public class ReflectTest {
public static void main(String[] args) throws Exception {
String str = "itcast";
//通过反射的方式调用charAt(1);
Method charAtMethod = str.getClass().getMethod("charAt", int.class);
System.out.println(charAtMethod.invoke(str, 2));
//1,普通方式调用TestArguments类中的main方法: TestArguments.main(String[]{"111","222","333"});
//2,用反射方式执行TestArguments类中的main方法,需要将类名作为参数传递给调用者,右键-->Run As-->Run Configuration-->arguments-->
//-->Programe arguments中写入完整类名即可(Name就是调用者)
String StartinhClassName = args[0];//一个对象
Method mainMethod = Class.forName(StartinhClassName).getMethod("main", String[].class);
mainMethod.invoke(null, (Object)new String[]{"111","222","333"});//强转是为了让编译器知道传的是一个对象,而不是一个数组(数组中有多个对象)
}
}
数组的反射:
1,有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
2,代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
3,基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
4,数组大反射操作用Array工具类来完成。
hashCode方法与HashSet类:
1, hashCode的由来:
如果想要查找一个集合中是否包含某个对象,你通常是逐一取出每个元素与要查找的对象进行比较,当发现某个对象与要查找的对象进行equals方法比较的结果相等时,则停止查找并返回肯定信息,否则返回否定的信息。假如一个集合中有很多元素,譬如一万个元素,并且 没有包含要查找的对象时,则意味着程序需要从该集合中取出一万个元素进行逐一比较才能得到结论。所以出现了一种哈希算法来提高从集合中查找元素的效率,这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储在哪个区域。
2,只有类的实例对象要被采用哈希算法进行存入和检索时,这个类才需要按要求复写hashCode()方法,通常要求hashCode()和equals()两者一并被覆盖。
3,通常来说,一个类的两个实例对象用equals方法比较的结果相等时,它们的哈希码也必须相等,但反过来则不成立,即equals方法比较结果不相等的对象可以有相同的哈希码,或者说哈希码相同的两个对象equals方法比较的结果可以不等。例如,字符串"BB"和"Aa"的equals方法比较的结果肯定不相同,但是它们的hashCode方法返回值却相等。
4,当一个对象被存储进HashSet集合中,就不能再修改参与计算哈希值的字段,否则对象被修改后的哈希值与最初被存入的HashSet集合中的哈希值就不同了。在这种情况下,即使contains()方法是用对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。简单说,之前存入的对象和改后的对象,是具有不同的哈希值,被认为是不同的两个对象,这样的对象不再使用,又不移除,而越来越多,就会导致内存泄露。
补充:内存泄露:某些对象不再使用了,占用着内存空间,并未被释放,就会导致内存泄露。
注意:对象在调用方法时,对象会先进行一次自身hashCode()方法的调用,再进行操作方法。
反射的作用--->实现框架功能:
框架:通过反射调用位置Java类的一种方式。
如房地产商造房子用户住,门窗和空调等等内部都是由用户自己安装,房子就是框架,用户需使用此框架,安好门窗等放入到房地产商提供的框架中。
框架和工具类的区别:工具类被用户类调用,而框架是调用用户提供的类。
2、框架机器要解决的核心问题:
我们在写框架(造房子的过程)的时候,调用的类(安装的门窗等)还未出现,正是由于框架无法知道要被调用的类名,所以在程序中无法直接new其某个类的实例对象,而要用反射来做。
3、简单框架程序的步骤:
1)创建一个配置文件,在文件中写入键值对:className=java.util.ArrayList,等号右边的可以自己定义集合的名称,即用户可以对此记事本修改成自己的类名。
2)代码实现,加载此文件:将文件读取到读取流中,用Properties类的load()方法将流加载经内存,提取文件中的信息。然后将读取流关闭因为流中的数据已经加载进内存。
3)通过getProperty()方法获取类名属性,将传入的类名赋值给指定变量。
4)用反射的方式,创建对象newInstance()
5)进行相关的具体操作。
代码演示:
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;
public class ReflectTest2 {
public static void main(String[] args) throws Exception {
InputStream in = new FileInputStream("src\\cn\\itcast\\day01\\config.properties");
Properties prop = new Properties();
prop.load(in);
String className = prop.getProperty("className");
Collection collection = (Collection)Class.forName(className).newInstance();
ReflectPoint pt1 = new ReflectPoint(3, 3);
ReflectPoint pt2 = new ReflectPoint(5, 5);
ReflectPoint pt3 = new ReflectPoint(3, 3);
collection.add(pt1);
collection.add(pt2);
collection.add(pt3);
collection.add(pt1);
System.out.println(collection.size());
}
}
类加载器
1,简述:类加载器可以将.class的文件加载经内存,也可将普通文件中的信息加载进内存。
2,java文件的加载:
1)eclipse在编译的时候会将源程序中的所有.java文件加载成.class文件,然后放到classPath指定的目录中去。并且会将非.java文件原封不动的复制到.class指定的目录中去。在真正编译的时候,使用classPath目录中的文件。
2)写完程序是要将配置文件放到.class文件目录中一同打包,这些都是类加载器加载的。
3)框架中的配置文件一般情况下要放到classPath指定的文件夹中,原因是因为它的内部就是用类加载器加载的文件。
3,资源文件的加载:是使用类加载器。
1)由类加载器ClassLoader的一个对象加载进内存,即用getClassLoader()方法加载。若要加载普通文件,可用getResourseAsStream(String name)在classPath的文件中逐一查找要加载的文件。
2).class也提供了方法来加载资源文件,其实它内部就是先调用了Loader方法,再加载的资源文件。
如:Reflect.class.getResourseAsStream(String name)
4, name的路径问题:
1)如果配置文件和classPath目录没关系,就必须写上绝对路径,
2)如果配置文件和classPath目录有关系,即在classPath目录中或在其子目录中,
那么就得写相对路径,因为它自己了解自己属于哪个包,是相对于当前包而言的。
内省:
1,JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,
且方法名符合某种命名规则。
2,如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常
称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,
则需要通过一些相应的方法来访问,JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据
其中的成员变量。去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的
首字母改成小的。比如getAge/setAge-->age;gettime-->time;setTime-->time;getCPU-->CPU。
总之,一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部
的成员变量。
3,JavaBean的好处:
一个符合JavaBean特点的类当做普通类一样可以使用,但是把它当做JavaBean类用肯定有好处的:
1)在JavaEE开发中,经常要使用JavaBean。很多环境就要求按JavaBean的方式进行操作,别人都这么用,
那么就必须要求这么做。
2)JDK中提供了对JavaBean进行操作的API,这套API称为内省,若要自己通过getX的方式来访问私有x,
可用内省这套API,操作JavaBean要比使用普通的方式更方便。
对JavaBean的复杂内省操作:
1、在IntroSpector类中有getBeanInfo(Class cls)的方法。
2、获取Class对象的Bean信息,返回的是BeanInfo类型。
3、BeanInfo类中有getPropertyDescriptors()的方法,可获取所有的BeanInfo的属性信息,返回一个PropertyDescriptor[]。
4、在通过遍历的形式,找出与自己想要的那个属性信息。
代码演示:
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class IntroSpectorTest {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
ReflectPoint2 pt = new ReflectPoint2(3, 5);
String propertyName = "x";
Object retVal = getProperty(pt, propertyName);
System.out.println(retVal);
Object value = 7;
setProperty(pt, propertyName, value);
System.out.println(pt.getX());
}
private static void setProperty(Object pt, String propertyName,
Object value) throws IntrospectionException,
IllegalAccessException, InvocationTargetException {
PropertyDescriptor pd1 = new PropertyDescriptor(propertyName, pt.getClass());
Method methodSetX = pd1.getWriteMethod();
methodSetX.invoke(pt, value);
}
private static Object getProperty(Object pt, String propertyName)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
PropertyDescriptor pd = new PropertyDescriptor(propertyName, pt.getClass());
Method methodGetX = pd.getReadMethod();
Object retVal = methodGetX.invoke(pt);
/*
//内省的复杂操作
BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass());
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
Object retVal = null;
for (PropertyDescriptor pd : pds) {
if (pd.getName().equals(propertyName)) {
Method methodGetX = pd.getReadMethod();
retVal = methodGetX.invoke(pt1);
break;
}
}
*/
return retVal;
}
}
Beanutils工具包:
1,在eclipse中导入工具包(一共需要两个包,commons-beanutils.jar和commons-logging.jar)
1)在网上下载一个Beenutils工具包,然后解压。
2)将解压后的commons-beanutils.jar和commons-logging.jar 包复制到程序所在的工程下(最好是在工程下先建一个文件夹,将jar包复制到文件夹下)。
3)右键工程下的jar包选择Build Path, Add to Build Path。
2,使用BeanUtil工具包的好处:
1)工具包中提供了set和get方法,不需要自己写了,这样做减少了代码的书写。
2)提供的set或get方法中,传入的是字符串,返回的还是字符串,因为在浏览器中,用户输入到文本框的
都是以字符串的形式发送至服务器上的,所以操作的都是字符串。也就是说这个工具包的内部有自动将整数转换为
字符串的操作。
3)支持属性的级联操作,即支持属性链。如可以设置:人的脑袋上的眼镜的眼珠的颜色。
这种级联属性的属性链如果自己用反射,那就很困难了,通过这个工具包就可以轻松调用。
使用BeanUtil类get属性时返回的结果为字符串,set属性时可以接受任意类型的对象,通常使用字符串。
而使用PropertyUtils类 get属性时返回的结果为该属性本来的类型,set属性时只接受该属性本来的类型。
代码演示:
public static void beanutilMethod()throws Exception {
ReflectPoint2 pt = new ReflectPoint2(3, 5);
String propertyName = "x";
//通过Beanutils工具包获取和设置值
System.out.println(BeanUtils.getProperty(pt, "x"));//获取
System.out.println(BeanUtils.getProperty(pt, "x").getClass().getName());//获取x的类型
BeanUtils.setProperty(pt, "x", "9");//设置,因为x是以String类型设置进去的,所以这里应该写"9"
System.out.println(pt.getX());
BeanUtils.setProperty(pt, "birthday.time", "1989");//birthday.time就是属性的级联操作
System.out.println(BeanUtils.getProperty(pt, "birthday.time"));
System.out.println(PropertyUtils.getProperty(pt, "x").getClass().getName());//打印的结果是返回的Integer类型
PropertyUtils.setProperty(pt, "x", 9);//因为是Integer类型所以9就不可以加引号,如果是"9"会出现IllegalArgumentException: 类型不匹配
/*
//jdk1.7的新特性
Map map = {name:"wbl",age:24};
BeanUtils.setProperty(map, "name, "myy");
*/
}
--------------android培训、java培训、期待与您交流! ----------------------