针对 java 语言的 安装、介绍;语法、流程控制、数组、javaBean 等知识,可以查询上一个内容
API(Application Programming Interface)应用程序编程接口:
是一组定义了软件组件之间交互的规范和约定的接口。
在软件开发中,API允许不同的软件系统、服务或组件之间进行通信和交互,从而实现数据传输、功能调用等操作。
API可以被视为一个软件模块提供给其他程序员或开发者使用的一组工具和规则。
通过API,开发者可以利用已经实现的功能,而不必了解其内部实现细节。
这种抽象层有助于简化开发流程,提高代码的模块化和可重用性。
在 Java 的包中,java.lang 为核心包,不需要手动导入;其他的则需要 import
导入;
更多内容可以下载下方手册查阅文档
案例
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
是 Java
当中的一个核心类,任何双引号的字符串都是这个类的实例对象
特点
String
类多了一种创建实例的方式,只要写双引号就已经是该实例的对象;字符串一旦被创建就不可更改;
而变量若想修改字符串,只能使用替换的方式;
// 创建 String 类
String a = "abc";
String b = new String("abc");
// 因为是对象 所以也有类方法
a.toUpperCase(); // ABC
String
类的字符串虽然不可以改变,但是它是可以被共享的
在Java中,对于字符串常量的处理是通过字符串池(String Pool)来实现的。
当创建一个字符串常量时,Java会首先检查字符串池中是否已经存在相同内容的字符串,
如果存在,则返回已存在的字符串的引用,而不会创建新的字符串对象。
字符串常量池在 jdk7 版本前,是放在方法区,在 jdk7 版本开始后,它才被放到 堆内存中
// 使用双引号创建字符串的情况
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
的数组
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
生成一个新的字符串对象,这个对象会存储在堆中,
且如果这个字符串是常量的话,可能会存储在字符串常量池中
常量 (字面量创建的就是常量) 拼接,Java有常量优化机制,会编译时预先拼接并尝试存在常量池中;当常量池存在则引用相同地址,因此会有如下结果:
String a = "abc";
String b = "a" + "b" + "c"; // 会在编译时变成 "abc"
System.out.println(a == b); // true
equals
使用 equals()
方法对字符串内容与另一个字符串的内容进行比较,而 Java
中的 ==
只是比较地址,不适用于内容比较
equals()
是 String
类中的方法,使用 .equals()
即调用
String a = "abc";
String b = new String("abc");
System.out.println(a.equals(b)); // 内容一致 返回 true
equalsIgnoreCase
使用 equalsIgnoreCase()
方法对字符串的内容与另一个字符串的内容进行比较,不考虑大小写 (仅对 26 个英文字母)
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[]
数组并返回这个数组,实现逐个打印和计算长度
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
的值,搭配 for
和 length()
获取字符串长度,实现获取字符串的遍历;
String str = "Hello World";
for (int i = 0; i < str.length(); i++) {
System.out.println(str.charAt(i));
}
案例:实现分析字符串中 大写、小写、数字出现次数
字符都有一个 ascll
表的对照,通过判断是否在对应内容的范围区间,实现分析
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 + "个");
效果演示
使用 substring(beginIndex,endIndex?)
substring(int beginIndex);
根据传入的索引开始截取,截取到字符串的末尾,并返回新的字符串对象substring(int beginIndex , int endIndex);
根据传入的索引开始截取,截取到 endIndex
的位置之前,并返回新的字符串对象 (包含头,不含尾)
截取出来的内容是作为新的字符串返回,需要通过变量去接收返回值
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 个符号位) 位直到末尾的字符串,进行拼接实现
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
,并返回新字符串对象
String str = "学习真快乐,真的很快乐";
str = str.replace("快乐", "痛苦");
System.out.println(str); // 学习真痛苦,真的很痛苦
replaceFiest(target,replacement)
与上面代码类似,但是只是正则替换首个匹配成功的字符串
String str = "学习真快乐,真的很快乐";
str = str.replaceFirst("快乐", "痛苦");
System.out.println(str); // 学习真痛苦,真的很快乐
split(regex,limit?)
根据传入的字符串正则作为规则来切割当前的字符串并返回一个字符串 String[]
的数组
传入的为正则表达式,因此部分特殊意义的符号要实现规则需要进行 \\
转义
String str = "今天开始,我要,自己,上厕所";
String[] strs = str.split(","); // {"今天开始","我要","自己","上厕所"}
String ip = "192.168.1.1"
String[] ips = str.split("//."); // 特殊符号需要转义 {"192","168","1","1"}
StringBuilder
是 Java 中用于处理字符串的一个类,它可以解决在构建大量字符串时遇到的性能问题
StringBuilder
类提供了可变的字符串序列,允许在字符串中进行添加、插入、删除和修改等操作,
而不会创建新的字符串对象。这样可以避免频繁创建新的字符串对象,提高字符串操作的效率
主要优点包括:
- 性能优化:
StringBuilder
的可变性避免了频繁创建新的字符串对象,提高了字符串操作的性能。 - 方便的操作方法:
StringBuilder
提供了丰富的方法来进行字符串的添加、插入、删除和修改,使得操作字符串更加方便和灵活。 - 线程不安全:
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毫秒
}
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
容器中时都会转为字符串
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)
构造一个字符串生成器,并初始化为指定的字符串内容
// StringBuilder 的构造方法允许传值 作为初始值
StringBuilder sb = new StringBuilder("hello world");
System.out.println(sb); // hello world
常用类方法
append()
将指定的数据添加到字符串的末尾,并返回 StringBuilder
它本身的对象
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
类 它是可支持链式编程的
StringBuilder sb = new StringBuilder();
sb.append("这是一句话,").append("这是第二句话,").append("这是末尾");
System.out.println(sb); // 这是一句话,这是第二句话,这是末尾
substring()
对 StringBuilder
类的字符串进行范围截取,类似字符串的 substring
方法,它返回一个新的字符串对象
StringBuilder sb = new StringBuilder("smmmax");
String str = sb.substring(1); // mmmax
reverse()
对 StringBuilder
类的缓冲区的内容进行反转,并返回 StringBuilder
它本身的对象
缓冲区:StringBuilder
类内部维护了一个字符数组(char array
)作为缓冲区(buffer
)
StringBuilder sb = new StringBuilder("你好");
StringBuilder sb1 = sb.reverse();
System.out.println(sb); // 好你
System.out.println(sb1); // 好你
lenght()
返回 StringBuilder
类缓冲区内容的字符文本总长度
StringBuilder sb = new StringBuilder("你好");
int len = str.length();
System.out.println(len); // 2
toString()
将 StringBuilder
类中缓冲区内容,以String
类型的字符串返回;
将 StringBuilder
类型转为 String
类型的目的是因为 String
类提供了其他操作字符的方法:例如 split()
StringBuilder sb = new StringBuilder("hello,world");
String str2 = sb.toString(); // hello,world
String[] arr = sb.toString().split(","); // {"hello","world"}
StringBuilder
和 StringBuffer
都是用来处理字符串的类,方法一致;它们之间的主要区别在于线程安全性和性能
StringBuffer
在 JDK1
版本首次出现,StringBuilder
在 JDK1.5
版本首次出现
- 线程安全性:
- StringBuilder:StringBuilder 是非线程安全的,也就是说在多线程环境下,
如果多个线程同时访问同一个 StringBuilder 对象,可能会导致数据不一致的问题。 - StringBuffer:StringBuffer 是线程安全的,它的方法是同步的,因此可以安全地在多线程环境中使用。
- StringBuilder:StringBuilder 是非线程安全的,也就是说在多线程环境下,
- 性能:
- StringBuilder:StringBuilder 的性能比 StringBuffer 更好,因为它不是线程安全的。
由于不需要进行同步操作,StringBuilder 的操作速度更快。 - StringBuffer:由于 StringBuffer 是线程安全的,它的性能可能比 StringBuilder 稍差一些,
因为需要进行同步操作以确保线程安全。
- StringBuilder:StringBuilder 的性能比 StringBuffer 更好,因为它不是线程安全的。
一般来说,如果代码是在单线程环境下运行,推荐使用 StringBuilder,因为它性能更好。
如果代码需要在多线程环境下运行,或者需要线程安全性,那么应该使用 StringBuffer。
在 JAVA 的 api 中,有线程安全的效率就偏低,没有线程安全的,效率就高
ArrayList
是 集合,集合是一种容器,用来装数据的,类似于数组
特性
ArrayList
类在构建的时候会创建一个初始长度为 10
的空列表,
当需要添加的内容溢出时,会将原数组扩容 1.5
倍;之后将原数组数据拷贝到新数组中,并将需要添加的元素添加进数组;以此类推
集合和数组的选择
数组:存储的元素个数固定不变
集合:存储的元素个数经常发生改变
arrayList 类虽然默认有 10
的长度,但是只需要关心自己的真实数据有几个即可,默认可以插入任意类型的数据
new 创建
由于并不存在核心包中,需要 import
引入对应包
// 需要引入对应包
import java.util.ArrayList;
public class demo1 {
public static void main(String[] args) {
// 创建 ArrayList 类
ArrayList arr = new ArrayList();
}
}
add 插入
向集合尾部增加一位成员,插入一个元素
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
- 基本数据类型:
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]
}
}
集合中常用的方法如下
add
add()
可添加对应泛型约束,并返回一个添加成功 true
/失败 false
的结果 (返回必定为 true
,一般不用接收返回值)
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
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)
移除列表中指定索引位置的元素,并返回这个删除的元素
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)
移除列表中对应对象元素,并返回是否删除成功的布尔值状态
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
之前,通过以下方式也能实现数组全部删除指定元素
// 正序查询 匹配到删除项目删除后需要调整指针
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)
用指定元素替代此列表中指定位置上的元素,并返回原位置被覆盖的元素
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)
根据索引,获取指定索引下标中的元素
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
System.out.println(list.get(1)); // b
size
size()
返回此列表的元素数量
rrayList<String> strArr = new ArrayList<>();
strArr.add("张三");
strArr.add("李四");
strArr.add("王五");
system.out.println(strArr.size()); // 3
static
是关键字: 作为修饰符,可以修饰成员方法、成员对象。被修饰的方法、对象将会被共享
Java 类中的静态数据是常驻在内存中的,它们在类加载时被初始化,并且一直存在于内存中,直到 JVM 终止
特点
- 被类的所有对象共享
- 多了一种调用方式,可以通过类名进行调用
- 随着类的加载而加载,优先于对象存在
用途
- 节约内存空间,所有
static
修饰的成员将只占用一个内存地址;共享数据 - 制作工具类,可以直接调用类中的工具方法而不需要实例化类,节约内容空间
main
方法,就是一个静态修饰的方法
-
main()
入口函数,使用了static
修饰后,就可以无需new
的实例化直接调用并执行内容 -
main()
入口函数,使用了public
修饰后,可以直接被JVM
调用,因为访问的权限足够大 -
main()
入口函数,使用了void
修饰后,可以被JVM
调用后,无需给JVM
提供任意返回值 main()
入口函数,使用了main
关键字,是一个通用名字,虽然不是关键字但会被JVM
识别main()
入口函数,使用了String[] args
传参,以前作为键盘录入数据,现在用途不太大
特殊说明
static
方法中,只能访问静态成员(直接访问)static
方法中,不允许使用 this
关键字
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);
}
}
public class Student {
String name;
int age;
static String school; // 修饰为 static
}
效果展示
类中的数据将得到共享
接上文代码,被 static
修饰的成员,可以直接通过类名进行调用和操作内容(推荐使用类名),且无需实例化(优先对象存在)
/** 由于 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);
效果展示
类中的数据将得到共享
如果一个类中,所有的方法都加了 static
的修饰,通常是该类就是工具类。它不是用作描述事情的类,而是提供帮助的类。
通常会私有该工具类的构造方法,不让用户重复实例化
/** 数组工具类 仅作协助操作,不进行存值 */
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();
}
}
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);
}
}
效果展示
声明了 static
修饰的静态方法可以直接调用,无需 new
操作
静态方法只能调用静态成员,例如 main()
入口函数只能调用带有 static
修饰的方法或者成员。
(因为非静态的成员需要通过 new
实例化才能存在于内存中才可调用;而静态成员只要调用后就会存在内存中)
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 才可以使用
}
}
效果展示
非静态成员需创建对象,因此无法直接调用
让类与类之间产生关系(子父类关系),子类可以直接使用父类中的非私有的成员。目的是简化代码,提高代码的可复用性
java
只支持单继承(一次只可以 extends
一个父类),不支持多继承,但是支持多层继承。所有的类都继承 Object
类
子类在初始化之前,会先完成初始化父类的操作;因为子类很有可能会在初始化期间调用父类方法或者通过 super()
调用构造方法
继承的格式
- 格式:
public class
子类名extends
父类名{}
- 范例:
public class Zi extends Fu {}
Fu
:是父类,也被成为基类、超类Zi
:是子类,也被成为派生类
适用范围
当类与类之间存在相同(共性)关系,并且产生了 is
a
的关系,就可以考虑抽出放置在需要继承的类中来优化代码
书写案例
通过 extends
可继承父级的非私有成员
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 {
}
效果展示
遵循 javaBean
写法,即使是private
的属性,也是可以实现传值
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 {
}
效果展示
类调用顺序
继承中若存在同名成员,采用就近原则调用子类,若需要调用父类同名成员则可以使用 super
关键字去修饰调用
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);
}
}
效果展示
子父类中,若存在声明相同的情况(方法名、参数、返回值),子类会重写(Override
)父类方法,因此效果就为调用子类
重载与重写的区别
- 方法重载(
Overload
):在同一个类中,方法名相同,参数不同(类型不同、个数不同、顺序不同),与返回值无关 - 方法重写 (
Override
) :在子父类当中,出现了方法声明一模一样的方法(方法名、参数、返回值)
父类中私有方法不能被复写,子类复写父类方法时,访问权限必须要大于等于父类
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);
}
}
效果展示
注意区分重载和重写
多层继承
允许子类继承父级,父级继续向上继承。实现子类通过多层继承的效果去调用多个类的方法
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方法");
}
}
效果展示
通过多层继承,子拥有了所有方法
子类执行构造方法时,会优先执行父类构造方法
在所有的构造方法中,都默认隐藏了一句 super();
通过这个代码来访问来访问父类的空参构造方法
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("子类的空参构造方法");
}
}
效果演示
子类执行实例化时,会优先执行其父类的构造方法
通过给 super();
传参,实现调用父级指定的带参构造方法
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("子类带参的构造方法");
}
}
效果展示
可通过 super();
传参调用指定父类构造方法
权限修饰符回顾
protected
修饰符可让父级的成员只允许在子类中使用,而不允许实例化后提供直接调用
效果演示
package cn.smmcat.a;
public class Fu {
// 使用 protected 修饰符
protected void show() {
System.out.println("父类方法");
}
}
package cn.smmcat.b;
import cn.smmcat.a.Fu;
public class Zi extends Fu {
public void method(){
// protected 只允许在不同的子类中调用
super.show();
}
}
package cn.smmcat.b;
public class Test {
public static void main(String[] args) {
Zi zi = new Zi();
// zi.show(); 权限为 protected 无法调用
// 通过子类包装后的方法调用 protected 修饰的父类成员
zi.method();
}
}
效果展示
this
代表本类对象的引用super
代表父类存储空间的标识
super 调用父类成员的省略规则
一般调用父类内容是 super.
,但如果被调用的变量和方法,在子类中不存在,super.
是可以被省略的。甚至可以改写为 this.
调用
因为通过 extends
关键字继承父类的成员时,相当于子类的成员中就增加了父类而来的成员,所以这里的 super
等同于 this
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();
}
}
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
,不然调用的就是子类复写的成员
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() 来优化代码
更新迭代时需要做开闭原则: 对功能扩展做开放, 对修改代码做关闭
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
常量, 尽量遵循 java
常量规则:
- 如果是一个单词,所有字母大写: 例
max
改为MAX
- 如果是多个单词,单词之间需要使用
_
隔开: 例maxValue
改为MAX_VALUE
特点
- 修饰方法: 表明该方法是最终方法, 不能被重写
- 修饰类: 表明该类是最终类, 不能被继承
- 修饰变量: 表明该变量是常量, 不能被再次赋值
案例演示
在类中使用 final
修饰的方法, 不能被子类继承后进行重写
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
类允许有父类
final class Fu {
}
// 不允许任何类继承 final 修饰的类
// class Zi extends Fu {
// }
被 final
修饰的变量将作为常量使用,不能被再次赋值
final class Fu {
public final void method() {
final int a = 233;
// a = 666; 无法将值赋给 final 变量 'a'
}
}
被 final
修饰的类成员变量时需要在构造方法结束之前完成赋值, 或者直接创建时就赋值
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;
}
}
包的描述
包本质来说就是文件夹, 用来管理类文件. 一般使用 域名倒写.技术名称 命名,包名建议全英文小写,且命名有实际意义
package cn.smmcat.test; // 建包语句需在第一位 一般 IDEA 自动创建
public class Test {
}
- 相同包下的类可以直接访问, 不同包下的类必须导包才可以使用. 导包格式:
import 包名.类名;
- 假如一个类中需要用到不同类, 而这两个类的名称是一样的, 那么默认只能导入一个类, 另一个类需要带包名访问
包名冲突解决方案
当不同包创建的类名一致时,需要使用 全类名创建 类来解决命名冲突
package cn.smmcat.clash.a;
public class Student {
public void eat() {
System.out.println("学生在吃饭");
}
}
package cn.smmcat.clash.b;
public class Student {
public void sleep() {
System.out.println("学生在睡觉");
}
}
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();
}
}
效果展示
如果创建的类名与 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
修饰的方法可以被类名调用,类名调用抽象方法没有意义
用处
当将共性的方法抽取到父类之后,发现这个方法在父类中无法给出具体描述. 而这个方法是子类必须要有的方法, 就可以设计为抽象方法
// 定义抽象类
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
去创建规则
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
接口
创建方式:interfac
e 接口名 {}
调用方式:class 类名 implements 接口名 {}
调用接口的类,也叫 实现类(接口的子类):它可以重写所有抽象方法,也可以将 实现类 变成 抽象类
注意:接口是不允许实例化的!
接口携带的变量默认有 static
, public
, final
修饰
通过在 interface
中创建的 变量, 是可以直接通过 接口名.变量
调用的, 且不可更改. 因为接口携带的变量默认携带了 static
和 final
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 = "屎猫猫";
}
效果展示
接口携带的方法默认有 public abstract
修饰
接口中携带的方法只能是成员方法, 因为系统会默认给接口中的方法添加 public abstract
关键字
interface MyInterface {
// 默认给方法添加 public abstract
String getStrData();
Number getNumData();
boolean getBoolData();
}
接口可以单实现, 也可以多实现 甚至可以在继承一个类的同时实现多接口
通过 implements 接口A,接口B...
实现多接口继承
interface A {
void showA();
}
interface B {
void showB();
}
class C implements A, B {
@Override // 实现 A 接口功能
public void showA() {
}
@Override // 实现 B 接口功能
public void showB() {
}
}
也可以通过继承父类方法, 来实现接口要求的抽象方法
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”关系,即一个类具有某种行为或特征
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
编译成字节码文件后该处直接为调用父类名.方法()
因此调用为父类,并且子类无法重写父类静态方法
函数可以将形参写为两个子类的父类,实现多类型接收
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("狗吃东西");
}
}
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();
}
}
效果展示
接口可以用作定义变量,也可以被接口继承的类赋值
创建对象时时候,左右两边的类型可以不一致
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("国外订单地址是...");
}
}
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();
}
}
效果展示
继承父类的类,可以用使用父类的类型变量接收
成员变量:编译看左边(父类),运行看左边(父类);对象方法:判断父类是否存在方法,但运行时执行子类方法(包括子无方法调父类方法)
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;
}
多态的转型
父类变量引用子类,也可以改变回子类变量引用子类 (实际就是可以回到最初调用的方法)
父类引用子类为了调用某项子类方法时,为了解决安全隐患,最好使用 instanceof
判断
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("子类方法");
}
}
当项目需要调用特有子类方法时,可以使用强转调用
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();
}
}