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

发布于:2024-12-18 ⋅ 阅读:(28) ⋅ 点赞:(0)

🌸个人主页: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 定义

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()方法.
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.
在这里插入图片描述
2. 反射的使用
我们通过反射拿到Student类中的私有方法和属性之后来进行一系列的操作

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.** 对于任意一个类,都可以知道这个类的所有属性和方法,对于任意一个对象**,都能够调用它的任意一个方法.
2. 增加程序的灵活性和扩展性,降低耦合性,提高自适应能力.
3. 反射已经运用在力很多流行的框架中,比如Spring.
缺点:
1. 使用反射会有效率问题,会导致程序的效率降低.
2. 反射技术绕过了源代码的技术,因而会带来维护问题,反射代码比相应的直接代码更加复杂.

2. 枚举的使用

2.1 概念

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

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

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

public enum TestEnum {
    RED,BLACK,GREEN
}

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

2.2 使用

  1. 可以直接使用枚举类名+枚举类中的对象来获取到枚举类中的对象.
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;
        }
    }
}

运行结果:
在这里插入图片描述
2. Enum类常用方法

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

使用示例:

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修饰),构造方法,静态方法,但是枚举类中不可以有成员方法.我们在枚举一个枚举类实例的时候,需要在枚举实例后面加上括号,括号中用来传入构造方法的参数.
下面是使用示例:

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. 方法体:可以是表达式也可以是代码块,代码块可返回一个值或者什么都不反回,这里的代码块块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不反回。

使用示例:

// 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注解,那么编译器就会按照函数式接口的定义来要求这个接口,如果有两个抽象方法,程序编译会报错.

@FunctionalInterface
public interface Functional {
    void test();
}

接下来我们来理解lambda表达式的本质:
首先我们来准备一些函数式接口:

@FunctionalInterface
public interface  {
    void test();
}
@FunctionalInterface
public interface OneParamNoReturn {
    void test(int a);
}
@FunctionalInterface
public interface OneParamOneReturn {
    int test(int a);
}

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

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表达式的形式:

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中,源码如下:
default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

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

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表达式:

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的源码需要根据传入比较器©来指定元素之间的比较原则.
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);
    }
}

使用示例:

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表达式简化为:

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()中的一系列操作.
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);
    }
}

使用示例:

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表达式简化之后:

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. 不容易进行调试