Java基础语法

1.第一份程序

1.1.代码编写

/*块注释
HelloWord.java 内部
*/

/**文档注释
* 作者:limou3434
*/
public class HelloWord {
    public static void main(String[] args){
        System.out.println("Hello Word!");//打印“Hello Word!”
    }
}

直接上代码,上面就是一段 Java 代码,我们先来观察一下这段代码的细节:

  1. 首先看到的是一个 main() 函数(每一个类只能有一个 main()),主函数没有所谓的返回值,因此返回类型是 void,在 main() 的内部有一个字符数组类型的 args 变量,用来接收命令行参数

  2. 在一个 Java 文件中只能有一个 public 类,并且 pubilc 类的名字必须和 .java 文件名字相同。而 main() 函数被包含在这个类中,不允许被写在类外

  3. main() 内部,有一个调用,调用了打印函数 println(),这个函数后面的 ln 指的是换行,打印函数内部包含了需要被打印的内容

  4. Java0 中有三种注释:行注释、段注释、文档注释,这里故意都使用了一遍

1.2.代码运行

那么如何编译这一份代码呢?

1.2.1.命令行编译

  1. 在最原始的情况下只需要将上述代码写入 HelloWord.java,然后通过使用 Java 的编译器 javac 这个可执行程序,使用命令 javac ./HelloWord.java(注意需要正确写好 Hellow.java 的相对路径或者绝对路劲)

  2. 此时在 HelloWord.java 的同级目录中就存在一个经过编译的字节码文件 HelloWord.class

  3. 运行 Java 代码代码直接使用 Java HelloWord 即可

  4. 需要注意的是,Java 不是单个文件就生成单个字节码的文件,而是有多少个类就有多少个字节码文件,并且字节码文件名和类相同,这点和很多编程语言有很大的不同。

注意:这里如果因为编码问题无法通过编译,则可以尝试在编译的时候设置某个编码运行,例如 javac -encoding utf-8 HellowWord.java 就可以使用 utf-8 来进行编码。

1.2.2.IEDA 编译

这个后面补上…

1.3.代码文档

Java 有一个非常独特的功能,给代码生成文档,使用 javadoc -d [想要生成的代码文档目录名] -author -version UTF-8 -charset UTF-8 [您自己的Java代码文件名] 即可生成一个 html 帮助文档。

2.运行过程

在我们安装 JDKJava 开发开发工具包)的时候,JDK 里包含 JREJava 运行时环境),JRE 里包含 JVMJava 虚拟机)

.java 后缀的 Java 文件使用 javac 编译成 .class 后缀的字节码文件,再通过 JVM 虚拟机转化为机器码运行起来。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

因此哪怕是其他语言,如果能被转化成字节码,也同样可以在虚拟机上运行。

在这里插入图片描述

3.关键字

有些关键字被 Java 所保留,不可以给用户创建标识符来使用,这些关键字的类别有很多,例如:intclasscatch 等等,我们后面再来一一介绍。

4.标识符

Java 中可以将类名、对象名、变量名、方法名称为“标识符”。Java 的标识符可以包含:字母、数字、下划线、$ 符号等。

不过需要注意的是不可以使用数字作为标识符的起始字符,但是可以把 $ 作为标识符的开头(但是不建议)。

在命名的时候,不仅要注意命名合法,还要注意合理。在本系列文章中我统一采用:

  1. 类名:大驼峰

  2. 方法名:小驼峰

  3. 变量名:小驼峰

5.常量与变量

在提及常量和变量的时候,就需要先提及数据类型。

5.1.数据类型

Java 的数据类型分为两种:基本数据类型和引用类型。

引用类型我们暂时不理会,后面会提及。

其中基本数据类型有四类八种。

5.1.1.四类

  1. 整型(整数)

  2. 浮点型(小数)

  3. 字符型(一个字符)

  4. 布尔类型(truefalse,就是对和错)

5.1.2.八种

数据类型关键字内存占用范围
字节型byte1 字节 [ − 128 , 127 ] [-128,127] [128,127]
短整型short2 字节 [ − 32768 , 32767 ] [-32768,32767] [32768,32767]
整型int4 字节 [ − 2 31 , 2 31 − 1 ] [-2^{31},2^{31-1}] [231,2311]
长整型long8 字节 [ − 2 63 , 2 63 − 1 ] [-2^{63},2^{63}-1] [263,2631]
单精度浮点数float4 字节有,但是不关注
双精度浮点数double8 字节有,但是不关注
字符型char2 字节 [ 0 , 65535 ] [0,65535] [0,65535]
布尔型booleantruefalse

Java 的数据类型是固定的不会受平台影响,因此很方便做代码移植。

5.2.字面常量

数据类型可以给字面常量(数据)做出分类。

类似 1003.14"abcdef"false 等这种一眼就能看出数据的都是字面常量,字面常量的类别也是根据数据类型来分类的。

类似 100 就是整型常量、3.14 就是浮点数常量、"abcdef" 就是字符串常量、构成字符串的每一个字符 abc…就是字符常量、falsetrue 就是布尔常量。

