类与对象

类的创建流程

1
2
3
4
5
6
7
8
9
10
public class Puppy{
public Puppy(String name){
//这个构造器仅有一个参数:name
System.out.println("小狗的名字是 : " + name );
}
public static void main(String[] args){
// 下面的语句将创建一个Puppy对象
Puppy myPuppy = new Puppy( "tommy" );
}
}

**
类的定义中只有一个参数name
代码执行从main函数开始
新建一个Puppy类myPuppy并在新建是传入参数tommy,即为name参数的内容
执行时创建传参tommy,并且在创建时执行定义中的System.out.println函数输出内容
**

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
public class Puppy{
int puppyAge;
public Puppy(String name){
// 这个构造器仅有一个参数:name
System.out.println("小狗的名字是 : " + name );
}

public void setAge( int age ){
puppyAge = age;
}

public int getAge( ){
System.out.println("小狗的年龄为 : " + puppyAge );
return puppyAge;
}

public static void main(String[] args){
/* 创建对象 */
Puppy myPuppy = new Puppy( "tommy" );
/* 通过方法来设定age */
myPuppy.setAge( 2 );
/* 调用另一个方法获取age */
myPuppy.getAge( );
/*你也可以像下面这样访问成员变量 */
System.out.println("变量值 : " + myPuppy.puppyAge );
}
}

输出结果
小狗的名字是 : tommy
小狗的年龄为 : 2
变量值 : 2

执行流程
new puppy类 myPuppy 传参tommy到name
输出小狗名字
myPuppy.setAge传参age
myPuppy.getAge访问变量puppyAge
这里访问变量的方式有两种,一种是通过set和get函数,一种是类名.函数名

源文件相关规则

  • 源文件的名称应该和 public 类的类名保持一致。
  • 一个源文件中只能有一个 public 类
  • 载入 java_installation/java/io 路径下的所有类
1
import java.io.*;

Java基本的数据类型

boolean :Java中的布尔类型数据,只有两个取值 true和false 默认为==false==

1
boolean ifbool = true;

也可通过包装类(库)中.SIZE .MAX_VALUE .MIN_VALUE 函数直接通过代码给出取值范围

1
2
3
4
5
6
public class TypeTest{
public static void main(String[] args){
System.out.println("byte型的二进制位数"+Byte.SIZE);
System.out.println("取值范围"+Byte.MIN_VALUE+"到"+Byte.MAX_VALUE);
}
}

image|500

Java的值传递

Java函数中的参数传递是按值传递的,即在函数传值形成新的变量副本,不会影响到原来变量的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RunoobTest {
    public static void main(String[] args) {
        int a = 10, b = 20;
        swap(a, b); // 调用swap方法
        System.out.println("a = " + a + ", b = " + b); // 输出a和b的值
    }
   
    public static void swap(int x, int y) {
        int temp = x;
        x = y;
        y = temp;
    }
}
//这里在传递变量a与b到swap函数时,实际上只传了变量对应的值,在函数中的修改只会影响到副本,不会影响到原变量

如果需要对原来变量的值进行修改,有如下几种方法

  • swap函数方法直接写到main函数中
  • 变量a与b采用数组形式储存,这样传参时实际上就是传了引用(类似c++中数组指针)
1
2
3
4
5
6
7
8
9
10
11
12
13
public class RunoobTest {
public static void main(String[] args) {
int[] arr = {10, 20};
swap(arr); // 调用swap方法
System.out.println("a = " + arr[0] + ", b = " + arr[1]); // 输出a和b的值
}

public static void swap(int[] arr) {
int temp = arr[0];
arr[0] = arr[1];
arr[1] = temp;
}
}

类变量(静态变量)

public static int count1 = 0;

1
2
3
4
5
public class MyClass {
    public static int count1 = 0;
    public static int count2 = count1 + 1;
    // 其他成员变量和方法
}

静态变量是可变的,类名.变量名或者实例名.变量名都可以访问,多个相同类型类里面只能有一个同名静态变量,与之对比的是常量
静态变量的一个重要用途是用于计数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Counter {
private static int count = 0;

public Counter() {
count++;
}

public static int getCount() {
return count;
}

public static void main(String[] args) {
Counter c1 = new Counter();
Counter c2 = new Counter();
Counter c3 = new Counter();
System.out.println("目前为止创建的对象数: " + Counter.getCount());
}
}

每创建一个count值就+1

常量

常量的值是不可变的,常用于类的固定属性,也可于静态变量配合
public static final int BOXWIDTH = 6;

final 继承

final 方法(函数)可以被继承,但不能修改内容
fianl 类不可以被继承

