javac 编译程序到字节码。
java 通过虚拟机来运行 class 程序。
jshell 一个类似与解释器一样的 shell。

类名与文件名一定要一模一样,不然报错。

变量

定义变量必须先约定用来那种类型的值,存就用 int

int x;
x = 1
int = 2;

int 能存的值还有个范围大约是正负 21 亿,超出范围就无法处理。

变量的名字(标识符)不能用与数字开头,也不能用像 publicint 等关键字,作为标识符。

Java 程序的结构:

  1. 表达式(expression), 像是 int x; 就是声明了一个 x 变量,除去分号,剩余的都是表达式,表达式一般用于运算。
  2. 语句(statement),一个带分号的表达式就是语句,像单个 ; 就是一个空的表达式,语句是由多个表达式构成,像是 int x = 1 + 2;,就是由变量 x 和 1 + 2 这两个表达式构成。
  3. 代码块(code blocks),一个带大括号的方法就是代码块,像 {} 就是一个空的代码块,像我们敲的 main 方法就是一个代码块。

数据类型

整数

byte 占用 1 个 byte,值域(取值范围) -128 ~ 127。

short 占用 2 个 byte ,值域(取值范围) -32768 ~ 32767。

int 占用 4 个 byte -2147483648 ~ 2147483647,在 Java 中整数默认值是 int。

long 占用 8 个 byte -9223372036854775808 ~ 9223372036854775807。

当你声明一个值使用 long shortValue = 99,因为是整数类型嘛,所以是默认 int 型,并不是使用了 long 关键词声明就成为了 long 类型,只要当你超出 int 范围并且加了 L 才是正确声明 long,比如 long shortValue = 999999999L,在数字最后一位加上 L 此时才是 long,不加 L 超出范围则会报错,不推荐使用小写字母 l,不好和数字 1 区分。

浮点数

float 占用 4 个 byte 有一个超大的范围值。

double 占用 8 个 byte 精度是 float 两倍,既然精度是两倍范围值也肯定是两倍吧(待确认)。在 Java 中浮点数默认值是 double。

因为浮点数默认值是 double 类型,所以在声明 float 类型的浮点数时要加个 f,比如 float floatValue = 1.1f,必须要加 f,一样是不区分大小写。

浮点数精度的问题是所有语言都无法避免的,参考 FAQ 01章-为什么计算机里的浮点数不精确.md

符号位

为什么都是从负数到正数?比如一个 byte 有 8 位 bit,其中一位必须用来标识你存的这个数是整数还是负数,0 代表这个数为整数,1 则是负数。

布尔和字符

boolean 占用 1 个 byte,值域 true,false。在 Python 里大写开头

char 占用 2 个 byte,值域 任意字符。

如果使用字符串必须使用双引号,将一个个字符串起来嘛。关于字符串在 Java 中不是基础数据类型的一部分,不过可以使用 String 来声明,例如 String user = "用户名",不过 String 不是 Java 的保留字。

如果想要表示单个字符,就可以用单引号将字符引起来 'A',当你在里面输入多个字符就会报错。char 可以和 int 类型相互转换,一个字符对应着 ASCII,这个 ASCII 其实就是数字。

char x = 'a' + 1;
int z = 65;
char c = (char) z;

System.out.println(x);
System.out.println(c);

数据类型自动转换

在做数字运算时,低精度和高精度做运算,会自动转换未高精度类型。

精度从大到小排列:double > float > long > int > short > byte

从高精度转为低精度可能会有一个数据丢失的问题,所以需要手动强制转换,具体操作时在数值前写一个小括号,括号里填类型,(类型) 数值

所以这就需要你考虑到要操作的数值一个范围,去选择合适的数据类型来存储,比如有两个相同类型的变量计算得到的结果不能超出此数据类型能够存储的阈值,像两个 int 做运算得到超出 int 能够存储的最大值则直接截断数据,这样会造成数据丢失。

运算

= 赋值运算符

+ 运算后不会对被加数进行更改。

% 取余数

整数在做除法不会得出小数,两个相同数据类型做运算后不会改变原有类型,比如

int a = 10;
int b = 3;
float c = 3.0;

a / b // 结果也是 int
a / c // 结果才是 float

做数学运算时也是按照先乘除后加减的优先级进行。

比较运算符

