Java特性之枚举、注解和Lambda表达式

导语: 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。

enum构造方法

枚举也可以有构造方法,这样在定义枚举的成员变量的时候,就可以用构造方法来进行初始化。 注意:

  1. 枚举的构造方法只能用private来修饰,否则报错。
  2. 枚举的常量必须在最前面定义,并以;隔开。

在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的使用

EnumMap是一种特殊的Map,它要求所有的键都必须来自同一个枚举。 请看EnumMap使用的案例:

//EnumMap使用
    @SuppressWarnings({"unchecked","unused"})
    @Test
    public void test3(){
        //创建的EnumMap实例的键为Orientation枚举类型,值为String类型,参数为键类型的Class对象
        EnumMap<Orientation,String> enumMap=new EnumMap<Orientation, String>(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引入的重要概念。注解是写在代码里的特殊标记,它以标准化和结构化的方式,采用能被编译器检查、验证的格式存储有关程序的额外信息。

扫描二维码关注公众号,回复: 7692111 查看本文章

注解的分类

按照生成方式和功能的不同,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()

    }
}
复制代码

报错的截图如下:

@Override注解报错

@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{
		......}
复制代码

注意:

  1. 在自定义注解里写成员变量,要接括号,例如:public int id();
  2. 程序员自己定义一个接口继承Annotation是不会被编译器当做注解的,所以要想自定义注解只能写成@interface xxx{}
  3. @interface xxx,这样写之后编译器会自动继承Annotation接口。

下面写一个完整的自定义注解,代码如下:

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<TestEnumAnnotation> clazz= (Class<TestEnumAnnotation>) ea.getClass();
        Class<TestEnumAnnotation> 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表达式

lambda表达式简介

lambda是Java8的新特色,它的写法为:参数列表->lambda表达体 箭头的左边是参数列表,如果没有参数,可以直接写一对括号;箭头右边是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注解用于指定某个接口是函数式接口。

lambda表达式与函数式接口的应用

@Test
    public void test1(){
        // lambda表达式在函数式接口中的应用(1)
        MyVal myVal=()->24.6;
        System.out.println(myVal.getVal());
    }
复制代码

当把lambda表达式()->24.6赋给接口myVal引用后,该lambda表达式会自动创建一个实现了接口抽象方法的类的实例。接口中的抽象方法由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表达式与匿名内部类的应用

分别使用匿名内部类和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;
        });
    }
复制代码

猜你喜欢

转载自juejin.im/post/5db836a76fb9a0206e1fe733
今日推荐