:::success 🚩 本篇文章记录基础语法以及 Web 技术栈
💻 使用 JDK17,环境配置略过了~
📌 面向对象的概念不在赘述,如有需要移步 面向对象程序设计
❓文中会涉及到很多和 C++ 的对比学习 :::

JAVA 语法基础

0 从HelloWorld开始

创建一个HelloWorld.java文件,文件名需要和类名一致。

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

运行上面的文件,输出如下:

  1. $ javac HelloWorld.java
  2. $ java HelloWorld
  3. HelloWorld
  • 运行javac后,会在同级目录编译生成HelloWorld.class文件。
  • 运行java时,例如java HelloWorld,不能使用java HelloWorld.class也不能java ./HelloWorld
  • 大小写敏感:Java 是大小写敏感的,这就意味着标识符 Hello 与 hello 是不同的。
  • 类名:按照习惯,类名使用大驼峰体。
  • 方法名:按照习惯,方法名使用小驼峰体。
  • 源文件名:源文件名必须和类名相同。当保存文件的时候,你应该使用类名作为文件名保存,文件名的后缀为 .java。(如果文件名和类名不相同则会导致编译错误)。也由于这个原因,一个文件里只能有一个类(非接口类)。
  • 主方法入口:所有的 Java 程序由public static void main(String[] args)方法开始执行。

    1 概述

  • Java 丢弃了 C++ 中的操作符重载、多继承、自动的强制类型转换。

  • Java 语言不使用指针,而是引用。并提供了自动分配和回收内存空间(不用自己管内存了,好耶)
  • Java 只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制(关键字为 implements)。
  • Java 语言全面支持动态绑定,而 C++语言只对虚函数使用动态绑定。
  • Java 语言支持 Web 应用的开发,在基本的 Java 应用编程接口中有一个网络应用编程接口( java net ),它提供了用于网络应用编程的类库,包括 URL、URLConnection、Socket、ServerSocket 等。
  • Java 是强类型语言。Java 的异常处理、垃圾的自动收集等是 Java 程序鲁棒性的重要保证。

    2 基础语法

    2.1 标识符

    Java 所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符。所有的标识符都应该以字母(A-Z 或者 a-z),美元符$或者下划线_开始。

    2.2 变量与数据类型

    2.2.1 基本数据类型

    Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。
    **byte** :::info

  • 8位有符号数

  • 最小值是-128,最大值是127,默认值是0
  • byte 类型用在大型数组中节约空间,主要代替整数
  • byte a = 50; ::: **short** :::info

  • 16位有符号数

  • 最小值-32768,最大值32767,默认值为0
  • short a = 1000; ::: **int** :::info

  • 32位有符号数

  • 最小值JAVA 学习 - 图1,最大值JAVA 学习 - 图2,默认为0 ::: **long** :::info

  • 64位有符号数

  • 最小值JAVA 学习 - 图3,最大值JAVA 学习 - 图4,默认为0L ::: byteintlong、和short都可以用十进制、16进制以及8进制的方式来表示。当使用字面量的时候,前缀 0 表示 8 进制,而前缀 0x 代表 16 进制。例如:

    1. int dec = 100;
    2. int oct = 0144;
    3. int hex = 0x64;

    **float** :::info

  • IEEE754 标准单精度浮点数

  • 默认为0.0f ::: **double** :::info

  • IEEE754 标准双精度浮点数

  • 默认为0.0d ::: **char** :::info

  • 16位的 Unicode 字符

  • 最小值为 \u0000(0),最大值为 \uffff(65535)
  • 可以储存任何字符 ::: **boolean** :::info

  • 默认值为false

  • 不能用0或者1表示 false 或 true :::

    2.2.2 常量

    Java 中使用final关键字来修饰常量,例如final double PI = 3.14;。常量在运行时不能被修改。

    2.2.3 变量

    1. public class Test {
    2. public String name; // 实例变量
    3. private double salary; // 实例变量
    4. static public String depart; // 静态类变量
    5. public Test(String name_, double salary_) {
    6. name = name_;
    7. salary = salary_;
    8. }
    9. public void Age(){
    10. int age = 19; // 局部变量,必须要初始化,否则编译错误
    11. System.out.println("Age: " + age);
    12. }
    13. public static void main(String[] args){
    14. depart = "dev"; // 静态变量被创建
    15. Test test = new Test("kenshin", 5000f);
    16. test.Age();
    17. System.out.println("Name: " + test.name);
    18. System.out.println("Name: " + Test.depart);
    19. }
    20. }

    局部变量 :::info

  • 局部变量声明在方法、构造方法或者语句块中**_line7_**

  • 局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,变量将会被销毁
  • 访问修饰符不能用于局部变量
  • 局部变量只在声明它的方法、构造方法或者语句块中可见
  • 局部变量是在栈上分配的
  • 局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用**_line7_** ::: 实例变量(成员变量) :::info

  • 实例变量声明在一个类中,但在方法、构造方法和语句块之外(可以理解为成员变量)

  • 访问修饰符可以修饰实例变量_**line2 line3**_
  • 实例变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见
  • 实例变量具有默认值。数值型变量的默认值是0,布尔型变量的默认值是false,引用类型变量的默认值是null。变量的值可以在声明时指定,也可以在构造方法中指定
  • 实例变量可以直接通过变量名访问**_line2_**,但在静态方法以及其他类中,就应该使用完全限定名🔗_ObjectReference.VarName_ ::: 静态变量 :::info

  • 类变量也称为静态变量,在类中以static关键字声明,但必须在方法之外**_line4_**

  • 静态变量储存在静态存储区。
  • 静态变量在第一次被访问时创建,在程序结束时销毁。
  • 为了对类的使用者可见,大多数静态变量声明为 public类型。
  • 默认值和实例变量相似。数值型变量默认值是0,布尔型默认值是false,引用类型默认值是null。变量的值可以在声明的时候指定,也可以在构造方法中指定,还可以在静态语句块中初始化**_line17_**
  • 静态变量可以通过:_ClassName.VarName_的方式访问**_line21_** :::

    2.3 类型转换

    2.3.1 自动类型转换

    :::info

  • 转换自低向高:byte, short, char -> int -> long -> float -> double

  • 浮点数转换到整数是舍弃小数部分而不是四舍五入。
  • 由大转小时,转换溢出部分将被舍弃。例如一个浮点数(超过了整数的最大范围)转化为整数时,小数部分、超出最大范围的部分都将被舍弃,即转化成的整数为其范围的最大值。 :::

    2.3.2 强制类型转换

    和 C 语言相同,不再赘述。

    2.4 引用类型

    Java 相比 C++ 没有指针,取而代之的是全部都是引用。Java 为引用类型专门定义了一个Reference类。Java 中的四种引用类型分别是:强引用,软引用,弱引用和虚引用

    Final Reference | 强引用
    1. public class FinalReferenceUsage {
    2. public void stringReference(){
    3. Object obj = new Object();
    4. // obj = null;
    5. }
    6. }

    这个被new出来的obj对象是new Object()的强引用。Java 中不加指明的就是强引用。只要强引用存在,被引用的对象就不会被垃圾回收,即使内存不足时,Java 会抛出 OutOfMemoryError但不会回收强引用对象。如果想要中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样 JVM 就可以适时的回收对象了。

    Soft Reference | 软引用

    软引用只有在内存不足的情况下,被引用的对象才会被回收。在内存充足的情况下,软引用的对象不会被回收。可以通过java.lang.ref.SoftReference使用软引用。下面是一个例子👇🏻 ```java import java.lang.ref.SoftReference;

public class Test { public static void main(String[] args) { Object obj = new Object(); // 强引用

  1. // sf就是一个软引用
  2. SoftReference sf = new SoftReference(obj);
  3. obj = null;
  4. System.out.println("内存足够软引用引用的对象" + sf.get());
  5. try {
  6. final byte[] bytes = new byte[8 * 1024 * 1024];
  7. } catch (Exception e) {
  8. e.printStackTrace();
  9. } finally {
  10. System.out.println("内存不够:软引用引用的对象:" + sf.get());
  11. }
  12. }

}

// 编译选项:java -Xmx4m -Xms4m Test / 内存足够软引用引用的对象java.lang.Object@251a69d7 内存不够:软引用引用的对象:null Exception in thread “main” java.lang.OutOfMemoryError: Java heap space at Test.main(Test.java:15) /

  1. 由此可见,在内存紧张的情况下,软引用被回收。
  2. <a name="X4OjE"></a>
  3. ##### Weak Reference | 弱引用
  4. 弱引用的引用强度比软引用要更弱一些,无论内存是否足够,**只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收**。用`java.lang.ref.WeakReference`来保存对一个Java对象的弱引用。下面是一个例子👇🏻
  5. ```java
  6. import java.lang.ref.WeakReference;
  7. public class Test {
  8. public static void main(String[] args) {
  9. Object obj = new Object(); // 强引用
  10. // sf就是一个弱引用
  11. WeakReference wf = new WeakReference(obj);
  12. obj = null;
  13. System.out.println("没有回收时的弱引用对象" + wf.get());
  14. System.gc();
  15. System.out.println("回收后的弱引用对象" + wf.get());
  16. }
  17. }
  18. /*
  19. 没有回收时的弱引用对象java.lang.Object@251a69d7
  20. 回收后的弱引用对象null
  21. */

