黑马程序员--Java基础加强(高新技术)学习第二天
二十六、ArryList、HashSet的比较及 hashCode()分析
1、hashCode()
(1)首先引入哈希表的概念:
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
(2)对于Set类,要求元素是无序的、不重复的。怎么保证元素不重复?判断两个元素是否重复的依据是什么?添加一个元素时先计算元素的hashcode定位它放置的物理地址,如果这个位置上没有元素,直接存放。如果这个位置有元素,调用它的equals()方法与新元素进行比较,若相同,不保存;若不相同,就散列到其他地址保存。
(3)所以Java对于equals()方法和hashCode()方法就有这样的规定:
1)如果两个对象相同,那么他们的hashcode值一定相同。
2)如果两个对象的hashcode相同,它们并不一定相同。(相同指的就是equals()的比较)
2、equals()
(1) '=='是用来比较两个变量(基本类型和对象类型)的值是否相等的, 如果两个变量是基本类型的,那很容易,直接比较值就可以了。如果两个变量是对象类型的,那么它还是比较值,只是它比较的是这两个对象在栈中的引用(即地址)。
对象是放在堆中的,栈中存放的是对象的引用(地址)。由此可见'=='是对栈中的值进行比较的。如果要比较堆中对象的内容是否相同,那么就要重写equals方法了。
(2) Object类中的equals方法就是用'=='来比较的,所以如果没有重写equals方法,equals和==是等价的。
通常我们会重写equals方法,让equals比较两个对象的内容,而不是比较对象的引用(地址)因为往往我们觉得比较对象的内容是否相同比比较对象的引用(地址)更有意义。
(3)Object类中的hashCode是返回对象在内存中地址转换成的一个int值(可以就当做地址看)。所以如果没有重写hashCode方法,任何对象的hashCode都是不相等的。通常在集合类的时候需要重写hashCode方法和equals方法,因为如果需要给集合类(比如:HashSet)添加对象,那么在添加之前需要查看给集合里是否已经有了该对象,比较好的方式就是用hashCode。
(4) 注意的是String、Integer、Boolean、Double等这些类都重写了equals和hashCode方法,这两个方法是根据对象的内容来比较和计算hashCode的。(详细可以查看jdk下的String.java源代码),所以只要对象的基本类型值相同,那么hashcode就一定相同。
(5) equals()相等的两个对象,hashcode()一般是相等的,最好在重写equals()方法时,重写hashcode()方法; equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。换句话说,equals()方法不相等的两个对象,hashcode()有可能相等。 反过来:hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。在object类中,hashcode()方法是本地方法,返回的是对象的引用(地址值),而object类中的equals()方法比较的也是两个对象的引用(地址值),如果equals()相等,说明两个对象地址值也相等,当然hashcode()也就相等了。
3、ArrayList和HashSet的比较,代码如下:
import java.util.*;
public class HashDemo
{
public static void main(String[] args)
{
String s1=new String("aaa");
String s2=new String("bbb");
String s3=new String("ccc");
String s4=new String("bbb");
Collection c1=new ArrayList();
c1.add(s1);
c1.add(s2);
c1.add(s3);
c1.add(s4);
System.out.println(c1);//[aaa, bbb, ccc, bbb]
Collection c2=new HashSet();
c2.add(s1);
c2.add(s2);
c2.add(s3);
c2.add(s4);
System.out.println(c2);//[aaa, ccc, bbb]
s2=new String("ddd");
c2.remove(s2);
System.out.println(c2);//[aaa, ccc, bbb],这里并没有删除成c2中s2原来的值
//因为s2的哈希值已经变了,HashSet中没有保存现在的哈希值。
}
}
二十七、框架的概念及用反射技术开发框架的原理
二十八、用类加载器的方式管理资源和配置文件
import java.io.*;
import java.util.Collection;
import java.util.Properties;
public class LoaderResDemo
{
public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException
{
InputStream ips=new FileInputStream("config.properties");//config.properties文件放在项目目录下。
//使用Class的类加载器
//InputStream ips=LoaderResDemo.class.getClassLoader().getResourceAsStream("cn/itcast/day02/config.properties");//bin\cn\itcast\day02目录下
//InputStream ips=LoaderResDemo.class.getResourceAsStream("config.properties");//和LoaderResDemo类在同一个包中
//InputStream ips=LoaderResDemo.class.getResourceAsStream("res/config.properties");//在LoaderResDemo类所在的子包res包中。
//InputStream ips=LoaderResDemo.class.getResourceAsStream("/cn/itcast/day02/res/config.properties");//在LoaderResDemo类所在的子包res包中,使用绝对路径
Properties props=new Properties();
props.load(ips);
ips.close();
String className=props.getProperty("className");
Collection c=(Collection) Class.forName(className).newInstance();
c.add(new String("aaa"));
c.add(new String("abc"));
System.out.println(c);
}
}
二十九、由内省引出JavaBean的讲解
JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。
如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问。JavaBean的属性就是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名是setId,中文意思即为设置id。
一个类被当做JavaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到Java类内部的成员变量。
三十、对JavaBean的简单内省操作
import java.beans.*;
import java.lang.reflect.InvocationTargetException;
public class IntrospectorDemo
{
public static void main(String[] args) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException
{
String propertyName="x";
Point pt=new Point(3,5);
Object obj = getProperty(propertyName, pt);
System.out.println(obj);
setProperty(pt,propertyName,100);
System.out.println(pt.getX());
}
//使用java.beans.PropertyDescriptor类
private static void setProperty(Object obj,String propertyName, Object value)
throws IllegalAccessException, InvocationTargetException, IntrospectionException {
PropertyDescriptor dp=new PropertyDescriptor(propertyName,obj.getClass());
dp.getWriteMethod().invoke(obj, value);
}
private static Object getProperty(String propertyName, Object obj)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
PropertyDescriptor dp=new PropertyDescriptor(propertyName,obj.getClass());
return dp.getReadMethod().invoke(obj);
}
}
三十一、对JavaBean的复杂内省操作
private static Object getProperty(String propertyName, Object obj)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
BeanInfo bi=Introspector.getBeanInfo(obj.getClass());
PropertyDescriptor[] pds=bi.getPropertyDescriptors();
Object value=null;
for(PropertyDescriptor dp:pds)
{
if(dp.getName().equals(propertyName))
{
value=dp.getReadMethod().invoke(obj);
}
}
return value;
}
三十二、使用BeanUtils工具包操作JavaBean
import java.lang.reflect.InvocationTargetException;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
public class BeanUtilsDemo
{
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException
{
Point pt=new Point(2,8);
String x=BeanUtils.getProperty(pt, "x");
System.out.println(x);
BeanUtils.setProperty(pt, "x", "100");
System.out.println(pt.getX());
BeanUtils.setProperty(pt, "date.time", "111");
System.out.println(pt.getDate().getTime());
PropertyUtils.setProperty(pt, "y", 99);//
System.out.println(PropertyUtils.getProperty(pt, "y"));
}
}
三十三、了解和入门注解的应用
java.lang包中提供3个注解类型(Annotation Types):
1、@SuppressWarnings取消警告
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
deprecation:使用了不赞成使用的类或方法时的警告
unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型。
fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告。
path:在类路径、源文件路径等中有不存在的路径时的警告。
serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告。
finally:任何 finally 子句不能正常完成时的警告。 all 关于以上所有情况的警告。
2、@Deprecated已过时
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
3、@Override覆盖
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
三十四、注解的定义与反射调用
1、自定义annotation
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
2、使用自定义annotation
@MyAnnotation("abc")
public class Program
{
public static void main(String[] args)
{
}
}
3、对“应用了注解类的类”进行反射操作
@MyAnnotation("abc")
public class Program
{
public static void main(String[] args)
{
if(Program.class.isAnnotationPresent(MyAnnotation.class))
{
MyAnnotation annotation=Program.class.getAnnotation(MyAnnotation.class);
System.out.println(annotation.value());
}
}
}
4、java.lang.annotation包
(1)Annotation接口
(2)ElementType枚举
ANNOTATION_TYPE 注释类型声明
CONSTRUCTOR 构造方法声明
FIELD 字段声明(包括枚举常量)
LOCAL_VARIABLE 局部变量声明
METHOD 方法声明
PACKAGE 包声明
PARAMETER 参数声明
TYPE 类、接口(包括注释类型)或枚举声明
(3)RetentionPolicy枚举
CLASS 编译器将把注释记录在类文件中,但在运行时 VM 不需要保留注释。
RUNTIME 编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。
SOURCE 编译器要丢弃的注释。
(4)注视类型:Documented、Inherited、Retention、Target
三十五、为注解添加属性
1、为注解添加属性和应用属性
2、为属性指定缺省值
3、数组类型的属性
如果数据属性中只有一个元素,属性值部分可以省略大括号。
4、枚举类型的属性
5、注解类型的属性
6、用反射的方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法
7、注解的属性类型只能是:基本数据类型、String类型、Class类型、枚举类型、注解类型和以上类型的数组。
三十六、入门泛型的基本应用
1、ArrayList al=new ArrayList();
al.add(1);
al.add(1L);
int i=(Integer)al.get(1);//编译要强制类型转换且运行时出错!
2、ArrayList<Integer> al=new ArrayList<Integer>();
al.add(1);
//al.add(1L);
//al.add(“abc”);//这两行编译时报告语法错误
Int i=al.get(0);//不需要再进行类型转换
3、泛型是提供给javac编译器使用的,可以先定集合中的输入类型,让编译器阻止源程序中的非法输入,编译器编译带参数说明的集合时会去掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其他类型的数据。
三十七、泛型的内部原理及更深应用
1、使用反射跳过编译器
public class GenericDemo
{
public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException
{
ArrayList<String> al1=new ArrayList<String>();
Constructor c=String.class.getConstructor(StringBuffer.class);
String s=(String)c.newInstance(new StringBuffer("abc"));
System.out.println(s);
ArrayList<Integer> al2=new ArrayList<Integer>();
System.out.println(al1.getClass());
System.out.println(al2.getClass());
System.out.println(al1.getClass()==al2.getClass());//true
al2.getClass().getMethod("add", Object.class).invoke(al2, "aaa");
System.out.println(al2.get(0));
}
}
2、了解泛型
(1)参数化类型与原始类型的兼容性:
1)参数化类型可以引用一个原始类型的对象,编译报告警告,例如
Collection<String> c=new Vector();
2)原始类型可以引用一个参数化类型的对象,编译报告警告,例如
Collection c=new Vector<String>();
原来的方法接受一个集合参数,新的类型也要能传进去。
(2)参数化类型不考虑类型参数的继承关系:
Vector<String> v=new Vector<Object>();//错误
Vector<Object> v=new Vector<String>();//错误
(3)在创建数组实例时,数组的元素不能使用参数化的类型,下面语句有错误:
Vector<Integer> v[]=new Vector<Integer>[10];
(4)Vector v1=new Vector<String>();//没有错误
Vector<Object> v2=v1; //没有错误