JAVA学习 第二部分

前言

针对 java 语言的 安装、介绍;语法、流程控制、数组、javaBean 等知识,可以查询上一个内容

Java学习 第一部分

API 介绍

API(Application Programming Interface)应用程序编程接口:

是一组定义了软件组件之间交互的规范和约定的接口。
在软件开发中,API允许不同的软件系统、服务或组件之间进行通信和交互,从而实现数据传输、功能调用等操作。

API可以被视为一个软件模块提供给其他程序员或开发者使用的一组工具和规则。
通过API,开发者可以利用已经实现的功能,而不必了解其内部实现细节。
这种抽象层有助于简化开发流程,提高代码的模块化和可重用性。

在 Java 的包中,java.lang 为核心包,不需要手动导入;其他的则需要 import 导入;
更多内容可以下载下方手册查阅文档

案例

Java
package cn.smmcat.test;

import java.util.Scanner;

public class ScannerDemo {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        sc.nextInt(); // 从输入中获取下一个整数值并返回
        sc.next(); // 从输入中获取下一个单词(以空格分隔)并返回
        sc.nextLine(); // 从输入中获取一整行文本(包括空格)并返回
    }
    
    /*
    sc.nextLine() 会监听回车;
    而当 sc.nextInt() 使用后触发回车,会导致
    sc.nextLine() 准备时提前结束录入;
    
    虽然 sc.next() 只会录入空格前的内容 可能导致录入不完整
    但是能保证每次都能录入数据;
    
    而 sc.nextLine() 会捕获回车,并不会传给下个接收回车的  sc.nextLine() 方法;
    因此仅 sc.nextLine() 多次调用是可行的。
    */
}
String

StringJava 当中的一个核心类,任何双引号的字符串都是这个类的实例对象

介绍

特点

String 类多了一种创建实例的方式,只要写双引号就已经是该实例的对象;字符串一旦被创建就不可更改;
而变量若想修改字符串,只能使用替换的方式;

Java
// 创建 String 类
String a = "abc";
String b = new String("abc");

// 因为是对象 所以也有类方法
a.toUpperCase(); // ABC

String 类的字符串虽然不可以改变,但是它是可以被共享的

在Java中,对于字符串常量的处理是通过字符串池(String Pool)来实现的。
当创建一个字符串常量时,Java会首先检查字符串池中是否已经存在相同内容的字符串,
如果存在,则返回已存在的字符串的引用,而不会创建新的字符串对象。

字符串常量池在 jdk7 版本前,是放在方法区,在 jdk7 版本开始后,它才被放到 堆内存中

Java
// 使用双引号创建字符串的情况
String a = "abc";
String b = "abc";
String c = new String("abc"); // abc 

// 字符串常量池会共享相同内容的字符串对象
System.out.println(a == b); // true,因为a和b指向同一个字符串对象

//  由于 new 会开辟新地址,该操作相当于创建传入字符串参数的副本,地址不同返回 false
System.out.println(a == c); // false 

其他

char[] a = {'a','b','c'} 输出结果等同于 String a = "abc"

常见构造方法

字符串类可通过字面量或构造方法两种方式去创建,字符串底层实际上是字节类型 char 的数组

%title插图%num
Java
public static void main(String[] args) {
    String a = new String();
    char[] s1 = {'a', 'b', 'c'}; // 空白
    String b = new String(s1); // abc
    String c = new String("abc"); // abc 
    
    System.out.println(c); // abc 打印的是内容不是地址
    
    /*
    在Java中,当调用System.out.println()打印一个对象时,
    实际上会调用该对象的toString()方法来获取要打印的内容。
    对于String类来说,toString()方法已经被重写,
    使其返回字符串对象的内容而不是默认的对象引用地址。
    */
}

字面量创建和构造方法创建的区别

放置维护的位置不同,字面量创建的字符串会放置在 字符串常量池 中,
而 构造方法 new 出来的字符串会在堆内存中有自己独立的一块地址

字符串拼接的逻辑

当字符串执行拼接操作时,Java 在必要时会开辟地址创建一个 StringBuilder 负责完成拼接;
并使用 toString 方法转为 String 生成一个新的字符串对象,这个对象会存储在堆中,
且如果这个字符串是常量的话,可能会存储在字符串常量池中

%title插图%num

常量 (字面量创建的就是常量) 拼接,Java有常量优化机制,会编译时预先拼接并尝试存在常量池中;当常量池存在则引用相同地址,因此会有如下结果:

Java
String a = "abc";
String b = "a"  + "b" + "c"; // 会在编译时变成 "abc"
System.out.println(a == b); // true
字符串比较

equals

使用 equals() 方法对字符串内容与另一个字符串的内容进行比较,而 Java 中的 == 只是比较地址,不适用于内容比较

equals()String 类中的方法,使用 .equals() 即调用

Java
String a = "abc";
String b = new String("abc");
System.out.println(a.equals(b)); // 内容一致 返回 true

equalsIgnoreCase

使用 equalsIgnoreCase() 方法对字符串的内容与另一个字符串的内容进行比较,不考虑大小写 (仅对 26 个英文字母)

Java
String a = "abc";
String b = new String("AbC");
System.out.println(a.equals(b)); // false
System.out.println(a.equalsIgnoreCase(b)); // true
字符串遍历

使用 toCharArray()

通过 String 类方法中的 toCharArray() 对字符串转换成一个新的字符 char[] 数组并返回这个数组,实现逐个打印和计算长度

Java
String str = "Hello World";
char[] chars = str.toCharArray();
for (int i = 0; i < chars.length; i++) {
    System.out.println(chars[i]); // h e l l o  w o r l d
}

使用 charAt() + length()

通过 String 类方法中的 charAt() 可返回指定索引处 char 的值,搭配 forlength() 获取字符串长度,实现获取字符串的遍历;

Java
String str = "Hello World";
for (int i = 0; i < str.length(); i++) {
    System.out.println(str.charAt(i));
}

案例:实现分析字符串中 大写、小写、数字出现次数

