String、反射、枚举、lambda表达式以及泛型进阶(数据结构系列16)

news2024/11/15 8:35:56

目录

前言:

1. String

1.1 字符串常量池

1.1.1 创建对象的思考

1.1.2 字符串常量池(StringTable)

1.1.3 再谈String对象创建

1.1.4 intern方法

2. 反射

2.1 反射的定义

2.2 反射的用途

2.3 反射的基本信息

2.4 反射相关的类

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

2.4.1.1 Class类中的相关方法

2.5 反射实例

2.5.1 获得Class对象的三种方式

2.5.2 反射的使用

2.5.2.1 反射创建对象

2.5.2.2 反射调用私有的构造方法

2.5.2.3 反射获取私有的属性

2.5.2.4 反射获取私有方法

2.5.3 反射的优缺点

3. 枚举

3.1 枚举的使用

3.2 枚举的常用方法

3.3 枚举的优缺点

4. 枚举和反射

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

5. Lambda表达式

5.1 lambda表达式的语法

5.2 函数式接口

5.3 Lambda表达式的基本使用

5.4 变量捕获

5.4.1 匿名内部类

5.4.2 匿名内部类的变量捕获

5.5 Lambda的变量捕获

5.6 Lambda在集合当中的使用

5.6.1 Collection接口

5.6.2 List接口

5.6.3 Map接口

5.7 lambda表达式的优缺点

6. 泛型进阶

6.1 什么是泛型

6.2 引出泛型

6.3 语法

6.4 泛型类的使用

6.4.1 语法

6.4.2 裸类型

6.5 泛型的编译

6.5.1 擦除机制

6.5.2 为什么不能实例化泛型类数组

6.6 泛型的上界

6.7 泛型方法

6.7.1 定义语法

6.8 通配符

6.8.1 通配符解决什么问题

6.8.2 通配符上界

6.8.3 通配符的下界

结束语:


前言:

这节中小编主要与大家分享一下有关于String方面之前没有分享的剩下的知识点,主要了解什么是字符串常量池,创建对象时的一些小知识点,在反射、枚举以及lambda表达式中主要掌握反射以及枚举以及lambda表达式的基本使用,在泛型进阶中我们主要了解通配符的使用,好了话不多说我们来给大家一一讲解吧。

1. String

1.1 字符串常量池

1.1.1 创建对象的思考

我们先来思考一下下面这两种创建String对象的方式是否相同。

代码:

package 再谈String;

public class Test1 {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "hello";
        String s3 = new String("hello");
        String s4 = new String("hello");
        System.out.println(s1 == s2);//true
        System.out.println(s1 == s3);//false
        System.out.println(s3 == s4);//false
    }
}


结果:

在上述的结果中我们可以看到s1和s2引用的是同一个对象,而s3和s4不是同一个对象这是为什么呢?

在Java程序中,类似于:1,2,3,3,3.14,“hello”等字面类型的常量经常频繁使用,为了使程序的运行速度更快更节省内存,Java为8种基本数据类型和String类都提供了常量池。

“池”是编程中常见的一种概念,是重要的提升效率的一种方式,我们会在未来的学习中遇到各种“内存池”、“线程池”、“数据库连接池”......

举个例子,比如:在大学的时候家里给大家大生活费的方式

  1. 家里经济拮据,每个月定时打生活费,有时候可能会晚,最差的情况下可能需要向家里张口要,速度就会比较慢。
  2. 家里有矿,一次性打一年的生活费放到银行卡中,随用随取,效率非常的高,常见的池的技术比如:数据库连接池、线程池等。

为了节省存储空间以及程序的运行效率,java中引入了:

  1. class文件常量池:每个.java源文件编译后生成.class文件中会保存当前类中的字面常量以及符号信息。
  2. 运行时常量池:在.class文件被加载时,.class文件中的常量池被加载到内存中称为运行时常量池,运行时常量池每个类都有一份。
  3. 字符串常量池

1.1.2 字符串常量池(StringTable)

字符串常量池在JVM中是StringTable类,实际是一个固定大小的HashTable(一种高效用来查找的数据结构),不同JDK版本下字符串常量池的位置以及默认大小是不同的:

JDK版本字符串常量池的位置大小设置
Java6(方法区)永久代固定大小:1009
Java7堆中可设置,没有大小限制,默认大小:60013
Java8堆中可设置,有范围限制,最小是1009

关于方法区、堆等内存结果的具体局部,后续JVM中会给大家详细介绍。

1.1.3 再谈String对象创建

由于不同的JDK版本对字符串常量池的处理方式不同,此处在java8 HotSpot上分析。

1.直接使用字符串常量进行赋值

代码:

package 再谈String;

public class Test2 {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "hello";
        System.out.println(s1 == s2);//true
    }
}


结果:

2.通过new创建String类对象

代码:

package 再谈String;

public class Test3 {
    public static void main(String[] args) {
        String s3 = new String("world");
        String s4 = new String("world");
        System.out.println(s3 == s4);//false
    }
}


结果:

下面来简单模拟一下创建对象的过程。

 从上面我们可以看出只要是new出来的对象,都是唯一的。

通过上述的例子可以看出来,使用常量串创建String类型对象的效率更高,而且更节省空间,用户也可以将创建出来的字符串对象通过intern方式添加进字符串常量池中。至于什么是intern方法我们下面来给大家介绍一下。

1.1.4 intern方法

intern是一个native方法(Native方法指:底层使用C++实现的,看不到其实现的源代码),该方法的作用是手动将创建的String对象添加到常量池中。

代码:

package 再谈String;

public class Test4 {
    public static void main(String[] args) {
        char[] ch = new char[]{'a', 'b', 'c'};
        String s1 = new String(ch);//s1对象并不在常量池中
        s1.intern();//s1.intern;调用之后,会将s1对象的引用放入到常量池中
        String s2 = "abc";//"abc"在常量池中存在了,s2创建时直接使用常量池中的"abc"的引用
        System.out.println(s1 == s2);
    }
}