Phantom Reference | 虚引用

虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,可以通过java.lang.ref.PhantomReference来使用。
比较特殊的是,虚引用的get方法始终返回null,这意味着无法通过虚引用来获取对象,虚引用必须和引用队列一起使用,作用在于跟踪垃圾回收过程。
当 JVM 准备回收一个对象,如果发现它还有虚引用,就会在回收之前把这个虚引用加入到与之关联的ReferenceQueue中。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。所谓必要的行动,我理解就是一些无法靠 Java 的垃圾回收机制做到的事。 :::danger

  • 软引用和弱引用常常在缓存技术当中用到 :::

    2.5 修饰符

    2.5.1 访问修饰符

    访问修饰符也即public``private``protected这些,Java 相比 C++ 多了一个default
    2.5.1.1 访问控制表

    包的概念🔗

修饰符 当前类 同一包内 子类(同一包) 子类(不同包) 其他包
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方法。

    2.5.1.2 访问控制继承规则

    :::danger

  • 父类中声明为public的方法在子类中也必须为public

  • 父类中声明为protected的方法在子类中要么声明为protected,要么声明public,不能声明为private
  • 父类中声明为private的方法,不能够被子类继承。 :::

    2.5.2 非访问修饰符

    2.5.2.1 static
    Java 的静态修饰和 C++ 类似,在static修饰的方法内没有this指针,只能访问静态方法和静态变量,不能访问非静态方法或变量。反之,在非静态方法里可以访问静态方法和变量。
    static修饰的成员变量和 C++ 基本一致,但 Java 中**static**不允许修饰局部变量,也即static只能作用于类变量。(大概是有悖 Java 面向对象的理念,C++ 又为了兼容 C 而背上了包袱)
  1. static成员变量的初始化顺序按照声明的顺序设置为默认值,但在此步中不初始化。
  2. 声明结束后,按声明顺序依次设置为初始化的值,如果没有初始化的值就跳过 ```java public class Test { public static Foo f = new Foo(); public static int a = 0; public static int b;

    public static void main(String[] args) {

    1. Foo.f();
    2. System.out.println(Test.a);
    3. System.out.println(Test.b);

    } }

class Foo { Foo() { Test.a++; Test.b++; } }

/ 输出 0 1 /

  1. 1. 先声明并设置默认值,`f <- null``a <- 0``b <- 0`
  2. 1. 再初始化,先初始化`f``f <- 某个地址``Foo`构造器完成后这时`a``b`均为1
  3. 1. 初始化`a`0
  4. 1. 初始化`b`,但`b`没有初始化的值,故跳过。
  5. `static`还有一个作用就是用来形成静态代码块以优化程序性能。
  6. - `static`块可以置于类中的任何地方**(但不能出现在方法内部)**
  7. - 类中可以有多个`static`块。
  8. - 在类初次被加载的时候,会按照`static`块的顺序来执行每个`static`块,并且只会执行一次。
  9. ```java
  10. public class Test {
  11. Person person = new Person("Test");
  12. static {
  13. System.out.println("test static");
  14. }
  15. public Test() {
  16. System.out.println("test constructor");
  17. }
  18. public static void main(String[] args) {
  19. new MyClass();
  20. }
  21. }
  22. class Person {
  23. static {
  24. System.out.println("person static");
  25. }
  26. public Person(String str) {
  27. System.out.println("person "+str);
  28. }
  29. }
  30. class MyClass extends Test {
  31. Person person = new Person("MyClass");
  32. static {
  33. System.out.println("myclass static");
  34. }
  35. public MyClass() {
  36. System.out.println("myclass constructor");
  37. }
  38. }

