狂神学 Java 零基础学习笔记

特别感谢狂神在Java全栈之路上的陪伴
视频地址:https://www.bilibili.com/video/BV12J41137hu?p=1

Java 安装

HelloWorld

安装好 JDK 11 版本后(经百度2020年左右各个大厂开始使用 JDK 11,而不是像以前一样时 JDK 8)

在 Notepad++ 里写如下代码,并保存为 helloworld.java,然后就是充满仪式性的 helloworld

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

保存后在该文件夹目录下启动 cmd 命令行(直接在文件路径前敲 cmd 即可,或者在命令行里 cd 到文件位置)

请添加图片描述

Java 运行机制

编译和解释混合型语言

在这里插入图片描述

IDEA安装

在这里插入图片描述

在这里插入图片描述

IDEA快捷键

在这里插入图片描述

新建项目后我们再一次完成 Hello World 的代码,这次我们使用快捷键 也就是每个关键字的首字母 psvm 和 sout 就代表的就是下面的一整块代码

public static void main(String[] args){
    
    } // psvm
System.out.print("Hello,World!");  // sout

Java 基础语法

1.注释

注释不仅是给别人看的,注释其实就是代码的一个笔记,方便自己和他人阅读

public class HelloWorld {
    
    
    public static void main(String[] args) {
    
    
        // 单行注释
        // 输出一个HelloWorld
        System.out.println("HelloWorld");
        /*
        多行注释
         */
        // JavaDoc:文档注释  /**  */
        /**
         * @description HelloWorld
         * @author Marine
         */
    }
}

其中文档注释每个 @都有其特定的含义,一般大公司标准化作业时需要在开头写上

2.标识符

也就是保留字,取名时需要区别开,标识符是大小写敏感的

所有标识符都应该以字母(A-Z 或者 a-z),美元符( ) , 或 者 下 划 线 ( ) 开 始 。 首 字 母 之 后 可 以 是 字 母 ( A − Z 或 者 a − z ) , 美 元 符 ( ),或者下划线()开始。首字母之后可以是字母(A-Z 或者 a-z),美元符( )线()(AZaz)(),下划线()或者数字的任意字符组合,但不能使用关键字作为变量名或者方法名。

3.数据类型

Java是一种强类型语言:要求变量的使用要求严格符合规定,所以变量都必须先定义后才能使用(强类型优点安全性高,相对而言速度慢一点)

public class Demo02{
    
    
    public static void main(String[] args){
    
    
        String a = "hello";
        int num = 10;
        System.out.println(a);
        System.out.println(num);
    }
}

八大数据类型

public class Demo03{
    
    
	public static void main(String[] args){
    
    
	// 八大基本数据类型
	
	// 整数
	int num1 = 10;
	byte num2 = 20;
	short num3 = 30;
	long num4 =40L;
	
	// 小数:浮点数
	float num5 = 50.1F; // float类型要在数字后面加个F
	double num6 = 3.141592653;

	// 字符类型
	char name = 'a'; // 字符指的是一个字母
	// 字符串,String 不是关键字,是一个类
	String namea = "秦疆";
	
	// 布尔型:是非
	boolean flag = true;
	// boolean flag = false;
	}
}

4.数据类型拓展

public class Demo04{
    
    
	public static void main(String[] args){
    
    
	// 整数拓展 进制 二进制以0b开头 八进制以0开头 十进制 十六进制以0x开头
	int num1 = 10; // 十进制10
	int num2 = 010; // 八进制的10 也就是十进制的8
	int num3 = 0x10; // 十六进制的10 也就是十进制的16
	System.out.println(num1); // 10
	System.out.println(num2); // 8
	System.out.println(num3); // 16

	// ======================================================
	// 浮点数拓展
	// 金融公司用 float 和 double 计算钱会有问题
	float f = 0.1f; // 0.1
	double d = 1.0 / 10; // 0.1
	System.out.println(f==d); // false

	// float 表现的字长是有限、离散的 存在舍入误差,只能提供接近但不等于的值
	// 最好避免使用浮点数进行比较
	float f1 = 1212121212121212f;
	float f2 = f1 + 1;
	System.out.println(f1==f2); // false

	// 有一个 BigDecimal 数字工具类进行表示
	// ======================================================
	// 字符拓展 所有的字符本质都是数字 编码问题 Unicode 对字母汉字进行编码
	char c1 = ‘a’;
	char c2 = '中';
	System.out.println(c1);
	System.out.println(int(c1)); // 强制转换数据类型到int
	System.out.println(c1);
	System.out.println(int(c1));

	// 转义字符 \t 制表符  \n 换行
	System.out.println(Hello\tWorld!\t”);
	}
}

类型转换
由于Java是强类型语言,所以要进行有些运算的时候需要用到类型转换

低  ---------------------------------------->  高
byte,short,char -> int -> long -> float -> double

运算中,不同类型的数据先转化为同一类型,然后进行运算(因为容量不同,表示范围不同)

public class Demo04{
    
    
   public static void main(String[] args){
    
    
   int i = 128;
   byte b = (byte)i; // 内存溢出 b=-128 因为byte 范围 -128~127
   // 强制转换  高-->低 (类型)变量名  
   // 自动转换  低-->高
   /*
   注意点:
   1.不能对布尔值进行转换
   2.不能把对象类型转换为不相干的类型
   3.把高容量转换到低容量的时候,强制转换
   4.转换的时候可能存在内存溢出,或者精度问题
   */
   System.out.println(===================);
   System.out.println((int)23.7); // 23  将double的23.7转换为int 
   System.out.println((int)-45.89f); // -45  将float的-45.89转换为int

5.变量

Java变量是一种强类型语言,每个变量都必须声明其类型,Java变量是程序中最基本的存储单元,其要素包括变量名,变量类型和作用域,其实质是一个内存地址名

	type varName [=value] [{
    
    , varName[=value]}];
//数据类型 变量名   =	值; 
//可以用逗号隔开来声明多个类型变量,但是不推荐这样做保证程序可读性

注意变量声明是一个完整的语句,因此必须以分号结束;

6.常量

常量在初始化后不能再改变!不会变动的值。

常量可以理解为一种特殊的变量,它的值被设定后,在程序运行中不允许被改变

final 常量名=值;
final double PI=3.14//常量名一般使用大写字母表示
 
public class Demo09{
    
    
    //修饰符不区分前后位置,其中static和final都是修饰符,使用两种输出结果相同
    static final double PI = 3.14;
    // final static double PI = 3.14;
    public static void main(String[] args){
    
    
        System.out.println(PI);
    }
}

变量的命名规范

  • 所有变量、方法、类名:见名知意
  • 类成员变量、局部变量、方法名:首字母小写和驼峰原则(除了第一个单词外,后面单词的首字母大写lastName)
  • 类名:首字母大写和驼峰原则
  • 常量:大写字母和下划线MAX_NUM

7.运算符

  • 算术运算符:+ - * / % ++ –
  • 赋值运算符:=
  • 关系运算符:>、<、>=、<=、==、!=、instanceof
  • 逻辑运算符:&& 、||、!
  • 位运算符:&、|、^、~、>>、<<、>>>
  • 条件运算符:?:
  • 拓展赋值运算符:+=、-=、*=、/=
package baseDemo;

public class demo02 {
    
    
    public static void main(String[] args) {
    
    
        // 一元运算符:自增 ++ 自减 --
        int a = 3;
        int b = a++; // a++ 是先赋值后自增,所以此时b=3,语句结束后a=4
        System.out.println(a); // 4
        int c = ++a; // 推荐使用++a 是先自增后赋值,所以赋值前a=5, c=5

        System.out.println(a); // 5
        System.out.println(b); // 3
        System.out.println(c); // 5

        // 幂运算,等需要使用 Math 工具类进行运算
        double power = Math.pow(2, 3);
        System.out.println(pow);
    }
}
package baseDemo;

public class demo03 {
    
    
    public static void main(String[] args) {
    
    
        // 逻辑运算:与或非
        boolean a = true;
        boolean b = false;

        System.out.println("a && b: " + (a && b)); // 两个都为真才为真
        System.out.println("a || b: " + (a || b)); // 两个为假才为假
        System.out.println("!(a && b): " + (!(a && b)));
    }
}

/*
a && b: false
a || b: true
!(a && b): true
Process finished with exit code 0
*/

包机制——类似文件夹

为了更好地组织类,Java提供了包机制,用于区别类名的命名空间
包语句的语法格式为:

package com.kuangstudy.www // 用 . 隔开各个层级文件夹

一般用公司域名倒置作为包名;有时候为了使用某一个包的成员,我们需要在Java程序中明确导入该包。使用 import 语句即可。

import com.kuangstudy.* //  .* 是通配符表示该层级下所有文件

JavaDoc

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

  • @author 作者名
  • @version 版本号
  • @since 指明需要最早使用的JDK版本
  • @param 参数名
  • @return 返回值情况
  • @throws 异常抛出情况

Java 流程控制

1.Scanner对象

java.util.Scanner 是 Java5 的新特征,我们可以通过 Scanner 类来获取用户的输入。

Scanner s = new Scanner(System.in);

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

package scanner;

import java.util.Scanner;

public class Demo01 {
    
    
    public static void main(String[] args) {
    
    
        // 创建一个扫描对象,用于接受键盘数据
        Scanner scanner = new Scanner(System.in);
        System.out.println("使用next方式接受:");

        // 判断用户有没有输入字符串
        if (scanner.hasNext()){
    
    
            // 使用next方式接受
            String str = scanner.next();
            System.out.println("输出的内容为:" + str );
        }

        // 关闭 Scanner ,解除 IO 流资源占用
        scanner.close();
    }
}

// 输出界面如下
使用next方式接受:
hello world		
输出的内容为:hello		
// 由于使用的是 scanner.next() 一次读入一个单词以空格划分,若使用 scanner.nextLine() 则是 hello world

Process finished with exit code 0

next()方法:

  1. 一定要读取到有效字符才可以结束输入
  2. 对输入有效字符之前遇到的空白,next()方法会自动将其去掉
  3. 只有输入有效字符后才将其后面的空白作为分隔符或者结束符
  4. next()不能得到带有空格的字符串

nextLine():

  1. 以 Enter 为结束符,也就是说 nextLine() 方法返回的是输入回车之前的所有字符
  2. 可以获得空白
package scanner;

import java.util.Scanner;

public class Demo01 {
    
    
    public static void main(String[] args) {
    
    
        // 创建一个扫描对象,用于接受键盘数据
        Scanner scanner = new Scanner(System.in);
        // 计算数值的和
        double sum = 0;
        // 计算有多少个数
        int count = 0;
        System.out.println("使用next方式接受:");

        // 通过循环判断是否还有输入,并在里面对每一次进行求和统计
        while(scanner.hasNextDouble()){
    
    
            double num = scanner.nextDouble(); // 读取数字赋值给中间变量 num
            count++; // 将计数器加1
            sum += num;
        }

        System.out.println(count + "个数的和为" + sum);
        System.out.println(count + "个数的平均值是" + (sum / count));

        // 关闭 Scanner ,解除 IO 流资源占用
        scanner.close();
    }
}

// 输出界面如下
使用next方式接受:
10
20
30
40
x		// 输入不是 double 类型时才会推出循环
4个数的和为100.0
4个数的平均值是25.0

Process finished with exit code 0

2.顺序结构

Java的基本结构就是顺序结构,除非特别指明,否则就按照顺序一句一句执行。顺序结构时最简单的算法结构,语句与语句之间,是按从上到下的顺序进行的它是由若干个依次执行的处理步骤组成的,他是任何一个算法都离不开的一种基本算法结构。

3.选择结构

  • if 单选择结构:我们很多时候需要判断一个东西是否可行,然后我们才去执行,这样一个过程在程序中用 if 语句来表示
if(布尔表达式){
    
    
    // 如果布尔表达式为 true 将执行的语句
}
// 如果布尔表达式为假则直接跳过 if(){} 语句,执行下一个语句
package structure;

import java.util.Scanner;

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

        System.out.println("请输入内容:");
        String s = scanner.nextLine(); // 读取一行输入给 s

        // equals:判断字符串是否相等
        if (s.equals("Hello")){
    
    
            System.out.println(s);
        }

        System.out.println("End");
        scanner.close();
    }
}

