Java基础笔记(狂神版)

java基础

Java开发规范参见Java开发手册(嵩山版)免费在线阅读_藏经阁-阿里云开发者社区 (aliyun.com)

概述 (Java SE 17 & JDK 17) (oracle.com)

注释

标识符

java中标识符是为方法、变量或其他用户定义项所定义的名称。

标识符由数字(0 ~ 9)和字母(az和AZ)、美元符号($)、下划线(__)以及Unicode字符集中符号大于0xC0的所有符号组合构成(各符号之间没有空格)

  • 标识符的第一个符号为字母、下划线和美元符号,后面可以是任何字母、数字、美元符号或下划线
  • Java区分大小写
  • 不能使用数字开头
  • 不能使用任何Java关键字作为标识符,标识符可以包含关键字,但不能与关键字重名
  • 不能赋予标识符任何标准的方法名

标识符分为两类,分别为关键字和用户自定义标识符。

  1. 关键字是有特殊含义的标识符,如 true、false 表示逻辑的真假。

  2. 用户自定义标识符是由用户按标识符构成规则生成的非保留字的标识符,如 abc 就是一个标识符。

关键字

关键字(或者保留字)是对编译器有特殊意义的固定单词,不能在程序中做其他目的使用。关键字具有专门的意义和用途,和自定义的标识符不同,不能当作一般的标识符来使用

Java 的关键字对 Java 编译器有特殊的意义,它们用来表示一种数据类型,或者表示程序的结构等。保留字是为 Java 预留的关键字,它们虽然现在没有作为关键字,但在以后的升级版本中有可能作为关键字


Java 语言目前定义了 51 个关键字,这些关键字不能作为变量名、类名和方法名来使用。以下对这些关键字进行了分类。

  1. 数据类型:boolean、int、long、short、byte、float、double、char、class、interface

  2. 流程控制:if、else、do、while、for、switch、case、default、break、continue、return、try、catch、finally

  3. 修饰符:public、protected、private、final、void、static、strict、abstract、transient、synchronized、volatile、native

  4. 动作:package、import、throw、throws、extends、implements、this、supper、instanceof、new

  5. 保留字:true、false、null、goto、const

数据类型

  • 强类型语言

    ​ 要求变量的使用要严格符合规定,所有变量都必须先定义后才能使用

  • 弱类型语言
  • Java的数据类型分为两类
    • 基本数据类型
    • 引用类型

在这里插入图片描述

注: *Long类型要在数字后面加个L

​ *Float类型要在数字后面加个F

​ *Boolean类型占一位,即1bit

字节

  • 位(bit):是计算机内部数据储存的最小单位,11001100是一个八位二进制数
  • 字节(byte):是计算机中数据处理的基本单位,习惯上用大写B来表示
  • 1B(byte,字节) = 8bit(位)
  • 字符:是指计算机中使用的字母、数字、字和符号
  • 1bit表示1位
  • 1Byte表示一个字节 1B = 8b
  • 1024B = 1KB
  • 1024KB = 1M
  • 1024M = 1G

数据转换

数据类型的级别/优先级/大小排序:

-------------------------------------------------->byte -> short -> char -> int -> long -> float -> double

强制类型转换(优先级高 -> 低)

强制类型转换格式:(类型)变量名,比如:

//强制类型转换  (类型)变量名  优先级高 -> 低
int i = 128;
byte b = (byte)i;//内存溢出问题
System.out.println(i);//128
System.out.println(b);//-128
//由于`byte`数据类型的范围是`-128 ~ 127`,要转换的这个数i超过了`byte`的范围,这也是为什么转换出来的是`-128`

自动类型转换(优先级低 -> 高)

自动类型转换不需要添加任何东西可以直接转换,拿上面的byte b举例:

//自动类型转换  优先级低 -> 高
double d = i;
System.out.println(d);//128.0

变量

  • 局部变量—必须声明和初始化值(方法内)
  • 实列变量— 不自行初始化,则为变量类型默认值,布尔值默认是false,除了基本类型。其余默认值都是null(类里面方法外面,从属于对象)
  • 类变量(static)

常量

  • 关键词—final

  • 常量字符大写

    例:static final double PI = 3.14

运算符

在这里插入图片描述

int b = a++//先赋值,再加1
int c = ++a;//先加1,再赋值

Math类:

