JavaSE学习总结(六)面向对象(中)代码块/继承/this和super/父类没有无参构造子类怎么办/方法重写/重写和重载的区别/final关键字/多态/多态成员访问特点/向上转型向下转型/孔子装爹

面向对象(中)

一、代码块

(一)代码块概述

在Java中,使用{}括起来的代码被称为代码块。

(二)代码块分类

根据其位置和声明的不同,可以分为局部代码块、构造代码块、静态代码块和同步代码块(多线程部分讲解)。

(三)常见代码块的应用

1.局部代码块

在方法中出现;限定变量生命周期,及早释放,提高内存利用率

2.构造代码块

在类中方法外出现;多个构造方法方法中相同的代码存放到一起,每次调用构造方法都执行,并且在构造方法前执行

3.静态代码块

在类中方法外出现,并加上static修饰;静态代码块用于给类进行初始化,随着类的加载而加载,优先于构造代码块和局部代码块执行,且由于类只加载了一次,因此静态代码块只执行一次静态代码块中只能访问静态变量

案例演示1

public class MyTest1 {
    public static void main(String[] args) {
        //局部代码块:定义在方法中的代码块
        int b=100;
        {
            //局部代码块,可以尽早的释放空间和资源
            int a=200;
            System.out.println("这是一个局部代码块");
            System.out.println(a);
            System.out.println(b);
        }
        //System.out.println(a);//报错!当局部代码块的右括号结束后,括号内的资源全部释放,因此变量a不再存在
        System.out.println(b);
    }
}

在这里插入图片描述
案例演示2

class Student{
    static {
        System.out.println("Student 静态代码块"); //3
    }

    {
        System.out.println("Student 构造代码块"); //4 6
    }

    public Student() {
        System.out.println("Student 构造方法"); //5 7
    }
}
class StudentDemo {
    static {
        System.out.println("StudentDemo的静态代码块");//1
    }

    public static void main(String[] args) {
        System.out.println("我是main方法");//2
        Student s1 = new Student();
        Student s2 = new Student();
    }
}

在这里插入图片描述

二、继承

(一)继承概述

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。

(二)继承格式

通过extends关键字可以实现类与类的继承

class 子类名 extends 父类名 {}  

单独的这个类称为父类,基类或者超类;继承该类的这多个类可以称为子类或者派生类。
那么什么时候可以使用继承呢?
继承其实体现的是一种关系:“is a” .
采用假设法:如果有两个类A、B。只要他们符合A是B的一种,或者B是A的一种,就可以考虑使用继承。

案例演示1

public class MyTest {
    public static void main(String[] args) {
        Zi zi = new Zi();
        //子类可以继承父类的成员变量和成员方法,并且可以使用
        System.out.println(zi.num);
        zi.show();
    }
}
class Fu{
    int num=100;
    public void show(){
        System.out.println("这是一个show方法");
    }
}
class Zi extends Fu{

}

案例演示2

public class MyTest2 {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.name = "汤姆";
        cat.age = 1;
        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 = 10;
        System.out.println(dog.name);
        System.out.println(dog.age);
        dog.eat();
        dog.sleep();
        dog.lookDoor();
    }
}
class Animal {
    String name;
    int age;
    public void eat() {
        System.out.println("吃饭");
    }
    public void sleep() {
        System.out.println("睡觉");
    }
}
//猫和狗都有名字、年龄、吃饭、睡觉等共性,如果在每个类中都写一次就会增加重复代码
//因此我们可以讲这些共性抽取到动物类中,而猫和狗类继承这个动物类,猫和狗类中只需要写自己的特性代码
class Cat extends Animal{
    public void catchMouse(){
        System.out.println("抓老鼠");
    }
}
class Dog extends Animal{
    public void lookDoor() {
        System.out.println("看门");
    }
}

在这里插入图片描述

(三)继承的优点和弊端

1.优点:
  • 提高了代码的复用性
  • 提高了代码的维护性
  • 让类与类之间产生了关系,继承是多态的前提
2.弊端:
  • 类的耦合性增强了

