抽象类
概念
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
注:
- 抽象类不能实例化对象,其余的属性和方法和普通的类相同。
- 由于其不能实例化对象,所以抽象类必须被继承;因父类本身是抽象的,所以其不能使用其内部的方法。
- 在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
例1:
Animal是动物类,但由于它不是一个具体的动物,所以其内部back()方法无法实现,Dog是狗类,它是动物。所以它继承类动物类,并且狗是一个具体的动物,即back()方法可以实现;同理可知,Cat是猫类,所以它继承类动物类,并且狗是一个具体的动物,即back()方法可以实现。
例2:
Shape是图像类,它不是一个具体的形状,所以内部draw()方法无法实现,但矩形,圆形,三角形都是具体的图像,它们就可以继承抽象类Shape类,实现其draw()属性。
我们发现父类的draw()方法无法实现,但可以交付其子类完成;像这种没有实际工作的方法, 我们可以把它设计成一个抽象方法(abstractmethod), 包含抽象方法的类我们称为 抽象类(abstract class).
语法
public abstract class Shape {
abstract public double area();
abstract public double perimeter();
abstract public void draw();
}
注:
- 抽象类也是类可以添加属性和普通方法,以及构造方法。
特性
- 成员变量不可以被abstract修饰,
public abstract class Shape {
abstract private int Q;
abstract public int v;
abstract int s;
}
运行结果:
- 抽象类中方法不可以被private修饰
public abstract class Shape {
abstract private int aa();
}
运行结果: 注:抽象方法没有加访问限定符时,默认是public。
- 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
public abstract class Shape {
abstract final void methodA();
abstract public static void methodB();
}
运行结果: 4. 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰
package package2;
public abstract class Shape {
abstract public double area();
abstract public double perimeter();
abstract public void draw();
}
package package2;
public class Rectangle extends Shape{
private double length;
private double width;
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
@Override
public double area() {
return getWidth() * getLength();
}
@Override
public double perimeter() {
return 2 * (getLength() + getWidth());
}
@Override
public void draw() {
System.out.println("矩形");
}
}
package package2;
public class Triangle extends Shape{
private double a, b, c;
public double getA() {
return a;
}
public void setA(double a) {
this.a = a;
}
public double getB() {
return b;
}
public void setB(double b) {
this.b = b;
}
public double getC() {
return c;
}
public void setC(double c) {
this.c = c;
}
@Override
public double area() {
double p = (getA() + getB() + getC()) / 2;
return Math.sqrt(p * (p - getA()) * (p - getB()) * (p - getC()));
}
@Override
public double perimeter() {
return getA() + getB() + getC();
}
@Override
public void draw() {
System.out.println("三角形");
}
}
package package2;
public class Circle extends Shape{
private double diameter;
public double getDiameter() {
return diameter;
}
public void setDiameter(double diameter) {
this.diameter = diameter;
}
@Override
public double area() {
return Math.PI * Math.pow(getDiameter() / 2, 2);
}
@Override
public double perimeter() {
return Math.PI * getDiameter();
}
@Override
public void draw() {
System.out.println("圆形");
}
}
- 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
- 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
作用
在我们初学者看来抽象类和抽象方法是多余的,并且该类还不能实例化对象,但是Java语法提供这种方法,必然有它的道理,比如:当我们继承抽象类时,我们必须要知道它某个具体的子类;使用抽象方法时,子类就知道他必须要实现该方法,而不可能忽略。其次无论是写程序,还是平时做任何别的事情的时候,每个人都可能会犯错,减少错误不能只依赖人的优秀素质,还需要一些机制,使得一个普通人都容易把事情做对,而难以把事情做错。抽象类就是Java提供的这样一种机制。
接口
概念
接口(Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。它是对行为的抽象。
注意事项:
- 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
- 接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。
语法
[可见度] interface 接口名称 [extends 其他的接口名] {
}
使用
接口不能直接使用,必须要有一个 " 实现类"来"实现" 该接口,实现接口中的所有抽象方法
public class 类名称 implements 接口名称{
}
注意:子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系。
请实现笔记本电脑使用USB鼠标、USB键盘的例子
- USB接口:包含打开设备、关闭设备功能
- 笔记本类:包含开机功能、关机功能、使用USB设备功能
- 鼠标类:实现USB接口,并具备点击功能
- 键盘类:实现USB接口,并具备输入功能
package package2;
public interface USB {
void openDevice();
void closeDevice();
}
package package2;
public class Computer {
public void powerOn() {
System.out.println("打开笔记本电脑");
}
public void powerOff() {
System.out.println("关闭笔记本电脑");
}
public void useDevice(USB usb) {
usb.openDevice();
if (usb instanceof Mouse) {
Mouse mouse = new Mouse();
mouse.click();
} else if(usb instanceof keyBoard) {
keyBoard keyBoard = new keyBoard();
keyBoard.inPut();
}
usb.closeDevice();
}
}
package package2;
import package2.USB;
public class Mouse implements USB {
@Override
public void openDevice() {
System.out.println("打开鼠标");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标");
}
public void click() {
System.out.println("鼠标点击");
}
}
package package2;
import package2.USB;
public class keyBoard implements USB {
@Override
public void openDevice() {
System.out.println("打开键盘");
}
@Override
public void closeDevice() {
System.out.println("关闭键盘");
}
public void inPut() {
System.out.println("键盘输入");
}
}
package package2;
public class TestUSB {
public static void main(String[] args) {
Computer computer = new Computer();
computer.powerOn();
computer.useDevice(new Mouse());
computer.useDevice(new keyBoard());
computer.powerOff();
}
}
运行结果:
特性
- 接口中每一个方法是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。所以我们建议接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.
public interface USB {
public abstract void method1();
public void method2();
abstract void method3();
void method();
}
- 接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
public interface USB {
int price = 20;
void openDevice();
void closeDevice();
}
public class TestUSB {
public static void main(String[] args) {
System.out.println(USB.price);
USB.price = 20;
}
}
Error:(13, 12) java: 无法为最终变量price分配值
- 接口类型是一种引用类型,但是不能直接new接口的对象
public static void main(String[] args) {
USB usb = new USB();
}
Error:(12, 19) java: package2.USB是抽象的; 无法实例化
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
public interface USB {
void openDevice() {
System.out.println("打开USB设备");
}
void closeDevice();
Error:(12, 23) java: 接口抽象方法不能带有主体
-
接口,接口方法都是抽象的,所以不必使用abstract关键字,且接口中方法都是共有的。 -
重写接口中方法时,不能使用default访问权限修饰
package package2;
import package2.USB;
public class Mouse implements USB {
@Override
void openDevice() {
System.out.println("打开鼠标");
}
}
Error:(14, 11) java: package2.Mouse中的openDevice()无法实现package2.USB中的openDevice()正在尝试分配更低的访问权限; 以前为public
- JDK 1.8 以后,接口里可以有静态方法和方法体了
- JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。
- JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰
- 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
- 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类。
接口的继承
一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。
public interface Person {
void setSex(String sex);
void setAge(int age);
void setBirthDay(int year, int month, int day);
}
public interface Teacher extends Person{
void assignHomework(String homework);
void solveTheProblem(String problem);
}
public interface MathematicsTeacher extends Teacher{
void teachingMath();
}
public interface Psychologist extends Person{
void teachingPsychology();
}
public interface Doctor extends Person{
void treatPatient();
}
Person接口声明3个方法,Teacher接口声明2个方法,MathematicsTeacher 接口和Psychologist接口 都声明了一个方法,如果要实现其中一个类都要实现6个方法。相似的,实现Doctor接口需要实现4个方法。
- 在Java中,类的多继承是不合法,但接口允许多继承。
public interface Students extends Person,Study{
}
实现多个接口
在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。下面通过类来表示一组动物
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
而后我们提供三个接口,分别是跑,飞,游泳;
public interface IFlying {
void fly();
}
public interface IRunning {
void run();
}
public interface ISwimming {
void swim();
}
接下来我们创建几个具体的动物
public class Cat extends Animal implements IRunning{
public Cat(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在用四条腿跑");
}
}
public class Frog extends Animal implements ISwimming,IRunning{
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在用两条腿奔跑");
}
@Override
public void swim() {
System.out.println(this.name + "正在用两条腿游泳");
}
}
public class Fish extends Animal implements ISwimming{
public Fish(String name) {
super(name);
}
@Override
public void swim() {
System.out.println(this.name + "正在用尾巴游泳");
}
}
public 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 + "正在用两只脚蹼游泳");
}
}
上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口.继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性
- 猫是一种动物, 具有会跑的特性.
- 青蛙也是一种动物, 既能跑, 也能游泳
- 鸭子也是一种动物, 既能跑, 也能游, 还能飞
实现了接口的类,因为它具有这种特性,所以即使忘记类型,但我们知道它具备这种能力,例如,我们实现一个”游泳“方法
public class TestSwimming {
public static void swim (ISwimming swimming) {
System.out.println("我和小动物去游泳");
swimming.swim();
}
public static void main(String[] args) {
Cat cat = new Cat("小小猫");
Duck duck = new Duck("小鸭子");
swim(duck);
Fish fish = new Fish("小鱼");
swim(fish);
}
}
即使我们不知道它是否有这种特性,编译器会告诉我们;这里假如不知道猫会游泳
Error:(17, 14) java: 不兼容的类型: package3.Cat无法转换为package3.ISwimming
运行结果:
甚至参数不是“动物”,只有这个类实现了ISwimming接口,比如:航空母舰
public class AircraftCarrier implements ISwimming{
private String name;
public AircraftCarrier(String name) {
this.name = name;
}
@Override
public void swim() {
System.out.println(this.name + "正在海上航行");
}
}
public static void main(String[] args) {
AircraftCarrier aircraftCarrier = new AircraftCarrier("航空母舰");
swim(aircraftCarrier);
}
接口使用实例
给学生数组按照成绩排序
public class Student implements Comparable {
private String name;
private double score;
public Student(String name, double score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "[" + this.name + ":" + this.score + "]";
}
@Override
public int compareTo(Object o) {
Student s = (Student)o;
if(this.score > s.score) {
return -1;
} else if (this.score < s.score) {
return 1;
} else {
return 0;
}
}
}
public class Test {
public static void main(String[] args) {
Student[] students = new Student[] {
new Student("张三",92),
new Student("李四",96),
new Student("王五",97),
new Student("赵六",95)
};
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
我们不能够直接调用sort方法,因为它不知道按照什么规则来比较,所以我们要重写CompareTo接口。
对象克隆
我们克隆基本类型(boolean,char,byte,short,float,double.long)非常简单,例如
int a = 10;
int b = a;
并且我们修改b的值并不会影响a的值。但对象的克隆并不是这么简单,如果我们按照这种方法来克隆:
public class Money {
public double m = 100;
}
public class Person {
public Money money = new Money();
}
public class Test {
public static void main(String[] args) {
Person person1 = new Person();
Person person2 = person1;
System.out.println("通过Person2修改前的结果");
System.out.println(person1.money.m);
System.out.println(person2.money.m);
System.out.println("通过Person2修改后的结果");
person2.money.m = 200;
System.out.println(person1.money.m);
System.out.println(person2.money.m);
}
}
运行结果: 结果很明显,这里的 Person person2 = person1; 让person2的引用指向person1所引用的对象;所以,person1和person2指向堆的同一个对象。这里的克隆就是浅拷贝。
Object类中的clone()
但在Object类中提供的clone()方法; 在这个文件夹下找到clone()的声明
protected native Object clone() throws CloneNotSupportedException;
我们来看官方文档对其解释:
它还是一个native方法,说明是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。
- 第一次声明保证克隆对象将有单独的内存地址分配。
- 第二次声明表明,原始和克隆的对象应该具有相同的类类型,但它不是强制性的。
- 第三声明表明,原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的。
因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。要想对一个对象进行复制,就需要对clone方法覆盖。
为什么要克隆??
为什么要克隆一个对象呢?为什么我们可以直接new个对象啊,这是因为我们new的对象是“全新的”,整体属性和功能都是“出厂设置”,然而我们想克隆的对象,他本身已经被修改过了,已经具有某种属性,所以克隆是保留这种“状态”。
浅克隆
在浅拷贝中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
-
被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法) -
覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。
package package5;
public class Student implements Cloneable{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
package package5;
public class Test {
public static void main(String[] args) {
Student stu1 = new Student("张三",20);
Student stu2 = (Student)stu1.clone();
System.out.println("通过stu2修改前");
System.out.println("学生1" + stu1.getName() + " " + stu1.getAge());
System.out.println("学生2" + stu2.getName() + " " + stu2.getAge());
System.out.println("通过stu2修改前");
stu2.setAge(30);
stu2.setName("李四");
System.out.println("学生1" + stu1.getName() + " " + stu1.getAge());
System.out.println("学生2" + stu2.getName() + " " + stu2.getAge());
}
}
运行结果: 可以看出其sut2被修改了,如果还不相信它们是不是同一对象,我们可以加上这一句;
System.out.println(stu1 == stu2);
结果:false
上面的克隆叫做浅克隆。下面我们解释深克隆
深克隆
在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
现在我们新增一个School类
package package5;
public class Student implements Cloneable{
private String name;
private School school;
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class School {
private String school;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
public static void main(String[] args) {
School school = new School();
school.setSchool("AAAA");
Student stu1 = new Student();
stu1.setName("0.0");
stu1.setSchool(school);
Student stu2 = (Student)stu1.clone();
System.out.println("修改前");
System.out.println("学生1:"+ stu1.getName() +" " + stu1.getSchool().getSchool());
System.out.println("学生2:"+ stu2.getName() +" " + stu2.getSchool().getSchool());
System.out.println("修改后");
school.setSchool("BBBB");
System.out.println("学生1:"+ stu1.getName() +" " + stu1.getSchool().getSchool());
System.out.println("学生2:"+ stu2.getName() +" " + stu2.getSchool().getSchool());
}
结果:
二者的school竟然相同,原因是浅复制只是复制了school变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。
所以,我们需要将School类可复制化,并且修改clone方法;
@Override
public Object clone() {
School sch = null;
try{
sch = (School) super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return sch;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
stu.school = (School)school.clone();
return stu;
}
运行结果:
抽象类和接口的区别
抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法 更多详细区别在这链接
|