Java编程 I 基础

———— 基于《On Java》

什么是对象?

在许多编程书籍和技术博客中,对象常被定义为“一个真实存在的实体”或“方法和属性的集合”。

然而,这些定义往往只停留在表层,并未真正解释“什么是对象”这一核心问题。比如,“实体”本身是什么意思?为什么属性和方法的组合可以称为一个‘对象’?带着这些问题,我们可以从设计层面重新思考对象的本质。

设计层面:对象是面向目的的实体特征与行为的抽象。

什么是实体?

动物是不是一种实体?房屋是不是一种实体?房屋?比特币呢?笛卡尔的“我思故我在”呢?

实体并不一定是以“真实存在”作为必要条件的,实体的重要特征是“一种可描述的概念”。比如“独角兽”是一只长有犄角和翅膀的马,比如“西方的恶龙覆盖有厚厚的鳞片能够喷吐火焰”。这些概念的重点不是“真实存在”,而是“可描述”。推而广之,一些抽象概念也可以作为“实体”,比如 FIFO的队列,代理模式中的代理概念。

这样我们就可以得到实体的定义 “一种可描述的概念”(无论它是否真的存在)

那么什么是对象?

我们永远没办法将一个实体完整地用计算机语言去描述。但可以将特征和行为抽象出来,作为我们用计算机去模拟这个物体的方法。这暗含着对象设计是面向功能的,有取舍的。因为我们实际关注的不是实体到底是怎么样的,而是关注实体通过哪些特征和行为达成了我们想要的结果。当我们考虑一头牛的时候,在虚拟世界里,我们不会去考虑牛住在哪里,何时休息。考虑的是我们为了达成目的,需要”供应“什么,“产出”什么。所以我们在设计时真正构建的对象是以目的为核心,对实体特征和行为的抽象。目的是我们的最终结果,而实体的特征和行为是为了达成目的所必须的组件

实现层面:对象是经过结构化组织的内存。

内存与对象

程序员对内存和程序结构拥有近乎完全的控制权。他们可以自由地决定每一个内存地址的用途,这在空间受限的场景中显得尤为高效,可以最大限度地压榨出每一字节的潜力。但也导致了一些问题:

在功能设计的初期就需要周全的考虑对象(结构体)的设计,不然会加大后期的维护,重构,新增功能的复杂度。

编程人员对程序有绝对的权限,意味着相同的功能可能会设计不同的系统来处理相同的流程,如内存分配/回收。消息处理。对于多人开发的大型系统会增加系统的复杂度。

在这一层面上,一些更现代的编程语言选择放弃了C语言的“自由“,现代系统的设计都是层次化的,当我进行上层数据的处理时,仍然要去关心最底层的内存是如何排布的显然是一个不够高效的做法。因此它们开始去预定义一些结构化的内存,从”内存交给你,你来从0开始完成系统“,到“由我提供一些基础组件,你在这个基础之上组织系统”。通过对内存块的结构化设计,将“设计-实现”的问题,变成了“组织-实现”。

另一方面因为所有基础模块是编程语言预先定义的,所以在后期使用时,作为内存-系统的中间层(如一些虚拟机程序),可以对系统中的内存使用进行更好的控制,这样既可以通过系统的管理,将已加载的功能模块灵活复用,也可以更好的提供系统级的监控管理,像垃圾回收,对象的动态加载创建等,对于更上层的编程人员来说,进行类设计,对象设计。反而是一些顺便带来的好处。

那么什么是面向对象?

当我们明白了对象是一种可描述的概念,并且对象设计的核心是面向功能的,以及为什么使用对象而不是直接使用内存来设计我们的系统。那么面向对象就是在设计系统时,首先完成对系统功能的抽象,将系统的实现变为多个核心对象之间的消息通信和方法调用。如:

对数据进行存储和维护而设计的类,如用户类,仓库类

对数据组织结构进行抽象的类,如数据包类,工厂类

以某种操作流程为核心建模的设计的类,队列、栈

通过这种方式,我们不仅构建了更具结构化的系统,同时也提升了系统的可读性、可维护性与可扩展性 —— 这正是面向对象设计的核心价值所在。

一些扩展性设计内容

万物皆对象

允许向对象发出请求,让它执行一些操作,对于你想要解决的问题中的任何元素,你都可以在程序中用对象来呈现

一段程序实际上就是多个对象通过发送消息来通知彼此要干什么

通过将现有的几个对象打包在一起,你就创建了一种新的对象。

每一个对象都有类型。

每个对象都是通过类生成的实例,类 就等同于 类型,一个类最为显著的特征是 你可以发送什么消息给它

