跳转至

Coding/Java

『从C到Java』零 - 3. 初见包与访问控制修饰符

T18:12:00+08:00

一、包

是一种为了更好地组织类与代码而产生的机制。

还记得我们用 IntelliJ IDEA 创建的项目初始的样子么?

IDEAProject 视图 下,src/ 目录下有一个看起来像是名为 com.azurice 的文件夹,而在其中的 Main.java 开头也有一句 package com.azurice;。 实际上,com.azurice 并不是 一个 文件夹,而是 com/azurice/ 这样 两个套着的 文件夹,这个 com.azurice 就是一个包。

通过 ,你可以将相同功能或同一模块的类放在一个包;你可以在两个包中写两个名字相同的类,而这两个名字相同的类可以由包名区分开,进行导入与使用。

1.1 定义包

包名 是与 路径 一一对应的。

定义一个包的方法:

Java
package 包名

例如 Main.java 中的那句 package com.azurice 它规定这个文件属于 com.azurice 包,而它就在 com/azruice/ 目录下,这个包被称作 Base Package 基包,一般其他的包都创建于这个包之下,来其名称来自于创建项目时的设定。

1.2 引入包

引入一个包的方法:

Java
import 包名.类名 // 引入包中的一个类
import 包名.*   // 引入包中的所有类 

java 中有一个 java.util 包,其中包含了很多实用工具,其中就包含一个方便日期操作的 Date ,我们可以借助它来编写一个打印当前日期的程序。

Java
1
2
3
4
5
6
7
8
9
package com.azurice

import java.util.Date; // 引入 java.util包 中的 Date类

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println(new Date());
    }
}

new Date() 创建对象时,会以当前日期对其属性进行初始化。

1.3 我们来写一个包吧

比如创建一个 com.azurice.util (创建文件夹):

在这个包中创建一个 TimeUtil.java 文件。

在其中实现了一个 TimeUtil 并实现了一个用来打印当前时间的方法 printTime()

Java
1
2
3
4
5
6
7
8
9
package com.azurice.util;

import java.util.Date;

public class TimeUtil {
    void printTime() {
        System.out.println(new Date());
    }
}

然后我们就可以在 主类 中使用它了:

Java
package com.azurice;

import com.azurice.util.TimeUtil;

public class Main {
    public static void main(String[] args) {
        TimeUtil timeUtil = new TimeUtil();
        timeUtil.printTime();
    }
}

什么?报错了?

这里就要引入 修饰符 的概念了。

二、访问控制修饰符

Java中,可以使用 访问控制修饰符 来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。

  • default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口(以后会讲)、变量、方法。
  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
  • public : 对所有类可见。使用对象:类、接口、变量、方法
  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
修饰符 当前类中 同一包内 其他包
public Y Y Y
protected Y Y N
default Y Y N
private Y N N

2.1 public

我们在 com.azurice 包中能够使用 com.azurice.util 包中的类,就是因为这个类是 public 修饰的,因为 printTime() 函数没有加修饰符,所以其实它只能在同一包以及当前类中访问。我们为它加上 public 修饰的,顾名思义“公共的”。

  • 一个文件中只能有一个 public 类,且其名字要与文件名相同
Java
1
2
3
4
5
6
7
8
9
package com.azurice.util;

import java.util.Date;

public class TimeUtil {
    public void printTime() {
        System.out.println(new Date());
    }
}

可以发现,报错消失了。

运行得到输出:

Text Only
Wed Dec 22 22:46:56 CST 2021

2.2 private

如果加上 private 那么在就只有当前类中能够访问。

2.3 protected

这个,以后再讲~

三、static 修饰方法以及属性

static 意为 静态,何为 静态?就是不涉及 this(对象自身)。 一个 static 修饰的方法被称作 静态方法他被允许在对象没有被创建的情况下被调用。

我们刚刚写的函数其实并没有涉及到 TimeUtil 类型对象自身,所以我们可以把它写成静态方法:

