[数据结构] 反射,枚举与lambda表达式
目录
1. 反射
1.1 概念
Java的反射是在运行时检查,访问和修改类的接口,字段和方法的机制。
1.2 反射相关的类

1.2.1 Class类
java程序再运行后生成一个.class文件,而这个文件会被JVM接收将该文件转换为一个对象java.long.Class。而每个java文件都会变成对应一个Class类的实例,我们可以通过反射机制去改变这个实例的属性和方法。
1.2.2 Class类相关的方法
获得类的常用方法(Class类):

获取类中的属性的常用方法(Field类):

获取类中的构造器相关的方法(Constructor类):

获取类中的方法的相关方法(Method类):

获取该类中注解相关的方法:

1.3 反射的举例
1.3.1 获取Class对象的三种方式
当我们想利用反射去修改一个类时,我们首先要获得这个类在JVM中对应的对象,找到这个对象就可以修改该类中的部分方法和字段了。
下面是获取Class对象的三种方式:
方法一:是利用实例化对象的getClass方法。
方法二:是利用利用类名.class获得的。
方法三:利用Class类里面的forName(“类的路径”)方法获得的,路径有包的加入包:包名.类名,这种方法会抛出ClassNotFoundException异常需要处理。
package reflectdemo;
class Student {
public String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void ReadBook() {
System.out.println(name + "正在读书");
}
private void Sleep() {
System.out.println(name + "正在睡觉");
}
}
public class TestDemo {
public static void main(String[] args) {
Student student1 = new Student("张三",18);
//方法一:使用类对象的getClass()方法
Class<?> c1 = student1.getClass();
//方法二:通过类名.class获得
Class<?> c2 = Student.class;
//方法三:通过Class类里面的forName静态方法获得
Class<?> c3 = null;
try {
c3 = Class.forName("reflectdemo.Studnet");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
1.3.2 反射的使用
所有和反射相关的包都存储在java.long.reflect文件下面。
下面是创建Student类的对象,反射构造方法,反射私有的方法,反射私有的属性的代码:
package reflectdemo;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectClassDemo {
//创建Student类的对象
public static void reflectNewInstance() {
Class<?> stu;
//获取类的对象
try {
stu = Class.forName("reflectdemo.Student");
Student student = (Student)stu.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() {
Class<?> stu;
try {
//获取类的对象
stu = Class.forName("reflectdemo.Student");
//获取类的构造方法
Constructor<?> constructor = stu.getDeclaredConstructor(String.class,int.class);
//私有可以访问
constructor.setAccessible(true);
//修改构造方法
Student student = (Student)constructor.newInstance("张三",18);
System.out.println(student);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
//反射私有属性
public static void reflectPrivateField() {
Class<?> stu;
try {
stu = Class.forName("reflectdemo.Student");
Field field = stu.getDeclaredField("name");
field.setAccessible(true);
Student student = (Student)stu.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() {
Class<?> stu;
//获取类对象
try {
stu = Class.forName("reflectdemo.Student");
//获取类的私有方法
Method method = stu.getDeclaredMethod("function",String.class);
//访问私有
method.setAccessible(true);
//创建对象
Student student = (Student)stu.newInstance();
//修改方法参数
method.invoke(student,"你好呀");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException 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) {
//创建class对象
reflectNewInstance();
//获取构造方法
//reflectPrivateConstructor();
//获取私有属性
//reflectPrivateField();
//获取私有方法
//reflectPrivateMethod();
}
}
2. 枚举
2.1 概念
枚举是用来统一存储和管理常量的。
下面是一个枚举:
public enum TestEnum {
RED,YELLOW,BLACK;
}
枚举可以将常量统一存储起来。
自己写的枚举类会默认继承java.long.Enum类。
2.2 使用
1. switch语句
public enum TestEnum {
RED,YELLOW,BLACK;
public static void main(String[] args) {
TestEnum testEnum = TestEnum.RED;
switch(testEnum) {
case RED:
System.out.println("red");
break;
case YELLOW:
System.out.println("yellow");
break;
case BLACK:
System.out.println("black");
break;
default:
break;
}
}
}
2.3 常用方法
Enum类中常用的方法:

使用实例:
public static void main(String[] args) {
TestEnum[] testEnum1 = TestEnum.values();
for (int i = 0; i < testEnum1.length; i++) {
System.out.print(testEnum1[i] + " ");
}
System.out.println();
System.out.println(RED.ordinal());
System.out.println("===============");
TestEnum tmp = TestEnum.valueOf("RED");
System.out.println(tmp);
TestEnum testEnum2 = TestEnum.RED;
TestEnum testEnum3 = TestEnum.YELLOW;
System.out.println(testEnum2.compareTo(testEnum3));
}
2.4 枚举的构造方法
枚举的构造方法是私有的,当枚举有参数是,需要设置构造方法。
public enum TestEnum {
RED("red",1),YELLOW("yellow",2),BLACK("black",3);
public String color;
public int key;
TestEnum(String color,int key) {
this.color = color;
this.key = key;
}
}
枚举常量更简单,但是不能继承拓展。
2.5 枚举与反射
我们是否可以通过反射获得枚举的构造方法呢?
我们写了以下代码尝试:
public enum TestEnum {
RED("red",1),YELLOW("yellow",2),BLACK("black",3);
public String color;
public int key;
TestEnum(String color,int key) {
this.color = color;
this.key = key;
}
public static void reflectPrivateConstructor() {
Class<?> tmp;
try {
tmp = Class.forName("enumdemo.TestEnum");
Constructor<?> constructor =
tmp.getDeclaredConstructor(String.class,int.class,String.class,int.class);
constructor.setAccessible(true);
TestEnum testEnum = (TestEnum)constructor.newInstance("绿色",4,"子类参数",88);
System.out.println(testEnum);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException 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) {
reflectPrivateConstructor();
}
}
上面代码如果不给构造方法传四个参数的话,运行时会报下面的错误:

因为所有的自定义枚举类都默认继承Enum类,而Enum类中存在一个构造方法:

需要给父类也传入两个参数,而前两个参数是给父类传的参数,后两个参数是给自定义的类传的参数。
此时运行上面代码剩下一个报错:

我们可以查看下报错信息:

Java中会保证枚举类的唯一性和安全性,会检查以下该类是否是枚举类,如果反射访问就会报错。
2.6 单例模式
单例模式是确保每一个对象只有一个实例。
2.6.1 饿汉式
在程序运行时就实例化一个对象,无论是否会使用。
代码如下:
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
}
public Singleton getSingleton() {
return singleton;
}
}
2.6.2 懒汉式
对象在首次访问时才实例化对象。
代码如下:
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public Singleton getSingleton() {
if(singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
2.6.3 静态内部类实现
public class Singleton {
private Singleton() {
}
public static Singleton getSingleton() {
return UserSingletonHolder.STR;
}
//静态内部类
private static class UserSingletonHolder {
private static final Singleton STR = new Singleton();
}
}
2.6.4 枚举实现
public enum NewEnum {
INSTANCE;
public NewEnum getInstance() {
return INSTANCE;
}
public void print() {
System.out.println("枚举实现");
}
}
3. Lambda表达式
3.1 语法
基本语法:(parameters)->expression或(parameters)->{statements;}
parameters:是需要传入的参数,当只有一个参数时()可以不写。
-> :被用于的意思
方法体:也就相当于前面传入的参数在方法体里面调用得到的返回值。
举例:
1.不需要参数
() -> 2
2. 一个参数
x -> 2 * x
3. 两个参数
(x,y) -> x+y
4. 两个整型
(int x, int y) -> x*y
5. 接收一个String类型,打印
(String s) -> System.out.print(s);
3.2 函数式接口
如果一个接口只有一个抽象方法,那么这个接口就是函数式接口。
我们可以用@FunctionalInterface 来进行注解,用来提醒你这个接口是函数式接口。
里面包含default方法也可以。
3.3 Lambda表达式的基本使用
Lambda表示式其实就是匿名内部类的简化,实例化一个类,实现了接口,并重写了接口里面的方法。
这里我们先创建几个函数式接口:
//无返回值无参数
@FunctionalInterface
interface NoParameterNoReturn {
void test();
}
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn {
void test(int a);
}
//无返回值多个参数
@FunctionalInterface
interface MoreParameterNoReturn {
void test(int a,int b);
}
//有返回值无参数
@FunctionalInterface
interface NoParameterReturn {
int test();
}
//有返回值一个参数
@FunctionalInterface
interface OneParameterReturn {
int test(int a);
}
//有返回值多参数
@FunctionalInterface
interface MoreParameterReturn {
int test(int a,int b);
}
匿名内部类调用方式如下:
NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn() {
public void test() {
System.out.println("nihao");
}
};
如果使用Lambda表达式可以写成下面代码:
NoParameterNoReturn noParameterNoReturn = () -> {
System.out.println("haha");
};
OneParameterNoReturn oneParameterNoReturn = (int a) -> {
System.out.println(a);
};
MoreParameterNoReturn moreParameterNoReturn = (int a, int b) -> {
System.out.println(a + b);
};
NoParameterReturn noParameterReturn = () -> {
System.out.println("hehe");
return 10;
};
3.3 语法精简
- 当参数列表中只有一个参数时,可以省略()。
- 参数列表的参数可以省略参数类型,要省略都省略。
- 方法体中只有一条代码,大括号可以省略。
- 方法体中只有一条语句,并且是return语句,大括号可以省略,并且return可以省略。
3.4 变量捕获
3.4.1 匿名内部类的变量捕获
class School {
public void func() {
System.out.println("aaaaa");
}
}
public class Test {
School school = new School() {
int a = 10;
@Override
public void func() {
System.out.println("重写func方法" + a);
}
};
这里的a就是变量捕获,如果我们要在重写的方法中调用匿名内部类中的变量,这个变量要么是被final修饰,要么调用的这个常量不能再修改了。
3.4.2 Lambda表达式的变量捕获
这里a不是写在代码块中的,而是在代码块外面。
//无返回值无参数
@FunctionalInterface
interface NoParameterNoReturn {
void test();
}
public class Test {
int a = 10;
NoParameterNoReturn noParameterNoReturn = () -> {
System.out.println("haha" + a);
};
}
3.5 Lambda表达式在集合中的使用
在集合当中也存在一些接口配合Lambda表达式使用:

其中Collection里面的forEach()方法是从java.long.Iterable接口中拿来的。
3.5.1 Collection接口
forEach方法演示,该方法的原型在Iterable接口中:

该方法的作用是对容器里面的每个元素执行action操作:
下面是将这个链表遍历一遍:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.print(s + " ");
}
});
}
我们可以把上面的代码修改成利用lambda表达式的形式:
list.forEach(s -> {
System.out.print(s + " ");
});
3.5.2 List接口
sort()方法演示:
作用是进行排序
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
System.out.println(list);
}
上面的代码我们可以改写成用lambda表达式来进行编写:
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
list.sort((o1,o2) -> (o2.compareTo(o1)));
System.out.println(list);
3.5.3 Map接口
HasMap的forEach方法的使用:
该方法的作用是Map中的每个映射执行action操作。
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
map.put("aaa",1);
map.put("bbb",2);
map.put("ccc",3);
map.put("ddd",4);
map.forEach(new BiConsumer<String, Integer>() {
@Override
public void accept(String s, Integer integer) {
System.out.println("K:" + s + " " + "V:" + integer);
}
});
}
上面代码可以利用lambda修改成:
map.forEach((k,v) -> {
System.out.println("K:" + k + " " + "V:" + v);
});
总的来说lambda可以让我们的代码更加简单,容易进行并行计算,可以改善集合操作,
但是会导致代码的可读性变差,不容易进行调试。
更多推荐



所有评论(0)