结果:

注意:如果没有调用intern函数的话,那么结果就是false,在Java6和Java7、8中Intern的实现会有些许的差别。


2. 反射

2.1 反射的定义

Java的反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到,那么我们就可以修改部分信息这种动态获取信息以及动态调用对象的方法功能就称之为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 反射相关的类

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

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

Class类代表类的实体,在运行的Java应用程序中表示类和接口

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

2.4.1.1 Class类中的相关方法

  • 常获得类相关的方法:
方法用途
getClassLoader()获得类的加载器
getDeclaredClasses()返回一个数组,数组中包含该类中所有类和接口类的对象(包括私有的)
forName(String className)根据类名返回类的对象
newInstance()

创建类的实例

getName()获得类的完整路径的名字
  • 常用获得类中属性相关的方法:(以下返回值为Field)
方法用途
getField(String name)获得某个公有属性对象
getField()获得所有公有的属性对象
getDeclaredField(String name)获得某个属性对象
getDeclaredFields()获得所有属性对象
  • 获得类中注解相关的方法:
方法用途
getAnnotation(Class annotationClass)返回该类中参与类型匹配的公有注解对象
getAnnotations()返回该类所有公有注解对象
getDeclaredAnnotation(Class annotationClass)返回该类中参与参数类型匹配的所有注解对象
getDeclaredAnnotations()返回该类所哟的注解对象
  • 获得类中构造器相关的方法(以下返回值为Constructor)
方法用途
getConstructor(Class...<?>parameterTypes)获得该类中与参数匹配的公有构造方法
getConstructors()获得该类的所有公有构造方法
getDeclaredConstructor(Class...<?>parameterTypes)获得该类中与参数类型匹配的构造方法
getDeclaredConstructors()获得该类所有构造方法
  • 获得类中方法相关的方法(以下方法返回值为Method)方法
方法用途
getMethod(String name,Class...<?> parameterTypes)获得该类某个公有的方法
getMethods()获得该类所有公有的方法
getDeclaredMethod(String name, Class...<?> parameterTypes)获得该类某个方法
getDeclaredMethods()获得该类所有方法

2.5 反射实例

2.5.1 获得Class对象的三种方式

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

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

前提:已经明确类的全路径名。

第二种:使用.class方法。

说明:仅适合在编译前就已经明确要操作的Class。

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

代码演示:

package 反射;
class Student{
    //私有属性
    private String name = "bit";
    //公有属性
    public int age = 19;
    //不带参数的构造方法
    public Student() {
        System.out.println("Student()");
    }
    private Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Student(String,name)");
    }
    private void eat() {
        System.out.println("i am eat");
    }
    public void sleep() {
        System.out.println("i am pig");
    }
    private void function(String str) {
        System.out.println(str);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test1 {
    public static void main(String[] args) {
        //1.通过getClass获取Class对象
        Student s1 = new Student();
        Class<?> c1 = s1.getClass();

        //2.直接通过类名.class的方式得到,该方法最为安全可靠,程序性能更高
        // 这说明任何一个类都有一个隐含的静态成员变量class
        Class<?> c2 = Student.class;

        //3.通过Class对象的forName()静态方法来获取,用的最多,
        // 但可能会抛出ClassNotFoundException异常
        Class<?> c3 = null;
        try {
            //注意这里是类的全路径,如果有包需要加包的路径
            c3 = Class.forName("反射.Student");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        //一个类只能拥有一个class对象
        //一个类在JVM中只会有一个Class实例,即我们对上面获取的
        // c1,c2,c3进行equals比较,发现都是true
        System.out.println(c1.equals(c2));
        System.out.println(c1.equals(c3));
        System.out.println(c2.equals(c3));
    }
}

结果展示:

注意:
在上述三种获取class对象的方法中我们最常使用的就是第三种方法。通过.forName来进行获取。

2.5.2 反射的使用

接下来我们开始使用反射,我们依旧反射上面的Student类,把反射的逻辑写到另外的类当中进行理解。

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

2.5.2.1 反射创建对象

代码:

package 反射;
//在类外通过反射来创建出一个对象
public class ReflectClassDemo {
    public static void reflectNewInstance() {
        try {
            Class<?> c1 = Class.forName("反射.Student");
            Student student = (Student) c1.newInstance();
            System.out.println("学生对象:" + student);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }

    }
    public static void main(String[] args) {
        reflectNewInstance();
    }
}


结果:

通过反射创建出来一个对象之后然后调用之前类中的不带参数的构造方法。 

2.5.2.2 反射调用私有的构造方法

代码:

package 反射;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

//在类外通过反射来创建出一个对象
public class ReflectClassDemo {
    public static void reflectNewInstance() {
        try {
            Class<?> c1 = Class.forName("反射.Student");
            Student student = (Student) c1.newInstance();
            System.out.println("学生对象:" + student);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }

    }
    public static void reflectPrivateConstructor() {
        try {
            Class<?> c1 = Class.forName("反射.Student");
            Constructor<?> constructor = c1.getDeclaredConstructor(String.class,int.class);
            //相当于是程序的开关,确定是否运行上述代码
            constructor.setAccessible(true);
            Student student = (Student) constructor.newInstance("gaolele",20);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }

    }
    public static void main(String[] args) {
//        reflectNewInstance();
        reflectPrivateConstructor();
    }
}

结果:

2.5.2.3 反射获取私有的属性

代码:

package 反射;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

//在类外通过反射来创建出一个对象
public class ReflectClassDemo {
    public static void reflectNewInstance() {
        try {
            Class<?> c1 = Class.forName("反射.Student");
            Student student = (Student) c1.newInstance();
            System.out.println("学生对象:" + student);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }

    }
    public static void reflectPrivateConstructor() {
        try {
            Class<?> c1 = Class.forName("反射.Student");
            Constructor<?> constructor = c1.getDeclaredConstructor(String.class,int.class);
            //相当于是程序的开关,确定是否运行上述代码
            constructor.setAccessible(true);
            Student student = (Student) constructor.newInstance("gaolele",20);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }

    }
    public static void reflectPrivateField() {
        try {
            Class<?> c1 = Class.forName("反射.Student");
            Field field = c1.getDeclaredField("name");
            field.setAccessible(true);
            //获取一个对象
            Student student = (Student) c1.newInstance();
            field.set(student,"唐老鸭");
            System.out.println(student);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
    public static void main(String[] args) {
//        reflectNewInstance();
//        reflectPrivateConstructor();
        reflectPrivateField();
    }
}


结果:

2.5.2.4 反射获取私有方法

代码:

package 反射;

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() {
        try {
            Class<?> c1 = Class.forName("反射.Student");
            Student student = (Student) c1.newInstance();
            System.out.println("学生对象:" + student);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }

    }
    //通过反射调用私有的构造方法
    public static void reflectPrivateConstructor() {
        try {
            Class<?> c1 = Class.forName("反射.Student");
            Constructor<?> constructor = c1.getDeclaredConstructor(String.class,int.class);
            //相当于是程序的开关,确定是否运行上述代码
            constructor.setAccessible(true);
            Student student = (Student) constructor.newInstance("gaolele",20);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }

    }
    //通过反射获取私有属性
    public static void reflectPrivateField() {
        try {
            Class<?> c1 = Class.forName("反射.Student");
            Field field = c1.getDeclaredField("name");
            field.setAccessible(true);
            //获取一个对象
            Student student = (Student) c1.newInstance();
            field.set(student,"唐老鸭");
            System.out.println(student);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
    //通过反射调用私有方法
    public static void reflectPrivateMethod() {
        try {
            Class<?> c1 = Class.forName("反射.Student");
            Method method = c1.getDeclaredMethod("function", String.class);
            method.setAccessible(true);
            //获取一个对象
            Student student = (Student) c1.newInstance();
            method.invoke(student,"我是一个参数!");
            System.out.println(student);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
    public static void main(String[] args) {
//        reflectNewInstance();
//        reflectPrivateConstructor();
//        reflectPrivateField();
        reflectPrivateMethod();
    }
}


结果:

2.5.3 反射的优缺点

优点:

  • 对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法。
  • 增加程序的灵活性和扩展性,降低耦合性,提高子树自适能力。
  • 反射已经运用在了很多流行框架中,如:Struts、Hibernate、Spring等等。

缺点:

  • 使用反射会有效率问题,会导致程序效率降低。
  • 反射技术绕过了源代码的技术,因而会带来维护问题,反射代码比相应的直接更复杂。

3. 枚举

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

public static final int RED = 1;

public static final int GREEN = 2;

public static final int BLACK = 3;

但是常量举例有不好的地方,例如:可能碰巧有个数字1,但是他有可能误会为是RED,现在我们可以直接用枚举来进行组织,这样一来,就拥有了类型,枚举类型。而不是普通的整形1。

public enum TestEnum{

        RED,BLACK,GREEN;

}

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

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

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

3.1 枚举的使用

代码实例:

package 枚举;

public class Test1 {
    public enum TestEnum{
        RED,BLACK,WHITE,GREED;
    }
    public static void main(String[] args) {
        TestEnum testEnum = TestEnum.BLACK;
        switch (testEnum) {
            case RED:
                System.out.println("red");
                break;
            case BLACK:
                System.out.println("black");
                break;
            case WHITE:
                System.out.println("white");
                break;
            case GREED:
                System.out.println("greed");
                break;
            default:
                break;
        }
    }
}


结果展示:

3.2 枚举的常用方法

Enum类的常用方法

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

代码1:

package 枚举;

public class Test2 {
    public enum TestEnum{
        RED,BLACK,GREEN,WHITE;
    }
    public static void main(String[] args) {
        //以数组的形式返回枚举类型的所有实例
        TestEnum[] testEnums = TestEnum.values();
        //打印枚举成员以及获取枚举成员的索引位置
        for (int i = 0; i < testEnums.length; i++) {
            System.out.println(testEnums[i] + " " + testEnums[i].ordinal());
        }
        System.out.println("======================");
        //将普通字符串转换为枚举实例
        System.out.println(TestEnum.valueOf("GREEN"));
    }
}

 
结果1:

代码2:

package 枚举;

import static 枚举.Test3.TestEnum.BLACK;
import static 枚举.Test3.TestEnum.RED;

public class Test3 {
    public enum TestEnum{
        RED,BLACK,GREEN,WHITE;
    }
    public static void main(String[] args) {
        //拿到枚举实例BLACK
        TestEnum testEnum = BLACK;
        //拿到枚举实例RED
        TestEnum testEnum1 =  RED;
        System.out.println(testEnum.compareTo(testEnum1));
        System.out.println(BLACK.compareTo(RED));
        System.out.println(RED.compareTo(BLACK));
    }
}


结果2:

刚刚说过,在java当中枚举实际上就是一个类,所以我们在定义枚举的时候,还可以这样定义和使用枚举:

注意:枚举的构造方法默认是私有的

代码:

package 枚举;

import javax.swing.text.html.parser.TagElement;

public class Test4 {
    public enum TestEnum{
        RED("red",1),BLACK("black",2),WHITE("white",3),GREEN("green",4);
        private String name;
        private int key;
        /**
         * 1.当枚举对象有参数后,需要提供相应的构造函数
         * 2.枚举的构造函数默认是私有的,这个一定要记住
         */
        private TestEnum(String name, int age) {
            this.name = name;
            this.key = age;
        }
        //获取枚举类中的索引值所对应的枚举成员
        public static TestEnum getEnumKey(int key){
            for (TestEnum t : TestEnum.values()) {
                if (t.key == key) {
                    return t;
                }
            }
            return null;
        }
        public static void main(String[] args) {
            System.out.println(getEnumKey(2));
        }
    }
}


结果:

3.3 枚举的优缺点

优点:

  • 枚举常量更简单安全。
  • 枚举具有内置方法,代码更优雅。

缺点:

  • 不可以继承,无法扩展。

4. 枚举和反射

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

我们刚刚在反射里边看到了,任何一个类,哪怕其构造方法是私有的,我们也可以通过反射拿到它的实例对象,那么枚举的构造方法也是私有的,我们是否可以拿到呢?接下来小编就带着大家一起来探究一下吧。

同样我们使用上面提供的枚举类来进行举例。

代码:

package 反射与枚举;

import java.lang.reflect.Constructor;

public enum TestEnum {
    RED("red", 1), BLACK("black", 2), WHITE("white", 3), GREEN("green", 4);
    private String name;
    private int key;

    private TestEnum(String name, int key) {
        this.name = name;
        this.key = key;
    }

    public static TestEnum getEnumKey(int key) {
        for (TestEnum t : TestEnum.values()) {
            if (t.key == key) {
                return t;
            }
        }
        return null;
    }
    public static void reflectPrivateConstructor() {
        try {
            Class<?> c1 = Class.forName("反射与枚举.TestEnum");
            //注意传入对应的参数,获得对应的构造对象,当前枚举类是提供了两个参数
            //分别是String和int
            Constructor<?> declareConstructorStudent = c1.getDeclaredConstructor(String.class, int.class,String.class, int.class);
            //设置为true后可修改访问权限
            declareConstructorStudent.setAccessible(true);
            Object objectStudent = declareConstructorStudent.newInstance("绿色",666,"橙色",666);
            TestEnum testEnum = (TestEnum) objectStudent;
            System.out.println("获得枚举的私有构造函数:" + testEnum);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    public static void main(String[] args) {
        reflectPrivateConstructor();
    }
}


结果:
注意:上述结果我们可以看出枚举可以避免反射问题。

5. Lambda表达式

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

5.1 lambda表达式的语法

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

lambda表达式由三部分组成:

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

5.2 函数式接口

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

注意:

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

定义方式:

@FunctionalInterface
interface NoParameterNoReturn{
    //注意:只能有一个抽象方法
    void test();
}

也可以通过下述方法来定义:

@FunctionalInterface
interface NoParameterNoReturn{
    void test1();
    default void test() {
        System.out.println("JDK1.8新特征,default默认方法可以有具体的实现");
    }
}

5.3 Lambda表达式的基本使用

首先我们先来准备好几个接口:

@FunctionalInterface
interface NoParameterNoReturn{
    //注意:只能有一个抽象方法
    void test();
}
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn{
    void test(int a);
}
//无返回值多个参数
@FunctionalInterface
interface MoreParameterNoReturn{
    void test(int a, double b);
}

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

我们在上面提到过,lambda可以理解为:lambda就是匿名内部类的简化,实际上是创建了一个类,实现了接口,重写了接口的方法。

没有使用lambda表达式的时候调用方式:

public static void main(String[] args) {
        NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn() {
            @Override
            public void test() {
                System.out.println("hello");
            }
        };
        noParameterNoReturn.test();
    }

具体的使用如下代码所示:

package lambda表达式;
@FunctionalInterface
interface NoParameterNoReturn{
    //注意:只能有一个抽象方法
    void test();
}
//@FunctionalInterface
//interface NoParameterNoReturn{
//    void test1();
//    default void test() {
//        System.out.println("JDK1.8新特征,default默认方法可以有具体的实现");
//    }
//}
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn{
    void test(int a);
}
//无返回值多个参数
@FunctionalInterface
interface MoreParameterNoReturn{
    void test(int a, double b);
}

//有返回值无参数
@FunctionalInterface
interface NoParameterReturn{
    int test();
}
//有返回值一个参数
@FunctionalInterface
interface OneParameterReturn{
    int test(int a);
}
//有返回值多个参数
@FunctionalInterface
interface MoreParameterReturn{
    int test(int a, int b);
}
class Demo<T> {

}

public class Test1 {
    public static void main(String[] args) {
        NoParameterNoReturn noParameterNoReturn = () ->{
            System.out.println("无参数无返回值");
        };
        noParameterNoReturn.test();

        OneParameterNoReturn oneParameterNoReturn = (int a) -> {
            System.out.println("一个参数无返回值:" + a);
        };
        oneParameterNoReturn.test(3);

        //注意:当前后参数的类型一致的时候可以省略类型
        MoreParameterNoReturn moreParameterNoReturn = (a,b) -> {
            System.out.println("多个参数无返回值:" + a + " " + b);
        };
        moreParameterNoReturn.test(3,6);

        NoParameterReturn noParameterReturn = () -> {
            System.out.println("有返回值无参数!");
            return 40;
        };
        int ret1 = noParameterReturn.test();
        System.out.println(ret1);
        OneParameterReturn oneParameterReturn = (int a) -> {
            System.out.println("有返回值一个参数!");
            return a;
        };
        int ret2 = oneParameterReturn.test(50);
        System.out.println(ret2);

        MoreParameterReturn moreParameterReturn = (int a, int b) -> {
            System.out.println("有返回值多个参数!");
            return a + b;
        };
        int ret3 = moreParameterReturn.test(60,70);
        System.out.println(ret3);
    }
    public static void main2(String[] args) {
        NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn() {
            @Override
            public void test() {
                System.out.println("hello");
            }
        };
        noParameterNoReturn.test();
    }
    public static void main1(String[] args) {
        //<>当中的数据类型不参与类型的组成 JVM当中没有泛型的概念 泛型 只存在于编译阶段
        Demo<String> demo = new Demo<>();
        System.out.println(demo);
        Demo<Integer> demo1 = new Demo<>();
        System.out.println(demo1);
    }
}

结果如下所示:

注意:

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

5.4 变量捕获

Lambda表达式中存在变量捕获,了解了变量捕获之后,我们才能更好的理解Lambda表达式的作用域,Java当中匿名类中,会存在变量捕获。

5.4.1 匿名内部类

匿名内部类就是没有名字的内部类,我们这里只是为了说明变量捕获,所以,匿名内部类只要会使用就好了,匿名接下来我们简单的看一下匿名内部类的使用就好了。

代码展示:

package lambda表达式;
class Test{
    public void func() {
        System.out.println("func()");
    }
}
public class Test2 {
    public static void main(String[] args) {
        new Test(){
            @Override
            public void func() {
                System.out.println("我是内部类,且重写了func这个方法!");
            }
        }.func();
    }
}


结果展示:

注意:要调用内部类的时候直接在后面进行 .函数 即可。

上述我们看到的是一个简单的匿名内部类的例子,下面我们来演示一下匿名内部类的变量是如何捕获的。

5.4.2 匿名内部类的变量捕获

代码展示:

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

结果展示:

注意:在上述代码当中变量a就是捕获的变量,这个变量要么是被final修饰,如果不是被final修饰的你要保证在使用之前没有修改,如下代码就是错误的代码!!!

由于有编译错误所以小编这里就不运行代码了。

我们可以看到上述代码中我在内部类中是不可以对变量进行修改的。

5.5 Lambda的变量捕获

在lambda当中也可以进行变量的捕获,具体我们看一下代码。 

代码展示:

package lambda表达式;
@FunctionalInterface
interface NoParameterNoReturn1{
    void test();
}
public class Test5 {
    public static void main(String[] args) {
        int a = 10;
        NoParameterNoReturn1 noParameterNoReturn1 = () -> {
//            a = 99;//error
            System.out.println("捕获变量:" + a);
        };
        noParameterNoReturn1.test();
    }
}


结果展示:

5.6 Lambda在集合当中的使用

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

对应的接口新增的方法
CollectionremoveIf() spliterator() stream() parallelStream() forEach()
ListreplaceAll() sort()
MapgetOrDefault() forEach() replaceAll() putlfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge()

下面我们将演示一些方法,注意:Collection和forEach()方法是从接口java.lang.lterable拿过来的。

5.6.1 Collection接口

forEach方法的演示:
该方法子啊接口Iterable当中,原型如下所示:
 

@Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            action.accept(elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

该方法表示:对容器中的每一个元素执行action指定的动作。

代码如下所示:

@Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            action.accept(elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

结果如下所示:

那么既然我们上面学习了有关于lambda的使用那么这里我们就可以修改上述代码。

代码展示:

package lambda表达式;

import java.util.ArrayList;

public class Test7 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("bit");
        list.add("hello");
        list.add("lambda");
        //表示调用一个不带参数的方法,其执行花括号内的语句,为原来的函数体内容。
        list.forEach(s->{
            System.out.print(s + " ");
        });
    }
}

结果展示:

5.6.2 List接口

sort()方法的演示:
sort方法源码:该方法根据c指定的比较规则对容器元素进行排序。

@Override
    @SuppressWarnings("unchecked")
    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }

使用如下所示:

代码展示:

package lambda表达式;

import java.util.ArrayList;
import java.util.Comparator;

public class Test8 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("bit");
        list.add("hello");
        list.add("lambda");
        list.sort(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                //注意这里比较的是长度
                return o1.length() - o2.length();
            }
        });
        System.out.println(list);
    }
}


结果展示:

修改为lambda表达式:

代码展示:

package lambda表达式;

import java.util.ArrayList;

public class Test9 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("bit");
        list.add("hello");
        list.add("lambda");
        //调用带有两个参数的方法,且返回长度的差值
        list.sort((s1,s2) -> s1.length() - s2.length());
        System.out.println(list);
    }
}


结果展示:
 

5.6.3 Map接口

HashMap的forEach()

该方法原型如下所示:
 

@Override
    public void forEach(BiConsumer<? super K, ? super V> action) {
        Node<K,V>[] tab;
        if (action == null)
            throw new NullPointerException();
        if (size > 0 && (tab = table) != null) {
            int mc = modCount;
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next)
                    action.accept(e.key, e.value);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }

具体的使用,它的作用是对Map中的每个映射执行action指定的操作。

代码展示:

package lambda表达式;

import java.util.HashMap;
import java.util.function.BiConsumer;

public class Test10 {
    public static void main(String[] args) {
        HashMap<Integer, String> map = new HashMap<>();
        map.put(1,"Hello");
        map.put(2,"bit");
        map.put(3,"hello");
        map.put(4,"lambda");
        map.forEach(new BiConsumer<Integer, String>() {
            @Override
            public void accept(Integer k, String v) {
                System.out.println(k + " = " + v);
            }
        });
    }
}


结果展示:

使用lambda表达式后的代码:
代码展示:

package lambda表达式;

import java.util.HashMap;

public class Test11 {
    public static void main(String[] args) {
        HashMap<Integer, String> map = new HashMap<>();
        map.put(1,"Hello");
        map.put(2,"bit");
        map.put(3,"hello");
        map.put(4,"lambda");
        map.forEach((k,v) -> System.out.println(k + " = " +v));
    }
}


结果展示:

5.7 lambda表达式的优缺点

优点:

  • 代码简洁,开发迅速。
  • 方便函数式编程。
  • 非常容易进行并行计算。
  • Java引入lambda,改善了集合操作。

缺点:

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

6. 泛型进阶

6.1 什么是泛型

一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类,如果要编译可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用许多类型,从代码上讲,就是对类型实现了参数化。

6.2 引出泛型

实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值。

思路:

  • 我们以前学过的数组,只能存放指定类型的元素,例如:int[] array = new int[10];String[] str = new String[10];
  • 所有类的父类,默认为Object类,数组是否可以创建为Object?

 如下所示:

以上代码实现后发现:

  • 任何类型数据都可以存放。
  • 1号下标本身就是字符串,但是却编译报错,必须进行强制类型转换。

虽然在这种情况下,当前数组任何数据都可以存放,但是更多情况下,我们还是希望他只能够持有一种数据类型。而不是同时持有这么多类型,所以,泛型的主要目的就是:指定当前的容器,要持有什么类型的对象,让编译器去做检查,此时就需要把类型,作为参数传递,需要什么类型,就传入什么类型。

6.3 语法

class 泛型类名称<类型形参列表> {

        //这里可以使用类型参数 

}

class ClassName<T1,T2,...,Tn> {

}

class 泛型类名称<类型形参列表> extends 继承类{

        //这里可以使用类型参数

}

class ClassName<T1,T2,...,Tn> extends ParentClass<T1> {

        //可以只使用部分类型参数

}

将上述代码改写后:

package lambda表达式;
class MyArray1<T> {
    public T[] array = (T[])new Object[10];
    public T getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos, T val) {
        this.array[pos] = val;
    }
}
public class Test13 {
    public static void main(String[] args) {
        MyArray1<Integer> myArray1 = new MyArray1<>();
        myArray1.setVal(0, 10);
        myArray1.setVal(1, 12);
        int ret = myArray1.getPos(1);
        System.out.println(ret);
//        myArray1.setVal(2,"bit");//后面就会出现编译错误,因为上面我们指定的类型是Integer
    }
}


结果如下所示:
 

代码解释:

  • 类名后的<T>代表占位符,表示当前类是一个泛型类。
  • 在代码中我们使用的是:public T[] array = (T[])new Object[10];而不是T[] array = new T[10];意味着这样写是不对的。
  • 在<>中我们加入Integer指定当前的类型。
  • 既然以及指定类型那么我们在取数据的时候就不需要进行强制类型转换。
  • myArray1.setVal(2,"bit");在此处代码会编译出错,此时因为上面已经指定了类型,所以编译器会在存放元素的时候帮助我们进行类型检查。

类型形参一般使用一个大小写字母表示,常用的名称有:

  • E:表示Element
  • K:表示Key
  • V:表示Value
  • N:表示NUmber
  • T:表示Type

6.4 泛型类的使用

6.4.1 语法

泛型类<类型实参> 变量名;//定义一个泛型类引用

new 泛型类<类型实参>(构造方法实参);//实例化一个泛型类对象

MyArray<Integer> list = new MyArray<Integer>();

注意:泛型只能接收类,所有的基本数据类型必须使用包装类。

6.4.2 裸类型

裸类型是一个泛型类但没有带着类型实参,例如MyArray list就是一个裸类型。

如:MyArray list = new MyArray();

注意:我们不要自己去使用裸类型,裸类型是为了兼容老版本的API保留的机制。

6.5 泛型的编译

6.5.1 擦除机制

什么是擦除机制呢?泛型到底是怎么编译的,我们可以通过查看字节码文件就会发现所有的T都是Object。在编译的过程中,将所有的T替换为Object这种机制,我们称为:擦除机制。

Java的泛型机制是在编译级别实现的,编译器生成的字节码在运行期间并不包含泛型的类型信息。

6.5.2 为什么不能实例化泛型类数组

代码:

package lambda表达式;
class MyArray2<T> {
    public T[] array = (T[])new Object[10];
    public T getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos, T val) {
        this.array[pos] = val;
    }
    public T[] getArray() {
        return array;
    }
}
public class Test14 {
    public static void main(String[] args) {
        MyArray2<Integer> myArray2 = new MyArray2<>();
        Integer[] strings = myArray2.getArray();
    }
}