字符都有一个 ascll 表的对照,通过判断是否在对应内容的范围区间,实现分析

Java
Scanner sc = new Scanner(System.in);
System.out.println("请输入字符串,进行分析:");
String str = sc.nextLine();
// 计数
int upCount = 0;
int lowCount = 0;
int numCount = 0;
// 转为字符数组 方便分析
char[] chars = str.toCharArray();
for (int i = 0; i < chars.length; i++) {
    if (chars[i] >= 'a' && chars[i] <= 'z') {
        lowCount++;
    } else if (chars[i] >= 'A' && chars[i] <= 'Z') {
        upCount++;
        // 注意数字区间需要使用 '' 字符标识,否则会误解数字对应的 ascll 对应的字符
    } else if (chars[i] >= '0' && chars[i] <= '9') {
        numCount++;
    }
}
System.out.println("您输入的:\n大写字母" + 
upCount + "\n小写字母" + 
lowCount + "\n数字" + numCount + "");

效果演示

%title插图%num
字符串截取

使用 substring(beginIndex,endIndex?)

substring(int beginIndex); 根据传入的索引开始截取,截取到字符串的末尾,并返回新的字符串对象
substring(int beginIndex , int endIndex);根据传入的索引开始截取,截取到 endIndex 的位置之前,并返回新的字符串对象 (包含头,不含尾)

截取出来的内容是作为新的字符串返回,需要通过变量去接收返回值

Java
String str = "smmmaxpro";
String str2 = str.substring(3); // maxpro
String str3 = str.substring(3,6); // max
System.out.println(str2);
System.out.println(str3);

案例:手机号隐藏四位

通过截取前3位字符串,和第 7 (前面 3 位 + 4 个符号位) 位直到末尾的字符串,进行拼接实现

Java
public static void main(String[] args) {
    MobileNumberHide("17650003221");
}
public static void MobileNumberHide(String mobile) {
    String before = mobile.substring(0, 3);
    String after = mobile.substring(7);
    System.out.println(before + "****" + after); // 176****3221
}
字符串替换

replace(target,replacement)

使用 replace(charSequence target , charSequence replacement); 将所有的旧值 target 替换新值 replacement,并返回新字符串对象

Java
String str = "学习真快乐,真的很快乐";
str = str.replace("快乐", "痛苦");
System.out.println(str); // 学习真痛苦,真的很痛苦

replaceFiest(target,replacement)

与上面代码类似,但是只是正则替换首个匹配成功的字符串

Java
String str = "学习真快乐,真的很快乐";
str = str.replaceFirst("快乐", "痛苦");
System.out.println(str); // 学习真痛苦,真的很快乐
字符串切割

split(regex,limit?)

根据传入的字符串正则作为规则来切割当前的字符串并返回一个字符串 String[] 的数组

传入的为正则表达式,因此部分特殊意义的符号要实现规则需要进行 \\ 转义

Java
String str = "今天开始,我要,自己,上厕所";
String[] strs = str.split(""); // {"今天开始","我要","自己","上厕所"}

String ip = "192.168.1.1"
String[] ips = str.split("//.");  // 特殊符号需要转义 {"192","168","1","1"}
StringBuilder

StringBuilder 是 Java 中用于处理字符串的一个类,它可以解决在构建大量字符串时遇到的性能问题

说明

StringBuilder 类提供了可变的字符串序列,允许在字符串中进行添加、插入、删除和修改等操作,
而不会创建新的字符串对象。这样可以避免频繁创建新的字符串对象,提高字符串操作的效率

主要优点包括:

  1. 性能优化StringBuilder 的可变性避免了频繁创建新的字符串对象,提高了字符串操作的性能。
  2. 方便的操作方法StringBuilder 提供了丰富的方法来进行字符串的添加、插入、删除和修改,使得操作字符串更加方便和灵活。
  3. 线程不安全StringBuilder 是非线程安全的,如果在多线程环境中使用,可能需要考虑使用 StringBuffer 类,它是线程安全的版本

StringBuilder 是一个可变的字符序列,它是字符串缓冲区;
可理解为容器,这个容器可以存储任意数据类型,但是存到容器时会转为字符串

提升效率的展示

字符串拼接
public static void main(String[] args) {
    // 获取开始时间戳
    long start = System.currentTimeMillis();
    String s1 = "";
    for (int i = 0; i < 100000; i++) {
        s1 += i;
    }
    // 获取结束时间戳
    long end = System.currentTimeMillis();
    System.out.println("运行完成,用时"
     + (end - start) + "毫秒"); // 运行完成,用时4384毫秒
}
StringBuilder
public static void main(String[] args) {
    long start = System.currentTimeMillis();
    StringBuilder stringBuilder = new StringBuilder();
    for (int i = 0; i < 100000; i++) {
        stringBuilder.append(i);
    }
    long end = System.currentTimeMillis();
    System.out.println("运行完成,用时" + (end - start) + "毫秒"); // 运行完成,用时5毫秒
}

可以存储任意类型数据,转字符串

StringBuilder 可以添加任意类型的数据,但当进到 StringBuilder 容器中时都会转为字符串

Java
StringBuilder sb = new StringBuilder();
sb.append(233); // 插入整数
sb.append(3.14); // 插入小数
sb.append(true); // 插入布尔值
sb.append(''); // 插入字符
sb.append("家伙"); // 插入字符串
System.out.println(sb); // 2333.14true好家伙
构造方法、类方法

说明

StringBuilder() 构造一个不带任何字符的字符串生成器,其初始容量为 16 个字符,超出自动扩容
StringBuilder(int capacity) 构造一个不带任何字符的字符串生成器,其初始容量由 capacity 参数指定
StringBuilder(String str) 构造一个字符串生成器,并初始化为指定的字符串内容

Java
// StringBuilder 的构造方法允许传值 作为初始值
StringBuilder sb = new StringBuilder("hello world");
System.out.println(sb); // hello world

常用类方法

%title插图%num

append()

将指定的数据添加到字符串的末尾,并返回 StringBuilder 它本身的对象

