Java SE(一)基础
Java SE
JAVA语言的跨平台实现原理
特点: 一次编译,到处运行
解释: Java语言代码编写一次,代码就可以在不同操作系统中运行,并且还能得到相同的运行结果
Java语言的使用可以无视操作系统之间的差异性
JVM : Java虚拟机,理解成一个软件,模拟计算机实现过程, 一个虚拟机就像是一个小型的计算机,主要功能就是可以运行标准的Java代码, JVM虚拟机为Java代码营造出相同运行环境
JDK&JRE&JVM
简介:
-
JVM (Java Virtual Machine): Java虚拟机,是运行所有Java程序的假想计算机,是Java程序的运行环境,是Java 最具吸引力的特性之一.我们编写的Java代码,都运行在 JVM 之上.
-
JRE (Java Runtime Environment): 是Java程序的运行时环境,但是单独的JVM虚拟机不能自己运行,需要支持JVM运行的功能代码,JRE包含 JVM 和运行时所需要的 核心类库。
核心类库:很多功能代码, 代码太多, 为了便于管理, 因此将代码放置到一个库中管理.
JRE = JVM + JVM运行时需要的核心类库功能;
Runtime表示运行的概念
-
JDK (Java Development Kit): 是Java程序开发工具包,包含 JRE 和开发人员使用的工具,例如 javac.exe(编译器) 和 javadoc.exe(可以自动为Java的代码生成说明文档)
JDK : 全称Java开发工具包, 主要功能就是可以进行Java代码的编译(编写), 还有运行
JDK = JRE + 开发时需要工具包(核心类库);
三个组件之间的关系
JAVA开发环境搭建
下载官网:https://www.oracle.com/java/technologies/downloads/#java8-windows
环境变量配置
配置环境变量的原因
当安装JDK成功之后, JDK有bin文件夹, 包含了所有的可以直接转换成命令运行的应用程序, 未来Java代码编译和运行,都需要从JDK的bin中找到命令, 执行代码;
问题 : 目前, JDK的bin文件夹路径下的命令只能在bin路径下使用, 有很大的局限性, 真正环境应该是可以在设备的任意路径都可以使用的
如何解决问题, 可以配置环境变量.
实现bin目录下的java相关命令可以在任意目录下使用
关键字
JAVA数据类型
整数类型
注意事项:
-
整数类型中字面值常量的默认类型是int类型
-
long类型的变量在定义的时候,当字面值超过int范围需要在字面值后加 L / l,建议 L
/*
整数类型注意事项2 :
定义一个long类型时, 如果数值范围超出了int,那么需要在数值之后
使用一个L或者l表示这个数据是长整型数据, 否则报错
*/
long lon2 = 1456785643125L;
System.out.println(lon2);
// 整数注意事项1 : 整数类型中字面值常量的默认类型是int类型
System.out.println(5);
小数(浮点)类型
注意事项:
-
小数类型的字面值常量默认类型是 double类型
-
float 类型变量在定义的时候,需要在字面值后加 F / f,建议F
-
double 类型的变量在定义的时候,可以在字面值后加 D / d,也可以不加.
-
浮点型存储的数据是不精确的数据,所以在运算过程中,其结果也有可能不精确.
double a = 0.1; double b = 0.2; System.out.println(a + b);// 0.30000000000000004
class Demo04_基本数据类型变量 { public static void main(String[] args) { // 2. 浮点类型变量定义 // 注意事项1 : 需要在float类型数据之后,添加一个F 或者 f, // 表示单精度的float类型数据 float f = 3.14F; System.out.println(f); double d = 5.999; System.out.println(d); double d1 = 0.1; double d2 = 0.2; /* 实际开发中 : 数据小数点精确度问题, 通常保留到两数后两位, 第三位四舍五入 计算金额,使用浮点类型最常见, 计算税率等等... 金额 : 通常2位小数 税率或者其他精确运算 : 一般小数点后4位, 从第五位四舍五入 */ System.out.println(d1 + d2);// 0.30000000000000004 } }
字符类型
- 字符类型变量的赋值方式:
(1) 通过 '' 形式赋值
案例:
char c = 'A';
(2) 通过ASCII码表赋值, 但是如果直接使用整数给char类型赋值, 那么整数范围必须在0-65535之间, 因为人类的语言文字, 放在一起, 一共有65535个字符可以表示, 超出范围,无法对应编码表, 代码报错
案例:
char c = 65;
-
编码表存在的解释说明:
人类语言文字,需要被计算机识别, 而计算机底层全部都是二进制, 也就是数字; 因此需要将人类的语言文字与数字形成对应关系, 目的就是为了让计算机以数字的形式将字符存储下来, 于是最早期, 美国人形成了一张表, 简称ASCII编码表;
ASCII编码表中存储了所有的字母大小写, 英文符号以及数字与整数的对应关系
A : 0100 0001 // A在计算机中实际存储的是二进制的0100 0001
注意 : 比较常见编码需要记住:
常用的ASCII码表值
'A' -- 65 'a' -- 97 '0' -- 48
ASCII编码表:
中国有自己的编码表, 兼容了ASCII, 同时咱们编码表中, 包含所有中文文字
GBK : 中国标准信息交换码表
UTF-8 : 国际标准码表, 包含了各个国家语言文字
布尔类型
引用数据类型
String 类是字符串,在定义String类型的变量的时候,使用 "" 括起来表示字符串常量内容
举例 : String str = “写任意字符序列”;
基本数据类型转换
基本数据类型转换概念
(1) 转换原因: Java是强类型语言,数据类型之间不能随意转换,但运算时数据类型不匹配,所以需要转换.
(2) 数据类型转换分类:
a : 自动类型提升
b : 强制类型转换
1. 自动类型提升
范围小的数据类型可以直接转换为范围大的数据类型
举例 : 50ml水杯, ---> 小数据类型
500ml水瓶---> 大数据类型
将水杯中水倒入到水瓶中, 可以, 容量完全符合
数据类型范围大小排序:
boolean类型不参与大小排序
注意事项:
int类型以下运算提升为int,int以上计算提升为较大类型
2.强制类型转换
(1) 大范围数据类型的变量/常量 赋值 给一个小范围数据类型的变量,需要进行强制转换.
举例 : 50ml水杯, ---> 小数据类型
500ml水瓶---> 大数据类型
将水瓶中水倒入到水杯中, 可以, 但是水有可能会溢出
(2) 格式:
目标数据类型 变量名 = (目标数据类型)原数据类型变量名或原常量值;
(3) 注意事项:
-
强制类型转换可能会损失精度,得到错误的数据.
-
小数类型强制转换为整数类型时,直接舍去小数部分,只保留整数部分.
(4) 常量优化机制: 在编译时,整数常量的计算会直接算出结果,并且会自动判断该结果是否在该数据类型取值范围内
运算符
1.算术运算符
2.赋值运算符
3.比较运算符
4.逻辑运算符
5.三元运算
1.元的概念: 可以操作的数据或者表达式.
2.三元运算符: 别名三目运算符,同时可以操作三个数据或者表达式的运算符.
3.格式: (典型的两种情况选择其中一种)
表达式1 ? 表达式2 : 表达式3;
4.说明:
1)表达式1必须是boolean类型的表达式,计算结果只能是true、或者false
2)表达式2和表达式3可以是任意类型的表达式
5.运算流程:
1)计算表达式1的值,要么为true,要么为false
2)如果表达式1计算结果为true,就选择表达式2作为三元表达式的计算结果
3)如果表达式1计算结果为false,就选择表达式3作为三元表达式的计算结果
6.位运算符
<<:左移 | 空位补0,被移除的高位丢弃 |
---|---|
>>:右移 | 被移位的二进制最高位是0,右移后,空缺位补0;最高位是1,高位补1。 |
>>>:无符号右移 | 被移位二进制最高位无论是0或者是1,空缺位都用0补。 |
A = 0000 1010
B = 0000 0101
A & B = 0000 0000 按位与运算符: 全为1 才为 1
A | B = 0000 1111 按位或运算符: 有一个为1 就为1
A ^ B = 0000 1111 按位异或运算符: 不同才为1
~A = 1111 0101 按位取反运算符: 全部取反
A << 2 将得到 0010 1000,即为 40 :左移运算符: 左移1 代表 乘2, 左移2 代表 乘4
A >> 2 将得到 0000 0010,即为 2 :右移运算符: 右移1 代表 除2, 右移2 代表 除4
位运算符的小技巧:
1、判断奇偶性
按位与: &1(0000 0001) 等于 0 为偶数, 等于 1 为奇数,因为偶数末位一定为0,其他位都是在 &0 结果都是0
2、判断是否是2的幂次方
return num & (num -1)== 0
比如4 100 & 011 = 0,是2的幂次方
3、按位或: x | 0 等于 x
将两个值(x,y)拼在一起作为新的值z
z = x << 32 | y
说明:上面的前提是z的类型是64位,这样z的前32位代表的是x,后32位代表的y。想要再通过z获取x,则
x = z >> 32
4、按位与: x & 1 等于 x
5、常用操作
& = 取交集
| = 取并集,比如 x | y = x 中再加 y
& ~x : 将x去掉(跟x的反取交集,相当于去掉x)
流程控制
-
流程控制 : 代码的执行顺序, 分为三大类: 顺序结构, 分支结构, 循环结构
-
分支结构: 当代码执行到某种情况下, 接下来可以有多种选择, 挑选最符合当下场景的其中一种逻辑运行, 多选一, 实现,主要就是if语句, switch语句
1. if 系列语句:
if(boolean表达式结果){// true
语句体;
}
if(boolean表达式结果){// 二选一
语句体1;
}else{
语句体2;
}
if(表达式1){
语句体1;
}else if(表达式2){
语句体2;
}
...
else{// else可以没有
语句体n;
}
2. switch语句
switch(表达式){// 类型 : byte,short,char,int ,String, 枚举
case 常量1:
语句体;
break;
...
default: // 可以没有
语句体n;
}
//没有break就会发生, case穿透性
3. 循环结构: 只要判断条件为true,循环体就能执行
for( 初始化语句; 循环判断条件 ; 初始化语句变化){
循环体;
}
while(){
}
do{
}while();
//死循环 : 循环不结束, 使用场景, 未知循环次数, while(true).登录案例
continue : 结束本次循环,继续进行下一次循环, 只要可以进行循环中数据的筛选操作
break : 结束循环
1) 结束switch
2) 结束循环
方法
方法定义的格式
修饰符 返回值类型 方法名称(参数列表) {
方法体语句;
return语句;
}
方法调用
方法定义注意事项
1.方法定义注意事项:
1)方法不能嵌套定义,每一个方法都是独立的
2)方法的先后没有区别,都是平级关系
3)方法可以嵌套调用,甚至可以自己调用自己
方法内存理解
栈内存概念:
JVM虚拟机运行代码: JVM虚拟机就是运行Java代码的容器,JVM本身就是个软件,运行时就需要在内存中开辟空间,JVM将其占有的内存空间划分成5块空间区域,其中一块空间,称为栈内存
栈内存: 代码中的方法运行, 需要在栈内存中开辟空间运行; 方法调用时进入栈内存中开辟空间, 方法运行完毕, 出栈内存, 占有空间释放(方法弹栈死亡)
方法重载
方法的重载: Overload,超载单词
方法重载的概念记住 : 有时笔试题中, 会出现 “什么是Overload(重载), 什么是Override(重写)”
在同一个类中,方法名相同,参数列表不同,与返回值类型无关的多个方法,称为重载
数组
数组含义: 集中存储相同类型数据的容器
数组特点:
1)存储数据长度固定的容器, 数组是一个定长容器, 当定义一个数组时, 必须指定数组的长度(可以存储的数据的个数)
2)存储数据的数据类型要一致
数组动态初始化
数组的定义:
数据类型[] 数组名称 = new 数据类型[数组长度]; // 更常见
数据类型 数组名称[] = new 数据类型[数组长度];
数组的访问格式
索引访问数组中的元素:
数组名[索引] = 数值; //为数组中的元素赋值
变量 = 数组名[索引]; //获取出数组中的元素
扩展:
当定义出一个数组时, JVM虚拟机会默认为数组中的每一个元素进行默认的赋初值动作, 根据不同数组中存储的数据类型不同, 初值也不同
整数 : 0
浮点类型 : 0.0
char类型 : ‘ ’
boolean类型 : false
引用数据类型 : null, null表示引用数据类型为空
数组静态初始化
1.含义: 在创建数组时,直接将元素确定的定义方式
2.格式:
数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, ..., 元素n};
注意事项:
1)在第二个方括号中,不能写数组元素个数
2)元素值的罗列,元素和元素之间,使用逗号分隔
3)罗列出来的元素值,数据类型必须和声明数组时数据类型一致
3.简化格式:
数据类型[] 数组名 = {元素1, 元素2, ..., 元素n};
注意事项: 不能分成两行写
数组的异常
1.异常(Exception): 表示在Java代码编译和运行过程中,出现了不正确的,不正常的,不符合实际场景的情况,统称为异常
2.数组中常见的异常有两种:
a: 数组索引越界异常
b: 空指针异常
数组索引越界异常
1.索引越界异常:
ArrayIndexOutOfBoundsException
数组 索引 超出 边界 异常
2.发生索引越界异常原因:
使用的索引在数组中不存在
3.代码分析:
观察一下代码,运行后会出现什么结果
public static void main(String[] args) {
int[] arr = {1,2,3};
System.out.println(arr[3]);
}
创建数组,赋值3个元素,数组的索引就是0,1,2,没有3索引,因此我们不能访问数组中不存在的索引,程序运行后,将会抛出 ArrayIndexOutOfBoundsException 数组越界异常.在开发中,数组的越界异常是不能出现的,一旦出现了,就必须要修改我们编写的代码.
4.越界异常避免方案
不要访问不存在的索引,使用索引时,验证索引在"0--数组长度-1"范围
5.数组的长度属性: 每个数组都具有长度,Java中赋予了数组一个属性,用于获取数组的长度,语句为:数组名.length
空指针异常
1.空指针异常
NullPointerException
空 指针 异常
2.发生空指针异常原因:
当引用数据类型值设置为null(前提),证明这个变量引用没有指向任何堆内存地址,但仍然想通过这个变量引用访问堆内存中数据,就会报出空指针异常
注: null,即为空,用于表示在栈内存的引用中,不记录任何堆内存的地址
3.代码分析:
观察一下代码,运行后会出现什么结果
public static void main(String[] args) {
int[] arr = {1,2,3};
arr = null;
System.out.println(arr[0]);
}
arr = null这行代码,意味着变量arr将不会再保存数组的内存地址,也就不允许再操作数组了,因此运行的时候会抛出NullPointerException 空指针异常.在开发中,数组的越界异常是不能出现的,一旦出现了,就必须要修改我们编写的代码.
数组的内存理解
JVM的内存划分:
数组在内存中的存储
一个数组的内存图
数组引用的输出的结果是地址,是数组在内存中的地址.new出来的内容,都是在堆内存中存储的,而方法中的变量arr保存的是数组的地址.
两个数组的内存图
引用数据类型每次new,都在内存中开辟新的空间地址.
多个引用指向同一个数组空间
任意一个引用修改了数组中的内容,其他引用访问到的就是修改后的数据.
数组的操作
遍历数组元素
class Demo11_数组的遍历 {
public static void main(String[] args) {
// 方法调用需要传递实际参数
// int[] arr = {17,-9,0,7,8,6678};
int[] arr = null;
getArrayElement(arr);
}
// 定义出一个方法功能 : 将一个int[]数组中的每一个元素都获取到
public static void getArrayElement(int[] arr){
if(arr != null && arr.length > 0){
// 以数组的索引为遍历依据
for(int index = 0; index < arr.length; index++){
int ele = arr[index];
System.out.println(ele);
}
}else{
System.out.println("null数组或者数组中没有元素, 无法进行遍历操作");
}
}
}
数组获最值
class Demo01_数组求最值 {
public static void main(String[] args) {
// 1. 定义出一个int[] 数组
int[] arr = {12,88,17,-9,16};
if(arr != null && arr.length > 0){
// 2. 设置出一个默认的最大值, 数组中的0索引位置元素
int max = arr[0];
// 3. 依次获取到数组中的剩余元素
for(int index = 1; index < arr.length; index++){
int ele = arr[index];
// 4. 如果数组中的元素大于默认值max,进行最大值替换
if(ele > max){
max = ele;
}
}
System.out.println("数组中的最大值为:" + max);
}else{
System.out.println("数组为null或者数组中没有元素, 无法获取出最大值");
}
}
}
数组元素交换
class Demo02_数组中元素交换 {
public static void main(String[] args) {
int[] arr = {20,21,22,23,24};
changeArrayEle(arr,0,3);
}
// 定义出一个方法功能, 将一个int[]数组中的两个指定索引元素值进行位置交换
public static void changeArrayEle(int[] arr, int index1, int index2){
if(arr != null && (index1 >=0 && index1 <= arr.length-1) &&
(index2 >=0 && index2 <= arr.length-1)){
// 1. 获取出index1索引元素值, 并且使用一个临时变量temp保存
int temp = arr[index1];
// 2. 将index2索引位置元素值给index1进行赋值
arr[index1] = arr[index2];
// 3. 将index1索引位置的原值赋值给index2索引位置
arr[index2] = temp;
// 4. 查看数组中元素结果
for(int index = 0; index < arr.length; index++){
System.out.println(arr[index]);
}
}else{
System.out.println("数组或者索引不符合替换规则,无法进行操作");
}
}
}
数组的反转
class Demo03_数组元素的反转 {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
reverseArray(arr);
}
public static void reverseArray(int[] arr){
if(arr != null && arr.length > 0){
// 每一次循环都需要两个对称的索引元素进行位置交换
for(int beginIndex = 0, endIndex = arr.length-1;
beginIndex < endIndex ;beginIndex++ , endIndex--){
// 1. 元素替换
int temp = arr[beginIndex];
arr[beginIndex] = arr[endIndex];
arr[endIndex] = temp;
}
// 查看数组中元素结果
for(int index = 0; index < arr.length; index++){
System.out.println(arr[index]);
}
}else{
System.out.println("数组不符合反转需求操作");
}
}
}
冒泡排序
class Demo04_冒泡排序 {
public static void main(String[] args) {
/*
第一轮 : 比较三次
索引位置比较
0,1
1,2
2,3
第二轮 : 比较2次
0,1
1,2
第三轮 : 比较1次
0,1
*/
int[] arr = {12,-8,88,19};
// i值表示目前比较的轮数
for(int i = 0; i < arr.length-1; i++){// 外层循环表示冒泡排序需要比较的轮数
for(int index = 0; index < arr.length-1-i;index ++){
// index 需要与 index+1进行比较, 如果小索引位置的值比大索引位置值
// 更大, 需要元素之间的位置交换
if(arr[index] > arr[index+1]){
int temp = arr[index];
arr[index] = arr[index+1];
arr[index+1] = temp;
}
}
}
for(int index = 0; index < arr.length; index++){
System.out.println(arr[index]);
}
}
}
IDEA快捷键
1、ctrl+alt+空格 代码提示,代码补全
2、psvm 提示主方法生成
3、sout 提示打印语句
4、ctrl+alt+l 格式化代码
5、ctrl+y 删除当前行,删除的就是鼠标所在行
6、alt+enter 智能提示
7、alt+insert 快速生成代码, 面向对象中生成代码快捷键(构造器, setter和getter方法...)
8、ctrl+alt+t 快速包裹一段代码
实际开发中使用很多快捷键:
9、ctrl + / : 单行注释
10、ctrl + shift + / : 多行注释
11、ctrl + f 文件内查找. f表示find
ctrl + r 查找+替换,r表示replace, 替换
跟进源代码查看 : ctrl + 方法名或者变量名
esc退出查找和替换
12、查看当前文件大纲 : ctrl + F12
面向对象
面向对象概述:
面向过程 : 有一个需求实现,思想, 我怎么去实现, 强调的是做事的步骤,需要自己一步一步的去操作实现。
面向对象的对象泛指现实中一切事物,每种事物都具备自己的属性和行为。面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,描述成计算机事件的设计思想。
面向对象区别于面向过程思想,有一个需求实现思想, 谁来帮我实现, 强调的是通过调用对象的行为来实现功能,而不是自己一步一步的去操作实现。
面向过程是面向对象的基础, 面向对象基于面向过程实现的, 但是代码思想层面上, 面向对象时更高级的设计思想
面向对象的特征:
1、封装
2、继承
3、多态
类和对象
类的概述
类是一组相关属性和行为的集合
属性 : 一个事物的特征(成员变量或者全局变量)
行为 : 一个事物可以做功能(方法)
对象的概述
java是一类事物的具体体现。
对象是类的一个实例,必然具备该类事物的属性和行为。
类与对象的关系
类是对一类事物的描述,是抽象的。
对象是一类事物的实例,是具体的。
类是对象的模板,对象是类的实体。
类的定义
格式:
修饰符 class 类名 {
成员变量1;
成员变量2;
......
成员方法1;
成员方法2;
......
}
类中的内容:
属性:
对事物特征的描述体现 java代码中使用变量体现 被称之为成员变量。
【和以前定义变量几乎是一样的。只不过位置发生了改变。在类中,方法外。】
行为:
对事物功能【能干什么】的描述 java代码中使用方法体现 被称之为成员方法。
【和以前定义方法几乎是一样的。只不过把static去掉,static的作用在后面课程中再详细讲解】
对象的使用
-
创建对象: 类名 对象名 = new 类名(); // 类本身就是一种引用数据类型
-
成员的使用:
获取成员变量值:
变量 = 对象名.成员变量;
修改成员变量值:
对象名.成员变量 = 新值;
访问成员方法:
对象名.成员方法(实参);
- 成员变量的默认值
对象内存图
一个对象创建的内存图
两个对象的内存图
两个对象引用指向同一个内存空间
匿名对象的使用
-
概述 : 没有名字的对象
-
格式 : new 类名(实参);
-
特点 : 只能使用一次
-
使用场景: 如果某个成员或方法只使用一次, 就可以使用匿名对象
-
优势: 在内存中存在时间短, 相对节省内存
public class TestPerson { public static void main(String[] args) { // 1. 创建出一个对象, 有名对象 // 类名 对象名 = new 类名(); // 使用很多次 Person p = new Person(); // 2. 匿名对象: 这个对象在堆内存中开辟的空间地址,没有任何变量存储 // 匿名对象使用特点 : 只能使用一次 // new 类名().可以调用一次成员变量或者一次方法功能 new Person(); // 3. 匿名对象的使用 new Person().eat(); System.out.println(new Person().age);// 0 // 4. 匿名对象的使用场景 : 如果对于某一个对象中的成员只需要使用一次, 那么可以使用匿名对象 // 因为匿名对象使用一次结束之后, 这个对象就再也不能使用, JVM虚拟机对于无法再使用对象就会变成 // 垃圾, 效果,这个匿名对象很快就会被JVM清理掉, 占有的空间就会消失 // 因此 匿名对象在内存中存在时间很短, 相当于变相的优化内存的使用 } }
成员变量和局部变量的区别
-
定义位置不同
成员变量: 类中方法外
局部变量:方法中 -
内存位置不同
成员变量:堆空间
局部变量:栈空间 -
生命周期不同
成员变量:随着类对象的创建而创建,随着对象的消失而消失
局部变量:随着所在方法的执行而创建,随着方法的执行完毕而消失 -
默认值不同
成员变量:有默认值
局部变量:没有默认值
封装
- 封装的原则:
-
- 将属性隐藏起来
-
- 使用时对外提供公共的访问方式
- 封装的好处:
-
- 隐藏了事物的实现细节
-
- 提高了代码的复用性
-
- 提高了代码的安全性
private关键字
-
private : 是一个关键字, 是私有的的意思, 是一个修饰符
-
修饰内容:
修饰成员变量(最常用)
修饰成员方法
修饰构造方法
修饰内部类 -
修饰效果:
只能在本类中使用,其他所有外类不能直接使用private修饰的成员
getter&setter
- getter方法和setter方法是对属性封装后对外提供的统一访问属性的访问方式
- 作用:
getXxx():获取封装属性的值
setXxx(参数列表):修改封装属性的值
注意:Xxx代表的是封装属性的名称,首字母大写
public class Animal {
String name;
// 1. 使用private修饰age, 那么age对于其他外类不能直接访问
private int age;
// 2. 需要为age对外提供公共的访问方式
// 定义出两个方法功能 :
// 1) setter 方法 : 提供对外的给私有成员变量age赋值方式
// 2) getter 方法 : 提供对外的获取私有成员变量age方式
public void setAge(int a){
if(a < 0){
System.out.println("输入的命令有误,无法进行赋值,年龄默认值为0");
}else{
age = a;
}
}
public int getAge(){
return age;
}
}
public class TestAnimalPrivate {
public static void main(String[] args) {
// 1. 创建出一个Animal类型对象
Animal a = new Animal();
a.name = "小羊";
// 问题 : 对于一个对象中属性直接进行操作时, 数据的正确性无法保证
// 代码中需要,正确的数据
// 于是:不要直接操作成员变量(属性),在属性前面添加一个关键字 private
// private : 表示私密的, 私有的
// 使用了private修饰的成员 : 只能在当前类中使用
// a.age = -2;
a.setAge(-2);
System.out.println(a.getAge());
a.setAge(12);
System.out.println(a.getAge());
// System.out.println(a.name + "---" + a.age);
}
}
封装优化
把封装后对相关操作的修改操作称之为封装的优化
变量的访问原则
- 总体原则:就近访问原则
- 就近访问:
当在访问某个变量名称的时候,会先寻找最近的该变量名称的定义,如果寻找到了,就使用该变量,如果没有找到,才到更远的位置寻找该变量名称的定义。
比如:
当局部变量和成员变量同名的时候,一定是先使用局部位置定义的变量
this关键字
- this关键字含义:代表所在类的当前对象的引用(地址值),即对象自己的引用。
- this关键字作用:
- 当成员变量和局部变量重名, 带有this关键字的变量表示成员变量的使用
- 开发中, setter方法中局部变量和成员变量同名赋值失败的问题就可以使用this关键字来解决
哪个对象调用了带有this关键字的方法, this关键字就表示哪个对象本身
构造方法
Cat c = new Cat();
类名 对象名 = new 类名(); // 对象创建过程中, 最后的小括号就表示构造方法调用
-
构造方法概述: 当一个对象被创建时候,构造方法用来初始化该对象,给对象的成员变量赋初始值。构造方法有自己独有的方法格式。
解释 : 创建对象,构造方法必须调用,既然必须要调用构造,就要让这个构造方法发挥最大价值, 因此可以在创建对象的同时, 就给对象中成员变量进行赋值 -
构造方法分类: 有参构造, 无参构造
-
构造方法定义格式:
修饰符 构造方法名(参数列表){
// 方法体,主要就是给对象中成员变量进行赋值
}
- 构造方法特点:
-
- 方法名必须和类名一致
-
- 没有返回值类型,连void都不写
-
- 不需要返回值类型
-
- 默认被jvm调用使用, 构造方法调用时机:
在创建对象同时, 被JVM虚拟机主动调用,并且创建一次对象, 构造方法只调用一次, 因为构造方法无法手动通过对象名调用
- 默认被jvm调用使用, 构造方法调用时机:
注意事项:
- 如果你不提供构造方法,系统会给出无参数构造方法。
- 如果你提供了构造方法,系统将不再提供无参数构造方法。
- 构造方法是可以重载的,既可以定义参数,也可以不定义参数。
如果代码中即定义了空参数构造, 也同时定义了有参数构造, 那么创建对象时, 可以从多个构造中选择其中任意一个调用即可 - 如果代码中定义有参数构造, 请一定要加上空参数; 代码中必须要有一个空参数构造,面向对象中的很多使用, 都是空参数构造的调用. 或者自己不定义构造, 使用系统默认生成的空参构造就可以
方法重载 : 定义在同一个类中, 方法名字相同, 参数列表不同的多个方法之间, 称为方法重载, 与方法是否有返回值无关
标准JavaBean
JavaBean是Java语言编写类的一种标准规范。符合JavaBean 的类,要求
- 类必须是具体的和公共的
- 所有属性使用private修饰
- 提供用来操作成员变量的set 和get 方法
- 并且具有无参数的构造方法(建议有)
格式:
public class ClassName{
//成员变量【必须封装】
//构造方法
//无参构造方法【必须】
//有参构造方法【建议】
//成员方法
//getXxx()
//setXxx()
}
静态
静态概述
-
静态就是 static , 主要用来修饰java的变量和方法的关键字。
-
没有静态与有静态的场景对比:
a : 没有静态
如果某个类型的所有对象,都具有一个相同的属性值,那么这个属性值就没有必要在所有对象中,都存储一份。还有坏处:浪费内存空间;维护难度大,一旦需要修改,就得修改所有的对象。b: 有静态
如果某个类型的所有对象,都具有一个相同的属性值,那么就在这个属性的定义上,加一个static静态关键字。让该变量存储在方法区字节码的静态区中,避免了所有对象都存储相同数据的问题,节省了内存空间,将来维护容易(只需要修改一次)
静态的内存理解
没有静态内存图:
有静态的内存图:
静态变量的特点
- 静态变量属于类, 不属于对象
- 加载时机:
随着类.class文件的加载而加载
静态变量随着类的加载进方法区,就直接在静态区给开辟了存储静态变量的内存空间 - 静态变量优先于对象而存在
- 静态变量被所有该类对象所共享
- 调用方式:
类名调用 或者 创建对象调用
扩展 : 类型中的静态成员变量, 或者是静态方法, 都可以直接使用类名调用, 而不需要创建对象
public class TestStudentStatic {
public static void main(String[] args) {
// 1. 创建出一个学生对象
Student s1 = new Student();
System.out.println(s1.schoolName);// 第一中学
// 修改schoolName
s1.schoolName = "第二中学";
System.out.println(s1.schoolName);// 第二中学
// 2. 创建出一个学生对象
Student s2 = new Student();
System.out.println(s2.schoolName);// 第二中学
// 3. 静态成员可以使用类名直接调用
// 类名.直接调用静态 最推荐的静态使用方式
System.out.println(Student.schoolName);// 第二中学
}
}
静态访问的注意事项
总结: 静态方法不能直接访问非静态资源(变量,方法,this关键字)
1、静态方法:在方法声明上,加上了static关键字的方法,就是静态方法
2、静态方法不能访问非静态的变量
原因:
静态方法可以在没有创建对象的时候调用,而非静态的变量只有在对象创建之后才存在。如果静态方法可以访问非静态的变量,那么就相当于在对象创建之前,就访问了对象创建之后的数据。明显不合理。
3、静态方法不能访问非静态的方法
原因:
静态方法可以在没有创建对象的时候调用;非静态的方法可以访问非静态的变量。如果静态方法可以访问非静态的方法,就相当于静态方法间接的访问了非静态的变量,和第2点矛盾。
4、静态方法中不能存在this关键字
原因:
this关键字表示本类当前对象。静态方法可以在对象创建之前调用。如果静态方法可以访问this关键 字,相当于在创建对象之前,就使用了对象本身---矛盾
静态成员变量和非静态成员变量的区别
-
概念上,所属不同:
非静态变量属于对象
静态变量属于类,类变量 -
内存空间不同,存储位置不同
非静态变量属于对象,所以存储在堆内存中
静态变量属于类,存储在方法区的静态区中 -
内存时间不同,生命周期不同
非静态变量属于对象,所以生命周期和对象相同,随着对象的创建而存在,随着对象的消失而消失
静态变量属于类,所以生命周期和类相同,随着类的加载而存在,随着类的消失(内存管理)而消失 -
访问方式不同
非静态变量只能使用对象名访问
静态变量既可以使用对象访问,也可以通过类名访问:
类名.静态变量名 或者 对象名.静态变量
类名.静态方法名()
继承
格式:
class 子类 extends 父类 { }
父类:被继承的类,超类、基类
子类:用于继承的类,派生类
继承的优势:
- 能提高代码的复用性
- 提高代码的可维护性
- 为多态提供了前提
继承中弊端:
继承让类与类之间产生了关系,类的耦合性增强了(关联程度太高),当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性
实际Java开发中:
- 高内聚 : 类中功能进行尽量多
- 低耦合 : 少与外类发生密切关联
继承的注意事项
1、私有的成员不能被继承
父类中有一些私有成员,不能在子类中直接使用
其实在子类对象中,仍然包含了父类中定义的私有成员变量
只不过在子类中,不能直接访问父类中定义的私有成员变量
2、父类中的构造方法,不能继承
原因:
父类的构造方法需要和父类的类名一致、子类的构造方法需要和子类类名一致,父类和子类的类名不一样。因此无法继承,名称有冲突。
父类的构造方法用于给父类的成员变量赋值,子类的构造方法用于给子类的成员变量赋值,子类的成员变量较多,使用父类的构造方法无法将子类中所有的成员变量都进行赋值,因此不继承父类的构造方法
解决:
子类不继承父类的构造,但是可以【调用】父类的构造方法。
Java语言中继承的特点
1、java支持单继承,不支持多继承,java支持多层继承
单继承:一个子类只能继承一个父类(一个孩子只能有一个亲爹)
不能多继承:一个子类不能同时继承多个父类
可以多层继承:A类可以继承B类,B类可以继承C类,A类中拥有B、C类中的所有属性和方法。说明:越是顶层的类,定义的功能越是共性功能,功能和属性就越少;越是底层的类,定义的特有功能和属性就越多,就更加强大。学习一个体系的时候,先学顶层的类,共性功能学习完,学习底层特有的方法即可;使用一个类创建对象的时候,选择底层的类型,功能更多更强大。
2、原因:
如果支持多继承,那么可能一个子类继承了两个父类,两个父类中有相同的方法声明,却拥有不同的方法实现。子类继承之后,就不知道该走哪个父类的方法实现了。(安全隐患)
继承中成员变量的关系
子类可以继承并且使用父类中的所有非私有的成员变量
-
父类中定义了成员变量,子类中没有定义,那么子类可以直接使用父类中非私有成员
-
父类中没有定义成员变量,子类中定义了,子类可以自己调用自己的变量,父类不能调用子类特有变量
总结: 子类可以自己和父类的成员变量,父类只能使用自己的成员变量 -
父类中定义了变量,子类中重新定义了这个变量,在子类中调用的就是子类的变量
原因: 变量的访问就近原则
-
- 方法内部,自己定义了变量,使用方法内部变量
-
- 方法内部没有定义,找当前类中成员变量
-
- 类中成员变量也没有定义,找父类中的成员变量
-
- 父类中没有定义变量,再继续找父类的父类,直到找到最后(Object类,所有类的直接或者间接的父类,Object是一个最顶层的类,也没有找到,才报错
-
如果子父类中成员变量重复定义, 想调用父类成员变量, 那么使用关键字 super
super : 关键字, 表示当前类型父类引用(super使用于子类类型中)
使用 : super.父类成员变量名;注意 : super关键字第一种使用场景, 区分子类和父类重名成员, 带有super.成员表示父类成员使用
关键字this和super的详细解释
1、this和super:
this表示本类当前对象的引用
super表示本类当前对象父类的引用
2、继承关系的内存理解:
1) 如果Zi类类型需要进入到内存中使用, Zi类对应的父类Fu.class字节码文件先进入到方法区中, 然后Zi.class字节码文件才进入到方法区中
2) 创建子类类型对象, 在堆内存中开辟了空间之后, Fu类中的成员先进入到内存中, 由JVM虚拟机先为父类中成员进行赋值
3) 子类成员后进入到堆内存中
4) 使用成员, 优先使用子类中定义成员, 如果子类中没有对应成员或者使用super关键字, 调用父类中继承到成员使用
总结 : 父类优先于子类进入到内存中, 父类先为子类将可继承数据准备好, 自己进入到内存中才能继承使用
继承中成员方法的关系
-
父类中私有方法不能被子类继承使用
-
子类中定义的和父类中继承到方法不同, 子类可以调用自己特有方法功能, 可以调用从父类继承来的功能
-
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容, 子类重写是为了让方法越来越好
-
重写Override注意事项:
-
- 重写需要与父类原方法 返回值类型, 方法名 ,参数列表相同
-
- 私有方法不能被重写(父类私有成员子类是不能继承的)
-
- 子类方法访问权限不能更低(public > 默认 > 私有)
-
- 在子类重写方法上, 通常使用注解 @Override, 标识和验证方法是重写方法
- 子类重写了从父类继承来的方法功能, 子类重写方法优先调用和运行
如果子类父类中有相同方法定义, 使用super关键字,区分子父类中重名方法功能
super.父类方法名(需要实际参数);
注意 : 重写的方法本身还是属于父类,只是在子类中重新实现了这个方法的内容。
笔试题:
什么Overload(重载)? 什么Override(重写)?
-
重载 : 定义在同一个类中, 方法名相同,参数列表不同, 与方法返回值类型无关的多个方法之间, 称为重载
-
重写 : 在子父类关系中, 子类重写从父类继承来的方法功能, 要求方法参数列表, 返回值类型, 方法名与父类方法一致, 方法内容可以修改
重写注意事项:
-
- 私有方法不能重写
-
- 重写后方法的权限大于等于父类原有权限
-
- 重写方法通常使用@Override注解进行标识
继承中构造方法的关系
-
父类中构造无法被子类继承, 但是子类构造方法中可以调用父类构造; 在子父类继承关系中, 父类优先于子类进入到内存, 父类中数据优先于子类进行初始化(赋值)
-
如果子类构造方法中, 没有手动调用任何构造(本类, 父类),系统会默认在子类构造方法第一行调用super(); 目的是为了让父类中成员优先于子类进入内存中赋值
super() : 表示调用父类空参数构造方法 -
如果子类构造方法中, 手动调用本类或者父类构造, 那么系统不会再默认调用任何过构造, 一律以手动调用构造为准
-
super() : 父类构造调用. 必须写在构造方法第一位置上, 直接保证父类构造优先于子类进入内存运行
public class Fu {
int x = 25;
public Fu(){
x = 99;
System.out.println("我是父类构造方法");
}
}
public class Zi extends Fu{
int k;
// 没有给Zi类手动定义任何构造,系统默认添加一个空参数构造
public Zi(){
// 系统默认的调用父类的空参数构造方法 super(父类构造中实际参数);
// 子类构造方法中调用父类的构造 : 原因是为了保证父类的成员优先于子类进入到内存中
// 进行成员数据的初始化(赋值使用)
super();
k = 22;
}
}
public class TestExtendsCon {
public static void main(String[] args) {
Zi z = new Zi();
System.out.println(z.x);// 99
}
/*
* 1. 创建一个子类对象,调用子类构造
* 2. 因为有父类, 为了让父类先进内存
* 1)于是在子类构造方法第一行,调用super(), 表示调用父类构造
* 2)父类构造可以给父类成员变量进行赋值
* 3)因此父类的成员变量最先进入到内存中,父类构造才能运行给父类成员赋值
* 3. 当父类成员加载完毕, 父类构造运行完毕,轮到了子类初始化数据
* 4. 子类的成员变量进入内存, 子类构造是可以给子类成员变量赋值
*/
}
this和super使用总结
1、含义:
this关键字表示本类当前对象的引用
哪个对象在调用this所在的方法,this就表示哪个对象
super关键字表示本类当前对象的父类的引用
哪个对象在调用super所在的方法,super就表示哪个对象中的父类部分的数据
2、super和this都可以访问成员变量
super只能访问父类中定义的成员变量
super.成员变量名
this既可以访问子类中定义的成员变量,也可以访问父类中定义的成员变量
this.成员变量名
3、super和this都可以访问成员方法
super只能访问父类中定义的成员方法
super.成员方法名()
this不仅可以访问子类中定义的成员方法,也可以访问父类中定义的成员方法
this.成员方法名()
4、super和this都可以访问构造方法:this语句和super语句
this():访问本类的其他构造方法
super():访问父类的构造方法
实际代码中this和super的使用:
-
this : 当前类型对象的使用
可以区分成员变量与局部变量的重名问题, 带有this关键字的变量表示成员变量的使用 -
super : 当前类型对象的父类引用
-
- 子父类定义重名成员变量, super.成员变量表示父类成员的使用
-
- 子父类定义重名成员方法(子类重写), super.方法() 父类中的方法功能
-
- 子类构造方法第一行, 一定要调用父类构造, 为了保证父类成员优先于子类进入到内存中 super(父类构造实际参数);
代码块
1、使用大括号包起来的一段代码。放在不同的位置,有不同的名称,有不同的作用,有不同的执行时机。
2、分类:
局部代码块
构造代码块
静态代码块
同步代码块(多线程)
局部代码块
1、格式:使用大括号包起来的一段代码
2、位置:方法中
3、作用:
限定变量的生命周期
在局部代码块中【声明】的变量,只能在局部代码块的范围内使用,一旦出了局部代码块的大括号,变量就不能继续使用了。
某个变量一旦不能使用了,就会被回收,节省内存空间
4、注意事项:
如果是在局部代码块中声明了变量,会减少变量的声明周期,出了局部代码块就无法继续使用局部代码块中声明的变量。
如果是在局部代码块中修改了局部代码块外声明的变量,局部代码块结束之后,并不会消除局部代码块对这个变量的修改。
public class Demo01_局部代码块 {
public static void main(String[] args) {
int i = 10;
// 局部代码块 : 主要功能就是限制定义在局部代码块中的变量使用范围
// 局部代码块作用 : 可以限定变量使用范围
// 局部代码块的目的 : 通过限定变量使用范围,达到节省内存的目的
// 如果一个变量无法再使用, 那么这个变量就会变成垃圾等待被回收
{
int y = 20;
i = 25;
}
System.out.println(i); // 25
}
}
构造代码块
1、格式:使用大括号包起来的一段代码
2、位置:类中方法外
3、作用:
用于给成员变量初始化赋值
4、构造代码块的执行说明:
1、在创建对象的时候执行,由jvm默认调用
2、在构造方法执行之前,执行
3、任意一个构造方法执行之前,都会执行一次构造代码块的内容
4、如果每个构造方法都会执行的内容,提取到构造代码块中
public class Demo02_构造代码块 {
private String name;
private int age;
public String getName() {
return name;
}
public int getAge() {
return age;
}
// 构造代码块 :可以给对象中成员变量进行赋值; 或者如果多个构造方法中有相同代码逻辑, 可以将
// 此部分逻辑提升到构造代码块中设计和使用
// 运行机制 : 每次创建对象时, 构造代码块都在构造方法之前运行, 被JVM虚拟机主动调用一次
{
name = "张三";
age = 20;
// System.out.println("构造代码块执行了");
System.out.println("我是构造方法,执行了");
}
public Demo02_构造代码块(){
//System.out.println("我是构造方法,执行了");
}
public Demo02_构造代码块(String name){
// System.out.println("我是构造方法,执行了");
}
public static void main(String[] args) {
Demo02_构造代码块 demo = new Demo02_构造代码块();
System.out.println(demo.getName() + "--" + demo.getAge());
Demo02_构造代码块 demo1 = new Demo02_构造代码块();
}
}
静态代码块
1、格式:
static {
静态代码块的内容
}
2、位置:类中方法外
3、作用:
用于给静态的成员变量初始化赋值
用于执行那些只需要执行一次的代码,例如驱动加载等
4、执行特点:
1、随着类的加载而执行
2、类只加载一次,所以静态代码块只执行一次
3、执行的时机最早:早于所有的对象相关内容
public class Demo03_静态代码块 {
static int x = 88;
int j = 10;
public Demo03_静态代码块(){
System.out.println("构造方法");
}
{
System.out.println("构造代码块");
}
// 静态代码块
// 静态代码块的执行机制: 静态属于类,不属于任何对象, 因此静态与对象无关
// 静态代码块, 属于类, 因此当Demo03_静态代码块类对应的.class字节码文件一旦加载进入到内存
// 中, JVM虚拟机先将静态成员变量分配区域, 赋值, 马上调用静态代码块, 且Demo03_静态代码块类
// 中静态代码块, 值执行一次, 与创建对象的次数无关
static{
// j = 122; 静态中不能直接使用非静态, 因此静态代码块不给非静态成员变量进行赋值
x = 199;// 静态代码块可以给静态成员变量赋值
System.out.println("我是静态代码块,我运行了");
}
public static void main(String[] args) {
System.out.println(Demo03_静态代码块.x);
Demo03_静态代码块 demo = new Demo03_静态代码块();
Demo03_静态代码块 demo1 = new Demo03_静态代码块();
}
}
封装 : 隐藏事物实现细节, 多外提供公共访问方式, 方法, private关键字, 提高代码安全性, 提高代码的复用性
继承 : 类与类之间发生子父类继承关系, extends关键字, 子类可以直接继承使用父类中的成员(私有,构造除外), 提高代码的复用性, 可维护性, 为多态提供了前提条件; 弊端: 类与类之间的耦合度过高
多态 : 事物的多种形态, 要求有继承关系(或实现关系), 子类重写父类中方法, 父类的引用指向之类的对象; 多态的表达式中, 对于方法调用, 编译看左, 运行看右; 多态好处就是可以极大提高代码的扩展性
多态
多态概述
-
事物的多种表现形态就是多态;
java中的多态就理解为对象的不同数据类型的体现也就是子类对象充当父类类型对象 -
多态的发生前提:
-
- 必须要有继承或实现关系
-
- 有方法的重写
-
- 父类引用指向子类的对象
- 格式:
父类类型 变量名 = new 子类名(实参);
多态中成员变量的访问原则
原则:
编译看左边,运行看左边
1、编译的时候,要看【=】左边的引用所属的类型中,是否有该变量的定义,如果有,就编译成功,如果没有,就编译失败
2、运行的时候,也是使用等号左边类型中的变量
Person p = new Teacher();
当有了一个多态的表达式, 需要通过变量p调用成员变量
-
编译代码时(写代码时), 检查, 等号左边的Person类中,是否定义了该成员变量, 如果没有定义代码报错
-
当Person类中定义了该成员, 实际调用成员变量时, 调用的仍然还是等号左边的Person类型中的成员变量
public class Person {
int a = 10;
}
public class Teacher extends Person{
int a = 10;
int x = 999;
}
public class Doctor extends Person{
// 从父类Person中继承到成员变量a
}
public class TestPerson {
public static void main(String[] args) {
// 1. 创建出一个Teacher教师对象
Teacher t = new Teacher();
// 2. 使用多态的方式创建出一个一个Teacher类型实际对象
Person p = new Teacher();
// 多态中成员变量的使用
// 1) 运行看等号左边
System.out.println(p.a);// 10
// 2) 编译看等号左边
// System.out.println(p.x);
// 3. 使用多态创建出一个Doctor实际类型对象
Person p1 = new Doctor();
System.out.println(p1.a);// 10
}
}
多态中成员方法的访问特点
-
编译看左,运行看右
-
编译的时候,要看【=】左边的引用所属的类型中,是否有该方法的定义,如果有,就编译成功,如果没有,就编译失败。
-
运行的时候,要看【=】右边的对象所属的类型中,是如何实现这个方法的。最终运行的是子类重写过的方法实现。
引用数据类型的向上向下转型
1、向上转型:
使用子类的引用指向子类的对象(正常情况)
多态中,使用父类的引用指向子类的对象(向上转型)
Person p = new Teacher();// 向上转型
本质:缩小了对象本身的访问范围,减少了访问的权限(只能访问父类中定义的内容)
2、向下转型:
类比性记忆 : 子类想成年轻人, 年龄小; 父类想成老年人, 年龄大; 向下就是老年人, 回复成年轻人, 年龄向下
概念:
让指向子类对象的父类引用,【恢复】成子类的引用
格式:
子类类型 引用名称 = (子类类型)父类类型的引用;
本质:
【恢复】子类类型原本就有的访问范围
3、缺陷: 向下转型的时候有可能转为其他子类的类型,编译不会报错但是运行时会发生类型转换异常
4、解决方案:
- 转之前判断一下要转换的类型是不是多态对象之前的类型。使用一个关键字 instanceof 来判断向下转换类型是不是自己的类型
- 格式:
多态对象 instanceof 指定的数据类型 , 返回值为布尔类型的数据
多态的好处
-
提高了代码的可扩展性(灵活性)
-
在方法的参数列表中,形式参数是父类类型的引用,将来调用方法的时候,父类类型的任意子类对象,都可以作为方法的实际参数。
抽象类
抽象方法
1、抽象:抽取像的、相同的、相似的内容出来
2、抽象方法:java中只有方法声明没有方法实现并且被关键字abstract修饰的方法就是抽象方法
3、格式:
修饰符 abstract 返回值类型 方法名(参数列表);
4、注意:
抽象方法只能定义在抽象类中和接口中
抽象类
1、抽象类: 可以定义抽象方法的类,就是抽象类。
2、java中被关键字abstract修饰的类就是抽象类
3、定义格式:
修饰符 abstract class 类名 {
}
// 抽象类中可以定义抽象方法
public abstract class Aniaml {
public abstract void eat();
}
抽象类的特点
1、抽象类和抽象方法都需要使用abstract关键字修饰
抽象类:abstract class {}
抽象方法:public abstract void test();
2、抽象类和抽象方法的关系:
抽象方法所在的类必须是抽象类
抽象类中未必一定都定义抽象方法,抽象类中未必存在抽象方法
3、抽象类的实例化(抽象类如何创建对象)
抽象类不能直接实例化
定义抽象类的子类,由子类创建对象,调用方法
注意 : 抽象类存在意义就是为了有子类继承, 重写抽象方法
4、抽象类子类的前途
在子类中,将父类所有的抽象方法全部重写(实现),子类就成了一个普通类,就可以创建对象
在子类中,没有将父类中所有的抽象方法全部实现,子类就还是一个抽象类,还需要使用abstract关键字修饰子类。
抽象类中的成员
普通类中可以定义成员 + 抽象方法 = 抽象类中成员
1、成员变量:可以定义变量,也可以定义常量,但是不能被抽象
2、构造方法:有
虽然本类无法创建对象,但是抽象类一定有子类,子类会访问父类的抽象方法。
是否有构造方法,不取决于是否可以创建对象,而是取决于是否可以定义成员变量。如果可以定义成员变量,那么就需要初始化成员变量,就是构造方法来完成的。
3、成员方法:
既可以是抽象方法:强制子类重写
也可以是非抽象方法:用于给子类继承,提高代码的复用性
接口
接口的概述
接口是java用来描述多种不同规则的集合体;
规则在java中就是抽象方法,接口就是存放了不同的抽象方法的集合体
java中使用关键字interface表示,接口和类同级别的
好处:
一旦将命名规则定义出来,【方法的调用】和【方法的实现】就分离开了,可以提升开发效率,降低代码的耦合性
抽象类 : 毕竟是个类, 类与类之间单继承关系, 一个子类只能有一个直接父类, 类与类之间有很大的耦合度
接口 : 不是类, 类与接口之间的关系, 可以多个对应关系, 一个类可以同时实现多个接口, 接口中全是抽象方法, 因此方法的规则, 都必须要实现类重写, 因此接口与实现类之间的耦合度, 降低
接口的定义特点及注意事项
- 格式:
修饰符 interface 接口名 {接口的内容}
接口的源文件也是.java文件,照样参与编译,编译后的文件依然是字节码文件【.class】
- 内容:
属性:接口中的成员变量, 实际是成员常量,默认被 public static final修饰
注意:使用接口名访问即可
方法:JDK1.8之前:只有抽象方法, 默认被public abstract 修饰
- 注意事项:
- 没有构造方法 不能直接创建对象的
功能必须需要类来实现之后被类的对象调用使用
public interface MyInterface {
// 2. 接口中还有成员常量
// 接口中定义出的成员变量, 其实都是成员常量, 因为所有的成员变量默认使用
// public static final 修饰, 不管不写, 还是写一部分, 默认补全
// 公共的 静态 不可改变的, 使用fianl修饰的所有变成都是常量
static int i = 10;
public static final int j = 20;
// 1. JDK1.8之前:只有抽象方法
// 目前,先学习接口中全都是抽象方法的场景
public abstract void fun();
public abstract int getSum(int x, int y);
// 接口中所有抽象方法都默认修饰符 : public abstract
void eat();
// 3. 接口中是否可以定义出构造方法? 不可以
// 原因 : 因为接口中不能定义出成员变量, 只有可以定义成员变量的类型, 才能够定义出构造方法
/*public MyInterface(){
}*/
// 4. 接口不能new对象的, 原因2个 : 1) 没有构造 2) 接口中全是抽象方法, 不能运行, 不能调用
}
public class TestInterface {
public static void main(String[] args) {
// 1. 可以使用接口名.直接调用接口中成员常量
System.out.println(MyInterface.i);// 10
// 2. 成员常量都是使用final修饰的, 因此其值不能改变
// MyInterface.i = 99;
}
}
接口实现
- 概述: 接口书写好之后里面的规则要想被使用,需要类来重写,一个类又需要这个接口的功能,要类和接口发生关系,就使类拥有接口中的规则功能。
类去重写接口规则的过程就叫做实现接口
- 类实现接口:
使用关键字implements连接类和接口
- 格式:
类 implements 接口名1,接口名2....{类的内容}
- 接口的实现类前途:
是一个抽象类,该类没有实现接口中的所有抽象方法
是一个普通类,该类实现了接口中的所有抽象方法
- 单实现:
一个类实现了一个接口的实现方式
类 implements 接口名{
重写接口中所有的抽象方法
}
- 多实现:
一个类同时实现多个不同接口的实现方式就是多实现
类 implements 接口名1,接口名2....{
1. 重写所有接口中所有的抽象方法
2. 不同的接口中相同方法声明的抽象方法只需要重写一次
}
类与类、类与接口、接口与接口之间的关系
1、类与类
继承的关系,使用extends关键字
可以单继承、不可以多继承、可以多层继承
2、类与接口:
实现关系,使用implements关键字
java中有单实现和多实现
多实现没有安全隐患:即使两个接口中有一样的方法声明,但是在类中也只有一个实现
在继承一个父类的前提下,还可以实现多个接口
格式:
class 子类类名 extends 父类类名 implements 接口1, 接口2.....{
//重写父类和所有接口中的所有抽象方法
}
注意 : 父优先
3、接口与接口:
继承关系,使用extends
可以单继承、也可以多继承、可以多层继承
多继承的格式:
interface 接口名 extends 父接口1, 父接口2.....{
相当于继承了所有父接口的所有抽象方法
}
4、类和接口的区别(设计区别):
抽象类:定义物体本身具有的固有属性和行为
接口:定义物体通过学习、训练而扩展出来的行为
public class Fu {
public void fun(){
System.out.println("我是父类中fun方法");
}
}
// 一个类可以在继承一个父类前提下, 同时实现多个接口. 需要将所有抽象方法全部重写
public class Zi extends Fu implements MyInterface,MyInterface2{
// MyInterface,MyInterface2接口中的fun方法, Zi类无须重写, 已经从Fu父类汇总
// 继承到了一个fun, 正好可以满足接口需要重写的规则
@Override
public int getSum(int x, int y) {
return 0;
}
@Override
public void eat() {
}
@Override
public void sleep() {
}
}
// MyInterfaceZi子接口中具有了所有父接口中的抽象方法, 但是这个子接口也是需要实现类将所有抽象方法重写
public interface MyInterfaceZi extends MyInterface,MyInterface2{
}
内部类
- 定义在一个类中的另一个类就叫做内部类
举例:在一个类A的内部定义一个类B,类B就被称为内部类
- 格式:
public class Outer { // 外部类
public class Inner { }// 内部类
}
- 分类: 根据定义的位置不一样以及是否有名分为了不同的内部类,具体分类如下:
-
- 成员内部类
-
- 局部内部类
-
- 匿名内部类
- 访问特点:
内部类可以直接访问外部类的成员,包括私有成员
外部类要访问内部类的成员,必须创建对象
成员内部类
-
定义位置: 在类中方法外,跟成员变量是一个位置
-
外界创建成员内部类格式
格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
举例:Outer.Inner oi = new Outer().new Inner();
-
成员内部类的推荐使用方案
将一个类,设计为内部类的目的,大多数都是不想让外界去访问,所以内部类的定义应该私有化,私有化之后,再提供一个可以让外界调用的方法,方法内部创建内部类对象并调用。
案例: 定义一个身体Body类, Body类中定义一个Heart心脏内部类, 测试内部类的使用
// Demo01 理解成是Body身体类
public class Demo01_成员内部类 {
int blood = 120;
int drink = 5;
// 普通成员内部类, 可以理解成一个成员变量
class Heart{
// 内部类中也可以定义成员变量, 方法功能
int jump = 80;
public void show(){
// 1. 成员内部类可以直接使用外部类中的成员和方法
System.out.println("血压为:" + blood + ",心跳为:" + jump);
}
}
// 2. 如果外部类想要使用内部类成员, 需要创建出一个内部类对象
public void useHeart(){
Heart h = new Heart();
System.out.println(h.jump);
h.show();
}
// 私有成员内部类, 理解成一个私有成员变量
private class Shen{
int wc = 6;
public void show(){
// 私有成员内部类可以直接访问外部类中成员
System.out.println("每天喝" + drink + "升水, 至少去WC" + wc + "次");
}
}
// 对外提供一个公共的访问私有成员内部类方式
public void useShen(){
Shen sh = new Shen();
System.out.println(sh.wc);
sh.show();
}
}
public class TestInnerClass {
public static void main(String[] args) {
// 1. 创建出一个外部类对象
Demo01_成员内部类 demo = new Demo01_成员内部类();
demo.useHeart();
// 2. 除了外部类之外的其他类中, 如果想创建出一个成员内部类对象
// 语法结构 : 外部类名.内部类名 内部类对象名 = new 外部类名().new 内部类名();
Demo01_成员内部类.Heart he = new Demo01_成员内部类().new Heart();
System.out.println(he.jump);
he.show();
// 3. 私有成员内部类, 只能通过公共访问方式进行调用
demo.useShen();
}
}
局部内部类
-
定义位置: 局部内部类是定义在方法中的类
-
访问方式:
-局部内部类,外界是无法直接使用,需要在方法内部创建对象并使用
-该类可以直接访问外部类的成员,也可以访问方法内的局部变量
public class Demo02_局部内部类 {
public static void main(String[] args) {
breath();
}
// 方法体现了封装, 将指定功能代码使用一对大括号包裹上
// 当需要次部分功能时候, 直接调用方法, 使用方法名代表这段代码
// 方法只能直接调用使用, 无法将一个中的局部, 单独获取到
public static void breath(){
// number表示抽烟的数量,局部变量
int number = 20;
// 局部内部类, 理解成是一个局部变量
// 局部内部类只能在当前方法中使用
class Fei{
String color = "black";
public void smoke(){
System.out.println("每天吸" + number + "根烟, 少抽点, n年以后肺有可能会变成" + color);
}
}
// 当在方法中定义了局部内部类之后, 可以在当前方法中直接创建出一个局部内部类使用
// 调用breath方法 , 间接的调用Fei局部内部类
Fei f = new Fei();
System.out.println(f.color);
f.smoke();
}
}
匿名内部类
-
概述: 想要使用一个类的子类或接口实现类对象的时候,匿名内部类就是其中的一种获取子类对象或实现类对象的方式,他是一个固定的语法格式。
-
匿名内部类的使用前提:
存在一个类或者接口,这里的类可以是具体类也可以是抽象类
-
匿名内部类的格式
格式:
new 类名(实参) { 重写相关方法 }
或
new 接口名(实参) { 重写相关方法 }
举例:
new Inter(){
@Override
public void method(){}
};
-
匿名内部类的本质
本质:是一个类的子类对象或者一个接口的实现类对象
-
匿名内部类的使用:
-
- 匿名内部类可以通过多态的形式接受
-
- 匿名内部类作为对象直接使用(只能使用一次)
-
- 当发现某个方法需要接口或抽象类的子类对象,我们就可以传递一个匿名内部类过去,来简化传统的代码
public interface MyInter {
public abstract void fun();
public abstract boolean equal(double d, double d1);
}
public class Test匿名内部类 {
public static void main(String[] args) {
/*
匿名内部类可以简化 : 一个类的子类或者一个接口实现类使用过程
new 类名/接口名(){ // 大括号就表示类的子类或者是接口的实现类
// 将需要重写的所有方法重写
};
匿名内部类语法结构: 实际上就是一个类的子类对象或者是一个接口的实现类对象
*/
// 匿名内部类对象只能使用一次, 因为匿名, 对象地址没有保存下来, 无法使用第二次
new MyInter(){
// 重写MyInter接口中所有抽象方法
@Override
public void fun() {
System.out.println("匿名内部类实现了接口MyInter中抽象方法");
}
@Override
public boolean equal(double d, double d1) {
return d == d1;
}
}.fun();
// 匿名内部类对象的优化 : 从无名变成有名,可以让重写后的方法多次使用
// 对于语法结构只知道是MyInter接口的一个实现类对象
// 父接口引用 指向 实现类对象
MyInter my = new MyInter(){
// 重写MyInter接口中所有抽象方法
@Override
public void fun() {
System.out.println("有名的内部类对象实现了接口MyInter中抽象方法");
}
@Override
public boolean equal(double d, double d1) {
return d == d1;
}
};
my.fun();
System.out.println(my.equal(3.14,3.14));
}
}
final关键字
1、final是一个关键字 含义:最终的,最后的,表示不能再改变的。
2、final关键字:可以修饰类、方法、变量
3、修饰类:
表示一个最终类,表示不能有子类,【不能被其他类继承】
一旦一个类型不能被继承,那么其中所有的方法都不能被重写
不影响当前类的方法被调用
4、修饰方法:
表示一个最终方法,【该方法不能被重写】
5、修饰变量:
表示一个最终变量,该【变量变成了常量】,就只能赋值一次
使用final修饰的变量, 不能进行二次赋值, 值不能改变, 因此成为常量, 命名规范 : 所有单词前大写, 多个单词之间使用_进行分隔了; 举例 : SCHOOL_NAME PI
定义常量的好处:见名知意,容易理解;可维护性高
总结 :
-
- final修饰的类不能有子类
-
- final修饰的方法不能被重写
-
- final修饰的变量,称为常量,其值不能修改
包
- 包的概述: 用来统一分类管理源代码资源的特殊的文件夹;这个文件夹会参与编译。
比如:
com.ujiuye.demo 下的 Person类进行编译的时候类所在的包参与编译,编译后
字节码文件名:com.ujiuye.demo.Person
字节码文件中类名:com.ujiuye.demo.Person【全限定类名】
- 作用:
-
- 统一管理代码资源
-
- 保证代码编译后类名唯一
-
- 保证代码运行的过程直接进入包找到对应的类
- 命名规则:
-
- 使用公司域名的反写【保证每一个包路径唯一】
-
- 全部小写
-
- 包名和包名之间使用.隔开
- 包的声明:
使用关键字 package + 包路径【IDE生成类的时候自动声明】
- 导包:
- 同包下:不需要导包
- 不同包:需要导包【jdk的lang包除外】
- 使用 import + 包路径
权限修饰符
- 概述: 用来限定资源的使用范围的修饰词汇,就叫做权限(使用)修饰符,不同的符号有不同的限定范围
分类: 从小到大权限罗列
-
private :私有的 限定范围是:本类中使用, 通常修饰成员变量
-
默认的 :啥也不写 限定范围: 本类和本包(package)中使用
说明 : 定义类, 定义方法, 定义一个变量, 如果没有使用任何权限修饰符, 那么就使用默认权限, 切记, 什么都不写, 写出来反而报错
- protected :受保护的 限定范围: 本类和本包中使用, 可以外包的子类内部使(protected 受保护权限被外包子类继承了, 那么在外包子类中, 相当于private)
说明 : protected 只能修饰方法和变量, 不能修饰类, protected 关键字也是封装的一种体现形式
- public :公共的 限定范围:没有范围, 通常修饰类和方法