结果: 

原因:
替换后的方法为:

public Object[] getArray() {
    return array;
}

将Object分配给Integer[]引用 ,程序就会报错,通俗的讲就是返回的Object数组里面,可能存放的是任何的数据类型,可能是String,可能是Person,运行的时候,直接转给Integer类型的数组,编译器认为是不安全的。

正确的做法如下所示:
 

package lambda表达式;

import java.lang.reflect.Array;

class MyArray3<T> {
    public T[] array;
    public MyArray3() {

    }
    //通过反射创建,指定类型的数组
    public MyArray3(Class<T> clazz, int capacity){
        array = (T[]) Array.newInstance(clazz, capacity);
    }
    public T getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos, T val) {
        this.array[pos] = val;
    }
    public T[] getArray() {
        return array;
    }
}
public class Test15 {
    public static void main(String[] args) {
        MyArray3<Integer> myArray3 = new MyArray3<>(Integer.class,10);
        Integer[] integers = myArray3.getArray();
    }
}

6.6 泛型的上界

在定义泛型类的时候,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。

语法:
class 泛型类名称<类型形参 extends 类型边界>{

}

如下所示:

public class MyArray<E extends Number> {

}

只接受NUmber的子类作为E的类型实参。

  • MyArray<Integer>  //正常,因为Integer是Number的子类。
  • MyArray<String>  //编译错误,因为String不是Number的子类。