Java
1
2
3
4
5
6
7
8
9
package com.azurice.util;

import java.util.Date;

public class TimeUtil {
    public static void printTime() {
        System.out.println(new Date());
    }
}

这样在主方法中可以不创建对象的情况下,直接使用 类名.方法名() 对其进行调用:

Java
1
2
3
4
5
6
7
8
9
package com.azurice;

import com.azurice.util.TimeUtil;

public class Main {
    public static void main(String[] args) {
        TimeUtil.printTime();
    }
}

同理,static 也可以修饰属性


差不多,有一定的理解就行啦,只是初步的形成认识,之后还要整体从头详细地讲一遍。

『从C到Java』零 - 2. 完全不一样的“对象”和“类”

T23:13:00+08:00 对象 Object,有自己的属性,自己还能够做出一些行为。

类 Class,是用来描述对象的样子的。

可以说 Class 很像结构体,而 Object 很像一个结构体变量,区别就是,类和对象其中可以有函数,在这里被称作 方法 Method

这一篇,用以初步理解 对象 的概念。

一、写个像结构体的类

1.1 声明一个 Good 类

Java
package com.azurice;

// 声明一个 Good 类
class Good {
    int id;
    int stock;
    float price;
}

public class Main {
    public static void main(String[] args) {
        System.out.println("HelloWorld!");
    }
}

是不是很像结构体。

1.2 声明一个 Good 类的对象

Java
package com.azurice;

// 声明一个 Good 类
class Good {
    int id;
    int stock;
    float price;
}

public class Main {
    public static void main(String[] args) {
        // 声明了一个 Good 类的变量
        Good g;

        System.out.println(g); // 编译器报错,变量未初始化
    }
}

这里声明出来的 g 并没有关联到一个具体的对象身上。

如果在这个时候访问这个对象,编译器会产生错误不允许编译,因为这个变量并没有初始化(对象并没有实例化)。

1.3 实例化对象

引入一个语句:new 来创建一个具体的对象(实例化对象)。

Java
package com.azurice;

// 声明一个 Good 类
class Good {
    int id;
    int stock;
    float price;
}

public class Main {
    public static void main(String[] args) {
        // 声明了一个 Good 类的对象
        Good g;

        g = new Good();

        System.out.println(g);
    }
}

得到输出:

Text Only
com.azurice.Good@4eec7777

@ 前是对象所属的类,@ 后是地址。

看看它的值:

Java
1
2
3
System.out.println(g.id);
System.out.println(g.stock);
System.out.println(g.price);

得到输出:

Text Only
1
2
3
0
0
0.0

你可以像对结构体成员操作一样来对这个对象的属性进行操作:

Java
1
2
3
4
5
6
g.id = 1;
g.stock = 7
g.price = 9.96f;
System.out.println(g.id);
System.out.println(g.stock);
System.out.println(g.price);

得到输出:

Text Only
1
2
3
1
7
9.96

1.4 整理 & 与C语言对比

Java语言

Java
package com.azurice;

// 声明一个 Good 类
class Good {
    int id;
    int stock;
    float price;
}

public class Main {
    public static void main(String[] args) {
        // 声明了一个 Good 类的对象
        Good g;

        g = new Good();

        g.id = 1;
        g.stock = 7;
        g.price = 9.96f;

        System.out.println(g.id);
        System.out.println(g.stock);
        System.out.println(g.price);
    }
}

C语言

C
#include <stdio.h>
#include <stdlib.h>

// 声明一个 Good 结构体
struct Good {
    int id;
    int stock;
    float price;
}

int main() {
    // 声明了一个 Good 结构体的指针
    struct Good *g;

    g = (Good *)malloc(sizeof(struct Good));

    g->id = 1;
    g->stock = 7
    g->price = 9.96;

    printf("%d\n", g->id);
    printf("%d\n", g->stock);
    printf("%f\n", g->price);
    return 0;
}

二、给类写点不像结构体的东西

2.1 方法

