一、入门介绍

  1. 回顾:

    • C 语言通过编译,将高级语言代码翻译为计算机能够直接执行的指令;
    • Python 并不会先进行编译,而是直接交给解释器解释执行
      1
      print("Hello World!")
      image-20220916150119407
  2. 一般来说,编程语言分为两大类:

    • 编译型语言:需要先编译为计算机可以直接执行的命令才可以运行。优点是计算机直接运行,性能高;缺点是与平台密切相关,在一种操作系统上编译的程序,无法在其他非同类操作系统上运行,比如 Windows 下的 exe 程序在 Mac 上就无法运行。
    • 解释型语言:只需要通过解释器代为执行即可,不需要进行编译。优点是可以跨平台,因为解释是解释器的事情,只需要在各个平台上安装对应的解释器,代码不需要任何修改就可以直接运行;缺点是需要依靠解释器解释执行,效率肯定没直接编译成机器指令运行的快,并且会产生额外的资源占用。
  3. Java语言(Java之父:James Gosling,詹姆斯·高斯林)

    Write Once, Run Anywhere.

    这是Java语言的标语,它的目标很明确:一次编写,到处运行,它旨在打破平台的限制,让Java语言可以运行在任何平台上,并且不需要重新编译,实现跨平台运行。
    Java自1995年正式推出以来,已经度过了快28个春秋,而基于Java语言,我们的生活中也有了各种各样的应用:
    image-20220916151604563

    • 诺基亚手机上的很多游戏都是使用Java编写的。
    • 安卓系统中的各种应用程序也是使用Java编写的。
    • 著名沙盒游戏《Minecraft》也有对应的Java版本,得益于Java跨平台特性,无论在什么操作系统上都可以玩到这款游戏。
  4. Java 运行机制
    实际上,Java程序也是需要进行编译才可以运行的,这一点与C语言是一样的,Java程序编译之后会变成 .class 结尾的二进制文件:
    image-20220916153102763
    不过不同的是,这种二进制文件计算机并不能直接运行,而是需要交给JVM(Java虚拟机)执行。
    image-20220916152514450
    JVM 类似于 Python 解释器,将编译完成的 .class 文件交给 JVM 运行,程序中要做的事情,都由 JVM 告诉计算机该如何去执行。
    在不同的操作系统下,都有对应的 JVM 实现,程序员只需要将 Java 程序编译为 .class 文件就可以直接交给 JVM 运行,无论是什么操作系统,JVM 都采用的同一套标准读取和执行 .class 文件,所以编译之后,在任何平台都可以运行,从而实现跨平台。
    由于 Java 又需要编译同时还需要依靠 JVM 解释执行,所以说 Java 既是编译型语言,也是解释型语言。

  5. Java 版本

    • **JavaSE:**标准版 Java
    • **JavaME:**微缩版 Java,已经基本没人用了。
    • **JavaEE:**企业级 Java,比如网站开发。

二、环境安装

(一)JDK 安装

  1. JRE 和 JDK 区别
    • JRE(Java Runtime Environment):Java 的运行环境,安装了运行环境之后,Java 程序才可以运行,一般不做开发,只是需要运行 Java 程序直接按照 JRE 即可。
    • JDK(Java Development Kit):包含 JRE,并且还附带了大量开发者工具。
      image-20220916154906732
  2. JDK 安装
  3. jenv 安装

(二)Java 版本管理

查看当前 Java 版本:

1
java --version

查看已安装的所有版本:

1
jenv versions

切换不同版本:

1
jenv global 1.8

添加新安装版本:

1
jenv add /Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home

删除已安装版本:

1
jenv remove xxx

三、Java 基础

