【Java基础面试题004】封装、继承、重载、多态、接口和抽象类是什么?
回答重点
封装
Java的封装指的是:通过将类的成员变量和方法声明为private或protected,避免直接被外部访问。对外提供每个成员变量和方法的getter/setter方法进行交互
主要目的是隐藏对象的内部实现细节,只暴露必要的功能
继承
Java的继承指的是:允许一个类使用extends关键字继承另一个类的非私有属性和方法,类之间形成层次结构,支持代码重用和扩展
Java支持单继承,即一个类只能继承一个类
如果子类的构造方法没有显式调用父类的构造方法(即没有手动使用 super关键字
),Java 编译器会自动在子类构造方法的第一行插入一个对父类无参构造方法的调用。这意味着,子类构造方法仍然会调用父类的构造方法,前提是父类必须有一个无参构造函数可供调用
重载
Java的重载指的是:一个类定义多个同名的方法,这些方法名/返回值相同但是参数类型/数量/顺序不同,Java编译器javac.exe在编译时会根据方法调用时传入的参数类型和数量和顺序,决定调用哪一个重载方法
多态
Java的多态指的是:同一个接口或父类引用变量可以指向不同的对象实例,并根据指向的对象类型执行相应的方法,它允许同一方法在不同对象上表现出不同的行为
降低代码耦合度,增强可扩展性,新增实现类或者子类时,无需修改原有代码,只需通过接口或父类引用调用即可
接口
Java的接口指的是:与定义类相似,使用interface关键字定义,也会被变异成.class文件,但接口不是类,是另一种引用数据类型。接口本身不能创建实例,可以创建接口的实现类对象。它定义了一组方法,但不提供具体实现。支持多态性,多继承,多实现。
抽象类
抽象类是一种不能被实例化的类,用abstract定义,它可以包含抽象方法(没有实现的方法)和具体方法(有实现的方法),抽象类中不一定包含抽象方法,但是有抽象方法的一定是抽象类,抽象类的子类必须重写所有抽象方法
扩展知识
继承的优缺点
编译时多态&运行时多态
是面向对象编程中多态性的两种实现方式。他们分别在不同的阶段决定方法的绑定
- 编译时多态:通过方法重载实现,在编译时确定方法的调用
- 运行时多态:通过方法重写实现,在运行时确定方法的调用
编译时多态
也称为静态多态,毕竟重载的方法们都是写死的,是在编译阶段确定方法的调用。
编译时多态主要通过方法重载实现
运行时多态
也称为动态多态,毕竟编译时只是验证父类和接口类型,而不检查具体实现细节,是在运行时确定方法的调用。
运行时多态通过方法重写实现
重写&重载区别
这里关于为什么静态方法不能被重写的原因,涉及到更深层次的语法虚方法
这里就不解释了
为什么Java不支持多继承?
主要是多重继承会产生菱形继承(钻石问题),Java之父余麻子老师就是吸取C++的教训
具体细节看我另一个博客:【继承】钻石问题-CSDN博客
为什么Java支持接口多实现?
Java 8 之前,接口中只能声明方法,而不能提供任何方法体(实现),所以是不会发生钻石问题的
Java 8 开始,接口引入了 默认方法(default methods)和 静态方法(static methods),这使得接口能写方法体,那么就可能发生钻石问题
不过,Java强制规定,如果多个接口内有相同的默认方法,子类就必须重写这个默认方法
否则过不了编译期
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。
接口的成员
- Java8之前,接口只能写 公共的抽象方法 和 公共的静态常量
- Java8开始,接口引入了 默认方法 和 静态方法 的概念,这两种方法都可以有方法体,不需要实现类去实现,默认方法可以被实现类重写,静态方法则不能被实现类直接使用,只能被接口自身调用
- 这两种方法的引入解决了接口升级和维护的问题,使得接口可以更加灵活地扩展功能
- Java9开始,接口还可以定义私有方法,这些私有方法只能被接口内部的其他方法调用,本能被接口的实现类直接访问和调用
抽象类的注意事项
- 不能用abstract修饰变量、代码块、构造器
- 不能用abstract修饰私有方法、静态方法、final的方法、final的类
接口&抽象类的区别
更多细节看我另一个博客:【Java】接口与抽象类的区别-CSDN博客
接口和抽象类在设计动机上有所不同。
- 接口的设计是自上而下的。我们知晓某一行为,于是基于这些行为约束定义了接口,一些类需要有这些行为,因此实现对应的接口。
- 抽象类的设计是自下而上的。我们写了很多类,发现他们之间有共性,有很多代码可以复用,,因此将公共逻辑封装成一个抽象类,减少代码冗余.
- 所谓的自上而下指的是先约定接口,再实现。
- 而自下而上指的是现有一些类,才抽象了共同父类(与学校教的不同,企业开发中很多时候都是i因为重构才有的抽象)。
如下是抽象类自下而上的例子:
// 现有的类
class Dog {
void bark() {
System.out.println("Bark");
}
}
class Cat {
void meow() {
System.out.println("Meow");
}
}
// 从已有的类中抽象出一个共同的父类
abstract class Animal {
abstract void makeSound(); // 抽象方法
}
// 修改现有的类使其继承抽象类
class Dog extends Animal {
@Override
void makeSound() {
bark();
}
void bark() {
System.out.println("Bark");
}
}
class Cat extends Animal {
@Override
void makeSound() {
meow();
}
void meow() {
System.out.println("Meow");
}
}