一、接口和多态基础知识
1. 抽象类
1.1 子类调用父类
现在有IDEA集成开发环境,可以给大家实时提醒哪个地方编译错误,但假如要大家用.txt
文件编写程序呢。胡广问:现在这段代码错在了哪?
class Base {
public Base(String s) {
System.out.print("B");
}
}
public class Derived extends Base {
public Derived (String s) {
System.out.print("D");
}
public static void main(String[] args) {
new Derived("C");
}
}
假如父类和子类同时拥有有参构造方法,子类的构造方法必须显性地调用父类的构造方法,否则会编译错误。所以正常的写法应该是这样。
public Derived (String s) {
super(s);
System.out.print("D");
}
另外大家还需要注意一点,调用父类的构造方法必须在子类构造方法的第一行,调用父类的构造方法也只能出现在子类的构造方法上,否则也会是编译报错。
1.2 子类访问父类
如下代码,一共有两处编译错误。提示:错误在Child类里,能快速找出来吗?
class Parent {
public static String staticVar = "Static Variable from Parent";
private static String privateStaticVar = "Private Static Variable from Parent";
public static void staticMethod() {
System.out.println(staticVar);
}
private static void privateStaticMethod() {
System.out.println(privateStaticVar);
}
}
class Child extends Parent {
public void staticMethod() {
System.out.println("Static method in Child");
}
public void display() {
System.out.println(staticVar);
System.out.println(privateStaticVar);
privateStaticMethod();
staticMethod();
}
(1)父类的私有变量、私有方法,子类是有继承的,但是不能访问。所以Child.display()
里的以下调用是编译错误的。
System.out.println(privateStaticVar);
privateStaticMethod();
(2)子类可以继承,同时也可以访问父类的static变量、方法。但父类的static
方法大家需要注意,子类是不能直接覆盖的,所以以下代码会编译错误。
public void staticMethod() {
System.out.println("Static method in Child");
}
正确的做法是为该方法添加一个static修饰符,代表这是子类的一个新方法。这种写法叫做方法隐藏,子类和父类中都有一个相同名称和参数的静态方法时,子类的方法将隐藏父类的方法。
public static void staticMethod() {
System.out.println("Static method in Child");
}
另外如果父类的方法使用final修饰,子类也是不能覆盖的。
1.3 父类不可访问的方法
紧跟着上文代码的例子,父类的方法同样使用static
修饰,子类的privateStaticMethod
方法算不算覆盖父类的方法呢?有没有编译报错?
class Parent {
public static String staticVar = "Static Variable from Parent";
private static String privateStaticVar = "Private Static Variable from Parent";
private static void privateStaticMethod() {
System.out.println(privateStaticVar);
}
}
class Child extends Parent {
public void privateStaticMethod() {
System.out.println(staticVar);
}
}
答案是编译正常。
父类中不可访问的方法,子类编写相同名称和参数的方法并不算覆盖。父类的方法都不能访问了,也就没有覆盖这一说法了。。。
2. 接口
2.1 访问修饰符的区别
接口和抽象类有三个方面的区别,分布是类的修饰、方法的修饰、变量的修饰。我们往下看看。
(1)类
接口使用interface
修饰,而抽象类使用abstract
修饰。当它们作为外部类时,只能使用public、default修饰,不能使用private修饰。
(2)方法
普通接口方法只能由public abstract
、default
、static
修饰。
抽象接口方法可以由所有修饰符修饰,除了final。
总结下,它们两者也有共同点,就是都不能使用final修饰。
(3)变量
普通接口变量只能由public static final
修饰。
抽象接口变量可以由所有修饰符修饰。
2.2 静态分派
这算是一个很偏的知识点了,如下代码有三个名为getType
的重载方法,它们的返回类型相同、方法名也相同,只有入参类型不同。
胡广问:程序执行结果是什么?
public class Test {
public static void main(String[] args) {
for(Collection<?> collection: collections) {
System.out.println(getType(collection));
}
}
public static final Collection<?>[] collections = {new HashSet<String>(), new ArrayList<String>()};
public static String getType(Collection<?> collection) {
return "Super:collection";
}
public static String getType(List<?> list) {
return "Super:list";
}
public String getType(ArrayList<?> list) {
return "Super:arrayList";
}
}
胡广给大家这么一行代码:Collection<?> collection = new ArrayList<Integer>()
,左边的Collection<?>
其实是静态类型,右边的new ArrayList<Integer>()
其实是动态类型。
而编译器在处理重载方法时,是根据参数的静态类型作为判断依据,而不是根据动态类型。collections
数组里面的所有实例的静态类型都是Collection<?>
,getType
方法也都是执行上文的第一个重载方法。
# 程序执行结果
Super:collection
Super:collection
你学会(fei)了吗?学fei之后就开始看看面试题把,看看自己是否能过关呢?
二、接口和多态常见面试题
1. 什么是 Java 接口?接口的主要用途是什么?
回答: Java 接口是一种特殊的引用数据类型,用于定义类必须实现的一组方法。接口只能包含方法的声明,而不能包含方法的实现。接口的主要用途是提供一种机制,使得不同的类可以以一致的方式进行交互。接口支持多继承,可以让类实现多个接口,提供了灵活的设计方式。
2. 接口和抽象类的区别是什么?
回答: 接口和抽象类都是用于定义规范的工具,但有以下主要区别:
- 接口: 只能包含方法的声明(从 Java 8 起,可以有默认方法和静态方法),不能有构造函数、实例变量。一个类可以实现多个接口。
- 抽象类: 可以包含方法的实现、构造函数和实例变量。一个类只能继承一个抽象类(Java 语言只支持单继承)。
3. Java 8 中接口有什么新特性?
回答: Java 8 引入了几个接口的新特性:
- 默认方法: 可以在接口中定义具有默认实现的方法,使用
default
关键字。 - 静态方法: 可以在接口中定义静态方法。
- 函数式接口: 使用
@FunctionalInterface
注解来标记一个接口为函数式接口,确保接口只有一个抽象方法。
4. 如何在 Java 中实现多态?
回答: 多态是在 Java 中实现灵活、可扩展的对象行为的一种机制。主要有两种实现方式:
- 方法重载: 同一个类中方法名相同但参数不同。
- 方法重写: 子类重写父类的非静态方法。多态通过方法重写和引用类型的向上转型实现,即使用父类引用指向子类对象,可以调用子类重写的方法。
5. 什么是方法重载?方法重载和方法重写的区别是什么?
回答:
- 方法重载: 在同一个类中,方法名相同但参数列表不同(参数类型、个数或顺序不同),且方法的返回类型可以不同。
- 方法重写: 子类重新实现父类的已存在方法,方法名、参数列表和返回类型必须完全相同。
6. 什么是抽象方法?如何定义抽象方法?
回答: 抽象方法是没有实现的方法,只包含方法的声明。定义抽象方法时,使用 abstract
关键字,且方法体为空。抽象方法只能在抽象类或接口中定义,子类必须实现抽象方法,除非子类也是抽象类。
7. 如何使用 Java 中的接口进行回调?
回答: 接口可以用于实现回调机制。例如:
public interface Callback {
void onComplete(String result);
}
public class Task {
private Callback callback;
public Task(Callback callback) {
this.callback = callback;
}
public void doWork() {
// 执行一些工作
callback.onComplete("任务完成");
}
}
public class Main {
public static void main(String[] args) {
Task task = new Task(result -> System.out.println(result));
task.doWork();
}
}
在这个例子中,Callback
接口用于回调机制,Task
类接受一个 Callback
实例,并在完成工作后调用 onComplete
方法。
8. 什么是接口的默认方法?你能给一个示例吗?
回答: 接口的默认方法是在接口中定义的具有默认实现的方法,使用 default
关键字。例如:
public interface MyInterface {
default void defaultMethod() {
System.out.println("这是一个默认方法");
}
}
public class MyClass implements MyInterface {
// 可以选择重写默认方法,也可以使用默认实现
}
public class Main {
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.defaultMethod(); // 输出:这是一个默认方法
}
}
9. 如果一个类实现了多个接口,其中包含相同的方法名但有不同的默认实现,如何解决冲突?
回答: 如果一个类实现了多个接口,并且这些接口有相同的方法名但不同的默认实现,编译器会报错。解决这个问题的方法是,在实现类中显式地重写这个方法,并提供一个新的实现。例如:
public interface InterfaceA {
default void method() {
System.out.println("InterfaceA 的实现");
}
}
public interface InterfaceB {
default void method() {
System.out.println("InterfaceB 的实现");
}
}
public class MyClass implements InterfaceA, InterfaceB {
@Override
public void method() {
System.out.println("MyClass 的实现");
}
}
10. 如何判断一个类是否实现了某个接口?
回答: 可以使用 instanceof
操作符来判断一个对象是否实现了某个接口。例如:
if (obj instanceof MyInterface) {
// obj 实现了 MyInterface 接口
}
11. 你能解释一下“鸭子类型”在多态中的作用吗?
回答: “鸭子类型”是一种编程范式,基于对象的行为而非对象的实际类型。在 Java 中,这种类型的实现方式就是接口。如果一个对象实现了某个接口的方法,我们可以说这个对象是该接口的实现类型。通过这种方式,我们可以在不关心具体实现的情况下,利用对象的行为特性来进行编程。
12. 如何在接口中定义常量?
回答: 在接口中定义的常量使用 `public static final` 修饰符。例如: java public interface MyInterface { int CONSTANT_VALUE = 42; } 接口中的常量默认是 `public static final`,并且必须初始化。
13. Java 接口的继承和实现有什么区别?
回答: 接口的继承使用 `extends` 关键字,可以继承多个接口,并且可以继承其他接口的方法声明。而实现接口的类使用 `implements` 关键字,必须实现接口中定义的所有抽象方法。接口之间的继承是为了扩展接口的功能,而类的实现则是提供具体的实现。
14. 接口的多个继承会导致冲突吗?如何解决?
回答: 接口的多重继承不会导致问题,因为接口只定义方法的签名,不包含实现。如果多个接口中有相同的方法名但不同的默认实现,冲突会在实现类中解决。实现类需要重写这个方法,提供一个新的实现。
15. 什么是函数式接口?如何创建一个函数式接口?
回答: 函数式接口是只包含一个抽象方法的接口,可以用来作为 lambda 表达式或方法引用的目标。使用 `@FunctionalInterface` 注解来标记一个接口为函数式接口。例如: java @FunctionalInterface public interface MyFunctionalInterface { void doSomething(); }
16. 什么是接口的多继承?这与类的多继承有何不同?
回答: 接口的多继承指的是一个接口可以继承多个接口,这种继承方式是合法的且支持的。接口之间的多继承是允许的,因为接口只定义方法签名,不包含实现。与此不同的是,Java 不支持类的多继承,以避免复杂的继承关系和潜在的冲突。
17. 在 Java 中,如何实现接口的动态代理?
回答: 可以使用 `java.lang.reflect.Proxy` 类来创建接口的动态代理。例如:
public interface MyInterface {
void doSomething();
}
public class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用方法:" + method.getName());
return null;
}
}
public class Main {
public static void main(String[] args) {
MyInterface proxyInstance = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[]{MyInterface.class},
new MyInvocationHandler()
);
proxyInstance.doSomething();
}
}
18. Java 中如何使用接口来实现策略模式?
回答: 策略模式是一种行为设计模式,用于定义一系列算法,并使它们可以互换。通过接口来定义这些算法,然后在上下文中使用。示例:
public interface Strategy {
int execute(int a, int b);
}
public class AdditionStrategy implements Strategy {
@Override
public int execute(int a, int b) {
return a + b;
}
}
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int a, int b) {
return strategy.execute(a, b);
}
}
public class Main {
public static void main(String[] args) {
Strategy strategy = new AdditionStrategy();
Context context = new Context(strategy);
System.out.println(context.executeStrategy(5, 3)); // 输出:8
}
}
19. Java 接口是否支持静态方法?如何定义和使用?
回答: 是的,Java 接口支持静态方法,从 Java 8 开始,可以在接口中定义静态方法。静态方法不能被实现类重写,只能通过接口名调用。例如:
public interface MyInterface {
static void staticMethod() {
System.out.println("接口的静态方法");
}
}
public class Main {
public static void main(String[] args) {
MyInterface.staticMethod(); // 输出:接口的静态方法
}
}
20. 如何在接口中定义默认方法,并让实现类选择是否重写?
回答: 默认方法在接口中使用 `default` 关键字定义,可以为接口中的方法提供默认实现。实现类可以选择是否重写默认方法。如果实现类不重写默认方法,类将使用接口提供的默认实现。例如:
public interface MyInterface {
default void defaultMethod() {
System.out.println("默认实现");
}
}
public class MyClass implements MyInterface {
@Override
public void defaultMethod() {
System.out.println("重写后的实现");
}
}
public class Main {
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.defaultMethod(); // 输出:重写后的实现
}
}
让我们一起学习,一起进步!期待在评论区与你们见面。
祝学习愉快!