Nrich's blog Nrich's blog
首页
  • Java
  • Golang
  • 深度学习
  • Git
  • Linux
  • DataStructure
  • CloudNative
  • Redis
  • MySQL
  • 路由劫持
GitHub (opens new window)

Nrich

小聪明
首页
  • Java
  • Golang
  • 深度学习
  • Git
  • Linux
  • DataStructure
  • CloudNative
  • Redis
  • MySQL
  • 路由劫持
GitHub (opens new window)
  • Java

    • 黑马Java入门基础-学习笔记

      • Java 基础语法
      • 数组
      • 方法
      • 面向对象基础
      • 常用API(String、ArrayList)
      • 面向对象进阶1(static、单例、代码块、继承)
      • 面向对象进阶2(包、权限修饰符、抽象类、接口)
      • 面向对象进阶3(多态、内部类、常用API)
        • 1. 多态:面向对象三大特征之三
          • 1.1 多态的概述、形式
          • 1.2 多态的优势
          • 1.3 多态下引用数据类型的类型转换
          • 1.4 多态的案例
        • 2. 内部类
          • 2.1 内部类的概述
          • 2.2 内部类的使用场景、作用
          • 2.3 内部类的分类
          • 2.3.1 静态内部类
          • 2.3.2 成员内部类
          • 2.3.3 局部内部类
          • 2.3.4 匿名内部类
          • 2.3.5 匿名内部类常见使用形式
        • 3. 常用 API
          • 3.1 Object 类
          • 3.2 Objects 类
          • 3.3 StringBuilder 类
          • 3.4 Math 类
          • 3.5 System 类
          • 3.6 BigDecimal 类
      • 面向对象进阶4(常用日期API、正则、Arrays类、Lambda)
      • 面向对象进阶5(集合体系)
      • 面向对象进阶6(集合体系之Map)
      • 面向对象进阶-补充(可变参数)
    • 面向对象
    • 常用API
    • 并发、并行、异步、同步(暂存)
    • 《Spring Boot 进阶-郑天民》

  • GoLang

  • 开发
  • Java
  • 黑马Java入门基础-学习笔记
Nrich
2023-03-08
目录

面向对象进阶3(多态、内部类、常用API)

# 面向对象进阶3(多态、内部类、常用 API)

# 1. 多态:面向对象三大特征之三

# 1.1 多态的概述、形式

  • 啥是多态
    • 同一父类型的对象,执行同一个行为时,会表现出不同的行为特征。
  • 多态的常见形式
    • 父类类型 对象名称 = new 子类构造器; (父类类型指向一个子类的对象)
    • 接口 对象名称 = new 实现类构造器;(接口相当于一个干爹,实现类相当于儿子)
    • 具体的行为方法在子类中重写
  • 多态中成员访问特点-
    • 方法调用:编译看左边,运行看右边。
    • 变量调用:编译看左边,运行也看左边。(多态侧重行为多态,变量没有多态的概念)
      • 即对于变量调用,还是会使用父类中的变量,而方法是会使用子类中重写的方法的。
  • 多态的前提
    • 有继承/实现关系
    • 有父类引用指向子类对象
    • 有方法重写

# 1.2 多态的优势

  1. 在多态形式下,右边对象可以实现解耦合,便于扩展和维护。
Animal a = new Dog();
a.run(); // 后续业务行为随对象而变,后续代码无需修改
1
2

​ 上述代码中,若后续业务需要将 Dog 子类改为Tortoise 子类,则直接改“=”右侧的子类类型即可,后续代码中的行为随着对象的改变而直接变化了,无需修改很多后续的代码。

  1. 定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利

多态下会产生的一个问题

多态下不能使用子类的独有功能,因为编译时会使用左侧的父类中的行为。

solve:使用强制类型转换,将父转为子类型。

# 1.3 多态下引用数据类型的类型转换

在多态下会有两种类型转换:

  • 自动类型转换(从子到父)
  • 强制类型转换(从父到子)

关于强制类型转换:

  • 子类类型 对象变量 = (子类)父类类型的变量

    代码示例:

    // 自动类型转换
    Animal a = new Dog();
    a.run();
    // 强制类型转换:可以实现调用子类独有功能的
    Dog d = (Dog) a;
    d.lookDoor();
    
    1
    2
    3
    4
    5
    6
  • 作用:可以解决多态下的劣势,可以实现调用子类独有的功能

  • 注意: 如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会报错 ClassCastException(类型转换异常)