(一)基础语法

  1. main 主方法,是整个程序的入口点,执行过程从 main 主方法内开始从下往下依次执行。

    1
    2
    3
    4
    5
    6
    7
    public class Main {
    public static void main(String[] args){
    System.out.println("Hello World!");
    System.out.println("Hello");
    System.out.println("World");
    }
    }

    ⚠️

    • Java 中严格区分大小写;
    • 每行代码写完后需要添加分号;
  2. 注释的使用

    • 单行注释 //
    • 多行注释 /* xxx */
    • 说明文档 /** xxx */
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * @author angfff
    */
    public class Main {
    public static void main(String[] args){
    System.out.println("Hello World!"); //输出语句
    /*
    多行注释
    Test
    */
    }
    }
  3. 变量

    • 变量声明

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      [数据类型] [变量名称];

      public class Main {
      public static void main(String[] args){
      // 方法一
      int X; // 定义整数类型变量 X
      X = 10; // 为 X 赋值为 10
      // 方法二
      int Y = 20; // 定义变量 Y 并赋值为 20
      // 方法三
      int Z = Y; // 定义变量 Z 并赋值为 Y 的值
      }
      }
    • 关键字 final : 在变量前添加 final 关键字,可以将变量定义为不可变变量(常量),也即只能进行一次赋值,后续不能对其进行修改。

      1
      2
      3
      4
      5
      6
      7
      8
      public class Main {
      public static void main(String[] args){
      final int x; // 常量 x
      x = 10;
      x = 20; // 报错,无法进行修改
      System.out.println(x);
      }
      }

(二)基本数据类型

  1. 整数类型

    • byte 字节型 (8 个 bit,也就是 1 个字节)范围:-128 ~ +127
    • short 短整形(16 个 bit,也就是 2 个字节 / 一个字)范围:-32768 ~ +32767
    • int 整形(32 个 bit,也就是 4 个字节 / 双字)最常用的类型,范围:-2147483648 ~ +2147483647
    • long 长整形(64 个 bit,也就是 8 个字节 / 四字)范围:-9223372036854775808 ~ +9223372036854775807

    补充:隐式类型转换

    • 从存储范围小的类型到存储范围大的类型,会发生隐式类型转换,自动将某种类型的值,转换为另一种类型。
    • 实际上我们在为变量赋一个常量数值时,也发生了隐式类型转换,比如:
    1
    2
    3
    public static void main(String[] args) {
    byte b = 10; //这里的整数常量10,实际上默认情况下是int类型,但是由于正好在对应类型可以表示的范围内,所以说直接转换为了byte类型的值
    }
    • 由于直接编写的整数常量值默认为int,这里需要特别注意一下,比如下面这种情况:

      image-20220916232420547

      按照 long 类型的规定,实际上是可以表示这么大的数字的,但是为什么这里报错了呢?这是因为我们直接在代码中写的常量数字,默认情况下就是 int 类型,这么大肯定是表示不下的,如果需要将其表示为一个 long 类型的常量数字,那么需要在后面添加大写或是小写的 L 才可以。

    1
    2
    3
    public static void main(String[] args) {
    long a = 922337203685477580L; //这样就可以正常编译通过了
    }
    • 针对于这种很长的数字,为了提升辨识度,我们可以使用下划线分割每一位:
    1
    2
    3
    public static void main(String[] args) {
    int a = 1_000_000; //当然这里依然表示的是1000000,没什么区别,但是辨识度会更高
    }
    • 我们也可以以8进制或是16进制表示一个常量值:
      • **十六进制:**以 0x 开头的都是十六进制表示法
      • **八进制:**以 0 开头的都是八进制表示法
    1
    2
    3
    4
    public static void main(String[] args) {
    System.out.println(0xA);
    System.out.println(012);
    }
  2. 浮点类型

    • float 单精度浮点数(32bit,4 字节)
    • double 双精度浮点数(64bit,8 字节)

    image-20220917102209246

    根据国际标准 IEEE 754,任意一个二进制浮点数 V 可以表示成下面的形式:

    V=(1)S×M×2EV = (-1)^S \times M \times 2^E

    • (-1)^S 表示符号位,当 S=0,V 为正数;当 S=1,V 为负数。
    • M 表示有效数字,大于等于 1,小于 2,但整数部分的 1 不变,因此可以省略。(例如尾数为 1111010,那么 M 实际上就是1.111010,尾数首位必须是 1,1 后面紧跟小数点,如果出现0001111 这样的情况,去掉前面的 0,移动 1 到首位;题外话:随着时间的发展,IEEE 754标准默认第一位为 1,故为了能够存放更多数据,就舍去了第一位,比如保存 1.0101 的时候, 只保存 0101,这样能够多存储一位数据)
    • 2^E 表示指数位。(用于移动小数点,所以说才称为浮点型)

    浮点类型的大致取值范围:

    • 单精度:±3.40282347*10^38
    • 双精度:±1.79769313486231570 *10^308

    ⚠️ 跟整数类型常量一样,小数类型常量默认都是double类型,所以说如果我们直接给一个float类型赋值:

    image-20220917105141288

    由于float类型的精度不如double,如果直接给其赋一个double类型的值,会直接出现错误。

    同样的,我们可以给常量后面添加大写或小写的 F 来表示这是一个float类型的常量值:

    1
    2
    3
    public static void main(String[] args) {
    float f = 9.9F; //这样就可以正常编译通过了
    }

    ⚠️ 隐式类型转换规则总结:

    byte → short(char) → int → long → float → double

  3. 字符类型

    • char 字符型(16个bit,2字节,不带符号)范围 0 ~ 65535

    img

    • String 字符串类型

    ⚠️ ’ ’ 单引号用于 char 字符类型;" " 双引号用于 String 字符串类型

  4. 布尔类型

    • true - 真
    • false - 假

    布尔类型(boolean)只有truefalse两种值,也就是要么为真,要么为假,布尔类型的变量通常用作流程控制判断语句(不同于C语言,C语言中一般使用0表示false,除0以外的所有数都表示true)布尔类型占据的空间大小并未明确定义,而是根据不同的JVM会有不同的实现。

    1
    2
    3
    4
    public static void main(String[] args) {
    boolean b = true; //值只能是 true 或 false
    System.out.println(b);
    }

