hashCode()及HashSet集合类
hashCode()方法,是Object类中的方法, 返回对象的哈希码值,这个值是根据对象的引用以某种方式转换而来的,不同的对象,其引用值不一样,其散列码也不一样
HashSet集合就是采用哈希算法存取对象的集合,它将存储区域分成n个,存对象时:对象需调用hashCode()方法,得到自身的哈希码值,然后用这个值对n进行取模运算,根据取模得到的值,再将对象存于0--(n-1)中特定的某个区域,存之前,需将此对象与本区域的所有已存在的对象作比较(调用equals(Objct obj)方法),如果存在和这个对象所对应的Class对象及每个属性值相同的对象,就说明要存储的这个对象和此对象是同一个对象则不能重复存,(即:如果有两个对象被分配到了同一个区域,且它们的属性值和Class对象都相同,即使它们的对象名和对象引用不同,HashSet集合也会认为它们是同一个对象,则不允许重复存)如果我们想把由同一个类创建的,属性值相同,而对象引用和对象名不同的对象看做是同一个对象,即不让它们在HashSet里重复存,则我们只需要让这两个对象被分配到了同一个区域),即:使得它们调用hashCode()时返回的散列码相同,这样再对n取模,就会相等,也就会被分到同一个区域。默认的时候它们调用的是Object类的hashCode(),返回的哈希码肯定不同,因为它们的引用不同,对n取模后,不一定能落在同一个区域,这样即使它们的属性值和Class对象都相同,也有可能同时存进去。这不是我们所希望的,这时我们就要在这两个对象所在的类里需要覆盖掉Object类的hashCode()方法,使得只要是两个对象的属性值和Class对象相同,它们调用hashCode()返回的哈希码就相同,在用HashSet集合存储的时候就会落到同一个区域,达到属性值和Class对象都相同的对象不能重复存取的效果。
综上所述: hashCode()的作用就是将同一个类所创建的、属性值相等的、不同的对象,在采用哈希算法存取对象的集合里认为是同一个对象,不允许它们重复存取。例:
Collection collections = new HashSet();
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);/*如在ReflectPoint类中覆盖了hashCode()和equals(Object obj)方法,pt3==pt1, HashSet集合不允许重复存入对象,pt1已加进去,所以pt3加不进去*/
pt1.y = 7;
collections.remove(pt1); 如在加入pt1后再改变其变量值 ,然后再想删除它,就不可能了,因为对象变量值变了,取对象时,对象的哈希码值就和以前不一样了(在对象的hashCode()方法中计算散列码时用到了对象变量值),算出的查找区域也就和存的时候不一样了,这样永远也取不出pt1,pt1所占用的内存也就永远不会被释放,也就出现了内存泄露
public class ReflectPoint {
private int x;
public int y;
public ReflectPoint(int x, int y) {
super();
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;
final ReflectPoint other = (ReflectPoint) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}
注:如果不用哈希算法对象集合,hashCode()算法对于上面的程序就没有什么用了如用ArryList集合类,平时所比较的obj1==obj2,比较的是对象的引用值,不是哈希值
框架
怎么将一个配置文件放在classpath路径下?
只需将这个文件放在Eclipse的源文件所在的目录中,在编译的时候,Eclipse会自动将这个文件原封不动的复制到与字节码所在的同一个文件夹中,在Eclipse中classpath的路径为:
项目:\bin;
真正在用的时候是用的classpath路径下的配置文件
/*getRealPath();//金山词霸/内部
一定要记住用完整的路径,但完整的路径不是硬编码,而是运算出来的。*/
//InputStream ips = new FileInputStream("config.properties");
/*InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/resources/config.properties"); 这是类加载器在classpath指定的路径bin:\下找cn/itcast/day1/config.properties这个资源,cn/itcast/day1/这是包的路径名,加载类的时候直接在classpath指定的路径下加载cn.itcast.day1.类名就行,而config.properties这是个配置文件不是类,所以加载的时候得把包名写成路径的形式且 cn前不可加“/”*/
InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");
/* 此时由Class对象直接调用资源,其内部实际还是用类加载器调用的,这时,配置文件的路径可以是相对ReflectTest2类字节码的路径,也可以是绝对路径,绝对路径的根目录是classpath指定的路径bin:\,这是系统指定的,只要从这往下写就行了 。如下:
InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/resources/config.properties"); 注:cn前必须加“/”*/
Properties props = new Properties();
props.load(ips);
ips.close();
String className = props.getProperty("className");
Collection collections = (Collection)Class.forName(className).newInstance();
内省(IntroSpector)
JavaBean -特殊的JAVA类
方法名符合某种规则的类:方法前面都有get或set,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段(成员变量)。JavaBean的属性是根据其中的getter或setter方法来确定的,而不是根据其中的成员变量
去掉方法名前面的get 和set剩下来的字段就是JavaBean类的属性
如:
getAge-àAge(但需要把” A”àa)-àage
这有一个规则,如果剩下来的字段的第二个字母是小的,则把第一个字母变成小的,否则不变
例:getageàage
getAgeàage
getAGEàAGE
总之:一个类被当做JavaBean使用时,它的属性是根据方法名推断出来的,它根本看不到JavaBean类内部的成员变量
一个符合JavaBean特点的的类可以当做普通类来使用,也可以当做一个JavaBean类来使用,把它当做JavaBean用的好处如下:
(1) 如果在两个模块之间要传递很多信息,就可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value-Object)简称:VO
对JavaBean的简单内省操作:
public class IntroSpectorTest
{
private static void setProperty(Test1 t, String propertyname, int value)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException
{
PropertyDescriptor pro2=newPropertyDescriptor(propertyname,t.getClass());
Method methodsetAge=pro2.getWriteMethod();
methodsetAge.invoke(t, value);
}
public static Object getProperty(Test1 t, String propertyname)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException
{
PropertyDescriptor pro=newPropertyDescriptor(propertyname,t.getClass());
Method methodgetAge=pro.getReadMethod();
Object retval=(Object)methodgetAge.invoke(t);
/* BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass());
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
Object retval = null;
for(PropertyDescriptor pd : pds){
if(pd.getName().equals(propertyname))
{
Method methodGetX = pd.getReadMethod();
retval= methodGetX.invoke(t);
break;
}
}*/
return retval;
}
public static void main(String []args)
{
Test1 t=new Test1();
String propertyname1="age";
String propertyname2="num";
Object retval1 = getProperty(t, propertyname1);
System.out.println(propertyname1+"="+retval1);
Object retval2 = getProperty(t,propertyname2);
System.out.println(propertyname2+"="+retval2);
int value=7;
setProperty(t, propertyname1, value);
Object retval3=getProperty(t,propertyname1);
System.out.println(retval3);
setProperty(t, propertyname2, value);
Object retval4=getProperty(t,propertyname2);
System.out.println(retval4);
}
}
使用BeanUtils工具包操作JavaBean
首先,需要下载BeanUtils工具包,BeanUtils工具包内又用到了logging包,所以logging包也需要下载
将外部jar包导入到Eclipse工程里面,供工程使用,方法:
工程->New->Folder(name:lib(任意))->将jar包复制到lib->在Eclipse里右键点jar包-> buildPath-> addtoBuildPath
public class IntroSpectorTest
{
public static void main(String []args)
{
Test1 t=new Test1();
String propertyname1="age";
String propertyname2="num";
/*java7的新特性,BeanUtils可以为map设置key和value,BeanUtils类还可以将对象
的属性及值转换为map的key和value,也可以将map的key和value转换成对象的属性及值
Map map = {name:"zxx",age:18};
BeanUtils.setProperty(map, "name", "lhm");
*/
BeanUtils.setProperty(t, "birthday.time", "111");//此处传递的对象的属性值参数是字符串类型, birthday为Date对象,Date内有setTime()方法,所以Date类可看做javaBean类,由此birthday有属性time,此处用到了属性嵌套
System.out.println(BeanUtils.getProperty(t, "birthday.time"));
PropertyUtils.setProperty(t, "age", 9);//此处传递的对象的属性值参数是整数形式,和属性age类型相同
System.out.println(PropertyUtils.getProperty(pt1,"age").getClass().getName());//结果:java.lang.Integer
//BeanUtils类与PropertyUtils类的区别:设置对象属性值时传递的参数类型不同
}
}
public class Test1 {
private int i=4;
private int j=5;
private Date birthday=null;
public Test1()
{}
public Test1(int i,int j)
{this.i=i;this.j=j;}
public int getAge()
{return i;}
public int getNum() {
return j;
}
public void setNum(int j) {
this.j = j;
}
public void setAge(int i) {
this.i = i;
}}
注解(Annotation Types)
用于告诉编译器或工具软件,向它传递一种信息
一个注解就是一个类对应的实例
@SuppressWarnings("deprecation"):压缩警告注解,告诉编译器忽略过时警告
@Deprecated:过时注解,告诉编译器这是个过时的东西
@Override //例如在子类里覆盖了父类的一个方法,在子类的这个方法前面用这个注释
用以说明这个类是重写的父类的方法,如果哪个参数 返回值等与父类不同,编译器会报错
Alt+/
总结:注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,编译器,开发工具,和其他程序可以反射来了解你在类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加载包,类,字段,方法,方法的参数以及局部变量上。
定义一个简单的注解:
元注解:注解的注解
@Retention(RetentionPolicy.RUNTIME)//这个元注解,用于设置这个注释的生存期为运行阶段。RetentionPolicy是一个枚举,可取的值有:SOURCE(在源文件里保存这个注释,其他时期将废弃这个注释)CLASS(在编译后的class文件里仍然保存这个注释),RUNTIME(在内存中的字节码文件里仍然保存这个注释)
@Target(ElementType.METHOD,ElementType.TYPE)//这个元注解,用于设置这个注解可运用的范围 ElementType也是一个枚举,TYPE代表可注释类,枚举,接口
Public @interface IncastAnnotation{
}
把注解IncastAnnotation加到类AnnotationTest上,并用反射测试AnnotationTest上是否有注解@IncastAnnotation
@IncastAnnotation
public class AnnotationTest {
public static final void main(String[]args)
{
if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class))
{
ItcastAnnotation annotation = (ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class);
System.out.println(annotation);}
}
}
为注解增加属性:
属性类型可为8个基本类型、String、Class类型、枚举、注解、以及前面这些类型的数组
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD,ElementType.TYPE)
Public @interface IncastAnnotation{
String color() default “blue”;//如果注解在此时设置默认属性值,则在用的时候可以不用写此属性
int value();/*这是个特殊的属性名,如果其他属性已设置默认值,则在用的时候,如果只需要设置此属性值,可以只写值不用写“value=” 用value名定义一个数组也可以,即:
int[] value();兼有数组属性和value属性的共同特点*/
int[]array() default {1,3,5};//数组属性,在下面用的时候
}
EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.RED;//定义枚举属性EnumTest是一个类,TrafficLamp是类中的一个交通灯枚举
MetaAnnotation annotationAttr() default @MetaAnnotation("lhm");/*定义一个注解属性annotationAttr,属性值是注解类型的,即MetaAnnotation的一个实例对象*/
public @interface MetaAnnotation {
String value();
}
在用的时候为它设置属性值:
@IncastAnnotation(color=”abc”,value=12,array={2,5})
/*如果数组属性只有一个值,可以省略{},即@IncastAnnotation (annotationAttr=@MetaAnnotation("flx"), color=”abc”,value=12,array=5)*/
public class AnnotationTest {
public static final void main(String[]args)
{
if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class))
{
ItcastAnnotation annotation = (ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class);}
}
System.out.println(annotation.color());
System.out.println(annotation.value());
System.out.println(annotation.array().length);
System.out.println(annotation.lamp().nextLamp());//Green
System.out.println(annotation.annotationAttr().value());
}
}
Java语言规范:langspec-3.0 j3TOC.html->Annotation