比较返回 true 或 false

==
!=
>
>=
<
<=

布尔运算符

单个 |&,是不会使用短路运算的,就算左边操作数运算完右边的操作数也要计算,就是无论如何右边操作数都会进行运算。

在大多数程序这是 &&|| 用这种运算符,它会进行短路运算,只要左边操作数条件确定了,右边不参与运算,像 && 左为 False,直接停止运算得到结果 False,而 || 呢 只要左为 True 直接得到 True 不会再和右操作数进行运算。

! // not 运算符
& // and 运算符
&& // andand 运算符
| // or
|| // oror

运算符优先级

  • ()
  • !
  • *,/,%
  • +,-
  • >,>=,<,<=
  • ==
  • !=
  • &,&&,|,||
  • =

位运算符

将一个数变为 bit 做运算,实际中用的少。

& 做按位与操作,和 Boolean 一样 两个 1 得 1,只要有一个 0 结果就为 0。

| 做按位或操作,只要有一个 1 则结果为 1。只有两个操作数为 0 结果才是 0。

^ 做按位异或操作,两个操作数相同结果为 0,不同结果为 1。

~ 单操作数,对每个 bit 进行取反,包括符号位。

以 0 开头的值是八进制,0x 开头是十六进制,大小写不区分。

位移运算符

和位运算符一样将数值转为 bit 做左右移动运算。

<< 左移,将所有 bit 位整体移动 n 位,在末位补 0。

比如 3 << 1,先将 3 转为二进制,在进行位移,一共 8 位溢出的数字会被丢弃。

   0000 0011 // 数字 3
 0 0000 0110 // 整体向左移 1 位,在末位补 0。

一直左移,符号位变成 1 那整个结果就为负数。

>> 除符号位外所有 bit 整体右移(也叫带符号右移),最后在符号位后补数,整数补 0,负数补 1。

一样拿 3 举例,向右移 1 bit 得到 十进制 1

0000 0011   // 数字 3
0000 0001 1 // 所有 bit 整体向右移 1 位,由于 3 是整数所以在符号位后面补0。

>>> 所有 bit 整体右移,包括符号位(无符号右移),移动后上补 0。

数字 13 向右移 1 bit,得到十进制 6。

0000 1101   // 数字 13
0 0000 1101 // 所有 bit 整体向右移 1 位,在符号位后面补 0。

所有运算符都可使用简化操作,比如变量 a 要加 1,原始写法是 a = a + 1,新写法是 a += 1,这种操作叫计算并赋值运算符。

自增自减运算符

a++ 是先操作完 a 再进行 ++。比如 "testNumber" + a++; 则是字符串先和变量 a 拼接,操作完成后再进行 ++。

++a是先对 a 进行++,再使用 a。

自减操作也是一样的原理

流程控制

条件分支

最简化

if (boolean) {
    statement;
} else {
    statement;
}

进一步

if (boolean) {
    statement;
} else if (boolean) {
    statement;
} else {
     statement;
}

如何 block 里只有一行语句可以简写,多行则报错。不推荐使用此方式。

if (boolean)
    statement
else
    statement

switch 条件是必须为 int 型(在 JRE 11 中可以使用字符串当作条件),其中 case 匹配成功会执行里面的语句,语句中如果没有 break; 会一下向下执行 其他 case 的内容直到遇见 break;,当所有 case 匹配失败则执行 default 中的语句,这个 default 可以放在任意位置只要遇到就执行,也可以不写。

int n = 3;
String str = "数字是:";

switch (n) {
    case 1:
        str += "一";
        break;
    case 2:
        str += "二";
        break;
    default:
        System.out.println('x');
}

循环

for 循环,可以控制循环次数。第一次循环执行顺序是,首先初始化变量,接着循环体条件表达式会判断初始变量有没满足条件,满足执行 block 中的语句,执行完会执行循环体后语句,第二次则从换循环体条件表达式开始判断初始变量是否满足,满足还是执行 block 内容,接着执行循环体语句,第三、四、N...都是如此,直到不满足条件。

for (初始变量; 循环体条件表达式; 循环体后语句) {
    statement;
}

此外循环体后语句可以有多个,不过不推荐使用。

for (初始变量; 循环体条件表达式; 循环体后语句, 循环体后语句2) {
    statement;
}

