Java基础

程序流程

  • Java默认初始值与无默认区分

    • public class DefaultValuesExample {
          int instanceVar;          // 默认值: 0
          static int staticVar;     // 默认值: 0
          int[] array = new int[5]; // 数组元素默认值: 0
      
          public void method() {
              int localVar;         // 局部变量,必须显式初始化
              // System.out.println(localVar); // 编译错误
          }
      
          public static void main(String[] args) {
              DefaultValuesExample example = new DefaultValuesExample();
              System.out.println("Instance variable: " + example.instanceVar);
              System.out.println("Static variable: " + staticVar);
              System.out.println("Array element: " + example.array[0]);
          }
      }
      

      局部变量必须有显式初始化

  • Java简易程序

    • public class Hello {
          public static void main(String[] args) {
              System.out.println("Hello, world!");
          }
      }
      
      • 定义称为class(类),这里的类名是Hello,大小写敏感,class用来定义一个类,public表示这个类是公开的,publicclass都是Java的关键字,必须小写,Hello是类的名字,按照习惯,首字母H要大写。而花括号{}中间则是类的定义
      • 方法是可执行的代码块,一个方法除了方法名main,还有用()括起来的方法参数,这里的main方法有一个参数,参数类型是String[],参数名是argspublicstatic用来修饰方法,这里表示它是一个公开的静态方法,void是方法的返回类型,而花括号{}中间的就是方法的代码
      • 方法的代码每一行用;结束
      • 把代码保存为文件时,文件名必须是Hello.java,而且文件名也要注意大小写
    • 运行Java程序

      • 需要先用javacHello.java编译成字节码文件Hello.class,然后,用java命令执行这个字节码文件:
      ┌──────────────────┐
      │    Hello.java    │◀── source code
      └──────────────────┘
                │ compile
                ▼
      ┌──────────────────┐
      │   Hello.class    │◀── byte code
      └──────────────────┘
                │ execute
                ▼
      ┌──────────────────┐
      │    Run on JVM    │
      └──────────────────┘
      
      • 第一步,在保存Hello.java的目录下执行命令javac Hello.java

        $ javac Hello.java
        

        第二步,执行Hello.class,使用命令java Hello(注意不是java Hello.class):

        $ java Hello
        Hello, world!
        
    • 注释

      • 第一种是单行注释,以双斜线开头,直到这一行的结尾结束:

        // 这是注释
        

        而多行注释以/*星号开头,以*/结束,可以有多行:

        /*
        这是注释
        */
        

        特殊的多行注释,以/**开头,以*/结束,如果有多行,每行通常以星号开头:

        /**
         * 可以用来自动创建文档的注释
         * 
         * @auther wang
         */
        
  • 变量和数据结构

    • 变量:

      • 基本类型变量

        • 在Java中,变量必须先定义后使用,在定义变量的时候,可以给它一个初始值。例如:

          int x = 1;
          
        • 不写初始值,就相当于给它指定了默认值。

        • 变量的一个重要特点是可以重新赋值

