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]
[/ol]
非访问修饰符
为了实现一些其他的功能,Java 提供了很多非访问修饰符,如:
[ol]
[/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