反射、枚举和lambda表达式

一、反射

(一)反射的相关概念

反射的定义
Java的反射(reflection)机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到那么,我们就可以修改部分类型信息;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射(reflection)机制

反射的用途
1、在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法 。
2、反射最重要的用途就是开发各种通用框架,比如在spring中,我们将所有的类Bean交给spring容器管理,无论是XML配置Bean还是注解配置,当我们从容器中获取Bean来依赖注入时,容器会读取配置,而配置中给的就是类的信息,spring根据这些信息,需要创建那些Bean,spring就动态的创建这些类

反射的基本信息
Java程序中许多对象在运行时会出现两种类型:运行时类型(RTTI)和编译时类型,例如Person p = newStudent();这句代码中p在编译时类型为Person,运行时类型为Student。程序需要在运行时发现对象和类的真实信息。而通过使用反射程序就能判断出该对象和类属于哪些类。

(二)反射相关的类和方法

类名用途
Class类代表类的实体,在运行的Java应用程序中表示类和接口
Field类代表类的成员变量/类的属性
Method类代表类的方法
Constructor类代表类的构造方法

Java文件被编译后,生成了.class文件,JVM此时就要去解读.class文件,被编译后的Java文件.class也被JVM解析为一个对象,这个对象就是 java.lang.Class .这样当程序在运行时,每个java文件就最终变成了Class类对象的一个实例。我们通过Java的反射机制应用到这个实例,就可以去获得甚至去添加改变这个类的属性和动作,使得这个类成为一个动态的类

常用获得类相关的方法

方法用途
getClassLoader()获得类的加载器
getDeclaredClasses()返回一个数组,数组中包含该类中所有类和接口类的对象(包括私有的)
forName(String className)根据类名返回类的对象
newInstance()创建类的实例
getName()获得类的完整路径名字

常用获得类中属性相关的方法(以下方法返回值为Field相关)

方法用途
getField(String name)获得某个公有的属性对象
getFields()获得所有公有的属性对象
getDeclaredField(String name)获得某个属性对象
getDeclaredFields()获得所有属性对象

获得类中注解相关的方法

方法用途
getAnnotation(Class annotationClass)返回该类中与参数类型匹配的公有注解对象
getAnnotations()返回该类所有的公有注解对象
getDeclaredAnnotation(Class annotationClass)返回该类中与参数类型匹配的所有注解对象
getDeclaredAnnotations()返回该类所有的注解对象

获得类中构造器相关的方法(以下方法返回值为Constructor相关)

方法用途
getConstructor(Class…<?> parameterTypes)获得该类中与参数类型匹配的公有构造方法
getConstructors()获得该类的所有公有构造方法
getDeclaredConstructor(Class…<?> parameterTypes)获得该类中与参数类型匹配的构造方法
getDeclaredConstructors()获得该类所有构造方法

获得类中方法相关的方法(以下方法返回值为Method相关)

方法用途
getMethod(String name, Class…<?> parameterTypes)获得该类某个公有的方法
getMethods()获得该类所有公有的方法
getDeclaredMethod(String name, Class…<?> parameterTypes)获得该类某个方法
getDeclaredMethods()获得该类所有方法

(三)反射实例

