【Java基础】泛型+反射+枚举+Lambda表达式 知识点总结

简介: 本文重点介绍Java基础:泛型、反射、枚举、Lambda表达式知识点总结。

 【大家好,我是爱干饭的猿,本文重点介绍Java基础:泛型、反射、枚举、Lambda表达式知识点总结。

后续会继续分享其他重要知识点总结,如果喜欢这篇文章,点个赞👍,关注一下吧】

上一篇文章:《【web】Java虚拟机(JVM)(重点:JVM 执行流程&垃圾回收相关算法)》


🤞目录🤞

💳1. 泛型

1.1 什么是泛型?

1.2 泛型类和泛型方法

1.3 extends类型边界

1.4 类型擦除

1.5 通配符

1. 通配符-上界

2. 通配符-下界

💳2. 反射

2.1 定义

2.2 用途

2.3 反射基本信息

2.4 反射相关的类

1. Class类(反射机制的起源 ) 和 Class类中的相关方法

2. 反射示例(使用)

2.5 反射优点和缺点

💳3. 枚举

3.1 背景及定义

3.2 使用

3.3 枚举优点缺点

3.4 枚举是否可以通过反射,拿到实例对象呢?

💳4. Lambda 表达式

4.1 背景

1. Lambda表达式的语法

2. 函数式接口

4.2 Lambda表达式的基本使用

4.3 Lambda在集合当中的使用

4.4 优点和缺点


💳1. 泛型

1.1 什么是泛型?

作用参数校验,将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参)然后在使用/调用时传入具体的类型(类型实参)。

也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