// 输出界面如下
请输入内容:
Hello
Hello
End

Process finished with exit code 0
  • if 双选择结构——二选一
if(布尔表达式){
    
    
    // 如果布尔表达式为 true 将执行的语句
}
else{
    
    
    // 如果布尔表达式为 false 将执行的语句
}
package structure;

import java.util.Scanner;

public class ifDemo {
    
    
    public static void main(String[] args) {
    
    
        // 考试分数大于60就是及格,小于60就是不及格

        Scanner scanner = new Scanner(System.in);

        System.out.println("请输入成绩:");
        int score = scanner.nextInt(); // 读取成绩给 score

        // equals:判断字符串是否相等
        if (score >= 60){
    
    
            System.out.println("恭喜你及格了,成绩为" + score + "分");
        }
        else{
    
    
            System.out.println("很抱歉,你挂科了,成绩为" + score + "分");
        }
        System.out.println("End");
        scanner.close();
    }
}

// 输出界面如下
请输入成绩:
80
恭喜你及格了,成绩为80End

Process finished with exit code 0
  • if 多选择结构——只能执行其中一条语句,直接跳过其他语句
if(布尔表达式1){
    
    
    // 如果布尔表达式1为 true 将执行的语句
}
else if(布尔表达式2){
    
    
    // 如果布尔表达式2为 true 将执行的语句
}
else if(布尔表达式3){
    
    
    // 如果布尔表达式3为 true 将执行的语句
}
else if(布尔表达式4){
    
    
    // 如果布尔表达式4为 true 将执行的语句
}
...
package structure;

import java.util.Scanner;

public class ifDemo {
    
    
    public static void main(String[] args) {
    
    
        // 考试分数大于60就是及格,小于60就是不及格

        Scanner scanner = new Scanner(System.in);

        System.out.println("请输入成绩:");
        int score = scanner.nextInt(); // 读取成绩给 score

        // equals:判断字符串是否相等
        if (score==100){
    
    
            System.out.println("恭喜你是满分");
        }
        else if(score < 100 && score >=90){
    
    
            System.out.println("恭喜你成绩是A");
        }
        else if(score < 90 && score >=80){
    
    
            System.out.println("恭喜你成绩是B");
        }else if(score < 80 && score >=70){
    
    
            System.out.println("恭喜你成绩是C");
        }else if(score < 70 && score >=60){
    
    
            System.out.println("恭喜你成绩是D");
        }else if(score < 60 && score >=0){
    
    
            System.out.println("很遗憾你不及格");
        }
        else{
    
    
            System.out.println("输入不合法,请重新输入成绩(整数):");
        }
        System.out.println("End");
        scanner.close();
    }
}



// 输出界面如下
请输入成绩:
65
恭喜你成绩是D
End

Process finished with exit code 0

注意

  1. if 语句至多有一个 else 语句,else 语句在所有的 else if 语句之后
  2. if 语句可以有若干个 else if 语句,它们必须在 else 语句之前。
  3. 一旦其中一个 else if 语句检测为 true,其他的 else if 以及 else 的语句都将跳过执行。

4.switch 多选择结构

多选择语句还有一个实现方式就是 switch case 语句,switch case 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。

switch(expression){
    
    
    case value : // value 的变量类型可以是 byte、short、int 或者 char,现在可以比较字符串 String 了
        // 语句
        break; // 可选
    case value :
        // 语句
        break; // 可选
        // 你可以有任意数量的case语句
    default : // 可选
        // 语句
}
...

养成规范:每一个 case 语句后面加上一个 break 防止穿透现象执行语句过多。

package structure;

public class switchDemo {
    
    
    public static void main(String[] args) {
    
    
        // switch 匹配一个具体的值
        char grade = 'C';

        switch (grade){
    
    
            // case 穿透会从第一个匹配分支依次执行每个 case 直到遇到 break 或者 default
            case 'A':
                System.out.println("优秀");
                break; //可选
            case 'B':
                System.out.println("良好");
            case 'C':
                System.out.println("及格");
            case 'D':
                System.out.println("再接再厉");
            case 'E':
                System.out.println("不及格");
            default:
                System.out.println("重新输入");
        }
    }

}

// 输出界面如下
及格
再接再厉
不及格
重新输入

Process finished with exit code 0

5.while 循环结构

  • while 是最基本的循环,它的结构为
while( 布尔表达式 ){
    
    
    // 循环内容
}
  • 只要布尔值为 true,循环就会一直执行下去
  • 我们大多数情况是会让循环停止下来的,我们需要一个让表达式失效的方式来结束循环
  • 少部分情况需要循环一直执行,比如服务器的请求响应监听等
  • 循环条件一直为 true 就会造成死循环
package structure;

public class whileDemo {
    
    
    public static void main(String[] args) {
    
    
        // 输出1~100

        int i = 0;
        // 每次循环都是先进行判断,表达式为 true 才执行循环体,如此往复
        while(i<100){
    
    
            i++;
            System.out.println(i);
        }
    }
}

6.do while 循环

  • 对于 while 语句而言,如果不满足条件,则不能进入循环。但有时候我们需要即使不满足条件,也至少执行一次
  • do while 循环和 while 循环相似,不同的是,do while 循环至少会执行一次
  • while 和 do while 的区别:while 是先判断后执行,do while 是先执行后判断;do while 总是保证循环体会被至少执行一次
do{
    
    
    // 代码语句
}while(布尔表达式);

7.for 循环

for 循环使得循环结构变得更加简单,for 循环是一种支持迭代的一种通用结构,最有效、最灵活,for 循环执行的次数在执行之前就确定的。格式如下:

for(初始化; 布尔表达式;更新){
    
    
    // 循环体
}
100.for // 快捷键 IDEA直接转化成100次的for循环,这是一种快捷方法,前面100可以改成任意数字

for(int i = 0; i < 100; i++) {
    
    
    
}

for(;;i++){
    
     // 死循环
    
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-whn4DKNb-1633524465713)(.\Markdown_picture\for.100.png)]

最先执行初始化步骤。可以声明一种类型,但可初始化一个或多个循环控制变量,当然也可以是空语句(使用已有变量作为循环控制变量),然后,检测布尔表达式的值。如果为 true,则循环体被执行。如果为 false 则循环终止,跳过循环体执行后面语句。执行一次循环体后,必须更新循环控制变量,再次检测布尔表达式,循环执行上述过程。

package structure;

public class forDemo {
    
    
    public static void main(String[] args) {
    
    
        // 练习:计算0-100之间奇数和偶数的和
        int sumOdd = 0; // 奇数和
        int sumEven = 0; // 偶数和
        for(int i = 0; i <= 100;i++){
    
    

            if((i%2)==0){
    
     // 通过取余判断奇偶性,是偶数则对偶数和迭代
                sumEven = sumEven + i;
                System.out.println(i+"是偶数");

            }
            else{
    
     // 不满足偶数的 i 就是奇数了,更新奇数和
                sumOdd = sumOdd + i;
                System.out.println(i+"是奇数");
            }
        }
        System.out.println("偶数和为" + sumEven);
        System.out.println("奇数和为" + sumOdd);
    }
}

