一、继承
1.1 为什么需要继承

1.2 继承概念

1.3 继承的语法
修饰符 class 子类 extends 父类{
//…
}
注:访问修饰限定符只能决定访问权限,不能决定是否继承
子类会将父类中的成员变量或者成员方法继承到子类中
1.4 父类成员的访问
1.4.1 子类中访问父类成员
1. 子类和父类不存在同名成员变量
public class Base {
public int a ;
public int b;
}
class Test extends Base{
public int c ;
public void method(){
a = 10;
b = 20;
c = 30;
}
}
public class Main {
public static void main(String[] args) {
Test test = new Test();
}
}
2. 子类和父类成员变量名相同
public class Base {
public int a = 10;
public int b = 20;
}
class Test extends Base{
public int a;
public char b;
public int c ;
public void method(){
a = 30;
b = 40;
System.out.println("a = "+a);
System.out.println("b = "+b);
}
}
public class Main {
public static void main(String[] args) {
Test test = new Test();
test.method();
}
}
1.4.2 子类中访问父类的成员方法
1. 成员方法名不同
public class Base {
public void method1(){
System.out.println("666");
}
}
class Test extends Base{
public void method2(){
System.out.println("12345");
}
public void demo(){
method1();
method2();
}
}
public class Main {
public static void main(String[] args) {
Test test = new Test();
test.demo();
}
}