为什么会增加泛型机制呢?

    • 对于集合的使用更为规范。
    • 使用了泛型就解决了元素不确定性。

    1.2 泛型类和泛型方法

    泛型类:就是一个具有多种类型变量的类。

    泛型方法:就是在调用方法的时候指明泛型的具体类型 。

    一个最普通的泛型类:

    public class Test {
        public static void main(String[] args) {
            IMessage<String> message = new MessageImpl1<String>();
            message.printMsg("123");
            IMessage message1 = new MessageImpl2();
            message1.printMsg(123);
        }
    }
    interface IMessage<T> {
        // 泛型方法
        <T> void printMsg(T t);
    }
    class MessageImpl1<T> implements IMessage<T> {
        @Override
        public <T1> void printMsg(T1 t1) {
            System.out.println(t1);
            System.out.println("是泛型类");
        }
    }
    class MessageImpl2<Integer> implements IMessage<Integer> {
        @Override
        public <T> void printMsg(T t) {
            System.out.println(t);
            System.out.println("是普通类");
        }
    }

    image.gif

    1.3 extends类型边界

    在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。泛型的限定符extends限定符,就是字面意思,限定了他的范围,即缩小泛型的类型范围。

    public class demo1 {
        public static void main(String[] args) {
            MyArray<Number> myArray = new MyArray();
            MyArray<Integer> myArray1 = new MyArray<>();
        }
    }
    class MyArray<T extends Number> {
        // ...
    }

    image.gif

    1.4 类型擦除

    泛型是作用在编译期间的一种机制,实际上运行期间是没有这么多类的,那运行期间是什么类型呢?这里就是类型擦除在做的事情。

    class MyArray<E> {
        // 类型擦除为 Object
    }
    class MyArray2<E extends Number> {
        // 类型擦除为 Number
    }
    class MyArray3<E extends Comparable<E>> {
        // 类型擦除为 Comparable
    }

    image.gif

    类型擦除主要看其类型边界而定。

    编译器在类型擦除阶段在做什么?

    1. 将类型变量用擦除后的类型替换,即 Object 或者 其他

    2. 加入必要的类型转换语句

    3. 加入必要的 bridge method 保证多态的正确性

    1.5 通配符

    ? 用于在泛型的使用,即为通配符

    public class Demo2 {
        public static void printAll(MyArrayList<?> list){}
        public static void main(String[] args) {
            // 可以传入任意类型的 MyArrayList
            printAll(new MyArrayList<Integer>());
            printAll(new MyArrayList<String>());
            printAll(new MyArrayList<Object>());
        }
    }
    class MyArrayList<E> {}

    image.gif

    1. 通配符-上界

    <? extends 上界>

    用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

    public class Demo2 {
        // 可以传入类型实参是 Number 子类的任意类型的 MyArrayList
        public static void printAll(MyArrayList<? extends Number> list){}
        public static void main(String[] args) {
            printAll(new MyArrayList<Integer>());
            printAll(new MyArrayList<Number>());
            printAll(new MyArrayList<Double>());
        }
    }
    class MyArrayList<E> {}

    image.gif

    2. 通配符-下界

    <? super 下界>

    用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object。

    public class Demo2 {
        // 可以传入类型实参是 Integer 父类的任意类型的 MyArrayList
        public static void printAll(MyArrayList<? super Integer> list){}
        public static void main(String[] args) {
            printAll(new MyArrayList<Integer>());
            printAll(new MyArrayList<Number>());
            printAll(new MyArrayList<Object>());
        }
    }
    class MyArrayList<E> {}

    image.gif

    a. 泛型的限制

    1. 泛型类型参数不支持基本数据类型

    2. 无法实例化泛型类型的对象

    3. 无法使用泛型类型声明静态的属性

    4. 无法使用 instanceof 判断带类型参数的泛型类型

    5. 无法创建泛型类数组

    6. 无法 create、catch、throw 一个泛型类异常(异常不支持泛型) 7. 泛型类型不是形参一部分,无法重载

    b. 常用的 T,E,K,V,?

    本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。

    通常情况下:

      • ? 表示不确定的 java 类型
      • T (type) 表示具体的一个java类型
      • K V (key value) 分别代表java键值中的Key Value
      • E (element) 代表Element
      • S, U, V 等等 - 第二、第三、第四个类型

      💳2. 反射

      2.1 定义

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

      2.2 用途

      1、在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应 用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法 。

      2、反射最重要的用途就是开发各种通用框架,比如在spring中,我们将所有的类Bean交给spring容器管理,无论 是XML配置Bean还是注解配置,当我们从容器中获取Bean来依赖注入时,容器会读取配置,而配置中给的就是类 的信息,spring根据这些信息,需要创建那些Bean,spring就动态的创建这些类。

      2.3 反射基本信息

      Java程序中许多对象在运行时会出现两种类型:运行时类型(RTTI)和编译时类型,例如Person p = new Student();这句代码中p在编译时类型为Person,运行时类型为Student。程序需要在运行时发现对象和类的真实 信心。而通过使用反射程序就能判断出该对象和类属于哪些类。

      2.4 反射相关的类

      image.gif编辑

      1. Class类(反射机制的起源 ) 和 Class类中的相关方法

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

      常用获得类相关的方法

      image.gif编辑

      常用获得类中属性相关的方法

      image.gif编辑获得类中注解相关的方法

      image.gif编辑获得类中构造器相关的方法

      image.gif编辑

      获得类中方法相关的方法

      image.gif编辑

      2. 反射示例(使用)

      a. 获得Class对象的三种方式

      在反射之前,我们需要做的第一步就是先拿到当前需要反射的类的Class对象,然后通过Class对象的核心方法,达 到反射的目的,即:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象, 都能够调用它的任意方法和属性,既然能拿到那么,我们就可以修改部分类型信息。

      第一种,使用 Class.forName("类的全路径名"); 静态方法。 前提:已明确类的全路径名。

      第二种,使用 .class 方法。 说明:仅适合在编译前就已经明确要操作的 Class。

      第三种,使用类对象的 getClass() 方法。

      b. 反射的使用

      注意:所有和反射相关的包都在 import java.lang.reflect 包下面。

      Son 类

      public class Son {
          private String name;
          private int age;
          public int aa;
          int bb;
          Son(){
              System.out.println("我是包无参构造");
          }
          private Son(String name) {
              System.out.println("我是私有有参构造 name = " + name);
          }
          public Son(String name, int age){
              System.out.println("我是公有有参构造 name = " + name + "age" +age);
          }
          void method1(){
              System.out.println("我是无参方法");
          }
          private void method2(String dd){
              System.out.println("我是包访问权限有参方法" + dd);
          }
          public void method3(String dd, String cc){
              System.out.println("我是公共有参方法" + dd);
          }
      }

      image.gif

      使用

      public class Test {
          public static void main(String[] args) throws Exception{
              // 得到class
              Class<Son> c1 = Son.class;
              Class<? extends Son> c2 = new Son().getClass();
              Class<?> c3 = Class.forName("Object.reflect.Son");
              System.out.println(c1 == c2);
              System.out.println(c2 == c3);
              // 得到构造方法
              Constructor<?>[] constructors = c1.getConstructors();
              System.out.println(Arrays.toString(constructors));
              Constructor<?>[] declaredConstructors = c1.getDeclaredConstructors();
              System.out.println(Arrays.toString(declaredConstructors));
              // 得到方法
              Method[] methods = c1.getMethods();
              System.out.println(Arrays.toString(methods));
              Method[] declaredMethods = c1.getDeclaredMethods();
              System.out.println(declaredMethods);
              // 得到属性
              Field[] fields = c1.getFields();
              System.out.println(fields);
              Field[] declaredFields = c1.getDeclaredFields();
              System.out.println(declaredFields);
              // 使用反射使用对象私有的构造方法
              Class<?> clSon = Class.forName("Object.reflect.Son");
              Constructor<?> constructor = clSon.getDeclaredConstructor(String.class);
              constructor.setAccessible(true);
              Object hh = constructor.newInstance("hh");
              Son son = (Son) hh;
              System.out.println(son);
              // 使用反射设置对象私有的属性
              Class<Son> sonClass = Son.class;
              Field field = sonClass.getDeclaredField("name");
              field.setAccessible(true);
              Son son1 = sonClass.newInstance();
              field.set(son1, "dd");
              System.out.println(field.get(son1));
              // 使用反射使用对象私有方法
              Class<Son> sonClass1 = Son.class;
              Method method2 = sonClass1.getDeclaredMethod("method2", String.class);
              method2.setAccessible(true);
              Son son2 = sonClass1.newInstance();
              method2.invoke(son2, "李四");
          }
      }

      image.gif

      2.5 反射优点和缺点

      优点:

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

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

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

      缺点:

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

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


      💳3. 枚举

      枚举的使用

      3.1 背景及定义

      枚举是在JDK1.5以后引入的。主要用途是:将一组常量组织起来,在这之前表示一组常量通常使用定义常量的方式。

      优点:将常量组织起来统一进行管理

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

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

      3.2 使用

      1. switch语句

      public enum Enum1 {
          Red, Blue, Pink;
          public static void main(String[] args) {
              Enum1 enum1 = Enum1.Red;
              switch (enum1){
                  case Red:
                      System.out.println("Red"); break;
                  case Blue:
                      System.out.println("Blue"); break;
                  case Pink:
                      System.out.println("Pink"); break;
              }
          }
      }

      image.gif

      2. 常用方法

      Enum 类的常用方法

      image.gif编辑

      public enum Enum2 {
          Red, Blue, Pink;
          public static void main(String[] args) {
              Enum2[] values = Enum2.values();
              // 得到枚举成员及其索引位置
              for (Enum2 value : values) {
                  System.out.println("枚举成员:" + value +" 索引位置:"+value.ordinal());
              }
              // 将普通字符串转换为枚举实例
              System.out.println(Enum2.valueOf("Red"));
              // 得到成员实例
              Enum2 red = Enum2.Red;
              Enum2 blue = Enum2.Blue;
              // compereTo比较两个枚举成员在定义时的顺序
              System.out.println(red.compareTo(blue));
              System.out.println(Red.compareTo(Blue));
              System.out.println(Pink.compareTo(Blue));
          }
      }

      image.gif

      Java当中枚举实际上就是一个类

      public enum Enum3 {
          Red("red",1), Blue("blue",2), Pink("pink", 3);
          private String name;
          private int key;
          private Enum3 (String name,int key) {
              this.name = name;
              this.key = key;
          }
          public static Enum3 getEnumKey (int key) {
              for (Enum3 t: Enum3.values()) {
                  if(t.key == key) {
                      return t;
                  }
              }
              return null;
          }
          public static void main(String[] args) {
              System.out.println(Enum3.getEnumKey(1));
          }
      }

      image.gif

      3.3 枚举优点缺点

      优点:

      1. 枚举常量更简单安全 。

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

      缺点:

      1. 不可继承,无法扩展

      3.4 枚举是否可以通过反射,拿到实例对象呢?

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

      不能。

      没有对应的构造方法,我们所有的枚举类,都是默认继承与 java.lang.Enum,继承了父类除构造函数外的所有东西,并且子类要帮助父类进行构造! 而我们写的类,并没有帮助父类构造!

      枚举被过滤了,所以不能通过反射获取枚举类的实例!这道题是2017年阿里巴巴曾经问到的一个问题。原版问题是:为什么枚举实现单例模式是安全的?

      用枚举实现一个单例模式

      public enum TestEnum {
          INSTANCE;
          public TestEnum getInstance(){
              return INSTANCE;
          }
          public static void main(String[] args) {
              TestEnum singleton1=TestEnum.INSTANCE;
              TestEnum singleton2=TestEnum.INSTANCE;
              System.out.println("两个实例是否相同:"+(singleton1==singleton2));
          }
      }

      image.gif


      💳4. Lambda 表达式

      4.1 背景

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

      1. Lambda表达式的语法

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

      Lambda表达式由三部分组成:

      1. paramaters:类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明 也可不声明而由JVM隐含的推断。另外当只有一个推断类型时可以省略掉圆括号。

      2. ->:可理解为“被用于”的意思

      3. 方法体:可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不反 回,这里的代码块块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不反回。

      2. 函数式接口

      要了解Lambda表达式,首先需要了解什么是函数式接口,函数式接口定义:一个接口有且只有一个抽象方法 。

      注意:

      1. 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口

      2. 如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求 该接口,这样如果有两个抽象方法,程序编译就会报错的。所以,从某种意义上来说,只要你保证你的接口 中只有一个抽象方法,你可以不加这个注解。加上就会自动进行检测的。

      @FunctionalInterface
      interface IMessage{
          void printMessage(String msg);
          default void printOther(){
              System.out.println("接口中的普通方法");
          }
      }

      image.gif

      4.2 Lambda表达式的基本使用

      // 无返回值无参
      interface NonReturnNonParameter{
          void test();
      }
      // 无返回值有参
      interface NonReturnHasParameter{
          void test(String name, int age);
      }
      // 有返回值无参
      interface HasReturnNonParameter{
          String test();
      }
      // 有返回值有参
      interface HasReturnHasParameter{
          String test(String name, int age);
      }
      public class LambdaUsage {
          public static void main(String[] args) {
              NonReturnNonParameter i1 = () -> System.out.println("Lambda 无返回值无参");
              i1.test();
              NonReturnHasParameter i2 = (name, age) -> System.out.println("Lambda 无返回值有参");
              i2.test("张三", 18);
              HasReturnNonParameter i3 = () -> "Lambda 有返回值无参";
              System.out.println(i3.test());
              HasReturnHasParameter i4 = (name, age) -> "Lambda 有返回值有参";
              System.out.println(i4.test("张三", 18));
          }
      }

      image.gif

      4.3 Lambda在集合当中的使用

      为了能够让Lambda和Java的集合类集更好的一起使用,集合当中,也新增了部分接口,以便与Lambda表达式对接。

      image.gif编辑

      4.4 优点和缺点

      优点:

      1. 代码简洁,开发迅速

      2. 方便函数式编程

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

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

      缺点:

      1. 代码可读性变差

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

      3. 不容易进行调试


      分享到此,感谢大家观看!!!

      如果你喜欢这篇文章,请点赞关注吧,或者如果你对文章有什么困惑,可以私信我。

      🏓🏓🏓

      相关文章
      |
      3天前
      |
      Java 程序员 编译器
      Java的反射技术reflect
      Java的反射技术允许程序在运行时动态加载和操作类,基于字节码文件构建中间语言代码,进而生成机器码在JVM上执行,实现了“一次编译,到处运行”。此技术虽需更多运行时间,但广泛应用于Spring框架的持续集成、动态配置及三大特性(IOC、DI、AOP)中,支持企业级应用的迭代升级和灵活配置管理,适用于集群部署与数据同步场景。
      |
      5天前
      |
      存储 Java
      探索Java中的Lambda表达式
      【9月更文挑战第6天】Lambda表达式是Java 8引入的一个强大特性,它允许我们将函数作为参数传递或作为返回值。在这篇文章中,我们将深入探讨Lambda表达式的概念、语法和用法,以及如何在实际项目中应用它们来简化代码。通过学习本文,你将能够更好地理解Lambda表达式的作用,并掌握如何在Java中使用它们。
      |
      2天前
      |
      并行计算 Java 开发者
      探索Java中的Lambda表达式:简化代码,提升效率
      Lambda表达式在Java 8中引入,旨在简化集合操作和并行计算。本文将通过浅显易懂的语言,带你了解Lambda表达式的基本概念、语法结构,并通过实例展示如何在Java项目中应用Lambda表达式来优化代码,提高开发效率。我们将一起探讨这一现代编程工具如何改变我们的Java编码方式,并思考它对程序设计哲学的影响。
      |
      9天前
      |
      安全 前端开发 Java
      浅析JVM invokedynamic指令与Java Lambda语法的深度融合
      在Java的演进历程中,Lambda表达式无疑是Java 8引入的一项革命性特性,它极大地简化了函数式编程在Java中的应用,使得代码更加简洁、易于阅读和维护。而这一切的背后,JVM的invokedynamic指令功不可没。本文将深入探讨invokedynamic指令的工作原理及其与Java Lambda语法的紧密联系,带您领略这一技术背后的奥秘。
      9 1
      |
      11天前
      |
      Java 开发者
      探索Java中的Lambda表达式:简化你的代码
      【8月更文挑战第31天】 在Java 8的发布中,Lambda表达式无疑是最令人兴奋的新特性之一。它不仅为Java开发者提供了一种更加简洁、灵活的编程方式,而且还极大地提高了代码的可读性和开发效率。本文将通过实际代码示例,展示如何利用Lambda表达式优化和重构Java代码,让你的编程之旅更加轻松愉快。
      |
      11天前
      |
      Java 开发者
      探索Java中的Lambda表达式:简化代码的现代方法
      【8月更文挑战第31天】Lambda表达式在Java 8中首次亮相,为Java开发者提供了一种更简洁、灵活的编程方式。它不仅减少了代码量,还提升了代码的可读性和可维护性。本文将通过实际示例,展示Lambda表达式如何简化集合操作和事件处理,同时探讨其对函数式编程范式的支持。
      |
      11天前
      |
      Java
      探索Java中的Lambda表达式
      【8月更文挑战第31天】在Java的世界中,Lambda表达式是一股清新之风,它简化了代码的编写方式,让函数式编程成为可能。本文将带你领略Lambda表达式的魅力,通过实例演示其在Java中的应用,让你的代码更加简洁、高效。
      |
      Java
      QuartZ Cron表达式在java定时框架中的应用
      CronTrigger CronTriggers往往比SimpleTrigger更有用,如果您需要基于日历的概念,而非SimpleTrigger完全指定的时间间隔,复发的发射工作的时间表。 CronTrigger,你可以指定触发的时间表如“每星期五中午”,或“每个工作日9:30时”,甚至“每5分钟一班9:00和10:00逢星期一上午,星期三星期五“。
      1081 0
      |
      8天前
      |
      监控 Java 调度
      【Java学习】多线程&JUC万字超详解
      本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
      64 6
      【Java学习】多线程&JUC万字超详解
      |
      2天前
      |
      Java 调度 开发者
      Java并发编程:深入理解线程池
      在Java的世界中,线程池是提升应用性能、实现高效并发处理的关键工具。本文将深入浅出地介绍线程池的核心概念、工作原理以及如何在实际应用中有效利用线程池来优化资源管理和任务调度。通过本文的学习,读者能够掌握线程池的基本使用技巧,并理解其背后的设计哲学。