// 输出界面如下
......
99是奇数
100是偶数
偶数和为2550
奇数和为2500

Process finished with exit code 0
package structure;

public class forDemo {
    
    
    public static void main(String[] args) {
    
    
        // 练习:用 for 循环输出1-1000之间能被5整除的数,并且每行输出3个
        int count = 0; // 初始化输出计数变量为0
        for(int i = 1; i <= 1000;i++){
    
    
            if((i%5)==0){
    
     // 通过取余判断能否被5整除,只输出能被5整除的数
                // 控制每行输出3个数,新增一个计数变量 count,每当 count 被3整除时输出换行符
                count++;
                if(count%4 == 0){
    
     // 余数为1,2,3时输出,为0时输出空行
                    System.out.println(); // 每隔3个数输出一个空行
                }
                else{
    
    
                    System.out.print(i+"\t");
                }
            }
        }
    }
}

// 输出界面如下
......
945 950 955 
965 970 975 
985 990 995 

Process finished with exit code 0

在这里插入图片描述

package structure;

public class forDemo {
    
    
    public static void main(String[] args) {
    
    
        // 练习:打印九九乘法表

        for(int i = 1; i <= 9;i++){
    
     // 外层循环9次,循环控制变量 i 同时作为右乘数
            for(int j = 1; j <= i; j++){
    
     // 内层循环 j 次,循环控制变量 j 同时作为左乘数,j <= i形成下三角的乘法表
                System.out.print(j+"x"+i+"="+(i*j)+"\t"); // 输出乘法公式 i x j = i*j
            }
            System.out.println(); // 每一行执行完后换行
        }
    }
}

// 输出界面如下
1x1=1	
1x2=2	2x2=4	
1x3=3	2x3=6	3x3=9	
1x4=4	2x4=8	3x4=12	4x4=16	
1x5=5	2x5=10	3x5=15	4x5=20	5x5=25	
1x6=6	2x6=12	3x6=18	4x6=24	5x6=30	6x6=36	
1x7=7	2x7=14	3x7=21	4x7=28	5x7=35	6x7=42	7x7=49	
1x8=8	2x8=16	3x8=24	4x8=32	5x8=40	6x8=48	7x8=56	8x8=64	
1x9=9	2x9=18	3x9=27	4x9=36	5x9=45	6x9=54	7x9=63	8x9=72	9x9=81	

Process finished with exit code 0

增强 for 循环

主要用于遍历数组和集合,后面会经常使用

for(声明语句 : 表达式){
    
     
    // 代码语句
}
  • 声明语句:声明新的局部变量,该变量类型必须和数组元素的类型匹配,作用域限定在循环语句内,其值与此时数组元素的值相等
  • 表达式:表达式时要访问的数组名,或者时返回值为数组的方法
package structure;

public class forDemo {
    
    
    public static void main(String[] args) {
    
    
        int[] numbers = {
    
    10,20,30,40,50}; // 定义了一个数组
        // 遍历数组的元素 迭代器
        for(int x:numbers){
    
    
            System.out.println(x);
        }
    }
}

// 输出界面如下
10
20
30
40
50

Process finished with exit code 0

break contuine

  • break 在任何循环语句的主体成分,均可以使用 break 控制循环的流程。break 用于强行退出循环,不执行循环中剩余的语句(跳出整个循环体)
  • continue 语句用在循环语句体中,用于终止某次循环过程,即跳出循环体尚未执行的语句,接着进行下一次是否执行循环的判定(只跳过一次循环迭代)
package structure;

public class forDemo {
    
    
    public static void main(String[] args) {
    
    
        // 打印三角形 5行
        for (int i = 1; i <= 5; i++) {
    
     // 先分解为5行,每一行先输出(5-i)个空格和 2i 个*
            int count = 5 - i; // 每一行的空格数目
            for (int j = 0; j < count; j++) {
    
     // 先输出空格
                System.out.print(" ");
            }
            for (int k = 1; k <= (2*i-1); k++) {
    
     // 再输出星星
                System.out.print("*");
            }
            System.out.println(); // 换行
        }
    }
}

// 输出界面如下
    *
   ***
  *****
 *******
*********

Process finished with exit code 0

Java 方法

1.何为方法?

  • sout-> System.out.println() 具体的含义是什么呢?其中 System 是一个类,所以 System.out 其实是 System 类的一个标准输出对象 out,最后 println() 就是一个方法,从属于对象并且一般在类里面定义好。
  • 方法是语句的集合,它们在一起执行一个功能。方法是解决一类问题的步骤的有序组合,方法包含于类或对象中,方法在程序中被创建,在其他地方被引用。
  • 设计方法的原则:方法的本意是功能块,就是实现某个功能的语句块的集合。我们设计方法的时候,最好保持方法的原子性,就是一个方法只完成一个功能,这样有利于后期的扩展
package method;

public class demo01 {
    
    
    // main 方法只是一个致敬,并不是必须从main函数开始执行
    public static void main(String[] args) {
    
    
        int sum = add(5,4);
        System.out.println(sum);

    }
    public static int add(int a, int b){
    
     // static的目的是使add变成全局作用域
        return a+b;
    }
}

2.方法的定义及调用

方法的定义

Java的方法类似于其他语言的函数,都是一段用来完成特定功能的代码片段,一般情况下,定义一个方法包含以下语法:

方法包含一个方法头和一个方法体。

修饰符 返回值类型 方法名(参数类型 参数名...){
    
    
    ...
    方法体
    ...
    return 返回值;
}

下面是一个方法的所有部分:

  • 修饰符:可选项,告诉编译器如何调用该方法,定义了该方法的访问类型

  • 返回值类型:方法可能有返回值,returnValueType 是方法返回值的数据类型。有些方法执行所需的操作,但是没有返回值,在这种情况下 returnValueType 是 void

  • 方法名:方法的实际名称,方法名和参数表共同构成方法签名

  • 参数类型:参数像是一个占位符,当方法被调用时,传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型、顺序和参数的个数。参数也是可选的,方法可以不包含任何参数。

    形参——在方法调用时在栈里新建的用来接收实参的占位符。实参——调用方法时实际传给方法的数据

  • 方法体:方法体包含具体的执行语句,定义了该方法的功能

    package method;
    
    public class demo01 {
          
          
        // main 方法只是一个致敬,并不是必须从main函数开始执行
        public static void main(String[] args) {
          
          
            int maxNumber = max(5,4);
            System.out.println(maxNumber);
    
        }
    
        // 两个整数比大小,取最大值
        public static int max(int a, int b){
          
          
            if(a>=b){
          
          
                return a;
            }
            else{
          
          
                return b;
            }
        }
    }
    
    // 输出界面如下
    5
    
    Process finished with exit code 0
    

    方法的调用

    • 调用方法:对象名.方法名(实参列表)

    • Java 支持两种调用方法的方式,根据方法是否返回值来选择

    • 但方法返回一个值的时候,方法调用通常被当作一个值。例如: int larger = max(30, 40);

    • 如果方法返回值是 void,方法调用一定是一条语句。例如:System.out.println(“Hello World”)

      Java都是值传递,没有引用传递

3.方法重载

  • 重载就是在一个类中,有相同的函数名称,但形参不同的方法。
  • 方法重载的规则:方法名称必须相同;参数列表必须不同;方法的返回值类型可以相同也可以不同;仅仅返回类型不同不足以成为方法的重载
  • 实现理论:方法名称相同时,编译器会根据调用方法的参数个数、参数类型等去逐个匹配,以选择对应的方法,如果匹配失败,则编译器报错

4.命令行传参

有时候你希望运行一个程序时在传递给它消息,这要靠传递命令行参数给main()函数实现

package method;

public class commandLine {
    
    
    public static void main(String[] args) {
    
    
        // args.length 数组长度
        for(int i = 0; i < args.length; i++){
    
    
            System.out.println("args["+i+"]"+args[i]);
        }
    }
}
Microsoft Windows [版本 10.0.19043.928]
(c) Microsoft Corporation。保留所有权利。

E:\Study\Windows工作文件\2021.9\Java从零开始\src\method>javac commandLine.java	// 文件夹下面会编译多出一个class文件

E:\Study\Windows工作文件\2021.9\Java从零开始\src\method>cd ..	// 此时必须退回到src文件夹下,因为class文件有依赖关系

E:\Study\Windows工作文件\2021.9\Java从零开始\src>java method.commandLine	// 其实class文件已经运行,只是我们没有给main传参

E:\Study\Windows工作文件\2021.9\Java从零开始\src>java method.commandLine this is shit	// this is shit 被当作2个string参数
args[0]this
args[1]is
args[2]shit

5.可变参数

  • 在 JDK1.5 开始,Java支持传递同类型的可变参数给一个方法,在方法声明中,在指定参数类型后面加上一个省略号(…)。

  • 一个方法中只能指定一个可变参数,它必须是方法的最后一个参数,任何普通参数必须在它之前声明。

  • 在数组中详解

    package method;
    
    public class commandLine {
          
          
        public static void main(String[] args) {
          
          
            commandLine test = new commandLine();
            test.printMax(1,2,5,6,45,222.0);
            }
            public static void printMax(double... numbers){
          
          
            // 健壮性测试
            if(numbers.length == 0){
          
          
                System.out.println("No argument passed");
                return;
            }
    
            double result = numbers[0];
            // 排序
            for(int i = 1; i < numbers.length; i++){
          
          
                if(numbers[i] > result){
          
          
                    result = numbers[i];
                }
            }
            System.out.println("The max value is " + result);
        }
    }
    
    // 输出界面如下
    The max value is 222.0
    
    Process finished with exit code 0
    