注意:如果没有指定类型的边界E,可以视为E extends Object。

复杂类型:

public class MyArray<E extends Comparable<E>>{

}

注意:E必须是实现了Comparator接口的。

6.7 泛型方法

6.7.1 定义语法

方法限定符<类型形参列表> 返回值类型 方法名称(形参列表){...}

实例演示:

package 泛型;

import java.util.Arrays;

public class Test5 {
    //静态的泛型方法需要在static后用<>声明泛型类型参数
    public static <E> void swap(E[] array, int i, int j) {
        E t = array[i];
        array[i] = array[j];
        array[j] = t;
    }
    public static void main(String[] args) {
        //使用类型推导
        Integer[] a = {1,2,3,4};
        swap(a,0,3);
        System.out.println(Arrays.toString(a));
        String[] b = {"hello", "bit","hello", "world"};
        swap(b,0,3);
        for (String str : b) {
            System.out.print(str + " ");
        }
        //不使用类型推导
        Integer[] c = {2,3,4,5,6};
        Test5.<Integer>swap(c,0,4);
    }
}

结果展示:

6.8 通配符

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

6.8.1 通配符解决什么问题

代码如下所示:

package 泛型;
class Message<T> {
    private T message;
    public T getMessage() {
        return message;
    }
    public void setMessage(T message) {
        this.message = message;
    }
}
public class Test6 {
    public static void main(String[] args) {
        Message<String> message = new Message<>();
        message.setMessage("Hello world");
        fun(message);
    }