幂运算:Math.pow(3,2) //3*2=9


int a = 10;
int b = 20;
System.out.println(""+a+b);//1020
System.out.println(a+b+"");//30

包机制

公司域名倒置作为包名

导入一个包下的所有类 —import com.kuang.base.*;

用户交互Scanner

Next()

public static void main(String[] args) {
    //创建一个扫描对象,用于接收键盘数据
    Scanner scanner = new Scanner(System.in);
    System.out.println("使用next方式接收:");
    //判断用户有没有输入字符串
    if (scanner.hasNext()) {
        String str = scanner.next();
        System.out.println("输出的内容为:" + str);
    }
    //凡是属于IO流的类如果不关闭会一直占用资源
    scanner.close();
}

nextLine()

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    System.out.println("使用nextLine方式接收:");
    //判断用户有没有输入字符串
    if (scanner.hasNextLine()) {
        String str = scanner.nextLine();
        System.out.println("输出的内容为:" + str);
    }
    //凡是属于IO流的类如果不关闭会一直占用资源
    scanner.close();
}
  • next()
    • 一定要读取到有效字符才可以结束输入
    • 对输入有效字符之前遇到的空白,next()方法会将其去掉
    • 只有输入有效字符后才将其后面输入的空白作为结束符
    • next()不能得到带有空格的字符串
  • nextLine()
    • 以Enter作为结束符,即返回输入回车之前所有的字符
    • nextLine()可以获取空白

更多Scanner用法参见[Java 基础——Scanner 类](Java 基础——Scanner 类_java scanner-CSDN博客)

顺序结构

按照顺序一行一一行的执行

选择结构

  • if单选择结构----if
  • if双选择结构-----if…else
  • if多选择结构-----if…else if…else if…else
  • 嵌套if结构
  • switch多选择结构
switch (变量或表达式的值) {
    case1:
        // 当变量或表达式的值等于值1时执行的代码
        break; // 跳出 switch 语句
    case2:
        // 当变量或表达式的值等于值2时执行的代码
        break; // 跳出 switch 语句
    case 值n:
        // 当变量或表达式的值等于值n时执行的代码
        break; // 跳出 switch 语句
    default:
        // 当变量或表达式的值与上面任何一个 case 都不匹配时执行的代码
        // default 是可选的
        break; // 这里的 break 也可以省略,因为它是 switch 语句的最后一个语句
}

循环结构

  • while循环

    int i = 0;
    while (i < 10) {
        System.out.println(i);
        i++;
    }
    
  • do while循环

    int i = 0;
    do {
        System.out.println(i);
        i++;
    } while (i < 10);
    
  • for循环

for (int i = 0; i < 10; i++) {
    System.out.println(i);
}
  • 增强for循环

    int[] numbers = {1, 2, 3, 4, 5};
    for (int number : numbers) {
        System.out.println(number);
    }
    

    break

    break 关键字用于立即退出最内层的循环或者 switch 语句

for (int i = 0; i < 10; i++) {
    if (i == 5) {
        break; // 当 i 等于 5 时,循环会提前结束
    }
    System.out.println(i);
}
// 输出:0 1 2 3 4

continue

continue 关键字用于跳过当前循环的剩余部分

for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) {
        continue; // 跳过偶数
    }
    System.out.println(i);
}
// 输出:1 3 5 7 9

标签(Labels):在 Java 中,标签可以与 breakcontinue 语句一起使用,用于跳出嵌套循环或者继续下一次迭代。

outer: // 标签
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (i == j) {
            continue outer; // 跳过当前外层循环的迭代
        }
        System.out.println(i + ", " + j);
    }
}

return

return 用于从函数中返回值并结束函数的执行。

def find_first_even(numbers):
    for num in numbers:
        if num % 2 == 0:
            return num  # 函数在返回第一个偶数后停止执行
    return None  # 如果没有偶数,返回 None

print(find_first_even([1, 3, 5, 7, 8]))
# 输出 8,因为它是列表中的第一个偶数

方法

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

方法重载

方法重载,指在同一个类中存在多个同名的方法,但它们的参数列表不同。参数列表的不同可以体现在参数的数量、类型或者参数的顺序上。

方法调用

  • 静态方法(static)

    类名.方法名

  • 非静态方法

    ​ 实例化这个类----new Student()

形参和实参

