Smali 语法解析 —— 类

简介: Smali 语法解析 —— 类

上一篇学习了 Smali 的数学运算,条件判断和循环,接下来学习类的基本使用,包括接口,抽象类,内部类等等。


直接上代码吧,抽象类 Car.java :

public abstract class Car {
    protected String brand;
    abstract void run();
}
复制代码


接口 IFly.java

public interface IFly {
    void fly();
}
复制代码


BMW.java

public class BMW extends Car implements IFly{
    private String brand = "BMW";
    @Override
    void run() {
        System.out.println(brand + " run!");
    }
    @Override
    public void fly() {
        System.out.println("I can fly!");
    }
    public static void main(String[] args){
        BMW bmw=new BMW();
        bmw.run();
        bmw.fly();
    }
}
复制代码


javac 编译之后使用 dx 生成 dex 文件,和以往不同的是,这次是三个 class 文件生成一个 dex,具体命令如下:

dx --dex --output=BMWCar.dex Car.class IFly.class BMW.class
复制代码


最后再使用 baksmali 生成 Smali 文件:

baksmali d BMWCar.dex
复制代码


可以看到在当前目录 out 文件夹下生成了三个文件 Car.smali  IFly.smaiiBMW.smali。下面逐一进行分析。


抽象类


Car.smali :

.class public abstract LCar; // 表明为抽象类
.super Ljava/lang/Object;
.source "Car.java"
# instance fields
.field protected brand:Ljava/lang/String; // proteced String brand
# direct methods
.method public constructor <init>()V
    .registers 1
    .prologue
    .line 1
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    return-void
.end method
# virtual methods
.method abstract run()V // abstract void run()
.end method
复制代码


抽象类的表示没有什么特殊的地方,第一行中会使用 abstract 来声明。


接口


IFly.smali

.class public interface abstract LIFly; // 表明是接口
.super Ljava/lang/Object;
.source "IFly.java"
# virtual methods
.method public abstract fly()V // abstract void fly()
.end method
复制代码


接口使用 interface 声明。从上面的 smali 代码也可以看到接口中的方法默认是 abstract 修饰的。


实现类


BMW.smali :

.class public LBMW;
.super LCar;    // 父类是 Car
.source "BMW.java"
# interfaces
.implements LIFly; // 实现了接口 IFly
# instance fields
.field private brand:Ljava/lang/String; // priva String brand
# direct methods
.method public constructor <init>()V
    .registers 2
    .prologue
    .line 1
    invoke-direct {p0}, LCar;-><init>()V
    .line 3
    const-string v0, "BMW"
    iput-object v0, p0, LBMW;->brand:Ljava/lang/String; // String brand = "BMW";
    return-void