1
public final class Test { // 类体 }

do while 循环

1
2
3
4
do {
//代码语句
}while(布尔表达式);

先执行一次代码在进行判断,如果符合条件就开始循环,直到最后一次判定不通过

增强 for 循环

增强for循环实际上就是for-each循环
定义:

1
2
3
for (元素类型 元素变量 : 集合) {
// 循环体内的操作,使用元素变量访问当前迭代的元素
}

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Testfor {  
public static void main(String[] args){
int [] numbers = {10, 20, 30, 40, 50};

for(int x : numbers ){
System.out.print( x );
System.out.print(",");
}
System.out.print("\n");
String [] names ={"James", "Larry", "Tom", "Lacy"};
for( String name : names ) {
System.out.print( name );
System.out.print(",");
}
}
}

这里两个for-each循环分别实现了按顺序遍历数组中的元素
第一个循环实现了在int型的bumbers数组对象中顺序取出了每一个int类型元素
第二个循环取出了String类型的元素

continue

continue;
用于跳转到下一条循环

if…else if…else

1
2
3
4
5
6
7
8
9
if(布尔表达式 1){
//如果布尔表达式 1的值为true执行代码
}else if(布尔表达式 2){
//如果布尔表达式 2的值为true执行代码
}else if(布尔表达式 3){
//如果布尔表达式 3的值为true执行代码
}else {
//如果以上布尔表达式都不为true执行代码
}

无论是if…else if…else还是if…else
都不需要考虑缩进

switch case 条件语句

1
2
3
4
5
6
7
8
9
10
11
switch(expression){
case value :
//语句
break; //可选
case value :
//语句
break; //可选
//你可以有任意数量的case语句
default : //可选
//语句
}

expression是需要匹配的==表达式==或值
case value匹配值,匹配后再执行对应语句
需要注意的是,当前case已经匹配,而且没有break跳出判断,那么后面所有case的语句都会被执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {
public static void main(String args[]){
int i = 1;
switch(i){
case 0:
System.out.println("0");
case 1:
System.out.println("1");
case 2:
System.out.println("2");
default:
System.out.println("default");
}
}
}
output:
1
2
default

数据类型的包装类 Java Number & Math 类

1
2
3
4
5
6
7
8
public class Test{

public static void main(String[] args){
Integer x = 5;
x = x + 10;
System.out.println(x);
}
}

如以上代码中的Integer x = 5,这样使用包装类直接定义基本数据类型数据时,可以用于对应函数只==允许传参对象==的情况,因为直接使用包装类进行定义,实际上就是创建了对应的数据对象

1
2
3
4
5
6
7
8
9
10
11
|包装类|基本数据类型|
|---|---|
|Boolean|boolean|
|Byte|byte|
|Short|short|
|Integer|int|
|Long|long|
|Character|char|
|Float|float|
|Double|double|
这里是包装类与对应数据类型的对照表

String 类

String创建字符串的两种方式:

  • String s1 = “谁是带篮子” 这样创建的字符串存储在公共池中,创建时会先对字符进行搜索,如果已经存在就直接返回引用。
  • String s2 = new String(“谁是带篮子”) 使用new创建的字符串存放在堆中,即使相同字符也会创建新的对象
    注意 String创建的对象一旦创建是不可以进行更改的,在某些情况下,需要对已经创建的字符串进行修改,这就出现了==StringBuffer 和 StringBuilder 类==

StringBuffer 和 StringBuilder 类

这两种类创建的字符串对象可以进行多次修改,而且不会产生新的对象
这两种类都是对对象本身进行操作,区别在于:

  • StringBuffer是线程安全的,即多个线程可以同时访问和修改StringBuffer对象,不会引起并发问题
  • StringBuilder没有线程安全性,但由于不需要进行线程同步,使用一般单线程的程序使用StringBuilder创建对象性能较高,速度较快

StringBuilder 类的对象创建及相关函数(使用方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
public class RunoobTest{
public static void main(String args[]){
StringBuilder sb = new StringBuilder(10);//创建长度为10的字符串对象
sb.append("Runoob..");//串尾追加Runoob..
System.out.println(sb);
sb.append("!");//追加!
System.out.println(sb);
sb.insert(8, "Java");//下标为8插入Java,同时长度适应
System.out.println(sb);
sb.delete(5,8);//delete下标所在区域 for(int i=5;i<8;i++)
System.out.println(sb);
}
}

image|600

实际上类似于c++中对于字符串数组的修改
在程序对于线程安全有要求时必须使用==StringBuffer==
需要注意的是==StringBuilder sb = “aaa”;==这样的用法是错误的
StringBuilder是一个类,不能直接像String一样快捷创建字符串,必须要先创建一个空的可变字符串对象,再使用.append(“this is string”)函数填充内容
tringBuilder sb = new StringBuilder();

数组

定义

1
2
3
4
5

dataType[] arrayRefVar;//声明
arrayRefVar = new dataType[arraySize];//创建
//声明并创建
dataType[] arrayRefVar = {value0, value1, ..., valuek};

Java中的数组使用需要先声明然后进行创建
也可以一条语句同时实现声明并创建
下面给出一个同时声明并创建的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TestArray {
public static void main(String[] args) {
// 数组大小
int size = 10;
// 定义数组
double[] myList = new double[size];
myList[0] = 5.6;
myList[1] = 4.5;
myList[2] = 3.3;
myList[3] = 13.2;
myList[4] = 4.0;
myList[5] = 34.33;
myList[6] = 34.0;
myList[7] = 45.45;
myList[8] = 99.993;
myList[9] = 11123;
// 计算所有元素的总和
double total = 0;
for (int i = 0; i < size; i++) {
total += myList[i];
}
System.out.println("总和为: " + total);
}
}

这里同时声明并创建了double类型的数组myList,大小为10个字符
需要注意的是Java的数组是严格的,一旦创建之后长度就不可以改变,但是Java中动态数据有着其他的结构可以使用,例如ArrayList

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.ArrayList;

ArrayList<String> fruits = new ArrayList<String>();

fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");

String fruit = fruits.get(0);
System.out.println(fruit); // 输出:Apple
//基础用法
//常用函数还有set,remove

For-Each循环

遍历数组

1
2
3
4
5
6
7
8
9
10
public class TestArray {
public static void main(String[] args) {
double[] myList = {1.9, 2.9, 3.4, 3.5};

// 打印所有数组元素
for (double element: myList) {
System.out.println(element);
}
}
}

这种特殊的循环会自己更新下标来寻找数组中的所有元素

数组传参

1
2
3
4
5
public static void printArray(int[] array) {
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
1
printArray(new int[]{3, 1, 2, 6, 4, 2});

这里说明了Java数组的传参方式实际上跟c++没有任何区别

Arrays 类

java.util.Arrays类中有很多成熟的函数可供使用

1
2
3
4
5
6
7
8
//二分快速查找
public static int binarySearch(Object[] a, Object key)
//比较数组,相同返回true
public static boolean equals(long[] a, long[] a2)
//整个数组填充指定数据
public static void fill(int[] a, int val)
//升序排列
public static void sort(Object[] a)

日期时间

获取当时时间🌰

1
2
3
4
5
6
7
8
9
10
11
import java.util.Date;

public class DateDemo {
public static void main(String[] args) {
// 初始化 Date 对象
Date date = new Date();

// 使用 toString() 函数显示日期时间
System.out.println(date.toString());
}
}

long getTime( )这个函数用于返回自1970.1.1 00.00以来的毫秒值,常用于计算,而上面用到的String toString( )函数用于返回一个指定格式的时间

指定格式输出时间

使用 SimpleDateFormat格式化时间

🌰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import  java.util.*;
import java.text.*;

public class DateDemo {
public static void main(String[] args) {

Date dNow = new Date( );
SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss");
//HH为24小时制,hh为12小时制
System.out.println("当前时间为: " + ft.format(dNow));
}
}
//当前时间为: 2023-10-19 14:21:09

创建了一个ft对象,并使用SimpleDateFormat函数对.format()函数要使用的格式进行了规定,ft.format(dNow)函数用于输出对应格式的时间对象

printf中的时间格式化方法

这种格式化输出的方法能够直接在输出的过程中选择需要输出的内容
先给出一个🌰

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

public class DateDemo {

public static void main(String[] args) {
// 初始化 Date 对象
Date date = new Date();

//c的使用
System.out.printf("全部日期和时间信息:%tc%n",date);
//f的使用
System.out.printf("年-月-日格式:%tF%n",date);
//d的使用
System.out.printf("月/日/年格式:%tD%n",date);
//r的使用
System.out.printf("HH:MM:SS PM格式(12时制):%tr%n",date);
//t的使用
System.out.printf("HH:MM:SS格式(24时制):%tT%n",date);
//R的使用
System.out.printf("HH:MM格式(24时制):%tR",date);
}
}

输出结果:

image.png|500

代码中给出了几个常用的printf输出时间的简单格式,大多数情况已经足够

字符串转date对象

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
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

public class UserInputStringToTime {
public static void main(String[] args) {
// 定义日期时间格式
String dateFormat = "yyyy-MM-dd HH:mm:ss";

// 创建 SimpleDateFormat 对象
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);

// 创建 Scanner 对象来获取用户输入
Scanner scanner = new Scanner(System.in);

System.out.print("请输入日期时间字符串(格式:" + dateFormat + "):");
String userInput = scanner.nextLine();

try {
// 调用 parse 方法将用户输入的字符串解析为 Date 对象
Date date = sdf.parse(userInput);

// 输出解析后的日期时间
System.out.println("解析结果:" + date);
} catch (Exception e) {
System.out.println("日期时间格式不匹配或解析失败。");
} finally {
scanner.close();
}
}
}

数值化计算时间间隔

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

public class DiffDemo {

public static void main(String[] args) {
try {
long start = System.currentTimeMillis( );
System.out.println(new Date( ) + "\n");
Thread.sleep(5*60*10);
System.out.println(new Date( ) + "\n");
long end = System.currentTimeMillis( );
long diff = end - start;
System.out.println("Difference is : " + diff);
} catch (Exception e) {
System.out.println("Got an exception!");
}
}
}

这里使用System.currentTimeMillis( )来获取当前时间的毫秒值
同时使用了try catch方法来实现一个执行并异常处理的逻辑,如果休眠未成功,就会自动进入catch异常处理块中,并抛出异常

java中的休眠

Thread.sleep()函数用于阻塞当前进程,参数单位是毫秒

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.*;  

public class SleepDemo {
public static void main(String[] args) {
try {
System.out.println(new Date( ) + "\n");
Thread.sleep(1000*3); // 休眠1*1000毫秒=3秒
System.out.println(new Date( ) + "\n");
} catch (Exception e) {
System.out.println("休眠失败");
}
}
}

实践应用中常用于脚本频率检测机制的反制,可见用途:抢票,爆破,爬虫

Calendar类

之前我们已经实现了日期的读取,写入,格式化,但是对于日期的指定数据的获取,数值计算,还没有特别好的方法实现,使用在这里引入了Calendar类来处理这些问题,Calendar和Date类类似,但是有着更加强大的功能

日期对象创建

  • 创建一个代表系统当前日期的对象
1
Calendar c = Calendar.getInstance();//默认是当前日期
  • 创建指定日期对象
1
2
3
//创建一个代表2023年6月12日的Calendar对象
Calendar c1 = Calendar.getInstance();
c1.set(2023, 6 - 1, 12);

可以发现当创建对象时,使用的Calendar.getInstance()对象默认创建的都是当前日期,需要指定日期时,在创建对象后再使用.set函数指定
需要注意的是,这里并没有使用c1.clear();来清除所有数据,所以未设定的数据还是获取到的当前时间

set使用
  • 年月日默认格式设置
1
2
public final void set(int year,int month,int date)
c1.set(2009, 6, 12);//把Calendar对象c1的年月日分别设置为:2009、6、12
  • 指定数据设置
1
2
3
4
5
public void set(int field,int value)
c1.set(Calendar.DATE,10);
c1.set(Calendar.MONTH,11);
c1.set(Calendar.YEAR,2008);
//2008.11.10

附上对应表

image.png|500

  • add加减日期
1
2
3
4
c1.add(Calendar.DATE, 10);
//加上10天
c1.add(Calendar.DATE, -10);
//减去10天
get获取部分日期数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//创建对象
Calendar c1 = Calendar.getInstance();

// 获得年份
int year = c1.get(Calendar.YEAR);
// 获得月份
int month = c1.get(Calendar.MONTH) + 1;
// 获得日期
int date = c1.get(Calendar.DATE);
// 获得小时
int hour = c1.get(Calendar.HOUR_OF_DAY);
// 获得分钟
int minute = c1.get(Calendar.MINUTE);
// 获得秒
int second = c1.get(Calendar.SECOND);
// 获得星期几(注意(这个与Date类是不同的):1代表星期日、2代表星期1、3代表星期二,以此类推)
int day = c1.get(Calendar.DAY_OF_WEEK);

这里发现Calendar的月份时从0开始的

GregorianCalendar类

GregorianCalendar实现了公历的日期格式
这里只给出一个简单的利用案例

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

public class GregorianCalendarDemo {

public static void main(String[] args) {
String months[] = {
"1", "2", "3", "4",
"5", "6", "7", "8",
"9", "10", "11", "12"};
//默认从0开始的月份作为引用转换成正常格式月份数组输出
int year;
// 初始化 Gregorian 日历
// 使用当前时间和日期
// 默认为本地时间和时区
GregorianCalendar gcalendar = new GregorianCalendar();
// 显示当前时间和日期的信息
System.out.print("日期: ");
System.out.print(months[gcalendar.get(Calendar.MONTH)]);
System.out.print(" " + gcalendar.get(Calendar.DATE) + " ");
System.out.println(year = gcalendar.get(Calendar.YEAR));
System.out.print("时间: ");
System.out.print(gcalendar.get(Calendar.HOUR) + ":");
System.out.print(gcalendar.get(Calendar.MINUTE) + ":");
System.out.println(gcalendar.get(Calendar.SECOND));

// 测试当前年份是否为闰年
if(gcalendar.isLeapYear(year)) {
System.out.println("当前年份是闰年");
}
else {
System.out.println("当前年份不是闰年");
}
}
}

这里只需要学会GregorianCalendar gcalendar = new GregorianCalendar();
这样使用默认时间创建即可,这样创建的日期还有着闰年判断,可以少写一段闰年判断

Java正则表达式

正则表达式常用于文本的格式筛选和处理

  • 一个字符串就是一个简单的正则表达式,例如this is text就能匹配==”this is text”==字符
  • this\s+is\s+text这里的\s+用于匹配一个或者多个空格,所以这里匹配的字符同样包含上述字符
  • ^\d+(\.\d+)?
1
2
3
4
5
6
7
8
9
^ 定义了以什么开始

\d+ 匹配一个或多个数字

? 设置括号内的选项是可选的

\. 匹配 "."

可以匹配的实例:"5", "1.5" 和 "2.21"。

Java的正则来自java.util.regex
包中有Pattern 类,Matcher 类,PatternSyntaxException类
实际上,用法是Pattern类创建对象,Marcher进行匹配,PatternSyntaxException进行语法异常处理
给出一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.regex.*;

class RegexExample1{
public static void main(String[] args){
String content = "I am noob " +
"from runoob.com.";

String pattern = ".*runoob.*";//创建正则匹配字符串对象

boolean isMatch = Pattern.matches(pattern, content);
//Marcher类中.matches方法,返回是否匹配布尔值
System.out.println("字符串中是否包含了 'runoob' 子字符串? " + isMatch);
}
}

这里较为具体的用法和原理暂且按下不表,在实际应用时直接生成时再针对理解

Java 方法

System.out.println()这里就是调用系统类System中的标准输出对象out中的方法println()
方法的定义就是包含在类或对象中,解决某一类问题的步骤的有序组合,然后能被其他地方引用
其实就是其他程序语言中的函数
结构如下

1
2
3
4
5
6
修饰符 返回值类型 方法名(参数类型 参数名){
...
方法体
...
return 返回值;
}

image.png|700

常规的用法跟c++没区别,需要注意的是调用方法值传参时会创建参数副本,修改并不会影响到原来参数

重载

1
2
3
4
5
6
public static double max(double num1, double num2) {
if (num1 > num2)
return num1;
else
return num2;
}

这里给出了一个例子,相同方法名,但是传递参数的数据类型不同,就会执行不同的代码块
这种方法重写就叫重载

可变参数

可变参数的类型是数组
声明参数

1
typeName... parameterName

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class VariableArguments {  
public static void main(String[] args) {
printValues("Hello", "World");
printValues("Java", "is", "awesome");

}
public static void printValues(String... values) {
for (String value : values) {
System.out.println(value);
}
}

}
//output
Hello
World
Java
is
awesome

这里通过String... values声明了方法传递的参数时可变参数values,参数的内容在调用方法时传参指定

Java 流(Stream)、文件(File)和IO

输入读取

这里读取输入有两种常见类

  • BufferedReaderBufferedReader 类主要用于以文本方式读取字符流,它提供了高效的缓冲机制,可以一次读取一行或指定大小的字符数据。使用 BufferedReader 可以逐行读取文本文件的内容。
  • ScannerScanner 类具有更多的功能,可以用于解析原始类型和字符串,并从输入中提取特定类型的值。它可以对不同类型的输入进行解析和筛选,并提供了方便的方法来获取用户的输入。
    在实际的使用中,BufferedReader常用于文本文档或者大量输入的读取,Scanner则用于少量用户输入内容的读取
    下面给出两个类获取用户输入的示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class BufferedReaderExample {
public static void main(String[] args) {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
try {
System.out.print("请输入您的姓名: ");
String name = reader.readLine();
System.out.println("您好, " + name + "!");
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
import java.util.Scanner;

public class ScannerExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入您的年龄: ");
int age = scanner.nextInt();
System.out.println("您的年龄是: " + age);
scanner.close();
}
}

会发现实际上BufferReader类的使用较Scanner而言十分繁琐,因此在一般项目的构建中只使用Scanner类

文件读写

FileInputStream 读取文件

创建输入流对象

  • InputStream f = new FileInputStream("C:/java/hello");
  • File f = new File("C:/java/hello");InputStream in = new FileInputStream(f);

FileOutputStream 写入文件

创建输出流对象

1
2
3
4
OutputStream f = new FileOutputStream("C:/java/hello")

File f = new File("C:/java/hello");
OutputStream fOut = new FileOutputStream(f);

需要注意的是这两个类在写入文件时是二进制形式的,对于文本文件,我们更常用的类是BufferedReader和BufferedWriter

BufferedReader 读取文本

1
2
3
4
5
6
7
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
String line;
while ((line = reader.readLine()) != null) {

// 处理每一行的文本数据
}
reader.close();

BufferedWriter 写入文本

1
2
3
4
5
BufferedWriter writer = new BufferedWriter(new FileWriter("file.txt"));
String data = "Hello, World!";
writer.write(data);
writer.flush();//刷新缓冲区,确保写入完成
writer.close();

需要注意的是,BufferedReaderBufferedWriter都实现了AutoCloseable接口,所以可以通过try-with-resources语句(try-catch)来创建对象,这样他会自动关闭输入输出流

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

public class Example {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {

String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}

} catch (IOException e) {
e.printStackTrace();
}
}
}
//cp input.txt output.txt

目录

创建目录

1
2
3
4
5
6
7
8
9
10
import java.io.File;

public class CreateDir {
public static void main(String[] args) {
String dirname = "/tmp/user/java/bin";
File d = new File(dirname);
// 现在创建目录
d.mkdirs();//mkdirs()创建文件夹及文件夹的所有父文件夹
}
}

读取目录

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

public class DirList {
public static void main(String args[]) {
String dirname = "/tmp";
File f1 = new File(dirname);
if (f1.isDirectory()) {
System.out.println("目录 " + dirname);
String s[] = f1.list();
for (int i = 0; i < s.length; i++) {
File f = new File(dirname + "/" + s[i]);
if (f.isDirectory()) {
System.out.println(s[i] + " 是一个目录");
} else {
System.out.println(s[i] + " 是一个文件");
}
}
} else {
System.out.println(dirname + " 不是一个目录");
}
}
}

直接读取路径,isDirectory()判断是否为目录

删除目录

java.io.File.delete()
这个方法只能删除空目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.io.File;

public class DeleteFileDemo {
public static void main(String[] args) {
// 这里修改为自己的测试目录
File folder = new File("/tmp/java/");
deleteFolder(folder);
}

// 删除文件及目录
public static void deleteFolder(File folder) {
File[] files = folder.listFiles();
if (files != null) {
for (File f : files) {
if (f.isDirectory()) {
deleteFolder(f);
} else {
f.delete();
}
}
}
folder.delete();
}
}

递归遍历每个不为空的文件夹,删除其中文件,检查为空则删除文件夹
这样实现了删除指定目录下的所有文件

Java Scanner 类

调用Scannner类获取用户输入
创建对象

1
Scanner s = new Scanner(System.in);

简单例子

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

public class ScannerDemo {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
// 从键盘接收数据

// nextLine方式接收字符串
System.out.println("nextLine方式接收:");
// 判断是否还有输入
if (scan.hasNextLine()) {
String str2 = scan.nextLine();
System.out.println("输入的数据为:" + str2);
}
scan.close();
}
}