刚刚提到,类之中可以包含函数,叫做 方法

程序开始运行的 "Main 函数",在 "class Main"(主类)中,因此我们称其为 "Main 方法"。

那么我们在类里写一个打印信息的函数试试:

Java
struct Good {
    int id;
    int stock;
    float price;

    void printInfo() {
        System.out.println("id: " + id);
        System.out.println("stock: " + g.stock);
        System.out.println("price: " + price);
    }
}

在主方法中可以调用:

Java
public static void main(String[] args) {
    // 声明了一个 Good 类的对象
    Good g;

    g = new Good();

    g.id = 1;
    g.stock = 7;
    g.price = 9.96f;

    g.printInfo(); // 调用 g 的 printInfo 方法
}

得到输出:

Text Only
1
2
3
id: 1
stock: 7
price: 9.96

2.2 另一个方法

当然,方法也可以有参数,有返回值,还可以修改自身的属性:

Java
struct Good {
    int id;
    int stock;
    float price;

    void printInfo() {
        System.out.println("id: " + id);
        System.out.println("stock: " + stock);
        System.out.println("price: " + price);
    }

    // 成功卖出返回 true,否则返回 false
    boolean sell(int num) {
        if (num > stock) {
            return false;
        } else {
            stock -= num;
            return true;
        }
    }
}

主方法:

Java
public static void main(String[] args) {
    Good g;

    g = new Good();

    g.id = 1;
    g.stock = 7;
    g.price = 9.96f;

    g.printInfo();
    if (!g.sell(8)) {
        System.out.println("Don't have enough stock");
    } else {
        System.out.println("Sell success");
    }
    g.printInfo();
    if (!g.sell(2)) {
        System.out.println("Don't have enough stock");
    } else {
        System.out.println("Sell success");
    }
    g.printInfo();
}

得到输出:

Text Only
id: 1
stock: 7
price: 9.96
Don't have enough stock
id: 1
stock: 7
price: 9.96
Sell success
id: 1
stock: 5
price: 9.96

2.3 更特殊的东西 —— 构造函数

相信注意到了,实例化对象的语句中使用的是类名加(),很像是在调用函数:

Java
g = new Good();

其实确实是这样,这个函数叫做构造函数,默认的构造函数参数为空,而我们可以为类亲自写一个构造函数:

Java
struct Good {
    int id;
    int stock;
    float price;

    Good() {

    }

    // ...
}

构造函数就是一个名字与类名相同的函数,参数随意,其作用就是”构造”,比如可以传进去 idstockprice 直接为此对象完成初始化。

按照局部优先,如果参数名与属性名相同的话,用 id 访问到的其实是参数的 id 而非这个对象内部的属性 id

所以需要让参数名改一改才行:

Java
1
2
3
4
5
Good(int _id, int _stock, float _price) {
    id = _id;
    stock = _stock;
    price = _price;
}

或者有一个新的东西叫做 this 它代表的是当前的对象:

Java
1
2
3
4
5
Good(int id, int stock, float price) {
    this.id = id;
    this.stock = stock;
    this.price = price;
}

都可以实现在实例化的时候就为对象初始化属性值的功能。

主方法:

Java
1
2
3
4
5
6
7
public static void main(String[] args) {
    Good g;

    g = new Good(1, 7, 9.96f);

    g.printInfo();
}

得到输出:

Text Only
1
2
3
id: 1
stock: 7
price: 9.96

三、所谓的 “面向对象” 到底是什么

现在大概已经对 对象 有了初步的理解。

我们一直在说 C语言 是面向过程的,而 Java语言 是面向对象的,那这两个词到底是什么意思呢?

考虑一个图书管理系统,用 C语言 来写的话,是考虑一个个的功能,去实现一个个的函数,再把这些函数串起来,像是你学会了做一件件事情的方法,然后去使用这一个个方法达成目标;而用 Java语言 来写的话,是一个个的类,每一个类有自己的属性和方法,像是你教会了一个个小人他们的职责,然后指挥一个个小人去做事情。