函数或方法定义时使用的参数称为形参,而在调用函数或方法时提供的参数称为实参。

值传递和引用传递

在 Java 中,所有的参数传递都是按值传递的。这意味着不管是一个原始数据类型还是一个对象,传递的都是一个副本。但是,对于对象来说,这个副本是一个指向对象内存地址的引用,而不是对象本身的副本。因此,尽管 Java 中的对象传递在技术上来说是按值传递的,但是因为传递的是引用,所以它的行为更像是按引用传递。

原始数据类型(Primitives):

Java 中的原始数据类型(如 int、float、double、byte、char 等)在传递给方法时是按值传递的。这意味着传递的是数据值的一个副本,而不是数据的内存地址。在方法内部对这个值的任何修改都不会影响到原始数据。

public class Main {
    public static void main(String[] args) {
        int x = 10;
        changeValue(x);
        System.out.println(x);  // 输出:10,x 的值没有改变
    }

    public static void changeValue(int num) {
        num = 20;
    }
}
对象(Objects):

Java 中的对象传递的是对象引用的副本。这意味着,如果在方法内部改变了引用指向的对象的状态,那么原始对象也会受到影响,因为引用指向的是同一个对象。但是,如果在方法内部改变了引用本身,使其指向一个新的对象,那么原始引用不会改变。

public class Main {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("Hello");
        changeObject(sb);
        System.out.println(sb);  // 输出:HelloWorld,sb 对象的内容被改变了
    }

    public static void changeObject(StringBuilder obj) {
        obj.append("World");
    }
}

可变参数

在Java中,可变参数通过在参数类型后面加上三个连续的点(…)来表示,这种参数被称为“变长参数”。

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

public class VarArgsExample {

    // 使用可变参数的方法
    public static int sum(int... numbers) {
        int sum = 0;
        for (int num : numbers) {
            sum += num;
        }
        return sum;
    }

    public static void main(String[] args) {
        // 调用sum方法,传入不同数量的参数
        System.out.println(sum());           // 输出 0
        System.out.println(sum(1));          // 输出 1
        System.out.println(sum(1, 2));       // 输出 3
        System.out.println(sum(1, 2, 3));    // 输出 6
        System.out.println(sum(1, 2, 3, 4)); // 输出 10
    }
}

递归

递归是一种编程技巧,它允许一个函数或方法调用自身。递归通常用于解决可以分解为多个相似或更小问题的问题,特别是那些具有自然递归结构的问题,如树遍历、图形搜索、排序算法(如快速排序和归并排序)以及一些数学计算(如计算阶乘、斐波那契数列等)。

public class Factorial {

    // 使用递归计算阶乘
    public static int factorial(int n) {
        // 递归基:0! = 1
        if (n == 0) {
            return 1;
        } else {
            // 递归调用:n! = n * (n-1)!
            return n * factorial(n - 1);
        }
    }

    public static void main(String[] args) {
        int result = factorial(5); // 计算5的阶乘
        System.out.println("5! = " + result); // 输出 5! = 120
    }
}

数组

  • 数组是相同类型数据的有序集合
  • 数组元素通过索引访问,数组索引从0开始
  • 获取数组长度----arrays.length
  • 数组长度是确定的,不可改变的,如果越界,报错ArrayIndexOutofBounds
int[] nums ;//int nums[]不推荐
nums = new int[10];
//等同于  int[] nums = new int[10];
nums[0] = 1;//赋值 

内存分析

在这里插入图片描述

三种初始化

  • 静态初始化

    int[] numbers = {1, 2, 3, 4, 5}; // 静态初始化一个整型数组
    String[] words = {"Hello", "World"}; // 静态初始化一个字符串数组
    
    
  • 动态初始化

    int[] dynamicNumbers = new int[size]; // 动态初始化一个整型数组
    
    
  • 默认初始化

默认初始化发生在数组被声明但没有显式地赋予初始值时。在这种情况下,数组中的元素会被自动赋予该数据类型的默认值。

  • 整型数组的默认值是0。
  • 浮点型数组的默认值是0.0。
  • char类型默认值是 ‘\u0000
  • 布尔型数组的默认值是false。
  • 引用类型数组的默认值是null。
int[] defaults = new int[5]; // 默认初始化一个整型数组
// 此时,defaults 数组中的元素自动被初始化为 {0, 0, 0, 0, 0}

数组的使用