5.3.数据变量

变量可以理解为一个容器,可以用来存储一个常量,不同类别的常量需要靠不同类别的变量来存储。
而我们需要用一些关键字(也就是前面在数据类型中提到的),使得变量变成只存储某一字面常量类型的变量。

//定义变量的语法形式为:
int a = 10;//int 是关键字,a 是标识符,标记一个变量,10 为字面常量
//此时变量 a 存储了 10 这个字面量

注意 1Java 时没有全局变量这个术语的,但有类似的。

注意 2:在 Java 编程中如果没有对变量初始化就使用的话,很多编译器直接就会报错,编译也不会通过。

注意 3:如果赋值给变量过大或者过小的值,Java 是不会通过编译的。

补充:字符类型

Java 的字符类型是包装类型,不是基本数据类型。为了方便后续一些代码的使用,这里提前讲解一下字符类的相关概念。

C 没有字符类型,但是 Java 利用类的优势,使用 String 类定义字符串类型。

//使用 String 类
public static viod main() {
 String s1 = "Hello";
 String s2 = "I am limou3434";
 String s3 = "100";
 System.out.prinln(s1 + s2);
 //String 转 int
 int number = Inreger.parseInt(str);
 //int 转 String
 String s = String.valueOf(number);
}

如果您学过面向对象的语言(例如 C++),那您就能很快理解什么是包装类,您可以简单理解为基本数据类型被包装为一个类类型,这么做的好处是统一参数,由于 Java 是纯粹的面向对象语言,因此传递参数的时候,大部分使用的是类的实例化对象,因此将基本数据类型就被包装为包装类,可供程序传递参数使用。

6.类型转化

需要注意的是,Java 是强类型语言,有些不合适的类型转化将会直接报错(C 语言是弱类型语言)。

6.1.隐式类型转换

代码不需要经过处理,编译器会进行处理不同类型的转换,但是 Java 在这一方面检查得比较严格,不允许溢出和不相关类型转化。

//使用隐式类型转化
public class Main {
    public static void main(String[] args) {
        int a = 10;
        long b = 100L;
        b = a;//可以,发生了隐式类型转化,a 从 int 变成 long
        //a = b;//不可以,不够存

        float f = 3.14F;
        double d = 5.12;
        d = f;//可以,f 从 float 转化为 double
        //f = d;//不可以,不够存
    }
}

6.2.显式类型转换

这方面类似 C 语言,也是使用 () 来转换,但是不一定能成功,小范围可以转化为大范围的,赋值的数值一定不能溢出,前强制转换的类型要相关且合理。

//使用显示类型转化
public class Main {
    public static void main(String[] args) {
        int a = 10;
        long b = 100L;
        b = a;//可以,发生了隐式类型转化,a 从 int 变成 long
        a = (int)b;//可以,将 b 从 long 强制转化为 int

        float f = 3.14F;
        double d = 5.12;
        d = f;//可以,f 从 float 转化为 double
        f = (float)d;//可以,将 d 从 double 前置转化为 float
    }
}

6.3.整型提升

在不同变量的运算的运算中,Java 也存在整型提升,和 C 基本差不多,需要注意是 Java 对溢出极其敏感(C 对待溢出十分迟钝),提升后需要赋给对应的容器(除非强转)。

//查看整型提升现象
public class Main {
    public static void main(String[] args) {
        byte a = 127;
        byte b = 127;
        //byte c = (byte)(a + b);//a 和 b 提升为 int 进行计算,结果也为 int,虽然可以强转为 byte,但是 很危险
        int d = a + b;//这样才比较好
    }
}

7.运算符