Java
StringBuilder sb = new StringBuilder("这是一句话,");
StringBuilder sb2 = sb.append("这是第二句话,"); // 末尾插入内容
StringBuilder sb3 = sb.append("这是末尾"); // 末尾插入内容
System.out.println(sb2 == sb3); // true

// 返回的是对象它自身,改动后,其他都会影响
System.out.println(sb2); // 这是一句话,这是第二句话,这是末尾
System.out.println(sb3); // 这是一句话,这是第二句话,这是末尾

因此,StringBuilder 类 它是可支持链式编程的

Java
StringBuilder sb = new StringBuilder();
sb.append("这是一句话,").append("这是第二句话,").append("这是末尾");
System.out.println(sb); // 这是一句话,这是第二句话,这是末尾

substring()

StringBuilder 类的字符串进行范围截取,类似字符串的 substring 方法,它返回一个新的字符串对象

Java
StringBuilder sb = new StringBuilder("smmmax");
String str = sb.substring(1); // mmmax

reverse()

StringBuilder 类的缓冲区的内容进行反转,并返回 StringBuilder 它本身的对象

缓冲区:StringBuilder 类内部维护了一个字符数组(char array)作为缓冲区(buffer)

Java
StringBuilder sb = new StringBuilder("你好");
StringBuilder sb1 = sb.reverse();
System.out.println(sb); // 好你
System.out.println(sb1); // 好你

lenght()

返回 StringBuilder 类缓冲区内容的字符文本总长度

Java
StringBuilder sb = new StringBuilder("你好");
int len = str.length();
System.out.println(len); // 2

toString()

StringBuilder 类中缓冲区内容,以String 类型的字符串返回;

StringBuilder 类型转为 String 类型的目的是因为 String 类提供了其他操作字符的方法:例如 split()

Java
StringBuilder sb = new StringBuilder("hello,world");
String str2 = sb.toString(); // hello,world
String[] arr = sb.toString().split(","); // {"hello","world"}
StringBuffer

StringBuilderStringBuffer 都是用来处理字符串的类,方法一致;它们之间的主要区别在于线程安全性和性能

StringBufferJDK1 版本首次出现,StringBuilderJDK1.5 版本首次出现

  1. 线程安全性
    • StringBuilder:StringBuilder 是非线程安全的,也就是说在多线程环境下,
      如果多个线程同时访问同一个 StringBuilder 对象,可能会导致数据不一致的问题。
    • StringBuffer:StringBuffer 是线程安全的,它的方法是同步的,因此可以安全地在多线程环境中使用。
  2. 性能
    • StringBuilder:StringBuilder 的性能比 StringBuffer 更好,因为它不是线程安全的。
      由于不需要进行同步操作,StringBuilder 的操作速度更快。
    • StringBuffer:由于 StringBuffer 是线程安全的,它的性能可能比 StringBuilder 稍差一些,
      因为需要进行同步操作以确保线程安全。

一般来说,如果代码是在单线程环境下运行,推荐使用 StringBuilder,因为它性能更好。
如果代码需要在多线程环境下运行,或者需要线程安全性,那么应该使用 StringBuffer。

在 JAVA 的 api 中,有线程安全的效率就偏低,没有线程安全的,效率就高

ArrayList

ArrayList 是 集合,集合是一种容器,用来装数据的,类似于数组

说明

特性

ArrayList 类在构建的时候会创建一个初始长度为 10 的空列表,
当需要添加的内容溢出时,会将原数组扩容 1.5 倍;之后将原数组数据拷贝到新数组中,并将需要添加的元素添加进数组;以此类推

集合和数组的选择

数组:存储的元素个数固定不变
集合:存储的元素个数经常发生改变

创建和添加

arrayList 类虽然默认有 10 的长度,但是只需要关心自己的真实数据有几个即可,默认可以插入任意类型的数据

new 创建

由于并不存在核心包中,需要 import 引入对应包

Java
// 需要引入对应包
import java.util.ArrayList;
public class demo1 {
    public static void main(String[] args) {
        // 创建 ArrayList 类
        ArrayList arr = new ArrayList();
    }
}

add 插入

向集合尾部增加一位成员,插入一个元素

Java
import java.util.ArrayList;

public class demo1 {
    public static void main(String[] args) {
        ArrayList arr = new ArrayList();
        arr.add("a");
        arr.add(100);
        arr.add(0.01);
        arr.add(true);
        System.out.println(arr); // [a, 100, 0.01, true]
    }
}

泛型

当不做类型限制时,操作集合很容易出现意想不到的问题,需要 <> 做类型限制

泛型中,不允许编写基本的数据类型,例如 int 是不允许作为泛型的。如果需要使用基本数据类型,则使用所对应的包装类
基本数据类型通过 包装类 变成 引用数据类型

基本数据类型和泛型支持的对应包装类:

  • boolean:
    • 基本数据类型: boolean
    • 包装类: Boolean
  • byte:
    • 基本数据类型: byte
    • 包装类: Byte
  • short:
    • 基本数据类型: short
    • 包装类: Short
  • int:
    • 基本数据类型: int
    • 包装类: Integer
  • long:
    • 基本数据类型: long
    • 包装类: Long
  • float:
    • 基本数据类型: float
    • 包装类: Float
  • double:
    • 基本数据类型: double
    • 包装类: Double
  • char:
    • 基本数据类型: char
    • 包装类: Character
Java
public class demo1 {
    public static void main(String[] args) {
        // JDK7 版本后 new 后末尾的 <> 不用写类型限制
        ArrayList<String> arr = new ArrayList<>();
        arr.add("a");
        arr.add("b");
        arr.add("c");
        System.out.println(arr);
        
        // 使用包装类实现基本数据类型做泛型
        ArrayList<Integer> numArr = new ArrayList<>();
        numArr.add(1);
        numArr.add(2);
        numArr.add(3);
        System.out.println(numArr); // [1, 2, 3]
    }
}
常用方法

集合中常用的方法如下

