目录
一.继承
继承的概念: 对共性的抽取,实现代码的复用
1.1继承的语法:
使用extends关键字 如下:
//Static代表子类,Dog代表父类
public class Static extends Dog{
//使用extends 关键字 实现子类继承父类的关系
}
当然注意:
- 子类会将父类中的成员变量或者成员方法继承到子类中了
- 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
继承代码使用场景:
class Animal{//这是我们创建的父类Animal
//父类成员变量
public String name = "父类name";
public int age = 99; //注意父类中有姓名和年龄
//父类成员方法
public void eat(){
System.out.println(this.name + "父类正在吃饭");
}
public void sleep(){
System.out.println(this.name + "父类正在睡觉"); //父类中还有吃饭和睡觉
}
}
class Dog extends Animal{//这是我们创建的子类狗类。让它去继承Animal
public String name = "子类name";
public int height = 30;
public void show(){
System.out.println(name); //1.子类的
System.out.println(this.name); //2.子类的
System.out.println(super.name); //3.父类的
System.out.println("父类成员变量age:"+age); //4.父类的
System.out.println("父类成员变量age:"+this.age); //5.父类的
System.out.println("子类成员变量height:"+height); //6.子类的
System.out.println("子类成员变量height:"+this.height); //7.子类的
eat(); //8. //子类
this.eat(); //9. //子类
super.eat(); //10.父类
sleep(); //11.父类
this.sleep(); //12.父类
super.sleep(); //13.父类
wangWang(); //14.子类
this.wangWang(); //15.子类
}
public void eat(){
System.out.println(this.name + "子类正在吃饭");
}
void wangWang(){
System.out.println(this.name + "子类喜欢汪汪叫!");
}
}
public class GJiCheng {
public static void main(String[] args) {
//用Dog类实例化一个对象
System.out.println("--------------------------------");
Dog dog1 = new Dog();
dog1.show();
System.out.println("--------------------------------");
}
}
在子类方法中 或者 通过子类对象访问成员时:
- 如果访问的成员变量子类中有,优先访问自己的成员变量
- 如果访问的成员变量子类无,则访问父类继承下来的,如果父类也没有定义,则编译报错
- 如果访问的成员变量与父类的成员变量同名时,则优先访问自己的
成员变量访问遵循就近原则,子类有优先子类的,如果没有再去父类中找
子类构造方法中调用父类的构造方法:
class eat{
private String name;
private int age;
//父类带参数的构造方法
eat(String name,int age){
this.age =age;
this.name =name;
System.out.println("父类构造方法"+this.name+this.age);
}
}
//Static代表子类,eat代表父类
public class Static extends eat{
//子类构造方法中调用·父类的构造方法
Static(){
//super里面的参数顺序,个数,类型必需和父类的构造参数一样,否则报错
super("父类",15);
}
}
注意:
1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的 super() 调用,即调用基类构 造方法2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的 父类构造方法调用,否则编译失败。3. 在子类构造方法中, super(...) 调用父类构造时,必须是子类构造函数中第一条语句。4. super(...) 只能在子类构造方法中出现一次,并且不能和 this 同时出现
想要访问子类对象的成员或者方法时使用 this. 访问父类对象的成员和方法时 使用 super.)
java 封装 (内部类)-优快云博客 这篇博客 我写了有关this关键字和super关键字 让大家更好的去理解
在继承关系下代码的执行顺序:
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person:构造方法执行");
}
{
System.out.println("Person:实例代码块执行");
}
static {
System.out.println("Person:静态代码块执行");
}
}
class Student extends Person{
public Student(String name,int age) {
super(name,age);
System.out.println("Student:构造方法执行");
}
{
System.out.println("Student:实例代码块执行");
}
static {
System.out.println("Student:静态代码块执行");
}
}
public class TestDemo4 {
public static void main(String[] args) {
Student student1 = new Student("张三",19);
System.out.println("===========================");
Student student2 = new Student("gaobo",20);
}
}
执行结果:
Person:静态代码块执行
Student:静态代码块执行
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
===========================
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
分析执行结果,得出以下结论:
- 最先执行父类静态代码块 接着执行子类静态代码块
- 父类实例代码块和父类构造方法紧接着执行
- 子类的实例代码块和子类的构造方法紧接着在执行
- 第二次实例化对象时,父类和子类的静态代码块都不会在执行(静态代码块仅执行一次)
1.2继承的方式
继承的几种方式:
注意 Java中不支持多继承 最好不要继承关系超过三层
1.3 final修饰词
final关键可以用来修饰变量、成员方法以及类。
1.final修饰的变量,不可以修改,表示常量
final int n=10;
n=20; //就会编译报错
2. final修饰的方法,不可以被重写,但可以有多个重载
final void fun(){
}
3. 用final修饰的类,不可以被继承
final class Dog{
}
class Animal extends Dog{ //继承final修饰的类就直接报错了
}
4.组合
class Dog{
//小狗
}
class Cat{
//小猫
}
class Bird{
//小鸟
}
class Animal{
Dog dog = new Dog(); //可以复用小狗的属性和方法
Cat cat = new Cat();//可以复用小猫的属性和方法
Bird bird = new Bird();//可以复用小鸟的属性和方法
}
public class Text extends Animal {
//动物类里面包含了 狗 猫 鸟的属性和方法 只需要继承Animal类就可以直接使用了
}
二 多态
2.1什么是多态
父类引用 子类对象不一样的时候,调用重写的方法,所表现出的行为不一样,这种思想————多态
(大白话)不同对象的引用,调用同一个方法,产生的结果不一样————多态
2.2多态的实现条件
java中实现多态,必须要满足如下几个条件,缺一不可:
1.继承关系
向上转型
2. 方法重写
子类必须重写父类的方法
3.通过父类对象引用调用重写的方法
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。
2.3动态绑定,静态绑定和向上转型
class Animal{
public Animal() {
}
public void eat(){
System.out.println("父类正在吃饭");
}
}
class Dog extends Animal{
@Override //重写父类 eat方法
public void eat(){
System.out.println("子类Dog在吃饭");
}
}
class cat extends Animal{
@Override //重写父类 eat方法
public void eat() {
System.out.println("子类CAT在吃饭");
}
}
public class JiCheng {
//产生多态方法二
public static void fun(Animal animal){
animal.eat();
}
public static void main(String[] args) {
//产生多态方法一
//创建实例局部变量来调用重写的方法
Animal animal1 = new Dog();
Animal animal2= new cat();
animal1.eat();
animal2.eat();
//产生多态方法二
//不需要创建实例 直接调用方法配合 上面的静态fun方法 来调用重写的方法
fun(new Animal());
fun(new Dog());
fun(new cat());
}
}
重点:
方法一:属于静态绑定,没有编译之前已经确定的具体具体调用那个方法
方法二: 属于动态绑定 编译期间不能确定方法的行为,在程序运行时才能确定具体调用哪个类
上面的两种方法均产生了向上转型 :
方法一 直接赋值:子类对象赋值给父类对象
方法二 方法传参:形参为父类型引用,可以接收任意子类的对象
向上转型的优点:让代码实现更简单灵活。向上转型的缺陷:不能调用到子类特有的方法。
2.4向下转型
向上转型之后可以当成父类对象使用,若需要调用子类特有的方法,则需要将父类对象再还原为子类对象。这就称作向下转型。
子类类型 子类引用名 = (子类类型) 父类引用
Dog dog = (Dog) animal;
2.4重写
重写 (override) :也称为覆盖。重写是子类对父类非静态、非 private 修饰,非 final 修饰,非构造方法等的实现过程进行重新编写 , 返回值和形参都不能改变 。 即外壳不变,核心重写! 重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
方法重写的规则
- 子类在重写父类的方法时,必须和父类的 返回值类型 (返回类型也可以是父子关系) 方法名 (参数列表) 要完全一致
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方 法就不能声明为protected
- 父类被static、private修饰的方法、构造方法都不能被重写。
- 重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验.
重写与重载的区别:
重载(Overload)指的是在同一个类中,根据方法的参数列表的不同,定义多个具有相同名称但参数类型或个数不同的方法。重载的方法具有相同的名称,但方法签名不同。
重写(Override)指的是子类重新定义和实现了从父类中继承的方法。重写的方法具有与父类方法相同的名称、参数列表和返回类型。
即:方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现
2.5多态的优缺点
多态的优点:
降低代码的圈复杂度
一段代码中条件语句和循环语句越多,说明该代码圈复杂度越高
可扩展能力更强
想更改功能,只需在进行方法重写
多态缺陷:
1. 属性没有多态性
当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
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();
}
}
//最终结果 为0,
- 构造 D 对象的同时, 会调用 B 的构造方法.
- B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
- 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1.
- 所以在构造函数内,尽量避免使用实例方法,除了final和private方法。
注意:
尽量不要在构造器中调用方法 ( 如果这个方法被子类重写 , 就会触 发动态绑定, 但是此时子类对象还没构造完成 ), 可能会出现一些隐藏的但是又极难发现的问题