    private static void fun(Message<String> message) {
        System.out.println(message.getMessage());
    }
}


结果如下所示:

以上程序会带来新的问题,如果现在泛型的类型设置不是String,而是Integer。

那么我们需要的解决方案:可以接收所有泛型类型,但是又不能够让用户随意修改,这种情况就需要通配符“?”来处理。

如下所示:

package 泛型;
class Message1<T> {
    private T message;
    public T getMessage1() {
        return message;
    }
    public void setMessage1(T message) {
        this.message = message;
    }
}
public class Test7 {
    public static void main(String[] args) {
        Message1<Integer> message1 = new Message1<>();
        message1.setMessage1(55);
        fun(message1);
    }

    //此时使用通配符“?”描述的是可以接收任意类型,但是由于不确定类型,所以无法修改。
    private static void fun(Message1<?> message1) {
        System.out.println(message1.getMessage1());
    }
}


结果展示:

在 “?”的基础上又产生了两个子通配符:

  • ?extends 类:设置通配符上限。
  • ?super类:设置统配符下限。

接下来我们就一一看一下吧。

6.8.2 通配符上界

语法:

<? extends 上界> 

<? extends Number> //可以传入实参类型是Number或者Number的子类。

如下面例子所示:
 

package 泛型;
class Food{

}
class Fruit extends Food{

}
class Apple extends Fruit{

}
class Banana extends Fruit{

}
class Message3<T> {
    //设置泛型
    private T message;
    public T getMessage() {
        return message;
    }
    public void setMessage(T message) {
        this.message = message;
    }
}
public class Test8 {
    public static void main(String[] args) {
        Message3<Apple> message3 = new Message3<>();
        message3.setMessage(new Apple());
        fun(message3);//1
        Message3<Banana> message31 = new Message3<>();
        message31.setMessage(new Banana());
        fun(message31);//2
    }

