导语: Java语言自诞生起,经历了两次较大的革新:第一次是在2004年,Java5引入了枚举类型、注解和泛型;第二次是在2014年,Java8引入了lambda表达式。本文就重点介绍一下枚举、注解和lambda表达式。
Java5使用关键字enum来表示枚举类型。定义一个枚举很简单,如下所示:
public enum Season{
SPRING,SUMMER,AUTUMN,WINTER;
}
上述代码创建了一个名为Season的枚举类型,它的四个成员分别为:SPRING,SUMMER,AUTUMN,WINTER。枚举类型的实例都是常量,故按照Java命名规则,其成员都应该大写。
Season season=Season.SPRING;
上述代码创建了一个枚举的引用,并将Season中的SPRING赋给该实例。
values()
方法用于返回在枚举中按照声明顺序产生的常量值组成的数组。
ordinal()
方法返回某个枚举常量的索引(从0开始)。
案例:
//values()是枚举的数组(按照枚举中定义的顺序构成的数组)
for(Season s:Season.values()){
//ordinal()是当前枚举成员的下标(索引)
System.out.println(s.ordinal()+":"+s);
}
为了加深对枚举的熟悉程度,这里写一个小练习,用enum来模拟交通信号灯。
代码如下:
public enum TrafficLight {
RED,GREEN,YELLOW;
}
public class TestLight {
private TrafficLight light=TrafficLight.RED;
public void change(){
switch(light){
case RED: //对于枚举,在case中不能写成TrafficLight.RED,只能写RED
this.light=TrafficLight.GREEN;
break;
case GREEN:
this.light= TrafficLight.YELLOW;
break;
case YELLOW:
this.light=TrafficLight.RED;
break;
default:
break;
}
}
@Test
public void test(){
int i;
for(i=0;i<10;++i){
System.out.println(this.light);
change();
}
}
}
注意: 在case中,枚举成员不能写成Season.RED,必须写成RED。
枚举也可以有构造方法,这样在定义枚举的成员变量的时候,就可以用构造方法来进行初始化。
注意:
private
来修饰,否则报错。;
隔开。在enum中定义构造方法和普通方法:
public enum Orientation {
NORTH("北京"),SOUTH("南京"),WEST("西藏"),EAST("上海");
private String city;
private Orientation(String city){ // 枚举中的构造方法必须是private修饰,否则报错
this.city=city;
}
public String getCity() {
return city;
}
}
@Test
public void test2(){
Orientation o1=Orientation.NORTH;
Orientation o2=Orientation.SOUTH;
Orientation o3=Orientation.WEST;
Orientation o4=Orientation.EAST;
System.out.println(o1.getCity());
System.out.println(o2.getCity());
System.out.println(o3.getCity());
System.out.println(o4.getCity());
}
EnumMap是一种特殊的Map,它要求所有的键都必须来自同一个枚举。
请看EnumMap使用的案例:
//EnumMap使用
@SuppressWarnings({"unchecked","unused"})
@Test
public void test3(){
//创建的EnumMap实例的键为Orientation枚举类型,值为String类型,参数为键类型的Class对象
EnumMap enumMap=new EnumMap(Orientation.class);
enumMap.put(Orientation.NORTH,"beijing");
enumMap.put(Orientation.SOUTH,"nanjing");
enumMap.put(Orientation.WEST,"xizang");
enumMap.put(Orientation.EAST,"shanghai");
for(Orientation o:Orientation.values()){
System.out.println(enumMap.get(o));
}
System.out.println();
for(String str:enumMap.values()){
System.out.println(str);
}
}
注解(Annotation,又称元数据),它是在Java5引入的重要概念。注解是写在代码里的特殊标记,它以标准化和结构化的方式,采用能被编译器检查、验证的格式存储有关程序的额外信息。
按照生成方式和功能的不同,Java的注解可以分为三大类:
内置注解就是Java内部已经写好的注解,程序员可以直接在代码中使用。
Java5预定义了3种标准注解,具体如下:
@Override
该注解表示当前方法是重写的父类中的方法,如果方法名写错了或者返回类型等错误,那么编译器就会报错。
例如,下面的代码中:
public class Father {
public void fun1(){
System.out.println("this is father's fun1()");
}
}
public class Son extends Father {
@Override
public void fun2(){ //此处会报错,因为方法fun2()不是重写的父类中的方法,要把fun2()改成fun1()
}
}
@Deprecated
该注解表示某个类或方法已经过时,当使用已经过时的类或方法时,编译器会发出警告(注意: 是警告,不会报错)。过时的意思是该类或方法已经不合时宜,已经不建议使用,已经有新的类或方法取代它们了。
public class Father {
@Deprecated
public void fun2(){
System.out.println("fun2方法已经过时!");
}
public static void main(String[] args) {
Father father=new Father();
father.fun2(); //使用fun2方法,会在fun2上画删除线
}
}
上述代码中,在fun2方法上加了@Deprecated
注解,这表示fun2方法已经过时,不建议使用,所以在使用fun2方法时,会出现删除线的标志,具体截图如下:
SuppressWarnings
该注解用于关闭指定的编译器警告信息。例如:
List list=new ArrayList();
这句代码没有写泛型,那么就会出现警告信息。可以使用@SuppressWarnings(“unchecked”)注解来消除警告信息。
@SuppressWarnings注解里有一个名为value的String类型的数组,该数组用于接收像unchecked这类关键字,故注解@SuppressWarnings()里面参数的完整写法为@SuppressWarnings(value={“xxx”,“xxx”}),也可以简写为@SuppressWarnings({“xxx”,“xxx”})。当只有一个参数时,可以写成@SuppressWarnings(“xxx”)
常用@SuppressWarnings("…")关键字举例
unchecked
消除没有进行类型检查操作的警告。
unused
消除程序元素没有被使用的警告。
自定义注解就是程序员自己定义的注解。
public @interface MyAnnotation{
......}
注意:
下面写一个完整的自定义注解,代码如下:
public @interface MyAnnotation {
public int id();
//name有一个默认值,如果在使用该注解时没有给name赋值,那么就会使用默认值
public String name() default "xurenyi";
}
元注解就是给注解本身进行的注解。Java8在java.lang.Annotation包下提供了6个元注解:
@Target
该注解表示被修饰的注解能用于哪些元素类型。它有一个参数ElementType用于表示适用的元素类型,其值有CONSTRUCTOR(构造函数)、METHOD(方法)、PACKAGE(包)、PRAMETER(参数)、TYPE(类、接口、注解类型、枚举)、FIELD(成员变量)、LOCAL_VARIABLE(局部变量)、ANNOTATION_TYPE(标准注解)。
@Target注解中也有一个名为value的ElementType类型的数组,故参数的写法同@SuppressWarnings注解。
该注解用法如下:
@Target({ElementType.METHOD,ElementType.CONSTRUCTOR,ElementType.PARAMETER,ElementType.TYPE,ElementType.PACKAGE})
public @interface MyAnnotation {
public int id();
//name有一个默认值,如果在使用该注解时没有给name赋值,那么就会使用默认值
public String name() default "xurenyi";
}
@Retention
表示被修饰注解的保存级别。参数RetentionPolicy表示保存级别。RetentionPolicy的取值有:SOURCE(只保留在源代码中,编译时直接丢弃)、 CLASS(保留在class文件中,但运行时jvm不能获取注解信息。)、RUNTIME(保留在class文件中,并且运行时jvm可以获取注解信息)。
该注解的用法如下:
@Retention(RetentionPolicy.RUNTIME) //保存级别
public @interface MyAnnotation {
public int id();
//name有一个默认值,如果在使用该注解时没有给name赋值,那么就会使用默认值
public String name() default "xurenyi";
}
@Documented
指定被修饰的注解将被javadoc或其他类似工具提取成文档。
@Inherited
表示被修饰的注解具有继承性。也就是,加入某个类被@XXX注解修饰了,那么当有子类继承该类时,子类也自动会被@XXX注解修饰。
@Repeatable
这是Java8新增的重复注解。
Type Annotation
类型注解,是Java8新增的元注解,可以用在任何用到类型的地方。
在代码中,我们可以使用反射来读取注解信息,案例如下:
public @interface MyAnnotation {
public int id();
//name有一个默认值,如果在使用该注解时没有给name赋值,那么就会使用默认值
public String name() default "xurenyi";
}
public class TestEnumAnnotation {
public static void main(String[] args) throws NoSuchMethodException {
TestEnumAnnotation ea=new TestEnumAnnotation();
// 获得TestEnumAnnotation的Class类对象
//Class clazz= (Class) ea.getClass();
Class clazz=TestEnumAnnotation.class;
// 获得testMyAnnotation()方法
Method method=clazz.getMethod("testMyAnnotation");
// 如果注解MyAnnotation存在于方法method中
if(method.isAnnotationPresent(MyAnnotation.class)){
// 获取方法中的注解
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
// 获取注解中的变量
int id=annotation.id();
String name=annotation.name();
System.out.println(id+":"+name);
}
}
//测试自定义注解
@MyAnnotation(id=25,name="张三李四王五")
public void testMyAnnotation(){
System.out.println("这是在测试自定义注解。。。。");
}
}
lambda是Java8的新特色,它的写法为:参数列表->lambda表达体 箭头的左边是参数列表,如果没有参数,可以直接写一对括号;箭头右边是lambda表达体,可以理解为lambda的具体实现。
()->23.6
这个lambda表达式会返回23.6,相当于double myVal(){return 23.6;};
这个方法。
当lambda需要参数时,那么就需要在左侧的参数列表中指定。例如,(value)->(value%2)==0
,这个lambda表达式的意思为:当value是偶数时,则返回true,否则返回false。
函数式接口是只有一个抽象方法的接口。例如:
// 函数式接口:只有一个(且只能有一个)抽象方法的接口
@FunctionalInterface //指定该接口是函数式接口
public interface MyVal {
double getVal();
}
接口MyVal就是一个函数式接口,因为它只有一个抽象方法。代码中的@FunctionalInterface
注解用于指定某个接口是函数式接口。
@Test
public void test1(){
// lambda表达式在函数式接口中的应用(1)
MyVal myVal=()->24.6;
System.out.println(myVal.getVal());
}
当把lambda表达式()->24.6
赋给接口myVal引用后,该lambda表达式会自动创建一个实现了接口抽象方法的类的实例。接口中的抽象方法由lambda表达式来实现。
lambda表达式是匿名内部类的一种简化。
匿名内部类,顾名思义,就是没有名字的内部类。接下来,结合一段代码来讲解一下什么是匿名内部类:
public class AnonymousTest {
public void fun1(){
System.out.println("this is anonymous's fun1()");
}
// 匿名内部类介绍
@SuppressWarnings("unused")
@Test
public void test2(){
AnonymousTest anonymousTest=new AnonymousTest(){
@Override
public void fun1(){
System.out.println("this is son's fun1()");
}
public void fun2(){
System.out.println(".........");
}
};
anonymousTest.fun1();
}
}
上述代码中的AnonymousTest anonymousTest=new AnonymousTest(){......}
代码片段就产生了一个匿名内部类。{......}
中就是匿名内部类的具体代码,而anonymousTest就是该匿名内部类的父类。
分别使用匿名内部类和lambda表达式来实现同一个功能(字符串逆序输出)
public class AnonymousTest {
public static void main(String[] args) {
AnonymousTest anonymousTest=new AnonymousTest();
// 使用匿名内部类来实现
anonymousTest.display("qishiyi", new MyStringFunction(){
public String reverse(String str){
int i;
String result="";
for(i=str.length()-1;i>=0;--i){
result=result+str.charAt(i);
}
return result;
}
});
}
public void display(String str,MyStringFunction myStringFunction){
System.out.println(myStringFunction.reverse(str));
}
}
@Test
public void test1(){
AnonymousTest anonymousTest=new AnonymousTest();
// 使用lambda表达式来实现
anonymousTest.display("xurenyi",(str)->{
int i;
String result="";
for(i=str.length()-1;i>=0;--i){
result=result+str.charAt(i);
}
return result;
});
}