Java SE 学习笔记(五)—— 面向对象编程(3)

1 多态

1.1 多态的概述


什么是多态?

  • 同一个对象,在不同时刻表现出来的不同形态
  • 同类型的对象,执行同一个行为,会表现出不同的行为特征

举例:

  • 我们可以说猫是猫:猫 cat = new 猫();
  • 我们也可以说猫是动物:动物 animal = new 猫();
  • 这里说明了猫在不同时刻表现出来了不同的形态,这就是多态

多态的前提

  • 要有继承或实现关系
  • 要有方法的重写
  • 要有父类引用指向子类对象

🙋举个栗子:

public class Test1Polymorphic {
    public static void main(String[] args) {
        // 当前事物, 是一只猫
        Cat c = new Cat();
        // 当前事物, 是一只动物
        Animal a = new Cat();
        a.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("狗吃骨头");
    }
}

1.2 多态中成员访问特点


  • 构造方法:同继承一样,子类会通过super访问父类的构造方法
  • 成员变量:编译看左边(父类),执行看左边(父类)
    • 如果父类当中没有写这个成员变量,编译会报错
  • 成员方法:编译看左边(父类),执行看右边(子类)
    • 如果父类当中没有写这个成员方法,编译会报错,有的话,可以保证编译通过,然后运行子类的方法逻辑

为什么成员方法和成员变量的访问不一样?

  • 因为成员方法是有重写(子类重写父类的方法),而成员变量没有
public class Test2Polymorpic {
    public static void main(String[] args) {
        Fu f = new Zi();
        System.out.println(f.num); // 10
        f.method(); // Zi.. method
    }
}

class Fu {
    int num = 10;

    public void method(){
        System.out.println("Fu.. method");
    }
}

class Zi extends Fu {
    int num = 20;
	@Override
    public void method(){
        System.out.println("Zi.. method");
    }
}

1.3 多态的利与弊


好处

  • ​在多态形式下,右边对象可以实现解耦合,便于程序的维护和扩展
  • 定义方法时候,使用父类类型作为参数,该方法就可以接收这个父类的任意子类对象,在使用的时候,使用具体的子类型参与操作

弊端 ​

  • 不能使用子类的特有功能

🙋举个栗子:

public class Test3Polymorpic {
    public static void main(String[] args) {
        useAnimal(new Dog());  // 狗吃肉
        useAnimal(new Cat());  // 猫吃鱼
    }
	
	// 这个方法就可以接收Animal的一切子类对象
    public static void useAnimal(Animal a){  // Animal a = new Dog();
                                             // Animal a = new Cat();
        a.eat();
        //a.watchHome(); 编译会报错

		
//        Dog dog = (Dog) a;
//        dog.watchHome();  // ClassCastException  类型转换异常
// 		  调用useAnimal(new Cat());时,执行Animal a = new Cat();将原本是猫的a转成狗了
//        若执行上述注释的代码,运行结果为:狗吃肉\n看家\n猫吃鱼\n报ClassCastException 类型异常
    }
}

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

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

    public void watchHome(){ //这是子类Dog类特有的方法
        System.out.println("看家");
    }
}

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

1.4 多态的转型


多态中的转型有两种:

  • 向上转型,也叫自动类型转换
    • 从子到父
    • 父类引用指向子类对象
  • 向下转型,也叫强制类型转换
    • 从父到子
    • 父类引用转为子类对象
    • 格式:子类 对象变量=(子类)父类类型变量;
    • 作用:可以解决多态下的劣势,实现调用子类独有的功能

🙋举个栗子:

public class Test4Polymorpic {
    public static void main(String[] args) {
        // 1. 向上转型 : 父类引用指向子类对象
        Fu f = new Zi();
        f.show();
        // 多态的弊端: 不能调用子类特有的成员
        // f.method(); 编译看左边(父类)没有这个方法会报错

		// 多态的方式,一般也只会调用共有的方法,如果一定要调用子类中特有的成员,有以下两种方法:
        // A: 直接创建子类对象
        // B: 向下转型

        // 2. 向下转型 : 从父类类型, 转换回子类类型
        Zi z = (Zi) f; // 相当于强制类型转换
        z.method();
    }
}

class Fu {
    public void show(){
        System.out.println("Fu..show...");
    }
}

class Zi extends Fu {
    @Override
    public void show() {
        System.out.println("Zi..show...");
    }

    public void method(){
        System.out.println("我是子类特有的方法, method");
    }
}