class Student{
    //私有属性name
    private String name = "fl";
    //公有属性age
    public int age = 22;
    //不带参数的构造方法
    public Student(){
        System.out.println("Student()");
    }
    private Student(String name,int age) {
        this.name = name;
        this.age = age;
        System.out.println("Student(String, name)");
    }
    private void eat(){
        System.out.println("eating");
    }
    public void sleep(){
        System.out.println("sleeping");
    }
    private void function(String str) {
        System.out.println(str);
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

在这个Student类的外部,无法调用带有参数的私有的构造函数,因此需要用到反射

在反射之前,我们需要做的第一步就是先拿到当前需要反射的类的Class对象,然后通过Class对象的核心方法,达到反射的目的,即:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到那么,我们就可以修改部分类型信息

获取Class对象有三种方式
第一种:使用 Class.forName(“类的全路径名”); 静态方法
前提是已明确类的全路径名

public class TestDemo {
    public static void main(String[] args) {
        try {
            Class<?> c1 = Class.forName("reflectdemo.Student");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

因为forName内部会存在一个异常,需要我们自行处理一下

在这里插入图片描述

第二种方式:使用 .class 方法
仅适合在编译前就已经明确要操作的 Class

public class TestDemo {
    public static void main(String[] args) {
        Class<?> c2 = Student.class;
    }
}

第三种方式:使用类对象的 getClass() 方法

public class TestDemo {
    public static void main(String[] args) {
        Student student = new Student();
        Class<?> c3 = student.getClass();
    }
}

通过这三种方式,我们已经拿到了c1、c2和c3三Class对象,并判断它们是否相等
在这里插入图片描述

结果它们都是相等的。说明不管通过哪种方式来获取反射类的Class对象,此时的这个对象只有一个

通过Class类中的newInstance方法,获取学生的一个实例

public static void reflectNewInstance() {
   try {
       //获取反射类的Class对象
       Class<?> c1 = Class.forName("reflectdemo.Student");
       
       //通过Class类中的newInstance方法(返回T类型,需要类型转换)创建Student类的实例,
       Student student = (Student) c1.newInstance();

       System.out.println(student);
   } catch (InstantiationException e) {
       e.printStackTrace();
   } catch (IllegalAccessException e) {
       e.printStackTrace();
   } catch (ClassNotFoundException e) {
       e.printStackTrace();
   }
}

在这里插入图片描述

这里获取的实例调用的是默认的构造函数,接下来,我们需要通过反射调用私有的构造函数

public static void reflectPrivateConstructor() {
   try {
       //获取反射类的Class对象
       Class<?> c1 = Class.forName("reflectdemo.Student");

       //通过c1中的getDeclaredConstructor方法获得该类中与参数类型匹配的构造方法
       //公有的和私有的构造方法都能拿到
       Constructor<?> constructor =  c1.getDeclaredConstructor(String.class,int.class);

       //因为获取的构造方法还是私有的,所以还需要修改访问权限
       constructor.setAccessible(true);

       //通过已经获取的构造方法constructor来构造实例
       Student student = (Student) constructor.newInstance("feng", 21);

       System.out.println(student);
   } catch (ClassNotFoundException e) {
       e.printStackTrace();
   } catch (NoSuchMethodException e) {
       e.printStackTrace();
   } catch (IllegalAccessException e) {
       e.printStackTrace();
   } catch (InstantiationException e) {
       e.printStackTrace();
   } catch (InvocationTargetException e) {
       e.printStackTrace();
   }
}

在这里插入图片描述

通过反射,成功的调用了私有的构造函数,接下来我们反射私有属性

public static void reflectPrivateField() {
   try {
       //获取反射类的Class对象
       Class<?> c1 = Class.forName("reflectdemo.Student");

       //通过Class对象创建一个反射类实例
       Student student = (Student) c1.newInstance();

       System.out.println("change before:" + student);
       //获取反射类的属性
       Field field =  c1.getDeclaredField("name");

       //修改访问权限
       field.setAccessible(true);

       //私有属性修改
       field.set(student, "lei");

       System.out.println("change after:" + student);
   } catch (ClassNotFoundException e) {
       e.printStackTrace();
   } catch (IllegalAccessException e) {
       e.printStackTrace();
   } catch (InstantiationException e) {
       e.printStackTrace();
   } catch (NoSuchFieldException e) {
       e.printStackTrace();
   }
}

在这里插入图片描述

反射私有属性也能成功,接下来反射私有方法

public static void reflectPrivateMethod() {
   try {
       //获取反射类的Class对象
       Class<?> c1 = Class.forName("reflectdemo.Student");

       //通过Class对象创建一个反射类实例
       Student student = (Student) c1.newInstance();

       //获取反射类的私有方法
       Method method = c1.getDeclaredMethod("function", String.class);

       //修改访问权限
       method.setAccessible(true);

       //调用该方法
       method.invoke(student, "我是私有方法的参数");

   } catch (ClassNotFoundException e) {
       e.printStackTrace();
   } catch (IllegalAccessException e) {
       e.printStackTrace();
   } catch (InstantiationException e) {
       e.printStackTrace();
   } catch (NoSuchMethodException e) {
       e.printStackTrace();
   } catch (InvocationTargetException e) {
       e.printStackTrace();
   }
}

在这里插入图片描述

(四)反射优缺点

优点:

1.对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法
2. 增加程序的灵活性和扩展性,降低耦合性,提高自适应能力
3. 反射已经运用在了很多流行框架如:Struts、Hibernate、Spring 等等

缺点:

1.使用反射会有效率问题。会导致程序效率降低
2.反射技术绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂

二、枚举

枚举的主要用途是将一组常量组织起来,在这之前表示一组常量通常使用定义常量的方式:

public static int final RED = 1;
public static int final GREEN = 2;
public static int final BLACK = 3;

但是常量举例有不好的地方,例如:可能碰巧有个数字1,但是他有可能误会为是RED,现在我们可以直接用枚举来进行组织,这样一来,就拥有了类型,枚举类型,而不是普通的整形1

优点:将常量组织起来统一进行管理
场景:错误状态码,消息类型,颜色的划分,状态机等等…

本质:是 java.lang.Enum 的子类,也就是说,自己写的枚举类,就算没有显示的继承 Enum ,但是其默认继承了这个类

枚举可用于switch语句

public enum TestEnum {
    RED,BLACK,GREEN,WHITE;
    public static void main(String[] args) {
        TestEnum testEnum2 = TestEnum.BLACK;
        switch (testEnum2) {
            case RED:
                System.out.println("red");
                break;
            case BLACK:
                System.out.println("black");
                break;
            case WHITE:
                System.out.println("white");
                break;
            case GREEN:
                System.out.println("green");
                break;
            default:
                break;
        }
    }
}

运行结果:

在这里插入图片描述

(一)枚举类的常用方法

方法名称描述
values()以数组形式返回枚举类型的所有成员
ordinal()获取枚举成员的索引位置
valueOf()将普通字符串转换为枚举实例
compareTo()比较两个枚举成员在定义时的顺序

values()方法
以数组形式返回枚举类型的所有成员

public enum TestEnum {
    RED,BLACK,GREEN,WHITE;
    public static void main(String[] args) {
        TestEnum[] testEnums = TestEnum.values();
        for(int i = 0; i < testEnums.length; ++i) {
            System.out.print(testEnums[i] + " ");
        }
    }
}

在这里插入图片描述

ordinal()方法
获取枚举成员的索引位置

public enum TestEnum {
    RED,BLACK,GREEN,WHITE;
    public static void main(String[] args) {
        TestEnum[] testEnums = TestEnum.values();
        for(int i = 0; i < testEnums.length; ++i) {
            System.out.print(testEnums[i] + "->" + testEnums[i].ordinal());
        }
    }
}

在这里插入图片描述

valueOf()方法
将普通字符串转换为枚举实例,如果没有该枚举成员,则不能转换

public enum TestEnum {
    RED,BLACK,GREEN,WHITE;
    public static void main(String[] args) {
        TestEnum testEnum = TestEnum.valueOf("RED");
        System.out.println(testEnum);
    }
}

在这里插入图片描述

compareTo()方法
比较两个枚举成员在定义时的顺序,默认索引比较

在这里插入图片描述

public enum TestEnum {
    RED,BLACK,GREEN,WHITE;
    public static void main(String[] args) {
        System.out.println(RED.compareTo(BLACK));
        System.out.println(BLACK.compareTo(RED));
    }
}

在这里插入图片描述

看到这里,细心的人可能发现,我们写TestEnum类并没有继承其他的类,也没有写这些方法,那这些方法是哪里来的呢?

在这里插入图片描述

首先,enum声明实际上定义了一个类。它继承了Enum,Enum中有许多方法可供我们使用,因此我们可以通过定义的enum调用其方法。

在这里插入图片描述

java编译器会自动在enum类型中插入一些方法,其中就包括values()——所以我们的程序在没编译的时候,自然没法查看values()方法的源码了。

在enum类中,RED,BLACK,GREEN,WHITE是一个个对象,我们可以为其提供构造函数,去初始化这些对象

public enum TestEnum {
    RED("red", 1),BLACK(),GREEN,WHITE;

    public String color;
    public int ordinal;

    TestEnum(String color, int ordinal) {
        this.color = color;
        this.ordinal = ordinal;
    }

    TestEnum() {

    }
}

枚举构造方法默认是private,不能修改其访问权限。所以这些对象只能在内类使用,在类外拿不到

(二)枚举无法反射

既然枚举的构造方法是私有的,那么是否能通过反射来获取枚举对象的实例呢?

public class TestEnumReflect {
    public static void reflectPrivateConstructor(){
        try {
            //获取反射类的Class对象
            Class<?> classEnum = Class.forName("enumdemo.TestEnum");

            //获取反射类的私有构造方法
            Constructor<?> dl = classEnum.getDeclaredConstructor(String.class,int.class);

            //修改访问权限
            dl.setAccessible(true);

            //调用私有构造方法,创建对象
            TestEnum eenum = (TestEnum) dl.newInstance("粉红色", 6);
            System.out.println("成功反射枚举" + eenum);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        reflectPrivateConstructor();
    }
}

在这里插入图片描述

运行代码结果有异常,通过异常信息可以知道并没有TestEnum(String, int)这个构造方法。但是实际上我们是写了这个构造方法的。

这里为什么会报错呢?
因为我们写的TestEnum类默认是继承Enum抽象类的,而这个抽象类也有一个构造函数

在这里插入图片描述

当我们调用TestEnum的构造函数时,会默认的调用Enum抽象类的构造函数,而我们的TestEnum的构造函数中只有两个参数,这两个参数就用来调用Enum抽象类的构造函数,所以还差两个参数,也就是说TestEnum的构造函数需要有四个参数

修改完毕后,再次运行

在这里插入图片描述

但这次不是因为没有合适的构造函数,而是不能创建反射类型的实例

在这里插入图片描述

点击这里的newInstance,查看源码

在这里插入图片描述

原来是在底层判断了是否是枚举类型,如果是枚举类型,就不让通过反射来获取枚举实例

总的来说,枚举非常安全,不能通过反射来获取到枚举的实例对象

因为枚举不能反射,所以枚举可以用来实现一个线程安全的单例模式

public enum TestEnum {
	INSTANCE;
	public TestEnum getInstance(){
		return INSTANCE;
	}
	public static void main(String[] args) {
		TestEnum singleton1=TestEnum.INSTANCE;
		TestEnum singleton2=TestEnum.INSTANCE;
		System.out.println("两个实例是否相同:"+(singleton1==singleton2));
	}
}

(三)lambda表达式

lambda表达式是语法糖,不仅在java当中有,在其他语言中也有,例如C++
ambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。 Lambda 表达式可以看作是一个匿名函数

(一)函数式接口

函数式接口在Java中是指:有且仅有一个抽象方法的接口

函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是 Lambda,所以函数式接口就是可以适用于Lambda使用的接口

//函数式接口
interface NoParameterNoReturn {
    //定义一个抽象方法
    void test();
}

可以在接口上方,添加注解关键字@FunctionalInterface,让编译器强制去检查该接口是否是函数式接口

在这里插入图片描述

我们可以使用类似于匿名内部类的方式去重写这个接口,并使用

@FunctionalInterface
interface NoParameterNoReturn {
    void test();
}

public class TestDemo {
    public static void main(String[] args) {
        NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn() {
            @Override
            public void test() {
                System.out.println("我是重写的test()");
            }
        };
        noParameterNoReturn.test();
    }
}

在这里插入图片描述

但这样写太繁琐,我们可以使用lambda表达式来简化这种写法

public class TestDemo {
    public static void main(String[] args) {
        NoParameterNoReturn noParameterNoReturn = ()-> System.out.println("我是重写的test()");
        noParameterNoReturn.test();
    }
}

为什么能这样做,接下来就来说一下lambda表达式的语法

(二)lambda表达式语法及示例

基本语法:(parameters) -> expression 或 (parameters) ->{ statements; }

lambda表达式由三部分组成

  1. paramaters:类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明也可不声明而由JVM隐含的推断。另外当只有一个推断类型时可以省略掉圆括号
  2. ->:可理解为“被用于”的意思
  3. 方法体:可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不返回,这里的代码块块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不返回

Lambda表达式本质是一个匿名函数,函数的方法是:返回值 方法名 参数列表 方法体。在,Lambda表达式中我们只需要关心:参数列表 方法体

例1:无返回值无参数

@FunctionalInterface
interface NoParameterNoReturn {
    void test();
}

public class TestDemo {
    public static void main(String[] args) {
        NoParameterNoReturn noParameterNoReturn = ()->{
            System.out.println("我是重写的test()");
        };
        noParameterNoReturn.test();
    }
}

在这里插入图片描述

例2:无返回值一个参数

@FunctionalInterface
interface OneParameterNoReturn {
    void test(int a);
}

public class TestDemo {
    public static void main(String[] args) {
        OneParameterNoReturn oneParameterNoReturn = (a)->{ System.out.println("我重写了test(int a) a:" + a); };

        //可以再继续简化
        //如果只有一个参数,小括号可以省略,只有一条打印语句,花括号也能省略
        OneParameterNoReturn oneParameterNoReturn1 = a->System.out.println("我重写了test(int a) a:" + a);
        oneParameterNoReturn.test(721);
        oneParameterNoReturn1.test(720);
    }
}

在这里插入图片描述

例3:无返回值多个参数

@FunctionalInterface
interface MoreParameterNoReturn {
    void test(int a,int b);
}

public class TestDemo {
    public static void main(String[] args) {
    	//小括号不能省略,因为有两个参数,花括号能省略
        MoreParameterNoReturn moreParameterNoReturn = (a,b)->{
            System.out.println("我重写了test(int a,int b) a+b:" + (a+b));
        };
        moreParameterNoReturn.test(10, 20);
    }
}

在这里插入图片描述

例4:有返回值无参数

@FunctionalInterface
interface NoParameterReturn {
    int test();
}

public class TestDemo {
    public static void main(String[] args) {
        NoParameterReturn noParameterReturn = ()->{ return 10; };
        //继续简写
        NoParameterReturn noParameterReturn1 = ()->20;
    }
}

例5:有返回值一个参数

@FunctionalInterface
interface OneParameterReturn {
    int test(int a);
}

public class TestDemo {
    public static void main(String[] args) {
        OneParameterReturn oneParameterReturn = (a)->{return a;};
        //继续简写
        OneParameterReturn oneParameterNoReturn1 = a->a+11;
    }
}

例6:有返回值多参数

@FunctionalInterface
interface MoreParameterReturn {
    int test(int a,int b);
}

public class TestDemo {
    public static void main(String[] args) {
        MoreParameterReturn moreParameterReturn = (a,b)->{return a+b;};
        //继续简写
        MoreParameterReturn moreParameterReturn1 = (a,b)->a+b;
    }
}

(三)变量捕获

首选看一下匿名内部类的变量捕获

class Test {
    public void func(){
        System.out.println("func()");
    }
}

public class TestDemo {
    public static void main(String[] args) {
        int a = 100;
        new Test(){
            @Override
            public void func() {
                System.out.println("我是内部类,且重写了func这个方法!");
                System.out.println("我是捕获到变量 a == " + a
                        +" 我是一个常量,或者是一个没有改变过值的变量!");
            }
        }.func();
    }
}

上述代码当中的变量a就是,捕获的变量。这个变量要么是被final修饰,如果不是被final修饰的,你要保证在使用之前,没有修改
如下代码就是错误的代码

在这里插入图片描述

lambda表达式中的变量捕获和匿名内部类中的变量捕获是一样的,捕获的变量要么没有被修改过,要么是被final修饰的常量

interface Test {
    void test();
}

public class TestDemo {
    public static void main(String[] args) {
        int a = 10;
        Test t = ()->{ System.out.println(a); };
        t.test();
    }
}

在这里插入图片描述

(四)总结

Lambda表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读

优点:

1.代码简洁,开发迅速
2.方便函数式编程
3.非常容易进行并行计算
4.Java 引入 Lambda,改善了集合操作

缺点:

1.代码可读性变差
2.在非并行计算中,很多计算未必有传统的 for 性能要高
3.不容易进行调试

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值