Java基础-入门

Java基础-入门

大学期间我已经学过 C、C++、Python、Java 等语言,其中 Java 前后学了有一年半(基本围绕着各种CRM管理系统进行CRUD开发),Python 学了大概半年多(基本围绕着Tensorflow做强化学习+深度学习,有论文产出,中间还看过爬虫),C++将近一年(保研结束后整体比较松散,基本围绕着C++11学习语法、看一些C项目等),现在明确了以后走后端开发决定重新捡起 Java,真正透彻的掌握 Java 相关知识。

参考文章:菜鸟教程-Java特性

1.主要特性

  • Java 语言是简单的

Java 语言的语法与 C 语言和 C++ 语言很接近,使得大多数程序员很容易学习和使用。另一方面,Java 丢弃了 C++ 中很少使用的、很难理解的、令人迷惑的那些特性,如操作符重载、多继承、自动的强制类型转换。特别地,Java 语言不使用指针而是引用,并提供了自动分配和回收内存空间,使得程序员不必为内存管理而担忧。

Java 和 C++ 作为后端开发中的两种主流语言(当然还有Go),其使用形式非常接近,不过写过 C++ 的便知道 C++ 明显更加细致和啰嗦,而且 C++ 因为严谨和复杂一些特性比较抽象,例如 C++ 中通过运算符重载能够完成 Student1 + Student2 这种类相加操作,Java则完全不允许 + 运算符的这种行为;C++ 中子类还能够同时继承多个父类,当父类中有名称一样的属性时便发生了“多重继承二义性”现象,此时子类访问同名属性需要作用域限定符 “::”,并且 C++ 只针对父类的虚函数进行动态绑定(虚函数表原理),Java 则完全不需要担心这些东西。最重要的一点是,Java 不使用指针而是引用,消除了 C++ 程序员最为头疼的内存分配与回收的问题(垃圾回收原理)。

  • Java 语言是面向对象的

Java 语言提供类、接口和继承等面向对象的特性,为了简单起见,只支持类的单继承,但支持接口的多继承,并支持类与接口之间的实现机制(关键字为 implements)。Java 语言全面支持动态绑定,而 C++ 语言只对虚函数使用动态绑定。总之,Java 语言是一个纯粹的面向对象程序设计语言。

C++ 也是面向对象的编程语言,同时支持面向模板的泛型编程,还保留了 C 语言的低级特性。Java 过于纯粹了哦!

  • Java 语言是分布式的

Java 语言支持 Internet 应用的开发,在基本的 Java 应用编程接口中有一个网络应用编程接口(java net),它提供了用于网络应用编程的类库,包括 URL、URLConnection、Socket、ServerSocket 等。Java 的 RMI(远程方法激活)机制也是开发分布式应用的重要手段。

Java 本身并不是”分布式的”,但 Java 在设计和实现上考虑了分布式计算的需求,因此可以很容易地用于开发分布式系统。如网络编程(Java 提供了丰富的网络编程 API)、RMI(远程方法调用)、JNDI(Java 命名和目录接口,JNDI 提供了访问命名和目录服务的 API,使得分布式系统可以轻松查找和访问不同的资源)、序列化(Java 的序列化机制允许将 Java 对象转换为字节流,并在不同计算机之间传输,实现对象的跨网络传输)等。

  • Java 语言是健壮的

Java 的强类型机制、异常处理、垃圾的自动收集等是 Java 程序健壮性的重要保证。对指针的丢弃是 Java 的明智选择。Java 的安全检查机制使得 Java 更具健壮性。

Java 是静态类型语言,编译器在编译时会进行类型检查,Java 提供了异常处理机制,允许开发者在程序出现异常时进行适当处理,防止程序崩溃或安全漏洞,同时 Java 的垃圾回收机制自动处理不再使用的对象,防止内存泄漏,减少了许多与动态内存管理相关的问题。

  • Java 语言是安全的

Java 通常被用在网络环境中,为此,Java 提供了一个安全机制以防恶意代码的攻击。除了 Java 语言具有的许多安全特性以外,Java 对通过网络下载的类具有一个安全防范机制(类 ClassLoader),如分配不同的名字空间以防替代本地的同名类、字节代码检查,并提供安全管理机制(类 SecurityManager)让 Java 应用设置安全哨兵。