6.递归

  • 递归就是:A方法调用A方法!就是自己调用自己

  • 利用递归可以用简单的程序来解决一些复杂的问题。它通常把一个大型复杂的问题层层转化为一个与原问题相似的较小规模的问题来求解,递归策略只需要少量的程序就可描述出解题过程所需要的多次重复计算,大大减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合

  • 递归结构包含两个部分:

    递归头:什么时候不调用自身方法,如果没有头,将陷入死循环

    递归体:什么时候需要调用自身方法

在基数较小的情况下可以使用递归,当基数较大的时候就会调用函数次数过多,把栈耗尽导致内存溢出无法执行。所以尽量使用迭代方法替代递归

package method;

public class demo02 {
    
    
    // 阶乘 n!
    public static void main(String[] args) {
    
    
        System.out.println(func(5)); // 超过50就溢出,结果就是0 
    }
    public static int func(int n){
    
    
        // 递归头提出退出递归的条件:边界条件
        if(n == 1){
    
    
            return 1;
        }else{
    
    
            return n*func(n-1);
        }
    }
}
// 输出界面如下
120

Process finished with exit code 0

package method;

import java.util.Scanner;

// 设计一个计算器,能一直算的那种
public class computer {
    
    
    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in); // 读入键盘上的输入
        System.out.println("请输入ENTER进入计算,想结束程序请输入EXIT");
        while(scanner.nextLine()!="EXIT") {
    
    
            System.out.println("请输入第一个整数:");
            int number1 = scanner.nextInt(); // 用于接收键盘上的第一个操作数
            System.out.println("请输入操作符+ - * /");
            String operator = scanner.next();
            System.out.println("请输入第二个整数:");
            int number2 = scanner.nextInt(); // 用于接收键盘上的第二个操作数

            // 主体是Switch结构选择方法
            switch (operator) {
    
    
                case "+":
                    System.out.println(add(number1, number2));
                    break;
                case "-":
                    System.out.println(sub(number1, number2));
                    break;
                case "*":
                    System.out.println(multi(number1, number2));
                    break;
                case "/":
                    System.out.println(div(number1, number2));
                    break;
                default:
                    System.out.println("输入错误!");
            }
        }
        scanner.close();

    }
    public static int add(int a, int b){
    
    
        return a + b;
    }
    public static int sub(int a, int b){
    
    
        return a - b;
    }
    public static int multi(int a, int b){
    
    
        return a * b;
    }
    public static int div(int a, int b){
    
    
        return a / b;
    }
}

Java 数组

1.数组概述

数组的定义

数组是相同类型数据结构的有序集合,数组描述的是相同类型的若干数据,按照一定的先后次序排列组合而成,其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们。

2.数组声明创建

首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法:

dataType[] arrayRefVar; // 首选的方法
dataType arrayRefVar[]; // 效果相同,但不是首选的方法

Java语言使用 new 操作符来创建数组,语法如下:

dataType[] arrayRefVar = new dataType[arraySize];

数组的元素是通过索引 index 进行访问的,数组索引从 0 开始,获取数组长度:array.length

内存分析

Java内存分析:

  1. 堆区:存放 new 的对象和数组,可以被所有线程共享,不会存放别的对象引用
  2. 栈区:存放基本变量类型(包含这个基本类型的具体数值),引用对象的变量(存放这个引用在堆里的具体地址)
  3. 方法区:可以被所有线程共享,包含了所有的 class 和 static 变量

对应于数组的创建过程我们这里带入堆栈的内部

int[] numbers; // 1.声明一个数组,在栈区新建一个“指针”,但没有初始化为随机值,准备指向数组的内存地址
numbers = new int[5]; // 2.创建数组,用 new 给数组在堆区分配了5个 int 的空间,并把地址给到前面的“指针”

numbers[0] = 5; // 3.声明后只能通过下标给数组元素赋值,相当于直接对堆区的每一个 int 空间进行赋值操作
numbers[1] = 15;
numbers[2] = 20;
numbers[3] = 25;
numbers[4] = 30;

在这里插入图片描述

三种初始化

静态初始化;动态初始化;数组默认初始化(数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量的同样的方式被隐式初始化)

package array;

public class demo02 {
    
    
    public static void main(String[] args) {
    
    
        // 静态初始化:创建 + 赋值
        int[] a = {
    
    1, 2, 3};
        System.out.println(a[2]);
        // 动态初始化:包含默认初始化
        int[] b = new int[10]; // 此时数组元素均为0
        b[0] = 10;
        System.out.println(b[0]);
        System.out.println(b[1]); // 默认初始化为0
    }
}
// 输出界面如下
3
10
0

Process finished with exit code 0

数组的4个基本特点

  • 其长度是确定的,数组一旦被创建,它的大小就是不可改变的
  • 数组元素必须是相同类型,不允许出现混合类型
  • 数组中的元素可以是任何数据类型,包括基本类型和引用类型
  • 数组变量属于引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的

数组边界

下标的合法区间:[0, length-1],如果越界就会报错 ArrayIndexOutOfBoundsException:数组下标越界异常!

小结:1.数组是相同数据类型的有序集合 2.数组也是对象,数组的元素相当于对象的成员变量 3.数组长度是确定的,不可变的,如果越界就会报错。

3.数组使用

  • for-each循环

    package array;
    
    public class demo02 {
          
          
        public static void main(String[] args) {
          
          
            int[] arrays = {
          
          1, 2, 3, 4, 5};
            // 遍历打印全部数组元素
            for (int i = 0; i < arrays.length; i++) {
          
          
                System.out.println(arrays[i]);
            }
            System.out.println("===============");
            // 计算所有元素的和
            int sum = 0;
            for (int i = 0; i < arrays.length; i++) {
          
          
                sum += arrays[i];
            }
            System.out.println("数组元素和为:"+sum);
            System.out.println("===============");
            // 查找最大元素
            int max = arrays[0];
            for (int i = 0; i < arrays.length; i++) {
          
          
                if(arrays[i] > max){
          
          
                    max = arrays[i];
                }
            }
            System.out.println("数组最大元素为:"+max);
        }
    }
    // 输出界面如下
    1
    2
    3
    4
    5
    ===============
    数组元素和为:15
    ===============
    数组最大元素为:5
    
    Process finished with exit code 0
    
    package array;
    
    public class demo03 {
          
          
        public static void main(String[] args) {
          
          
            int[] arrays = {
          
          1, 2, 3, 4, 5};
            // JDK1.5开始支持,不用下标使用迭代器遍历数组,和Python一样适合打印但不适合下标操作
            for(int array:arrays){
          
          
                System.out.println(array);
            }
        }
    }
    // 输出界面如下
    1
    2
    3
    4
    5
    
    Process finished with exit code 0
    
  • 数组作方法入参

    package array;
    
    public class demo03 {
          
          
        public static void main(String[] args) {
          
          
            int[] arrays = {
          
          1, 2, 3, 4, 5};
            // JDK1.5开始支持,不用下标使用迭代器遍历数组,和Python一样适合打印但不适合下标操作
            printArray(arrays);
        }
        // 通过使用方法调用将数组当作参数传入
        public static void printArray(int[] arrays){
          
          
            for(int array:arrays){
          
          
                System.out.println(array);
            }
        }
    }
    // 输出界面如下
    1
    2
    3
    4
    5
    
    Process finished with exit code 0
    
  • 数组作返回值

    package array;
    
    public class demo03 {
          
          
        public static void main(String[] args) {
          
          
            int[] arrays = {
          
          1, 2, 3, 4, 5};
            // JDK1.5开始支持,不用下标使用迭代器遍历数组,和Python一样适合打印但不适合下标操作
            printArray(reverseArray(arrays));
        }
        // 打印数组
        public static void printArray(int[] arrays){
          
          
            for(int array:arrays){
          
          
                System.out.println(array);
            }
        }
        // 翻转打印数组
        public static int[] reverseArray(int[] arrays){
          
          
            int[] reverse = new int[arrays.length]; // 新建同样大小数组用于接收原数据
            // 进行元素收尾对调
            for (int i = 0; i < arrays.length; i++) {
          
          
                reverse[arrays.length-i-1] = arrays[i]; //即大小为5的话,r[4]=a[0],记住-1
            }
            return reverse;
        }
    }
    // 输出界面如下
    5
    4
    3
    2
    1
    
    Process finished with exit code 0
    

4.多维数组

多维数组可以看成是数组的数组,比如二维数组就是一个特殊的数组,其中每一个元素都是一个一维数组。 以此类推推广到多维数组,嵌套数组即可

int a[][] = new int[2][5]; // 2行5列的数组,想象成一个链表一样的int[2]是个数组元素是指针指向另一个int[5]的数组首地址

5.Arrays 类

  • 数组的工具类 java.util.Arrays,由于数组对象本身没有声明方法可以供我们调用,但API中提供了一个工具类 Arrays 供我们使用,从而可以对数据对象进行一些基本的操作。注意查看JDK帮助文档。Arrays 类中的方法都是static修饰的静态方法,在使用的时候可以直接使用类名进行调用,而“不用”使用对象来调用(注意:是不用而不是不能)

  • 具有以下常用功能:

    给数组赋值:fill 方法

    对数组排序:sort 方法,按升序

    比较数组:equals 方法,比较数组中元素值是否相等

    查找数组元素:binarySearch 方法能对排序好的数组进行二分查找

package array;


import java.util.Arrays;

