Java基础
一、基础语法
1.1 注释
单行注释:
//这是单行注释
多行注释:
/*这是一个
多行注释*/
文档注释:
/**这是
文档注释*/
1.2 命名规则
- Java是大小写敏感的
- Java只能以字母、美元符或下划线开始
- 类名的首字母大写,如果类名由若干单词组成,那么每个单词的首字母应该大写,如:TestClass
- 变量名、方法名、包名都应该以小写字母开头,如果方法名含有若干单词,则后面的每个单词首字母大写,如:studentName(驼峰命名法)
- 常量的所有字母都大写,如: public static final SPRING
1.3 修饰符
访问修饰符
修饰符 | 当前类 | 同一包内 | 子孙类(同一包) | 子孙类(不同包) | 其他包 |
---|---|---|---|---|---|
public | Y | Y | Y | Y | Y |
protected (子类) | Y | Y | Y | Y | N |
default (同一包) | Y | Y | Y | N | N |
private | Y | N | N | N | N |
- public:对所有类可见。使用对象:类、接口、变量、方法
- protected:对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰外部类
- default :在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法
- private:在同一类内可见。使用对象:变量、方法。 注意:不能修饰外部类
非访问控制修饰符
- final, abstract, static, synchronized,transient,volatile等
1.4 运算符
-
算术运算符
- 加法
+
:用于两个数相加或字符串拼接 - 减法
-
:用于计算两个数的差 - 乘法
*
:用于计算两个数的积 - 除法
/
:用于计算两个数的商 - 取模
%
:用于计算两个数相除的余数 - 自增
++
:将变量的值增加 1(前缀++i
或后缀i++
) - 自减
--
:将变量的值减少 1(前缀--i
或后缀i--
)
赋值运算符
=
:简单赋值,将右边的值赋给左边的变量。- 复合赋值运算符:+=
(加后赋值),
-=,
*=,
/=,
%=`
关系运算符
- 等于
==
:判断两个值是否相等 - 不等于
!=
:判断两个值是否不相等 - 大于
>
:判断左操作数是否大于右操作数 - 小于
<
:判断左操作数是否小于右操作数 - 大于等于
>=
:判断左操作数是否大于等于右操作数 - 小于等于
<=
:判断左操作数是否小于等于右操作数
逻辑运算符
- 与
&&
:当两个条件都为true
时结果为true
- 或
||
:当至少一个条件为true
时结果为true
- 非
!
:取反,将true
变为false
,false
变为true
- 异或
^
:当两个条件中仅有一个为true
时结果为true
三元运算符
- 条件表达式?值1:值2
- 如:int max = (a > b) ? a : b
- 执行流程:首先计算表达式的值,如果为true,返回值1,如果为false,返回值2
位运算符
- 按位与
&
:对两个整数的二进制位执行按位与操作 - 按位或
|
:对两个整数的二进制位执行按位或操作 - 按位异或
^
:对两个整数的二进制位执行按位异或操作 - 按位非
~
:对整数的每个二进制位取反 - 左移
<<
:将操作数的二进制位左移指定的位数 - 右移
>>
:将操作数的二进制位右移指定的位数,保持符号位 - 无符号右移
>>>
:将操作数的二进制位右移指定的位数,用 0 填充左侧空位
instanceof 运算符
- 用于测试一个对象是否是某个类或其子类的实例
类型转换运算符
-
用于将一种数据类型强制转换为另一种类型。如:
double d = 9.5; int i = (int) d;
- 加法
1.5 变量
public class Main {
int a; // 实例变量(成员变量)
static int b; // 静态变量(类变量)
public void function(){
int c; // 局部变量
}
}
局部变量
- 定义位置:局部变量是在方法、构造方法或代码块中定义的变量。
- 作用范围:局部变量的作用范围仅限于其定义的方法或代码块内。
- 初始化要求:局部变量在使用前必须被显式初始化,否则会引发编译错误。
- 生命周期:方法执行完后即销毁
实例变量(成员变量)
- 定义位置:实例变量是在类中定义的,但不在方法或构造方法内。
- 作用范围:实例变量属于类的每个对象,每个对象有自己独立的实例变量,不同对象的实例变量互不影响。
- 默认初始化:如果实例变量没有被显式初始化,Java 会为其提供默认值(数值类型默认是
0
,布尔类型是false
,引用类型是null
)。- 生命周期:实例变量在对象创建时初始化,在对象被垃圾回收时销毁。
静态变量(类变量)
定义位置:使用
static
关键字修饰,定义在类中,但不在方法内。作用范围:静态变量属于整个类,而不是任何一个对象,所有对象共享同一个静态变量。
初始化:静态变量在类加载时初始化,且只有一份。如果没有显式初始化,Java 会提供默认值。
生命周期:静态变量的生命周期贯穿整个程序运行期间,在类被卸载时销毁。
1.6 数据类型
基本数据类型
byte:占1字节、范围是-128到127,默认值是0
boolean:占1字节、默认值是false
short:占2字节、范围是-32768到32767,默认值是0
char:占2字节、默认值是空字符**
\u0000
**,在 ASCII 表中对应于值 0。int:占4字节、范围是-2,147,483,648到2,147,483,647,默认值是0
float:占4字节、默认值是0.0
double:8字节、默认值是0.0
long:8字节、默认值是0
注意:
随便写一个整数字面量默认是int类型的,如果想要当成long类型,需要在其后加l/L
随便写一个小数字面量默认是double类型的,如果想要当成float类型,需要在其后加f/F
字符char要用单引号’ ',字符串String要用双引号""
基本数据类型直接存储数值,内存开销小,性能高
引用数据类型
- 类
- 接口
- 数组
- 注意:
- 引用类型默认为
null
- 引用数据类型用于复杂的数据结构,存储对象引用
1.7 类型转换
自动类型转换(隐式转换)
向上转型,将数据类型从一种较小范围的类型转换为一种较大范围的类型时,可以自动进行转换
这种转换不需要额外的语法
可以从 低精度 到 高精度 进行转换,例如
byte
->short
->int
->long
->float
->double
char
类型可以自动转换为int
、long
、float
或double
,因为char
实际上是一个无符号的整数类型。int i = 100; long l = i; // int 自动提升为 long float f = l; // long 自动提升为 float
强制类型转换(显式转换)
- 向下转型,将一种数据类型转换为另一种不兼容的数据类型,通常是从 高精度 到 低精度
- 显式转换需要在目标类型前加上 类型转换符,即
(type)
- 将较大范围的数据类型转换为较小范围的数据类型,例如
double
->float
->long
->int
->short
->byte
- 如果将浮点数转换为整数类型,小数部分将被截断(不会四舍五入)
- 需要注意 数据溢出 的情况,因为强制转换可能导致精度损失或值的变化
double d = 9.99; int i = (int) d; // 强制将 double 转换为 int,小数部分将被截断
注意:对象转型之后,属性看编译类型。方法看运行类型。
1.8 数组
静态数组
- 定义数组的时候直接给数组赋值
- 格式一:数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3,…}
- 格式二:数据类型[] 数组名 = {元素1,元素2,元素3,…}
例如:
int arr[] = {18,19,20,21}; int arr2[] = new int[]{1,2,3,4,5} //注意int array[] = new int[5]{1,2,3,4,5}是错误的
动态数组
- 定义数组的时候只确定元素的类型和数组的长度,之后再存入具体的数据
- 格式:数据类型[] 数组名 = new 数据类型[长度]
例如:
int arr [] = new int[10];
二位数组
- 格式:数据类型[] [] 数组名=new 数据类型[行数] [列数]
例如:
int arr[][] = new int[2][4];
1.9 内存分配
堆内存
- 堆内存是线程共享的
- jdk8之前主要是用来存储对象实例和数组,jdk8之后原本存放于方法区的字符串常量池和静态变量也存储在了堆内存,堆内存的数据在初始化时,会为存储空间添加默认值
虚拟机栈
虚拟机栈是线程私有的
用来存储局部变量和方法调用
方法区(元空间)
方法区是线程共享的
用来存储类信息、常量池表和运行时常量池,jdk8之前字符串常量池和静态变量也存储在方法区
本地方法栈
- 本地方法栈是线程私有的
- 用来存储本地方法的调用信息,为本地方法服务
程序计数器
程序计数器是线程私有的
用来存储正在执行的字节码指令的地址
直接内存
- 直接内存位于本地内存,不属于JVM内存
二、程序流程
2.1 顺序结构
顺序结构是最基本的流程控制结构。
表示程序中的语句按照从上到下的顺序逐一执行,不涉及任何条件判断或循环。
2.2 选择结构(分支结构)
if
if (condition1) {
// 当条件1为 true 时执行
} else if (condition2) {
// 当条件2为 true 时执行
} else {
// 当上述条件都为 false 时执行
}
switch
switch (expression) {
case value1:
// 当 expression 等于 value1 时执行
break;
case value2:
// 当 expression 等于 value2 时执行
break;
default:
// 当没有匹配的值时执行
}
2.3 循环结构
普通for循环
for(初始化语句;循环条件;迭代语句){
循环体语句;
}
//如:
for (int i = 0; i < 5; i++) {
System.out.println("Number: " + i);
}
增强for循环
for(元素数据类型 变量名∶数组或者collection集合){
循环体语句;
}
//如:
int arr[]={1,2,3,4,5};
for (int i : arr) {
System.out.println("Number: " + i);
}
普通while循环
while(循环条件){
循环体语句;
}
//如:
while (i < 5) {
System.out.println("Number: " + i);
i++;
}
do-while循环
do{
循环体语句;
}while(循环条件)
//如:
int i = 0;
do {
System.out.println("Number: " + i);
i++;
} while (i < 5);
三、集合
3.1 Collection集合
- Collection是单列集合的祖宗接口
3.1.1 List集合
特点
- 有序:
List
是有序集合,元素按照插入顺序进行排列。- 可重复:允许存储重复的元素,列表中的每个元素都有其索引位置。
- 索引访问:可以通过索引访问元素,并对元素进行增删改查操作。
- 动态大小:
List
的实现类(如ArrayList
、LinkedList
)通常是动态大小的,可以随时扩展或缩小。
常用方法
添加元素
boolean add(E e)
: 添加元素到列表末尾。void add(int index, E element)
: 在指定位置插入元素。删除元素
E remove(int index)
: 删除指定索引处的元素,并返回删除的元素。boolean remove(Object o)
: 删除第一次出现的指定元素。修改元素
E set(int index, E element)
: 替换指定位置的元素,并返回被替换的元素。获取元素和查找元素
E get(int index)
: 获取指定索引处的元素。
int indexOf(Object o)
: 返回指定元素的第一个索引。
int lastIndexOf(Object o)
: 返回指定元素的最后一个索引。其他
int size()
: 返回列表中元素的数量。
boolean isEmpty()
: 判断列表是否为空。
实现类
ArrayList
- 特点:基于动态数组实现,查询速度快,适合频繁的随机访问。
- 缺点:插入和删除操作(尤其是在中间位置)较慢,因为需要移动元素。
- 应用场景:适合读操作频繁的场景。
LinkedList
- 特点:基于双向链表实现,插入和删除速度快,适合频繁的增删操作。
- 缺点:查询速度较慢,需要从头部或尾部逐个遍历元素。
- 应用场景:适合增删操作频繁的场景。
Vector
- 特点:基于动态数组实现,类似于
ArrayList
,但是Vector
是线程安全的,所有方法都被synchronized
修饰。- 缺点:由于线程安全机制,性能稍差于
ArrayList
。- 应用场景:在多线程环境下使用较多,但在现代开发中不推荐,通常使用
Collections.synchronizedList
或CopyOnWriteArrayList
替代。
3.2.2 Set集合
特点
- 无序性:
Set
集合中的元素没有固定的顺序(但某些实现类如LinkedHashSet
可以保持插入顺序)。- 不可重复:
Set
集合不允许存储重复的元素。如果试图将重复元素加入Set
,则添加操作将被忽略。- 允许包含一个
null
元素:大多数Set
实现类(如HashSet
)允许包含一个null
元素,但在TreeSet
中不允许。
常用方法
添加元素
boolean add(E e)
: 将元素添加到集合中,如果添加成功返回true
,否则返回false
。删除元素
boolean remove(Object o)
: 删除指定的元素。查询元素
boolean contains(Object o)
: 判断集合中是否包含指定元素。获取集合大小
int size()
: 返回集合中的元素数量。清空集合
void clear()
: 清空集合中的所有元素。
实现类
hashSet
- 特点:基于哈希表实现,元素无序,插入、删除、查找速度较快,且允许
null
值。- 实现原理:依赖于元素的
hashCode()
和equals()
方法来判断元素的唯一性。对于效率较高的查找和插入操作,HashSet
是无序的。- 应用场景:适合频繁进行插入、删除和查找的场景。
LinkedHashSet
- 特点:
LinkedHashSet
是HashSet
的子类,使用链表维护元素的插入顺序,因此具有 有序性。- 实现原理:在
HashSet
的基础上,增加了一个双向链表,用于维护元素的插入顺序。- 应用场景:适合需要有序遍历且希望具备
HashSet
插入、删除性能的场景。TreeSet
- 特点:
TreeSet
基于红黑树实现,存储的元素是有序的,按照自然排序或自定义的比较器排序,不允许null
值。- 实现原理:使用红黑树来存储元素,因此具有
O(log n)
的时间复杂度,适合用于需要排序的场景。- 应用场景:适合需要自动排序或范围查询的场景,例如排行榜、按字母顺序排列的字典。
3.2Map集合
特点
- 键值对存储:
Map
以键值对(key-value)的形式存储元素,每个键(key)映射到一个值(value)。- 键的唯一性:
Map
中的键是唯一的,不能重复;但是值(value)可以重复。- 允许一个
null
键:多数Map
实现(如HashMap
)允许一个null
键和多个null
值,但TreeMap
不允许null
键。
常用方法
添加元素
V put(K key, V value)
: 将指定的键值对加入Map
,如果键已存在,则会更新该键的值并返回旧值。获取元素
V get(Object key)
: 获取指定键对应的值,如果键不存在则返回null
。删除元素
V remove(Object key)
: 删除指定键的键值对,返回删除的值。判断是否包含
boolean containsKey(Object key)
: 判断Map
中是否包含指定的键。boolean containsValue(Object value)
: 判断Map
中是否包含指定的值。获取键和值的集合
Set<K> keySet()
: 获取Map
中所有键的集合。Collection<V> values()
: 获取Map
中所有值的集合。Set<Map.Entry<K, V>> entrySet()
: 获取Map
中所有键值对的集合,每个键值对被封装为一个Map.Entry
对象。
实现类
HashMap
- 特点:基于哈希表实现,无序,允许一个
null
键和多个null
值,查询和插入速度快。- 实现原理:通过键的
hashCode()
方法将键值对分配到不同的存储位置(哈希桶)中。- 应用场景:适合频繁的插入、删除和查找操作。
LinkedHashMap
- 特点:
LinkedHashMap
是HashMap
的子类,保持插入顺序,适合需要有序遍历的场景。- 实现原理:在
HashMap
的基础上增加了一个双向链表来维护元素的插入顺序。- 应用场景:适合需要按插入顺序遍历元素的场景。
TreeMap
- 特点:基于红黑树实现,具有排序功能,默认按键的自然顺序排序或自定义的比较器排序,不允许
null
键。- 实现原理:使用红黑树结构存储键值对,插入、删除和查找的时间复杂度为
O(log n)
。- 应用场景:适合需要自动排序或范围查询的场景。
Hashtable
- 特点:线程安全的哈希表,不允许
null
键和null
值。性能较HashMap
慢,适合需要线程安全的场景。- 实现原理:所有方法都是同步的,适用于多线程环境,但通常不推荐在现代开发中使用
Hashtable
,而是使用ConcurrentHashMap
。- 应用场景:不推荐在单线程中使用,更推荐使用
ConcurrentHashMap
作为替代。
3.3遍历方式
遍历方式 | 选择情况 |
---|---|
迭代器 | 在遍历的过程中需要删除元素,请使用迭代器。 |
列表迭代器 | 在遍历的过程中需要添加元素,请使用列表迭代器。 |
增强for循环 | 仅仅想遍历,那么使用增强for或Lambda表达式。 |
Lambda表达式 | 仅仅想遍历,那么使用增强for或Lambda表达式。 |
普通for | 如果遍历的时候想操作索引,可以用普通for。 |
3.3.1 Collection的遍历
增强for遍历
特点
- 增强for的底层就是迭代器,为了简化迭代器的代码书写的。
- 它是JDK5之后出现的,其内部原理就是一个lterator迭代器
- 所有的单列集合和数组才能用增强for进行遍历。
增强for遍历格式
for(集合中元素的数据类型 变量名:数组或者集合){ ...... }
细节和注意事项
- 修改增强for中的变量,不会改变集合中原本的数据。
forEach配合Lambda遍历
lambda表达式特点:()->{}
Lambda表达式遍历格式
集合名.forEach((集合中元素的数据类型 变量名)->{ 循环体... }); //如果循环体只有一行代码,则可以简化成以下形式 集合名.forEach(变量名 -> 循环体);
迭代器遍历
特点
- 在Java中的代表是lterator(),迭代器是集合的专用遍历方式
- 迭代器不依赖索引
- 迭代器就好比是一个箭头,默认指向集合的0索引处
常用方法
boolean hasNext() //询问当前位置是否有元素存在,存在返回true ,不存在返回false E next() //获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界 void remove() //删除当前获取到的元素
迭代器遍历格式
Iterator<集合中元素的类型> iterator = list.iterator(); while(iterator.hasNext()){ 集合中元素的类型 变量名 = iterator.next(); }
细节和注意事项
- 当指到最后一个位置时,如果还继续用next,就会报错NoSuchElementException
- 迭代器遍历完毕,指针不会复位循环中
- 在循环中最好不要用两个或以上次数的next方法
- 迭代器遍历时,不能用集合的方法进行增加或者删除,可以用迭代器提供的remove删除
3.3.2 Map的遍历
forEach配合Lambda遍历
map.forEach((key,value)->{ System.out.println(key+":"+value); });
键找值增强for遍历
//1.通过keySet方法,得到一个包含所有key的单列集合 Set<String> keySet = map.keySet(); //2.遍历包含键的单列集合 for (String key : keySet) { //3.通过get方法得到键对应的值 Integer value = map.get(key); System.out.println(key+":"+value); }
键值对增强for遍历
//1.通过entrySet方法,得到一个包含所有键值对的单列集合 Set<Map.Entry<String, Integer>> entrySet = map.entrySet(); //2.遍历包含键值对的单列集合 for (Map.Entry<String, Integer> entry : entrySet) { //3.通过getKey()、和getValue()得到对应的键和值 String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key+":"+value); }
简化版
for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey()+":"+entry.getValue()); }
四、异常与错误
4.1 异常的分类
错误
- 由java虚拟机生成并抛出的异常,程序无法处理,通常指程序中出现的严重问题。
- Error(错误)是不可查的,而且也常常在应用程序的控制和处理能力之外
- 因此当Error(错误)出现时,程序会立即奔溃,Java虚拟机立即停止运行
异常
指程序本身可以处理的异常(可以向上抛出或者捕获处理)
编译时异常
- 程序在编译过程中发现的异常,受检异常
- 如果代码中没有对这类异常进行处理,编译器会报错,程序无法通过编译。
- 必须在代码中通过try-catch语句处理,或者通过throws声明由上层调用者处理
运行时异常
- 又称非受检异常
- Java编译器不会对这些异常进行检查
- 这些异常通常是由程序逻辑错误引起的,如空指针异常、数组越界等
- 运行时异常不需要在编译时处理,可以在运行时选择捕获处理,也可以不处理
4.2 异常的处理
- Java处理异常的默认方式是中断处理
try-catch-finally
try{ //可能出现问题的代码 }catch(异常类型 异常名称){ //处理异常的代码 }catch(异常类型 异常名称){ //处理异常的代码 }finally{ //总是执行的代码(如资源清理 }
使用try、catch、finaly捕获异常后程序会继续执行
catch中声明的异常类型应该和实际抛出的异常类型要么相同要么有继承关系
finally修饰的代码一定会执行
try语句中,在执行return语句时,try中先把要返回的结果存放到不同于x的局部变量中去,执行完finally之后,在从中取出返回结果,因此,即使finally中对变量x进行了改变,但是不会影响返回结果。它应该使用栈保存返回值。
public static void main(String[] args) { System.out.println(f()); } public static int f(){ int x=1; try{ x++; return ++x; }finally { x++; System.out.println(x); } } //结果输出为4和3
throws和throw
- 使用throws和throw抛出的异常类型,出现异常后,程序终止
throws
- 位置:
throws
关键字用于方法声明中,紧跟在方法签名后。- 作用:表示当前方法可能抛出某种类型的异常(或多种异常),这些异常需要由调用该方法的代码来处理。
- 用法:当一个方法可能会抛出受检异常(
Checked Exception
)时,必须在方法签名中使用throws
声明这些异常。- 多个异常:如果一个方法可能会抛出多个异常,可以用逗号分隔列出所有异常类型。
[修饰符] 返回值类型 方法名(参数列表) [throws 异常1,异常2...]{ //可能抛出异常的代码 }
throw
- 位置:
throw
关键字用于方法体内。- 作用:用于显式抛出一个异常实例,通常用于手动抛出异常。
- 用法:
throw
关键字后跟异常对象(Exception
或其子类的实例)。可以通过new
创建异常对象来抛出。- 常见用途:通常用于根据某些条件抛出自定义异常或特定异常
throw new 异常
4.3 自定义异常
Java中的异常都是Throwable或者Exception或者RuntimeException的子类,那么我们要创建一个自定义的异常,其实就是创建其对应的子类。
/**
* 编写一个分数必须在0-100之间的异常
*/
public class Main{
public static void main(String[] args) {
int score = 101;
if(score<0||score>100)throw new ScoreException("分数不可分法");
}
}
class ScoreException extends RuntimeException{
public ScoreException(){
}
public ScoreException(String msg){
super(msg);
}
}
五、方法
5.1 方法的定义
修饰符 返回值类型 方法名(参数列表){ 方法体; return 返回值; }
- 如果方法的返回值类型为void(无返回值),方法内则不能使用return返回数据
- 如果方法的返回值类型写了具体类型,方法内部则必须使用return返回
- java的参数传递都是值传递
- 基本类型的参数传输存储的数据值
- 引用类型的参数传输存储的地址值
5.2 构造方法
- 当一个对象被创建时候,构造方法用来初始化该对象。
- 构造方法和它所在类的名字相同。
- 构造方法没有返回值,但可以有return,return在这里只是表示结束,并不是返回的表示。
- 默认构造方法的访问修饰符和类的访问修饰符相同
- 不管你是否自定义构造方法,所有的类都有构造方法,因为 Java 自动提供了一个默认构造方法
- 一旦你定义了自己的构造方法,默认构造方法就会失效
//有参构造方法 public Person(String name, int age) { this.name = name; this.age = age; } //无参构造方法 public Person(){ }
5.3 方法重载和重写
重载
在同一个类(或子类)里面
方法名字相同,参数列表不同
返回类型和权限修饰符可以相同也可以不同
可以抛出新的或更广的异常
被重载的方法必须改变参数列表(参数个数或类型不一样)
public class Person { void say(){ System.out.println("hello"); } //方法重载 void say(String talk){ System.out.println(talk); } }
重写
子类对父类的允许访问的方法的实现过程进行重新编写
方法名、参数列表和返回类型都相同(返回类型可以是子类)
权限修饰符要大于父类方法;不能抛出新的或者更广的异常
构造方法不能被重写、声明为 final 的方法不能被重写
声明为 static 的方法不能被重写,但是能够被再次声明(隐藏)
public class Person { void say(){ System.out.println("hello"); } } class Student extends Person{ //方法重写 void say(){ System.out.println("hello word"); } }
区别点 | 方法重载 | 方法重写 |
---|---|---|
参数列表 | 必须修改 | 不能修改 |
返回类型 | 可以修改 | 不能修改(可以是子类) |
异常 | 可以修改 | 不能抛出新的或者更广的异常 |
访问 | 可以修改 | 可以降低限制 |
六、面向对象
6.1 封装
概念
封装的主要思想是将对象的状态(属性)和行为(方法)隐藏起来,只暴露出必要的接口,以此来控制外部对对象的访问。
核心思想
- 隐藏对象的内部实现细节:对象的属性(字段)通常声明为私有(
private
),这样就不能直接从外部访问它们。- 提供公共方法访问属性:通过公共的(
public
)方法(如getter
和setter
)来访问和修改私有属性,这样可以控制和校验属性的合法性。- 保护数据的完整性和安全性:通过封装,能够防止外部直接访问或修改对象的状态,从而保证对象数据的一致性。
优点
增强代码的灵活性,便于维护和扩展(get和set方法)
隐藏实现细节,提高代码的安全性(private)
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("Jybzzz");
person.setAge(23);
System.out.println(person.getName()+" "+person.getAge()+"岁");
}
}
class Person{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
6.2 继承
核心概念
创建一个类,并继承另一个类的属性和行为。
父类(基类、超类、父类):被继承的类,称为父类,用于提供基本属性和行为。
子类(派生类、子类):继承父类的类,称为子类,子类自动拥有父类的所有属性和方法,还可以添加新的属性和方法,或者重写父类的方法。
继承的语法:在定义类时,使用
extends
关键字来表示继承关系。
优点
- 代码复用:子类可以直接复用父类的代码,从而提高代码的可重用性和扩展性
- 结构清晰:将通用的属性和方法放在父类中,使得代码结构更清晰,便于管理。
- 多态性:继承是实现多态的基础,可以通过父类引用指向子类对象,从而实现动态绑定。
特性
- Java 的继承是单继承,但是可以多重继承
- 子类拥有父类非 private 的属性、方法
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展
- 子类可以用自己的方式实现父类的方法
public class Main {
public static void main(String[] args) {
Student student = new Student();
student.setName("Jybzzz");
student.setGender("男");
student.setAge(23);
System.out.println(student.getName()+" "+student.getGender()+" "+student.getAge()+"岁");
}
}
class Person{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Student extends Person{
private String gender;
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
6.3 多态
核心概念
- 多态是指相同的操作作用于不同的对象时,可以产生不同的行为。
- 使用父类或接口类型的引用来指向子类对象,并在运行时根据实际对象类型调用相应的方法。
- 三大必要条件:继承、重写、父类引用指向子类对象
实现方式
编译时多态(静态多态):主要通过方法重载(Overloading)实现。
运行时多态(动态多态):主要通过方法重写(Overriding)实现,这是 Java 中多态最常用的形式。
优点
- 代码复用和可扩展性:多态允许我们编写更通用的代码,只需要使用父类或接口类型来处理不同子类的对象。添加新的子类不会影响到调用方的代码,便于扩展。
- 灵活性和可维护性:可以在不修改现有代码的情况下,增加新的实现,提高了代码的灵活性和可维护性。
- 减少重复代码:多态让我们在父类中定义公共的接口,使得不同子类可以共享相同的代码接口,避免重复代码。
特点
- 方法:编译看左边,运行看右边
- 变量:编译看左边,运行也看左边
interface Shape {
void draw();
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
public class Main {
public static void main(String[] args) {
Shape shape1 = new Circle(); // 接口引用指向实现类对象
Shape shape2 = new Rectangle();
shape1.draw(); // 输出: Drawing a circle
shape2.draw(); // 输出: Drawing a rectangle
}
}
6.4 static和final
static
static
可以用于变量、方法、代码块和嵌套类,其主要特点是与类绑定,而不是与类的实例绑定静态成员变量和方法的访问:
//方式一(推荐) 类名.静态成员变量 类名.静态方法 //方式二(推荐) 对象.静态成员变量 对象.静态方法
特点
- 静态方法只能访问静态成员和方法,不可以“直接”访问实例成员和方法
- 随着类的加载而被加载
- 优先于对象存在,被所有对象共享
- 局部变量不能被static修饰
final
final
关键字用于表示不可更改的特性,可以应用于变量、方法和类。特点
- 它修饰的类不能被继承。
- 它修饰的成员变量是一个常量,必须初始化
- 它修饰的成员方法是不能被子类重写的
6.5 this和super
this
this
关键字用于引用当前对象- 它在类的内部使用,指向调用当前方法或访问当前属性的那个对象。
使用场景
- 访问当前对象的属性
- 调用当前对象的其他方法
- 调用当前类的构造方法(构造器重载)
- 将当前对象作为参数传递
super
super
关键字用于引用父类对象- 在子类中,
super
关键字指向的是父类的对象,主要用于访问父类的属性和方法。使用场景
- 访问父类的属性
- 调用父类的方法
- 调用父类的构造方法
特性 | this | super |
---|---|---|
引用的对象 | 当前对象 | 父类对象 |
访问属性和方法 | 调用本类的其他构造方法 this(...) | 调用父类的构造方法 super(...) |
构造方法的调用 | 可以包含抽象方法和具体方法 | 默认方法是抽象的(Java 8+ 支持 default 方法) |
重写方法的调用 | 调用当前类的重写方法 | 调用父类中的原始方法(适用于重写的情况) |
使用场景 | 当前类内部,用于区分同名属性、方法调用 | 子类中,访问或调用父类的属性、方法和构造方法 |
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
private String breed;
public Dog(String name, String breed) {
super(name); // 调用父类的构造方法
this.breed = breed; // 使用 this 指向当前类的属性
}
@Override
public void sound() {
super.sound(); // 调用父类的 sound 方法
System.out.println("Dog barks");
}
public void displayInfo() {
System.out.println("Name: " + this.name); // 使用 this 指向当前对象的属性
System.out.println("Breed: " + this.breed);
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog("Buddy", "Golden Retriever");
dog.displayInfo();
dog.sound();
}
}
6.6 抽象类和接口
抽象类(is)
抽象类是使用
abstract
关键字声明的类,不能被实例化通常用于作为其他类的基类,提供部分实现或定义通用属性和方法。抽象类可以包含抽象方法和具体方法。
特点
- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类
- 抽象类中的抽象方法只是声明,不包含方法体
- 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类
- 抽象方法不能使用private、static、final修饰
- 抽象类可以有构造方法,但不能直接实例化,只能被子类调用。
- 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法
// 定义抽象类
abstract class Animal {
protected String name;
// 抽象方法(无方法体)
public abstract void sound();
// 具体方法(有方法体)
public void eat() {
System.out.println(name + " is eating");
}
}
// 子类继承抽象类
class Dog extends Animal {
public Dog(String name) {
this.name = name;
}
@Override
public void sound() {
System.out.println(name + " barks");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog("Buddy");
myDog.sound(); // 调用抽象方法的实现
myDog.eat(); // 调用具体方法
}
}
接口(like)
- 接口用interface关键字
特点
- 接口不能用于实例化对象,接口不是被类继承了,而是要被类实现
- 接口不能包含成员变量,除了 static 和 final 变量
- 接口中的方法会被隐式的指定为 public abstract
- 接口中的变量会被隐式的指定为 public static final 变量
- Java 8 之后允许
default
方法和static
方法
特性 | 抽象类 | 接口 |
---|---|---|
关键字 | abstract | interface |
多继承 | 不支持多继承(一个类只能继承一个抽象类) | 支持多实现(一个类可以实现多个接口) |
方法类型 | 可以包含抽象方法和具体方法 | 默认方法是抽象的(Java 8+ 支持 default 方法) |
访问修饰符 | 可以有任何访问修饰符 | 默认是 public |
构造方法 | 可以有构造方法(子类可调用) | 没有构造方法 |
设计目的 | 定义一个模板,包含通用属性和方法实现 | 定义一种能力或行为约定,实现多态性 |
// 定义接口
interface Animal {
void sound(); // 抽象方法
void eat();
}
// 实现接口的类
class Dog implements Animal {
@Override
public void sound() {
System.out.println("Dog barks");
}
@Override
public void eat() {
System.out.println("Dog is eating");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
myDog.sound(); // 调用接口方法的实现
myDog.eat(); // 调用接口方法的实现
}
}