Java 的安全性体现在很多方面。例如强类型检查、数组边界检查、无指针、访问控制、类加载机制、安全管理机制等。有趣的是,Java 中没有全局变量的概念(只有实例变量、静态变量、局部变量),所有的变量都必须定义在类的作用域内,这样才能保证变量的访问权限和生命周期。

  • 局部变量存储在栈内存中,具有较短的生命周期,当方法执行完毕或离开作用域时,局部变量被销毁。
  • 成员变量存储在堆内存中,与对象的生命周期相同,当对象被销毁时,成员变量的内存空间也会被回收。
  • 静态变量存储在方法区中,与类的生命周期相同,当类被卸载时,静态变量的内存空间将被释放。
  • Java 语言是体系结构中立的

Java 程序(后缀为 .java 的文件)在 Java 平台上被编译为体系结构中立的字节码格式(后缀为 .class 的文件),然后可以在实现这个 Java 平台的任何系统中运行。这种途径适合于异构的网络环境和软件的分发。

Java 虚拟机是 Java 语言的核心执行环境,它充当了 Java 程序与底层操作系统之间的中间层。不同平台上的 JVM 实现可以解释执行相同的字节码,从而实现了跨平台的特性。然而,需要注意的是,Java 的平台中立性主要体现在 Java 源代码和字节码层面。一旦涉及到与底层操作系统直接交互的部分,如文件操作、网络套接字等,就可能涉及到平台相关性,需要特别注意处理平台差异。因此,在编写跨平台的 Java 程序时,需要避免直接依赖于底层系统特定的功能。

  • Java 语言是可移植的

这种可移植性来源于体系结构中立性,另外,Java 还严格规定了各个基本数据类型的长度。Java 系统本身也具有很强的可移植性,Java 编译器是用 Java 实现的,Java 的运行环境是用 ANSI C 实现的。

Java 的跨平台特性使得开发者可以在一台平台上编写 Java 程序,然后将生成的字节码文件拷贝到其他平台的 JVM 上执行,而不需要重新编译。

  • Java 语言是解释型的

如前所述,Java 程序在 Java 平台上被编译为字节码格式,然后可以在实现这个 Java 平台的任何系统中运行。在运行时,Java 平台中的 字节码解释器对这些字节码进行解释执行,执行过程中需要的类在联接阶段被载入到运行环境中。

准确的说,Java 语言是一种混合型语言,Java 程序的执行过程涉及解释和编译两个阶段:

  1. 编译阶段: Java 源代码首先经过编译器编译成中间代码,称为字节码(Bytecode),它是一种与平台无关的中间形式。
  2. 解释阶段: 在执行阶段,Java 虚拟机(JVM)负责将字节码解释成特定平台的机器码,或者通过即时编译(JIT Compilation)技术将部分字节码编译成本地机器码,以提高执行效率。
  • Java 是高性能的

与那些解释型的高级脚本语言相比,Java 的确是高性能的。事实上,Java 的运行速度随着 JIT(Just-In-Time)编译器技术的发展越来越接近于 C++。

Java是一种相对高性能的编程语言,但性能取决于具体的使用场景和优化措施。

  1. 即时编译(JIT Compilation): Java 虚拟机(JVM)使用即时编译器(JIT Compiler)将字节码转换成本地机器码,从而提高程序的执行速度。JIT 编译可以根据代码的执行情况进行优化,对热点代码进行特殊处理,从而进一步提高性能。
  2. 垃圾回收(Garbage Collection): Java 的内存管理是由垃圾回收机制来处理的。垃圾回收可以自动回收不再使用的内存,避免了内存泄漏和悬挂指针等问题。但垃圾回收也可能导致一定的性能损耗,特别是在进行大规模内存回收时。因此,在高性能应用中,需要适当地调整垃圾回收策略和选择合适的垃圾回收器。
  3. 多线程支持: Java 具有强大的多线程支持,可以充分利用多核处理器的性能。但同时,多线程编程也带来了线程安全和同步的挑战,如果不正确地处理多线程问题,可能会导致性能下降或者出现并发问题。
  4. 底层交互: Java 提供了本地方法接口(JNI),可以与底层系统进行交互。在一些性能敏感的场景,可以使用 JNI 来调用 C 或C++ 编写的本地库,以获得更高的性能。
  5. 平台中立性: Java 的跨平台特性允许同一份 Java 代码在不同平台上运行,但有时为了实现跨平台性,可能需要在某些特定平台上做出性能上的妥协。