开发的原则:高内聚,低耦合。
耦合:类与类之间关联的程度(一个类要完成某个功能需要依赖其他类的程度)
内聚:一个类单独完成某个功能的能力

(四)Java中类的继承特点

1.Java只支持单继承,不支持多继承

有些语言是支持多继承,例如C++,格式:extends 类1,类2,…

2.Java支持多层继承(可以使用多层继承关系中上几层的类的非私有成员)

Object类是所有类的顶层父类,所有类都是直接或间接地继承Object类

(五)继承的注意事项

1.子类只能继承父类所有非私有的成员(成员方法和成员变量)
2.子类不能继承父类的构造方法

但是可以通过super(下边讲)关键字去访问父类构造方法。

3.不要为了部分功能而去继承

会增加耦合性,根据实际情况,如果共同属性和行为不多就不建议继承。

案例演示

public class MyTest3{
    public static void main(String[] args) {
            C c1 = new C();
            System.out.println(c1.a);//C类对象可以使用上两层的A类的非私有变量a
            System.out.println(c1.b);
            // System.out.println(c1.d);报错:d是B类的私有成员变量
            // B b1 = new B();
            // b1.show();报错:show()是A类的私有方法
            // C c2 = new C();
            // c2.show();报错:show()是A类的私有方法
    }
}
class A{//默认有个extends Object
    int a=100;
    private void show(){
        System.out.println("这是一个私有的方法");
    }
}
class B extends A{
    int b=200;
    private int d=10;
}
class C extends B{

}

(六)继承中成员变量的“就近原则”

如果子类中的成员变量和父类中的成员变量名称一样,在子类中访问一个该变量的查找顺序遵循"就近原则":

  1. 在子类的方法的局部范围找,有就使用
  2. 在子类的成员范围找,有就使用
  3. 在父类的成员范围找,有就使用
  4. 如果还找不到,就报错

案例演示

public class MyTest4 {
    public static void main(String[] args) {
        B b = new B();
        int num=1;
        b.show(num);
    }
}
class A{
    int num=100;
}
class B extends A{
    int num=200;
    public void show(int num){
        System.out.println(num); //1        
        // 变量的访问原则:遵循就近原则,先在局部找这个变量,找到就使用,如果局部没找到,去本类的成员位置找,找到就使用
        //如果本类的成员位置没找到,去父类的成员位置找,找到就使用。
        System.out.println(this.num);//200  指的是本类对象的成员变量num
        System.out.println(new A().num);//100  指的是A类的成员变量num
        //System.out.println(super.num);//100  也可以用super的方法,指的是父类的成员变量num,下边讲
    }
}

(七)this和super的区别和应用

1.this和super的区别

this 代表的是本类对象的引用
super 代表的是父类存储空间的标识(可以理解成父类的引用,可以操作父类的成员)

2.this和super的应用
  • 调用成员变量
    this.成员变量 调用本类的成员变量
    super.成员变量 调用父类的成员变量
  • 调用构造方法
    this(...) 调用本类的构造方法
    super(...) 调用父类的构造方法
  • 调用成员方法
    this.成员方法 调用本类的成员方法
    super.成员方法 调用父类的成员方法

案例演示

public class MyTest3 {
    public static void main(String[] args) {
        B b = new B();
        b.show(1);
        b.test1();
        System.out.println("------------");
        b.test();
    }
}
class A {
    int a = 200;
    public void test1() {
        System.out.println("这是父类的test1方法");
    }
}
class B extends A {
    int a = 100;
    public void show(int a) {
        System.out.println(a);
        System.out.println(this.a);
        System.out.println(super.a);
    }
    public void test2() {
        System.out.println("这是子类的test2方法");
    }
    public void test() {
        this.test2();
        this.test1();//本类对象调用父类的方法
        super.test1();//使用父类的空间标识去调用父类的方法
    }
}

在这里插入图片描述

(八)继承中构造方法的关系

注意: 构造方法不参与继承,子类中所有的构造方法默认都会访问父类中空参数的构造方法

