Java反射、枚举类和lambda表达式

前言:

        本章我们就来了解Java中的反射和枚举类。枚举类和反射其实有些关系,接下来我们就来学习他们的使用。

反射:

反射的作用:

        反射:反射允许对成员变量,成员方法和构造方法的信息进行编程访问。

        Java中有一个很有趣的知识——反射,它就类似于一个照妖镜,可以让一个类中所有的元素都无所遁形。

        Java的反射(reflection)机制是在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法。

        我们在使用IDEA的时候,总是输入什么以后会提示东西,其实这就是反射的应用。

b590111c45f347cd8d73b2c03270412f.png

        说白了就是从类中拿东西,比如成员变量,成员方法,构造方法等。 

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

获取class对象: 

        通过上面的说明,当我们使用反射时,必须先拿到一个类。

f38b3e3227a24defa305a9ddf2b920f1.png

        此时我们就介绍拿到类的3种方法:

1.在Java中已经定一个了一个类叫做Class,就是用来描述字节码文件的。其中有一个静态方法叫做forName,参数为全类名。

be6cda5e822745248f60d85310ef9a46.png

2.类名.class

3.对象.class

530ed437d3c048c184b2c1b12fd17e82.png

        我们一般使用Class类中forName方法获取类,上面说了要获取全类名,那么什么是全类名呢?

        全类名:全类名是指package + (.)类的名字

        我们其实很容易获取全类名,我们找到要获取的类,之后右击类名,复制参数即可:

0abebe7a181b4e0ea828f3ffd089b975.png

         一定在双引号中复制,否则就是类的名,不是全类名。

bdad898e7d98408e91ea3708f7e26fd2.png

        当我们只知道方法不知道该使用什么类时,可以先写方法并传入参数,之后使用快捷键生成左边: 

        Ctrl + Alt + V : 自动生成左边

        我们说有三种获取类的方法,如果获取的是同一个类,其实获取的都是一样的。

public class MyReflect {
    public static void main(String[] args) throws ClassNotFoundException {
        /*
         * 获取class的三种方式:
         *   1.Class.forName("全类名");
         *   2.类名.class
         *   3.对象.getClass();
         * */

        //1.Class.forName("全类名");
        //全类名 : 包名 + 类名
        Class clazz1 = Class.forName("dome1.Student");//全类名
        //因为里面有受查异常,所以在主方法中加上
        System.out.println(clazz1);

        //2.类名.class
        Class clazz2 = Student.class;
        System.out.println(clazz2);

        System.out.println(clazz1 == clazz2);//获取的都是字节码问价,所以相同

        //3.对象.getClass()
        Student s = new Student();
        Class clazz3 = s.getClass();
        System.out.println(clazz1 == clazz2);
        System.out.println(clazz1 == clazz3);

    }
}
package dome1;

public class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

 

78ea853d3fcb4e62bd29ba6ae19074c1.png

        其中最常用的就是第一种方式,第三种方式局限性最大,必须有当前对象才能使用。

获取构造方法:

        此时就获取了字节码文件了,之后我们就要从中获取构造方法等内容了。

12a5bf493d4446959a63de7d9159021b.png

        想一想,我们既然获取了构造方法,是不是就能通过该构造方法创建对象了呢?我们先来看相关方法:

9de3982b928840bf913eae1ae79853ca.png

package dome1;

public class Student {
    private String name;
    private int age;

    protected Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Student() {
    }

    private Student(String name) {
        this.name = name;
    }

    public Student(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    private void setName(String name) {
        this.name = name;
    }

    protected int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
package dome1;

import java.lang.reflect.Constructor;

public class MyReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        //1.获取class字节码文件对象
        Class clazz = Class.forName("dome1.Student");

        //2.获取构造方法
        Constructor[] cons = clazz.getConstructors();
        for (Constructor con : cons) {
            System.out.println(con);
        }
    }
}

ed63aa372f894957a8d1bb49490962e1.png

        可以看到,我们已经获取了所有的public修饰的构造方法。为了不冗余,我们直接给出图片,我们更改getConstructors方法,并观察区别:

d94b89a3f981456bb34429868903f02d.png

        单独获取可以指定获取构造方法,通过参数来指定。

f974a6744c564abe8933681503a01b9c.png

获取修饰权限付: 

        当我们获取万构造方法以后,我们可以获取这个构造方法的权限修饰符,通过getModifiers方法,返回的是一个整形。

        至于每个权限修饰符,都有对应的整数。由于博主在叙利亚,下面给出战损版:

88673b01832e469fa585a14ad24c644b.png

Constructor con2 = clazz.getDeclaredConstructor(String.class);
//我们单独获取构造方法,往里面添加参数,就可以指定获取那个构造方法
//System.out.println(con2);

int modifiers = con2.getModifiers();
//获取构造方法的权限修饰符,以整数形式体现
System.out.println(modifiers);

9bfc370dca4c4202b9e279246bcd7886.png

        IDEA中可以提示参数,也就是通过反射原理来实现的。 

ce42ec436575498581954beb909c7875.png

        当我们不知道一个方法中有哪些参数是,可以使用:Ctrl + P 来进行提示。

获取方法参数:

        使用getParameter方法来获取参数:

e128985f10764c5ea7e6876e4699fafe.png

Parameter[] parameters = con2.getParameters();
//获取构造方法所有参数
for (Parameter x : parameters) {
    System.out.println(x);
}

c17c42eba12d411486b744612f474c90.png

获取私有方法并实例化对象:

        此时我们已经获取了构造方法,那么我们就可以通过获取的构造方法去实例化一个对象,使用newInstance方法并传入参数去实例化对象。

Constructor con2 = clazz.getDeclaredConstructor(String.class);
//con2.setAccessible(true);//相当于临时取消修饰权限校验

//此时我们已经拿到了构造方法,那么我们就可以通过此构造方法去创建对象
Student stu = (Student) con2.newInstance("张三");
System.out.println(stu);

74dcb27439864915bcb5f90f773d1508.png

        因为报错是private,我们此时也就是看到了该构造方法,但不能直接去构造。我们要加上setAccessible去临时修改权限。

d57c83aad0064b1eac05ce2d5ecec1c8.png

        这就是暴力反射,因为是私有方法,但是通过反射使用了。

获取类中的成员:

c9ea48a641174914bf49fac227dfe74e.png

package dome2;

public class Student {
    private String name;
    private int age;
    public String gender;

    public Student() {
    }

    public Student(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }
}
public class MyReflect {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        //1.获取字节码文件
        Class clazz = Class.forName("dome2.Student");

        //2.获取公共成员变量
        Field[] fields = clazz.getFields();
        for (Field x : fields) {
            System.out.println(x);
        }

        //获取单个成员变量
        Field name = clazz.getDeclaredField("name");
        System.out.println(name);

        //获取成员变量名字
        String n = name.getName();//获取成员变量名
        System.out.println(n);

        //获取成员变量的数据类型
        Class<?> type = name.getType();
        System.out.println(type);

        //获取成员变量记录的值 和对象有关,所以先初始化一个
        Student s = new Student("张三",23,"男");
        name.setAccessible(true);//临时取消访问权限
        String value = (String) name.get(s);//获取该对象name记录的值
        System.out.println(value);

        //修改对象里面记录的值
        name.set(s, "lisi");
        System.out.println(s);
    }
}

e53321f7db4e4574aab8a31b62eb17da.png

获取类中的方法和抛出的异常:

         我们再来看利用反射获取成员方法。54286784d82f4b98b52d2ad98f7f3890.png

package demo3;

import java.io.IOException;