2.Java关键字

Java关键字类别 Java关键字 关键字含义
访问控制 private 一种访问控制方式:私有模式,可以应用于类、方法或字段(在类中声明的变量)的访问控制修饰符
访问控制 protected 一种访问控制方式:保护模式,可以应用于类、方法或字段(在类中声明的变量)的访问控制修饰符
访问控制 public 一种访问控制方式:共用模式,可以应用于类、方法或字段(在类中声明的变量)的访问控制修饰符。
类、方法和变量修饰符 abstract 表明类或者成员方法具有抽象属性,用于修改类或方法
类、方法和变量修饰符 class 声明一个类,用来声明新的Java类
类、方法和变量修饰符 extends 表明一个类型是另一个类型的子类型。对于类,可以是另一个类或者抽象类;对于接口,可以是另一个接口
类、方法和变量修饰符 final 用来说明最终属性,表明一个类不能派生出子类,或者成员方法不能被覆盖,或者成员域的值不能被改变,用来定义常量
类、方法和变量修饰符 implements 表明一个类实现了给定的接口
类、方法和变量修饰符 interface 接口
类、方法和变量修饰符 native 用来声明一个方法是由与计算机相关的语言(如C/C++/FORTRAN语言)实现的
类、方法和变量修饰符 new 用来创建新实例对象
类、方法和变量修饰符 static 表明具有静态属性
类、方法和变量修饰符 strictfp 用来声明FP_strict(单精度或双精度浮点数)表达式遵循IEEE 754算术规范
类、方法和变量修饰符 synchronized 表明一段代码需要同步执行
类、方法和变量修饰符 transient 声明不用序列化的成员域
类、方法和变量修饰符 volatile 表明两个或者多个变量必须同步地发生变化
程序控制 break 提前跳出一个块
程序控制 continue 回到一个块的开始处
程序控制 return 从成员方法中返回数据
程序控制 do 用在do-while循环结构中
程序控制 while 用在循环结构中
程序控制 if 条件语句的引导词
程序控制 else 用在条件语句中,表明当条件不成立时的分支
程序控制 for 一种循环结构的引导词
程序控制 instanceof 用来测试一个对象是否是指定类型的实例对象
程序控制 switch 分支语句结构的引导词
程序控制 case 用在switch语句之中,表示其中的一个分支
程序控制 default 默认,例如:用在switch语句中,表明一个默认的分支。Java8 中也作用于声明接口函数的默认实现
错误处理 try 尝试一个可能抛出异常的程序块
错误处理 catch 用在异常处理中,用来捕捉异常
错误处理 throw 抛出一个异常
错误处理 throws 声明在当前定义的成员方法中所有需要抛出的异常
包相关 import 表明要访问指定的类或包
包相关 package 包
基本类型 boolean 基本数据类型之一,声明布尔类型的关键字
基本类型 byte 基本数据类型之一,字节类型
基本类型 char 基本数据类型之一,字符类型
基本类型 double 基本数据类型之一,双精度浮点数类型
基本类型 float 基本数据类型之一,单精度浮点数类型
基本类型 int 基本数据类型之一,整数类型
基本类型 long 基本数据类型之一,长整数类型
基本类型 short 基本数据类型之一,短整数类型
字面量 null 空,表示无值,不能将null赋给原始类型(byte、short、int、long、char、float、double、boolean)变量
字面量 true 真,boolean变量的两个合法值中的一个
字面量 false 假,boolean变量的两个合法值之一
变量引用 super 表明当前对象的父类型的引用或者父类型的构造方法
变量引用 this 指向当前实例对象的引用,用于引用当前实例
变量引用 void 声明当前成员方法没有返回值,void可以用作方法的返回类型,以指示该方法不返回值
保留字 ==goto== 保留关键字,没有具体含义
保留字 ==const== 保留关键字,没有具体含义,是一个类型修饰符,使用const声明的对象不能更新