public class demo04 {
    
    
    public static void main(String[] args) {
    
    
        int[] array = {
    
    25, 333, 254, 64, 128};
        // 打印数组元素
        System.out.println(array); // [I@49e4cb85
        // 打印数组元素 Arrays.toString
        System.out.println(Arrays.toString(array));
        // 升序排序
        Arrays.sort(array);
        System.out.println(Arrays.toString(array));
        
        Arrays.fill(array, 20);// 使用20来填充每一个数组元素 
        System.out.println(Arrays.toString(array));
    }
}
// 输出界面如下
[I@49e4cb85
[25, 333, 254, 64, 128]
[25, 64, 128, 254, 333]
[20, 20, 20, 20, 20]

Process finished with exit code 0

冒泡排序

冒泡排序无疑是最为出名的排序算法之一,总共有八大排序。冒泡排序的代码相当简单,两层循环嵌套,外层记冒泡次数,内层依次比较该次最小值归位。时间复杂度为 O(n^2)

package array;

import java.util.Arrays;

public class demo05 {
    
    
    public static void main(String[] args) {
    
    
        int[] a = {
    
    1,5161,11,2457,1346,1846,32,466,55};
        int[] sort = sort(a);
        System.out.println(Arrays.toString(sort));
    }
    // 冒泡排序方法
    public static int[] sort(int[] array){
    
    
        // 外层循环判断走多少次
        for (int i = 0; i < array.length - 1; i++) {
    
    
            // 内层循环比较两个数,每次把最大的数排到最左侧
            for (int j = 0; j < array.length - 1 - i; j++) {
    
    
                // 内层循环比大小,因为外层循环了 i 次所以确定了 i 个数,所以循环次数为 array.length - 1 - i
                if(array[j] < array[j+1]){
    
    
                    // 通过中间杯子交换两个数
                    int temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp; // 相邻两个数组元素交换完毕
                }
            }
        }
        return array;
    }
}
// 输出界面如下
[5161, 2457, 1846, 1346, 466, 55, 32, 11, 1]

Process finished with exit code 0

6.稀疏数组

当一个数组中大部分元素为0,或者为同一值的数组时,可以使用稀疏数组来保存该数组。稀疏数组的处理方法是:记录数组一共有几行几列,有多少个不同的值;把具有不同值得元素和行列及值记录在一个小规模数组中,从而缩小程序的规模。

package array;

public class demo06 {
    
    
    public static void main(String[] args) {
    
    
        // 1.创建一个二维数组 11*11 0:没有棋子  1:黑棋  2:白棋
        int[][] array = new int[11][11]; // 新建二维数组,默认值为0
        array[1][2] = 1;
        array[2][3] = 2;
        // 输出原始数组
        System.out.println("输出原始数组:");
        for (int[] ints : array) {
    
    
            for (int anInt : ints) {
    
    
                System.out.print(anInt + "\t");
            }
            System.out.println();// 打印一行后换行
        }
        System.out.println("=========================================");
        // 转换为稀疏数组保存,先获取有效值的个数
        int sum = 0;
        for (int i = 0; i < 11; i++) {
    
    
            for (int j = 0; j < 11; j++) {
    
    
                if(array[i][j] != 0){
    
    
                    sum++;
                }
            }
        }
        System.out.println("有效值的个数:" + sum);

        // 创建一个稀疏数组
        int[][] newArray = new int[sum+1][3];
        newArray[0][0] = 11;// 表头几行几列几个有效值
        newArray[0][1] = 11;
        newArray[0][2] = sum;
        // 遍历二维数组,将非 0 值存放在稀疏数组
        int count = 0;
        for (int i = 0; i < array.length; i++) {
    
    
            for (int j = 0; j < array.length; j++) {
    
    
                if(array[i][j] != 0){
    
    
                    count++; // 计数加换行的作用
                    newArray[count][0] = i; // 先存行号
                    newArray[count][1] = j; // 再存列号
                    newArray[count][1] = array[i][j]; // 再存值大小
                }
            }
        }
        System.out.println("输出稀疏数组:");
        for (int[] ints : newArray) {
    
    
            for (int anInt : ints) {
    
    
                System.out.print(anInt + "\t");
            }
            System.out.println();// 打印一行后换行
        }
    }
}
// 输出界面如下
输出原始数组:
0	0	0	0	0	0	0	0	0	0	0	
0	0	1	0	0	0	0	0	0	0	0	
0	0	0	2	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
=========================================
有效值的个数:2
输出稀疏数组:
11	11	2	
1	1	0	
2	2	0	

Process finished with exit code 0

Java 面向对象编程

Java 的核心思想就是 OOP

1.初识面向对象

  • 面向过程思想:步骤清晰简单,第一步做什么,第二部做什么;面向过程适合处理一些较为简单的问题

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

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

    所以并没有说面向对象就比面向过程好,这就和深度学习和传统编程一样,只是针对不同问题哪种更加合适罢了。面对简单单薄的问题,面向过程就能很好的处理;面对复杂庞大的问题,使用面向对象的方法就能很好的化整为零,免得无从下手,从复杂问题中提取出好的特点从而物以类聚,逐个击破。

    属性 + 方法 = 类

什么事面向对象

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

  • 三大特性:封装(权限限定);继承(减少重复);多态(多种形态)

  • 从认识论角度考虑是先有对象后有类。对象,是具体的事物。类,是对对象的一种抽象

  • 从代码运行的角度考虑是先有类后有对象,类是对象的模板。

2.方法回顾和加深

方法的定义

  • 修饰符:public static 保证全局访问权限
  • 返回类型:与 return 值类型保持一致
  • break 和 return 的区别:一个是退出当前循环体,一个是退出当前函数体
  • 方法名:驼峰原则,见名知意
  • 参数列表:参数列表,参数名
  • 异常抛出:最后在 oop 讲解

方法的调用

  • 静态方法:static,和类一起加载的时间特别早,所以可以在主函数直接调用
package oop;

public class demo01 {
    
    
    public static void main(String[] args) {
    
    
        Student.say(); // 在静态方法调用时直接用类方法就可以进行调用

    }
}
// 输出界面如下
学生说话了

Process finished with exit code 0
    
package oop;

public class Student {
    
    
    public static void say(){
    
    
        System.out.println("学生说话了");
    }
}
  • 非静态方法,只有当该类实例化以后才被加载
package oop;

public class demo01 {
    
    
    public static void main(String[] args) {
    
    
        // 对象类型 对象名 = 对象值;
        Student student = new Student(); // 在非静态方法调用时需要实例化对象后调用
		student.say();
    }
}
// 输出界面如下
学生说话了

Process finished with exit code 0
    
package oop;

public class Student {
    
    
    public static void say(){
    
    
        System.out.println("学生说话了");
    }
}

所以,当一个类是 static 另一个类不是的时候就不能相互调用,因为非static的类还没有加载

  • 形参与实参
package oop;

public class demo02 {
    
    
    public static void main(String[] args) {
    
    
      	int a = 1; // 与change方法内 int a 同名不同命,实际是两个内存地址
        System.out.println(a);
        
        demo02.change(a);
		System.out.println(a);
    }
    // 返回值为空,仅进行方法体
    public static void change(int a) {
    
    
        int a = 10; // 作用域有限,仅在方法内有效,方法结束后就被释放了
    }
}
// 输出界面如下
1
1

Process finished with exit code 0

  • 值传递和引用传递:值传递不改变原值,引用传递则两者都改变
package oop;

public class demo02 {
    
    
    public static void main(String[] args) {
    
    
        Person person = new Person(); // 实例化
        System.out.println(person.name);

        demo02.changeName(person);
        System.out.println(person.name);


    }
    public static void changeName(Person a){
    
    
        a.name = "Marine";
    }
}

class Person{
    
    
    String name; // 默认是null
}
// 输出界面如下
null
Marine

Process finished with exit code 0
  • this 关键字

3.对象的创建分析

类和对象的关系

类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但是不能代表某一个具体的事物。这些类都是用来描述某一类具体的事物应该具备的特点和行为。

对象是抽象概念的具体实例,能够体现出特点,展现出功能的是具体的实例,而不是一个抽象的概念。

创建与初始化对象

  • 使用 new 关键字创建对象

主函数类(一个程序只能有一个)

package oop.application;

// 一个项目应该只有一个 main 方法
public class Application {
    
    

    public static void main(String[] args) {
    
    
        // 类是抽象的,需要实例化一个个体
        // 类实例化后会返回一个自己类的对象
        Student Jack = new Student(); // 这里的 Student() 其实就是学生类的一个同名构造函数,系统自动生成
        Student Brand = new Student();
        Jack.age = 15;
        Jack.name = "Jack";
        Brand.name = "Brand";
        Brand.age = 16;
        System.out.println(Jack.name+"今年"+Jack.age+"岁");
        System.out.println(Brand.name+"今年"+Brand.age+"岁");
    }
}
// 输出界面如下
Jack今年15Brand今年16Process finished with exit code 0

成员类

package oop.application;

// 学生类
public class Student {
    
    
    // 属性
    String name;
    int age;

    // 方法
    public void study(){
    
    
        System.out.println(this.name + "学生在学习"); // this 代表当前这个类,this.name 则是当前类的name属性
    }

}

  • 使用 new 关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用

  • 类中的构造器也称为构造方法,是在进行创建对象的时候必须调用的。并且构造器有以下两个特点:

    1.必须和类的名字相同 2.必须没有返回类型,也不能写 void

一个类即使什么都不写,它也会存在一个方法——构造方法;当然你也可以显示写构造方法,进行初始值限定。

package oop.application;

public class Person {
    
    
    String name;

    // 显示定义无参构造方法
    public Person(){
    
    
        this.name = "Marine"; // 显示赋初始值为 Marine
    }
    // 有参构造方法:一旦定义了有参构造,无参构造就必须显示定义,否则就无效
    public Person(String name){
    
    
        this.name = name; // this.name 的name是对象的属性,而 String name 的name是形参
    }
}
package oop.application;

// 一个项目应该只有一个 main 方法
public class Application {
    
    

    public static void main(String[] args) {
    
    
        // 类是抽象的,需要实例化一个个体
        // 1.使用 new 关键字时,本质是调用构造器
        // 2.初始化值
        Person person = new Person(); 
        System.out.println(person.name); // 此时优先调用用户定义的显示构造方法
    }
}
// 输出界面如下
Marine

Process finished with exit code 0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0C3A6VT6-1633524465715)(.\Markdown_Picture\alt+insert)]