为什么呢?
因为有了继承关系后,子类要去继承父类的数据,可能还会要使用父类的数据,所以肯定先要让父类的构造方法执行,来完成对父类数据的初始化,然后再完成子类的数据的初始化。即初始化子类前,先要完成父类数据的初始化。

其实:
每一个构造方法的第一条语句默认都是:super(),只是没显示出来,也可以自己手动写出来。

案例演示

public class MyTest {
    public static void main(String[] args) {
        Son son1 = new Son();
        System.out.println("-------------");
        Son son2 = new Son(102);
    }
}
class Father{
    int num=1000;
    public Father() {
        super();
        System.out.println("这是父类的空参构造");
    }
}
class Son extends Father{
    int b=10;
    public Son() {
        super();
        System.out.println("这是子类的空参构造");

    }
    public Son(int b) {
        super();
        this.b = b;
        System.out.println("这是子类的有参构造");
    }
}

在这里插入图片描述

注意事项:
1.父类没有无参构造方法,子类怎么办?有以下三种方法:

①子类的构造方法通过super去显式调用父类其他的带参的构造方法
②子类的构造方法通过this去调用本类的其他构造方法,但是这里的其他构造方法也必须首先访问了父类的带参构造方法
③在父类中添加一个无参的构造方法

2.super(…)或者this(….)必须出现在第一条语句上, 否则,可能会对父类数据进行多次初始化。

案例演示

public class MyTest {
    public static void main(String[] args) {
        Zi zi = new Zi();
        System.out.println("-------------");
        Zi zi1 = new Zi(1009);
        System.out.println(zi.num);
        System.out.println(zi1.num);
    }
}
class Fu{
    int num=10;
    /*注释掉父类的空参构造
    public Fu() {
        System.out.println("父类的空参构造执行了");
    }
    */
    public Fu(int num) {
        this.num = num;
        System.out.println("父类的有参构造执行了");
    }
}
class Zi extends Fu{
    int num=100;
    public Zi() {
        super(10); //方法1:通过super去显式调用父类其他的带参的构造方法
        System.out.println("子类的空参构造执行了");
    }
    /*
    public Zi(int num) {
        super(num);
        System.out.println("子类的有参构造执行了");
    }
     */
    public Zi(int num) {
        this();//方法2:通过this去调用本类的其他构造方法,但是这里的其他构造方法也必须首先访问了父类的带参构造方法
        System.out.println("子类的有参构造执行了");
    }
}

在这里插入图片描述


思考题:继承中的代码块执行顺序

public class MyTest{
	public static void main(String[] args){
		Zi z=new Zi();
	}
}
class Fu {
	static {
		System.out.println("静态代码块Fu");
	}

	{
		System.out.println("构造代码块Fu");
	}

	public Fu() {
		System.out.println("构造方法Fu");
	}
}

class Zi extends Fu {
	static {
		System.out.println("静态代码块Zi");
	}

	{
		System.out.println("构造代码块Zi");
	}

	public Zi() {
		System.out.println("构造方法Zi");
	}
}

在这里插入图片描述

(九)方法重写

我们知道,当子类的方法名和父类的方法名一样,而参数列表(或者参数列表和返回值类型)不一样的时候,通过子类调用方法:

  1. 先查找子类中有没有该方法,如果有就使用
  2. 再看父类中有没有该方法,有就使用
  3. 如果都没有就报错

那么,如果子类中出现了方法名、参数列表、返回值类型都和父类中一模一样的方法声明,又该怎么办呢?

1.什么是方法重写?

子类中出现了和父类中一模一样的方法声明(方法名,参数列表,返回值类型),也被称为方法覆盖,方法复写。

2.方法重写的应用:

如果说子类对父类的方法实现“不满意”,那么子类就可以覆盖他,或者说,子类想要对父类的方法的实现功能进行扩展,也可以使用方法重写的这种机制。这样,既沿袭了父类的功能,又定义了子类特有的内容。

案例演示