例如上面的购买商品的例子:

面向过程 ,算法 + 数据结构,是考虑到 商品购买,实现购买商品的函数。当整个项目体量很大的时候,用这种思考方式要考虑的内容实在太多。

面向对象 ,是考虑 商品本身,商品本身的属性以及它所能有的行为(例如:被购买,数量减少),还有商品与其他类型的对象之间的关系。就算当整个项目体量很大,再具体实现某一个类的某一个方法的时候,需要考虑的内容要没有那么复杂,同时这种思维方式也更接近人一些。当你将项目中的对象们都完善完成,你会发现,整个项目十分清晰。

之后还要接触到 Java类继承多态 等一系列性质,对 面向对象 的理解也会逐步加深。


以上。

『从C到Java』零 - 1. 那些和C挺像的

T18:12:00+08:00

一、基本类型

整数:

数据类型 类型名 大小(Byte) 数据范围 默认值
byte(💥标准C没有💥) 字节型 1 -128 ~ 127 0
short 短整型 2 -32768 ~ 32767 0
int 整型 4 -2147483648 ~ 2147483647 0
long 长整型 8 -9223372036854774808~9223372036854774807 0L

小数:

数据类型 类型名 大小(Byte) 数据范围 默认值
float 浮点型 4 3.402823e+38~1.401298e-45 0.0f
double 双精度浮点型 8 1.797693e+308~4.9000000e-324 0.0d

字符:

数据类型 类型名 大小(Byte) 数据范围 默认值
char 字符型 1 '\u0000'(0) 到 '\uffff'(65535) '\u0000'(空格)

💥 java 中的 字符 是一个 16位Unicode字符。💥

有关字符编码,见这篇 字符编码 【还没写】

布尔型:

数据类型 类型名 大小(Byte) 数据 默认值
boolean(💥标准C没有💥) 布尔型 是个谜,不过挺小 truefalse false

二、运算符

2.1 算数运算符

C/C++。

2.2 关系运算符

C/C++。

2.3 位运算符

C/C++,多了一个 >>> 就是左边会补零的按位右移。

什么?你没学?快滚去把 C语言的位运算看了!传送门:嘿嘿还没写

2.4 逻辑运算符

C/C++

同样有短路效应

2.5 赋值运算符

C/C++

2.6 三目运算符

C/C++

三、选择语句

3.1 if 语句

C/C++

3.2 switch 语句

C/C++

但是!!!有一点区别!!!

Java
switch(expression){
    case value1 :
        // statement
        break;
    case value :
        // statement
        break;
    default :
        // statement
}
  • 💥Java7expression 的值可以是字符串类型的,对应的 value 也应该是常量。💥
  • 💥Java14 起 正式支持 witch表达式,可以这么写:
Java
1
2
3
4
5
6
7
8
9
char grade = switch(expression){
  case value -> // statement
    case value1, value2, value3 -> {
        //statement
        //statement
        yield 'A'; // 返回 'A'
    }
    default -> 'F'
}

??但是在基础数据类型里不是没有字符串么??

不要着急,后面会讲

四、循环语句

4.1 for 语句

C/C++

4.2 while 语句

C/C++

4.3 do ... while 语句

C/C++

五、“函数”

定义的方式是一样的,不过在 Java 里,它们并不是一样的东西,以后慢慢讲。


差不多这些?

其他的数组啊、文件输入输出啊、字符串啊,什么的,慢慢讲吧。

『从C到Java』零 - 0. 起步

T18:06:00+08:00

一、Java 与 C 的不同

先来看看一个 C语言程序 从编写到运行所经历的过程:

flowchart LR
    subgraph 源代码
        A[main.c]
        a[utils.c]
    end

    A --预处理--> B[main.i]
    a --预处理--> b[utils.i]

    subgraph 汇编代码
        C[main.s]
        c[utils.s]
    end

    B --编译--> C
    b --编译--> c

    subgraph 机器码
        D[main.o]
        d[utils.o]
    end

    C --汇编--> D
    c --汇编--> d
    机器码 --链接--> 可执行程序
    可执行程序 --> z([运行])