+ 引用类型变量
  • 基本数据类型:CPU可以直接进行运算的类型

    • 整数类型:byte,short,int,long

      浮点数类型:float,double

      字符类型:char

      布尔类型:boolean

    • 不同数据类型占用的字节数不一样。Java基本数据类型占用的字节数:

             ┌───┐
        byte │   │
             └───┘
             ┌───┬───┐
       short │   │   │
             └───┴───┘
             ┌───┬───┬───┬───┐
         int │   │   │   │   │
             └───┴───┴───┴───┘
             ┌───┬───┬───┬───┬───┬───┬───┬───┐
        long │   │   │   │   │   │   │   │   │
             └───┴───┴───┴───┴───┴───┴───┴───┘
             ┌───┬───┬───┬───┐
       float │   │   │   │   │
             └───┴───┴───┴───┘
             ┌───┬───┬───┬───┬───┬───┬───┬───┐
      double │   │   │   │   │   │   │   │   │
             └───┴───┴───┴───┴───┴───┴───┴───┘
             ┌───┬───┐
        char │   │   │
             └───┴───┘
      

      byte恰好就是一个字节,而longdouble需要8个字节

      计算机内存的最小存储单元是字节(byte),一字节就是一8位二进制数,即8个bit

  • 整型

    • 同一个数的不同进制的表示是完全相同的,例如15=0xf0b1111
  • 浮点数

    • 浮点类型的数就是小数

    • float f1 = 3.14f;
      float f2 = 3.14e38f; // 科学计数法表示的3.14x10^38
      float f3 = 1.0; // 错误:不带f结尾的是double类型,不能赋值给float
      
      double d3 = 4.9e-324; // 科学计数法表示的4.9x10^-324
      
  • 布尔类型booleantruefalse

    • Java语言对布尔类型存储没有规定,理论上存储布尔类型只需要1 bit,但是通常JVM内部会把boolean表示为4字节整数
  • 字符类型

    • 字符类型char表示一个字符。使用单引号',且仅有一个字符

    • Java的char类型除了可表示标准的ASCII外,还可以表示一个Unicode字符

      char a = 'A';
      char zh = '中';
      
  • 引用类型

    • 引用类型的变量类似于C语言的指针,内部存储一个“地址”,指向某个对象在内存的位置

    • 除了上述基本类型的变量,剩下的都是引用类型

    • 例如,引用类型最常用的就是String字符串:

      String s = "hello";
      
  • 常量

    • 定义变量的时候,如果加上final修饰符

    • 常量在定义时进行初始化后就不可再次赋值,再次赋值会导致编译错误

    • 为了和变量区分开来,根据习惯,常量名通常全部大写

      final double PI = 3.14; // PI是一个常量
      double r = 5.0;
      double area = PI * r * r;
      PI = 300; // compile error!
      
  • var关键字

    • 有些时候,类型的名字太长,写起来比较麻烦。例如:

      StringBuilder sb = new StringBuilder();
      
    • 如果想省略变量类型,可以使用var关键字:

      var sb = new StringBuilder();
      
    • 使用var定义变量,仅仅是少写了变量类型而已

    • 如果想省略变量类型,可以使用var关键字:

      var sb = new StringBuilder();
      

      编译器根据赋值语句推断变量sb的类型StringBuilder。对编译器来说,语句:

      var sb = new StringBuilder();
      
  • 变量的作用范围

    • 在Java中,多行语句用{ ... }括起来。很多控制语句,例如条件判断和循环,都以{ ... }作为它们自身的范围,例如:

      if (...) { // if开始
          ...
          while (...) { // while 开始
              ...
              if (...) { // if开始
                  ...
              } // if结束
              ...
          } // while结束
          ...
      } // if结束
      
  • 整数运算

    • 整数运算遵循四则运算,可使用任意嵌套的小括号。四则运算规则和初等数学一致

      int i = (100 + 200) * (99 - 88); // 3300
      int n = 7 * (5 + (i - 9)); // 23072
      
    • 整数的数值不但是精确的,而且整数运算永远是精确的,即使除法也是精确,因为两个整数相除只能得到结果的整数部分:

      int x = 12345 / 67; // 184
      

      求余运算使用%

      int y = 12345 % 67; // 12345÷67的余数是17
      

      整数的除法对于除数为0时运行时将报错,但编译不会报错

    • 溢出

      • 整数由于存在范围限制,如果计算结果超出了范围,就会产生溢出,而溢出不会出错,却会得到一个奇怪的结果

        int x = 2147483640;
        int y = 15;
        int sum = x + y;
        System.out.println(sum); // -2147483641
        
      • 要解决上面的问题,可以把int换成long类型,由于long可表示的整型范围更大

        long x = 2147483640;
        long y = 15;
        long sum = x + y;
        System.out.println(sum); // 2147483655
        
    • 自增自减

      • 注意++写在前面和后面计算结果是不同的,++n表示先加1再引用n,n++表示先引用n再加1
      • 不建议把++运算混入到常规运算中,容易自己把自己搞懵了
    • 移位运算

      • 左移实际上就是不断地×2,右移实际上就是不断地÷2

      • 正数左移

        int n = 7;       // 00000000 00000000 00000000 00000111 = 7
        int a = n << 1;  // 00000000 00000000 00000000 00001110 = 14
        int b = n << 2;  // 00000000 00000000 00000000 00011100 = 28
        int c = n << 28; // 01110000 00000000 00000000 00000000 = 1879048192
        int d = n << 29; // 11100000 00000000 00000000 00000000 = -536870912
        
      • 正数右移

        int n = 7;       // 00000000 00000000 00000000 00000111 = 7
        int a = n >> 1;  // 00000000 00000000 00000000 00000011 = 3
        int b = n >> 2;  // 00000000 00000000 00000000 00000001 = 1
        int c = n >> 3;  // 00000000 00000000 00000000 00000000 = 0
        
      • 负数右移:最高位的1不动,结果仍然是一个负数

        int n = -536870912;
        int a = n >> 1;  // 11110000 00000000 00000000 00000000 = -268435456
        int b = n >> 2;  // 11111000 00000000 00000000 00000000 = -134217728
        int c = n >> 28; // 11111111 11111111 11111111 11111110 = -2
        int d = n >> 29; // 11111111 11111111 11111111 11111111 = -1
        
      • 无符号的右移运算,使用>>>

        不管符号位,右移后高位总是补0,因此,对一个负数进行>>>右移,它会变成正数,原因是最高位的1变成了0

        int n = -536870912;
        int a = n >>> 1;  // 01110000 00000000 00000000 00000000 = 1879048192
        int b = n >>> 2;  // 00111000 00000000 00000000 00000000 = 939524096
        int c = n >>> 29; // 00000000 00000000 00000000 00000111 = 7
        int d = n >>> 31; // 00000000 00000000 00000000 00000001 = 1
        
    • 位运算

      • 位运算是按位进行与、或、非和异或的运算
        • 与运算:必须两个数同时为1,结果才为1
        • 或运算:只要任意一个为1,结果就为1
        • 非运算:01互换
        • 异或运算:如果两个数不同,结果为1,否则为0
      • 与运算可以看作两个整数的IP地址10.0.17.7710.0.17.0,通过与运算,可以**快速判断一个IP是否在给定的网段内**
    • 类型自动提升与强制转型

      • 运算过程中,如果参与运算的两个数类型不一致,那么计算结果为较大类型的整型

        short s = 1234;
        int i = 123456;
        int x = s + i; // s自动转型为int
        short y = s + i; // 编译错误!
        
      • 将结果强制转型,即将大范围的整数转型为小范围的整数。强制转型使用(类型)

        int i = 12345;
        short s = (short) i; // 12345
        

        要注意,超出范围的强制转型会得到错误的结果

  • 浮点数运算

    • 浮点数运算和整数运算相比,只进行加减乘除这些数值计算,不能做位运算和移位运算

    • 浮点数常常无法精确表示,比如0.1在计算机中就无法精确表示

    • 由于浮点数存在运算误差,所以比较两个浮点数是否相等常常会出现错误的结果。正确的比较方法是判断两个浮点数之差的绝对值是否小于一个很小的数:

      // 比较x和y是否相等,先计算其差的绝对值:
      double r = Math.abs(x - y);
      // 再判断绝对值是否足够小:
      if (r < 0.00001) {
          // 可以认为相等
      } else {
          // 不相等
      }
      
    • 类型提升

      • 如果参与运算的两个数其中一个是整型,那么整型可以自动提升到浮点型

        int n = 5;
        double d = 1.2 + 24.0 / n; // 6.0
        
      • 在一个复杂的四则运算中,两个整数的运算不会出现自动提升的情况。例如:

        double d = 1.2 + 24 / 5; // 结果不是 6.0 而是 5.2
        
    • 溢出

      • 整数运算在除数为0时会报错,而浮点数运算在除数为0时,不会报错

        double d1 = 0.0 / 0; // NaN
        double d2 = 1.0 / 0; // Infinity
        double d3 = -1.0 / 0; // -Infinity
        
    • 强制转型

      • 将浮点数强制转型为整数。在转型时,浮点数的小数部分会被丢掉。如果转型后超过了整型能表示的最大范围,将返回整型的最大值

        int n1 = (int) 12.3; // 12
        int n2 = (int) 12.7; // 12
        int n3 = (int) -12.7; // -12
        int n4 = (int) (12.7 + 0.5); // 13
        int n5 = (int) 1.2e20; // 2147483647
        
  • 布尔运算

    • 对于布尔类型boolean,永远只有truefalse两个值

      boolean isGreater = 5 > 3; // true
      int age = 12;
      boolean isZero = age == 0; // false
      boolean isNonZero = !isZero; // true
      boolean isAdult = age >= 18; // false
      boolean isTeenager = age >6 && age <18; // true
      
    • 短路运算:

      如果布尔运算的表达式能提前确定结果,则后续的计算不再执行,直接返回结果

      // 短路运算
      public class Main {
          public static void main(String[] args) {
              boolean b = 5 < 3;
              boolean result = b && (5 / 0 > 0); // 此处 5 / 0 不会报错
              System.out.println(result);
          }
      }
      
    • 三元运算符b ? x : y

      三元运算b ? x : y会首先计算b,如果btrue,则只计算x,否则,只计算y。此外,xy的类型必须相同,因为返回值不是boolean,而是xy之一

  • 字符和字符串

    • 字符类型char是基本数据类型,一个char保存一个Unicode字符:

      char c1 = 'A';
      char c2 = '中';
      

      直接用转义字符\u+Unicode编码来表示一个字符:

      // 注意是十六进制:
      char c3 = '\u0041'; // 'A',因为十六进制0041 = 十进制65
      char c4 = '\u4e2d'; // '中',因为十六进制4e2d = 十进制20013
      
    • 字符串String

      是引用类型,我们用双引号"..."表示字符串。一个字符串可以存储0个到任意个字符

      String s = "ABC\n\u4e2d\u6587"; // 包含6个字符: A, B, C, 换行符, 中, 文
      
    • 字符串连接

      String s1 = "Hello";
      String s2 = "world";
      String s = s1 + " " + s2 + "!";
      System.out.println(s); // Hello world!
      

      +连接字符串和其他数据类型,会将其他数据类型先自动转型为字符串,再连接:

      int age = 25;
      String s = "age is " + age;
      System.out.println(s); // age is 25 
      
    • 多行字符串

      如果我们要表示多行字符串,使用+号连接会非常不方便:

      String s = "first line \n"
               + "second line \n"
               + "end";
      

      从Java 13开始,字符串可以用"""..."""表示多行字符串(Text Blocks)了。eg

      // 多行字符串
      public class Main {
          public static void main(String[] args) {
              String s = """
                         SELECT * FROM
                           users
                         WHERE id > 100
                         ORDER BY name DESC
                         """;
              System.out.println(s);
          }
      }
      
    • 不可变特性

      public class Main {
          public static void main(String[] args) {
              String s = "hello";
              System.out.println(s); // 显示 hello
              s = "world";
              System.out.println(s); // 显示 world
          }
      }
      

      变的不是字符串,而是变量s的“指向”

    • 空值null :指向一个空值null,表示不存在,即该变量不指向任何对象

      String s1 = null; // s1是null
      String s2 = s1; // s2也是null
      String s3 = ""; // s3指向空字符串,不是null
      

      区分空值null和空字符串"",空字符串是一个有效的字符串对象,它不等于null

  • 数组类型

    • Java的数组有几个特点:

      • 数组所有元素初始化为默认值,整型都是0,浮点型是0.0,布尔型是false
      • 数组一旦创建后,大小就不可改变。
    • 注意数组是引用类型,并且数组大小不可变。我们观察下面的代码:

      public class Main {
          public static void main(String[] args) {
              // 5位同学的成绩:
              int[] ns;
              ns = new int[] { 68, 79, 91, 85, 62 };
              System.out.println(ns.length); // 5
              ns = new int[] { 1, 2, 3 };
              System.out.println(ns.length); // 3
          }
      }
      

      原有的5个元素的数组并没有改变,只是无法通过变量ns引用到它们而已

    • 字符串数组

      • public class Main {
            public static void main(String[] args) {
                String[] names = {"ABC", "XYZ", "zoo"};
                String s = names[1];
                names[1] = "cat";
                System.out.println(s); // "XYZ"
            }
        }
        
    • 数组是同一数据类型的集合,数组一旦创建后,大小就不可变;

      可以通过索引访问数组元素,但索引超出范围将报错;

      数组元素可以是值类型(如int)或引用类型(如String),但数组本身是引用类型

