内容导航: MyEclipse使用 静态导入 可变参数 增强for循环 基本数据类型自动拆/装箱 HashSet、hashCode分析 beanutils工具包的使用
反射:Class类 Field类 Constructor类 Method类 数组的反射 反射的作用:实现框架功能 JavaBean
- 低版本的java不能运行高版本的javac编译的文件。
- 选择运行时的编译器:Window>Preferences>Java>Complier
- Java编译器5.0就是1.5,JDK1.5.0即JDK5.0,MyEclipse6.6自带的运行环境是5.0的
- 选择运行时的JAVA运行环境:Window>Preferences>Java>Installed JREs>添加>标准VM>JRE 主目录,如选择:C:\Java\jdk1.6.0\jre—>单击完成即可。
查看个体工程的运行环境:
- 右击工程名>Run As>Run Configurations>右边窗口的JRE选项卡
- 右击工程名>Preferences>Run/Debug Settins>选择右边窗口的类>Edit>JRE
- 切换工作间:File>Switch Workspace
- 重命名工程:右击工程名>Refactor>Rename
- 新建类时,包名一般用公司的域名,域名要倒过来写,如cn . itcast
- 变量名、类名等要用英文单词,不要用A、B这样来命,如果不会单词就查词典
MyEclipse调式:
public static void main(String args[])
{
int x=0;
x++;
System.out.println(x);
}
在x++;语句前面双击增加一个断点,在输出语句前面双击增加一个断点,右击代码窗口,选择Debug As>Java Application,然后在代码窗口选择x++中的x,然后右击>Watch,然后点击工具栏上的Step Over(F6)就可以查看该x变量的值的变化情况了。
- 调出各种窗口:Window>Show View
- 代码模板:
选中代码右击>Surround With
设置:Window>Preferences>Java>Editor>Templates>New:
Name处输入模板名称,Pattern处输入:
try{
${line_selection}//代表当前选择的内容
}
finally{
${cursor}//代表光标定位到这里
}
单击OK>Aplly>OK。
从一个工作间导出一个工程到另一个工作间:
- 把这个工程的整个目录复制到新工作间根目录下
- File>Import>General>Existing Projects into Workspace>Next>Browse…>选择工作间根目录或工程的根目录>确定> 在Projects勾选要导入的工程>Finish
- 注意:如果导进来的工程的JRE System Libray的安装目录与我们的工作间的不一样就会出问题,解决方法:右击工程名>Build Path>Configure Build Path…>切换到Libraries选项卡>选择库>Remove,然后再加入我们自己的库即可。
- Libraries选项卡各按钮介绍:
- Add JARs… 增加单独的一个JAR包,且这个JAR包是存放在工程目录下的。
- Add External JARs… 增加一个单独的JAR包,且这个JAR包不是存放在工程目中的。
- Add Library… 增加一个JAR包库,点击Add Library…后,选择库即可,其中“User Library”选项为用户自定义的jar包库,选择该项然后按Next即可看到自定义的用户库,如果要增加一个,可单击User Libraries… >New>输入一个库名>OK>Add JARs..增加jar包进来即可。
静态导入 返回顶部
- import语句可导入一个类或某个包中的所有类
- import static 语句导入一个类中的某个静态方法或所有静态方法
实例:
package cn.itcast.day1;
import static java.lang.Math.max; //静态导入一个类中的一个方法
import static java.lang.Math.*; //静态导入一个类中的所有方法
public class StaticImport {
public static void main(String args[])
{
System.out.println(Math.max(3, 6));//静态导入后可真直接使用:max(3,6),无需加类名
System.out.println(Math.abs(3 - 6));//求会对值
}
}
可变参数: 返回顶部
特点:
- 只能在参数列表的最后
- …位于变量类型和变量名之间,前后有无空格都可以
- 调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数。
当一个方法要接受的参数不固定时可以使用可变参数,如一个add()方法,可接受任意个数字进行相加,代码如下:
public class VariableParameter { public static void main(String[] args) { System.out.println(add(1, 2)); System.out.println(add(1,2,3)); } public static int add(int x,int ... args) { int sum=x; for(int i=0;i<args.length;i++) { sum+=args[i]; } return sum; } }
块注释快捷键:选中要注释的代码,按Ctrl+Shift+/
增强for循环 返回顶部
语法:for(type 变量名: 集合变量名){ … }
功能:用 变量名 去逐个取出 集合变量名 中的元素,每取出一个元素就执行一次{ }中的语句
注意事项:迭代变量必须在( )中定义!
集合变量可以是数组或实例了Iterable接口的集合类
举例:
public static int add(int x,int ... args) { int sum=x; for(int arg:args) { sum+=arg; } return sum; }
基本数据类型的自动装箱与拆箱(这是1.5版本的特性) 返回顶部
自动装箱:
Integer num1=12;//自动把一个基本数据类型装成了一个Integer对象类型再赋给num1引用变量
自动拆箱:
System.out.println(num+12);//把Integer类型的num变量自动转换成基本数据类型再与12相加
基本数据类型的对象缓存:
Integer num1=12;
Integer num2=12;
System.out.println(num1 == num2);//true
Integer num1=129;
Integer num2=129;
System.out.println(num1 == num2);//false
基本数据类型int转换为Integer类型,如果这个数的大小在1字节大小之间(-128~127),则把这个数字放到缓冲池里面,如果下次用到就直接在缓冲池取出,而不用再重新分配。这就叫享元模式(flyweight )
Integer num1=Integer.valueOf(12);
Integer num2=Integer.valueOf(12);
System.out.println(num1 == num2);//true
星期天用0表示,星期一到星期六用1~6表示。
英文,简写,中文
Monday , Mon 星期一
Tuesday, Tue 星期二
Wednesday,Wed 星期三
Thursday,Thu 星期四
Friday,Fri 星期五
Saturday,Sat 星期六
Sunday,Sun 星期日
枚举 返回顶部
枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错。枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。
普通类如何实现枚举功能:
- 私有的构造方法
- 每个元素分别用一个公有的静态成员变量表示
- 可以有若干公有方法或抽象方法,例如,要提供nextDay方法必须是抽象的。
实例:模拟枚举的功能,让一个类只能产生固定的实例。
EnumTest.java public class EnumTest { public static void main(String[] args) { WeekDay mon=WeekDay.MON; WeekDay nextday=WeekDay.MON.nextDay(); System.out.println(nextday);//打印一个实例对象名会自动调用该对象的toString()方法。 } } WeekDay.java public abstract class WeekDay { private WeekDay(){} public static final WeekDay MON=new WeekDay(){ public WeekDay nextDay() { return SUN; }}; public static final WeekDay SUN=new WeekDay(){ public WeekDay nextDay() { return MON; }}; public abstract WeekDay nextDay(); public String toString() { return this==MON?"MON":"SUN"; } }
枚举的基本应用
- 扩展:枚举类的values,valueOf,name,toString,ordinal等方法
- 总结:枚举是一种特殊的类,其中的每个元素老师该类的一个实例对象。
实例:
public class EnumTest { public static void main(String[] args) { WeekDay weekday=WeekDay.MON; System.out.println(weekday); System.out.println(weekday.toString()); System.out.println(weekday.name());//返回这个实例名 System.out.println(weekday.ordinal());//打印MON元素在枚举列表中的序号,第一位从0开始 System.out.println(weekday.getClass());//返回这个实例的类名 //以下是枚举的静态方法 System.out.println(WeekDay.valueOf("SAT"));//通过指定字符串返回一个枚举实例。 System.out.println(WeekDay.values()[0]);//把所有的枚举元素封装成一个数组返回 } public enum WeekDay { SUN(1),MON(),TUE,WED,THU,FRI,SAT;//此处的分号可要可不要,如果还有成员的话,那么分号必须要 //如果有还有其他类成员,则这个元素列表要放在所有类成员的最前面 //在元素后面加(参数),则可以指定调用哪个构造方法 private WeekDay(){ //枚举的构造方法必须是私有的 System.out.println("First"); } private WeekDay(int day){ System.out.println("Second"); } } }
注:内部类可以有4个修饰符,而外部类只能有public和默认两种修饰符
内部类编译后的文件形式为: 外部类名$内部类名
匿名内部类编译后的形式为: 外部类名$1
外部类名$2
实例:定义一个交通灯枚举
public enum TrafficLamp { RED(60){ public TrafficLamp nextLamp() { return GREEN; }}, GREEN(40) { public TrafficLamp nextLamp() { return YELLOW; }}, YELLOW(5){ public TrafficLamp nextLamp() { return RED; }}; private int time; public abstract TrafficLamp nextLamp();//因为这里定义为abstract方法后这个枚举类就是一个抽象类, //所以不能直接用new产生这个枚举的实例,必须定义一个子类来产生实例,所以在枚举元素名的后面加{ 代码 }来 //产生匿名内置类,而这元素名就是这个匿名内置类的实例的引用变量。 private TrafficLamp(int time) { this.time=time; } }
枚举只有一个成员时,就可以作为一种单例的实现方法。
反射的基石→Class类 返回顶部
java程序中的各个java类属于同一类事物,描述这类事物的Java类就是Class。
Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属的包名,字段名称的列表,方法名称的列表等等。
众多的人用Person表示,众多的Java类用Class表示
Person类它的实例对象就是张三、李四这样一个个具体的人
Class类的各个实例对象又分别对应什么呢:
- 对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。
- 一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可以分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?就是Class类型
如何得到各个字节对应的实例对象(Class类型)
- 类名.class,例如:System.class
- 对象.getClass(),例如,new Date().getClass()
- Class.forName(“类名”),例如:Classic.forName(“java.util.Date”);这里有两种情况:1、如果这个类已经加载过,这个类的字节码已经存在Java虚拟机中了,则直接返回。如果这个类还没有加载过,则类加载器先把这个类加载到Java虚拟机缓存起来,然后再返回这个类字节码。
Person p1 = new Person();
Person p2=new Person();
Date类
Math类
Class cls1=Date.class//字节码
Class cls2=Person.class //字节码
p1.getClass();//获取该实例所属的类的字节码
Class.forName(“java.lang.String”)//返回String类的字节码
public class ReflectTest { public static void main(String args[]) throws Exception { 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); } }
9个预定义的Class实例对象:
有九种预定义的 Class 对象,表示八个基本类型和 void。这些类对象由 Java 虚拟机创建,与其表示的基本类型同名,即 boolean、byte、char、short、int、long、float 和 double。
参看Class.isPrimitive方法的帮助
Int.class= = Integer.TYPE
数组类型的Class实例对象
Class.isArray()
总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如int, int[],void等类型
public static void main(String args[]) throws Exception { Class cls1 = str.getClass(); System.out.println(cls1.isPrimitive());//结果false,该方法:判定指定的 Class 对象是否表示一个基本类型。 System.out.println(int.class.isPrimitive());//结果true System.out.println(int.class == Integer.class);// 结果 false System.out.println(int.class == Integer.TYPE);// 结果true,TYPE属性代表Integer对象所包装的的基本类型的字节码 }
反射
反射就是把Java类中的各种成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。
Constructor类 返回顶部
- Constructor类代表某个类中的一个构造方法
- 得到某个类所有的构造方法:
例子:Constructor[ ] constructors=Class.forName(“java.lang.String”).getConstructors();
- 得到某一个构造方法:
- 例子:Constructor constructor=Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);//获得方法时要用到类型
- 创建实例对象:
- 通常方式:String str=new String(new StringBuffer(“abc”));
- 反射方式:String str=(String)constructor.newInstance(new StringBuffer(“abc”));
//调用获得的方法时要用到上面相同类型的实例对象。
- Class.newInstance()方法:
- 例子:String object=(String)Class.forName(“java.lang.String”).newInstance();
- 该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
- 该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。
Field类(字段也就是成员变量) 返回顶部
Field类代表某个类中的一个成员变量
问题:得到的Field对象 对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,那该关联哪个呢?所以字段Field代表的是x的定义,而不是具体的x变量。
public class FieldTest { private int x; public int y; public FieldTest(int x, int y) { this.x = x; this.y = y; } public static void main(String[] args) throws Exception { FieldTest ft = new FieldTest(3, 5); Field fieldY = ft.getClass().getField("y");//该方法只能获取公共的字段 System.out.println(fieldY.get(ft)); Field fieldX = ft.getClass().getDeclaredField("x");//获取字段(包括私有的) fieldX.setAccessible(true);//设置私有字段可以被访问 System.out.println(fieldX.get(ft)); } }
注:字节码的比较用等号(=)比较,而不用equals。
实例:将一个对象中的所有String成员变量中的字母b改为a。
package cn.itcast.day1; import java.lang.reflect.Field; public class FieldTest { public String str1="ball"; public String str2="basketball"; public String str3="itcast"; public String toString() { return str1 + ":::" +str2 + ":::" + str3; } public static void main(String[] args) throws Exception { FieldTest ft = new FieldTest(); ft.changeStringValue(ft); System.out.println(ft); } public void changeStringValue(Object obj) throws Exception { Field [] fields = obj.getClass().getFields(); for(Field field: fields) { if(field.getType() == String.class) { String oldStr = (String)field.get(obj); String newStr = oldStr.replace('b', 'a'); field.set(obj, newStr); } } } }
Method类 返回顶部
Method类代表某个类中的一个成员方法
得到类中的某一个方法:
Method charAt = Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);
调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式:System.out.println(charAt.invoke(str,1));
如果传递给Method对象的invoke()方法的一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!
jdk1.4和jdk1.5的invoke方法的区别:
jdk1.5:public Object invoke(Object obj,Object… args)
jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应一个参数对象。
注意:参数类型是用Class来表示的,反射不能读非public的类
实例,调用用反射调用String类中的charAt方法
String s = "abcd";
Method str = String.class.getMethod("charAt",int.class);
System.out.println(str.invoke(s, 2));//输出 c,虽然这里给的参数是2,是一个基本类型,但是会自动装箱成Integer对象的。用JDK1.4来使用这个方法为:
Object[] obj = new Object[]{2};
Str.invoke(s,obj);或者:
Str.invoke(s, new Object[]{2});
//invoke(Object,Object …args)如果第一个参数为null,则说明调用的这个方法是静态方法,因为静态方法被调用时不需要产生对象,所以不用指定对象。
注:人在黑板上画圆,这里有3个对象:人、黑板、圆,那画圆的方法应该属于哪个对象上的方法呢?在圆对象上有圆心、半径的私有成员属性,而画圆需要这些属性,不可能让外面的对象来方法吧,所以画圆的方法属于圆对象。记住一句话,就是谁拥有数据,谁就拥有方法。
用反射方式执行某个类中的main方法
目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
问题:启动Java程序的main方法的参数是一个字符串数组,通过 反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成若干个单独的参数。所以,在给main方法传递参数时,不能使用mainMethod.invoke(null,new
String[]{“xxx”}),;javac只把它当作jdk1.4的语法进行理解。而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。
解决办法:
mainMethod.invoke(null,new Object[]{new String[]{“xx”}});
mainMethod.invoke(null,(Object) new String[]{“xx”});编译器会作特殊处理,编译时不把参数当作数组看待,也就不会把数组打散成若干个参数了。.
用普通的方法调用mian方法:
class TestArguments { public static void main(String[] args) { for(String arg: args) System.out.println(arg); } } class InvokeMain { public static void main(String[] args) { new TestArguments().main(new String[] {"abc","111"}); } }
用反射的方式调用mian方法:
注:在“运行方式>运行配置”中的“自变量”选择卡中的“程序自变量”的内容即为运行类时传递给main函数的参数。
在使用Class.foName()获取一个类的字节码时,这里面的类名需要写完整方式,即包括包名。
class TestArguments { public static void main(String[] args) { for(String arg: args) System.out.println(arg); } } class InvokeMain { public static void main(String[] args) { String startClassName = args[0]; Class startClassByteCode = Class.forName(startClassName); Method mainMethod =startClassByteCode.getMethod("main", String[].class); mainMethod.invoke(null, new Object[] {new String[] {"aaa","bbb","ccc"}}); } }可以看到使用反射调用main的好处,即编写代码时不知道要调用哪个类的main方法,在运行时你给一个类名作参数,给哪个类名就运行哪个类的main方法。
数组的反射 返回顶部
具有相同维数和元素的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回父类为Object类对应的Class。
Array.asList(Object[] obj ),这是jdk1.4的
Array.asList(Object… obj ),这是jdk1.5的,由于高版本兼容低版本,所以当这个方法传递的对象是一个数组是,则使用jdk1.4的处理方式,如果给的是一个一个的对象参数,则用jdk1.5的处理方式。
基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当作Object类型使用,又可以当做Object[]类型作用。
Array.asList( )方法处理int[]和String[]时的差异。实例:
int [] a = new int[] {1,2,3};//这个数组只能当作一个Object类型使用
String [] strs = new String[] {"a","b","c"};
System.out.println(Arrays.asList(a));//输出为:[[I@1bc4459]
System.out.println(Arrays.asList(strs));//输出为:[a, b, c]
Array工具类用于完成对数组的反射操作。
实例:编写一个方法,参数为一个Object,当这个Object为一个对象时则打印该对象,如果为数组,则打印数组元素:
public static void main(String[] args) throws Exception { String[] str1 = new String[] {"a","b","c"}; String str2 = "ddd"; printObj(str1); printObj(str2); } public static void printObj(Object obj) { Class objClass = obj.getClass(); if(objClass.isArray()) { int len = Array.getLength(obj); for(int i = 0 ;i<len;i++) System.out.println(Array.get(obj,i)); } else System.out.println(obj); }思考:怎么得到数组中的元素类型呢?
如:int [] a = new int[3];这是取得数组a是什么类型的
如:Object[] a = new Object[]{“a”,1};这里数组元素可以为String,也可以为int,所以也是无法取得数组a是什么类型的,但是可以取得每个具体的数组中的元素的类型:a[0].getClass().getName();
HashSet类与hashCode方法 返回顶部
如果想查找一个集合中是否包含有某个对象,大概的程序代码怎样写呢?你通常是逐一取出每个元素与要查找的对象进行比较,当发现某个元素与要查找的对象进行equals方法比较的结果相等时,则停止继续查找并返回肯定的信息。假如一个集合中有一万个元素,并且没有包含要查找的对象时,则意味着你的程序需要从集合中取出一万个元素进行逐一比较才能得到结论。于是有人发明了一种哈希算法来提高从集合中查找元素的效率,这种方式将集合分成若干个存储区域,根据一个对象的哈希码就可以确定该对象应该存储在哪个区域,如下图:
HashSet就是采用哈希算法存取对象的集合,它内部采用对某个数字n进行取余的方式对哈希码进行分组和划分对象的存储区域,比如HashSet分成了8个存取区域,当一个对象要存进来时先进行哈希值计算,然后将该值对8取余,则余数为0-7,余数为哪一个就存到哪一个区域,Object类中定义了一个hashCode()方法来返回每个Java的哈希码,当从HashSet集合中查找某个对象时,Java系统首先调用对象的hashCode()方法获得该对象的哈希码,然后根据哈希码找到相应的存储区域,并取出该存储区域内的每个元素与该对象进行equals方法比较,这样不用遍历集合中的所有元素就可以得到结论。可见,HashSet集合具有很好的对象检索性能,但是,HashSet集合存储对象的效率相对要低些,因为在向HashSet集合中添加一个对象时,要先计算出该对象的哈希码和根据这个哈希码确定该对象在集合中的存放位置。
所以,只有当一个集合的存在区域是采用哈希算法的,这时的hashCode方法算出来的哈希码才有价值,否则的话该方法没有用。
为了保证一个类的实例对象能在HashSet正常存储,要求这个类的两个实例对象用equals()方法比较的结果相等时,还要它们的哈希码也相等。就是说,如果obje1.equals(obj2)的结果为true,那么obj1.hashCode()==obj2.hashCode()也必须要为true。如果一个类的hashCode()方法没有遵循这个要求,那么,当这个类的两个实例对象用equals()方法比较的结果相等时,它们本来应该无法被同时存储进Set集合中,但是,如果将它们存储进HashSet集合时,由于它们的hashCode()方法的返回值不同,第二个对象首先按哈希码计算,可能会被放进与第一个对象不同的区域中,这样,它就不可能与第一个对象进行equals方法比较了,也就可能被存储进HashSet集合中了。Object类中的hashCode()方法不能满足对象被存入到HashSet中的要求,因为它的返回值是通过对象的内存地址推算出来的,所以,如果两个对象的成员属性相同,但是如果这两个对象的内存地址不同则它们的hashCode码不同。
只有类的实例对象要被采用哈希算法进行存储和检索时,这个类才需要按要求覆盖hashCode方法。即使程序可能暂时不会用到当前类的hashCode方法,但是为它提供一个hashcode方法也不会有什么不好,没准以后什么时候又用到这个方法了,所以,通常要求hashCode方法和equals方法被同时覆盖。
提示:
- 通常来说,一个类的两个实例对象用equals方法比较的结果相等时,它们的哗然码也必须相等,但反之则不成立,即equals方法比较的结果不相等时,它们的哈希码有可能相等,或者说哈希码相同的两个对象的equals方法比较的结果可以不等,例如,字符串“bb”和“Aa”的equals方法比较结果肯定不相等,但它们的hashCode方法返回值却相等。
- 当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段(成员变量)了,否则,对象修改后的哈希值与最初存储进HashSet集合中的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。
实例:
往HashSet集合里装对象,只有对象的equals方法比较相同,且hashCode码相同时,这个对象才不会被存进去,但实际上,只要成员属性相同我们就认为他是同一个对象,不应该再存进去,所以我们要覆盖该对象的equals方法与hashCode方法来达到目的。
import java.util.*; public class ReflectTest2 { public static void main(String[] args) { //Collection collections = new ArrayList(); Collection collections = new HashSet(); ReflectPoint p1 = new ReflectPoint(3,3); ReflectPoint p2 = new ReflectPoint(4,4); ReflectPoint p3 = new ReflectPoint(3,3); collections.add(p1); collections.add(p2); collections.add(p3); collections.add(p1); System.out.println(collections.size()); } } class ReflectPoint { int x; int y; public ReflectPoint(int x, int y) { this.x = x; this.y = y; } public int hashCode() { final int prime = 31; int result = 1; result = prime * result + x; result = prime * result + y; return result; } 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; } }(1)当没有hashCode与equals时,Java默认的hashCode方法的计算是有对象的内存地址参与计算的,所以只要对象的内存地址不同,则hashCode码就不同。Java默认的equals方法是比较两个对象的引用,所以只有引用相同才会返回true;
上面例子的3个对象的内存地址都不同,所以他们的hashCode码都不一样,p1对象存了两次,但只存进去一次,因为是同一个对象,所以它的equals返回结果肯定为true,它的hashCode码也一定相等,所以不会被存两次。所以在这里能存进去3个对象。
(2)覆盖后,hashCode的计算是对象的成员变量参与计算的,只有成员变量相同,hashCode就相同。而且equals方法比较的也是成员变量,只要成员变量相同则返回true。
p1和p3的equasl返回为true,而且他们的hashCode计算后相同,所以只存进了p1。所以在这里能存进去2个对象(p1、p2)。
反射的作用:实现框架功能 返回顶部
框架与框架要解决的核心问题
我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中,框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
框架要解决的核心问题
我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象,而要用反射方式来做。
实例:将上面的例子用反射来实现,
我们将要使用的集合名称保存到一个“config.properties”配置文件中,然后从这里读取使用这个集合类,这就是一个小框架实例应用。
其中ReflectPoint类的代码不变。
- 右击项目>新建>文件>在文件名处输入:config.properties>确定,在代码窗口的左下角点击“source”,然后输入 className = java.util.HashSet
- 其他代码如下:
package cn.itcast.day1 import java.io.*; import java.util.*; public class ReflectTest2 { public static void main(String[] args) throws Exception { File f = new File("config.properties");//这里一定要记住用完整的路径,但完整的路径不是给条固定不变的绝对路径,而是通过运算出来的绝对路径。 InputStream in = new FileInputStream(f); Properties p = new Properties(); p.load(in); String className = p.getProperty("className"); Collection collections = (Collection)Class.forName(className).newInstance(); ReflectPoint p1 = new ReflectPoint(3,3); ReflectPoint p2 = new ReflectPoint(4,4); ReflectPoint p3 = new ReflectPoint(3,3); collections.add(p1); collections.add(p1); collections.add(p2); collections.add(p3); System.out.println(collections.size()); } }
上面中的代码可以改为 :
1、InputStream in = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties"); 这是通过类加载器来加载config.properties文件到输入流中,这种方法有个缺点就是不能保存数据,因为没有输出流。
注:通常把配置文件放到java源文件的目录下,MyEclipse会把这个文件复制到java的class文件目录中的。
- InputStream in = ReflectTest2.class.getResourceAsStream("config.properties");//这里的路径是相对于ReflectTest2.java文件所在的目录,如果这个config.properties文件放在ReflectTest2.java当前目录下面的resouce目录中,则应该写为:
InputStream in = ReflectTest2.class.getResourceAsStream("resource/config.properties");
这种写法是相对.java文件所在目录的相对目录的方法
还有一种是相对.java文件根目录的相对目录的方法,如:
如果在参数的最前面加上了“/”,则代表是相对ReflectTest2.java的根目录的,如ReflectTest2.java在包:cn/itcast/day1中,即上面代码要写成这样:
InputStream in = ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/config.properties ");
内省->了解JavaBean 返回顶部
JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类中方法主要用于访问私有字段,且方法名符合某种命名规则。
如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称这为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法的名称叫什么好呢?JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,至于你把它存到哪个变量上,用管吗?如果方法名为getId,中文意思即为获取id,至于你从哪个变量上取,用管吗?去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小写的。
setId()的属性名:id
isLast()的属性名:last
getCPU的属性名:CUP
总之,一个类被当作JavaBeam使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到Java类内部的成员变量。
一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处我们才会去了解和使用它!好处如下:
在Java EE开发中,经常要使用到JavaBeam。很多环境就要求按JavaBean方式进行操作,别人都这么要求这么做,那你就没什么挑选的余地!
JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的x,怎么做,有一定难度吧?用内省这套API操作JavaBean比用普通类方式更方便。
实例:定义一个JavaBean类,然后通过JDK中提供的PropertyDescriptor类来获取所定义的JavaBean类中的私有变量x的值:
- 定义JavaBean类:
package cn.itcast.day1; class ReflectPoint2 { private int x; private int y; public ReflectPoint2(int x,int y) { this.x = x; this.y = y; } 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; } }
- 使用PropertyDescriptor类来获取所定义的JavaBean类中的私有变量x的值
package cn.itcast.day1; import java.beans.*; import java.lang.reflect.*; public class JavaBeanTest { public static void main(String[] args) throws Exception { ReflectPoint2 rp = new ReflectPoint2(3,5); String propertyName = "x"; //"x"-->"X"-->"getX"-->MethodGetX--> 获取名为"x"的变量的值的流程 PropertyDescriptor pd = new PropertyDescriptor(propertyName, rp.getClass()); Method methodGetX = pd.getReadMethod();//获取get方法 Object retVal = methodGetX.invoke(rp);//使用get方法 System.out.println(retVal); Object value = 8; PropertyDescriptor pd2 = new PropertyDescriptor(propertyName, rp.getClass()); Method methodSetX = pd2.getWriteMethod();//获取set方法 methodSetX.invoke(rp,value);//使用set方法 System.out.println(rp.getX()); } }
技巧:选择红色代码,按Shift+Alt+M进行方法抽取,然后在方法名处输入:getProperty—>确定即可自动生成一个方法,然后再把生成的方法的参数的ReflectPoint2类型改为Object,这样的方法比较通用一些。
上面的三句蓝色代码也可以进行一样的操作抽取出一个setProperty方法。设置后的代码如下:
package cn.itcast.day1; import java.beans.*; import java.lang.reflect.*; public class JavaBeanTest { public static void main(String[] args) throws Exception { ReflectPoint2 rp = new ReflectPoint2(3,5); String propertyName = "x"; //"x"-->"X"-->"getX"-->MethodGetX--> 获取名为"x"的变量的值的流程 Object retVal = getProperty(rp, propertyName); System.out.println(retVal); Object value = 8; setProperty(rp, propertyName, value); System.out.println(rp.getX()); } private static void setProperty(Object rp, String propertyName, Object value) throws IntrospectionException, IllegalAccessException, InvocationTargetException { PropertyDescriptor pd2 = new PropertyDescriptor(propertyName, rp.getClass()); Method methodSetX = pd2.getWriteMethod();//获取set方法 methodSetX.invoke(rp,value);//使用set方法 } private static Object getProperty(ReflectPoint2 rp, String propertyName) throws IntrospectionException, IllegalAccessException, InvocationTargetException { PropertyDescriptor pd = new PropertyDescriptor(propertyName, rp.getClass()); Method methodGetX = pd.getReadMethod();//获取get方法 Object retVal = methodGetX.invoke(rp);//使用get方法 return retVal; } }
得到BeanInfo最好采用“obj.getClass()”方式,而不要采用“类名.class”方式,这样的程序更通用。
采用遍历BeanInfo的所有属性方式来查找和设置某个RefectPoint对象的x属性。在程序中把一个类当作JavaBean来看,就是调用IntroSpector.getBeanInfo方法,得到的BeanInfo对象封装了把这个类当作JavaBean看的结果信息。修改上面的getProperty方法的代码如下:
private static Object getProperty(ReflectPoint2 rp, String propertyName) throws IntrospectionException, IllegalAccessException, InvocationTargetException { BeanInfo beanInfo = Introspector.getBeanInfo(rp.getClass()); PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); Object retVal = null; for(PropertyDescriptor pd: pds) { if(pd.getName().equals("x")) { Method methodGetX = pd.getReadMethod(); retVal = methodGetX.invoke(rp); break; } } return retVal; }
beanutils、logging工具包的使用 返回顶部
这是Apche提供的Java工具包,下载后解压,然后复制commons-beanutils.jar包,右击项目,新建一个文件夹为lib,右击该文件夹选择粘贴,然后右击该jar包,选择“构建路径->添加到构建路径”即可。用这种方式添加Jar包的好处是当把这个项目发给别人时jar包也跟着一起过去了。
在.java源文件中导入:import org.apache.commons.beanutils.BeanUtils;
然后就可以使用BeanUtils类中的各种方法了,其中的get/setProperty()方法用于设置与获得JavaBean(类)的成员变量的值,而这个JavaBean必须是public类型的。实例如下:
class BeanUtilsTest { public static void main(String[] args) throws Exception { Test01 t = new Test01(); System.out.println(BeanUtils.getProperty(t, "x"));//此方法返回的是字符串类型 BeanUtils.setProperty(t, "x", "8");//此方法可以接受任意类型的对象作参数,数字型的通常使用字符串 System.out.println(t.getX()); } } public class Test01 { //用作JavaBean的类必须是public类型的 private int x; public int getX() { return x; } public void setX(int x) { this.x = x; } }此时运行将会报错,说找不到某个类,这是因为beanutils工具包是Apache开发的,但是这个包同时也用到了其他的包中的类,所以这里还要导入用到的logging工具包才行。
假如在上在, 例子的Test01类中有一个Date成员变量:birthday,而这个Date类有一个setTime方法,这个方法名符合JavaBean的规范,所以可以把Date当成一个JavaBean来使用,相当于Date有了一个time方法,实例如下
import java.util.Date; import org.apache.commons.beanutils.BeanUtils; class BeanUtilsTest { public static void main(String[] args) throws Exception { Test01 t = new Test01(); BeanUtils.setProperty(t, "birthday.time", "111"); System.out.println( BeanUtils.getProperty(t, "birthday.time")); } } public class Test01 { Date birthday = new Date(); public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } }
注:BeanUtils.getProperty(t, "birthday.time")这句代码就相当于获取对象t的birthday属性,再获取birthday属性的time属性。即获取对象t的birthday属性的time属性。这如果用前面的反射方法来自己做将是非常难办到的事情。
还用PropertyUtils.get/setProperty()方法,用法与BeanUtils的差不多,
区别:
1、PropertyUtils.setProperty()方法要求的参数为对象属性本身的类型,
BeanUtil.setProperty()方法要求的参数可心是任何对象,一般为String。
2、PropertyUtils.getProperty()法是返回的是属性本身的类型
BeanUtil.getProperty()方法返回的是字符串类型
总结就是PropertyUtils的get/set方法是针对对象属性本身的类型进行操作的,而BeanUtils的get/set方法是针对字符串进行操作的。