继承
继承表示的语意:is - a
继承的目的就是为了让代码更够很好的被重复使用
继承机制:是面对对象程序设计使用代码可以复用的最重要的手段。
继承主要解决的问题:共性的抽取
构造子类实例的时候,会在子类实例的内部自动构造一个父类的实例(子类实例中包含了父类的实例,父类的实例里就包含了父类的属性)
不写构造方法,就相当与生成了一个无参数的空的构造方法
父类只有一个带参数的构造方法,子类必须显式调用
创建子类实例的时候,先构造父类对象(执行父类构造方法的逻辑),在构造子类对象(再执行子类构造方法逻辑)
如果是显式调用父类的构造方法,那么必须把父类的构造方法放到第一行代码去执行,不能放到下面
为了以访继承层数过多而导致忘记,一般来说继承的层数最多为三层
语法规则
基本语法:
class 子类 extends 父类{
}
- 使用 extends 指定父类
- Java中一个子类只能继承一个父类
- 子类会继承父类所有 public 的字段和方法
- 对于父类的 private 的字段和方法,子类中数无法访问的
- 子类的实例中,也包含着父类的实例,可以通过 super 关键字得到父类实例的引用
extends 英文原意指 “扩展”. 而我们所写的类的继承,也可以理解成基于符立进行代码上的 “扩展”
例:创建一个 Animal 类,在创建一个 Cat 类和一个 Bird 类,Cat 类和 Bird 类都为 Animal 类的子类
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
class Cat extends Animal {
public Cat(String name) {
// 使用 super 调用父类的构造方法.
super(name);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly() {
System.out.println(this.name + "正在飞");
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat("小黑");
cat.eat("猫粮");
Bird bird = new Bird("大白");
bird.fly();
}
}
protected 关键字
如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了 “封装” 的初衷.两全其美的办法就是 protected 关键字
- 对于类的调用者来说,protected 修饰的字段和方法是不能访问的
- 对于类的 子类 和 同一个包的其他类 来说,protected 修饰的字段和方法是可以访问的
封装(访问权限控制,能被访问的范围越小就认为越高):
- private:只能再类内部访问 (权限最高)
- default(不写,默认的):包级权限,可以被同包的其他类访问 (第二)
- protected:可以被子类访问,也可以被同包的其他类访问 (第三)
- 可以再类外部访问 (权限最低)
final 关键字
final 关键字,在修饰一个变量或者字段的时候,表示常量(不能修改)
final 关键字也能修饰类,此时表示被修饰的类不能被继承
使用 final 显式的禁止继承,防止继承被滥用
final public class Animal {
}
class Dog extends Animal{
}
组合
组合表示的语意:has - a
组合和继承类似,组合也是一种表示类之间关系的方式,也是能够达到代码重用的效果
例:一个学校
public class School{
public SchlloMaster schlloMaster = new SchlloMaster;
public Student student1 = new Student;
public Student student2 = new Student;
public Student student3 = new Student;
public ClassRoom classRoom = new ClassRoom;
}
class SchlloMaster{
}
class Student{
}
class ClassRoom{
}
组合并没有设计特殊的语法(如 ectends 关键字),仅仅是将一个类的实例作为里一个类的字段
多态
向上转型
向上转型这样的写法可以结合 is - a 语意来理解
向上转型:在创建子类实例的时候,会先构造父类的实例
在向上转型中,父类的引用只能访问到父类的属性和方法,访问不到子类独有的属性和方法
方法一:
public class Main{
public static void main(String[] args) {
Cat cat = new Cat();
Animal animal = null;
animal = cat;
}
}
class Animal{
}
class Cat extends Animal{
}
方法二:
public class Main{
public static void main(String[] args) {
Animal animal = new Cat();
}
}
class Animal{
}
class Cat extends Animal{
}
方法三:
public class Main{
public static void main(String[] args) {
fun(new Cat());
}
public static void func(Animal animal){
}
}
class Animal{
}
class Cat extends Animal{
}
方法四:
public class Main{
public static void main(String[] args) {
Animal animal = func();
}
public static Animal func(){
return new Cat();
}
}
class Animal{
}
class Cat extends Animal{
}
动态绑定
规则:
父类 和 子类 中有同名的方法,并且参数也相同。
如果父类中包含的方法在子类中有队形的同名同参数的方法,就会进行 动态绑定.
例:
public class Test{
public static void main(String[] args) {
Animal animal = new Cat();
animal.eat("鱼");
}
}
class Animal{
public String name;
public void eat(String food){
System.out.println("Animal 正在吃" + food);
}
}
class Cat{
}
如果 eat 方法只在父类中存在,此时调用此方法不涉及动态绑定.
public class Test{
public static void main(String[] args) {
Animal animal = new Cat();
animal.eat("鱼");
}
}
class Animal{
public String name;
}
class Cat{
public void eat(String food){
System.out.println("Animal 正在吃" + food);
}
}
如果 eat 方法只在子类中存在,此时调用此方法就会编译报错,不涉及动态绑定.
public class Test{
public static void main(String[] args) {
Animal animal = new Cat();
animal.eat("鱼");
}
}
class Animal{
public String name;
public void eat(String food){
System.out.println("Animal 正在吃" + food);
}
}
class Cat{
public void eat(String food){
System.out.println("Animal 正在吃" + food);
}
}
如果 eat 方法在父类和子类中都存在,并且参数相同,此时调用此方法就会涉及 动态绑定.
在程序运行时,看 animal 究竟是指向一个父类的实例还是子类的实例,指向父类实例就调用父类版本的 eat ,指向子类实例就调用子类版本的 eat .
如果父类和子类中存在同名但不同参数的方法,此时执行的一定是父类中的方法
方法重写
子类实现父类同名的方法,并且参数的类型和个数完全相同,这种情况称为 覆写/重写/覆盖(Override)
注:
- 重写和重载完全不一样
- 普通方法可以重写,static 修饰的静态方法不能重写
- 重写中子类的返回的访问权限不能低于父类方法的访问权限
- 重写的方法的返回值类型不一定和父类的方法相同(但是建议最好写成相同的,特殊情况除外)
推荐在代码中重写方法时显式加上 @Override 注解
有了这个注解能帮助我们进行一些些合法性校验,例如不小心将名字拼写错了时,那么编译器就会发现父类中没有此方法,就会编译报错,提示无法后成重写.
重写的规则
对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容
重载 | 重写 | |
---|---|---|
概念 | 方法名称相同,参数的类型及个数不同 | 方法名称、返回值类型、参数的类型及个数完全相同 |
范围 | 一个类 | 继承关系 |
限制 | 没有权限要求 | 被重写的方法不能拥有比父类更严格的访问权限 |
理解多态
多态:是一种程序设计的思想方法
具体的语法体现:向上转型、方法重写、动态绑定
多态直观的理解:一个引用,对应到多种形态(不同类型的实例)
在 Java 的体系中,必须搭配继承才能使用多态
多态的优势:
- 多态是封装的更进一步,让类的使用者不需要关注具体对象的类型也能正确使用
- 方便扩展.未来如果需要新增新的子类,对于类的使用者来说影象很小
- 消灭一些分支语句(减少if / else swich / case),降低程序的圈复杂度
public class Main {
public static void main(String[] args) {
Shape shape1 = new Circle();
Shape shape2 = new Rect();
Shape shape3 = new Flower();
draw(shape1);
draw(shape2);
draw(shape3);
}
public static void draw(Shape shape){
shape.draw();
}
}
class Shape {
public void draw(){
}
}
class Circle extends Shape{
@Override
public void draw(){
System.out.println("○");
}
}
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 main(String[] args) {
Animal animal = new Cat();
Cat cat = (Cat)animal;
}
}
class Animal {
}
class Cat extends Animal{
}
class Bird extends Animal{
}
这样的转换就是正确
public class Test {
public static void main(String[] args) {
Animal animal2 = new Bird();
Cat cat2 = (Cat)animal2;
}
}
class Animal {
}
class Cat extends Animal{
}
class Bird extends Animal{
}
这样转换系统就会抛出一个异常
为了规避这样的异常,可以使用 instencesof 关键字,用于判定此时的 animal2 引用是否只想 Cat 类
public class Test {
public static void main(String[] args) {
if(animal2 instenceod Cat){
Animal animal2 = new Bird();
Cat cat2 = (Cat)animal2;
}
}
}
class Animal {
}
class Cat extends Animal{
}
class Bird extends Animal{
}
super 关键字
super 表示获取到父类实例的引用
使用 super 调用父类的构造器
public class Animal {
public String name = "小何";
public Animal(String name){
this.name = name;
}
}
class Cat extends Animal{
public Cat(String name){
super(name);
}
}
使用 super 来调用父类的普通方法
public class Animal {
public String name;
public Animal(String name){
this.name = name;
}
public void eat(String food){
System.out.println("小动物正在吃" + food);
}
}
///分割线//
public class Bird extends Animal{
public Bird(String name){
super(name);
}
public void eat(String food){
super.eat("谷子");
}
}
super 和 this 的区别
this | super | |
---|---|---|
概念 | 访问本类中的属性和方法 | 由子类访问父类中的属性、方法 |
查找范围 | 先查找本类,如果本类没有就调用父类 | 不查查找本类而直接调用父类定义 |
特殊 | 表示当前对象 | 无 |
在构造方法中调用重写(坑)
class A {
public A() {
func();
}
public void func() {
System.out.println("A.func()");
}
}
class B extends A {
private int num = 1;
@Override
public void func() {
System.out.println("B.func() " + num);
}
}
public class Test {
public static void main(String[] args) {
B b = new B();
}
}
- A 是 B 的父类. 构造 B 的时候, 就需要先构造 A 的实例
- 构造 A 的实例, 就会调用 A 的构造方法
- 调用 A 的构造方法的时候, 此时就会调用到 this.func(). 此时的 this 是指向子类的实例, 触发方法的动态绑定
- 如果在 B.func 打印 B 的 num 属性的值, 就会发现, 结果是 0. 在执行父类的构造方法的时候就触发了打印 num 值的操作,而此时此刻, B 的初始化代码(包括就地初始化和代码块以及构造方法都没有执行到)
抽象类
给一个类前面加上 abstract,此时这就是一个抽象类
如果尝试创建抽象类的实例,就会编译报错
抽象类除了不能实例化之外,其他的语法规则都和普通类一样
- 抽象类可以有普通的属性和方法
- 抽象类可以有静态的属性和方法
- 抽象类继承其他的类,也可以被其他类继承
给方法前面加上 abstract ,此时这个方法就是一个抽象方法
- 抽象方法不需要方法体
- 抽象方法只能在抽象类中存在(也可以在接口中存在),不能在普通类中存在的
- 抽象方法存在的意义就是为了让子类进行重写
基本格式:
abstract public class 类名 {
abstract public 类型 方法名();
}
接口
接口在命名的时候一般用 I(大写) 作为前缀,且一般使用形容词词性的单词进行命名
语意:一个类具有 xxx 的特性
接口相当于一种约束,要求了实现该接口的类,必须重写所用的接口中的抽象方法
接口中可以放抽象方法,这里的抽象方法不必写 abstract 关键字(写或不写都是抽象方法)
接口中不能放普通的方法和属性,只能放 public static final 修饰的属性(写或者不写都是有 public static final 修饰)
接口不能继承自其他的类,但可以继承自其他的接口
接口不能被类继承,而是被其他的类 “实现” ,或者说某个类实现了接口
抽象类和接口的对比:
- 抽象类和普通类差不多,只是不能实例化,而接口和普通类之间相去甚远(包含属性、方法、和其他类的关系)
- 一个类纸鞥继承自一个抽象类,但一个类可以同时实现多个接口
为什么要有接口这样的语法呢??
为了解决 Java 中不能多继承的问题
Java 的继承是单继承,但有些场景下多继承是有用的,Java 中可以通过 继承一个类,实现多个接口 的方式来完成乐死于多继承的效果
接口存在意义:
技能实现类似于多继承的效果,同时又能规避多继承带来的问题
基本格式:
public interface 类名{
public static final 类型 属性名;
public static final 类型 方法名();
}
\\\\\\\\\\\\\\\\\分割线\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
public class 方法名 implements 接口方法名{
}
例:
public interface shape{
public static final int = 10;
public static final void draw();
}
\\\\\\\\\\\\\\\\\分割线\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
public class Cercle implements shape{
}
接口之间可以继承的(说是“继承”,表述成“组合”)
例:
public interface IFlying {
public static final void fly();
}
public interface ISwiming {
public static final void swim();
}
\\\\\\\\\\\\\\\\\分割线\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
public class Cercle implements IFlying, ISwiming {
}