nextLine方法只会识别回车为结束输入

image.png|600

next方法不会接收空格后输入,仍然是回车结束输入

image.png|600

这里给出一个输入作为变量的简单例子

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

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);

System.out.print("请输入一组字符串,以空格分隔:");
String input = scanner.nextLine();

// 将输入字符串按空格分割为字符串数组
String[] strings = input.split(" ");

System.out.println("您输入的字符串数组为:");
for (String str : strings) {
System.out.println(str);
}

scanner.close();
}
}

 hasNextXxx()方法可以判断输入类型,返回值为bool类型,建议在每次输入时首先检验,避免恶意代码强制闭合注入😈

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

public class ScannerDemo {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
// 从键盘接收数据
int i = 0;
float f = 0.0f;
System.out.print("输入整数:");
if (scan.hasNextInt()) {
// 判断输入的是否是整数
i = scan.nextInt();
// 接收整数
System.out.println("整数数据:" + i);
} else {
// 输入错误的信息
System.out.println("输入的不是整数!");
}
System.out.print("输入小数:");
if (scan.hasNextFloat()) {
// 判断输入的是否是小数
f = scan.nextFloat();
// 接收小数
System.out.println("小数数据:" + f);
} else {
// 输入错误的信息
System.out.println("输入的不是小数!");
}
scan.close();
}
}