%title插图%num

add

add() 可添加对应泛型约束,并返回一个添加成功 true /失败 false 的结果 (返回必定为 true ,一般不用接收返回值)

Java
ArrayList<Integer> numArr = new ArrayList<>();
boolean type = numArr.add(1);
System.out.println(numArr); // [1]
System.out.println(type); // true

add() 可以插入两个参数,为 add(int index,e element) ,相当于根据指定索引位置插入

当插入的索引长度超过集合的内容长度,会报错
Exception in thread “main” java.lang.IndexOutOfBoundsException: Index: 3, Size: 2

Java
ArrayList<String> arr = new ArrayList<>();
arr.add("a");
arr.add("b");
arr.add(1, "e");
System.out.println(arr); // [a, e, b]

remove

remove(int index) 移除列表中指定索引位置的元素,并返回这个删除的元素

Java
ArrayList<String> arr = new ArrayList<>();
arr.add("a");
arr.add("b");
arr.add("c");
arr.add("d");
arr.add("e");
String result = arr.remove(2); // 移除下标为 2 的元素
System.out.println(arr); // [a, b, d, e]
System.out.println(result); // c

remove(Object object) 移除列表中对应对象元素,并返回是否删除成功的布尔值状态

Java
ArrayList<String> numArr2 = new ArrayList<>();
numArr2.add("张三");
numArr2.add("李四");
numArr2.add("王五");
Boolean flag = numArr2.remove("李四");
Boolean flag2 = numArr2.remove("刘能");
System.out.println(numArr2); // [张三, 王五]
System.out.println(flag); // true
System.out.println(flag2); // false => 没有该元素

removeAll

在学习 removeAll 之前,通过以下方式也能实现数组全部删除指定元素

Java
// 正序查询 匹配到删除项目删除后需要调整指针
for (int i = 0; i < data.size(); i++) {
    if ("del".equals(data.get(i))) {
        data.remove(i);
        i--;
    }
}
// 倒序查询 由于元素删除不会影响将要后续查询元素下标,无需调整指针
for (int i = data.size() - 1; i >= 0; i--) {
    if ("del".equals(data.get(i))) {
        data.remove(i);
    }
}

set

set(int index,Element element) 用指定元素替代此列表中指定位置上的元素,并返回原位置被覆盖的元素

Java
ArrayList<String> numArr = new ArrayList<>();
numArr2.add("张三");
numArr2.add("李四");
numArr2.add("王五");
String result = numArr.set(1, "刘能");
System.out.println(numArr2); // [张三, 刘能, 王五]
System.out.println(result); // 李四

get

get(int index) 根据索引,获取指定索引下标中的元素

Java
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
System.out.println(list.get(1)); // b

size

size() 返回此列表的元素数量

Java
rrayList<String> strArr = new ArrayList<>();
strArr.add("张三");
strArr.add("李四");
strArr.add("王五");
system.out.println(strArr.size()); // 3
对象和类 进阶
Static

static 是关键字: 作为修饰符,可以修饰成员方法、成员对象。被修饰的方法、对象将会被共享
Java 类中的静态数据是常驻在内存中的,它们在类加载时被初始化,并且一直存在于内存中,直到 JVM 终止

特点

  • 被类的所有对象共享
  • 多了一种调用方式,可以通过类名进行调用
  • 随着类的加载而加载,优先于对象存在

用途

  • 节约内存空间,所有 static 修饰的成员将只占用一个内存地址;共享数据
  • 制作工具类,可以直接调用类中的工具方法而不需要实例化类,节约内容空间

main 方法,就是一个静态修饰的方法

  • main() 入口函数,使用了 static 修饰后,就可以无需 new 的实例化直接调用并执行内容
  • main() 入口函数,使用了 public 修饰后,可以直接被 JVM 调用,因为访问的权限足够大
  • main() 入口函数,使用了 void 修饰后,可以被 JVM 调用后,无需给 JVM 提供任意返回值
  • main() 入口函数,使用了 main 关键字,是一个通用名字,虽然不是关键字但会被 JVM 识别
  • main() 入口函数,使用了 String[] args 传参,以前作为键盘录入数据,现在用途不太大

特殊说明

static 方法中,只能访问静态成员(直接访问)
static 方法中,不允许使用 this 关键字

Java
public class StaticDemo1 {
    public static void main(String[] args) {
        Student stu1 = new Student();
        stu1.name = "smm";
        stu1.age = 18;
        Student.school = "家里蹲";
        printInfo(stu1);
        Student stu2 = new Student();
        stu2.name = "aipo";
        stu2.age = 22;
        printInfo(stu2);
    }

    private static void printInfo(Student stu) {
        System.out.println(stu.name + "---" + stu.age + "---" + stu.school);
    }
}
Java
public class Student {
    String name;
    int age;
    static String school; // 修饰为 static
}

效果展示

%title插图%num

类中的数据将得到共享

接上文代码,被 static 修饰的成员,可以直接通过类名进行调用和操作内容(推荐使用类名),且无需实例化(优先对象存在)

Java
/** 由于 static 全局共享的特性,
*   可以直接修改并影响所有实例数据,而无需实例化
*/
Student.school = "家里蹲"; 
Student stu1 = new Student(); 
stu1.name = "smm";
stu1.age = 18;
printInfo(stu1);
Student stu2 = new Student();
stu2.name = "aipo";
stu2.age = 22;
printInfo(stu2);

效果展示

%title插图%num

类中的数据将得到共享

如果一个类中,所有的方法都加了 static 的修饰,通常是该类就是工具类。它不是用作描述事情的类,而是提供帮助的类。
通常会私有该工具类的构造方法,不让用户重复实例化

Java
/** 数组工具类 仅作协助操作,不进行存值 */
public class ArrayTools {

    // 私有构造方法 目的不让其他类再次创建该对象
    private ArrayTools() {
    }