(三)运算符

  1. 赋值运算符

    赋值运算符可以直接给某个变量赋值:

    1
    2
    3
    public static void main(String[] args) {
    int a = 666; //使用等号进行赋值运算
    }

    **使用规则为:**赋值运算符的左边必须是一个可以赋值的目标,比如变量,右边可以是任意满足要求的值,包括变量。

  2. 算术运算符

    • " + " 加法运算,除了支持算数运算外,还可以对字符串进行拼接

      1
      2
      3
      4
      public static void main(String[] args) {
      String str = "Hello" + "abc";
      System.out.println(str);
      }
      1
      2
      3
      4
      public static void main(String[] args) {
      String str = "Hello" + true + 1.5 + 'A';
      System.out.println(str);
      }
  • " - " 减法运算

  • " * " 乘法运算

  • " / " 除法运算

    两个整数在进行除法运算时,得到的结果也是整数(会直接砍掉小数部分,注意不是四舍五入)

    1
    2
    3
    4
    public static void main(String[] args) {
    int a = 8, b = 5;
    System.out.println(a / b); // 结果为 1
    }
  • " % " 取模(取余数)运算

⚠️ 不同类型之间可以进行运算:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
int a = 5;
short b = 10;
int c = a + b;
//不同类型的整数一起运算,小类型需要转换为大类型;
//其中 short、byte、char 一律转换为 int 再进行计算(无论算式中有无 int,都需要转换),结果也是 int ;
//如果算式中出现了long类型,那么全部都需要转换到long类型再进行计算,结果也是long,反正就是依大的来
}
  1. 括号运算符

    • 提升优先级

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public static void main(String[] args) {
      int a = 10;
      int b = (a = 8) * (-a + 10);
      /*
      1. 括号的优先级是最高的,需要先计算括号中的内容,如果存在多个括号,就从左往右计算
      2. 首先是 a = 8,计算完成之后a变成8,并且运算结果也为8
      3. 然后是后面的加法,-a就是-8,加上10就是2
      4. 最后才是乘法,左边此时是8,右边是2,最后结果为16
      */
      System.out.println(b);
      }
    • 强制类型转换

      1
      2
      3
      4
      public static void main(String[] args) {
      int a = 10;
      short b = (short) a; //在括号中填写上强制转换的类型,就可以强制转换到对应的类型了
      }
  2. 自增自减运算符

    • " ++ " 自增运算符
    • " – " 自减运算符
    • “+=”、“-=”、“*=”、“/=”、“%=”
    1
    2
    3
    4
    5
    public static void main(String[] args) {
    int a = 8;
    int b = a++; //先出结果,再自增
    System.out.println(b); //b得到的是a自增前的值 8
    }
    1
    2
    3
    4
    5
    public static void main(String[] args) {
    int a = 8;
    int b = ++a; //先自增,再出结果
    System.out.println(b); //b得到的是a自增之后的结果 9
    }

    ⚠️ 自增运算符放在前面,是先自增再得到结果;而自增运算符放到后面,是先出结果再自增(自减同理)

    ⚠️ 自增自减运算符的优先级与正负号等价比如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static void main(String[] args) {
    int a = 8;
    int b = -a++ + ++a;
    //我们首先来看前面的a,因为正负号和自增是同一个优先级,结合性是从右往左,所以说先计算a++
    //a++的结果还是8,然后是负号,得到-8
    //接着是后面的a,因为此时a已经经过前面变成9了,所以说++a就是先自增,再得到10
    //最后得到的结果为 -8 + 10 = 2
    System.out.println(b);
    }
  3. 位运算符

    • " & " 按位与 运算

      1
      2
      3
      4
      5
      public static void main(String[] args) {
      int a = 9, b = 3;
      int c = a & b; //进行按位与运算
      System.out.println(c);
      }

      按位与,就是让这两个数每一位都进行比较,如果这一位两个数都是 1,那么结果就是 1,否则就是 0:

      • a = 9 = 1001
      • b = 3 = 0011
      • c = 1 = 0001(因为只有最后一位,两个数都是1,所以说结果最后一位是1,其他都是0)
    • " | " 按位或 运算

      1
      2
      3
      4
      5
      public static void main(String[] args) {
      int a = 9, b = 3;
      int c = a | b;
      System.out.println(c);
      }

      按位或,就是只要任意一个为1(不能同时为0)那么结果就是1:

      • a = 9 = 1001
      • b = 3 = 0011
      • c =11= 1011(只要上下有一个是1或者都是1,那结果就是1)
    • " ^ " 按位异或 运算

      1
      2
      3
      4
      5
      public static void main(String[] args) {
      int a = 9, b = 3;
      int c = a ^ b;
      System.out.println(c);
      }

      Java 中并没有乘方运算符,^ 是按位异或运算符

      异或,就是只有两边不相同的情况下,结果才是1,也就是说一边是1一边是0的情况:

      • a = 9 = 1001
      • b = 3 = 0011
      • c =10= 1010(从左往右第二位、第四位要么两个都是0,要么两个都是1,所以说结果为0)
    • " ~ " 按位取反 运算

      1
      2
      3
      4
      public static void main(String[] args) {
      byte c = ~127;
      System.out.println(c);
      }

      按位取反,跟前面的正负号一样,只操作一个数,最好理解,如果这一位上是1,变成0,如果是0,变成1:

      • 127 = 01111111
      • -128 = 10000000
    • " << " 左移;" << " 右移

      • **左移操作 <<:**高位直接丢弃,低位补0
      • **右移操作 >>:**低位直接丢弃,符号位是什么高位补什么
    • " >>> " 无符号右移

      1
      2
      3
      4
      public static void main(String[] args) {
      int c = -1 >>> 1;
      System.out.println(c);
      }
      • -1 = 11111111 11111111 11111111 11111111
      • 右移: 01111111 11111111 11111111 11111111(无符号右移使用0填充高位)

      此时得到的结果就是正数的最大值

      ⚠️ 不存在无符号左移。

  4. 关系和逻辑运算符

    • 关系判断的结果只可能是真或是假,所以说得到的结果是一个boolean类型的值。
    1
    2
    3
    4
    5
    6
    >   大于
    < 小于
    == 等于(注意是两个等号连在一起,不是一个等号,使用时不要搞混了)
    != 不等于
    >= 大于等于
    <= 小于等于
    • 逻辑运算符
    1
    2
    3
    &&     与运算,要求两边同时为true才能返回true
    || 或运算,要求两边至少要有一个为true才能返回true
    ! 非运算,一般放在表达式最前面,表达式用括号扩起来,表示对表达式的结果进行反转
  5. 三元运算符

    三元运算符可以根据判断条件,返回不同的结果

    1
    2
    3
    4
    5
    public static void main(String[] args) {
    int a = 10;
    char b = a > 10 ? 'A' : 'B'; //三元运算符需要三个内容,第一个是判断语句,第二个是满足判断语句的值,第三个是不满足判断语句的值
    System.out.println(b);
    }

    三元运算符:

    1
    判断语句 ? 结果1 : 结果2
  6. 运算符优先级总结

    优先级运算符结合性
    1( )从左向右
    2~ - + (强制类型转换) ++ –从右向左
    3* / %从左向右
    4+ -从左向右
    5<< >> >>>从左向右
    6> < >= >=从左向右
    7== !=从左向右
    8&从左向右
    9^从左向右
    10|从左向右
    11&&从左向右
    12||从左向右
    13? :从右向左
    14= += -= *= /= %= &= |= ^= <<= >>= >>>=从右向左

