目录
一、抽象类
1.概念
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类
比如:打印图形
class Shape{
public void draw(){
System.out.println("Shape::draw()");
}
}
class Rect extends Shape{
@Override
public void draw(){
System.out.println("♦");
}
}
class Flower extends Shape{
@Override
public void draw(){
System.out.println("❀");
}
}
public class Test{
public static void drawMap(Shape shape){
shape.draw();//动态绑定
}
public static void main(String[] args){
Shape shape1 = new Rect();//向上转型
Shape shape2 = new Flower();//向上转型
drawMap(shape1);
drawMap(shape2);
}
}
2.语法规则
abstract class Shape {
abstract public void draw();
}
abstract class Shape{
public abstract void draw();
}
class Rect extends Shape{
@Override
public void draw(){
System.out.println("♦");
}
}
class Flower extends Shape{
@Override
public void draw(){
System.out.println("❀");
}
}
public class Test{
public static void drawMap(Shape shape){
shape.draw();//动态绑定
}
public static void main(String[] args){
Shape shape1 = new Rect();//向上转型
Shape shape2 = new Flower();//向上转型
drawMap(shape1);
drawMap(shape2);
}
}
3.一些注意事项
(1)抽象类是不可以被实例化的,因此,抽象类只能被继承
(2)抽象类当中也可以包含和普通类一样的成员和方法
abstract class Shape{
public int a;
public void func(){
System.out.println("测试普通方法!");
}
public abstract void draw();//抽象方法
}
(3)一个普通类如果继承了一个抽象类,那么这个普通类当中需要重写这个抽象类的所有抽象方法
abstract class Shape{
public int a;
public void func(){
System.out.println("测试普通方法!");
}
public abstract void draw();//抽象方法
}
class Rect extends Shape{
@Override
public void draw(){
System.out.println("♦");
}
}
(4)一个抽象类A如果继承了一个抽象类B,那么这个抽象类A可以不实现抽象父类B的抽象方法;但是但A再次被一个普通类继承后,那么A和B这两个抽象类当中的抽象方法必须被重写(出来混总是要还的)
abstract class Shape{
public int a;
public void func(){
System.out.println("测试普通方法!");
}
public abstract void draw();//抽象方法
}
abstract class A extends Shape{
public abstract void funcA();
}
class B extends A{
@Override
public void funcA(){
......
}
@Override
public abstract void draw(){
......
}
}
(5)抽象类不能被final修饰,抽象方法也不可以被final修饰(final的功能是限制类被继承,但是抽象类的作用就是被继承,两者相互矛盾)
(6)抽象方法不能是 private 的
(7)抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类
(8)构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法
4.抽象类的作用
二、接口
1.阐述
接口是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
2.语法规则
interface IShape {
//public abstract void draw();完整格式
void draw();//简化格式
}
class Cycle implements IShape {
@Override
public void draw() {
System.out.println("○");
}
}
public class Test {
public static void main(String[] args) {
IShape shape = new Rect();
shape.draw();
}
}
(1)使用 interface 定义一个接口
(2)接口中的方法一定是抽象方法, 因此可以省略 abstract;
(3)接口中的方法一定是 public, 因此可以省略 public
(4)Cycle 使用 implements 继承接口. 此时表达的含义不再是 "扩展", 而是 "实现"
(5)接口当中可以有静态方法
(6)接口当中的成员变量,默认是public static final修饰的
(7)当一个类实现了一个接口,就必须重写接口当中的抽象方法
(8)当一个类实现一个接口之后,重写这个方法的时候,这个方法前面必须加上public
interface IA{
int A = 10;
void funcA();//public abstract
}
class AClass implements IA{
public void funcA(){//子类的访问修饰权限符要>=父类
.......
}
}
(9)接口当中的普通方法,不能有具体的实现,如果非要实现,只能通过关键字default来修饰这个方法
interface IShape{
public abstract void draw();
default public void func(){
System.ou.println("helloworld");
}
}
(10)接口不能单独被实例化
(11)在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例
3.一些书写规范
4.实现多个接口
class Animal{
protected String name;
public Animal(String name){
this.name = name;
}
}
interface IFlying{
void flying();
}
interface IRunning{
void running();
}
class Duck extends Animal implements IFlying,IRunning{
public Duck(String name){
super(name);
}
@Override
public void flying(){
System.out.println(this.name+" 正在飞!");
}
@Override
public void running(){
System.out.println(this.name+" 正在跑!");
}
}
class Cat extends Animal implements IRunning {
public Cat(String name) {
super(name);
}
@Override
public void running() {
System.out.println(this.name + " 正在跑");
}
}
public class TestDemo{
public static void runFunc(IRunning iRunning){
iRunning.running();
}
public static void main(String[] args){
runFunc(new Duck("haha"));
runFunc(new Cat("hehe"));
}
}
编译并运行该程序,输出如下:
haha 正在跑
hehe 正在跑
5.接口间的继承
从上面可以知道,类和类之间、类和接口之间的关系是implements操作的。接口和接口之间可以使用extends来操作它们的关系,这里的extends意为“拓展”。一个接口B通过extends来拓展另一个接口C的功能,此时当一个类D通过impkements实现这个接口B时,重写的方法不仅仅是B的抽象方法,还有它从C接口拓展来的功能(方法)
interface IA{
void funcA();
}
interface IB extends IA{//此时这里也有了IA的功能
void funcB();
}
class C implements IB{
@Override
public void funcB(){
System.out.println("hello");
}
@Override
public void funcB(){
System.out.println("world");
}
}
6.接口使用实例
(1)Comparable接口
import java.util.Arrays;
class Student implements Comparable<Student>{//比较学生
public int age;
public String name;
public double score;
public Student(int age, String name, double score) {
this.age = age;
this.name = name;
this.score = score;
}
//和普通的整数不一样, 两个整数是可以直接比较的, 大小关系明确. 而两个学生对象的大小关系需要额外指定,∴我们让 Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法
@Override
public int compareTo(Student o) {
/*if(this.age > o.age){//谁调用这个方法,谁就是this
return 1;
}else if(this.age == o.age){
return 0;
}else{
return -1;
}*/
//return this.age - o.age;//从小到大排序
//return o.age - this.age;//从大到小比较
//比较分数
//return (int)(this.score - o.score);
//比较名字
return this.name.compareTo(o.name);//引用类型的比较必须借助它可比较的方法
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
}
public class Test {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student(12,"zhangsan",98.3);
students[1] = new Student(6,"lisi",85.9);
students[2] = new Student(16,"wangwu",60.8);
System.out.println(Arrays.toString(students));
Arrays.sort(students);//默认从小到大排序,只有遇到排序自定义类型的时候才可以修改是升序排序还是降序排序
System.out.println(Arrays.toString(students));
}
}
注意:对于 sort 方法来说, 需要传入的数组的每个对象都是 "可比较" 的, 需要具备 compareTo 这样的能力. 通过 重写 compareTo 方法的方式, 就可以定义比较规则
这个接口的一个缺点是:对类的侵入型非常强,一旦代码写好了,不敢轻易改动
(2)Comparator接口(比较器)
import java.util.Arrays;
import java.util.Comparator;
class Student{//比较学生
public int age;
public String name;
public double score;
public Student(int age, String name, double score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
}
class AgeComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
class NameComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
public class Test {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student(12,"zhangsan",98.3);
students[1] = new Student(6,"lisi",85.9);
students[2] = new Student(16,"wangwu",60.8);
System.out.println(Arrays.toString(students));
//AgeComparator ageComparator = new AgeComparator();
NameComparator nameComparator = new NameComparator();
Arrays.sort(students,nameComparator);//默认从小到大排序,只有遇到排序自定义类型的时候才可以修改是升序排序还是降序排序
System.out.println(Arrays.toString(students));
}
}
Comparator接口灵活,对类的侵入性非常弱
但是我们在选择用哪种接口时,要看我们业务的处理
7.Clonable 接口和深拷贝
s Person implements Cloneable{
public int age;
public void eat(){
System.out.println("吃!");
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestDemp {
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
person.age = 99;
Person person2 = (Person)person.clone();//Person默认继承Object,理论上来说是可以调用克隆方法,但是它还比较特殊,调用克隆方法后还要重写克隆方法
System.out.println(person2);
System.out.println("------------------");
person2.age = 100;
System.out.println(person);
System.out.println(person2);
}
}
编译并运行该代码,输出如下:
从这个代码实现来看,是一份深拷贝,但是我们要知道,决定深拷贝还是浅拷贝不是方法的用途,而是代码的实现。我们来看一下这份代码的内存分布图
我们再来看一段代码:
class Money{
public double m = 12.3;
}
class Person implements Cloneable{
public int age;
public Money money = new Money();
public void eat(){
System.out.println("吃!");
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestDemp {
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
Person person2 = (Person)person.clone();//Person默认继承Object,理论上来说是可以调用克隆方法,但是它还比较特殊,调用克隆方法后还要重写克隆方法
System.out.println(person.money.m);
System.out.println(person2.money.m);
System.out.println("------------------");
person2.money.m = 99.99;
System.out.println(person.money.m);
System.out.println(person2.money.m);
}
}
编译并运行该代码,输出如下:
从这个代码实现来看,是一份浅拷贝,我们来看一下这份代码的内存分布图:
那么现在我们想把它变成深拷贝,如何实现呢?只需要将Money也克隆一份,如下代码:
class Money implements Cloneable{
public double m = 12.3;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable{
public int age;
public Money money = new Money();
public void eat(){
System.out.println("吃!");
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
//return super.clone();
//等价于:
//Person tmp = (Person)super.clone();
//return tmp;
Person tmp = (Person)super.clone();
tmp.money = (Money) this.money.clone();
return tmp;
}
}
public class TestDemp {
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
Person person2 = (Person)person.clone();
System.out.println(person.money.m);
System.out.println(person2.money.m);
System.out.println("------------------");
person2.money.m = 99.99;
System.out.println(person.money.m);
System.out.println(person2.money.m);
}
}
编译并运行该代码,输出如下:
我们来看一下这份代码的内存分布图
总结:决定深拷贝还是浅拷贝不是方法的用途,而是代码的实现
8.总结抽象类和接口的区别
No | 区别 | 抽象类 | 接口 |
1 | 结构组成 | 普通类+抽象方法 | 抽象方法+全局变量 |
2 | 权限 | 各种权限 | public |
3 | 子类使用 | 使用extends关键字继承抽象类 | 使用implements关键字实现接口 |
4 | 关系 | 一个抽象类可以实现若干接口 | 接口不能继承抽象类,但是接口可以使用extends关键字继承多个父接口 |
5 | 子类限制 | 一个子类只能继承一个抽象类 | 一个子类可以实现多个接口 |