遍历数组

// 使用for循环
for (int i = 0; i < numbers.length; i++) {
    System.out.println(numbers[i]);
}

// 使用for-each循环 快捷键:numbers.for +enter
for (int number : numbers) {
    System.out.println(number);
}

多维数组

声明和初始化

// 声明一个二维整型数组
int[][] matrix;

// 动态初始化二维数组
matrix = new int[3][4]; // 3行4列的矩阵

// 静态初始化二维数组
int[][] staticMatrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// 三维数组的声明和初始化
int[][][] threeDimensionalArray = new int[2][3][4]; // 2个3行4列的矩阵组成的数组

遍历多维数组

// 遍历二维数组
for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
        System.out.print(matrix[i][j] + " ");
    }
    System.out.println();
}

数组的长度

int rows = matrix.length; // 获取行数
int cols = matrix[0].length; // 获取第一列的列数(假设所有行的列数相同)

Arrays类

排序

  • static void sort(int[] a): 对指定的整数数组进行排序。
  • static void sort(Object[] a): 根据元素的自然顺序对指定对象数组进行排序。
  • static <T> void sort(T[] a, Comparator<? super T> c): 根据指定比较器产生的顺序对指定对象数组进行排序。

搜索

—通过binarySearch方法对排好序的数组进行二分查找操作
  • static int binarySearch(int[] a, int key): 在指定的整数数组中搜索指定的值。
  • static int binarySearch(Object[] a, Object key): 在指定的对象数组中搜索指定的值。
  • static int binarySearch(Object[] a, Object key, Comparator<? super T> c): 根据指定比较器搜索指定对象数组中的指定值。

填充

  • static void fill(int[] a, int val): 将指定的整数值分配给指定整数数组的每个元素。
  • static void fill(Object[] a, Object val): 将指定的对象引用分配给指定对象数组的每个元素。
  • static void fill(int[] a, int fromIndex, int toIndex, int val) ;指定开始和结束索引的填充

复制

  • static int[] copyOf(int[] original, int newLength): 复制指定的数组,截断或填充空位(如有必要),以使副本具有指定的长度。
  • static <T> T[] copyOf(T[] original, int newLength): 复制指定的数组,截断或填充空位(如有必要),以使副本具有指定的长度。

比较

  • static boolean equals(int[] a, int[] a2): 如果两个指定的整数数组相等,则返回 true
  • static boolean equals(Object[] a, Object[] a2): 如果两个指定的对象数组相等,则返回 true

转换为字符串

  • static String toString(int[] a): 返回一个表示指定数组内容的字符串。
  • static String toString(Object[] a): 返回一个表示指定数组内容的字符串。
import java.util.Arrays;

public class ArraysExample {
    public static void main(String[] args) {
        int[] numbers = {3, 1, 4, 1, 5, 9};

        // 排序数组
        Arrays.sort(numbers);
        System.out.println(Arrays.toString(numbers)); // 输出排序后的数组
        // 结果: [1, 1, 3, 4, 5, 9]

        // 搜索元素
        int index = Arrays.binarySearch(numbers, 5);
        System.out.println("Index of 5: " + index); // 输出5的索引
             // 结果: Index of 5: 4

        // 填充数组
        Arrays.fill(numbers, 0);
        System.out.println(Arrays.toString(numbers)); // 输出填充后的数组
            // 结果: [0, 0, 0, 0, 0, 0]

        // 复制数组
        int[] copiedNumbers = Arrays.copyOf(numbers, 10);
        System.out.println(Arrays.toString(copiedNumbers)); // 输出复制后的数组
           // 结果: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

        // 比较数组
        int[] numbers2 = {0, 0, 0, 0, 0, 0};
        boolean areEqual = Arrays.equals(numbers, numbers2);
        System.out.println("Are arrays equal? " + areEqual); // 输出数组比较结果
         // 结果: Are arrays equal? false
    }
}

冒泡排序

算法步骤

  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个。
  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后已经排序好的元素。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

代码示例

import java.util.Arrays;
public class demo01 {
    public static void main(String[] args) {
        int[] numbers = {64, 34, 25, 12, 22, 11, 90};
        System.out.println(Arrays.toString(bubbleSort(numbers)));
    }
    public static int[] bubbleSort(int[] arr) {
        int n = arr.length;
        for (int i = 0; i < n - 1; i++) {
            boolean flag = false;
            for (int j = 0; j < n - i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    // 交换 arr[j] 和 arr[j+1]
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                    flag = true;
                }
            }
            if(flag == false){
                break;
            }
        }
        return arr;
    }
}