    public static int getMax(int[] arr) {
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (max < arr[i]) {
                max = arr[i];
            }
        }
        return max;
    }

    public static int getMin(int[] arr) {
        int min = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (min > arr[i]) {
                min = arr[i];
            }
        }
        return min;
    }

    public static void printArray(int[] arr) {
        System.out.print("[");
        for (int i = 0; i < arr.length - 1; i++) {
            System.out.print(arr[i] + ",");
        }
        System.out.print(arr[arr.length - 1] + "]");
        System.out.println();
    }
}
Java
public class Test {
    public static void main(String[] args) {
        // ArrayTools arrayTools = new ArrayTools(); 报错 不让实例化
        int[] arr = {2, 3, 1, 4, 3, 33};
        int max = ArrayTools.getMax(arr); // 33
        int min = ArrayTools.getMin(arr); // 1
        ArrayTools.printArray(arr); // [2,3,1,4,3,33]
        System.out.println(max);
        System.out.println(min);
    }
}

效果展示

%title插图%num

声明了 static 修饰的静态方法可以直接调用,无需 new 操作

静态方法只能调用静态成员,例如 main() 入口函数只能调用带有 static 修饰的方法或者成员。
(因为非静态的成员需要通过 new 实例化才能存在于内存中才可调用;而静态成员只要调用后就会存在内存中)

Java
public class Test2 {
    static String str = "屎猫猫";

    public static void print() {
        System.out.println("打印...");
    }

    public void sayHi() {
        System.out.println("这个方法静态成员不可直接调用");
    }

    public static void main(String[] args) {
        System.out.println(str); // 屎猫猫
        print(); // 打印...
        // sayHi(); 无法从 static 上下文引用非 static 方法 'sayHi()'
        new Test2().sayHi(); // 需要 new 才可以使用
    }
}

效果展示

%title插图%num

非静态成员需创建对象,因此无法直接调用

继承

让类与类之间产生关系(子父类关系),子类可以直接使用父类中的非私有的成员。目的是简化代码,提高代码的可复用性

java 只支持单继承(一次只可以 extends 一个父类),不支持多继承,但是支持多层继承。所有的类都继承 Object

子类在初始化之前,会先完成初始化父类的操作;因为子类很有可能会在初始化期间调用父类方法或者通过 super() 调用构造方法

继承的格式

  • 格式:public class 子类名 extends 父类名 {}
  • 范例:public class Zi extends Fu {}
  • Fu:是父类,也被成为基类、超类
  • Zi:是子类,也被成为派生类

适用范围

当类与类之间存在相同(共性)关系,并且产生了 is a 的关系,就可以考虑抽出放置在需要继承的类中来优化代码

书写案例

通过 extends 可继承父级的非私有成员

Java
public class MyExtends {
    public static void main(String[] args) {
        Coder s = new Coder();
        /* 拥有 从父级 得到的属性 */
        s.name = "屎猫猫";
        System.out.println(s.name);
    }
}

/*
  创建类的细节:
 一个 .java 文件中可以编写多个 class
 1. 保证类与类之间是平级关系
 2. 只能有一个被 public 修饰
 */
class Employee {
    String name;
    int age;
    double salary;
}

/* 从 Employee 继承内容 */
class Manager extends Employee {
}

/* 从 Employee 继承内容 */
class Coder extends Employee {
}

效果展示

%title插图%num

遵循 javaBean 写法,即使是private 的属性,也是可以实现传值

Java
public class MyExtends {
    public static void main(String[] args) {
        Coder s = new Coder();
        s.setName("屎猫猫");
        System.out.println(s.getName());
    }
}

class Employee {
    private String name;

    public Employee() {
    }

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

    public String getName() {
        return name;
    }

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

class Coder extends Employee {
}

效果展示

%title插图%num

类调用顺序

继承中若存在同名成员,采用就近原则调用子类,若需要调用父类同名成员则可以使用 super 关键字去修饰调用

Java
public class MyExtends {
    public static void main(String[] args) {
      Zi zi = new Zi();
      zi.method();
    }
}

class Fu {
    String name = "张三";
}

class Zi extends Fu {
    String name = "张三儿子";

    public void method() {
        System.out.println(name);
        System.out.println(super.name);
    }
}

效果展示

%title插图%num

子父类中,若存在声明相同的情况(方法名、参数、返回值),子类会重写(Override)父类方法,因此效果就为调用子类

重载与重写的区别

  • 方法重载(Overload):在同一个类中,方法名相同,参数不同(类型不同、个数不同、顺序不同),与返回值无关
  • 方法重写 (Override) :在子父类当中,出现了方法声明一模一样的方法(方法名、参数、返回值)

父类中私有方法不能被复写,子类复写父类方法时,访问权限必须要大于等于父类

Java
public class MyExtends {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.method();
        zi.method("李四");
    }
}

class Fu {
    public void method() {
        System.out.println("父级方法");
    }
}

class Zi extends Fu {
    @Override /* 验证方式 左侧标注若不报错说明就是方法重写 */
    public void method() {
        System.out.println("子级方法重写");
    }

    /* 不符合重写的规则 该方法为 方法重载 */
    public void method(String str) {
        System.out.println("子级方法重载,内容:" + str);
    }
}

效果展示

%title插图%num

注意区分重载和重写

多层继承

允许子类继承父级,父级继续向上继承。实现子类通过多层继承的效果去调用多个类的方法

Java
public class ExtendsDemo {
    public static void main(String[] args) {
        C c = new C();
        c.methodA(); // 这是爷级的A方法
        c.methodB(); // 这是父级的B方法
        c.methodC(); // 这是子级的C方法
    }
}

class A {
    public void methodA() {
        System.out.println("这是爷级的A方法");
    }
}

class B extends A {
    public void methodB() {
        System.out.println("这是父级的B方法");
    }
}

class C extends B {
    public void methodC() {
        System.out.println("这是子级的C方法");
    }
}

效果展示

%title插图%num

通过多层继承,子拥有了所有方法

子类执行构造方法时,会优先执行父类构造方法

在所有的构造方法中,都默认隐藏了一句 super(); 通过这个代码来访问来访问父类的空参构造方法

