安全学的真的是太菜了,所以不得已只能转了开发,emmmmm,打工人,努力去学习搬砖吧!

Java 简介

编译 or 解释?Java的独特之处

Java 是一种介于编译性和解释性的语言,首先需要明确两个概念,编译性和解释性。

编译性语言:在执行之前,需要使用编译器编译成可以被计算机执行机器码,然后计算机直接运行对应的机器码。这样的语言被称为编译性语言,比如 C / C++。这样好处是性能很好,但是因为不同架构的 CPU 使用的是不同的指令集,所以需要对应每一个平台都进行不同的编译才可以。

解释性语言:在运行的时候,使用解释器将代码解释成对应的机器码,然后执行。这是一个同步的过程,解释和运行。所以写出来的代码只需要在运行的时候用对应平台的解释器来运行就可以了,所以可拓展性就很强,比如 Python ,就是解释性语言。

编译-解释性语言:同时拥有上面两种语言的特性的Java就属于 编译-解释性 语言,Java 代码在编写出来之后,会首先经历 编译。但是这个编译并不是要生成机器码,而是生成字节码。因为机器码是高度依赖硬件的,所以为了实现良好的可移植性,Java 通过编译生成字节码来表达代码运算过程。 第二步就是 解释 了,当Java的字节码来到需要运行的平台,就轮到对应平台的解释器了上场了。Java解释器会把字节码翻译成对应平台的机器码,翻译一行就运行一行。所以Java是结合了编译和解释两种方式的语言。

JDK 和 JRE 是什么意思?

JDK: Java Development Kit (Java开发套件)

JRE: Java Runtime Environment (Java运行环境)

JDK 是包括 JRE的,他们之间的关系如下。

1
2
3
4
5
6
7
8
9
10
11
 ┌─    ┌──────────────────────────────────┐
│ │ Compiler, debugger, etc. |
│ └──────────────────────────────────┘
JDK ┌─ ┌──────────────────────────────────┐
│ │ │ │
│ JRE │ JVM + Runtime Library │
│ │ │ │
└─ └─ └──────────────────────────────────┘
┌────────┐┌──────┐┌───────┐┌───────┐
│Windows││ Linux││ macOS││others │
└────────┘└──────┘└───────┘└───────┘

所以作为开发者,直接下载 JDK 就可以了。

Java 的兄弟姐妹们

Java一共有三个不同的版本,分别是:

  • Java SE:Standard Edition
  • Java EE:Enterprise Edition
  • Java ME:Micro Edition

Java ME 原本是用来做嵌入式开发用的, 其 JVM 和 Runtime Library 被大幅度削减,但是没啥人用它。

Java SE 是Java的标准版,也是整个 Java 平台的核心。

Java EE 是Java的企业版,里面加了大量的API和库,方便开发使用。

Java的编译器和解释器

我们可以在安装的Java的 ./jdk/bin 文件夹里面发现一些可执行文件,比如:

  • java.exe 这个东西就是 java 的 JVM(解释器),运行 java 程序的时候,就是它将程序的字节码一句一句翻译成机器码,然后一行一行执行。
  • javac.exe 这个东西是 java 的编译器,它将 java 代码 (.java) 编译成 java 字节码文件 (.class)
  • jar.exe 将一组 .class 的字节码文件打包成 .jar 文件,然后发布到外界(应用程序)
  • javadoc.exe 用于从 java 代码中提取注释,然后整合成文档。
  • jdb.exe 用于 java 的调试。

下面就是 java 运行过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──────────────────┐
│ Hello.java │<─── source code
└──────────────────┘
│ compile (javac.exe)

┌──────────────────┐
│ Hello.class │<─── byte code
└──────────────────┘
│ execute (java.exe)

┌──────────────────┐
│ Run on JVM │
└──────────────────┘

Java变量

Java的基本数据类型

byte:Java 比 C++ 多了一个整形 byte 占用一个字节,可以表示 -128 到 127 的整数。

long:等效于 C++ 中的 long long 类型,所以只需要写一个 long 就可以了

