JDK1.5新特性
目录
1.之前已经学习过的JDK1.5新特性
2.静态导入 StaticImport
3.可变参数 ...
4.高级for循环
5.枚举
6.泛型 Generic
7.注解
注:本章全部为重点内容。
####################################################################################
####################################################################################
1.之前已经学习过的JDK1.5新特性
(1)StringBuilder
(2)Lock
(3)Condition
(4)自动装箱与拆箱
2.静态导入 StaticImport
import 语句可以导入一个类或某个包中的所有类
import static 语句导入一个类中的某个静态成员或所有静态成员
注意:
a.当类中方法都是静态方法(如工具类)可以使用静态导入
b.当类名重名时,需要指定具体的包名。当方法重名时,指定具备所属的对象或者类。
代码示例
1 import java.util.*;//导入了Util包中所有的类 2 import static java.util.Arrays.*;//导入leArrays这个类中的所有静态成员。 3 import static java.util.Collections.sort;//导入了Collections类中的sort方法。 4 import static java.lang.System.out;//导入了System类中的out。 5 class StaticImport 6 { 7 public static void main(String[] args) 8 { 9 out.println("haha");//省略了System. 10 int[] arr = {3,1,5}; 11 sort(arr); 12 out.println(Arrays.toString(arr)); 13 //这里的Arrays不能省略,因为该类默认继承了Object类,而Object类也有toString方法,当方法重名时,指定具备所属的类 14 ArrayList al = new ArrayList(); 15 al.add(1); 16 al.add(3); 17 al.add(2); 18 out.println(al); 19 sort(al); 20 out.println(al); 21 } 22 }
3.可变参数 ...
函数的另一种表现形式
返回值类型 函数名(参数类型 ... 形式参数)
{
执行语句;
}
其实接收的是一个数组,可以指定实际参数个数。
注意:
a.可变参数只能出现在参数列表的最后;
b. ...位于变量类型和变量名之间,前后有无空格都可以;
c.调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数。
当方法接收得参数个数不固定时,如何实现:
//方法一 重载
1 class ParamMethodDemo 2 { 3 public static void main(String[] args) 4 { 5 //show(3,4); 6 7 public static void show(int a,int b) 8 { 9 //执行语句 10 } 11 public static void show(int a,int b,int c) 12 { 13 //执行语句 14 } 15 }
//方法二 数组 虽然少定义了多个方法。但是每次都要定义一个数组。作为实际参数。
1 class ParamMethodDemo 2 { 3 public static void main(String[] args) 4 { 5 int[] arr = {3,4}; 6 show(arr); 7 8 int[] arr1 = {2,3,4,5}; 9 show(arr1); 10 11 public static void show(int[] arr) 12 { 13 //执行语句 14 } 15 }
//方法三 可变参数...
//数组参数的简写形式。不用每一次都手动的建立数组对象。只要将要操作的元素作为参数传递即可。隐式的将这些参数封装成了数组。
1 class ParamMethodDemo 2 { 3 public static void main(String[] args) 4 { 5 show("haha",2,3,4,5,6); 6 //show();//传了一个0长度的数组 7 8 } 9 //public static void show(int... arr,String str)//编译失败,可变参数要在参数列表最后面 10 public static void show(String str,int... arr) 11 { 12 //执行语句 13 } 14 }
4.高级for循环
(1)格式:
for(数据类型 变量名 : 被遍历的集合(Collection)或者数组)
{
}
(2)特点:
凡是支持迭代器的集合也都支持高级 for 循环,它是迭代器的简写格式.有泛型时可以指定数据类型,没有泛型时要用 Object 来接受任意类型的数据。
注意事项:
迭代变量必须在( )中定义!
集合变量可以是数组或实现了Iterable接口的集合类
(3)高级for循环和迭代器的区别:
对集合进行遍历。只能获取集合元素。但是不能对集合进行操作。迭代器除了遍历,还可以进行remove集合中元素的动作。如果是用ListIterator,还可以在遍历过程中对集合进行增删改查的动作。
(4)传统for和高级for的区别
高级for有一个局限性。必须有被遍历的目标。建议在遍历数组的时候,还是希望是用传统for。因为传统for可以定义角标。
(5)代码示例
对于数组
int[] arr = {3,5,1};
for(int i : arr)
{
System.out.println("i:"+i);
}
对于Collection集合
ArrayList<String> al = new ArrayList<String>();
al.add("abc1");
al.add("abc2");
al.add("abc3");
for(String s : al)
{
System.out.println(s);
}
问题 判断打印结果
ArrayList<String> al = new ArrayList<String>();
al.add("abc1");
al.add("abc2");
al.add("abc3");
for(String s : al)
{
s = "kk";
}
System.out.println(al);
s只是指向了集合中的元素,在遍历过程中被打印。当s指向其它对象时,集合中的元素没有变化,所以打印集合结果也不会变化,为原集合。
对于Mpa集合
keySet方法
Set<Integer> keySet = hm.keySet();
for(Integer i : keySet)
简写格式
for(Integer i : hm.keySet())
entrySet方法
Set<Map.Entry<Integer,String>> entrySet = hm.entrySet();
for(Map.Entry<Integer,String> me : entrySet)
简写格式
for(Map.Entry<Integer,String> me : hm.entrySet())
5.枚举
(1)为什么要有枚举
假设用1-7分别表示星期一到星期日,但有人可能会写成int weekday = 0;或即使使用常量方式也无法阻止意外。枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错。枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。
(2)普通类与枚举
定义一个Weekday的普通类来模拟枚举功能。
1 public abstract class WeekDay { 2 private WeekDay(){} 3 public static final WeekDay SUN=new WeekDay(){ 4 public WeekDay nextDay(){ 5 return MON; 6 } 7 }; 8 public static final WeekDay MON=new WeekDay(){ 9 public WeekDay nextDay(){ 10 return SUN; 11 } 12 }; 13 public abstract WeekDay nextDay(); 14 public String toString(){ 15 return this==SUN?"SUN":"MON"; 16 } 17 }
枚举的做法
1 enum WeekDay{ 2 SUN{ 3 public WeekDay nextDay(){ 4 return MON; 5 } 6 }, 7 MON{ 8 public WeekDay nextDay(){ 9 return SUN; 10 } 11 }; 12 13 public abstract WeekDay nextDay(); 14 }
注意:
a.枚举是一种特殊的类,其中的每个元素都是该类的一个实例对象
b.枚举也可以定义构造方法、成员变量、普通方法和抽象方法。但构造方法必须定义成私有的,
c.枚举元素必须位于枚举体中的最开始部分,枚举元素列表的后要有分号与其他成员分隔。把枚举中的成员方法或变量等放在枚举元素的前面,编译器会报告错误。
d.枚举类的实例对象个数是有限的,就是那些成员,当枚举只有一个成员时,就可以作为一种单例的实现方式。
6.泛型 Generic
用于解决安全问题,是一个类型安全机制。
(1)泛型概述
泛型作用:
没有使用泛型时,只要是对象,不管是什么类型的对象,都可以存储进同一个集合中。使用泛型集合,可以将一个集合中的元素限定为一个特定类型,集合中只能存储同一个类型的对象,这样更安全;并且当从集合获取一个对象时,编译器也可以知道这个对象的类型,不需要对对象进行强制类型转换,这样更方便。
泛型的好处
1)提高了程序的安全性
2)将运行期遇到的问题转移到了编译期
3)省去了类型强转的麻烦
4)泛型类的出现优化了程序设计
泛型术语:
ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
整个ArrayList<E>称为泛型类型
ArrayList<E>中的E称为类型变量或类型参数
整个ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<>念typeof
ArrayList称为原始类型
参数化类型与原始类型的兼容性:
参数化类型可以引用一个原始类型的对象,编译报告警告,
原始类型可以引用一个参数化类型的对象,编译报告警告,
注意:
编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型,
(2)泛型的定义
泛型既可以定义在类上,也可以定义在方法上,包括静态方法和非静态方法,还可以定义在接口上和异常中。
1)泛型定义在类上
如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,泛型类定义的泛型,在整个类中有效。如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了。语法格式如下:
public class GenericDao<T> {
private T field1;
public void save(T obj){}
public T getById(int id){}
}
代码示例
1 //泛型被定义在类上 2 class Demo<T> 3 { 4 public void show(T t) 5 { 6 System.out.println("show:"+t); 7 } 8 public void print(T t) 9 { 10 System.out.println("print:"+t); 11 } 12 } 13 class GenericDemo 14 { 15 public static void main(String[] args) 16 { 17 Demo<Integer> d = new Demo<Integer>(); 18 19 d.show(new Integer(4)); 20 d.print(3); 21 //编译通过,泛型定义在类上,明确操作类型后,所有方法都只能操作该类型 22 23 Demo<String> d1 = new Demo<String>(); 24 d1.print("haha"); 25 d1.show(5); 26 //编译失败,已经明确数据为String,show方法却操作了Intager 27 28 } 29 }
2)泛型定义在方法上
为了让不同方法可以操作不同类型,而且类型还不确定。那么可以将泛型定义在方法上。用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。如交换数组中的两个元素的位置的泛型方法语法定义如下:
static <E> void swap(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]本身已经是对象了,编译器不会对new int[3]中的int自动拆箱和装箱了。
代码示例
1 class Demo 2 { 3 public <T> void show(T t) 4 { 5 System.out.println("show:"+t); 6 } 7 public <T> void print(T t)//两个方法中的T没有关系。因为泛型只在方法内有效 8 { 9 System.out.println("show:"+t); 10 } 11 } 12 class GenericDemo 13 { 14 public static void main(String[] args) 15 { 16 Demo d = new Demo(); 17 d.show("haha"); 18 d.show(new Integer(4)); 19 d.print("heihei"); 20 //编译通过,泛型定义在方法上,只在方法中有效,彼此不影响 21 22 } 23 }
3)泛型既定义在类上,又定义在方法上
1 class Demo<T> 2 { 3 public void show(T t) 4 { 5 System.out.println("show:"+t); 6 } 7 public <Q> void print(Q q) 8 { 9 System.out.println("print:"+q); 10 } 11 } 12 class GenericDemo 13 { 14 public static void main(String[] args) 15 { 16 Demo <String> d = new Demo<String>(); 17 d.show("haha"); 18 //d.show(4);//编译失败,show方法只能调用已经明确的操作类型 19 d.print(5); 20 d.print("hehe"); 21 //编译通过,泛型定义在print方法上,可以操作不同类型。 22 } 23 }
4)泛型定义在静态方法上
静态方法不可以访问类上定义的泛型。因为泛型定义在类上时,操作类型是在对象建立时明确的,只有对象带着类型在运行。而静态方法方法存在时对象还不存在,所以不能访问类上定义的泛型。如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。
代码示例
class Demo<T>
{
public static <W> void method(W t)
{
System.out.println("method:"+t);
}
}
class GenericDemo
{
public static void main(String[] args)
{
Demo.method("hahahahha");
}
}
5)泛型定义在接口上
interface Inter<T>
{
void show(T t);
}
//当实现时已经确定操作类型
class InterImpl implements Inter<String>
{
public void show(String t)
{
System.out.println("show :"+t);
}
}
class GenericDemo
{
public static void main(String[] args)
{
InterImpl i = new InterImpl();
i.show("haha");
}
}
//当实现时不确定操作类型
class InterImpl<T> implements Inter<T>
{
public void show(T t)
{
System.out.println("show :"+t);
}
}
class GenericDemo5
{
public static void main(String[] args)
{
InterImpl<Integer> i = new InterImpl<Integer>();
i.show(4);
}
}
6)泛型定义在异常内
也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。用下面的代码说明对异常如何采用泛型:
private static <T extends Exception> sayHello() throws T
{
try{
//被检测的语句
}catch(Exception e){
throw (T)e;
}
}
注意:
a.在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
b.当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。
(3)泛型限定
? 通配符
当对象类型不确定时,可以用一个通配符来表示。也可以理解为占位符。使用?通配符可以引用其他各种参数化的类型,?主要用作引用,不能用它去给其他变量赋值;可以调用与参数化无关的方法,不能调用与参数化有关的方法。
? extends E: 可以接收E类型或者E的子类型。上限。
? super E: 可以接收E类型或者E的父类型。下限
注意:
限定通配符总是包括自己。
4)类型参数的类型推断
编译器判断范型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(new String[5],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.5f) static <T> void fill(T[] a, T v)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x =(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>(), new Integer[5]) static <T> void copy(Collection<T> a , T[] b);
5)扩展阅读:
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
Java中的泛型类型(或者泛型)类似于 C++ 中的模板。但是这种相似性仅限于表面,Java 语言中的泛型基本上完全是在编译器中实现,用于编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种实现技术称为擦除(erasure)(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为 Java 厂商升级其 JVM 造成难以逾越的障碍。所以,java的泛型采用了可以完全在编译器中实现的擦除方法。
7.注解 Annotation
(1)概述
1)什么是注解
对于过时的语句,java会提示过时了,通过@SuppressWarnings("Deprecation")可以在DOS命令行中取消提示,但Eclipse无法取消。这就是注解,相当于标记。编译器、开发工具、javac通过反射获得注解里的内容,进而明确应该做什么、不应该做什么。注解可以加在包、类、属性、方法、参数及局部变量之上。一个注解就是一个类。
2)java.lang包中的注解
@SuppressWarnings 取消警告,保留到SOURCE阶段
@Deprecated 标识已过时,保留到RUNTIME阶段
@Override 覆盖父类方法,保留到SOURCE阶段
(2)元注解
元注解有2个:Rentention和Target。对注解类的注解,可以理解为注解类的属性。
1)Rentention 注解类
注解的生命周期由Rentention的3种取值决定,Rentention的值是枚举RententionPolicy的值,只有3个:SOURCE、CLASS、RUNTIME。
RententionPolicy.SOURCE对应Java源文件,RententionPolicy.CLASS(默认值)对应class文件、RententionPolicy.RUNTIME对应内存中的字节码。
2)Target注解类
性质和Rentention一样,都是注解类的属性,表示注解类应该在什么位置,对那一块的数据有效。例如,@Target(ElementType.METHOD)。Target内部的值使用枚举ElementType表示,表示的主要位置有:注解、构造方法、属性、局部变量、函数、包、参数和类(默认值)。多个位置使用数组,例如,@Target({ElementType.METHOD,ElementType.TYPE})。
(3)注解属性
属性,给注解提供更加详细的信息。注解相当于接口,属性相当于方法。例如,@ItcastAnnotation(color="black"),给属性赋值,取值时类似调用方法,例如System.out.println(annotation.color()); 所有的属性必须全部出现,除非有缺省值。
1)为属性指定缺省值:
String color() default "yellow";
2)value属性:
String value() default "bbb";
当只设置value属性时,入股其他属性都采用默认值或者只有一个value属性,那么可以省略value=部分,例如:@MyAnnotation("aaa")。
3)数组类型的属性
int[] arr() default {3,7,5};,MyAnnotation(arr={3,7,6}) 如果数组只有1个元素,可以不加{}。@Target({ElementType.METHOD,ElementType.TYPE}) 也是数组类型的属性。
4)枚举类型的属性
EnumTest.TrafficLamp lamp() ;
@MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)
5)注解类型的属性
将一个注解类作为属性加入到另一个注解类中。
MetaAnnotation annotationAttr() default @MetaAnnotation("xxxx");
@MyAnnotation(annotationAttr=@MetaAnnotation(“yyy”) )