    //此时使用统配符“?”描述的是他可以直接接收任意类型,但是由于不确定类型,所以无法修改。
    private static void fun(Message3<? extends Fruit> message3) {
        System.out.println(message3.getMessage());
    }
}

结果:

 

 

注意:统配符的上界只能读取数据,不能写入数据。

6.8.3 通配符的下界

语法:

<? super 下界>

<? super Integer> //代表可以传入的实参的类型是Integer或者是Integer的父类类型。

代码:

package 泛型;
class Food{

}
class Fruit extends Food{

}
class Apple extends Fruit{

}
class Plate<T> {
    private T plate;
    public T getPlate() {
        return plate;
    }
    public void setPlate(T plate) {
        this.plate = plate;
    }
}
public class Test9 {
    public static void main(String[] args) {
        Plate<Fruit> plate1 = new Plate<>();
        plate1.setPlate(new Fruit());
        fun(plate1);

        Plate<Food> plate2 = new Plate<>();
        plate2.setPlate(new Food());
        fun(plate2);
    }

    private static void fun(Plate<? super Fruit> plate1) {
        //此时可以修改,添加的是Fruit或者是Fruit的子类
        plate1.setPlate(new Apple());//这个是Fruit的子类。
        plate1.setPlate(new Fruit());//这个是Fruit本身
//        Fruit fruit = plate1.getPlate();//不能接收,这里无法确定是哪个父类
        System.out.println(plate1.getPlate());//只能直接输出
    }
}


结果:

注意:统配符的下界,不能读取数据,只能写入数据。

结束语:
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/697061.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android实现发送短信功能

Android发送短信 效果图代码实现先添加权限.java由用户输入手机号 自定义短信内容跳转系统发送短信页面 自定义手机号短信内容全部代码 .xml 效果图 代码实现 先添加权限 <!-- 允许程序发送SMS短信 --><uses-permission android:name"android.permission.SEND_S…

nodejs线上环境远程调试

将nodejs部署到线上环境之后&#xff0c;这个时候想要去调试还是有点困难的&#xff0c;但是好在nodejs提供了一个可以让你进行调试的开关&#xff1a;--inspect 我们可以使用这个开关来启动一个可以调试的服务&#xff0c;使用非常简单&#xff0c;在启动服务的时候加上这个指…

高性能计算怎么入门?

如果入门没有专业方向&#xff0c;自学摸索的话&#xff0c;不容易有自己的核心竞争力。国内目前生态尚不完善&#xff0c;学习资料也比较少~如果想要系统学习的话&#xff0c;一定不要错过国内首家专业做高性能计算的系统学习课程。 ~我们是国内首家做高性能计算人才培养和推…

微型逆变器会不会迎来CC2340时代

光伏领域的朋友对微型逆变器并不陌生。而CC2340&#xff0c;对大家而言则都是陌生的主。它是何方神仙&#xff1f;和微型逆变器能有什么关系&#xff1f;你还说它可能引领微型逆变器的下一个时代&#xff1f;不急&#xff0c;我们一起来看看。 在全球为双碳愿景努力以及俄乌战…

VR虚拟展会——打造商企展厅线上展示新模式

近期的大雨磅礴&#xff0c;不知道大家都感受过吗&#xff1f;就连在武汉展开的国际连锁加盟展产业博览会也都受到了一定的影响&#xff0c;为期三天的展会&#xff0c;接连下了三天的雨&#xff0c;导致很多客户无缘展会。这就是实体展会的限制之一&#xff0c;加上实体展会的…

vscode修改markdown侧边预览pdf字体等设置

文章目录 1.按CtrlShiftP打开命令窗口2.在命令窗口出输入Markdown Preview Enhanced: Customize Css&#xff0c;打开style.less文件 1.按CtrlShiftP打开命令窗口 2.在命令窗口出输入Markdown Preview Enhanced: Customize Css&#xff0c;打开style.less文件 然后在文件内加…