Java
public class Test {
    public static void main(String[] args) {
        Zi z = new Zi();
    }
}

class Fu {
    public Fu() {
        System.out.println("父类的空参构造方法");
    }
    public Fu(String name) {
        System.out.println("父类的带参构造方法");
    }
}

class Zi extends Fu {
    public Zi() {
        /* 默认调用 super(); 无需添加 */
        System.out.println("子类的空参构造方法");
    }
}

效果演示

%title插图%num

子类执行实例化时,会优先执行其父类的构造方法

通过给 super(); 传参,实现调用父级指定的带参构造方法

Java
public class Test {
    public static void main(String[] args) {
        Zi z = new Zi("zs");
    }
}

class Fu {
    public Fu() {
        System.out.println("父类的空参构造方法");
    }
    public Fu(String name) {
        System.out.println("父类的带参构造方法");
    }
}

class Zi extends Fu {
    public Zi() {
        /* 默认调用 super(); 无需添加 */
        System.out.println("子类的空参构造方法");
    }
    public Zi(String name) {
        /* 指定调用父类对应带参的构造方法 */
        super(name);
        System.out.println("子类带参的构造方法");
    }
}

效果展示

%title插图%num

可通过 super(); 传参调用指定父类构造方法

protected

权限修饰符回顾

%title插图%num

protected 修饰符可让父级的成员只允许在子类中使用,而不允许实例化后提供直接调用

效果演示

cn.smmcat.a/Fu
package cn.smmcat.a;

public class Fu {
    // 使用 protected 修饰符
    protected void show() {
        System.out.println("父类方法");
    }
}
cn.smmcat.b/Zi
package cn.smmcat.b;

import cn.smmcat.a.Fu;

public class Zi extends Fu {
  public void method(){
      // protected 只允许在不同的子类中调用
      super.show();
  }
}
cn.smmcat.b/Test
package cn.smmcat.b;

public class Test {
    public static void main(String[] args) {
        Zi zi = new Zi();
        // zi.show(); 权限为 protected 无法调用
        // 通过子类包装后的方法调用 protected 修饰的父类成员
        zi.method(); 
    }
}

效果展示

%title插图%num
this 和 super
  • this 代表本类对象的引用
  • super 代表父类存储空间的标识
%title插图%num

super 调用父类成员的省略规则

一般调用父类内容是 super. ,但如果被调用的变量和方法,在子类中不存在,super. 是可以被省略的。甚至可以改写为 this.调用
因为通过 extends 关键字继承父类的成员时,相当于子类的成员中就增加了父类而来的成员,所以这里的 super 等同于 this

Java
class Fu {
    String name = "smm";

    public void sayHi() {
        System.out.println("我叫" + name);
    }
}

class Zi extends Fu {

    public void method() {
        /* 省略 super 关键字 */
        System.out.println(name);
        sayHi();
    }
}
同理
class Fu {
    String name = "smm";

    public void sayHi() {
        System.out.println("我叫" + name);
    }
}

class Zi extends Fu {

    public void method() {
        System.out.println(super.name);
        super.sayHi();
    }
}
直接使用 this 也可以
class Fu {
    String name = "smm";

    public void sayHi() {
        System.out.println("我叫" + name);
    }
}

class Zi extends Fu {

    public void method() {
        System.out.println(this.name);
        this.sayHi();
    }
}

仅在子类不存在该成员才可以省略 super ,不然调用的就是子类复写的成员

Java
class Fu {
    String name = "smm";

    public void sayHi() {
        System.out.println("我叫" + name);
    }
}

class Zi extends Fu {
    String name = "夜夜酱";

    @Override
    public void sayHi() {
        System.out.println("她叫" + name);
    }

    public void method() {
        System.out.println(name); // 夜夜酱
        System.out.println(super.name); // smm
        sayHi(); // 她叫夜夜酱
        super.sayHi(); // 我叫smm
    }
}

这是一个特性,建议一般还是写上完整写法。增加代码阅读性

通过 this() 来优化代码

更新迭代时需要做开闭原则: 对功能扩展做开放, 对修改代码做关闭

Java
public class Test {
    public static void main(String[] args) {
        // 1.0 版本
        A a1 = new A(1, 2, 3);
        // 1.1 版本增加的操作 不应该影响 1.0
        A a2 = new A(1, 2, 3, 4);
    }
}

class A {
    int a;
    int b;
    int c;
    int d; // 1.1 新增内容

    public A() {
    }

    // 1.0 版本功能
    public A(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    // 1.1 版本新增功能
    public A(int a, int b, int c, int d) {
        // 通过 this 去调用 1.0 的旧构造方法
        this(a, b, c);
        this.d = d;
    }
}

注意: this()super() 不能共存, 因为它们都需要放在构造方法中的首位

final

final 关键字是最终的意思, 可以修饰(方法, 类, 变量)

命名规则

如果要定义 final 常量, 尽量遵循 java 常量规则:

  • 如果是一个单词,所有字母大写: 例 max 改为 MAX
  • 如果是多个单词,单词之间需要使用 _ 隔开: 例 maxValue 改为 MAX_VALUE

特点

