继承
1、基本概述
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
通过extends关键字可以实现类与类的继承
class 子类名 extends 父类名 {}
单独的这个类称为父类,基类或者超类;这多个类可以称为子类或者派生类。
继承的好处
- 提高了代码的复用性
- 提高了代码的维护性
- 让类与类之间产生了关系,是多态的前提
继承的弊端
- 类的耦合性增强了
开发的原则:高内聚、低耦合。
耦合:完成一个功能,需要依赖别的类;
内聚:自己完成某件事情的能力。
package org.westos.demo1;
/**
* 1、继承的概述
* Animal父类,将多个类的相同属性和行为抽取到父类中
* @author lwj
* @date 2020/4/17 13:42
*/
public class Animal {
/** default缺省的权限修饰符,修饰成员变量,在本包下的类可以访问 */
String name;
int age;
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
/**
* 继承Animal类,继承了Animal所有的非私有成员
*/
class Cat extends Animal {
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}
/**
* 继承Animal类,继承了Animal所有的非私有成员
*/
class Dog extends Animal {
public void actAsDoorkeeper() {
System.out.println("看家护院");
}
}
class Demo {
public static void main(String[] args) {
Cat cat = new Cat();
cat.name = "小咪";
cat.age = 2;
System.out.println(cat.name);
System.out.println(cat.age);
cat.eat();
cat.sleep();
cat.catchMouse();
System.out.println("=============");
Dog dog = new Dog();
dog.name = "旺财";
dog.age = 3;
System.out.println(dog.name);
System.out.println(dog.age);
dog.eat();
dog.sleep();
dog.actAsDoorkeeper();
}
}
继承的特点
- Java中的继承是单继承,不支持多继承;(一个类只能有一个直接父类)
- Java支持多层继承(继承体系)
继承的注意事项
- 子类只能继承父类的所有非私有成员(成员方法和成员变量)
- 构造方法不参与继承,子类不能继承父类的构造方法,但是可以通过super关键字去访问父类构造方法
- 不要为了父类的部分功能而去继承
- 继承体现的是一种"is a"的关系
继承中成员变量的关系
- 子类中的成员变量与父类中的成员变量名称不同
- 子类中的成员变量和父类中的成员变量名称相同
- 在子类中访问一个变量的查找顺序("就近原则")
- 在子类的方法的局部范围找,有就使用
- 在子类的成员范围找,有就使用
- 在父类的成员范围找,有就使用
- 如果还找不到,就报错。
- 在子类中访问一个变量的查找顺序("就近原则")
package org.westos.demo2;
/**
* @author lwj
* @date 2020/4/17 14:34
*/
public class MyTest {
public static void main(String[] args) {
new SubClass().show();
}
}
class SuperClass {
int a = 10;
}
class SubClass extends SuperClass {
int b = 20;
public void show() {
int c = 30;
System.out.println(c);
//30
System.out.println(b);
//20
System.out.println(a);
//10
}
}
2、this与super
- this代表本类的对象引用,访问本类成员(成员变量与成员方法)和本类构造方法;
- super代表父类存储空间的标识,理解为父类的引用,可以操作父类的成员和父类构造方法;
使用
1、调用成员变量
- this.成员变量 调用本类成员变量
- super.成员变量 调用父类成员变量
2、调用构造方法
- this(...) 调用本类构造方法
- super(...) 调用父类构造方法
3、调用成员方法
- this.成员方法() 调用本类成员方法
- super.成员方法() 调用父类成员方法
package org.westos.interview;
/**
* 继承的面试题一
* @author lwj
* @date 2020/4/15 17:04
*/
public class MyTest {
public static void main(String[] args) {
Son son = new Son();
//初始化子类之前,必须先初始化父类,父类的构造方法在子类的构造方法之前执行
son.show();
}
}
class Father {
public int num = 10;
public Father() {
System.out.println("父类无参构造方法执行了");
//1
}
}
class Son extends Father {
public int num = 20;
//子类的成员变量与父类的成员变量名相同
public Son() {
System.out.println("子类的无参构造方法执行了");
//2
}
public void show() {
int num = 30;
System.out.println(num);
//30,就近原则
System.out.println(this.num);
//20
System.out.println(super.num);
//10
//super.父类成员变量
//super.父类成员方法
//super()父类构造方法
}
}
3、继承中构造方法的关系
子类中所有的构造方法默认都会访问父类中空参的构造方法。
因为子类会继承父类中的数据,还有可能会使用父类中的数据,所以子类初始化之前一定要先完成父类数据的初始化。
其实,每一个构造方法的第一条语句默认都是:super()。
继承中构造方法的注意事项:
如果父类没有无参构造函数怎么办?
package org.westos.demo;
/**
* 继承的构造方法关系
* @author lwj
* @date 2020/4/15 17:31
*/
public class SuperClass {
private String name;
private int age;
//父类有一个有参构造,未设置无参构造方法,子类直接报错,没有默认的可利用的构造方法,子类所有的构造方法默认都会访问父类的无参构造方法
/**
* 解决方案三:终极解决方案,在父类增加一个无参构造方法(有了有参构造方法,默认的无参构造方法消失,如果想要使用,显式增加一个无参构造)
*/
public SuperClass() {
}
public SuperClass(String name, int age) {
this.name = name;
this.age = age;
}
}
package org.westos.demo;
/**
* @author lwj
* @date 2020/4/15 17:31
*/
public class SubClass extends SuperClass {
private String address;
/**
* 解决方案一:子类的构造方法显式调用父类其他带参构造方法
*/
public SubClass() {
super("张三", 12);
}
/**
* 解决方案二:this关键字调用本类的其他构造方法,但是该构造方法必须super()调用了父类构造方法,(子类初始化之前,必须先初始化父类)
* @param address 地址
*/
public SubClass(String address) {
this();
}
}
super(…)或者this(….)必须出现在第一条语句上,并且只能存在一个。
出现在第一句:
因为子类的初始化必须在父类的初始化之后,所以必须首先super()或者this()初始化父类,然后再初始化本类。
只能存在一个:
如果super(...)和this(...)同时存在,那么会存在重复初始化父类的数据,因为this(...)调用的本类其他构造方法,也会super(...)调用父类的构造方法。
package org.westos.interview;
/**
* 继承的面试题二
* @author lwj
* @date 2020/4/15 17:09
*/
public class MyDemo {
public static void main(String[] args) {
new SubClass();
//初始化子类之前,如果发现父类还没有初始化,那么必须先初始化父类
//初始化:执行静态变量的赋值语句和静态代码块,而且父类在子类之前
//创建对象:执行父类的构造代码块,父类的构造方法,子类的构造代码块,子类的构造方法
}
}
class SuperClass {
static {
System.out.println("Super Class static block");
//1
}
{
System.out.println("Super Class Constructor block");
//3
}
public SuperClass() {
System.out.println("Super Class Constructor Method");
//4
}
}
class SubClass extends SuperClass {
static {
System.out.println("SubClass static block");
//2
}
{
System.out.println("SubClass Constructor block");
//5
}
public SubClass() {
System.out.println("SubClass Constructor Method");
//6
}
}
4、继承中成员方法的关系
- 子类的方法名和父类的方法名不同;
- 子类的方法名与父类的方法名相同
- 通过子类调用方法
- 首先查找子类中有没有该方法,如果有就使用
- 再看父类中有没有该方法,有就使用
- 如果没有就报错。
- 通过子类调用方法
package org.westos.demo3;
/**
* @author lwj
* @date 2020/4/17 15:51
*/
public class MyDemo {
public static void main(String[] args) {
new SubClass().eat();
//子类的eat方法
}
}
class SuperClass {
public void eat() {
System.out.println("父类的eat方法");
}
}
class SubClass extends SuperClass {
@Override
public void eat() {
System.out.println("子类的eat方法");
}
}
5、方法重写
概述
方法重写:子类中出现了和父类中一模一样的方法声明(方法名,参数列表,返回值类型),也称为方法覆盖。
方法重写的应用:
当子类需要父类的功能,但是不想采用父类的实现,想要有自己的实现时,那么子类可以重写父类的方法。
package org.westos.demo4;
/**
* 方法重写
* @author lwj
* @date 2020/4/17 15:55
*/
public class MyTest {
public static void main(String[] args) {
new Dog().eat();
//我要吃骨头
new Cat().eat();
//我要吃鱼
}
}
class Animal {
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("我要吃骨头");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("我要吃鱼");
}
}
在子类的重写方法中,可以使用super.成员方法(),来沿袭父类的实现。
@Override:注解,检测该方法是否是重写父类的;
方法重写的注意事项
- 父类中的私有方法不能被重写,因为父类私有方法子类根本就无法继承
- 子类抛出的异常不能大于父类抛出的异常
- 子类的访问级别不能小于父类的访问级别
- 子类的返回值类型不能大于父类的返回值类型
- 静态方法不参与重写
6、final关键字
为什么会有final?
由于继承中有一个方法重写的现象,而有时候我们不想让子类去重写父类的方法,对于这种情况Java就给我们提供了一个关键字:final。
概述:
final关键字就是最终的意思,可以修饰类、变量(成员变量和局部变量)和成员方法。
final关键字修饰的特点:
- 修饰类:被修饰类不能被继承
- 修饰方法:被修饰方法不能被重写
- 修饰变量:被修饰的变量不能被重写赋值,因为这个量其实是一个常量
- 基本类型:值不能改变
- 引用类型:地址值不能改变,该引用不能再指向其他对象
package org.westos.demo5;
/**
* @author lwj
* @date 2020/4/17 16:41
*/
public class MyDemo {
/** final修饰的成员变量初始化时机,<init>方法执行时期,即成员变量的赋值、构造代码块或者构造方法 */
final Person person;
final int num = 20;
{
person = new Person();
}
public MyDemo() {
//person = new Person();
//person = new Person();
//final修饰的引用变量地址值不能更改,引用不能再指向其他对象
person.name = "小白";
person.age = 12;
//final修饰的引用变量所指向空间的值可以改变
//num = 30;
//final修饰的基本类型变量值不能改变
}
public static void main(String[] args) {
MyDemo myDemo = new MyDemo();
//myDemo.person = new Person();
//同样也是不能修改地址值
myDemo.person.name = "大佬";
myDemo.person.age = 22;
//final修饰的引用类型变量的内容可以改变
System.out.println(myDemo.person.age);
//22
System.out.println(myDemo.person.name);
//大佬
final int num = 100;
//num = 200;
//final修饰的基本类型局部常量值不能改变
}
}
class Person {
String name;
int age;
}
多态
1、概述
多态的前提:继承和方法的重写,父类引用指向子类对象。
多态:访问成员方法(编译看左,运行看右)
package org.westos.demo;
/**
* 1、多态的概述
* @author lwj
* @date 2020/4/17 17:11
*/
public class MyTest {
public static void main(String[] args) {
Animal animal = new Cat();
//多态:父类引用指向子类对象
animal.eat();
//猫吃鱼
//编译器看左边(父类是否有该方法),运行时看右边(执行子类重写的方法)
}
}
class Animal {
public void eat() {
System.out.println("吃饭");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
多态:访问成员变量(编译看左,运行看左)
package org.westos.demo2;
/**
* @author lwj
* @date 2020/4/17 17:16
*/
public class MyDemo {
public static void main(String[] args) {
SuperClass superClass = new SubClass();
System.out.println(superClass.num);
//20
//采用多态形式访问成员变量,访问的是父类的成员变量
//访问成员变量:编译看左边,运行看左边
}
}
class SuperClass {
int num = 20;
}
class SubClass extends SuperClass {
int num = 200;
}
多态:构造方法(先初始化父类,再初始化子类)
package org.westos.demo2;
/**
* @author lwj
* @date 2020/4/17 17:16
*/
public class MyDemo {
public static void main(String[] args) {
SuperClass superClass = new SubClass();
System.out.println(superClass.num);
//20
//采用多态形式访问成员变量,访问的是父类的成员变量
//访问成员变量:编译看左边,运行看左边
//多态形式访问构造方法
}
}
class SuperClass {
int num = 20;
public SuperClass() {
System.out.println("父类的构造方法执行了");
//1
}
}
class SubClass extends SuperClass {
int num = 200;
public SubClass() {
System.out.println("子类的构造方法执行了");
//2
}
}
多态:静态方法(编译看左,运行看左)
package org.westos.demo2;
/**
* @author lwj
* @date 2020/4/17 17:16
*/
public class MyDemo {
public static void main(String[] args) {
SuperClass superClass = new SubClass();
System.out.println(superClass.num);
//20
//采用多态形式访问成员变量,访问的是父类的成员变量
//访问成员变量:编译看左边,运行看左边
//多态形式访问构造方法
//多态形式访问静态方法:编译看左,运行看左
SuperClass.staticMethod();
//父类的静态方法
//静态方法和静态成员好像不能用对象访问了,只能用类访问
}
}
class SuperClass {
int num = 20;
public SuperClass() {
System.out.println("父类的构造方法执行了");
//1
}
public static void staticMethod() {
System.out.println("父类的静态方法");
}
}
class SubClass extends SuperClass {
int num = 200;
public SubClass() {
System.out.println("子类的构造方法执行了");
//2
}
public static void staticMethod() {
System.out.println("子类的静态方法");
}
}
2、多态的好处
package org.westos.demo3;
/**
* 多态的好处:提高扩展性
* @author lwj
* @date 2020/4/17 17:33
*/
public class MyTest {
public static void main(String[] args) {
Animal animal1 = new Cat();
Utils.test(animal1);
//猫吃鱼
Animal animal2 = new Dog();
Utils.test(animal2);
//狗吃骨头
Animal animal3 = new Tiger();
Utils.test(animal3);
//老虎吃肉
}
}
class Utils {
private Utils() {}
//面向父类编程
public static void test(Animal animal) {
animal.eat();
}
}
class Animal {
public void eat() {
System.out.println("吃饭");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
class Tiger extends Animal {
@Override
public void eat() {
System.out.println("老虎吃肉");
}
}
3、向下转型
多态的弊端:不能直接调用子类特有的方法。(因为编译看左,父类如果没有子类特有的方法,那么将编译报错)
package org.westos.demo4;
/**
* @author lwj
* @date 2020/4/17 19:05
*/
public class MyDemo {
public static void main(String[] args) {
SuperClass superClass = new SubClass();
//向上转型
SubClass subClass = (SubClass) superClass;
//向下转型
subClass.test();
//调用子类特有的方法
}
}
class SuperClass {
public void show() {
System.out.println("父类方法");
}
}
class SubClass extends SuperClass {
@Override
public void show() {
System.out.println("子类方法");
}
public void test() {
System.out.println("子类独有的方法");
}
}
向下转型失败
package org.westos.demo4;
/**
* @author lwj
* @date 2020/4/17 19:08
*/
public class MyDemo2 {
public static void main(String[] args) {
Animal animal = new Cat();
//Dog dog = (Dog) animal;
//类型转换异常
//Exception in thread "main" java.lang.ClassCastException: org.westos.demo4.Cat cannot be cast to org.westos.demo4.Dog
}
}
class Animal {
public void eat() {
System.out.println("吃饭");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
public void actAsDoorKeeper() {
System.out.println("狗看门");
}
}
向下转型的内存图
package org.westos.demo4;
/**
* @author lwj
* @date 2020/4/17 19:44
*/
public class MyDemo3 {
public static void main(String[] args) {
Father father = new Son();
System.out.println(father.num);
//30
father.show();
//子类重写父类的show方法
Son son = (Son) father;
//向下转型
System.out.println(son.num);
//300
son.method();
//子类特有的方法
}
}
class Father {
int num = 30;
public void show() {
System.out.println("父类的show方法");
}
}
class Son extends Father {
int num = 300;
@Override
public void show() {
System.out.println("子类重写父类的show方法");
}
public void method() {
System.out.println("子类特有的方法");
}
}
父类引用指向子类对象中的父类实例,当进行了向下转型后,引用指向子类对象。
4、多态题目
package org.westos.demo5;
/**
* @author lwj
* @date 2020/4/17 22:04
*/
public class MyTest {
public static void main(String[] args) {
GrandFather a = new Father();
a.show();
/*
* 编译看左,GrandFather类中有show()方法,子类Father没有重写show()方法,所以执行GrandFather的show()方法,
* show2()方法,由于Father类重写了GrandFather类的show2()方法,所以执行的是Father的show2()方法,输出"爱"
* */
Father b = new Son();
b.show();
/*
* 编译看左,Father类中继承了GrandFather类的show()方法,所以编译不报错,Son类重写了show()方法,执行super.show()方法,
* 执行show2()方法,Son类重写了show2()方法,执行Son类的show2()方法,输出"你"
* */
}
}
class GrandFather {
public void show() {
show2();
}
public void show2() {
System.out.println("我");
}
}
class Father extends GrandFather {
@Override
public void show2() {
System.out.println("爱");
}
}
class Son extends Father {
@Override
public void show() {
super.show();
}
@Override
public void show2() {
System.out.println("你");
}
}
抽象类
1、概述
在继承阶段的Animal类,动物的eat()方法不应该有具体的实现,应该设定为抽象方法,由子类实现该方法。
在Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类。
抽象类的特点:
- 抽象类和抽象方法必须用abstract关键字修饰
- 抽象类格式:abstract class 类名 {}
- 抽象方法格式:abstract void eat();
- 抽象类不一定有抽象方法,但是有抽象方法的类一定是抽象类
- 抽象类中可以有构造方法,但是抽象类不能进行实例化,构造方法的作用是用于子类创建对象时初始化父类的属性;
- 抽象类不能直接实例化,可以由具体的子类实例化;(抽象类多态,多态的一种)
- 抽象类的子类
- 要么是抽象类
- 要么重写抽象类中所有的抽象方法
package org.westos.demo;
/**
* @author lwj
* @date 2020/4/17 20:20
*/
public class MyDemo {
public static void main(String[] args) {
Animal animal = new Animal() {
@Override
public void eat() {
System.out.println("吃东西");
}
};
animal.eat();
//吃东西
//animal是抽象类Animal的子类(实现类)的一个实例。
//只不过定义该子类的同时直接实例化了一个该子类的对象,并且指向了父类的引用。
}
}
abstract class Animal {
public Animal() {
}
/** 抽象方法 */
public abstract void eat();
}