❗️注意:

  • 如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现ClassCastException
  • 解决办法:关键字instanceof(建议强制类型转换之前,先判断变量指向对象的真实类型)
    • 使用格式:变量名 instanceof 类型
      • 判断关键字左边的变量是否是右边的类型,返回 boolean 类型结果
  • 另外,有继承或实现关系的2个类型就可以进行强制类型转换,编译无问题,运行时,如果发现强制类型转换后的类型不是对象的真实类型则报错

接着1.3中的例子,可以在 useAnimal 方法中加下以下代码进行判断

if(a instanceof Dog){
	Dog dog = (Dog) a;
	dog.watchHome();
}

2 内部类

2.1 内部类概述


内部类:就是在一个类中定义一个类。举例:在A类的内部定义一个B类,B类就被称为内部类

格式:

修饰符 class 外部类名{
	修饰符 class 内部类名{
	}
}

内部类的使用场景与作用:

  • 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计
  • 内部类通常可以方便的访问外部类的成员,包括私有成员
  • 内部类提供了更好的封装性,内部类本身就可以用privateprotected等修饰,封装性可以做更多控制

按照内部类在类中定义的位置不同,可以分为如下两种形式

  • 成员内部类
    • 在类的成员位置
  • 局部内部类
    • 在类的局部位置

2.2 成员内部类

2.2.1 一般成员内部类


也叫实例内部类,没有static修饰,属于外部类的对象所有。

在JDK16之前,成员内部类中不能定义静态成员,JDK16开始可以定义静态成员了。

内部类的访问特点:

  • 内部类可以直接访问外部类的成员,包括私有
  • 外部类要访问内部类的成员,必须创建对象

创建内部类对象的格式:

外部类名.内部类名 对象名 = new 外部类对象().new 内部类对象();

示例代码

public class Test1Inner {
    public static void main(String[] args) {
		// 外部类要访问内部类的成员,必须创建对象
        Outer.Inner i = new Outer().new Inner();
        System.out.println(i.num); 
        i.show(); 
        		  
    }
}
class Outer {
    private int a = 10;
    private void aa(){
        System.out.println("我是我外部类中的实例方法");
    }
    class Inner {
        int num = 10;

        public void show(){
            System.out.println("Inner..show");
            // 内部类, 访问外部类成员, 可以直接访问, 包括私有
            System.out.println(a);
            aa(); 
        }
    }
}

成员内部类,也属于成员,既然属于成员就可以被一些修饰符所修饰

  • private 私有成员内部类
  • static 静态成员内部类

❗️注意:

  • 成员内部类中可以直接访问外部类的静态成员(有static修饰)
    • 外部类的静态成员只有一份,可以被共享
  • 成员内部类中可以直接访问外部类的实例成员(没有static修饰)
    • 因为必须先有外部类对象,才能有成员内部类对象,所以可以直接访问外部类的实例成员

✨ 面试题拓展:观察如下代码,写出合适的代码对应其注释要求输出的结果

在这里插入图片描述

public class People {
    private int heartBeat=150;
    public class Heart{
        private int heartBeat=110;
        public void show(){
            int heartBeat=78;
            System.out.println(heartBeat); // 78
            System.out.println(this.heartBeat); // 110
            System.out.println(People.this.heartBeat); // 150
        }
    }
}

❗️注意:

  • 在成员内部类中访问所在外部类的对象,格式:外部类名.this

2.2.2 私有成员内部类


public class Test2Innerclass {
    public static void main(String[] args) {
        // Outer.Inner oi = new Outer().new Inner(); 内部类为私有,会报错

        Outer o = new Outer();
        o.method();
    }
}

class Outer {
    private class Inner { //内部类私有了
        public void show(){
            System.out.println("inner..show");
        }
    }

    public void method(){
        Inner i = new Inner();
        i.show();
    }
}

2.2.3 静态成员内部类


静态内部类:有static修饰,属于外部类本身,它的特点和使用与普通类是完全一样的,类有的成分它都有,只是位置在别人里面而已

要注意对于静态成员内部类,其创建对象的格式发生了变化:

 外部类名.内部类名 对象名 = new 外部类名.内部类名();

示例代码

public class Test3Innerclass {
    public static void main(String[] args) {
        Outer.Inner oi = new Outer.Inner();
        oi.show();

        Outer.Inner.method(); // 静态定义的内容可以直接通过类名调用
    }
}