public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age, String gender) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void sleep() {
        System.out.println("睡觉");
    }

    private void eat(String st) throws IOException,NullPointerException,ClassCastException {
        System.out.println("在吃" + st);
    }

    private void eat(String st, int a) {
        System.out.println("在吃" + st);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class MyReflect {
    public static void main(String[] args) throws ClassNotFoundException {
        //1.获取class字节码文件
        Class clazz = Class.forName("demo3.Student");

        //2.获取里面所有的方法对象(包括父类的公共方法)
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
    }
}

ac12a311757b42d9bf7320f602f58fe8.png

        我们使用getDeclareMethods方法,获取的是该类中的所有方法(没有父类中的方法)。

public static void main(String[] args) throws ClassNotFoundException {
    //1.获取class字节码文件
    Class clazz = Class.forName("demo3.Student");

    /*//2.获取里面所有的方法对象(包括父类的公共方法)
    Method[] methods = clazz.getMethods();
    for (Method method : methods) {
        System.out.println(method);
    }*/

    //3.获取里面所有的方法对象(不获取父类,但可以获取本类中所有方法
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
        System.out.println(method);
    }

}

589d34e57d694ea396d96bae03793650.png

        注意这里的getMethods和getDeclaredMethods是不一样的,getMethods是获取所有公共方法(包括父类公共方法);getDeclaredMethods是获取本类所有方法。 

调用获取的方法(invoke方法): 

        一样的,我们获取了方法,也就可以使用该方法,但是因为Java中有方法重载,所以必须传入参数,这样才可以调用指定参数。

        获取方法后,我们也可以使用Class类中的getExceptionTypes方法获取该方法抛出的异常。

        获取方法后, Method中有invoke方法,是调用指定方法,第一个参数是对象,也就是说,我们使用之前要现获取构造方法,之后实例化一个对象,之后将对象传入invoke方法的第一个参数。

public class MyReflect {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //1.获取class字节码文件
        Class clazz = Class.forName("demo3.Student");

        /*//2.获取里面所有的方法对象(包括父类的公共方法)
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }*/

        /*//3.获取里面所有的方法对象(不获取父类,但可以获取本类中所有方法
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method);
        }*/

        //获取指定单一方法
        Method m = clazz.getDeclaredMethod("eat", String.class);
        System.out.println(m);
        //因为有方法重载,所以要指定参数

        //获取方法修饰符
        int modifiers = m.getModifiers();
        System.out.println(modifiers);

        //获取方法名字
        String name = m.getName();
        System.out.println(name);

        //获取方法形参
        Parameter[] parameters = m.getParameters();
        for (Parameter parameter : parameters) {
            System.out.println(parameter);
        }

        //获取方法抛出的异常
        Class[] exceptionTypes = m.getExceptionTypes();
        for (Class exceptionType : exceptionTypes) {
            System.out.println(exceptionType);
        }

        //方法运行
        /*Method 类中用于创建对象
        * Object invoke(Object obj, Object... args): 运行方法
        * 参数一:用obj对象调用的方法
        * 参数二:调用方法传递的参数(如果是void就不写)
        * 返回值:方法的返回值(如果没有就不写)
        * */

        Student s = new Student();
        //参数一s:方法调用者
        //参数二"羊肉":表示调用方法的时候传递的实际参数
        m.setAccessible(true);//记住这个方法是私有的
        m.invoke(s,"羊肉");
        //如果有返回值就接受,可以强制转换

    }
}

50781fb7f45342b1a8492c14586846b6.png

        注意,这里面的Student类还是上面demo3里面的Student类。

枚举:

枚举的作用:

        我们有时候使用的值是不会改变的,像一年四季,这些都是常量,不会发生改变,所以便有了枚举类。枚举是JDK1.5以后引入的。主要是将一组常量组织起来。

自定义枚举类:

        看枚举之前,我们先来看自定义枚举类,此时我们自己定义一个枚举类。

/*
* 1.构造器私有化
* 2.本类内部创建一组对象 四个 春夏秋冬
* 3.对外暴露对象 -> 通过为对象前加 public final static 修饰符
* 4.可以提供 get 方法 但是只读不写(没有set方法)
* */


//自定义枚举类
public class TestEnum {
    private String name;
    private String character;//特点

