elric
23 次阅读
约 37 分钟 · 11801 字
Java 学习

Java 学习

2024-2-27~2024-3-13

JDK20 API 文档

每个牛 B 人物背后都有段苦逼的岁月,只要像 sb 的一样坚持,终将牛 B!

2026-4-6 :当时好像看的是狂神说的

Java 入门

Java 的小知识

特性与优势

简单性 面向对象 可移植性
高性能 分布式 动态性(反射的特性)
多线程 安全性(内存问题少) 健壮性(用的人多)

分为三个版本

  • JavaSE:标准版
  • JavaME:阉割版,现在几乎没了,主要是性能上升了,弱性能的也不用 Java 开发
  • JavaEE:企业版

要先学好标准版才行

JDK、JRE、JVM

JDK:Java Development Kit (Java 开发者工具)(包含下两)

JRE:Java Runtime Environment (Java 运行时环境)(包含 JVM)

JVM:Java Virtual Machine (Java 虚拟机)

HelloWorld

先新建一个文件,改为Hello.java 然后编写代码

Java
public class Hello {    public static void main(String[] args) {        System.out.println("Hello,World!");    }}

之后通过 cmd 命令行,使用 javac 编译文件,然后使用 java 运行文件

Bash
javac Hello.javajava Hello# 输出:Hello,World!

(当然也可以使用 VSCode 自带的运行功能在命令行中运行)

Java
System.out.print("Hello, world!");  	  // 输出System.out.println("Hello,World!"); 	  // 输出+换行System.out.printf("Hello, %s!", "world"); // C语言的输出方式

Java 运行机制

就是先编译成字节码,然后在通过解释器解释,介于编译型与解释型两者之间的语言

Java 基础

因为是因为 C 语言的影响设计的,当时制作 java 时就是想做一个语法像 C 语言,又没有 C++复杂的机制的语言,于是 Java 诞生了(刚开始还想叫 C++--),所以和 C 语言一样的我就不多写了

注释

Java
// 单行注释/* 多行注释 *//** * 文本注释 */

标识符

标识符还可以使用美元符号 $ 和 中文字符

Java
int $cc = 0;String 中文¥="1";

关键字

abstract assert boolean break byte
case catch char class const
continue default do double else
enum extends final finally float
for goto if implements import
instanceof int interface long native
new package private protected public
return strictfp short static super
switch synchronized this throw throws
transient try void volatile while

数据类型

C++ Java 值范围/其他
byte(整数) -128~127
int(32 位)/
short(64 位)
short -32768~32767
int(64 位)/
long
int -2,147,483,648 ~ 2,147,483,647 (2*10^9^)
long long long -9223372036854775808 ~ 9223372036854775807 (9*10^18^)
char(1 字节) char(2 字节) Java 默认使用 Unicode 字符集,包括 UTF-8 等。C++使用的是 ASCII 字符集
string String 注:string/String 是类,而不是基本数据类型
bool boolean

java 分为基本数据类型与引用类型(对象),引用类型分为

  • 类
  • 接口
  • 数组

java 对于long类型和float类型,有点不同,

  • 对于long类型,如果long类型的数后面不加一个L,会被编译器识别成int类型,会报错 “整数过大”,要在后面加一个 L 才会识别成long类型,从而不报错;

  • 对于float类型的话默认会变成double类型,如果想要变成float类型,要在后面加一个F

Java
// C++char n1  ='阿';  // ×char n2[]="阿";  // √string n3="阿";  // √ // Javachar n4  ='阿';  // √String n5="阿";  // √

和 C++一样,可以使用特定的前缀来表示不同进制

进制 C++/Java
二进制 0b1010
八进制 012
十进制 10
十六进制 0xA

数字还有前缀中的字母也是不分大小写的


运算符

C++ Java
>> >> 右移一位,左边填充与最左边的位相同的值
>>> 右移一位,左边全填充 0
instanceof A instanceof B 用于检查 变量名 A 是否属于对象 B(引用类型 B) (instance + of)
Java
// 按照顺序运算的System.out.println(""+(10+20));  // 输出:30System.out.println(""+10+20);    // 输出:1020System.out.println(10+20+"");    // 输出:30

包机制

包(软件包) 就是 文件夹 ,像是一个文件夹中无法放置两个相同名字的文件,但只要将这两个文件分别放到两个文件夹中就可以用同一个名字了

包机制就可以用同样的方法更好的组织类,用于区别类名的命名空间,还可以分类的整整齐齐

一般利用公司域名倒置作为包名;如 com.baidu.www

三个文件夹:	com	  baidu	  	www

以下例子要用的路径

- src  - com    - myapp      - Helper.java      - Main.java      - main.cpp      - util        - Document.java   - lib    - mylib      - MyLibrary.java      - sakura.h

包语句的语法格式

Java
package pkg1[.pkg2[.pkg3…]];// 用于声明一个类所在的包(要从类路径开始写包所在的路径)// 点. 表示子包// 类路径:包含顶级包com和lib的文件夹(就是上面的src)
Java
package com.myapp; // 声明Main在"com/myapp"下 public class Main {    // ...}

导入包的语句

Java
import pkg1[.pkg2…].(classname|*);// 在 Java 中,如果两个类在同一个包(也就是同一个目录)中,那么它们可以直接互相访问,无需使用 import 语句// 如上面的Main.java可以直接访问Helper.java,无需使用import导入// 但是,这个文件夹中的文件夹的文件是不可以直接访问的,仍需导入后才可以
C++
package com.myapp; import lib.mylib.MyLibrary      // 不用写.javaimport com.myapp.util.Helper.*	// 导入该文件夹下的所有类 public class Main {    // ...}
Java
// 导入的区别 // C++#include <../../lib/mylib/sakura.h>// Javaimport lib.mylib.MyLibrary; /*java是直接从类路径开始寻找的,而c++是从当前路径开始寻找的*/

JavaDoc

javadoc 命令是用来生成自己写的 API 的帮助文档的

JDK20 API 文档

API 是 Application Programming Interface(应用程序编程接口)的缩写。它是一组预定义的规则和规范,允许一个软件应用(或组件)与另一个软件应用(或组件)进行交互和通信。

API 可以是函数、类、方法、数据结构等,以及它们的预期行为(例如输入、输出和副作用)。

相当于一个黑盒,只用知道输入,输出即可,中间的过程不需要知道

C++的 STL(Standard Template Library,标准模板库)也被视为一种 API,它是一组 C++模板类。

javadoc 是通过

Java
/* * */

上述的注释生成的,一般放在类或者方法的前面,用于解释它们


Java
/* *@author 作者名 *@version 版本号 *@since 指明需要最早使用的jdk版本 *@param参数名 *@return 返回值情况 *@throws 异常抛出情况 */
Java
package base; /** * @author sakurakugu * @version 1.0 * @since 1.8 */public class Doc {    String name;     /**     * @param name     * @return name     * @throws Exception     */    public String test(String name) throws Exception {        return name;    }}

生成 javaadoc 文档(网页)

Bash
# -d ../base/doc  :用于改变生成路径为../base/doc# -encoding UTF-8 :用于指定源代码文件的字符编码位UTF-8# -charset UTF-8  :用于指定生成的 HTML文档的字符集为UTF-8javadoc -d ../base/doc -encoding UTF-8 -charset UTF-8 Doc.java

流程控制的语句

Scanner 输入

