------- android培训、java培训、期待与您交流! ----------
Java5的新特性
1、静态导入
使用静态导入可以使被导入类的所有静态变量和静态方法在当前类直接可见,使用这些静态成员无需再给出他们的类名。
import 语句可以导入一个类或某个包中的所有类。
import static 语句导入一个类中的某个静态方法或所有静态方法。
例如:
import static java.lang.Math.*;
2、可变参数(Varargs)
可变参数的特点:
只能出现在参数列表的最后;
...位于变量类型和变量名之间,前后有无空格都可以;
调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数。
例如:
public static int add(int x, int ... args)
{
int sum=x;
for(int i=0;i<args.length;i++)
{
sum+=args[i];
}
return sum;
}
3、增强for循环
语法:
for(type 变量名 : 集合变量名){...}
注意事项:
迭代变量必须在()中定义。
集合变量可以是数组或实现了Iterable接口的集合类
例如:
public static int add(int x, int ... args)
{
int sum=x;
//增强for循环
for(int arg : args)
{
sum+=arg;
}
return sum;
}
4、自动装箱拆箱(Autoboxing/Unboxing)
简单的说是类型自动转换。
自动装包:基本类型自动转为包装类(int ——Integer)
自动拆包:包装类自动转为基本类型(Integer——int)
享元设计模式
享元模式(Flyweight):有很多个小的对象,它们有很多属性相同,把它们变成一个对象。那些不同的属性,把它们变成方法的参数,称之为外部状态。那些相同的属性称之为这个对象的内部状态。
5、枚举(Enums)
为什么要有枚举?
枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错。
枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。
用普通类如何实现枚举功能,定义一个Weekday的类来模拟枚举功能。
私有的构造方法
每个元素分别用一个公有的静态成员变量表示
可以有若干公有方法或抽象方法。例如:要提供nextDay方法必须是抽象的。
采用抽象方法定义nextDay就将大量的if else 语句转移成了一个个独立的类。
代码示例:
public abstract class WeekDay {
private WeekDay(){}
public abstract WeekDay nextDay();
public final static WeekDay SUN = new WeekDay(){
@Override
public WeekDay nextDay() {
// TODO Auto-generated method stub
return MON;
}
}; //Sunday
public final static WeekDay MON = new WeekDay(){
@Override
public WeekDay nextDay() {
// TODO Auto-generated method stub
return SUN;
}
}; //Monday
public String toString(){
return this == SUN?"SUN":"MON";
}
}
枚举的基本应用
举例:定义一个Weekday的枚举。
扩展:枚举类的values.valueOf,name.toString,ordinal等方法。
总结:枚举是一种特殊的类,其中的每个元素都是该类的一个实例对象。
例如,可以调用WeekDay.SUM.getClass().getName和WeekDay.class.getName()。
如果想在一个类中编写完各个枚举类和测试调用类,那么可以将枚举类定义成调用类的内部类。
枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法。
枚举元素必须位于枚举体中的最开始部分,枚举元素列表的后面要有分号与其他成员分隔。
把枚举中的成员方法或变量等放在枚举元素的前面,编译器报告错误。
枚举只有一个成员时,就可以作为一种单例的实现方式。
带构造方法的枚举
构造方法必须定义成私有的。
如果有多个构造方法,该如何选择哪个构造方法?
枚举元素MON和MON()的效果一样,都是调用默认的构造方法。
例如:
public enum WeekDay{
SUN(1),MON(),TUE,WED,THI,FRI,SAT;
private WeekDay(){System.out.println("first");}
private WeekDay(int day){System.out.println("second");}
}
带抽象方法的枚举
定义枚举TrafficLamp
实现普通的next方法。
实现抽象的next方法:每个元素分别是由枚举的子类来生成的实例对象,这些子类采用类似内部类的方式进行定义。
增加上表示时间的构造方法。
例如:
public enum TrafficLamp{
RED(30){
@Override
public TrafficLamp nextLamp() {
// TODO Auto-generated method stub
return GREEN;
}
},
GREEN(45){
@Override
public TrafficLamp nextLamp() {
// TODO Auto-generated method stub
return YELLOW;
}
},
YELLOW(5){
@Override
public TrafficLamp nextLamp() {
// TODO Auto-generated method stub
return RED;
}
};
public abstract TrafficLamp nextLamp();
private int time;
private TrafficLamp(int time){
this.time = time;
}
}
6、注解(Annotation)
了解注解及java提供的几个基本注解
先通过@SuppressWarnings的应用让大家直观地了解注解:
通过System.runFinalizersOnExit(true);的编译警告引出@SuppressWarnings("deprecation")
@Deprecated
直接在刚才的类中增加一个方法,并加上@Deprecated标注,在另外一个类中调用这个方法。
@Override
public boolean equals(Reflect other)方法与HashSet结合讲解。
总结:
注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记。以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。
自定义注解及其应用
定义一个最简单的注解:public @interface MyAnnotation{}
把它加在某个类上:@MyAnnotation public class AnnotationTest{}
用反射进行测试AnnotationTest的定义上是否有@MyAnnotation
根据反射测试的问题,引出@Retention元注解的讲解,其三种取值:RetetionPolicy.SOURCE、RetetionPolicy.CLASS、RetetionPolicy.RUNTIME
分别对应:java源文件-->class文件-->内存中的字节码
思考:@Override、@SuppressWarnings和@Deprecated这三个注解的属性值分别是什么?
RetetionPolicy.SOURCE、RetetionPolicy.SOURCE、RetetionPolicy.RUNTIME
演示和讲解@Target元注解
Target的默认值为任何元素,设置Target等于ElementType.METHOD。原来加在类上的注解就报错了,改为用数组方式设置{ElementType.METHOD,ElementType.TYPE}就可以了。
元注解以及其枚举属性值不用记,只要会看jdk提供那几个基本注解的API帮助文档的定义或其源代码,按图索骥即可查到,或者直接看java.lang.annotation包下面的类。
创建一个ItcastAnnotation注解文件:
//元注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface ItcastAnnotation {
}
//主程序代码:
@ItcastAnnotation
public class AnnotationTest {
@SuppressWarnings("deprecation")
@ItcastAnnotation
public static void main(String[] args) {
// TODO Auto-generated method stub
System.runFinalizersOnExit(true);
if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class))
{
ItcastAnnotation annotation = (ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class);
System.out.println(annotation); //@cn.itcast.day02.ItcastAnnotation()
}
}
@Deprecated
public static void sayHello()
{
System.out.println("hi,传智播客!");
}
}
为注解增加基本属性
什么是注解的属性
加了属性的标记效果为:@MyAnnotation(color = "red")
定义基本类型的属性和应用属性
在注解类中增加String color();
@MyAnnotation(color = "red");
用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法。
MyAnnotation a = (MyAnnotation)AnnotationTest.class.getAnnotation(MyAnnotation.class);
System.out.println(a.color);
可以认为上面这个@MyAnnotation是MyAnnotation类的一个实例对象。
为属性指定缺省值:
String color() default "yellow";
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("xxx");
@MyAnnotation(annotationAttr=@MetaAnnotation("yyy"))
可以认为上面这个@MyAnnotation是MyAnnotation类的一个实例对象,同样的道理,
可以认为上面这个@MyAnnotation是MyAnnotation类的一个实例对象,调用代码如下:
MetaAnnotation ma = myAnnotation.annotationAttr();
System.out.println(ma.value());
MetaAnnotation类定义:
public @interface MetaAnnotation {
String value();
}
创建一个ItcastAnnotation注解文件:
import cn.itcast.day01.EnumTest;
//元注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface ItcastAnnotation {
String color() default "blue";
String value();
//数组类型的属性
int[] arrayAttr() default {3,4,5};
//枚举类型的属性
EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.RED;
//注解类型的属性
MetaAnnotation annotationAttr() default @MetaAnnotation("lhm");
}
//主程序代码:
@ItcastAnnotation(annotationAttr=@MetaAnnotation("flx"),color="red",value="abc",arrayAttr=1)
public class AnnotationTest {
@SuppressWarnings("deprecation")
@ItcastAnnotation("xyz")
public static void main(String[] args) {
// TODO Auto-generated method stub
System.runFinalizersOnExit(true);
if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class))
{
ItcastAnnotation annotation = (ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class);
System.out.println(annotation); //@cn.itcast.day02.ItcastAnnotation()
System.out.println(annotation.color()); //red
//数组类型
System.out.println(annotation.value()); //abc
System.out.println(annotation.arrayAttr().length); //1
//枚举类型
System.out.println(annotation.lamp().nextLamp().name()); //GREEN
//注解类型
System.out.println(annotation.annotationAttr().value()); //flx
}
}
@Deprecated
public static void sayHello()
{
System.out.println("hi,传智播客!");
}
}
7、泛型(Generics)
jdk1.5 的集合类希望你在定义集合时,明确表示你要向集合中装哪种类型的数据,无法加入指定类型以外的数据。泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉"类型"信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据。例如:用反射得到集合,再调用其add方法即可。
ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
整个称为ArrayList<E>泛型类型
ArrayList<E>中的E称为类型变量或类型参数
整个ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<>念着typeof
ArrayList称为原始类型
参数化类型与原始类型的兼容性:
参数化类型可以引用一个原始类型的对象,编译报告警告。
例如:Collection<String> s = 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<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();
提示:限定通配符总是包括自己。
总结:
使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
示例代码:
public static void printCollection(Collection<?> collection){
//collection.add("abc");
System.out.println(collection.size());
for(Object obj : collection)
{
System.out.println(obj);
}
}
定义泛型方法
java的泛型方法没有C++模板函数功能强大,java中的如下代码无法通过编译:
template<class T>
T add(T x, T y){
return (T)(x+y);
//return null;
}
交换数组中的两个元素的位置的泛型方法语法定义如下:
static <E> void swap(E[], int i, int j){
E t = a[i];
a[i] = a[j];
a[j] = t;
}
用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。只有引用类型才能作为泛型方法的实际参数,swap(new int[3],35);语句会报告编译错误。除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符。例如:Class.getAnnotation()方法的定义。并且可以用&来指定多个边界。
如:<V extends Serializable & cloneable> void method(){}
普通方法、构造方法和静态方法中都可以使用泛型。编译器也不允许创建类型变量的数组。也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分。
例如:
public static <K,V> V getValue(K key){return map.get(key);}
示例代码:
public class GenericTest {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
//1.
Object obj = "abc";
String x3 = autoConvert(obj);
//4、5
copy1(new Vector<String>(), new String[10]);
copy2(new Date[10], new String[10]);
}
//5.定义一个方法,把任意参数类型的一个数组中的数据安全地复制到相应类型的另一个数组中。
public static <T> void copy2(T[] dest, T[] src){
}
//4.定义一个方法,把在任意参数类型的集合中的数据安全地复制到相应类型的数组。
public static <T> void copy1(Collection<T> dest, T[] src){
}
//3.采用自定泛型方法的 式打印出任意参数化类型的集合中的所有内容。
public static <T> void printCollection2(Collection<T> collection, T obj2){
System.out.println(collection.size());
for(Object obj : collection)
{
System.out.println(obj);
}
collection.add(obj2);
}
//2.定义一个方法,可以将任意类型的数组中的所有元素填充为相应类型的某个对象。
private static <T> void fillArray(T[] a,T obj){
for(int i=0; i<a.length; i++)
{
a[i] = obj;
}
}
//1.编写一个泛型方法,自动将Object类型的对象转换成其他类型。
private static <T> T autoConvert(Object obj){
return (T)obj;
}
}
类型参数的类型推断
编译器判断泛型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型。例如:
swap(new String[3],3,4) ——> static <E> void swap(E[] a, int i, int j);
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:
add(3,5) ——> static <T> T add(T a, T b);
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型。例如:下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3,5) ——> static <T> void fill(T[] a, T v);
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应了不同的类型,并且使用返回值,这时候优先考虑返回值的类型。例如:下面语句实际对应的类型就是Integer了,编译将报告错误,将变量的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x = (3, 3.5f) ——> static <T> add(T a, T b);
参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类型实例将类型变量直接确定为String类型,编译将出现问题:
copy(new Integer[5], new String[5]) ——> static <T> void copy(T[] a, T[] b);
定义泛型类型
如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
public class GenericDao<T>{
private T field;
public void save(T obj){}
public T getById(int id){}
}
类级别的泛型时根据引用该类名时指定的类型信息来参数化类型变量的。例如,如下两种方式都可以:
GenericDao<String> dao = null;
new genericDao<String>();
注意:
在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。
问题:类中只有一个方法需要使用泛型,是使用类级别的泛型,还是使用方法级别的泛型?
使用类级别的泛型
通过反射获得泛型的实际类型参数
示例代码:
public static void main(String[] args) throws Exception{
Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class);
Type[] types = applyMethod.getGenericParameterTypes();
ParameterizedType pType = (ParameterizedType)types[0];
System.out.println(pType.getRawType()); //class java.util.Vector
//System.out.println(pType.getActualTypeArguments()); //[Ljava.lang.reflect.Type;@16a38b5
System.out.println(pType.getActualTypeArguments()[0]); //class java.util.Date
}
public static void applyVector(Vector<Date> v1){
}
8、反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射就是把Java类中的各种成分映射成相应的java类。
反射的基石-->Class 类
Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。
如何得到各个字节码对应的实例对象(Class类型)
1.类名.class。例如:System.class
2.对象.getClass()。例如:new Date().getClass();
3.Class.forName("类名")。例如:Class.forName("java.util.Date");