稀疏数组

在这里插入图片描述

面向对象

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

  • 抽象
  • 三大特性
    • 封装
    • 继承
    • 多态

类与对象的关系:

  • 类是对象的模板,对象是类的实例。
  • 类定义了对象的结构和行为,而对象则实现了这些定义,并具有具体的数据。

构造器

构造器的特点:

  • 构造器的名称必须与类名完全相同。
  • 构造器没有返回类型,也不可以使用 void 关键字。
  • 构造器在对象创建时自动调用,且只调用一次。
  • 构造器可以重载,即可以有多个构造器,它们之间在参数数量或类型上有所不同。
public class Person {
    private String name;
    private int age;

    // 默认无参构造器,没有参数,将 `name` 初始化为 “Unknown”,`age` 初始化为 0。
    public Person() {
        name = "Unknown";
        age = 0;
    }

    // 带参数的构造器,用于同时初始化 `name` 和 `age`。
    public Person(String n, int a) {
        name = n;
        age = a;
    }

    // 构造器的重载,用于初始化 `name`,并将 `age` 默认设置为 18。
    public Person(String n) {
        name = n;
        age = 18; // 默认年龄为18岁
    }

    public void introduce() {
        System.out.println("My name is " + name + " and I am " + age + " years old.");
    }
}
注意:一旦定义有参构造,无参构造必须显示定义

封装

-----高内聚低耦合

属性私有,方法公有

public class Person {
    private String name; // 私有属性,外部不能直接访问
    private int age;     // 私有属性,外部不能直接访问

    // 公共构造器
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 公共方法,用于获取名字
    public String getName() {
        return name;
    }

    // 公共方法,用于设置名字
    public void setName(String name) {
        this.name = name;
    }

    // 公共方法,用于获取年龄
    public int getAge() {
        return age;
    }

    // 公共方法,用于设置年龄
    public void setAge(int age) {
        if (age >= 0) { // 在设置年龄前进行校验
            this.age = age;
        } else {
            System.out.println("年龄不能为负数");
        }
    }
}

继承

快捷键:Ctrl +H---------查看继承关系

在Java在所有类,都默认直接或间接的继承Object类

  • 继承本质是对某一批类的抽象
  • extends关键词,子类对父类的扩展
  • Java中只有单继承,没有多继承
  • 子类和父类之间,具有“is a”关系

super详解

  • this 用于引用当前对象,可以用来访问当前对象的属性和方法,也可以用来调用当前类的构造器。
  • super 用于引用父类,可以用来访问父类的属性和方法,也可以用来调用父类的构造器。

调用父类构造器,super()必须要在第一行

相关详细内容参见

this和super(由浅入深、超详细,建议收藏)_super和this-CSDN博客

方法重写

方法重写是面向对象多态性的一个重要方面,它允许子类以特定的方式实现父类的行为,从而提供更丰富的功能和灵活性。

方法重写的条件:

  1. 继承关系:子类必须继承自父类。
  2. 方法签名:子类中的方法必须与父类中被重写的方法具有相同的方法名和参数列表(方法签名)。
  3. 返回类型:子类中的方法的返回类型必须与父类中被重写的方法的返回类型相同,或者是被重写方法返回类型的子类型(covariant return type)。
  4. 访问权限:子类中方法的访问权限不能比父类中被重写的方法的访问权限更低(例如,如果父类方法是public,子类方法不能是private)。
  5. 静态与非静态:子类重写的方法必须与父类中被重写的方法具有相同的静态或非静态属性(不能将父类的实例方法重写为静态方法,反之亦然)。

静态方法-----与方法重写无关

class Animal {
    public static void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    // 这不是重写,而是方法隐藏
    public static void makeSound() {//静态方法
        System.out.println("Dog barks");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal.makeSound(); // 输出 "Animal makes a sound"
        Dog.makeSound(); // 输出 "Dog barks"
    }
}
//方法调用只与左边静态类有关

非静态方法------重写

class Animal {
    public void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    @Override//方法重写
    public void makeSound() {
        System.out.println("Dog barks");
    }
}

