JavaSE个人复习式整理知识点之面向对象四(多态、final、权限、内部类)

1 多态

1.1 概述

引⼊

多态是继封装、继承之后,⾯向对象的第三⼤特性。同⼀⾏为,通过不同的事物,可以体现出来的不同的形
态。多态,描述的就是这样的状态。

定义

多态:是指同⼀⾏为,具有多个不同表现形式 。
前提【重点】

  1. 继承或者实现【⼆选⼀】
  2. ⽅法的重写【意义体现:不重写,⽆意义】
  3. ⽗类引⽤指向⼦类对象【格式体现】

1.2 多态的实现

多态体现的格式:

⽗类类型 变量名 = new ⼦类对象;
变量名.⽅法名();

⽗类类型:指⼦类对象继承的⽗类类型,或者实现的⽗接⼝类型。

代码如下:

Fu f = new Zi();
f.method();

当使⽤多态⽅式调⽤⽅法时,⾸先检查⽗类中是否有该⽅法,如果没有,则编译错误;如果有,执⾏的是⼦类重写后⽅法。代码如下:
定义⽗类:

public abstract class Animal {
	public abstract void eat();
}

定义⼦类:

class Cat extends Animal {
	public void eat() {
		System.out.println("吃⻥");
    }
}
class Dog extends Animal {
	public void eat() {
		System.out.println("吃⻣头");
    }
}

定义测试类:

public class Test {
	public static void main(String[] args) {
		// 多态形式,创建对象
		Animal a1 = new Cat();
		// 调⽤的是 Cat 的 eat
		a1.eat();
		// 多态形式,创建对象
		Animal a2 = new Dog();
		// 调⽤的是 Dog 的 eat
		a2.eat();
    }
}

1.3 多态的好处

实际开发的过程中,⽗类类型作为⽅法形式参数,传递⼦类对象给⽅法,进⾏⽅法的调⽤,更能体现出多态的扩展性与便利。代码如下:
定义⽗类:

abstract class Animal {
	public abstract void eat();
}

定义⼦类:

class Cat extends Animal {
	public void eat() {
		System.out.println("吃⻥");
   }
}
class Dog extends Animal {
	public void eat() {
		System.out.println("吃⻣头");
   }
}

定义测试类:

public class Test01 {
    public static void main(String[] args) {
        // 多态形式,创建对象
        Cat c = new Cat();
        Dog d = new Dog();
        // 调⽤showCatEat
        showCatEat(c);
        // 调⽤showDogEat
        showDogEat(d);
        /*
         * 以上两个⽅法,均可以被showAnimalEat(Animal a)⽅法所替代
         * ⽽执⾏效果⼀致
         */
        showAnimalEat(c);
        showAnimalEat(d);
    }

    public static void showCatEat(Cat c) {
        c.eat();
    }

    public static void showDogEat(Dog d) {
        d.eat();
    }

    public static void showAnimalEat(Animal a) {
        a.eat();
    }
}

由于多态特性的⽀持, showAnimalEat ⽅法的 Animal 类型,是 Cat 和 Dog 的⽗类类型,⽗类类型接收⼦类对象,当然可以把 Cat 对象和 Dog 对象,传递给⽅法。当 eat ⽅法执⾏时,多态规定,执⾏的是⼦类重写的⽅法,那么效果⾃然与 showCatEa t、 showDogEat ⽅法⼀致, 所以 showAnimalEat 完全可以替代以上两⽅法。不仅仅是替代,在扩展性⽅⾯,⽆论之后再多的⼦类出现,我们都不需要编写showXxxEat ⽅法了,直接使⽤ showAnimalEat 都可以完成。所以,多态的好处,体现在,可以使程序编写的更简单,并有良好的扩展。

1.4 引⽤类型转换

多态的转型分为向上转型与向下转型两种:

向上转型

向上转型:多态本身是⼦类类型向⽗类类型向上转换的过程,这个过程是默认的。当⽗类引⽤指向⼀个⼦类对象时,便是向上转型。
使⽤格式:

⽗类类型 变量名 = new ⼦类类型();
如:Animal a = new Cat();
向下转型
  • 向下转型:⽗类类型向⼦类类型向下转换的过程,这个过程是强制的。

⼀个已经向上转型的⼦类对象,将⽗类引⽤转为⼦类引⽤,可以使⽤强制类型转换的格式,便是向下转型。
使⽤格式:

⼦类类型 变量名 = (⼦类类型) ⽗类变量名; 如: Cat c =(Cat) a;
为什么要转型

当使⽤多态⽅式调⽤⽅法时,⾸先检查⽗类中是否有该⽅法,如果没有,则编译错误。也就是说,不能调⽤⼦类拥有,⽽⽗类没有的⽅法。编译都错误,更别说运⾏了。这也是多态给我们带来的⼀点"⼩麻烦"。所以,想要调⽤⼦类特有的⽅法,必须做向下转型。
转型演示,代码如下:
定义类:

abstract class Animal {
    abstract void eat();
}

class Cat extends Animal {
    public void eat() {
        System.out.println("吃⻥");
    }

    public void catchMouse() {
        System.out.println("抓⽼⿏");
    }
}

class Dog extends Animal {
    public void eat() {
        System.out.println("吃⻣头");
    }

    public void watchHouse() {
        System.out.println("看家");
    }
}

定义测试类:

public class Test {
    public static void main(String[] args) {
        // 向上转型
        Animal a = new Cat();
        a.eat(); // 调⽤的是 Cat 的 eat
        // 向下转型
        Cat c = (Cat) a;
        c.catchMouse(); // 调⽤的是 Cat 的 catchMouse
    }
}
转型的异常

转型的过程中,⼀不⼩⼼就会遇到这样的问题,请看如下代码:

public class Test {
    public static void main(String[] args) {
        // 向上转型
        Animal a = new Cat();
        a.eat(); // 调⽤的是 Cat 的 eat
        // 向下转型
        Dog d = (Dog) a; 
        d.watchHouse(); // 调⽤的是 Dog 的 watchHouse 【运⾏报错】
    }
}

这段代码可以通过编译,但是运⾏时,却报出了 ClassCastException ,类型转换异常!这是因为,明明创建了Cat类型对象,运⾏时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。
为了避免 ClassCastException 的发⽣,Java提供了 instanceof 关键字,给引⽤变量做类型的校验,格式如下:

变量名 instanceof 数据类型
如果变量属于该数据类型,返回true。
如果变量不属于该数据类型,返回false。

所以,转换前,我们最好先做⼀个判断,代码如下:

public class Test {
    public static void main(String[] args) {
        // 向上转型
        Animal a = new Cat();
        a.eat(); // 调⽤的是 Cat 的 eat
        // 向下转型
        if (a instanceof Cat) {
            Cat c = (Cat) a; c.catchMouse(); // 调⽤的是 Cat 的 catchMouse
        } else if (a instanceof Dog) {
            Dog d = (Dog) a; d.watchHouse(); // 调⽤的是 Dog 的 watchHouse
        }
    }
}

2 final关键字

2.1概述

学习了继承后,我们知道,⼦类可以在⽗类的基础上改写⽗类内容,⽐如,⽅法重写。那么我们能不能随意的继承API中提供的类,改写其内容呢?显然这是不合适的。为了避免这种随意改写的情况,Java提供了 final 关键字, ⽤于修饰不可改变内容。

  • final:不可改变。可以⽤于修饰类、⽅法和变量。
    • 类:被修饰的类,不能被继承。
    • ⽅法:被修饰的⽅法,不能被重写。
    • 变量:被修饰的变量,不能被重新赋值。

2.2 使⽤⽅式

修饰类

格式如下:

final class 类名 {
}

查询API发现像 public final class String 、 public final class Math 、 public final class Scanner 等,很多我们学习过的类,都是被 final 修饰的,⽬的就是供我们使⽤,⽽不让我们所以改变其内容。