这里我们只挑出几个比较特殊的运算符,其余的运算符和 C 基本差不多(包括“结合性”和“优先级”),这里就不再赘述。

  1. Java 的除法和 C 类似,会向下取整,并且除以 0 会抛出异常 ArithmeticException(算数异常)

    //使用除法的示例
    public class Main {
        public static void main(String[] args) {
            System.out.println(5 / 2); //2
            System.out.println(5.0 / 2); //2.5
            System.out.println(5 / 2.0); //2.5
            System.out.println((float)5 / 2); //2.5
            System.out.println(5 / (float)2); //2.5
            System.out.println((float)(5 / 2)); //2.0
        }
    }
    
  2. 由于除法一样,所以 % 运算也是一样的,需要注意的是,该运算符也可以对小数进行操作(就是很少用)

    //使用取模的示例
    public class Main {
        public static void main(String[] args) {
            System.out.println(10 % 3); //1
            System.out.println(-10 % 3); //-1
            System.out.println(10 % -3); //1
            System.out.println(-10 % -3); //-1
    
            System.out.println(-10.3 % 3); //-1.3000000000000007
        }
    }
    
  3. 在增量运算符中有的时候会发生类型转化,等价于强转

    //使用增量运算符发生隐式强转
    public class Main {
        public static void main(String[] args) {
            int num1 = 10;
            double num2 = 3.14;
    
            int add1 = 0;
            double add2 = 0.0;
    
            //方式一
            //add1 = num1 + num2; //4 字节和 8 字节相加,发生整形提升,只用 4 个字节是放不下去的
            //System.out.println(add1);
            add2 = num1 + num2;
            System.out.println(add2);
            System.out.println();
    
            //方式二
            add1 = (int) (num1 + num2);
            System.out.println(add1);
            add2 = (double) (num1 + num2);
            System.out.println(add2);
            System.out.println();
    
            //方式三
            num1 += num2; //等价于 a = (int)(a + b)
            System.out.println(num1);
        }
    }
    

    而自增、自减运算符还有一种特殊的情况需要您注意,您应该避免出现下面这样的代码…

    //有关于加加和减减的一个很坑的点
    public class Main {
        public static void main(String[] args) {
            int a = 10;
            a = a++;
            System.out.println(a);
            //结果为 10,而不是 11,这个原因涉及底层,
            //以后我再来填坑,这里充分体现了 Java 和 C/C++ 不一样!
        }
    }
    
  4. 关系运算符、逻辑运算符(也具有短路效应)的操作表达式必须为布尔表达式,其运算结果为 trueflase,不是 非00。而 iffor 判断的环节中使用的也是布尔表达式

  5. Java 的移位操作符有三个 <<>>>>>>> 是左补符号位,>>> 是左补 0

  6. 也有一个唯一的三目操作符:条件运算符 表达式1 ? 表达式2 : 表达式3。并且该表达式必须是被使用的(其结果必须被使用),不能单独存在

  7. 对于位操作,建议还是使用括号引导表达式的逻辑,避免出现意想不到的后果(在 C 中也最好一样)

注意:如果摒弃掉 C 的“非零为真”的概念在接下来的编码中会轻松很多…

8.控制语句

和大部分编程语言类似,Java 也有自己的控制语句,和 C 也有些类似,但是在入口判断有很大的不同,不同的根源来自于:C 使用整数的非零和 0 来判断真假,而 Java 完全使用布尔类型来判断真假,而 Java 的布尔类型和整数是无法进行比较的,这就导致 Java 写出来的入口判断会更加清晰(也可以说没有 C/C++ 那样整洁,至于是清晰好还是简洁好,看您喜好而定)

8.1.分支语句

8.1.1.if 语句

Javaif 语句,几乎和 C/C++ 的使用一样,并且也有类似的悬挂 else 的问题,真要说有哪些点不同,就是 C/C++Java 的代码缩进风格不太一样。

//使用 if 语句
public class Main {
    public static void main(String[] args) {
        int a = 1;
        if (a == 1) { //这里只能是整型
            System.out.println(a);
        } else {
            System.out.println("a != 1");
        }
    }
}

8.1.2.switch 语句

同样,几乎和 C 一样,就是需要注意的是:switch 的入口只能使用 charbyteshortintCharacterByteShortIntegerStringenum 类型,其他类型一概不支持(比如 long 就不行),这点很重要。

//使用 switch 语句
public class Main {
    public static void main(String[] args) {
        int a = 1;
        switch (a) { //这里只能是整型
            case 1:
                System.out.println(a);
                break;
            case 2:
                System.out.println(a);
                break;
            default:
                System.out.println("default");
        }
    }
}

8.2.循环语句

8.2.1.for 语句

//使用 for 语句
public class Main {
    public static void main(String[] args) {
        for (int a = 1; a < 10; a++) {
            System.out.println(a);
        }
    }
}

8.2.2.while 语句

//使用 while 语句
public class Main {
    public static void main(String[] args) {
        int a = 1;
        while (a < 10) {
            System.out.println(a);
            a++;
        }
    }
}

8.2.3.do-while 语句

//使用 do-while 语句
public class Main {
    public static void main(String[] args) {
        int a = 1;
        do {
            System.out.println(a);
            a++;
        } while (a < 10);
    }
}

让我们借助循环语句,顺便来讲解一下在 IDEA 中如何调试代码:

请添加图片描述

9.输入输出

9.1.输出

Java 有三种常用的输出,均体现在下述代码中:

//使用三种输出语句
public class Main {
    public static void main(String[] args) {
        System.out.print("limou"); //输出,但不换行(无需关注类型)
        System.out.println("limou"); //输出,但是换行(无需关注类型)
        System.out.printf("%s", "limou"); //格式化输出输出
    }
}

您可能会好奇格式化输出是否和 C 一样,实际上有些类似,也有些不同:

  • %o%d%x:整数的八进制、十进制、十六进制输出
  • %f%e%g%a:定点浮点数、指数浮点数、通用浮点数、十六进制浮点数输出
  • %s%c:字符串、字符输出
  • %b:布尔值输出
  • %h:散列码输出
  • %%:百分号输出

此外,也有一些修饰符,您稍微了解一下即可。

9.2.输入

