Java反射、枚举类和lambda表达式

前言:

        本章我们就来了解Java中的反射和枚举类。枚举类和反射其实有些关系,接下来我们就来学习他们的使用。

反射:

反射的作用:

        反射:反射允许对成员变量,成员方法和构造方法的信息进行编程访问。

        Java中有一个很有趣的知识——反射,它就类似于一个照妖镜,可以让一个类中所有的元素都无所遁形。

        Java的反射(reflection)机制是在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法。

        我们在使用IDEA的时候,总是输入什么以后会提示东西,其实这就是反射的应用。

b590111c45f347cd8d73b2c03270412f.png

        说白了就是从类中拿东西,比如成员变量,成员方法,构造方法等。 

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

获取class对象: 

        通过上面的说明,当我们使用反射时,必须先拿到一个类。

f38b3e3227a24defa305a9ddf2b920f1.png

        此时我们就介绍拿到类的3种方法:

1.在Java中已经定一个了一个类叫做Class,就是用来描述字节码文件的。其中有一个静态方法叫做forName,参数为全类名。

be6cda5e822745248f60d85310ef9a46.png

2.类名.class

3.对象.class

530ed437d3c048c184b2c1b12fd17e82.png

        我们一般使用Class类中forName方法获取类,上面说了要获取全类名,那么什么是全类名呢?

        全类名:全类名是指package + (.)类的名字

        我们其实很容易获取全类名,我们找到要获取的类,之后右击类名,复制参数即可:

0abebe7a181b4e0ea828f3ffd089b975.png

         一定在双引号中复制,否则就是类的名,不是全类名。

bdad898e7d98408e91ea3708f7e26fd2.png

        当我们只知道方法不知道该使用什么类时,可以先写方法并传入参数,之后使用快捷键生成左边: 

        Ctrl + Alt + V : 自动生成左边

        我们说有三种获取类的方法,如果获取的是同一个类,其实获取的都是一样的。

public class MyReflect {public static void main(String[] args) throws ClassNotFoundException {/** 获取class的三种方式:*   1.Class.forName("全类名");*   2.类名.class*   3.对象.getClass();* *///1.Class.forName("全类名");//全类名 : 包名 + 类名Class clazz1 = Class.forName("dome1.Student");//全类名//因为里面有受查异常,所以在主方法中加上System.out.println(clazz1);//2.类名.classClass clazz2 = Student.class;System.out.println(clazz2);System.out.println(clazz1 == clazz2);//获取的都是字节码问价,所以相同//3.对象.getClass()Student s = new Student();Class clazz3 = s.getClass();System.out.println(clazz1 == clazz2);System.out.println(clazz1 == clazz3);}
}
package dome1;public class Student {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}public Student() {}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}

 

78ea853d3fcb4e62bd29ba6ae19074c1.png

        其中最常用的就是第一种方式,第三种方式局限性最大,必须有当前对象才能使用。

获取构造方法:

        此时就获取了字节码文件了,之后我们就要从中获取构造方法等内容了。

12a5bf493d4446959a63de7d9159021b.png

        想一想,我们既然获取了构造方法,是不是就能通过该构造方法创建对象了呢?我们先来看相关方法:

9de3982b928840bf913eae1ae79853ca.png

