抽象类、接口
抽象类
什么是抽象类
在之前多态的打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class).
public class Shape {
public void draw(){
System.out.println("什么都不干");
}
}
语法规则
- 在draw方法前加上abstract关键字,表示这是一个抽象方法,同时抽象方法没有方法体(没有{代码块},不能执行具体代码)
- 对于包含抽象方法的类,必须加上abstract关键字表示这是一个抽象类
abstract class Shape {
abstract public void draw();
}
注意事项
- 抽象类不能实例化(可以简单地理解为抽象抽象肯定不能实例化啦)
- 在抽象类中可以拥有和普通类一样的属性和方法
- 抽象类可以被继承(不然要你干啥)可以说抽象类的的最大意义就是为了继承
- 当一个普通类A继承了一个抽象类,那么这个普通类一定要重写抽象类中的抽象方法,但当这个普通类A不想再在自己的内部实现父类抽象类的抽象方法,那么可以将这个普通类修改为抽象类A,此时便可以不实现抽象方法,但如果一个普通类B继承了这个抽象类A,此时便需要实现这个抽象方法了,但还不想实现的话就像上面一样设置为抽象类,但最后总归是要实现的(因为出来混总是要还的)
- 抽象方法不能是private的(废话,因为你抽象方法就是要被重写的,你设成private还怎么重写,因为重写方法的话的话子类的访问权限要大于父类的访问权限)
接口
什么是接口
接口是抽象类的更进一步,抽象类中还可以包含非抽象的方法和字段,而接口中包含的方法都是抽象方法,字段也只能静态常量。
语法规则
在刚才的打印图形的示例中, 我们的父类 Shape 并没有包含别的非抽象方法, 所以也可以设计成一个接口
interface IShape {
void draw();
}
class Circle implements IShape {
@Override
public void draw() {
System.out.println("○");
}
}
public class Test {
public static void main(String[] args) {
IShape shape = new Rect();
shape.draw();
}
}
- 使用interface定义一个接口
- Cycle 使用 implements 继承接口. 此时表达的含义不再是 “扩展”, 而是 “实现”
- 在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例. 接口不能单独被实例化.
扩展(extends) vs 实现(implements)
- 扩展指的是当前已经有一定的功能了, 进一步扩充功能.
- 实现指的是当前啥都没有, 需要从头构造出来.
interface A {
void funcA();
}
interface B {
void funcB();
}
//implements实现
class C implements A {
@Override
public void funA() {
System.out.println("funA");
}
}
//extends:扩展
interface D extends A,B{
void funcD();
}
//此时D有funAfunBfunD三种方法
注意事项
- 接口当中的方法不能具体实现(抽象方法)
- 接口中方法默认为public abstract可以省略
- 接口当中的成员变量默认为public static final(定义的同时初始化)
- 接口当中default修饰的方法,是当前接口的默认方法。
- 接口不可以进行实例化
- 接口也可以继承
- 一个类可以继承一个类(抽象类或者普通类),但可以继承多个接口
- 接口可以扩展多个接口,所以接口的出现就是为了解决Java的多继承
举例,下面这段代码包含和上边所需要注意的事项:
package 理解接口;
public interface IFlying {
void fly();
/*
default void func(){
}*/
}
public interface IRunning {
void run();
}
public interface ISwimming {
void swim();
}
// 两栖的动物, 既能跑, 也能游 接口的继承
interface IAmphibious extends IRunning, ISwimming {
}
class Animal{
protected String name;
public Animal(String name){
this.name=name;
}
}
class Duck extends Animal implements IRunning,ISwimming,IFlying {
public Duck(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name + " 正在飞!!");
}
@Override
public void run() {
System.out.println(this.name + " 正在跑!!");
}
@Override
public void swim() {
System.out.println(this.name + " 正在游泳!!");
}
}
class Robot implements IRunning{
@Override
public void run() {
System.out.println("机器人正在跑....");
}
}
public class Test {
public static void goFly(IFlying iFlying){
iFlying.fly();
}
public static void goRun(IRunning iRunning){
iRunning.run();
}
public static void goSwim(ISwimming iSwimming){
iSwimming.swim();
}
public static void main(String[] args) {
goFly(new Duck("唐老鸭"));
goRun(new Duck("唐老鸭"));
goSwim(new Duck("唐老鸭"));
goRun(new Robot());
}
/*public static void main(String[] args) {
IRunning iRunning=new Duck("唐老鸭");
iRunning.run();
IFlying iFlying=new Duck("唐老鸭");
iFlying.fly();
ISwimming iSwimming=new Duck("唐老鸭");
iSwimming.swim();
}*/
}
接口的实例(常见的接口)
Compareable
对于Comparable接口来说,一般用在类的内部,修改类的本身,底层是compareTo。
举例
//Test.java类(一个类)
import java.util.*;
//对于Comparable接口来说,一般用再累的内部 修改类的本身
class Student implements Comparable<Student>{
public String name;
@Override
//o传入的引用 默认从小到大排序
public int compareTo(Student o) {
if(this.score>o.score)
{
return 1;
}else if(this.score==o.score)
{
return 0;
}else{
return -1;
}
}
public int score;
public Student(String name,int score){
this.name=name;
this.score=score;
}
//直接在类的内部修改
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
public class Test {
public static void main(String[] args) {
Student[] students=new Student[3];
students[0] = new Student("bit",89);
students[1] = new Student("abc",19);
students[2] = new Student("htf",59);
System.out.println(Arrays.toString(students));
System.out.println("===============排序==============");
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
Compareator
Compareato(比较器), 一般用在类外,底层实现是Compare。
举例
//NameComparator.java类
import java.util.Comparator;
public class NameComparator implements Comparator<Student> {
@Override
public int compare(Student2 o1, Student2 o2) {
return o1.name.compareTo(o2.name);
}
}
//ScoreComparator.java类
import java.util.Comparator;
public class ScoreComparator implements Comparator<Student> {
@Override
public int compare(Student2 o1, Student2 o2) {
return o1.score-o2.score;
}
}
//Test.java类
import java.util.*;
//比较器 一般用在类外
class Student {
public String name;
public int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
public class Test {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("bit",89);
students[1] = new Student("abc",19);
students[2] = new Student("htf",59);
System.out.println(Arrays.toString(students));
System.out.println("===============根据分数排序==============");
ScoreComparator scoreComparator=new ScoreComparator();
Arrays.sort(students,scoreComparator);
System.out.println(Arrays.toString(students));
System.out.println("===============根据姓名进行排序==============");
NameComparator nameComparator = new NameComparator();
Arrays.sort(students,nameComparator);
System.out.println(Arrays.toString(students));
}
}
Cloneable(实现深拷贝)
这里就不具体说了,之后会详细写到深拷贝和浅拷贝(有兴趣的话可以看看)
Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”. 但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常.
class Person implements Cloneable{
public String name;
public Person(String name){
this.name=name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test2 {
public static void main(String[] args) throws CloneNotSupportedException {
Person personOdd = new Person("bit");
personOdd.m.money=666;
Person personNew = (Person)personOdd.clone();
System.out.println(personOdd.name);//bit
System.out.println(personNew.name);//bit
System.out.println("============修改name============");
personNew.name = "zjc";
System.out.println(personOdd.name);//bit
System.out.println(personNew.name);//zjc
}
}
- 此时拷贝了一份旧的personOdd给新的personNew,因为string不是自定义的类型,所以修改personNew的名字,原来personOdd的名字不会改变
但当自定义类型中还有自定义类型的话,这样拷贝的话就属于浅拷贝了,因为他们指向的的引用属于同一个引用,修改新的数值的话会影响原数值。
class Money {
public double money=666;
}
class Person implements Cloneable{
public String name;
public Money m=new Money();
public Person(String name){
this.name=name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test2 {
public static void main(String[] args) throws CloneNotSupportedException {
Person personOdd = new Person("bit");
personOdd.m.money=666;
Person personNew = (Person)personOdd.clone();
System.out.println(personOdd.name);//bit
System.out.println(personNew.name);//bit
System.out.println(personOdd.m.money);//666
System.out.println(personNew.m.money);//666
System.out.println("============修改money============");
personNew.m.money=888;
System.out.println(personOdd.m.money);//888
System.out.println(personNew.m.money);//888
}
}
输出结果为
888
888
所以我们的Money需要也需要继承Conleable,并重写clone方法,且在Person类中进行重写clone的时候创建新的引用m,personNew的m指向新的引用而不是原有personOdd的m,这样的话修改其personNew的money值便不会影响personOdd的money值了(深拷贝)。
class Money implements Cloneable{
public double money=12.8;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable{
public String name;
public Money m=new Money();
public Person(String name){
this.name=name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person personNew = (Person)super.clone();
personNew.m = (Money) this.m.clone();
return personNew;
//return super.clone();
}
}
public class Test2 {
public static void main(String[] args) throws CloneNotSupportedException {
Person personOdd = new Person("bit");
personOdd.m.money=666;
Person personNew = (Person)personOdd.clone();
System.out.println(personOdd.m.money);//666
System.out.println(personNew.m.money);//666
System.out.println("============修改money============");
personNew.name = "zjc";
personNew.m.money=888;
System.out.println(personOdd.m.money);//666
System.out.println(personNew.m.money);//888
}
}
此时输出的结果为
666
888
总结
抽象类和接口都是 Java 中多态的常见使用方式. 都需要重点掌握. 同时又要认清两者的区别
- 核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法.
- 如之前写的 Animal 例子. 此处的 Animal 中包含一个 name 这样的属性, 这个属性在任何子类中都是存在的. 因此此处的 Animal 只能作为一个抽象类, 而不应该成为一个接口.
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
好了,如果有错误或者补充,欢迎大家留言评论。