同一个类型的对象可以接收相同的消息

Class

定义一个类

1
2
3
class ATypeName {
// 类具体实现
}

类中允许定义两类元素: 方法和属性

1
2
3
4
5
6
7
8
9
10
11
12
class DataOnly{
int i;
double d ;
boolean b ;
}

DataOnly data = new DataOnly();
/* DataOnly 类声明
* data 变量名
* new 关键字用于 申请内存,调用初始化方法,返回指向该对象的指针
* DataOnly() 调用类创建实现的对象
*/

通过”.” 访问对象的成员

1
2
3
data.i = 47;
data.d = 1.1;
data.b = false;

基本类型的初始化

java中会自动进行类中定义的变量的初始化

函数中的局部变量不会主动进行初始化,但是会被jvm检查

1
2
3
4
5
6
7
8
boolean           false 
char \u0000 null
byte (byte)0
short (short)0
int 0
long 0L
float 0.0f
double 0.0d

方法,参数以及返回值

方法的定义

1
2
3
ReturnType methodName(/* 参数列表 */){
// 方法体
}

定义一个带参的方法

1
2
3
4
int storage (Strings s){
return s.length() * 2;
}

static 关键字

通过static关键字来对 类中的属性和方法进行管理,使它们从属于类,直接与类进行绑定。这样在使用时可以直接通过调用类来完成对这些属性和方法的设置与绑定。

这里可以结合我们的上一节进行思考,即通过static 关键字使得 对应的方法和属性存储在类结构中的静态字段,并且在类加载阶段就通过索引完成了与类的绑定。

当类中的静态属性或者方法被调用时,也会触发类的初始化

定义一个静态方法

1
2
3
class StaticTest{
static int i = 47 ;
}

Java程序的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//objects/HelloDate.java
import java.util.* ;

public class HelloDate{
public staic void main(String[] args){
System.out.print("Hello, it's: ")
System.out.println(new Date());
}
}

// import 向文件中导入需要的库
// public class HelloDate{}
// 声明一个公有类,这意味着允许其他所有对象对这个类进行访问
// class 定义这是一个类 HelloDate 类名 后续是该类的代码块
// public static void main(String[] args)
// 定义了一个静态公有方法main,这意味着它是由从类中调用的。返回值是void
// 后续是参数列表,声明了接收一个字符数组 args 作为参数。后续是函数的函数体
// System.out.println()
// 调用了来自System.out库中的println() 方法,它接收一个字符信息并且在打印后换行

java文件需要进行一个编译/执行的过程

1
2
3
4
5
6
7
javac HelloDate.java # 将java文件编译为字节码文件HelloData.class
java HelloData # 执行编译后的文件
# 在较新版本的java 中可以直接使用java 执行java文件
java HelloData.java

# 两种情况均会输出 :
Hello , it's Sat Apr 19 16:53:22 CST 2025

编程风格

类名通常遵循大驼峰命名法

类似 : AllTheColosOfTheRainbow 首字母大写

操作符

左值与右值

在使用操作符赋值时,需要确定 “=” 两边的能力。

左值 : 必须是一个独特的命名变量

右值 : 可以是任何常量,变量或者可以产生值的表达式

左值具有特殊性,即可以通过左值确定的查询到右值的内容

而右值必须是一个可返回的值。(即存在的值)

当左值指向一个对象

一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Tank{
int level ;
// static int level ;
}
public class Assignment{
public static void main(String[] args){
Tank t1 = new Tank();
//Tank t2 = t1 ;
Tank t2 = new Tank();
t1.level = 9 ;
t2.level = 47 ;
System.out.println("1: t1.level "+t1.level+",t2.level: "+ t2.level);
t1.level = 27;
System.out.println("3: t1.level "+t1.level+",t2.level: "+ t2.level);
}
}

// 情况1 level 没有被 static 修饰,那么它会作为一个实例变量被处理,
// 当Tank t1 和Tank t2 分布创建了对应的Tank() 对象时,t1.level 和t2.level是互相独立的
// 情况2 将level作为一个静态变量处理,那么它会随着类加载被初始化为0.
// 创建的t1,t2两个实例中查询到的都是同一个level变量,所以当t2修改level时,t1中也会产生响应的修改
// 情况3 创建了t1的对象,并创建t2指向t1。 这时t1和t2都是对new Tank()的引用
// 所以当t1修改了level的值时,过程是t1 查询到它引用的对象中level变量的值,并修改
// 这时的t2也是指向该对象的,所以当t1修改后,t2去查询变量的值时已经变为了修改后的值。

方法调用中的别面馆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Letter {
char c;
}