源代码 编译为 汇编代码 再汇编为 机器码 再链接为最终的 可执行程序

不同平台(如 Max, Windows, Linux...)所识别的机器语言是不同的。

C语言程序 在不同平台运行的实现方式是 针对不同平台进行特定的编译操作 得到对应平台的机器码,即 到处编译,到处运行

Java跨平台 的语言,是无论对于什么目标平台都编译为统一的 字节码,在不同的平台上引入一个 虚拟机 来执行这些字节码(将这些字节码“翻译”为对应平台的机器码并执行),即 一次编译,到处运行,而这个虚拟机有一个名字 —— Java Virtual Machine(JVM)

不过这样也有不方便的缺点,即要想运行就需要有 JVM 环境。

以下是一个 Java语言程序 从编写到运行所经历的过程:

flowchart LR
    subgraph 源代码
        A[.java]
    end

    subgraph 字节码
        A --编译--> B[.class]
    end

    字节码 --> z([通过JVM运行])

因此,开发一个 Java语言程序 所需要的东西就很清晰了:

  1. 编译器
  2. 虚拟机

Java8 及之前版本时,发行的工具包有两个版本:

  • Java Runtime Environment(JRE) 其中只包含 JVM
  • Java Development Kit(JDK) 其中包含 JVM 以及 编译器

Java8 之后,就只发行 JDK 了。

Pasted image 20220104175443.png

图:JDKJRE 的关系 可以在这个网站深入挖掘一下 Java Platform Standard Edition 8 Documentation (oracle.com)

二、开发环境配置 - 上

Java 最初的开发公司是 Sun公司 后来被 甲骨文(Oracle)公司 收购,这是 Oracle官网

Oracle | Cloud Applications and Cloud Platform

在其中可以找到 JDK 的下载页面(Products -> Java -> Download Java):

Java Downloads | Oracle

在这里,很多 Java 版本并没有被显示出来,目前出现在页面中可以下载的 Java8, Java11Java17 是一些长期支持 Long-Term Support(LTS) 的版本。

LTS版本 即为开发商保持维护(修bug之类的工作)的版本。

如今 Java 每6个月便会迭代一个主要版本,公布一些新特性以及虚拟机的优化。

直接冲最新版就好了😁。

Pasted image 20220103150650.png

选择 Windows 下载 Installer (两个都行),然后安装一路下一步(最好不要改位置),之后它应该会出现在这里:

Pasted image 20220103150729.png

打开看一看里面的结构:

Text Only
1
2
3
4
5
6
7
jdk-17.0.1
    ├─bin
    ├─conf
    ├─include
    ├─jmods
    ├─legal
    └─lib

bin/ 目录下是可执行文件,其中就包含我们会经常使用到的:

  • 编译器 javac.exe
  • JVM虚拟机
  • java.exe
  • javaw.exe w代表windows,不会显示控制台内容(在运行有 图形用户界面 Graphical User Interface(GUI) 的程序时就不会出现控制台的黑框。

此时打开 CmdPowerShell,输入 java --version,应该会得到如下内容:

PowerShell
1
2
3
4
PS C:\Users\azurice> java --version
java 17.0.1 2021-10-19 LTS
Java(TM) SE Runtime Environment (build 17.0.1+12-LTS-39)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.1+12-LTS-39, mixed mode, sharing)
  • 打开 Cmd 方法

Windows + R 输入 cmd 确定。

Pasted image 20220103150807.png - 打开 PowerShell 方法

Windows + R 输入 powershell 确定。

Pasted image 20220103150828.png

如果没有,提示不是命令或无法识别的话还需要配置环境变量:

在终端输入命令运行一个程序的时候,计算机会在当前目录去查找对应的可执行文件。