class Main {
    public static void main(String[] args) {
        Animal myAnimal01 = new Animal();
        myAnimal01.makeSound(); // 输出 "Animal makes a sound"

        Dog myDog = new Dog();
        myDog.makeSound(); // 输出 "Dog barks"

        Animal myAnimal02 = new Dog();
        myAnimal02.makeSound(); // 输出 "Dog barks"
    }
}
//方法重写,看new的对象

多态

示例1:

Dog myDog = new Dog();
Animal myAnimal02 = new Dog();
  • ​ 子类能调用的方法都是自己的或者继承父类的
  • ​ 父类可以指向子类类型,但不能调用子类的独有方法;调用子类独有方法,可将父类型强转为子类型

示例2:

class Animal {
    public void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Cat meows");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myAnimal1 = new Dog();
        Animal myAnimal2 = new Cat();

        myAnimal1.makeSound(); // 输出 "Dog barks"
        myAnimal2.makeSound(); // 输出 "Cat meows"
    }
}

myAnimal1 和 myAnimal2 都是 Animal 类型的引用,但它们分别指向了一个 Dog 对象和一个 Cat 对象。当调用 makeSound() 方法时,会根据实际对象的类型来执行相应的方法,这就是运行时多态。

instanceof和类型转化

instanceof

instanceof是一个关键字,用于检查一个对象是否是一个特定类的实例

class Animal {}

class Dog extends Animal {}

class Cat extends Animal {}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();
        
        System.out.println(animal1 instanceof Animal);  // true
        System.out.println(animal1 instanceof Dog);     // true
        System.out.println(animal1 instanceof Cat);     // false
        
        System.out.println(animal2 instanceof Animal);  // true
        System.out.println(animal2 instanceof Dog);     // false
        System.out.println(animal2 instanceof Cat);     // true
    }
}

对象的类型转化

  1. 向上转型(Upcasting):向上转型是指将一个子类对象转换为其父类类型的过程。由于子类对象可以被看作是父类对象,所以向上转型是安全的,不会导致数据丢失或溢出。在向上转型中,父类引用指向了子类对象。
class Animal {}
class Dog extends Animal {}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Animal animal = dog; // 向上转型
    }
}
  1. 向下转型(Downcasting):向下转型是指将一个父类类型的对象转换为其子类类型的过程。向下转型需要显式地进行,使用强制类型转换操作符。由于父类对象并不具备子类特有的属性和行为,所以在向下转型时需要确保对象实际上是子类对象,否则可能会导致ClassCastException异常。
class Animal {}
class Dog extends Animal {}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();
        Dog dog = (Dog) animal; // 向下转型,强制转化
    }
}

需要注意的是,在进行向下转型时,必须确保对象的实际类型是目标类型,否则会抛出ClassCastException异常。为了避免此类异常,可以使用instanceof运算符来检查对象的类型,再进行向下转型。

if (animal instanceof Dog) {
    Dog dog = (Dog) animal; // 向下转型
}

static详解

static是一个关键字,用于声明类级别的成员,也就是说,被static修饰的成员属于类本身,而不是类的实例。

  • 静态变量(类变量):使用static修饰的变量称为静态变量,也叫类变量。所有该类的对象共享相同的静态变量,它们的值在所有实例之间是共享的。静态变量在类加载时初始化,并且只会被初始化一次。静态变量可以通过类名直接访问,无需创建类的实例。
class Student {
    private static int age; // 静态变量
    private double score;//非静态变量

    public static void main(String[] args) {
        Student student = new Student();
        System.out.println(Student.age);//静态变量可以通过类名直接访问
        System.out.println(student.score);
        System.out.println(student.age);
    }
}
  • 静态方法:使用static修饰的方法称为静态方法。静态方法可以直接通过类名调用,无需创建类的实例。静态方法只能访问静态变量和调用静态方法,无法访问非静态的实例变量和实例方法。
class MyClass {
    static void staticMethod() {
        System.out.println("This is a static method.");
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass.staticMethod(); // 调用静态方法
    }
}

  • 静态代码块:静态代码块在类被加载时执行,仅执行一次。它常用于静态变量的初始化或执行一些静态操作。
class MyClass {
    {
        //代码块(匿名代码块,赋初始值)
    }
    static {
        System.out.println("Static block is executed."); // 静态代码块
    }
}
  • 静态内部类:使用static关键字修饰的内部类称为静态内部类。静态内部类与外部类的实例无关,可以直接通过外部类的类名访问,无需先创建外部类的实例。