public class PassObject {
static void f(Letter y){
y.c = 'z';
}
public static void main(String[] args){
Letter x = new Letter();
x.c = 'a';
System.out.println("1: x.c: "+ x.c);
f(x);
System.out.println("2: x.c: "+ x.c);
}
}

当传入一个对象作为函数的参数时,对该对象的属性进行修改,也会对原对象产生修改。

本质是因为向函数传递的值本身就是一个对象的地址,即使是作为副本被传进去,仍然会获取到源对象

算数操作符

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
import java.util.* ;

public class MathOps{
public static void main(String[] args){
Random rand = new Random(47);
int i , j , k ;
j = rand.nextInt(100)+1;
System.out.println("j : "+ j );
k = rand.nextInt(100)+1;
System.out.println("j : "+ j );
i = j + k ;
System.out.println("j + k : " + i );
i = j - k ;
System.out.println("j - k : " + i );
i = k / j ;
System.out.println("j / k : " + i );
i = k * j ;
System.out.println("j * k : " + i );
i = k % j ;
System.out.println("j % k : " + i );
j %= k;
System.out.println("j %= k : " + j );
float u,v,w ;
v = rand.nextFloat();
System.out.pruntln("v : "+ v );
w = rand.nextFloat();
System.out.pruntln("w : "+ w );
u = v + w ;
System.out.pruntln("v + w : " + u );
u = v - w ;
System.out.pruntln("v - w : " + u );
u = w / v ;
System.out.pruntln("v / w : " + u );
u = w * v ;
System.out.pruntln("v * w : " + u );
u = w % v ;
System.out.pruntln("v % w : " + u );
v %= w;
System.out.pruntln("v %= w : " + v );
}
}
// nextInt() 获取一个随机整数, nextFloat()获取一个随机浮点数

一元操作符

“ - ” 反转变量符号

1
2
int i = 4 ;
int c = -i ; // c = -4 ;

“++” 自增操作符

1
2
3
4
5
int i = 4 ;
int c = 2 + i++ ; // i++ 先返回i原值参与运算,再将i自增
System.out.println("c: "+c+", i: "+i); // 输出 6,5
int d = 2 + ++i ; // ++i 先对i进行自增运算,再进行外部运算
System.out.println("d: "+d+", i: "+i); // 输出 8, 6

“ - - ” 自减运算

1
2
3
4
5
int i = 4 ;
int c = 2 + i-- ; // i++ 先返回i原值参与运算,再将i自增
System.out.println("c: "+c+", i: "+i); // 输出 6,3
int d = 2 + --i ; // ++i 先对i进行自增运算,再进行外部运算
System.out.println("d: "+d+", i: "+i); // 输出 4, 2

二元运算符

关系操作符

“==” 用于 判断两个对象的地址是否一致,

.equals 用于判断 两个对象的值是否一致

在java 9 以及以后的版本中 :

-127 - 127 作为缓存整数值,当使用 value 或valueof 时,会使用同一个int对象。所以这时的 value和 valueOf 都会返回true

128 + 则都需要通过 new 来创建

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
public class Equivalence{
static void show(String desc , Integer n1 , Integer n2){
System.out.println(desc + ":");
System.out.println(
"%d==%d %b %b%n", n1 , n2 , n1 == n2 , n1.equals(n2));
}
@SuppressWarnings("deprecation")
public static void test(int value){
Integer i1 = value ;
Integer i2 = value ;
show("Automatic",i1,i2);
Integer r1 = new Integer(value);
Integer r2 = new Integer(value);
show("new Integer()",r1,r2);
Integer v1 = Integer.valueOf(value);
Integer v2 = Integer.valueOf(value);
show("Integer.valueOf()", v1 , v2);
int x = value ;
int y = value ;
System.out.println("Primitive int :");
System.out.println("%d==%d %b%n", x, y, x == y);
}
public static void main(String[] args){
test(127);
test(128);
}
}

逻辑运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.*;

public class Bool {
public static void main(String[] args){
Random rnd = new Random(47);
int i = rnd.nextInt(100);
int j = rnd.nextInt(100);
System.out.println("i="+i);
System.out.println("j="+j);
System.out.println("i > j is " + (i>j) );
System.out.println("i < j is " + (i<j) );
System.out.println("i>=j is " + (i>=j) );
System.out.println("i<=j is " + (i<=j) );
System.out.println("i==j is " + (i==j) );
System.out.println("i!=j is " + (i!=j) );
System.out.println("i<10 && j>10 is " + ((i<10) && (j>10)) );
System.out.println("i<10 || j>10 is " + ((i<10) || (j>10)) );
}

}