修饰⽅法

格式如下:

修饰符 final 返回值类型 ⽅法名(参数列表) {
	//⽅法体
}

重写被 final 修饰的⽅法,编译时就会报错。

修饰变量

1.局部变量 – 基本类型
基本类型的局部变量,被 final 修饰后,只能赋值⼀次,不能再更改。代码如下:

public class FinalDemo1 {
    public static void main(String[] args) {
        // 声明变量,使⽤final修饰
        final int a;
        // 第⼀次赋值
        a = 10;
        // 第⼆次赋值
        a = 20; // 报错,不可重新赋值
        // 声明变量,直接赋值,使⽤final修饰
        final int b = 10;
        // 第⼆次赋值
        b = 20; // 报错,不可重新赋值
    } 
}

思考,如下两种写法,哪种可以通过编译?
写法1:

final int c = 0;
	for (int i = 0; i < 10; i++) {
		c = i;
		System.out.println(c);
}

写法2:

for (int i = 0; i < 10; i++) {
	final int c = i;
	System.out.println(c);
}

根据 final 的定义,写法1报错!写法2,为什么通过编译呢?因为每次循环,都是⼀次新的变量c。这也是⼤家需要注意的地⽅。

2.局部变量 – 引⽤类型
引⽤类型的局部变量,被 final 修饰后,只能指向⼀个对象,地址不能再更改。但是不影响对象内部的成员变量值的修改,代码如下:

public class FinalDemo2 {
    public static void main(String[] args) {
        // 创建 User 对象
        final User u = new User();
        // 创建 另⼀个 User对象
        u = new User(); // 报错,指向了新的对象,地址值改变。
        // 调⽤setName⽅法
        u.setName("张三"); // 可以修改
    }
}

3.成员变量
成员变量涉及到初始化的问题,初始化⽅式有两种,只能⼆选⼀:

  • 显示初始化:
public class User {
	final String USERNAME = "张三";
	private int age;
}
  • 构造⽅法初始化:
public class User {
	final String USERNAME ;
	private int age;
	public User(String username, int age) {
		this.USERNAME = username;
		this.age = age;
   }
}

被 final 修饰的常量名称,⼀般都有书写规范,所有字⺟都⼤写。

3 权限修饰符

3.1概述

在Java中提供了四种访问权限,使⽤不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限:
- public:公共的
- protected:受保护的
- friendly(不填,也称为default):默认的
- private:私有的

3.2 不同权限的访问能⼒

publicprotecteddefaultprivate
同一个类中☑️☑️☑️☑️
同一个包中(子类与无关类)☑️☑️☑️
不同包的子类☑️☑️
不同包中的五官类☑️
可⻅, public 具有最⼤权限。 private 则是最⼩权限。 编写代码时,如果没有特殊的考虑,建议这样使⽤权限:
  • 成员变量使⽤ private ,隐藏细节。
  • 构造⽅法使⽤ public ,⽅便创建对象。
  • 成员⽅法使⽤ public ,⽅便调⽤⽅法。

4 内部类

4.1 概述

什么是内部类

将⼀个类A定义在另⼀个类B⾥⾯,⾥⾯的那个类A就称为内部类,B则称为外部类。

成员内部类
  • 成员内部类:定义在类中⽅法外的类。

定义格式:

class 外部类 {
	class 内部类 {
   }
}

在描述事物时,若⼀个事物内部还包含其他事物,就可以使⽤内部类这种结构。⽐如,汽⻋类 Car 中包含发动机类 Engine ,这时,Engine 就可以使⽤内部类来描述,定义在成员位置。
代码举例:

class Car { //外部类
	class Engine { //内部类
   }
}
访问特点

内部类可以直接访问外部类的成员,包括私有成员。

  • 外部类要访问内部类的成员,必须要建⽴内部类的对象。
  • 创建内部类对象格式:
外部类名.内部类名 对象名 = new 外部类型().new 内部类型();

访问演示,代码如下:
定义类:

public class Person {
    private boolean live = true;

    class Heart {
        public void jump() {
            // 直接访问外部类成员
            if (live) {
                System.out.println("⼼脏在跳动");
            } else {
                System.out.println("⼼脏不跳了");
            }
        }
    }

    public boolean isLive() {
        return live;
    }

    public void setLive(boolean live) {
        this.live = live;
    }
}

定义测试类:

public class InnerDemo {
    public static void main(String[] args) {
        // 创建外部类对象
        Person p = new Person(); // 创建内部类对象
        Heart heart = p.new Heart();
        // 调⽤内部类⽅法
        heart.jump();
        // 调⽤外部类⽅法
        p.setLive(false);
        // 调⽤内部类⽅法
        heart.jump();
    } 
}
输出结果:
⼼脏在跳动
⼼脏不跳了

内部类仍然是⼀个独⽴的类,在编译之后会内部类会被编译成独⽴的.class⽂件,但是前⾯冠以外部类的类名和 $ 符号 。⽐如,Person$Heart.class

4.2 匿名内部类【重点】

  • 匿名内部类:是内部类的简化写法。它的本质是⼀个带具体实现的⽗类或者⽗接⼝的匿名的⼦类对象。

开发中,最常⽤到的内部类就是匿名内部类了。以接⼝举例,当你使⽤⼀个接⼝时,似乎得做如下⼏步操作:
1.定义⼦类
2.重写接⼝中的⽅法
3.创建⼦类对象
4.调⽤重写后的⽅法
我们的⽬的,最终只是为了调⽤⽅法,那么能不能简化⼀下,把以上四步合成⼀步呢?匿名内部类就是做这样的快捷⽅式。

前提

匿名内部类必须继承⼀个⽗类或者实现⼀个⽗接⼝。

格式
new ⽗类名或者接⼝名() {
	// ⽅法重写
	@Override
	public void method() {
		// 执⾏语句
   }
};
使⽤⽅式

以接⼝为例,匿名内部类的使⽤,代码如下:
定义接⼝:

public abstract class FlyAble {
	public abstract void fly();
}

创建匿名内部类,并调⽤:

public class InnerDemo {
    public static void main(String[] args) {
        /*
         * 1.等号右边:是匿名内部类,定义并创建该接⼝的⼦类对象
         * 2.等号左边:是多态赋值,接⼝类型引⽤指向⼦类对象
         */
        FlyAble f = new FlyAble() {
            public void fly() {
                System.out.println("我⻜了~~~");
            }
        };
        // 调⽤fly⽅法,执⾏重写后的⽅法
        f.fly();
    }
}

以上两步,也可以简化为⼀步,代码如下:

public class InnerDemo3 {
    public static void main(String[] args) {
        /*
         * 创建匿名内部类,直接传递给showFly(FlyAble f)
         */
        showFly(new FlyAble() {
            public void fly() {
                System.out.println("我⻜了~~~");
            }
        });
    }
    public static void showFly(FlyAble f) {
        f.fly();
    }
}

5 引⽤类型⽤法总结

实际的开发中,引⽤类型的使⽤⾮常重要,也是⾮常普遍的。我们可以在理解基本类型的使⽤⽅式基础上,进⼀步去掌握引⽤类型的使⽤⽅式。基本类型可以作为成员变量、作为⽅法的参数、作为⽅法的返回值,那么当然引⽤类型也是可以的。

5.1 class作为成员变量

类作为成员变量时,对它进⾏赋值的操作,实际上,是赋给它该类的⼀个对象。

5.2 interface作为成员变量

我们使⽤⼀个接⼝,作为成员变量,以便随时更换技能,这样的设计更为灵活,增强了程序的扩展性。
接⼝作为成员变量时,对它进⾏赋值的操作,实际上,是赋给它该接⼝的⼀个⼦类对象。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值