:::success
🚩 本篇文章记录基础语法以及 Web 技术栈
💻 使用 JDK17,环境配置略过了~
📌 面向对象的概念不在赘述,如有需要移步 面向对象程序设计
❓文中会涉及到很多和 C++ 的对比学习
:::
JAVA 语法基础
0 从HelloWorld
开始
创建一个HelloWorld.java
文件,文件名需要和类名一致。
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
运行上面的文件,输出如下:
$ javac HelloWorld.java
$ java HelloWorld
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**
:::info8位有符号数
- 最小值是-128,最大值是127,默认值是0
- byte 类型用在大型数组中节约空间,主要代替整数
byte a = 50;
:::**short**
:::info16位有符号数
- 最小值-32768,最大值32767,默认值为0
short a = 1000;
:::**int**
:::info32位有符号数
最小值,最大值,默认为0 :::
**long**
:::info64位有符号数
最小值,最大值,默认为0L :::
byte
、int
、long
、和short
都可以用十进制、16进制以及8进制的方式来表示。当使用字面量的时候,前缀 0 表示 8 进制,而前缀 0x 代表 16 进制。例如:int dec = 100;
int oct = 0144;
int hex = 0x64;
**float**
:::infoIEEE754 标准单精度浮点数
默认为0.0f :::
**double**
:::infoIEEE754 标准双精度浮点数
默认为0.0d :::
**char**
:::info16位的 Unicode 字符
- 最小值为 \u0000(0),最大值为 \uffff(65535)
可以储存任何字符 :::
**boolean**
:::info默认值为
false
-
2.2.2 常量
Java 中使用
final
关键字来修饰常量,例如final double PI = 3.14;
。常量在运行时不能被修改。2.2.3 变量
public class Test {
public String name; // 实例变量
private double salary; // 实例变量
static public String depart; // 静态类变量
public Test(String name_, double salary_) {
name = name_;
salary = salary_;
}
public void Age(){
int age = 19; // 局部变量,必须要初始化,否则编译错误
System.out.println("Age: " + age);
}
public static void main(String[] args){
depart = "dev"; // 静态变量被创建
Test test = new Test("kenshin", 5000f);
test.Age();
System.out.println("Name: " + test.name);
System.out.println("Name: " + Test.depart);
}
}
局部变量 :::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 强制类型转换
2.4 引用类型
Java 相比 C++ 没有指针,取而代之的是全部都是引用。Java 为引用类型专门定义了一个
Reference
类。Java 中的四种引用类型分别是:强引用,软引用,弱引用和虚引用。Final Reference | 强引用
public class FinalReferenceUsage {
public void stringReference(){
Object obj = new Object();
// obj = null;
}
}
这个被
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(); // 强引用
// sf就是一个软引用
SoftReference sf = new SoftReference(obj);
obj = null;
System.out.println("内存足够软引用引用的对象" + sf.get());
try {
final byte[] bytes = new byte[8 * 1024 * 1024];
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("内存不够:软引用引用的对象:" + sf.get());
}
}
}
// 编译选项: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) /
由此可见,在内存紧张的情况下,软引用被回收。
<a name="X4OjE"></a>
##### Weak Reference | 弱引用
弱引用的引用强度比软引用要更弱一些,无论内存是否足够,**只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收**。用`java.lang.ref.WeakReference`来保存对一个Java对象的弱引用。下面是一个例子👇🏻
```java
import java.lang.ref.WeakReference;
public class Test {
public static void main(String[] args) {
Object obj = new Object(); // 强引用
// sf就是一个弱引用
WeakReference wf = new WeakReference(obj);
obj = null;
System.out.println("没有回收时的弱引用对象" + wf.get());
System.gc();
System.out.println("回收后的弱引用对象" + wf.get());
}
}
/*
没有回收时的弱引用对象java.lang.Object@251a69d7
回收后的弱引用对象null
*/
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
Java 的静态修饰和 C++ 类似,在static
static
修饰的方法内没有this
指针,只能访问静态方法和静态变量,不能访问非静态方法或变量。反之,在非静态方法里可以访问静态方法和变量。static
修饰的成员变量和 C++ 基本一致,但 Java 中**static**
不允许修饰局部变量,也即static
只能作用于类变量。(大概是有悖 Java 面向对象的理念,C++ 又为了兼容 C 而背上了包袱)
static
成员变量的初始化顺序按照声明的顺序设置为默认值,但在此步中不初始化。声明结束后,按声明顺序依次设置为初始化的值,如果没有初始化的值就跳过 ```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) {
Foo.f();
System.out.println(Test.a);
System.out.println(Test.b);
} }
class Foo { Foo() { Test.a++; Test.b++; } }
/ 输出 0 1 /
1. 先声明并设置默认值,`f <- null``a <- 0``b <- 0`。
1. 再初始化,先初始化`f`,`f <- 某个地址`,`Foo`构造器完成后这时`a``b`均为1。
1. 初始化`a`为0。
1. 初始化`b`,但`b`没有初始化的值,故跳过。
`static`还有一个作用就是用来形成静态代码块以优化程序性能。
- `static`块可以置于类中的任何地方**(但不能出现在方法内部)**
- 类中可以有多个`static`块。
- 在类初次被加载的时候,会按照`static`块的顺序来执行每个`static`块,并且只会执行一次。
```java
public class Test {
Person person = new Person("Test");
static {
System.out.println("test static");
}
public Test() {
System.out.println("test constructor");
}
public static void main(String[] args) {
new MyClass();
}
}
class Person {
static {
System.out.println("person static");
}
public Person(String str) {
System.out.println("person "+str);
}
}
class MyClass extends Test {
Person person = new Person("MyClass");
static {
System.out.println("myclass static");
}
public MyClass() {
System.out.println("myclass constructor");
}
}
输出(密码asd
)static
块优先于所有构造器执行,然后按继承顺序执行构造器。
2.5.2.2 final
和 C++ 的const
有些类似,可以来修饰变量,修饰方法,不同于 C++ 的地方是可以用于修饰类。final
修饰的变量(可以是局部变量或者类变量)必须显式的指明初始值,如public static final int BOXWIDTH = 6;``final
修饰的引用在初始化之后不能再指向其他对象。
final
修饰的方法可以被子类继承但不能被子类重写。声明final
方法的主要目的是防止该方法的内容被修改。
public class Test{
public final void Foo(){
// ...
}
}
`final`修饰类时,这个类**不能被继承,**也即,如果一个类你永远不会被继承,就可以用`final`进行修饰。`final`类中的成员变量不会被隐式的指明为`final`,成员方法会被隐式的指定为`final`。<br />《Java 编程思想》如是说:
“使用 final 方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的 Java 版本中,不需要使用 final 方法进行这些优化了。”
2.5.2.3 abstract
abstract
类似于 C++ 的 virtual
。
抽象类不能实例化对象,不能被abstract
和final
同时修饰。有抽象方法的类一定要声明为抽象类,但抽象类可以不包含抽象方法。继承自抽象类的子类必须实现父类的所有抽象方法,除非子类也是抽象类。
抽象方法也不能被abstract
和final
同时修饰。
abstract class Base {
private double price;
private String model;
private String year;
public abstract void goFast();
public abstract void changeColor();
}
class Derived extends Base {
public void goFast {
// ...
}
public void changeColor {
// ...
}
}
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()
方法。
public class Test {
public static void main(String[] args) {
int int1 = 1;
int int2 = 1;
String str1 = "hello2";
String str2 = "hello";
String str3 = str2 + 2;
System.out.println((str1 == str3));
System.out.println((int1 == int2));
}
}
/*
false
true
*/
3 类
3.1 Number & Math 类
Java 语言为每一个内置数据类型提供了对应的包装类。所有的包装类(Integer, Long, Byte、Double, Float, Short)都是抽象类 Number 的子类。Java 是单根结构,基本数据类型的继承图如下:
这种由编译器特别支持的包装称为装箱,所以当内置数据类型被当作对象使用的时候,编译器会把内置类型装箱为包装类。相似的,编译器也可以把一个对象拆箱为内置类型。Number 类属于 java.lang
包。
public class Test{
public static void main(String[] args){
Integer x = 5;
x = x + 10;
System.out.println(x);
}
}
3.2 String 类
最基础的构造方法如下,line3``line4``line5
是在栈上创建的,并且s2
是s1
的一个强引用,这在line10``line11``line12
打印出的地址相同可以体现。(至于这三个为什么地址全都一样可以看:编译期的时候识别并且折叠了相同的字符串字面量)line5``line6``line7
均是在堆上创建的,line6
虽然使用s1
构造,但地址和s1
并不相同。
字符串的连接提供了+
和concat()
两种方法,length()
可以得到字符串的长度。
public class Test {
public static void main(String[] args) {
String s0 = "abc";
String s1 = "abc";
String s2 = s1;
String s3 = new String("abc");
String s4 = new String(s1);
String s5 = new String(new char[]{'a', 'b', 'c'});
System.out.println("s0 " + System.identityHashCode(s0));
System.out.println("s1 " + System.identityHashCode(s1));
System.out.println("s2 " + System.identityHashCode(s2));
System.out.println("s3 " + System.identityHashCode(s3));
System.out.println("s4 " + System.identityHashCode(s4));
// 连接
String s6 = s1 + s2;
String s7 = s1.concat(s2);
// 长度
int len = s1.length();
}
}
/*
s0 1175962212
s1 1175962212
s2 1175962212
s3 798154996
s4 681842940
*/
值得注意的一点是String
类的对象的值是无法改变(immutable)的。从下面这段 jdk 源码也可以看出String
其实是char
的封装,而且还加了 final
修饰,不可变也很自然。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
3.3 StringBuffer & StringBuilder 类
这是 String 类,StringBuffer 类以及 StringBuilder 类的继承关系:
4 package | 包
为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间,这一点类似于 C++ 的namespace
。
- 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
- 同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。
- 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。