【网站维护】网络杂谈(7)之web网站的维护

涉及知识点 如何进行web网站的维护&#xff0c;如何进行web网站的更新与测试&#xff0c;搭建web网站后期如何管理维护。 原创于&#xff1a;CSDN博主-《拄杖盲学轻声码》&#xff0c;更多内容可去其主页关注下哈&#xff0c;不胜感激 文章目录 涉及知识点前言1&#xff0e;网…

仿牛客社区——7.19生成长图

采用异步方式&#xff0c;通过任务方式&#xff0c;将次命令丢到消息队列中进行执行 关于wkhtmltopdf的配置&#xff08;applaction.properties&#xff09; #关于wk的配置&#xff08;生成长图 wk.image.commandD:/wkhtmltopdf/bin/wkhtmltoimage wk.image.storageD:/wkData/w…

婴儿摇篮音乐芯片 N9300-S16:为宝宝带来高品质的音乐体验

​对于父母来说&#xff0c;给婴儿提供一个安稳舒适的睡眠环境是至关重要的。宝宝的睡眠品质对于其健康和发展至关重要。在成长过程中&#xff0c;音乐对婴儿的情绪、认知和智力发展都有积极的影响。因此&#xff0c;厂家在婴儿摇篮中选择一款合适的婴儿摇篮音乐芯片尤为重要。…

共见·价值成就|亚马逊云科技中国峰会宣布三大举措全面升级

2023年6月27日&#xff0c;亚马逊云科技举办一年一度的中国合作伙伴峰会。本届峰会以“共见价值成就”为主题&#xff0c;面向合作伙伴发布智荟出海计划、可持续发展伙伴计划、合作伙伴解决方案工厂以及获客激励计划等多项计划&#xff0c;以进一步强化合作伙伴“33战略”&…

ASEMI代理ST可控硅BTA16的工作原理与应用分析

编辑-Z 本文将对可控硅BTA16的工作原理与应用进行详细的分析。首先&#xff0c;我们将介绍可控硅BTA16的基本概念和工作原理&#xff0c;然后&#xff0c;我们将探讨其在电力电子设备中的应用&#xff0c;接着&#xff0c;我们将分析其在电力调节中的作用&#xff0c;最后&…

edge自带断网游戏

在没有网络时你会不会很无聊&#xff1f;博主告诉你一个edge浏览器自带的断网小游戏&#xff0c;让你在断网时也能玩游戏&#xff01; 网址&#xff1a; 打开edge://surf这个断网游戏网站即可游玩&#xff1a; 作弊码既隐藏模式&#xff1a; 输入microsoft&#xff08;意思就…

C语言学习(二十八)---字符串相关函数

在上一节的内容结束后&#xff0c;有关指针的内容就告一段落了&#xff0c;指针是开发中非常重要的一环&#xff0c;大家务必要对其深入理解并且掌握&#xff0c;今天我们将继续往下学习&#xff0c;主要学习字符串操作相关的函数&#xff0c;分为不限制长度和限制长度两种&…

vetcor使用移动构造取代拷贝构造实现push_back

昨天说到&#xff1a;vector变量push_back一个对象或变量的时候&#xff0c;本质上是执行拷贝构造&#xff0c;但我想使用移动构造&#xff0c;而不是拷贝构造&#xff0c;本文就修改调试过程&#xff0c;详细分析如何实现移动构造。 昨天的代码如下&#xff1a;(如果有人想测…

Keil5 创建工程

一、 在桌面新建一个 TEST 的文件夹&#xff0c;然后在 TEST 文件夹里面新建 USER 文件夹&#xff0c;将工程名 字设为 test&#xff0c;保存在这个 USER 文件夹里面,选择对应芯片的安装包 启动代码作用&#xff1a; 1、堆栈&#xff08;SP&#xff09;的初始化&#xff1b; 2…

java.util.concurrent.Executionexception 异常

报错截图&#xff1a; 今天运行时发生了如下报错。自己捣鼓半天也没发现问题出在哪儿&#xff0c;感谢大佬的帮助&#xff0c;记录下来防止再犯。。 caused by org.apache.flink.client.program.programInvocationException: Job failed。程序调用异常。网上找了很多解决方法…

Qt 中线程池的使用

1. 线程池的原理 我们使用线程的时候就去创建一个线程&#xff0c;这样实现起来非常简便&#xff0c;但是就会有一个问题&#xff1a;如果并发的线程数量很多&#xff0c;并且每个线程都是执行一个时间很短的任务就结束了&#xff0c;这样频繁创建线程就会大大降低系统的效率&…

第14章-Python-人工智能-语言识别-调用百度语音识别

百度语音识别API是可以免费试用的&#xff0c;通过百度账号登录到百度智能云&#xff0c;在语音技术页面创建的应用&#xff0c;生成一个语音识别的应用&#xff0c;这个应用会给你一个APIKey和一个Secret Key&#xff0c;如图14.1所示。 我们在自己的程序中用 API Key 和 Secr…

轻松搞定 Git

目录 前言 一、下载 二、安装 三、基本使用 四、git的基本原理 五、通过案例学习git 5.1 创建空的项目文件夹 5.2 初始化git 5.3 创建项目文件 5.4 查看git状态 5.5 添加到暂存区 5.6 提交到本地仓库 5.7 查看git提交到本地仓库的记录 5.8 .gitignore文件 六、分…

mysql基础2——增、删、改、查

文章目录 一、DDL操作1.1 数据库操作1.2 表操作1.3 用户操作1.4 查看命令show1.5 获取帮助 二、DCL操作2.1 用户授权2.2 查看授权2.3 取消授权 三、DML操作3.1 插入insert3.2 查询select3.2.1 常规查询3.2.2 条件查询3.2.3 order by用法3.2.4 group by用法3.2.5 内连接&左连…