【Java数据结构】反射、枚举以及lambda表达式

🔒文章目录:

1.❤️❤️前言~🥳🎉🎉🎉

2.反射 

2.1反射定义

2.2反射主要应用场景 

 2.3Class类(反射机制的起源) 

2.31获取Class类

2.32Class类常用方法

2.33获得类中的成员变量

2.34使用类中的成员方法 

2.35 使用类中的构造方法

2.4反射的优缺点

3.枚举 

3.1枚举的语法

 3.2 枚举常用方法

 3.3枚举的构造方法

3.4 枚举与反射

3.5总结

4.Lambda表达式

4.1什么是Lambda表达式? 

4.2匿名内部类的回顾 

4.3 Lambda表达式进行简化

 4.4Lambda表达式进行简化的要求

4.5Lambda 表达式省略规则

4.6匿名内部类的变量捕获 

 4.7Lambda表达式的优缺点

 5.总结


 1.❤️❤️前言~🥳🎉🎉🎉

Hello, Hello~ 亲爱的朋友们👋👋,这里是E绵绵呀✍️✍️。

如果你喜欢这篇文章,请别吝啬你的点赞❤️❤️和收藏📖📖。如果你对我的内容感兴趣,记得关注我👀👀以便不错过每一篇精彩。

当然,如果在阅读中发现任何问题或疑问,我非常欢迎你在评论区留言指正🗨️🗨️。让我们共同努力,一起进步!

加油,一起CHIN UP!💪💪

🔗个人主页:E绵绵的博客
📚所属专栏:

1. JAVA知识点专栏

        深入探索JAVA的核心概念与技术细节

2.JAVA题目练习

        实战演练,巩固JAVA编程技能

3.c语言知识点专栏

        揭示c语言的底层逻辑与高级特性

4.c语言题目练习

        挑战自我,提升c语言编程能力

📘 持续更新中,敬请期待❤️❤️ 

 

2.反射 

2.1反射定义

Java反射是Java语言的一种特性,它允许程序在运行时自我检查并对内部成员进行操作。这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。具体来说,反射机制允许在运行状态中

对于任意一个类,都能够知道这个类的所有属性和方法;

对于任意一个对象,都能够调用它的任意方法和属性,并且能改变它的属性


Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。其本质是JVM得到Class对象之后,再通过Class对象进行反编译,从而获取对象的各种信息。

2.2反射主要应用场景 

  • 在运行时判断任意一个对象所属的类;
  • 在运行时实例化任意一个类的对象;
  • 在运行时获取任意类的名称、package信息、所有属性、方法、注解、类型、类加载器等;
  • 在运行时获取任意对象的属性,并且能改变对象的属性;
  • 在运行时调用任意对象的方法。

 2.3Class类(反射机制的起源) 

2.31获取Class类

其中有三种方法:

  • Class.forName("类的全路径");
  • 类名.class
  • 对象名.getClass();

而当获取到class类时再用其获取原本的类的对象实例我们就可以用

newInstance() //创建一个类的实例

代码如下:

public class Test {
    public static void main(String[] args) throws Exception{
        //方法1:通过 Class.forName("类的全路径");获取Class
        Class<?> class1 = Class.forName("Student");
        //方法2:通过 类名.class 获取Class
        Class<?> class2 = Student.class;
        //方法3:通过 对象名.getClass(); 获取Class
        Student student = new Student("E棉", "35", 17);
        Class<?> class3 = student.getClass();
        try{
            Student   student1 = (Student) class3.newInstance();
    }catch (Exception e) {}
        
    }

}


class Student{
    private String name;
    private String number;
    public String sex;
    public int age;
    public Student(String name, String number, int age) {
        this.name = name;
        this.number = number;
        this.age = age;
    }
    public Student(String name){
        this.name = name;
    }
    private Student(String name, String number){
        this.name = name;
        this.number = number;
    }
    private void schooling(){
        System.out.println("去上学");
    }
    public String getName() {
        return name;
    }

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

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

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

这里我们出现了三个现象:


1.对于forname其实有编译时异常ClassNotFoundException这个隐患,所以需要用throws将其抛出。

2. newInstans方法的返回值是T,所以被擦除成了Object,要进行强制类型转换

3. newInstans方法会抛出 InstantiationException, IllegalAccessException两个异常,所以需要用try catch将其捕获,因为会出现异常,所以我们很少使用它。一般我们都是用constructor,newinstance(参数)去生成对应的类,这样就不会出现异常,对于该知识点我们在后面会讲到

这里涉及异常知识点,如果不懂可以看我的异常文章,讲的很清楚

异常第一部分

异常第二部分

 

2.32Class类常用方法

调用Class的方法一般会返回三种类型的对象