    //对外暴露
    public static final TestEnum SPRING = new TestEnum("春天","花香");
    public static final TestEnum SUMMER = new TestEnum("夏天","烈日");
    public static final TestEnum AUTUMN = new TestEnum("秋天","气爽");
    public static final TestEnum WINNER = new TestEnum("冬天","大雪");

    //构造器私有化 -> 这样外界就不能直接创建对象 -> 防止直接 new
    private TestEnum() {

    }
    private TestEnum(String name, String character) {
        this.name = name;
        this.character = character;
    }

    //只提供 get 方法 只读不写
    public String getName() {
        return name;
    }

    public String getCharacter() {
        return character;
    }

    @Override
    public String toString() {
        return "TestEnum{" +
                "name='" + name + '\'' +
                ", character='" + character + '\'' +
                '}';
    }
}
public class Test {
    public static void main(String[] args) {
        System.out.println(TestEnum.SPRING);
        System.out.println(TestEnum.SUMMER);
        System.out.println(TestEnum.AUTUMN);
        System.out.println(TestEnum.WINNER);
    }
}

2330f3d7962b40c19faa538ef797076f.png

        定义为静态就是说明不需要实例化对象,而且为了防止其修改使用final。

        枚举类构造方法默认被 private 修饰,写成其他则报错。

使用枚举类(values方法和枚举类的方法):

        我们每次使用IDEA去创建类时,总会有一些选项,其中就有一个enum选项,我们去创建一个。

public enum SEnum {
    SPRING("春天","花香"),
    SUMMER("夏天","烈日"),
    AUTUMN("秋天","气爽"),
    WINTER;//这里WINTER调用无参构造器

    private String name;
    private String character;

    SEnum() { //注意,这里没有 private 修饰,因为被默认修饰了
    }

    SEnum(String name, String character) {
        this.name = name;
        this.character = character;
    }

    public String getName() {
        return name;
    }

    public String getCharacter() {
        return character;
    }

    @Override
    public String toString() {
        return "SEnum{" +
                "name='" + name + '\'' +
                ", character='" + character + '\'' +
                '}';
    }
}

        我们使用枚举类发现能省去很多代码。

        我们不能将枚举类进行继承: 

f0398f5caf7042fa89debb7143c70141.png

        可以发现其报错,因为Java不能多继承,所以我们猜测可能是其可能已经继承了一个类。

        我们进行反编译(在IDEA左边右击该类,找到Open In -> Explorer,之后回退到Out目录,进入以后找到这个类生成的.class文件,并在目录中输入 cmd ,利用 javap 字节码文件名进行反编译):

13f954670f924db786241e92a62de4d7.png

70f7ac3705da476a8e369ebbb0dad64b.png

        当然这里面有些是父类的方法。 

        发现其默认继承Enum并且是被final修饰的。 

        values()方法可以将枚举类转变为一个枚举类型的数组,因为枚举中没有下标,我们没有办法通过下标来快速找到需要的枚举类,这时候,转变为数组之后,我们就可以通过数组的下标,来找到我们需要的枚举类。

        其实枚举在定义的时候就已经创建了对象,当然枚举里面也可以定义成员并且定义方法。

public enum A {
    //注意:枚举类第一行必须罗列的是枚举对象的名字
    X, Y, Z;
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class Test {
    public static void main(String[] args) {
        //认识枚举
        A a1 = A.X;
        System.out.println(a1);

        //1.枚举类的构造方法是私有的,不能对外创建对象
        //A a = new A();

        //2.枚举类的第一行都是常量,记住的是枚举类的对象
        A a2 = A.Y;

        //3.枚举类提供一些额外的API
        A[] as = A.values();
        for (A x : as) {
            System.out.print(x + " ");
        }
        System.out.println();
        A a3 = A.valueOf("Y");
        System.out.println(a3.name());//拿名字
        System.out.println(a3.ordinal());//拿索引
    }
}

cc19d1f7c11c4c5d986548f26a5c9b80.png

注意事项:

        常量定义要放到最开始,构造方法放在其下面,否则报错。

d780ba9d16fd46b2beaf73735146c40a.png

