文章目录
- 前言
- 1.4 反射机制
- 1.4.1 Class对象的获取
- 1.4.2 Class类的方法
- 1.4.3 通过反射机制修改只读类的属性
- 1.5 Lambda表达式
- 1.5.1 函数式接口
- 1.5.2 Lambda表达式的使用
- 1.6 多态
- 1.6.1 多态的概念
- 1.6.2 多态的实现条件
- 1.6.3 重载(Overload)和重写(Override)
前言
往期精选:
再探Java为面试赋能(一)Java基础知识(一)变量初始化顺序、构造方法、clone方法
1.4 反射机制
Java语言的反射机制,是指动态获取类或对象的属性以及方法,从而完成调用功能的一种机制。它主要实现了以下功能:
- 获取类的访问修饰符、属性、方法以及父类信息。
- 在运行时根据类名创建对象,还可以调用对象的任意方法。
- 生成动态代理。
1.4.1 Class对象的获取
在反射机制中,Class类是一个非常重要的类,获取Class对象主要有3种方法:
- 1)通过
className.class
获取:
public class Food {
static {
System.out.println("Food类的静态代码块执行了...");
}
{
System.out.println("Food类的非静态代码块执行了...");
}
public static void main(String[] args) {
Class<Food> aClass = Food.class;
System.out.println("className = " + aClass.getName());
}
}
程序运行结果:
Food类的静态代码块执行了...
className = test03.Food
- 2)通过
Class.forName()
获取:
public class Food {
static {
System.out.println("Food类的静态代码块执行了...");
}
{
System.out.println("Food类的非静态代码块执行了...");
}
public static void main(String[] args) throws ClassNotFoundException {
Class<?> food = Class.forName("test03.Food");
System.out.println("className = " + food.getName());
}
}
程序运行结果:
Food类的静态代码块执行了...
className = test03.Food
- 3)通过
Object.getClass()
获取:
public class Food {
static {
System.out.println("Food类的静态代码块执行了...");
}
{
System.out.println("Food类的非静态代码块执行了...");
}
public static void main(String[] args) {
Class<? extends Food> aClass = new Food().getClass();
System.out.println("className = " + aClass.getName());
}
}
程序运行结果:
Food类的静态代码块执行了...
Food类的非静态代码块执行了...
className = test03.Food
如上面的例子所示,三种方式都可以获得类的Class对象,但也有一些区别:
- 方法 1)2)执行静态代码块,但不执行非静态代码块;
- 方法 3)由于创建了对象,因此会执行静态代码块和非静态代码块。
1.4.2 Class类的方法
Class类提供了非常多的方法,常用的有三类:
- 1)获取类的构造方法
构造方法的封装类为Constructor。
源码:java.lang.Class
// 返回类的所有的public构造方法
public Constructor<?>[] getConstructors()
// 返回类的指定参数的public构造方法
public Constructor<T> getConstructor(Class<?>... parameterTypes)
// 返回类的所有构造方法
public Constructor<?>[] getDeclaredConstructors()
// 返回类的指定参数的构造方法
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
- 2)获取类的成员变量的方法
成员变量的封装类为Field。
源码:java.lang.Class
// 返回类的所有public成员变量
public Field[] getFields()
// 返回类的指定名称的public成员变量
public Field getField(String name)
// 返回类的所有成员变量
public Field[] getDeclaredFields()
// 返回类的指定名称的成员变量
public Field getDeclaredField(String name)
- 3)获取类的方法
方法的封装类为Method。
源码:java.lang.Class
// 返回类的所有public方法
public Method[] getMethods()
// 返回类的指定名称和参数的public方法
public Method getMethod(String name, Class<?>... parameterTypes)
// 返回类的所有方法
public Method[] getDeclaredMethods()
// 返回类的指定名称和参数的方法
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
1.4.3 通过反射机制修改只读类的属性
有如下代码:
public class ReadOnlyClass {
private Integer age = 20;
public Integer getAge() {
return age;
}
}
在这个类中,age属性被修饰为private,且只提供了getter方法,而没有提供setter方法,因此这个类型是一个只读的类,无法通过常规方法修改age属性。
但通过Java的反射机制就可以修改age属性。例如:
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
ReadOnlyClass roc = new ReadOnlyClass();
System.out.println("修改前age = " + roc.getAge());
Class<ReadOnlyClass> aClass = ReadOnlyClass.class;
Field field = aClass.getDeclaredField("age");
field.setAccessible(true);
// 修改roc对象的age属性
field.set(roc, 30);
System.out.println("修改后age = " + roc.getAge());
}
执行结果:
修改前age = 20
修改后age = 30
1.5 Lambda表达式
简单来说,Lambda表达式就是对匿名内部类的简写。它的基本语法是:
<函数式接口> <变量名> = (参数1, 参数2, ...) -> {
// 方法体
}
1.5.1 函数式接口
函数式接口就是一个有且仅有一个抽象方法的接口。
例如Runnable接口就是一个函数式接口:
源码:java.lang.Runnable
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
可见,Runnable接口中只包含一个抽象的run()
方法,并且在接口上标注了一个@FuncationInterface
注解,此注解就是 Java 8 新增的注解,用来标识一个函数式接口。
想要自定义一个函数式接口也非常简单,只需要在接口中定义一个抽象方法,然后在接口上标记@FunctionalInterface
注解。
当然,该注解也可以不标记,只是说标记了该注解,编辑器会自动检测自定义的函数式接口是否有问题。例如:
1.5.2 Lambda表达式的使用
下面是一个自定义的函数式接口,定义了一个有参数有返回值的sum()
方法:
@FunctionalInterface
public interface MyInterface {
public abstract int sum(int a, int b);
}
使用Lambda表达式调用该方法:
public static void main(String[] args) {
// 使用匿名内部类方式
MyInterface myInterface = new MyInterface() {
@Override
public int sum(int a, int b) {
return a+b;
}
};
int sum = myInterface.sum(5, 10);
System.out.println("匿名内部类方式 sum = " + sum);
// 使用Lambda表达式
MyInterface myi = (a, b) -> a+b;
int sum1 = myi.sum(2, 8);
System.out.println("Lambda表达式方式 sum1 = " + sum1);
}
执行结果:
匿名内部类方式 sum = 15
Lambda表达式方式 sum1 = 15
可见,使用Lambda表达式后,代码变得更加简介。
Lambda表达式还有更多编写规则如下:
- 1)参数只需要参数名称,不需要参数类型,编译器会自动推断类型。如示例中:
(a, b)
。 - 2)如果参数只有一个,可以省略小括号
()
,没有参数或者参数多于一个,不能省略小括号。 - 3)方法体只有一行代码时,可以省略中括号
{}
。 - 4)方法体只有一行代码时,且有返回值时,可以省略
return
关键字。如示例中:-> a+b
。
1.6 多态
1.6.1 多态的概念
多态是面向对象编程中的一个重要概念,它允许不同类型的对象对同一方法进行不同的实现。
具体来说,多态是指通过父类的引用变量来指向子类的对象,从而实现对不同对象的统一操作。
例如,猫和狗都是动物,都有“吃”这个共同行为,具体表现在狗身上可能是啃骨头,表现在猫身上可能是吃猫粮。这就是多态的表现,即同一件事情,发生在不同的对象身上,就会产生不同的结果。
1.6.2 多态的实现条件
在Java中,要实现多态性,就必须满足以下条件:
- 1)继承关系
- 2)方法重写
- 3)父类引用指向子类对象
例如,首先定义一个父类Animal,该类有一个eat()
方法:
public class Animal {
public void eat() {
System.out.println("动物吃东西...");
}
}
再定义两个子类Dog和Cat,均继承父类Animal,并重写其eat()
方法:
public class Dog extends Animal {
// 1.继承关系
// 2.方法重写
@Override
public void eat() {
System.out.println("狗啃骨头...");
}
}
public class Cat extends Animal {
// 1.继承关系
// 2.方法重写
@Override
public void eat() {
System.out.println("猫吃猫粮...");
}
}
最后编写测试方法:
public class Main {
public static void main(String[] args) {
// 3.父类引用指向子类对象
Animal dog = new Dog();
Animal cat = new Cat();
dog.eat();
cat.eat();
}
}
执行结果:
狗啃骨头...
猫吃猫粮...
1.6.3 重载(Overload)和重写(Override)
Java中多态的实现条件之一是方法重写,是指子类重新定义和实现从父类继承而来的方法,以改变方法的行为,提供自己特定的实现。
方法重写必须遵循一些规则:
- 1)子类重写方法的名称、参数列表(包括数量、类型、顺序)和返回值类型都必须与父类中被重写的方法相同。
- 2)重写方法的访问修饰符的权限不能低于父类方法的权限。例如,父类方法被
public
修饰,则子类中重写方法就不能声明为protected
。 - 3)重写方法不能抛出比父类方法更多或更宽泛的异常,重写方法可以抛出相同的异常或更具体的异常,或者不抛出异常。
- 4)重写方法可以通过
super()
函数调用父类方法的逻辑。
重载是指在一个类中定义了多个同名的方法。 它也必须遵循一些规则:
- 1)重载的方法必须定义在一个类中,它们的名称必须相同。
- 2)重载的方法的参数列表必须不同,可以通过参数的个数、类型、顺序的不同来进行区分。
- 3)重载的方法的返回值类型可以相同也可以不同。
总结一下,重载和重写的区别:
- 1)定义位置。重载方法定义在同一个类中,而重写方法定义在父类和子类之间。
- 2)方法签名。重载方法具有相同的名称,但方法签名(参数类型、个数或顺序)不同,而重写方法具有相同的名称和方法签名。
- 3)调用时机。重载方法是根据方法签名的不同进行静态绑定,在编译时就确定,而重写方法是根据对象的实际类型进行动态绑定,要在运行时确定。
…
本节完,更多内容请查阅分类专栏:再探Java为面试赋能
感兴趣的读者还可以查阅我的另外几个专栏:
- SpringBoot源码解读与原理分析(已完结)
- MyBatis3源码深度解析(已完结)
- Redis从入门到精通(持续更新中…)