反射,枚举,lambda表达式

news/2024/5/18 5:21:13 标签: jvm, Lambda, 反射, 枚举

目录

1、反射

1.1 基本概念

1.2 反射相关的类

1.3 创建 Class 对象

1.4 反射的使用

1.4.1 通过反射创建对象:

1.4.2 获取私有的构造方法

1.4.3 获取私有的成员变量

1.4.4 获取私有的方法

1.5 总结

2、枚举

2.1 认识枚举 

2.2 使用枚举

2.3 枚举反射的那些事

Lambda%20%E8%A1%A8%E8%BE%BE%E5%BC%8F-toc" style="margin-left:0px;">3、Lambda 表达式

Lambda%20%E8%A1%A8%E8%BE%BE%E5%BC%8F-toc" style="margin-left:40px;">3.1 认识 Lambda 表达式

3.2 语法

3.3 函数式接口

Lambda%20%E7%9A%84%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8-toc" style="margin-left:40px;">3.4 Lambda 的基本使用


1、反射

1.1 基本概念

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

1.2 反射相关的类

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

每个类里面都有很多相关的方法啊,这里具体的方法我就不一一列举出来了,详细的可以去查看 Java 的官方文档。

1.3 创建 Class 对象

创建一个 Class 对象,通常使用以下三种方法:

1. 调用某个对象里面的 getClass 方法:

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

2. 采取类名.class的方法:

public static void main(String[] args) {
    Class<?> c2 = Student.class; //这种方法说明每个类默认隐式包含一个静态的成员变量 class
}

3. 通过 Class.forName() 获取:

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

注意:这个 forName 中要放传入类的完整路径,比如如果是 String 的话,即:java.lang.String

1.4 反射的使用

这里我们自定义一个 Student 类:

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

    public Student() {
        System.out.println();
    }

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

    public void eat() {
        System.out.println(name + "正在吃饭!");
    }

    private void sleep() {
        System.out.println(name + "正在睡觉!");
    }

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

1.4.1 通过反射创建对象:

public static void reflectClassDemo() {
    try {
        Class<?> c = Class.forName("Student");
        Object objectStudent = c.newInstance();
    } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
        e.printStackTrace();
    }
}

1.4.2 获取私有的构造方法

public static void reflectPrivateConstructor() {
    try {
        Class<?> c = Class.forName("Student");
        Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true); //设置为true后可修改访问权限
        Object objectStudent = constructor.newInstance("张三", 12);
        System.out.println(objectStudent);
    } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) {
        e.printStackTrace();
    }
}

1.4.3 获取私有的成员变量

public static void reflectPrivateField() {
    try {
        Class<?> c = Class.forName("Student");
        Field field = c.getDeclaredField("name"); //获取名为 name 的成员变量
        field.setAccessible(true);
        Object student = c.newInstance();
        field.set(student, "张三"); //将 student 对象的name 设置成 "张三"
        System.out.println(field.getName());
    } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | InstantiationException e) {
        e.printStackTrace();
    }
}

1.4.4 获取私有的方法

public static void reflectPrivateMethod() {
    try {
        Class<?> c = Class.forName("Student");
        Method method = c.getDeclaredMethod("eat");
        method.setAccessible(true);
        //方法有参数的写法:
        //Method methodStudent = classStudent.getDeclaredMethod("function",String.class);
        
        Object objectStudent = c.newInstance();
        Student student = (Student)objectStudent;
        System.out.println("私有方法方法名: " + method.getName());
        method.invoke(objectStudent); // 调用获取到的方法, student对象中的
    } catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) {
        e.printStackTrace();
    }
}

1.5 总结

反射眼中,对应任意一个类,都能够知道这个类的属性和方法,对于任意一个对象,都能调用它任意一个方法, 这样使程序的灵活性大大提高,以及可扩展性,但是这样一来,似乎就在告诉大家,之前封装的一些方法和属性在反射面前就是一个摆设。

反射是一把双刃剑,是一种非常规的编程手段,不到必要的时候,不建议使用反射,使用反射会有效率问题。会导致程序效率降低,而且反射技术绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂 。


2、枚举

2.1 认识枚举 

枚举顾名思义,一一列举,作用将一组常量组织起来,Java中常量就是常量,并没有常变量这种说法,利用我们现在的知识,如果让你定义三个常量,分别表示 红色,黑色,绿色,你可能会这样定义:

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

这样的话,代码中出现的 1,有可能会被误认为是 RED,是可能会出现歧义的,于是就有一种类型枚举来进行组织:

public enum MyEnum {
    RED, BLACK, GREEN;
}

这样里面定义的每个都是枚举类型,不再是普通的数字了,我们自己写的 enum 会默认继承 Enum 类,所以不用显示的去继承 Enum 这个抽象类。

2.2 使用枚举

public static void main(String[] args) {
    MyEnum myEnum = MyEnum.BLACK;
    switch (myEnum) {
        case RED:
            System.out.println("红色!");
            break;
        case BLACK:
            System.out.println("黑色!");
            break;
        case GREEN:
            System.out.println("绿色!");
            break;
        default:
            System.out.println("其他颜色!");
            break;
    }
}

使用场景:错误状态码,消息类型,颜色的划分,状态机等等....

Enum 类的常用方法:

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

枚举是一种类型,也就是Java中的枚举就是一个类,那么就可以这样去写代码:

public enum MyEnum {
    RED("红色", 1), BLACK("黑色", 2), GREEN("绿色", 3);
    private String name;
    private int key;
    private MyEnum(String name, int key) {
        this.name = name;
        this.key = key;
    }
}

枚举的构造方法默认是私有的.

枚举常量是更简单安全的,安全体现在哪,马上就说到了,而且枚举拥有内置方法,使用起来方便,缺点也有,由于Java中支持单继承,因此枚举类型不能再继承其他类,无法扩展。

2.3 枚举反射的那些事

通过反射,能否拿到枚举的私有构造方法呢?我们写个代码来测试一下:

public class TestMyEnum {
    public static void main(String[] args) {
        try {
            Class<?> c = Class.forName("MyEnum");
            Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class);
            constructor.setAccessible(true);
            Object myEnum = constructor.newInstance("红色", 123);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

这里居然报错了,报错信息提示没有该构造方法???为什么会没有呢,我们的 MyEnum 默认继承了 Enum 类,实例化子类对象的时候,先调用父类的构造方法,那么这里我们就去看一下 Enum 的构造方法。

Enum 只有这一个构造方法,还带有两个参数,但是在 JavaSE 的学习中,如果在子类的构造方法中,没有显式写明 super(),则会在子类构造方法第一行默认有 super(),也就是调用父类的无参构造,枚举比较特殊虽然我们写的是两个,但默认他还添加了 name,和 ordinal 参数。也就是说,我们需要提供四个参数:

Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class, String.class, int.class);
constructor.setAccessible(true);
Object myEnum = constructor.newInstance("红色", 123, "红色", 321);

这里还是报错了,但是这里的报错是第10行的,newInstance 方法报错,那么我们就进入该方法源码去一看究竟:

在JavaSE语法上,if 中的 & 会被认为 &&,所以枚举在这里被过滤了,这也就是你不能通过反射获取到枚举类的实例!

所以在这里可以发现,枚举是安全的,可以避免反射的问题。


Lambda%20%E8%A1%A8%E8%BE%BE%E5%BC%8F">3、Lambda 表达式

Lambda%20%E8%A1%A8%E8%BE%BE%E5%BC%8F">3.1 认识 Lambda 表达式

Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码 块)。 Lambda 表达式(Lambda expression),基于数学中的 λ 演算得名,也可称为闭包(Closure)。 

3.2 语法

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

Lambda表达式由三部分组成:

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

3.3 函数式接口

函数式接口:一个接口中,只有一个抽象方法。

@FunctionalInterface 注解:如果我们在某个方法上声明了该注解,那么编译器就会按照函数式接口的定义来要求该接口。如果不符合函数式接口的语法,那么则会报错!

例子:

@FunctionalInterface
public interface TestFuncInterface {
    void work();
}

也可也这样写:

@FunctionalInterface
public interface TestFuncInterface {
    void work();
    default void test() {
        System.out.println("hello");
    }
}

在 JDK 1.8 中,default 默认方法可以有具体的实现。

Lambda%20%E7%9A%84%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8">3.4 Lambda 的基本使用

//无返回值无参数
@FunctionalInterface
interface NoParameterNoReturn {
    void work();
}
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn {
    void work(int a);
}
//无返回值多个参数
@FunctionalInterface
interface MoreParameterNoReturn {
    void work(int a,int b);
}
//有返回值无参数
@FunctionalInterface
interface NoParameterReturn {
    int work();
}

//有返回值一个参数
@FunctionalInterface
interface OneParameterReturn {
    int work(int a);
}
//有返回值多参数
@FunctionalInterface
interface MoreParameterReturn {
    int work(int a,int b);
}