        主方法直接使用枚举类名调用其中成员,因为是静态变量,并且不能初始化其中成员,而且也不能添加。

f286b38e30be45d9a74ae3972a881ed4.png

        枚举类(Enum)中只有一个构造方法:

9806359a5f614689950c37591bc2b563.png

        此时父类中有构造方法,但是我们写的子类没有构造方法,没有调用super。这也就证明了枚举类是一个特殊的类。

        枚举类无法被继承,无法被扩展,也就证明了它很安全。

        我们比较获取的索引:

public static void main(String[] args) {
    System.out.println(SEnum.SPRING);
    SEnum[] sEnum = SEnum.values();
    for (SEnum anEnum : sEnum) {
        System.out.println(anEnum.ordinal());
        //获取枚举成员的索引位置
    }
    System.out.println("====");
    SEnum v1 = SEnum.valueOf("SPRING");
    System.out.println(v1);
    /*SEnum v2 = SEnum.valueOf("eheh");//前提是枚举类型中必须包含该常量
    System.out.println(v2);*/

    //因为 Enum 类实现了 Comparable 接口
    System.out.println(SEnum.SPRING.compareTo(SEnum.SUMMER));//比较

}

e70d234e80db4d6f90c6636a1b80f06b.png

枚举类无法反射: 

        上面我们讲过,反射可以让一个类中的任何东西都无所遁形,那么我们利用反射区获取枚举类中的属性。

public static void main(String[] args) throws ClassNotFoundException,
        NoSuchMethodException, InvocationTargetException,
        InstantiationException, IllegalAccessException {
    Class clazz = Class.forName("demo1.SEnum");
    Constructor con = clazz.getDeclaredConstructor(String.class, String.class);
    con.setAccessible(true);
    SEnum o = (SEnum) con.newInstance("晴天", "开心");
    //此时通过反射新建了一个枚举对象
    System.out.println(o);
}

5dc8cb0f652a43a39719a71467aea17a.png

        因为enum继承于Enum,Enum中的构造方法又有两个形参,所以我们在传入4个参数。所以对以上代码进行以下局部修改:

Constructor con = clazz.getDeclaredConstructor(String.class,int.class,String.class, String.class);
con.setAccessible(true);
SEnum o = (SEnum) con.newInstance("黑夜",9,"晴天", "开心");

279918222c734618ab96b1df6cd9c5d6.png

        此时我们进入源码观察。 

9d82c7a0b69c44e597920decffd04a87.png

        所以证明了枚举对象是非常安全的,是不可以通过反射来创建一个枚举对象的。

抽象枚举:

        其实就是在枚举类中定义了抽象方法,因为枚举对象就定义在枚举类中,所以定义的对象必须实现该抽象方法。

//抽象枚举
public enum B {
    X() {
        @Override
        public void go() {

        }
    }, Y("张三") {
        //调用有参构造器
        @Override
        public void go() {
            System.out.println((getName() + "吃饭"));
            //调用方法
        }
    };
    public abstract void go();
    //这里面定义了一个抽象方法
    //因为枚举类中就定义了对象
    //所以定义时必须实现该抽象方法

    B(String name) {
        this.name = name;
    }
    
    //注意:这里面构造方法都是默认私有的
    B() {
    }

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class Test {
    public static void main(String[] args) {
        B a = B.Y;
        a.go();
    }
}

bd738ba972624ea7aed99fce689a5f78.png 

lambda表达式:

lambda表达式基本语法:

        (parameters)-> expression 或 (Parameters)-> { statements;}

088b43473f474a36ad82cec19c4d1a92.png

        Lambda表达式由三部分组成:

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

        我们举一个相关例子:

//1.不需要参数,返回值为2
() -> 2;

//2.接收一个参数(数字类型),返回其2倍的值
x -> 2 * x;

//3.接收2个参数(数字),并返回他们的和
(x, y) -> x + y;

//4.接收2个int型参数,返回他们的乘积
(int x, int y) -> x * y;

//5.接收一个String对象,并在控制台打印,无返回值
(String s) -> System.out.println(s);

函数式接口:

        要想学好lambda表达式,我们首先要来了解什么是函数式接口。

        函数式接口定义:一个接口有且只有一个抽象方法(在Java1.8中,新增了default修饰的抽象方法也可以定义在函数式接口。)。

//函数式接口
@FunctionalInterface
interface NPNR {
    //只能有有一个方法
    void test();
    default void test2() {
        System.out.println("JDK1.8新特性,default默认方法可以有具体的实现");
    }
}

e5404fb1e19740e9858901a9d6cee39d.png

7236eef6e2ab4e2092f99c7216fee53d.png

lambda表达式和匿名内部类的关系: 

        暂停,接下来有涉及到一个知识,匿名内部类,我们只有了解它之后才能更好的使用lambda表达式。

//函数式接口
@FunctionalInterface
interface NPNR {
    //只能有有一个方法
    void test();
}
public static void main(String[] args) {
    //此时我们先不使用 lambda 表达式
    NPNR npnr1 = new NPNR() {
        @Override
        public void test() {
            System.out.println("这是一个匿名内部类,里面重写了 test() 方法");
        }
        //这个类实现了test并重写,因为是匿名内部类,所以这个类没有名字
    };

    //我们使用 lambda 表达式来进行修改
    NPNR npnr2 = () -> System.out.println("使用 lambda 表达式重写了 test 方法");
    NPNR npnr3 = () -> {
        System.out.println("因为这里有多条语句,所以用大括号");
        System.out.println("前面小括号不能省略");
    };
    npnr1.test();
    npnr2.test();
    npnr3.test();
}

2b87445f1d064284a088cea2ac96b248.png

        关于匿名内部类更加详细的知识可以去看我的这篇文章Java中的内部类-CSDN博客。当然,以下还有详细对比其和lambda表达式的区别,我们也可以继续阅读。

lambda表达式的使用:

        得知了匿名内部类之后,lambda表达式其实就是简化了匿名内部类的写法。接下来我们举一些实际的例子:

//函数式接口
@FunctionalInterface
interface NPNR {
    //只能有有一个方法
    void test();
}

//有一个参数无返回值
interface OPNR {
    void test(int a);
}

//有一个参数一个返回值
interface OPOR {
    int test(int a);
}

interface MPNR {
    void test(int a, int b);
}

interface NPR {
    int test();
}

interface OPR {
    int test(int a);
}

interface MPR {
    int test(int a, int b);
}

public class Test {
    public static void main(String[] args) {
        /*OPNR opnr = a -> {
            System.out.println("这是有一个参数,无返回值的 lambda表达式");
        };*/
        //因为只有一个参数,所以可以把前面的括号省略
        OPNR opnr = (a) -> {
            System.out.println("这是有一个参数,无返回值的 lambda表达式");
        };
        opnr.test(1);

        OPOR opor = a -> {
            System.out.println("有一个参数,一个返回值");
            return 5;
        };
        opor.test(5);

        MPNR mpnr = (a, b) -> {
            System.out.println("两个参数,没有返回值");
            System.out.println(a + b);
        };
        mpnr.test(1, 2);

        NPR npr = () -> {
            System.out.println("没有参数,一个返回值");
            return 10;
        };
        npr.test();

        OPR opr = (a) -> {
            System.out.println("一个参数,一个返回值");
            return 10;
        };
        opr.test(10);

        MPR mpr = (a, b) -> {
            System.out.println("有多个参数,一个返回值");
            return 20;
        };
        mpr.test(10, 20);

    }
}

a4ae8de15173461b9f7dcbae8a27df34.png

        看到这里,我们其实就可以把lambda表达式当做一个匿名内部类。

变量捕获: 

        注意这句话叫做变量捕获,也就是说它是捕获常量的。

@FunctionalInterface
interface Opp {
    void test();
}

public class TestLast {
    public static void main(String[] args) {
        int sz = 100;
        sz = 99;
        Opp opp = () -> {
            System.out.println("调用" + sz);
        };
    }
}

82021520bd3744428e54a90da0c935a0.png