float: 在java中float类型,需要加上f后缀,比如float Xorex=1.2f; ,直接原因是 JVM 不太聪明,根本原因是浮点型的默认数据类型是double,然而 float 和 double 两种数据在内存中的存储方式是不一样的,所以需要在 float 数据后面加上 f 来标识,具体请参看博文:内存中的基本类型数据

boolean:这里和 C++ 不太一样哦,是 boolean ,虽说理论上只会占用 一个字节的空间,但是JVM常常给它 四个字节,

char:这里 char 类型占用两个字节16位,所以和 C++ 的 char 不同的是它能表示的范围就大了很多,不仅仅是 ASCII 的那些,它最大可以表示到 65535,意味着16位 Unicode 的单字符 char 也可以保存下来。 这里 char 和 C++ 一样,只能使用单引号 '' 来表示字符。

除了上面的基本类型,剩下的就只有 Java 的引用类型了,比如 String 类型。

final 常量

Java中有一个可以把变量表示为常量的标识符 final ,为什么不叫 const 呢,因为它真的不是一个狭义上的常量。

final 类型被叫作 出生即为最终的变量 比较合适,因为它被声明之后,被允许有且仅有一次的赋值,一旦拥有,那么这就是它最终的状态了。而常量,是除了初始化以外,是不允许任何赋值操作的。

你可以这样写:

1
2
3
4
5
6
public static void main(String[] args)
{
final int Num;
Num=1;
System.out.println(Num);
}

也可以写成像 const 一样:

1
2
3
4
5
public static void main(String[] args)
{
final int Num=1;
System.out.println(Num);
}

智能的 var

Java 给了一个特别的关键字 var 这个关键字可以自动帮你定义数据类型,就像 Python 和 PHP 一样,你不需要关心你需要定义什么样的数据类型,只管用就完事了。

1
2
3
4
5
public static void main(String[] args)
{
var Xorex="Tempest";
System.out.println(((Object)Xorex).getClass().toString());
}

然后输出这个 Xorex 变量的类型是 String 类型,可以看出 var 智能的分配了变量所需要的类型,需要注意的是,var 的类型判断的基础是对声明的变量进行了初始化,如果不进行初始化,那么就 var 就没有办法通过初始化的数据来智能的判断变量的类型了。

当然我们可以自己对变量的类型进行强制转换,方法和 C语言 的相同。

作用域

作用域指的是一个变量有效的区域,这个区域的划分是依靠 {} 来决定的。也就是说一个变量的声明之后,其作用域就是声明位置所在的根大括号内并且在声明位置后面的区域。

Java计算

Java表达式计算

Java在计算过程中,不会对计算结果范围溢出的地方进行类型扩大,也就是说,对于下面的代码,输出结果是会溢出为负数的:

1
2
3
4
5
6
7
8
9
10
public class test
{
public static void main(String[] args)
{
int x = 2147483640;
int y = 15;
long z=x+y;
System.out.println(z);
}
}

上面的类型决定 z=x+y 中,因为 x 和 y 都是 int 类型,所以说 x+y 的结果也是 int 型,所以就会溢出,即使接受的 z 是一个 long 类型,但是拿到的也仅仅是已经溢出的数据。所以正确的解决方案就是将 x 或 y 直接转化为 long 类型,然后再执行运算。

下面是Java代码在执行的时候,运算符的优先级顺序:

优先级 运算符 结合性
1 ()、[]、{} 从左向右
2 !、+、-、~、++、– 从右向左
3 *、/、% 从左向右
4 +、- 从左向右
5 «、»、>>> 从左向右
6 <、<=、>、>=、instanceof 从左向右
7 ==、!= 从左向右
8 & 从左向右
9 ^ 从左向右
10 | 从左向右
11 && 从左向右
12 || 从左向右
13 ?: 从右向左
14 =、+=、-=、*=、/=、&=、|=、^=、~=、«=、»=、>>>= 从右向左

短路运算

Java也是个老懒狗了,对于双目逻辑运算 &&|| ,如果 && 左边已经是 false 了,那么它右边的表达式就不会去计算了,对于 || 如果左边已经是 ture 它同样也不会计算了,这个特性被成为短路运算。