class Outer {
	public static int a=100;
	public String name;
    static class Inner {
        public void show(){
            System.out.println("inner..show");
        }
		// 在静态成员内部类中再定义一个静态方法,
        public static void method(){
            System.out.println("inner..method");
			
			// 静态内部类中可以直接访问外部类的静态成员
			System.out.println(a);
			// 外部类的实例成员必须用外部类对象访问 
			Outer o=new Outer();
			System.out.println(o.name);
        }

    }
}

❗️注意:

  • 静态内部类中可以直接访问外部类的静态成员(有static修饰)
    • 外部类的静态成员只有一份,可以被共享
  • 静态内部类中不可以直接访问外部类的实例成员(没有static修饰)
    • 外部类的实例成员必须用外部类对象访问

2.3 局部内部类


局部内部类

  • 概念
    • 是定义在方法中的类
  • 使用方法
    • 所以外界是无法使用的,只能在方法内部创建对象并使用
  • 访问特点
    • 该类可以直接访问外部类的成员,也可以访问方法内的局部变量
public class Test4Innerclass {
    public static void main(String[] args) {
        Outer o = new Outer();
        o.method();
    }
}

class Outer {
    int a = 10;
    public void method(){
        int b = 20;
        class Inner {
            public void show(){
                System.out.println("show...");
                System.out.println(a);//10
                System.out.println(b);//20
            }
        }

        Inner i = new Inner();
        i.show();
    }
}

2.4 匿名内部类(重点)


概念:匿名内部类本质上是一个特殊的局部内部类(一般定义在方法,代码块中)【匿名内部类的使用更加频繁,更为重要】

作用:方便创建子类对象,最终目的是为了简化代码的编写

前提:存在一个接口或者类

特点:

  • 匿名内部类是一个没有名字的内部类
  • 匿名内部类写出来就会产生一个匿名内部类对象
  • 匿名内部类的对象类型相当于是当前 new 的那个类型的子类类型

格式:

new 类名或接口名(){
	重写方法;
} 

示例代码

public class Test5Innerclass {
    /*
        1. 创建实现类, 通过implements关键字去实现接口
        2. 重写方法
        3. 创建实现类对象
        4. 调用重写后的方法
     */
    public static void main(String[] args) {
        InterImpl ii = new InterImpl();
        ii.show(); // InterImpl  重写的show方法

        // 匿名内部类的理解: 将继承\实现, 方法重写, 创建对象, 放在了一步进行.
        // 解释: 实现了Inter接口的, 一个实现类【对象】
        new Inter() {
            @Override
            public void show() {
                System.out.println("我是匿名内部类中的show方法");
            }
        }.show();

        // 情况: 接口中存在多个方法,如何调用多个方法?
        // "new Inter2(){……}"是接口的一个实现类对象,可以在前面用父接口的引用承接一下,就可以以多态的形式
        Inter2 i = new Inter2() {
            @Override
            public void show1() {
                System.out.println("show1...");
            }
            @Override
            public void show2() {
                System.out.println("show2...");
            }
        };
        i.show1();
        i.show2();
    }
}

interface Inter {
    void show();
}

interface Inter2 {
    void show1();
    void show2();
}

class InterImpl implements Inter {
    @Override
    public void show() {
        System.out.println("InterImpl  重写的show方法");
    }
}

匿名内部类的使用场景

  • 当发现某个方法的形式参数是接口或抽象类时,我们就可以将匿名内部类作为实际参数传过去,来简化传统的代码
public class TestSwimming {
    public static void main(String[] args) {
        goSwimming(new Swimming() {
            @Override
            public void swim() {
                System.out.println("铁汁, 我们去游泳吧");
            }
        });
    }

    /**
     * 使用接口的方法
     */
    public static void goSwimming(Swimming swimming){
        swimming.swim();
    }
}

/*
    游泳接口
 */
interface Swimming {
    void swim();
}

3 lambda表达式

3.1 函数式编程思想概述


在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”。

面向对象思想

  • 强调“必须通过对象的形式来做事情”

函数式思想

  • 则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”

而Lambda表达式就是函数式思想的体现

3.2 lambda表达式概述


lambda表达式是 JDK8 开始的一种新语法格式,其主要作用就是 简化匿名内部类 的代码


格式: (形式参数) -> {代码块}

(匿名内部类被重写方法的形式参数)>{
	被重写方法的方法体;
}
  • 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
  • ->:由英文中画线和大于符号组成,固定写法,没有实际含义,仅代表指向动作
  • 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容

lambda 表达式的使用前提:

  • 有一个 接口
  • 接口中 有且仅有 一个 抽象方法

3.3 lambda表达式的使用

3.3.1 无参数无返回值