这里同时体现了输入为int类型的常用获取输入方法
需要注意不能忘记在末尾==scan.close();==

异常处理

捕获异常

1
2
3
4
5
6
7
8
try
{
// 程序代码
}catch(ExceptionName e1)
{
//Catch 块
//catch(异常类型 异常变量名)
}

try中执行代码,如果产生异常就会将异常类型传到catch块,根据对应的异常名称,执行不同的代码
给出一个实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 文件名 : ExcepTest.java
import java.io.*;
public class ExcepTest{

public static void main(String args[]){
try{
int a[] = new int[2];
System.out.println("Access element three :" + a[3]);
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("Exception thrown :" + e);
}
System.out.println("Out of the block");
}
}

这里访问了大小为2的数组的第四个元素,产生了数组索引越界异常,传到catch块,发现异常名称匹配,执行了输出报错信息

image.png

这里的System.out.println(“Out of the block”);是是否执行catch块都会执行的部分

多重捕获块

1
2
3
4
5
6
7
8
9
try{
// 程序代码
}catch(异常类型1 异常的变量名1){
// 程序代码
}catch(异常类型2 异常的变量名2){
// 程序代码
}catch(异常类型3 异常的变量名3){
// 程序代码
}

发生异常后逐个catch块匹配异常类型

finally关键字