  • Field对象:存放获取对象的成员变量属性
  • Method对象:存放获取对象的方法
  • Constructor对象:存放获取对象的构造方法

下面是Class类的一些常用方法  

获取成员变量


getFields()   //获取所有公开的成员变量(返回一个数组)

getDeclaredFields() //获取所有本类定义的成员变量,包括私有(返回一个数组)

getField(变量名) //获取指定公共属性的Field对象

 getDeclaredField(变量名) //获取指定包括私有,


获取成员方法


getMethods()  //获取所有公开的方法(返回一个数组)

getMethod(方法名,参数类型列表) //获取指定公开方法的Method对象
getDeclaredMethods()  //获取所有本类定义的的方法,包括私有(返回一个数组)
getDeclaredMethod(方法名,int.class,String.class)  //获取指定包括私有


获取构造方法


getConstructor(参数类型列表)  //获取公开的构造方法 
getConstructors()  //获取所有的公开的构造方法(返回一个数组)
getDeclaredConstructors()  //获取所有的构造方法,包括私有(返回一个数组)
getDeclaredConstructor(int.class,String.class)  //获取指定包括私有

2.33获得类中的成员变量

 这里我们获取一个类中的私有成员变量并对其进行修改。

​
public class Test2 {
    public static void main(String[] args) throws NoSuchFieldException,ClassNotFoundException,IllegalAccessException{
        Class<?> studentClass = Class.forName("Student");
        Field field = studentClass.getDeclaredField("name");
        field.setAccessible(true);
        Student student=new Student("lq","102",18);
        field.set(student, "max");
        System.out.println(student);
    }
}

class Student{
    private String name;
    private String number;
    public String sex;
    public int age;
    public Student(String name, String number, int age) {
        this.name = name;
        this.number = number;
        this.age = age;
    }
    public Student(String name){
        this.name = name;
    }
    private Student(String name, String number){
        this.name = name;
        this.number = number;
    }
    private void schooling(){
        System.out.println("去上学");
    }
    public String getName() {
        return name;
    }

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

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

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

​

 需要注意的是,如果我们需要修改私有变量的话,就要多加一个代码:

field.setAccessible(true);

2.34使用类中的成员方法 

这里我们使用一个类中的私有成员方法。

public class Test3 {
    public static void main(String[] args) throws NoSuchMethodException,InvocationTargetException,
IllegalAccessException,ClassNotFoundException
 {
        Class<?> studentClass = Class.forName("Student");
        Method method = studentClass.getDeclaredMethod("setName", String.class);
        method.setAccessible(true);
        Student student=new Student("lq","102",18);
        method.invoke(student, "max");
        System.out.println(student);
    }
}
class Student{
    private String name;
    private String number;
    public String sex;
    public int age;
    public Student(String name, String number, int age) {
        this.name = name;
        this.number = number;
        this.age = age;
    }
    public Student(String name){
        this.name = name;
    }
    private Student(String name, String number){
        this.name = name;
        this.number = number;
    }
    private void schooling(){
        System.out.println("去上学");
    }
    public String getName() {
        return name;
    }

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

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

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

同理,需要注意的是,如果我们需要使用私有方法的话,就要多加一个代码:

   method.setAccessible(true);

2.35 使用类中的构造方法

这里我们使用一个类中的私有构造方法,跟前面大不相同。

​
public class Test4 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException,IllegalAccessException,ClassNotFoundException,InstantiationException{
        Class<?> studentClass = Class.forName("Student");
        Constructor constructor = studentClass.getDeclaredConstructor(String.class, String.class);
        constructor.setAccessible(true);
        Student student= (Student) constructor.newInstance("lq","102");
        System.out.println(student);
    }
}

​class Student{
    private String name;
    private String number;
    public String sex;
    public int age;
    public Student(String name, String number, int age) {
        this.name = name;
        this.number = number;
        this.age = age;
    }
    public Student(String name){
        this.name = name;
    }
    private Student(String name, String number){
        this.name = name;
        this.number = number;
    }
    private void schooling(){
        System.out.println("去上学");
    }
    public String getName() {
        return name;
    }

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

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

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

 同理私有时依旧需要该方法

 constructor.setAccessible(true);

因为用该构造方法不会抛出异常,只会存在异常的隐患,所以相较于之前的直接用class.newInstance()会抛出异常,我们更倾向于用这个。 

2.4反射的优缺点

优点:

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

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

3. 反射已经运用在了很多流行框架如:Struts、Hibernate、Spring 等等。

缺点:

1. 使用反射会有效率问题。会导致程序效率降低。具体参考 相关文章

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

3.枚举 

3.1枚举的语法

 在 Java 中,可以通过关键字 enum 来定义枚举类型。枚举类型的定义格式如下:

enum EnumName {
    Constant1,
    Constant2,
    Constant3,
    ...
}

其中 EnumName 是枚举类型的名称,Constant1Constant2Constant3 等是枚举类型的常量,这些常量就是枚举类型创建的实例,枚举常量的名称通常采用大写字母命名,多个单词之间用下划线分隔。

对于枚举内部除了以上部分,其他的跟正常类是一样的,都可以有成员方法和成员变量

在 Java 中,可以通过枚举类型的名称来访问枚举常量。例如,假设有一个名为 Weekday 的枚举类型,可以通过如下方式来访问枚举常量:

Weekday monday = Weekday.Monday;


这里的 Weekday.Monday 表示 Weekday 枚举类型中的 Monday 常量,因为该常量为Weekday的实例,所以可以接收

枚举类型可以定义方法,这些方法可以在枚举常量上调用。例如,可以在 Weekday 枚举类型中定义一个 isWeekend 方法来判断当前枚举常量是否为周末:

enum Weekday {
    Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday;
 
    public boolean isWeekend() {
        return this == Saturday || this == Sunday;
    }
}

在上面的例子中,通过在枚举常量后面定义方法,可以在每个枚举常量上调用这个方法。例如,可以通过 Saturday.isWeekend() 来判断 Saturday 是否为周末,因为枚举常量是枚举实例。

枚举类型本身就是一个类,其默认继承java.lang.Enum,也就是说,自己写的枚举类,就算没有显示的继承Enum ,但是其默认继承这个类。

 3.2 枚举常用方法

1. name()
返回枚举常量的名称

Color color = Color.RED;
String name = color.name();
System.out.println(name);// 输出:RED

2.valueOf(String name)
根据枚举常量名称,返回枚举实例

​
Color color = Color.valueOf("RED");
System.out.println(color.name());	// 输出:RED

​

3.values()
返回枚举类所有的枚举常量(数组形式)

Color[] colors = Color.values();
for (Color color : colors) {
  System.out.println(color.name());
}
*/
输出:
RED
GREEN
BLUE
*/

4.ordinal()
返回此枚举常量的序数(其枚举声明中的位置,其中初始常量被分配一个序号零)


Color color = Color.RED;
int ordinal = color.ordinal();
System.out.println(ordinal);//输出:0

5.compareTo()
比较两个枚举值常量序数之差

Color color = Color.RED;
int n = color.compareTo(Color.BLUE) ;//0 减 2
System.out.println(n);           

 3.3枚举的构造方法

枚举类型在Java中可以包含构造方法,用于初始化枚举常量(所以说枚举常量是实例)。枚举的构造方法必须是私有的,因为枚举常量在定义时就被实例化,并且不能在其他地方进行实例化。

这些构造方法只能在枚举常量的定义中被调用,并且只能用来初始化枚举常量的值。当枚举常量有参数后,需要提供相应的构造方法。

enum Weekday {
    Monday("星期一"), Tuesday("星期二"), Wednesday("星期三"), Thursday("星期四"), Friday("星期五"), Saturday("星期六"), Sunday("星期日");
 
    private String value;
 
    private Weekday(String value) {
        this.value = value;
    }
 
    public String getValue() {
        return value;
    }
}

3.4 枚举与反射

我们知道反射对于任何一个类,哪怕其构造方法是私有的,我们也可以通过反射拿到他的实例对象,那么枚举的构造方法也是私有的,我们是否可以拿到呢?

这里我们直接公布答案 


根据Java源代码, newInstance() 方法在创建实例之前会先检查目标类是否是一个枚举类型。如果目标类是枚举类型, newInstance() 方法会抛出 InstantiationException 异常,阻止通过反射去创建枚举实例。

所以说我们无法通过反射去使用枚举的构造方法,也可以知道枚举它是特别安全的。

3.5总结

1、枚举本身就是一个类,其构造方法默认为私有的,且都是默认继承 java.lang.Enum


2、枚举可以避免反射和序列化问题


3、枚举的优点:

 枚举常量更简单安全  

 枚举具有内置方法 ,代码更优雅

4.枚举的缺点:

 不可继承,无法扩展


4.Lambda表达式

4.1什么是Lambda表达式? 

Lambda 表达式是一种匿名函数,它可以在代码中以简洁的方式表示一个功能或行为。Lambda 表达式通常由参数列表、箭头符号和函数体组成。参数列表定义了该函数接受的输入参数,箭头符号表示函数体的开始,函数体则定义了函数的具体实现。Lambda 表达式可以作为一个整体被传递给其他函数或方法,也可以直接被调用。它跟匿名内部类有关。

4.2匿名内部类的回顾 

既然跟匿名内部类相关,那我们先来回顾匿名内部类。 

匿名内部类是一种在Java中定义匿名类的方式。它允许在创建对象的同时定义该对象的类,并且不需要为该类单独命名。通常情况下,我们需要先定义一个类,然后才能创建该类的对象。但是在某些场景下,我们只需要定义一个临时的类来实现某个接口或继承某个类的功能,这时就可以使用匿名内部类。简单的理解就是减少了新类型的出现。

public class Test2 {


    public static void main(String[] args) {


        Animal cat = new Animal(){
            @Override
            public void fun() {
                System.out.println("是只猫");
            }
        };

        Animal dog = new Animal() {
            @Override
            public void fun() {
                System.out.println("是只狗");
            }
        };

        cat.fun();
        dog.fun();

    }
}

interface Animal{
    public void fun();
}

 以上就是匿名内部类的例子,而我们之后要用Lambda表达式对其进行简化。

4.3 Lambda表达式进行简化


​public class Test2 {


    public static void main(String[] args) {

        Animal cat = () -> {
            System.out.println("是只猫");
        };
        Animal dog = () -> {
            System.out.println("是只狗");
        };
        cat.fun();
        dog.fun();
    }
}

interface Animal{
    public void fun();
}

 

所以Lambdab 表达式存在的意义就是简化匿名内部类,具体格式为 (被重写方法的形参列表)-> {(被重写方法的方法体)} ;

 4.4Lambda表达式进行简化的要求

并不是所有的匿名内部类都可以用 Lambda 表达式进行简化的,只有函数式接口才可以被 Lambda 表达式简化。

函数式接口是指只包含一个抽象方法的接口。在Java中,函数式接口是支持函数式编程的基础,它可以用作 Lambda 表达式的目标类型。

Java 8引入了@FunctionalInterface注解,用于标识一个接口为函数式接口。编译器会检查被标注的接口是否符合函数式接口的定义,如果不符合,编译器会报错。

总的来说,如果是接口并且只包含了一个抽象方法,或者引用了 @FunctionalInterface注解的时候都为函数式接口,都可以被 Lambda 表达式简化。

4.5Lambda 表达式省略规则

我们通过以上的讲解,现在知道可以通过 Lambda 表达式进行简化,那么还能不能更进一步简化呢?

答案肯定是可以的,可以继续简化。

        具体来介绍省略的规则:

        1. 参数类型可以省略不写,但名称要一模一样。

        2. 如果只有一个参数的,参数类型可以省略不写,同时()也可以不写。

        3. 如果重写的方法体只有一行代码,可以省略大括号不写,同时要省略分号!此时,如果这行代码是 return 语句,也必须去掉 return 语句。

以下我们给个例子去直观显示:

​
 
public class Test {
    public static void main(String[] args) {
        double[] arr = new double[]{1.0,2.0,9,4.0,5.0};
 
        //第一代的样子
/*        Arrays.setAll(arr, new IntToDoubleFunction() {
            @Override
            public double applyAsDouble(int value) {
                return arr[value] * 0.8;
            }
        });*/
 
        //第二代的样子
/*        Arrays.setAll(arr, (int value) ->{
                return arr[value] * 0.8;
            });*/
 
        //第三代的样子
        Arrays.setAll(arr,value ->arr[value] * 0.8 );
        System.out.println(Arrays.toString(arr));
 
    }
}

​

 所以这就是我们的Lambda表达式省略规则。

4.6匿名内部类的变量捕获 

对于匿名内部类中出现的变量,要么必须用final修饰,要么其是变量但从初始化后从未修改过一次。否则就不能用到匿名内部类中,这就是变量捕获。

下面给个例子。

 以下是正确代码,可以执行

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

 如下代码是错误代码,不能执行,编译错误

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

 4.7Lambda表达式的优缺点

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


优点:

1. 代码简洁,开发迅速

2. 方便函数式编程

3. 非常容易进行并行计算

4. Java 引入 Lambda,改善了集合操作


缺点:

1. 代码可读性变差

2. 在非并行计算中,很多计算未必有传统的 for 性能要高

3. 不容易进行调试

 5.总结

所以这篇文章就这样结束了,下篇文章将带来泛型的进阶,讲述之前没讲过的泛型知识点。

在此,我们诚挚地邀请各位大佬们为我们点赞、关注,并在评论区留下您宝贵的意见与建议。让我们共同学习,共同进步,为知识的海洋增添更多宝贵的财富!🎉🎉🎉❤️❤️💕💕🥳👏👏👏  

 

 

评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值