(四)流程控制

  1. 代码块与作用域

    作用域:在代码块内定义的变量无法在代码块之外使用。

    1
    2
    3
    4
    5
    6
    7
    public static void main(String[] args) {
    {
    int a = 10; //此时变量在代码块内定义
    System.out.println(a); //处于其作用域内部的代码可以调用
    }
    System.out.println(a); //作用域外的代码无法调用
    }
  2. 条件判断

    • IF 语句
    1
    2
    3
    4
    5
    6
    7
    if (条件判断) {
    判断成功执行的代码 1;
    }else if () {
    判断成功执行的代码 2;
    }else {
    判断成功执行的代码 3;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static void main(String[] args) {
    int score = 2;
    if(score >= 90)
    System.out.println("优秀");
    else if (score >= 70)
    System.out.println("良好");
    else if (score >= 60)
    System.out.println("及格");
    else
    System.out.println("不及格");
    }
    • Switch 语句
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    switch (目标) {   //目标变量 或 计算表达式
    case 匹配值: //如目标值等于给定匹配值,执行case的代码
    代码...
    break; //代码执行结束后需要使用break来结束,否则会溜到下一个case继续执行代码

    case 匹配值:
    代码...
    break;

    default:
    代码...
    }
  3. 循环语句

    • for 循环
    1
    2
    3
    for (表达式1;表达式2;表达式3) {
    循环体;
    }
    • while 循环
    1
    2
    3
    while(循环条件) {
    循环体;
    }
    • do……while 循环
    1
    2
    3
    4
    5
    6
    7
    public static void main(String[] args) {
    int i = 0; //比如现在我们想看看i不断除以2得到的结果会是什么,但是循环次数我们并不明确
    do { //无论满不满足循环条件,先执行循环体里面的内容
    System.out.println("Hello World!");
    i++;
    } while (i < 10); //再做判断,如果判断成功,开启下一轮循环,否则结束
    }
    • continue 和 break

      ⚠️ 就近原则,也可以利用标签指定跳转位置

      a. continue + (标签) 跳过本轮循环进入下一轮

      b. break + (标签) 跳出 / 结束循环

      1
      2
      3
      4
      5
      6
      outer: for (int i = 1; i < 4; ++i) {   //在循环语句前,添加 标签: 来进行标记
      inner: for (int j = 1; j < 4; ++j) {
      if(i == j) break outer; //break后紧跟要结束的循环标记,当i == j时终止外层循环
      System.out.println(i+", "+j);
      }
      }