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

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