流程控制

  • 输入输出

    • 输出

      System.out.printf(),通过使用占位符%?printf()可以把后面的参数格式化成指定格式:

      // 格式化输出
      public class Main {
          public static void main(String[] args) {
              double d = 3.1415926;
              System.out.printf("%.2f\n", d); // 显示两位小数3.14
              System.out.printf("%.4f\n", d); // 显示4位小数3.1416
          }
      }
      
      占位符说明
      %d格式化输出整数
      %x格式化输出十六进制整数
      %f格式化输出浮点数
      %e格式化输出科学计数法表示的浮点数
      %s格式化字符串
    • 输入

      import java.util.Scanner;
      
      public class Main {
          public static void main(String[] args) {
              Scanner scanner = new Scanner(System.in); // 创建Scanner对象
              System.out.print("Input your name: "); // 打印提示
              String name = scanner.nextLine(); // 读取一行输入并获取字符串
              System.out.print("Input your age: "); // 打印提示
              int age = scanner.nextInt(); // 读取一行输入并获取整数
              System.out.printf("Hi, %s, you are %d\n", name, age); // 格式化输出
          }
      }
      
      $ java Main
      Input your name: Bob ◀── 输入 Bob
      Input your age: 12   ◀── 输入 12
      Hi, Bob, you are 12  ◀── 输出
      
  • if条件判断

    • // 条件判断
      public class Main {
          public static void main(String[] args) {
              int n = 90;
              if (n > 90) {
                  System.out.println("优秀");
              } else if (n >= 60) {
                  System.out.println("及格了");
              } else {
                  System.out.println("挂科了");
              }
          }
      }
      
    • 前面讲过了浮点数在计算机中常常无法精确表示,并且计算可能出现误差,因此,判断浮点数相等用==判断不靠谱:

      // 条件判断
      public class Main {
          public static void main(String[] args) {
              double x = 1 - 9.0 / 10;
              if (x == 0.1) {
                  System.out.println("x is 0.1");
              } else {
                  System.out.println("x is NOT 0.1");
              }
          }
      }
      

      正确的方法是利用差值小于某个临界值来判断:

      // 条件判断
      public class Main {
          public static void main(String[] args) {
              double x = 1 - 9.0 / 10;
              if (Math.abs(x - 0.1) < 0.00001) {
                  System.out.println("x is 0.1");
              } else {
                  System.out.println("x is NOT 0.1");
              }
          }
      }
      
    • 判断引用类型相等

      • 使用==运算符。但是,判断引用类型的变量是否相等,==表示“引用是否相等”,或者说,是否指向同一个对象

        public class Main {
            public static void main(String[] args) {
                String s1 = "hello";
                String s2 = "HELLO".toLowerCase();
                System.out.println(s1);
                System.out.println(s2);
                if (s1 == s2) {
                    System.out.println("s1 == s2");
                } else {
                    System.out.println("s1 != s2");
                }
            }
        }
        
        hello
        hello
        s1 != s2
        
      • 判断引用类型的变量内容是否相等,必须使用equals()方法

        public class Main {
            public static void main(String[] args) {
                String s1 = "hello";
                String s2 = "HELLO".toLowerCase();
                System.out.println(s1);
                System.out.println(s2);
                if (s1.equals(s2)) {
                    System.out.println("s1 equals s2");
                } else {
                    System.out.println("s1 not equals s2");
                }
            }
        }
        
        hello
        hello
        s1 equals s2
        
      • 执行语句s1.equals(s2)时,如果变量s1null,会报NullPointerException

        // 条件判断
        public class Main {
            public static void main(String[] args) {
                String s1 = null;
                if (s1.equals("hello")) {
                    System.out.println("hello");
                }
            }
        }
        

        要避免NullPointerException错误,可以利用短路运算符&&

        // 条件判断
        public class Main {
            public static void main(String[] args) {
                String s1 = null;
                if (s1 != null && s1.equals("hello")) {
                    System.out.println("hello");
                }
            }
        }
        
  • switch多重选择

    • public class Main {
          public static void main(String[] args) {
              int option = 99;
              switch (option) {
              case 1:
                  System.out.println("Selected 1");
                  break;
              case 2:
                  System.out.println("Selected 2");
                  break;
              case 3:
                  System.out.println("Selected 3");
                  break;
              default:
                  System.out.println("Selected other");
                  break;
              }
          }
      }
      

      switch语句根据switch (表达式)计算的结果,跳转到匹配的case结果,然后继续执行后续语句,直到遇到break结束执行

      当没有匹配到任何case时,执行default

    • 使用switch时,注意**case语句并没有花括号{}**,而且,case语句具有“穿透性”,漏写break将导致意想不到的结果:

      // switch
      public class Main {
          public static void main(String[] args) {
              int option = 2;
              switch (option) {
              case 1:
                  System.out.println("Selected 1");
              case 2:
                  System.out.println("Selected 2");
              case 3:
                  System.out.println("Selected 3");
              default:
                  System.out.println("Selected other");
              }
          }
      }
      

      option = 2时,将依次输出"Selected 2""Selected 3""Selected other"

    • 编译检查

      使用IDE时,可以自动检查是否漏写了break语句和default语句

      在Idea中,选择Preferences - Editor - Inspections - Java - Control flow issues,将以下检查标记为Warning:

      • ‘switch’ statement without ‘default’ branch:缺少default语句时警告;
      • Fallthrough in ‘switch’ statement:某个case缺少break时警告。

      switch语句存在问题时,即可在IDE中获得警告提示。

    • 注意新语法使用->,如果有多条语句,需要用{}括起来。不要写break语句,因为新语法只会执行匹配的语句,没有穿透效应

      public class Main {
          public static void main(String[] args) {
              String fruit = "apple";
              switch (fruit) {
              case "apple" -> System.out.println("Selected apple");
              case "pear" -> System.out.println("Selected pear");
              case "mango" -> {
                  System.out.println("Selected mango");
                  System.out.println("Good choice!");
              }
              default -> System.out.println("No fruit selected");
              }
          }
      }
      
    • yield

      如果需要复杂的语句,我们也可以写很多语句,放到{...}里,然后,用yield返回一个值作为switch语句的返回值

      public class Main {
          public static void main(String[] args) {
              String fruit = "orange";
              int opt = switch (fruit) {
                  case "apple" -> 1;
                  case "pear", "mango" -> 2;
                  default -> {
                      int code = fruit.hashCode();
                      yield code; // switch语句返回值
                  }
              };
              System.out.println("opt = " + opt);
          }
      }
      
  • while循环

    • public class Main {
          public static void main(String[] args) {
              int sum = 0; // 累加的和,初始化为0
              int n = 1;
              while (n <= 100) { // 循环条件是n <= 100
                  sum = sum + n; // 把n累加到sum中
                  n ++; // n自身加1
              }
              System.out.println(sum); // 5050
          }
      }
      

      死循环将导致100%的CPU占用,用户会感觉电脑运行缓慢,要避免编写死循环代码

    • 表面上死循环,Java的int类型有最大值,达到最大值后,再加1会变成负数,结果,意外退出了while循环

      public class Main {
          public static void main(String[] args) {
              int sum = 0;
              int n = 1;
              while (n > 0) {
                  sum = sum + n;
                  n ++;
              }
              System.out.println(n); // -2147483648
              System.out.println(sum);
          }
      }
      
  • do while循环

    • while循环:先判断循环条件,再执行循环

    • do while循环:先执行循环,再判断条件

    • 把对1到100的求和用do while循环改写一下:

      // do-while
      public class Main {
          public static void main(String[] args) {
              int sum = 0;
              int n = 1;
              do {
                  sum = sum + n;
                  n ++;
              } while (n <= 100);
              System.out.println(sum);
          }
      }
      
  • for循环

    • for (初始条件; 循环检测条件; 循环后更新计数器) {
          // 执行语句
      }
      
    • public class Main {
          public static void main(String[] args) {
              int sum = 0;
              for (int i=1; i<=100; i++) {
                  sum = sum + i;
              }
              System.out.println(sum);
          }
      }
      

      注意for循环的初始化计数器总是会被执行,并且for循环也可能循环0次

    • for each循环

      public class Main {
          public static void main(String[] args) {
              int[] ns = { 1, 4, 9, 16, 25 };
              for (int n : ns) {
                  System.out.println(n);
              }
          }
      }
      

      除了数组外,for each循环能够遍历所有“可迭代”的数据类型,包括后面会介绍的ListMap

  • breakcontinue

    • break语句跳出当前循环,总是跳出自己所在的那一层循环
    • continue语句可以提前结束本次循环

