------- android培训、java培训、期待与您交流!----------
26、ArrayList_HashSet的比较及Hashcode分析
- ArrayList是按照先后顺序依次将引用地址放进去。而HashSet放进来前先看看这里面有没有这个对象,即比较两个对象是否相等,如果有的话就不放。如果你想放一个对象盖掉原来的对象你要怎么做,你要先remove()掉原来的对象,再放进去新的。
- 要想使你的对象的hashCode()方法有价值的话,你的对象必须要存储在HashSet中。
- 哈希算法:用来提高从集合中查找元素的效率,这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储在哪个区域。
- import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Properties;
public class ReflectTest2 {
publicstatic void main(String[] args) throws Exception{
/*getRealPath();//金山词霸/内部
一定要记住用完整的路径,但完整的路径不是硬编码,而是运算出来的。
*/
//InputStreamips = new FileInputStream("config.properties");
//InputStreamips =ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");
//getClassLoader()类加载器,它有一个方法getResourceAsStream(),这个方法会在classpath路径下逐一的查找文件。classpath是根路径,还要加入你自己加的包的路径才能找到想要找的文件。注意:直接使用类加载器时,不能以/打头,在cn的前面不能加“/”了,不然会报错。这种方式只能读一些不再改的配置文件。而前面的那种还可以用FileOutputStream写入一些东西。SSH它们的配置文件都是放在classpath文件路径下的。因为这个框架内部用的就是这个类加载器。
//InputStreamips =ReflectTest2.class.getResourceAsStream("resources/config.properties");
//类Class提供了一个便利方法,用加载当前类的那个类加载器去加载相同包目录下的文件。getResouceAsStream(String),这个方法不是ClassLoader中的方法。它只需要写上配置文件的名字,不需要写上目录名。本语句中相对路径,它是相对于包文件下的子包reources文件的config.properties
InputStreamips =ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/resources/config.properties");
//用“/”的话就表示不是用的相对路径,而是用的相对于classpath的根,要从根开始写上完整的了。
Propertiesprops = new Properties();
//Properties对象等效于一个HashMap,以键值对的方式存在,但它扩展了功能:它可以把内存中的键值对存在硬盘文件上去。也可以初始化的时候把文件从硬盘中加载进来。
props.load(ips);
ips.close();
StringclassName = props.getProperty("className");
Collection collections =(Collection)Class.forName(className).newInstance();
//Collectioncollections = new HashSet();
ReflectPointpt1 = new ReflectPoint(3,3);
ReflectPointpt2 = new ReflectPoint(5,5);
ReflectPointpt3 = new ReflectPoint(3,3);
//如果你想让pt1和pt3相等,你就要重写equals方法。
collections.add(pt1);
collections.add(pt2);
collections.add(pt3);
collections.add(pt1);
//pt1.y= 7;
//collections.remove(pt1); //修改过pt1后,就找不到原来存储的位置了,不能移除。
System.out.println(collections.size());
//如果没有实现hashCode()方法,返回结果为3。因为虽然equals比较是相等的,pt1和pt3本来是认为是相同的对象被存放到了不同的区域,所以pt3也能放进去。结果为3。为了让相等的对象放在相同的区域,即equals()相等的话,hashCode()也要相等。
}
}
- 当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中的哈希值就不同了。在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到的对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。
27、框架的概念及用反射技术开发框架的原理
- 反射的作用-->实现框架功能
- 框架与框架要解决的核心问题
Ø 你做的门调用锁,锁是工具,你做的门被房子调用,房子是框架,房子和锁都是别人提供的。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
- 框架要解决的核心问题
Ø 我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
Ø 因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接new 某个类的实例对象了,而要用反射方式来做。
- 综合案例
Ø 先直接用new 语句创建ArrayList和HashSet的实例对象,演示用eclipse自动生成 ReflectPoint类的equals和hashcode方法,比较两个集合的运行结果差异。
Ø 然后改为采用配置文件加反射的方式创建ArrayList和HashSet的实例对象,比较观察运行结果差异。
- 使用别人写的类有两种使用方式:一、你去调用别人的类。二、别人的类调用你的类。
- 为什么出去做开发人家让你用框架(半成品),因为这样的话项目完成的快,做事效率高。
- 不是用args传入类的名字,而是从一个配置文件中读取类名。等到程序最后运行的时候,不用改java源程序,只用改properties,文件类就换了。客户没有javac,不能改动源文件,但是客户有记事本,用记事本改改就完了。
28、用类加载器的方式管理资源和配置文件(程序见26)
- properties文件放在工程下面,我们给客户的时候,我们只把.class文件打成jar包给客户,而不会把java源文件给他,也不会把config.properties文件给它。在实际的项目中没有以相对路径这么搞的,要用就用绝对地址,而这个绝对路径不是直接写在程序中的,而是用某种方式get出来的,写在一个配置文件中让用户用配(如果一台主机没有D盘,只有一个C盘,你直接写一个D盘在程序中也不合适)。config.properties文件用户想放在哪里都可以,但用户要在配置文件中写一下,我把config.properties文件放在了哪里。
- Window-->Preferences…-->Openg mode中的Singleclick
- 如果config.properties文件不是放在工程目录下,在eclipse中直接将这个文件拖放在源文件的包中,这个文件会在classpath目录下也拷贝出一个。真正运行的时候用的是classpath路径下的config.properties
29、由内省引出JavaBean的讲解
- IntroSpector-->JavaBean(对JavaBean进行操作)-->特殊的Java类(这个类的方法的名称符合某个约定的规则)。如getAge(),和setAge()方法的名字以小写的set和get打头。符合这种特殊的规则的java类,我们称之为JavaBean.(一个普通的java类中,如果有get和set方法,那么就可以把它当做一个JavaBean来操作)
- JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。
- 如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问。JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,如果方法名为getId,中文意思即为获取id,去掉get、set前缀,剩余部分就是JavaBean属性名。如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的(Age-->如果第二个字母是小的,则把第一个字母变成小的-->age)。
Ø setId()的属性名-->id
Ø gettime-->time
Ø isLast()的属性名-->last
Ø setCPU的属性名是什么?-->CPU
总之,一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。
30、对于JavaBean的简单内省操作
- 一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean!好处如下:
Ø 在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就没什么挑选的余地!
Ø JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的x,怎么做,有一定难度吧?用内省这套api操作JavaBean比用普通类的方式更方便。
- 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;
import java.util.Map;
importorg.apache.commons.beanutils.BeanUtils;
importorg.apache.commons.beanutils.PropertyUtils;
public class IntroSpectorTest {
publicstatic void main(String[] args) throws Exception {
ReflectPointpt1 = new ReflectPoint(3,5);
StringpropertyName = "x";
//"x"-->"X"-->"getX"-->MethodGetX-->
ObjectretVal = getProperty(pt1, propertyName);
System.out.println(retVal);
Objectvalue = 7; //为了能抽取出三个变量,才写的这条语句。
setProperties(pt1,propertyName, value);
//把对象给我,把属性名给我,把值给我。
System.out.println(BeanUtils.getProperty(pt1,"x").getClass().getName());
BeanUtils.setProperty(pt1,"x", "9");//以BeanUtils来操作的时候,设置进去是以String来设置的。在Web开发中通过浏览器传给服务器的是字符串的9。JavaBean可以自动的完成类型的转换。
System.out.println(pt1.getX());
//选中代码-->右键-->Refactor-->ExtractMethod…,可以对选中的代码进行方法的抽取。
/*
//java7的新特性,可以这样去定义Map。用新技术去解决老问题
Mapmap = {name:"zxx",age:18};
BeanUtils.setProperty(map,"name", "lhm");
*/
BeanUtils.setProperty(pt1,"birthday.time", "111");
//相当于为pt1对象身上的birthday对象的time属性赋值。BeanUtils支持属性的级连操作(birthday.time)。
System.out.println(BeanUtils.getProperty(pt1,"birthday.time"));
PropertyUtils.setProperty(pt1,"x", 9);
//PropertyUtils是以属性本身的类型进行操作。BeanUtils是以字符串进行操作。如果不想用类型转换或者类型转换的时候转换错了,可以直接用PropertyUtils.
System.out.println(PropertyUtils.getProperty(pt1,"x").getClass().getName());
}
privatestatic void setProperties(Object pt1, String propertyName,
Objectvalue) throws IntrospectionException,
IllegalAccessException,InvocationTargetException {
PropertyDescriptorpd2 = new PropertyDescriptor(propertyName,pt1.getClass());
MethodmethodSetX = pd2.getWriteMethod();
methodSetX.invoke(pt1,value);
}
privatestatic Object getProperty(Object pt1, String propertyName)
throwsIntrospectionException, IllegalAccessException,
InvocationTargetException{
/*
PropertyDescriptor pd = newPropertyDescriptor(propertyName,pt1.getClass());
MethodmethodGetX = pd.getReadMethod();
ObjectretVal = methodGetX.invoke(pt1);
*/
//用getBeanInfo的方法来做。不知道上面的简单的方式的时候,就是用这种复杂的方式做出来的。
BeanInfobeanInfo = Introspector.getBeanInfo(pt1.getClass());
PropertyDescriptor[]pds = beanInfo.getPropertyDescriptors();
//beanInfo只能得到所有的属性描述。
ObjectretVal = null;
for(PropertyDescriptorpd : pds){
if(pd.getName().equals(propertyName))
{
MethodmethodGetX = pd.getReadMethod();
retVal= methodGetX.invoke(pt1);
break;
}
}
returnretVal;
}
}
31、对于JavaBean的复杂方式内省操作(代码见30)
- BeanInfo代表了,把你当作JavaBean来看,看出来的结果。
32、BeanUtils工具包操作JavaBean(代码见30)
- 由于对JavaBean的属性进行设置和读取操作,使用的频率很高,所以开源的牛人们就写成了一些针对JavaBean的工具,如Apache的Beanutils工具包(下载后的包名为:commons-beanutils-current.zip。正载完成后解压,读README.txt,如果写的不好,在网上查要用哪个jar包,在这里拷贝一个比较全的包,commons-beanutils.jar,这个包又用到了一个Apache的日志包,是个日志包是commons-logging-1.1.zip,解压后,将最大的commons-logging-1.1.jar,拷贝到lib文件夹下,也建立Build Path)。
- 在工程下面新建一个普通的文件夹(File-->New-->Folder),将jar文件拷贝内到这个新建的文件夹下。这个时候虽然jar包在你的工程内部,但是还没有加到你的Build Path中来。在拷贝过来的jar包上点右键-->BuildPath-->Add to Build Path,这个时候会看到一个小奶瓶,就表示已经加到BuildPath中去了。
- 演示用eclipse如何加入jar包,先只是引入beanutils包,等程序运行出错后再引入logging包。
- 在前面内省例子的基础上,用BeanUtils类先get原来设置好的属性,再将其set为一个新值。
Ø get属性时返回的结果为字符串,set属性时可以接受任意类型的对象,通常使用字符串。
- 用PropertyUtils类先get原来设置好的属性,再将其set为一个新值。
Ø get属性时返回的结果为该属性本来的类型,set属性时只接受该属性本来的类型。
- 演示去掉JavaBean(ReflectPoint)的public修饰符时,BeanUtils工具包访问javabean属性时出现的问题。
- staticjava.util.Map describe(java.lang.object bean):把JavaBean转换为Map
- staticvoid popuplate(java.lang.Object bean, java.util.Map properties):把Map的东西填充到bean中去
33、了解注解及注解的应用
- 未来的开发模式都是基于注解的。JPA、Spring、Hibernate、Structs2部分都是基于注解的。
- 为简化开发,JDK1.5的新特性有:泛型,增强for循环,自动装包/拆包,枚举,可变参数, 静态导入等。
- AnnotationTest,Test放在前面和放在后面有一点的不同,放在后面表示“注解的测试”,放在后面表示“测试注解”。类名和属性名都是名词,方法名都是动词或者动词加名词
- 过时了,就是对于老的人说,这个是比较老的方法了,但是还能用。对于新的人说,不要用这个方法了,已经过时了。
- 先通过@SuppressWarnings的应用让大家认识和了解一下注解:
Ø 通过System.runFinalizersOnExit(true);的编译警告引出@SuppressWarnings("deprecation") 。(它会告诉编译器,这个过时我已经知道了,不要再提示我了。用了这条语句后命令行下不会有提示了)
- @Deprecated
Ø 直接在刚才的类中增加一个方法,并加上@Deprecated标注,在另外一个类中调用这个方法。
- @Override
Ø public boolean equals(Reflect other)方法。在运用HashSet时,这里的equals()因为传入的参数不是Object类型的所以来是重写的Object类中的equals(Objcetobj)方法。
- 总结:
Ø 注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记。加了以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。
Ø 看java.lang包,可看到JDK中提供的最基本的annotation。
- java.lang包的最下面还有AnnotatinTypes,在它下有三个类表示注解。
34、注解的定义与反射调用
- 注解就相当于一个你的源程序中要调用的一个类,要在源程序中应用某个注解,得先准备好了这个注解类。就像你要调用某个类,得先有开发好这个类。
- 包上右键-->New-->Other-->Java-->Annotation,建立一个注解的类文件。不用工具写的话,可以在interface上加一个@,即@interface。
自定义注解及其应用
- 定义一个最简单的注解:public @interface MyAnnotation {}
- 把它加在某个类上:@MyAnnotation public class AnnotationTest{}
- 用反射进行测试AnnotationTest的定义上是否有@MyAnnotation
- 根据发射测试的问题,引出@Retention元注解的讲解,其三种取值:RetetionPolicy.SOURCE、RetetionPolicy.CLASS、RetetionPolicy.RUNTIME;分别对应:java源文件-->class文件-->内存中的字节码。(注解的生命周期分为三个阶段,javac将源文件编译成class文件的时候可能去掉这个注解,类加载器,把class调到内存中去的时候也有可能去掉注解。所以可以加上Retention来说明这个注解的生命周期在哪个阶段。默认的阶段是class阶段。RetentionPolicy是枚举的类型,它在三个值分别是SOURCE、CLASS、RUNTIME)
Ø 思考:@Override、@SuppressWarnings和@Deprecated这三个注解的属性值分别是什么?分别是SOURCE、SOURCE、RUNTIME。
- 演示和讲解@Target元注解
Ø Target的默认值为任何元素,设置Target等于ElementType.METHOD,原来加在类上的注解就报错了,改为用数组方式设置{ElementType.METHOD,ElementType.TYPE}就可以了。
- 元注解以及其枚举属性值不用记,只要会看jdk提供那几个基本注解的API帮助文档的定义或其源代码,按图索骥即可查到,或者直接看java.lang.annotation包下面的类。
- import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import cn.itcast.day1.EnumTest;
@Retention(RetentionPolicy.RUNTIME)
//我定义注解的时候又加了一个注解,加的注解是为自定义的注解服务的。Retention(RetentionPolicy.RUNTIME),这个注解的意思是一直保留到运行时。java源程序要由javac去编译,javac把源程序编译成class,编译成class的时候,它会把源程序的一些注解给去掉。class文件中的东西不是字节码,只有把class文件的东西加载到内存中后,类加载器会对会对于文件进行一些处理,如安全性检查等。处理完了之后,在内存中得到的二进制东西,才是字节码。
@Target({ElementType.METHOD,ElementType.TYPE})
//这样写了之后,就可以将这个注解不仅写在方法的前面,还可以写在类的前面。Target中要写的是一个数组,用{}来括起来。用TYPE比用CLASS更准确,CLASS的父类是TYPE。
public @interface ItcastAnnotation {
Stringcolor() default "blue";
Stringvalue();
int[]arrayAttr() default {3,4,4};
EnumTest.TrafficLamplamp() default EnumTest.TrafficLamp.RED;
MetaAnnotationannotationAttr() default @MetaAnnotation("lhm");
//default后要搞一个MetaAnnotation的实例对象。
}
import java.lang.reflect.Method;
import javax.jws.soap.InitParam;
@ItcastAnnotation(annotationAttr=@MetaAnnotation("flx"),color="red",value="abc",arrayAttr=1) //这是一个注解,注解里面的属性值还是一个注解。数组arrayAttr中只有一个元素1,可以不用加{}。
public class AnnotationTest {
@SuppressWarnings("deprecation") //这里面也是一个属性,只不过属性的值很特殊,可以把名称和=省略
@ItcastAnnotation("xyz") //当只有value属性要设置值的时候,就不需要写value=了。如果这里显示有错了,可以设置color的默认属性为blue就不会有错了。
publicstatic void main(String[] args) throws Exception{
//TODO Auto-generated method stub
System.runFinalizersOnExit(true);
if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class)){
ItcastAnnotationannotation = (ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class);
//只知道返回一个注解,但不知道你要返回的是什么类型的注解,要进行强制类型转换。
System.out.println(annotation.color());
//设置值的时候以属性的方式来设置值,调用的时候还是以方法的形式来调用。
System.out.println(annotation.value()); //字符串
System.out.println(annotation.arrayAttr().length); //数组
System.out.println(annotation.lamp().nextLamp().name()); //枚举
System.out.println(annotation.annotationAttr().value()); //注解
}
MethodmainMethod = AnnotationTest.class.getMethod("main", String[].class);
ItcastAnnotationannotation2 = (ItcastAnnotation)mainMethod.getAnnotation(ItcastAnnotation.class);
System.out.println(annotation2.value());
}
@Deprecated
publicstatic void sayHello(){
System.out.println("hi,传智播客");
}
}
- 一旦在某个地方加上了@,就等于有了这个注解的一个实例对象。
- 元注解,元信息,元数据。(元信息就是信息的信息)
35、为注解增加各种属性(代码见34)
- 什么是注解的属性
Ø 一个注解相当于一个胸牌,如果你胸前贴了胸牌,就是传智播客的学生,否则,就不是。如果还想区分出是传智播客哪个班的学生,这时候可以为胸牌在增加一个属性来进行区分。加了属性的标记效果为:@MyAnnotation(color="red")
- 定义基本类型的属性和应用属性:
Ø 在注解类中增加String color();
Ø @MyAnnotation(color="red")
- 用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法
Ø MyAnnotation a = (MyAnnotation)AnnotationTest.class.getAnnotation(MyAnnotation.class);
Ø System.out.println(a.color());
Ø 可以认为上面这个@MyAnnotation是MyAnnotaion类的一个实例对象
- 为属性指定缺省值:
Ø String color() default "yellow";
- l value属性:
Ø String value() default "zxx";
Ø 如果注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略value=部分,例如:@MyAnnotation("lhm")。
- 注解很像接口,它的属性很像方法。
为注解增加高级属性
- 数组类型的属性
Ø int [] arrayAttr()default {1,2,3};
Ø @MyAnnotation(arrayAttr={2,3,4})
Ø 如果数组属性中只有一个元素,这时候属性值部分可以省略大括
- 枚举类型的属性
Ø EnumTest.TrafficLamp lamp() ;
Ø @MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)
- 注解类型的属性:
Ø MetaAnnotation annotationAttr()default@MetaAnnotation("xxxx");
Ø @MyAnnotation(annotationAttr=@MetaAnnotation(“yyy”))
Ø 可以认为上面这个@MyAnnotation是MyAnnotaion类的一个实例对象,同样的道理,可以认为上面这个@MetaAnnotation是MetaAnnotation类的一个实例对象,调用代码如下:
MetaAnnotation ma = myAnnotation.annotationAttr();
System.out.println(ma.value());
- 注解的详细语法可以通过看java语言规范了解,即看java的languagespecification。
- 枚举和注解都是特殊的类,不能用new 创建它们的实例对象,创建枚举的实例对象就是在其中增加元素。
36、入门泛型的基本应用
体验泛型
- Jdk 1.5以前的集合类中存在什么问题
ArrayList collection = newArrayList();
collection.add(1);
collection.add(1L);
collection.add("abc");
int i = (Integer) collection.get(1);//编译要强制类型转换且运行时出错!
- Jdk 1.5的集合类希望你在定义集合时,明确表示你要向集合中装哪种类型的数据,无法加入指定类型以外的数据
ArrayList<Integer> collection2 = newArrayList<Integer>();
collection2.add(1);
/*collection2.add(1L);
collection2.add(“abc”);*///这两行代码编译时就报告了语法错误
int i2 = collection2.get(0);//不需要再进行类型转换
- 泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
- 有eclipse中如果定义的某一个名字都错了,可以在这个名字上击右键-->Refactor-->Rename…,就可以将所有名字都修改成要修改成的名字。
- import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import cn.itcast.day1.ReflectPoint;
public class GenericTest {
publicstatic void main(String[] args) throws Exception {
ArrayListcollection1 = new ArrayList();
collection1.add(1);
collection1.add(1L);
collection1.add("abc");
//inti = (Integer)collection1.get(1); //有两个不方便,一、需要将类型进行强制类型的转换。二、如果取出来的数据不是要转换成的类型,会出现错误。
ArrayList<String>collection2 = new ArrayList<String>();
//collection2.add(1);
//collection2.add(1L);
collection2.add("abc");
Stringelement = collection2.get(0); //不用进行类型转换了
//newString(new StringBuffer("abc"));
Constructor<String>constructor1 = String.class.getConstructor(StringBuffer.class);
Stringstr2 = constructor1.newInstance(/*"abc"*/newStringBuffer("abc"));
System.out.println(str2.charAt(2));
ArrayList<Integer>collection3 = new ArrayList<Integer>();
System.out.println(collection3.getClass()== collection2.getClass());
//返加在结果为true,说明String和Integer只是给编译器使用的,编译完成后,就将String和Integer给去掉了。指向的同一个字节码文件。
//collection3.add("abc");
collection3.getClass().getMethod("add",Object.class).invoke(collection3, "abc");
//泛型是给编译器看的,在这里用完反射后,针对collection3对象调用刚才的方法,可以传入字符串类型的对象abc。
System.out.println(collection3.get(0));
printCollection(collection3);
HashMap<String,Integer>maps = new HashMap<String, Integer>();
maps.put("zxx",28);
maps.put("lhm",35);
maps.put("flx",33);
Set<Map.Entry<String,Integer>>entrySet = maps.entrySet();
for(Map.Entry<String,Integer> entry : entrySet){
System.out.println(entry.getKey()+ ":" + entry.getValue());
}
add(3,5);
Numberx1 = add(3.5,3); //返回的类型是两个数的交集。
Objectx2 = add(3,"abc");
swap(newString[]{"abc","xyz","itcast"},1,2);
//swap(newint[]{1,3,5,4,5},3,4);
Objectobj = "abc";
Stringx3 = autoConvert(obj);
copy1(newVector<String>(),new String[10]);
copy2(newDate[10],new String[10]);
//copy1(newVector<Date>(),new String[10]);
GenericDao<ReflectPoint>dao = new GenericDao<ReflectPoint>();
dao.add(newReflectPoint(3,3));
//Strings = dao.findById(1);
//Vector<Date>v1 = new Vector<Date>();
MethodapplyMethod = GenericTest.class.getMethod("applyVector",Vector.class);
Type[]types = applyMethod.getGenericParameterTypes();
ParameterizedTypepType = (ParameterizedType)types[0];
System.out.println(pType.getRawType());
//获取原始的类型,反加在结果为class java.util.Vector。
System.out.println(pType.getActualTypeArguments()[0]);
//获取实际的类型参数,返加的结果是class java.util.Date,这种就知道了Vector中的类型。这样就知道了Vector中装的是Date。这个类型可能有多个,如Class HashMap<K,V>中就定义了两个。
}
//定义了这个方法后,我无法通过v1知道Vector<Date>变量的类型,但是可以通过这个方法知道Vector <Date> v1的参数列表的类型。因为Vector中有反射的Mehod,如,TypegetGenericReturnType(),用于获取泛型的返回类型。Type[] getGenericParameterTypes(),获取泛型的参数类型。Class<?>[] getParameterAnnotations(),每一个方法都可以得到它的参数类型。
publicstatic void applyVector(Vector<Date> v1){
//很多框架都帮助我们生成Date对象,并把这个Date对象填到这个集合中去。为什么能生成Date对象呢?它一写是用了上面的代码获得了Vector中的实际参数类型。
}
//定义一个方法,可以将任意类型的数组中的所有元素填充为相应类型的某个对象。
privatestatic <T> void fillArray(T[] a,T obj){
for(inti=0;i<a.length;i++){
a[i]= obj;
}
}
// 编编写一个泛型方法,自动将Object类型的对象转换成其他类型。
privatestatic <T> T autoConvert(Object obj){
return(T)obj;
}
privatestatic <T> void swap(T[] a,int i,int j){
Ttmp = a[i];
a[i]= a[j];
a[j]= tmp;
}
privatestatic <T> T add(T x,T y){
returnnull;
}
publicstatic void printCollection(Collection<?> collection){
//这里不能写成为Object来接收任何类型。如果传Integer类型的collection3参数进来的话,它是不能传给一个Object类型的。
//collection.add(1);
System.out.println(collection.size());
for(Objectobj : collection){
System.out.println(obj);
}
}
//采用自定泛型方法的方式打印出任意参数化类型的集合中的所有内容。
- 在这种情况下,前面的通配符方案要比范型方法更有效,当一个类型变量用来表达两个参数之间或者参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用而不是仅在签名的时候使用,才需要使用范型方法。
publicstatic <T> void printCollection2(Collection<T> collection/*,Tobj2*/){
//collection.add(1);//不可以这样写万一人家传入的是一个字符串呢。
System.out.println(collection.size()); //这个方法可以调,因为它和传入的类型没有关系。
for(Objectobj : collection){
System.out.println(obj);
}
//collection.add(obj2);
}
//定义一个方法,把任意参数类型的集合中的数据安全地复制到相应类型的数组中。
publicstatic <T> void copy1(Collection<T> dest,T[] src){
}
//定义一个方法,把任意参数类型的一个数组中的数据安全地复制到相应类型的另一个数组中。
public static <T> void copy2(T[] dest,T[] src){
}
}
37、泛型的内部原理及更深应用
- 下面程序的main方法中的第二行代码和注释中的两行代码表达的意思完全相同,注释中的两行代码不能通过编译,而第二行(采用方法调用链)却可以顺利通过编译。
public class Test
{
public void func()
{
System.out.println("func");
}
public static void main(String args[]) throws Exception
{
Object obj = new Test();
//下面这行可以成功编译
((Test)obj).getClass().newInstance().func();
//下面这两行无法通过编译
/*Class c = ((Test)obj).getClass();
c.newInstance().func(); */
}
}
- 因为Generic, 编译器可以在编译期获得类型信息所以可以编译这类代码。你将下面那两行改成
Class<? extends Test> c =((Test)obj).getClass();
c.newInstance().func();
应该就能通过编译了。
- 在JDK 1.5中引入范型后,Object.getClass()方法的定义如下:
Ø public final Class<? extends Object>getClass()
这说明((Test)obj).getClass()语句返回的对象类型为Class<?extends Test>,而Class<T>的newInstance()方法的定义如下:
Ø public T newInstance() throwsInstantiationException,IllegalAccessException
即对于编译器看来,Class<Test>的newInstance()方法的对象类型为Test,而((Test)obj).getClass()返回的为对象类型为Class<? extends Test>,所以,编译器认为((Test)obj).getClass().newInstance()返回的对象类型为Test。
- 下面这两行代码之所以无法通过编译
Class c = ((Test)obj).getClass();
c.newInstance().func();
是因为((Test)obj).getClass()返回的为对象类型为Class<?extends Test>,但是我们在第一行将结果强制转换成了Class,然后再去调用Class的newInstance方法,而不是去调用Class<Test>的newInstance方法,编译器当然不再认为Class的newInstance方法返回的对象为Test了。
了解泛型
- ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
Ø 整个称为ArrayList<E>泛型类型
Ø ArrayList<E>中的E称为类型变量或类型参数
Ø 整个ArrayList<Integer>称为参数化的类型
Ø ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
Ø ArrayList<Integer>中的<>念着typeof
Ø ArrayList称为原始类型
- 参数化类型与原始类型的兼容性:
Ø 参数化类型可以引用一个原始类型的对象,编译报告警告,例如,
Collection<String> c = new Vector();//可不可以,不就是编译器一句话的事吗?
Ø 原始类型可以引用一个参数化类型的对象,编译报告警告,例如,
Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去
- 参数化类型不考虑类型参数的继承关系:
Ø Vector<String>v = new Vector<Object>(); //错误!///不写<Object>没错,写了就是明知故犯
Ø Vector<Object>v = new Vector<String>(); //也错误!
- 编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
Vector<Integer> vectorList[] = new Vector<Integer>[10];
- 思考题:下面的代码会报错误吗?
Vector v1 = new Vector<String>();
Vector<Object> v = v1; //不会所错误因为编译器是一行一行的在检查语法有没有错误
38、泛型的扩展应用(代码见36)
泛型中的?通配符
- 问题:
Ø 定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?
- 错误方式:
public static voidprintCollection(Collection<Object> cols) {
for(Objectobj:cols) {
System.out.println(obj);
}
/*cols.add("string");//没错
cols = new HashSet<Date>();//会报告错误!*/
}
- 正确方式:
public static voidprintCollection(Collection<?> cols) {
for(Objectobj:cols) {
System.out.println(obj);
}
//cols.add("string");//错误,因为它不知自己未来匹配就一定是String
cols.size();//没错,此方法与类型参数没有关系
cols = new HashSet<Date>();
}
- 总结:
Ø 使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
泛型中的?通配符的扩展
- 限定通配符的上边界:
Ø 正确:Vector<? extends Number> x = newVector<Integer>();
Ø 错误:Vector<? extends Number> x = newVector<String>();
- 限定通配符的下边界:
Ø 正确:Vector<? super Integer> x = newVector<Number>();
Ø 错误:Vector<? super Integer> x = newVector<Byte>();
- 提示:
Ø 限定通配符总是包括自己。
Ø ?只能用作引用,不能用它去给其他变量赋值
Vector<?extends Number> y = new Vector<Integer>();
Vector<Number>x = y;
上面的代码错误,原理与Vector<Object > x11 = newVector<String>();相似,只能通过强制类型转换方式来赋值。
39、泛型集合的综合应用案例(代码见36)
- 能写出下面的代码即代表掌握了Java的泛型集合类:
HashMap<String,Integer> hm = newHashMap<String,Integer>();
hm.put("zxx",19);
hm.put("lis",18);
Set<Map.Entry<String,Integer>> mes= hm.entrySet();
for(Map.Entry<String,Integer> me : mes) {
System.out.println(me.getKey() + ":" + me.getValue());
}
- 对在jsp页面中也经常要对Set或Map集合进行迭代:
<c:forEach items=“${map}”var=“entry”>
${entry.key}:${entry.value} //Entry有get setKey()方法,也有get setValue()方法,可以将entry看作是一个JavaBean.
</c:forEach>
- 对于Map有三种方式去操作:第一种,得到所有的Key,得到所有的Value,得到所有元素的组合体(Key-Value)。如果想迭代出集合中的所有内容,可以先得到Kye,再由Key去得到里面的所有的value。也可以得到每一个的组合体,再从组合体中去得到每一个的Key和Value。组合体可以用一个类来表示:Entry。Entry这个类,只应用于Map,为了表示它和Map的特殊的血缘关系将Entry定义成Map内部的一个类,它完整的名称是:Map.Entry。
- 如果想对于Map进行迭代,可在先用Map中的方法:Set<Map.Entry<K,V>>entrySet();得到一个Entryr的集合,而Set是可以迭代的,因为它实现了Iterable<E>接口。
40、自定义泛型方法及应用(代码见36)
- 把自己的类,自己的方法,定义成泛型让人家去用。java的范型是从C++中借鉴过来的。java的泛型没有C++的强大,这是由于Java虚拟机设计的原因。【Java中的泛型类型(或者泛型)类似于 C++ 中的模板。但是这种相似性仅限于表面,Java 语言中的泛型基本上完全是在编译器中实现,用于编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种实现技术称为擦除(erasure)(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为 Java 厂商升级其 JVM 造成难以逾越的障碍。所以,java的泛型采用了可以完全在编译器中实现的擦除方法。
例如,下面这两个方法,编译器会报告错误,它不认为是两个不同的参数类型,而认为是同一种参数类型。
private static voidapplyGeneric(Vector<String> v){
}
private static voidapplyGeneric(Vector<Date> v){
}
】
- Java的泛型方法没有C++模板函数功能强大,java中的如下代码无法通过编译:
<T> T add(T x,T y) {
return(T) (x+y);
//returnnull;
}
- 用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。
- 交换数组中的两个元素的位置的泛型方法语法定义如下:
static <E> voidswap(E[] a, int i, int j) {
E t = a[i];
a[i] = a[j];
a[j] = t;
}//或用一个面试题讲:把一个数组中的元素的顺序颠倒一下
- 只有引用类型(不能为基本类型)才能作为泛型方法的实际参数,swap(new int[3],3,5);语句会报告编译错误。(编译器不会对new int[3]中的int自动拆箱和装箱了,因为new int[3]本身已经是对象了)
- 除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如,Class.getAnnotation()方法的定义:public <A extends Annotation> AgetAnnotation(Class<A> annotationClass)。并且可以用&来指定多个边界,如<V extends Serializable & cloneable> voidmethod(){}
- 普通方法、构造方法和静态方法中都可以使用泛型。
- 也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。
private static <T extends Exception>sayHello() throws T
{
try{
}catch(Exception e){
throw (T)e;
}
}
- 在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:
public static <K,V> V getValue(K key){ return map.get(key);}
41、自定义泛型方法的练习与类型推断总结(见代码36)
类型参数的类型推断
- 编译器判断范型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
- 根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
Ø 当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(newString[3],3,4) --> static <E> voidswap(E[] a, int i, int j)
Ø 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:
add(3,5) --> static <T> T add(T a, T b)
Ø 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(newInteger[3],3.5f) --> static<T> void fill(T[] a, T v)
Ø 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
intx =(3,3.5f) --> static <T> T add(T a, T b)
Ø 参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(new Integer[5],new String[5]) --> static<T> void copy(T[] a,T[] b);
copy(new Vector<String>(), newInteger[5]) --> static <T> void copy(Collection<T> a ,T[] b);
Ø 最后,总之一定要记住在自定义泛型的时候,返回值的前面一定要用<>来说明类型。
42、自定义泛型类的应用
- 定义泛型类型:不是在方法上而是在类上定义泛型。如,java.util中的Class Vector<E>
- 如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
private T field1;
public void save(T obj){}
public T getById(int id){}
}
- 类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:
Ø GenericDao<String>dao = null;
Ø newgenericDao<String>();
- 注意:
Ø 在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
Ø 当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。
- 问题:类中只有一个方法需要使用泛型,是使用类级别的泛型,还是使用方法级别的泛型?
- import java.util.Set;
//dao data access object--->crud,我们搞JavaEE的整天搞的就是对数据库的增删改查。(DAO是数据访问对象,表示某个类创造的对象是用来进行数据访问的。)
public class GenericDao<E> {
publicvoid add(E x){ //怎么让add()方法中void前的E和findById()方法中E前面的E统一,相互之间的制约呢?因为两个方向是独立的,要想实现约束,就必须把这个E定义在内存上。
}
publicE findById(int id){
returnnull;
}
publicvoid delete(E obj){
}
publicvoid delete(int id){
}
publicvoid update(E obj){
}
publicstatic <E> void update2(E obj){ //静态的泛型要自己搞不能用类的泛型,否则会出现错误的。
}
publicE findByUserName(String name){ //如查找所有性别为男的人。
returnnull;
}
publicSet<E> findByConditions(String where){
returnnull;
}
}
43、通过反射获得泛型的实际类型参数(代码见36、42)
- 通过反射的方式拿到泛型里面的实际类型(高难度知识点)。
示例代码:
ClassGenericalReflection {
private Vector<Date> dates = newVector<Date>();
public void setDates(Vector<Date>dates) {
this.dates = dates;
}
public static void main(String[] args) {
Method methodApply =GenericalReflection.class.getDeclaredMethod("applyGeneric",Vector.class);
ParameterizedType pType =(ParameterizedType)
(methodApply.getGenericParameterTypes())[0];
System.out.println("setDates("
+ ((Class)pType.getRawType()).getName() + "<"
+ ((Class)(pType.getActualTypeArguments()[0])).getName()
+ ">)" );
}
}