Scanner 类用于获取用户的输入

Java
// 基本语法Scanner s = new Scanner(System.in);
Java
new Scanner(System.in); // 输入这个,然后右键重构->引入变量

通过 Scanner 类的 next() 与 nextLine() 方法获取输入的字符串,在读取前我们一般需要使用 hasNext() 与 hasNextLine() 判断是否还有输入的数据。

Java
Scanner scanner = new Scanner(System.in);String str = scanner.next(); System.out.println("使用next方式接收:");System.out.println(str); System.out.println("使用nextLine方式接收:");System.out.println(str); scanner.close();
Java
Scanner scanner = new Scanner(System.in); scanner.close();

就是 scanf("%s")与 gets/getline 的在读取上面的区别

switch

java 的 switch 语句相比于 c++,支持在 case 中用字符串作为检测值,其原理是通过将字符串转换为哈希值,然后再对比。

Java
switch(name){    case "sakurakugu":        System.out.println("英文");break;    case "樱花莎":        System.out.println("中文");break;    ……}

for

Java
int[] a ={1,2,3};for(int i:a)	System.out.println(i);

for 循环也支持这种遍历,但没有 auto 语句

Java 方法

命名规范:是驼峰命名法,对于方法,还要首字母小写

基础知识

总的来说,所有的方法都是函数,但并非所有的函数都是方法。如果一个函数是某个类或对象的一部分,那么它就是一个方法。

Java
修饰符+c语言的函数的写法  // 修饰符可以没有
Java
// 如这个方法就是有 public static 两个修饰符,其创建的就是一个公共的静态的方法public static void main(String[] args) { }

静态方法属于类,而实例方法属于类的实例

Java
public class MyClass {    public static void myStaticMethod() {        System.out.println("This is a static method.");    }    public void myInstanceMethod() {        System.out.println("This is an instance method.");    }} // 第一个直接用 MyClass.myStaticMethod() 即可调用// 第二个要先创建一个类的实例 new MyClass().myInstanceMethod() 才可调用

java 都是值传递(输入的格式都是值传递的格式),但对于基本数据类型,它是使用值传递。但对于对象、数组等,它是传递了一个指向对象的引用的副本(引用 ≈ 更安全的指针,但牺牲了一些低级别的控制)

重载

重载就是在一个类中,有相同的函数名称,但形参不同的函数。

方法的重载的规则:

  • 方法名称必须相同。
  • 参数列表必须不同(个数不同、或类型不同、参数排列顺序不同等)。方法的返回类型可以相同也可以不相同。
  • 仅仅返回类型不同不足以成为方法的重载。

实现理论:

  • 方法名称相同时,编译器会根据调用方法的参数个数、参数类型等去逐个匹配,以选择对应的方法,如果匹配失败,则编译器报错。(重载的字面意思是重新载入)
Java
public class MyClass {    // 方法重载:同名方法,参数列表不同    public void display(String s) {        System.out.println("One parameter: " + s);    }     public void display(String s, int i) {        System.out.println("Two parameters: " + s + ", " + i);    }}

命令行传参

Java
public class Main {    public static void main(String[] args) {        for (String arg : args) {            System.out.println(arg);        }    }}
Bash
java Main Hello World# java Main:运行Main类。其后面的是参数# 输出:HelloWorld

注:如果有顶级包的话(以 “demo02” 中的路径为例),要在顶级包的路径中运行以下代码才行

Java
package com.sakurakugu.method; public class demo02 { public static void main(String[] args) {     for (String arg : args) {         System.out.println(arg);     } }}
Bash
# 要先切换到以下目录才可以编译java类cd "E:/elric/Programming/study/Java/self_study/JavaSE/Basic grammar/src"# 编译# 注:这里用的是路径,而非包名javac com/sakurakugu/method/demo02.java # 或者直接使用完整路径也可以javac "E:/elric/Programming/study/Java/self_study/JavaSE/Basic grammar/src/com/sakurakugu/method/demo02.java" # 运行# 注:这里用的是包名java com.sakurakugu.method.demo02 Hello World ! # 输出:HelloWorld!

可变参数

可变参数(Varargs)是一种特性,允许你在调用函数时传入任意数量的参数。可以通过在参数类型后面添加三个点(...)来定义一个接受可变参数的函数。

可变参数必须是函数参数列表中的最后一个参数。如果一个函数有多个参数,那么可变参数必须放在最后

C++
public class Main {    public static void main(String[] args) {        printNumbers(1, 2, 3, 4, 5);        printNumbers(10, 20, 30);    }     public static void printNumbers(int... numbers) {        for (int number : numbers) {            System.out.println(number);        }    }    // 如果有多个参数的情况    public static void printMessageAndNumbers(String message, int... numbers) {        System.out.println(message); // 输出message        for (int number : numbers) {            System.out.println(number);        }    }}// 输出:12345102030    a11121314
Java
public static void printMessageAndNumbers(String message, int... numbers) {    System.out.println(message);    for (int number : numbers) {        System.out.println(number);    }}

Java 数组

创建数组

声明数组

Java
数据类型[] 数组名; // 推荐使用这种方法 数据类型 数组名[]; // 这种是类C语法,但其都没有本质区别

使用上面的那种方法是因为在构建函数时,可以将数组本身作为一种数据类型使用,而不用像 C 语言一样使用繁琐的指针

Java
public static int[] reverse(int[] arrays){ int[] result = new int[arrays.length]; for(int i=0,j=result.length-1; i< arrays.length; i++,j--){     result[j] = arrays[i]; } return result;}

创建数组

Java
数据类型[] 数组名 = new 数据类型[数组大小]; // 例:int[] arr2 = new int[5]; // 创建一个长度为5的整数数组int[] arr = {1, 2, 3, 4, 5}; // 创建一个包含指定元素的整数数组

仅仅使用声明的方法是没有数组大小的,是无法那样创建的

Java
// 可以使用 lenght方法来获取数组长度,(这不是C,没有括号,没有size() )for(int i=0;a.lenght;i++) {    a[i]=i;}

所有的对象都要使用 new 创建

像是 String str = "Hello"; 在内部等价于 String str = new String("Hello");。但是,直接使用 new String("Hello") 是不推荐的,因为它会创建不必要的字符串对象。

静态初始化

在程序运行前就能确定数组的大小和数组中的初始数据我们称之为静态初始化。

Java
float[] stuScores = {80.0f,70.0f,90.0f,66.5f};int[] nums = {80,70, 90,66};char[] chs = {‘a’,’b’, ‘c’,’d’};String[] stuNames = new String[]{"张三","李四","王五"}
动态初始化

数组的大小或数组中数据在程序运行时才能决定,且用到new这个关键字来确定数组的大小或初始数据我们称之为动态初始化。 定义数组:变量类型[] 数组名 = new 数据类型 [ 数组长度 ];

Java
int[] scores = new int[10];int[] stu = new int[size];

定义数组与初始化分离:

Java
int[] scores;scores = new int[lenght];

二维数组

数据类型[行][列] 数组名

静态:

Java
int [][] scores={    {80,90,70},    {80,90,70},    {80,90,70},    {80,90,70},}

动态:

Java
// 法一int[][] scores = new int[2][3];// 法二int[][] scores;scores = new int[2][3];// 法三int[][] scores = new int[3][];scores[0] = new int[7];scores[1] = new int[5];
C++
// 行是三行排,之前把列与行的定义搞反了// 反正记住二维数组是先竖着多少个,然后横着多少个public static void main(String[] args) {    int[][] a={        {1,2,3,4},        {3,4,5,5},        {67,3,9,6}    };    for(int i=0;i<3;i++)    {        for(int j=0;j<4;j++)            System.out.print(a[i][j]+" ");        System.out.println();    }}// 输出:1 2 3 43 4 5 567 3 9 6

Java 内存分析

Java内存