真的是老懒狗了,我们可以利用这个性质,把容易引发短路运算的表达式放到前面,这样可以减少运算。

字符和字符串

字符char

char类型保存的是一个 Unicode 字符,占用两个字节,所以一个字符保存一个汉字也是没有问题的啦。char类型可以可以使用对应的 Unicode 编码来表示,一共有两种表示方法:

char ch=0x41;char ch='A';char ch='\u0041' 其中第一个可以用任意进制来表示,第三个在使用 \u 转码的时候只能使用十六进制,并且满足两个字节的大小。

字符串String

字符串类型是引用类型,所谓的引用类型,本质上就是一个类,String 变量里面保存的是字符串的地址,而不是字符串本身,所以被称为引用类型。

字符串之间可以直接相加,字符串和其他类型之间也可以相加减,只不过会被类型转化为字符串类型,转化方法也很直接,你看起来是啥转化之后就是啥,比如下面的代码运行结果就是 Boolean is: false

1
2
3
boolean This=(0>1);
String That="Boolean is:";
System.out.println(This+That);

当然,对于Java来说,多行字符串也不是问题,使用和 Python 相同的处理方法:"""@#$%()&""" 三个双引号里面可以想搞什么就搞什么。

Java的数组

数组声明

数组在Java中也是一个引用类型。

比如整形数组的声明方式: int[] a=new int[1001];

比如字符数组的声明方式:char[] ch=new char[1001];

当然也可以在声明的时候直接对数组进行初始化:

int[] a=new int[]{1,2,3,4,5};

或者更简单的:

int[] a={1,2,3,4,5};

对于多维数组,其实就是套娃,直接叠加方括号就可以了 [] ,比如下面的:

int[][] a=new int[3][3]; 这就成功的声明了一个数组。

int[][] a={{1,2,3},{1},{1,2}}; 这样就成功的初始化了二维数组,需要注意的是,因为 Java 的数组是引用类型的,也就是说 a 引用的是一个一位数组,这个一位数组每个元素都引用一个一位数组,正是因为这种引用的关系,所以二位数组每一行的列数量不需要相等。(区别于C中在内存连续的数组,它需要每一行列数量相等才可以通过指针运算来访问行列的内存地址) 因为这个原因,我们因此可以初始化一个 a[0][2] 存在,但是 a[1][2] 不存在的二维数组。

1
2
3
4
5
6
7
8
9
                    ┌───┬───┬───┬───┐
┌───┐ ┌──>│ 1 │ 2 │ 3 │ 4 │
a ─────>│░░░│──┘ └───┴───┴───┴───┘
├───┤ ┌───┬───┐
│░░░│─────>│ 5 │ 6 │
├───┤ └───┴───┘
│░░░│──┐ ┌───┬───┬───┐
└───┘ └──>│ 7 │ 8 │ 9 │
└───┴───┴───┘

字符串数组

字符串数组故名思意,就是里面指向了很多个不同的字符串的集合。

String[] a=new String[1001]; 或者 String[] a={"cat","dog","pig"};

你可以对单独的一个元素进行改变 a[2]="pigs";

Java的输入输出

输出

print() 普通的直接输出里面的内容。

println() print line 输出后换行,其他和普通输出相同。

printf() print format 标准化输出,使用方法和C语言的相同。

输入

Java的输入要麻烦的很多,如果像输出一样直接用 System.out 里面的 print() 函数家族类似的 System.in 输入的话,的确是可以的,但是会非常麻烦,这里使用的是一种简化的方法:

首先需要导入 java.util 下面的 Scanner 类,然后创建一个实例 scanner 并将 System.in 导入。这样实例 scanner 就可以读入不同类型的数据了。

1
2
3
4
5
6
7
8
9
10
import java.util.Scanner;
public class test
{
public static void main(String[] args)
{
Scanner scanner= new Scanner(System.in);
int n=scanner.nextInt();
System.out.print(n);
}
}

