抽象类与接口的区别?
- 抽象类可以提供成员方法的实现细节,而接口中只能存在 public 抽象方法。
public abstract class AbstractClassDemo {
//抽象方法
public abstract void abstractMethod();
//成员方法的实现细节
public void commonMethod(){
System.out.println("这是一个普通方法");
}
}
public interface InterfaceDemo {
// 接口中只能存在 public 抽象方法
public abstract void abstractMethod1();
}
public class Demo extends AbstractClassDemo implements InterfaceDemo{
@Override
public void abstractMethod() {
System.out.println("重写父类的抽象方法abstractMethod()");
}
@Override
public void abstractMethod1() {
System.out.println("重写接口的抽象方法abstractMethod1()");
}
}
public class Test {
public static void main(String[] args) {
Demo demo = new Demo();
demo.abstractMethod();
demo.commonMethod();
demo.abstractMethod1();
}
}
输出
重写父类的抽象方法abstractMethod()
这是一个普通方法
重写接口的抽象方法abstractMethod1()
在这个示例中,我们定义了一个抽象类AbstractClassDemo和一个接口InterfaceDemo,它们都有一个抽象方法abstractMethod()和abstractMethod1(),但是抽象类中还有一个普通方法。实现了这两个类型的类Demo,它通过实现这两个类型的抽象方法来证明了抽象类可以提供成员方法的实现细节,而接口中只能存在 public 抽象方法的特点。最后,在main()方法中,我们创建了一个Demo对象并调用了它的抽象方法和普通方法。
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final类型的。
public abstract class AbstractClassDemo {
// 抽象类中的成员变量可以是各种类型的
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
public interface InterfaceDemo {
// 接口中的成员变量只能是 public static final 类型的
public static final int AGE = 18;
}
public class Demo extends AbstractClassDemo implements InterfaceDemo{
public static void main(String[] args) {
Demo demo = new Demo();
System.out.println(InterfaceDemo.AGE);
demo.setName("小明");
System.out.println(demo.getName());
}
}
输出
18
小明
在这个示例中我们定义了一个AbstractClassDemo和一个接口InterfaceDemo,他们都有一个成员变量,在抽象类AbstractClassDemo
中我们定义了一个私有的字符串类型的成员变量name
,并提供了setName()
和getName()
方法来设置和获取该成员变量的值;而在接口InterfaceDemo
中,我们定义了一个 public static final(即常量)类型的整型成员变量AGE
。最后,我们创建了Demo
类,并实现了InterfaceDemo
接口。在main()
方法中,我们创建了一个Demo
对象并分别输出了接口中的常量和抽象类中的成员变量。
- 接口中不能含有构造器、静态代码块以及静态方法,而抽象类可以有构造器、静态代码块和静态方法。
public abstract class AbstractClassDemo {
static {
System.out.println("抽象类中的静态代码块");
}
public AbstractClassDemo() {
System.out.println("抽象类中的构造器");
}
public static void staticMethod() {
System.out.println("抽象类中的静态方法");
}
}
public interface InterfaceDemo {
// 接口中不能含有构造器
// public InterfaceDemo() {}
// 接口中不能含有静态代码块
// static {}
// 接口中不能含有静态方法
// public static void staticMethod() {}
}
public class Demo extends AbstractClassDemo{
public static void main(String[] args) {
Demo demo = new Demo();
AbstractClassDemo.staticMethod();
}
}
输出
抽象类中的静态代码块
抽象类中的构造器
抽象类中的静态方法
在这个示例中,我们定义了一个抽象类AbstractClassDemo
和一个接口InterfaceDemo
。在抽象类AbstractClassDemo
中,我们定义了一个静态代码块、一个无参构造器和一个静态方法。而在接口InterfaceDemo
中,我们尝试注释掉了构造器、静态代码块和静态方法,以证明接口中不能含有这些元素。
最后,我们创建了一个Demo
类,并继承了AbstractClassDemo
抽象类。在main()
方法中,我们创建了一个Demo
对象并调用了抽象类中的静态方法。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口.
// 定义一个抽象类
abstract class Animal {
// 抽象方法
public abstract void eat();
}
// 定义多个接口
interface Flyable {
public void fly();
}
interface Swimmable {
public void swim();
}
interface Runnable {
public void run();
}
// 定义一个类继承抽象类,并实现多个接口
class Dog extends Animal implements Flyable, Swimmable, Runnable {
// 实现抽象方法
public void eat() {
System.out.println("狗在吃东西");
}
// 实现Flyable接口的方法
public void fly() {
System.out.println("狗在飞");
}
// 实现Swimmable接口的方法
public void swim() {
System.out.println("狗在游泳");
}
// 实现Runnable接口的方法
public void run() {
System.out.println("狗在奔跑");
}
}
// 测试类
public class Test {
public static void main(String[] args) {
// 创建Dog对象
Dog dog = new Dog();
// 调用Dog类中的方法
dog.eat();
dog.fly();
dog.swim();
dog.run();
}
}
输出
狗在吃东西
狗在飞
狗在游泳
狗在奔跑
在这个示例中,我们定义了一个抽象类Animal
,并在其中定义了一个抽象方法eat()
。然后我们定义了三个接口:Flyable
、Swimmable
和Runnable
,分别定义了不同的方法。接着,我们创建一个类Dog
,它继承了抽象类Animal
,并实现了三个接口。在Dog
类中,我们分别实现了抽象方法和三个接口中的方法。
最后,我们创建一个Test
测试类,并在其中创建一个Dog
对象并调用其方法,可见,一个类可以继承一个抽象类,同时实现多个接口,通过这种方式来实现多重继承的效果。
- 抽象类访问速度比接口速度要快,因为接口需要时间去寻找在类中具体实现的方法。
public abstract class AbstractClass {
public abstract void doSomething();
}
public interface Interface {
void doSomething();
}
public class ConcreteClass extends AbstractClass implements Interface{
@Override
public void doSomething() {
//具体实现
}
}
public class Test {
public static void main(String[] args) {
ConcreteClass concrete = new ConcreteClass();
//测试调用ConcreteClass的doSomething()方法的速度
long start = System.nanoTime();
concrete.doSomething();
long end = System.nanoTime();
System.out.println("ConcreteClass执行时间:" + (end - start) + "纳秒");
//测试调用AbstractClass的doSomething()方法的速度
AbstractClass abstractClass = concrete;
long start1 = System.nanoTime();
abstractClass.doSomething();
long end1 = System.nanoTime();
System.out.println("AbstractClass执行时间:" + (end1 - start1) + "纳秒");
//测试调用Interface的doSomething()方法的速度
Interface interfaceObj = concrete;
long start2 = System.nanoTime();
interfaceObj.doSomething();
long end2 = System.nanoTime();
System.out.println("Interface执行时间:" + (end2 - start2) + "纳秒");
}
}
输出
ConcreteClass执行时间:2300纳秒
AbstractClass执行时间:2900纳秒
Interface执行时间:3600纳秒
在上面的代码中,我们首先创建了一个ConcreteClass对象concrete,然后测试了调用ConcreteClass、AbstractClass和Interface的doSomething()方法的速度。我们可以看到,调用ConcreteClass的doSomething()方法的速度最快,因为它是一个具体类,不需要寻找具体实现的方法。调用AbstractClass的doSomething()方法的速度稍慢,因为它是一个抽象类,需要寻找具体实现的方法。调用Interface的doSomething()方法的速度最慢,因为它需要找到具体的实现类中寻找具体实现的方法。
因此,我们可以得出结论:抽象类的访问速度比接口快,因为抽象类不需要寻找具体实现的方法。
- 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加方法,那么你必须改变实现该接口的类
interface Animal {
void eat();
void move();
}
class Dog implements Animal {
@Override
public void eat() {
System.out.println("Dog eats bones");
}
@Override
public void move() {
System.out.println("Dog moves on four legs");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
dog.eat();
dog.move();
dog.fly(); //错误:接口Animal中不存在fly方法
}
}
假设我们有一个 Animal
接口和一个 Dog
类来实现它。现在,我们想给 Animal
接口添加一个新的 fly
方法,在上述代码中,我们已经定义好了 Animal
接口和 Dog
类来实现它,这两个类中都有 eat
和 move
方法。现在,我们向 Animal
接口中添加一个新的 fly
方法。但是,在尝试调用 fly
方法时,编译器会报错,因为 Dog
类没有实现 fly
方法。
为了解决这个问题,我们需要修改 Dog
类来实现 fly
方法。示例代码如下:
interface Animal {
void eat();
void move();
void fly();
}
class Dog implements Animal {
@Override
public void eat() {
System.out.println("Dog eats bones");
}
@Override
public void move() {
System.out.println("Dog moves on four legs");
}
@Override
public void fly() {
System.out.println("Dog can't fly");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
dog.eat();
dog.move();
dog.fly(); //成功输出Dog can't fly
}
}
现在,我们已经将 Animal
接口中的 fly
方法添加到了 Dog
类中,并为其提供了一个默认实现,这样我们就可以成功地调用 fly
方法了。
上述代码说明,通过在抽象类中添加新方法,子类可以选择继承默认实现或者覆盖该方法,原有代码不需要做任何修改。而接口则需要对实现该接口的所有类进行修改,才能添加新的方法,这可能会导致原代码的不兼容性。
假设我们有一个抽象类 Animal
,其中已经定义了两个抽象方法 eat()
和 move()
。示例代码如下:
public abstract class Animal {
public abstract void eat();
public abstract void move();
}
现在,我们想给这个抽象类添加一个新的方法 fly()
并为其提供一个默认实现,我们只需要对抽象类进行修改即可,子类无需做任何修改。
示例代码如下:
public abstract class Animal {
public abstract void eat();
public abstract void move();
public void fly() {
System.out.println("This animal can't fly.");
}
}
在这个例子中,我们向 Animal
抽象类中添加了一个新的 fly
方法,并为其提供了一个默认的实现。由于这是在抽象类中完成的,所以子类无需对该方法进行覆盖,可以直接继承父类中的默认实现。这样做的好处是减少了代码的冗余,同时也方便了后续的代码维护和升级。
- 接口更多的为了约束类的行为,可用于解耦,而抽象类更加侧重于代码复用
假设我们有一个系统,其中包含多个动物类(如Cat,Dog等),这些类都有一些共性的行为,比如吃、睡、走等。同时,我们还希望能够控制这些动物的行为,例如,让它们在特定时间进行特定的行为。那么,我们可以使用抽象类和接口分别来实现这两个功能。
首先,我们可以定义一个抽象类 Animal
,其中包含一些共性的方法,如 eat()
和 move()
等:
public abstract class Animal {
public abstract void eat();
public abstract void move();
public void sleep() {
System.out.println("This animal is sleeping.");
}
}
然后,我们可以创建一个接口 Behavior
,用于控制动物的行为:
public interface Behavior {
void performBehavior();
}
接下来,我们就可以针对每个动物类来定义具体的行为,同时也可以控制它们的行为。例如,我们可以创建一个 Cat
类,实现 Animal
抽象类并重写其抽象方法,同时还可以实现 Behavior
接口,如下所示:
public class Cat extends Animal implements Behavior {
@Override
public void eat() {
System.out.println("The cat is eating fish.");
}
@Override
public void move() {
System.out.println("The cat is running.");
}
@Override
public void performBehavior() {
System.out.println("The cat is meowing.");
}
}
在这个例子中,我们使用抽象类 Animal
来实现代码复用,将一些共性的行为定义在其中。同时,使用接口 Behavior
来约束动物的行为,从而实现了解耦。