package oop.application;
// 使用 alt + insert 快速添加构造器
public class Person {
    
    
    String name;

    public Person() {
    
    
    }

    public Person(String name) {
    
    
        this.name = name;
    }
}

小结

  • 构造器:1.和类名相同 2.没有返回类型
  • 作用:1.使用 new 关键字时,本质是调用构造器 2.初始化对象的值
  • 注意点:1.定义有参构造方法之后,如果想使用无参构造,就必须显示定义,否则就无效 2.快捷键 ALT + INS,创建构造器 3. this 指向当前类,常用在类的内部定义
    在这里插入图片描述

main 函数运行在栈区,而堆区包含方法区和对象实例区等,静态方法区就是我们之前图方便一直使用的修饰符 static,因为它有着和类一起加载的特性可以直接在类中调用。——栈区:程序使用;堆区:程序员使用

在这里插入图片描述

实际上 main 函数内新建的对象都是引用变量,对象实际存在内存的堆区通过继承方法区内类的方法如shout(),和调用构造函数进行初始化。后续在 main 函数内对对象的操作也是对堆区内对应内存的更改。

4.面向对象三大特性

封装

  • 改露的露,该藏的藏

    程序设计要求追求**“高内聚,低耦合”**。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合就是仅暴露少量的方法给外部使用。

  • 封装(数据的隐藏)

    通常,应禁止直接访问一个对象中数据的实际表现,而应通过操作接口来访问,这称为信息隐藏

  • 记住 属性私有,get/set,只能通过开放的方法 get/set 对类里的私有属性进行有限操作。

package oop.application;

// 学生类
public class Student {
    
    
    // 私有属性
    private String name; // 姓名
    private int ID; // 学号
    private int age; // 年龄
    private char sex; // 性别

    // 提供外部能使用的方法
    // get获得这个数据
    public String getName(){
    
    
        return this.name;
    }
    // set设置这个数据
    public void setName(String name){
    
    
        this.name = name;
    }

}
package oop.application;

// 一个项目应该只有一个 main 方法
public class Application {
    
    

    public static void main(String[] args) {
    
    
        // 类是抽象的,需要实例化一个个体
        Student student1 = new Student();
        System.out.println(student1.getName());
        student1.setName("Marine");
        System.out.println(student1.getName());
    }
}
// 输出界面如下
null
Marine

Process finished with exit code 0

好处:1.提高程序的安全性,保护数据 2.隐藏代码的实现细节 3.统一接口 4.系统可维护性

继承

继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。extends 的意思是“扩展”,子类是父类的扩展。Java中类只有单继承,没有多继承!

继承是类和类之间的一种关系,除此之外,类和类之间的关系还有依赖、组合、聚合等。继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字 extends 来表示。子类和父类之间,从意义上讲应该具有“is a”的关系。

package oop.demo03;

public class Person {
    
    
    public int money = 10_0000_0000; // 财产10亿,属性最好私有get方法访问
    public void say(){
    
    
        System.out.println("Person说了一句话");
    }
}
package oop.demo03;
// 学生 is 人:派生类,子类
// 子类继承了父类,就会拥有父类的全部方法和属性
public class Student extends Person{
    
    
}
package oop.demo03;

public class Application {
    
    
    public static void main(String[] args) {
    
    
        Student student = new Student();
        student.say();
        System.out.println(stuednt.money);
    }
}
// 输出界面如下
Person说了一句话
1000000000
    
Process finished with exit code 0

权限的四个优先级由低到高:public–>protected–>default–>private

在这里插入图片描述

在类中按 CTRL + h 就可以在右侧栏显示继承树的信息,所有类都是默认直接或者间接继承了 Object 类,有一些方法内置其中

super

对比 this 指针是指向当前类,super 指向当前类的父类,从而可以访问父类中的一些开放的属性及方法。

package oop.demo03;

public class Person {
    
    
    protected String name = "Person";
}
package oop.demo03;

public class Student extends Person{
    
    
    public String name = "Student";

    public void test(String name){
    
    
        System.out.println(name); // Marine,形参的值
        System.out.println(this.name); // Student,指向当前类的 name 属性
        System.out.println(super.name); // Person,指向父类的 name 属性
    }
}
package oop.demo03;

public class Application {
    
    
    public static void main(String[] args) {
    
    
        Student student = new Student();
        student.test("Marine");
    }
}
// 输出界面如下
Marine
Student
Person

Process finished with exit code 0

子类对象构造函数默认先调用父类构造函数

package oop.demo03;

public class Person {
    
    
    public Person(){
    
    
        System.out.println("Person无参构造函数");
    }
}
package oop.demo03;

public class Student extends Person{
    
    
    public Student(){
    
    
        // 隐藏代码:调用了父类的无参构造函数
        super(); // super() 默认用来调用父类构造器,必须在子类构造器第一行
        System.out.println("Student无参构造函数");
    }
}
package oop.demo03;

public class Application {
    
    
    public static void main(String[] args) {
    
    
        Student student = new Student();

    }
}
// 输出界面如下
Person无参构造函数
Student无参构造函数

Process finished with exit code 0

super小结

  1. super 调用父类的构造方法,必须在构造方法的第一个
  2. super 必须只能出现在子类的方法或者构造方法中
  3. super 和 this 不能同时出现在构造方法中(两者都得在第一行)

VS this:

代表的对象不同:this 指向本身调用者这个对象;super 指向父类对象的引用

前提不同:this 没有用继承也可以使用;super 只能在继承条件下才可以使用

构造方法不同:this 调用本类的构造方法;super 调用父类的构造方法

方法重写

重写都是方法重写与属性无关,而且重写方法与静态方法还是非静态方法相关

静态方法:

package oop.demo04;

public class A extends B{
    
    
    public static void test(){
    
    
        System.out.println("A=>test()");
    }
}
package oop.demo04;

public class B {
    
    
    public static void test(){
    
    
        System.out.println("B=>test()");
    }
}
package oop.demo04;

public class Application {
    
    
    public static void main(String[] args) {
    
    
        // 方法的调用只和左边,定义的数据类型有关
        A a = new A(); // a 的数据类型是 A
        a.test();
        // 父类的引用可以指向子类
        B b = new A(); // b 的数据类型是 B
        b.test();
    }
}
// 输出界面如下
A=>test()
B=>test()
    
Process finished with exit code 0

非静态方法:

package oop.demo04;

public class A extends B{
    
    
    // Override 重写
    @Override // 注解:有功能的注释!
    public void test(){
    
    
        System.out.println("A=>test()");
    }
}
package oop.demo04;

public class B {
    
    
    public void test(){
    
    
        System.out.println("B=>test()");
    }
}
package oop.demo04;

public class Application {
    
    
    
    // 静态方法和非静态方法区别很大!
    public static void main(String[] args) {
    
    
        
        A a = new A(); 
        a.test();
        // 父类的引用可以指向子类
        B b = new A(); // 子类重写了父类的方法
        b.test();
    }
}
// 输出界面如下
A=>test()
A=>test()
    
Process finished with exit code 0

重写小结:前提是有继承关系,而且必须是子类重写父类方法!

  1. 方法名必须相同
  2. 参数列表也相同(否则就是重载了)
  3. 修饰符:范围可以扩大;可以从父类的 private 方法扩到子类的 public 方法。 public–>protected–>default–>private
  4. 抛出的异常:范围可以缩小但不能扩大。

重写,子类的方法名必须和父类的保持一致,方法体不同!

为什么需要重写?父类的功能,子类不一定需要,或者不能使用 快捷键ALT + INSERT 选 Override

多态

即同一方法可以根据发送对象的不同而采用多种不同的行为方式。一个对象的实际类型是确定的,但可以指向对象的引用类型有很多

多态存在的条件:有继承关系,子类重写父类方法,父类引用指向子类对象

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

package oop.demo03;

public class Person {
    
    
    public void run(){
    
    
        System.out.println("run");
    }
}
package oop.demo03;

public class Student extends Person{
    
    
    public void run(){
    
    
        System.out.println("son");
    }
    public void eat(){
    
    
        System.out.println("eat");
    }
}
package oop.demo03;

public class Application {
    
    
    public static void main(String[] args) {
    
    
        // 一个对象的实际类型是不确定的
        // new Student(); new Person();的类型不确定
        // 一个对象的实际类型是确定的,但是指向它的引用类型就不确定了:父类的引用指向子类
        // Student 能调用的方法都是自己的或者继承父类的!
        Student s1 = new Student();
        // Person 属于是父类引用指向了子类,但是不能调用子类独有的方法
        Person s2 = new Student(); // 有继承关系的才行

        s1.run();
        s1.eat();
        s2.run();
        // 对象能执行哪些方法,主要看对象左边的类型,和右边关系不大
        ((Student) s2).eat(); // 子类重写了父类的方法,执行子类的方法必须强制转换
    }
}
// 输出界面如下
son
eat
son
eat

Process finished with exit code 0

多态的注意事项:

  1. 多态是方法的多态,属性没有多态
  2. 父类和子类因为有联系才有多态,否则就是类型转换异常 ClassCastException!
  3. 存在的条件:有继承关系,且方法需要重写,父类引用指向子类对象 Father f1 = new Son();

instanceof

instanceof 判断一个对象是什么类型,是否有父子关系 Object object = new Student();System.out.println(object instanceof Student); 看object 对象是不是 Student 类型,返回 true。