其中 Scanner 类中有很多读入不同类型数据的方法,next()是根据空格换行来分割读取字符,和 scanf 效果类似,但是返回的数据全都是 String 类型。nextLine() 读入一整行的数据并将其返回为 String 数据。当然我们可以读入其他类型的数据,只需要使用比如:nextInt() , nextDouble() 就可以了。

需要注意的是,这里面是没有nextChar() 方法的!因为读入是以空格作为分隔符,所以会有一些歧义。比如C语言的 scanf()就没有办法读入一个空格字符,所以可能是这个原因使得没有 nextChar() 方法。

这里可以使用 next() 方法来读取一个字符串,然后分割成字符。或者使用 System.in.read() ,下面是后者的使用方法:

1
2
3
4
5
6
7
8
9
import java.io.IOException;
public class test
{
public static void main(String[] args) throws Exception
{
char a=(char)System.in.read();
System.out.print(a);
}
}

首先需要导入IO异常处理 java.io.IOException 然后在 main 方法后面加上 throws Exception 表示这个方法可能会抛出异常,如果有异常则交给调用方处理。(这里IO异常时必须要加上的,不然编译会报错)

然后就可以使用 read() 方法了,注意的时这个 read() 是从缓冲区里面读取 一个字节 的字符,然后返回这个字节的 ASCII 的 int 数值,所以如果要使用 System.in.read() 来读取字符的话,一定要强制类型转化。

控制语句

if语句

这里不太一样的是当判断引用类型的数据的时候,相等的原则不是内容相等,而是引用对象相等,比如下面的代码:

1
2
3
4
String a="hello";
String b="Hello".toLowerCase();
if(a==b) System.out.print("True");
else System.out.print("False");

结果输出的就是 False,虽然 a 和 b 引用的对象内容都是 hello ,但是并不是真正的同一个 hello,JVM的引用对象分配机制以后还会讨论的。

但是如果我们真的有需要判断引用的两个内容是不是相等的呢,这里就可以使用函数 equals() ,上面代码在判断 a==b 的地方替换成 a.equals(b) ,如果引用的两个内容相等,那么就会返回 True ! 但是需要保证的是 a 的值不能是 null ,否则就会报错 NullPointerException

switch case 语句

Java的 switch case 语句用法和 C语言 的一模一样。

1
2
3
4
5
6
7
8
9
10
11
switch(option)
{
case 1:
break;
case 2:
break;
case 3:
break;
default:
break;
}

swtich case 语句可以判断 字符串匹配,但是和 if 语句不同的是,这里判断的是内容相等而不是引用相等。

在 java12 的版本中,对 switch case 语句进行了改进,没有了穿透效应,但是语法有一些小小的改变,使用 -> 代替 : ,取消了 break ,如果要在 case 里面写多条语句的话需要加上大括号。

1
2
3
4
5
6
7
8
9
10
11
String name="admin";
switch(name)
{
case "Xorex" -> System.out.print("You are admin!");
case "Tempest" -> System.out.print("You are admin!");
default ->
{
String answer="guest";
System.out.print("You are a "+answer);
}
}

for each 语句

如果我们想要遍历一个数组里面的元组,想要直接获得里面的东西,可以使用 for each 语句,语法大概是这样的:

1
2
3
4
5
public static void main(String[] args)
{
int[] array={0,1,2,3,4,5,6,7,8,9};
for(int element : array) System.out.println(element);
}

没错,写法就是 for(int element : array) 这个遍历顺序是按照数组的元素下标进行遍历的。也就是说这个循环等效于下面的代码:

1
2
3
4
5
6
7
8
9
public static void main(String[] args)
{
int[] array={0,1,2,3,4,5,6,7,8,9};
for(int i=0;i<array.length;i++)
{
int element=array[i];
System.out.println(element);
}
}

main 参数

你可能会很好奇,为啥 main 函数里面要有参数 String[] args ,这个东西其实和C语言的 char **args 是一样的,用来传入命令行的参数。不过不同的是 Java 里面 main 函数是必须要写这个参数的,不然会报错,但是C语言可以不写。

这个叫 args 的 String 数组会把输入的命令保存下来,然后在程序中进行处理。

Java 的基础内容的学习笔记大概就是这样了,下面就是 Java 的灵魂