JavaSE学习文档
- 第一章 Java概述
- 1.2 计算机编程语言
- 1.3 Java语言版本概述
- 1.4 Java语言分类
- 1.5 JDK,JRE,JVM的关系
- 1.6 JDK安装
- 1.7 DOS命令
- 1.8 Java程序执行过程
- 1.9 编写HelloWorld
- 1.10 常见错误
- 1.11 编写程序时要注意的点
- 第二章 Java基础语法
- 2.1 Java中的注释
- 文档注释
- 2.2 关键字和保留字
- 2.3 API文档
- 2.4 标识符
- 2.5 数据类型
- 2.6 字面量
- 2.7 变量
- 2.8 final
- 2.9 进制(了解)
- 二进制转十进制
- 二进制和八进制十六进制的转换
- 进制的表示
- 2.10 数据交换
- 2.11 数据在计算机中的存储
- 整数
- 浮点数
- 字符
- 布尔类型
- 2.11 类型转换
- 自动类型提升
- 强制类型转换
- 2.12 运算符
- 算术运算符
- 赋值运算符
- 逻辑运算符
- 三元运算符
- 位运算符
- 运算符的优先级
- 2.13 print和println的区别
- 2.14 从键盘输入数据
- 第三章 流程控制语句
- 3.1 流程控制概述
- 3.2 if语句
- 格式一 :if
- 格式二 : if-esle(二选一)
- 格式三:if-else if-else
- 3.3 switch语句
- switch和if的比较
- 3.4 for语句
- 3.5 break和continue
- 3.6 while语句
- 3.7 do-while语句
- 3.8 while和do-while的区别
- 3.9 while和for的区别
- 3.10 死循环
- 3.11 嵌套循环
- 3.12 break和continue在嵌套循环中的使用
- 第四章 数组
- 4.1 数组概念
- 4.2 一维数组
- 一维数组的声明
- 一维数组的初始化
- 一维数组元素的赋值和取值
- 一维数组的长度
- 一维数组元素的遍历
- 一维数组元素的默认值
- 一维数组的内存解析
- 4.3 内存概述
- 4.4 一维数组的常见算法
- 查找数组中的最大值
- 数组元素查找-无序
- 数组元素查找-有序
- 数组元素的反转
- 数组元素的排序-选择排序
- 数组元素的排序-冒泡排序
- 4.4 二维数组
- 二维数组的概述
- 二维数组的声明
- 二维数组的初始化-静态初始化
- 二维数组的初始化-动态初始化
- 二维数组的长度和二维数组元素的长度
- 二维数组的遍历
- 二维数组的默认值
- 二维数组的内存解析
- 4.5 二维数组练习
- 杨辉三角
- 打印三角形
- 第五章 方法
- 5.1 方法的概述
- 5.2 方法的好处
- 5.3 自定义方法的格式
- 5.4 方法的分类
- 5.5 静态方法说明
- 5.6 使用方法时要注意的点
- 5.7 return的说明
- 5.8 值传递
- 5.9 可变形参
- 5.10 给main方法传参
- 5.11 调用方法时的内存
- 5.12 方法的重载
- 5.13 递归
- 第六章 面向对象上
- 6.1 面向对象和面向过程
- 6.2 类和对象
- 6.3 自定义类
- 6.4 创建对象
- 6.5 属性
- 变量的分类
- 成员变量的分类
- 实例变量的格式
- 实例变量的特点
- 实例变量的默认值
- 6.7 实例变量的内存图
- 6.8 空指针异常
- 6.9 类变量
- 类变量格式
- 类变量的特点
- 类变量的说明
- 类变量的内存图
- 6.10 类变量和实例变量的区别
- 6.10 实例方法
- 6.11 this
- 6.9 类变量
- 类变量格式
- 类变量的特点
- 类变量的说明
- 类变量的内存图
- 6.10 类变量和实例变量的区别
- 6.10 实例方法
- 6.11 this
- 6.12 静态方法和非静态方法再说明
- 6.13 局部变量
- 6.14 构造器
- 构造器格式
- 构造器的作用
- 构造器的说明
- 调用本类中的其它构造器
- 6.15 四种权限修饰符
- 6.16 封装性
- 6.17 对象的关联
- 案例一:没有使用封装
- 案例二:使用了封装
- 6.18 JavaBean
- 6.19 package
- 6.20 import
- 6.21 对象数组
- 案例一:
- 案例二:对数组中的对象排序
- 第七章 面向对象中
- 7.1 继承
- 继承的好处
- 什么时候可以使用继承
- 继承的格式
- 继承的说明
- 7.2 方法的重写
- 什么时候需要方法的重写
- 如何方法重写
- 在方法重写时要注意的细节
- 哪些方法不能被重写
- 7.3 super关键字
- super调用父类构造器
- super调用父类属性
- super调用父类方法
- 7.4 Object类
- 概述
- toString方法
- 7.5 ==的说明
- 7.6 多态性
- 多态的本意
- 多态的表现形式
- 多态的前提
- 多态的优点和缺点
- 属性有多态性吗?
- 向上转型和向下转型
- instanceof
- 虚方法
- 静态绑定
- 动态绑定
- 第八章 面向对象下
- 8.1 代码块
- 代码块的作用
- 代码块的格式
- 静态代码块
- 非静态代码块
- 8.2 final关键字
- 8.3 单例设计模式
- 概述
- 饿汉式
- 懒汉式
- 8.4 abstract
- 概述
- 抽象类
- 抽象方法
- 注意
- abstract不能和哪些关键字一起使用
- 8.5 模板设计模式
- 8.6 初始化
- 类的初始化
- 对象的初始化
- 8.7 接口
- 接口描述
- 接口的格式
- 说明
- 案例-USB和蓝牙
- 接口的新特性
- 8.8 内部类
- 内部类说明
- 内部类分类
- 成员内部类
- 局部内部类
- 说明
- 8.9 常量-public static final
- 8.10 枚举类
- JDK1.5之前枚举类的实现
- JDK1.5开始枚举类的实现
- 枚举类使用场景
- 枚举类中的API
第一章 Java概述
1.2 计算机编程语言
第一代:机器语言
第二代:汇编语言
第三代:高级语言(java,c,c++,php,.....)
1.3 Java语言版本概述
1.4 Java语言分类
JavaSE: 用于应用程序开发(淘汰) + 核心的部分(核心类库)
JavaEE: 用于网站开发
JavaME: 嵌入式开发(已经淘汰)
1.5 JDK,JRE,JVM的关系
JDK = JRE + 开发工具集
JRE = JVM + 核心类库
1.6 JDK安装
参考:JDK17安装笔记
可以同时安装多个JDK(我安装了JDK8和JDK17)
注意:
1.要安装在不同的目录
2.检查当前版本(命令提示符中): java -version
3.如何选择JDK版本? 要通过环境变量-谁在前当前版本就是谁
卸载JDK:
1.如果是免安装版(解压后可以直接使用)- 可以直接删除
2.如果是安装版(通过下一步 下一步 ....安装的)-只能通过控制面板中的卸载程序(工具也可以)
1.7 DOS命令
cd 目录的路径
cd 目录名
cd ..
cd /
切换盘符(切换到c盘) - c:
dir 查看当前目录
cls
exit
1.8 Java程序执行过程
1.9 编写HelloWorld
一 编写源文件
1.创建一个以.java结尾的文件该文件叫作源文件
2.在源文件中要先写一个类(Java组成的基本单元)。格式:class 类名{}
3.在类中(类的大括号中)写一个main方法(程序的入口):
public static void main(String[] args){ }
4.在main方法中(方法的大括号内)写输出语句
System.out.println("输出内容");
二 编译
javac 源文件名.java
三 运行
java 字节码文件名
1.10 常见错误
1.大小写(java严格区分大小写)
2.在写代码时一定要在英文状态下(""中的内容可以中文)
3.每条语句结束要以分号结尾
4.写完代码后一定要保存
5.在编译和运行时注意路径和名字
比如源文件或字节码文件在 d:/a/b/c
编译和运行时在 c:/a
javac 源文件名.java //会报找不到
java 字节码文件名 //会报找不到
1.11 编写程序时要注意的点
1.java严格区分大小写
2.在同一个源文件中可以有多个类但是类名不能重复
3.在同一个源文件中只能有一个类被public所修饰并且该类的类名必须和源文件的名一致。
4.如果在同一个源文件中有多个类那么编译后会生成多个字节码文件(有几个类就有几个字节码文件)
5.字节码文件的名字和类名相同
第二章 Java基础语法
2.1 Java中的注释
java中的注释 : 单行注释 多行注释 文档注释
//单行注释
/*
多行注释
*/
说明 :
1.多行注释不能嵌套使用
2.注释的作用:①对代码进行补充说明 ②可以对代码进行调试
3.使用场景:单行注释和多行注释没有严格区分
逻辑简单 代码简单 一行能描述清楚使用单行注释反之使用多行注释
文档注释
2.2 关键字和保留字
/*
关键字:凡是具有特殊含义的单词都叫作关键字
关键字的特点:全部是小写
保留字:暂时没有使用但未来某个版本作为关键字使用。
*/
2.3 API文档
/*
API文档:是用来对核心类库中的类,接口,方法...进行说明。
(理解:类似于小家电的说明书)
*/
2.4 标识符
/*
标识符:凡是需要自己起名字的地方都叫标识符(例如:类名,方法名,变量名,接口名,......)
标识符的规则(必须遵守否则报错):
1.标识符的内容只能有26个英文字母(大小写)、下划线、数字和$
2.数字不可以开头,中间不能有空格。
3.不要使用关键字和保留字。
4.不要使用特殊值(null true false)
标识符的规范(可以写但是不建议容易挨打):
1.要做到见名知意
2.类名,接口名 :每个单词的首字母都要大写(大驼峰标识)XxxYyyZzz
3.方法名,变量名 : 除第一个单词的首字母要小写其它单词的首字母全部大写(小驼峰标只)xxxYyyZzz
4.常量名(后面说) :所有字母全部大写每个单词之间用下划线分开. XXX_YYY_ZZZ
5.包名(后面说):所有字母都是小写每个单词用.分隔开
比如:com.atguigu.java
*/
2.5 数据类型
基本数据类型:
整型:byte(1byte = 8bit)
short (2字节)
int (4字节)
long (8字节)
浮点型 :
float (4字节)
double (8字节)
布尔类型:
boolean (1字节)
字符型:
char(2字节)
引用数据类型:类(String) 接口 枚举 注解 数组 记录
2.6 字面量
/*
常量值(字面量):在编译时就可以确定数据值,在运行的时候该值不可以改变。
字面量:可以理解成就是确定的值
*/
//整型的字面量
System.out.println(20);//整型确定的值默认是int类型
System.out.println(30L);//该字面量的类型就是Long类型 -- 注意:L大小写都可以一般不用小写
//浮点型的字面量
System.out.println(15.3);//浮点型确定的值默认是double类型--注意:也可以加d的大小写
System.out.println(15.3f);//该字面量的类型就是浮点型--注意:f大小写都可以
//布尔类型的字面量(只有两个true和false)
System.out.println(true);
System.out.println(false);
//字符类型的字面量(一定要在单引号中写而且只能写一个字符)
System.out.println('a');
System.out.println('中');
2.7 变量
/*
变量:在程序运行的过程中数值可以改变
作用:用来存储数据
格式:
变量的类型 变量名 = 值;
说明
1. =是赋值符号
2.变量的类型决定了①数据的大小 ②数据的类型
3.变量名:变量的名字 - 通过变量的名字获取变量中的数据。
4.值 :放在变量中的数据
注意:
1.变量一定是先声明后使用
2.变量的声明和赋值可以同时进行也可以分开。
3.在同一个作用域中变量名不能相同(作用域:声明那个变量所在的大括号内)
4.程序是从上向下依次执行
5.变量可以被多次赋值后一次赋值覆盖前一次的值。
6.变量可以参与运算(运算的是变量中的值)
7.变量在使用时必须赋值
8.在使用变量时必须进行初始化(该变量指的是在main方法内声明的变量)
*/
//程序是从上向下依次执行。
//声明一个变量并赋值
int a = 10;
System.out.println(a);
//再次给变量中的值赋值
//int a = 5;
a = 5;
//输出变量中的数据
System.out.println(a);//注意:没有双引号-因为要输出的是变量中的数据。
System.out.println("===================================");
//注意:变量一定是先声明后使用
//变量的声明
int num1;
//变量的赋值
num1 = 20;
System.out.println("===================================");
//变量的声明-同时声明多个变量
int n1,n2,n3;
//变量的赋值
n1 = 1;
n2 = 2;
n3 = 3;
n1 = n2 = n3 = 10;
System.out.println(n1);
System.out.println(n2);
System.out.println(n3);
System.out.println("===================================");
//声明变量和赋值
int nu1 = 5;
int nu2 = nu1;//将nu1变量的值赋值给nu2
System.out.println(nu1);
System.out.println(nu2);
System.out.println("===================================");
int v1 = 5;
int v2 = 10;
int result = v1 + v2;//对变量的数据进行加法运算
System.out.println(result);
int v3 = 3;
v3 = v3 + 10;
System.out.println(v3);
System.out.println("===================================");
int v4 = 3;
//int v4 = 5; 注意:在同一个作用域中变量名不能相同(作用域:声明那个变量所在的大括号内)。
System.out.println("===================================");
//int v5;//声明变量
//System.out.println(v5); 注意:在使用变量时必须进行初始化(该变量指的是在main方法内声明的变量)。
2.8 final
1.final修饰的变量值不可以被修改
2.final修饰的变量为常量所以名字要注意(全部大写多个单词之间用下划线分隔)
final int PERSON_ID = 10;
2.9 进制(了解)
进制的作用:用来简化数据可以更方便的记忆 传输,.....
Java中的进制有:二进制 八进制 十进制 十六进制
十进制和二进制的转换:除2取余数 (最后将余数反过来)
二进制转十进制
二进制和八进制十六进制的转换
进制的表示
/*
在计算中:
十六进制的表示(以0x开头x大小写都可以): 0x10
八进制的表示(以0开头) : 010
二进制的表示(以0b开头-b大小写都可以):0b10
*/
2.10 数据交换
int m = 5;
int n = 10;
//方式一 : 任意数据类型都可以实现交换
/*
int temp = m;
m = n;
n = temp;
*/
//方式二: 只能对数值类型进行交换(引用数据类型不能使用此方式)
m = m + n; // m = 10+5 n=10
n = m - n;// m = 10 +5 n=5
m = m - n;// m = 10 + 5 n=5
2.11 数据在计算机中的存储
整数
整数在计算机中是以补码的形式存储的
为什么要用被码存储整数?
因为早期的计算机不能做减法运算。如果用原码做加法运算结果不对(为了只做加法运算不做减法)。
正数(三码合一):原码 反码 补码都一样
整数分正数和负数
负数的存储:
原码:该数值的二进制(符号位是1)
反码:在原码的基础上0变1 1变0 (符号位除外)
补码: 在反码的基础上加1
浮点数
字符
//字符
char c = '尚';
int n = '尚'; //23578(编码集该字所对应的值)
//将字符所对应的值赋值给字符变量
c = 23578;//可以 赋值的是该值所对应的字符
c = 056032;//八进制 - 赋值的是该值所对应的字符
c = '\u5c1a';//将unicode值(使用的就是十六进制)5c1a所对应的字符赋值给c
System.out.println(c);
System.out.println("------------------------------");
char c2 = '\n';//转义字符-换行
//拼接后的内容理解成"abc\nccc" 结果是abc 换行 ccc
//System.out.println("abc" + c2 + "ccc");
System.out.println("abc\nccc");
System.out.println("------------------------------");
System.out.println("\"");//想要输出”内容必须加\--进行转义
布尔类型
存储的就是0或1
2.11 类型转换
自动类型提升
类型转换(不包括boolean)
自动类型提升:
当容量(取值范围)小的数据和容量(取值范围)大的数据做运算容量小的会先提升为容量大的
byte,short,char -> int -> long -> float -> double
注意:当byte,short和char三者参与运算时会先提升成int类型
强制类型转换
/*
强制类型转换 :将大容量的赋值给小容量。
说明:
1.如何使用? 要使用强制类型转换符。
格式 :(强转的数据类型)变量
2.强制类型可能会损失精度(如果强转后可以存得下该数据那么不会损失精度)
*/
public class ConversionTest2{
public static void main(String[] args){
int n1 = 12;
byte n2 = (byte)n1; //-128 ~ 127
System.out.println(n2);
//==============================================
int a = 10;
int b = 20;
//byte c = (byte)a + (byte)b;// byte做运算会先提升为int类型
byte c = (byte)(a + b);//将a+b的结果转成byte类型
System.out.println(c);
//==============================================
byte num1 = 10;
int num2 = (int)num1; //也可以通过强制类型转换符提升类型。
2.12 运算符
算术运算符
/*
算术运算符 : + - * / %(取模,取余) ++ --
*/
public class ArithmeticTest{
public static void main(String[] args){
//int a = +5;
int result = 10 / 4; //2
System.out.println(result);
double result2 = 10 / 4;//2.0
System.out.println(result2);
result2 = (double)10 / 4; //2.5
System.out.println(result2);
result2 = 10 / (double)4; //2.5
System.out.println(result2);
result2 = (double)(10 /4);//2.0
System.out.println(result2);
result2 = (10+0.0) / 4; //2.5
System.out.println(result2);
System.out.println("============================================");
System.out.println(0 % 2); //0
System.out.println(1 % 2); //1
System.out.println(2 % 2); //0
System.out.println(3 % 2); //1
System.out.println(4 % 2); //0
System.out.println(5 % 2); //1
System.out.println("============================================");
//取模的结果的正负和被模数有关(和左边的值的正负有关)。
System.out.println(-3 % 2);//-1
System.out.println(3 % -2);//1
System.out.println(-3 % -2);//-1
}
}
赋值运算符
/*
- 最基础的赋值运算符:=
- 组合的赋值运算符:
+=、-=、*=、/=、%=(这部分是赋值运算符与算术运算符结合)
[面试题]:下面的区别是什么?
short s = 3;
//s = s + 3;//不可以因为short会自动类型提升为int
s += 3;// 可以:不会改变原来的数据类型-底层做了强转的处理。
*/
逻辑运算符
/*
逻辑运算符:
& 逻辑与 | 逻辑或 !取反
&& 短路与 || 短路或 ^ 逻辑异或
说明:
&和&& :当符号两边的数据都为true结果为true(只要有false结果为false)
|和|| : 当符号两边的数据都为false结果为false(只要有true结果为true)
! : 取反 数据为true结果为false 数据为false结果为true
^ : 当符号两边的数据是一样的结果为false 两边的数据不一样结果为true
注意:
1.逻辑运算符是对布尔类型的数据做运算。结果为还为布尔类型。
[面试题]&和&&的区别是什么?|和||的区别是什么?
&和&&的区别是什么?
当运算符左边的结果为true时&和&&右边的式子都会执行。
当运算符左边的结果为false时(右边的值不能影响最终结果)
&右边的式子要执行
&&右边的式子不会执行
|和||的区别是什么?
当运算符左边的结果为false时&和&&右边的式子都会执行。
当运算符左边的结果为true时(右边的值不能影响最终结果)
|右边的式子要执行
||右边的式子不会执行
*/
public class LogicalTest{
public static void main(String[] args){
boolean bo1 = true;
boolean bo2 = false;
System.out.println(bo1 & bo2); //false
System.out.println(bo1 && bo2); //false
System.out.println(bo1 | bo2); // true
System.out.println(bo1 || bo2); // true
System.out.println(bo1 ^ bo2); //true
System.out.println(!bo2);//true
System.out.println("=====================================");
/*
&和&&的区别是什么?
当运算符左边的结果为true时&和&&右边的式子都会执行。
当运算符左边的结果为false时(右边的值不能影响最终结果)
&右边的式子要执行
&&右边的式子不会执行
*/
int i = 3;
System.out.println(false & (++i > 2));
System.out.println("i=" + i);
int i2 = 3;
System.out.println(false && (++i2 > 2));
System.out.println("i2=" + i2);
System.out.println("=====================================");
/*
|和||的区别是什么?
当运算符左边的结果为false时&和&&右边的式子都会执行。
当运算符左边的结果为true时(右边的值不能影响最终结果)
|右边的式子要执行
||右边的式子不会执行
*/
int j = 3;
System.out.println(true | (++j > 2));
System.out.println("j=" + j);
int j2 = 3;
System.out.println(true || (++j2 > 2));
System.out.println("j2=" + j2);
}
}
三元运算符
格式: 关系表达式?值1:值2;
说明:
1.关系表达式的结果必须为布尔类型
2.如果关系表达式的结果为true则返回值1的值否则返回值2
3.在接收返回值时一定注意返回值的类型。
比如:值1是整型 值2是浮点型 返回值就必须是浮点型
4.可以嵌套使用但是不建议
======================================================================
//求两个数的最大值
int a = 5;
int b = 10;
int maxNumber = a > b? a : b;
System.out.println("maxNumber:" + maxNumber);
System.out.println("====================================");
/*
//求三个数的最大值
int num1 = 5;
int num2 = 6;
int num3 = 4;
int maxNumber2 = num1 > num2? num1 : num2;
maxNumber2 = maxNumber2 > num3? maxNumber2 : num3;
System.out.println("maxNumber2:" + maxNumber2);
*/
System.out.println("====================================");
//注意1 :最好不要嵌套使用
int num1 = 5;
int num2 = 6;
int num3 = 4;
int maxNumber2 = (num1 > num2? num1 : num2) > num3? (num1 > num2? num1 : num2) : num3;
System.out.println("maxNumber2:" + maxNumber2);
System.out.println("====================================");
//注意2 :接收数据的变量的类型(该类型一定要能够接两个值才可以)
double n = 5 > 3? 1 : 2.3;
System.out.println("n:" + n);
System.out.println("====================================");
//注意3 :条件表达式的结果的条件的说明
int n1 = 5,n2 = 5;
int result = n1 > n2? 1 : 2; //n1 > n2 不满足的条件是n1小于等于n2
System.out.println("result:" + result);
位运算符
//<< : 左移(在一定范围内每向左移一位原来的值乘以2)
System.out.println(8 << 1);//16
System.out.println(8 << 2);//32
System.out.println(8 << 3);//64
System.out.println("==================================");
System.out.println(1 << 31);
System.out.println("==================================");
// >> :右移 (在一定范围内每向右移一位原来的值除以2)
//正数:高位用0补
System.out.println(6 >> 1);//3
System.out.println(6 >> 2);//1
//负数:高位用1补
System.out.println(-6 >> 1);//3
System.out.println(-6 >> 2);//-2
System.out.println("==================================");
// >>> : 无符号右移
// 注意:无论是正数还是负数高位全部用0补
//正数:高位用0补
System.out.println(6 >>> 1);//3
System.out.println(6 >>> 2);//1
//负数:高位用0补
System.out.println(-6 >>> 1);//
System.out.println(-6 >>> 2);//
运算符的优先级
2.13 print和println的区别
print和println用来输出内容---将内容输出到控制台
print在输出数据后不会换行。
println在输出数据后会换行
2.14 从键盘输入数据
/*
从键盘输入内容
1.导包:在类的上面和package下面 import java.util.Scanner;
2.创建对象 : Scanner s = new Scanner(System.in);
3.通过对象名调用方法.
练习:1、从键盘输入个人的信息,用合适的变量接收并输出。例如:姓名、年龄、性别、体重、婚否(true/false)等
*/
public class ScannerTest {
public static void main(String[] args) {
//2.创建对象 : new Scanner(System.in);
Scanner s = new Scanner(System.in);//因为对象所属的类的类名是Scanner
System.out.print("请输入您的名字:");//提示
//3.调用方法: 对象名.方法名 / 之间 类名.方法名 Math.random();
String name = s.next();//从控制读取字符串
System.out.print("请输入您的年纪:");
int age = s.nextInt();//从控制台读取int类型的数据
System.out.print("请输入您的性别:");
String gender = s.next();
System.out.print("请输入您的体重:");
double weight = s.nextDouble();//从控制台读取浮点类型的数据
System.out.print("请输入您是否结婚:");
boolean marry = s.nextBoolean();//从控制台读取布尔类型的数据
System.out.println("我的名字叫" + name + " 今年" + age + "岁");
System.out.println("我的性别是" + gender + " 体重" + weight + (marry? "我结婚了":"我未婚"));
}
}
第三章 流程控制语句
3.1 流程控制概述
用于控制代码执行顺序的语句结构称为流程控制语句结构
3.2 if语句
格式一 :if
格式
if(条件表达式){
执行语句;
}
说明:
1.条件表达式的结果为布尔类型。如果为true就执行执行语句否则就不执行。
2.如果条件表达式的结果为true那么执行执行语句,执行语句执行完后跳出if语句继续向下执行。
如果条件表达式的结果为false那么直接跳出if语句向下执行。
格式二 : if-esle(二选一)
格式:
if(条件表达式){
执行语句1;
}else{
执行语句2;
}
说明:
1.条件表达式的结果为布尔类型
2.条件表达式的结果为true执行执行语句1,执行语句执行完后跳出if语句继续向下执行。
条件表达式的结果为false执行执行语句2,执行语句执行完后跳出if语句继续向下执行。
格式三:if-else if-else
格式三 : 多选一
if(条件判断语句1){
执行语句1;
}else if(条件判断语句2){
执行语句2;
}else if(条件判断语句3){
执行语句3;
}
......
else{
执行语句n;
}
说明:
1.条件判断语句的结果为布尔类型
2.对条件判断语句依次执行一旦条件判断语句的结果为true就执行其中的执行语句,
执行完后直接跳出if-else if语句
如果所有的条件判断语句都不能满足(全部为false)那么执行else中的执行语句
再跳出if-else if语句
3.if -else if - else可以省略 : 就无法保证多选一 有可能一个也不满足.
注意:
1.如果多个条件判断语句中的范围是包含关系那么范围小的在上面范围大的在下面.
2.如果多个条件判断语句中的范围是互斥关系那么谁在上谁在下都可以.
3.3 switch语句
分支语句 - switch-case
格式:
switch(表达式){
case 常量值1:
执行语句1;
break;
case 常量值2:
执行语句2;
break;
case 常量值2:
执行语句2;
break;
......
default:
执行语句n;
break;
}
说明:
1.表达式的类型:byte,short,int,char,枚举,字符串
2.通过switch中的表达式的值和case后面的常量依次匹配。如果匹配成功就执行相应的执行语句。
如果匹配失败(全部失败)执行default中的执行语句。
3.如果匹配成功执行相应的执行语句直到遇到break然后跳出switch语句。
如果一直没有遇到break那就一直向下执行完所有的执行语句后跳出switch语句。
4.case后面只能是常量。常量值不能相同。
5.default的位置是可以任意的
6.break可以省略不写。
break的作用:用来终止switch-case语句。
switch和if的比较
switch可以被if所替换 反之不成立。
即可以使用switch又可以使用if的场景下我们优先选switch效率高一些。
3.4 for语句
循环语句: for while do-while
循环语句的四要素
初始化语句
循环体语句
循环条件语句
迭代条件语句
for循环:
格式:
for(1.初始化语句;2.循环条件语句;4.迭代条件语句){
3.循环体语句;
}
执行顺序:1 -> 2 -> 3 -> 4 -> 2-> 3 -> 4 ...... ->2
说明:
1.初始化语句只执行一次(一般用来声明变量并初始化)
2.循环条件语句的结果为布尔类型 用来控制循环的执行。
如果为true执行循环如果为false结束循环继续向下执行
3.迭代条件语句用来对循环条件语句中的变量进行操作(比如:累加)
4循环体语句:需要不断循环执行的代码
注意:初始化条件可以写在for循环的外面
3.5 break和continue
/*
break:
使用场景:①switch-case语句中 用来结束witch-case语句
②循环结构 用来结束当前循环
continue:
使用场景:①循环结构 用来结束当次循环
注意:break和continue关键字的下面(紧挨着)不能写其它语句。因为执行不到
*/
for (int i = 1; i <= 5; i++) {
if (i == 3){
break;//用来结束当前循环
//continue;//用来结束当次循环
//break和continue关键字的下面(紧挨着)不能写其它语句。因为执行不到
//System.out.println("aaaa");
}
System.out.println(i);
}
3.6 while语句
/*
循环语句-while
循环语句的四要素
初始化语句
循环体语句
循环条件语句
迭代条件语句
格式:
1.初始化语句
while(2.循环条件语句){
3.循环体语句
4.迭代条件语句 //不写就成死循环了
}
执行顺序:1 ->2->3->4->2->3.... 2
说明:
1.循环条件语句结果为布尔类型 。如果为true执行循环如果为false结束循环。
2.初始化语句没有在while中只执行一次。
3.循环体语句:要循环的代码
4.迭代条件语句(对循环条件语句中的变量累加) :不写就成死循环了
*/
//输出100以内的奇数和奇数的个数
int count = 0;//统计奇数的个数
int i = 1;// 初始化语句
while(i <= 100){//循环条件语句
//循环体语句
if (i % 2 != 0){
System.out.println(i);
count++;
}
//迭代条件语句 -不写就成死循环了
i++;
}
System.out.println("count=" + count);
3.7 do-while语句
/*
循环结构 : do-while
循环语句的四要素
初始化语句
循环体语句
循环条件语句
迭代条件语句
格式:
初始化语句;
do{
循环体语句;
迭代条件语句;
}while(循环条件语句)
*/
public static void main(String[] args) {
//需求:输出100以内的奇数和奇数的个数
int count = 0; //统计奇数的个数
int i = 1;//初始化语句;
do{
//循环体语句;
if (i % 2 != 0){
System.out.println(i);
count++;
}
//迭代条件语句;
i++;
}while(i <= 100);//循环条件语句
System.out.println("count:" + count);
}
3.8 while和do-while的区别
while和do-while的区别?
while的循环体可能一次都不执行。
do-while的循环体至少执行一次。
3.9 while和for的区别
while和for的循环的区别
while和for可以相互替换。
如果确定循环多少次往往用for。如果不确定循环多少次往往用while。
3.10 死循环
for(;;){
}
for(;true;){//让循环条件语句一直为true
}
while(true){
}
boolean boo = true;
while(boo){
//boo = false;
}
do{
}while(true);
//如何跳出死循环:①使用break ②让循环条件语句的结果为false(循环条件语句一定使用的是一个变量)
3.11 嵌套循环
/*
嵌套循环 : 在一个循环a中 在 写一个循环b。 a叫作外层循环 b叫作内层循环。
说明:
1.外层循环控制行 内层循环控制列
2.循环的次数 = 外层循环的次数 * 内层循环的次数
3.外层循环一次 内层循环一轮
*/
public class ForForTest {
public static void main(String[] args) {
/*
*****
*/
for (int i = 1; i <= 5; i++) {
System.out.print("*");
}
System.out.println();//换行
System.out.println("=======================================");
/*
*****
*****
*****
*****
*****
*/
for(int j = 1; j <= 5; j++) { //外层循环控制行 内层循环控制列
for (int i = 1; i <= 5; i++) {//外层循环一次 内层循环一轮
System.out.print("*");
}
System.out.println();
}
System.out.println();//换行
System.out.println("=======================================");
/*
行i 列j
* 1 1
** 2 2
*** 3 3
**** 4 4
***** 5 5
*/
for (int i = 1; i <= 5 ; i++) { //控制行
for (int j = 1; j <= i; j++) {//控制列
System.out.print("*");
}
System.out.println();
}
System.out.println();//换行
System.out.println("=======================================");
/* 行 i 列j
***** 1 5
**** 2 4
*** 3 3
** 4 2
* 5 1
*/
for (int i = 1; i <= 5 ; i++) {//控制行(控制轮)
for (int j = 1; j <= 6 - i; j++) {
System.out.print("*");
}
System.out.println();
}
System.out.println();//换行
System.out.println("=======================================");
/*
行i 列(空格)k 列j
----* 1 4 1
---* * 2 3 2
--* * * 3 2 3
-* * * * 4 1 4
* * * * * 5 0 5
*/
for (int i = 1; i <= 5 ; i++) {
/*
输出 空格
*/
for (int k = 0; k < 5 - i; k++) {
System.out.print(" ");
}
/*
输出 *
*/
for (int j = 1; j <= i ; j++) {
System.out.print("* ");
}
System.out.println();
}
System.out.println();//换行
System.out.println("=======================================");
/*
行i 列(空格)k 列j
----* 1 4 1
---* * 2 3 2
--* * * 3 2 3
-* * * * 4 1 4
* * * * * 5 0 5
-* * * * 1 1 4
--* * * 2 2 3
---* * 3 3 2
----* 4 4 1
*/
for (int i = 1; i <= 5 ; i++) {
for (int k = 0; k < 5 - i; k++) {
System.out.print(" ");
}
for (int j = 1; j <= i ; j++) {
System.out.print("* ");
}
System.out.println();
}
for (int i = 1; i <= 4 ; i++) {
for (int k = 1; k <= i ; k++) {
System.out.print("-");
}
for (int j = 1; j <= 5 - i; j++) {
System.out.print("* ");
}
System.out.println();
}
}
}
3.12 break和continue在嵌套循环中的使用
/*
在嵌套循环中
break : break结束的是包含它的那层循环当前循环
continue :结束的是包含它的那层循环的当次循环
结束指定的那层循环:
1.给那层循环起名字
2.在brak的后面跟上循环的名字
*/
public class BreakContinueTest {
public static void main(String[] args) {
for (int i = 1; i <= 3 ; i++) {
for (int j = 1; j <=3 ; j++) {
if (j == 2){
//break;//break结束的是包含它的那层循环的当前循环
continue;//结束的是包含它的那层循环的当次循环
}
System.out.println("i=" + i + " j=" + j);
}
}
System.out.println("==================================");
//结束外层循环
lable : for (int i = 1; i <= 3 ; i++) { // 给循环起名字: 名字:for(;;)
for (int j = 1; j <=3 ; j++) {
if (j == 2){
break lable;//结束指定的循环 : break 循环的名字;
}
System.out.println("i=" + i + " j=" + j);
}
}
System.out.println("程序结束");
}
}
第四章 数组
4.1 数组概念
/*
数组:可以用来存储多个相同类型的数据的容器
说明:
1.数组是引用数据类型。数组中的数据可以是基本数据类型也可以是引用数据类型(取决于是一个什么类型的数组)。
2.数组中的数据被称为元素。
3.数组一旦初始化完成数组的长度不可变。
数组的分类: 一维数组,二维数组,......多维数组。
*/
4.2 一维数组
一维数组的声明
//格式: 数据类型[] 数组名;
// 数据类型 数组名[];
int[] scores;//数组中的元素是int类型 -- 建议使用此写法
String names[];//数组中的元素是String类型。
一维数组的初始化
//2.静态初始化:用静态数据(编译时可以确定)对数组进行初始化。静态数据的个数就是数组的长度。
//方式一:数组的声明和初始化可以分开写。
scores = new int[]{5,2,4,1,9};//每个元素之间用逗号分隔开。该数组的长度为5
names = new String[]{"小龙哥","小饭老师","小泽老师"};//该数组的长度为3
//方式二 : 数组的声明和初始化不能分开。
int[] ids = {1,6,3,9,10};//可以理解成是方式一的简写
//动态初始化:通过指定数组的长度初始化数组但是数组中只有默认值。
// 初始化完成后再给数组中的元素进行赋值。
String[] names;
names = new String[3];//3指的是数组的长度。数组中现在只有默认值。
一维数组元素的赋值和取值
int[] ns = new int[2];
//给元素赋值
ns[0] = 5;
//获取元素的值
int n = ns[0];
一维数组的长度
int len = 数组名.length;
一维数组元素的遍历
String[] names = {"a","b"};
//遍历数组中的元素 (快捷方式: 数组名.fori)
for (int i = 0; i < names.length; i++) {
System.out.println(names[i]);
}
一维数组元素的默认值
基本数据类型:
整型默认值:0
浮点型默认值:0.0
布尔类型默认值: false
字符类型默认值: \u0000
引用数据类型:null
一维数组的内存解析
4.3 内存概述
4.4 一维数组的常见算法
查找数组中的最大值
int[] numbers = {10,5,9,20,3};
//最大值 - 默认把数组中的第一个元素当成最大值
int maxNumber = numbers[0];
int maxIndex = 0;
//比较
for (int i = 1; i < numbers.length; i++) {//遍历数组 不包括第一个
if (maxNumber < numbers[i]){//说明maxNumber中的值不是最大的
//将maxNumber中的值换掉
maxNumber = numbers[i];
maxIndex = i;
}
}
System.out.println("index=" + maxIndex + " maxNumber=" + maxNumber);
int[] numbers = {10,5,9,20,3};
int maxIndex = 0;//最大元素的索引位置
for (int i = 1; i < numbers.length; i++) {
//比较
if (numbers[i] > numbers[maxIndex]){//说明maxIndex记录的位置的元素不是最大的
//修改maxIndex的索引值
maxIndex = i;
}
}
System.out.println("maxIndex:" + maxIndex);
数组元素查找-无序
//1.数组中的数据是无序的
int[] numbers = {5,2,3,1,9};
//要查找的数据
int findNumber = 19;
//记录元素所在的位置(索引位置)
int index = -1;
//遍历数组
for (int i = 0; i < numbers.length; i++) {
if (findNumber == numbers[i]){
index = i;//将当前的索引值赋值给index
break;
}
}
if (index != -1) {
System.out.println("要查找的数据在数组的" + index + "索引位置");
}else{
System.out.println("没有找到该数据");
}
数组元素查找-有序
//1.数组中的数据是有序的
int[] numbers = {2,3,5,6,9};
//要查找的数据
int findNumber = 4;
//记录元素所在的位置(索引位置)
int index = -1;
//遍历数组
for (int i = 0; i < numbers.length; i++) {
System.out.println("===");
if (findNumber == numbers[i]){
index = i;//将当前的索引值赋值给index
break;
}else if(numbers[i] > findNumber){//判断当前元素的值是否大于要找的值。如果大于说明要找的值已经不存在了
break;
}
}
if (index != -1) {
System.out.println("要查找的数据在数组的" + index + "索引位置");
}else{
System.out.println("没有找到该数据");
}
数组元素的反转
int[] numbers = {5,6,9,11,20,23};
//方式一:
/*
for (int i = 0; i < numbers.length / 2; i++) {
int temp = numbers[i];
numbers[i] = numbers[numbers.length - 1 -i];
numbers[numbers.length - 1 -i] = temp;
}
*/
//方式二
for (int i = 0,j = numbers.length -1; i < numbers.length / 2; i++,j--) {
int temp = numbers[i];
numbers[i] = numbers[j];
numbers[j] = temp;
}
System.out.println(Arrays.toString(numbers));
数组元素的排序-选择排序
int[] numbers = {3,9,2,6,1};
//控制轮
for (int i = 0; i < numbers.length - 1; i++) {
//最小值所在的索引位置
int minIndex = i;
//比较(比较元素的索引的范围)
//j = i + 1 : 因为每i轮的第i个索引位置的元素当成最小值 所以要比较的范围就要从i+1开始到最后
for (int j = i + 1; j < numbers.length; j++) {
//比较两个数的大小
if (numbers[minIndex] > numbers[j]){//说明当前记录的最小值不是最小的
//重新修改最小值的索引值
minIndex = j;
}
}
if (minIndex != i) {
//交换-将该轮找到的最小值和第i个位置进行位置交换--- 我们排序是从小到大
int temp = numbers[minIndex];
numbers[minIndex] = numbers[i];
numbers[i] = temp;
}
}
System.out.println(Arrays.toString(numbers));
数组元素的排序-冒泡排序
int[] numbers = {5,3,6,4,2};
for (int i = 0; i < numbers.length - 1; i++) { //控制轮
for (int j = 0; j < numbers.length - 1 - i; j++) {//控制交换次数
//比较当前值和下一个值的大小
if (numbers[j] > numbers[j + 1]){
//数据交换
int temp = numbers[j];
numbers[j] = numbers[j + 1];
numbers[j + 1] = temp;
}
}
}
System.out.println(Arrays.toString(numbers));
4.4 二维数组
二维数组的概述
可以理解成:一维数组中的元素还是一个一维数组。
二维数组的声明
/*
数据类型[][] 数组名;
数据类型 数组名[][];
数据类型[] 数组名[];
*/
int[][] numbers;
int numbers2[][];
int[] numbers3[];
二维数组的初始化-静态初始化
//方式一 数组的声明和初始化可以分开
int[][] numbers;
numbers = new int[][]{{1,2},{2,2},{3,1}};//二维数组的长度3 二维数组的元素的长度是2
//方式二:注意 数组的声明和初始化不能分开写。
String[][] persons = {{"龙哥","男"},{"伟哥","女","110"},{"杰哥","x"}};
二维数组的初始化-动态初始化
//动态初始化: 先通过指定数组的长度的方式进行初始化 后面再赋值
//方式一:二维数组的元素的长度是一样的
String[][] ps = new String[3][2];//二维数组的长度为3 二维数组的元素(一维数组)的长度为2
//方式二:二维数组的元素的长度可以是不一样的
String[][] ps2 = new String[3][];//二维数组的长度为3
System.out.println(ps2[0]);
System.out.println(ps2[1]);
System.out.println(ps2[2]);
二维数组的长度和二维数组元素的长度
//二维数组的长度
System.out.println(persons.length);
//二维数组的元素的长度
System.out.println(persons[1].length);
二维数组的遍历
//遍历二维数组
String[][] persons2 = {{"a","男"},{"伟哥","女","110"},{"c","x"}};
//先遍历二维数组 -- 二维数组的元素是一维数组
for (int i = 0; i < persons2.length; i++) {
//获取的是二维数组的元素
String[] ps = persons2[i];
//遍历二维数组的元素(一维数组)
for (int j = 0; j < ps.length; j++) {
System.out.print(ps[j] + " ");
}
System.out.println();
}
System.out.println("===============================");
//先遍历二维数组 -- 二维数组的元素是一维数组
for (int i = 0; i < persons2.length; i++) {
//遍历二维数组的元素(一维数组)
for (int j = 0; j < persons2[i].length; j++) {
System.out.print(persons2[i][j] + " ");
}
System.out.println();
}
二维数组的默认值
二维数组的元素是一维数组,数组是引用数据类型所以默认值是null
二维数组的内存解析
4.5 二维数组练习
杨辉三角
/*
2.通过二维数组实现杨辉三角
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
*/
public class YangHuiTest {
public static void main(String[] args) {
//创建二维数组
int[][] numbers = new int[6][];
for (int i = 0; i < numbers.length; i++) {
//给二维数组中的元素赋值
numbers[i] = new int[i + 1];
//给一维数组的第一个和最后一个元素赋值
numbers[i][0] = numbers[i][i] = 1;
for (int j = 1; j < numbers[i].length - 1; j++) { //不包括第1个和最后一个元素
//numbers[i][j] : 表示中间的元素---当前的那个元素
/*
i 当成行
j 当成列
*/
numbers[i][j] = numbers[i - 1][j] + numbers[i - 1][j - 1];
}
}
//遍历二维数组
for (int i = 0; i < numbers.length; i++) {
for (int j = 0 ; j < numbers[i].length; j++) {
System.out.print(numbers[i][j] + " ");
}
System.out.println();
}
}
}
打印三角形
/**
* Author: Liang_Xinyu
* Date: 24/03/28
* Time: 21:44
*/
public class equilateral {
public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
for (int j = 0; j < 5 - i; j++) {
System.out.print(' ');
}
for (int j = 0; j < i; j++) {
System.out.print("* ");
}
System.out.println();
}
/**
* 倒过来。。。。
* *
* * *
* * * *
* * * * *
* * * * * *
* * * * *
* * * *
* * *
* *
*/
for (int i = 1; i < 5; i++) {
for (int j = 1; j <= i; j++) {
System.out.print(' ');
}
for (int j = 1; j <= 5 - i; j++) {
System.out.print("* ");
}
System.out.println();
}
}
}
第五章 方法
5.1 方法的概述
方法的理解:
方法可以将一段代码封装在一起。
一个方法就是一个具体的功能。
方法是类的成员(类中有方法。方法是在类的里面)。
方法是类中功能的基本单位。
5.2 方法的好处
减少代码的冗余,提高代码的复用性。
5.3 自定义方法的格式
格式:
[修饰符] 返回值类型 方法名([形参列表]) [throws 异常列表]{
方法体;
}
说明:
1.[]里的内容可写可不写。
2.修饰符:修饰符会影响到方法的调用(对象名.方法名 类名.方法名)。方法的可见范围。
3.返回值类型:当调用者对调该方法时,该方法是否需要给调用者返回数据。
返回值类型: void (不返回数据)
返回值类型: 具体的类型 (基本数据类型,引用数据类型)
4.方法名:调用者要通过方法名调用方法。方法名要遵守标识符的规则和规范(小驼峰)
5.形参列表 :用于描述是否需要调用者传数据给方法(本质就是在小括号内是否声明变量)。
6.[throws 异常列表] :后面讲
7.方法体 :方法功能的具体实现的代码。
注意:如果该方法需要返回值那么在方法体中需要使用关键字return返回数据。
5.4 方法的分类
静态方法(用static修饰的方法) vs 非静态方法
5.5 静态方法说明
静态方法要在方法上用修饰符static。
静态方法可以通过类名调用 :类名.方法名
5.6 使用方法时要注意的点
注意:
1.方法必须调用才会执行。调用一次执行一次。
2.方法的位置:方法在类中其它结构外
3.自己调用自己但必须有一个限制条件否则会无限递归下去 最终会导致StackOverflowError
4.如果是调用本类中的方法不需要在方法前"加类名."
5.7 return的说明
/*
return的再说明
说明:
1.return是在方法中使用。
2.如果自定义方法需要返回值那么就需要通过return关键字将数据返回 同时结束方法
如果自定义方法不需要返回值也可以使用retrun作用是结束方法
*/
public class ReturnTest {
public static int getId(){
return 1; //①返回数据 ②结束方法
}
public static void say(){
if ( 5 > 3) {
return; //结束方法
}
System.out.println("say()");
}
public static void main(String[] args) {
say();
}
}
======================================
public static boolean getBoolean(){
int a = 3,b = 5;
/*
//注意一定要有返回值(因为方法在定义时是需要返回值的)。
if (a > b){
return true;//因为返回值类型是boolean所以返回的数据要是布尔类型的数据。
}else{
return false;
}
*/
if (a > b){
return true;//一旦return方法结束了
}
return false;
//===========================================================================
/*
下面两个写法的结果不一样
if (a > b){
System.out.println("true");
}else{
System.out.println("false");
}
if (a > b){
System.out.println("true");
}
System.out.println("false");
*/
}
5.8 值传递
值传递:调用方法时传递的数据类型(基本数据类型 vs 引用数据类型)
在值传递中基本数据类型是将将具体的值赋值过去。
在值传递中引用数据类型是将地址值赋值过去
5.9 可变形参
/*
可变形参:形参的个数是任意个数
格式 :[修饰符] 返回值类型 方法名(数据类型 ... 变量名) [throws 异常列表]{
方法体;
}
说明:
1.在形参列表中可变形参的个数只能有一个并且是形参列表的最后一个
2.在形参列表中必须给非可变形的形参传值因为可变形参的个数是任意个数包括没有
3.可变形参的底层是数组所以使用可变形参要当成数组使用
4.我们可以将和可变形参一样类型的数组传递给可变形参
*/
public static void main(String[] args) {
sum(1,2,3,4);
sum(1);
String[] s = new String[]{"a","b","c"};
sum2(s);
}
public static void sum2(String ... s){
}
public static void sum(int n1,int ... n2){
System.out.println("n1=" + n1);
System.out.println("n2=" + Arrays.toString(n2));
}
5.10 给main方法传参
/*
给main方法传参:
1.通过IDEA给main方法传参:右上角 -> EditConfigurations ->
左边选类名,右边在program arguments中写参数: 参数1 参数2 参数3 ......
注意:左边选类名如果没有那么先运行一次该程序。
2.通过java 字节码文件 参数1 参数2 参数3 ......
*/
5.11 调用方法时的内存
5.12 方法的重载
/*
方法的重载: 在同一个类中可以出现多个相同名字的方法但是要求形参列表必须不同。
(两同(同一个类 同一个方法名) 一不同(不同的形参列表))
不同的形参列表指的是形参的个数,顺序和类型不同。
注意:
1.不同的形参列表和形参的名字没有关系。
2.方法的重载和方法的修饰符没有关系
3.方法的重载和返回值类型无关
*/
/*
调用重载的方法
找最匹配的:
1.找形参个数匹配的
2.找类型匹配的(找和当前匹配的 没有就向上找)
int -> long -> float -> doulbe
*/
========================================================================================
public static void main(String[] args) {
//编译不通过 第一个方法和第一个值最匹配 第二个方法和第二个值最匹配 编译器无法决择。
sum(1,1);
}
public static void sum(int a,double b){
System.out.println("byte a,byte b");
}
public static void sum(double f,int f2){
System.out.println("float f,float f2");
}
5.13 递归
/*
方法的递归:方法自己调用自己
直接递归:自己直接调用自己(a方法调用a方法)
间接递归: 比如 a调用b b调用a
注意:
1.在使用方法的递归时一定要有限制条件用来终止自己-不能无限调用自己。
2.在使用方法的递归时就算有限制条件也不能调用的太深(太多次)
假如栈中最多可以放60000个栈帧 而我们的限制条件在调用70000次方法后终止
仍然会报错。
3.建议可以使用循环的就尽量不用递归
*/
第六章 面向对象上
6.1 面向对象和面向过程
面向过程 :关注的是函数
面向对象 :关注的是函数所在的对象
6.2 类和对象
类:具有一类事物的抽象的描述
可以把类当成一张张设计图
对象:一类事物具体的实例(具体的一个一个的实例)
通过设计图造出来的一个个具体的实物
类和对象的关系: 对象是由类new出来的(派生出来的)
6.3 自定义类
格式:
[修饰符] class 类名{
}
类中的成员:构造器 代码块 内部类 属性(用来描述对象) 方法(实现功能)
6.4 创建对象
[类名 变量名 = ] new 类名([实参]);
6.5 属性
变量的分类
按位置分:
成员变量:在类中其它结构外
实例变量:没有使用static修饰的成员变量
类变量:使用static修饰的成员变量
局部变量:在方法中,方法的形参中,构造器,构造器的形参,代码块声明的变量都叫作局部变量
成员变量的分类
实例变量 vs 类变量
实例变量的格式
[修饰符] 数据类型 变量名;
实例变量的特点
每个对象各自拥有一份实例变量。
实例变量的默认值
基本数据类型:
整型:byte short int long -> 0
浮点型: float double -> 0.0
字符型 : char -> \u0000
布尔类型 : boolean -> false
引用数据类型:null
6.7 实例变量的内存图
6.8 空指针异常
Person p = null;
====下面无论调用属性或方法都会发生空指针异常:NullPointerException======
p.属性名
p.方法名
注意:只要发生空指针就说明对象名根本就没有指向某一个对象。
6.9 类变量
类变量格式
[修饰符] static 数据的类型 变量名;
类变量的特点
所有对象共同拥有一份类变量
类变量的说明
1.类变量是随着类的加载而加载的。
2.所有对象共同拥有一份类变量
3.类变量的默认值和实例变量的默认值相同。
4.类变量和类的生命周期是一样的。
5.类变量是在方法区(静态域)
类变量的内存图
6.10 类变量和实例变量的区别
声明:类变量加static 实例变量不加static
调用:类名.类变量 调用名.实例变量
内存:类变量在方法区
实例变量在堆中
数量:每个对象各自拥有一份实例变量
所有对象共同拥有一份类变量
加载时机 :类变量是随着类的加载而加载的
实例变量是随着对象的创建而加载的
生命周期:类变量的生命周期和类的生命周期一样的
实例变量的生命周期是和对象一样的
为什么不能通过类名调用实例变量?为什么类变量即可以通过类名又可以通过对象名调用?
因为加载时机不同。(千万不要错误的理解成只要类加载就一定造对象)
6.10 实例方法
/*
类的成员 : 属性,方法,代码块,构造器,内部类
类的成员之方法
方法的分类: 静态方法(类方法) vs 非静态方法(实例方法)
实例方法(非静态方法):不使用static修饰的方法
方法的格式:
[修饰符] 返回值类型 方法名([形参列表]) [throws 异常列表]{
方法体;
}
实例方法的说明:
1.位置:在类中其它结构外。
2.实例方法的调用 :对象名.方法名
3.一定是有了对象才能调用实例方法
*/
6.11 this
/*
实例方法 : 发生局部变量名和属性名相同
this:当前对象(谁调用该方法this就是谁)
this调用属性
当局部变量和属性名相同时为了区分属性和局部变量这个时候就需要在属性前加"this."。
如果局部变量名和属性名不相同那么属性名前加或不加"this."都可以
*/
抽象的描述
可以把类当成一张张设计图
对象:一类事物具体的实例(具体的一个一个的实例)
通过设计图造出来的一个个具体的实物
类和对象的关系: 对象是由类new出来的(派生出来的)
## 6.3 自定义类
格式:
[修饰符] class 类名{
}
类中的成员:构造器 代码块 内部类 属性(用来描述对象) 方法(实现功能)
## 6.4 创建对象
[类名 变量名 = ] new 类名([实参]);
## 6.5 属性
#### 变量的分类
按位置分:
成员变量:在类中其它结构外
实例变量:没有使用static修饰的成员变量
类变量:使用static修饰的成员变量
局部变量:在方法中,方法的形参中,构造器,构造器的形参,代码块声明的变量都叫作局部变量
#### 成员变量的分类
实例变量 vs 类变量
#### 实例变量的格式
[修饰符] 数据类型 变量名;
#### 实例变量的特点
每个对象各自拥有一份实例变量。
#### 实例变量的默认值
基本数据类型:
整型:byte short int long -> 0
浮点型: float double -> 0.0
字符型 : char -> \u0000
布尔类型 : boolean -> false
引用数据类型:null
## 6.7 实例变量的内存图
[外链图片转存中...(img-Pm5TFDPv-1712578814972)]
## 6.8 空指针异常
```java
Person p = null;
====下面无论调用属性或方法都会发生空指针异常:NullPointerException======
p.属性名
p.方法名
注意:只要发生空指针就说明对象名根本就没有指向某一个对象。
6.9 类变量
类变量格式
[修饰符] static 数据的类型 变量名;
类变量的特点
所有对象共同拥有一份类变量
类变量的说明
1.类变量是随着类的加载而加载的。
2.所有对象共同拥有一份类变量
3.类变量的默认值和实例变量的默认值相同。
4.类变量和类的生命周期是一样的。
5.类变量是在方法区(静态域)
类变量的内存图
[外链图片转存中…(img-tvLoHuPB-1712578814972)]
6.10 类变量和实例变量的区别
声明:类变量加static 实例变量不加static
调用:类名.类变量 调用名.实例变量
内存:类变量在方法区
实例变量在堆中
数量:每个对象各自拥有一份实例变量
所有对象共同拥有一份类变量
加载时机 :类变量是随着类的加载而加载的
实例变量是随着对象的创建而加载的
生命周期:类变量的生命周期和类的生命周期一样的
实例变量的生命周期是和对象一样的
为什么不能通过类名调用实例变量?为什么类变量即可以通过类名又可以通过对象名调用?
因为加载时机不同。(千万不要错误的理解成只要类加载就一定造对象)
6.10 实例方法
/*
类的成员 : 属性,方法,代码块,构造器,内部类
类的成员之方法
方法的分类: 静态方法(类方法) vs 非静态方法(实例方法)
实例方法(非静态方法):不使用static修饰的方法
方法的格式:
[修饰符] 返回值类型 方法名([形参列表]) [throws 异常列表]{
方法体;
}
实例方法的说明:
1.位置:在类中其它结构外。
2.实例方法的调用 :对象名.方法名
3.一定是有了对象才能调用实例方法
*/
6.11 this
/*
实例方法 : 发生局部变量名和属性名相同
this:当前对象(谁调用该方法this就是谁)
this调用属性
当局部变量和属性名相同时为了区分属性和局部变量这个时候就需要在属性前加"this."。
如果局部变量名和属性名不相同那么属性名前加或不加"this."都可以
*/
6.12 静态方法和非静态方法再说明
/*
方法的分类 : 静态方法(类方法) vs 非静态方法(实例方法)
说明:
1.静态方法是随着类的加载而加载,
实例方法是随着对象的创建加载(才有方法表的地址才能调实例方法)的。
2.静态方法的调用:类名.静态方法名 对象名.静态方法名
实例方法的调用:对象名.实例方法名
3.静态方法中只能调用类变量和类方法
实例方法中除了可以调用属性和实例方法外还可以调用类变量和类方法。
4.实例方法中可以使用this(默认就有this)
类方法中不能使用this(this表示当前对象)
5.在静态方法中如果想要调用非静态方法只能创建对象再通过对象去调用
*/
6.13 局部变量
1.在方法中,方法的形参,构造器,构造器的形参 代码块 中声明的变量都叫局部变量
2.局部变量没有默认值。
3.在调用局部变量时如果该局部变量没有赋值就会报错。
6.14 构造器
类的成员-属性 :用来对对象进行说明(保存对象的状态)
类的成员-方法 :用来实现具体的功能
类的成员-构造器 :①创建对象 ②初始化
构造器格式
格式:
[修饰符] class 类名{
[权限修饰符] 类名([形参列表]){
初始化的代码;
}
}
权限修饰符(用来控制可见范围) :public 缺省的(默认的) protected private
类名 :必须和类的名字保持一致。
形参列表 :用来告诉调用者是否需要传数据。
初始化的代码 :在构造器中要执行的代码
构造器的作用
①创建对象 ②初始化
构造器的说明
说明:
1.在一个类中如果没有显示的定义构造器那么系统会默认提供一个空参的构造器。
2.如果在类中已经显示定义了构造器那么系统将不会再提供空参构造器
3.构造器也叫构造方法。一个类中可以有多个构造器但是构造器彼此之间必须构成重载。
4.构造器在创建对象的时候被调用。一个对象只能调用一次构造器。
5.如果在对象中代码只需要执行一次那这样的代码可以放在构造器中(对象的初始化)。
调用本类中的其它构造器
/*
调用本类的其它构造器
格式 :this([形参列表])
说明:
1.this([形参列表])必须在构造器中使用
2.this([形参列表])必须放在构造器的首行。一个构造器中只能有一个this([形参列表])
3.如果有n个构造器那么this([形参列表])最多有n-1个。
4.在使用this([形参列表])千万不要形成死循环。
*/
class Employee{
int id;
String name;
//构造器
public Employee(){
System.out.println("梁哥我爱你!!!");
}
//构造器
public Employee(int id){
this();//调用的是本类的空参构造器
}
//构造器
public Employee(int id,String name){
this(id);
}
}
6.15 四种权限修饰符
/*
权限修饰符:
private 本类
缺省的 本类 本包
protected 本类 本包 其它包的子类
public 本类 本包 其它包的子类 其它包
注意:
1.类只能被public和缺省的修饰
2.类的成员4个-属性,方法,构造器,内部类可以被四种权限修饰符修饰
*/
6.16 封装性
/*
面向对象的三大特性(封装,继承,多态)之 : 封装性
为什么有封装性?为了保护数据不被随意修改,隐藏类的实现细节。增强了代码的安全性和可维护性。
该隐藏隐藏 该暴露暴露
封装性的实现?让调用者只能通过方法操作属性。可以在方法中加入一些限制条件或数据检查的操作。
封住性的直接体现:①私有化属性 ②提供set/get方法用来给属性赋值和获取属性的值
*/
class Student{
private String name;
//兄弟 千万不在赋值负数 我会被扣钱的
private int age;//数据不安全
//让调用者通过方法给age赋值
//我们把给属性赋值的方法约定俗成的叫作:setXxx
public void setAge(int age){//该方法的功能是给属性赋值
//对数组进行检查保数据类的安全
if (age < 1){
System.out.println("哥们你眼瞎啊 想害我没门");
this.age = 18;
}else {
this.age = age;
}
}
//自定义一个方法用来获取属性的值
//我们把给获取属性值的方法约定俗成的叫作:getXxx
public int getAge(){//该方法的功能是获取属性的值
return age;
}
//该方法的功能只是在本类中使用
private void run(){
}
}
6.17 对象的关联
案例一:没有使用封装
/*
对象和对象的关联
*/
class Person{
String name;
int age;
Dog dog;
}
class Dog{
String name;
int age;
String color;
}
public class PersonTest {
public static void main(String[] args) {
//需求:创建两个对象 梁哥和伟哥 要求梁哥的狗叫作小黄 伟哥的小狗叫作小黑
//创建一只狗
Dog dog = new Dog();
dog.name = "小黄";
dog.age = 2;
dog.color = "绿色";
Person p = new Person();
p.name = "梁哥";
p.age = 18;
p.dog = dog;//对象的关联
//======================
Dog dog2 = new Dog();
dog2.name = "小黑";
dog2.age = 2;
dog2.color = "白色";
Person p2 = new Person();
p2.name = "伟哥";
p2.age = 18;
p2.dog = dog2;//对象的关联
/*
快捷键 : ctrl + z 撤销 (后退)
ctrl + shift + z 反撤销 (前进)
*/
// Dog d = p.dog;
// String n = d.name;
System.out.println("我叫" + p.name + " 我的狗名字叫:" + p.dog.name + " 它的颜色是:" + p.dog.color);
System.out.println("我叫" + p2.name + " 我的狗名字叫:" + p2.dog.name + " 它的颜色是:" + p2.dog.color);
}
}
案例二:使用了封装
public class Dog{
private String name;
private int age;
private String color;
public Dog() {
}
public Dog(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
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;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
public class Person{
private String name;
private int age;
private Dog dog;
public Person() {
}
public Person(String name, int age, Dog dog) {
this.name = name;
this.age = age;
this.dog = dog;
}
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;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
}
public class PersonTest {
public static void main(String[] args) {
//需求:创建两个对象 梁哥和伟哥 要求梁哥的狗叫作小黄 伟哥的小狗叫作小黑
//创建一只狗
Dog dog = new Dog();
//dog.name = "小黄";
dog.setName("小黄");
//dog.age = 2;
dog.setAge(2);
//dog.color = "绿色";
dog.setColor("绿色");
Person p = new Person();
//p.name = "龙哥";
p.setName("龙哥");
//p.age = 18;
p.setAge(18);
//p.dog = dog;//对象的关联
p.setDog(dog);//对象的关联
/*
快捷键 : ctrl + z 撤销 (后退)
ctrl + shift + z 反撤销 (前进)
*/
// Dog d = p.dog;
// String n = d.name;
System.out.println("我叫" + p.getName() + " 我的狗名字叫:" + p.getDog().getName() + " 它的颜色是:" + p.getDog().getColor());
}
}
6.18 JavaBean
/*
JavaBean的作用 :用来存储数据。一条数据就是一个JavaBean对象。
1.JavaBean就是一个类
2.JavaBean的类必须是public所修饰
3.JavaBean的类中必须有空参构造器-构造器也用public修饰
4.JavaBean中的属性是private修饰
5.JavaBean中要给属性提供set/get方法。
*/
public class JavaBean {
private int id;
private String info;
public JavaBean(){
}
public JavaBean(int id){ //可选
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
//其它方法 -(可选)
}
6.19 package
/*
package的作用:
1.为了避免类的重名。不同包中的类名可以重复。
2.可以控制类及类的成员的可见范围。
3.可以对类进行分类管理-方便维护
说明:
1.package必须放在源文件的首行。
2.一个源文件中只能有一个package。
3.package的命名规范:全部小写,每个单词之间用"."隔开
4.package的名字一般是公司域名倒序 + 项目名称
com.atguigu.java
5.起包名时不要以java.开头。
*/
6.20 import
/*
通过import导包(因为我们要告诉编译器我们用的类在哪里)
1.位置:在package和类的中间声明
2.当我们使用其它包中的类时(不是本包的)那么就需要使用import导包
3.可以有多个import(需要哪个类就导那个类所在的包即可)
4.如果我们要使用同一个包中的多个类那么只需要在导包时用*替代类名即可(import com.atguigu.java.*)
5.如果使用的是本包中的类和java.lang包中的类那么不需要import。
6.使用不同包中相同类名的多个类那么需要使用类的全类名进行区分(必须会)
7.静态导入:可以导入类变量(知道就可)
*/
public class ImportTest {
public static void main(String[] args) {
//使用com.atguigu.java包中的Dog类
new Dog();
//使用com.atguigu.java包中的Person类
new Person();
new Scanner(System.in);
new JavaBean();
Math.random();
//=====================================================
//全类名 :包含包名在内的类的全名称(包名.类名)。
//使用不同包中相同类名的多个类那么需要使用类的全类名进行区分
Dog dog = new Dog();
com.atguigu.java3.Dog d = new com.atguigu.java3.Dog(1,"a");//全类名
//============================================================
out.println("aaa");
out.print("cccc");
}
}
6.21 对象数组
案例一:
对象数组 : 数组中的元素是引用数据类型(存放的是对象)
public static void demo2(){
Dog dog = new Dog(1, "哈士奇");
Dog[] dogs = new Dog[3];//因为数组中的元素是Dog类型---所以存的是对象的地址值
dogs[0] = dog;
dogs[1] = dog;
dogs[2] = dog;
dogs[0].id = 5;//给元素的属性赋值
//遍历数组
for (int i = 0; i < dogs.length; i++) {
System.out.println(dogs[i].id + " " + dogs[i].name);
}
}
public static void demo1(){
Dog[] dogs = new Dog[3];//因为数组中的元素是Dog类型---所以存的是对象的地址值
dogs[0] = new Dog(1,"哈士奇");
dogs[1] = new Dog(2,"土狗");
dogs[2] = new Dog(3,"金毛");
//遍历数组
for (int i = 0; i < dogs.length; i++) {
System.out.println(dogs[i].id + " " + dogs[i].name);
}
}
案例二:对数组中的对象排序
/*
练习
创建一个Student类型的数组长度为5
创建5个Student对象并放入到数组中
对数组中的对象按照分数排序。
*/
class Student{
int id;
int score;
String name;
public Student(int id, int score, String name) {
this.id = id;
this.score = score;
this.name = name;
}
public String getInfo(){
return id + " " + score + " " + name;
}
}
public class ArrayObjectTest2 {
public static void main(String[] args) {
//创建Student类型的数组
Student[] students = new Student[5];
//向数组中存放对象
students[0] = new Student(1,86,"圆圆");
students[1] = new Student(3,90,"亮亮");
students[2] = new Student(2,66,"方方");
students[3] = new Student(6,92,"正正");
students[4] = new Student(5,83,"扁扁");
//对数组中的元素排序-按照分数
for (int i = 0; i < students.length - 1; i++) {
for (int j = 0; j < students.length - 1 - i; j++) {
//比较分数
if (students[j].score > students[j + 1].score){//前一个人比后一个人分数据高
//交换数组中对象的地址值
Student temp = students[j];
students[j] = students[j + 1];
students[j + 1] = temp;
}
}
}
//遍历数组
for (int i = 0; i < students.length; i++) {
System.out.println(students[i].getInfo());
}
}
}
第七章 面向对象中
7.1 继承
继承的好处
1.减少代码的冗余
2.提高代码的复用性
3.继承是多态的前提
什么时候可以使用继承
什么时候可以使用继承?
设计者(什么时候创建一个父类):
当类与类之间存在大量相同的内容时。需要满足子类是父类中的一种(is a的关系)。
就可以将子类中重复的内容抽取到父类-这时子类就可以继承父类。
使用者(什么时候继承一个现有的父类):
1.那个类中的实例变量和实例方法需要在当前类中进行使用。那么可以继承那个类。
2.一定要存在is a的关系(子类是父类的一种)
继承的格式
继承的格式 :[修饰符] class 类名A extends 类名B
A类叫作子类-subClass
B类叫作父类-SuperClass
继承的说明
说明:
1.当子类继承父类后就拥有了父类中的实例变量和实例方法。
2.子类继承父类后也可以增加自己(子类)的属性和方法--子类比父类更强大
3.子类继承父类后父类中的属性和方法被private修饰,
子类就不能直接调用但我们还是认为子类继承了父类中私有的属性和方法(可以间接调用)。
4.java是单继承。一个子类只可以有一个父类。但是一个父类可以有多个子类。
5.子类除了可以继承直接父类中的实例变量和实例方法外还可以继承间接父类中的。
6.如果一个类没有显示继承其它类那么默认继承Object类。Object类是所有类的父类。
7.子类和父类一定要存在“is a”的关系(不要为了继承而继承)。
快捷键 :
查看继承关系 :ctrl +alt + u (图)
ctrl + H (继承树-家谱)
7.2 方法的重写
什么时候需要方法的重写
当子类继承父类后如果子类对父类中的方法不满意(父类中的方法不能满足子类的需求)
这个时候就需要子类重写父类的方法。
如何方法重写
直接将父类中需要重写的方法copy一下 粘贴到子类中即可。
在方法重写时要注意的细节
在方法的重写时要注意的细节
1.父类被重写的方法和子类重写的方法,方法名和形参列表必须一致。
2.父类被重写的方法的返回值和子类重写方法的返回值
被重写的方法(父类) 重写方法(子类)
void void
基本数据类型 对应的基本数据类型
---------------------------------
引用数据类型 要小于等于父类被重写方法的返回值类型(A3 继承 A2 继承 A1)
A2 A2和A3
---------------------------------
3.子类重写方法的权限修饰符>=父类被重写方法的权限修饰符
权限修饰符 : private -> 缺省的 -> protected -> public
哪些方法不能被重写
1.私有方法不能被子类重写
2.final修饰的方法不能被子类重写
3.类方法不能被重写
4.如果是跨包的子类 父类中缺省的方法也不能被子类重写因为权限-子类看不到
7.3 super关键字
super调用父类构造器
/*
super调用构造器:
格式: super([形参列表])
作用:调用父类的构造器
说明:
1.super([形参列表])必须放在子类的构造器中。
2.super([形参列表])必须放在构造器的首行。
super([形参列表])在同一个构造器中只能有一个。
3.如果在子类中没有显示的调用super([形参列表])和this([形参列表])
默认调用的是父类的空参构造器
4.创建子类对象必调父类构造器--(因为父类中的构造器可能会存在一定要初始化的事情)
(因为如果有N个构造器最多只能有N-1个this(形参列表)
如果子类的构造器中没有this(形参列表)默认就调父类的空参构造器)
*/
super调用父类属性
super调用属性:调用的是父类的属性
当子类继承父类后如果子类有和父类一样名字的属性。那我们又要调用父类中的属性这时就必须在该属 性名前加"super."当子类继承父类后如果子类没有和父类一样名字的属性.
那我们要调用父类中的属性这就属性名前的"super."可加可不加
注意:如果调用父类的属性时前面没有加"super."该属性就会产生不确定因素-一旦子类声明了和父类一样名字的属性,那么调用的就是子类的。如果没有声明一样名字的属性那么调用的就是父类的。
super调用父类方法
super调用方法 :调用的是父类的方法
当子类继承父类后如果子类重写了父类的方法。那么我们要调用父类被重写的方法就必须加"super."
如果没有重写父类的方法那么调用父类的方法时"super."可加可不加。
注意:如果调用父类的方法时前面没有加"super."该方法就会产生不确定因素---
一旦子类重写就变成调用子类重写的方法。如果没有重写就是父类的方法。
7.4 Object类
概述
/*
Object类:
1.Object是所有类的父类(基类,超类)
2.当一个类如果没有显示的继承其它类那么默认继承Object类。
3.所有对象包括数组都实现了Object中的方法(可以调用Object中的方法)。
*/
toString方法
/*
一 Object中的toString方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
getClass().getName() :获取类的全类名
Integer.toHexString() :将数值转成十六进制
hashCode() : 获取哈希值(哈希码)-- 每个对象都有属于自己的哈希码(可以通过哈希码区分对象)
二 我们发现核心类库中的类基本上都重写了Object中的toString方法。用来输出内容而非地址值。
如果是自定义的类建议也重写toString方法输出内容而非地址值。
*/
class A{
public int id;
public String name;
public A(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "id=" + id + " name=" + name;
}
}
public class ObjectTest {
public static void main(String[] args) {
A a = new A(1,"a");
//当我们输出对象时 实际上输出的是该对象调用toString方法后的返回值
System.out.println(a); //输出的是对象的地址值
System.out.println(a.toString());//输出的是对象的地址值
System.out.println("=========================================");
//核心类库的类
//重写toString方法 - 输出的是内容(属性的值)
String s = new String("aaa");
System.out.println(s);
System.out.println(s.toString());
//重写toString方法- 输出的是内容(属性的值)
Date date = new Date();
System.out.println(date);
System.out.println(date.toString());
//重写toString方法- 输出的是内容(属性的值)
Integer intVar = new Integer(1);
System.out.println(intVar);
System.out.println(intVar.toString());
}
}
7.5 ==的说明
/*
== :
引用数据类型: 比较的是地址值(两个对象是否指向同一块内存区域 - 是否是同一个对象)。
基本数据类型 :比较的是具体的值。
*/
class Demo{
}
public class EqualsTest {
public static void main(String[] args) {
Demo d1 = new Demo();
Demo d2 = new Demo();
Demo d3 = d2;
//比较两个对象的地址值(两个对象是否指向同一块内存区域)。
System.out.println(d1 == d2);
System.out.println(d2 == d3);
System.out.println("===============================");
int a = 5;
int b = 5;
System.out.println(a == b);//比较的是实际的值
System.out.println("===============================");
System.out.println(5 == 5.0);//true : 自动类型提升
}
}
7.6 多态性
多态的本意
多态的本意就是多种形态(比如:形参是Animal类型那么可以传的实参有Dog Cat )
多态的表现形式
父类的引用指向子类的对象
多态的前提
①要有继承性 ②要有方法的重写 ③一定要父类的引用指定子类的对象
多态的优点和缺点
优点:提高代码的灵活性,扩展性,简化代码结构
缺点:不能调用子类独有的属性和方法
(因为编译看左边-在编译的时候看不到(左边是父类)右边对象中的内容)
属性有多态性吗?
没有 (通过哪个类型调用的属性 那么该属性就是那个类的)
例:
Animal a = new Dog();
System.out.println(a.age);//父类的Animal类的
Dog d = new Dog();
System.out.println(d.age);//Dog类的
===============================
class A{
int id = 2;
}
class B extends A{
int id = 3;
}
class C extends B{
int id = 4;
}
C c = new C();
System.out.println(c.id);
A a = c;//向上转型
System.out.println(a.id);
B b = c;//向上转型
System.out.println(b.id);
向上转型和向下转型
向上转型 :在编译的时候将子类的对象以父类的形态呈现。( 多态(父类的引用指向子类的对象))
向下转型:让一个父类的变量在编译期间以子类的形式呈现即可
========================
为什么要向下转型?
为了使用子类独有的属性和方法。
instanceof
/*
NullPointerException : 空指针异常
ArrayIndexOutOfBoundsException : 数组下角标越界
*/
向下转型时要注意:ClassCastException-类型转换异常
class Animal{
}
class Dog extends Animal{
}
class Cat extends Animal{
}
Animal a = new Dog();
Cat c = (Cat)a;//ClassCastException -- 实际对象狗和转换的类型猫没有任何关系
Object o = new String("aaa");
Dog d = (Dog)o;//ClassCastException
==========================================================================================
/*
在向下转型的时候有可能会发生ClassCastException
如何避免ClassCastException ?
可以使用instanceof进行类型判断再向下转型。
格式 : 对象 instanceof 类名
说明:
1.判断对象是否属于右边的类的类型。
2.返回值为布尔类型。
3.在使用向下转型时一定要先类型判断。
注意:
1. C 继承 B B继承 A
当我们创建B类的对象时 用该对象和上面三个类instanceof发现 和B类结果为true
和A类结果为true(向上(当前类继承上面所有的)不向下(当前类和子类没关系))
2.如果是在if -else if中进行类型判断(instanceof)一定要从小到大依次判断
*/
class Person {
protected String name="person";
protected int age=50;
public String getInfo() {
return "Name: "+ name + "\n" +"age: "+ age;
}
}
class Student extends Person {
protected String school="pku";
public String getInfo() {
return "Name: "+ name + "\nage: "+ age
+ "\nschool: "+ school;
}
}
class Graduate extends Student{
public String major="IT";
public String getInfo()
{
return "Name: "+ name + "\nage: "+ age
+ "\nschool: "+ school+"\nmajor:"+major;
}
}
public class InstanceTest {
public static void main(String[] args) {
method2(new Person());
}
/*
(2)根据e的类型执行:
如果e为Person类的对象,输出:
“a person”;
如果e为Student类的对象,输出:
“a student”
“a person ”
如果e为Graduate类的对象,输出:
“a graduated student”
“a student”
“a person”
*/
public static void method2(Person e){
/*
如果是在if -else if中进行类型判断(instanceof)一定要从小到大依次判断
*/
/*
if (e instanceof Graduate){
System.out.println("a student");
System.out.println("a person");
System.out.println("a graduated student");
}else if(e instanceof Student){
System.out.println("a student");
System.out.println("a person");
} else if (e instanceof Person){
System.out.println("a person");
}
*/
if (e instanceof Graduate){
System.out.println("a graduate student");
}
if (e instanceof Person){
System.out.println("a person");
}
if (e instanceof Student){
System.out.println("a student");
}
}
public static void method(Person p){
System.out.println(p.getInfo());
}
}
虚方法
能被子类重写的方法叫作虚方法。
非虚方法:类方法,final修饰的方法,私有的方法 (跨包的子类缺省的方法也不能被重写)
静态绑定
在编译时就可以确定调用哪些方法(不是虚方法)。
动态绑定
在编译时调用哪个方法并不能确定 在运行时再确定调用哪个方法。
两个步骤:
编译时静态分派:先看这个对象xx的编译时类型,在这个对象的编译时类型中找到能匹配的方法
匹配的原则:看实参的编译时类型与方法形参的类型的匹配程度
A:找最匹配 实参的编译时类型 = 方法形参的类型
B:找兼容(多态) 实参的编译时类型 < 方法形参的类型
运行时动态绑定:再看这个对象xx的运行时类型,
如果这个对象xx的运行时类重写了刚刚找到的那个匹配的方法,
那么执行重写的,否则仍然执行刚才编译时类型中的那个匹配的方法
第八章 面向对象下
8.1 代码块
代码块的作用
对类和对象进行初始化
代码块的格式
{
}
注意:只能被static修饰
静态代码块
1.作用:用来对类进行初始化(在静态代码块中用来放在类加载的过程中只执行一次的代码)
2.静态代码块可以有多个从上向下依次执行。
3.静态代码块是随着类的加载而加载(执行)类加载只加载一次。
4.不能调用非静态的-因为加载时机不同
可以调用静态的
5.静态代码块的执行先于非静态代码块的执行
非静态代码块
1.作用:用来对对象进行初始化(在对象中只执行一次的代码可以放在非静态代码块中执行)
2.每创建一次对象就会调用一次非静态代码块
3.非静态代码块的执行先于构造器。
4.如果有多个非静态代码块从上向下依次执行。
5.非静态代码块可以调用类变量和类方法(当创建对象时已经类加载过了)。
8.2 final关键字
/*
final关键字
[面试题]请简述final关键字
final修饰类(最终的类)-太监类:该类不能被继承。(比如:String StringBuilder,....)
final修饰方法(最终的方法):不能被重写
final修饰的变量 :值不能被修改
final修饰的属性:没有默认值要求必须赋值
赋值的方式 :①显示赋值 ②代码块赋值 ③构造器赋值-
一定要保证无论调用哪个构造器都可以给该变量赋值
*/
8.3 单例设计模式
概述
单例设计模式:在程序运行的过程中某一个类的对象只能存在一个。
单例设计模式的实现 : 饿汉式 vs 懒汉式
饿汉式:线程安全(后面讲),在我们调用方法之前该类的对象就已经创建好了-浪费内存
懒汉式:线程不安全(后面讲),在我们调用方法的时候再创建对象(延迟加载-懒加载) - 一定程序上节省了内存
饿汉式
class Employee{
//私有化构造器-不让在类的外边调用该构造器
private Employee(){}
//创建一个本类的对象 - 因为要通过类方法调用此类变量。
private static final Employee e = new Employee();
//通过方法获取类中的对象 - 因为类的外面无法创建本类对象所以就无法调用本类中的实例方法
public static Employee getInstance(){
return e;
}
}
懒汉式
class Bank{
//私有化构造器-不让在类的外边调用该构造器
private Bank(){}
//声明一个本类类型的属性
private static Bank bank = null;
//通过方法获取类中的对象 - 因为类的外面无法创建本类对象所以就无法调用本类中的实例方法
public static Bank getInstance(){ //什么时候调用此方法 什么时候创建对象
if (bank == null){
bank = new Bank();
}
return bank;
}
}
8.4 abstract
概述
抽象类就是对不同对象中相同特性的内容进行描述(抽取)
抽象类
abstract修饰类:抽象类
1.抽象类不能被实例化。
2.抽象方法所在的类必须为抽象类。
3.抽象类中不一定有抽象方法
4.抽象类中有构造器(抽象类就是一个类只不过该类不能被实例化)
抽象方法
abstract修饰方法 :抽象方法
1.抽象方法没有方法体。
2.抽象方法必须被子类实现。
注意
1.抽象类的子类必须实现父类中所有的抽象方法。
如果抽象方法被父类已经实现那么子类不用再实现该抽象方法。
2.抽像类中不一定有抽象方法但抽象方法一定在抽象类中。
3.如果子类不想实现父类中的抽象方法那么该类可以声明为抽象类
abstract不能和哪些关键字一起使用
abstract和final不能一起修饰方法和类。
abstract和static不能一起修饰方法。
abstract和private不能一起修饰方法。
abstract和native不能一起修饰方法 (native修饰的方法没有方法体但不是抽象方法 该方法的实现是用其它语言实现的)
8.5 模板设计模式
/*
设计模式:用来解决特殊问题(结构层面)的思路
常见的设计模式有23种设计模式(单例设计模式,模板设计模式,代理设计模式,装饰设计模式,工厂设计模式...)
模板设计模式:定义一个操作的固定的算法架构,并允许子类在不改变结构的前提下通过实现特定步骤从而实复用和扩展。
注意:在实际开发中大多数情况下不是一开始就知道要用什么设计模式或者哪些类作为父类和子类。
而是在不断的写过程中发现总结然后对代码进行修改甚至重构。
案例: 计算输出10000以内偶数所需要的时间
案例 :计算输出10000以内奇数所需要的时间
*/
abstract class Computer{
public void run(){
//this ---- 指向的是子类对象
//1.获取当前时间 - 开始执行前的时间
//currentTimeMillis() : 获取当前时间到1970年1月1日的毫秒数
long startTime = System.currentTimeMillis();
//2.执行代码
this.runCode();
//3.获取当前时间 - 执行后的时间
long endTime = System.currentTimeMillis();
//4.计算时间差
System.out.println("用时:" + (endTime - startTime));
}
public abstract void runCode();
}
/*
输出奇数
*/
class ComputerOddNumber extends Computer{
@Override
public void runCode() {
for (int i = 0; i <= 10000 ; i++) {
if (i % 2 != 0) {
System.out.println(i);
}
}
}
}
/*
输出偶数
*/
class ComputerEvenNumber extends Computer{
@Override
public void runCode() {
for (int i = 0; i <= 10000 ; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
public class AbstractTest2 {
public static void main(String[] args) {
Computer c = new ComputerOddNumber();//多态
c.run();
}
}
8.6 初始化
类的初始化
/*
类的初始化过程 : 就是给类变量赋值的过程
哪些操作会初发类的初始化:
1.创建对象 (先有类再有对象)
2.调用类中的静态资源
说明:
1.对子类进行初始化时会先对父类进行类的初始化
2.类的初始化只执行一次
3.通过子类调用父类的静态资源时不会对子类进行类的初始化
4.当我们进行类的初始化时底层会调用一个clinit方法
(该方法的数量和有几个父类没有关系-只有一个)
在clinit方法中将父类还有子类中的初始化操作全部合并到clinit方法中执行。
注意:静态代码块和类变量谁在上面先执行谁。
*/
对象的初始化
/*
对象的初始化过程--对属性赋值的过程
说明:
1.在对子类的对象进行初始化时必先对父类进行初始化。
2.初始化的过程(从子类的构造器到父类的构造器再到下面的流程):
父类
属性、代码块(谁在上谁先) ,构造器
子类
属性、代码块(谁在上谁先) ,构造器
3.在继承关系中每个类都会对应一个init方法。
4.init方法:在程序的执行过程中 会将属性,代码块,构造器的操作全部合并到init方法中执行。
注意:前两个(属性和代码块)看顺序 最后一定是构造器。
注意:
1.每创建一次对象都会执行一次对象的初始化过程
*/
8.7 接口
接口描述
接口:对不同类型不同事物相同功能的描述(一定程度上解决了单继承的局限性)
接口可以理解成一种标准 规范
当类实现这个接口就实现了这个标准或规范
接口的格式
权限修饰符 interface 接口名{
}
注意:权限修饰符只能是public和缺省的
说明
1.类和接口是并列的结构。
2.接口中有
JDK1.8之前 :常量,抽象方法
JDK1.8之后: 常量,抽象方法,静态方法,默认方法
3.接口不能实例化,接口中没有构造器。
4.接口和类的关系 :实现关系而且多实现
类名 implements 接口名1,接口名2,.......
5.当类实现接口后要实现接口中所有的抽象方法。如果不想实现该类可以声明为抽象类
6.接口和实现类的多态性 :接口的类型指向实现类的对象
可以向上转型 向下转型 可以类型判断
7.接口和接口的关系 :继承关系并且是多继承
类和类的关系:单继承
类和接口的关系 :多实现
接口和接口的关系 :多继承
案例-USB和蓝牙
/*
USB接口-标准 规范 约定
*/
interface USB{
void open();
void close();
}
interface Bluetooth{
void connect();
void disconnect();
}
/*
电脑
*/
class Computer{
public void runUSB(USB usb){ //接口的类型 变量名 = new 实现类的对象
usb.open();
System.out.println("==================");
usb.close();
}
public void runBluetooth(Bluetooth b){
b.connect();
b.disconnect();
}
}
class Keyboard implements USB {
@Override
public void open() {
System.out.println("键盘灯亮起.....");
}
@Override
public void close() {
System.out.println("键盘灯熄灭");
}
}
/*
设备
*/
class Mouse implements USB,Bluetooth{
@Override
public void open() {
//驱动程序
System.out.println("鼠标的灯亮起来");
System.out.println("鼠标的指针闪起来");
System.out.println("请主人使用");
}
@Override
public void close() {
System.out.println("鼠标的灯全部熄灭");
System.out.println("主人你真的要那么做吗");
}
@Override
public void connect() {
System.out.println("鼠标蓝牙连接成功");
}
@Override
public void disconnect() {
System.out.println("鼠标蓝牙断开连接");
}
}
public class InterfaceTest2 {
public static void main(String[] args) {
//创建电脑对象
Computer c = new Computer();
//创建设备对象
Mouse m = new Mouse();
//将设备运行在电脑上
c.runUSB(m);//多态:接口和实现类的多态性
System.out.println("-------------------------");
Keyboard k = new Keyboard();
c.runUSB(k);
System.out.println("----------运行蓝牙设备--------------");
c.runBluetooth(m);
}
}
接口的新特性
/*
JDK1.8开始 接口中增加了静态方法和默认方法
说明:
1.接口中的静态方法的调用 : 接口名.静态方法名
2.接口中的默认方法的调用 : 实现类的对象.默认方法名
3.类优先原则 :父类中的实例方法和接口中的默认方法如果是同名
同参并且是public修饰那么类优先。
4.接口冲突 : 当实现的多个接口中出了同名同参的默认方法时那么实现类必须重写此方法。
注意:在类中重写接口中的默认方法不要加default
5.在实现类的重写方法中调用接口中被重写的方法 : 接口名.super.默认方法名
JDK1.9开始 :接口中增加私有方法(方法被private修饰)--知道即可
*/
8.8 内部类
内部类说明
内部类:在一个类A的内部再定义一个类B。类B叫作内部类,类A叫作外部类。
内部类的作用:
1.内部类可以很好的实现类的隐藏。
2.可以实现多继承的效果。
3.可以很好的实现高内聚低偶合
内部类分类
成员内部类 :位置在类中其它结构外
局部内部类 :在其它结构(方法,代码块,构造器)内
成员内部类
静态成员内部类:
非静态成员内部类:
局部内部类
普通局部内部类
匿名局部内部类
说明
1.内部类可以被四种权限修饰符修饰。外部类只能被public和缺省的修饰
2.内部类可以调用外部类私有的成员。外部类不能调用内部类私有的成员。
3.创建非静态成员内部类的对象:外部类的对象.new 非静态成员内部类名();
创建静态成员内部类的对象 : new 外部类名.静态成员内部类类名();
4.在非静态成员内部类调用外部类的成员(属性和方法) :外部类名.this.属性名/方法名
在静态成员内部类调用外部类的成员(只能调用静态的) : 外部类名.类变量名/类方法名
5.在非静态成员内部类中不能声明类变量和类方法 - 因为类是非静态的(类的成员)
注意:可以有常量 - public static final 修饰 属性
/**
* Author: Liang_Xinyu
* Date: 24/04/09
* Time: 9:10
* 在Demo类中自定义一个方法public static void flyTest(Fly fly) --- 在方法中使用flying方法
* 要求调用flyTest方法
* (要求实参使用成员内部类,局部内部类,匿名内部类)三种方式实现
*/
public interface Fly {
void flying();
}
class Demo{
class cf implements Fly{
@Override
public void flying() {
System.out.println("成员内部类");
}
}
public static void main(String[] args) {
Demo d = new Demo();
flyTest(d.new cf());
flyTest(new Fly() {
@Override
public void flying() {
System.out.println("匿名内部类");//用的最多
}
});
class pf implements Fly{
@Override
public void flying() {
System.out.println("普通内部类");
}
}
flyTest(new pf());
}
public static void flyTest(Fly fly){
fly.flying();
}
}
8.9 常量-public static final
public static final 数据类型 常量名 = 值;
说明:常量名中的每个单词都要全部大写各单词之间用“_”分隔开。
注意:当我们通过类名调用常量时那么该类不会进行类的初始化。
接口中定义的属性都是常量 系统会默认加上public static final
8.10 枚举类
JDK1.5之前枚举类的实现
class Season{
//属性 - 每个对象各自拥有一份。
private final String NAME;
private final String INFO;
//私有化构造器-不能在类的外面创建该类的对象
private Season(String name,String info){
this.NAME = name;
this.INFO = info;
}
//创建对象
public static final Season SPRING = new Season("春天","春暖花开"); //常量
public static final Season SUMMER = new Season("夏天","夏天蚊子咬");
public static final Season AUTUMN = new Season("秋天","秋天有落叶");
public static final Season WINTER = new Season("冬天","冬天穿棉袄");
public String getNAME() {
return NAME;
}
public String getINFO() {
return INFO;
}
}
JDK1.5开始枚举类的实现
/*
JDK1.5之后的枚举类如何实现
格式 : 权限修饰符 enum 枚举类的名字{
}
权限修饰符 :只能是public和缺省的。
说明:
1.枚举类不能再继承其它类。因为默认继承了java.lang.Enum类
2.创建对象 - 枚举类中声明的对象必须放在类中的首行。
多个对象之间用","分隔开最后用“;”结尾
3.枚举类的构造器默认是private修饰也只能是private修饰
4.如果我们要在枚举类中自定义属性 那么建议该属性用final修饰、
(因为对象都是常量 所以对象中的属性也要常量但不要被static修饰)。
5.switch-case中 switch中的变量的类型可以是枚举类型。
*/
enum Season3{
SPRING,SUMMER,AUTUMN,WINTER;
Season3(){
}
}
枚举类使用场景
/*
在类的设计中 如果是关于某些状态一般用枚举类(比较少)
在类的设计中 如果是关于某些参数(比较多)(比如: ip地址 端口号 线程数量 ....... 等后面Kafka就知道了)
*/
enum ComputerState{
OPEN,CLOSE;//对象
}
public class EnumTest3 {
public static void main(String[] args) {
//setComputerState(-5);
setComputerState(ComputerState.OPEN);
}
/*
用枚举类作为方法的形参的好处就是只能传枚举类中的某一个对象。
*/
public static void setComputerState(ComputerState state){
if (state == ComputerState.CLOSE){
}else{
}
}
}
枚举类中的API
/*
下面了解即可
1.String toString(): 默认返回的是常量名(对象名),可以继续手动重写该方法!
2.String name():返回的是常量名(对象名)
3.int ordinal():返回常量的次序号,默认从0开始
4.枚举类型[] values():返回该枚举类的所有的常量对象,
返回类型是当前枚举的数组类型,是一个静态方法
5.枚举类型 valueOf(String name):根据枚举常量对象名称获取枚举对象
*/
public static void main(String[] args) {
//枚举类型[] values():返回该枚举类的所有的常量对象,
//返回类型是当前枚举的数组类型,是一个静态方法
Season2[] values = Season2.values();
System.out.println(Arrays.toString(values));
//枚举类型 valueOf(String name):根据枚举常量对象名称获取枚举对象
Season2 spring = Season2.valueOf("SPRING");//实参是个字符串 - 严格区分大小写
System.out.println(spring.getName());
//String toString(): 默认返回的是常量名(对象名),可以继续手动重写该方法!
String string = Season2.SPRING.toString();
System.out.println(string);
//String name():返回的是常量名(对象名)
System.out.println(Season2.SPRING.getName());
//int ordinal():返回常量的次序号,默认从0开始
System.out.println(Season2.SPRING.ordinal());
}