7,反射。JDK1.2的特性。
Class类,它的对象代表内存中的一份字节码。
反射就是把
java
类中的各种成分映射成相应的
java
类。
例如,一个
java
类中用一个
Class
类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的
java
类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示
java
类中的
Class
类显然要提供一系列的方法,来获取其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应的类的实例对象来表示,他们是
Field
,
Method
,
Contructor
,
Package
等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。
得到字节码对应的实例对象的
3
种方法:
1)
类名
.class
,例如:
System.class
2)
对象
.getClass()
,例如:
new Date().getClass()
3)C
lass.forName(“
类名
”)
,例如:
Class.forName(“java.util.Date”)
//第三种方式,把类名作为一个参数传进来。写源程序时还不知道类的名字,它的名字是在运行时作为参数传进来的,所以反射通常用这种方式。
九个预定义的
Class
对象:八个基本类型
+void
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.util.Arrays;
public class ReflectTest {
public static void main(String[] args)throws Exception{
String str1="abc";
Class cls1=str1.getClass();
Class cls2=String.class;
Class cls3=Class.forName("java.lang.String");
System.out.println(cls1==cls2); //true
System.out.println(cls2==cls3); //true,
可知在内存中只有一份字节码。
//Class类的一些基本方法。
System.out.println(cls1.isPrimitive()); //false是否是原子类型。
System.out.println(int.class.isPrimitive()); //true
System.out.println(int.class==Integer.class); //false,两份字节码
System.out.println(int.class.equals(Integer.class));
//没有复写Object的equals,代表比较Class对象的引用。
System.out.println(int.class==Integer.TYPE); //true,
包装类型.TYPE代表他所包装的基本类型的字节码。
System.out.println(int[].class.isPrimitive() ); //false,数组不是原子类型
System.out.println(int[].class.isArray());
//true,判断Class对象是否是数组字节码
//总之,在源程序中出现的类型,都有各自的Class实例对象。 例如:int[ ],void
1)//将Class对象中的构造方法反射到Constructor对象
//用反射来实现new String(new StringBuffer("abc"));
//只接受参数类型是StringBuffer的一个构造方法。
Constructor constructor1=String.class.getConstructor(StringBuffer.class);
String str2=(String)constructor1.newInstance(/*"abc"*/new StringBuffer("abc"));
System.out.println(str2.charAt(2));
//Class提供的newInstance() 直接可以new对象,但只可new一个无参的对象。查看API源代码可知它内部实际就是上述反射操作,但它把这个空参数的构造方法缓存起来,以便下次直接使用,这也说明的反射比较耗费资源。
2)//将Class对象中的成员变量反射到Field对象
ReflectPoint pt1=new ReflectPoint(3,5);
//fieldY不是对象身上的变量,而是类上,要用它去取某个对象上对应的值。
Field fieldY=pt1.getClass().getField("y");
Field fieldX=pt1.getClass().getDeclaredField("x");
//得到私有的x变量。
System.out.println(fieldY.get(pt1));
fieldX.setAccessible(true); ·//暴力反射私有变量。
System.out.println(fieldX.get(pt1));
//调用自定义的方法改变String变量的值
changeStringValue(pt1);
//反射点已复写toString方法,可以直接打印对象了。
System.out.println(pt1);
3)//将Class对象中的方法反射到Method对象
//用反射来实现这个操作str1.charAt(1);
Method methodCharAt = String.class.getMethod("charAt", int.class);
System.out.println(methodCharAt.invoke(str1, 1));
//第一个参数为null,则methodCharAt肯定是一个静态方法的对象。
//System.out.println(methodCharAt.invoke(null, 1));
//按JDK1.4的语法调用,没有可变参数,用Object数组。
System.out.println(methodCharAt.invoke(str1, new Object[]{Integer.valueOf(2) /*2自动装箱*/}));
//自己写程序去调用人家的main方法。
//用反射实现TestArguments.main(new String[]{"zhangsan","lisi","wangwu"});
//为什么要用反射?
提前不知类的名字,用参数传递的方式把类名传进来,
执行时,传进来哪个类执行哪个类。
String startClassName = args[0];
Method main = Class.forName(startClassName).getMethod("main", String[].class);
main.invoke(null, new String[]{"111","222","333"}); //编译报错
java.lang.ArrayIndexOutOfBoundsException
,自动拆包一次,变成3个参数
。
//编译器为兼容1.4版本,对字符串数组自动拆包一次。
解决方法1:
main.invoke(null, new Object[]{new String[]{"111","222","333"}});
解决方法2
:
main.invoke(null, (Object)new String[]{"111","222","333"});
4)//数组的反射。
int[] a1=new int[]{1,2,3};
int[] a2=new int[4];
int[][] a3=new int[2][3];
String[] a4=new String[]{"a","b","c"};
System.out.println(a1.getClass() == a2.getClass());
//true,具有相同维度且具有相同的类型。
System.out.println(a1.getClass() == a4.getClass()); //false
System.out.println(a1.getClass() == a3.getClass()); //false
System.out.println(a1.getClass().getSuperclass().getName()); //java.lang.Object
System.out.println(a4.getClass().getSuperclass().getName()); //java.lang.Object
Object obj1 = a1;
Object obj2 = a4;
//Object[] obj3 = a1; int类型不是Object类型。
Object[] obj4 = a3;
Object[] obj5 = a4;
System.out.println(a1);
System.out.println(a4);
//Arrays.asList()方法处理int[]和String[]时的差异。int[]本身就是一个对象,把它看为一个整体。
System.out.println(Arrays.asList(a1));
//[[I@efb549]
System.out.println(Arrays.asList(a4)); //[a,b,c]
//Array工具类,用于完成对数组的反射操作。
printObject(a1);
printObject("xyz");
}
//自定义方法数组反射。
private static void printObject(Object obj) {
Class clazz = obj.getClass();
if(clazz.isArray()){
int len = Array.getLength(obj);
for(int i=0; i
System.out.println(Array.get(obj, i));
}
}else{
System.out.println(obj);
}
}
//自定义一个改变反射点对象的String变量值的方法。
private static void changeStringValue(Object obj) throws IllegalArgumentException, IllegalAccessException {
Field[] fields=obj.getClass().getFields(); //得到所有的成员变量
for (Field field : fields) { //遍历成员变量数组
//if(field.getType().equals(String.class)){
//字节码只有一份,用==比,语义更明确。
if(field.getType() == String.class) { //如果成员变量的类型是String,则进行以下操作。
String oldValue = (String)field.get(obj); //得到某一对象的成员变量
String newValue = oldValue.replace('b', 'a'); //把该变量的b字符换成a字符。
field.set(obj, newValue); //把新的字符串赋给该对象的String变量。
}
}
}
}
//用发射来执行这个类
class TestArguments {
public static void main(String[] args){
for (String arg : args) {
System.out.println(arg);
}
}
}
//定义一个反射点,专门用来做反射用。
import java.util.Date;
public class ReflectPoint {
private Date birthday = new Date();
private int x;
public int y;
public String str1="ball";
public String str2="basketball";
public String str3="itheima";
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
@Override
//hashCode(),应用在底层必须是hash表的数据结构,先算出一个区域。
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
//参数写成ReflectPoint pt,equals()为重载,不是覆盖。
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ReflectPoint other = (ReflectPoint) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
@Override
public String toString() {
return str1 + ":" + str2 + ":" + str3;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
8,hashCode()本质和内存泄露,面试重点。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Properties;
import java.util.TreeSet;
public class ReflectTest2 {
public static void main(String[] args) throws Exception {
//模拟一个框架,用反射来获取集合。
InputStream ips = new FileInputStream("config.properties"); //
尽量面向父类或接口编程。
//实际开发中,没人用这种相对路径。
//解决方法1(最常用的):使用绝对路径,但不是硬编码,而是运算得出来的。javaWeb中getRealPath()(得到某个工程所在的实际磁盘位置)+内部路径(相对于这个工程的路径),这两个拼起来就得到配置文件的绝对路径。
//解决方法2:利用类加载器,装载classPath文件夹下的配置文件。
缺点:只能读出,不能写入。
方案2的第一种方式:
//InputStream ips =
ReflectTest2.class.getClassLoader().getResourceAsStream(
"com/itheima/day1/config.properties");
这句不能以“/”打头。证明是一个相对路径,相对于classPath指定目录下。
SSH三大框架内部用的就是类加载器加载的原理,所以他的配置文件放在classPath指定的路径下。
方案2的第二种方式:
//利用CLass提供的方法直接获取配置文件,书写更简便。
//InputStream ips = ReflectTest2.class.getResourceAsStream("resources/config.properties");
直接用类提供的方法更聪明,它相对与所在的包下面。
当然用类提供的方法也可以使用绝对
路径,以“/"打头,表示相对于classpath的根目录,与类加载器道理一样了。
InputStream ips = ReflectTest2.class.getResourceAsStream("/com/itheima/day1/resources/config.properties");
Properties props = new Properties();
//Properties是一个增强的Map集合,可以直接从硬盘上获取key,value值到内存。
props.load(ips);
ips.close(); //用完马上关闭,否则会有内存泄露,不是对象不被释放而是对象关联的系统资源不被释放。对象ips的释放由java的垃圾回收器释放。
String className = props.getProperty("className");
Collection collections = (Collection)Class.forName(className).newInstance();
Collection collections = new HashSet();
//这个集合来保证元素唯一,复写hashCode()和equals()方法。
ReflectPoint pt1 = new ReflectPoint(3, 3);
ReflectPoint pt2 = new ReflectPoint(5, 5);
ReflectPoint pt3 = new ReflectPoint(3, 3);
collections.add(pt1);
collections.add(pt2);
collections.add(pt3);
collections.add(pt1);
//更改变量y的值,利用hashCode()找不到pt1对象,pt1无法删除,造成内存泄露。
内存泄露,即某一对象不再用,它一直占用内存空间,而不被释放掉。
pt1.y = 7;
System.out.println(collections.remove(pt1));
System.out.println(collections.size());
}
}
9
,反射的主要应用:实现框架功能。例如
struts,spring,hibernate
。
例如:1)
在我们还没有写程序时,框架就已经写好了。
2)
还有就是上述7中利用
反射Method方法调用别人类的main方法,当别人的类还没有定义出来时,我们的类就可以编译了。
框架和工具类的区别,工具类是被调用,框架是调用别人。
模拟一个框架。
1)定义一个配置文件config.properties。 className=java.util.ArrayList。运行时用户只用改一下配置文件就Ola。
2) 查看上述8的程序,用反射来模拟一个框架,实现集合创建。
10,反射的第二种应用:内省IntroSpector。用来对JavaBean操作。
JavaBean是一个特殊的java类,这种java类内部的方法名称符合某种约定的规则。例如setAge(),getAge()。
一个符合JavaBean特点的类可以当普通类来用,但普通Java类不一定能当作JavaBean来处理。
JavaBean主要用来传递数据信息,所以如果两个模块之间传递多个信息,可以将这些信息封装在JavaBean中。这种Java类的方法主要用于访问私有的字段。
JavaBean的特点:
1)JavaBean必须是个公开的类,即它的访问权限必须是public的。
javaBean是为了给其他类来用,所以其方法一般都是Public类型的。
2)JavaBean必须具有一个无参数的构造方法。如果在JavaBean中定义了自定义的有参构造方法,就必须添加一个无参数的构造方法,否则将无法设置属性;如果没有定义自定义的有参构造方法,则可以利用编译器自动添加的无参构造方法。
3)JavaBean一般将属性设置成私有的,通过使用getXXX()和setXXX()方法来进行属性的取得和设置。
javaBean的属性是根据set,get方法得到的:去掉get和set后就是JavaBean类的属性,属性命名规则,如果第二个字母是小的,则把第一个字母变成小的Age-->age。如果第二个字母是大的,则第一个字母保持不变CPU-->CPU。
接下来用内省的方式来完成对javaBean的操作。查阅下篇日记高新技术2。
11,常用英语:
java ee——
Java Platform
,
Enterprise Edition
IDE
——IntegratedDevelopment Environment,
集成开发环境
jms ——
Java
消息服务(
JavaMessageService
)
JMX ——
JavaManagementExtensions
,即
Java
管理扩展
JNDI——
JavaNamingandDirectoryInterface
,
Java
命名和目录接口