2.成员方法名相同
public class Base {
public void method(){
System.out.println("666");
}
}
class Test extends Base{
public void method(){
System.out.println("12345");
}
public void demo(){
method();
}
}
public class Main {
public static void main(String[] args) {
Test test = new Test();
test.demo();
}
}
1.5 super关键字
public class Base {
public int a = 10;
public int b = 20;
public void method(){
System.out.println("666");
}
}
class Test extends Base{
public int a = 30;
public char b = 40;
public void method(){
System.out.println("12345");
}
public void demo1(){
method();
System.out.println(this.a);
System.out.println(this.b);
}
public void demo2(){
super.method();
System.out.println(super.a);
System.out.println(super.b);
}
public class Main {
public static void main(String[] args) {
Test test = new Test();
test.demo1();
test.demo2();
}
}
1.6 子类构造方法



造方法,然后执行子类的构造方法,因为:子类对象中成员是有两部分组成的,基类继承下来的以
及子类新增加的部分 。父子父子肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类
的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新
增加的成员初始化完整 。
此外,调用父类构造方法是为了帮助初始化子类从父类继承过来的成员,并不会生成父类对象
1.7 super和this
1.7.1 相同点

属性
1.8 再说初始化
public class Animal {
public String name;
int age;
static {
System.out.println("Animal静态");
}
{
System.out.println("Animal实例");
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Cat extends Animal{
public Cat(String name, int age) {
super(name, age);
}
static {
System.out.println("Cat静态");
}
{
System.out.println("Cat实例");
}
public void miaomiao(){
System.out.println(this.name+"正在喵喵叫");
}
}
public class Main {
public static void main(String[] args) {
Cat cat = new Cat("咪咪",2);
Cat cact2 = new Cat("meme",2);
}
}
通过输出的结果我们可以得出以下结论
1.9 protected关键字

package demo1;
public class Test {
protected int a = 10;
}
package demo2;
import demo1.Test;
public class Test2 extends Test {
public void func()
{
System.out.println(super.a);
}
public static void main(String[] args) {
Test test = new Test();
// System.out.println(test.a);
}
}
被protected修饰的父类成员在不同包的子类中不能直接访问
1.10 继承方式

1.11 final关键字
final int a = 10;
a = 30;//编译出错


1.12 继承与组合
比如一个学校中有很多学生和老师组成
public class Student {
}
public class Teacher {
}
public class School {
public Student[] students;
public Teacher[] teachers;
public School() {
students = new Student[10];
teachers = new Teacher[10];
}
}
注:组合只是一种设计方式 ,并不是面向对象的特征
2.多态
2.1 多态的概念

当父类引用引用的子类对象不一样的时候,调用重写的方法,所表现出来的行为不一样,把这种思
想称为多态
2.2 多态实现条件
public class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(this.name+"正在吃饭");
}
}
public class Cat extends Animal{
public Cat(String name, int age) {
super(name, age);
}
public void meme(){
System.out.println(this.name+"正在咪咪叫");
}
}
public class Dog extends Animal{
public Dog(String name, int age) {
super(name, age);
}
public void bark(){
System.out.println(this.name+"正在汪汪叫");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal("小白",2);
animal.bark();
}
}
通过父类引用,只能调用父类自己特有的成员,方法或成员变量
2.2.1 什么是向上转型
把子类的对象给到父类,这个过程称为向上转型
public class Main {
public static void main(String[] args) {
Dog dog = new Dog("大黄",2);
Animal animal = dog;
}
可以直接这样写
public class Main {
public static void main(String[] args) {
Animal animal = new Dog("大黄",2);
}
因为Animal与Dog之间存在继承关系,所以可以这样赋值
这里表示animal这个引用指向了dog这个引用所指向的对象
这个过程中就把子类对象给到了父类类型的引用,这就是向上转型
2.2.1.1 常见的可以发生向上转型的三个时机:
1. 直接赋值
public class Main {
public static void main(String[] args) {
Animal animal = new Dog("大黄",2);
}
2. 作为方法的参数,传参的时候向上转型
public class Main {
public static void func1(Animal animal){
}
public static void main(String[] args) {
Dog dog = new Dog("小黄",2);
func1(dog);
}
3. 作为方法的返回值时向上转型
public class Main {
public static Animal func2(){
Dog dog = new Dog("测试",10);
return dog;
}
public static void main(String[] args) {
Animal animal = new Dog("大黄",2);
Animal animal1 = func2();
}
2.3 重写
这里在Cat与Dog中分别再定义eat方法
public class Cat extends Animal{
public Cat(String name, int age) {
super(name, age);
}
public void meme(){
System.out.println(this.name+"正在咪咪叫");
}
public void eat(){
System.out.println(this.name+"正在吃猫粮");
}
}
public class Dog extends Animal{
public Dog(String name, int age) {
super(name, age);
}
public void bark(){
System.out.println(this.name+"正在汪汪叫");
}
public void eat(){
System.out.println(this.name+"正在吃狗粮");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog("大黄", 2);
Animal animal1 = new Cat("花花", 3);
animal.eat();
animal1.eat();
}
}
那么这里发现,通过父类的引用调用eat方法的时候,调用的是子类的eat方法
这个过程,称之为动态绑定
此时,就满足了上文中所提到的条件



public class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
void eat(){
System.out.println(this.name+"正在吃饭");
}
}
public class Cat extends Animal{
public Cat(String name, int age) {
super(name, age);
}
public void meme(){
System.out.println(this.name+"正在咪咪叫");
}
@Override
public void eat(){
System.out.println(this.name+"正在吃猫粮");
}
}

@Override
protected void eat(){
System.out.println(this.name+"正在吃猫粮");
}
}
也没有报错
@Override
private void eat(){
System.out.println(this.name+"正在吃猫粮");
}
}
报错了
public class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
private void eat(){
System.out.println(this.name+"正在吃饭");
}
}
public class Cat extends Animal{
public Cat(String name, int age) {
super(name, age);
}
public void meme(){
System.out.println(this.name+"正在咪咪叫");
}
@Override
private void eat(){
System.out.println(this.name+"正在吃猫粮");
}
}
public class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public static void eat(){
System.out.println("正在吃饭");
}
}
public class Cat extends Animal{
public Cat(String name, int age) {
super(name, age);
}
public void meme(){
System.out.println(this.name+"正在咪咪叫");
}
@Override
public private void eat(){
System.out.println(this.name+"正在吃猫粮");
}
}
public class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public final void eat(){
System.out.println(this.name + "正在吃饭");
}
}
public class Cat extends Animal{
public Cat(String name, int age) {
super(name, age);
}
public void meme(){
System.out.println(this.name+"正在咪咪叫");
}
@Override
public final void eat(){
System.out.println(this.name+"正在吃猫粮");
}
}
这里形式一致也报错
说明被private或static或final修饰的方法不可以被重写
那么还有没有其他情况呢?
public Animal test(){
return null;
}
@Override
public Cat test(){
return null;
}
并没有报错
这说明
实现重写的条件:
1. 最基本的返回值,参数列表,方法名必须一样
2.被重写的方法的访问限定修饰符在子类中要大于等于父类的访问限定修饰符
3.被private或static或final修饰的方法不可以被重写
4.被重写的方法返回值类型可以不同,但是必须是具有父子关系的(协变类型)
5.构造方法不能被重写
注:
认为所有类的父类都是Object
到这里就可以提到之前笔者类与对象中重写toString
public class Main {
public static void main(String[] args) {
Dog dog = new Dog("大黄",2);
System.out.println(dog);
}
}
2.3.1 重载与重写的区别
区别点 | 重载 | 重写 |
参数列表 | 一定要修改 | 一定不能修改 |
返回类型 | 可以修改 | 除非是协变类型,否则不能修改 |
访问修饰限定符 | 可以修改 | 子类中要大于等于父类 |
2.3.2 重写的设计原则

