目录
一、Java 数据类型介绍
1. 数据类型的分类
2. 四类基本数据类型
3. Java 的标识符(Identifier)
二、运算符与表达式
1. 运算符
2. 表达式
三、流程控制语句
1. 程序的三种基本流程:顺序、分支、循环
2. 分支语句
3. 循环语句
4. 特殊流程控制语句
四、数组——引用类型
1. 数组概述
2. 数组的声明
3. 数组初始化
4. 数组元素的引用
5. 增强的 for 语句
6. 数组的复制
7. 多维数组
8. 综合应用
一、Java 数据类型介绍
1. 数据类型的分类
→ 数据类型决定数据的存储方式和运算方式
→ Java 中的数据类型分为两大类:基本数据类型、引用类型
① primitive types:byte,short,int,long;float,double;char;boolean
→ Java 中有8种基本数据类型(4类:整数型、浮点数型、逻辑型、字符型)
② reference type:class,interface,数组
【注意】基本类型:变量在栈(这里)
引用类型:变量引用到堆(那里),类似于 C++ 里面的指针
double d = 4;
Person p = new Person();
double d2 = d; // 复制的值
Person p2 = p; // 复制的引用(内存地址,引用到同样的位置)
【注意】赋值时,基本类型复制的值;引用类型复制的是引用
2. 四类基本数据类型
① 逻辑型(1个字节)
boolean b = true; // 只能取 true 或 false
// [注意] 不可以用 零 或 非零来替代 true 或 false
// if (b = 5) 是不允许的
if (b) {
System.out.print("true");
}
→ 关于 true 和 false 的说明:
class JavaBoolean {
public static void main(String[] args) {
boolean a = true; // 编译过后,在计算机中是以 0,1 存储的
boolean b = false; // 但是,在编程时整型和布尔型是不能混淆的
System.out.println(a + ", " + b);
}
}
② 字符型(2个字节)
char c = 'A';
// char c1 = "A"; 不能用双引号
System.out.println(c);
char c2 = '\u4e00';
// Java 字符采用 Unicode 编码,每个字符占两个字节
System.out.println(c2);
→ 转义字符:
【说明】\ddd 1到3位八进制数所表示的字符 ddd;
\uxxxx 1到4位十六进制数所表示的字符 xxxx;
\' \" \\ 分别表示单引号、双引号、反斜杠;
\r 回车 \n 换行 \f 换页 \t 横向制表符 \b 退格;
③ 整数型(整型——4个字节;长整型——8个字节)
// 八进制,0 开头;十六进制 0x 0X 开头;二进制 0b 0B 开头
int i = 3; // Java 语言的整型常量默认为 int 型
long l = 3L; // 声明 long 型常量后可以加 l 或 L
// Java 中没有无符号数 uint 可以用 long 来模拟
【注意】Java 各整数类型有固定的表数范围和字符长度,而不受具体操作系统影响,以保证 Java 程序的可移植性
→ Java 语言整型常量的三种表示形式:
① 十进制整数:如12,-314,0
② 八进制整数:要求以 0 开头,如 012
③ 十六进制整数:要求 0x 或者 0X,如 0x12
④ 二进制整数:以 0b 或 0B 开头,如 0b00010010
【注意】Java 语言的整型常量是 int 型,如 int i = 3;
声明 long 型常量可以后加 l 或 L,如 long l = 3L;
④ 浮点数型(float 型——4个字节;double 型——8个字节)
→ Java 语言浮点类型常量有两种表示形式:
① 十进制数形式,必须含有小数点,如:3.14,314.0,.314
→ Java 7 以上,123_456.789_000(千分位分隔符用下划线表示)
② 科学计数法形式,如:3.14e2,3.14E2,314E2
【注意】Java 浮点型常量默认为 double 型
如果要声明一个常量为 float 型,需要在数字后面加 f 或 F,如:float f = 3.14f;
3. Java 的标识符(Identifier)
→ 由字母、数字、下划线、$ 组成;必须以字母、下划线、$ 开头;对大小写敏感
→ 类名首字母用大写(Pascal);其余的首字母用小写(camel)
→ 常量随使用随定义;尽量少用下划线
二、运算符与表达式
1. 运算符
运算符有算术运算符 + - * / % ++ --;逻辑运算符 & && | || ! ^ ;字符串连接运算符 + 等
① 算术运算符
int a = 15 / 3; // 整数除法
double b = 15 / 2.0; // 实数除法
System.out.println(b);
System.out.println(a % 2);
System.out.println(-a % 2);
System.out.println(a % -2);
System.out.println(-a % -2);
// % 的负符号根据被除数决定
② 逻辑运算符
【注意】区分逻辑运算符和短路逻辑运算符:&& 短路逻辑运算符
public class Main {
public static void main(String[] args) {
MyDate d = new MyDate();
d.setDay(12);
if ((d != null) && (d.getDay() > 31)) {
System.out.print(d.getDay());
}
}
}
class MyDate {
private int day;
public void setDay(int day) {
this.day = day;
}
public int getDay() {
return this.day;
}
}
→ && 第一个操作数为假 → 不判断第二个操作数
→ || 第一个操作数为真 → 不判断第二个操作数
→ ~ & | ^ 根据操作数来确定是逻辑还是数(^ 不是乘方)
System.out.println(1 << 3);
System.out.println(-9 >> 3);
System.out.println(-9 >>> 3); // 无符号右移
③ 位移运算符(适用于 byte、short、char、int、long)
class BitwiseOperator {
public static void main(String[] args) {
int a = 0b1100;
int b = 0b1010;
myPrint("a: ", a);
myPrint("b: ", b);
myPrint("a & b", a & b);
myPrint("a | b", a | b);
myPrint("a ^ b", a ^ b);
myPrint("~a", ~a);
myPrint("a << 2", a << 2);
myPrint("a >> 2", a >> 2);
myPrint("a >>> 2", a >>> 2);
}
static void myPrint(String prefix, int n) {
StringBuilder s = new StringBuilder(Integer.toBinaryString(n));
while (s.length() < 4) {
s.insert(0, "0");
}
System.out.println(prefix + " " + s);
}
}
→ 左移 a << b:将二进制形式的 a 左移 b 位,最低位空出的 b 位补 0
→ 带符号右移 a << b:将二进制形式的 a 右移 b 位,最高位空出的 b 位补原来的符号位
无符号右移 a <<< b:将二进制形式的 a 右移 b 位,最高位空出的 b 位补 0
【注意】对于低于位 int 型的操作数将先自动转换为 int 型,再进行移位(整型提升,对于所有的运算都是这样)
→ 对 int 型数移位 a >> b,先将 b 对 32 取模;对 long 型数移位 a >> b,先将 b 对 64 取模
class Test {
public static void main(String[] args) {
System.out.println(-2227); // 输出 -2227
System.out.println(-2227 << 3); // 输出 -17816
System.out.println(-2227 >> 3); // 输出 -279
System.out.println(-2227 >>> 3); // 输出 536870633
}
}
④ 赋值运算符 =
→ 当 "=" 两侧数据类型不一致时,可以使用默认类型转换或强制类型转换原则处理
→ 可以将整型常量直接赋值给 byte,short,char 等类型变量,而不需要进行强制类型转换,只要不超出表数范围即可
long l = 100;
// int i = l; [错误]
int i = (int) l;
int n = 1000;
++n;
long m = n; // [正确]
System.out.println(i);
System.out.println(m);
byte b = 12;
System.out.println(b);
// byte b = 4096; [错误]
→ 扩展赋值运算符与 C++ 类似
⑤ 字符串连接运算符 +
class ConnectString {
public static void main(String[] args) {
int i = 300 + 5;
String s = "Hello, " + "world!";
System.out.println(i);
System.out.println(s);
// "+" 运算符两侧的操作数中只要有一个是 String 类型
// 系统会自动将另一个操作数转换为字符串然后再进行连接
String string = "Hello, " + i + "号";
System.out.println(string);
System.out.println(1 + 1 + "1"); // 输出 21
System.out.println(1 + '1'); // 输出 50
System.out.println(1 + '1' + "1"); // 输出 501
System.out.println(1 + "1" + '1'); // 输出 111
}
}
2. 表达式
① 概念:符合一定语法规则的运算符和操作数的序列(和 C 语言类似)
② 表达式的类型与值
→ 表达式的值:对表达式中的操作数进行运算得到的结果
→ 表达式的类型:表达式的值的数据类型
③ 表达式的运算顺序:适当使用括号增强可读性
首先应按照运算符的优先级从高到低的顺序进行
优先级相同的运算符按照事先约定的结合方向进行
④ 表达式中的类型转换
→ 当有不同类型的混合运算时:int → long → float → double
→ 整型提升: 所有的 byte,short,char 参与算术运算等转化为 int
⑤ 一些问题
class Expression {
public static void main(String[] args) {
int a = 2;
int b = a++ + ++a; // 可读性很差 a++ 的副作用
System.out.println(b);
}
}
三、流程控制语句
1. 程序的三种基本流程:顺序、分支、循环
→ 任何一个复杂的事件序列,都可以由上面三种结构及其嵌套来实现
→ 最简单的语句:方法调用语句,赋值语句(注意分号)
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
int x = 8, y = -9; // x, y; 是不合法的
// 没有表达式语句这个概念 x + y; 是不合法的
System.out.println(x + y);
}
}
2. 分支语句
① if - else
class LeapYear {
public static boolean isLeapYear(int year) {
return (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0);
}
public static void main(String[] args) {
int year = 2022;
if (isLeapYear(year)) {
System.out.println(year + " is a leap year.");
} else {
System.out.println(year + " is not a leap year.");
}
}
}
② switch 语句
→ 变量类型是整数、字符、字符串(String Java 6 以上)
→ case 后面是常量,不能是变量
→ 一般来说注意 break; 的使用
【举例】switch 语句的基本用法
→ 一般的 switch 语句
class GradeLevel {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
char grade = scanner.next().toCharArray()[0];
switch (grade) {
case 'A':
System.out.println(grade + " is 85~100");
break;
case 'B':
System.out.println(grade + " is 70~84");
break;
case 'C':
System.out.println(grade + " is 60~69");
break;
case 'D':
System.out.println(grade + " is <60");
break;
default:
System.out.println("Input Error");
// 最后的 default 可以不要 break;
}
}
}
→ 提升的 switch 语句(Java 8 无法实现)
class SwitchStatement {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int exp = scanner.nextInt() + 10;
// enhanced switch statement
switch (exp) {
case 11 -> System.out.println("result = " + 11 + "**");
case 12 -> System.out.println("result = " + 12 + "--");
default -> System.out.println("result = other");
}
}
}
【举例】自动出题并判分小程序
import java.awt.*;
import javax.swing.*;
import java.util.Scanner;
class AutoScore extends JFrame {
// 自动出题并判分
public AutoScore() {
init();
setSize(350, 400);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
public void init() {
// 初始化过程
setLayout(null); // 没有布局 用程序来布局,不用自动布局
setSize(400, 350);
buttonNew.setLabel("出题");
getContentPane().add(buttonNew);
buttonNew.setBounds(36, 96, 98, 26);
buttonJudge.setLabel("判分");
getContentPane().add(buttonJudge);
buttonJudge.setBounds(216, 96, 94, 25);
labelA.setText(" ");
getContentPane().add(labelA);
labelA.setFont(new Font("Dialog", Font.PLAIN, 24));
labelA.setBounds(36, 24, 36, 36);
labelOperator.setText(" ");
getContentPane().add(labelOperator);
labelOperator.setFont(new Font("Dialog", Font.PLAIN, 24));
labelOperator.setBounds(72, 24, 36, 36);
labelB.setText(" ");
getContentPane().add(labelB);
labelB.setFont(new Font("Dialog", Font.PLAIN, 24));
labelB.setBounds(108, 24, 34, 36);
labelEq.setText("=");
getContentPane().add(labelEq);
labelEq.setFont(new Font("Dialog", Font.PLAIN, 24));
labelEq.setBounds(168, 24, 30, 36);
textAnswer.setFont(new Font("Dialog", Font.PLAIN, 24));
getContentPane().add(textAnswer);
textAnswer.setBounds(216, 24, 85, 42);
listDisplay.setFont(new Font("Dialog", Font.PLAIN, 16));
getContentPane().add(listDisplay);
listDisplay.setBounds(36, 144, 272, 196);
SymAction lSymAction = new SymAction();
buttonNew.addActionListener(lSymAction);
buttonJudge.addActionListener(lSymAction);
}
int a = 0, b = 0, res = 0;
String op = "";
java.awt.Button buttonNew = new java.awt.Button();
java.awt.Button buttonJudge = new java.awt.Button();
java.awt.Label labelA = new java.awt.Label();
java.awt.Label labelB = new java.awt.Label();
java.awt.Label labelOperator = new java.awt.Label();
java.awt.Label labelEq = new java.awt.Label();
java.awt.TextField textAnswer = new java.awt.TextField();
java.awt.List listDisplay = new java.awt.List(0);
void buttonNewActionPerformed() {
a = (int) (Math.random() * 9 + 1);
b = (int) (Math.random() * 9 + 1);
int c = (int) (Math.random() * 4);
switch (c) {
case 0 -> {
op = "+";
res = a + b;
}
case 1 -> {
op = "-";
res = a - b;
}
case 2 -> {
op = "*";
res = a * b;
}
case 3 -> {
op = "/";
res = a / b; // 没有考虑小数除法
}
}
labelA.setText("" + a);
labelB.setText("" + b);
labelOperator.setText("" + op);
textAnswer.setText("");
}
class SymAction implements java.awt.event.ActionListener {
public void actionPerformed(java.awt.event.ActionEvent event) {
Object object = event.getSource();
if (object == buttonNew) {
buttonNewActionPerformed();
} else if (object == buttonJudge) {
buttonJudgeActionPerformed();
}
}
}
void buttonJudgeActionPerformed() {
String str = textAnswer.getText();
double d = Double.parseDouble(str);
String display = "" + a + op + b + "=" + str + " ";
if (d == res) {
display += "√";
} else {
display += "×";
}
listDisplay.add(display);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(AutoScore::new);
}
}
3. 循环语句
① 循环语句的功能:在满足条件的情况下,反复执行特定的代码
② 循环的五个要素:初始化部分 init_statement,循环条件部分 test_expression,循环体部分 body_statement,迭代部分 alter_statement,结束后处理(可以没有)
③ 循环语句的三种写法:for 循环,while 循环(先判断再执行,直到条件不满足),do-while 循环(先执行再判断,至少执行一次)
【举例】三种循环求和
class ForAndWhile {
public static void main(String[] args) {
// for 循环(用于事先确定大小的情况)
int res = 0;
for (int i = 1; i <= 100; ++i) {
res += i;
}
System.out.println("result = " + res);
// while 循环——注意不要写成死循环
int sum = 0, i = 1;
while (i <= 100) {
sum += i;
++i;
}
System.out.println("sum = " + sum);
// do-while 循环(至少执行一次循环)
int val = 0, j = 1;
do {
val += j;
++j;
} while (j <= 100);
System.out.println("val = " + val);
}
}
【举例】画圆形的小程序
class Circle99Frame extends JFrame {
public static void main(String[] args) {
JFrame jFrame = new Circle99Frame();
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setSize(600, 600);
jFrame.setVisible(true);
}
public void paint(Graphics g) {
g.drawString("circle 99", 20, 20);
int x0 = getSize().width / 2;
int y0 = getSize().height / 2;
for (int r = 0; r < getSize().height / 2; r += 10) {
g.setColor(getRandomColor());
g.drawOval(x0 - r, y0 - r, r * 2, r * 2);
}
}
Color getRandomColor() {
return new Color((int) (Math.random() * 256), (int) (Math.random() * 256), (int) (Math.random() * 256));
}
}
4. 特殊流程控制语句
① goto 语句及其弊端:有关 goto 语句的争论(执行流程和书写流程不一样)
→ Java 中的解决方式:在循环中用 break 和 continue
② break 语句:用于终止某个语句的执行
【举例】break 语句的用法
class BreakStatement {
public static void main(String[] args) {
for (int i = 0; i < 10; ++i) {
if (i == 3) {
break;
}
System.out.println("i = " + i);
}
System.out.println("Game over!");
}
}
【注意】出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块,用 break 语句只能从里向外跳转
【举例】Java 中 break 语句可以终止指定层的语句块
class BreakExample {
public static void main(String[] args) {
label0:
{
label1:
{
for (int i = 0; i < 100; ++i) {
for (int j = 0; j < 100; ++j) {
System.out.println(i + " " + j);
if (i == 22 && j == 88) {
System.out.println("请输入跳出循环的层级 0 或 1:");
Scanner scanner = new Scanner(System.in);
if (scanner.nextInt() == 1) {
break label1;
} else {
break label0;
}
}
}
}
}
System.out.println("Game Over! Label 1");
return;
}
System.out.println("Game Over! Label 0");
}
}
③ continue 语句:用于跳过某个循环语句块的一次执行
【注意】continue 语句出现在多层循环语句体中时,也可以通过标签指明要继续执行的是哪一层循环
【举例】求 100-200 之间的素数
class FindPrime {
public static void main(String[] args) {
System.out.println("求 100-200 之间的素数,结果如下:");
int n = 0;
outer:
for (int i = 101; i < 200; i += 2) {
for (int j = 2; j <= Math.sqrt(i); ++j) {
if (i % j == 0) {
continue outer; // 直接进入下一层循环
}
}
System.out.print(i + "\t"); // 打印出质数
++n; // 计数器++
if (n < 10) {
continue;
}
System.out.println(); // 打印出换行
n = 0; // 计数器归零
}
System.out.println();
}
}
四、数组——引用类型
1. 数组概述
→ 数组:多个相同类型数据的组合
→ 一维数组的声明方式:方括号可以写在变量的前面或后面,但是建议写在前面
int[] a; double[] arr; MyDate[] date;
float[] b, c; // 这里 b 和 c 都是数组
char p[], x; // p 是数组,x 不是数组
【注意】数组定义与为数组分配空间分开进行
2. 数组的声明
→ Java 中,声明数组时不能指定长度(元素个数)
→ 数组是引用类型:a 只是一个引用(分配在堆空间中的)
【注意】一定要先对数组分配空间,再进行使用(与 C++ 语言数组的第一个区别)
3. 数组初始化
→ 静态初始化:定义数组时,为数组元素分配空间并赋值
→ 默认初始化:数组一经分配空间,其中每个元素也被按照成员变量同样的方式被隐式初始化
【注意】在 Java 中,数值类型初始化为 0,引用类型初始化为 null(与 C++ 中数组的第二个区别)
【举例】MyDate 日期数组
public class Main {
public static void main(String[] args) {
int[] y = {3, 9, 8};
int[] yy = {3, 9, 8,}; // 最后可以多一个逗号
int[] t = new int[]{3, 9, 8};
// int[] tt = new int[3]{3, 9, 8}; [错误]
System.out.println(y[0] + "\t" + yy[2] + "\t" + t[1]);
MyDate[] myDates = {new MyDate(2002, 2, 22),
new MyDate(1901, 1, 1),
new MyDate(2022, 7, 3)};
myDates[2].PrintDate();
@SuppressWarnings("MismatchedReadAndWriteOfArray") MyDate[] myDate1 = new MyDate[2];
if (myDate1[1] == null) {
System.out.println("myDate1[1] == null");
}
System.out.println("数组 y 的长度为: " + y.length);
for (int i = 0; i < y.length; ++i) {
System.out.printf("y[%d] = %d\n", i, y[i]);
}
}
}
class MyDate {
public int year, month, day;
public MyDate(int _year, int _month, int _day) {
year = _year;
month = _month;
day = _day;
}
public void PrintDate() {
System.out.println("yyyy-m-d: " + year + "-" + month + "-" + day);
}
}
4. 数组元素的引用
→ index 为数组元素下标:可以是整型常量或整型表达式
→ 数组元素下标从 0 开始,长度为 n 的数组合法下标范围是 [0, n - 1]
【注意】每个数组都有属性 length 指明它的长度(与 C++ 语言数组的第三个区别)
5. 增强的 for 语句
【举例】Java 中增强的 for 语句(类似于 C++ 的 range-based for loop)
class EnhancedFor {
// 方便地处理数组、集合中的各个元素
public static void main(String[] args) {
int[] a = {4, 1, 3, 5, 8, 7, 2};
for (int i : a) {
System.out.println(i); // 只读式的遍历(不能赋值)
}
}
}
【举例】C++11 中的 range-based for loop(leetcode 347 题)
#include <iostream>
#include <vector>
#include <map>
class Solution {
public:
static std::vector<int> topKFrequent(const std::vector<int> &nums, int k) {
std::vector<int> res;
auto [min, max] = std::minmax_element(nums.begin(), nums.end());
int diff = *max - *min, arr[diff + 1];
memset(arr, 0, 4 * (diff + 1));
for (auto num: nums) {
++arr[num - *min];
}
std::map<int, std::vector<int>, std::greater<>> freq_count;
for (int i = 0; i < diff + 1; ++i) {
if (arr[i]) {
freq_count[arr[i]].emplace_back(i + *min);
}
}
int sum = 0;
for (const auto &i: freq_count) {
sum += int(i.second.size());
std::copy(i.second.begin(), i.second.end(), std::back_inserter(res));
if (sum == k) { break; }
}
return res;
}
};
int main() {
std::vector<int> vector = Solution::topKFrequent({-1, -1}, 1);
std::ostream_iterator<int> ostream_iterator(std::cout, " ");
std::copy(vector.begin(), vector.end(), ostream_iterator);
vector = Solution::topKFrequent({1, 1, 1, 2, 2, 3}, 2);
std::ostream_iterator<int> ostream_iterator1(std::cout, " ");
std::copy(vector.begin(), vector.end(), ostream_iterator1);
return 0;
}
6. 数组的复制
【举例】Java 数组的深拷贝(C 语言中可以用 memcpy 函数实现)
class ArrayCopy {
public static void main(String[] args) {
int[] src = {1, 2, 3, 4, 5, 6};
int[] dest = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
System.arraycopy(src, 0, dest, 0, src.length);
for (int i: dest) {
System.out.println(i);
}
// 复制原数组中从下标 0 开始的 src.length 歌元素到目标数组,从下标 0 的位置开始存储
}
}
7. 多维数组
→ 二维数组是数组的数组
【注意】多维数组的声明和初始化,按照从高维到低维的顺序进行(与 C++ 数组的第四个区别,C++ 是指定每行有多少元素)
class TwoDimensionArray {
public static void main(String[] args) {
int[][] a = {{1, 2}, {3, 4, 0, 9}, {5, 6, 7}};
// 二维数组——数组的数组
int[][] t = new int[3][];
t[0] = new int[2]; // 数组中第 0 个元素又引用了一个数组
t[1] = new int[4];
t[2] = new int[3];
for (int i = 0; i < t.length; ++i) {
System.arraycopy(a[i], 0, t[i], 0, t[i].length);
}
// int t1[][] = new int[][4]; [错误]
}
}
8. 综合应用
class Random_36_7 {
public static void main(String[] args) {
int[] a = new int[7];
for (int i = 0; i < a.length; ++i) {
one_num:
while (true) {
a[i] = (int) (Math.random() * 36) + 1;
for (int j = 0; j < i; ++j) {
if (a[i] == a[j]) {
continue one_num; // 看是否发生重复,是否需要重新产生
}
}
break;
}
}
for (int num : a) {
System.out.print(num + "\t");
}
System.out.println();
}
}