强转转换的一点建议

规定:有继承或者实现关系的2个类型就可以强制类型转换,故编译时可能没问题,但运行时发现强制转换后的类型不是对象真实类型而报错。

Java 建议强转转换前使用 instanceof 判断当前对象的真实类型,再进行强制转换。

关于 instanceof 关键字的使用:

变量名 instanceof 真实类型

判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者是其子类类型,是则返回true,反之false。

代码示例:

public static void go(Animal a){  // 参数类型为父类类型,保证各种动物都可以使用go方法
    System.out.println("预备~~~");
    a.run();
    // 独有功能
    if(a instanceof Tortoise){
        Tortoise t = (Tortoise) a;
        t.layEggs();
    }else if(a instanceof Dog){
        Dog d1 = (Dog) a;
        d1.lookDoor();
    }
    System.out.println("结束~~~~");
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 1.4 多态的案例

需求:设计一个电脑对象,可以安装2个USB设备。鼠标:被安装时可以完成接入、调用点击功能、拔出功能;键盘:被安装时可以完成接入、调用打字功能、拔出功能。

分析:

①定义一个USB的接口(申明USB设备的规范必须是:可以接入和拔出)。

②提供2个USB实现类代表鼠标和键盘,让其实现USB接口,并分别定义独有功能。

③创建电脑对象,创建2个USB实现类对象,分别安装到电脑中并触发功能的执行。

# 2. 内部类

# 2.1 内部类的概述

内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外部类可以理解成(宿主)。

如,心脏和人的关系:

public class People{
    // 内部类
    public class Heart{
    }
}
1
2
3
4
5

# 2.2 内部类的使用场景、作用

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

# 2.3 内部类的分类

# 2.3.1 静态内部类

  • 有static修饰,属于外部类本身
  • 特点、使用与普通类是一样的,类有的成分它都有,只是位置在别人里面而已
  • 可以直接访问外部类的静态成员,不能直接访问外部类的实例成员(可以创建一个外部类对象,再访问其实例成员)
  • 注意:开发中实际上用的还是比较少
  • 定义示例:
public class Outer{
        // 静态成员内部类
    public static class Inner{
    }
}
1
2
3
4
5
  • 创建对象的格式:Outer.Inner in = new Outer.Inner();

# 2.3.2 成员内部类

  • 无 static 修饰,属于外部类的对象
  • JDK16 之前,成员内部类中不能定义静态成员,JDK 16 开始也可以定义静态成员了
  • 特点:可以直接访问外部类的静态成员,实例方法中可以直接访问外部类的实例成员(因为必须先有外部类对象,才能有成员内部类对象,所以可以直接访问外部类对象的实例成员)
  • 定义示例:
public class Outer {
    // 成员内部类
    public class Inner {        
    }
}
1
2
3
4
5
  • 创建对象格式:Outer.Inner in = new Outer().new Inner(); (注意外部类对象后面要加() )
  • 相关面试题:
    • 在成员内部类中访问所在外部类对象,格式:外部类名.this.value,如:System.out.println(People.this.heartbeat);
    • 访问当前对象的成员变量,要用 this.value

# 2.3.3 局部内部类

  • 鸡肋语法,了解即可
  • 局部内部类放在方法、代码块、构造器等执行体中
  • 局部内部类编译后也会有对应的类文件,其类文件名为: 外部类$N内部类.class

# 2.3.4 匿名内部类

  • 本质上是一个没有名字的局部内部类,定义在方法中、代码块中、等

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

  • 特点

    • 匿名内部类写出来就会产生一个匿名内部类的对象
    • 匿名内部类的对象类型相当于是当前 new 的那个的类型的子类类型(可以视为是 Animal 的子类 Tiger 类)
  • 格式

new 类|抽象类名|或者接口名() {
    重写方法;
};
1
2
3
Animal a = new Animal() {
    public void run() {
    }
};
a. run();
1
2
3
4
5

⚠️ 直接 new abstract_class() 是不可以的,故后面加上 {} 进行方法的重写,实现一个匿名内部类。

简单示例:

line3 等号后 ~ line8 的部分相当于 Tiger 类了。

public class Test {
    public static void main(String[] args) {
        Animal a = new Animal(){
            @Override
            public void run() {
                System.out.println("老虎跑的块~~~");
            }
        };
        a.run();
    }
}

//class Tiger extends Animal{
//    @Override
//    public void run() {
//        System.out.println("老虎跑的块~~~");
//    }
//}

abstract class Animal{
    public abstract void run();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 2.3.5 匿名内部类常见使用形式

匿名内部类通常作为方法的实际参数进行传输。

示例代码:

public class Test2 {
    public static void main(String[] args) {
        
        go(new Swimming() {
            @Override
            public void swim() {
                System.out.println("运动员泳🏊的贼快~~~~~");
            }
        });

    /**
       学生 老师 运动员可以一起参加游泳比赛
     */
    public static void go(Swimming s){
        System.out.println("开始。。。");
        s.swim();
        System.out.println("结束。。。");
    }
}
    
interface Swimming{
    void swim();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

对象回调:

image-20230308222150352

提示

开发中不是我们主动去定义匿名内部类的,而是别人需要我们写或者我们可以写的时候才会使用。

匿名内部类的代码可以实现代码进一步的简化。

# 3. 常用 API

# 3.1 Object 类

  • Object 类的方法是一切子类对象都可以直接使用的,所以我们要学习 Object 类的方法。

  • 一个类要么默认继承了 Object 类,要么间接继承了 Object 类,Object 类是 Java 中的祖宗类

  • Object类的常用方法

方法名 说明
public String toString() 默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址
public boolean equals(Object o) 默认是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false
  • 一般直接在 IDEA 中自动重写对应类的这两个方法即可。

toString 方法:

因为 toString 默认输出对象的地址在开发中毫无意义,更多是希望看到对象的内容数据)

toString 方法存在的意义就是为了被子类重写,以便返回对象的内容信息,而不是地址信息

equals 方法:

直接比较两个对象的地址是否相同完全可以用 “==” 替代 equals

父类 equals 方法存在的意义就是为了被子类重写,以便子类自己来定制比较规则

# 3.2 Objects 类

Objects 是一个工具类,提供了一些方法去完成一些功能

Objects 类与 Object 类也是继承关系。

Objects 类是从 JDK1.7 之后才有的。

官方在进行字符串比较时(自动重写的 equals 方法),没有用字符串对象的的 equals 方法,而是选择了 Objects 的 equals 方法来比较。

原因:使用Objects 的 equals 方法在进行对象的内容比较时会更安全。

使用 Objects 的 equals 方法在进行对象的内容比较时更安全的原因

Objects 的 equals 方法在比较时,底层会先进行非空判断,从而可以避免空指针异常。再进行 equals 比较

源码分析:

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}
1
2
3

若直接使用 equals 方法比较时,可能会出现空指针异常,例如:

public class Test {
    public static void main(String[] args) {
        String s1 = null;
        String s2 = new String("itheima");

        // System.out.println(s1.equals(s2));   // 留下了隐患,可能出现空指针异常。
    }
}
1
2
3
4
5
6
7
8

String 的 equals 方法 VS 类的 equals 方法

字符串可以直接用 equals 比较内容,因为 String 类已经重写过该方法了;但自定义的类要重写 equals 方法。

⚠️ String 不能用 “==” 比较内容是否一样,因为 “==” 会视为比较的二者为不同的对象

# 3.3 StringBuilder 类

  • StringBuilder 是一个可变的字符串类,我们可以把它看成是一个对象容器。(String 是不可变字符串类)

  • 作用:提高字符串的操作效率,如拼接、修改等。

  • StringBuilder 常用方法

方法名称 说明
public StringBuilder append(任意类型) 添加数据并返回StringBuilder对象本身
public StringBuilder reverse() 将对象的内容反转,会直接将对象本身修改
public int length() 返回对象内容长度
public String toString() 通过toString()就可以实现把StringBuilder转换为String
  • StringBuilder 支持链式编程,e.g. sb1.append("a").append("b").append("c").append("我爱你中国");

⚠️ 注意:StringBuilder 只是拼接字符串的手段,效率好,使用时最终还是要转成 String。 用 toString 恢复 String。故 定义字符串用 String,拼接、修改等操作字符串使用 StringBuilder。

示例代码:

StringBuilder sb2 = new StringBuilder();
sb2.append("123").append("456");
// 恢复成String类型
String rs = sb2.toString();
check(rs); // 使用字符串

public static void check(String data){
    System.out.println(data);
}
1
2
3
4
5
6
7
8
9
内存原理

String 类拼接字符串时,一个加号会导致堆内存中创建两个对象,一个是拼接时的 StringBuilder 对象,一个是拼好后转换过去的 String 对象。

image-20230309104010550

StringBuilder 类拼接字符串时,只会在堆内存中创建一个 StringBuilder 对象,所有拼接操作都在这个对象中完成。效率变高。

image-20230309104033219

# 3.4 Math 类

包含执行基本数字运算的方法,Math类没有提供公开的构造器

**如何使用类中的成员呢?**看类的成员是否都是静态的,如果是,通过类名就可以直接调用

Math 类的常用方法

方法名 说明
public static int abs(int a) 获取参数绝对值
public static double ceil(double a) 向上取整
public static double floor(double a) 向下取整
public static int round(float a) 四舍五入
public static int max(int a,int b) 获取两个int值中的较大值
public static double pow(double a,double b) 返回a的b次幂的值
public static double random() 返回值为double的随机值,范围[0.0,1.0)

# 3.5 System 类

System 也是一个工具类,代表了当前系统,提供了一些与系统相关的方法

System 类的常用方法

方法名 说明
public static void exit(int status) 终止当前运行的 Java 虚拟机,非零表示异常终止
public static long currentTimeMillis() 返回当前系统的时间毫秒值形式
public static void arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数) 数组拷贝 (了解)

注意:System.exit() 相当于删库跑路,可不能瞎用!!!

关于时间毫秒值:

  • 计算机认为时间是有起点的,起始时间: 1970年1月1日 00:00:00

  • 时间毫秒值:指的是从1970年1月1日 00:00:00走到此刻的总的毫秒数,应该是很大的。 1s = 1000ms。

  • System.currentTimeMillis() 可以用来进行时间的计算:性能分析。

# 3.6 BigDecimal 类

(大数据类型)用于解决浮点型运算精度失真的问题

浮点型运算的时候直接+ * / 可能会出现数据失真(精度问题)。

BigDecimal 的使用:

创建对象 BigDecimal 封装浮点型数据(最好的方式是调用方法)

⚠️ 阿里开发规范:禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为 BigDerimal 对象。

原因:BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。如:BigDecimalg = new BigDecimal(0.1F);实际的存储值为:0.10000000149

正例:优先推荐入参为String 的构造方法,或使用 BigDecimal 的 valueof 方法,此方法内部其实执行了Double 的 toString,而 Double 的 toString 按 double 的实际能表达的精度对尾数进行了截断。

BigDecimal recommend1 = new BigDecimal("0.1");

BigDecimal recommend2 = BigDecimal.valueOf(0.1);

⚠️ 注意:BigDecimal 只是手段,效率好,使用时最终还是要转成 对应的 double 数据类型。 使用 doubleValue() 转为 double。

示例代码:

// 包装浮点型数据成为大数据对象 BigDeciaml
BigDecimal a1 = BigDecimal.valueOf(a);
BigDecimal b1 = BigDecimal.valueOf(b);
BigDecimal c1 = a1.add(b1);
// BigDecimal c1 = a1.subtract(b1);
// BigDecimal c1 = a1.multiply(b1);
// BigDecimal c1 = a1.divide(b1);
System.out.println(c1);

// 目的:double
double rs = c1.doubleValue();
System.out.println(rs);
1
2
3
4
5
6
7
8
9
10
11
12

⚠️ BigDecimal 是一定要精度运算的

如,10/3 就无法精确运算,故 BigDecimal 会抛出异常。

solve: public BigDecimal divide (另一个BigDecimal对象,精确几位,舍入模式)

编辑 (opens new window)
上次更新: 2023/03/15, 14:34:12
面向对象进阶2(包、权限修饰符、抽象类、接口)
面向对象进阶4(常用日期API、正则、Arrays类、Lambda)

← 面向对象进阶2(包、权限修饰符、抽象类、接口) 面向对象进阶4(常用日期API、正则、Arrays类、Lambda)→

最近更新
01
YAML、Pod、Job、CronJob、ConfigMap、Secret
06-06
02
Kubernetes 的安装与基本架构
06-04
03
初识容器
05-30
更多文章>
Theme by Vdoing | Copyright © 2022-2023 Nrich | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式