while,只要条件不满足停止循环

while (boolean) {
    statement;
}

do-while 和 while/for 区别在于 do-while 中的循环体不管条件怎么样都会执行一次。实际中用的少。

do {
    statement;
} while (boolean);

作用域

code block 内可以使用外面的变量,反之则不行,block 内容按照顺序执行完毕会被回收。

数组

一个数组创建之后数量和类型是明确指定的,存储内容时不能放入其他类型数据,也不能改变数量。

new int[10] 首先使用 new 创建一个 int 型数组将它长度设定为 10 ,接着计算机会在内存创建一段连续的地址,把第这段值第一个值作为数组的地址,int[] arryTest 只是声明了这个变量是要存放 int 类型的数组,arrayTest 地址的值指向整段数组内存地址的第一个值(没错一个地址的值指向另一个地址),这第一个值就是数组的地址,由于当前数组第一个值是自己,索引是 0,通过偏移量加 1 就对应获得第 2 个值。

int[] arryTest = new int[10];

数组被创建后必须初始化,像上面代码就将初始化为能够存储 10 位数字的数组,如果不给它赋值将会被定义为这个类型的缺省值,像 int 是 0,boolean 是 false,double 是 0.0。

下面直接初始化的例子,一种简写方式。

int[] arryTest = {
    1,
    2,
    3
};

testArray = null;
testArray = new int[] {1, 2, 3, 4}  //相当一个匿名数组赋值给变量 testArray

多维数组只是多个一维数组的嵌套,像洋葱一样剥开进行使用。

double [][] = new int[2][10]

OOP(Object Oriented Programming)

实例化类时需要使用 new 关键字,后面跟对应类名,变量呢也要使用类名表示对应类型。

对象和数组一样它们被称作引用类型,new className() 创建了一个实例,它将在堆(heap)中创建一个地址,这个地址的值是对象具体成员变量,className variableName 创建了一个变量其类型是 className,这个变量也会在堆中产生一个地址,通过 = 赋值运算符,className 对象的地址将被赋予 variableName 变量地址的值(是将实例的地址赋给变量),这也相当与引用嘛。

className variableName = new className();

访问的一个流程是先找到 variableName 对应地址的值,这个值指向一个实例地址,这是第一次访问,接着通过实例地址去找它的值就是相关成员变量,这是第二次访问。和数组访问类似,只是没用索引而已。

类创建实例(instance)后其成员变量(属性)会被初始化为对应类型默认值,如果是一个引用类型则是 null(除了自定义的实例是引用类型,其成员变量 String 类型也是)。

总结:String、array、实例都是引用类型,如果没初始化值则为 None。

自定义类型

package & import

通过包来划分模块,方便管理,还解决了两个类同名的问题,是会通过路径名来区分。

如果一个文件夹下放一堆类,会有一个默认包,不需要再类里写 package 语句。

GeekBangJava
└─ phone
       ├─ CPU.java
       ├─ Mainboard.java
       ├─ Memory.java
       ├─ MyPhoneMaker.java
       ├─ Phone.java
       ├─ PhoneMaker.java
       ├─ Screen.java
       ├─ Storage.java
       └─ software
              └─ ViewNews.java

在包里的类文件第一行有效的代码必须是 package 路径。

phone.Phone

package phone; // 第一行有效的代码必须是 package

public class CPU {
    double speed;
    String producer;
}

PS:路径是使用 dir.yardtea.cc 生成的。

如果想使用包里的类就要用 import,下面要调用 phone/software/ViewNews.java 中的 title 内容。

ViewNews.java

package phone.software;

public class ViewNews {
    public title = "咨询标题";
}

MyPhoneMaker.java 直接调用。

package phone; // 第一行有效的代码必须是 package
import phone.software.ViewNews

public class MyPhoneMaker {
    // 创建一个 ViewNews 类型的变量
    ViewNews news;
}

如果 ViewNews.java 这个类或类的成员变量有添加 public 属性访问修饰符,那你导入可以直接使用,没有(缺省的访问修饰符)的话只有在相同包里的类能调这个属性。

PS:类的全限定名 = 包名 + 类名

参考资料

编码风格

google-java-style

JavaGuide

JavaGuide

安全编码

参考官方文档。

参考链接

标签: none

讨论讨论讨论!