2.3.3 动态绑定与静态绑定

public class Main {
public static void main(String[] args) {
Animal animal = new Dog("大黄",2);
animal.eat();
}
}
来看一下这段代码的底层
可以看到程序在编译的时候,确实调用的是Animal的eat
程序在运行时,调用了Dog的方法
这就是动态绑定
2.4 向上转型和向下转型
2.4.1 向上转型
public class Main {
public static void main(String[] args) {
Animal animal = new Dog("大黄",2);
animal.eat();
}
}
这里看起来好像是调用了子类中的eat,实际是程序在运行的过程中发生了动态绑定
2.4.2 向下转型



public class Main {
public static void main(String[] args) {
Animal animal = new Dog("大黄",2);
animal.eat();
Dog dog = (Dog)animal;
dog.bark();
}
}
这里看起来好像没问题
那接下来,我如果想让这个狗喵喵叫呢
public class Main {
public static void main(String[] args) {
Animal animal = new Dog("大黄",2);
// animal.eat();
// Dog dog = (Dog)animal;
// dog.bark();
Cat cat = (Cat) animal;
cat.meme();
}
}
这里也没报错,运行一下
类型转换异常错误
强制类型转换本来就是不安全的
那么如何解决呢,这里可以用到instanceof
public class Main {
public static void main(String[] args) {
Animal animal = new Dog("大黄",2);
// 如果animal引用的对象是Cat对象的实例,返回true
if (animal instanceof Cat){
Cat cat = (Cat) animal;
cat.meme();
}
else {
System.out.println("不是对应实例");
}
}
}
所以,向下转型是非常不安全的
2.5 多态的优缺点
2.5.1 优点
1.能够降低代码的 "圈复杂度", 避免使用大量的 if - else
假如现在要求画一些几何图形
public class Shape {
public void draw(){
System.out.println("画一个图形!");
}
}
public class Triangle extends Shape{
@Override
public void draw() {
System.out.println("画了一个三角形");
}
}
public class Rect extends Shape{
@Override
public void draw() {
System.out.println("画了一个矩形!");
}
}
public class Cycle extends Shape {
@Override
public void draw() {
System.out.println("画了一个圆形!");
}
}
如果不使用多态,那么代码可能会这样写
public class Test {
public static void main(String[] args) {
Cycle cycle = new Cycle();
Triangle triangle = new Triangle();
Rect rect = new Rect();
String[] strings = {"cycle","cycle","rect","triangle","rect"};
for(String x: strings){
if(x.equals("cycle")){
cycle.draw();
} else if (x.equals("rect")) {
rect.draw();
} else if (x.equals("triangle")) {
triangle.draw();
}
}
}
}
这样写确实可以满足要求,但是在代码中存在大量的if-else语句
如果用多态的思想来解决
public class Test {
public static void drawMap(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Shape[] shapes = {new Cycle(),new Cycle(),new Rect(),new Triangle(),new Rect()};
for (Shape shape :shapes){
shape.draw();
}
}
}
一样的效果,但是简化了很多,没有前者那么冗杂。
2. 可扩展能力更强
public 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[] shapes = {new Cycle(),new Cycle(),new Rect(),new Triangle(),new Rect(),new Flower()};
for (Shape shape :shapes){
shape.draw();
}
}
}
2.5.2 缺点
1. 属性没有多态性
2. 构造方法没有多态性
2.6 避免在构造方法中调用重写方法
package demo2;
class B {
public B() {
// do nothing
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() ");
}
}
public class Test {
public static void main(String[] args) {
D d = new D();
}
}
这说明,在父类的构造方法中,是可以调用子类和父类重写的方法,此时会调用子类重写的方法,
发生动态绑定
那么看一下D中num的值
package demo2;
class B {
public B() {
// do nothing
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() "+num);
}
}
public class Test {
public static void main(String[] args) {
D d = new D();
}
}
可以看到num的值是0