public class TestLambda {
    public static void main(String[] args) {
        NoParameterNoReturn n1 = () -> System.out.println("NoParameterNoReturn");
        n1.work();
        
        // 只有一个参数, 可以省略小括号
        OneParameterNoReturn n2 = x -> {
            System.out.println(x + "OneParameterNoReturn");
            System.out.println("多条语句则不能省略大括号!!!");
        };
        n2.work(5);

        MoreParameterNoReturn n3 = (x, y) -> System.out.println(x + y + "OneParameterNoReturn");
        n3.work(5, 8);

        NoParameterReturn n4 = () -> 88; //只有 return 一条语句可以省略 return
        int ret1 = n4.work();

        OneParameterReturn n5 = (x) -> {
            System.out.println("OneParameterReturn");
            return x + 10; //多条语句时, return 和 {} 都不能省略
        };
        int ret2 = n5.work(12);

        // 如果不省略形参类型, 必须都不省略, 省略的话必须全部省略
        MoreParameterReturn n6 = (int x, int y) -> (x + y + 8);
        int ret3 = n6.work(5, 5);
    }
}

Lambda表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读。至于 Lambda 的更多使用,会在后续文章中慢慢体现出来。这里我们了解下语法即可。

优点:

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

缺点:

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

下期预告:【MySQL】数据库的基本认识 


http://www.niftyadmin.cn/n/61046.html

相关文章

内存溢出、内存泄露的概述及常见情形

内存溢出&#xff08;OutofMemoryError&#xff09; 简述 java doc 中对 Out Of Memory Error 的解释是&#xff0c;没有空闲内存&#xff0c;并且垃圾收集器也无法提供更多内存。 JVM 提供的内存管理机制和自动垃圾回收极大的解放了用户对于内存的管理&#xff0c;由于 GC&…

最长递增子序列问题

最长递增子序列问题 首先我们应该明白一个事情&#xff1a;”子序列“不一定是连续的。但是相对位置不能改变。 动态规划的核心设计思想是数学归纳法。 相信大家对数学归纳法都不陌生&#xff0c;高中就学过&#xff0c;而且思路很简单。比如我们想证明一个数学结论&#xf…

svg.js使用教程

在日常web开发过程中&#xff0c;我们会需要显示一些图形化的元素&#xff0c;使用divcss、ps图片是常见的实现方式。 但使用svg来绘制可能更加合适&#xff0c;SVG是可缩放矢量图形&#xff0c;有一些预定义的形状元素&#xff0c;可被开发者使用和操作&#xff1a; 矩形(rec…

C语言学习笔记(六): 探索函数与变量

函数的定义 形参和实参 在定义函数时函数名后面括号中的变量名称为“形式参数”&#xff08;简称“形参”&#xff09;或“虚拟参数”。 在主调函数中调用一个函数时&#xff0c;函数名后面括号中的参数称为“实际参数”&#xff08;简称“实参”&#xff09;。 当函数被调用…

vue关于eslint的基本使用和关闭

什么是eslint ESLint 是一个代码检查工具&#xff0c;用来检查你的代码是否符合指定的规范&#xff08;例如&#xff1a; 的前后必须有一个空格&#xff09;。 规范 行业推荐的规范&#xff1b; 在创建项目时&#xff0c;我们使用的是 JavaScript Standard Style 代码风格的规…

2023前端面试题——JS篇

1.判断 js 类型的方式 1. typeof 可以判断出’string’,‘number’,‘boolean’,‘undefined’,‘symbol’ 但判断 typeof(null) 时值为 ‘object’; 判断数组和对象时值均为 ‘object’ 2. instanceof 原理是 构造函数的 prototype 属性是否出现在对象的原型链中的任何位置 …

sqlserver连表上下增加操作(SQL UNION 和 UNION ALL 操作符)

SQL UNION 操作符 UNION 操作符用于合并两个或多个 SELECT 语句的结果集。 请注意&#xff0c;UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时&#xff0c;每条 SELECT 语句中的列的顺序必须相同。 SQL UNION 语法 SELECT column_name(s)…

operator的两种用法(重载和隐式类型转换)

文章目录重载隐式类型转换构造函数的隐式类型转换补充operator算子的隐式类型转换重载 略 隐式类型转换 构造函数的隐式类型转换 利用operator进行的隐式类型转换成为operator算子的隐式类型转换&#xff0c;讲这个之前先了解构造函数的隐式类型转换&#xff0c;请看以下代…