Hello, Hello~ 亲爱的朋友们👋👋,这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章,请别吝啬你的点赞❤️❤️和收藏📖📖。如果你对我的内容感兴趣,记得关注我👀👀以便不错过每一篇精彩。 当然,如果在阅读中发现任何问题或疑问,我非常欢迎你在评论区留言指正🗨️🗨️。让我们共同努力,一起进步! 加油,一起CHIN UP!💪💪
Java反射是Java语言的一种特性,它允许程序在运行时自我检查并对内部成员进行操作。这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。具体来说,反射机制允许在运行状态中 对于任意一个类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能够调用它的任意方法和属性,并且能改变它的属性。
Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。其本质是JVM得到Class对象之后,再通过Class对象进行反编译,从而获取对象的各种信息。
其中有三种方法:
而当获取到class类时再用其获取原本的类的对象实例我们就可以用
newInstance() //创建一个类的实例
代码如下:
public class Test {
public static void main(String[] args) throws Exception{
//方法1:通过 Class.forName("类的全路径");获取Class
Class<?> class1 = Class.forName("Student");
//方法2:通过 类名.class 获取Class
Class<?> class2 = Student.class;
//方法3:通过 对象名.getClass(); 获取Class
Student student = new Student("E棉", "35", 17);
Class<?> class3 = student.getClass();
try{
Student student1 = (Student) class3.newInstance();
}catch (Exception e) {}
}
}
class Student{
private String name;
private String number;
public String sex;
public int age;
public Student(String name, String number, int age) {
this.name = name;
this.number = number;
this.age = age;
}
public Student(String name){
this.name = name;
}
private Student(String name, String number){
this.name = name;
this.number = number;
}
private void schooling(){
System.out.println("去上学");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
这里我们出现了三个现象:
1.对于forname其实有编译时异常ClassNotFoundException这个隐患,所以需要用throws将其抛出。
这里涉及异常知识点,如果不懂可以看我的异常文章,讲的很清楚
调用Class的方法一般会返回三种类型的对象
下面是Class类的一些常用方法
获取成员变量getFields() //获取所有公开的成员变量(返回一个数组) getDeclaredFields() //获取所有本类定义的成员变量,包括私有(返回一个数组) getField(变量名) //获取指定公共属性的Field对象 getDeclaredField(变量名) //获取指定包括私有,
获取成员方法
getMethods() //获取所有公开的方法(返回一个数组)
getMethod(方法名,参数类型列表) //获取指定公开方法的Method对象
getDeclaredMethods() //获取所有本类定义的的方法,包括私有(返回一个数组)
getDeclaredMethod(方法名,int.class,String.class) //获取指定包括私有
获取构造方法
getConstructor(参数类型列表) //获取公开的构造方法
getConstructors() //获取所有的公开的构造方法(返回一个数组)
getDeclaredConstructors() //获取所有的构造方法,包括私有(返回一个数组)
getDeclaredConstructor(int.class,String.class) //获取指定包括私有
这里我们获取一个类中的私有成员变量并对其进行修改。
public class Test2 {
public static void main(String[] args) throws NoSuchFieldException,ClassNotFoundException,IllegalAccessException{
Class<?> studentClass = Class.forName("Student");
Field field = studentClass.getDeclaredField("name");
field.setAccessible(true);
Student student=new Student("lq","102",18);
field.set(student, "max");
System.out.println(student);
}
}
class Student{
private String name;
private String number;
public String sex;
public int age;
public Student(String name, String number, int age) {
this.name = name;
this.number = number;
this.age = age;
}
public Student(String name){
this.name = name;
}
private Student(String name, String number){
this.name = name;
this.number = number;
}
private void schooling(){
System.out.println("去上学");
}
public String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
需要注意的是,如果我们需要修改私有变量的话,就要多加一个代码:
field.setAccessible(true);
这里我们使用一个类中的私有成员方法。
public class Test3 {
public static void main(String[] args) throws NoSuchMethodException,InvocationTargetException,
IllegalAccessException,ClassNotFoundException
{
Class<?> studentClass = Class.forName("Student");
Method method = studentClass.getDeclaredMethod("setName", String.class);
method.setAccessible(true);
Student student=new Student("lq","102",18);
method.invoke(student, "max");
System.out.println(student);
}
}
class Student{
private String name;
private String number;
public String sex;
public int age;
public Student(String name, String number, int age) {
this.name = name;
this.number = number;
this.age = age;
}
public Student(String name){
this.name = name;
}
private Student(String name, String number){
this.name = name;
this.number = number;
}
private void schooling(){
System.out.println("去上学");
}
public String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
同理,需要注意的是,如果我们需要使用私有方法的话,就要多加一个代码:
method.setAccessible(true);
这里我们使用一个类中的私有构造方法,跟前面大不相同。
public class Test4 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException,IllegalAccessException,ClassNotFoundException,InstantiationException{
Class<?> studentClass = Class.forName("Student");
Constructor constructor = studentClass.getDeclaredConstructor(String.class, String.class);
constructor.setAccessible(true);
Student student= (Student) constructor.newInstance("lq","102");
System.out.println(student);
}
}
class Student{
private String name;
private String number;
public String sex;
public int age;
public Student(String name, String number, int age) {
this.name = name;
this.number = number;
this.age = age;
}
public Student(String name){
this.name = name;
}
private Student(String name, String number){
this.name = name;
this.number = number;
}
private void schooling(){
System.out.println("去上学");
}
public String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
同理私有时依旧需要该方法
constructor.setAccessible(true);
因为用该构造方法不会抛出异常,只会存在异常的隐患,所以相较于之前的直接用class.newInstance()会抛出异常,我们更倾向于用这个。
优点: 1. 对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法 2. 增加程序的灵活性和扩展性,降低耦合性,提高自适应能力 3. 反射已经运用在了很多流行框架如:Struts、Hibernate、Spring 等等。 缺点: 1. 使用反射会有效率问题。会导致程序效率降低。具体参考 相关文章 2. 反射技术绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂
在 Java 中,可以通过关键字 enum
来定义枚举类型。枚举类型的定义格式如下:
enum EnumName {
Constant1,
Constant2,
Constant3,
...
}
其中 EnumName
是枚举类型的名称,**Constant1
**、**Constant2
**、**Constant3
** 等是枚举类型的常量,这些常量就是枚举类型创建的实例,枚举常量的名称通常采用大写字母命名,多个单词之间用下划线分隔。
对于枚举内部除了以上部分,其他的跟正常类是一样的,都可以有成员方法和成员变量
在 Java 中,可以通过枚举类型的名称来访问枚举常量。例如,假设有一个名为 Weekday 的枚举类型,可以通过如下方式来访问枚举常量:
Weekday monday = Weekday.Monday;
这里的 Weekday.Monday 表示 Weekday 枚举类型中的 Monday 常量,因为该常量为Weekday的实例,所以可以接收
枚举类型可以定义方法,这些方法可以在枚举常量上调用。例如,可以在 Weekday
枚举类型中定义一个 isWeekend
方法来判断当前枚举常量是否为周末:
enum Weekday {
Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday;
public boolean isWeekend() {
return this == Saturday || this == Sunday;
}
}
在上面的例子中,通过在枚举常量后面定义方法,可以在每个枚举常量上调用这个方法。例如,可以通过 Saturday.isWeekend()
来判断 Saturday
是否为周末,因为枚举常量是枚举实例。
枚举类型本身就是一个类,其默认继承java.lang.Enum,也就是说,自己写的枚举类,就算没有显示的继承Enum ,但是其默认继承这个类。
Color color = Color.RED;
String name = color.name();
System.out.println(name);// 输出:RED
2.valueOf(String name)
根据枚举常量名称,返回枚举实例
Color color = Color.valueOf("RED");
System.out.println(color.name()); // 输出:RED
3.values()
返回枚举类所有的枚举常量(数组形式)
Color[] colors = Color.values();
for (Color color : colors) {
System.out.println(color.name());
}
*/
输出:
RED
GREEN
BLUE
*/
4.ordinal()
返回此枚举常量的序数(其枚举声明中的位置,其中初始常量被分配一个序号零)
Color color = Color.RED;
int ordinal = color.ordinal();
System.out.println(ordinal);//输出:0
5.compareTo()
比较两个枚举值常量序数之差
Color color = Color.RED;
int n = color.compareTo(Color.BLUE) ;//0 减 2
System.out.println(n);
枚举类型在Java中可以包含构造方法,用于初始化枚举常量(所以说枚举常量是实例)。枚举的构造方法必须是私有的,因为枚举常量在定义时就被实例化,并且不能在其他地方进行实例化。
这些构造方法只能在枚举常量的定义中被调用,并且只能用来初始化枚举常量的值。当枚举常量有参数后,需要提供相应的构造方法。
enum Weekday {
Monday("星期一"), Tuesday("星期二"), Wednesday("星期三"), Thursday("星期四"), Friday("星期五"), Saturday("星期六"), Sunday("星期日");
private String value;
private Weekday(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
我们知道反射对于任何一个类,哪怕其构造方法是私有的,我们也可以通过反射拿到他的实例对象,那么枚举的构造方法也是私有的,我们是否可以拿到呢? 这里我们直接公布答案
根据Java源代码, newInstance() 方法在创建实例之前会先检查目标类是否是一个枚举类型。如果目标类是枚举类型, newInstance() 方法会抛出 InstantiationException 异常,阻止通过反射去创建枚举实例。
所以说我们无法通过反射去使用枚举的构造方法,也可以知道枚举它是特别安全的。
1、枚举本身就是一个类,其构造方法默认为私有的,且都是默认继承 java.lang.Enum
2、枚举可以避免反射和序列化问题
3、枚举的优点:
枚举常量更简单安全
枚举具有内置方法 ,代码更优雅
4.枚举的缺点:
不可继承,无法扩展
Lambda 表达式是一种匿名函数,它可以在代码中以简洁的方式表示一个功能或行为。Lambda 表达式通常由参数列表、箭头符号和函数体组成。参数列表定义了该函数接受的输入参数,箭头符号表示函数体的开始,函数体则定义了函数的具体实现。Lambda 表达式可以作为一个整体被传递给其他函数或方法,也可以直接被调用。它跟匿名内部类有关。
既然跟匿名内部类相关,那我们先来回顾匿名内部类。 匿名内部类是一种在Java中定义匿名类的方式。它允许在创建对象的同时定义该对象的类,并且不需要为该类单独命名。通常情况下,我们需要先定义一个类,然后才能创建该类的对象。但是在某些场景下,我们只需要定义一个临时的类来实现某个接口或继承某个类的功能,这时就可以使用匿名内部类。简单的理解就是减少了新类型的出现。
public class Test2 {
public static void main(String[] args) {
Animal cat = new Animal(){
@Override
public void fun() {
System.out.println("是只猫");
}
};
Animal dog = new Animal() {
@Override
public void fun() {
System.out.println("是只狗");
}
};
cat.fun();
dog.fun();
}
}
interface Animal{
public void fun();
}
以上就是匿名内部类的例子,而我们之后要用Lambda表达式对其进行简化。
public class Test2 {
public static void main(String[] args) {
Animal cat = () -> {
System.out.println("是只猫");
};
Animal dog = () -> {
System.out.println("是只狗");
};
cat.fun();
dog.fun();
}
}
interface Animal{
public void fun();
}
所以Lambdab 表达式存在的意义就是简化匿名内部类,具体格式为 (被重写方法的形参列表)-> {(被重写方法的方法体)} ;
并不是所有的匿名内部类都可以用 Lambda 表达式进行简化的,只有函数式接口才可以被 Lambda 表达式简化。 函数式接口是指只包含一个抽象方法的接口。在Java中,函数式接口是支持函数式编程的基础,它可以用作 Lambda 表达式的目标类型。 Java 8引入了**
@FunctionalInterface
**注解,用于标识一个接口为函数式接口。编译器会检查被标注的接口是否符合函数式接口的定义,如果不符合,编译器会报错。
总的来说,如果是接口并且只包含了一个抽象方法,或者引用了 @FunctionalInterface
**注解的时候都为函数式接口,都可以被 Lambda 表达式简化。**
我们通过以上的讲解,现在知道可以通过 Lambda 表达式进行简化,那么还能不能更进一步简化呢? 答案肯定是可以的,可以继续简化。 具体来介绍省略的规则: 1. 参数类型可以省略不写,但名称要一模一样。 2. 如果只有一个参数的,参数类型可以省略不写,同时()也可以不写。 3. 如果重写的方法体只有一行代码,可以省略大括号不写,同时要省略分号!此时,如果这行代码是 return 语句,也必须去掉 return 语句。 以下我们给个例子去直观显示:
public class Test {
public static void main(String[] args) {
double[] arr = new double[]{1.0,2.0,9,4.0,5.0};
//第一代的样子
/* Arrays.setAll(arr, new IntToDoubleFunction() {
@Override
public double applyAsDouble(int value) {
return arr[value] * 0.8;
}
});*/
//第二代的样子
/* Arrays.setAll(arr, (int value) ->{
return arr[value] * 0.8;
});*/
//第三代的样子
Arrays.setAll(arr,value ->arr[value] * 0.8 );
System.out.println(Arrays.toString(arr));
}
}
所以这就是我们的Lambda表达式省略规则。
对于匿名内部类中出现的变量,要么必须用final修饰,要么其是变量但从初始化后从未修改过一次。否则就不能用到匿名内部类中,这就是变量捕获。 下面给个例子。
以下是正确代码,可以执行
class Test {
public void func() {
System.out.println("func()");
}
}
public class TestDemo {
public static void main(String[] args) {
int a = 100;
Test test = new Test() {
@Override
public void func() {
System.out.println("我是内部类,且重写了func这个方法!");
System.out.println("我是捕获到变量 a == " + a
+ " 我是一个常量,或者是一个没有改变过值的变量!");
}
};
test.func();
}
}
如下代码是错误代码,不能执行,编译错误
class Test {
public void func() {
System.out.println("func()");
}
}
public class TestDemo {
public static void main(String[] args) {
int a = 100;
Test test = new Test() {
@Override
public void func() {
a = 99;
System.out.println("我是内部类,且重写了func这个方法!");
System.out.println("我是捕获到变量 a == " + a
+ " 我是一个常量,或者是一个没有改变过值的变量!");
}
};
test.func();
}
}
Lambda表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读。
优点:
1. 代码简洁,开发迅速
2. 方便函数式编程
3. 非常容易进行并行计算
4. Java 引入 Lambda,改善了集合操作
缺点:
1. 代码可读性变差
2. 在非并行计算中,很多计算未必有传统的 for 性能要高
3. 不容易进行调试
所以这篇文章就这样结束了,下篇文章将带来泛型的进阶,讲述之前没讲过的泛型知识点。 在此,我们诚挚地邀请各位大佬们为我们点赞、关注,并在评论区留下您宝贵的意见与建议。让我们共同学习,共同进步,为知识的海洋增添更多宝贵的财富!🎉🎉🎉❤️❤️💕💕🥳👏👏👏