需求:

  • 编写一个接口ShowHandler
  • 在该接口中存在一个抽象方法show,该方法是无参数无返回值
  • 在测试类ShowHandlerDemo中存在一个方法useShowHandler
    • 方法的参数是ShowHandler类型的
    • 在方法内部调用了ShowHandlershow方法
public class TestLambda {
    public static void main(String[] args) {
        useShowHandler(new ShowHandler() {
            @Override
            public void show() {
                System.out.println("我是匿名内部类中的show方法");
            }
        });

        // Lambda实现
        useShowHandler( () -> System.out.println("我是Lambda中的show方法"));
    }

    public static void useShowHandler(ShowHandler showHandler){
        showHandler.show();
    }
}

interface ShowHandler {
    void show();
}

3.3.2 带参数无返回值


需求:

  • 首先存在一个接口StringHandler
  • 在该接口中存在一个抽象方法printMessage,该方法是有参数无返回值
  • 在测试类StringHandlerDemo中存在一个方法useStringHandler
    • 方法的的参数是StringHandler类型的
    • 在方法内部调用了StringHandlerprintMessage方法
public class StringHandlerDemo {
    public static void main(String[] args) {
        useStringHandler(new StringHandler() {
            @Override
            public void printMessage(String msg) {
                System.out.println("我是匿名内部类" + msg);
            }
        });

        // Lambda实现
        useStringHandler( (msg) -> {System.out.println("我是Lambda表达式" + msg);});
    }

    public static void useStringHandler(StringHandler stringHandler){
        stringHandler.printMessage("hello world");
    }
}

interface StringHandler {
    void printMessage(String msg);
}

3.3.3 无参数有返回值


需求:

  • 首先存在一个接口RandomNumHandler
  • 在该接口中存在一个抽象方法getNumber,该方法是无参数但是有返回值
  • 在测试类RandomNumHandlerDemo中存在一个方法useRandomNumHandler
    • 方法的的参数是RandomNumHandler类型的
    • 在方法内部调用了RandomNumHandlergetNumber方法
import java.util.Random;

public class RandomNumHandlerDemo {
    public static void main(String[] args) {
        useRandomNumHandler(new RandomNumHandler() {
            @Override
            public int getNumber() {
                Random r = new Random();
                int num = r.nextInt(10) + 1;
                return num;
            }
        });

        useRandomNumHandler( () -> {
                Random r = new Random();
                int num = r.nextInt(10) + 1;
                return num;
                // 注意: 如果lambda所操作的接口中的方法, 有返回值, 一定要通过return语句, 将结果返回
                // 否则会出现编译错误
        } );
    }

    public static void useRandomNumHandler(RandomNumHandler randomNumHandler){
        int result = randomNumHandler.getNumber();
        System.out.println(result);
    }
}

interface RandomNumHandler {
    int getNumber();
}

3.3.4 带参数带返回值


需求:

  • 首先存在一个接口Calculator
  • 在该接口中存在一个抽象方法calc,该方法是有参数也有返回值
  • 在测试类CalculatorDemo中存在一个方法useCalculator
    • 方法的的参数是Calculator类型的
    • 在方法内部调用了Calculatorcalc方法
public class CalculatorDemo {
    public static void main(String[] args) {
        useCalculator(new Calculator() {
            @Override
            public int calc(int a, int b) {
                return a + b;
            }
        });

        useCalculator( ( a,  b) ->
             a + b
         );
    }

    public static void useCalculator(Calculator calculator){
        int result = calculator.calc(10,20);
        System.out.println(result);
    }
}

interface Calculator {
    int calc(int a, int b);
}

lambda表达式的省略规则:

  • 参数类型可以省略,但是有多个参数的情况下,不能只省略一个
  • 如果参数有且仅有一个,那么小括号可以省略,没有参数时不能省略
  • 如果代码块的语句只有一条,可以省略大括号,同时要省略分号
  • 如果代码块的语句只有一条,而且是return语句,可以省略大括号,同时必须要省略return和分号

3.4 与匿名内部类的区别


  • 所需类型不同
    • 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
    • Lambda表达式:只能是接口
  • 使用限制不同
    • 如果接口中有且仅有一个抽象方法,可以使用 Lambda 表达式,也可以使用匿名内部类
    • 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用 Lambda 表达式
  • 实现原理不同
    • 匿名内部类:编译之后,产生一个单独的.class字节码文件保存在硬盘当中
    • Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成,不会保留到本地的硬盘当中

下一篇:Java SE 学习笔记(六)—— 常用API(1)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值