在finally段中的代码在try-catch执行完后总会被执行

1
2
3
4
5
6
7
8
9
try{
// 程序代码
}catch(异常类型1 异常的变量名1){
// 程序代码
}catch(异常类型2 异常的变量名2){
// 程序代码
}finally{
// 程序代码
}

🌰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ExcepTest{
public static void main(String args[]){
int a[] = new int[2];
try{
System.out.println("Access element three :" + a[3]);
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("Exception thrown :" + e);
}
finally{
a[0] = 6;
System.out.println("First element value: " +a[0]);
System.out.println("The finally statement is executed");
}
}
}

throws/throw关键字

1
2
3
4
5
public void checkNumber(int num) {
  if (num < 0) {
    throw new IllegalArgumentException("Number must be positive");
  }
}

throw关键字抛出异常,实际上用来告知调用者当前代码的执行情况,类似于输出报错语句

throws是在方法的声明中指定可能抛出的异常类型,并且传给调用的函数,可以同时指定多个异常类型
例子

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
public class Main {
public static void main(String[] args) {
String filePath = "example.txt";
try {
readFile(filePath);
} catch (IOException e) {
System.err.println("An error occurred while reading the file: " + e.getMessage());
// 其他异常处理逻辑,比如记录日志或者进行其他操作
}
}

public static void readFile(String filePath) throws IOException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(filePath));
String line = reader.readLine();
while (line != null) {
System.out.println(line);
line = reader.readLine();
}
} finally {
if (reader != null) {
reader.close();
}
}
}
}

这里在readFile函数声明中指定了可能抛出IOException,在调用的catch块中匹配到readFile抛出的IOException,执行对应的处理

throw/throws的区别实际上是一个是直接在方法中输出报错信息,一个是由方法抛出异常,在调用代码中再处理异常

