安卓逆向入门笔记(二)——Java与smali基础

查看 109|回复 9
作者:sigewangdaiduie   
一、Java基础
Java作为一种面向对象语言。最为重要的两个概念那就是类和对象:
:类是一个模板,它描述一类对象的行为和状态。
对象:对象是类的一个实例,有状态和行为。
类和对象之间的关系就像人类和某个人之间的关系,人类拥有一些行为和状态,这些行为和状态作为了人类的特征;用面向对象的思维来说,每个人可以说是人类这个类实例化的对象,在拥有人类的行为和状态下还拥有各自的特点。
基础数据类型
数据类型分为四大类八种,四大类是:整形、浮点型、字符型、布尔型;整形可以细分为byte、short、int、long;浮点可以细分为float、double。
1、整数类型默认为int类型,占4字节,当使用字节大小是大于它的long类型时,除了需要定义变量为long类型,还需要在值后面加L或者l才能将该变量表示为long类型。如:long l = 123456789l;
2、浮点类型默认为double类型,占8字节,在值后面加上F/f就是float类型了。如:float f = 3.14f;
类型转换
package com.java.TypeDome;
public class TypeDome {
    public static void main(String[] args) {
        // 1、自动类型转换
        // 占内存小的类型的变量可以赋值给内存占的大的类型的变量
        byte a = 10;
        int b = a;  // 发生了自动的类型转换
        System.out.println(a);
        System.out.println(b);
        System.out.println("---------------------------------------------");
        // 2、表达式的自动类型转换
        // 表达式的最终结果类型由表达式中的最高类型决定
        int i = 10;
        int j = 10;
        double ij = 1.0;
        // int ji = i + j + ij;  java: 不兼容的类型: 从double转换到int可能会有损失
        double ji = i + j + ij;
        System.out.println(ji);
        // 在表达式中,byte、short、char是直接转换成int类型参与运算的
        byte one = 20;
        byte two = 25;
        // byte three = one + two;  java: 不兼容的类型: 从int转换到byte可能会有损失
        int three = one + two;
        System.out.println(three);
        System.out.println("---------------------------------------------");
        // 3、强制类型转换
        // 强制类型转换可能造成数据的丢失
        // 下面的数据都为有符号位
        int o = 1500;  // 00000101 11011100
        byte t = (byte) o;  // 11011100
        System.out.println(t);  // 输出结果:-36
        // 小数强制转换为整数是直接舍弃小数保留整数
        double dou = 81.5;
        int interesting = (int) dou;
        System.out.println(interesting);  // 输出结果:81
    }
}
修饰符
Java提供了两类修饰符。分别是访问修饰符和非访问修饰符。
访问修饰符
访问修饰符是一种用来限制类、接口、类成员(字段、方法、构造函数等)访问权限的关键字,主要包括以下四种:
[ol]
  • public:公共访问修饰符,表示该类或类成员可以被任何其他类访问。
  • private:私有访问修饰符,表示该类或类成员只能在本类中被访问,其他类无法访问。
  • protected:受保护的访问修饰符,表示该类或类成员只能在本类和其子类中被访问,其他类无法访问。
  • default(默认,即不写访问修饰符):默认访问修饰符,表示该类或类成员只能在同一包内被访问,其他包中的类无法访问。
    [/ol]
    非访问修饰符
    为了实现一些其他的功能,Java 提供了很多非访问修饰符,如:
    [ol]
  • static:静态修饰符,表示该类成员是静态的,可以直接通过类名调用而不需要创建实例。
  • final:最终修饰符,表示该类、方法或变量是不可修改的,一旦被赋值则无法再次修改。
  • abstract:抽象修饰符,表示该类或方法是抽象的,不能被实例化或调用,只能被子类继承或实现。
  • synchronized:同步修饰符,表示该方法或代码块是同步的,多个线程不能同时访问,保证线程安全。
  • transient:瞬态修饰符,表示该变量在序列化时会被忽略,即不会被保存到文件中。
  • volatile:易失修饰符,表示该变量是易失的,多个线程对其进行操作时不会进行缓存,保证可见性和原子性。
    [/ol]
    运算符
    package com.java.operator;
    public class Operators {
        public static void main(String[] args) {
            // 1、运算符有+、-、*、/、%,和c、和python中的运算符一模一样
            // 2、+除了做基本的数学运算符,在与字符串进行+运算时会被当成连接符,其结果还是字符串;加法口诀:能算则算,不能算则拼接成字符串
            int i = 10;
            String str = "Thank You";
            System.out.println((str + i));  // 输出显示:Thank You10
            System.out.println("---------------------------------------------");
            // 3、自增运算符++和自减运算符--的运算规则:++或者--在变量的前面表示先自增或者自减后再运算,在变量的后面表示先运算再自增或者自减
            int m = 10;
            int n = m++;
            System.out.println(m);  // 11
            System.out.println(n);  // 10
            System.out.println("---------------------------------------------");
            // 4、赋值运算符:+=、-=、*=、/=、%=;赋值运算符格式:变量 赋值运算符 (变量的类型) 值
            int o = 10;
            double t = 20.0;
            o += t;
            System.out.println(o);  // 显示结果:30
            System.out.println("---------------------------------------------");
            // 5、条件运算符:==、!=、>=、、= 10));
            System.out.println(d2);
            // 短路与:必须都为true,有一个false就为false,但前一个为false后一个就不执行
            System.out.println((d1 = 10));
            System.out.println(d2);
            // 逻辑或:得有一个为true,全部为false就为false。但前一个为true后一个依旧执行
            // 短路或:得有一个为true,全部为false就为false。但前一个为true后一个就不执行
            // 逻辑非:取反
            // 逻辑异或:相同是false,不同是true
            System.out.println("---------------------------------------------");
            // 7、三元运算符格式:条件表达式?条件表达式为true时返回的值: 条件表达式为false时返回的值;
            int zhen = d1 >= d2? d1: d2;
            System.out.println(zhen);
        }
    }
    流程控制语句
    if……else……语句
    package com.java.ProcessControl;
    import java.util.Scanner;
    public class IfElse {
        public static void main(String[] args) {
            // if(条件表达式1) {
            //  条件表达式1返回值为true时执行的代码……
            // } else if(条件表达式2) {
            //  条件表达式1不成立但条件表达式2成立时执行的代码
            // }……else {
            //  如果上面的所有条件表达式都不成立时执行的代码
            // }
            Scanner sca = new Scanner(System.in);
            System.out.println("请输入该同学的成绩:");
            int grades = sca.nextInt();
            if (grades  100) {
                System.out.println("成绩不存在");
            }else  if (grades >= 90) {
                System.out.println("该同学必成龙凤!!");
            }else if (grades >= 80) {
                System.out.println("该同学成绩优秀");
            }else if (grades >= 70) {
                System.out.println("该同学成绩一般");
            }else if (grades >= 60) {
                System.out.println("该同学成绩及格,还需努力!");
            }else {
                System.out.println("挂科");
            }
        }
    }
    switch语句
    switch case 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。
    package com.java.ProcessControl;
    import java.util.Scanner;
    public class SwitchCase {
        public static void main(String[] args) {
            Scanner s = new Scanner(System.in);
            System.out.println("请输入当前汽车挡位");
            int i = s.nextInt();  // 输入一个整形数据
            switch (i) {  // switch (表达式) {}中表达式不支持double、float、long类型
                case 1:  // case给出的值不允许重复,且只能是字符串常量或字面量。
                    System.out.println("很稳健!");
                    break;  // 不要忘记写break,不然会往下执行,直到退出switch
                case 2:
                    System.out.println("你准备加速了!");
                    break;
                case 3:
                    System.out.println("请注意别超速");
                    break;
                case 4:
                    System.out.println("请注意减速!");
                    break;
                case 5:
                    System.out.println("你开的太快了!");
                    break;
                case '6':
                    System.out.println("此车没有倒挡!");
                    break;
                default:
                    System.out.println("没有此挡位!");
            }
        }
    }
    switch case 执行时,一定会先进行匹配,匹配成功就开始执行case 语句之后的语句,再根据是否有 break,判断是否继续输出,或是跳出判断。
    循环语句
    package com.java.circulate;
    public class Circulate {
        public static void main(String[] args) {
            /*
            * for (初始化数据语句;循环条件;迭代语句) {
            *   循环代码;
            * }
            * */
            int sum=0;
            for (int i=1;i
    数组
    package com.java.ArrayDemo;
    public class JavaArray {
        public static void main(String[] args) {
            // 数据类型[] 数组名称 = new 数据类型[]{元素1、元素2……}  // new代表创建的意思
            int[] arr1 = new int[] {1,2,3,4,5,6};
            // 简化写法:
            int[] arr2 = {7,8,9,10,11,12,13,14,15};
            // 取值:数组名[索引];
            System.out.println(arr2[5]);
            int i = arr1[3];
            System.out.println(i);
            // 赋值:数组名[索引] = 值;
            arr2[3] = 100;
            System.out.println(arr2[3]);
            // 获取数组最大的长度:数组名.length;
            System.out.println(arr2.length);
            // 数组其他写法:数据类型 数组名称[]
            int arr3[] = {1,2,3,4,5};
            // 什么类型的数组只能存放什么类型的数据
            // 数组一旦定义出来之后,类型和长度也就固定了
            /*
            * 动态初始化数组:当前还不清楚要存哪些数据就使用动态初始化数组
            * byte、short、char、int、long动态初始化默认值为0;float、double动态初始化默认值为0.0;boolean动态初始化默认值为false
            * 类、API接口、数组、String动态初始化数组为null
            * */
            int[] arr4 = new int[3];
            System.out.println(arr4[0]); // 0
            System.out.println(arr4[1]); // 0
            arr4[2] = 100;
            System.out.println(arr4[2]); // 100
        }
    }
    方法
    package com.java.method;
    public class Methods {
        public static void main(String[] args) {
            /* 方法格式:
            *修饰符 返回值类型 方法名(形参列表){
            *   方法中要执行的代码
            *   return 要返回的值;  // 方法声明了具体的返回值类型,内部必须使用return返回对应的数据类型的数据
            * }
            * 注意:基本类型数据传入到方法中是传入的值,修改传入到方法的值是不会影响到全局变量;
            * 而引用类型数据传入到方法中的是地址,修改方法内的值是会改变全局变量的
            * */
            int s = sum(100,200);
            System.out.println(s);
        }
        public  static int sum(int a,int b) {
            return a+b;
        }
    }
    面向对象
    面向对象是Java的重中之重,之间简单介绍了对象和类的关系,现在正式来讲解一下面向对象相关知识。
    构造器
    每个类都有构造器。如果没有主动的为类定义构造器,Java 编译器将会为该类提供一个默认的无参构造器。构造器的主要作用是初始化对象的数据成员,在创建一个对象时,构造器会被自动调用来为对象的各个数据成员赋初值。
    面向对象基础实例:
    ClassMethod.java文件
    package com.java.ClassObject;
    /*
    * JAVA类声明中关键字public起到什么作用呢?如下ClassMethod类的声明:
    * 按着字面的意思理解是:ClassMethod类是公共的,要求ClassMethod类与类文件名必须保持一致,并且一个java文件中只能有一个public关键字声明的类。
    * public是表示公有的,private是表示私有的,私有的只能在面向对象中访问。
    * */
    /*
    * Java面向对象底层原理:
    * Java面向对象底层是由三个区域组成,分别是方法区、栈内存、堆内存,执行流程大概如下所示:
    * 1、将类加载到方法区
    * 2、将类中的main方法压入栈内存当中
    * 3、执行main方法中的代码,如果创建对象又会将对象对应的类加载到方法区,并且将对象的相对偏移地址压入栈内存中存储,而对象在堆内存中存储。
    * 4、堆内存中的对象不仅可以存储属性数据,还可以存储方法(即对象的方法),但方法是以引用地址的形式存储在堆内存当中,方法代码需要通过引用地址去方法区调用。
    * 5、方法区中存储了类的结构信息,包括类的属性、方法、常量池等。
    * */
    public class ClassMethod {
        public static void main(String[] args) {
            // 创建对象:类名 对象名 = new 类名();
            Zoo z = new Zoo("市中心动物园", 10);
            z.n = 1;
            z.main("长颈鹿", 11.4, 0.8);
        }
    }
    Zoo.java文件
    package com.java.ClassObject;
    public class Zoo {
        public String name;
        public int num;
        // 定义无参数构造器,构造器就是对面向对象属性进行初始化
        public Zoo() {
            System.out.println("这是无参数构造器!");
        }
        // 定义有参数构造器
        public Zoo(String name, int num) {
            this.name = name;  // this关键字代表用于存储当前对象的存储地址
            this.num = num;
            System.out.println("动物园叫" + name + ",动物园有" + num + "动物园区");
            System.out.println("这是有参数构造器!");
        }
        // 定义/初始化变量:修饰符 数据类型 变量名称 = 初始化值;
        public int n;
        public static void dong_wu(String name,double h,double w) {
            System.out.println("在动物园看到了" + name + ",它身高有" + h + "米,它宽度有" + w + "米");
        }
        public void main(String z, double h, double w) {
            for (;n!=0;n--) {
                dong_wu(z, h, w);
            }
        }
    }
    面向对象三大特性之封装
    封装指的是将数据和操作数据的方法封装在一个单元内部,并通过访问权限控制来限制外部对该单元的访问。
    例如,下面是一个简单的Java类,它封装了一个学生的姓名和年龄:
    public class Student {
        private String name;
        private int age;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
    }
    可以看到在这个类中,姓名和年龄都是私有成员变量,只能通过公开的getter和setter方法来访问和修改。这样,我们就可以在外部控制和限制对这些成员变量的访问。
    面向对象三大特性之继承
    继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
    Java中的继承是单继承的,也就是说每个子类只能继承一个父类。Java中通过使用关键字"extends"来实现继承。
    继承的语法格式如下所示:
    class SubClass extends SuperClass {
        // SubClass 类的属性和方法
    }
    其中,SubClass是子类,SuperClass是父类。子类可以继承父类的属性和方法,同时可以添加自己的属性和方法。
    Java中的所有类都继承自Object类。因此,如果没有指定父类,Java中的类默认继承自Object类。
    Java中的继承关系可以形成继承层次结构,也就是说一个子类可以成为另一个子类的父类。Java中的继承层次结构可以使用继承关系图来表示。
    在子类继承父类后,构造器会有以下特点:
    [ol]

  • 子类中所有的构造器默认都会先访问父类中无参的构造器,再执行自己。这是因为子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。

  • 如果父类中没有无参的构造器,子类必须使用super关键字显式调用父类的构造器,并传入相应的参数。例如:
    public class SubClass extends SuperClass {
      public SubClass(int x, int y) {
         super(x); // 调用父类的有参构造器
         // 子类的构造器逻辑
      }
    }

  • 子类通过this(...)去调用本类的其他构造器,本类其他构造器会通过super去手动调用父类的构造器,最终还是会调用父类构造器的。例如:
    public class SubClass extends SuperClass {
      public SubClass(int x) {
         this(x, 0); // 调用本类的有参构造器
      }
      public SubClass(int x, int y) {
         super(x); // 调用父类的有参构造器
         // 子类的构造器逻辑
      }
    }

  • 注意:this(...)和super(...)都只能放在构造器的第一行,所以二者不能共存在同一个构造器中。
    [/ol]
    继承的优点在于它可以提高代码的重用性和可维护性,同时可以使代码更加灵活和可扩展。通过继承,子类可以继承父类的属性和方法,从而减少了代码的编写量。此外,继承也可以使代码更加灵活,能够更好地适应需求的变化。
    继承的缺点在于它可能会引入过多的复杂性,使程序难以维护和扩展。如果继承关系设计不当,会导致代码的耦合性过高,增加了代码的复杂度和维护成本。因此,在设计继承关系时,需要仔细考虑继承的层次,避免出现过多的继承关系。
    面向对象三大特性之多态
    在 Java 中,多态是一种基于继承、多态和重载的特性。它允许同一种类型的对象在不同的场景下表现出不同的行为。
    Java 实现多态的方式主要有两种:继承和接口。
    [ol]
  • 继承实现多态
    [/ol]
    子类可以继承父类的方法,并且可以重写父类的方法,从而实现多态。当子类重写了父类的方法后,当通过父类对象调用该方法时,实际上会调用子类重写后的方法。
    示例代码:
    class Animal {
        public void makeSound() {
            System.out.println("未知的叫声");
        }
    }
    class Dog extends Animal {
        public void makeSound() {
            System.out.println("汪汪汪");
        }
    }
    class Cat extends Animal {
        public void makeSound() {
            System.out.println("喵喵喵");
        }
    }
    public class Test {
        public static void main(String[] args) {
            Animal animal = new Animal();
            Animal dog = new Dog();
            Animal cat = new Cat();
            animal.makeSound();
            dog.makeSound();
            cat.makeSound();
        }
    }
    输出结果:
    未知的叫声
    汪汪汪
    喵喵喵
    [ol]
  • 接口实现多态
    [/ol]
    接口是一种规范,它定义了一组方法,但并不提供方法的具体实现。不同的类可以实现同一个接口,并且每个类都可以根据自己的实际情况来实现接口中的方法,从而实现多态。
    示例代码:
    interface Animal {
        void makeSound();
    }
    class Dog implements Animal {
        public void makeSound() {
            System.out.println("汪汪汪");
        }
    }
    class Cat implements Animal {
        public void makeSound() {
            System.out.println("喵喵喵");
        }
    }
    public class Test {
        public static void main(String[] args) {
            Animal dog = new Dog();
            Animal cat = new Cat();
            dog.makeSound();
            cat.makeSound();
        }
    }
    输出结果:
    汪汪汪
    喵喵喵
    无论是通过继承还是接口实现多态,都可以使代码更加灵活、可扩展性更强。
    虚方法
    虚函数的存在是为了多态,虚方法是实现多态的重要手段,可以使不同的子类对象调用同一个方法时产生不同的行为。虽然Java 中没有虚方法这个概念,但是Java中每个方法都是虚方法,除了被 final 关键字修饰的方法。
    在Java中,虚方法的实现依赖于方法表和虚方法表。方法表是每个类的一部分,它包含了该类所有的方法的信息,包括方法名、参数类型、返回值类型等。虚方法表是每个类的一个隐藏的数据结构,它包含了该类所有的虚方法的信息,包括方法的地址、偏移量等。每个对象在内存中都有一个指向其所属类的虚方法表的指针,称为vtable指针。
    当调用一个虚方法时,Java虚拟机首先根据对象的实际类型找到其对应的虚方法表,然后根据方法的偏移量找到要调用的方法的地址,并执行该方法。如果子类重写了父类的虚方法,则子类的虚方法表中会覆盖父类相应的方法地址,从而实现了多态。
    以下是一个示例代码:
    class Animal {
        public void eat() {
            System.out.println("Animal is eating");
        }
    }
    class Cat extends Animal {
        public void eat() {
            System.out.println("Cat is eating");
        }
    }
    class Dog extends Animal {
        public void eat() {
            System.out.println("Dog is eating");
        }
    }
    public class Main {
        public static void main(String[] args) {
            Animal a = new Animal();
            Animal b = new Cat();
            Animal c = new Dog();
            a.eat(); // Animal is eating
            b.eat(); // Cat is eating
            c.eat(); // Dog is eating
        }
    }
    在上面的代码中,Animal、Cat和Dog都有一个eat方法,但是它们的行为不同。在main方法中,分别创建了一个Animal、一个Cat和一个Dog对象,并调用它们的eat方法。由于eat方法是虚方法,因此在运行时会根据对象的实际类型来确定要调用哪个方法,从而实现了多态。
    方法重写
    方法重写指的是在子类中定义一个与父类方法名、返回类型、参数列表都相同的方法,但是方法体不同的过程。
    当子类对象调用被重写的方法时,将优先调用子类中的方法,而不是父类中的方法。
    方法重写的条件为:
    [ol]
  • 方法名、返回类型、参数列表必须与父类中被重写的方法相同。
  • 访问权限不能低于父类中被重写的方法的访问权限。
  • 子类方法抛出的异常不能大于父类方法抛出的异常。
  • 静态方法不能被重写,但是可以被隐藏。
    [/ol]
    下面是一个方法重写的例子:
    public class Animal {
        public void move() {
            System.out.println("动物在移动");
        }
    }
    public class Dog extends Animal {
        @Override
        public void move() {
            System.out.println("狗在奔跑");
        }
    }
    public class Test {
        public static void main(String[] args) {
            Animal animal = new Animal();
            animal.move(); // 输出:动物在移动
            Dog dog = new Dog();
            dog.move(); // 输出:狗在奔跑
        }
    }
    在上面的例子中,Animal类中定义了一个move()方法,输出“动物在移动”。在Dog类中重写了这个方法,并输出“狗在奔跑”。当我们调用animal.move()时,输出的是父类Animal中的move()方法的内容;当我们调用dog.move()时,输出的是子类Dog中重写的move()方法的内容。
    static修饰符
    static是静态的意思,可以修饰成员变量和成员方法;static修饰的成员变量和成员方法在内存中只用存储一份,因为可以被类和对象共享访问、修改。
    static注意事项:
    1、静态方法只能访问静态成员(静态变量或者静态方法),不能直接访问实例成员,因为静态成员是属于类的,而不是直接属于对象的,静态成员会同类一同创建。
    2、实例方法可以访问静态成员,也可以访问实例成员,因为静态成员是可以被共享访问的。
    3、静态方法中不可以出现this关键字,因为this只能代表当前对象,而静态方法不一定使用对象调用。
    4、静态方法中也不能使用super关键字,因为super关键字代表父类对象,而静态方法没有对象的概念。
    5、静态方法也不能被子类重写,因为静态方法是属于类的,而不是属于对象的。
    package com.java.day1_static;
    public class User {
        public static int usernum;
        public static int Val(int num1, int num2) {
            return Math.max(num1, num2);
        }
    }
    静态代码块
    package com.java.day2_static_code;
    public class StaticCode {
        // 静态代码块:如果在启动系统时对静态资源进行初始化,则建议使用静态代码块完成数据的初始化作用
        public static String username;
        static {
            System.out.println("静态代码块对静态资源进行初始化啦!");
            username = "张三";
        }
        public static void main(String[] args) {
            System.out.println("main方法开始执行了!");
        }
    }
    抽象类
    抽象类是不能被实例化的类,如果你是新手,那你是不是很疑惑,一个类不能被实例化那还有什么卵用?
    别急嘛!抽象类的存在主要是为了被子类继承和实现。抽象类中可以包含抽象方法,抽象方法是一种没有实现的方法,只有方法的声明,没有方法体。
    抽象类的特点如下:
    [ol]
  • 抽象类不能被实例化,只能被继承。
  • 抽象类可以包含抽象方法和非抽象方法。
  • 抽象方法必须被子类通过方法重写实现,否则子类也必须声明为抽象类。
  • 抽象类可以拥有构造方法,但抽象类不能被用来创建对象。
    [/ol]
    下面是一个抽象类的例子:
    public abstract class Animal {  // abstract表示该类是抽象类
        private String name;
        public Animal(String name) {
            this.name = name;
        }
        public abstract void move();
        public void eat() {
            System.out.println(name + "在吃东西");
        }
    }
    public class Dog extends Animal {
        public Dog(String name) {
            super(name);
        }
        @Override
        public void move() {
            System.out.println(super.getName() + "在奔跑");
        }
    }
    public class Test {
        public static void main(String[] args) {
            Animal animal = new Dog("小狗");
            animal.move();
            animal.eat();
        }
    }
    在上面的例子中,Animal类是一个抽象类,它包含了一个抽象方法move()和一个非抽象方法eat()。在Dog类中继承了Animal类,并实现了move()方法。在Test类中,我们创建了一个Animal类型的对象,实际上是一个Dog对象,然后调用了它的move()和eat()方法。由于Dog类重写了move()方法,因此输出的是“小狗在奔跑”,而eat()方法是从父类继承而来,输出的是“小狗在吃东西”。
    接口
    接口是一种特殊的抽象类,它只包含了抽象方法和常量,没有任何实现。接口中的所有方法都是公共的,不能包含实例域或构造器,因此不能被实例化。
    一个类可以实现多个接口,但只能继承一个类。
    接口的定义格式如下:
    public interface 接口名 {
        // 常量定义
        // 方法声明
    }
    接口中的方法默认为public abstract类型,可以省略这两个关键字。接口中的常量必须是public static final类型的(这三个修饰符表示该变量是公共的、静态的、不可改变的,也叫该变量为常量),可以省略这三个关键字。接口中的方法不能包含方法体,必须由实现类去实现。
    下面是一个接口的例子:
    public interface Animal {
        int LEGS = 4;
        void move();
    }
    public class Dog implements Animal {
        @Override
        public void move() {
            System.out.println("狗在奔跑");
        }
    }
    public class Test {
        public static void main(String[] args) {
            Animal animal = new Dog();
            animal.move();
            System.out.println("狗有" + Animal.LEGS + "条腿");
        }
    }
    在上面的例子中,Animal是一个接口,包含了一个常量LEGS和一个抽象方法move()。Dog类实现了Animal接口,并实现了move()方法。在Test类中,我们创建了一个Animal类型的对象,实际上是一个Dog对象,然后调用了它的move()方法。由于Dog类实现了Animal接口,因此可以使用Animal类型来引用Dog对象,从而实现了多态性。输出的结果为“狗在奔跑,狗有4条腿”。
    内部类
    内部类其实就是在一个类里面再定义一个类,内部类可以访问其外部类的所有成员,包括私有成员。内部类可以分为成员内部类、静态内部类、局部内部类和匿名内部类。
    [ol]
  • 成员内部类
    [/ol]
    成员内部类就是在一个类的内部定义的另一个类,它可以访问外部类的所有成员,包括私有成员,并且可以使用外部类的引用来访问外部类的成员。成员内部类的定义格式如下:
    public class Outer {
        private int x = 10;
        public class Inner {
            public void print() {
                System.out.println("x = " + x);
            }
        }
    }
    在上面的例子中,Inner是Outer的成员内部类,它可以访问Outer的私有成员x。在外部类中创建Inner对象的方法如下:
    Outer outer = new Outer();
    Outer.Inner inner = outer.new Inner();
    inner.print();
    ​ 2.静态内部类
    静态内部类是在一个类的内部定义的静态类,它和普通的类一样,不依赖于外部类的实例,因此可以直接通过类名来访问。静态内部类不能访问外部类的非静态成员,只能访问外部类的静态成员。静态内部类的定义格式如下:
    public class Outer {
        private static int x = 10;
        public static class Inner {
            public void print() {
                System.out.println("x = " + x);
            }
        }
    }
    在上面的例子中,Inner是Outer的静态内部类,它可以访问Outer的静态成员x。在外部类中创建Inner对象的方法如下:
    Outer.Inner inner = new Outer.Inner();
    inner.print();
  • 局部内部类
    [/ol]
    局部内部类是定义在方法内部的类,它只能在该方法内部使用,对外部不可见。局部内部类可以访问外部类的所有成员,包括私有成员。局部内部类的定义格式如下:
    public class Outer {
        private int x = 10;
        public void test() {
            class Inner {
                public void print() {
                    System.out.println("x = " + x);
                }
            }
            Inner inner = new Inner();
            inner.print();
        }
    }
    在上面的例子中,Inner是Outer方法内部的局部内部类,它可以访问Outer的私有成员x。在方法内部创建Inner对象的方法如下:
    Outer outer = new Outer();
    outer.test();
  • 匿名内部类
    [/ol]
    匿名内部类是没有名字的内部类,它通常用于创建一个只需要使用一次的类。匿名内部类可以继承一个类或者实现一个接口,它没有构造方法,但可以使用构造代码块进行初始化。匿名内部类的定义格式如下:
    new 父类构造器/接口() {
        // 匿名内部类的内容
    }
    在上面的例子中,父类构造器可以是有参数的构造器,也可以是无参数的构造器;接口可以是有多个方法的接口,也可以是只有一个方法的接口。下面是一个使用匿名内部类实现接口的例子:
    public interface Animal {
        void move();
    }
    public class Test {
        public static void main(String[] args) {
            Animal animal = new Animal() {
                @Override
                public void move() {
                    System.out.println("动物在移动");
                }
            };
            animal.move();
        }
    }
    在上面的例子中,我们创建了一个实现Animal接口的匿名内部类,并实现了move()方法。在main方法中,我们创建了一个Animal类型的对象,实际上是一个匿名内部类的对象,然后调用了它的move()方法。输出的结果为“动物在移动”。
    异常
    Java中的异常指程序在运行过程中出现的错误或异常情况,例如类型错误、数组下标越界等。Java提供了异常处理机制来处理这些异常情况。
    Java中的异常分为两种:受检异常和非受检异常。
    [ol]
  • 受检异常
    [/ol]
    受检异常指的是编译器在编译时会检查的异常,程序必须在代码中显式地处理这些异常,否则编译会报错。受检异常通常是由Java API或自定义API中的方法抛出的,例如文件读写、网络通信等操作可能会抛出IOException异常。
    处理受检异常的方式有两种:捕获异常和声明抛出异常。
    捕获异常的语法格式如下:
    try {
        // 可能抛出异常的代码
    } catch (ExceptionType e) {
        // 异常处理代码
    } finally {
        // 可选的finally代码块
    }
    在try代码块中,我们可以放置可能抛出异常的代码。如果代码没有抛出异常,则程序会继续执行catch代码块之后的代码。如果代码抛出了异常,则程序会跳转到对应的catch代码块进行异常处理。catch代码块中的ExceptionType指定了要捕获的异常类型,可以是任何一个Exception的子类。在catch代码块中,我们可以编写异常处理的代码,例如输出错误信息、重新抛出异常等。finally代码块是可选的,它包含的代码会在try或catch代码块结束后执行。
    声明抛出异常的语法格式如下:
    public void method() throws ExceptionType {
        // 可能抛出异常的代码
    }
    在方法声明中,我们可以使用throws关键字声明该方法可能抛出的异常类型。如果方法中的代码抛出了异常,则该异常会被传递到调用该方法的地方进行处理。还有要注意在方法中并没有抛出异常,那么该方法也需要在调用它的地方进行异常处理。否则编译器也会报错。
    [ol]
  • 非受检异常
    [/ol]
    非受检异常是指编译器在编译时不会检查的异常,程序可以选择处理这些异常,也可以不处理。非受检异常通常是由程序中的错误逻辑导致的,例如数组下标越界、空指针引用等。
    处理非受检异常的方式是在程序中使用try-catch语句块捕获异常。如果程序没有处理非受检异常,则该异常会被抛出到调用栈中,直到被捕获或程序崩溃为止。
    Java中常见的非受检异常有RuntimeException及其子类,例如NullPointerException、IllegalArgumentException等。
    二、smali基础语法
    dalvik字节码
    1、先来了解一下dalvik虚拟机:
    dalvik虚拟机是Android 5.0以前用于运行安卓应用的虚拟机,从 Android 4.4 开始,Google 开始引入了全新的虚拟机 ART(Android Runtime),直到Android5.0开始ART虚拟机就替代了dalvik虚拟机。既然dalvik虚拟机被ART虚拟机替代了,那我们还有学的必要吗?ART 是向下兼容的,ART虚拟机对DEX字节码的运行是兼容的,因此针对 Dalvik 开发的应用也能在 ART 环境中运作。但是Dalvik 采用的一些技术并不适用于 ART,但不妨碍我们了解和学习dalvik字节码。
    2、我们再来了解一下dalvik寄存器和寄存器的命名方法:
    Dalvik寄存器中的寄存器都是32位大小,支持所有类型,对于小于或等于32位的类型,使用一个寄存器就可以了;对于64位(long和double)类型,需要使用两个相邻的寄存器来存储。
    寄存器的命名方法有两种:v命名法和p命名法:
    ​             v命名法:局部变量寄存器用v开头数字结尾的符号来表示,如v0、 v1、v2。
    ​             p命名法:函数参数寄存器则使用p开头数字结尾的符号来表示,如p0、p1、p2。
    特别注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指“this",p1表示函数的第一个 参数,p2代表函数中的第二个参数。而在static函数中p0才对应第一个参数(因为Java的static方法中没有this方法)
    寄存器赋值:
    const/4 p5, 0x1 //p5赋值1
    const/16 v0, 0xa //v0赋值10,在16进制里a表示10
    const/4和const/16是表示定义一个4位和16位值,这里只是修饰一下,记住Dalvik寄存器是32位寄存器,这只是表示32位寄存器中存的值是4位和16位而已,不要被这个给影响了。
    3、再来了解一下dalvik字节码的类型与Java数据类型的对应关系:
    数据类型对应:
    [table]
    [tr]
    smali类型[/td]
    java类型[/td]
    注释[/td]
    [/tr]
    [tr]
    [td]V

    方法, 部类

  • zhou123gao   

    正需要,学一学
    shitinghui2021   

    非常不错,谢谢分享
    CXC303   

    收藏学习,感谢分享
    鹏飞   

    很专业啊 学习一下
    雨落惊鸿,   

    这么多,感谢分享
    fleamboy   

    非常详细,学习收藏!
    feng710   

    技术贴,顶一个
    romoi   

    讲的不错的
    superworker2022   

    继续学习中。
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部