class OuterClass {
    static class StaticInnerClass {
        void display() {
            System.out.println("Static Inner Class.");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();
        inner.display(); // 访问静态内部类
    }
}

抽象类

关键词:extends

  • 抽象方法只有方法签名(方法名、返回类型、参数列表),没有具体实现(没有方法体)。
  • 抽象方法必须在抽象类中声明。
  • 不能new 这个抽象类,只能靠子类去实现它:约束!
  • 抽象类可以写非抽象方法,构造器,静态成员
  • 子类继承抽象类时,必须实现所有的抽象方法,除非子类也是抽象类。
abstract class Animal {//抽象类
    // 抽象方法
    abstract void eat();

    // 非抽象方法
    void sleep() {
        System.out.println("Zzz...");
    }
}

class Dog extends Animal {
    @Override//重写抽象方法
    void eat() {
        System.out.println("Dog is eating.");
    }
}

public class Main {
    public static void main(String[] args) {
        // 不能直接实例化抽象类
        // Animal animal = new Animal();
        
        Dog dog = new Dog();
        dog.eat();
        dog.sleep();
    }
}

接口

定义:接口(Interface)是一种定义行为的方式,它是完全抽象的,用来声明方法但不包含方法的实现。 接口使用interface关键字来定义,其中的方法默认都是公共的(public)和抽象的(abstract),即使你不明确写出这些修饰符。

  • 普通类:只有具体实现
  • 抽象类:具体实现和规范(抽象方法)都有
  • 接口:只有规范!自己无法写方法
public interface MyInterface {
    //常量~public static final
    int AGE = 99;
    
    void method1(); // 抽象方法
    default void method2() {
        // 默认方法的实现
    }
    static void method3() {
        // 静态方法的实现
    }
}
public class MyClass implements MyInterface {
    @Override
    public void method1() {
        // 实现接口中的抽象方法
    }
}

多重继承: Java不支持类的多重继承,但可以通过实现多个接口来达到类似的效果,从而实现多重继承的功能。

public class MyClass implements A ,B{   
    
}

接口与抽象类的区别:

  • 抽象类可以有构造方法,可以有实例变量,而接口不能有构造方法。
  • 类只能单继承(extends)一个抽象类,但可以实现多个接口(implements)。
  • 抽象类可以有抽象方法和非抽象方法,接口中的方法默认都是抽象的(除非是默认方法或静态方法)。

内部类

  1. 成员内部类: 成员内部类是定义在另一个类内部的类。它具有对外部类成员的完全访问权限,并且可以访问外部类的所有成员,包括私有成员。

    class OuterClass {
        private int outerVar;
    
        class InnerClass {
            void display() {
                System.out.println("Outer variable: " + outerVar);
            }
        }
    }
    
    // 实例化成员内部类
    OuterClass outerObj = new OuterClass();
    OuterClass.InnerClass innerObj = outerObj.new InnerClass();
    
  2. 静态内部类: 静态内部类是定义在另一个类内部的静态类。它不依赖于外部类的实例,并且不能直接访问外部类的非静态成员。

    class OuterClass {
        static class StaticInnerClass {
            void display() {
                System.out.println("Static Inner Class");
            }
        }
    }
    
    // 实例化静态内部类
    OuterClass.StaticInnerClass staticInnerObj = new OuterClass.StaticInnerClass();
    
  3. 局部内部类: 局部内部类是定义在方法或作用域内部的类。它只在所定义的方法或作用域内可见,并且不能使用访问修饰符 public、protected 或 private。

    class OuterClass {
        void someMethod() {
            class LocalInnerClass {
                void display() {
                    System.out.println("Local Inner Class");
                }
            }
    
            // 实例化局部内部类
            LocalInnerClass localInnerObj = new LocalInnerClass();
            localInnerObj.display();
        }
    }
    
  4. 匿名内部类: 匿名内部类是没有显式名称的内部类,通常用于创建一个临时的类实例,通常是接口或抽象类的实现。

    interface MyInterface {
        void display();
    }
    