package dome1;public class Student {private String name;private int age;protected Student(String name, int age) {this.name = name;this.age = age;}public Student() {}private Student(String name) {this.name = name;}public Student(int age) {this.age = age;}public String getName() {return name;}private void setName(String name) {this.name = name;}protected int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
package dome1;import java.lang.reflect.Constructor;public class MyReflectDemo {public static void main(String[] args) throws ClassNotFoundException {//1.获取class字节码文件对象Class clazz = Class.forName("dome1.Student");//2.获取构造方法Constructor[] cons = clazz.getConstructors();for (Constructor con : cons) {System.out.println(con);}}
}

ed63aa372f894957a8d1bb49490962e1.png

        可以看到,我们已经获取了所有的public修饰的构造方法。为了不冗余,我们直接给出图片,我们更改getConstructors方法,并观察区别:

d94b89a3f981456bb34429868903f02d.png

        单独获取可以指定获取构造方法,通过参数来指定。

f974a6744c564abe8933681503a01b9c.png

获取修饰权限付: 

        当我们获取万构造方法以后,我们可以获取这个构造方法的权限修饰符,通过getModifiers方法,返回的是一个整形。

        至于每个权限修饰符,都有对应的整数。由于博主在叙利亚,下面给出战损版:

88673b01832e469fa585a14ad24c644b.png

Constructor con2 = clazz.getDeclaredConstructor(String.class);
//我们单独获取构造方法,往里面添加参数,就可以指定获取那个构造方法
//System.out.println(con2);int modifiers = con2.getModifiers();
//获取构造方法的权限修饰符,以整数形式体现
System.out.println(modifiers);

9bfc370dca4c4202b9e279246bcd7886.png

        IDEA中可以提示参数,也就是通过反射原理来实现的。 

ce42ec436575498581954beb909c7875.png

        当我们不知道一个方法中有哪些参数是,可以使用:Ctrl + P 来进行提示。

获取方法参数:

        使用getParameter方法来获取参数:

e128985f10764c5ea7e6876e4699fafe.png

Parameter[] parameters = con2.getParameters();
//获取构造方法所有参数
for (Parameter x : parameters) {System.out.println(x);
}

c17c42eba12d411486b744612f474c90.png

获取私有方法并实例化对象:

        此时我们已经获取了构造方法,那么我们就可以通过获取的构造方法去实例化一个对象,使用newInstance方法并传入参数去实例化对象。

Constructor con2 = clazz.getDeclaredConstructor(String.class);
//con2.setAccessible(true);//相当于临时取消修饰权限校验//此时我们已经拿到了构造方法,那么我们就可以通过此构造方法去创建对象
Student stu = (Student) con2.newInstance("张三");
System.out.println(stu);

74dcb27439864915bcb5f90f773d1508.png

        因为报错是private,我们此时也就是看到了该构造方法,但不能直接去构造。我们要加上setAccessible去临时修改权限。

d57c83aad0064b1eac05ce2d5ecec1c8.png

        这就是暴力反射,因为是私有方法,但是通过反射使用了。

获取类中的成员:

c9ea48a641174914bf49fac227dfe74e.png

package dome2;public class Student {private String name;private int age;public String gender;public Student() {}public Student(String name, int age, String gender) {this.name = name;this.age = age;this.gender = gender;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", gender='" + gender + '\'' +'}';}
}
public class MyReflect {public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {//1.获取字节码文件Class clazz = Class.forName("dome2.Student");//2.获取公共成员变量Field[] fields = clazz.getFields();for (Field x : fields) {System.out.println(x);}//获取单个成员变量Field name = clazz.getDeclaredField("name");System.out.println(name);//获取成员变量名字String n = name.getName();//获取成员变量名System.out.println(n);//获取成员变量的数据类型Class<?> type = name.getType();System.out.println(type);//获取成员变量记录的值 和对象有关,所以先初始化一个Student s = new Student("张三",23,"男");name.setAccessible(true);//临时取消访问权限String value = (String) name.get(s);//获取该对象name记录的值System.out.println(value);//修改对象里面记录的值name.set(s, "lisi");System.out.println(s);}
}

e53321f7db4e4574aab8a31b62eb17da.png

获取类中的方法和抛出的异常:

         我们再来看利用反射获取成员方法。54286784d82f4b98b52d2ad98f7f3890.png

package demo3;import java.io.IOException;public class Student {private String name;private int age;public Student() {}public Student(String name, int age, String gender) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public void sleep() {System.out.println("睡觉");}private void eat(String st) throws IOException,NullPointerException,ClassCastException {System.out.println("在吃" + st);}private void eat(String st, int a) {System.out.println("在吃" + st);}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
public class MyReflect {public static void main(String[] args) throws ClassNotFoundException {//1.获取class字节码文件Class clazz = Class.forName("demo3.Student");//2.获取里面所有的方法对象(包括父类的公共方法)Method[] methods = clazz.getMethods();for (Method method : methods) {System.out.println(method);}}
}

ac12a311757b42d9bf7320f602f58fe8.png

        我们使用getDeclareMethods方法,获取的是该类中的所有方法(没有父类中的方法)。

public static void main(String[] args) throws ClassNotFoundException {//1.获取class字节码文件Class clazz = Class.forName("demo3.Student");/*//2.获取里面所有的方法对象(包括父类的公共方法)Method[] methods = clazz.getMethods();for (Method method : methods) {System.out.println(method);}*///3.获取里面所有的方法对象(不获取父类,但可以获取本类中所有方法Method[] methods = clazz.getDeclaredMethods();for (Method method : methods) {System.out.println(method);}}

589d34e57d694ea396d96bae03793650.png

        注意这里的getMethods和getDeclaredMethods是不一样的,getMethods是获取所有公共方法(包括父类公共方法);getDeclaredMethods是获取本类所有方法。 

调用获取的方法(invoke方法): 

        一样的,我们获取了方法,也就可以使用该方法,但是因为Java中有方法重载,所以必须传入参数,这样才可以调用指定参数。

        获取方法后,我们也可以使用Class类中的getExceptionTypes方法获取该方法抛出的异常。

        获取方法后, Method中有invoke方法,是调用指定方法,第一个参数是对象,也就是说,我们使用之前要现获取构造方法,之后实例化一个对象,之后将对象传入invoke方法的第一个参数。

public class MyReflect {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {//1.获取class字节码文件Class clazz = Class.forName("demo3.Student");/*//2.获取里面所有的方法对象(包括父类的公共方法)Method[] methods = clazz.getMethods();for (Method method : methods) {System.out.println(method);}*//*//3.获取里面所有的方法对象(不获取父类,但可以获取本类中所有方法Method[] methods = clazz.getDeclaredMethods();for (Method method : methods) {System.out.println(method);}*///获取指定单一方法Method m = clazz.getDeclaredMethod("eat", String.class);System.out.println(m);//因为有方法重载,所以要指定参数//获取方法修饰符int modifiers = m.getModifiers();System.out.println(modifiers);//获取方法名字String name = m.getName();System.out.println(name);//获取方法形参Parameter[] parameters = m.getParameters();for (Parameter parameter : parameters) {System.out.println(parameter);}//获取方法抛出的异常Class[] exceptionTypes = m.getExceptionTypes();for (Class exceptionType : exceptionTypes) {System.out.println(exceptionType);}//方法运行/*Method 类中用于创建对象* Object invoke(Object obj, Object... args): 运行方法* 参数一:用obj对象调用的方法* 参数二:调用方法传递的参数(如果是void就不写)* 返回值:方法的返回值(如果没有就不写)* */Student s = new Student();//参数一s:方法调用者//参数二"羊肉":表示调用方法的时候传递的实际参数m.setAccessible(true);//记住这个方法是私有的m.invoke(s,"羊肉");//如果有返回值就接受,可以强制转换}
}

50781fb7f45342b1a8492c14586846b6.png

        注意,这里面的Student类还是上面demo3里面的Student类。

枚举:

枚举的作用:

        我们有时候使用的值是不会改变的,像一年四季,这些都是常量,不会发生改变,所以便有了枚举类。枚举是JDK1.5以后引入的。主要是将一组常量组织起来。

自定义枚举类:

        看枚举之前,我们先来看自定义枚举类,此时我们自己定义一个枚举类。

/*
* 1.构造器私有化
* 2.本类内部创建一组对象 四个 春夏秋冬
* 3.对外暴露对象 -> 通过为对象前加 public final static 修饰符
* 4.可以提供 get 方法 但是只读不写(没有set方法)
* *///自定义枚举类
public class TestEnum {private String name;private String character;//特点//对外暴露public static final TestEnum SPRING = new TestEnum("春天","花香");public static final TestEnum SUMMER = new TestEnum("夏天","烈日");public static final TestEnum AUTUMN = new TestEnum("秋天","气爽");public static final TestEnum WINNER = new TestEnum("冬天","大雪");//构造器私有化 -> 这样外界就不能直接创建对象 -> 防止直接 newprivate TestEnum() {}private TestEnum(String name, String character) {this.name = name;this.character = character;}//只提供 get 方法 只读不写public String getName() {return name;}public String getCharacter() {return character;}@Overridepublic String toString() {return "TestEnum{" +"name='" + name + '\'' +", character='" + character + '\'' +'}';}
}
public class Test {public static void main(String[] args) {System.out.println(TestEnum.SPRING);System.out.println(TestEnum.SUMMER);System.out.println(TestEnum.AUTUMN);System.out.println(TestEnum.WINNER);}
}

2330f3d7962b40c19faa538ef797076f.png

        定义为静态就是说明不需要实例化对象,而且为了防止其修改使用final。

        枚举类构造方法默认被 private 修饰,写成其他则报错。

使用枚举类(values方法和枚举类的方法):

        我们每次使用IDEA去创建类时,总会有一些选项,其中就有一个enum选项,我们去创建一个。

public enum SEnum {SPRING("春天","花香"),SUMMER("夏天","烈日"),AUTUMN("秋天","气爽"),WINTER;//这里WINTER调用无参构造器private String name;private String character;SEnum() { //注意,这里没有 private 修饰,因为被默认修饰了}SEnum(String name, String character) {this.name = name;this.character = character;}public String getName() {return name;}public String getCharacter() {return character;}@Overridepublic String toString() {return "SEnum{" +"name='" + name + '\'' +", character='" + character + '\'' +'}';}
}

        我们使用枚举类发现能省去很多代码。

        我们不能将枚举类进行继承: 

f0398f5caf7042fa89debb7143c70141.png

        可以发现其报错,因为Java不能多继承,所以我们猜测可能是其可能已经继承了一个类。

        我们进行反编译(在IDEA左边右击该类,找到Open In -> Explorer,之后回退到Out目录,进入以后找到这个类生成的.class文件,并在目录中输入 cmd ,利用 javap 字节码文件名进行反编译):

13f954670f924db786241e92a62de4d7.png

70f7ac3705da476a8e369ebbb0dad64b.png

        当然这里面有些是父类的方法。 

        发现其默认继承Enum并且是被final修饰的。 

        values()方法可以将枚举类转变为一个枚举类型的数组,因为枚举中没有下标,我们没有办法通过下标来快速找到需要的枚举类,这时候,转变为数组之后,我们就可以通过数组的下标,来找到我们需要的枚举类。

        其实枚举在定义的时候就已经创建了对象,当然枚举里面也可以定义成员并且定义方法。

public enum A {//注意:枚举类第一行必须罗列的是枚举对象的名字X, Y, Z;private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
public class Test {public static void main(String[] args) {//认识枚举A a1 = A.X;System.out.println(a1);//1.枚举类的构造方法是私有的,不能对外创建对象//A a = new A();//2.枚举类的第一行都是常量,记住的是枚举类的对象A a2 = A.Y;//3.枚举类提供一些额外的APIA[] as = A.values();for (A x : as) {System.out.print(x + " ");}System.out.println();A a3 = A.valueOf("Y");System.out.println(a3.name());//拿名字System.out.println(a3.ordinal());//拿索引}
}

cc19d1f7c11c4c5d986548f26a5c9b80.png

注意事项:

        常量定义要放到最开始,构造方法放在其下面,否则报错。

d780ba9d16fd46b2beaf73735146c40a.png

        主方法直接使用枚举类名调用其中成员,因为是静态变量,并且不能初始化其中成员,而且也不能添加。

f286b38e30be45d9a74ae3972a881ed4.png

        枚举类(Enum)中只有一个构造方法:

9806359a5f614689950c37591bc2b563.png

        此时父类中有构造方法,但是我们写的子类没有构造方法,没有调用super。这也就证明了枚举类是一个特殊的类。

        枚举类无法被继承,无法被扩展,也就证明了它很安全。

        我们比较获取的索引:

public static void main(String[] args) {System.out.println(SEnum.SPRING);SEnum[] sEnum = SEnum.values();for (SEnum anEnum : sEnum) {System.out.println(anEnum.ordinal());//获取枚举成员的索引位置}System.out.println("====");SEnum v1 = SEnum.valueOf("SPRING");System.out.println(v1);/*SEnum v2 = SEnum.valueOf("eheh");//前提是枚举类型中必须包含该常量System.out.println(v2);*///因为 Enum 类实现了 Comparable 接口System.out.println(SEnum.SPRING.compareTo(SEnum.SUMMER));//比较}

e70d234e80db4d6f90c6636a1b80f06b.png

枚举类无法反射: 

        上面我们讲过,反射可以让一个类中的任何东西都无所遁形,那么我们利用反射区获取枚举类中的属性。

public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException, InvocationTargetException,InstantiationException, IllegalAccessException {Class clazz = Class.forName("demo1.SEnum");Constructor con = clazz.getDeclaredConstructor(String.class, String.class);con.setAccessible(true);SEnum o = (SEnum) con.newInstance("晴天", "开心");//此时通过反射新建了一个枚举对象System.out.println(o);
}

5dc8cb0f652a43a39719a71467aea17a.png

        因为enum继承于Enum,Enum中的构造方法又有两个形参,所以我们在传入4个参数。所以对以上代码进行以下局部修改:

Constructor con = clazz.getDeclaredConstructor(String.class,int.class,String.class, String.class);
con.setAccessible(true);
SEnum o = (SEnum) con.newInstance("黑夜",9,"晴天", "开心");

279918222c734618ab96b1df6cd9c5d6.png

        此时我们进入源码观察。 

9d82c7a0b69c44e597920decffd04a87.png

        所以证明了枚举对象是非常安全的,是不可以通过反射来创建一个枚举对象的。

抽象枚举:

        其实就是在枚举类中定义了抽象方法,因为枚举对象就定义在枚举类中,所以定义的对象必须实现该抽象方法。

//抽象枚举
public enum B {X() {@Overridepublic void go() {}}, Y("张三") {//调用有参构造器@Overridepublic void go() {System.out.println((getName() + "吃饭"));//调用方法}};public abstract void go();//这里面定义了一个抽象方法//因为枚举类中就定义了对象//所以定义时必须实现该抽象方法B(String name) {this.name = name;}//注意:这里面构造方法都是默认私有的B() {}private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
public class Test {public static void main(String[] args) {B a = B.Y;a.go();}
}

bd738ba972624ea7aed99fce689a5f78.png 

lambda表达式:

lambda表达式基本语法:

        (parameters)-> expression 或 (Parameters)-> { statements;}

088b43473f474a36ad82cec19c4d1a92.png

        Lambda表达式由三部分组成:

  1. paramaters:类似方法中的参数形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明也可以不声明而由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对象,并在控制台打印,无返回值
(String s) -> System.out.println(s);

函数式接口:

        要想学好lambda表达式,我们首先要来了解什么是函数式接口。

        函数式接口定义:一个接口有且只有一个抽象方法(在Java1.8中,新增了default修饰的抽象方法也可以定义在函数式接口。)。

//函数式接口
@FunctionalInterface
interface NPNR {//只能有有一个方法void test();default void test2() {System.out.println("JDK1.8新特性,default默认方法可以有具体的实现");}
}

e5404fb1e19740e9858901a9d6cee39d.png

7236eef6e2ab4e2092f99c7216fee53d.png

lambda表达式和匿名内部类的关系: 

        暂停,接下来有涉及到一个知识,匿名内部类,我们只有了解它之后才能更好的使用lambda表达式。

//函数式接口
@FunctionalInterface
interface NPNR {//只能有有一个方法void test();
}
public static void main(String[] args) {//此时我们先不使用 lambda 表达式NPNR npnr1 = new NPNR() {@Overridepublic void test() {System.out.println("这是一个匿名内部类,里面重写了 test() 方法");}//这个类实现了test并重写,因为是匿名内部类,所以这个类没有名字};//我们使用 lambda 表达式来进行修改NPNR npnr2 = () -> System.out.println("使用 lambda 表达式重写了 test 方法");NPNR npnr3 = () -> {System.out.println("因为这里有多条语句,所以用大括号");System.out.println("前面小括号不能省略");};npnr1.test();npnr2.test();npnr3.test();
}

2b87445f1d064284a088cea2ac96b248.png

        关于匿名内部类更加详细的知识可以去看我的这篇文章Java中的内部类-CSDN博客。当然,以下还有详细对比其和lambda表达式的区别,我们也可以继续阅读。

lambda表达式的使用:

        得知了匿名内部类之后,lambda表达式其实就是简化了匿名内部类的写法。接下来我们举一些实际的例子:

//函数式接口
@FunctionalInterface
interface NPNR {//只能有有一个方法void test();
}//有一个参数无返回值
interface OPNR {void test(int a);
}//有一个参数一个返回值
interface OPOR {int test(int a);
}interface MPNR {void test(int a, int b);
}interface NPR {int test();
}interface OPR {int test(int a);
}interface MPR {int test(int a, int b);
}public class Test {public static void main(String[] args) {/*OPNR opnr = a -> {System.out.println("这是有一个参数,无返回值的 lambda表达式");};*///因为只有一个参数,所以可以把前面的括号省略OPNR opnr = (a) -> {System.out.println("这是有一个参数,无返回值的 lambda表达式");};opnr.test(1);OPOR opor = a -> {System.out.println("有一个参数,一个返回值");return 5;};opor.test(5);MPNR mpnr = (a, b) -> {System.out.println("两个参数,没有返回值");System.out.println(a + b);};mpnr.test(1, 2);NPR npr = () -> {System.out.println("没有参数,一个返回值");return 10;};npr.test();OPR opr = (a) -> {System.out.println("一个参数,一个返回值");return 10;};opr.test(10);MPR mpr = (a, b) -> {System.out.println("有多个参数,一个返回值");return 20;};mpr.test(10, 20);}
}

a4ae8de15173461b9f7dcbae8a27df34.png

        看到这里,我们其实就可以把lambda表达式当做一个匿名内部类。

变量捕获: 

        注意这句话叫做变量捕获,也就是说它是捕获常量的。

@FunctionalInterface
interface Opp {void test();
}public class TestLast {public static void main(String[] args) {int sz = 100;sz = 99;Opp opp = () -> {System.out.println("调用" + sz);};}
}

82021520bd3744428e54a90da0c935a0.png

         匿名内部类或者lambda表达式中如果想使用外部的变量,这个变量不能修改,这叫做变量捕获。而且更不能在里面改变外部变量。

lambda表达式和匿名内部类的使用:

        为了方便复习:a388ee68d8ef404aba0220e0ae9332e0.png 

        总体来说,这两者相辅相成。还是否记得我们之前使用PriorityQueue,构建的默认是小根堆,构建大根堆需要我们传入比较器,每次都要去先建一个类,之后传入,有三种实现方法:

创建比较器实现:

class Eg implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {//构建大堆return o2.compareTo(o1);}
}public class TestLast {public static void main(String[] args) {PriorityQueue<Integer> queue = new PriorityQueue<>(new Eg());//此时我们自己构建比较器queue.offer(1);queue.offer(2);queue.offer(3);System.out.println(queue.peek());}
}

da7e831a407240fba7dfe9c1510cc697.png 

使用匿名内部类实现: 

public static void main(String[] args) {PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {//匿名内部类实现大堆@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}});queue.offer(1);queue.offer(2);queue.offer(3);System.out.println(queue.peek());
}

9033e54e743d4f4dbf61b5707da78a5a.png

使用lambda表达式实现:

        这里有一个大前提,就是接口必须实现匿名内部类:

4d3ff8f6a20946efae50b1ad119c4188.png

public static void main(String[] args) {PriorityQueue<Integer> queue = new PriorityQueue<>((o1,o2) -> {return o2.compareTo(o1);});queue.offer(1);queue.offer(2);queue.offer(3);System.out.println(queue.peek());
}

232b30e60f6e4493a0f39e394caf09ab.png

forEach方法:

        forEach方法可以理解为遍历,我们观察其源码。

public static void main(String[] args) {ArrayList<String> list = new ArrayList<>();list.add("hello");list.add("bit");list.add("lambda");list.forEach(new Consumer<String>() {//还是匿名内部类@Overridepublic void accept(String s) {System.out.println(s);}});
}

36b3fc49dc9d4c0994cbd84541a2a067.png

        使用lambda表达式会更加简洁:

45c3d89ed33e4669b90acbff54e0f5ba.png

        在HashMap中也可以利用forEach方法去获取map中的所有元素: 

public static void main(String[] args) {HashMap<Integer, String> map = new HashMap<>();map.put(1, "hello");map.put(2, "bit");map.put(3, "lambda");map.forEach(new BiConsumer<Integer, String>() {@Overridepublic void accept(Integer integer, String s) {System.out.println("key: " + integer + " val: " + s);}});
}

796f5b4b61c14210b69bc328f7b28382.png

87b7a36be28342abb335a7e6fc158a1f.png

        使用lambda表达式改写以后为:

public static void main(String[] args) {HashMap<Integer, String> map = new HashMap<>();map.put(1, "hello");map.put(2, "bit");map.put(3, "lambda");/*map.forEach(new BiConsumer<Integer, String>() {@Overridepublic void accept(Integer integer, String s) {System.out.println("key: " + integer + " val: " + s);}});*/map.forEach((a, s) -> System.out.println("key: " + a + " val: " + s));
}

总结:

        Java的反射(reflection)机制是在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法。

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

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

        合理利用反射,一定在安全环境下使用。我们应该熟练的使用以上的几个类。

        枚举是一种特殊的类,第一行必须罗列枚举的枚举对象的名名字,之后可以在里面使用构造方法。

        枚举对象无法新建,构造方法是必须而且默认是私有的。

        抽象枚举其实就是在里面定义了抽象方法。

        lambda表达式其实就是匿名内部类的简写。

        这些东西多去使用我们就可以掌握,有更好的见解评论区见。

        

 

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

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

相关文章

CVE-2021-31440:eBPF verifier __reg_combine_64_into_32 边界更新错误

文章目录 前言漏洞分析构造 vuln reg 漏洞利用漏洞修复参考 前言 影响版本&#xff1a;Linux 5.7 ~ 5.11.20 8.8 编译选项&#xff1a;CONFIG_BPF_SYSCALL&#xff0c;config 所有带 BPF 字样的编译选项。General setup —> Choose SLAB allocator (SLUB (Unqueued Allocat…

从0到1手把手实现RPC|01 RpcProvider本地实现

RPC的简化版原理如下图&#xff08;核心是代理机制&#xff09;。 1.本地代理存根: Stub2.本地序列化反序列化3.网络通信4.远程序列化反序列化5.远程服务存根: Skeleton6.调用实际业务服务7.原路返回服务结果8.返回给本地调用方 注意处理异常。 RpcProvider的本地实现 1、工…

xss.haozi.me靶机 通关

0x00 没有任何过滤可以直接注入<img srcx οnerrοralert(1)> 0x01 使用了testarea标签将我们的输入内容以普通的字符串进行展示 但是我们可以将标签进行闭合 </textarea><img srcx οnerrοralert(1)> 0x02 我们依然可以先闭合之后添加属性a" οncl…

Java17 --- SpringCloud之Consul

目录 一、consul的使用 1.1、主要功能 1.2、安装及运行 1.3、添加微服务到consul 1.3.1、8001微服务添加相关pom、配置文件、注解 1.3.2、80微服务添加相关pom、配置文件、注解 1.4、三个注册中心异同 1.5、consul进行分布式配置 1.5.1、修改8001的yml配置文件 1.5.2…

数字化运营在教育行业的技术架构实践总结

随着科技的不断进步和数字化时代的到来&#xff0c;教育行业也正面临着数字化转型的挑战和机遇。教育行业的数字化运营需要依靠合理的技术架构来支撑&#xff0c;本文将探讨教育行业数字化运营的技术架构设计。 ## 第一步&#xff1a;需求分析和架构设计 在构建教育行业数字化…

SpringMVC08、Json

8、Json 8.1、什么是JSON&#xff1f; JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式&#xff0c;目前使用特别广泛。采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。易于人阅读和…

LeetCode 173.二叉搜索树迭代器

实现一个二叉搜索树迭代器类BSTIterator &#xff0c;表示一个按中序遍历二叉搜索树&#xff08;BST&#xff09;的迭代器&#xff1a; BSTIterator(TreeNode root) 初始化 BSTIterator 类的一个对象。BST 的根节点 root 会作为构造函数的一部分给出。指针应初始化为一个不存在…

探索机器学习的无限可能性:从初学者到专家的旅程

探索机器学习的无限可能性&#xff1a;从初学者到专家的旅程 在当今数字时代&#xff0c;机器学习无疑是最引人注目的技术之一。它已经深入到我们生活的方方面面&#xff0c;从个性化推荐到自动驾驶汽车&#xff0c;再到医疗诊断和金融预测。但是&#xff0c;即使我们已经见证…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:RotationGesture)

用于触发旋转手势事件&#xff0c;触发旋转手势的最少手指为2指&#xff0c;最大为5指&#xff0c;最小改变度数为1度。 说明&#xff1a; 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 接口 RotationGesture(value?: …

三、N元语法(N-gram)

为了弥补 One-Hot 独热编码的维度灾难和语义鸿沟以及 BOW 词袋模型丢失词序信息和稀疏性这些缺陷&#xff0c;将词表示成一个低维的实数向量&#xff0c;且相似的词的向量表示是相近的&#xff0c;可以用向量之间的距离来衡量相似度。 N-gram 统计语言模型是用来计算句子概率的…

docker 子网

当需要给容器分配指定 ip &#xff0c;为避免ip 冲突&#xff0c;指定容器子网处理 创建 subnet 子网 docker network create --subnet 10.0.0.0/24 --gateway 10.0.0.1 subnet-testdocker network ls NETWORK ID NAME DRIVER SCOPE ... f582ecf297bc sub…

vs2022的下载及安装教程(Visual Studio 2022)

vs简介 Visual Studio在团队项目开发中使用非常多且功能强大&#xff0c;支持开发人员编写跨平台的应用程序;Microsoft Visual C 2022正式版(VC2022运行库)&#xff0c;具有程序框架自动生成&#xff0c;灵活方便的类管理&#xff0c;强大的代码编写等功能&#xff0c;可提供编…

HIVE伪分布安装

引言 Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张表,类似于RDBMS(关系型数据库,如MySQL、Oracle、PgSQL),并提供类SQL的查询功能。 实验准备 1.搭建好伪分布安装模式的Hadoop的虚拟机,并配置了Linux网络。(可看我前面发布的文章) 2.apache…

Windows系统安装Tomcat并结合内网穿透实现公网访问本地网页

文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 前言 Tomcat作为一个拥有强大功能的轻量级服务器&#xff0c;由于其可以实…

k8s应用综合实例

k8s应用综合实例 目录 k8s应用综合实例 目录 原文链接 推荐文章 实验环境 实验软件 本节实战 预期 原理 高可用 稳定性 避免单点故障 使用 PDB 健康检查 服务质量 QoS QoS类型 资源回收策略 滚动更新 失败原因 零宕机 HPA 安全性 持久化 Ingress FAQ …

Python Web应用程序构建的最佳实践:代码实例与深度解析【第122篇—装饰器详解】

Python Web应用程序构建的最佳实践&#xff1a;代码实例与深度解析 在当今数字时代&#xff0c;构建高效、可扩展的Web应用程序是开发者们的一项重要任务。Python&#xff0c;作为一种简洁、强大的编程语言&#xff0c;为Web开发提供了丰富的工具和框架。在本篇文章中&#xff…

力扣hot100:76.最小覆盖子串(滑动窗口)

本题使用滑动窗口解决&#xff0c;用right表示滑动窗口的右边界&#xff0c;left表示滑动窗口的左边界。寻找可行解&#xff0c;我们可以这样约定滑动窗口的意义&#xff1a;right指针向右移动&#xff0c;是使得滑动窗口找到可行解。left指针向右移动是为了更新窗口使得其可以…

MongoDB 可调节的一致性,其他数据库都不行系列 (白皮书 翻译)--1

开头还是介绍一下群&#xff0c;如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis, Oceanbase, Sql Server等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;&#xff08;…

Flutter 开发环境搭建-VS Code篇

1.准备环境 Java SDK 下载及安装Flutter SDK 安装及配置环境变量 下载地址将flutter sdk解压目录下的bin目录放到系统环境变量中 检查环境&#xff0c;在系统终端中输入&#xff1a; # 打印flutter sdk版本号 flutter --version# 检查flutter运行环境 flutter doctor第一次运…

linuxOPS基础_linux安装配置

Linux系统下载 Linux系统版本选择&#xff1a;CentOS7.6 x64&#xff0c;【镜像一般都是CentOS*.iso文件】 问题&#xff1a;为什么不选择最新版的8 版本&#xff1f; 7.x 目前依然是主流 7.x 的各种系统操作模式是基础 官网&#xff1a;https://www.centos.org/ &#xff0c;…