第二部分 面向对象篇
第八章 继承
8.1 继承概述
继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。继承是面向对象最显著的一个特征。这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。
示例:
class Student {
String name;
int age;
void study() {
System.out.println("study....");
}
}
class Worker {
String name;
int age;
void work() {
System.out.println("work....");
}
}
我们从以上代码可以看到的是,多个事物之间有共同的属性或行为,这种代码的复用性比较差,所以我们可以将多个事物之间重复性的属性或行为进行抽取,抽取出来之后放在另外一个单独的类里面即可。示例如下:
class Student {
void study() {
System.out.println("study....");
}
}
class Worker {
void work() {
System.out.println("work....");
}
}
class Teacher {
void teach() {
System.out.println("teach....");
}
}
class Person {
String name;
int age;
}
如上所示,虽然我们将Person类抽取出来,但是目前 Worker 和 Student 和 Teacher 与 Person 还是没有关系的;我们需要使用关键字 extends 来让类之间产生这种父(Person)子(Worker Student Teacher)的关系。如下所示:
class Student extends Person{
void study() {
System.out.println("study....");
}
}
class Worker extends Person{
void work() {
System.out.println("work....");
}
}
class Teacher extends Person {
void teach() {
System.out.println("teach....");
}
}
class Person {
String name;
int age;
}
总结:把多个事物中重复性的内容进行抽取,并生成另外一个类,该类就是其他类的父类,其他的类称之为子类,父类与子类之间用 extends 关键字来标明。
继承的好处:
· 继承的出现提高了代码的复用性
· 继承的出现让类与类之间产生关系,也为我们后面多态提供了前提。
单继承与多继承:
在我们的现实生活中,父与子是一对多的关系,子与父是一对一关系。使用继承时,除了要考虑类与类之间重复的内容之外,更重要的是去考虑关系,必须是一种 “ is a ” 关系,即XXX是YYY的一种!!!例如:苹果是水果的一种,狗是动物的一种。在Java中类与类之间只能支持单继承,接口与接口之间可以支持多继承。
继承体系:
综上,既然有了单继承,也有了父-子关系,爷-孙关系,曾祖父-重孙关系,继承出现了层次,也就是继承体系。例如:
class A {}
class B extends A {}
class C extends A {}
class D extends C {}
......
注意:
· 继承和传统的理解稍微有些不同,子类并不是或是父类的一个子集!!!实际上,一个子类通常比它的父类包含更多的信息和方法。(即子类更多的是父类的一种升级和延申)
· 父类中的一些私有内容,子类是获取不到的。如果父类对其自身的私有内容设置了公共的访问器和修改器的话,子类可以通过该访问器和修改器来获取父类的私有内容。
· 不可以为了获取某一个特殊的属性或行为,而去乱认“爸爸”。
· 讲了这么多,总之就是想表达一个观点,在设计类和其继承关系时,一定要符合社会常识认知伦理问题。
子父类中,成员变量的特点:
· 子类没有,父类有且非私有,子类对象能获取
· 子类没有,父类有且私有,子类对象不能获取
· 子类有,父类有且非私有,子类对象获取的是子类的 (不存在重写的概念)
· 子类有,父类没有,子类对象获取的是子类的
示例:
public class Sample {
public static void main(String[] args) {
Zi zi = new Zi();
System.out.println(zi.num);
zi.show();
}
}
class Fu {
int num = 10;
}
//父类 超类 基类
class Zi extends Fu {
int num = 20;
void show() {
int num = 30;
System.out.println(num + "," + this.num + "," + super.num);
}
}
如果子类中,成员变量 和 父类变量 和 局部变量 重名时,如上面示例代码即可。这里面有一个特殊的 super 。
· this 表示的是当前对象,存的是当前对象在堆内存中的地址。
· super 不表示父类的对象,因为在此我们并没有去创建父类的对象!!! super 仅仅表示父类空间,并没有创建父类对象!!!
在父类中,构造函数的特点:
示例:
public class Sample {
public static void main(String[] args) {
Zi zi = new Zi();
}
}
class Fu {
Fu() {
System.out.println("Fu show...");
}
}
class Zi extends Fu {
Zi() {
System.out.println("Zi show...");
}
}
如上可以看到的是,当创建子类对象时,在子类的构造函数执行之前,父类的构造函数先执行,虽然父类的构造函数执行但不代表父类对象的创建。
· 每一个类中的构造函数第一句如果不是 this() 调用本类中其他构造函数的话,默认第一句是隐藏的 super()
· 是因为在创建子类对象的时候,需要父类为继承给子类的一些数据进行初始化。
public class Sample {
public static void main(String[] args) {
Zi zi = new Zi();
}
}
class Fu {
int num;
Fu() {
System.out.println("Fu show..." + num);
num = 4;
}
}
class Zi extends Fu {
Zi() {
super(); //调用父类的无参构造函数 默认隐藏的
//父类的构造函数一旦执行完毕 则紧接着执行子类成员变量的显示初始化
System.out.println("Zi show..." + num);
}
}
this() 与 super() 是否冲突?
public class Sample {
public static void main(String[] args) {
Zi zi1 = new Zi();
Zi zi2 = new Zi(1);
Zi zi3 = new Zi(1,2);
}
}
class Fu {
int num;
Fu() {
System.out.println("Fu show...");
}
}
class Zi extends Fu {
Zi() {
System.out.println("Zi show ... 0");
}
Zi(int a) {
System.out.println("Zi show ... 1");
}
Zi(int a , int b) {
System.out.println("Zi show ... 2");
}
}
如果子类的构造函数们之间没有调用关系,则每一个子类的构造函数第一句都是 super()
public class Sample {
public static void main(String[] args) {
Zi zi1 = new Zi();
Zi zi2 = new Zi(1);
Zi zi3 = new Zi(1,2);
}
}
class Fu {
int num;
Fu() {
System.out.println("Fu show...");
}
}
class Zi extends Fu {
Zi() {
this(1);
System.out.println("Zi show ... 0");
}
Zi(int a) {
this(1,2);
System.out.println("Zi show ... 1");
}
Zi(int a , int b) {
super();
System.out.println("Zi show ... 2");
}
}
在上一段代码中,Fu show 的执行,在每一个构造函数中的第一句 super() 执行
在这一段代码中,Fu show 的执行,在 Zi (int, int) 调用执行的。
注:
· 在构造函数中,第一句要么是 this() ,要么是 super()
· 不可能每一个构造函数第一句都是 this() ,因为会递归调用
· 有可能每一个构造函数第一句都是 super() ,因为构造函数之间可以不调用。
· this() 与 super() 本身不冲突,如果构造函数之间有调用关系,那么最后一个被调用的构造函数就不能再回调,那么其第一句就不能是 this() ,只能是 super()
public class Sample {
public static void main(String[] args) {
Zi zi1 = new Zi();
Zi zi2 = new Zi(1);
Zi zi3 = new Zi(1,2);
}
}
class Fu {
int num;
Fu(int a) {
System.out.println("Fu show...");
}
}
class Zi extends Fu {
Zi() {
this(1);
System.out.println("Zi show ... 0");
}
Zi(int a) {
this(1,2);
System.out.println("Zi show ... 1");
}
Zi(int a , int b) {
super(1);
System.out.println("Zi show ... 2");
}
}
如果父类中,没有无参构造函数的存在,只有有参数的构造函数的话,那么子类中默认的 super() 就调用不到父类无参构造函数!!!引发错误!
所以,建议每一个类都把它的无参构造函数写出来。
public class Sample {
public static void main(String[] args) {
Zi zi1 = new Zi();
Zi zi2 = new Zi(3);
Zi zi3 = new Zi(5,6);
}
}
class Fu {
int num;
Fu () {
System.out.println("Fu constructor... 0 " + "num = " + num);
num = 10;
}
Fu (int a) {
System.out.println("Fu constructor... 1 " + "num = " + num);
num = a;
}
}
class Zi extends Fu {
int num = 10;
Zi() {
System.out.println("Zi constructor... 0 " + "num = " + num + " fu num = " +
super.num);
}
Zi(int a) {
this(a,0);
System.out.println("Zi constructor... 1 " + "num = " + num + " fu num = " +
super.num);
}
Zi(int a, int b) {
super(a + b);
num = a + b;
System.out.println("Zi constructor... 2 " + "num = " + num + " fu num = " +
super.num);
}
}
子父类中,成员函数的特点:
public class Sample {
public static void main(String[] args) {
Zi zi = new Zi();
zi.showA();
zi.showB();
zi.showC();
zi.showD();
}
}
class Fu {
void showA() {
System.out.println("Fu showA...");
}
void showC() {
System.out.println("Fu showC...");
}
private void showD() {
System.out.println("Fu showD...");
}
}
class Zi extends Fu{
void showB() {
System.out.println("Zi showB...");
}
@Override
void showC() {
System.out.println("Zi showC...");
}
void showD() {
System.out.println("Zi showD...");
}
}
· 如果父类有,子类没有,调用的是父类的
· 如果父类没有,子类有,调用的是子类的
· 如果父类有,子类也有,调用的是子类的
· 如果父类有,但是为私有,则子类继承不到,除非子类自己写一个
上述第三种情况,我们称之为是函数的重写/覆盖/Override
重写有什么用呢?严格意义上而言,子类并非是父类的一个子集。子类的内容很大程度上,很多情况下,都是父类的一种扩展或增量,重写仅仅保留了父类的功能声明,但是具体的功能内容由子类来决定!!!
public class Sample {
public static void main(String[] args) {
IPhoneX ix = new IPhoneX();
ix.call();
}
}
class IPhone1 {
void call () {
System.out.println("电话拨号打电话...");
}
}
class IPhone4s extends IPhone1 {
void call() {
super.call();
System.out.println("多人同时通话....");
}
}
class IPhoneX extends IPhone4s {
void call() {
super.call();
System.out.println("facetime视频通话....");
}
void have5G() {
System.out.println("具有5G功能");
}
}
如果子类中有同名函数且参数列表相同(私有例外),编译器就认为是重写关系!!! 重写的时候,子类的权限必须大于等于父类的权限;重写的时候,返回值不能更改。
public class Sample {
public static void main(String[] args) {
Zi zi = new Zi();
zi.showC(10);
zi.showC(10,20);
}
}
//public > 默认 > protected > private
class Fu {
public void show(){
System.out.println("Fu show...");
}
public int showB() {
return -1;
}
public void showC(int a){
System.out.println("Fu showC...");
}
}
class Zi extends Fu {
void show() {
System.out.println("Zi show...");
}
public void showB() {
}
public void showC(int a,int b){
System.out.println("Zi showC....");
}
}
/*
Sample.java:13: 错误: Zi中的show()无法覆盖Fu中的show()
void show() {
^
正在尝试分配更低的访问权限; 以前为public
1 个错误
*/
/*
Sample.java:21: 错误: Zi中的showB()无法覆盖Fu中的showB()
public void showB() {
^
返回类型void与int不兼容
*/
子父类中,静态成员的特点:
静态变量的特点与成员变量是一致的
静态函数的特点与成员函数是一致的
public class Sample {
public static void main(String[] args) {
Zi zi = new Zi();
System.out.println(zi.num);
zi.show();
}
}
class Fu {
public static int num = 10;
public static void show() {
System.out.println("Fu static show....");
}
}
class Zi extends Fu {
public static int num = 100;
static void show() {
System.out.println("Zi static show....");
}
}
综合案例:
public class Sample {
public static void main(String[] args) {
ArrayList list = new ArrayList();
for (int i = 1; i <= 12; i++) {
list.add(0,i);
}
System.out.println(list);
System.out.println(list.get(3));
System.out.println(list.delete(3));
System.out.println(list);
Stack stack = new Stack();
for (int i = 1; i <= 5; i++) {
stack.push(i);
}
System.out.println(stack);
System.out.println(stack.pop());
System.out.println(stack);
Queue queue = new Queue();
for (int i = 1; i <= 5; i++) {
queue.enqueue(i);
}
System.out.println(queue);
System.out.println(queue.dequeue());
System.out.println(queue);
}
}
class ArrayList {
private int[] data;
private int size;
int capacity = 10;
public ArrayList() {
data = new int[capacity];
size = 0;
}
//在指定角标处加入元素e
public void add(int index,int e) {
//1.对index做合法性处理
if (index < 0 || index > size) {
System.out.println(">>>插入位置不合法!");
return;
}
//2.考虑扩容的问题
if (size == data.length) {
resize(data.length * 2);
}
//3.合法加入 别忘了移动元素 不合法 结束并提示
for (int i = size - 1; i >= index; i--) {
data[i + 1] = data[i];
}
data[index] = e;
size++;
}
//删除指定角标处的元素 并返回
public int delete(int index) {
//1.角标的合法性
if (index < 0 || index >= size) {
System.out.println(">>>删除位置不合法!");
return -1;
}
//2.删除即可 别忘了移动元素
int ret = data[index];
for (int i = index + 1; i < size; i++) {
data[i - 1] = data[i];
}
//3.考虑缩容的问题
size--;
if (size == data.length / 4 && data.length > capacity) {
resize(data.length / 2);
}
return ret;
}
//获取指定角标处的元素
public int get(int index) {
//1.考虑角标的合法性
if (index < 0 || index >= size) {
System.out.println(">>>获取位置不合法!");
return -1;
}
//2.直接返回
return data[index];
}
//扩容+缩容的操作
private void resize(int newlength) {
int[] newData = new int[newlength];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
data = newData;
}
public boolean isEmpty() {
return size == 0;
}
public int size() {
return size;
}
public String toString() {
//如果size == 0 返回 []
//如果不是[开始 拼接数字 每一个数字后面有个, 但是最后一个数字后面拼接]
String s = "[";
if (isEmpty()) {
s += "]";
} else {
for (int i = 0; i < size; i++) {
if (i == size - 1) {
s += data[i] + "]";
} else {
s += data[i] + ",";
}
}
}
return s;
}
}
class Stack extends ArrayList{
public Stack() {
super();
}
public void push(int e) {
add(size(),e);
}
public int pop() {
return delete(size() - 1);
}
public int peek() {
return get(size() - 1);
}
}
class Queue extends ArrayList {
public Queue() {
super();
}
public void enqueue(int e) {
add(size(),e);
}
public int dequeue() {
return delete(0);
}
public int front() {
return get(0);
}
public int rear() {
return get(size() - 1);
}
}
8.2 final关键字
final 翻译为最终的意思,final 可以修饰变量,函数,类
final 修饰变量:
表示该变量的值不可被改变
变量主要分为两种,基本数据类型,引用数据类型的变量
· final 修饰的是基本数据类型变量,表示变量所存储的常量值不能改变
· final 修饰的是引用数据类型变量,表示变量所存储的对象地址值不能改变,但是可以改变该对象中的数据(如果对象中的数据也是 final 则也不能修改)
public class Sample {
public static void main(String[] args) {
Demo d = new Demo();
System.out.println(d.num);
/*
d.num = 20;
Sample.java:5: 错误: 无法为最终变量num分配值
d.num = 20;
^
1 个错误
*/
final int a = 20;
/*
a = 30;
Sample.java:13: 错误: 无法为最终变量a分配值
a = 30;
^
1 个错误
*/
final int[] arr = new int[]{1,2,3};
/*
arr = new int[]{4,5,6};
Sample.java:21: 错误: 无法为最终变量arr分配值
arr = new int[]{4,5,6};
^
1 个错误
*/
arr[0] = 10;
}
}
class Demo {
//当前这个变量的值不能被修改
public final int num = 10;
}
public class Sample {
public static void main(String[] args) {
final Demo d = new Demo();
//d = new Demo(); //error
//d.num = 30;//error
d.haha = 40;//ok
}
}
class Demo {
public final int num = 10;
public int haha = 20;
}
一般而言,当我们在定义常量(字面量 用变量 + final 来表示),定义成静态变量
public static final 数据类型 变量名 = 常量数据;
对于常量的变量名起名规则为,全部单词大写,单词与单词之间用下划线分隔
public static final double PI = 3.14;
public static final int MAX_VALUE = 100;
final 修饰函数:
如果某一个类中的函数,不想让其子类去重写的话,该函数就可以声明为 final 类型
class Fu {
final void show(){}
}
class Zi extends Fu {
void show(){}
/*
Sample.java:10: 错误: Zi中的show()无法覆盖Fu中的show()
void show(){}
^
被覆盖的方法为final
1 个错误
*/
}
Zi 类确实继承到了 show() ,也可以调用该函数,但是由于 show 是 final 的,所以子类不能重写父类的 show 函数。
final 修饰类:
表示该类不能被继承
final class Fu {
}
class Zi extends Fu {
}
/*
Sample.java:9: 错误: 无法从最终Fu进行继承
class Zi extends Fu {
^
1 个错误
*/
8.3 抽象类
抽象:指的就是不具体,模糊不清这种含义,看不懂,不明白;不能直接将抽象和类挂钩,之所以有抽象类的存在,是因为有抽象函数!!!具有抽象函数的类,称之为叫抽象类!!!
抽象函数:
public class Sample {
public static void main(String[] args) {
Dog d = new Dog();
d.jiao();
d.eat();
Cat c = new Cat();
c.jiao();
c.eat();
Animal a = new Animal();
a.jiao();
a.eat();
}
}
abstract class Animal {
abstract void jiao();
abstract void eat();
}
class Dog extends Animal{
@Override
void jiao() {
System.out.println("汪汪汪~");
}
void kanmen() {
System.out.println("狗看门~");
}
@Override
void eat() {
System.out.println("狗吃粮");
}
}
class Cat extends Animal{
@Override
void jiao() {
System.out.println("喵喵喵~");
}
void catchmouse() {
System.out.println("猫捉老鼠~");
}
@Override
void eat() {
System.out.println("猫吃鱼~");
}
}
抽象函数:当我们将多个事物的共同行为(函数)进行抽取并封装到另外一个类中时,发现在该类中,这些方法的具体执行内容无法确定,只能由这些子类来决定该函数的具体执行,那么在该类中,将这些抽取来的函数仅保留函数声明,但不保留函数体即可,那么该函数就是抽象函数,用abstract关键字来修饰,既然有了抽象函数的存在,那么具有抽象函数的类也被称之为抽象类,也必须用abstract修饰。抽象类不能创建对象,只有其实现子类能够创建对象。
class Pig extends Animal {
void gongbaicai(){
System.out.println("猪拱白菜~");
}
}
此处的 Pig 虽是 Animal 的子类,但是并未重写/覆盖/实现 Animal 中的抽象函数,所以编译报错!!!如何解决呢? 要么把 Pig 声明为抽象类,要么 Pig 重写这些抽象函数!
抽象类的特点:
· 抽象类和抽象函数都需要被abstract修饰,抽象方法一定在抽象类中
· 抽象类不能创建对象,因为如果一旦创建对象,在调用其函数时,函数没有具体执行内容
· 只有覆盖了抽象类中所有的抽象函数后,子类才可以实例化。否则,该子类还是一个抽象类
关于抽象类的几个细节问题:
· 抽象类一定是一个父类吗?
是的,因为抽象类本身就是有多个事物进行抽取而来的
· 抽象类是否有成员变量、成员函数、构造函数呢?
有,抽象类与一般类唯一的区别就是抽象类中抽象函数,其他一律相同(抽象类不能创建对象)!
抽象类和一般类的异同点:
相同点:
1.它们都是用来描述事物的
2.它们之中都可以定义属性和行为
不同点:
1.一般类可以具体的描述是,抽象类描述事物时会有一些不具体的信息
3.抽象类比一般类可以多定义一个成员:抽象函数
4.一般类可以创建对象,而抽象类不能创建对象
· 抽象类中是否可以不定义抽象函数?
可以的。有抽象函数的类一定是抽象类,抽象类不一定有抽象函数!
· 抽象关键字abstract不能与那些其他的关键字共存
final:final修饰类,表示该类不能被继承;final修饰函数时,表示函数不能被重写;
不能,抽象类本就是父类,并且其中的抽象函数就等着被子类重写。
private:private修饰函数,表示函数被私有,不能被子类继承;不能,抽象函数就等着被子类重写。
static:static修饰的函数,属于类的,随着类的加载从而被加载方法区中,和对象没有关系了,可以直接用类来调用静态成员,如果抽象函数被静态修饰,被类调用时没意义。
综合案例:
public class Sample {
public static void main(String[] args) {
}
}
//定义一个线性表List类 声明了该线性表的共同操作
abstract class List{
//在指定角标index处添加元素e
public abstract void add(int index,int e);
//删除指定角标index处的元素,并返回该元素的值
public abstract int delete(int index);
//返回指定角标index处的元素
public abstract int get(int index);
//修改指定角标处index的元素为新元素e,并返回原先的值
public abstract int set(int index,int e);
//返回线性表中的有效元素个数
public abstract int size();
//清空线性表
public abstract void clear();
//判断线性表是否为空
public abstract boolean isEmpty();
}
class Node {
int data; //数据域
Node next ;//指针域
public Node() {
}
public Node(int data) {
this.data = data;
}
public Node(int data,Node next) {
this.data = data;
this.next = next;
}
public String toString() {
return "(" + data + ")";
}
}
class LinkedList extends List {
private Node head;
private int size;
public LinkedList() {
head = new Node();
size = 0;
}
//在指定角标index处添加元素e
public void add(int index,int e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("get() index out of range");
}
Node pre = getNode(index - 1);
Node n = new Node(e);
n.next = pre.next;
pre.next = n;
size++;
}
//删除指定角标index处的元素,并返回该元素的值
public int delete(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("get() index out of range");
}
Node pre = getNode(index - 1);
Node p = pre.next;
pre.next = p.next;
p.next = null;
size--;
return p.data;
}
//返回指定角标index处的元素
public int get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("get() index out of range");
}
return getNode(index).data;
}
//返回指定角标index处的结点对象
private Node getNode(int index) {
Node p = head;
for (int i = 0; i <= index; i++) {
p = p.next;
}
return p;
}
//修改指定角标处index的元素为新元素e,并返回原先的值
public int set(int index,int e) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("set() index out of range");
}
Node p = getNode(index);
int ret = p.data;
p.data = e;
return ret;
}
//返回线性表中的有效元素个数
public int size() {
return size;
}
//清空线性表
public void clear(){
head.next = null;
size = 0;
}
//判断线性表是否为空
public boolean isEmpty(){
return size == 0 && head.next == null;
}
}
8.4 接口
从代码的角度上而言,接口其实就是抽象类的一种特殊表现形式
当一个抽象类中,所有的方法都是抽象函数时,那么,该类就可以用接口来表示。
接口还是类吗?不是类了,一些类的功能和操作不再适用于接口。
接口中没有成员函数,没有成员变量,没有构造函数,没静态函数,没有静态变量
接口也不能直接去创建对象
interface List{
public abstract void add(int index,int e);
public abstract int delete(int index);
public abstract int get(int index);
public abstract int set(int index,int e);
public abstract int size();
public abstract void clear();
public abstract boolean isEmpty();
}
接口中的变量和函数就会有一些特殊的含义:
· 接口中的变量 默认是公共静态常量 public static final类型 就算不写这些关键字 也是默认的
· 接口中的函数 默认是公共抽象的函数 public abstract 类型 就算不写这些关键字 也是默认的
interface InterfaceA {
int num = 10;
public numA = 20;
//这里面num已经不是成员变量的含义 默认是public static final类型
void show();
public void showA();
//默认是public abstract 类型
}
既然接口已经不算是类了,它和类之间又有什么关系呢?
类与类之间是单继承关系,类与接口之间是多实现关系,接口与接口之间是多继承关系
类与接口之间的实现关系用implements关键字表示
· 要么这个类实现接口中所有的方法
· 要么这个类声明为abstract 这一点和继承抽象类一致的
interface InterfaceA {
void showA();
}
class DemoA implements InterfaceA{
@Override
public void showA(){
System.out.println("DemoA showA...");
}
}
abstract class DemoA implements InterfaceA{
}
类与接口也有着多实现的存在,要么把这几个接口全部实现,要么声明为abstract
interface InterfaceA {
void showA();
}
interface InterfaceB {
void showB();
}
class DemoB implements InterfaceA,InterfaceB {
@Override
public void showA(){}
@Override
public void showB(){}
}
接口与接口有着多继承的关系,对于InterfaceC接口的实现子类而言,要么把这几个接口全部实现,
要么声明为abstract
interface InterfaceA {
void showA();
}
interface InterfaceB {
void showB();
}
interface InterfaceC extends InterfaceA,InterfaceB{
void showC();
}
class DemoC implements InterfaceC {
@Override
public void showA(){}
@Override
public void showB(){}
@Override
public void showC(){}
}
接口与接口之间会不会存在实现关系? 不存在
interface InterfaceA {
void showA();
}
interface InterfaceB implements InterfaceA{
void showB();
}
接口中的特点:
· 接口中的变量都是全局静态常量
· 接口中的方法都是全局抽象函数
· 接口不可以创建对象
· 子类必须覆盖接口中所有的抽象方法,或声明为abstract
· 类与接口之间可以存在多实现关系
· 接口与接口之间可以存在多继承关系
· 类与类之间只能是单继承关系
接口的存在,主要解决的就是类与类之间只有单继承的关系
如果一个类,可以存在两种状态的描述时,类在继承的同时也可以进行接口的实现
class DemoA {
void showA() {}
}
interface DemoB {
void showB();
}
//如果DemoC既有DemoA的描述 也有DemoB的描述
//也就是意味着DemoC可能要有两个父类 Java机制中不允许多父类的存在
class DemoC extends DemoA implements DemoB{
public void showA(){}
public void showB(){}
}
当一个类在继承另外一个类的时候,就已经拥有了该继承体系的所有功能,接口的作用就是在这些体系功能之上,增加的一些额外的功能!大大的扩展了类的功能!
public class Sample {
public static void main(String[] args) {
拉布拉多 d1 = new 拉布拉多();
d1.jiao();
d1.eat();
d1.缉毒();
金毛 d2 = new 金毛();
d2.缉毒();
d2.防爆();
d2.导盲();
}
}
//表示一个实体描述类
abstract class Animal {
String name;
int age;
public abstract void jiao();
public abstract void eat();
}
interface 导盲{
void 导盲();
}
interface 缉毒{
void 缉毒();
}
interface 防爆 {
void 防爆();
}
class 拉布拉多 extends Animal implements 缉毒{
public 拉布拉多() {
name = "拉布拉多";
age = 10;
}
public void jiao() {
System.out.println("我是"+name+",我今年"+age+"岁");
}
public void eat() {
System.out.println("我是"+name+",吃香蕉");
}
public void 缉毒() {
System.out.println("我是"+name+",我可以缉毒");
}
}
class 黑贝 extends Animal implements 防爆{
public 黑贝() {
name = "黑贝";
age = 11;
}
public void jiao() {
System.out.println("我是"+name+",我今年"+age+"岁");
}
public void eat() {
System.out.println("我是"+name+",吃苹果");
}
public void 防爆() {
System.out.println("我是"+name+",我可以防爆");
}
}
class 金毛 extends Animal implements 导盲,缉毒,防爆{
public 金毛() {
name = "金毛";
age = 20;
}
public void jiao() {
System.out.println("我是"+name+",我今年"+age+"岁");
}
public void eat() {
System.out.println("我是"+name+",吃面包");
}
public void 防爆() {
System.out.println("我是"+name+",我可以防爆");
}
public void 缉毒() {
System.out.println("我是"+name+",我可以缉毒");
}
public void 导盲() {
System.out.println("我是"+name+",我可以导盲");
}
}
class 哈士奇 extends Animal{
public 哈士奇() {
name = "哈士奇";
age = 5;
}
public void jiao() {
System.out.println("我是"+name+",我今年"+age+"岁");
}
public void eat() {
System.out.println("我是"+name+",吃屁");
}
}
也可以将LinkedList扩展栈Stack和队列Queue的功能
public class Sample {
public static void main(String[] args) {
}
}
//定义一个线性表List类 声明了该线性表的共同操作
abstract class List{
//在指定角标index处添加元素e
public abstract void add(int index,int e);
//删除指定角标index处的元素,并返回该元素的值
public abstract int delete(int index);
//返回指定角标index处的元素
public abstract int get(int index);
//修改指定角标处index的元素为新元素e,并返回原先的值
public abstract int set(int index,int e);
//返回线性表中的有效元素个数
public abstract int size();
//清空线性表
public abstract void clear();
//判断线性表是否为空
public abstract boolean isEmpty();
}
interface Stack{
void push(int e);
int pop();
int peek();
}
interface Queue {
void enqueue(int e);
int dequeue();
int front();
int rear();
}
class ArrayList extends List implements Stack,Queue {
//自己写!
}
class LinkedList extends List implements Stack,Queue {
private Node head;
private int size;
public LinkedList() {
head = new Node();
size = 0;
}
public void push(int e) {
add(size,e);
}
public int pop() {
return delete(size - 1);
}
public int peek() {
return get(size - 1);
}
public void enqueue(int e) {
add(size,e);
}
public int dequeue() {
return delete(0);
}
public int front() {
return get(0);
}
public int rear() {
return get(size - 1);
}
//在指定角标index处添加元素e
public void add(int index,int e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("get() index out of range");
}
Node pre = getNode(index - 1);
Node n = new Node(e);
n.next = pre.next;
pre.next = n;
size++;
}
//删除指定角标index处的元素,并返回该元素的值
public int delete(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("get() index out of range");
}
Node pre = getNode(index - 1);
Node p = pre.next;
pre.next = p.next;
p.next = null;
size--;
return p.data;
}
//返回指定角标index处的元素
public int get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("get() index out of range");
}
return getNode(index).data;
}
//返回指定角标index处的结点对象
private Node getNode(int index) {
Node p = head;
for (int i = 0; i <= index; i++) {
p = p.next;
}
return p;
}
//修改指定角标处index的元素为新元素e,并返回原先的值
public int set(int index,int e) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("set() index out of range");
}
Node p = getNode(index);
int ret = p.data;
p.data = e;
return ret;
}
//返回线性表中的有效元素个数
public int size() {
return size;
}
//清空线性表
public void clear(){
head.next = null;
size = 0;
}
//判断线性表是否为空
public boolean isEmpty(){
return size == 0 && head.next == null;
}
}
class Node {
int data; //数据域
Node next ;//指针域
public Node() {
}
public Node(int data) {
this.data = data;
}
public Node(int data,Node next) {
this.data = data;
this.next = next;
}
public String toString() {
return "(" + data + ")";
}
}
接口除了作为类的功能扩展之外,接口还可以作为一种对外暴露的规则,来进行解耦操作
public class Sample {
public static void main(String[] args) {
Computer computer = new Computer();
Mouse mouse = new Mouse();
KeyBoard keyboard = new KeyBoard();
Camera camera = new Camera();
computer.getDev1(mouse);
computer.getDev2(keyboard);
computer.getDev3(camera);
computer.getDev3(mouse);
}
}
interface USB {
void getConnect();
}
class Computer {
public void getDev4(Camera c) {
}
public void getDev5(KeyBoard k) {
}
//获取USB接口的设备1
public void getDev1(USB dev) {
System.out.println("电脑连接了设备1");
dev.getConnect();
}
//获取USB接口的设备2
public void getDev2(USB dev) {
System.out.println("电脑连接了设备2");
dev.getConnect();
}
//获取USB接口的设备3
public void getDev3(USB dev) {//USB dev = new Mouse();
System.out.println("电脑连接了设备3");
dev.getConnect();
}
}
class Mouse implements USB{
public void getConnect() {
System.out.println("鼠标已经连接电脑,开始运行...");
}
}
class KeyBoard implements USB {
public void getConnect() {
System.out.println("键盘已经连接电脑,开始运行...");
}
}
class Camera implements USB {
public void getConnect() {
System.out.println("相机连接电脑,开始运行...");
}
}
接口之间可以多继承,类之间不能多继承
public class Sample {
public static void main(String[] args) {
Demo d = new Demo();
d.show();
}
}
interface InterfaceA {
void show();//妈妈希望你过的好
}
interface InterfaceB {
void show();//爸爸希望你过的好
}
interface InterfaceC extends InterfaceA,InterfaceB{
}
class Demo implements InterfaceC {
public void show() { //自己过的好不好 自己决定
System.out.println("showc....");
}
}
public class Sample {
public static void main(String[] args) {
DemoC dc = new DemoC();
dc.show();//产生调用的二义性 因为两个父类中的函数都有函数体 不知执行哪个!
}
class DemoA {
void show() { //妈妈希望你成才 成为医生
System.out.println("Kill DemoB");
}
}
class DemoB {
void show() { //爸爸希望你成才 成为教师
System.out.println("Kill DemoA");
}
}
class DemoC extends DemoA,DemoB {
//成才 成啥?不知道 二义性
}
归根结底,看函数的函数体
接口与抽象类的区别
相同点:
· 都位于继承或实现的顶端
· 都不能实例化
· 都包含抽象函数,其子类都必须覆盖这些方法
不同点:
· 一个类只能继承一个父类,但是可以实现多个接口
· 抽象类中其实可以存在一些已经实现好的方法,有部分未实现的方法由子类来决定;接口中只
能包含抽象函数,子类必须完全实现
8.5 多态
向上转型和向下转型:
向上转型:
通过子类对象(小)实例化父类对象(大),还可以说是,子类的对象赋值给父类的引用(或者是父类的引用指向子类的对象);对于接口而言,可以说是,实现类的对象赋值给接口的引用(或者是接口的引用指向实现类的对象);向上转型是可以自动转换的。
public class LianXi03 {
public static void main(String[] args) {
Fu f = new Zi(); //通过子类去实例化父类
f.print();
}
}
class Fu {
public void print() {
System.out.println("Fu:print");
}
}
class Zi extends Fu {
public void print() {
System.out.println("Zi:print");
}
}
运行打印:
可见打印的是class Zi 的 print() ,这是因为我们通过子类去实例化的,所以父类的print() 方法已经被子类的print() 方法覆盖了,因此打印结果如上所示。
注意:向上转型时,是面向父类或者接口编程,关注的是父类或接口的能力,忽略了子类类型,方便扩展(优点);但父类只能调用父类方法或者子类重写后的方法,而子类中的单独方法是无法调用的(缺点)。
向下转型:
通过父类对象(大)实例化子类对象(小)。就是通过父类强制转换为子类,从而来调用子类独有的方法。这里我们会学习一个新的关键字: instanceof 来保证向下转型能够顺利进行,它是用来判断某对象是否是某类的实例,如果是则返回 true ,否则为 false ,用法如下:
A a = new B(); //向上转型 (B类是A的子类)
a instanceof A; //返回true.
a instanceof B; //返回true
a instanceof C; //返回false
向下转型实例:
public class LianXi03 {
public static void main(String[] args) {
fun(new Fu());
fun(new Zi1());
fun(new Zi2());
}
public static void fun(Fu fu) {
fu.print();
if (fu instanceof Zi1) {
Zi1 zi1 = (Zi1) fu; //向下转型,通过父类实例化子类
zi1.funZi1(); //调用Zi1类独有的方法
} else if (fu instanceof Zi2) {
Zi2 zi2 = (Zi2) fu; //向下转型,通过父类实例化子类
zi2.funZi2(); //调用Zi2独有的方法
}
}
}
class Fu {
public void print() {
System.out.println("Fu:print");
}
}
class Zi1 extends Fu {
public void print() {
System.out.println("Zi1:print");
}
public void funZi1() {
System.out.println("fun...Zi1");
}
}
class Zi2 extends Fu {
public void print() {
System.out.println("Zi2:print");
}
public void funZi2() {
System.out.println("fun...Zi2");
}
}
运行结果如下:
如上,我们可以通过向下转型来调用子类独有的方法。
多态:
多态就是同一个行为具有多个不同表现形式或形态的能力;在程序中多态就是同一类型的对象(父类/接口的引用),执行相同的方法,结果不同。也就是指一个引用(类型)在不同的情况下的多种状态。其原理基于向上转型和动态绑定。
多态的前提:
继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类的对象,只有这样该引用才能够具备技能调用父类方法和子类的方法。
例如:由于每种动物的叫声不同,因此在实现动物叫的方法中,可以在方法中接收一个动物类型的参数,当传入具体的某种动物时发送具体的叫声。我们可以使用父类类型的变量引用一个子类类型的对象,根据子类对象特征的不同,得到的运行结果也不同。如下:
public class Test {
public static void main(String[] args) {
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.call();
a2.call();
}
}
class Animal {
public void call() {
System.out.println("动物叫");
}
}
class Dog extends Animal {
public void call() {
System.out.println("狗叫");
}
}
class Cat extends Animal {
public void call() {
System.out.println("猫叫");
}
public void Run() {
System.out.println("猫跑");
}
}
结果:
狗叫
猫叫
注意:向上转型时,子类单独定义的方法会丢失。比如Cat类中的Run()方法,当Animal引用Cat类实例时,是访问不到Run()方法的,a2.Run(); 会报错。
多态的呈现方式(简单模仿打印机):
· 父类类型做形参
public class Printer {
private String paper;
private String ink;
public Printer() {
}
public Printer(String paper , String ink) {
this.paper = paper;
this.ink = ink;
}
public void Print(Paper paper , Ink ink) {
System.out.println("正在使用" + ink.getColor() + "的墨在" + paper.getPaperSize() + "的纸上打印");
}
public void setPaper(String paper) {
this.paper = paper;
}
public void setInk(String ink) {
this.ink = ink;
}
}
public interface Paper {
String getPaperSize();
}
public interface Ink {
String getColor();
}
public class A4Paper implements Paper{
@Override
public String getPaperSize() {
return "A4";
}
}
public class B5Paper implements Paper {
@Override
public String getPaperSize() {
return "B5";
}
}
public class ColoredInk implements Ink {
@Override
public String getColor() {
return "彩色";
}
}
public class BlackInk implements Ink {
@Override
public String getColor() {
return "黑色";
}
}
public class Test {
public static void main(String[] args) {
Printer printer = new Printer();
Paper paper = new A4Paper();
Ink ink = new BlackInk();
printer.Print(paper , ink);
}
}
· 父类类型做属性
public class Printer {
private Paper paper;
private Ink ink;
public Printer() {
}
public Printer(Paper paper , Ink ink) {
this.paper = paper;
this.ink = ink;
}
public void Print() {
System.out.println("正在使用" + ink.getColor() + "的墨在" + paper.getPaperSize() + "的纸上打印");
}
public void setPaper(Paper paper) {
this.paper = paper;
}
public void setInk(Ink ink) {
this.ink = ink;
}
}
public class Test {
public static void main(String[] args) {
Printer printer = new Printer(new A4Paper() , new ColoredInk());
printer.Print();
}
}
注意:其他类同上,这里不再书写。
· 父类类型做返回值
public class Test {
public static void main(String[] args) {
Manager m = new Manager();
Pet pet = m.feed(2);
pet.eat();
}
}
//饲养员
class Manager {
public Pet feed(int type) {
Pet pet = null;
switch (type) {
case 1 :
pet = new Dog();
break;
case 2 :
pet = new Cat();
break;
default:
break;
}
return pet;
}
}
class Pet {
public void eat() {};
}
class Cat extends Pet {
public void eat() {
System.out.println("猫吃鱼");
}
}
class Dog extends Pet {
public void eat() {
System.out.println("狗吃狗粮");
}
}
多态的优缺点:
· 优点:代码重用、简化通过重载来实现相似的功能;扩展性强,OCP–开闭原则(面向扩展开放,面向修改关闭);父类的变量可以赋值不同的子类对象,而调用不同的子类重写的方法;面向父类/接口考虑问题,忽略具体子类。
· 缺点:不能使用子类特有的属性和方法。
多态中的成员特点:
· 属性:
属性没有重写,子类的属性不会覆盖掉父类的属性
编译时:参考引用型变量所属的类中的是否有调用的成员变量,有,编译通过,没有,编译失败。
运行时:参考引用型变量所属的类中的是否有调用的成员变量,并运行该所属类中的成员变量。
总的来讲:编译和运行都参考等号的左边。
· 方法
①非静态方法:
编译时:参考引用型变量所属的类中的是否有调用的函数。有,编译通过,没有,编译失败。
运行时:参考的是对象所属的类中是否有调用的函数。
总的来讲:编译看左边,运行看右边。因为成员函数存在覆盖特性。
②静态方法:
编译时:参考引用型变量所属的类中的是否有调用的静态方法。
运行时:参考引用型变量所属的类中的是否有调用的静态方法。
总的来讲:编译和运行都看左边。对于静态方法,是不需要对象的。直接用类名调用即可。
多态的理解:多态是面向对象的三大特征之一。
· 狭义:继承、重写、向上转型 – 运行过程中表现出来的(动态的多态)。
· 广义:重载,对象名.方法(不同的参数) – 编译过程表现出来的 – 静态的多态
8.6 内部类
在Java的一个类中可以嵌套另外一个类,将一个类定义在另一个给类里面或者方法里面,这样的类就被称为内部类。内部类可以分为四种:成员内部类、局部内部类、匿名内部类、静态内部类。
· 成员内部类
一般格式:
class A {
class B {
}
}
成员内部类可以无条件访问外部类的属性和方法,但是外部类想要访问内部类属性或方法时,必须要创建一个内部类对象,然后通过该对象访问内部类的属性或方法;如果成员内部类的属性或者方法与外部类的同名,将导致外部类的这些属性与方法在内部类被隐藏,也可按照该格式调用,外部类.this.属性/方法。
创建内部类对象:
成员内部类是寄生于外部类,创建内部类对象就必须先创造外部类对象。之后创建内部类有两种方式。
· 在定义它的外部类中,实例化对象InnerB b = new lnnerB();
· 在其他外部类中,Outter.lnnerB b = new Outter().new lnnerB();
成员内部类的访问权限:
成员内部类前可加上四种访问修饰符。
private:仅外部类可访问。
protected:同包下或继承类可访问。
default:同包下可访问。
public:所有类可访问。
· 局部内部类
局部内部类存在于方法中;他和成员内部类的区别在于局部内部类的访问权限仅限于方法或作用域内。
class C {
public void say() {
class D {
}
}
}
注意:局部内部类就像局部变量一样,前面不能访问修饰符以及static修饰符。
· 匿名内部类
没有名字的内部类,只使用一次;必须是接口或抽象类的子类。
实例:
public class Test {
public static void main(String[] args) {
driveCar(new Car(){
@Override
public void drive() {
System.out.println("驾驶着BMW汽车");
}
});
}
public static void driveCar(Car car){
car.drive();
}
}
interface Car {
void drive();
}
分析以上代码知道静态方法driveCar需要一个Car对象,我们通过实现接口创建一个匿名类对象传递过去。事实上还可以通过继承类来创建一个匿名内部类对象。
注意:匿名内部类没有构造方法。也是唯一没有构造方法的内部类。匿名内部类和局部内部类只能访问外部类的final变量。
· 静态内部类
静态内部类和成员内部类相比多了一个static修饰符。它与类的静态成员变量一般,是不依赖于外部类的。同时静态内部类也有它的特殊性。因为外部类加载时只会加载静态域,所以静态内部类不能使用外部类的非静态变量与方法。
同时可以知道成员内部类里面是不能含静态属性或方法的。
class E {
static class F {
}
}
对象的创建:外部类.内部类 类名 = new 外部类.内部类();
内部类的好处:内部类和外部类可以互相反问其私有成员;打破单继承的局限,通过内部类实现多继承;做事件监听,回调。
内部类的缺点:代码复杂,可读性不强。
8.7 包与权限
包相当于操作系统的文件夹;其好处是管理java文件,方便寻找(包名+类名)、解决重名问题;保护资源(结合着访问控制符,来限定资源的访问)。
包的使用:
定义包名:一般都用小写英文;见名知义。
创建:new Package
包的声明:package 必须位于类的第一行非注释语句。
包的导入:import java.util.Scanner; --类的完全限定名
import java.util.*; --导入包下的所有java类。
访问权限:
8.8 枚举类型
枚举类型(enum type)是指由一组固定的常量组成合法的类型。Java中由关键字enum来定义一个枚举类型;枚举类型是一种基本数据类型而不是构造数据类型。
示例:
public enum Season {
SPRING, SUMMER, AUTUMN, WINER;
}
由来:枚举出来之前都是拿class或者interface来组织管理常量 – 基本数据类型,其缺点是只要数据类型合适,就会编译通过,不考虑实际业务,可能造成错误。
特点:Java定义枚举类型的语句很简约。
使用关键字enum
类型名称,比如这里的Season
一串允许的值,比如上面定义的春夏秋冬四季
枚举可以单独定义在一个文件中,也可以嵌在其它Java类中。
除了这样的基本要求外,用户还有一些其他选择:
枚举可以实现一个或多个接口(Interface)
可以定义新的变量
可以定义新的方法
可以定义根据具体枚举值而相异的类
用法:
· 常量
public enum Color {
RED, GREEN, BLANK, YELLOW
}
· switch
enum Signal {
GREEN, YELLOW, RED
}
public class TrafficLight {
Signal color = Signal.RED;
public void change() {
switch (color) {
case RED:
color = Signal.GREEN;
break;
case YELLOW:
color = Signal.RED;
break;
case GREEN:
color = Signal.YELLOW;
break;
}
}
}
· 向枚举中添加新方法
public enum Color {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
// 普通方法
public static String getName(int index) {
for (Color c : Color.values()) {
if (c.getIndex() == index) {
return c.name;
}
}
return null;
}
// get set 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
· 覆盖枚举的方法
public enum Color {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
//覆盖方法
@Override
public String toString() {
return this.index+"_"+this.name;
}
}
· 实现接口
public interface Behaviour {
void print();
String getInfo();
}
public enum Color implements Behaviour{
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
//接口方法
@Override
public String getInfo() {
return this.name;
}
//接口方法
@Override
public void print() {
System.out.println(this.index+":"+this.name);
}
}
· 使用接口组织枚举
public interface Food {
enum Coffee implements Food{
BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO
}
enum Dessert implements Food{
FRUIT, CAKE, GELATO
}
}