//使用输入语句
//导入相关的包(可以将光标停在对应关键字上,使用快捷键 [alt+enter] 来快速导入)
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        //创建一个 scan 对象,并且 System.in 代表设置为“从键盘输入”
        Scanner scan = new Scanner(System.in);

        System.out.println("请输入您的年龄"); //提示用户输入
        int age = scan.nextInt(); //通过方法获取输入

        System.out.println("请输入您的名字"); //提示用户输入
        String name = scan.next(); //通过方法获取输入(会忽略空白字符)

        //使用 nextLine() 则会获取空白字符,但是有可能有
        //失效的问题也就是多余空白字符被误输入的问题
        String ch = scan.nextLine(); //除去上述输入得最后产生得换行符
        System.out.println("输入您的爱好");
        String hobby = scan.nextLine();

        System.out.println("请输入您的体重"); //提示用户输入
        float weight = scan.nextFloat(); //通过方法获取输入(会忽略空白字符)

        System.out.println("年龄:" + age); //输出信息
        System.out.println("名字:" + name); //输出信息
        System.out.println("爱好:" + hobby); //输出信息
        System.out.println("体重:" + weight); //输出信息

        scan.close(); //类似 C 语言的文件关闭(暂时先这么理解即可)
    }
}

/* 输出结果
请输入您的年龄
18
请输入您的名字
limou 3434
输入您的爱好
game and programme
请输入您的体重
51.2
年龄: 18
名字: limou
爱好: game and programme
体重: 51.2
*/

当然,一般建议文本输入最好放在最前面处理(尤其是多数据类型输入的时候)。

还有一个循环输入的代码也值得您一看:

//多组输入
//导入相关的包(可以将光标停在对应关键字上,使用快捷键 [alt+enter] 来快速导入)
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        System.out.print("请输入一个数字 ");
        while (scan.hasNextInt()) {
            int number = scan.nextInt();
            System.out.println("您输入的数字是:" + number);
            System.out.print("请输入一个数字 ");
        }
        //[ctrl+d] 会终止循环
    }
}

/* 输出结果
请输入一个数字 18
您输入的数字是: 18
请输入一个数字 20
您输入的数字是: 20
请输入一个数字 35
您输入的数字是: 35
请输入一个数字 5
您输入的数字是: 5
请输入一个数字 100
您输入的数字是: 100
请输入一个数字
*/

10.随机数

Java 的随机数生成也比较简单,使用如下代码即可:

//随机数生成
//导入相关的包(可以将光标停在对应关键字上,使用快捷键 [alt+enter] 来快速导入)
import java.util.Random;

public class Main {
    public static void main(String[] args) {
        int count = 0;

        Random random1 = new Random();
        while (count < 10) {
            int n = random1.nextInt();
            System.out.println(n);
            count++;
        }

        Random random2 = new Random();
        while (count > 0) {
            int n = random2.nextInt(100); //限定 [0, 100) 的随机数
            System.out.println(n);
            count--;
        }
    }
}

/* 输出结果
1341966210
210008845
453512808
804932370
28871118
-913616368
469568144
-904536397
190689066
-546299782
69
65
21
54
6
83
0
1
81
41
*/

还有一个数学库的随机数,您也可以去了解一下…

11.方法

11.1.方法的调用

方法和 C 中的函数是否类似(因为它们的工作是差不多的,都是通过调用来达到简化代码的目的),而为什么不继续延用“函数”这个术语,而使用“方法”呢?

简单来说就是类的出现导致的,Java 使用类来创建一个又一个的对象,这些对象很类似普通的变量,而类内写入的对象可以执行的方法,这样创建出一个对象就可以使用配套的对象方法,这些内容我将会在下一节的类中重新阐述(现在您把方法简单视为函数)。

//方法的使用
public class Main {
    public static void main(String[] args) {
        System.out.println(add(1, 2));
    }
    public static int Add(int a, int b) {
        return a + b;
    }
}

/* 输出结果
3
*/

另外,方法不能嵌套定义,一个方法的内部是不能定义另外一个方法的。

方法在类内是全局的,也就是说无论把 Add() 写到类的哪里,main() 都可以执行该方法。

11.2.方法的参数

Java 的方法也有和 C 函数类似的形参和实参的问题,但由于 Java 没有 C/C++ 的指针,如果传递一个参数过来,该怎么进行修改呢?这里挖个坑,我们以后再来补充…

//形参和实参的一个问题
public class Main {
    public static void main(String[] args) {
            int num1 = 5, num2 = 10;

            System.out.println("交换方法一");
            System.out.println("交换前 num1:" + num1 + " " + "num2:" + num2);
            int tmp = num1;
            num1 = num2;
            num2 = tmp;
            System.out.println("交换后 num1:" + num1 + " " + "num2:" + num2);

            System.out.println("交换方法二");
            System.out.println("交换前 num1:" + num1 + " " + "num2:" + num2);
            Swap(num1, num2);//使用方法交换(交换失败)
            System.out.println("交换后 num1:" + num1 + " " + "num2:" + num2);
    }
    public static void Swap(int num1, int num2) {
        int tmp = num1;
        num1 = num2;
        num2 = tmp;
    }
}
/*
交换方法一
交换前 num1: 5 num2: 10
交换后 num1: 10 num2: 5
交换方法二
交换前 num1: 10 num2: 5
交换后 num1: 10 num2: 5
*/