.end method
.method public static main([Ljava/lang/String;)V // main 方法
    .registers 2
    .prologue
    .line 16
    new-instance v0, LBMW; // 新建 BMW 对象,并将其引用存入 v0
    invoke-direct {v0}, LBMW;-><init>()V // 执行对象的构造函数
    .line 17
    invoke-virtual {v0}, LBMW;->run()V // 执行 run() 方法
    .line 18
    invoke-virtual {v0}, LBMW;->fly()V // 执行 fly() 方法
    .line 19
    return-void
.end method
# virtual methods
.method public fly()V  // fly() 方法
    .registers 3
    .prologue
    .line 12
    // 下面三行字节码执行了 System.out.println("I can fly!");
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
    const-string v1, "I can fly!"
    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    .line 13
    return-void
.end method
.method run()V  // run() 方法
    .registers 4
    .prologue
    .line 7
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; // 获取 Sysem.out 对象
    new-instance v1, Ljava/lang/StringBuilder; // 新建 StringBuilder 对象,并将其引用存到 v1
    invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V // 初始化
    iget-object v2, p0, LBMW;->brand:Ljava/lang/String; // 获取当前类的 brand 对象
    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // append()
    move-result-object v1 // 将上一步中执行 append() 返回的对象赋给 v1,这里指的是 StringBuilder 对象
    const-string v2, " run!"
    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // append()
    move-result-object v1 // 这里的 v1 存储的仍然是 StringBuilder 对象的引用
    invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; // toString()
    move-result-object v1 // 这里 v1 存储的是 StringB.toString() 执行的结果
    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // 打印语句
    .line 8
    return-void
.end method
复制代码


BMW 类中出现了几个还没遇到过的指令,逐一分析一下:

  1. invoke-direct {p0}, LCar;-><init>()V

p0 存储的是当前类的引用,这句话表示执行当前类中 Car 对象的 init() 方法。


通用表示:

invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB

表示调用指定的方法,之后通常会跟一句 move-result* 来获取方法的返回值。例如:

invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v1
复制代码


第一句中执行了 StringBuilder.toString() 方法,返回值是一个 String 对象,紧接着后面的 move-result-object 将返回的 String 对象引用存在了 v1 中。


invoke-virtual 指调用正常的虚方法(不是 private,static,final,也不是构造函数)。除此之外,还有一些方法调用指令,在下面的表格中列举出来:

指令 说明
invoke-virtual 调用正常的虚方法
invoke-super 调用最近超类的虚方法
invoke-direct 调用 private 方法或构造函数
invoke-static 调用 static 方法
invoke-interface 调用 interface 方法
  1. iput-object v0, p0, LBMW;->brand:Ljava/lang/String;

v0 中存储的是 BMW 字符串,赋给 p0 的 brand 字段。


通用用法:

iinstanceop vA, vB, field@CCCC
复制代码


vA 可以是源寄存器,也可以是目的寄存器。当使用 iput 命令时, vA 就是源寄存器,使用 iget 命令时, vA 就是目的寄存器。

此命令还有一个同样用法的变种 sputsget,从名字就可以看出来是对静态字段的操作,i 是对实例字段的操作。


  1. new-instance v1, Ljava/lang/StringBuilder;

创建一个 StringBuilder 对象,并将其引用存在 v1 寄存器。

通用用法:

new-instance vAA, type@BBBB
复制代码


注意这里的 type 不能是数组类型。数组使用的是 new-array 指令:

new-array vA, vB, type@CCCC
复制代码


run() 方法的 smali 代码中我们也学习到了一个小知识点。我们都知道在 Java 中,使用 = 进行字符串拼接是很低效的,run() 方法中执行的是 System.out.println(brand + " run!");,然而虚拟机并没有傻傻的使用 = 去拼接,而是自动使用 StringBuilder 去拼接,提高运行效率。


内部类


内部类大家应该都不陌生,在 Android 开发中使用最多的要数匿名内部类了,除此之后,还有静态内部类,成员内部类等。下面的 Outer.java 中便使用了这三种内部类:

public class Outer {
    // 成员内部类
    private class Inner {
        private void in() {
            System.out.println("I am inner class.");
        }
    }
    // 静态内部类
    private static class StaticInner {
        private void staticIn() {
            System.out.println("I am static inner class.");
        }
    }
    public static void main(String[] args) {
        Outer outer = new Outer();
        Inner inner = outer.new Inner();
        inner.in();
        StaticInner staticInner = new StaticInner();
        staticInner.staticIn();
        // 匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();
    }
}
复制代码


还记得内部类的使用方法吗?静态内部类可以直接使用 new 关键字进行实例化,如 new StaticInner()。而成员内部类则不行,必须通过外部类来初始化,如 out.new Inner()javac 编译之后会生成如下四个 class 文件:

  • OUter.class
  • Outer$Inner.clss
  • Outer$StaticInner.class
  • Outer$1.class


同样,使用 baksmali 反汇编之后也会生成四个 smali 文件。

首先来看外部类 Outer.smali

.class public LOuter;
.super Ljava/lang/Object;
.source "Outer.java"
# annotations 系统自动添加的注解,表示内部类列表
.annotation system Ldalvik/annotation/MemberClasses;
    value = {
        LOuter$StaticInner;,
        LOuter$Inner;
    }
.end annotation
# direct methods
.method public constructor <init>()V  // 构造函数
    .registers 1
    .prologue
    .line 1
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    return-void
.end method
.method public static main([Ljava/lang/String;)V // main() 方法
    .registers 4
    .prologue
    const/4 v2, 0x0
    .line 17
    new-instance v0, LOuter; // 创建外部类 Outer 对象,存入 v0
    invoke-direct {v0}, LOuter;-><init>()V // 执行 Outer 对象的构造函数
    .line 19
    new-instance v1, LOuter$Inner; // 创建内部类 Outer$Inner 对象,存入 v1
    // 获取外部类对象的引用
    invoke-virtual {v0}, Ljava/lang/Object;->getClass()Ljava/lang/Class;
    // 执行内部类 Out$Inner 的构造函数,注意这里执行的不是无参构造,后面具体说明
    invoke-direct {v1, v0, v2}, LOuter$Inner;-><init>(LOuter;LOuter$1;)V
    .line 20
    # invokes: LOuter$Inner;->in()V 
    // 执行 Outer$Inner 的 access$100() 方法,这个方法是自动生成的,
    // 实际执行的就是 in() 方法
    invoke-static {v1}, LOuter$Inner;->access$100(LOuter$Inner;)V
    .line 22
    new-instance v0, LOuter$StaticInner; // 创建静态内部类 Out$StaticInner 对象
    // 执行静态内部类的构造函数,这里也是有参构造
    invoke-direct {v0, v2}, LOuter$StaticInner;-><init>(LOuter$1;)V
    .line 23
    # invokes: LOuter$StaticInner;->staticIn()V
    // 执行 Outer$StaticInner 的 access$300() 方法,这个方法是自动生成的,
    // 实际执行的就是 staticIn() 方法
    invoke-static {v0}, LOuter$StaticInner;->access$300(LOuter$StaticInner;)V
    .line 25
    new-instance v0, Ljava/lang/Thread; // 创建 Thread 对象
    new-instance v1, LOuter$1; // 创建 Out$1 对象
    invoke-direct {v1}, LOuter$1;-><init>()V // 执行 Out$1 对象的构造函数
    invoke-direct {v0, v1}, Ljava/lang/Thread;-><init>(Ljava/lang/Runnable;)V // 执行 Thread 对象的构造函数
    .line 30
    invoke-virtual {v0}, Ljava/lang/Thread;->start()V // 执行 Thread.start() 方法
    .line 31
    return-void
.end method
复制代码


看完外部类 Outer 的 smali 代码,我们发现每个内部类初始化时执行的都是有参构造,但是我们并没有显示的声明任何有参构造。我们从内部类的 smali 代码中找找答案。


成员内部类

Out$Inner.smali

.class LOuter$Inner;
.super Ljava/lang/Object;
.source "Outer.java"
# annotations 
# EnclosingClass 注解,系统自动生成,value 值代表其作用范围
.annotation system Ldalvik/annotation/EnclosingClass;
    value = LOuter;
.end annotation
# InnerClass 注解,系统自动生成,表示内部类
.annotation system Ldalvik/annotation/InnerClass;
    accessFlags = 0x2 // private
    name = "Inner"
.end annotation
# instance fields
# synthetic 表示 this$0 是“合成” 的,并非源码中就有的
.field final synthetic this$0:LOuter;
# direct methods
# 有参构造
.method private constructor <init>(LOuter;)V
    .registers 2
    .prologue
    .line 3
    iput-object p1, p0, LOuter$Inner;->this$0:LOuter;
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    return-void
.end method
# 有参构造
.method synthetic constructor <init>(LOuter;LOuter$1;)V
    .registers 3
    .prologue
    .line 3
    invoke-direct {p0, p1}, LOuter$Inner;-><init>(LOuter;)V
    return-void
.end method
# 编译器生成的 access$100() 方法
.method static synthetic access$100(LOuter$Inner;)V
    .registers 1
    .prologue
    .line 3
    invoke-direct {p0}, LOuter$Inner;->in()V // 调用 in() 方法
    return-void
.end method
// in() 方法
.method private in()V
    .registers 3
    .prologue
    .line 5
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
    const-string v1, "I am inner class."
    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    .line 6
    return-void
.end method
复制代码


重点看一下成员内部类的有参构造函数:

.method private constructor <init>(LOuter;)V
    .registers 2
    .prologue
    .line 3
    iput-object p1, p0, LOuter$Inner;->this$0:LOuter;
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    return-void
.end method
复制代码


有参构造函数的参数是 LOuter;p0 存储的是当前类的引用,p1 存储的是构造函数的参数,即外部类的引用。这个构造函数一共执行了两步:

  1. 把外部类的引用赋值给 this$0
  2. 执行内部类的 init() 方法


由此可以,成员内部类是持有外部类的引用的,这也就解释了为什么成员内部类可以调用外部类的属性和方法。

再回头看一下 Outer.smali 中成员内部类 Outer$Inner 的初始化过程:

const/4 v2, 0x0
invoke-direct {v1, v0, v2}, LOuter$Inner;-><init>(LOuter;LOuter$1;)V
复制代码


发现调用的并不是上面那个构造函数,而是一个两个参数的构造函数:

.method synthetic constructor <init>(LOuter;LOuter$1;)V
    .registers 3
    .prologue
    .line 3
    invoke-direct {p0, p1}, LOuter$Inner;-><init>(LOuter;)V
    return-void
.end method
复制代码


这个构造函数中还是调用了 init(LOuter;) 函数,并且也没有使用 LOuter$1 参数。这个 LOuter$1 参数是什么呢?看到最后就知道,这是匿名内部类的引用。但是在实际调用中传入的是 0x0, 关于传递这个参数的意义,不知道有没有读者知道,可以讨论一下。


紧接着编译器为成员内部类自动生成了一个静态方法:

.method static synthetic access$100(LOuter$Inner;)V
    .registers 1
    .prologue
    .line 3
    invoke-direct {p0}, LOuter$Inner;->in()V // 调用 in() 方法
    return-void
.end method
复制代码


方法参数是 p0 存储的当前类的引用,然后通过 p0 直接调用 in() 方法。同样,在外部类中调用成员内部类的 in() 方法,是通过 invoke-static 调用这个静态方法来执行的,如下所示:

# invokes: LOuter$Inner;->in()V
invoke-static {v1}, LOuter$Inner;->access$100(LOuter$Inner;)V
复制代码


静态内部类

Outer$StaticInner.smali

.class LOuter$StaticInner;
.super Ljava/lang/Object;
.source "Outer.java"
# annotations
# EnclosingClass 注解,系统自动生成,value 值代表其作用范围
.annotation system Ldalvik/annotation/EnclosingClass;
    value = LOuter;
.end annotation
# InnerClass 注解,系统自动生成,表示内部类
.annotation system Ldalvik/annotation/InnerClass;
    accessFlags = 0xa # private static
    name = "StaticInner"
.end annotation
# direct methods
# 无参构造
.method private constructor <init>()V
    .registers 1
    .prologue
    .line 9
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    return-void
.end method
# 有参构造
.method synthetic constructor <init>(LOuter$1;)V
    .registers 2
    .prologue
    .line 9
    invoke-direct {p0}, LOuter$StaticInner;-><init>()V
    return-void
.end method
# 编译器生成的 static 方法
.method static synthetic access$300(LOuter$StaticInner;)V
    .registers 1
    .prologue
    .line 9
    invoke-direct {p0}, LOuter$StaticInner;->staticIn()V
    return-void
.end method
// staticIn() 方法
.method private staticIn()V
    .registers 3
    .prologue
    .line 11
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
    const-string v1, "I am static inner class."
    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    .line 12
    return-void
.end method
复制代码


结构与成员内部类基本相似,有两点区别。

第一, accessFlags 的不同。在系统生成的 InnerClass 注解中有 accessFlags 值。成员内部类 Outer$Inner 值为 0x2,表示 private,静态内部类 Outer$StaticInner 值为 0xa,表示 private static。关于 accessFlags 的表示方式,在我的另一篇文章 Class 文件格式详解 中有具体介绍。


第二,静态内部类不持有外部类的引用。Outer$StaticInner.smali 中并没有定义 this$0 字段,有参构造中的参数也不包含外部类引用。所以,这也验证了静态内部类只能调用外部类的静态属性和静态方法。


外部类对静态内部类方法的调用也是自动生成了一个静态方法,再通过这个静态方法来调用,如下所示:

# invokes: LOuter$StaticInner;->staticIn()V
invoke-static {v0}, LOuter$StaticInner;->access$300(LOuter$StaticInner;)V
复制代码


匿名内部类

Outer$1.smali

.class final LOuter$1; // 匿名内部类是 final 的
.super Ljava/lang/Object;
.source "Outer.java"
# interfaces
.implements Ljava/lang/Runnable; // 实现了 Runnable 接口
# annotations
# 作用域在 main() 方法中
.annotation system Ldalvik/annotation/EnclosingMethod;
    value = LOuter;->main([Ljava/lang/String;)V
.end annotation
.annotation system Ldalvik/annotation/InnerClass;
    accessFlags = 0x8
    name = null
.end annotation
# direct methods
# 无参构造
.method constructor <init>()V
    .registers 1
    .prologue
    .line 25
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    return-void
.end method
# virtual methods
# run() 方法
.method public run()V
    .registers 3
    .prologue
    .line 28
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
    invoke-static {}, Ljava/lang/Thread;->currentThread()Ljava/lang/Thread;
    move-result-object v1
    invoke-virtual {v1}, Ljava/lang/Thread;->getName()Ljava/lang/String;
    move-result-object v1
    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    .line 29
    return-void
.end method
复制代码


看起来并没有什么特别的地方,run() 方法中执行的 System.out.println(Thread.currentThread().getName()); 也很容易看懂。只有一个无参构造。对,只有一个无参构造,如果你的 Java 基础学的还可以的话,应该记得匿名内部类会持有外部类的引用,可以这里为什么只有一个无参构造呢?别忘了,这里是 main() 方法,是 static 方法。静态方法中只能引用类中的静态属性。如果换成一个普通方法,生成的 smali 代码中肯定会有 this$0 字段和有参构造。在 Outer.java 中添加如下代码:

public void test(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();
    }
复制代码


生成的 smali 代码中多了一个文件 Outer$2.smali

.class LOuter$2;
.super Ljava/lang/Object;
.source "Outer.java"
# interfaces
.implements Ljava/lang/Runnable;
# annotations
.annotation system Ldalvik/annotation/EnclosingMethod;
    value = LOuter;->test()V
.end annotation
.annotation system Ldalvik/annotation/InnerClass;
    accessFlags = 0x0
    name = null
.end annotation
# instance fields
.field final synthetic this$0:LOuter; // 外部类引用
# direct methods
# 有参构造
.method constructor <init>(LOuter;)V
    .registers 2
    .prologue
    .line 34
    iput-object p1, p0, LOuter$2;->this$0:LOuter;
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    return-void
.end method
# virtual methods
.method public run()V
    .registers 3
    .prologue
    .line 37
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
    invoke-static {}, Ljava/lang/Thread;->currentThread()Ljava/lang/Thread;
    move-result-object v1
    invoke-virtual {v1}, Ljava/lang/Thread;->getName()Ljava/lang/String;
    move-result-object v1
    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    .line 38
    return-void
.end method
复制代码


注意看两处添加注释的地方,这就验证了匿名内部类持有外部类的引用的说法。

关于 Smali 的学习就到这里了,在这个过程中,也验证了一些我们曾经熟知的知识点,加深了我们对 Java 的理解。当然在 Android 逆向过程中,我们碰到的要比这里说的复杂的多,这就需要我们积累足够的经验了。下一篇,正式进入 Android 领域了,介绍一些常用的 Android 逆向工具。



相关文章
|
Java 开发者
重学Java基础篇—Java类加载顺序深度解析
本文全面解析Java类的生命周期与加载顺序,涵盖从加载到卸载的七个阶段,并深入探讨初始化阶段的执行规则。通过单类、继承体系的实例分析,明确静态与实例初始化的顺序。同时,列举六种触发初始化的场景及特殊场景处理(如接口初始化)。提供类加载完整流程图与记忆口诀,助于理解复杂初始化逻辑。此外,针对空指针异常等问题提出排查方案,并给出最佳实践建议,帮助开发者优化程序设计、定位BUG及理解框架机制。最后扩展讲解类加载器层次与双亲委派机制,为深入研究奠定基础。
536 0
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
数据可视化 数据挖掘 BI
团队管理者必读:高效看板类协同软件的功能解析
在现代职场中,团队协作的效率直接影响项目成败。看板类协同软件通过可视化界面,帮助团队清晰规划任务、追踪进度,提高协作效率。本文介绍看板类软件的优势,并推荐五款优质工具:板栗看板、Trello、Monday.com、ClickUp 和 Asana,助力团队实现高效管理。
443 2
|
存储 监控 安全
重学Java基础篇—类的生命周期深度解析
本文全面解析了Java类的生命周期,涵盖加载、验证、准备、解析、初始化、使用及卸载七个关键阶段。通过分阶段执行机制详解(如加载阶段的触发条件与技术实现),结合方法调用机制、内存回收保护等使用阶段特性,以及卸载条件和特殊场景处理,帮助开发者深入理解JVM运作原理。同时,文章探讨了性能优化建议、典型异常处理及新一代JVM特性(如元空间与模块化系统)。总结中强调安全优先、延迟加载与动态扩展的设计思想,并提供开发建议与进阶方向,助力解决性能调优、内存泄漏排查及框架设计等问题。
636 5
|
设计模式 SQL Java
【再谈设计模式】解释器模式~语法的解析执行者
解释器模式定义了一种语言的语法表示,并定义一个解释器来解释该语言中的句子。它使用类来表示每个语法规则,并且通过递归调用这些类的方法来解释表达式。本质上,它将一个复杂的表达式分解为一系列简单的部分,然后按照特定的语法规则进行解析和执行。
397 8
|
安全 IDE Java
重学Java基础篇—Java Object类常用方法深度解析
Java中,Object类作为所有类的超类,提供了多个核心方法以支持对象的基本行为。其中,`toString()`用于对象的字符串表示,重写时应包含关键信息;`equals()`与`hashCode()`需成对重写,确保对象等价判断的一致性;`getClass()`用于运行时类型识别;`clone()`实现对象复制,需区分浅拷贝与深拷贝;`wait()/notify()`支持线程协作。此外,`finalize()`已过时,建议使用更安全的资源管理方式。合理运用这些方法,并遵循最佳实践,可提升代码质量与健壮性。
507 1
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
2429 12
|
传感器 监控 Java
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
598 5
|
机器学习/深度学习 人工智能 监控
鸿蒙赋能智慧物流:AI类目标签技术深度解析与实践
在数字化浪潮下,物流行业面临变革,传统模式的局限性凸显。AI技术为物流转型升级注入动力。本文聚焦HarmonyOS NEXT API 12及以上版本,探讨如何利用AI类目标签技术提升智慧物流效率、准确性和成本控制。通过高效数据处理、实时监控和动态调整,AI技术显著优于传统方式。鸿蒙系统的分布式软总线技术和隐私保护机制为智慧物流提供了坚实基础。从仓储管理到运输监控再到配送优化,AI类目标签技术助力物流全流程智能化,提高客户满意度并降低成本。开发者可借助深度学习框架和鸿蒙系统特性,开发创新应用,推动物流行业智能化升级。
498 1
|
安全 编译器 C语言
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。

推荐镜像

更多
  • DNS