         匿名内部类或者lambda表达式中如果想使用外部的变量,这个变量不能修改,这叫做变量捕获。而且更不能在里面改变外部变量。

lambda表达式和匿名内部类的使用:

        为了方便复习:a388ee68d8ef404aba0220e0ae9332e0.png 

        总体来说,这两者相辅相成。还是否记得我们之前使用PriorityQueue,构建的默认是小根堆,构建大根堆需要我们传入比较器,每次都要去先建一个类,之后传入,有三种实现方法:

创建比较器实现:

class Eg implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        //构建大堆
        return o2.compareTo(o1);
    }
}

public class TestLast {
    public static void main(String[] args) {
        PriorityQueue<Integer> queue = new PriorityQueue<>(new Eg());
        //此时我们自己构建比较器
        queue.offer(1);
        queue.offer(2);
        queue.offer(3);
        System.out.println(queue.peek());
    }
}

da7e831a407240fba7dfe9c1510cc697.png 

使用匿名内部类实现: 

public static void main(String[] args) {
    PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
        //匿名内部类实现大堆
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2.compareTo(o1);
        }
    });

    queue.offer(1);
    queue.offer(2);
    queue.offer(3);
    System.out.println(queue.peek());
}

9033e54e743d4f4dbf61b5707da78a5a.png

使用lambda表达式实现:

        这里有一个大前提,就是接口必须实现匿名内部类:

4d3ff8f6a20946efae50b1ad119c4188.png

public static void main(String[] args) {
    PriorityQueue<Integer> queue = new PriorityQueue<>(
            (o1,o2) -> {return o2.compareTo(o1);}
    );
    queue.offer(1);
    queue.offer(2);
    queue.offer(3);
    System.out.println(queue.peek());
}

232b30e60f6e4493a0f39e394caf09ab.png

forEach方法:

        forEach方法可以理解为遍历,我们观察其源码。

public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    list.add("hello");
    list.add("bit");
    list.add("lambda");
    list.forEach(new Consumer<String>() {
        //还是匿名内部类
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    });
}

36b3fc49dc9d4c0994cbd84541a2a067.png

        使用lambda表达式会更加简洁:

45c3d89ed33e4669b90acbff54e0f5ba.png

        在HashMap中也可以利用forEach方法去获取map中的所有元素: 

public static void main(String[] args) {
    HashMap<Integer, String> map = new HashMap<>();
    map.put(1, "hello");
    map.put(2, "bit");
    map.put(3, "lambda");

    map.forEach(new BiConsumer<Integer, String>() {
        @Override
        public void accept(Integer integer, String s) {
            System.out.println("key: " + integer + " val: " + s);
        }
    });
}

796f5b4b61c14210b69bc328f7b28382.png

87b7a36be28342abb335a7e6fc158a1f.png

        使用lambda表达式改写以后为:

public static void main(String[] args) {
    HashMap<Integer, String> map = new HashMap<>();
    map.put(1, "hello");
    map.put(2, "bit");
    map.put(3, "lambda");

    /*map.forEach(new BiConsumer<Integer, String>() {
        @Override
        public void accept(Integer integer, String s) {
            System.out.println("key: " + integer + " val: " + s);
        }
    });*/
    map.forEach((a, s) -> System.out.println("key: " + a + " val: " + s));
}

总结:

        Java的反射(reflection)机制是在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法。

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

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

        合理利用反射,一定在安全环境下使用。我们应该熟练的使用以上的几个类。

        枚举是一种特殊的类,第一行必须罗列枚举的枚举对象的名名字,之后可以在里面使用构造方法。

        枚举对象无法新建,构造方法是必须而且默认是私有的。

        抽象枚举其实就是在里面定义了抽象方法。

        lambda表达式其实就是匿名内部类的简写。

        这些东西多去使用我们就可以掌握,有更好的见解评论区见。

        

 

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值