11.3.方法的递归

除此之外,Java 也支持递归调用方法。

//使用方法递归计算阶乘
public class Main {
    public static void main(String[] args) {
        int number = 5;
        System.out.println(Test(number));
    }
    public static int Test(int number) {
        if (number == 0 || number == 1) {
            return 1;
        } else if (number < 0) {
            return -1;
        }
        return Test(number - 1) * number;
    }
}

/* 输出结果
120
*/

11.4.方法的重载

函数重载和 C++ 的重载类似,也就是说:Java 允许一个方法可以有多种实现,这些方法实现的方法名字是一样的,但是参数列表是不一样的(参数个数或参数顺序不一样)。

//需要使用方法重载的例子
public class Main {
    public static void main(String[] args) {
        int number1 = 5;
        System.out.println(Test(number1));
        double number2 = 5.0;
        System.out.println(Test(number2)); //无法调用 Test()
    }

    public static int Test(int number) {
        if (number == 0 || number == 1) {
            return 1;
        } else if (number < 0) {
            return -1;
        }
        return Test(number - 1) * number;
    }
}
//为 Test() 提供重载版本
public class Main {
    public static void main(String[] args) {
        int number1 = 5;
        System.out.println(Test(number1));

        double number2 = 5.0;
        System.out.println(Test(number2)); //成功调用 Test() 的重载版本
    }

    public static int Test(int number) {
        if (number == 0 || number == 1) {
            return 1;
        } else if (number < 0) {
            return -1;
        }
        return Test(number - 1) * number;
    }

    public static double Test(double number) {
        if (number == 0 || number == 1) {
            return 1;
        } else if (number < 0) {
            return -1;
        }
        return Test(number - 1) * number;
    }
}

/* 输出结果
120
120.0 
*/

12.数组

12.1.数组的创建

C/C++ 诡异的数组、指针创建风格曾折磨过不少初入门的家伙们,而 Java 的数组和 C/C++ 有很大的不同,永远都依照下面的格式创建数组:

//创建数组的格式
数组类型 数组名 = { 元素列表 }; //元素可以为变量(静态数组)

数组类型 数组名 = new 元素类型[元素个数]; //元素可以为变量(动态数组)
数组类型 数组名 = new 元素类型[]{ 元素列表 }; //元素可以为变量(动态数组)

//无论是静态创建还是动态创建,最终数组元素都会存储到堆上,而数组名作为引用变量指向/引用堆空间里的数组元素,并且使用下标/索引来进行访问
//初始化的元素列表只有在定义阶段使用,不能先定义然后另起一行进行初始化元素列表

Java 的数组可以使用索引来查找和修改对应元素,并且索引也是从 0 开始的,Java 的数组越界会抛出异常,这比 C/C++ 的基本数组要安全得多。数组的长度可以使用成员变量 length 来获得。

12.2.数组的遍历

//数组的使用(1)
public class Main {
    public static void main(String[] args) {
        int[] arr = new int[]{1, 2, 3, 4, 5}; //创建数组并且初始化
        for (int i = 0; i < arr.length; i++) {
            arr[i] *= 10;
            System.out.println(arr[i]);
        }
    }
}

/* 输出结果
10
20
30
40
50
*/

对于基本类型的数组,默认初始化为 0