短路

当进行逻辑运算时,中间的某个条件为false, 就不会再去执行后续条件的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ShortCircuit {
static boolean test1(int val){
System.out.println("test1("+val+")");
System.out.println("result: "+(val<1));
return val<1;
}
static boolean test2(int val){
System.out.println("test2("+val+")");
System.out.println("result: "+(val<2));
return val<2;
}
static boolean test3(int val){
System.out.println("test3("+val+")");
System.out.println("result: "+(val<3));
return val<3;
}
public static void main(String[] args) {
boolean b = test1(0) && test2(2) && test3(2);
System.out.println("expression is " + b);
}
}

字面值

可以通过一些字面值来表示某些对象的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Literals {
public static void main(String[] args) {
int i1 = 0x2f; //十六进制
System.out.println("i1 : "+Integer.toBinaryString(i1));
int i2 = 0x2F; //十六进制
System.out.println("i2 : "+Integer.toBinaryString(i2));
int i3 = 0177; //八进制
System.out.println("i3 : "+Integer.toBinaryString(i3));
char r = 0xffff; //十六进制
System.out.println("r : "+Integer.toBinaryString(r));
byte b = 0x7f; //八进制
System.out.println("b : "+Integer.toBinaryString(b));
short s = 0x7fff; //十六进制
System.out.println("s : "+Integer.toBinaryString(s));
long n1 = 200L;
long n2 = 200l;
long n3 = 200;
System.out.println("n1 : "+Long.toBinaryString(n1));
System.out.println("n2 : "+Long.toBinaryString(n2));
System.out.println("n3 : "+Long.toBinaryString(n3));

}
}

按位运算和位运算

按位运算是以二进制形式,对每一位进行与或非

位运算是直接操作值的位移运算

1
2
3
4
5
6
7
8
9
10
11
12
public class Literals {
public static void main(String[] args) {
int i = 13;
int j = 2 ;
System.out.println((i&j)); // 与 不同输出0, 相同输出1 所以输出0
System.out.println((i|j)); // 或 有1取1 输出 15
System.out.println((i^j)); // 非 不同输出1, 相同输出0 所以输出15
System.out.println((j<<1)); // 位运算 向左移1位 即 01 -> 10
System.out.println((j>>1)); // 位运算 向右移1位 即 10 -> 01

}
}

三元运算符

通过使用 ? 来进行判断的快速赋值 。 这时要求 两个值必须存在返回值

1
2
3
4
5
6
public  class Literals {
public static void main(String[] args){
int a = 2 > 3 ? 4 : 5;
}

}

流程控制

条件判断语句

if … else 根据条件判断决定执行的流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public  class Literals {
public static void main(String[] args){
int a = 3 ;
if (a==3) {
System.out.println("a is 3");
}else if (a==4) {
System.out.println("a is 4");
}
else {
System.out.println("a is not 3");
}
}

}

迭代语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public  class Literals {
public static void main(String[] args){
int a = 0 ;

while(a>5){
a++;
}
// while 循环
do {
a++;
}while(a>5);
// do while 循环 代码块至少会被执行一次
for(int i = 0;i<5;i++){
a++;
}
// for 循环 , for中允许使用“,” 定义多个值
}
}

使用for … in 来进行迭代

1
2
3
4
5
6
7
8
9
public  class Literals {
public static void main(String[] args){
int[] f = new int[]{1,2,3,4,5,6,7,8,9,10};
for(int a : f){
System.out.println(a);
}
}

}

循环中的控制符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
break // 跳出当前循环
continue // 跳过本次循环的后续步骤,继续循环
return // 从函数中返回

public static int somenum(int a){
while (a < 10){
if(a ==6 ){
a++;
continue; // 跳过后续流程,执行下一次循环
}else if(a == 7){
a++;
break; // 跳出循环
}else if(a == 8){
return a ; // 直接返回
}
a++;
}
return a ;
}

// 因为当a==7时 a继续自增了 之后break -> return 。所以最后输出 8

swtich 多条件分支时可以使用switch

也可以使用字符串进行分支选择

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
public static int somenum(int a){
while (a <= 10){
switch(a){
case 6 :
System.out.println("a is 6");
break;
case 7 :
System.out.println("a is 7");
break;
case 8 :
System.out.println("a is 8");
break;
case 9 :
System.out.println("a is 9");
break;
default :
System.out.println("a is not 6,7,8,9");
break;
}
a++;
}
if (a == 10){
return a ;
}
}

Java编程 I 基础
http://gadoid.io/2025/04/21/Java编程-I-基础/
作者
Codfish
发布于
2025年4月21日
许可协议