try-with-resources 自动关闭资源

结构

1
2
3
4
5
try (resource declaration) {
// 使用的资源
} catch (ExceptionType e1) {
// 异常块
}

在try中声明资源,在catch中处理资源关闭可能引发的异常
这样实现了能自动关闭所有含AutoCloseable 接口的资源。

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

public class RunoobTest {

    public static void main(String[] args) {
    String line;
        try(BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
            while ((line = br.readLine()) != null) {
                System.out.println("Line =>"+line);
                //逐行读取并输出到控制台
            }
        } catch (IOException e) {
            System.out.println("IOException in try block =>" + e.getMessage());
           
        }
    }
}
//output:IOException in try block =>test.txt (No such file or directory)

同时处理多个资源

在处理多个资源时,只需要资源声明之间加上;即可

1
2
3
4
5
6
7
8
9
10
11
12
//import java.io.*;
//import java.util.*;
class RunoobTest {
    public static void main(String[] args) throws IOException{
        try (Scanner scanner = new Scanner(new File("testRead.txt"));
            PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
            while (scanner.hasNext()) {
                writer.print(scanner.nextLine());
            }
        }
    }
}

这里实现了两个资源一起使用,逐行读取内容写入到testWrite.txt
这里没有写catch对于异常的处理,但并不会造成问题
换成更常用的Buffered来读取写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//import java.io.*;

class RunoobTest {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("testRead.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("testWrite.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
}
} catch (IOException e) {
System.out.println("An error occurred: " + e.getMessage());
}
}
}

这里如果需要指定行读取,可以通过添加计数器与指定数值进行比较来实现

异常的构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 文件名InsufficientFundsException.java
import java.io.*;

//自定义异常类,继承Exception类
public class InsufficientFundsException extends Exception
{
//此处的amount用来储存当出现异常(取出钱多于余额时)所缺乏的钱
private double amount;
public InsufficientFundsException(double amount)
{
this.amount = amount;
}
public double getAmount()
{
return amount;
}
}

需要注意的是需要继承Exception类
当放在一个文件夹(包)内时,直接正常抛出异常就行

继承

格式

1
2
3
4
5
class 父类 {
}

class 子类 extends 父类 {
}

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//父类
public class Animal {
private String name;
private int id;
public Animal(String myName, int myid) {
name = myName;
id = myid;
}
public void eat(){
System.out.println(name+"正在吃");
}
public void sleep(){
System.out.println(name+"正在睡");
}
public void introduction() {
System.out.println("大家好!我是" + id + "号" + name + ".");
}
}
1
2
3
4
5
6
//子类
public class Penguin extends Animal {
public Penguin(String myName, int myid) {
super(myName, myid);
}
}
1
2
3
4
5
6
//子类
public class Mouse extends Animal {
public Mouse(String myName, int myid) {
super(myName, myid);
}
}

这里的企鹅类和老鼠类都继承了父类animals的属性和方法,分别写了自己的构造函数,但是在构造函数中使用super函数调用了父类的构造函数初始化myName属性和myid属性
这里需要注意的是java的继承中,不支持多个父类的多继承,但是支持多次继承

继承关键字

extends关键字

extends关键字是默认使用的继承关键字,属于单一继承,一个子类只能有一个父类,一次只能继承一个类
之前的实例使用的是该关键字

implements关键字

这里使用interface构造接口,然后使用implements关键字,可以实现继承多个接口
这里给出继承和重写的一个例子

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 interface A {
public void eat();
public void sleep();
}

public interface B {
public void show();
}

public class C implements A, B {
@Override
public void eat() {
System.out.println("C is eating");
}

@Override
public void sleep() {
System.out.println("C is sleeping");
}

@Override
public void show() {
System.out.println("C is showing");
}
}

这里C类继承了A,B两个接口,并且重写了其中的函数

super 与 this 关键字

spuer函数执行当前类的父类

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
class Animal {
void eat() {
System.out.println("animal : eat");
}
}

class Dog extends Animal {
void eat() {
System.out.println("dog : eat");
}
void eatTest() {
this.eat(); // this 调用自己的方法
super.eat(); // super 调用父类方法
}
}

public class Test {
public static void main(String[] args) {
Animal a = new Animal();
a.eat();
Dog d = new Dog();
d.eatTest();
}
}
//output
animal : eat
dog : eat
animal : eat

这里Dog类中super.eat相当于Animal.eat,this.eat相当于Dog.eat

final 关键字

flinal关键字可以用于类,方法,和变量

1
2
3
final class MyClass {
// 类的定义
}

fianl类不可被继承

1
2
3
4
5
6
7
8
9
10
class Parent {
final void myMethod() {
// 方法的定义
}
}

class Child extends Parent {
// 无法重写或覆盖myMethod方法
}

final方法不可以被子类重写或者覆盖

1
final int myConstant = 10;

final变量被赋值后不可以被修改

重写(Override)与重载(Overload)

重写(Override)

子类继承的父类函数外壳不变,内容重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}

class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
}

public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象

a.move();// 执行 Animal 类的方法

b.move();//执行 Dog 类的方法
}
}

