1、继承
继承是描述两个类的关系,如果两个类有重复的属性和方法,我们就可以使用继承的方式来实现/设计。
- 继承中,子类会把父类的所有方法和属性都继承下来(除了private)。
- 子类继承父类后,还可以拥有自己的属性和方法。
- java中,(原则上)只能单继承(能写出来看的)。(一个类只能有一个父亲,但是有实现多继承的方法)
- 所有的类都是默认继承object(隐式).
- 继承是时刻进行的,即改变父类中的一个属性的数值,子类中继承的对应属性的数值也会改变
// 人
public class Person {
public String name = "人";
// 0未知,1男,2女
public String sex = "0";
public void eat() {
System.out.println("吃饭");
}
}
// 中国人
public class ChinesePerson extends Person {
public String name = "中国人";
// 生肖
public String shengxiao;
public void kungfu() {
System.out.println("功夫");
}
}
// 测试
public static void main(String[] args) {
ChinesePerson chinesePerson = new ChinesePerson();
// 子类没有定义sex属性,但是也可以使用,因为从父类继承了
System.out.println(chinesePerson.sex); // 0
// 子类、父类可以有同名属性
System.out.println(chinesePerson.name); // 中国人
// 子类可以有自己的属性
System.out.println(chinesePerson.shengxiao); // null
// 子类没有定义eat方法,但是可以使用,因为从父类继承了
chinesePerson.eat();
// 子类可以有自己的方法
chinesePerson.kungfu();
}
2、super
有继承关系的时候才有super
this指调用者本身,super指父亲类
- 子类实例化的过程中,父类构造器先被调用,然后再调用子类的构造器(在子类构造器内部默认调用super())。
- 如果子类的构造器中调用了父类的有参构造方法,那么父类无参的构造器不会被调用。
- super()的调用要放在第一行(保证初始化的顺序)。
- super可以表示父类的引用,我们可以使用super和this来区分父类和子类的同名属性和方法。
// 接着知识点1的案例
// Chinese添加方法test()
public void test() {
// 优先找当前类的name属性
System.out.println(name); // 中国人
// 优先找当前类的sex属性,找不到就到父类找
System.out.println(sex); // 0
// 使用this关键字指定获取子类还是父类的同名属性
System.out.println(this.name); // 中国人
System.out.println(super.name); // 人
// 方法和属性一样
super.eat();
}
3、重写
- 定义
重写(override):也称覆盖。重写是子类对父类非static,非private,非final方法的实现过程进行重新编写,返回值(JDK7以后,被重写的方法返回值类型可以不同,但是必须是具有父子关系的)和形参都不能改变,且访问权限不能比父类中被重写方法的返回权限低。即外壳不变,核心重写。
**重载(overload):**同一个类中,有相同的方法名,但是参数的类型或者参数的个数不同,这两个方法就是重载。
- 为什么需要重写
原有的方法无法满足新的需求,需要对这个方法进行改良来满足新的需求。子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。
示例:
// 人类
public class Person {
public String name = "人";
// 人都会吃饭,具体怎么吃饭的不清楚,每个区域的吃饭方式都不一样
public void eat() {
System.out.println("吃饭");
}
}
// 中国人
public class ChinesePerson extends Person{
public String name = "中国人";
// 对eat方法进行重写,延续了父类的动作,但是做了扩展,实现了具体的吃饭方式。
@Override
public void eat() {
System.out.println("使用筷子吃饭");
}
}
// 印度人
public class IndiaPerson extends Person{
public String name = "印度人";
@Override
public void eat() {
System.out.println("使用手吃饭");
}
}
4、多态
-
定义
同一个行为具有不同的表现形式。执行一段代码,Java 在运行时能根据不同的对象产生不同的结果。 -
前提条件
-
子类继承父类。
-
子类覆盖父类的方法(重写)。
-
父类引用指向子类对象。(申请开辟一段子类的内存空间然后赋值给父类)
- 示例
假设场景,学校食堂排队干饭。吃饭之前要先买饭,然后吃饭。
人都会吃饭,但是不同地区的人生活习惯不一样,吃饭方式也不一样。中国人使用筷子,英国人使用刀叉,印度人使用手。
-
封装Person类,设置方法eat()和buyFood();
-
再分别封装三个子类ChinesePerson、EnglishPerson、IndiaPerson;
-
子类重写eat()方法,因为每个人吃饭的方式可能不一样;
-
buyFood()不用重写,因为每个人都得排队买饭,行为是一样的。
// 人 public class Person { public String name = "人"; // 人都会吃饭,具体怎么吃还不清楚 public void eat() { System.out.println("吃饭"); } public void buyFood() { System.out.println("买饭"); } }
// 中国人 public class ChinesePerson extends Person{ public String name = "中国人"; // 重写父类eat方法,实现具体的吃饭方式 @Override public void eat() { System.out.println(name + "用筷子吃饭"); } } // 英国人 public class EnglishPerson extends Person{ public String name = "英国人"; @Override public void eat() { System.out.println(name + "用刀叉吃饭"); } } // 用手吃饭 public class IndiaPerson extends Person{ public String name = "印度人"; @Override public void eat() { System.out.println(name + "用手吃饭"); } }
// main函数测试1。 public static void main(String[] args) { // 子类对象赋值给父类的引用(父类类型的变量) Person person1 = new ChinesePerson(); Person person2 = new IndiaPerson(); // 执行的是子类的方法 person1.eat(); // 中国人用筷子吃饭 person2.eat(); // 印度人用手吃饭 }
// main函数测试2。实现食堂排队买饭场景。 // 我们封装ganfan()方法,过程为 买饭 + 吃饭。 public static void main(String[] args) { // 使用数组模拟排队 Person[] persons = { new ChinesePerson(), new IndiaPerson(), new EnglishPerson(), new IndiaPerson() }; // 循环排队干饭 for(int i = 0; i < persons.length; i++) { ganfan(persons[i]); // 运行过程中才确定是什么对象 System.out.println("-----------"); } } // 买饭 + 吃饭 // 干饭方法不需要具体知道是什么人吃饭 // 通过多态,实现不同的人使用不同的吃饭方式 public static void ganfan(Person person) { person.buyFood(); person.eat(); }
-
注意1:多态场景下,父类引用调用的方法是子类的方法,调用的属性是父类的属性。
注意2:父类引用不能调用子类独有的方法(即不是(继承||重写)父类的方法)
5、抽象类
5.1 定义
抽象类也是类,只是抽象类具备了一些特殊的性质。
我们以前编写一个类时,会为这个类编写具体的属性和方法,但有一些情况我们只知道一个类需要哪些属性方法,但不知道这些方法具体是什么,这时我们就要用到抽象类。
举个例子,有一位老师布置了一篇作文,要求以春天为题目写一篇字数不少于800字的写景作文,题材不限(诗歌除外)。在这个例子中,这篇作文就是一个抽象类,这个抽象类有两个属性:以春天为题目和字数不少于800,还有一个抽象方法:写景。现在全班学生就会按照老师所给的要求,即抽象类,去完成作业。
抽象类就像一个大纲,一种规范。
抽象类体现的是一种模板式设计。以抽象类作为其子类的模板,从而避免了子类设计的随意性。
有抽象方法的前提是其所属的类是抽象类;
抽象函数与普通函数最大的区别就是它不需要有大括号,只要有声明就可以了
5.2 语法
关键字:abstract。
// 抽象类
public abstract class 类名 {
// 抽象方法
public abstract 返回值 方法名();
}
- 抽象类不能实例化,即不能创建对象,只能被继承,然后实例化。
- 继承抽象类的非抽象类必须实现抽象方法。
- 抽象类中可以没有抽象方法,有抽象方法的类一定是抽象类。
- 抽象类可以继承抽象类,可以不用实现抽象方法。
- 抽象的方法不能private,final,static修饰。
6、接口
6.1 定义
接口这个概念在生活中并不陌生,比如计算机往往有多个USB接口,可以插各种USB设备,如键盘、鼠标、U盘、摄像头、手机等。
接口声明了一组能力,但它自己并没有实现这个能力,它只是一个约定。接口涉及交互两方对象,一方需要实现这个接口,另一方使用这个接口,但双方对象并不直接互相依赖,它们只是通过接口间接交互,上面的USB接口来说,USB协议约定了USB设备需要实现的能力,每个USB设备都需要实现这些能力,计算机使用USB协议与USB设备交互,计算机和USB设备互不依赖,但可以通过USB接口相互交互。
6.2 作用
Java中是单继承的,怎么才能让类实现多继承呢?
比如燕子会飞,飞机也会飞,他们具有相同的行为,但是他们是毫不相关的两个类,如果让燕子和飞机都继承于同一个抽象类,显然不合适。而且燕子会吃东西,飞机可以加油,这时候也不适合在同一个抽象类中定义让他们继承。
接口主要解决的问题:
-
多继承
-
不同子类实现相同的行为
6.3 语法
关键字:interface
interface 接口名 {
}
-
接口不能实例化,需要有类使用implements来实现接口,并且实现接口的抽象方法。
-
接口中的方法默认是抽象的。//自带public abstract,故类也是抽象的
-
接口的方法不能使用private和final、static修饰。
-
接口可以多继承,用逗号隔开。
-
接口可以定义常量,常量默认会带public static final 。该常量不能修改,一般我们通过【接口名.常量】来访问。
//extends:继承类
//implements继承接口(接口与类没有区别,接口也是类的一种)
//接口只能定义常量与抽象方法,常量必须被初始化
示例:假设场景,家里的门。门都有open( )和close( )两个动作,此时我们可以通过抽象类和接口来定义这个抽象概念。
public abstract class Door {
public abstract void open();
public abstract void close();
}
public interface Door {
public abstract void open();
public abstract void close();
}
但是现在如果我们需要门具有报警alarm( )的功能,那么该如何实现?下面提供两种思路:
-
将这三个功能都放在抽象类里面,但是这样一来所有继承于这个抽象类的子类都具备了报警功能,但是有的门并不一定具备报警功能;
-
将这三个功能都放在接口里面,需要用到什么功能我们就实现什么接口。
从这里可以看出, Door的open() 、close()和alarm()根本就属于两个不同范畴内的行为,open()和close() ,而alarm()属于延伸的附加行为。因此最好的解决办法是单独将报警设计为一个接口,包含alarm()行为,Door设计为单独的一个抽象类,包含open和close两种行为。
// 接口强调行为,报警的动作
public interface Alram {
void alarm();
}
// 抽象类强调事物,门具备开和关固有的行为特性
public abstract class Door {
void open();
void close();
}
// 如果是警报门。具备门基本的特征,所以继承Door。又具备报警的功能就实现Alarm接口进行扩展。
// 如果是普通门,把implements Alarm去掉即可。
class AlarmDoor extends Door implements Alarm {
void oepn() {
//....
}
void close() {
//....
}
void alarm() {
//....
}
}
// 假如是一个火灾警报器,不具备门的特性,我们直接实现Alarm接口就好了
class FireAlarm implements Alarm {
void alarm() {
//....
}
}
6.5 JDK1.8之后接口的变化
在jdk1.8之后接口可以定义static和default方法。
// 使用接口名.方法名调用
static void 方法名称() {
}
//此时这个方法属于类故不可以被继承
// 可以被子类继承,实例化子类之后调用
default void 方法名称() {
}
//子类可以选择是否继承
在1.8之前,接口与其实现类之间的耦合度太高,当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。