例如如果当前目录有一个 nb.exe,在 Cmd 中输入 nbnb.exePowerShell 中为 ./nb./nb.exe)的时候会在当前目录去查找并运行,找不到就会报错。

环境变量中的 Path 就包含很多路径,使计算机也在这些路径中进行查找。

自行百度查找 修改环境变量,打开下面的界面,编辑 Path 添加刚刚的 bin/ 目录

Pasted image 20220103151002.png

然后一路确定,重启一下大概就好了。

🐳、宕开一笔 小试一下

找个地方新建一个 HelloWorld.java,输入以下内容:

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

之后保存。

在文件所在位置打开 CmdPowerShell(Win10):

如果你是 Win11 的话,可以直接打开特别特别特别好用的 Windows Terminal 来使用 CmdPowerShell

(Windows Terminal 和 Cmd 或 PowerShell并不是一个东西,Terminal更像是个外壳,内容是后者,具体详细知识见 Shell 与 Terminal 的区别【还没写】)。

然后介绍一些基础命令来切换当前目录到文件所在目录:

  • 盘符 来切换到对应盘符下
  • cd xxx 来切换到xxx目录
  • dir 列出当前目录下的文件

Pasted image 20220103151133.png

目录、文件名什么的不用手动打完的,可以输一个 code 甚至 cod,按几下 tab键,就可以自动补全

🎈当然也有快捷的直接在目录打开的方法:

Pasted image 20220103151216.png

直接在这里输入 cmdpowershell 回车就可以啦。

🎈对于 powershell 也可以按着 Shift 右键当前目录的空白处:

Pasted image 20220103151237.png

🎈🎈🎈当然,Win11Windows Terminal 最方便,直接右键空白处:

Pasted image 20220103151249.png

打开以后我们来 编译运行 HelloWorld.java

注意下面执行的命令是 PS xxxxxx> 后面的内容!

  1. 编译
PowerShell
PS F:\Codes\Java> javac .\HelloWorld.java

你会发现多了一个 .class 字节码文件 2. 运行

PowerShell
PS F:\Codes\Java> java HelloWorld

得到输出:

PowerShell
PS F:\Codes\Java> java HelloWorld
Hello World

到这里,大概应该已经对 Java语言 有所了解了。

接下来推荐几款 集成开发环境 Integrated Development Environment(IDE),当然你要是想 繁华落尽见真淳 文本编辑器 + 命令行也不错,比如我这种 NeoVim 忠诚教徒

不过对于比较大的项目,当然还是 IDE 配置成本低一些,所以我的 NeoVim 主要写 C 或小体量项目。

二、开发环境配置 - 下

我就只推荐一个 —— IntelliJ IDEA

Jetbrain全家桶yyds

下载页面:下载 IntelliJ IDEA:JetBrains 功能强大、符合人体工程学的 Java IDE

注意要下载 Community 版本(除非你有钱,或者你像我一样申请了学生包✈️)

安装依旧一路下一步,这里建议把这个 Open Folder As A Project 勾上,蛮实用的:

三、IntelliJ IDEA 的使用

3.1 创建项目

image-20211220142847296

创建一个 HelloWorld 项目:

image-20211220143356158

Base package 暂时先不用管,以后会讲。

image-20211220143905509

创建完成:

image-20211220143948720

看一下目录结构:

Text Only
1
2
3
4
5
6
7
HelloWorld
    ├─.idea
    ├─src
    │   └─com
    │       └─azurice
    │           └─Main.java
    └─HelloWorld.iml
  • .idea/ 目录

IntelliJ IDEA 的项目相关文件,不用管 - src 目录

存放源码的目录 - HelloWorld.iml 文件

项目相关文件,不用管

3.2 运行

image-20211220144400314

image-20211220144443543

会在内置的终端运行:

image-20211220144630401

有些时候并不好用。。。(比如调试CSI序列等内容时)

3.3 调试

image-20211220145012949


那么,这篇大概就这样了?

一会见!