[Java基本语法] 反射机制+枚举类+lambda表达式

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343

🏵️热门专栏:

🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482

🍕 Collection与数据结构 (93平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482

🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482

🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482

🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482

🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482

🎃Redis(97平均质量分)https://blog.csdn.net/2301_80050796/category_12777129.html?spm=1001.2014.3001.5482

🐰RabbitMQ(97平均质量分) https://blog.csdn.net/2301_80050796/category_12792900.html?spm=1001.2014.3001.5482

感谢点赞与关注~~~

目录

  • [1. 反射机制](#1. 反射机制)
    • [1.1 定义](#1.1 定义)
    • [1.2 用途](#1.2 用途)
    • [1.3 反射相关的类](#1.3 反射相关的类)
      • [1.3.1 Class类中的常用方法](#1.3.1 Class类中的常用方法)
      • [1.3.2 Constructor类中的常用方法](#1.3.2 Constructor类中的常用方法)
      • [1.3.3 Method类中的常用方法](#1.3.3 Method类中的常用方法)
      • [1.3.4 Field类中的常用方法](#1.3.4 Field类中的常用方法)
      • [1.3.5 反射使用示例](#1.3.5 反射使用示例)
    • [1.4 反射的优点和缺点](#1.4 反射的优点和缺点)
  • [2. 枚举的使用](#2. 枚举的使用)
    • [2.1 概念](#2.1 概念)
    • [2.2 使用](#2.2 使用)
  • [3. lambda表达式](#3. lambda表达式)
    • [3.1 语法](#3.1 语法)
    • [3.2 从函数式接口开始理解lambda表达式](#3.2 从函数式接口开始理解lambda表达式)
    • [3.3 在集合中的应用](#3.3 在集合中的应用)
    • [3.4 总结](#3.4 总结)

1. 反射机制

1.1 定义

Java中的反射机制指的是在运行状态中,对于任意的一个类,我们都可以知道这个类所对应的属性和方法 ,对于任意的一个对象,都可以调用它的任意的方法和属性.既然可以拿到这些信息,那么我们就可以修改这些信息.这种动态获取信息以及动态调用对象方法的功能,我们称为Java语言的反射机制.

1.2 用途

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

1.3 反射相关的类

类名 用途
Class类 代表类的实体
Field类 代表类的成员变量/类的属性
Method类 代表类的方法
Constructor类 代表类的构造方法

1.3.1 Class类中的常用方法

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

下面是Class类的相关方法:

  • 常用的获得类相关的方法(Class类相关方法)
方法 用途
getClassLoader() 获得类的加载器
getDeclaredClasses() 返回一个数组,数组中包含该类中所有类和接口类的对象
static forName(String className) 根据类名返回类的对象
newInstance() 创建类的实例
getName() 获得类的完整路径的名字
  • 常用获得类中的属性相关的方法(Field类相关方法)
方法 用途
getField(String name) 获得某个公有的属性对象
getField() 获得所有公有的属性对象
getDeclaredField(String name) 获得某个属性对象
getDeclaredFields() 获得所有属性对象
  • 获得类中的构造器相关的方法(Constructor相关方法)
方法 用途
getConstructor(Class...<?> parameterTypes) 获得该类中与参数类型匹配的公有构造方法
getConstructors() 获得该类的所有公有构造方法
getDeclaredConstructor(Class...<?> parameterTypes) 获得该类中与参数类型匹配的构造方法
getDeclaredConstructors() 获得该类所有构造方法
  • 获得类中方法相关的方法
方法 用途
getMethod(String name, Class...<?> parameterTypes) 获得该类某个公有的方法
getMethods() 获得该类所有公有的方法
getDeclaredMethod(String name, Class...<?> parameterTypes) 获得该类某个方法
getDeclaredMethods() 获得该类所有方法

1.3.2 Constructor类中的常用方法

要想使用下面的这些方法,必须先要通过上面Class类中Constructor相关的方法获取到Constructor对象.

方法 用途
getModifiers() 得到构造方法的修饰符
getName() 得到构造方法的名称
getParameterTypes() 得到构造方法中参数的类型
newInstance(Object param...) 向Constructor对象对应的构造方法中传入参数,实例化对象
setAccessible(boolean flag) 设置构造方法是否可以被外部访问

1.3.3 Method类中的常用方法

方法 描述
getModifiers() 得到本方法的修饰符
getName() 得到方法的名称
getParameterTypes() 得到方法的全部参数类型
getReturnType() 得到方法的返回值类型
invoke(Object o,Object args...) 调用指定对象的对应方法,并传入参数
setAccessible(boolean flag) 设置一个方法是否可以被外部访问

1.3.4 Field类中的常用方法

方法 描述
getModifiers() 得到属性的修饰符
getName() 得到属性的名称
setAccessible(boolean flag) 设置一个属性是否可以被外部访问
get(Object o) 得到一个对象中属性的具体内容
set(Object o) 设置一个对象中属性的具体内容

1.3.5 反射使用示例

  1. 获得Classes对象的三种方式
    在反射之前,我们需要做的第一步就是先拿到当前需要反射的类的Class对象,然后通过Class对象的核心方法,达到反射的目的,即:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到那么,我们就可以修改部分类型信息。
    第一种,使用Class.forName("类的全路径名"),前提就是以明确类的全路径名.第二种,使用.class方法,仅适合在编译前就已经明确需要操作的Class.第三种,使用类对应实例的getClass()方法.
java 复制代码
class Student {
    private String name = "xiaoming";
    private int age = 20;
    public Student(){
        System.out.println("student()");
    }
    private Student(int age, String name) {
        this.age = age;
        this.name = name;
        System.out.println("student(age,name)");
    }
    private void eat(){
        System.out.println("我在吃饭");
    }
    public void sleep(){
        System.out.println("我在睡觉");
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
    private void function(String str) {
        System.out.println(str);
    }
}
public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Student student = new Student();
        Class<? extends Student> aClass = student.getClass();//通过类所创建的对象来获取到Class对象
        Class<Student> aClass1 = Student.class;//通过.class方法来获取Class对象
        Class<?> student1 = Class.forName("Student");//通过静态方法forName来获取类对象
        System.out.println(aClass);
        System.out.println(aClass1);
        System.out.println(student1);
    }
}

我们发现最后打印的Class对象都是Student.

  1. 反射的使用

我们通过反射拿到Student类中的私有方法和属性之后来进行一系列的操作

java 复制代码
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectClassDemo {
    //创建对象
    public static void reflectNewInstance() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class<?> student = Class.forName("Student");
        Object object = student.newInstance();
        Student student1 = (Student) object;
        System.out.println("获得到了学生对象"+student1);
    }
    //反射私有的构造方法
    public static void reflectPrivateConstructor() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Class<?> student = Class.forName("Student");
        Constructor<?> declaredConstructor = student.getDeclaredConstructor(int.class, String.class);
        declaredConstructor.setAccessible(true);//可以修改访问权限
        Object object = declaredConstructor.newInstance(22, "wangli");//使用修改权限之后的构造器来构造对象
        Student student1 = (Student) object;
        System.out.println("修改构造器访问权限来构造对象:"+student1);
    }
    //反射私有属性
    public static void reflectPrivateField() throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException {
        Class<?> student = Class.forName("Student");
        Field age = student.getDeclaredField("age");//根据Class类对象获取到age属性
        Student student1 = (Student) student.newInstance();//创建一个student对象
        age.setAccessible(true);//修改访问权限
        age.set(student1,21);//设置student1的age为21
        System.out.println("反射属性修改了age "+student1);
    }
    //反射私有方法
    public static void reflectPrivateMethod() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Class<?> student = Class.forName("Student");
        Method function = student.getDeclaredMethod("function", String.class);//获取到类中的function方法
        function.setAccessible(true);//修改访问权限
        Student student1 = (Student) student.newInstance();
        function.invoke(student1,"function参数");//调用student1中的function方法,并传入参数
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, NoSuchFieldException {
        ReflectClassDemo.reflectNewInstance();
        ReflectClassDemo.reflectPrivateConstructor();
        ReflectClassDemo.reflectPrivateField();
        ReflectClassDemo.reflectPrivateMethod();
    }
}

运行结果:

1.4 反射的优点和缺点

优点:

1.** 对于任意一个类,都可以知道这个类的所有属性和方法,对于任意一个对象**,都能够调用它的任意一个方法.

  1. 增加程序的灵活性和扩展性,降低耦合性,提高自适应能力.

  2. 反射已经运用在力很多流行的框架中,比如Spring.

缺点:

  1. 使用反射会有效率问题,会导致程序的效率降低.

  2. 反射技术绕过了源代码的技术,因而会带来维护问题,反射代码比相应的直接代码更加复杂.

2. 枚举的使用

2.1 概念

枚举是在JDK1.5以后引入的,主要的用途就是:将一组常量组织起来.我们在有枚举之前一般使用的是final的形式来定义常量的.

java 复制代码
private static final int RED = 1;
private static final int GREEN = 2;
private static final int BLACK = 3;

现在我们可以直接用枚举的形式来进行组织,这样一来,就拥有了类型,枚举类型,而不是普通的整体的整形1.

java 复制代码
public enum TestEnum {
    RED,BLACK,GREEN
}

优点就是可以将常量统一组织起来进行管理,使用场景一般有错误状态代码,消息类型等.
本质: 是java.lang.Enum的子类,我们自己写的子类,就算没有显式的继承Enum,但是也默认继承了这个类,枚举类本质上就是一个类,类中的每一个对象都相当于是这个枚举类的一个实例,但是这些对象都是写死的.

2.2 使用

  1. 可以直接使用枚举类名+枚举类中的对象来获取到枚举类中的对象.
java 复制代码
public enum TestEnum {
    RED,BLACK,GREEN;

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

运行结果:

  1. Enum类常用方法
方法名称 描述
values() 以数组的形式返回枚举类型的所有成员
static valueOf(String name) 把普通字符串转换为指定枚举类中的枚举实例
ordinal() 获取枚举成员的索引位置

使用示例:

java 复制代码
public enum TestEnum {
    RED,BLACK,GREEN;
    public static void main(String[] args) {
        TestEnum[] testEnums = TestEnum.values();
        for (TestEnum testEnum :testEnums) {
            System.out.println(testEnum + " " + testEnum.ordinal());
        }
        System.out.println(TestEnum.valueOf("BLACK"));
        System.out.println(TestEnum.valueOf("GREEN"));
        System.out.println(TestEnum.valueOf("RED"));
    }
}

运行结果:

我们刚刚说过,在java中枚举其实就是一个类,所以我们在定义枚举类的时候,还可以在枚举类中定义成员变量(一般使用final修饰),构造方法,静态方法,但是枚举类中不可以有成员方法 .我们在枚举一个枚举类实例的时候,需要在枚举实例后面加上括号,括号中用来传入构造方法的参数.

下面是使用示例:

java 复制代码
public enum TestEnum2 {
    RED(0,"red"),GREEN(1,"green"),BLACK(2,"black");
    public final int key;
    public final String name;

    TestEnum2(int key, String name) {
        this.key = key;
        this.name = name;
    }
    public static TestEnum2 getFromKey(int key){
        for (TestEnum2 testEnum2 :TestEnum2.values()) {
            if (testEnum2.key == key){
                return testEnum2;
            }
        }
        return null;
    }
    public static void main(String[] args) {
        System.out.println(TestEnum2.getFromKey(2));
    }
}

3. lambda表达式

lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块).

3.1 语法

(param...)->expression或者(param...)->{statements...}.

Lambda表达式由三部分组成:

  1. param(参数):类似方法中的参数列表,这里的参数类型可以明确的声明也可不声明而由JVM隐含的推断。
  2. ->:可以理解为"被用于"的意思.
  3. 方法体:可以是表达式也可以是代码块,代码块可返回一个值或者什么都不反回,这里的代码块块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不反回。

使用示例:

java 复制代码
// 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 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)

语法还可以做出以下的精简:

  1. 参数类型可以省略,如果需要省略,每个参数的类型都要省略。
  2. 参数的小括号里面只有一个参数,那么小括号可以省略
  3. 如果方法体当中只有一句代码,那么大括号可以省略
  4. 如果方法体中只有一条语句,且是return语句,那么大括号可以省略,且去掉return关键字。

3.2 从函数式接口开始理解lambda表达式

要理解Lambda表达式,首先需要了解什么是函数式接口,函数式接口就是:一个接口中有且只有一个抽象方法 .如果我们在某个接口上声明了@FunctionalInterface注解,那么编译器就会按照函数式接口的定义来要求这个接口,如果有两个抽象方法,程序编译会报错.

java 复制代码
@FunctionalInterface
public interface Functional {
    void test();
}

接下来我们来理解lambda表达式的本质:

首先我们来准备一些函数式接口:

java 复制代码
@FunctionalInterface
public interface  {
    void test();
}
@FunctionalInterface
public interface OneParamNoReturn {
    void test(int a);
}
@FunctionalInterface
public interface OneParamOneReturn {
    int test(int a);
}

lambda表达式可以理解为:lambda就是匿名内部类的简化,实际上是创建了一个类,实现了接口,重写的接口的方法.

如果我们使用匿名内部类的方法来调用接口:

java 复制代码
public class Test {
    NoParamNoReturn noParamNoReturn = new NoParamNoReturn() {
        @Override
        public void test() {
            System.out.println("调用NoParamNoReturn接口");
        }
    };
    OneParamNoReturn oneParamNoReturn = new OneParamNoReturn() {
        @Override
        public void test(int a) {
            System.out.println("调用OneParamNoReturn接口:"+a);
        }
    };
    OneParamOneReturn oneParamOneReturn = new OneParamOneReturn() {
        @Override
        public int test(int a) {
            System.out.println("调用OneParamOneReturn接口:"+a);
            return 0;
        }
    };
}

如果我们改为lambda表达式的形式:

java 复制代码
public class Test {
    NoParamNoReturn noParamNoReturn = () -> System.out.println("调用NoParamNoReturn接口");
    OneParamNoReturn oneParamNoReturn = a -> System.out.println("调用OneParamNoReturn接口:"+a);
    OneParamOneReturn oneParamOneReturn = a -> {
        System.out.println("调用OneParamOneReturn接口:"+a);
        return 0;
    };
}

3.3 在集合中的应用

  1. forEach()方法
    该方法在接口Iterable中,源码如下:
java 复制代码
default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

该方法表示对容器中的每一个元素执行Consumer.accept()中指定的动作.下面是使用forEach遍历List中的元素的代码示例:

java 复制代码
public class Test {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
    }
}

我们接下来把它简化为lambda表达式:

java 复制代码
public class Test {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.forEach(integer -> System.out.println(integer));
    }
}

运行结果:

  1. sort()方法
    sort的源码需要根据传入比较器©来指定元素之间的比较原则.
java 复制代码
default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

使用示例:

java 复制代码
public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("hello");
    list.add("world");
    list.add("lambda");
    //排序的时候比如按照长度排序
    list.sort(new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o1.length()-o2.length();
        }
    });
}

可以用lambda表达式简化为:

java 复制代码
public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("hello");
    list.add("world");
    list.add("lambda");
    //排序的时候比如按照长度排序
    list.sort((o1, o2) -> o1.length()-o2.length());
    System.out.println(list);
}

运行结果:

  1. Map中的foreach()
    源码如下:
    首先把Map转换为可遍历的Set<Map.Entry<K, V>>类型,之后再对其中的元素执行BiConsumer.accept()中的一系列操作.
java 复制代码
default void forEach(BiConsumer<? super K, ? super V> action) {
    Objects.requireNonNull(action);
    for (Map.Entry<K, V> entry : entrySet()) {
        K k;
        V v;
        try {
            k = entry.getKey();
            v = entry.getValue();
        } catch (IllegalStateException ise) {
            // this usually means the entry is no longer in the map.
            throw new ConcurrentModificationException(ise);
        }
        action.accept(k, v);
    }
}

使用示例:

java 复制代码
public static void main(String[] args) {
    Map<Integer,String> map = new HashMap<>();
    map.put(1,"hello");
    map.put(2,"world");
    map.put(3,"lambda");
    map.forEach(new BiConsumer<Integer, String>() {
        @Override
        public void accept(Integer integer, String s) {
            System.out.println(integer+"->"+s);
        }
    });
}

使用lambda表达式简化之后:

java 复制代码
public static void main(String[] args) {
    Map<Integer,String> map = new HashMap<>();
    map.put(1,"hello");
    map.put(2,"world");
    map.put(3,"lambda");
    map.forEach((integer, s) -> System.out.println(integer + "->" + s));
}

运行结果:

3.4 总结

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

优点:

  1. 代码简洁,开发迅速
  2. 方便函数式编程
  3. 非常容易进行并行计算
  4. Java 引入 Lambda,改善了集合操作
    缺点:
  5. 代码可读性变差
  6. 在非并行计算中,很多计算未必有传统的 for 性能要高
  7. 不容易进行调试
相关推荐
egekm_sefg5 分钟前
【微服务】SpringCloud 1-9章
java
计算机-秋大田14 分钟前
基于微信小程序的摄影竞赛系统设计与实现(LW+源码+讲解)
java·前端·后端·微信小程序·小程序·课程设计
等一场春雨1 小时前
Springboot Redisson 分布式锁、缓存、消息队列、布隆过滤器
java·spring boot·分布式
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS欢迪迈手机商城(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
码农研究僧1 小时前
Redis 中 TTL 的基本知识与禁用缓存键的实现策略(Java)
java·redis·缓存·缓存策略
落霞的思绪1 小时前
令牌主动失效机制实现——Redis登录优化
java·redis
苏-言2 小时前
SpringMVC 实战指南:文件上传
java·后端·spring
m0_748250742 小时前
SpringMVC详解
java
nanzhuhe2 小时前
tomcat状态一直是Exited (1)
java·tomcat
m0_748241122 小时前
【Spring】获取Cookie和Session(@CookieValue()和@SessionAttribute())
java·后端·spring