数组操作:遍历,排序

  • 遍历

    • public class Main {
          public static void main(String[] args) {
              int[] ns = { 1, 4, 9, 16, 25 };
              for (int i=0; i<ns.length; i++) {
                  int n = ns[i];
                  System.out.println(n);
              }
          }
      }
      
    • public class Main {
          public static void main(String[] args) {
              int[] ns = { 1, 4, 9, 16, 25 };
              for (int n : ns) {
                  System.out.println(n);
              }
          }
      }
      
  • 数组排序

    • // 冒泡排序
      import java.util.Arrays;
      
      public class Main {
          public static void main(String[] args) {
              int[] ns = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 };
              // 排序前:
              System.out.println(Arrays.toString(ns));
              for (int i = 0; i < ns.length - 1; i++) {
                  for (int j = 0; j < ns.length - i - 1; j++) {
                      if (ns[j] > ns[j+1]) {
                          // 交换ns[j]和ns[j+1]:
                          int tmp = ns[j];
                          ns[j] = ns[j+1];
                          ns[j+1] = tmp;
                      }
                  }
              }
              // 排序后:
              System.out.println(Arrays.toString(ns));
          }
      }
      

      特点:每一轮循环后,最大的一个数被交换到末尾,因此,下一轮循环就可以“刨除”最后的数,每一轮循环都比上一轮循环的结束位置靠前一位

    • Arrays.sort()就可以排序

      // 排序
      import java.util.Arrays;
      
      public class Main {
          public static void main(String[] args) {
              int[] ns = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 };
              Arrays.sort(ns);
              System.out.println(Arrays.toString(ns));
          }
      }
      
      • 对数组排序实际上修改了数组本身。例如,排序前的数组是:

        int[] ns = { 9, 3, 6, 5 };
        

        在内存中,这个整型数组表示如下:

              ┌───┬───┬───┬───┐
        ns───▶│ 9 │ 3 │ 6 │ 5 │ 
              └───┴───┴───┴───┘  
        

        当我们调用Arrays.sort(ns);后,这个整型数组在内存中变为:

              ┌───┬───┬───┬───┐
        ns───▶│ 3 │ 5 │ 6 │ 9 │
              └───┴───┴───┴───┘
        

        即变量ns指向的数组内容已经被改变了

      • 对一个字符串数组进行排序,例如:

        String[] ns = { "banana", "apple", "pear" };
        

        排序前,这个数组在内存中表示如下:

                           ┌──────────────────────────────────┐
                       ┌───┼──────────────────────┐           │
                       │   │                      ▼           ▼
                 ┌───┬─┴─┬─┴─┬───┬────────┬───┬───────┬───┬──────┬───┐
        ns ─────▶│░░░│░░░│░░░│   │"banana"│   │"apple"│   │"pear"│   │
                 └─┬─┴───┴───┴───┴────────┴───┴───────┴───┴──────┴───┘
                   │                 ▲
                   └─────────────────┘
        

        调用Arrays.sort(ns);排序后,这个数组在内存中表示如下:

                           ┌──────────────────────────────────┐
                       ┌───┼──────────┐                       │
                       │   │          ▼                       ▼
                 ┌───┬─┴─┬─┴─┬───┬────────┬───┬───────┬───┬──────┬───┐
        ns ─────▶│░░░│░░░│░░░│   │"banana"│   │"apple"│   │"pear"│   │
                 └─┬─┴───┴───┴───┴────────┴───┴───────┴───┴──────┴───┘
                   │                              ▲
                   └──────────────────────────────┘
        

        原来的3个字符串在内存中均没有任何变化,但是ns数组的每个元素指向变化

  • 二维数组

    • 多维数组的每个数组元素长度都不要求相同

    • 果我们定义一个普通数组arr0,然后把ns[0]赋值给它:

      // 二维数组
      public class Main {
          public static void main(String[] args) {
              int[][] ns = {
                  { 1, 2, 3, 4 },
                  { 5, 6, 7, 8 },
                  { 9, 10, 11, 12 }
              };
              int[] arr0 = ns[0];
              System.out.println(arr0.length); // 4
          }
      }
      

      arr0就获取了ns数组的第0个元素。因为ns数组的每个元素也是一个数组,因此,arr0指向的数组就是{ 1, 2, 3, 4 }

                  arr0 ─────┐
                            ▼
                          ┌───┬───┬───┬───┐
               ┌───┐  ┌──▶│ 1 │ 2 │ 3 │ 4 │
      ns ─────▶│░░░│──┘   └───┴───┴───┴───┘
               ├───┤      ┌───┬───┬───┬───┐
               │░░░│─────▶│ 5 │ 6 │ 7 │ 8 │
               ├───┤      └───┴───┴───┴───┘
               │░░░│──┐   ┌───┬───┬───┬───┐
               └───┘  └──▶│ 9 │10 │11 │12 │
                          └───┴───┴───┴───┘
      
  • 命令行参数

    • 这个命令行参数由JVM接收用户输入并传给main方法:
    public class Main {
        public static void main(String[] args) {
            for (String arg : args) {
                System.out.println(arg);
            }
        }
    }