public class MyTest {
    public static void main(String[] args) {
        new NewPhone().call();
    }
}
class Phone{
    public void call(){
        System.out.println("打电话");
    }
}
class NewPhone extends Phone{
	@Override//重写的注解 可以检测此方法是否是重写父类的 可加可不加 最好加上
    public void call() {//方法重写,新手机不仅可以打电话还能视频
        super.call();
        System.out.println("视频电话");
    }
}

在这里插入图片描述

3.Override(方法重写)和Overload(方法重载)的区别?

两者不同主要体现在:

  1. 目的
    overload用于增加程序的可读性(做法不同,但是做的是同一事情)。 override用于提供其父类已经提供的方法的特定实现。

  2. 范围
    overload 在相同的类范围内执行。 override发生在两个具有继承关系的类中。

  3. 参数列表
    overload参数列表必须不同。 override参数列表必须相同。

  4. 返回值类型
    overload中可以相同或不同,但是光是返回值类型不同不构成重载。 override必须是相同的。

4.方法重写注意事项

a:父类中私有方法不能被重写
因为父类私有方法子类根本就无法继承,何谈重写
b:子类重写父类方法时,访问权限不能更低
要比父类的高或者一样,最好一样
public>protected>缺省的>private
c:静态方法只能被继承,不能被重写
如果子类有和父类相同的静态方法,子类调用这个方法时,调用的是子类的静态方法,但这并不是重写的原因,而是父类的静态方法被隐藏了,对于子类不可见,也就是说,子类和父类中相同的静态方法是没有关系的方法,他们的行为不具有多态性。但是父类的静态方法可以通过父类.方法名调用。

(十)final关键字

1.为什么会有final

由于继承中有一个方法重写的机制,而有时候我们不想让子类去重写父类的方法,针对这种情况java就给我们提供了一个关键字: final

2.final概述

final关键字是最终的意思,可以修饰类、变量、成员方法、对象。

3.final修饰特点

修饰类: 被修饰的类不能被继承
修饰方法: 被修饰的方法可以被继承,但是不能被重写
修饰变量: 被修饰的变量不能被重新赋值,因为这个变量被修饰后会变成常量
修饰对象:被修饰的对象,内容可变,引用不可变。

4.final修饰局部变量

局部变量如果是基本类型,值不能被改变
局部变量如果是引用类型,地址值不能被改变

案例演示

public class MyTest {
    public static final int A=10; //公共的静态常量
    public static void main(String[] args) {
        System.out.println(A);
        System.out.println(MyTest.A);

        new Zi().test();//可以继承被final修饰的方法,但不能重写
        new Zi().show();

        //final 修饰基本数据类型,指的是这个值不能再次被改变,此变量为常量
        final int NUM=100;
        System.out.println(NUM);
        //NUM=99;报错
        //final 修饰引用数据类型,指的是这个地址值,不能再次被改变
        final Fu fu = new Fu();
        System.out.println(fu);
        //fu=new Fu();报错
    }
}
final class C{

}
/*报错,不能继承final修饰的类
class D extends C{

}
*/
class Fu{
    public void show(){
        System.out.println("这是父类的show");
    }
    public final void test(){
        System.out.println("这是父类的final-test");
    }
}
class Zi extends Fu{
    @Override
    public void show() {
        System.out.println("这是子类的show");
    }
}

在这里插入图片描述

三、多态

(一)多态概述

某一个事物,在不同时刻表现出来的不同状态。

举例:
家猫可以是猫类的对象,同时家猫也是动物的一种,也可以把家猫称为动物。

Cat c=new Cat();
Animal c=new Cat();

(二)多态前提条件

1.要有继承关系。
2.要有方法重写。 其实没有也是可以的,但是如果没有这个就没有意义。
3.要有父类引用指向子类对象。
父 f =  new();

案例演示1

public class MyTest {
    public static void main(String[] args) {
        //多态,父类引用指向的是子类对象
        Animal an=new Cat();
        an.sleep();
        an.eat();
        an.show();
    }
}
class Animal{
    public void eat(){
        System.out.println("吃饭");
    }
    public void sleep() {
        System.out.println("睡觉");
    }
    public void show() {
        System.out.println("父类中的show方法");
    }
}
class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("猫爱吃鱼");
    }
    @Override
    public void sleep() {
        System.out.println("猫爱白天睡觉");
    }
}