  • 修饰方法: 表明该方法是最终方法, 不能被重写
  • 修饰类: 表明该类是最终类, 不能被继承
  • 修饰变量: 表明该变量是常量, 不能被再次赋值

案例演示

在类中使用 final 修饰的方法, 不能被子类继承后进行重写

Java
class Fu {
    public final void method(){
        System.out.println("这个方法不能被重写,是唯一的!");
    }
}

class Zi extends Fu {
//    @Override 报错: 'method()' 无法重写 'xx' 
//    中的 'method()';重写的方法为 final
//    public void method() {
//        System.out.println("重写方法");
//    }
}

声明类时使用 final 去修饰类, 将不允许任何类去继承该类, 但是 final 类允许有父类

Java
final class Fu {
}

// 不允许任何类继承 final 修饰的类
// class Zi extends Fu {
// }

final 修饰的变量将作为常量使用,不能被再次赋值

Java
final class Fu {
    public final void method() {
        final int a = 233;
        // a = 666; 无法将值赋给 final 变量 'a'
    }
}

final 修饰的类成员变量时需要在构造方法结束之前完成赋值, 或者直接创建时就赋值

Java
class Student {
    // final int age; 报错: 变量 's' 可能尚未初始化
    final int age = 0; // 使用 final 修饰的变量需要做初始化
    final String name = "smm";
}
或者
class Student {
    final int age;
    // final 修饰的常量同样也可以在构造方法中赋值
    public Student(int age) {
        this.age = age;
    }
}

包的描述

包本质来说就是文件夹, 用来管理类文件. 一般使用 域名倒写.技术名称 命名,包名建议全英文小写,且命名有实际意义

Java
package cn.smmcat.test; // 建包语句需在第一位 一般 IDEA 自动创建
public class Test {
}
  • 相同包下的类可以直接访问, 不同包下的类必须导包才可以使用. 导包格式: import 包名.类名;
  • 假如一个类中需要用到不同类, 而这两个类的名称是一样的, 那么默认只能导入一个类, 另一个类需要带包名访问

包名冲突解决方案

当不同包创建的类名一致时,需要使用 全类名创建 类来解决命名冲突

cn.smmcat.clash.a
package cn.smmcat.clash.a;

public class Student {
    public void eat() {
        System.out.println("学生在吃饭");
    }
}
cn.smmcat.clash.b
package cn.smmcat.clash.b;

public class Student {
    public void sleep() {
        System.out.println("学生在睡觉");
    }
}
cn.smmcat.clash.test
package cn.smmcat.clash.test;

import cn.smmcat.clash.a.Student;

public class Test {
    public static void main(String[] args) {
        Student stu1 = new Student();
        stu1.eat();
        // 命名相同的类 可使用全类名创建
        cn.smmcat.clash.b.Student stu2 = new cn.smmcat.clash.b.Student();
        stu2.sleep();
    }
}

效果展示

%title插图%num

如果创建的类名与 java类 同名, (一般不建议同名), java 会优先调用用户创建的类名. 如果需要指定 java类,也可以带上 全类名 方式

Java
public class Scanner {
    public static void main(String[] args) {
        // Scanner sc = new Scanner(); // 错误 实际是调用自己
        java.util.Scanner scanner = new java.util.Scanner(System.in); // 正确操作
    }
}
抽象类

抽象类一般用作特殊的父类, 内部允许编写抽象方法. 当父类定义后, 继承该父类的子类必须复写该方法 (定义子类规则)

如果一个类中存在抽象 (abstract) 方法, 那它必须声明为抽象类

注意事项

  • 抽象类不能实例化 (防止调用抽象类中无意义的抽象方法)
  • 抽象类存在构造方法 (方便父类成员交给子类, 通过 super 进行访问)
  • 抽象类中可以存在普通方法 (方便子类继承继续使用)
  • 抽象类的子类
    • 要么重写抽象类中所有抽象方法
    • 要么是抽象类
  • 关键字冲突
    • abstract 修饰的抽象方法强制要求重写,所以不能被 final 关键字修饰
    • abstract 修饰的抽象方法强制要求重写,所以不能被 private 关键字修饰
    • static 修饰的方法可以被类名调用,类名调用抽象方法没有意义

用处

当将共性的方法抽取到父类之后,发现这个方法在父类中无法给出具体描述. 而这个方法是子类必须要有的方法, 就可以设计为抽象方法

Java
// 定义抽象类
abstract class Animal {
    public abstract void eat();
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
}

// 允许 抽象类 继承 抽象类
abstract class Other extends Animal {
    abstract public void eat();
}

相当于父类去设计, 子类来实现

接口

对于只声明规则的抽象类,有更好的选择:比如使用接口 interface 去创建规则

Java
interface A {
    public abstract void method1();
    public abstract void method2();
}

// 提供给抽象类 (比较少见)
abstract class AbsDemo implements A {
    abstract public void absMethod1();
}

// 提供给实现类
class Zi implements A {
    @Override
    public void method1() {
      // ...
    }
    @Override
    public void method2() {
      // ...
    }
}

介绍

java 中的接口更多体现的是对行为的抽象,对规则的声明。如果抽象类中没有成员变量,没有普通方法;实际它

JAVA 中的接口更多体现的是对行为的抽象,对规则的声明。如果发现抽象类中:没有成员变量、没有普通方法;
那么这样的抽象类只能作为声明规则的存在,没有声明类的意义;就可以改写为 incerface 接口

创建方式:interface 接口名 {}
调用方式:class 类名 implements 接口名 {}

调用接口的类,也叫 实现类(接口的子类):它可以重写所有抽象方法,也可以将 实现类 变成 抽象类
注意:接口是不允许实例化的!

接口携带的变量默认有 static , public , final 修饰

通过在 interface 中创建的 变量, 是可以直接通过 接口名.变量 调用的, 且不可更改. 因为接口携带的变量默认携带了 staticfinal

Java
public class Abstract1 {
    public static void main(String[] args) {
        System.out.println(MyInterface.NAME);
        // MyInterface.NAME = "yeye"; 报错, final 特性
    }
}

interface MyInterface {
    // 相当于添加了 public static final 前缀
    String NAME = "屎猫猫";
}

效果展示

%title插图%num

接口携带的方法默认有 public abstract 修饰

接口中携带的方法只能是成员方法, 因为系统会默认给接口中的方法添加 public abstract 关键字

Java
interface MyInterface {
    // 默认给方法添加 public abstract
    String getStrData();
    Number getNumData();
    boolean getBoolData();
}

接口可以单实现, 也可以多实现 甚至可以在继承一个类的同时实现多接口

通过 implements 接口A,接口B... 实现多接口继承

Java
interface A {
    void showA();
}

interface B {
    void showB();
}

class C implements A, B {
    @Override // 实现 A 接口功能
    public void showA() {

    }
    @Override // 实现 B 接口功能
    public void showB() {

    }
}

也可以通过继承父类方法, 来实现接口要求的抽象方法

Java
interface A {
    void show();
}

interface B {
    void show();
}

class Fu {
    public void show() {
        System.out.println("实现方法");
    }
}

//  子类继承父类 父类去实现接口
class C extends Fu implements A, B {
}

接口可以继承接口, 实现单继承 或者 多继承

类的继承通常用于描述”is-a”关系,即子类是父类的一种类型;而接口的继承则通常用于描述”has-a”关系,即一个类具有某种行为或特征

Java
interface A {
    void showA();
}

interface B {
    void showB();
}

interface C extends A, B {

}

抽象类和接口的对比

成员变量

