2.3编译时绑定
刚刚介绍了运行时绑定,现在来说说编译时绑定,所谓的编译时绑定,就是程序发生了方法的重载,这个重载在编译时就会发生,不像重写是在运行时发生的。我们来看一段含有重载的代码的反汇编就明白了。
public class Func {
public static int add(int a, int b) {
return a + b;
}
public static int add(int a, int b, int c) {
return a + b + c;
}
public static int add(int a, int b, int c, int d) {
return a + b + c +d;
}
public static void main(String[] args) {
int x = add(1,2);
int y = add(1, 2, 3);
int z = add(1, 2, 3 ,4);
System.out.println(x);
System.out.println(y);
System.out.println(z);
}
}
复制代码
⭐️方法重载的规则(复习):
- 方法名相同。
- 参数列表不同(包括形参类型与个数)。
- 返回值不做要求。
编译时绑定也称静态绑定,是程序编译时期的一种行为。
⭐️重写与重载的区别: 区别|重写|重载 |---|---|---| 概念|方法名相同,参数列表相同,返回值相同(协变返回类型除外)|方法名相同,参数列表不同,返回值不做要求 范围|继承关系(父子类关系)|一个类 限制|子类的重写方法的权限要大于父类被重写的访问权限,访问权限不能是private,不能被final,static修饰|无权限要求,final,static修饰也可发生重载
2.4向下转型
向下转型用的很少且不安全。所谓的向下转型就是在向上转型的基础上,使用子类引用的值等于父类引用的值,这个过程需要强制转换。 还是看一个老栗子:
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 + "正在吃饭!");
}
}
class Cat extends Animal {
public Cat(String name, int age) {
super(name, age);
}
public void cute() {
System.out.println(name + "忙着装可爱!");
}
@Override
public void eat() {
System.out.println(this.age + this.name + "正在安静地吃饭!");
}
}
class Bird extends Animal {
public Bird(String name, int age) {
super(name, age);
}
public void fly() {
System.out.println(this.name + "正在飞翔!");
}
@Override
public void eat() {
System.out.println(this.age + this.name + "正在慢慢地吃饭!");
}
}
复制代码
如果向下转型后类型匹配:
public class Instancesof {
public static void main(String[] args) {
Animal animal = new Cat("小猫", 1);
Cat cat = (Cat)animal;//向下转型
cat.eat();
}
}
复制代码
向下转型后类型不匹配:
public class Instancesof {
public static void main(String[] args) {
Animal animal = new Cat("小猫", 1);
Bird Bird = (Bird) animal;
bird.eat();
}
}
复制代码
为什么说它是不安全的呢,因为一个父类可能会有多个子类,向下转型可能会造成类型不匹配。所以为了避免这种异常的发生,我们在使用向下转型的时候,往往需要先判断类型是否匹配,这里我们可以使用关键字instanceof
对向下转型后子类引用是否类型匹配。
public class Instancesof {
public static void main(String[] args) {
Animal animal = new Cat("小猫", 1);
if (animal instanceof Bird) {
Bird bird = (Bird) animal;
bird.eat();
}
if (animal instanceof Cat) {
Cat cat = (Cat)animal;
cat.eat();
}
}
}
复制代码
2.5多态
2.5.1理解多态
在理解向上转型情况下,我们就能来试着理解多态了,先不讲多态的概念,因为很抽象,先来一个例子。我们生活中存在许许多多的形状,比如圆,正方形,菱形,五角星,三角形等等,使用继承和重写的方法实现一个类Shape输出多种形状。
首先定义Shape类,在这个类中创建一个draw方法,然后创建其他的具体图形类继承Shape。
public class Shape {
public void draw() {
System.out.println("我要打印一个形状!");
}
}
class Circle extends Shape{
@Override
public void draw() {
System.out.println("○");
}
}
class Triangle extends Shape{
@Override
public void draw() {
System.out.println("△");
}
}
class Rhombus extends Shape{
@Override
public void draw() {
System.out.println("◇");
}
}
class Flower extends Shape{
@Override
public void draw() {
System.out.println("❀");
}
}
class Star extends Shape{
@Override
public void draw() {
System.out.println("☆");
}
}
class Square extends Shape{
@Override
public void draw() {
System.out.println("□");
}
}
复制代码
方式1:使用父类数组存放子类的对象,然后循环调用子类的draw重写方法。
public class Test {
public static void main(String[] args) {
Flower flower = new Flower();
Square square = new Square();
Star star = new Star();
Rhombus rhombus = new Rhombus();
Circle circle = new Circle();
Triangle triangle = new Triangle();
Shape[] shapes = {flower, square, star, rhombus, circle, triangle};
for (Shape shape :shapes) {
shape.draw();
}
}
}
复制代码
方式2:创建一个方法,形参是Shape类型,方法的具体内容就是调用draw方法。
public class Test2 {
public static void drawMap(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
Flower flower = new Flower();
Square square = new Square();
Star star = new Star();
Rhombus rhombus = new Rhombus();
Circle circle = new Circle();
Triangle triangle = new Triangle();
drawMap(flower);
drawMap(square);
drawMap(star);
drawMap(rhombus);
drawMap(circle);
drawMap(triangle);
}
}
复制代码
使用方式2输出各类的形状就叫做多态,当类的调用者在编写 drawMap 这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例. 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现(和 shape 对应的实例相关), 这种行为就称为 多态。
所谓多态就是一个引用能够表现出多种不同的形态,这就是多态,多态可以让你忘记对象的类型,就如这个栗子,你不需要关心shape引用的是哪一个子类,它会根据不同的子类对象而执行不同的方法或者说产生不同的行为。
2.5.2多态的优点
✨1.能够让类调用者对类的使用成本降低。 封装是让类的调用者不需要知道类的实现细节. 多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可. 因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低. ✨2.降低程序的“圈复杂度”,减少分支语句的使用。 如果没有多态,输出不同种类的形状时需要先判断到底调用哪一个类的draw方法,势必会使用很多的if-else分支语句,分支语句多了,“圈复杂度”也变得更复杂了. 圈复杂度是一种描述一段代码复杂程度的方式,一段代码如果平铺直叙, 那么就比较简单容易理解, 而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂. 因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数,,这个个数就称为 "圈复杂度". 如果一个方法的圈复杂度太高, 就需要考虑重构,一般情况下公司要求圈复杂不大于10.
3.抽象类
3.1abstract
在前面实现多态的时候父类Shape的draw方法,在运行时都没有被调用,因为都发生了重写,调用的是子类的draw方法,所以父类draw方法没有必要有具体实现。但是在Java中并没有C语言里面的声明,如果按C那样的形式声明方法,在Java中是会报错的。 于是Java中引入了抽象方法,抽象方法是一个没有具体实现的方法,它被关键字abstract
修饰,抽象方法的作用就是用来被重写。 包含抽象方法的类必须是抽象类,换一种说法,如果一个类包含一个或多个抽象方法,该类必须必须被限定为抽象类,也就是使用关键字abstract
修饰这个类。 在抽象类中可以定义成员变量,成员方法,就对类里面的属性和方法而言,与普通类不同的是抽象类里面含有抽象方法,其他都是一样的。除此之外,抽象类是普通类的进一步抽象,抽象类不能被实例化成对象,抽象类最大的作用就是为了被继承。
3.2抽象类的特点
- 包含抽象方法的类,称为抽象类。
- 抽象方法是一个没有具体实现的方法,类似与C语言的声明。
- 抽象类不能够被实例化成对象。
- 抽象类最大的作用就是为了被继承,抽象方法最大的作用就是被重写。
- 一个普通类继承了一个抽象类,则该普通类需重写抽象类中所有的抽象方法。
- 如果一个抽象类B继承了抽象类A,普通类C继承了抽象类B,则普通类C要重写抽象类A和抽象类B中所有的抽象方法。
- 一个抽象类B继承了另一个抽象类A,则抽象类B不需要重写抽象类A的方法。
- 抽象类中可以包含与普通类一样的成员方法和成员字段。
- 抽象类和抽象方法不能被final修饰。
abstract public class Shape {
abstract public void draw();
}
class Circle extends Shape{
@Override
public void draw() {
System.out.println("○");
}
}
class Triangle extends Shape{
@Override
public void draw() {
System.out.println("△");
}
}
class Rhombus extends Shape{
@Override
public void draw() {
System.out.println("◇");
}
}
class Flower extends Shape{
@Override
public void draw() {
System.out.println("❀");
}
}
class Star extends Shape{
@Override
public void draw() {
System.out.println("☆");
}
}
class Square extends Shape{
@Override
public void draw() {
System.out.println("□");
}
}
复制代码
public class Test2 {
public static void drawMap(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
Flower flower = new Flower();
Square square = new Square();
Star star = new Star();
Rhombus rhombus = new Rhombus();
Circle circle = new Circle();
Triangle triangle = new Triangle();
drawMap(flower);
drawMap(square);
drawMap(star);
drawMap(rhombus);
drawMap(circle);
drawMap(triangle);
}
}
复制代码
4.接口
4.1初见接口
接口是抽象类的进一步抽象,想要创建一个接口,需要使用关键字替代interface
替代关键字class
,就像类一样,可以在interface
前面使用public
修饰(需要与文件名相同),如果不添加关键字public
,就是默认的包访问权限,接口和类一样不能使用protected
private
修饰。 接口名一般以大写I开头,接口中的普通方法,不能有具体实现,如果非要实现需要使用default
修饰。
public interface Shape {
public void draw();
default public void print() {
System.out.println("我非要实现接口中的方法!");//default后面的public可以不加
}
}
复制代码
4.2接口的特点
- 接口是抽象类的进一步抽象,使用关键字interface定义接口,接口只能被public修饰或者不被访问权限关键字修饰。
- 接口不能使用new来实例化对象,因为它是极度抽象的。
- 接口中的普通方法不能被具体实现,非要实现请使用default修饰。
- 接口中可以有静态方法。
- 接口里面所有的抽象方法默认都是public abstract修饰的,接口中所有方法都是public修饰的。
- 类可以通过关键字implements来实现接口。
- 一个类最多只能继承一个类或抽象类,同时可以实现多个接口,类继承写在前面,接口实现写在后面,使用逗号隔开。
- 当一个普通类实现了一个接口,必须重写这个接口及其扩展接口的所有方的抽象法。
- 接口内的成员变量都是public\ static\ final修饰的。
- 接口与接口之间存在扩展的关系,使用关键字extends表示扩展关系。
- 当一个类实现接口的时候,重写的方法访问权限必须是public。
使用接口实现图形输出:
public interface Shape {
public void draw();
default public void print() {
System.out.println("我非要实现接口中的方法!");//default后面的public可以不加
}
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("○");
}
}
class Triangle implements Shape {
@Override
public void draw() {
System.out.println("△");
}
}
class Rhombus implements Shape {
@Override
public void draw() {
System.out.println("◇");
}
}
class Flower implements Shape {
@Override
public void draw() {
System.out.println("❀");
}
}
class Star implements Shape {
@Override
public void draw() {
System.out.println("☆");
}
}
class Square implements 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) {
Flower flower = new Flower();
Square square = new Square();
Star star = new Star();
Rhombus rhombus = new Rhombus();
Circle circle = new Circle();
Triangle triangle = new Triangle();
drawMap(flower);
drawMap(square);
drawMap(star);
drawMap(rhombus);
drawMap(circle);
drawMap(triangle);
}
}
复制代码
使用接口描述动物行为:
public class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(this.name + "正在吃饭");
}
}
interface IRun{
void running();
}
interface ISwim{
void swimming();
}
interface IFly{
void flying();
}
interface ISkip{
void skipping();
}
class Frog extends Animal implements ISkip, ISwim{
public Frog(String name) {
super(name);
}
@Override
public void swimming() {
System.out.println(name + "擅长蛙泳!");
}
@Override
public void skipping() {
System.out.println(name + "要跳起来!");
}
}
class Bird extends Animal implements IFly{
public Bird(String name) {
super(name);
}
@Override
public void flying() {
System.out.println(name + "正在自由飞翔!");
}
}
class Cat extends Animal implements IRun,ISkip{
public Cat(String name) {
super(name);
}
@Override
public void running() {
System.out.println(name + "的狂奔!");
}
@Override
public void skipping() {
System.out.println(name + "也会跳!");
}
}
class Duck extends Animal implements ISwim, IRun{
public Duck(String name) {
super(name);
}
@Override
public void running() {
System.out.println(name + "也会努力奔跑的!");
}
@Override
public void swimming() {
System.out.println(name + "也会游泳!");
}
}
复制代码
public class TestDemo {
public static void running(IRun iRun) {
iRun.running();
}
public static void swimming(ISwim iSwim) {
iSwim.swimming();
}
public static void skipping(ISkip iSkip) {
iSkip.skipping();
}
public static void flying(IFly iFly) {
iFly.flying();
}
public static void main(String[] args) {
Bird bird = new Bird("小鸟");
Cat cat = new Cat("小猫咪");
Frog frog = new Frog("小青蛙");
Duck duck = new Duck("小鸭子");
flying(bird);
running(cat);
running(duck);
skipping(frog);
skipping(cat);
swimming(frog);
swimming(duck);
}
}
复制代码
评论(0)