🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (93平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
🎃Redis(97平均质量分)https://blog.csdn.net/2301_80050796/category_12777129.html?spm=1001.2014.3001.5482
🐰RabbitMQ(97平均质量分) https://blog.csdn.net/2301_80050796/category_12792900.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
目录
1. 反射机制
1.1 定义
Java中的反射机制指的是在运行状态中,对于任意的一个类,我们都可以知道这个类所对应的属性和方法,对于任意的一个对象,都可以调用它的任意的方法和属性.既然可以拿到这些信息,那么我们就可以修改这些信息.这种动态获取信息以及动态调用对象方法的功能,我们称为Java语言的反射机制.
1.2 用途
- 在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量,方法或者是属性是私有的或者是只是对操作系统开放,这时候我们就可以利用Java的反射机制来获取所需要的私有的成员或者是方法.
- 反射最重要的用途就是开发各种通用的框架,比如在Spring中,我们将所有的Bean交给Spring容器管理,无论是XML配置的Bean还是注解配置的Bean,当我们从容器中获取Bean来进行DI注入的时候,容器会读取配置,而配置中给的就是类的信息,Spring根据这些信息,需要创建哪些Bean,Spring就动态创建这些类.
1.3 反射相关的类
类名 | 用途 |
---|---|
Class类 | 代表类的实体 |
Field类 | 代表类的成员变量/类的属性 |
Method类 | 代表类的方法 |
Constructor类 | 代表类的构造方法 |
1.3.1 Class类中的常用方法
Java文件编译之后,生成了.class文件,JVM此时就要去解读.class文件,被编译之后的Java文件同时也被JVM解析为了一个对象,这个对象所属的类就是java.lang.Class
,这样在当程序运行时,每个Java文件就最终变成了一个Class类对象的实例.我们通过Java的反射机制应用到这个实例,就可以获得,甚至去添加改变这个类的属性和动作,使得这个类成为一个动态的类.
下面是Class类的相关方法:
- 常用的获得类相关的方法(Class类相关方法)
方法 | 用途 |
---|---|
getClassLoader() | 获得类的加载器 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
static forName(String className) | 根据类名返回类的对象 |
newInstance() | 创建类的实例 |
getName() | 获得类的完整路径的名字 |
- 常用获得类中的属性相关的方法(Field类相关方法)
方法 | 用途 |
---|---|
getField(String name) | 获得某个公有的属性对象 |
getField() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
- 获得类中的构造器相关的方法(Constructor相关方法)
方法 | 用途 |
---|---|
getConstructor(Class…<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class…<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
- 获得类中方法相关的方法
方法 | 用途 |
---|---|
getMethod(String name, Class…<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class…<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
1.3.2 Constructor类中的常用方法
要想使用下面的这些方法,必须先要通过上面Class类中Constructor相关的方法获取到Constructor对象.
方法 | 用途 |
---|---|
getModifiers() | 得到构造方法的修饰符 |
getName() | 得到构造方法的名称 |
getParameterTypes() | 得到构造方法中参数的类型 |
newInstance(Object param…) | 向Constructor对象对应的构造方法中传入参数,实例化对象 |
setAccessible(boolean flag) | 设置构造方法是否可以被外部访问 |
1.3.3 Method类中的常用方法
方法 | 描述 |
---|---|
getModifiers() | 得到本方法的修饰符 |
getName() | 得到方法的名称 |
getParameterTypes() | 得到方法的全部参数类型 |
getReturnType() | 得到方法的返回值类型 |
invoke(Object o,Object args…) | 调用指定对象的对应方法,并传入参数 |
setAccessible(boolean flag) | 设置一个方法是否可以被外部访问 |
1.3.4 Field类中的常用方法
方法 | 描述 |
---|---|
getModifiers() | 得到属性的修饰符 |
getName() | 得到属性的名称 |
setAccessible(boolean flag) | 设置一个属性是否可以被外部访问 |
get(Object o) | 得到一个对象中属性的具体内容 |
set(Object o) | 设置一个对象中属性的具体内容 |
1.3.5 反射使用示例
- 获得Classes对象的三种方式
在反射之前,我们需要做的第一步就是先拿到当前需要反射的类的Class对象,然后通过Class对象的核心方法,达到反射的目的,即:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到那么,我们就可以修改部分类型信息。
第一种,使用Class.forName(“类的全路径名”),前提就是以明确类的全路径名.第二种,使用.class方法,仅适合在编译前就已经明确需要操作的Class.第三种,使用类对应实例的getClass()方法.
class Student {
private String name = "xiaoming";
private int age = 20;
public Student(){
System.out.println("student()");
}
private Student(int age, String name) {
this.age = age;
this.name = name;
System.out.println("student(age,name)");
}
private void eat(){
System.out.println("我在吃饭");
}
public void sleep(){
System.out.println("我在睡觉");
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
private void function(String str) {
System.out.println(str);
}
}
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Student student = new Student();
Class<? extends Student> aClass = student.getClass();//通过类所创建的对象来获取到Class对象
Class<Student> aClass1 = Student.class;//通过.class方法来获取Class对象
Class<?> student1 = Class.forName("Student");//通过静态方法forName来获取类对象
System.out.println(aClass);
System.out.println(aClass1);
System.out.println(student1);
}
}
我们发现最后打印的Class对象都是Student.
2. 反射的使用
我们通过反射拿到Student类中的私有方法和属性之后来进行一系列的操作
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() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class<?> student = Class.forName("Student");
Object object = student.newInstance();
Student student1 = (Student) object;
System.out.println("获得到了学生对象"+student1);
}
//反射私有的构造方法
public static void reflectPrivateConstructor() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Class<?> student = Class.forName("Student");
Constructor<?> declaredConstructor = student.getDeclaredConstructor(int.class, String.class);
declaredConstructor.setAccessible(true);//可以修改访问权限
Object object = declaredConstructor.newInstance(22, "wangli");//使用修改权限之后的构造器来构造对象
Student student1 = (Student) object;
System.out.println("修改构造器访问权限来构造对象:"+student1);
}
//反射私有属性
public static void reflectPrivateField() throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException {
Class<?> student = Class.forName("Student");
Field age = student.getDeclaredField("age");//根据Class类对象获取到age属性
Student student1 = (Student) student.newInstance();//创建一个student对象
age.setAccessible(true);//修改访问权限
age.set(student1,21);//设置student1的age为21
System.out.println("反射属性修改了age "+student1);
}
//反射私有方法
public static void reflectPrivateMethod() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Class<?> student = Class.forName("Student");
Method function = student.getDeclaredMethod("function", String.class);//获取到类中的function方法
function.setAccessible(true);//修改访问权限
Student student1 = (Student) student.newInstance();
function.invoke(student1,"function参数");//调用student1中的function方法,并传入参数
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, NoSuchFieldException {
ReflectClassDemo.reflectNewInstance();
ReflectClassDemo.reflectPrivateConstructor();
ReflectClassDemo.reflectPrivateField();
ReflectClassDemo.reflectPrivateMethod();
}
}
运行结果:
1.4 反射的优点和缺点
优点:
1.** 对于任意一个类,都可以知道这个类的所有属性和方法,对于任意一个对象**,都能够调用它的任意一个方法.
2. 增加程序的灵活性和扩展性,降低耦合性,提高自适应能力.
3. 反射已经运用在力很多流行的框架中,比如Spring.
缺点:
1. 使用反射会有效率问题,会导致程序的效率降低.
2. 反射技术绕过了源代码的技术,因而会带来维护问题,反射代码比相应的直接代码更加复杂.
2. 枚举的使用
2.1 概念
枚举是在JDK1.5以后引入的,主要的用途就是:将一组常量组织起来.我们在有枚举之前一般使用的是final
的形式来定义常量的.
private static final int RED = 1;
private static final int GREEN = 2;
private static final int BLACK = 3;
现在我们可以直接用枚举的形式来进行组织,这样一来,就拥有了类型,枚举类型,而不是普通的整体的整形1.
public enum TestEnum {
RED,BLACK,GREEN
}
优点就是可以将常量统一组织起来进行管理,使用场景一般有错误状态代码,消息类型等.
本质: 是java.lang.Enum
的子类,我们自己写的子类,就算没有显式的继承Enum
,但是也默认继承了这个类,枚举类本质上就是一个类,类中的每一个对象都相当于是这个枚举类的一个实例,但是这些对象都是写死的.
2.2 使用
- 可以直接使用枚举类名+枚举类中的对象来获取到枚举类中的对象.
public enum TestEnum {
RED,BLACK,GREEN;
public static void main(String[] args) {
switch (TestEnum.BLACK){
case RED :
System.out.println("red");
break;
case BLACK:
System.out.println("black");
break;
case GREEN:
System.out.println("green");
break;
}
}
}
运行结果:
2. Enum类常用方法
方法名称 | 描述 |
---|---|
values() | 以数组的形式返回枚举类型的所有成员 |
static valueOf(String name) | 把普通字符串转换为指定枚举类中的枚举实例 |
ordinal() | 获取枚举成员的索引位置 |
使用示例:
public enum TestEnum {
RED,BLACK,GREEN;
public static void main(String[] args) {
TestEnum[] testEnums = TestEnum.values();
for (TestEnum testEnum :testEnums) {
System.out.println(testEnum + " " + testEnum.ordinal());
}
System.out.println(TestEnum.valueOf("BLACK"));
System.out.println(TestEnum.valueOf("GREEN"));
System.out.println(TestEnum.valueOf("RED"));
}
}
运行结果:
我们刚刚说过,在java中枚举其实就是一个类,所以我们在定义枚举类的时候,还可以在枚举类中定义成员变量(一般使用final修饰),构造方法,静态方法,但是枚举类中不可以有成员方法.我们在枚举一个枚举类实例的时候,需要在枚举实例后面加上括号,括号中用来传入构造方法的参数.
下面是使用示例:
public enum TestEnum2 {
RED(0,"red"),GREEN(1,"green"),BLACK(2,"black");
public final int key;
public final String name;
TestEnum2(int key, String name) {
this.key = key;
this.name = name;
}
public static TestEnum2 getFromKey(int key){
for (TestEnum2 testEnum2 :TestEnum2.values()) {
if (testEnum2.key == key){
return testEnum2;
}
}
return null;
}
public static void main(String[] args) {
System.out.println(TestEnum2.getFromKey(2));
}
}
3. lambda表达式
lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块).
3.1 语法
(param…)->expression或者(param…)->{statements…}.
Lambda表达式由三部分组成:
- param(参数):类似方法中的参数列表,这里的参数类型可以明确的声明也可不声明而由JVM隐含的推断。
- ->:可以理解为"被用于"的意思.
- 方法体:可以是表达式也可以是代码块,代码块可返回一个值或者什么都不反回,这里的代码块块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不反回。
使用示例:
// 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 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
语法还可以做出以下的精简:
- 参数类型可以省略,如果需要省略,每个参数的类型都要省略。
- 参数的小括号里面只有一个参数,那么小括号可以省略
- 如果方法体当中只有一句代码,那么大括号可以省略
- 如果方法体中只有一条语句,且是return语句,那么大括号可以省略,且去掉return关键字。
3.2 从函数式接口开始理解lambda表达式
要理解Lambda表达式,首先需要了解什么是函数式接口,函数式接口就是:一个接口中有且只有一个抽象方法.如果我们在某个接口上声明了@FunctionalInterface
注解,那么编译器就会按照函数式接口的定义来要求这个接口,如果有两个抽象方法,程序编译会报错.
@FunctionalInterface
public interface Functional {
void test();
}
接下来我们来理解lambda表达式的本质:
首先我们来准备一些函数式接口:
@FunctionalInterface
public interface {
void test();
}
@FunctionalInterface
public interface OneParamNoReturn {
void test(int a);
}
@FunctionalInterface
public interface OneParamOneReturn {
int test(int a);
}
lambda表达式可以理解为:lambda就是匿名内部类的简化,实际上是创建了一个类,实现了接口,重写的接口的方法.
如果我们使用匿名内部类的方法来调用接口:
public class Test {
NoParamNoReturn noParamNoReturn = new NoParamNoReturn() {
@Override
public void test() {
System.out.println("调用NoParamNoReturn接口");
}
};
OneParamNoReturn oneParamNoReturn = new OneParamNoReturn() {
@Override
public void test(int a) {
System.out.println("调用OneParamNoReturn接口:"+a);
}
};
OneParamOneReturn oneParamOneReturn = new OneParamOneReturn() {
@Override
public int test(int a) {
System.out.println("调用OneParamOneReturn接口:"+a);
return 0;
}
};
}
如果我们改为lambda表达式的形式:
public class Test {
NoParamNoReturn noParamNoReturn = () -> System.out.println("调用NoParamNoReturn接口");
OneParamNoReturn oneParamNoReturn = a -> System.out.println("调用OneParamNoReturn接口:"+a);
OneParamOneReturn oneParamOneReturn = a -> {
System.out.println("调用OneParamOneReturn接口:"+a);
return 0;
};
}
3.3 在集合中的应用
forEach()
方法
该方法在接口Iterable
中,源码如下:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
该方法表示对容器中的每一个元素执行Consumer.accept()
中指定的动作.下面是使用forEach遍历List中的元素的代码示例:
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
}
}
我们接下来把它简化为lambda表达式:
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.forEach(integer -> System.out.println(integer));
}
}
运行结果:
sort()
方法
sort的源码需要根据传入比较器©来指定元素之间的比较原则.
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
使用示例:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("lambda");
//排序的时候比如按照长度排序
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length()-o2.length();
}
});
}
可以用lambda表达式简化为:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("lambda");
//排序的时候比如按照长度排序
list.sort((o1, o2) -> o1.length()-o2.length());
System.out.println(list);
}
运行结果:
- Map中的
foreach()
源码如下:
首先把Map转换为可遍历的Set<Map.Entry<K, V>>
类型,之后再对其中的元素执行BiConsumer.accept()
中的一系列操作.
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch (IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
使用示例:
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
map.put(1,"hello");
map.put(2,"world");
map.put(3,"lambda");
map.forEach(new BiConsumer<Integer, String>() {
@Override
public void accept(Integer integer, String s) {
System.out.println(integer+"->"+s);
}
});
}
使用lambda表达式简化之后:
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
map.put(1,"hello");
map.put(2,"world");
map.put(3,"lambda");
map.forEach((integer, s) -> System.out.println(integer + "->" + s));
}
运行结果:
3.4 总结
Lambda表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读。
优点:
- 代码简洁,开发迅速
- 方便函数式编程
- 非常容易进行并行计算
- Java 引入 Lambda,改善了集合操作
缺点: - 代码可读性变差
- 在非并行计算中,很多计算未必有传统的 for 性能要高
- 不容易进行调试