  • 堆
    • 存放 new 的对象和数组
    • 可以被所有的线程共享,不会存放别的对象引用
  • 栈
    • 存放基本数据类型与引用(指针)
  • 方法区
    • 可以被所有的线程共享
    • 存放常量,class 类信息,static 静态变量

如下图,array 变量本身(即引用)存储在栈中,而它指向的数组对象(即数据)存储在堆中。这是 Java 中所有对象(包括数组)的通用行为

数组 Arrays 类的其他用法

懒得写了,去看 JDK 文档即可

JDK20 API 文档

面向对象 OOP

Object-Oriented Programming

面向过程 & 面向对象

面向过程思想 (如:打比赛)

  • 步骤清晰简单,第一步做什么,第二步做什么.....
  • 面对过程适合处理一些较为简单的问题(或具体操作方面的问题)

面向对象思想(如:做项目)

  • 物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索。
  • 面向对象适合处理复杂的问题,适合处理需要多人协作的问题!

对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理。

什么是面向对象

**面向对象编程的本质就是:**以类的方式组织代码,以对象的组织(封装)数据。

**核心思想:**抽象

三大特性:

  • 封装:封装成黑盒,只用知道输入输出即可
  • 继承:子类继承父类
  • 多态:如相同名称的方法,可以有不同的输入,并产生不同的输出

从认识论角度考虑是先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象 (从好多人中抽象提取出教师、学生等)

从代码运行角度考虑是先有类后有对象。类是对象的模板。 (从学生这个抽象的概念中创造出学生小明,学生小红)(int 这个抽象的数据结构中创建出变量/容器 a,b,c 等)

方法的定义

Java
/*   方法头: 修饰符 返回值类型 方法名(参数) { 	  // 方法体 	  return 返回值;   }*/

方法的调用

-src	-com		-opp			-demo01				Demo01.java				Student.java
Java
// Student.javapackage com.oop.demo01; public class Student {    // 静态的    public static void say(){        System.out.println("学生说话了啊啊啊");    }    // 非静态的    public void say1(){        System.out.println("学生说话了?");    }}
Java
// 在Demo01中调用Student类的方法package com.oop.demo01; public class Demo01 {    public static void main(String[] args) {        // 可直接调用静态方法        Student.say();         // 对于非静态方法,需要先用new来实例化这个类        Student student = new Student();        student.say1();         student.say(); // √ (通过实例引用)        Student.say(); // √ (静态引用/类引用)        Student.say(); // ×        student.say1();// √    }}

实例化一个类:

实例化一个类就是创建一个类的对象。在 Java 中,通过使用 new 关键字来实例化一个类。

当实例化一个类时,Java 会为该类的对象分配内存,并调用相应的构造函数来初始化对象。这个新创建的对象就是类的一个实例,它具有类定义的所有属性和方法。

例如,对于一个名为 Student 的类,可以这样实例化它:

Java
// 对象类型 对象名 = 对象值Student student = new Student();

在这个例子中,new Student() 创建了一个 Student 类的新实例,并将其引用赋值给 student 变量。现在,你可以使用 student 变量来访问 Student 类的属性和方法。

方法间的调用:

Java
public class Demo01 {     public static void a(){}     public void b(){        a(); // √    }     public static void c(){        a(); // √    }     public void d(){    	b(); // √    }     public static void e(){        b(); // ×    }}

因为 static 静态的是和类一起加载的,而非静态的是在类实例化后才存在的。

因此在未实例化之前,方法 b() 不存在,因此 方法 e() 是无法调用它的,所以不行,而其他的是可以的

值传递与引用传递

C++
// cpp中的void add1(int a){}  // 值传递void add2(int &a){} // 引用传递void add3(int *a){} // 指针传递
Java
// java中的package com.oop.demo01; public class Demo02 {    public static void main(String[] args) {        int a=1;        System.out.println(a);        change(a);        System.out.println(a);         Person person = new Person();        System.out.println(person.name);        change(person);        System.out.println(person.name);    }     public static void change(int a){        a=10;    }     public static void change(Person person){        person.name="Sakurakugu";    }} class Person{    String name;}// 输出:11nullsakurakugu

在 Java 中,所有的方法参数传递都是值传递。这意味着当你将一个变量传递给一个方法时,实际上是传递的该变量的一个副本。

对于基本类型(如 int、double、char 等),这个副本是变量的实际值。对于对象类型(如 String、数组或任何其他对象),这个副本是对象的引用。

当你传递一个 String 给一个方法时,你实际上是传递了一个指向 String 对象的引用的副本。(本质还是值传递)这意味着方法可以使用这个引用来访问和修改 String 对象。然而,由于 String 是不可变的,所以任何对 String 的修改实际上都会创建一个新的 String 对象,而不会影响原始的 String 对象。

所以,虽然在技术上,Java 对 String 的传递是引用的值传递,但由于 String 的不可变性,它在实践中表现得更像是值传递。

类与对象的创建

类与对象的关系:

  • 类是抽象的概念(因此要实例化为对象)
  • 对象是抽象的具体实例
  • 类是对象的模板(如 int a; 中,int 与 a 的关系)

类与对象的创建:

  • 一般根据规范,最好只有一个方法中有 main 方法
    • 像 C++一样,而且是写在单独的一个类(如:Application )中的 (如我之前的 C 语言控制台贪吃蛇游戏一样,这样也方便管理)
  • 使用 new 创建对象时,会分配空间并进行默认的初始化,以及对类中构造器的调用
  • 其他和 C++中结构体/类的使用方法差不多,这里就不再多赘述

学程序的作用也是对世界更好的建模。。。
之前学绘画的时候也是这么说的。。。

构造器

在 Java 中,构造器(Constructor)是一种特殊的方法,用于初始化新创建的对象。构造器的名称必须与类名完全相同,并且它没有返回类型。

当使用 new 关键字创建类的新实例时,Java 会自动调用相应的构造器来初始化新对象。我们可以为类定义多个构造器,以便在创建对象时提供不同的初始化选项。

以下是一个简单的 Student 类,其中包含两个构造器的例子:

Java
public class Student {    String name;    int age;     // 构造器 1:无参数    public Student() {        this.name = "Unknown";        this.age = 0;    }     // 构造器 2:有参数    public Student(String name, int age) {        this.name = name;        this.age = age;    }}

在这个例子中,Student 类有两个构造器。一个是无参数的构造器,它将 name 设置为 "Unknown",将 age 设置为 0。另一个是有参数的构造器,它接受 name 和 age 作为参数,并将它们赋值给 Student 对象的属性。

你可以根据需要选择使用哪个构造器来创建 Student 对象:

Java
Student s1 = new Student();  // 使用无参数构造器Student s2 = new Student("Alice", 20);  // 使用有参数构造器

但如果没有创建任何构造器,则在编译时 Java 会自己加一个空的构造器

Java
// Person.java源文件package com.oop.demo02; public class Penson {} // 反编译得到的Person.class字节码文件package com.oop.demo02; public class Penson {    public Penson() {    }}

如果只创建了一个含参构造器,但又用了无参的构造方法,就必须再创建一个无参的构造器,因为只要有了构造器,Java 在编译时就不会帮你形成一个构造器,所以会报错(这个特点与方法的重载有点像)

Java
// 如上Student的类// 若使用下面两种方法构造,没有任何问题Student student = new Student();Student student1 = new Student("aa",1);// 但如上的构造器1没有创建的话Student student = new Student();// 这个方法就不可以使用,它会被指向到 构造器2 中

一个类中若有多个构造器,这种情况被称为 构造器的重载

