Java基础学习
Java随想写
Hello, Java!
此部分内容学习来自:Java教程 - 廖雪峰的官方网站 (liaoxuefeng.com)
必要简介
-
Java最早是由SUN公司(已被Oracle收购)的詹姆斯·高斯林(高司令,人称Java之父)在上个世纪90年代初开发的一种编程语言,最初被命名为Oak,目标是针对小型家电设备的嵌入式应用
-
Java介于编译型语言和解释型语言之间。编译型语言如C、C++,代码是直接编译成机器码执行,但是不同的平台(x86、ARM等)CPU的指令集不同,因此,需要编译出每一种平台的对应机器码。
-
Java的三个版本与关系:
- Java SE:Standard Edition
- Java EE:Enterprise Edition
- Java ME:Micro Edition
-
相关名词:
- JDK:Java Development Kit
- JRE:Java Runtime Environment
- JSR规范:Java Specification Request
- JCP组织:Java Community Process
- RI:Reference Implementation
- TCK:Technology Compatibility Kit
编译方式
java源码本质上是一个文本文件,我们需要先用javac
把Hello.java
编译成字节码文件Hello.class
,然后,用java
命令执行这个字节码文件:
因此,可执行文件 javac
是编译器,而可执行文件java
就是虚拟机。
-
在不使用外部IDE情况下编译:
第一步,在保存
Hello.java
的目录下执行命令javac Hello.java
:$ javac Hello.java
如果源代码无误,上述命令不会有任何输出,而当前目录下会产生一个
Hello.class
文件:$ ls Hello.class Hello.java
第二步,执行
Hello.class
,使用命令java Hello
:$ java Hello Hello, world!
注意:给虚拟机传递的参数
Hello
是我们定义的类名,虚拟机自动查找对应的class文件并执行。 -
Java 11新增功能:直接运行一个单文件源码:
$ java Hello.java sh Hello, world!
需要注意的是,在实际项目中,单个不依赖第三方库的Java源码是非常罕见的,所以,绝大多数情况下,我们无法直接运行一个Java源码文件,原因是它需要依赖其他的库。
注意点
一个Java源码只能定义一个public
类型的class,并且class名称和文件名要完全一致;
练习使用代码
- Hello word!
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
Dismember Java!
与C/C++同而不同
基本单位:类(class)
Java是面向对象的语言,一个程序的基本单位就是class
,class
是关键字,这里定义的class
名字就是Hello
:
public class Hello { // 类名是Hello
// ...
} // class定义结束
类名要求:
- 类名必须以英文字母开头,后接字母,数字和下划线的组合
- 习惯以大写字母开头
类的嵌套:方法(method)
在class
内部,可以定义若干方法(method):
/** 特殊注释
* 可以用来自动创建文档的注释
*
* @auther liaoxuefeng
*/
public class Hello {
public static void main(String[] args) { // 方法名是main
// 方法代码...
System.out.println("Hello, world!"); //;结尾
} // 方法定义结束
}
方法定义了一组执行语句,方法内部的代码将会被依次顺序执行。
这里的方法名是main
,返回值是void
,表示没有任何返回值。
我们注意到public
除了可以修饰class
外,也可以修饰方法。而关键字static
是另一个修饰符,它表示静态方法,后面我们会讲解方法的类型,目前,我们只需要知道,Java入口程序规定的方法必须是静态方法,方法名必须为main
,括号内的参数必须是String数组,习惯以小写字母开头且只含字母。
有一种特殊的多行注释,以/**
开头,以*/
结束,如果有多行,每行通常以星号开头
数据类型
Java定义了以下几种基本数据类型:
- 整数类型:byte,short,int,long
- 浮点数类型:float,double
- 字符类型:char
- 布尔类型:boolean
整数类型
溢出:要特别注意,整数由于存在范围限制,如果计算结果超出了范围,就会产生溢出,而溢出不会出错,却会得到一个奇怪的结果(二进制最高位0变为1,成为负数)
移位/位运算/自增/自减:java提供了C/C++相同的特殊运算,增加了一种无符号的右移运算,使用>>>
,它的特点是不管符号位,右移后高位总是补0
浮点数类型
自动转换:一个例子将会帮你理解其优先性double d = 1.2 + 24 / 5; // 5.2
溢出:整数运算在除数为0
时会报错,而浮点数运算在除数为0
时,不会报错,但会返回几个特殊值:
NaN
表示Not a NumberInfinity
表示无穷大-Infinity
表示负无穷大
字符串类型
多行字符串:从Java 13开始,字符串可以用"""..."""
表示多行字符串(Text Blocks)
不可变特性:字符串String
是引用类型,字符串内容不会改变,但是会改变变量指向空间
空值:java中的引用变量可以指向空值null
,类似于C++的空指针,但是null
\(\neq\)""
需要注意的是,在java中字符(char
)与字符串(String
)是两个不同数据类型
var关键字
如果想省略变量类型,可以使用var
关键字:
var sb = new StringBuilder();
编译器会根据赋值语句自动推断出变量sb
的类型是StringBuilder
。对编译器来说,语句:
var sb = new StringBuilder();
实际上会自动变成:
StringBuilder sb = new StringBuilder();
因此,使用var
定义变量,仅仅是少写了变量类型而已。
数组类型
声明数组类型方式:
public class Hello {
public static void main(String[] avg) {
int[] arr = new int[5];
arr[0] = 68;
System.out.println(arr[1]); //0
System.out.println(arr.length); //5
//下述为 int[] ns = new int[] { 68, 79, 91, 85, 62 }; 的缩写
int[] ns = { 68, 79, 91, 85, 62 };
}
}
我们通过上述代码可以发现一些有意思的事情:
- Java所有元素都有初始化的默认值,值得提醒的是
Boolean
默认是false
- 与C++库类似地,有许多初始方法是可用的
- 因为此类型也是引用,因此
arr = {1, 2, 3}
其实是指向空间的变更
根据上述内容,我们会发现一个神奇的数据类型——字符串数组:
对于String[]
类型的数组变量names
,它实际上包含3个元素,但每个元素都指向某个字符串对象:
┌─────────────────────────┐
names │ ┌─────────────────────┼───────────┐
│ │ │ │ │
▼ │ │ ▼ ▼
┌───┬───┬─┴─┬─┴─┬───┬───────┬───┬───────┬───┬───────┬───┐
│ │░░░│░░░│░░░│ │ "ABC" │ │ "XYZ" │ │ "zoo" │ │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴───┘
│ ▲
└─────────────────┘
对names[1]
进行赋值,例如names[1] = "cat";
,效果如下:
┌─────────────────────────────────────────────────┐
names │ ┌─────────────────────────────────┐ │
│ │ │ │ │
▼ │ │ ▼ ▼
┌───┬───┬─┴─┬─┴─┬───┬───────┬───┬───────┬───┬───────┬───┬───────┬───┐
│ │░░░│░░░│░░░│ │ "ABC" │ │ "XYZ" │ │ "zoo" │ │ "cat" │ │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴───┴───────┴───┘
│ ▲
└─────────────────┘
这里注意到原来names[1]
指向的字符串"XYZ"
并没有改变,仅仅是将names[1]
的引用从指向"XYZ"
改成了指向"cat"
,其结果是字符串"XYZ"
再也无法通过names[1]
访问到了。
来尝试一段代码:
public class Hello {
public static void main(String[] avg) {
String[] strings = {"abc", "xyz", "zoo"};
var s = strings[1];
strings[1] = "cat";
System.out.println(s); //xyz
}
}
说明存储"xyz"
的空间仍然存在
练习使用的代码
- 方程求根
public class Hello {
public static void main(String[] args) {
double a = 1.0;
double b = 3.0;
double c = -4.0;
double r1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 * a;
double r2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 * a;
System.out.printf("x = %.2f, %.2f", r1, r2);
}
}
- Unicode值拼接字符串
这里的问题是挺有意思的,拼接方式不同得到的字符串是完全不同的,但是一般应该是第二种方式正确
public class Hello {
public static void main(String[] args) {
int a = 72;
int b = 105;
int c = 65281;
//第一种,先计算成和再转换成字符串
String s1 = (char) a + (char) b + (char) c + ""; //65458
System.out.println(s1);
String s4 = String.valueOf((char)a + (char)b + (char)c);
System.out.println(s4); //65458
//第二种,直接求得各个字符再相加
String s2 = "" + (char) a + (char) b + (char) c;
System.out.println(s2); //Hi!
String s5 = String.valueOf((char)a) + String.valueOf((char) b) + String.valueOf((char) c) ;// Hi!
System.out.println(s5); //Hi!
//第三种,直接拼接各个字符串
String s3 = "" + a + b + c;
System.out.println(s3); //7210565281
}
}
Step in Java!
此部分仍然倾向于从与C/C++和Python的异同出发
输入与输出
输出
格式化输出:此部分与C中的printf()
方法基本无区别,只不过需要注意12900000
在输出时自动转换成1.29E7
详细的格式化参数请参考JDK文档java.util.Formatter
输入
我们以下面这段代码为例:
import java.util.Scanner; //人工翻:引入java依赖中的扫描仪[输入类]
public class Hello {
public static void main(String[] avg) {
Scanner scanner = new Scanner(System.in); //System.in为输入流
System.out.print("Input your name: ");
var name = scanner.nextLine();
System.out.print("Input your age: ");
var age = scanner.nextInt();
System.out.println(name.getClass()); //class java.lang.String
}
}
可以发现,其对于输入内容的引入其实与Python的方式很相似,而且引入方式更接近于C++的scanf()
函数;
另外,此处提及一个方法Object.getClass()
,目前所学内容中只能适用于String
与Array
类型上。
当要测试输入,我们不能在线运行它,因为输入必须从命令行读取,因此,需要走编译、执行的流程:
$ javac Main.java
条件判断
-
if-else
与两门语言的区别也不算很大,但有几个必须注意的地方:-
浮点数在计算机中常常无法精确表示,并且计算可能出现误差,因此,判断浮点数相等用
==
判断不靠谱【利用差值小于某个临界值来判断会更好】if (x == 0.1){ ... } //改变: if (Math.abs(x - 0.1) < 0.00001){ ... }
-
在Java中,判断引用类型的变量是否相等,可以使用
==
运算符。但其判断的是两变量指向空间是否相同【引用类型变量内容相等判断应当使用Object1.equals(Object2)
方式】
注意:如果变量
Object1
为null
,会报NullPointerException
,因此最好先判断变量是否指空 -
-
switch
语句也与C/C++区别不大-
相比起C/C++可以增加字符串的匹配
-
而且自Java 12起,其将
case
语句的语法引入了->
:String fruit = "apple"; switch (fruit) { case "apple" -> System.out.println("Selected apple"); case "pear" -> System.out.println("Selected pear"); case "mango" -> { System.out.println("Selected mango"); System.out.println("Good choice!"); } default -> System.out.println("No fruit selected"); }
新语句没有穿透,因此不需要
break
-
\(yield\):大多数时候,在
switch
表达式内部,我们会返回简单的值。但是,如果需要复杂的语句,我们也可以写很多语句,放到
{...}
里,然后,用yield
返回一个值作为switch
语句的返回值:public class Main { public static void main(String[] args) { String fruit = "orange"; int opt = switch (fruit) { case "apple" -> 1; case "pear", "mango" -> 2; default -> { int code = fruit.hashCode(); yield code; // switch语句返回值 } }; System.out.println("opt = " + opt); } }
-
循环语句
其它循环语句较另外两个语言没有变化
for循环语句
这里介绍for-each
方式,其实这种方式在C++中也有:
public class Main {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
for (int n : ns) {
System.out.println(n);
}
}
}
和for
循环相比,for each
循环的变量n不再是计数器,而是直接对应到数组的每个元素。for each
循环的写法也更简洁。但是,for each
循环无法指定遍历顺序,也无法获取数组的索引。
数组操作
特殊的地方:
-
可以直接使用Java标准库提供的
Arrays.sort()
进行排序:import java.util.Arrays; public class Main { public static void main(String[] args) { int[] ns = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 }; Arrays.sort(ns); System.out.println(Arrays.toString(ns)); } }
-
多维数组:
//二维数组的定义方法 public class Main { public static void main(String[] args) { int[][] ns = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }; System.out.println(ns.length); // 3 } }
打印多维数组可以使用
Arrays.deepToString()
;
命令行参数
Java程序的入口是main
方法,而main
方法可以接受一个命令行参数,它是一个String[]
数组。
这个命令行参数由JVM接收用户输入并传给main
方法:
public class Hello {
public static void main(String[] avgs) {
for (String arg : avgs) {
if ("-version".equals(arg)){
System.out.println("v 1.0");
break;
}
}
}
}
执行以下命令:
可以看到完成了命令行的参数传递
练习使用代码
-
小明又有活儿了
请帮小明同学设计一个程序,输入上次考试成绩(int)和本次考试成绩(int),然后输出成绩提高的百分比,保留两位小数位(例如,21.75%)。
import java.util.Scanner;
public class Hello {
public static void main(String[] avg) {
System.out.print("Input your past mark: ");
var scan = new Scanner(System.in);
var past = scan.nextFloat();
System.out.print("Input your present mark: ");
var now = scan.nextFloat();
double per = now / past - 1;
System.out.printf("Increase %.2f",per);
}
}
-
救救孩子
请用
if ... else
编写一个程序,用于计算体质指数BMI,并打印结果。BMI = 体重(kg)除以身高(m)的平方
BMI结果:
- 过轻:低于18.5
- 正常:18.5-25
- 过重:25-28
- 肥胖:28-32
- 非常肥胖:高于32
import java.util.Scanner;
public class Hello {
public static void main(String[] avg) {
var scan = new Scanner(System.in);
System.out.print("Input your weight: ");
var weight = scan.nextFloat();
System.out.print("Input your length: ");
var len = scan.nextFloat();
float BMI = weight / (len * len);
if (BMI < 18.5) {
System.out.print("Level 1");
} else if (BMI < 25) {
System.out.print("Level 2");
} else if (BMI < 28) {
System.out.print("Level 3");
} else if (BMI <= 32) {
System.out.print("Level 4");
} else {
System.out.print("Level 5");
}
}
}
-
三位一体
使用
switch
实现一个简单的石头、剪子、布游戏
import java.util.Random;
import java.util.Scanner;
public class Hello {
public static void main(String[] avg) {
var r = new Random();
int ai_opt = r.nextInt(3);
System.out.print("""
There are three opts:
1. Rock
2. Knife
3. Clothing
You can select one from them, input your option:
""");
var scan = new Scanner(System.in);
var man_opt = scan.nextInt();
var res = ai_opt * 3 + man_opt;
if (man_opt < 1 || man_opt > 3) {
System.out.print("Error selection!");
return;
}
switch (res) {
case 1, 5, 9 -> {
System.out.print("Fair");
}
case 2, 6, 7 -> {
System.out.print("Fail");
}
default -> System.out.print("Win");
}
}
}
-
m到n(1)
使用
while
计算从m
到n
的和:
public class Hello {
public static void main(String[] avg) {
int m = 4, n = 100, res = 0;
while (m < n) {
res += m;
m++;
}
System.out.print("res = " + res);
}
}
-
m到n(2)
使用
do while
循环计算从m
到n
的和:
public class Hello {
public static void main(String[] avg) {
int m = 4, n = 100, res = 0;
do {
res += m;
m++;
} while (m < n);
System.out.print("res = " + res);
}
}
-
\(\pi\)的麦克劳林
圆周率π可以使用公式计算:
\(\frac{\pi}{4}=1-\frac{1}{3}+\frac{1}{5}-\frac{1}{7}+...\)
请利用
for
循环计算π:
public class Hello {
public static void main(String[] avg) {
double pi = 0, tmp = 1;
for (int i = 0; i < 100; i++) {
pi += 1 * 1.0 / (2 * i + 1) * tmp;
tmp *= (-1);
}
pi *= 4;
System.out.print("Pi = " + pi);
}
}
-
遍历数组(燃尽了)
请按倒序遍历数组并打印每个元素:
public class Hello {
public static void main(String[] avg) {
int[] ns = {1, 4, 9, 16, 25};
for (int i = ns.length - 1; i >= 0; i--)
System.out.printf("%d ", ns[i]);
}
}
- 数组排序
//冒泡排序为例
public class Hello {
public static void main(String[] avg) {
int[] ns = {28, 12, 89, 73, 65, 18, 96, 50, 8, 36};
for (int i = 0; i < ns.length - 1; i++) {
for (int j = 0; j < ns.length - i - 1; j++) {
if (ns[j] > ns[j + 1]) {
int tmp = ns[j];
ns[j] = ns[j + 1];
ns[j + 1] = tmp;
}
}
}
for (int i = 0; i < ns.length; i++) {
System.out.printf("%d ", ns[i]);
}
}
}