类型转换

强制类型转换可以由高到低 父类——>子类;由低到高是自动转换的,同 float 和 int 之间的关系类似。

package oop.demo03;

public class Person {
    
    
    public void run(){
    
    
        System.out.println("run");
    }
}
package oop.demo03;

public class Student extends Person{
    
    
    public void go(){
    
    
        System.out.println("go");
    }
}
package oop.demo03;

public class Application {
    
    
    public static void main(String[] args) {
    
    
        // 高             低
        Person student = new Student();
        // student.go(); 此时 student 对象是 Person 类型,无法调用 Student 类的 go()方法
        // 需要将 Person类型转化为 Student 类型才能使用 Student 类的方法
        ((Student) student).go(); // 带括号强制转换
        // 低             高
        student.run(); // 可以直接使用 Person 类的方法
        
    }
}
// 输出界面如下
go

Process finished with exit code 0

转换小结:1.父类引用指向子类对象 2.把子类转换为父类,向上转型 3.把父类转换为子类,向下转型——强制转换 4.方便方法的调用,减少代码的重复

抽象:封装、继承、多态

多态的理解 引用https://www.cnblogs.com/chenssy/p/3372798.html

封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据。对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法。

继承是为了重用父类代码。两个类若存在IS-A的关系就可以使用继承。同时继承也为实现多态做了铺垫。那么什么是多态呢?多态的实现机制又是什么?请看我一一为你揭开:

所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

比如你是一个酒神,对酒情有独钟。某日回家发现桌上有几个杯子里面都装了白酒,从外面看我们是不可能知道这是些什么酒,只有喝了之后才能够猜出来是何种酒。你一喝,这是剑南春、再喝这是五粮液、再喝这是酒鬼酒….在这里我们可以描述成如下:

酒 a = 剑南春

酒 b = 五粮液

酒 c = 酒鬼酒

这里所表现的的就是多态。剑南春、五粮液、酒鬼酒都是酒的子类,我们只是通过酒这一个父类就能够引用不同的子类,这就是多态——我们只有在运行的时候才会知道引用变量所指向的具体实例对象。

诚然,要理解多态我们就必须要明白什么是“向上转型”。在继承中我们简单介绍了向上转型,这里就在啰嗦下:在上面的喝酒例子中,酒(Win)是父类,剑南春(JNC)、五粮液(WLY)、酒鬼酒(JGJ)是子类。我们定义如下代码:

JNC a = new JNC();

对于这个代码我们非常容易理解无非就是实例化了一个剑南春的对象嘛!但是这样呢?

Wine a = new JNC();

在这里我们这样理解,这里定义了一个Wine 类型的a,它指向JNC对象实例。由于JNC是继承与Wine,所以JNC可以自动向上转型为Wine,所以a是可以指向JNC实例对象的。这样做存在一个非常大的好处,在继承中我们知道子类是父类的扩展,它可以提供比父类更加强大的功能,如果我们定义了一个指向子类的父类引用类型,那么它除了能够引用父类的共性外,还可以使用子类强大的功能。

但是向上转型存在一些缺憾,那就是它必定会导致一些方法和属性的丢失,而导致我们不能够获取它们。所以父类类型的引用可以调用父类中定义的所有属性和方法,对于只存在与子类中的方法和属性它就望尘莫及了(被重写了优先选子类的)。

public class Wine {
    
    
    public void fun1(){
    
    
        System.out.println("Wine 的Fun1...");
        fun2();
    }
    
    public void fun2(){
    
    
        System.out.println("Wine 的Fun2...");
    }
}

public class JNC extends Wine{
    
    
    /**
     * @desc 子类重载父类方法
     *        父类中不存在该方法,向上转型后,父类是不能引用该方法的
     * @param a
     * @return void
     */
    public void fun1(String a){
    
    
        System.out.println("JNC 的 Fun1...");
        fun2();
    }
    
    /**
     * 子类重写父类方法
     * 指向子类的父类引用调用fun2时,必定是调用该方法
     */
    public void fun2(){
    
    
        System.out.println("JNC 的Fun2...");
    }
}

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Wine a = new JNC();
        a.fun1();
    }
}
-------------------------------------------------
Output:
WineFun.....
JNC 的Fun2...

从程序的运行结果中我们发现,a.fun1()首先是运行父类Wine中的fun1().然后再运行子类JNC中的fun2()。

分析:在这个程序中子类JNC重载了父类Wine的方法fun1(),重写fun2(),而且重载后的fun1(String a)与 fun1()不是同一个方法,由于父类中没有该方法,向上转型后会丢失该方法,所以执行JNC的Wine类型引用是不能引用fun1(String a)方法。而子类JNC重写了fun2() ,那么指向JNC的Wine引用会调用JNC中fun2()方法。

所以对于多态我们可以总结如下:

指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)

对于面向对象而言,多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。

多态的实现

2.1实现条件

在刚刚开始就提到了继承在为多态的实现做了准备。子类Child继承父类Father,我们可以编写一个指向子类的父类类型引用,该引用既可以处理父类Father对象,也可以处理子类Child对象,当相同的消息发送给子类或者父类对象时,该对象就会根据自己所属的引用而执行不同的行为,这就是多态。即多态性就是相同的消息使得不同的类做出不同的响应。

Java实现多态有三个必要条件:继承、重写、向上转型。

  • 继承:在多态中必须存在有继承关系的子类和父类。
  • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
  • 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

​ 只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。对于Java而言,它多态的实现机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。

2.2实现形式

在Java中有两种形式可以实现多态。继承和接口。

2.2.1、基于继承实现的多态

基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。

public class Wine {
    
    
    private String name;
    
    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public Wine(){
    
    
    }
    
    public String drink(){
    
    
        return "喝的是 " + getName();
    }
    
    /**
     * 重写toString()
     */
    public String toString(){
    
    
        return null;
    }
}

public class JNC extends Wine{
    
    
    public JNC(){
    
    
        setName("JNC");
    }
    
    /**
     * 重写父类方法,实现多态
     */
    public String drink(){
    
    
        return "喝的是 " + getName();
    }
    
    /**
     * 重写toString()
     */
    public String toString(){
    
    
        return "Wine : " + getName();
    }
}

public class JGJ extends Wine{
    
    
    public JGJ(){
    
    
        setName("JGJ");
    }
    
    /**
     * 重写父类方法,实现多态
     */
    public String drink(){
    
    
        return "喝的是 " + getName();
    }
    
    /**
     * 重写toString()
     */
    public String toString(){
    
    
        return "Wine : " + getName();
    }
}

public class Test {
    
    
    public static void main(String[] args) {
    
    
        //定义父类数组
        Wine[] wines = new Wine[2];
        //定义两个子类
        JNC jnc = new JNC();
        JGJ jgj = new JGJ();
        
        //父类引用子类对象
        wines[0] = jnc;
        wines[1] = jgj;
        
        for(int i = 0 ; i < 2 ; i++){
    
    
            System.out.println(wines[i].toString() + "--" + wines[i].drink());
        }
        System.out.println("-------------------------------");

    }
}
OUTPUT:
Wine : JNC--喝的是 JNC
Wine : JGJ--喝的是 JGJ
-------------------------------

在上面的代码中JNC、JGJ继承Wine,并且重写了drink()、toString()方法,程序运行结果是调用子类中方法,输出JNC、JGJ的名称,这就是多态的表现。不同的对象可以执行相同的行为,但是他们都需要通过自己的实现方式来执行,这就要得益于向上转型了。我们都知道所有的类都继承自超类Object,toString()方法也是Object中方法,当我们这样写时:

Object o = new JGJ();

      System.out.println(o.toString());

输出的结果是Wine : JGJ。

Object、Wine、JGJ三者继承链关系是:JGJ—>Wine—>Object。所以我们可以这样说:当子类重写父类的方法被调用时,只有对象继承链中的最末端的方法才会被调用。但是注意如果这样写:

Object o = new Wine();

System.out.println(o.toString());

输出的结果应该是Null,因为JGJ并不存在于该对象继承链中。所以基于继承实现的多态可以总结如下:对于引用子类的父类类型,在处理该引用时,它适用于继承该父类的所有子类,子类对象的不同,对方法的实现也就不同,执行相同动作产生的行为也就不同。如果父类是抽象类,那么子类必须要实现父类中所有的抽象方法,这样该父类所有的子类一定存在统一的对外接口,但其内部的具体实现可以各异。这样我们就可以使用顶层类提供的统一接口来处理该层次的方法。

2.2.2、基于接口实现的多态

继承是通过重写父类的同一方法的几个不同子类来体现的,那么就可就是通过实现接口并覆盖接口中同一方法的几不同的类体现的。在接口的多态中,指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。

继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性。

static 关键字详解

package oop.demo01;

import oop.demo03.Person;

public class Student {
    
    
    // 静态属性
    private static int age; // 静态变量
    private double score; // 普通变量

    // 静态方法
    // 在同一个类中,普通方法可以调用静态方法,反之则不行,这是加载顺序决定的
    public void run(){
    
    
        System.out.println("run");
        go();
    }

    public static void go(){
    
    
        System.out.println("go");
    }

    public static void main(String[] args) {
    
    
        Student student = new Student();
        System.out.println(student.score); // 普通变量只能通过实例访问
        System.out.println(student.age);

        System.out.println(Student.age); // 静态变量可以通过类访问
    }
}

代码块详解

package oop.demo01;

public class Person {
    
    
    {
    
    
        // 匿名代码块,第二个执行,可以赋一些初始值
        System.out.println("匿名代码块");
    }
    static{
    
    
        // 静态代码块,第一个执行,但只执行一次
        System.out.println("静态代码块");
    }