在这里插入图片描述
那么这个多态到底有什么实际意义呢,再看这个案例。

案例演示2

public class MyTest {
    public static void main(String[] args) {
        Cat cat = new Cat();
        MyUtils.testEat(cat);
        Dog dog = new Dog();
        MyUtils.testEat(dog);
        Rabbit rabbit = new Rabbit();
        MyUtils.testEat(rabbit);
    }
}
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 Rabbit extends Animal{
    @Override
    public void eat() {
        System.out.println("兔子吃草");
    }
}
//工具类
class MyUtils{
    public static void testEat(Cat cat){//静态方法不需要创建对象使用,直接拿MyUtils类调用
        cat.eat();
    }
    public static void testEat(Dog dog){
        dog.eat();
    }
    public static void testEat(Rabbit rabbit){
        rabbit.eat();
    }
}

在这里插入图片描述
从案例2看出,当我们每创建一种动物类的对象,在工具类中就需要同步声明一个方法。比如我要是再创建一个老虎类Tiger,并且创建老虎类的对象,同时想通过工具类调用它重写的eat方法,那么我必须在MyUtils类中再声明一个public static void testEat(Tiger tiger){...},这样就十分麻烦,要是还有上百种动物类,那工程量可想而知。因此,我们就需要运用到多态的机制,MyUtils中只需要一个方法声明就能搞定所有动物。

class MyUtils{
    public static void testEat(Animal an){//例如当传进来是个猫类时,相当于Animal an=new Cat(),父类引用指向子类对象
        an.eat();
    }
}

(三)多态中的成员访问特点

1.成员变量

如果子类和父类变量重名,使用时也是使用的父类的变量。

2.成员方法

如果子类重写了父类的方法,那么调用时使用子类重写的方法,否则,使用父类的方法。

案例演示

public class MyTest {
    public static void main(String[] args) {
        Fu fu = new Zi();//多态,父类引用指向子类对象
        System.out.println(fu.num);//用的还是父类的变量
        fu.show();//用的是子类重写后的方法
    }
}
class Fu{
    int num = 100;
    public void show(){
        System.out.println("父类的show方法");
    }
}
class Zi extends Fu{
    int num=10;
    @Override
    public void show() {
        System.out.println("子类重写过后的show方法");
    }
}

在这里插入图片描述


思考题:写出结果

class A {
	public void show() {
		show2();
    }
	public void show2() {
		System.out.println("我");
	}
}
class B extends A {
	public void show2() {
		System.out.println("爱");
	}
}
class C extends B {
	public void show() {
		super.show();
	}
	public void show2() {
		System.out.println("你");
	}
}
public class MyTest {
	public static void main(String[] args) {
		A a = new B();
		a.show();
		B b = new C();
		b.show();
	}
}

解析:首先,从main方法看起,A a = new B(); a.show();执行完这两句后,先去B类找是否有show方法的重写,结果没有,那就使用A类的show方法,由于它调用了show2方法,那么再去看B类有没有show2方法的重写,结果有,就执行,输出“爱”。B b = new C(); b.show();接着,执行这两句,先去C类找是否有show方法的重写,结果有,但它的重写调用了父类的show方法,而父类B没有show方法,那再找上一层A类的show方法,找到后执行,由于它调用了show2方法,那么再去看C类有没有show2方法的重写,结果有,就执行,输出“你”。
结果
在这里插入图片描述

3.静态方法

一个指向子类对象的父类引用来调用父子同名的静态方法时,只会调用父类的静态方法

案例演示