这里Animal b = new Dog(); // Dog 对象向上转型,使用的所有函数都是Dog类中函数,但不包括Dog类独有的函数

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
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}

class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
public void bark(){
System.out.println("狗可以吠叫");
}
}

public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象

a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
b.bark();
}
}

这里运行到b.bark()会抛出编译错误,因为这里的b对象,是Dog对象向上转型来的,没有Dog中独有的bark方法

swing框架使用

一元二次求根案例

image|500

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class QuadraticRootsCalculator extends JFrame implements ActionListener {
private JTextField coefficientAField;
private JTextField coefficientBField;
private JTextField coefficientCField;
private JTextField root1Field;
private JTextField root2Field;

public QuadraticRootsCalculator() {
setTitle("一元二次方程求根"); // 设置窗口标题
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置窗口关闭操作
setSize(400, 300); // 设置窗口大小
setLocationRelativeTo(null); // 设置窗口居中显示

JPanel panel = new JPanel(); // 创建面板
panel.setLayout(null); // 设置面板布局为null,以便自由定位组件

// 创建系数 a 的标签和文本框
JLabel coefficientALabel = new JLabel("系数 a:");
coefficientALabel.setBounds(10, 20, 80, 25);
panel.add(coefficientALabel);
coefficientAField = new JTextField(10);
coefficientAField.setBounds(100, 20, 165, 25);
panel.add(coefficientAField);

// 创建系数 b 的标签和文本框
JLabel coefficientBLabel = new JLabel("系数 b:");
coefficientBLabel.setBounds(10, 50, 80, 25);
panel.add(coefficientBLabel);
coefficientBField = new JTextField(10);
coefficientBField.setBounds(100, 50, 165, 25);
panel.add(coefficientBField);

// 创建系数 c 的标签和文本框
JLabel coefficientCLabel = new JLabel("系数 c:");
coefficientCLabel.setBounds(10, 80, 80, 25);
panel.add(coefficientCLabel);
coefficientCField = new JTextField(10);
coefficientCField.setBounds(100, 80, 165, 25);
panel.add(coefficientCField);

// 创建根 1 的标签和文本框
JLabel root1Label = new JLabel("根 1:");
root1Label.setBounds(10, 110, 80, 25);
panel.add(root1Label);
root1Field = new JTextField(10);
root1Field.setBounds(100, 110, 165, 25);
root1Field.setEditable(false); // 设置文本框为不可编辑
panel.add(root1Field);

// 创建根 2 的标签和文本框
JLabel root2Label = new JLabel("根 2:");
root2Label.setBounds(10, 140, 80, 25);
panel.add(root2Label);
root2Field = new JTextField(10);
root2Field.setBounds(100, 140, 165, 25);
root2Field.setEditable(false); // 设置文本框为不可编辑
panel.add(root2Field);

// 创建求根按钮和退出按钮
JButton calculateButton = new JButton("求根");
calculateButton.setBounds(10, 180, 80, 25);
calculateButton.addActionListener(this);
/*
添加按钮的事件监听器
calculateButton.addActionListener即在重写的的ActionListener方法中寻找对应逻辑函数进行跳转
需要重写ActionListener函数
*/ panel.add(calculateButton);

JButton exitButton = new JButton("退出");
exitButton.setBounds(100, 180, 80, 25);
exitButton.addActionListener(this); // 添加按钮的事件监听器
//跳转ActionListener中exitButton对应函数
panel.add(exitButton);

add(panel); // 将面板添加到窗口中
}

public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
QuadraticRootsCalculator calculator = new QuadraticRootsCalculator();
calculator.setVisible(true); // 显示窗口
});
}

@Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("求根")) {
/*
使用 e.getActionCommand() 获取到触发事件的动作命令,并使用 equals() 方法将其与字符串 "求根" 进行比较。如果两者相等,返回true
*/ // 处理 "求根" 按钮的点击事件
// 执行计算根的逻辑
// 获取用户输入的系数值
double a = Double.parseDouble(coefficientAField.getText());
double b = Double.parseDouble(coefficientBField.getText());
double c = Double.parseDouble(coefficientCField.getText());

double discriminant = b * b - 4 * a * c; // 计算判别式

if (discriminant > 0) {
// 如果判别式大于0,则方程有两个实根
double root1 = (-b + Math.sqrt(discriminant)) / (2 * a);
double root2 = (-b - Math.sqrt(discriminant)) / (2 * a);
root1Field.setText(String.valueOf(root1)); // 将根1的值显示在文本框中
root2Field.setText(String.valueOf(root2)); // 将根2的值显示在文本框中
} else if (discriminant == 0) {
// 如果判别式等于0,则方程有一个实根
double root = -b / (2 * a);
root1Field.setText(String.valueOf(root)); // 将根的值显示在文本框中
root2Field.setText(String.valueOf(root)); // 将根的值显示在文本框中
} else {
// 如果判别式小于0,则方程无实根
root1Field.setText("无解"); // 在文本框中显示"无解"
root2Field.setText("无解"); // 在文本框中显示"无解"
}
} else if (e.getActionCommand().equals("退出")) {
System.exit(0); // 退出程序
}
}
}