//数组的使用(2)
public class Main {
    public static void main(String[] args) {
        int size = 5;
        int[] arr = new int[size]; //创建数组,但不进行初始化
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

/* 输出结果
0
0
0
0
0
*/

另外,我们还可以使用 foreach 来遍历数组(类似 C++ 的范围 for

//foreach 增强 for 循环格式
for( 存储数组元素变量 : 数组 ) {
    //这样变量里存储的就是数组的某一个元素
}
//数组的使用(3)
public class Main {
    public static void main(String[] args) {
        int[] arr = new int[]{1, 2, 3, 4, 5}; //创建数组并且初始化
        for (int e : arr) {
            e *= 10;
            System.out.println(e);
        }
    }
}

/* 输出结果
10
20
30
40
50
*/

Java 还有一个工具专门用来操控数组,这个工具就是 Arrays,这个工具内有很多方法,我们只需要将数组转化为字符串交给 Arrays 来处理即可(还有一些其他的方法,例如排序、二分…)。

//数组的使用(4)
import java.util.Arrays;
public class Main {
    public static void main(String[] args) {
        int[] arr = new int[]{1, 2, 3, 4, 5}; //创建数组并且初始化
        String ret = Arrays.toString(arr);
        System.out.println(ret);
    }
}

/* 输出结果
[1, 2, 3, 4, 5]
*/

12.3.数组的引用

这里还有一个需要您注意的点,数组是一个引用类型,那么什么是引用类型呢?

我们需要了解一下 Java 的内存分布,首先内存是连续分布的存储空间,程序中运行所需要的数据就存储在内存空间中,而 JVM 对内存的划分大致如下:

flowchart TD
subgraph "运行时数据区"
	subgraph "所有线程共享的数据区"
        a["方法区"]
        b["堆区"]
    end
	subgraph "线程隔离的数据区"
		c["虚拟机栈"] ~~~ d["本地方法栈"] ~~~ e["程序计数器"]
    end
end
  1. 方法区(Method Area): 用干存储已拟机加载的类信息.常量、静态变量.即编译器编译后的代码
  2. 堆(Heap): JVM 所管理的最大内存区域,使用 new 创建的对象都是在堆上保存,堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁
  3. 虚拟机栈(JVM stack):与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含有了局部变量表、操作数栈、动态链接、返回地址…保存的都是与方法执行时相关信息(例如局部变量,在方法运行结束后,栈帧就会被销毁,栈帧中保存的数据也跟着销毁了。
  4. 本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似,只不过保存的内容是 Native 方法(即原生方法)的局部变员.在有些版本的 JVM 实现中(例如 HotSpot),本地方法栈和虚拟机栈是在一起使用的
  5. 程序计数器(PC Register):只是一个很小的空间,保存下一条执行的指令的地址

这里简单看一下即可…我们主要焦距在堆和虚拟机栈上,让我们来分析下面这个代码的变量和数组的存储情况:

//分析内存情况
public class Main {
    public static void main(String[] args) {
        int a = 10;
        int b = 5;
        int[] arr = { 1, 2, 3, 4 };
    }
}
Stack
Heap
引用
a变量
b变量
arr的哈希地址(形式地址/引用)
存储数组元素[1][2][3][4]

因此我们在 Java 中就会遇到传递引用的情况,并且我还给出了图示:

//分析传引用的情况
public class Main {
    public static void main(String[] args) {
        int a = 10;
        int b = 5;
        int[] arr1 = { 1, 2, 3, 4 };
        for(int e : arr1) {
            System.out.print(e + " ");
        }
        System.out.println();

        int[] arr2 = arr1;
        for(int e : arr2) {
            System.out.print(e + " ");
        }
        System.out.println();

        arr2[2] = 100;
        for(int e : arr1) {
            System.out.print(e + " ");
        }
        System.out.println();
    }
}
/* 输出结果
1 2 3 4
1 2 3 4
1 2 100 4
*/
Stack
Heap
引用
通过 arr2 引用修改数组元素
a变量
b变量
arr1的哈希地址(形式地址/引用)
arr2的哈希地址(形式地址/引用)
存储数组元素[1][2][100][4]

注意:强调一下,从这里的引用就可以看出 Java 的引用和 C++ 的引用有很大的不同,我建议直接认为是两个不同的东西避免混淆…

还有一些特殊并且值得注意的传递引用情况:

//改变引用
public class Main {
    public static void main(String[] args) {
        int a = 10;
        int b = 5;
        int[] arr1 = { 1, 2, 3, 4 };
        for(int e : arr1) {
            System.out.print(e + " ");
        }
        System.out.println();

        int[] arr2 = { 4, 3, 2, 1 };
        for(int e : arr2) {
            System.out.print(e + " ");
        }
        System.out.println();

        arr1 = arr2;
        for(int e : arr1) {
            System.out.print(e + " ");
        }
        System.out.println();

        //尝试膝盖并且观察
        arr2[1] = 10000;
        for(int e : arr1) {
            System.out.print(e + " ");
        }
        System.out.println();
        for(int e : arr2) {
            System.out.print(e + " ");
        }
        System.out.println();
    }
}
/* 输出结果
1 2 3 4 
4 3 2 1 
4 3 2 1 
4 10000 2 1 
4 10000 2 1 
*/
Stack
Heap
取消引用
改变引用
引用
a变量
b变量
arr1的哈希地址(形式地址/引用)
arr2的哈希地址(形式地址/引用)
(由于没有引用指向该数组,此处将被自动销毁)存储数组元素[1][2][3][4]
存储数组元素[4][3][2][1]

也就是说,一个引用不能同时指向多个对象,但是一个对象能被多个引用指向。

引用类型的初始化可以使用 null,代表引用不指向任何的对象(直接使用索引进行读写操作就会出现空指针异常),值得一提的是:JavanullC/C++ 不一样,它不是指内存上的 0 地址,两者没有直接关联。

12.4.数组的传递

下面这个代码您需要好好分析一下:

//传递数组参数
import java.util.Arrays;
public class Main {
    public static void func1(int[] arr) {
        arr = new int[]{0, 0, 0, 0};
    }
    public static void func2(int[] arr) {
        arr[1] = 100;
    }

    public static void main(String[] args) {
        int[] arr = { 1, 2, 3, 4 };
        func1(arr);
        func2(arr);
        System.out.println(Arrays.toString(arr));
    }
}
/* 输出结果
[1, 2, 3, 4]
*/

这份代码可能会让您吃惊,让我们来看看引用指向和内存分布的分析图,调用方法 func1() 且尚未执行 arr = new int[]{0, 0, 0, 0}; 时:

Stack
Heap
引用
引用
形参 arr 的哈希地址(形式地址/引用)
实参 arr 的哈希地址(形式地址/引用)
存储数组元素[1][2][3][4]

执行 arr = new int[]{0, 0, 0, 0}; 后:

Stack
Heap
引用
取消引用
引用
形参 arr 的哈希地址(形式地址/引用)
实参 arr 的哈希地址(形式地址/引用)
存储数组元素[1][2][3][4]
存储数组元素[0][0][0][0]

func() 调用结束后,形参 arr 被销毁,执行的对象没有被引用,就会被 Java 自动销毁:

Stack
Heap
引用
实参 arr 的哈希地址(形式地址/引用)
存储数组元素[1][2][3][4]

而调用 func2() 并且执行语句 arr[1] = 100; 就会导致实参指向的数组也会跟着变化:

Stack
Heap
引用(并且对数组做修改)
引用
形参 arr 的哈希地址(形式地址/引用)
实参 arr 的哈希地址(形式地址/引用)
存储数组元素[1][100][3][4]

利用数组的引用传递,我们可以使用数组引用的特性来完成两数交换的目的。

//两数交换
import java.util.Arrays;
public class Main {
    public static int[] Swap(int[] arr) {
        int tmp = arr[0];
        arr[0] = arr[1];
        arr[1] = tmp;
        return arr;
    }

    public static void main(String[] args) {
        int[] arr = { 5, 10 };
        System.out.println(Arrays.toString(arr));

        int[] swapArr = Swap(arr);
        System.out.println(Arrays.toString(swapArr));
    }
}
/* 输出结果
[5, 10]
[10, 5]
*/

注意:不是所有的引用传递都能被修改。

13.断言

IDEA 中默认 assert 是关闭,开启方式为:设置 jvm 的参数,参数为 -enableassertions-ea 即可。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

//使用断言
public class Main {
    public static void main(String[] args) {
        int a = 1, b = 2;
        assert (a > b);
    }
}
/* 输出结果
Exception in thread "main" java.lang.AssertionError
	at Main.main(Main.java:5)
*/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/321307.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

工具篇--SpringCloud--openFeign--Feign.builder()自定义客户端

文章目录 前言一、自定义客户端&#xff1a;1.1 定义外部接口类&#xff1a;1.2 接口代理类生成&#xff1a;1.3 方法的远程调用&#xff1a; 二、Feign.builder()自定义客户端原理&#xff1a;2.1 FeignClientFactoryBean2.2 客户端的配置设置&#xff1a;2.3 代理类的生成&am…

【GitHub项目推荐--AI 开源项目/涵盖 OCR、人脸检测、NLP、语音合成多方向】【转载】

今天为大家推荐一个相当牛逼的AI开源项目&#xff0c;当前 Star 3.4k&#xff0c;但是大胆预判&#xff0c;这个项目肯定要火&#xff0c;未来 Star 数应该可以到 10k 甚至 20k&#xff01; 着急的&#xff0c;可以到 GitHub 直接去看源码 传送门&#xff1a;https://github.c…

GNSS差分码偏差(DCB)原理学习与数据下载地址

一、DCB原理 GNSS差分码偏差&#xff08;DCB&#xff0c;Differential Code Bias&#xff09;是由不同类型的GNSS信号在卫星和接收机不同通道产生的时间延迟&#xff08;硬件延迟/码偏差&#xff09;差异&#xff0c;按照频率相同或者不同又可以细分为频内偏差&#xff08;例如…

电子电器架构车载软件 —— 集中化架构软件开发

电子电器架构车载软件 —— 集中化架构软件开发 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任…

好物周刊#36:程序员简历

村雨遥的好物周刊&#xff0c;记录每周看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;每周五发布。 一、项目 1. SmartDNS 一个运行在本地的 DNS 服务器&#xff0c;它接受来自本地客户端的 DNS 查询请求&#xff0c;然后从多个上游 DNS 服务器获取 DNS 查询…

从零开始复现BERT,并进行预训练和微调

从零开始复现BERT 代码地址&#xff1a;https://gitee.com/guojialiang2023/bert 模型 BERT 是一种基于 Transformer 架构的大型预训练模型&#xff0c;它通过学习大量文本数据来理解语言的深层次结构和含义&#xff0c;从而在各种 NLP 任务中实现卓越的性能。 核心的 BER…

InseRF: 文字驱动的神经3D场景中的生成对象插入

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

基于特征选择和机器学习的酒店客户流失预测和画像分析

基于特征选择和机器学习的酒店客户流失预测和画像分析 基于特征选择和机器学习的酒店客户流失预测和画像分析摘要1. 业务理解2. 数据理解和处理2.1 特征理解2.2 数据基本情况2.3 特征相关性分析 3. 酒店客户流失预测模型构建和评估3.1 支持向量机3.2 K-means聚类用户画像构建 4…

ssh协议以及操作流程

ssh协议 1.是一种安全通道协议 2.对通信数据进行了加密处理&#xff0c;用于远程管理 3.对数据进行压缩 在日常生活中&#xff0c;我们使用的是openssh openssh 服务名称&#xff1a;sshd 服务端主程序&#xff1a;/usr/sbin/sshd 服务端配置文件&#xff1a;/etc/ssh/sshd_con…

pytorch一致数据增强—异用增强

前作 [1] 介绍了一种用 pytorch 模仿 MONAI 实现多幅图&#xff08;如&#xff1a;image 与 label&#xff09;同用 random seed 保证一致变换的写法&#xff0c;核心是 MultiCompose 类和 to_multi 包装函数。不过 [1] 没考虑不同图用不同 augmentation 的情况&#xff0c;如&…

《工具录》dig

工具录 1&#xff1a;dig2&#xff1a;选项介绍3&#xff1a;示例4&#xff1a;其他 本文以 kali-linux-2023.2-vmware-amd64 为例。 1&#xff1a;dig dig 是域名系统&#xff08;DNS&#xff09;查询工具&#xff0c;常用于域名解析和网络故障排除。比 nslookup 有更强大的功…

MISGAN

MISGAN:通过生成对抗网络从不完整数据中学习 代码、论文、会议发表: ICLR 2019 摘要: 生成对抗网络(GAN)已被证明提供了一种对复杂分布进行建模的有效方法,并在各种具有挑战性的任务上取得了令人印象深刻的结果。然而,典型的 GAN 需要在训练期间充分观察数据。在本文中…

【数据结构 | 希尔排序法】

希尔排序法 思路ShellSort 思路 希尔排序法又称缩小增量法。希尔排序法的基本思想是&#xff1a;先选定一个整数&#xff0c;把待排序文件中所有记录分成个组&#xff0c;所有距离为的记录分在同一组内&#xff0c;并对每一组内的记录进行排序。然后&#xff0c;取&#xff0c…

Spark原理——Shuffle 过程

Shuffle 过程 Shuffle过程的组件结构 从整体视角上来看, Shuffle 发生在两个 Stage 之间, 一个 Stage 把数据计算好, 整理好, 等待另外一个 Stage 来拉取 放大视角, 会发现, 其实 Shuffle 发生在 Task 之间, 一个 Task 把数据整理好, 等待 Reducer 端的 Task 来拉取 如果更细…

【开发板资料】Arduino NANO 资料分享(引脚分布图、原理图、亮灯程序等)

给出部分参考资料以及来源 引脚分布 PINOUT 来源&#xff1a;Nano | Arduino Documentation https://docs.arduino.cc/hardware/nano PINOUT PINOUT 来源&#xff1a;https://www.tumblr.com/pighixxx/42591353009/arduino-nano-pinout https://www.tumblr.com/pighixxx/…

求解建公路问题

课程设计题目 求解建公路问题 课程设计目的 深入掌握 Prim 和 Kruskal算法在求解实际问题中的应用 问题描述 假设有 n 个村庄,编号从到,现在修建一些道路使任意两个村庄之间可以互相连通。所谓两个村庄 A 和B是连通的,指当且仅当A 和 B之间有一条道路或者存在一个村庄 C 使得…

UG装配-WAVE几何链接器

自上向下&#xff08;自顶向下&#xff09;设计 先将产品主要结构&#xff08;或主要部件&#xff09;建立好&#xff0c;然后再根据要求设计其它组件&#xff0c;使每个组件之间有数据关联&#xff0c;适用于产品开发初期&#xff0c;便于修改&#xff0c;修改组件数据后&…

如何利用小程序介绍公司品牌形象?

企业小程序的建设对于现代企业来说已经成为了一项必不可少的工作。随着移动互联网的快速发展&#xff0c;越来越多的职场人士和创业老板希望通过小程序来提升企业形象&#xff0c;增强与用户的互动&#xff0c;实现更好的商业效果。在这个过程中&#xff0c;使用第三方制作平台…

C-操作符详解

1.进制转换 1.1 10进制转2进制 方法&#xff1a;短除法 1.2 2进制转换8进制 8进制的数字每⼀位是0~7的&#xff0c;0~7的数字&#xff0c;各⾃写成2进制&#xff0c;最多有3个2进制位就⾜够了&#xff0c;⽐如7的⼆进制是111&#xff0c;所以在2进制转8进制数的时候&#xf…

三、Qt Creator 使用

关于Qt的安装及环境配置&#xff0c;在我的上一篇《二、QT下载、安装及问题解决(windows系统)》已经讲过了。 本章节有一个重点&#xff0c;在新建 工程文件时&#xff0c;所在路径不要有中文&#xff0c;否则编译及运行程序不能正常运行。 在使用Qt Creator&#xff08;以下…
最新文章