Java 的 null 不是关键字,类似于 true 和 false,它是一个字面常量,不允许作为标识符使用。

3.源文件声明规则

当在一个源文件中定义多个类,并且还有 import 语句和 package 语句时,要特别注意这些规则。

  • 一个源文件中只能有一个 public 类,但可以有多个非 public 类,源文件的名称应该和 public 类的类名保持一致
  • 如果一个类定义在某个包中,那么 package 语句应该在源文件的首行,如果源文件包含 import 语句,那么应该放在 package 语句和类定义之间
  • import 语句和 package 语句对源文件中定义的所有类都有效

4.基本数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class PrimitiveTypeTest {  
public static void main(String[] args) {
// byte
System.out.println("基本类型:byte 二进制位数:" + Byte.SIZE);
System.out.println("包装类:java.lang.Byte");
System.out.println("最小值:Byte.MIN_VALUE=" + Byte.MIN_VALUE);
System.out.println("最大值:Byte.MAX_VALUE=" + Byte.MAX_VALUE);
System.out.println();

// short
System.out.println("基本类型:short 二进制位数:" + Short.SIZE);
System.out.println("包装类:java.lang.Short");
System.out.println("最小值:Short.MIN_VALUE=" + Short.MIN_VALUE);
System.out.println("最大值:Short.MAX_VALUE=" + Short.MAX_VALUE);
System.out.println();

// int
System.out.println("基本类型:int 二进制位数:" + Integer.SIZE);
System.out.println("包装类:java.lang.Integer");
System.out.println("最小值:Integer.MIN_VALUE=" + Integer.MIN_VALUE);
System.out.println("最大值:Integer.MAX_VALUE=" + Integer.MAX_VALUE);
System.out.println();

// long
System.out.println("基本类型:long 二进制位数:" + Long.SIZE);
System.out.println("包装类:java.lang.Long");
System.out.println("最小值:Long.MIN_VALUE=" + Long.MIN_VALUE);
System.out.println("最大值:Long.MAX_VALUE=" + Long.MAX_VALUE);
System.out.println();

// float
System.out.println("基本类型:float 二进制位数:" + Float.SIZE);
System.out.println("包装类:java.lang.Float");
System.out.println("最小值:Float.MIN_VALUE=" + Float.MIN_VALUE);
System.out.println("最大值:Float.MAX_VALUE=" + Float.MAX_VALUE);
System.out.println();

// double
System.out.println("基本类型:double 二进制位数:" + Double.SIZE);
System.out.println("包装类:java.lang.Double");
System.out.println("最小值:Double.MIN_VALUE=" + Double.MIN_VALUE);
System.out.println("最大值:Double.MAX_VALUE=" + Double.MAX_VALUE);
System.out.println();

// char
System.out.println("基本类型:char 二进制位数:" + Character.SIZE);
System.out.println("包装类:java.lang.Character");
// 以数值形式而不是字符形式将Character.MIN_VALUE输出到控制台
System.out.println("最小值:Character.MIN_VALUE="
+ (int) Character.MIN_VALUE);
// 以数值形式而不是字符形式将Character.MAX_VALUE输出到控制台
System.out.println("最大值:Character.MAX_VALUE="
+ (int) Character.MAX_VALUE);
}
}

image-20230802160113840

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

一般谈到数据类型就逃不过类型转换这个话题,Java 同样如此,表达式中不同类型的数据需要先转换成同一类型(往较大类型转型),再进行运算。当范围大的类型转换成范围小的类型时需要注意:可能发生数值溢出和精度损失。注意:可以将浮点型强制转为整型,但超出范围后将始终返回整型的最大值。

额外补充一个细节:位移运算符有三种:<<,>>,>>>,其中>>>不管符号位,右移时高位总是补0

5.变量类型