    class OuterClass {
        void someMethod() {
            // 匿名内部类实现 MyInterface 接口
            MyInterface anonymousInnerObj = new MyInterface() {
                @Override
                public void display() {
                    System.out.println("Anonymous Inner Class");
                }
            };
    
            anonymousInnerObj.display();
        }
    }
    

异常

在这里插入图片描述

捕获和抛出异常

关键词:try, catch,finally,throw,throws


try-catch-finally

用于捕获和处理异常,并在不管是否发生异常的情况下执行清理操作。-----------快捷键:Ctrl +Alt +T

基本语法
try {
    // 可能会抛出异常的代码块
    //ExceptionType即上图异常类
} catch (ExceptionType1 e1) {
    // 捕获并处理 ExceptionType1 类型的异常
} catch (ExceptionType2 e2) {
    // 捕获并处理 ExceptionType2 类型的异常
} finally {
    // 无论是否发生异常,都会执行的代码块,用于清理资源或执行其他必要的操作,可不要finally
}
//ExceptionType1类型要小于ExceptionType2类型,否则报错
示例:
public class TryCatchFinallyExample {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3};
        int x;

        try {
            // 尝试执行可能会抛出异常的代码
            x = numbers[3]; // 这里会抛出数组越界异常
            System.out.println("This line will not be executed because an exception is thrown.");
        } catch (ArrayIndexOutOfBoundsException e) {
            // 捕获并处理异常
            System.out.println("Caught an exception: " + e.getMessage());
        } finally {
            // 这段代码无论是否抛出异常都会执行
            System.out.println("This is the finally block, always executed.");
        }

        // 程序继续执行
        System.out.println("Program continues after try-catch-finally block.");
    }
}


throw

用于显式抛出一个异常。当你在代码中遇到某种错误情况,希望程序停止当前执行流程并传递错误信息时,就可以使用 throw 语句。

基本语法

Java

throw new ExceptionType(message);
  • ExceptionType 是你想要抛出的异常类型的名称,它必须是 Throwable 类或其子类的实例。

  • message 是一个可选的字符串参数,用于描述发生异常的具体情况,可不写。

示例:
public class Main {
    public static void main(String[] args) {
        try {
            throw new IllegalArgumentException("年龄不能为负数");
        } catch (IllegalArgumentException e) {
            System.out.println("捕获到异常: " + e.getMessage());
        }
    }
}

throws

用于声明方法可能会抛出的一个或多个异常类型。它告知调用者,调用这个方法时可能会遇到某些异常情况,需要调用者负责处理这些潜在的异常。

基本语法

在方法签名后使用 throws 关键字,后跟一个或多个异常类名,这些类必须是 Throwable 的子类:

public returnType methodName() throws ExceptionType1, ExceptionType2 {
    // 方法体
}
示例

Java

public void readFile(String fileName) throws FileNotFoundException, IOException {
    File file = new File(fileName);
    FileReader fr = new FileReader(file);
    BufferedReader br = new BufferedReader(fr);    
    // 假设此处有读取文件的逻辑
    br.close();
}

自定义异常

自定义异常是指根据特定需求创建的异常类,这些类通常继承自现有的异常类,如 Exception 类或其子类

1. 定义自定义异常类

首先,你需要定义一个新的类,让它继承自现有的异常类。最常见的是直接继承 Exception 类或它的子类,比如 RuntimeException。通常,自定义异常类至少包含一个无参构造函数和一个带有详细错误信息的构造函数

public class MyCustomException extends Exception {
    // 无参构造函数
    public MyCustomException() {
        super();
    }

    // 带有详细错误信息的构造函数
    public MyCustomException(String message) {
        super(message);
    }

    // 如果需要,还可以添加带有原因 throwable 的构造函数
    public MyCustomException(String message, Throwable cause) {
        super(message, cause);
    }
}

2. 抛出自定义异常

在需要的地方,你可以像抛出标准异常一样,使用 throw 关键字抛出自定义异常。

public void someMethod(String input) throws MyCustomException {
    if (input == null || input.isEmpty()) {
        throw new MyCustomException("输入不能为空");
    }
    // 其他逻辑...
}

3. 捕获和处理自定义异常

调用可能抛出自定义异常的方法时,你需要使用 try-catch 语句块来捕获并处理这个异常。

public static void main(String[] args) {
    try {
        someMethod(null);
    } catch (MyCustomException e) {
        System.out.println("捕获到自定义异常: " + e.getMessage());
    }
}
  • 26
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值