    public Person() {
    
    
        System.out.println("构造器");
    }

    public static void main(String[] args) {
    
    
        Person person = new Person(); // 第三个执行
		System.out.println("====================");
        Person person2 = new Person();
    }
}
// 输出界面如下
静态代码块
匿名代码块
构造器
====================
匿名代码块
构造器

Process finished with exit code 0
package oop.demo01;
// 静态导入包,就不用每次都写全称 Math.random() 可以直接写 random()
import static java.lang.Math.random;
public class Test {
    
    

    public static void main(String[] args) {
    
    
        System.out.println(random());
    }
}

注:final 类是常量,不能继承

5.抽象类和接口

abstract 修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类。抽象类中可以没有抽象方法,但是抽象方法的类一定要声明为抽象类。

抽象类,不能使用 new 关键字来创建对象,它是用来让子类继承的。抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的。子类继承抽象类,那么久必须实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类

package oop.demo02;

// 抽象类;子类 extends 只能单继承,只有接口可以多继承
public abstract class Action {
    
    

    // 只是一个声明,没有方法体;一种约束,子类帮忙实现
    public abstract void doSomething();
    // 1.不能new这个抽象类,只能考子类去实现它
    // 2.抽象类中可以写普通方法
    // 3.抽象方法必须在抽象类中
    // 优点:抽象出来,提高开发效率
}
package oop.demo02;

// 抽象类的所有方法,继承了它的子类,都必须要实现它的方法;除非它也是抽象类
public class A extends Action{
    
    
    @Override
    public void doSomething() {
    
    

    }
}

接口

  • 普通类:只有具体实现
  • 抽象类:具体实现和规范(抽象方法)都有!
  • 接口:只有规范!自己无法写方法~专业的约束!实现了约束与实现分离:面向接口编程

接口就是规范,定义的一组规则,体现了现实世界中“如果你是。。。则必须能。。。”的思想。如果你是天使,则必须能飞。如果你是汽车,则必须跑。接口的本质是契约,就像我们人之间的法律,制定好后大家都遵守。OO的精髓,是对对象的抽象,最能体现这一点的就是接口。为什么我们讨论设计模式都只针对具备了抽象能力的语言(比如C++,Java等),就是因为设计模式研究的,实际上就是如何合理的去抽象。

声明类的关键字是 class,声明接口的关键字是 interface

package oop.demo05;

// 接口定义需要的是抽象思维——Java架构师
// interface 关键字定义接口,接口都需要有实现类
public interface UserService {
    
    
    // 接口中定义的所有属性都是常量 public final
    int AGE = 20;
    // 接口中定义的所有方法都是默认抽象的 public static
    void add(String name);
    void delete(String name);
    void update(String name);
    void query(String name);
}
package oop.demo05;

public interface TimeService {
    
    
    void timer();
}
package oop.demo05;

// 实现类通过关键字 implements 对接口类实现
// 实现了接口的类,就需要重写接口中规范的方法
// 利用接口实现了多继承
public class UserServiceImpl 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.接口中方法默认有关键字 public abstract 4.接口中属性默认有关键字 public static final 5.接口不能被实例化,接口中也没有构造方法 6.implements 可以实现多个接口 7.实现类必须重写接口中的方法

6.内部类

内部类就是在一个类的内部定义一个类,比如,A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了。内部类可以直接访问外部类的一些东西,比如私有属性,这是正常类做不到的。

package oop.demo06;
// 外部类
public class Outer {
    
    

    private int ID = 10;
    public void out(){
    
    
        System.out.println("这是外部类的方法");
    }

    public class Inner{
    
    
        public void in(){
    
    
            System.out.println("这是内部类的方法");
        }
        // 可以获得外部类的私有属性
        public void getID(){
    
    
            System.out.println(ID);
        }
    }
}
package oop.demo06;

public class Application {
    
    
    public static void main(String[] args) {
    
    
        Outer outer = new Outer();
        // 通过外部类来实例化内部类
        Outer.Inner inner = outer.new Inner();
        inner.getID();
    }
}
// 输出界面如下
10

Process finished with exit code 0
package oop.demo06;
// 外部类
public class Outer {
    
    
    
}

//一个Java类文件中可以有多个class,但是只能有一个public class
//这样就可以在一个文件内测试了
class A{
    
    
    public static void main(String[] args) {
    
    
        
    }
}

异常机制

1.什么是异常

  • 实际工作中,遇到的情况不可能是非常完美的。比如:你写的某个模块,用户输入不一定符合你的要求、你的程序要打开某个文件,这个文件可能不存在或者文件格式不对、你要读取数据库的数据,结果数据可能是空的、我们的程序再跑着内存或者硬盘可能满了,等等。
  • 软件程序在运行过程中,非常可能遇到刚刚提到的这些异常问题,我们叫异常,英文是:Exception,意思是例外。这些,例外情况,或者叫异常,怎么让我们写的程序做出合理的处理,而不至于程序崩溃。
  • 异常指程序运行中出现的不期而至的各种状况,异常发生在程序运行期间,它影响了正常的程序执行流程

要理解 Java 异常处理是如何工作的,你需要掌握以下三种类型的异常:

  1. 检查性异常:最具代表性的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
  2. 运行时异常:运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
  3. 错误 ERROR:错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到。

Java异常处理框架

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

在这里插入图片描述

2.异常体系结构

Error

  • Error 类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。
  • Java 虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需要的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止;
  • 还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。

Exception

  • 在 Exception 分支中有一个重要的子类 RuntimeException(运行时异常)

    ArrayIndexOutOfBoundException(数组下标越界)

    NullPointerException(空指针异常)

    ArithmeticException(算术异常)

    MissingResourceException(丢失资源)

    ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理

  • 这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;

  • Error 和 Exception 的区别:Error 通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception 通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。

3.Java异常处理机制

抛出异常;捕获异常

异常处理五个关键字:try, catch, finally, throw, throws

package exception;

public class demo01 {
    
    
    public static void main(String[] args) {
    
    
        int a = 1;
        int b = 0;

        // 类似 if else 结构如果 try 代码块内发生 catch() 内的异常就执行 catch 代码块
        try{
    
    // try 代码块必须;把可能有异常的代码放进去进行监控
            System.out.println(a/b);
        }catch (ArithmeticException e){
    
    // catch 代码块必须;针对可能的异常进行处理()里是想要捕获的类型
            System.out.println("程序出现异常,变量b不能为0");
        }finally {
    
    // finally 代码块非必须;处理善后工作如资源归还关闭服务等,不论有没有出现异常都会执行
            System.out.println("finally");
        }
    }

}
// 输出界面如下
程序出现异常,变量b不能为0
finally

Process finished with exit code 0
package exception;

public class demo01 {
    
    
    public static void main(String[] args) {
    
    
        int a = 1;
        int b = 0;
		// 选中代码 CRTL + ALT + T 可以快捷键编写try catch代码
        // 捕获结构类似 if else 结构可以叠加,从上至下范围越来越大
        try{
    
    
            System.out.println(a/b);
        }catch (Error e){
    
    
            System.out.println("Error");
        }catch(Exception e){
    
    
            System.out.println("Exception");
        }catch(Throwable t){
    
    
            System.out.println("Throwable");
        }
        finally {
    
    
            System.out.println("finally");
        }
    }

}
// 输出界面如下
Exception
finally

Process finished with exit code 0
package exception;

public class demo01 {
    
    
    public static void main(String[] args) {
    
    
        new demo01().test(1,0); // 哪怕方法中没有定义除法运算也会抛出异常
    }

    // 假设这个方法中,处理不了这个异常,方法上抛出异常
    public void test(int a, int b){
    
    
        if( b == 0){
    
     // throw 与 throws 不同
            throw new ArithmeticException(); // 主动抛出异常,一般在方法中使用
        }
    }
}
// 输出界面如下
Exception in thread "main" java.lang.ArithmeticException
	at exception.Test.test(demo01.java:10)
	at exception.Test.main(demo01.java:5)

Process finished with exit code 1

4.自定义异常

使用Java内置的异常类可以描述在编程时出现的大部分异常情况,除此之前,用户还可以自定义异常。用户自定义异常类,只需继承 Exception 类即可。

在程序中使用自定义异常类,大体可分为以下几个步骤:

  1. 创建自定义异常类
  2. 在方法中通过 throw 关键字抛出异常对象
  3. 如果在当前抛出异常的方法中处理异常,可以使用 try-catch 语句捕获并处理;否则在方法的声明处通过 throws 关键字指明要抛出给方法调用的异常,继续下一步操作
  4. 在出现异常方法的调用者中捕获并处理异常
package exception;

// 自定义的异常类
public class MyException extends Exception{
    
    

    // 传递数字 > 10;
    private int detail;

    public MyException(int a){
    
    
        this.detail = a;
    }
    // toString:异常的打印信息

    @Override
    public String toString() {
    
    
        return "MyException{" +
                "detail=" + detail +
                '}';
    }
}
package exception;

public class Test {
    
    

    // 可能会存在异常的方法
    static void test(int a) throws MyException {
    
    
        System.out.println("传递的参数为:" + a);

        if( a > 10){
    
    
            throw new MyException(a); // 主动抛出异常,一般在方法中使用
        }
        System.out.println("OK");
    }

    public static void main(String[] args) {
    
    
        try {
    
     // 捕获异常
            test(11);
        } catch (MyException e) {
    
    
            System.out.println("MyException:" + e);;
        }
    }
}
// 输出界面如下
传递的参数为:11
MyException:MyException{
    
    detail=11}

Process finished with exit code 0

5.实际应用中的总结

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

猜你喜欢

转载自blog.csdn.net/m0_47455189/article/details/120629412