  • 抽象类:可以定义变量,也可以定义常量
  • 接口:只能定义常量

成员方法

  • 抽象类:可以是定义具体方法,也可以定义抽象方法
  • 接口:只能定义抽象方法

构造方法

  • 抽象类:有
  • 接口:没有

用处

  • 抽象类:针对事物进行的抽象(对事物做描述)
  • 接口:为程序制定规则,实现代码规范(对行为抽象)
多态

同一个行为具有多个不同表现形式或者形态的能力;创建对象的不同,得到的表现形式也不同

多态前提

  • 有继承 / 实现方法
  • 有方法重写
  • 有父类引用指向子类对象

多态种类

多态分为 对象多态 和 行为多态

  • 对象多态
    • Animal a1 = new Dog();
    • Animal a2 = new Cat();
    • useAnimal(new Cat()); / useAnimal(new Dog());
    • public static void useAnimal(Animal a){} 函数就能支持上个效果
    • 好处:使得函数可以接收多个类型的对象,提升扩展性
  • 行为多态
    • 好处:同一个方法具有多种不同表现方式,或者形态的能力

访问特点

成员变量

  • 编译看左边(父类),运行看左边(父类)

成员方法

  • 编译看左边(父类),运行看右边(子类):先看父类有没有方法,再去调用子类重写方法

静态方法

  • 由于 JAVA 编译成字节码文件后该处直接为调用 父类名.方法() 因此调用为父类,并且子类无法重写父类静态方法
%title插图%num

函数可以将形参写为两个子类的父类,实现多类型接收

Java
abstract class Animal1 {
    abstract public void eat();
    public void sayHi() {
        System.out.println("父类方法");
    };
}


class Cat1 extends Animal1 {
    @Override
    public void eat() {
        System.out.println("猫吃东西");
    }

    public void sayHi() {
        System.out.println("hi");
    }
}

class Dog1 extends Animal1 {
    @Override
    public void eat() {
        System.out.println("狗吃东西");
    }
}
Java
public class Demo1 {
    public static void main(String[] args) {
        // 父类类型 可以接收子类对象
        Animal1 s1 = new Dog1();
        Animal1 s2 = new Cat1();
        // 可接收父类下的子类 调用共有的重写方法或父方法
        useAnimal(new Cat1());
        useAnimal(new Dog1());
    }

    private static void useAnimal(Animal1 s1) {
        s1.eat();
        s1.sayHi();
    }
}

效果展示

%title插图%num

接口可以用作定义变量,也可以被接口继承的类赋值

创建对象时时候,左右两边的类型可以不一致

Java
interface Order {
    void getAddress();
}

class HomeOrder implements Order {
    @Override
    public void getAddress() {
        System.out.println("国内订单地址是...");
    }
}

class AbroadOrder implements Order {
    @Override
    public void getAddress() {
        System.out.println("国外订单地址是...");
    }
}
Java
public class UseInterface {
    public static void main(String[] args) {
        Order orderDemo = null;
        System.out.println("请输入订单:1 国内、2 国外");
        Scanner sc = new Scanner(System.in);
        int type = sc.nextInt();
        switch (type) {
            case 1:
                // 对环境不同 可灵活切换业务
                orderDemo = new HomeOrder();
                break;
            case 2:
                // 对环境不同 可灵活切换业务
                orderDemo = new AbroadOrder();
                break;
            default:
                System.exit(0);
        }
        orderDemo.getAddress();
    }
}

效果展示

%title插图%num

继承父类的类,可以用使用父类的类型变量接收

成员变量:编译看左边(父类),运行看左边(父类);对象方法:判断父类是否存在方法,但运行时执行子类方法(包括子无方法调父类方法)

Java
public class Demo1 {
    public static void main(String[] args) {
        Fu1 zi = new Zi1();
        zi.method(); // 父类 (子类调用父类)
        System.out.println(zi.a); // 10
    }
}

class Fu1 {
    int a = 10;

    public void method() {
        System.out.println("父类");
    }
}

class Zi1 extends Fu1 {
    int a = 20;
}

多态的转型

父类变量引用子类,也可以改变回子类变量引用子类 (实际就是可以回到最初调用的方法)

%title插图%num

父类引用子类为了调用某项子类方法时,为了解决安全隐患,最好使用 instanceof 判断

Java
class Fu1 {
    public void method() {
        System.out.println("父类方法");
    }
}

class Zi1 extends Fu1 {
    @Override
    public void method() {
        System.out.println("子类方法");
    }

    public void sayHi() {
        System.out.println("子类特有方法");
    }
}

class Zi2 extends Fu1 {
    @Override
    public void method() {
        System.out.println("子类方法");
    }
}

当项目需要调用特有子类方法时,可以使用强转调用

Java
public class Demo1 {
    public static void main(String[] args) {
        Fu1 zi = null;
        Scanner sc = new Scanner(System.in);
        int t = sc.nextInt();
        zi = switch (t) {
            case 1 -> new Zi1();
            case 2 -> new Zi2();
            default -> new Zi1();
        };
        // 当子类有对应方法时 做强转子类操作
        if (zi instanceof Zi1) {
            // 强转赋值子类变量 也可以  ((Zi1) zi).sayHi();
            Zi1 zzi = (Zi1) zi;
            // 调用个体子类特有方法
            zzi.sayHi();
        }
        zi.method();
    }
}