输出(密码asd
static块优先于所有构造器执行,然后按继承顺序执行构造器。

2.5.2.2 final

和 C++ 的const有些类似,可以来修饰变量,修饰方法,不同于 C++ 的地方是可以用于修饰类。
final修饰的变量(可以是局部变量或者类变量)必须显式的指明初始值,如public static final int BOXWIDTH = 6;``final修饰的引用在初始化之后不能再指向其他对象。
final修饰的方法可以被子类继承但不能被子类重写。声明final方法的主要目的是防止该方法的内容被修改。

  1. public class Test{
  2. public final void Foo(){
  3. // ...
  4. }
  5. }
  1. `final`修饰类时,这个类**不能被继承,**也即,如果一个类你永远不会被继承,就可以用`final`进行修饰。`final`类中的成员变量不会被隐式的指明为`final`,成员方法会被隐式的指定为`final`。<br />《Java 编程思想》如是说:

“使用 final 方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的 Java 版本中,不需要使用 final 方法进行这些优化了。”

2.5.2.3 abstract

abstract类似于 C++ 的 virtual
抽象类不能实例化对象,不能被abstractfinal同时修饰。有抽象方法的类一定要声明为抽象类,但抽象类可以不包含抽象方法。继承自抽象类的子类必须实现父类的所有抽象方法,除非子类也是抽象类。
抽象方法也不能被abstractfinal同时修饰。

  1. abstract class Base {
  2. private double price;
  3. private String model;
  4. private String year;
  5. public abstract void goFast();
  6. public abstract void changeColor();
  7. }
  8. class Derived extends Base {
  9. public void goFast {
  10. // ...
  11. }
  12. public void changeColor {
  13. // ...
  14. }
  15. }

2.5.3.x synchronized``transient``volatile

暂时用不到,先摸了()

2.6 运算符

整数运算的规则与C 基本相同。

  • 可以使用四则运算、取模、自增自减、移位运算与位运算。
    • 位运算中, >>> 表示无符号的右移运算,空位以0填充。
    • 整数运算中,除数为 0 时运行会报错。
  • 整数运算会出现溢出,但不会报错。

浮点数运算的规则与 C 基本相同。

  • 浮点数除数为0时不会报错,会返回几个特殊值
    • 0.0/0 // NaN
    • 1.0/0 // Infinity
    • -1.0/0 // -Infinity

关系运算符基本和 C 相同。但注意==在判断基本数据类型时是判断数据的值是否相等,在判断引用数据类型时是判断地址是否相等。如果只想判断值是否相等应该用equals()方法。

  1. public class Test {
  2. public static void main(String[] args) {
  3. int int1 = 1;
  4. int int2 = 1;
  5. String str1 = "hello2";
  6. String str2 = "hello";
  7. String str3 = str2 + 2;
  8. System.out.println((str1 == str3));
  9. System.out.println((int1 == int2));
  10. }
  11. }
  12. /*
  13. false
  14. true
  15. */

3 类

3.1 Number & Math 类

Java 语言为每一个内置数据类型提供了对应的包装类。所有的包装类(Integer, Long, Byte、Double, Float, Short)都是抽象类 Number 的子类。Java 是单根结构,基本数据类型的继承图如下:
image.png
这种由编译器特别支持的包装称为装箱,所以当内置数据类型被当作对象使用的时候,编译器会把内置类型装箱为包装类。相似的,编译器也可以把一个对象拆箱为内置类型。Number 类属于 java.lang包。

  1. public class Test{
  2. public static void main(String[] args){
  3. Integer x = 5;
  4. x = x + 10;
  5. System.out.println(x);
  6. }
  7. }

Math 类定义了基本数学函数,具体的用的时候再查吧。

3.2 String 类

最基础的构造方法如下,line3``line4``line5是在栈上创建的,并且s2s1的一个强引用,这在line10``line11``line12打印出的地址相同可以体现。(至于这三个为什么地址全都一样可以看:编译期的时候识别并且折叠了相同的字符串字面量
line5``line6``line7均是在堆上创建的,line6虽然使用s1构造,但地址和s1并不相同。
字符串的连接提供了+concat()两种方法,length()可以得到字符串的长度。

  1. public class Test {
  2. public static void main(String[] args) {
  3. String s0 = "abc";
  4. String s1 = "abc";
  5. String s2 = s1;
  6. String s3 = new String("abc");
  7. String s4 = new String(s1);
  8. String s5 = new String(new char[]{'a', 'b', 'c'});
  9. System.out.println("s0 " + System.identityHashCode(s0));
  10. System.out.println("s1 " + System.identityHashCode(s1));
  11. System.out.println("s2 " + System.identityHashCode(s2));
  12. System.out.println("s3 " + System.identityHashCode(s3));
  13. System.out.println("s4 " + System.identityHashCode(s4));
  14. // 连接
  15. String s6 = s1 + s2;
  16. String s7 = s1.concat(s2);
  17. // 长度
  18. int len = s1.length();
  19. }
  20. }
  21. /*
  22. s0 1175962212
  23. s1 1175962212
  24. s2 1175962212
  25. s3 798154996
  26. s4 681842940
  27. */

值得注意的一点是String类的对象的值是无法改变(immutable)的。从下面这段 jdk 源码也可以看出String其实是char的封装,而且还加了 final修饰,不可变也很自然。

  1. public final class String
  2. implements java.io.Serializable, Comparable<String>, CharSequence {
  3. /** The value is used for character storage. */
  4. private final char value[];

3.3 StringBuffer & StringBuilder 类

这是 String 类,StringBuffer 类以及 StringBuilder 类的继承关系:
image.png

4 package | 包

为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间,这一点类似于 C++ 的namespace

  • 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
  • 同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。
  • 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。