黑马程序员——java高新技术(新特性、反射、泛型)
2015-09-28 10:45
555 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
java高新技术 第一部分——[b]JDK1.5新特性[/b]
1、增强for循环
格式:
for(元素类型 变量名 : Collection集合 & 数组 ) { }//增强for循环括号里写两个参数,第一个是声明一个变量,第二个就是需要迭代的容器
高级for循环和传统for循环的区别:
高级for循环在使用时,必须要明确被遍历的目标。这个目标,可以是Collection集合或者数组,如果遍历Collection集合, 在遍历过程中还需要对元素进行操作,比如删除,需要使用迭代器。 如果遍历数组,还需要对数组元素进行操作,建议用传统for循环因为可以定义角标通过角标操作元素。 如果只为遍历获取,可以简化成高级for循环,它的出现为了简化书写。
下面通过几个小例子来说明增强for循环的使用:
输出结果为:
三、可变参数:(方法的重载)VariableParameter
如果一个方法在参数列表中传入多个参数,个数不确定,那么每次都要复写该方法。这时可以用数组作为形式参数。但是在传入时,每次都需要定义一个数组对象,作为实际参数。在JDK1.5版本后,就提供了一个新特性:可变参数。
用…这三个点表示,且这三个点位于变量类型和变量名之间,前后有无空格皆可。
可变参数其实就是数组参数的简写形式。不用每一次都手动的建立数组对象。只要将要操作的元素作为参数传递即可。隐式将这些参数封装成了数组。
在使用时注意:可变参数一定要定义在参数列表的最后面。
可变参数的特点:相当于一个可边长度的数组。
1.只能出现在参数列表的最后
2.位于变量类型和变量名之间,前后有无空格都可以。
3.调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数。
输出结果为:
3、静态导入
静态导入可以导入静态方法,这样就不必写类名而可以直接调用静态方法了。通过import static java.lang.Math.*;导入Math类下所有的静态方法。我们就可以直接通过函数名来调用了。
输出结果为:
注意:如果将javac设置为了Java5以下,那么静态导入等jdk1.5的特性都会报告错误。
四、自动拆箱和装箱
自动装箱、拆箱是指java能自动完成基本数据类型与包装类的自动转换。这里要说一说享元设计模式。
对于基本数据类型的说明:整数在-128 ~ 127之间的数,包装成Integer类型对象,会存入常量池中的缓存,再创建一个对象的时候,如果其值在这个范围内,就会直接到常量池中寻找,因为这些小数值使用的频率很高,所以缓存到常量池中,被调用时就方便很多。
如果有很多很小的对象,并且他们有相同的东西,那就可以把他们作为一个对象。
如果还有很多不同的东西,那就可以作为外部的东西,作为参数传入。
这就是享元设计模式(flyweight)。
例如示例中的Integer对象,在-128~127范围内的Integer对象,用的频率比较高,就会作为同一个对象,因此结果为true。超出这个范围的就不是同一个对象,因此结果为false。
java高新技术 第二部分——[b]反射[/b]
1、字节码文件
Person类代表人,它的实例对象就是张三,李四这样一个个具体的人,Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。
Class类代表Java类,它的各个实例对象又分别对应什么呢?
对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。
·一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型就是Class。
Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属于的包名,字段名称的列表、方法名称的列表,等等。学习反射,首先就要明白Class这个类。
·如何得到各个字节码对应的实例对象(Class类型)
1-类名.class,例如,System.class。
2-对象.getClass(),例如,new Date().getClass()。
3-Class.forName("类名"),例如,Class.forName("java.util.Date");。
九个预定义Class实例对象(八大原始类型+void):
2、反射的概念
反射就是把Java类中的各种成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。
3、构造方法的反射应用
Constructor类代表某个类中的一个构造方法。
Constructor对象代表一个构造方法,Constructor对象上会有的方法有:得到方法名字,得到所属于的类,产生实例对象。
得到某个类所有的构造方法:
例子:Constructor[] constructors= Class.forName("java.lang.String").getConstructors();
·得到某一个构造方法:
例子:Constructor constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);//获得方法时要用到类型
注意:
一个类有多个构造方法,用什么方式可以区分清楚想得到其中的哪个方法呢?根据参数的个数和类型,例如,Class.getMethod(name,Class... args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。重点:参数类型用什么方式表示?用Class实例对象。
·创建实例对象:
通常方式:String str = new String(new StringBuffer("abc"));
反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc")); //调用获得的方法时要用到上面相同类型的实例对象
·Class.newInstance()方法:
例子:String obj = (String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
4、成员变量的反射
Field类代表某个类中的一个成员变量。
得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象呢?所以字段field X 代表的是x的定义,而不是具体的x变量。通过get方法获取值时在指定具体对象。
如果类的某个成员变量的修饰符是private,那么直接通过getField方法获取Field类型的对象就会抛出java.lang.NoSuchFieldException异常。那么直接通过getDeclaredField方法获取Field类型的对象也会出现java.lang.IllegalAccessException异常。通过称之为暴力反射的方式解决,也就是使用setAccessible(true)使private类型的成员变量也可以被获取值。
练习:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"。
结果:
[align=left]aall:aasketaall:itcast[/align]
5、成员方法的反射
·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方法时,数组中的每个元素分别对应被调用方法中的一个参数。所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke("str", new Object[]{1})形式。
常用方法
Method[] getMethods();//只获取公共和父类中的方法。
Method[] getDeclaredMethods();//获取本类中包含私有。
Method getMethod("方法名",参数.class(如果是空参可以写null));
Object invoke(Object obj ,参数);//调用方法
如果方法是静态,invoke方法中的对象参数可以为null。
如:
获取某个类中的某个方法:(如String str =”abc”)
1)通常方式:str.charAt(1)
2)反射方式:
Method charAtMethod =Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);
charAtMethod.invoke(str,1);
说明:如果传递给Method对象的invoke()方法的第一个参数为null,说明Method对象对应的是一个静态方法
用反射方式执行某个main方法:
首先要明确为何要用反射:在写源程序时,并不知道使用者传入的类名是什么,但是虽然传入的类名不知道,而知道的是这个类中的方法有main这个方法。所以可以通过反射的方式,通过使用者传入的类名(可定义字符串型变量作为传入类名的入口,通过这个变量代表类名),内部通过传入的类名获取其main方法,然后执行相应的内容。
此时会出现下面的问题:
启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个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[]{"xxx"}});
mainMethod.invoke(null,(Object)new String[]{"xxx"});
这两种方式编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。
代码示例:
示例二、写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
此示例用eclipse运行时,需要在Run As——>RunConfigurations——>Arguments——>Program arguments中添加要执行的类名,如:cn.itheim.Test。
6、数组的反射
1、具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。数组字节码的名字:有[和数组对应类型的缩写,如int[]数组的名称为:[I
2、Object[]与String[]没有父子关系,Object与String有父子关系,所以new Object[]{“aaa”,”bb”}不能强制转换成new String[]{“aaa”,”bb”}; Object x =“abc”能强制转换成String x =“abc”。
3、如何得到某个数组中的某个元素的类型,
例:
int a = new int[3];Object[] obj=new Object[]{”ABC”,1};
无法得到某个数组的具体类型,只能得到其中某个元素的类型,
如:
Obj[0].getClass().getName()得到的是java.lang.String。
4、Array工具类用于完成对数组的反射操作。
Array.getLength(Object obj);//获取数组的长度
Array.get(Object obj,int x);//获取数组中的元素
5、基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
代码示例:
7、Hashcode分析
HashSet集合可以避免相同的对象,利用的是hashcode。同一个对象的hashcode是一样的。
如果没有复写hashCode方法,对象的hashCode值是按照内存地址进行计算的。这样即使两个对象的内容是想等的,但是存入集合中的内存地址值不同,导致hashCode值也不同,被存入的区域也不同。所以两个内容相等的对象,就可以存入集合中。
所以就有这样的说法:如果两个对象equals相等的话,应该让他们的hashCode也相等。当一个对象存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则对象修改后的哈希值与最初存储进HashSet集合中的哈希值就不同了。在这种情况下,调用contains方法或者remove方法来寻找或者删除这个对象的引用,就会找不到这个对象。从而导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。(程序中某一些对象不再被使用,以为被删掉了,但是没有,还一直在占用内存中,当这样的对象慢慢增加时,就会造成内存泄露。)
测试代码:
由上面的示例可以看到由于pt1对象的y属性被修改了,因此该对象的哈希值也被修改了,所以无法删除掉。因此size值还是2。
8、框架的概念及用反射技术开发框架的原理
框架与框架要解决的核心问题
我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
·框架要解决的核心问题
我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接new 某个类的实例对象了,而要用反射方式来做。
代码示例:
config.properties文件直接放在工程根目录下。
config.properties
[align=left]className= java.util.ArrayList[/align]
[align=left] [/align]
[align=left]结果:[/align]
[align=left]4[/align]
[align=left] [/align]
config.properties
[align=left]className= java.util.HashSet[/align]
[align=left] [/align]
[align=left]结果:[/align]
[align=left]3[/align]
java高新技术 第三部分——泛型
1、概述
泛型:1.5后出现的新特性,解决了安全问题,是一个类型安全机制。
好处:
1.将运行时期转移到编译时期,方便程序员解决问题。提高了安全性。
2.避免了强制转换的麻烦 ArrayList<String> 泛型语法 Iterator<String>
格式:通过<>来定义要操作的引用数据类型。
什么时候使用泛型?
在集合中很常见,只要见到<>就要定义泛型。 <>是用来接收数据类型的泛型技术:其实应用在编译时期,是给编译器使用的技术,到了运行时期,泛型就不存在了。? 为什么??因为泛型的擦除:也就是说,编辑器检查了泛型的类型正确后,在生成的类文件中是没有泛型的。? 在运行时,如何知道获取的元素类型而不用强转呢?? 泛型的补偿:因为存储的时候,类型已经确定了是同一个类型的元素,所以在运行时,只要获取到该元素的类型, 在内部进行一次转换即可,所以使用者不用再做转换动作了。
2、泛型应用
泛型类:
当类中操作的引用数据类型不确定的时候,早期定义Object来完成扩展。 现在定义泛型来完成扩展。
泛型方法:
泛型类定义,在整个类中有效,如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了。 为了让不同方法可以操作不同类型,而且类型还不确定,那么可以将泛型定义在方法上。
静态方法泛型:静态方法不可以访问类上定义的泛型,如果静态方法操作的数据类型不确定,可以将泛型定义在方法上。 public static <W> void menthod(W w){}
泛型接口:
3、泛型限定
通配符<?>,也可以理解为占位符号
? extends E:可以接受E类型或者E的子类型 上限<? extends E>
? super E:可以接受E或E的父类型 下限 <? super E>
上限什么时候用:往集合中添加元素时,既可以添加E类型对象,又可以添加E的子类型对象。为什么?因为取的时候, E类型既可以接收E类对象,又可以接收E的子类型对象。? 下限什么时候用:当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。
泛型的细节:
1)、泛型到底代表什么类型取决于调用者传入的类型,如果没传,默认是Object类型;
2)、使用带泛型的类创建对象时,等式两边指定的泛型必须一致; 原因:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了; 3)、等式两边可以在任意一边使用泛型,在另一边不使用(考虑向后兼容);
ArrayList<String> al = new ArrayList<Object>(); //错 // 要保证左右两边的泛型具体类型一致就可以了,这样不容易出错。
ArrayList<? extends Object> al = new ArrayList<String>(); al.add("aa"); //错 //因为集合具体对象中既可存储String,也可以存储Object的其他子类,所以添加具体的类型对象不合适,类型检查会出现安全问题。 ?extends Object 代表Object的子类型不确定,怎么能添加具体类型的对象呢?
public static void method(ArrayList<? extends Object> al) { al.add("abc"); //错 //只能对al集合中的元素调用Object类中的方法,具体子类型的方法都不能用,因为子类型不确定。 }
4、泛型类型推断
类型参数的类型推断(花了张老师两天的时间总结) l 编译器判断范型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
1.当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(new String[3],3,4) ;
static <E> void swap(E[] a, int i, int j)
2.当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如: add(3,5) ; static <T> T add(T a, T b)
3.当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3.5f) ;
static <T> void fill(T[] a, T v)
4.当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x =(3,3.5f) ;
static <T> T add(T a, T b)
5.参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(new Integer[5],new String[5]) ;
static <T> void copy(T[] a,T[] b);
copy(new Vector<String>(), new Integer[5]) ;
static <T> void copy(Collection<T> a , T[] b);
java高新技术总结到这里就结束了,很多技术理解起来还是非常有难度的,需要扎实的基础和不断反复的推敲,通过写代码来加深自己的理解。路漫漫其修远兮,吾将上下而求索!
java高新技术 第一部分——[b]JDK1.5新特性[/b]
1、增强for循环
格式:
for(元素类型 变量名 : Collection集合 & 数组 ) { }//增强for循环括号里写两个参数,第一个是声明一个变量,第二个就是需要迭代的容器
高级for循环和传统for循环的区别:
高级for循环在使用时,必须要明确被遍历的目标。这个目标,可以是Collection集合或者数组,如果遍历Collection集合, 在遍历过程中还需要对元素进行操作,比如删除,需要使用迭代器。 如果遍历数组,还需要对数组元素进行操作,建议用传统for循环因为可以定义角标通过角标操作元素。 如果只为遍历获取,可以简化成高级for循环,它的出现为了简化书写。
下面通过几个小例子来说明增强for循环的使用:
static void newFor() { // ArrayList<String> list=new ArrayList(); list.add("aaa"); list.add("bbb"); list.add("ccc"); //集合的高级for循环 for(String s:list) { System.out.println(s); } //数组的高级for循环 int[] arr={12,23,45,6,78,9}; for(int i:arr) { System.out.println(i); } Map<Integer,String> map=new HashMap<Integer,String>(); map.put(1,"aaa"); map.put(2,"bbb"); map.put(3,"ccc"); //map不能直接for循环,通过set集合来完成。 Set<Integer> keySet=map.keySet(); for(Integer i:keySet) { System.out.println(i+":"+map.get(i)); } for(Map.Entry<Integer,String> me:map.entrySet()) { System.out.println(me.getKey()+"......."+me.getValue()); } }
输出结果为:
三、可变参数:(方法的重载)VariableParameter
如果一个方法在参数列表中传入多个参数,个数不确定,那么每次都要复写该方法。这时可以用数组作为形式参数。但是在传入时,每次都需要定义一个数组对象,作为实际参数。在JDK1.5版本后,就提供了一个新特性:可变参数。
用…这三个点表示,且这三个点位于变量类型和变量名之间,前后有无空格皆可。
可变参数其实就是数组参数的简写形式。不用每一次都手动的建立数组对象。只要将要操作的元素作为参数传递即可。隐式将这些参数封装成了数组。
在使用时注意:可变参数一定要定义在参数列表的最后面。
可变参数的特点:相当于一个可边长度的数组。
1.只能出现在参数列表的最后
2.位于变量类型和变量名之间,前后有无空格都可以。
3.调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数。
import java.util.*; class JDK5 { public static void main(String[] args) { variablePara("你好",1,2,3); } static void variablePara(String str,int ... arr) { for(int i:arr) { System.out.println(str+":"+i); } } }
输出结果为:
3、静态导入
静态导入可以导入静态方法,这样就不必写类名而可以直接调用静态方法了。通过import static java.lang.Math.*;导入Math类下所有的静态方法。我们就可以直接通过函数名来调用了。
import java.util.*; import static java.lang.Math.*; class JDK5 { public static void main(String[] args) { staticImport(); } static void staticImport() { System.out.println(max(3,6)); System.out.println(abs(3-6)); } }
输出结果为:
注意:如果将javac设置为了Java5以下,那么静态导入等jdk1.5的特性都会报告错误。
四、自动拆箱和装箱
//装箱 Integer iObj = 3; //拆箱 System. out.println(iObj + 12); //结果:15 Integer i1 = 13; Integer i2 = 13; System. out.println(i1 == i2); //结果:true i1 = 137; i2 = 137; System. out.println(i1 == i2); //结果:false
自动装箱、拆箱是指java能自动完成基本数据类型与包装类的自动转换。这里要说一说享元设计模式。
对于基本数据类型的说明:整数在-128 ~ 127之间的数,包装成Integer类型对象,会存入常量池中的缓存,再创建一个对象的时候,如果其值在这个范围内,就会直接到常量池中寻找,因为这些小数值使用的频率很高,所以缓存到常量池中,被调用时就方便很多。
如果有很多很小的对象,并且他们有相同的东西,那就可以把他们作为一个对象。
如果还有很多不同的东西,那就可以作为外部的东西,作为参数传入。
这就是享元设计模式(flyweight)。
例如示例中的Integer对象,在-128~127范围内的Integer对象,用的频率比较高,就会作为同一个对象,因此结果为true。超出这个范围的就不是同一个对象,因此结果为false。
java高新技术 第二部分——[b]反射[/b]
1、字节码文件
Person类代表人,它的实例对象就是张三,李四这样一个个具体的人,Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。
Class类代表Java类,它的各个实例对象又分别对应什么呢?
对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。
·一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型就是Class。
Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属于的包名,字段名称的列表、方法名称的列表,等等。学习反射,首先就要明白Class这个类。
·如何得到各个字节码对应的实例对象(Class类型)
1-类名.class,例如,System.class。
2-对象.getClass(),例如,new Date().getClass()。
3-Class.forName("类名"),例如,Class.forName("java.util.Date");。
九个预定义Class实例对象(八大原始类型+void):
String str = "abc"; Class clazz1 = String.class; Class clazz2 = str.getClass(); Class clazz3 = Class.forName("java.lang.String" ); System. out.println(clazz1 == clazz2); //结果:true System. out.println(clazz2 == clazz3); //结果:true System. out.println(clazz1.isPrimitive()); //结果:false System. out.println(int.class.isPrimitive()); //结果:true System. out.println(int.class == Integer. class); //结果:false System. out.println(int.class == Integer. TYPE); //结果:true System. out.println(int[].class.isPrimitive()); //结果:false System. out.println(int[].class.isArray()); //结果:true
2、反射的概念
反射就是把Java类中的各种成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。
3、构造方法的反射应用
Constructor类代表某个类中的一个构造方法。
Constructor对象代表一个构造方法,Constructor对象上会有的方法有:得到方法名字,得到所属于的类,产生实例对象。
得到某个类所有的构造方法:
例子:Constructor[] constructors= Class.forName("java.lang.String").getConstructors();
·得到某一个构造方法:
例子:Constructor constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);//获得方法时要用到类型
注意:
一个类有多个构造方法,用什么方式可以区分清楚想得到其中的哪个方法呢?根据参数的个数和类型,例如,Class.getMethod(name,Class... args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。重点:参数类型用什么方式表示?用Class实例对象。
·创建实例对象:
通常方式:String str = new String(new StringBuffer("abc"));
反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc")); //调用获得的方法时要用到上面相同类型的实例对象
·Class.newInstance()方法:
例子:String obj = (String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
Constructor constructor = String.class.getConstructor(StringBuffer.class); String str = (String)constructor.newInstance( new StringBuffer("abc" )); System. out.println(str.charAt(2)); //结果:c
4、成员变量的反射
Field类代表某个类中的一个成员变量。
得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象呢?所以字段field X 代表的是x的定义,而不是具体的x变量。通过get方法获取值时在指定具体对象。
ReflectPoint rp = new ReflectPoint(3, 5); Field fieldY = rp.getClass().getField( "y"); System. out.println(fieldY.get(rp));//获取时要指定具体对象 //结果:5
如果类的某个成员变量的修饰符是private,那么直接通过getField方法获取Field类型的对象就会抛出java.lang.NoSuchFieldException异常。那么直接通过getDeclaredField方法获取Field类型的对象也会出现java.lang.IllegalAccessException异常。通过称之为暴力反射的方式解决,也就是使用setAccessible(true)使private类型的成员变量也可以被获取值。
ReflectPoint rp = new ReflectPoint(3, 5); Field fieldX = rp.getClass().getDeclaredField("x"); fieldX.setAccessible(true); System. out.println(fieldX.get(rp));
练习:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"。
//ReflectPoint.java package com.itheima.day1; public class ReflectPoint { private int x; public int y ; public String str1 = "ball"; public String str2 = "basketball"; public String str3 = "itcast"; public ReflectPoint(int x, int y) { super(); this.x = x; this.y = y; } @Override public String toString() { return str1 + ":" + str2 + ":" + str3; } } //ReflectTest.java package com.itheima.day1; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; public class ReflectTest { public static void main(String[] args) throws Exception { ReflectPoint rp = new ReflectPoint(3,5); changeStringValue(rp); System. out.println(rp); } public static void changeStringValue(Object obj) throws Exception { Field[] fields = obj.getClass().getFields(); for(Field field : fields){ if(field.getType() == String.class){ String oldValue = (String)field.get(obj); String newValue = oldValue.replace( 'b', 'a' ); field.set(obj, newValue); } } } }
结果:
[align=left]aall:aasketaall:itcast[/align]
5、成员方法的反射
·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方法时,数组中的每个元素分别对应被调用方法中的一个参数。所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke("str", new Object[]{1})形式。
常用方法
Method[] getMethods();//只获取公共和父类中的方法。
Method[] getDeclaredMethods();//获取本类中包含私有。
Method getMethod("方法名",参数.class(如果是空参可以写null));
Object invoke(Object obj ,参数);//调用方法
如果方法是静态,invoke方法中的对象参数可以为null。
如:
获取某个类中的某个方法:(如String str =”abc”)
1)通常方式:str.charAt(1)
2)反射方式:
Method charAtMethod =Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);
charAtMethod.invoke(str,1);
说明:如果传递给Method对象的invoke()方法的第一个参数为null,说明Method对象对应的是一个静态方法
用反射方式执行某个main方法:
首先要明确为何要用反射:在写源程序时,并不知道使用者传入的类名是什么,但是虽然传入的类名不知道,而知道的是这个类中的方法有main这个方法。所以可以通过反射的方式,通过使用者传入的类名(可定义字符串型变量作为传入类名的入口,通过这个变量代表类名),内部通过传入的类名获取其main方法,然后执行相应的内容。
此时会出现下面的问题:
启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个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[]{"xxx"}});
mainMethod.invoke(null,(Object)new String[]{"xxx"});
这两种方式编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。
代码示例:
//接上示例 //获取Person类中的方法 public static void getPersonMethod() throws Exception{ //如果想要获取方法,必须先要有对象。 Class clazz=Class.forName("cn.itheima.Person"); Person p=(Person)clazz.newInstance(); //获取所以方法 Method[] mes=clazz.getMethods();//只获取公共的和父类中的。 //mes=clazz.getDeclaredMethods();//获取本类中包含私有。 for(Method me:mes){ System.out.println(me); } //获取单个方法 Method me=clazz.getMethod("toString", null); Object returnVaule=me.invoke(p, null); System.out.println(returnVaule); }
示例二、写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
package cn.itheima; //定义一个测试类 class Test{ public static void main(String[] args){ for(String arg : args){ System.out.println(arg); } } } //用反射方式根据用户提供的类名,去执行该类中的main方法。 import java.lang.reflect.Method; public class PerformedMain{ public static void main(String[] args) throws Exception { //普通方式 Test.main(new String[]{"123","456","789"}); System.out.println("-----------------------------"); //反射方式 String className=args[0]; Class clazz=Class.forName(className); Method methodMain=clazz.getMethod("main",String[].class); //方式一:强制转换为超类Object,不用拆包 methodMain.invoke(null, (Object)new String[]{"123","456","789"}); //方式二:将数组打包,编译器拆包后就是一个String[]类型的整体 methodMain.invoke(null, new Object[]{new String[]{"123","456","789"}}); }
此示例用eclipse运行时,需要在Run As——>RunConfigurations——>Arguments——>Program arguments中添加要执行的类名,如:cn.itheim.Test。
6、数组的反射
1、具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。数组字节码的名字:有[和数组对应类型的缩写,如int[]数组的名称为:[I
2、Object[]与String[]没有父子关系,Object与String有父子关系,所以new Object[]{“aaa”,”bb”}不能强制转换成new String[]{“aaa”,”bb”}; Object x =“abc”能强制转换成String x =“abc”。
3、如何得到某个数组中的某个元素的类型,
例:
int a = new int[3];Object[] obj=new Object[]{”ABC”,1};
无法得到某个数组的具体类型,只能得到其中某个元素的类型,
如:
Obj[0].getClass().getName()得到的是java.lang.String。
4、Array工具类用于完成对数组的反射操作。
Array.getLength(Object obj);//获取数组的长度
Array.get(Object obj,int x);//获取数组中的元素
5、基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
代码示例:
package cn.itheima.Demo; import java.lang.reflect.Array; import java.util.Arrays; public class ArrayReflect { public static void main(String[] args) { 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().equals(a2.getClass()));//true System.out.println(a1.getClass().equals(a3.getClass()));//false System.out.println(a1.getClass().equals(a4.getClass()));//false System.out.println(a1.getClass().getName());//[I System.out.println(a4.getClass().getName());//[Ljava.lang.String; System.out.println(a1.getClass().getSuperclass());//class java.lang.Object System.out.println(a4.getClass().getSuperclass());//class java.lang.Object Object obj1=a1; Object obj2=a3; Object obj3=a4; // Object[] obj11=a1;//这样是不行的,因为a1中的元素是int类型,基本数据类型不是Object Object[] obj13=a3; Object[] obj14=a4;//这样可以,因为String数组中的元素属于Object System.out.println(a1);//[I@4caaf64e System.out.println(a4);//[Ljava.lang.String;@6c10a234 System.out.println(Arrays.asList(a1));//[I@4caaf64e System.out.println(Arrays.asList(a4));//[a, b, c] /* Arrays.asList()方法处理int[]和String[]时的差异。 * 打印Arrays.asList(a1);还是跟直接打印a1是一样的 打印Arrays.asList(a4);就会把a3的元素打印出来。 这是因为此方法在JDK1.4版本中,接收的Object类型的数组, 而a3可以作为Object数组传入。但是a1不可以作为Object数组传入,所以只能按照JDK1.5版本来处理。 在JDK1.5版本中,传入的是一个可变参数,所以a1就被当作是一个object,也就是一个参数, 而不是数组传入,所以打印的结果还是跟直接打印a1一样。 */ //Array工具类用于完成对数组的反射操作。如打印任意数值 printObject(a1); printObject(a4); printObject("abc"); } //打印任意数值 private static void printObject(Object obj) { Class clazz=obj.getClass(); //如果传入的是数组,则遍历 if(clazz.isArray()){ int len =Array.getLength(obj);//Array工具类获取数组长度方法 for(int x=0;x<len;x++){ System.out.println(Array.get(obj, x));//Array工具获取数组元素 } } else System.out.println(obj); } }
7、Hashcode分析
HashSet集合可以避免相同的对象,利用的是hashcode。同一个对象的hashcode是一样的。
如果没有复写hashCode方法,对象的hashCode值是按照内存地址进行计算的。这样即使两个对象的内容是想等的,但是存入集合中的内存地址值不同,导致hashCode值也不同,被存入的区域也不同。所以两个内容相等的对象,就可以存入集合中。
所以就有这样的说法:如果两个对象equals相等的话,应该让他们的hashCode也相等。当一个对象存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则对象修改后的哈希值与最初存储进HashSet集合中的哈希值就不同了。在这种情况下,调用contains方法或者remove方法来寻找或者删除这个对象的引用,就会找不到这个对象。从而导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。(程序中某一些对象不再被使用,以为被删掉了,但是没有,还一直在占用内存中,当这样的对象慢慢增加时,就会造成内存泄露。)
package com.itheima.day1; public class ReflectPoint { private int x ; public int y ; public ReflectPoint(int x, int y) { super(); this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + x; result = prime * result + y; return result; } @Override 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 ; } }
测试代码:
ReflectPoint.java package com.itheima.day1; import java.util.Collection; import java.util.HashSet; public class ReflectTest { public static void main(String[] args) throws Exception { 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); collections.add(pt1); pt1.y = 70; collections.remove(pt1); System. out.println(collections.size()); //结果:2 } }
由上面的示例可以看到由于pt1对象的y属性被修改了,因此该对象的哈希值也被修改了,所以无法删除掉。因此size值还是2。
8、框架的概念及用反射技术开发框架的原理
框架与框架要解决的核心问题
我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
·框架要解决的核心问题
我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接new 某个类的实例对象了,而要用反射方式来做。
代码示例:
//ReflectTest.java package com.itheima.day1; import java.io.FileInputStream; import java.io.InputStream; import java.util.Collection; import java.util.Properties; public class ReflectTest { public static void main(String[] args) throws Exception { InputStream is = new FileInputStream("config.properties" ); Properties props = new Properties(); props.load(is); is.close(); String className = (String)props.get( "className"); Collection collections = (Collection)Class.forName(className).newInstance(); 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); System. out.println(collections.size()); } }
config.properties文件直接放在工程根目录下。
config.properties
[align=left]className= java.util.ArrayList[/align]
[align=left] [/align]
[align=left]结果:[/align]
[align=left]4[/align]
[align=left] [/align]
config.properties
[align=left]className= java.util.HashSet[/align]
[align=left] [/align]
[align=left]结果:[/align]
[align=left]3[/align]
java高新技术 第三部分——泛型
1、概述
泛型:1.5后出现的新特性,解决了安全问题,是一个类型安全机制。
好处:
1.将运行时期转移到编译时期,方便程序员解决问题。提高了安全性。
2.避免了强制转换的麻烦 ArrayList<String> 泛型语法 Iterator<String>
格式:通过<>来定义要操作的引用数据类型。
什么时候使用泛型?
在集合中很常见,只要见到<>就要定义泛型。 <>是用来接收数据类型的泛型技术:其实应用在编译时期,是给编译器使用的技术,到了运行时期,泛型就不存在了。? 为什么??因为泛型的擦除:也就是说,编辑器检查了泛型的类型正确后,在生成的类文件中是没有泛型的。? 在运行时,如何知道获取的元素类型而不用强转呢?? 泛型的补偿:因为存储的时候,类型已经确定了是同一个类型的元素,所以在运行时,只要获取到该元素的类型, 在内部进行一次转换即可,所以使用者不用再做转换动作了。
2、泛型应用
泛型类:
当类中操作的引用数据类型不确定的时候,早期定义Object来完成扩展。 现在定义泛型来完成扩展。
class Utils<QQ> { private QQ q; public void setObject(QQ q); { this.q=q; } public QQ gerobject() { return q; } }
泛型方法:
泛型类定义,在整个类中有效,如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了。 为了让不同方法可以操作不同类型,而且类型还不确定,那么可以将泛型定义在方法上。
class Demo { public <T> void show(T t) { } }
静态方法泛型:静态方法不可以访问类上定义的泛型,如果静态方法操作的数据类型不确定,可以将泛型定义在方法上。 public static <W> void menthod(W w){}
泛型接口:
interface Inter<T> { void show(T t); } class InterImpl<R> implements Inter<R> { public void show(R r) { System.out.println("show:"+r); } }
3、泛型限定
通配符<?>,也可以理解为占位符号
? extends E:可以接受E类型或者E的子类型 上限<? extends E>
? super E:可以接受E或E的父类型 下限 <? super E>
上限什么时候用:往集合中添加元素时,既可以添加E类型对象,又可以添加E的子类型对象。为什么?因为取的时候, E类型既可以接收E类对象,又可以接收E的子类型对象。? 下限什么时候用:当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。
泛型的细节:
1)、泛型到底代表什么类型取决于调用者传入的类型,如果没传,默认是Object类型;
2)、使用带泛型的类创建对象时,等式两边指定的泛型必须一致; 原因:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了; 3)、等式两边可以在任意一边使用泛型,在另一边不使用(考虑向后兼容);
ArrayList<String> al = new ArrayList<Object>(); //错 // 要保证左右两边的泛型具体类型一致就可以了,这样不容易出错。
ArrayList<? extends Object> al = new ArrayList<String>(); al.add("aa"); //错 //因为集合具体对象中既可存储String,也可以存储Object的其他子类,所以添加具体的类型对象不合适,类型检查会出现安全问题。 ?extends Object 代表Object的子类型不确定,怎么能添加具体类型的对象呢?
public static void method(ArrayList<? extends Object> al) { al.add("abc"); //错 //只能对al集合中的元素调用Object类中的方法,具体子类型的方法都不能用,因为子类型不确定。 }
4、泛型类型推断
类型参数的类型推断(花了张老师两天的时间总结) l 编译器判断范型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
1.当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(new String[3],3,4) ;
static <E> void swap(E[] a, int i, int j)
2.当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如: add(3,5) ; static <T> T add(T a, T b)
3.当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3.5f) ;
static <T> void fill(T[] a, T v)
4.当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x =(3,3.5f) ;
static <T> T add(T a, T b)
5.参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(new Integer[5],new String[5]) ;
static <T> void copy(T[] a,T[] b);
copy(new Vector<String>(), new Integer[5]) ;
static <T> void copy(Collection<T> a , T[] b);
java高新技术总结到这里就结束了,很多技术理解起来还是非常有难度的,需要扎实的基础和不断反复的推敲,通过写代码来加深自己的理解。路漫漫其修远兮,吾将上下而求索!
相关文章推荐
- 【面试题2】阿里航旅事业部的前端开发面试题(转)
- 一个并不“艰难”的决定—一个程序员的成长史(3)
- Java程序员生存手册
- 万众创业,最受伤的还是程序员
- 周鸿祎在360新员工入职培训上的讲话_职场励志
- 二叉树 面试 JAVA
- 黑马程序员——JAVA之面向对象(一)
- 程序员技术练级攻略
- 黑马程序员——IO流:自顶向下的总结
- 黑马程序员——java介绍及配置环境变量
- 程序员学习能力提升三要素
- 黑马程序员——正则表达式
- 黑马程序员----C 语言学习笔记之数组指针与字符串指针
- 数据库面试中常用的问题
- iOS 面试题:OC标题的基本概念<延续>
- iflab隔壁ios组新生面试题
- 黑马程序员-----c学习中的小程序
- 面试步步受挫,在打击中成长
- 软件测试面试题解析(二)
- 面试---内存偏移