  • IDEA 中,按住 Alt+Ins (Insert) 可直接创造一个构造器

总结:

构造器:

  1. 和类名相同

  2. 没有返回值

作用:

  1. new 本质在调用构造方法
  2. 初始化对象的值

注意点:

  1. 定义有参构造之后,如果想使用无参构造,显示的定义一个无参的构造

  2. this.是指向当前类的指针

    在 Java 中,关键字"this"代表当前对象的引用。它可以用于引用当前类中的成员变量、成员方法和构造方法。

    当一个对象调用自身的成员变量或成员方法时,可以使用"this"关键字来指明当前对象。例如,如果一个类中有一个成员变量叫做"age",那么可以使用"this.age"来引用当前对象的"age"变量。

    此外,在构造方法中,"this"关键字可以用来调用当前类的其他构造方法。这种用法称为构造方法的重载。通过使用"this"关键字调用其他构造方法,可以避免代码的冗余。

    Java
    // 指向构造方法时public class Point { int x; int y; Point(){     System.out.println("创建并初始化了⼀个Point对象"); } Point(int x,int y){     this();     this.x=x;     this.y=y; } public double getDistance(){     return Math.sqrt(x*x+y*y); }  public static void main(String[] args) {     Point p = new Point(3, 4);     System.out.println(p.getDistance()); }}

    总之,"this"关键字在 Java 中用于引用当前对象,帮助我们区分成员变量和局部变量,并且可以用于构造方法的重载。

创建对象时内存的状态

** 类与对象的小结 **

  1. 类与对象

    • 类:是一个模板 / 蓝图,抽象的定义
    • 对象:是类的一个具体实例
  2. 方法

    • 定义:类的行为
    • 调用:通过对象或类名执行方法逻辑
  3. 引用

    • 基本类型:8种(如 int、double、char、boolean 等)
    • 引用类型:对象是通过引用来操作的(栈 → 堆)
  4. 属性(字段 / 成员变量)

    默认初始化值:

    类型 默认值
    数字 0 或 0.0
    char \u0000
    boolean false
    引用类型 null

    语法格式:
    修饰符 类型 属性名 = 初始值; // = 初始值 可选

  5. 对象的创建和使用

    创建对象:

    Java
    Person kuangshen = new Person();  // 必须使用 new 关键字 + 构造器

    使用对象:

    Java
    kuangshen.name;      // 访问属性kuangshen.sleep();   // 调用方法
  6. 类的结构

    • 静态属性:成员变量(字段)
    • 动态行为:方法
  7. 面向对象三大特性
    封装、继承、多态

封装

  • 该露的露,该藏的藏

    • 我们程序设计要追求==“高内聚,低耦合”==。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合:仅暴露少量的方法给外部使用。
    • 通过封装,我们可以控制类中的数据访问级别,并且可以修改私有数据,而不会影响到使用这些数据的代码
  • 封装(数据的隐藏)

    • 通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏。
  • 记住这句话就够了:属性私有,get/set

"高内聚,低耦合"是软件设计的一种理想状态,是面向对象设计原则的核心。

"高内聚"是指一个模块或者一个类的功能应该是高度集中的,即该模块或类只做与其功能密切相关的事情。这样可以使模块或类的设计更清晰,也更容易理解和维护。

"低耦合"是指模块或类之间的依赖关系应该尽可能的少。如果模块或类之间的耦合度过高,那么一个模块或类的改变可能会影响到其他的模块或类,这将大大增加维护的难度和风险。

在Student类中,say和say1方法都是关于"学生说话"的功能,这体现了高内聚。而这个类并没有依赖其他的类或模块,体现了低耦合。(self_study/JavaSE/Basic grammar/src/com/oop/demo02/Student.java)

属性私有,get/set

  • 属性私有:就是可以设置一个属性是否可以直接被外面调用,还是不可以。

    • private : 私有

    • public : 公有

  • get/set(getter/setter)

    • 就是设置两个公有的函数来调用私有的属性

    • Java
        public class Student{      // 私有的  不可使用 student.name 来调用      private String name;      // 公有的  可使用 student.id 来调用      public String id;  	// get      public String getName() {          return name;      }  	// set      public void setName(String name) {          this.name = name;      }  }
    • 可以通过 Alt+Ins 一键设置 get/set 函数

  • 为什么要设置为属性为私有,并用 get/set 来改变/访问它呢

    • Java
        // 看这个例子就明白了  public class Student{      private int age;        public int getAge() {          return age;      }        public void setAge(int age) {          if(age>120 || age<0) {              System.out.println("年龄不符合规范,请重写");          }          else {              this.age = age;          }      }  }  /*  使用 student.getAge.sout + Tab  可快速变为 System.out.println(student.getAge());  */
    • 其作用:

      • 提高程序的安全性,保护数据
      • 隐藏代码的实现细节
      • 统一接口(都是 get/set 开头的,方便记忆与使用)
      • 系统可维护增加了

点击某个方法,然后按住Ctrl可以快速查看其源码

继承

继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模

如把“很多人”抽象成“人”,但通过继承,将“人”再次抽象为/分为“中国人”,“美国人”,“男人”,“女人”等等

Java
// 学生 is a 人(“学生”继承“人”)public class Student extends Person{}

extnds的意思是“扩展”。子类是父类的扩展。

Java 中类只有单继承,没有多继承!(C++/Python 中可以多继承)

Java 中的接口可以多继承((类)单继承,(接口)多实现)

C++
// C++中的继承class Base1 {  // Base1的成员定义}; class Base2 {  // Base2的成员定义}; class Derived : public Base1, public Base2 {  // Derived的成员定义};

继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等。

继承关系的俩个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示。

子类和父类之间,从意义上讲应该具有“~ is a ~”的关系。

Java
package com.oop.demo04; public class Person {    public void say(){        System.out.println("我是人");    }}
Java
package com.oop.demo04; public class Student extends Person{    public void say(String s){        System.out.println("我是"+ s);    }}
Java
package com.oop.demo04; public class Application {    public static void main(String[] args) {        Student student = new Student();        student.say();        student.say("sakurakugu");    }}// 输出:我是人我是sakurakugu

在 Java 中,子类可以继承父类的以下内容:

  1. 成员变量:子类可以继承父类的所有成员变量(字段),无论它们是公有的、保护的还是默认的。但是,子类不能直接访问(调用)父类的私有成员变量。
  2. 方法:子类可以继承父类的所有方法,无论它们是公有的、保护的还是默认的。但是,子类不能直接访问父类的私有方法。
  3. 嵌套类:子类可以继承父类的嵌套类(内部类)。

注意,子类不能继承父类的构造器。每个类都有自己的构造器,不能被其他类继承。但是,子类可以通过 super 关键字来调用父类的构造器

按住 Ctrl+H 可以查看其继承树

this 关键字的作用和 super 关键字差不多,都时引用,只是一个是指向自身,一个是指向父类

object 类

在 Java 中,Object类是所有类的父类(老祖宗)。如果在类的声明中没有使用extends关键字明确指定一个父类,那么这个类就会默认继承Object类。

Object类位于java.lang包中,它定义了一些所有 Java 对象都共有的方法,如:

  • equals(Object obj):比较两个对象是否相等。
  • hashCode():返回对象的哈希码值。
  • toString():返回对象的字符串表示。
  • getClass():返回对象的运行时类。

在Student类中,虽然它直接继承的是Person类,但由于所有类都间接继承自Object类,所以Student类也可以使用Object类中定义的方法。

缺省类(默认类)

就是没有加 public、private 或 protected 成员访问符的类,其只能被当前包中的成员访问

super 关键字

在 Java 中,super关键字是一个特殊的引用,它引用的是当前对象的父类。super关键字主要有两种用途:

  1. 调用父类的构造器:在子类的构造器中,可以使用super关键字调用父类的构造器。例如,super()调用父类的无参数构造器,super(param)调用父类的带参数的构造器。
  2. 调用父类的方法或访问父类的成员变量:在子类的方法中,可以使用super关键字调用父类的方法或访问父类的成员变量。

    可以访问父类的公有(public)和保护(protected)成员(包括变量和方法),但不能直接访问父类的私有(private)成员。

    若要在子类中访问父类的私有成员,可以在父类中提供公有的或者保护的访问器方法(getter)和修改器方法(setter)。
Java
package com.oop.demo04; public class Person {     public Person(){        System.out.println("Person无参执行了");    }     protected String name="sakurakugu";     public void print(){        System.out.println("Person");    }}
Java
package com.oop.demo04; public class Student extends Person{     public Student(){        // 默认调用了父类的无参构造器 super();		// 通过super()或this()调用构造器必须放在构造器的代码的第一行,因此两者只能有一个        System.out.println("student无参执行了");     }     private String name="樱花莎";     public void test(String name){        System.out.println(name);        System.out.println(this.name);        System.out.println(super.name);    }     public void fun(){        print();        this.print();        super.print();    }    public void print(){        System.out.println("Student");    }}
Java
package com.oop.demo04; public class Application {    public static void main(String[] args) {        Student student = new Student();        student.test("灵依");        student.fun();    }}// 输出:Person无参执行了	  // 狗肇庆继承student无参执行了灵依				// 属性继承樱花莎sakurakuguStudent          // 方法继承StudentPerson

若子类与父类中有相同的方法,并且没有用super或this关键字来选择,则会使用就近原则来选

方法重写

重写都是指方法的重写,和属性无关

方法重写(Override)是子类提供与父类方法签名完全相同的方法的机制。为什么需要重写:

  1. 父类的功能,子类不一定需要,或者不一定满足时,就需要使用到方法重写。

方法重写的规则:

  1. 方法名、参数列表必须与父类方法完全相同,返回类型也必须相同或是父类方法返回类型的子类型。

  2. 访问修饰符的限制性不能比父类方法更严格。例如,如果父类方法是 public 的,那么子类重写的方法也必须是 public 的。public>Protected>Default>private

  3. 子类方法抛出的异常类型不能比父类方法更广泛。

  4. 还有:静态方法是类的方法,非静态是对象的方法

    Java
    public class A extends B{    public static void test(){        System.out.println("A=>test()");    }     public void test1(){        System.out.println("A=>test()");    }} public class B {    public static void test(){        System.out.println("B=>test()");    }     public void test1(){        System.out.println("B=>test()");    }} public class Application {    //静态的方法和非静态的方法区别很大!    //静态方法:方法的调用只和左边,定义的数据类型有关    //非静态:重写    public static void main(String[] args) {        A a =new A();        B b =new A();        a.test();        b.test();        a.test();        b.test();     }}// 输出:A=>test()B=>test()A=>test()A=>test()

可以通过 Alt+Ins 一键重写

多态

多态是指一个引用变量(如父类的引用)可以指向不同类型的对象(如子类的对象)。具体表现为“一种接口,多种实现”。

Java
package com.oop.demo05; public class Person {    public void run(){        System.out.println("run");    }}
Java
package com.oop.demo05; public class Student extends Person{     public void eat(){        System.out.println("eat");    }}
Java
package com.oop.demo05; public class Application {    public static void main(String[] args) {         // Student 能调用的方法都是自己的或者继承父类的!        Student s1 = new Student();        // 父类的引用指向子类的对象 (因为:学生可以是人)        // Person 父类型,可以指向子类,但是不能调用子类独有的方法        Person s2 = new Student();        // 同上理        Object s3 = new Student();        // 但是像下面 强制将猫转换成狗 就不可以了        String s4 =new Student();  // ×         // 这里就是继承的特性了,因为子类学生是继承于父类的,因此,当子类中找不到run()时,或没重写父方法时,往上找。        s2.run();         // 对象能执行哪些方法,主要看对象左边的类型,和右边关系不大        // 编译看左,运行看右        s2.eat();  // ×        ((Student)s2).eat();  // √ 只要强制类型转换就可以了    }}

在 Java 中,多态的实现主要依赖于两个机制:继承(inheritance)和接口(interface)。通过继承,我们可以创建具有共同属性和方法的类;通过接口,我们可以定义一组方法,然后由不同的类来实现这些方法。

多态存在的条件

  • 有继承关系
  • 子类重写父类方法
    • 无法重写的:
      • static 方法:属于类,它不属于实例
      • final:常量
      • private 方法:私有的方法
  • 父类引用指向子类对象

注:多态是方法的多态,属性没有多态性

instanceof 关键字

用于检查一个对象是否是一个类的实例,或者是其子类或实现的接口的实例

强制类型转换

如上,当父类的引用指向子类的对象时,

可以将其对象进行强制转换

可以

  • 把子类转换为父类,向上转型;(这是隐式的,⽆需进⾏特殊 处理。)
    • 但可能会丢失一些自己本来的方法
  • 把父类转换为子类,向下转型;(这是显式的,需要进⾏强 制类型转换。)

老子有的,儿子都有;儿子有的,老子不一定有;老子要有儿子的,必须强制转换。

作用:方便方法的调用,减少重复的代码

static 关键字

静态方法与属性在此就不细写了

静态代码块

Java
package com.oop.demo06; public class Student {    // 输出以查看运行顺序     // 匿名代码块可以用于赋予初始值    {        System.out.println("匿名代码块");    }     static {        System.out.println("静态代码块");    }     public Student(){        System.out.println("构造方法");    }     public static void main(String[] args) {        Student student = new Student();        System.out.println("============");        Student student1 = new Student();    }} // 输出:静态代码块匿名代码块构造方法============匿名代码块构造方法

可以发现,静态代码块只执行一次

静态导入包

静态导入包(static import)和直接导入包(regular import)是 Java 中导入包的两种方式,它们之间有一些区别:

  1. 直接导入包:使用 import 关键字导入包时,需要在代码中指定具体类的路径或者整个包的路径。例如:import java.util.List; 或者 import java.util.*;。在代码中使用被导入的类时,需要加上类名或者使用完整的类路径。
  2. 静态导入包:使用 import static 关键字可以导入静态成员(如静态方法、静态变量)而不是整个类。这样在代码中就可以直接使用被导入的静态成员,而不需要使用类名来限定。例如:import static java.lang.Math.*;,这样就可以直接在代码中使用 Math 类中的静态方法,而不需要写 Math.xxx。

总的来说,静态导入包用于简化代码,使得可以直接使用静态成员而不需要限定类名,但是过度使用静态导入可能会使代码难以理解,所以应该适度使用。

Java
package com.oop.demo06;// 静态导入包,如果不加static,则不能直接导入包中的静态成员import static java.lang.Math.random;import static java.lang.Math.PI; public class Test {    public static void main(String[] args) {        System.out.println(random());        System.out.println(PI);    }}// 输出:0.388065667556167453.141592653589793


在 Java 中,static是一个关键字,它表示一个成员(变量、方法、内部类)属于类本身,而不是类的实例。这意味着,无论我们创建了多少个类的实例,静态成员都只有一份。

以下是一些关于static关键字的重要点:

  1. 静态变量:静态变量是类的所有对象共享的变量。无论我们创建了类的多少个对象,它们都会共享同一个静态变量。
Java
class MyClass {    static int sharedVar = 10;}
  1. 静态方法:静态方法是属于类的,而不是类的实例。它们可以在没有创建类的对象的情况下被调用。静态方法不能访问类的非静态变量。
Java
class MyClass {    static void myMethod() {        System.out.println("This is a static method.");    }}

调用静态方法:

Java
MyClass.myMethod();
  1. 静态块:静态块用于初始化类的静态变量。这个块在类被加载到内存时执行一次。
Java
class MyClass {    static {        // 静态块    }}
  1. 静态类:在 Java 中,我们只能创建静态内部类,不能创建静态顶级类。静态内部类与非静态内部类的主要区别在于,静态内部类不需要外部类的引用,但非静态内部类需要。
Java
class OuterClass {    static class StaticNestedClass {        // ...    }}

final 关键字

在 Java 中,final是一个关键字,它可以用于修饰类、方法和变量。以下是final关键字的主要用途:

  1. final 变量:当变量被声明为 final,其值就不能被修改,换句话说,这就成了一个常量。
Java
final int MAX_VALUE = 10;

Java 中,const是一个保留关键字,但不能使用

如果要创建一个常量,需要使用final关键字

  1. final 方法:当方法被声明为 final,它就不能被子类覆盖(重写)。但是,子类可以继承 final 方法。
Java
class MyClass {    final void myMethod() {        //...    }}
  1. final 类:当一个类被声明为 final,它不能被继承。换句话说,final 类是最终类,没有任何子类。(断子绝孙)
Java
final class MyFinalClass {    //...}

final关键字主要用于实现不可变性和防止继承、重写。

抽象类

在 Java 中,抽象类是一种不能被实例化的类(因此不能使用new关键字来创建对象),只能被继承。抽象类用于表示一种抽象概念,它可能包含一些抽象方法(没有实现的方法)(可类比为 C 语言中的只有函数声明)(只有抽象类才可以包括抽象方法)和非抽象方法(已经实现的方法)。抽象类是用abstract关键字声明的。

抽象类使用abstract关键字来声明。抽象方法也使用abstract关键字声明,但是它们没有方法体。

Java
// 一个抽象类的例子abstract class Animal { //可以加pubilc等关键字的,    abstract void makeSound(); // 这里也同理,只是懒得加     void eat() {        System.out.println("The animal eats");    }}

在这个例子中,Animal是一个抽象类,它有一个抽象方法makeSound()和一个非抽象方法eat()。子类必须提供makeSound()方法的实现,否则它们也必须被声明为抽象类。

抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的。

当子类继承抽象类时,那么就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类。

Java
class Dog extends Animal {    void makeSound() {        System.out.println("The dog barks");    }}

在这个例子中,Dog类继承了Animal类,并提供了makeSound()方法的实现。现在,我们可以创建Dog类的对象了。

Java
Dog dog = new Dog(); // 创建Dog类的实例dog.makeSound(); // 调用实例的makeSound方法

抽象类也可以存在构造器或抽象构造器

抽象类的作用就相当于目录/大纲了

接口

普通类:只有具体实现
抽象类:具体实现和规范(抽象方法)都有!
接口:只有规范!

自己无法写方法~专业的约束!约束和实现分离:面向接口编程~

  • 接口定义了一个类所需要遵循的协议或契约,其他类可以通过实现这个接口来实现特定的行为。

  • 接口是面向对象的精髓,是对对象的抽象,最能体现这一点的就是接口。

  • 接口不是类,其中没有构造方法,因此不能实例化。

  • 一个类可以实现一个或多个接口,从而继承接口中的抽象方法。

在接口中可以包含以下内容:

  1. 常量:只能是 public、static 和 final 类型的域。
  2. 方法声明:只有方法的声明,没有方法体。默认是 public 和 abstract 类型的方法。
  3. 默认方法/静态方法:Java 8 开始的。default 方法 和 static 方法

示例 1:

Java
public interface Animal {    void eat();    void sleep();    // 默认方法    default void breathe() {        System.out.println("Breathing");    }}

breathe是一个默认方法,它在接口中有一个默认的实现。实现Animal接口的类可以选择覆盖这个方法,也可以选择不覆盖。如果不覆盖,那么就会使用接口中的默认实现。

然后,一个类可以实现这个接口,如下所示:

Java
public class Dog implements Animal {    @Override    public void eat() {        System.out.println("Dog is eating");    }     @Override    public void sleep() {        System.out.println("Dog is sleeping");    }     // 不覆盖breathe方法,使用接口中的默认实现}

示例 2:

一般一个接口的实现类的取名是 接口名+Impl

Java
package com.oop.demo08; public interface UserService {    // 都是常量;(public static final int AGE = 99;)    int AGE=99;    // 接口中的所有定义的方法其实都是抽象的 public abstract    void add(String name);    void delete(String name);    void update(String name);    void query(String name);}
Java
package com.oop.demo08; public interface TimeService {    void timer();}
Java
package com.oop.demo08; // 侧面实现了多继承public class UserServicelmpl implements UserService,TimeService{    @Override    public void add(String name) {}    @Override    public void delete(String name) {}    @Override    public void update(String name) {}    @Override    public void query(String name) {}    @Override    public void timer() {}}

接口的作用:

  1. 约束(实现多态性,允许不同的类实现相同的接口并提供不同的实现)
  2. 定义一些方法,规范了某种行为或功能,让不同的人实现~
  3. 实现解耦,降低代码之间的耦合度。

@Override

@Override 是 Java 中的一个注解,它告诉编译器以下的方法必须重写父类或者实现接口中的方法。如果没有在父类或接口中找到要重写的方法,编译器会报错。

这个注解不是必须的,但是使用它有两个主要的好处:

  1. 提高代码的可读性:@Override注解可以让其他开发者知道这个方法是重写了父类或接口的方法。

  2. 编译时检查:如果你打算重写父类或接口的方法,但是由于某些原因(例如方法签名的错误)并没有真正重写,那么编译器会给出错误提示。

例如:

Java
public class Dog implements Animal {    @Override    public void eat() {        System.out.println("Dog is eating");    }     @Override    public void sleep() {        System.out.println("Dog is sleeping");    }}

在这个例子中,Dog类实现了Animal接口,并且重写了eat和sleep方法,@Override注解就是用来标识这两个方法是重写了接口的方法。

N 种内部类

内部类就是在一个类的内部在定义一个类,比如,A 类中定义一个 B 类,那么 B 类相对 A 类来说就称为内部类,而 A 类相对 B 类来说就是外部类了。

根据内部类的位置和特性,分为四种类型:成员内部类、静态内部类、局部内部类和匿名内部类

Java
// 外部类package com.oop.demo09; public class Outer {    private int id=10;    public void out(){        System.out.println("这是外部类的方法");    }    // 成员内部类    // 加一个 static 就是静态内部类    public class Inner{        public void in(){            System.out.println("这是内部类的方法");        }        // 内部类中可以访问外部类中的私有属性        public int getID(){            return id;        }    }     public void method(){    // 局部内部类    class Inner{         }    }     public static void main(String[] args) {        // 匿名内部类		new Apple().eat();	}} // 一个Java类文件中,可以有多个class类,但只能有一个public class文件// 一般可用于临时的调试等等class Apple{    public void eat(){        System.out.println("救命呀,苹果吃人了!!!~");    }}
  1. 成员内部类:定义在类的成员位置,和成员变量、成员方法的位置是一样的。它可以无条件访问外部类的所有成员,但是外部类想要访问内部类的成员,必须要创建内部类的对象。

    Java
    public static void main(String[] args) {    // 先实例化外部类    Outer outer = new Outer();    // 通过外部类来实例化内部类    Outer.Inner inner = outer.new Inner();    inner.in();    System.out.println(inner.getID());}
  2. 静态内部类:定义在类的成员位置,但是它是用 static 修饰的。它的访问特性和静态成员方法是一样的,只能访问外部类的静态成员,不能访问非静态的成员。

  3. 局部内部类:定义在方法内、代码块内或者构造器内的类,它只能在定义这个内部类的方法内实例化并调用其方法,出了这个方法就无法访问。

  4. 匿名内部类:是局部内部类的一种,它没有名字,同时只能在定义的地方一次性的使用。

异常机制

什么是异常

在 Java 编程语言中,异常是在程序执行期间发生的问题,它会打断正常的指令流。异常是由 Java 虚拟机(JVM)或者 Java API 抛出的,也可以由用户程序抛出。

就是那些标红呀,命令行中的报错呀等等

Exception:例外、异常

Java 异常分为两大类:

  • 检查型异常(Checked Exceptions)

    • 检查型异常是那些必须在编译时处理的异常,否则编译器会报错。

      如:语法错误,打开不存在文件等等

  • 运行时异常(Runtime Exceptions)。

    • 运行时异常则是那些在程序运行时可能出现的异常,编译器不会强制要求处理这些异常。

      如:下标越界、访问空指针、算术溢出、丢失资源等等

  • 错误ERROR类:(不是异常Exception类)

    • Error类表示的是严重的问题,通常是程序无法自行处理的,如内存不足等,因此 JVM 一般会选择终止进程。在技术上也是很难处理的

      如:内存不足/内存溢出、堆栈溢出、类定义错误、未知错误等

异常处理是 Java 中的一个重要概念,它允许程序员在发生错误时采取适当的行动,而不是让程序崩溃。Java 提供了 try-catch-finally 语句块来捕获和处理异常。

异常体系结构

Java 把异常当作对象来处理,并定义一个基类 java.lang.Throwable 作为所有异常的超类。在 Java API 中已经定义了许多异常类,这些异常类分为两大类,错误 Error 和异常 Exception。


Java 的异常体系结构主要由Throwable类及其子类构成。

  • Throwable:这是所有错误和异常的超类。它有两个主要的子类
    • Error:这是当 Java 虚拟机(JVM)遇到严重问题时抛出的。例如,OutOfMemoryError和StackOverflowError。这些问题通常是严重的,程序无法恢复,因此,应用程序通常不会尝试捕获这些错误。
    • Exception:这是应用程序可能希望捕获并处理的问题,或者可能希望通知用户存在的问题。
      • 检查型异常(Checked Exceptions)
      • 运行时异常(Runtime Exceptions)

通常来说,Exception是编写、测试时,大多都可以解决的。Error是难以在少量测试数据中发现并解决的

这就是 Java 的异常体系结构。在编写代码时,我们通常只需要关注和处理Exception类及其子类。

Java 异常处理机制

分为:

  • 抛出异常
  • 捕获异常

Java 的异常处理机制是一种用于处理运行时错误的强大机制。它使用了五个关键字:try、catch、finally、throw 和 throws。

  1. try:try 块用于封装可能会抛出异常的代码。如果在 try 块中的代码抛出了异常,那么这个异常会被传递给与它匹配的 catch 块。

    Java
    try {    // 可能会抛出异常的代码}
  2. catch:catch 块用于处理 try 块中抛出的异常。每个 try 块可以跟随一个或多个 catch 块。

    Java
    catch (ExceptionType1 ex) {    // 处理ExceptionType1的代码}catch (ExceptionType2 ex) {    // 处理ExceptionType2的代码}
  3. finally:无论是否发生异常,finally 块中的代码都会被执行。通常,finally 块用于清理代码,如关闭文件或数据库连接。

    Java
    finally {    // 清理代码}
  4. throw:我们可以使用 throw 关键字手动抛出一个异常,并结束程序。

    Java
    throw new ExceptionType("异常信息");
  5. throws:如果一个方法可能会抛出异常,但并不处理它,我们可以在方法签名中使用 throws 关键字声明它可能会抛出的异常。

    Java
    public void myMethod() throws ExceptionType1, ExceptionType2 {    // 可能会抛出ExceptionType1或ExceptionType2的代码}

    如果一个方法可能会抛出一个可检查异常(即,这个异常是 Exception 的子类,但不是 RuntimeException 的子类)(或者说叫自定义异常),那么你必须在方法签名中使用 throws 关键字声明这个异常。如果你没有这样做,那么编译器就会给出一个错误。

处理异常

快捷键:Ctrl+Alt+T 用于将代码用语句包含起来

Java
package com.exception; public class Demo01 {    public static void main(String[] args) {        int a=1;        int b=0;        try{ //try块中是监控区域            System.out.println(a/b);        }        catch(ArithmeticException e){ // 捕获异常            System.out.println("程序出现异常,变量b不能为0");            System.out.println(e.getMessage());            // 程序不会结束,会继续运行,若有finally,则先运行它             // 这里面也可以加一下处理异常的代码,如b++;等        }        catch(Throwable e){ // 捕获异常            System.out.println("捕获到异常");        }        // 可以没有finally        finally { // 处理善后工作,如关闭IO流,资源等            System.out.println("finally");        }        System.out.println("==========");        new Demo01().test(1,0);    }     public void test(int a,int b){        if(b==0){            // 主动抛出异常,并结束程序            throw new ArithmeticException();        }        System.out.println(a/b);    }     // 在方法中抛出异常,如果出现以下两种异常之一,就抛出异常    public void test2(int a,int b) throws ArithmeticException,ClassCastException{        System.out.println(a/b);    }}// 输出:程序出现异常,变量b不能为0/ by zerofinally==========Exception in thread "main" java.lang.ArithmeticException: / by zero	at com.exception.Demo01.test2(Demo01.java:34)	at com.exception.Demo01.main(Demo01.java:23)

自定义异常

在 Java 中,可以通过继承 Exception 类(用于可检查异常)或 RuntimeException 类(用于运行时异常)来自定义异常。以下是一个自定义异常的基本步骤:

  1. 创建一个新的类,这个类应该继承自 Exception 或 RuntimeException。
  2. 提供一个无参数的构造函数,这个构造函数应该调用父类的无参数构造函数。
  3. 提供一个接受 String 参数的构造函数,这个构造函数应该调用父类的接受 String 参数的构造函数,这样你就可以提供一个关于这个异常的详细信息。

以下是一个自定义异常的例子:

Java
public class MyException extends Exception {    public MyException() {        super();    }     public MyException(String message) {        super(message);    }}

在这个例子中,MyException 是一个自定义的可检查异常。你可以像使用其他异常一样使用这个异常,例如,你可以使用 throw 关键字抛出这个异常,也可以在 catch 块中捕获这个异常。

例:

Java
kage com.exception; public class Test {    public static void main(String[] args) throws MyException {        test(1);    }     static void test(int a) throws MyException {        System.out.println("传递的参数为:"+a);        if(a>10){            throw new MyException(a);        }        else{            System.out.println("OK");        }    }}// 输出:传递的参数为:1OK
Java
//try块与上面自定义异常,优先输出自定义的异常try {    test(1);} catch (MyException e) {    System.out.println("MyException=>"+e);}// 输出:传递的参数为:1OK

实际应用中的总结

  • 处理运行时异常时,采用逻辑去合理规避同时辅助 try—catch 处理
  • 在多重 catch 块后面,可以加一个 catch(Exception)来处理可能会被遗的异常
  • 对于不确定的代码,也可以加上 try—catch,处理潜在的异常
  • 尽量去处理异常,切忌只是简单地调用 printStackTrace()去打印输出
  • 具体如何处理异常,要根据不同的业务需求和异常类型去决定
  • 尽量添加 finally 语句块去释放占用的资源

GUI 编程

网络编程

JavaWeb:网页编程(B/S 架构(浏览器)) ≠ 网络编程(TCP/IP)(C/S 架构(电脑))
核心目的:数据交换、相互通信

其核心概念包括:

  • 网络协议(定义规则)

    • TCP

      打电话:连接-接通-童话

    • UDP

      发短信 发送就完事了-接受

    • HTTP、FTP 等

  • 套接字(Socket)(建立连接)

  • 客户端-服务器模型(进行通信)

  • 异步编程(提高效率)

1.2 网络通信的要素

如何实现网络通信:

  • 通信双方地址
    • IP+端口号
  • 通信协议
    • TCP/IP
  • 万物皆对象

1.3 IP

Java 文档

  • IP 地址的分类:

    • IPV4/IPV6
    • 公网(互联网) - 私网(局域网)
  • 域名:记忆 IP 的问题

1.4 端口

端口表示计算机上的一个程序的进程

  • 不同的进程有不同的端口号,用来区分软件(不同进程的 PID 可以在任务管理器中查看)

  • 规定端口分为: 0~65535 个(每种连接协议)

  • 端口分类:

    • 公用端口 0~1023
      • FTP : 21
      • SSH :22
      • HTTP : 80
      • HTTPS: 443
    • 程序注册窗口:1024~49151
      • MySQL :3306
      • 远程桌面 :3389
      • 开发测试:8080
    • 动态、私有端口:49152~65535
    PowerShell
        netstat -ano # 查看所有端口    netstat -ans|findstr "5900" # 查看指定的端口

1.5 通信协议

协议:约定,就好比我们箱子说的是普通话
网络通信协议: 速率,传输码率,代码结构,传输控制

作用:为了将复杂的通讯简化,更安全可靠的进行通信交换

TCP/IP 协议簇
包含:

  • 互联网协议(IP)

    IP 协议负责定义计算机在网络上的地址系统,即 IP 地址。它提供了数据报文的基本传送服务,通过路由选择算法将数据包从源地址传送到目的地址。

  • 传输控制协议(TCP)

    TCP 协议建立在 IP 协议之上。它负责将数据分割成小的数据块,并确保这些数据块按顺序到达目的地。TCP 还处理数据重传、流量控制和拥塞控制等问题,以保证可靠的数据传输。

    • 可靠、稳定
    • 三次握手,四次挥手
    • 分为客户端、服务端
    • 传输完成,释放连接,效率低
  • 辅助协议

    • 用户数据报协议(UDP)

      与 TCP 类似,UDP 也是运行在 IP 协议上的传输层协议,UDP 适用于需要快速传输但允许少量数据丢失的应用,如视频流和音频传输。

      • 无连接的、不可靠
      • 客户端,服务端:没有明确的界限
      • DDOS 攻击
  • 应用层协议

    • HTTP
    • FTP
    • SMTP

三次握手

最少需要三次,以保证稳定连接客户端:我要连接。我叫A服务端:同意连接。  确认一下,我叫B,你叫A客户端:我也同意。再确认一下,我叫A,你叫B...开始连接...

四次挥手

客户端:我想断开,我叫A服务端:同意。确认一下,你叫A,我叫B服务器:我想断开,我又叫BB客户端:同意。确认一下,你叫B,又叫BB

1.6 TCP

简单通信

Java
package com.network.lesson01; import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import java.net.ServerSocket;import java.net.Socket; // 服务端public class TcpServer {    public static void main(String[] args) {        ServerSocket serverSocket = null;        Socket accept = null;        InputStream is = null;        ByteArrayOutputStream baos = null;        // 这两者都是try中为核心代码        try{            // 1. 我要先有个地址            serverSocket = new ServerSocket(9999);            // 2. 等待客户端链接过来            accept = serverSocket.accept();            // 3. 读取客户端的消息            is = accept.getInputStream();            // 管道流            baos = new ByteArrayOutputStream();            byte[] buffer = new byte[1024];            int len;            while ((len = is.read(buffer)) != -1) {                baos.write(buffer,0,len);            }            System.out.println(baos.toString());          } catch (IOException e){            e.printStackTrace();        } finally {            // 关闭资源            if(baos!=null) {                try {                    baos.close();                } catch (IOException e) {                    e.printStackTrace();                }            }            if(is!=null) {                try {                    is.close();                } catch (IOException e) {                    e.printStackTrace();                }            }            if(accept!=null) {                try {                    accept.close();                } catch (IOException e) {                    e.printStackTrace();                }            }            if(serverSocket!=null) {                try {                    serverSocket.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }    }}
Java
package com.network.lesson01; import java.io.IOException;import java.io.OutputStream;import java.net.InetAddress;import java.net.Socket;import java.nio.charset.StandardCharsets; // 客户端public class TcpClient {    public static void main(String[] args) {        Socket socket = null;        OutputStream os =null;        try {            // 1. 要知道服务器的地址,端口号            InetAddress serverIP = InetAddress.getByName("127.0.0.1");            int port = 9999;            // 2. 创建一个socket连接            socket = new Socket(serverIP,port);            // 3. 发送消息            os =socket.getOutputStream();            os.write("你好,我是sakurakugu".getBytes(StandardCharsets.UTF_8));         } catch (Exception e) {            e.printStackTrace();        } finally {            if(os!=null) {                try {                    os.close();                } catch (IOException e){                    e.printStackTrace();                }            }            if(socket!=null){                try{                    socket.close();                } catch (IOException e){                    e.printStackTrace();                }            }        }    }}

文件上传

评论区

分享你的想法,与大家交流讨论

Twikoo 加载失败

Twikoo 脚本加载失败