public class MyTest {
    public static void main(String[] args) {
        Fu fu = new Zi();
        fu.show();//多态情况下,调用父子同名的静态方法,只会调用父类的
        //非多态情况下,子类调用父子同名的静态方法,是调用子类的,但这不是重写,而是因为这种情况父类的静态方法对子类隐藏了
        Zi zi = new Zi();
        zi.show();
        Fu fu1 = new Fu();
        fu1.show();
    }
}
class Fu{
    public static void show(){
        System.out.println("父类的静态方法");
    }
}
class Zi extends Fu{
    public static void show() {
        System.out.println("子类的静态方法");
    }
}

在这里插入图片描述

(四)多态的优点和弊端

1.优点
  • 提高了代码的维护性(继承保证)
  • 提高了代码的扩展性(由多态保证)
2.弊端
  • 不能访问子类特有的成员变量和成员方法

解决方法:把父类的引用强制转换为子类的引用。(向下转型)

案例演示

public class MyTest {
    public static void main(String[] args) {
        Fu fu = new Zi();
        System.out.println(fu.a);
        //System.out.println(fu.num);报错,多态中无法访问子类独有的成员变量
        //fu.test();报错,多态中无法访问子类独有的成员方法
        //那么,我们就想要访问子类独有的成员怎么办呢?我们可以向下转型
        Zi zi= (Zi) fu; //向下转型  将Fu类的对象fu强行向下转型为Zi类对象zi
        System.out.println(zi.num);
        zi.test();
    }
}
class Fu{
    int a=10;
}
class Zi extends Fu{
    int num=20;
    public void test(){
        System.out.println("子类一个特有的方法");
    }
}

在这里插入图片描述
有了向下转型,那么有没有向上转型呢?
其实,多态就是向上转型。父 f = new 子();

为了更好地理解向上转型和向下转型,再举个例子:孔子装爹
纯属瞎编:有这么个故事,孔子的爹擅长Java编程教学,因此很多人慕名而来
有一天,一位学者请孔子的爹到家中去讲授JavaSE,留孔子一个人在家
过了一会,又有一位学者来到孔子家中想请教孔子的爹,但孔子爹已经出门,而孔子不想失去这个学生
孔子就起了私心,粘上了胡子,穿上了他爹的服装,开始装爹,给这位学者讲授论语
当讲授完后,孔子卸下了伪装,做回自己,跑去打王者荣耀了

public class MyTest{
    public static void main(String[] args) {
        孔子爹 k = new 孔子();//向上转型(多态):孔子装爹
        System.out.println(k.age);//此时看上去的年龄
        k.teach();//孔子讲授论语
        孔子 k1=(孔子) k;//向下转型:做回自己
        System.out.println(k1.age);//此时看上去的年龄
        k1.playGame();//做孔子独有的爱好:打王者荣耀
    }
}
class 孔子爹{
    int age=60;
    public void teach(){
        System.out.println("讲授Java");
    }
}
class 孔子 extends 孔子爹{
    int age=30;
    @Override
    public void teach() {
        System.out.println("讲授论语");
    }
    public void playGame(){
        System.out.println("玩王者荣耀");
    }
}

在这里插入图片描述
声明:本例只是为了更好地理解向上转型和向下转型,但是用中文给类命名实在不推荐,请勿模仿。

(五)多态的内存图解

在这里插入图片描述

首先,MyTest.class加载进方法区,将main()加载进栈执行,当执行到Fu fu=new Zi();时,在堆内存中创建Zi对象的内存空间,然而我们首先要对Fu类进行初始化,但是这句话没有创建Fu类的对象,那么Fu类的内存空间就创建在Zi类当中,此时Fu类的引用指向的是Zi类空间中的super的空间。将Fu类的变量num默认初始化为0,再显式初始化为100,然后将Zi类的变量num默认初始化为0,再显式初始化为200。由于引用fu指向super的空间,因此fu.num输出的是100。接着执行fu.show();,由于子类重写了show方法,所以它动态绑定到子类的show方法并调入进栈执行,输出“子类的show”,弹栈。然后Zi zi=(Zi) fu;向下转型,这时引用zi指向Zi类的this空间,因此zi.num输出的是200,zi.test();输出“子类的test”,执行完弹栈。最后main()弹栈。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值