Java 中成员变量和静态变量有默认的初始化值,而局部变量必须初始化才能使用,这是编译器的规定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class VariableInitialization {
// 成员变量默认初始化
int memberInt;
boolean memberBoolean;
char memberChar;
String memberString;
// 静态变量默认初始化
static int staticInt;
static boolean staticBoolean;
static char staticChar;
static String staticString;
// 局部变量必须初始化,否则编译器不通过
public static void main(String[] args) {
int localInt = 0;
boolean localBoolean = false;
char localChar = '\u0001';
String localString = "local String";

VariableInitialization initialization = new VariableInitialization();
System.out.println("成员变量int默认值:" + initialization.memberInt);
System.out.println("成员变量boolean默认值:" + initialization.memberBoolean);
System.out.println("成员变量char默认值:" + initialization.memberChar);
System.out.println("成员变量String默认值:" + initialization.memberString);
System.out.println("静态变量int默认值:" + VariableInitialization.staticInt);
System.out.println("静态变量boolean默认值:" + VariableInitialization.staticBoolean);
System.out.println("静态变量char默认值:" + VariableInitialization.staticChar);
System.out.println("静态变量String默认值:" + VariableInitialization.staticString);
System.out.println("局部变量int初始值:" + localInt);
System.out.println("局部变量boolean初始值:" + localBoolean);
System.out.println("局部变量char初始值:" + localChar);
System.out.println("局部变量String初始值值:" + localString);
}
}

image-20230802151642147

方法参数变量的值传递方式有两种:值传递和引用传递。

  • 值传递:在方法调用时,传递的是实际参数的值的副本。当参数变量被赋予新的值时,只会修改副本的值,不会影响原始值。Java 中的基本数据类型都采用值传递方式传递参数变量的值。
  • 引用传递:在方法调用时,传递的是实际参数的引用(即内存地址)。当参数变量被赋予新的值时,会修改原始值的内容。Java 中的对象类型采用引用传递方式传递参数变量的值。

实际上,Java 中的引用类型如数组对象、类对象也可以理解为值传递,只不过这个值是引用类型对象的内存地址值。

Java 中的引用类似于 C++ 中的指针,Java 引用"指向"堆中分配的类对象,传递引用类似于传递指针,参数变量与原变量引用同一个堆中的类对象,如果方法体内修改了引用的指向,则后续操作便不再影响原变量的内容!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class PassByReference {

// 值传递
public void swap(int x, int y) {
int temp = x;
x = y;
y = temp;
}

// 引用传递1
public void swap(InnerClass c1, InnerClass c2) {
InnerClass temp = new InnerClass(c1.num);
c1 = c2;
c2 = temp;
}

// 引用传递2
public void swap2(InnerClass c1, InnerClass c2) {
int temp = c1.num;
c1.num = c2.num;
c2.num = temp;
}

public static void main(String[] args) {
int a = 1, b = 2;
PassByReference passByReference = new PassByReference();
InnerClass c1 = passByReference.new InnerClass(1);
InnerClass c2 = passByReference.new InnerClass(2);
passByReference.swap(a, b);
passByReference.swap(c1, c2);
System.out.println("交换后a:" + a);
System.out.println("交换后b:" + b);
System.out.println("交换后c1.num:" + c1.num);
System.out.println("交换后c2.num:" + c2.num);
passByReference.swap2(c1, c2);
System.out.println("交换后c1.num:" + c1.num);
System.out.println("交换后c2.num:" + c2.num);
}

// 非静态内部类
private class InnerClass {
int num;
InnerClass(int num) {
this.num = num;
}
}
}

image-20230802155934497

Java 中没有指针的概念,但引用类似于指针,直观地看,针对引用类型的参数变量的修改(内部属性修改)会直接影响到原变量,这跟指针的工作方式不谋而合。

6.Java修饰符

访问修饰符 当前类 同一包内 子孙类(同一包) 子孙类(不同包) 其他包
public Y Y Y Y Y
protected Y Y Y Y/N N
default Y Y Y N N
private Y N N N N

protected 访问控制修饰符的作用范围比较难以理解,具体如下:

  • 基类的 protected 成员是包内可见的,并且对子类可见;
  • 若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法。

总结来说:与 protected 修饰的成员的基类所在同一个包的其他类可以随意访问(同一个包下该基类的子类的成员方法中可以直接使用继承的 protected 成员,也能通过基类实例访问) ;如果该基类的子类与基类不在同一个包下,则子类只能在成员方法中使用继承的 protected 成员。

image-20230802175807886

非访问修饰符也有很多,它们都很重要,以后会单独说。