目录
1、反射
1.1 基本概念
1.2 反射相关的类
1.3 创建 Class 对象
1.4 反射的使用
1.4.1 通过反射创建对象:
1.4.2 获取私有的构造方法
1.4.3 获取私有的成员变量
1.4.4 获取私有的方法
1.5 总结
2、枚举
2.1 认识枚举
2.2 使用枚举
2.3 枚举与反射的那些事
3、Lambda 表达式
3.1 认识 Lambda 表达式
3.2 语法
3.3 函数式接口
3.4 Lambda 的基本使用
Java的反射(reflection)机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到,那么我们就可以修改部分类型信息;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射(reflection)机制。
类名 | 用途 |
---|---|
Class 类 | 代表类的实体,在运行的Java程序中表示类和接口 |
Field 类 | 代表类的成员变量/类的属性 |
Method 类 | 代表类的方法 |
Constructor 类 | 代表类的构造方法 |
每个类里面都有很多相关的方法啊,这里具体的方法我就不一一列举出来了,详细的可以去查看 Java 的官方文档。
创建一个 Class 对象,通常使用以下三种方法:
1. 调用某个对象里面的 getClass 方法:
public static void main(String[] args) {
Student student = new Student();
Class> c1 = student.getClass();
}
2. 采取类名.class的方法:
public static void main(String[] args) {
Class> c2 = Student.class; //这种方法说明每个类默认隐式包含一个静态的成员变量 class
}
3. 通过 Class.forName() 获取:
public static void main(String[] args) {
Class> c3 = null;
try {
c3 = Class.forName("Student");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
注意:这个 forName 中要放传入类的完整路径,比如如果是 String 的话,即:java.lang.String
这里我们自定义一个 Student 类:
public class Student {
private String name;
private int age;
public Student() {
System.out.println();
}
private Student(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println(name + "正在吃饭!");
}
private void sleep() {
System.out.println(name + "正在睡觉!");
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void reflectClassDemo() {
try {
Class> c = Class.forName("Student");
Object objectStudent = c.newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
public static void reflectPrivateConstructor() {
try {
Class> c = Class.forName("Student");
Constructor> constructor = c.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); //设置为true后可修改访问权限
Object objectStudent = constructor.newInstance("张三", 12);
System.out.println(objectStudent);
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
public static void reflectPrivateField() {
try {
Class> c = Class.forName("Student");
Field field = c.getDeclaredField("name"); //获取名为 name 的成员变量
field.setAccessible(true);
Object student = c.newInstance();
field.set(student, "张三"); //将 student 对象的name 设置成 "张三"
System.out.println(field.getName());
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
public static void reflectPrivateMethod() {
try {
Class> c = Class.forName("Student");
Method method = c.getDeclaredMethod("eat");
method.setAccessible(true);
//方法有参数的写法:
//Method methodStudent = classStudent.getDeclaredMethod("function",String.class);
Object objectStudent = c.newInstance();
Student student = (Student)objectStudent;
System.out.println("私有方法方法名: " + method.getName());
method.invoke(objectStudent); // 调用获取到的方法, student对象中的
} catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
}
在反射眼中,对应任意一个类,都能够知道这个类的属性和方法,对于任意一个对象,都能调用它任意一个方法, 这样使程序的灵活性大大提高,以及可扩展性,但是这样一来,似乎就在告诉大家,之前封装的一些方法和属性在反射面前就是一个摆设。
反射是一把双刃剑,是一种非常规的编程手段,不到必要的时候,不建议使用反射,使用反射会有效率问题。会导致程序效率降低,而且反射技术绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂 。
枚举顾名思义,一一列举,作用将一组常量组织起来,Java中常量就是常量,并没有常变量这种说法,利用我们现在的知识,如果让你定义三个常量,分别表示 红色,黑色,绿色,你可能会这样定义:
public static final int RED = 1;
public static final int BLACK = 3;
public static final int GREEN = 3;
这样的话,代码中出现的 1,有可能会被误认为是 RED,是可能会出现歧义的,于是就有一种类型枚举来进行组织:
public enum MyEnum {
RED, BLACK, GREEN;
}
这样里面定义的每个都是枚举类型,不再是普通的数字了,我们自己写的 enum 会默认继承 Enum 类,所以不用显示的去继承 Enum 这个抽象类。
public static void main(String[] args) {
MyEnum myEnum = MyEnum.BLACK;
switch (myEnum) {
case RED:
System.out.println("红色!");
break;
case BLACK:
System.out.println("黑色!");
break;
case GREEN:
System.out.println("绿色!");
break;
default:
System.out.println("其他颜色!");
break;
}
}
使用场景:错误状态码,消息类型,颜色的划分,状态机等等....
Enum 类的常用方法:
方法名称 | 描述 |
---|---|
values() | 以数组的形式返回枚举类型的所有成员 |
ordinal() | 获取枚举成员的索引位置 |
valueOf() | 将普通字符串转换为枚举类型 |
compareTo() | 比较两个枚举成员在定义时候的顺序 |
枚举是一种类型,也就是Java中的枚举就是一个类,那么就可以这样去写代码:
public enum MyEnum {
RED("红色", 1), BLACK("黑色", 2), GREEN("绿色", 3);
private String name;
private int key;
private MyEnum(String name, int key) {
this.name = name;
this.key = key;
}
}
枚举的构造方法默认是私有的.
枚举常量是更简单安全的,安全体现在哪,马上就说到了,而且枚举拥有内置方法,使用起来方便,缺点也有,由于Java中支持单继承,因此枚举类型不能再继承其他类,无法扩展。
通过反射,能否拿到枚举的私有构造方法呢?我们写个代码来测试一下:
public class TestMyEnum {
public static void main(String[] args) {
try {
Class> c = Class.forName("MyEnum");
Constructor> constructor = c.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
Object myEnum = constructor.newInstance("红色", 123);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
这里居然报错了,报错信息提示没有该构造方法???为什么会没有呢,我们的 MyEnum 默认继承了 Enum 类,实例化子类对象的时候,先调用父类的构造方法,那么这里我们就去看一下 Enum 的构造方法。
Enum 只有这一个构造方法,还带有两个参数,但是在 JavaSE 的学习中,如果在子类的构造方法中,没有显式写明 super(),则会在子类构造方法第一行默认有 super(),也就是调用父类的无参构造,枚举比较特殊虽然我们写的是两个,但默认他还添加了 name,和 ordinal 参数。也就是说,我们需要提供四个参数:
Constructor> constructor = c.getDeclaredConstructor(String.class, int.class, String.class, int.class);
constructor.setAccessible(true);
Object myEnum = constructor.newInstance("红色", 123, "红色", 321);
这里还是报错了,但是这里的报错是第10行的,newInstance 方法报错,那么我们就进入该方法源码去一看究竟:
在JavaSE语法上,if 中的 & 会被认为 &&,所以枚举在这里被过滤了,这也就是你不能通过反射获取到枚举类的实例!
所以在这里可以发现,枚举是安全的,可以避免反射的问题。
Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码 块)。 Lambda 表达式(Lambda expression),基于数学中的 λ 演算得名,也可称为闭包(Closure)。
基本语法: (parameters) -> expression 或 (parameters) -> { statements; }
Lambda表达式由三部分组成:
- 1. paramaters:类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明 也可不声明而由JVM隐含的推断。另外当只有一个推断类型时可以省略掉圆括号。 2. ->:可理解为“被用于”的意思
- 3. 方法体:可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不反 回,这里的代码块块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不反回。
函数式接口:一个接口中,只有一个抽象方法。
@FunctionalInterface 注解:如果我们在某个方法上声明了该注解,那么编译器就会按照函数式接口的定义来要求该接口。如果不符合函数式接口的语法,那么则会报错!
例子:
@FunctionalInterface
public interface TestFuncInterface {
void work();
}
也可也这样写:
@FunctionalInterface
public interface TestFuncInterface {
void work();
default void test() {
System.out.println("hello");
}
}
在 JDK 1.8 中,default 默认方法可以有具体的实现。
//无返回值无参数
@FunctionalInterface
interface NoParameterNoReturn {
void work();
}
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn {
void work(int a);
}
//无返回值多个参数
@FunctionalInterface
interface MoreParameterNoReturn {
void work(int a,int b);
}
//有返回值无参数
@FunctionalInterface
interface NoParameterReturn {
int work();
}
//有返回值一个参数
@FunctionalInterface
interface OneParameterReturn {
int work(int a);
}
//有返回值多参数
@FunctionalInterface
interface MoreParameterReturn {
int work(int a,int b);
}
public class TestLambda {
public static void main(String[] args) {
NoParameterNoReturn n1 = () -> System.out.println("NoParameterNoReturn");
n1.work();
// 只有一个参数, 可以省略小括号
OneParameterNoReturn n2 = x -> {
System.out.println(x + "OneParameterNoReturn");
System.out.println("多条语句则不能省略大括号!!!");
};
n2.work(5);
MoreParameterNoReturn n3 = (x, y) -> System.out.println(x + y + "OneParameterNoReturn");
n3.work(5, 8);
NoParameterReturn n4 = () -> 88; //只有 return 一条语句可以省略 return
int ret1 = n4.work();
OneParameterReturn n5 = (x) -> {
System.out.println("OneParameterReturn");
return x + 10; //多条语句时, return 和 {} 都不能省略
};
int ret2 = n5.work(12);
// 如果不省略形参类型, 必须都不省略, 省略的话必须全部省略
MoreParameterReturn n6 = (int x, int y) -> (x + y + 8);
int ret3 = n6.work(5, 5);
}
}
Lambda表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读。至于 Lambda 的更多使用,会在后续文章中慢慢体现出来。这里我们了解下语法即可。
优点:
- 代码简洁,开发迅速
- 方便函数式编程
- 非常容易进行并行计算
- ava 引入 Lambda,改善了集合操作(比如传比较器)
缺点:
- 代码可读性变差
- 在非并行计算中,很多计算未必有传统的 for 性能要高
- 不容易进行调试
下期预告:【MySQL】数据库的基本认识