- Java的特点
- 注释
- Java的转义字符
- 变量
- 数据类型
- 基本数据类型的转换
- 自动类型转换
- 自动类型转换的注意事项
- 强制类型转换
- 强制类型转换的注意点
- 运算符
- 流程控制
- 数组
- 集合框架
List
接口的实现类ArrayList
类- Arrays类
- 基本数据类型和引用数据类型
- 多维数组
- 冒泡排序
- 面向对象编程
- 方法
- 面向对象编程
- 面向对象三大特性
- 方法的重载
- 重写
- static 关键字
- main()
- 代码块
- 引用转型
- 抽象方法
- 接口 interface
- 内部类
- 多态
- 正则表达式
- File类
- FileOutputStram
- 复制文件
- 字符输入流
- 字符输出流
- 复制文件
- 缓冲流
- 字符输入缓冲流
- 字节输出缓冲流
- Lambda表达式
- 函数式接口
- 多线程
- 线程状态
- 线程的优先级
- 线程不安全的案例
- 线程同步
- 网络编程
InetAddress
- 端口
- 协议
Java的特点
- Java语言是面向对象的语言(OOP面向对象编程)
- Java支持多线程
- Java语言拥有自动垃圾回收
- Java具有简单性,省去了很多C和C++的复杂的语言,比如:goto……
- Java语言具有跨平台的特性,一次编写到处运行
- Java具有鲁棒(健壮性),很严谨,Java属于请类型的编程使用变量之前必须声明变量的数据类型,Java拥有异常处理机制……
注释
- 单行注释
- 多行注释
- 文档注释
Java的转义字符
\n | 换行 |
---|---|
\t | tab键,制表符 |
\\ | 一个”\“ |
\“ | 一个双引号 |
\‘ | 一个单引号 |
\r | 回车 |
快捷键:Ctrl + w ,快速选中一段内容
变量
变量:变化的量
变量的组成:
- 数据类型
- 变量名字
-
变量使用的注意事项
变量表示一个内存空间
- 每一个空间都有自己的 名称(变量名)和 类型(数据类型)
- 变量一定要先声明后使用
- 这个 存储空间(变量)中的值是可以不停变化的,但是数据类型不可变
- 变量在同一个作用域中变量名称不可以重复,作用域就是一个{}
标识符的命名规范
命名的规则
在Java中对标识符有严格的规范(必须遵守)
- 由26个字母,0-9数字,_和 $ 组成
- 可以包含数字但是不可以用数字开头
- 不能是保留字或者关键字,关键字最直白的表现就是在开发工具中会有对应的颜色
- 变量命名严格区分呢大小写
- 标识符中不要有空格
命名的规范可以让代码更加清晰,体现开发人员的专业性
大驼峰 | 类名/接口名 |
---|---|
小驼峰 | 变量名/方法名 |
程序中 “+” 的用法
程序中+的作用,由 “+” 左右两边的类型决定
- 如果两边是数字,进行加法运算
- 两边出现字符,进行连接的操作
快捷键(快速生成变量):变量值.var
数据类型
在Java中对每一个数据都有明确的数据类型,Java是一种 强类型的编辑语言,在每一个变量声明之前必须声明数据类型
每一个数据类型所占用的空间大小是不一样的
Java中的数据类型分为两大类
- 基本数据类型
- 引用数据类型
- 类
- 接口
- 数组
基本数据类型(四类八种)
类型 | 占用空间(单位:字节) | 范围 |
---|---|---|
byte 字节型 | 1 | -128 ~ 127记住 |
short 短整型 | 2 | -215 ~ 215-1 -32768 ~ 32767 |
int 整型 | 4 | -231 ~ 231-1 -2,147,483,648 ~ 2,147,483,647 |
long 整型 | 5 | -263 ~ 263-1 |
整数类型使用的注意事项
Java中所有的整数 默认都是 int 类型,如果使用long类型,需要在变量值的后面加L或者l
了解:
bit(位):计算机中最小的存储单位
byte(字节):是计算机中的基本存储单元
换算:1 byte = b bit
byte 是一个字节的大小,八位
堆和栈的概念和区别
Java程序在运行时都要开辟空间,任何软件在运行时都要在内存中开辟空间,Java虚拟机运行时也要开辟空间的,JVM运行时在内存中开辟一片内存区域,启动时在自己的内存区域中进行更细致的划分,因为虚拟机中每一片内存处理的方式都不同,所以眼单独进行管理。
内存管理:由JVM管理
堆:new出来的对象(包括实例变量)
栈:正在调用的方法中的局部变量(包括方法的参数)
方法区:.class字节码文件(包括静态变量、所有方法)
JVM内存的划分由五片:
- 寄存器
- 本地方法区
- 方法区
- 栈内存
- 堆
- 内存
我们重点来说一下堆和栈:
栈内存
栈内存首先时一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命周期都很短。
堆内存
存储的是数组和对象(其实数组就是对象),凡是new建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据小时,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,垃圾有垃圾回收机制不定时的收取。
详细堆和栈
比如主方法里的语句 int[] arr = new int[3];在内存中是怎么定义的:
主函数先进栈,在栈中定义一个变量arr,接下来为arr赋值,但是右边不是一个具体值,是一个实体。实体创建在堆里,在堆里首先通过new关键字开辟一个空间,内存在存储数据的时候都是通过地址来体现的,地址是一块连续的二进制,然后给这个实体分配一个内存地址。数组都是有一个索引,数组这个实体在堆内存中产生之后每一个空间都会进行默认的初始化(这是堆内存的特点,未初始化的数据是不能用的,但在堆里是可以用的,因为初始化过了,但是在栈里没有),不同的类型初始化的值不一样。所以堆和栈里就创建了变量和实体:
那么堆和栈是怎么联系起来的呢?
我们刚刚说过给堆分配了一个地址,把堆的地址赋给arr,arr就通过地址指向了数组。所以arr想操纵数组时,就通过地址,而不是直接把实体都赋给它。这种我们不再叫他基本数据类型,而叫引用数据类型。称为arr引用了堆内存当中的实体。(可以理解为c或c++的指针,Java成长自c++和c++很像,优化了c++) <br />![](https://cdn.nlark.com/yuque/0/2022/png/23182771/1650609474128-e1166538-f23c-4eb6-b570-9e1142082306.png#clientId=udfff2921-8e6a-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u1844c5bd&margin=%5Bobject%20Object%5D&originHeight=476&originWidth=666&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u7af63f0f-b11d-4394-b5fa-03fc44fe4bb&title=)<br />
如果当int [] arr=null;
arr不做任何指向,null的作用就是取消引用数据类型的指向。
当一个实体,没有引用数据类型指向的时候,它在堆内存中不会被释放,而被当做一个垃圾,在不定时的时间内自动回收,因为Java有一个自动回收机制,(而c++没有,需要程序员手动回收,如果不回收就越堆越多,直到撑满内存溢出,所以Java在内存管理上优于c++)。自动回收机制(程序)自动监测堆里是否有垃圾,如果有,就会自动的做垃圾回收的动作,但是什么时候收不一定。
所以堆与栈的区别很明显:
1.栈内存存储的是局部变量而堆内存存储的是实体;
2.栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
3.栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。
[
](https://blog.csdn.net/pt666/article/details/70876410)
浮点数类型
浮点数的类型
类型 | 长度(字节) | 取值范围 |
---|---|---|
float | 4 | -3.403E38 ~ 3.403E38 - 1( E38 = 10*38 ) |
double | 5 | -1.798E308 ~ 1.798E308 - 1 |
浮点数的使用
- 浮点数进行运算,会造成精度的丢失
- 不建议使用小数进行比较
- 当我们使用float类型的时候,我们需要在变量值的尾部加上f或者F
- Java中所有的小数 默认都是 double 类型的
字符型char
Java中的字符型表示单个的字符,用char来进行声明,变量的值必须使用’’(单引号)包括
单个的字符:在’’ 中只能有一个字,不管是数字,中文,英文还是标点
ACSII值表
布尔类型
在Java中表示真和假,代码中对应着 true 和false
在大多数情况下是在流程控制中使用的
注意:Java中的 boolean 并不对应任何数字
基本数据类型的转换
数据类型转换:将类型转换成另一个类型
转换的方式分为两种
- 自动类型转换(隐式转换)
- 强制类型转换
自动类型转换
在程序赋值或者运算的过程中,将 数据范围小的类型转换为数据范围到的类型 不需要任何的操作,直接赋值即可,所以被称为自动类型转换,隐式转换
大小顺序
byte < short < int < long < float < double
char < int < long < float < double
自动类型转换的注意事项
注意点一:多种数据类型混合使用 系统会首先 将所有的数据类型提升为数据范围最大的那个,之后再进行计算
注意点二:byte short 不能和char 进行转换
注意点三:byte short char 编译器规定这三者在进行计算的时候,先提升到 int 类型在进行计算
强制类型转换
强制类型转换是 大类型 转换成 小类型
通过()来实现
注意:在进行强制类型转换的时候会造成精度的丢失
情况二:数据溢出
强制类型转换的注意点
强制类型转换只能操作最近的数字
解决方案:为整个表达式()阔起来
运算符
- 算数运算
- 关系运算
- 逻辑运算
- 三元运算
- 位运算
算数运算
运算符 | 运算 | 运算 | |
---|---|---|---|
+ | 正号 | 加法运算 | 字符串连接 |
- | 符号 | 减法运算 | |
* | 乘法运算 | ||
/ | 除法运算 | ||
% | 取余,求模 | ||
++ | 前置自增 后置自增 |
先加后用 先用后加 |
|
— | 前置自减 后置自减 |
先减后用 先用后减 |
关系运算符
关系运算结果是一个布尔值,要么是 true,要么是 false
经常在 分支语句 和 循环语句中使用
关系运算符 | 运算 |
---|---|
== | 判断相等 |
!= | 判断不相等 |
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
总结:
- 关系运算组成的表达式,我们称之为 关系表达式
注意 “=” 和 “==” 不要搞混了,单独等号代表赋值的意思,两个等号是判断关系
逻辑运算符
作用:用于连接多个条件(多个关系表达式),最终获得一个布尔值
短路与&&
- 表达式1 && 表达式2
- 如果表达式1和表达式2的结果为 true 的情况下,逻辑运算结果为true,如果有一个表达式为false,结果就是false
- 如果表达式1可以直接判断结果,就不进行表达式2的运算,表达式1为false的时候,运算会停止
- 会比逻辑与少算一次,效率更高
- 在实际开发中,我们会使用短路与进行比较,效率极高
- 如果表达式1和表达式2的结果为 true 的情况下,逻辑运算结果为true,如果有一个表达式为false,结果就是false
- 表达式1 && 表达式2
- 段落或||
- 表达式1 || 表达式2
- 如果表达式1 和表达式2 的结果为 false 的情况下,逻辑运算的结果false,如果有一个表达式为true,结果就是true
- 如果表达式1可以直接判断结果,就不进行表达式2 的运算,表达式1 为true的时候,运算会停止
- 会比逻辑与少算一次效率更高
- 在实际开发中,我们会使用短路或进行比较,效率极高
- 如果表达式1 和表达式2 的结果为 false 的情况下,逻辑运算的结果false,如果有一个表达式为true,结果就是true
- 表达式1 || 表达式2
- 取反!
- 作用:取相反的值,使用”!”
- 逻辑与&
- 表达式1 & 表达式2的结果为 true 的情况下,逻辑运算的结果true,如果有一个表达式为false,结果为fale
- 逻辑与:不管表达式1的结果是 true 还是 false ,都会运算表达式2,会比短路与多运算一次
- 表达式1 & 表达式2的结果为 true 的情况下,逻辑运算的结果true,如果有一个表达式为false,结果为fale
- 逻辑或|
- 表达式1 |表达式2
- 如果表达式1 和表达式2 的结果为 false的情况下,逻辑运算的结果false,如果有一个表达式为true,结果就是true
- 不管表达式1的结果是true 还是 false ,都会运算表达式2,会比短路与多运算一次
- 如果表达式1 和表达式2 的结果为 false的情况下,逻辑运算的结果false,如果有一个表达式为true,结果就是true
- 表达式1 |表达式2
- 逻辑异或^
- 需要一边是 true 一边是 false
赋值运算符
+=
-=
*=
/=
%=
三元/三目运算
基本语法
条件表达式 ? 表达式1 : 表达式2;
//运算规则
//如果表达式的结果为真 true,运算的结果就是表达式1的结果
//反之结果就是表达式2 的结果
键盘输入
在编程中,需要接收用户输入的一些信息,通过键盘的输入局域来获取
需要扫描器,Scanner,是Java的开发人员编写的,我们可以直接用的
步骤:
- 导入该类所在包,java.util
- 创建这个类的对象(声明变量)
- 调用对象的功能
位移运算
- 作位移 : >> , 6 >> 3 对应 6除以2的三次方
- 作位移 : << , 6 << 3 对应 6乘以2的三次方
流程控制
- 代码的正常执行是从上向下,按照流程执行
- 分支语句,会根据条件来判断执行哪些语句
- 循环,在某些特殊情况下需要代码执行多次,会用到循环
分支语句 if-else
if(/*条件判断*/) {
//if中的代码
}else if{
//else if 中的代码
}else{
//else 中的代码
}
流程:首先判断的结果是否是 true ,如果是 true 执行 if 中的代码,反之跳过 if ,直接向下执行
if else if多分支只会走一个分支,走一个之后其他的就不会走了
switch-case 多分支
语法:
switch(变量) {
case 值1:
//如果变量和值1相同,走这里的代码
break;
case 值2:
//如果变量和值2相同,走这里的代码
break;
case 值3:
//如果变量和值3相同,走这里的代码
break;
......
default:
//如果变量和前面左右case的值都不同,走这里的代码
break;
}
if-else-if 和 switch 的区别
- if-else-if 主要针对于范围型的 , 每一个分支都要进行判断 , 效率较慢
- switch 只要针对于固定的值 , 而且分支较少 , 效率较高 , 直接用变量和case中的值进行匹配
循环
就是反反复复的重复
在某些特殊的情况下我们需要相同代码执行多次
while循环
语法 :
while(条件表达式) {
//条件表达式要么为真,要么为假
//如果条件表达式为true,执行循环体,反之退出循环
}
do-while循环
do {
//循环体
}while(条件判断);
for循环
for(声明计数器变量; 条件表达式; 操作表达式) {
//循环体
}
break 和 continue
- break:休息,在Java 的循环中代表着停止循环
- continue:继续,在循环中对应的功能的功能是,跳出当此,直接进行下一次循环
数组
数组的特点:可以在数组中存储多个数据,但是数据类型要一致
比如整数的数组只能存放整数,不能放小数和字符串
数组的使用
- 声明数组
- 数组中的元素赋值
- 使用数组中的元素
- 静态声明:在声明的时候直接给数组中的元素赋值
- 动态声明:在声明的时候直接给数组中的元素赋值
数组的遍历
下标:是指数组中每一个元素的位置,从0开始
如果要使用数组中的元素需要通过下标获得
数组的默认值
使用数组进行动态声明的时候,此时数组只有长度,并没有赋值,但是每一个元素都有一个对应数据类型的默认值
类型 | 默认值 | |||
---|---|---|---|---|
byte | short | int | long | 0 |
float | double | 0.0 | ||
boolean | false | |||
char | \u0000 |
集合框架
集合
集合是多个元素组成的一个单元
类似于数组,但是数组是有缺陷的
数组的缺陷:
- 数组的长度是固定,一但创建长度就不可以改变,使用超过了下标位置的元素
ArrayIndexOutOfBoundsException
异常 - 数组只能存放一种数据类型
集合的分类
- 注意:集合中只允许存放引用类型,不允许存放基本数据类型
- Java的集合分为三种
set
集合- 无须且不重复
- 继承了
Collection
接口,是一个单列集合 set
不区分元素的顺序(存入和取出),不允许有重复的元素
list
集合- 有序可重复的
- 继承了
Collection
接口,是一个单列集合 list
集合区分元素的顺序(存入和取出),而且允许重复的元素list
集合会在添加元素的时候指定元素的索引/下标,所以可以存在重复元素的
map
集合- 没有继承
Collection
接口,是一个双列集合 - map结合存储数据是以 键值对的方式
(key ===> value)
- 不能有重复的
key
(键),但是可以有重复的value
(值)key
:身份证value
:人多的名字
- 没有继承
Collection
集合结构(继承接口实现类)
List
接口的实现类
LinkedList
:增删的效率高ArrayList
:查询效率高Vector
:线程同步,线程安全
ArrayList
类
特点:查询数据的效率很高,性能优越
存储方式:ArrayList
采用的和数组一样的存储方式,在内存中开辟了多个空间
ArrayList
的常用方法
- 构造方法
ArrayList
:构造一个初始容量为十的空列表 void add(int index,Object ele)
:在index
的位置插入元素boolean addAll(int index,Collection eles)
:index
位置开始将eles
中的所有元素添加进来Object get(int index)
:获取指定下标的元素int indexOf(Object o)
:返回参数o
在集合中首次出现的位置,如果没有找到返回-1
int lastIndexOf(Object o)
:返回参数o
在集合中末次出现的位置,如果没有找到返回-1
Object remove(int index)
:移除指定下标位置元素,并返回这个元素Object set(int index,Object ele)
:设置指定下标位置的元素为ele
,相当于替换List subList(int FronIndex,int toindex)
;返回从fromindex
(含)到toIndex
(不含)位置的集合boolean contains(Object ele)
:用来判断元素是否存在int size(Object ele)
:获取集合中元素的个数boolean containsAll(Collection c)
:用来判断多个元素是否存在,实现自List
接口中的方法
Arrays类
使用 sort 对 int 类型的数组进行排序
//定义一个数组
//测试Arrays这个类中的sort数组排序方法
int[] arr1 = {345, 234, 3453, 123};
//使用sort方法对数组进行排序
Arrays.sort(arr1);
//遍历数据,观察排序是否成功
for (int i = 0; i < arr1.length; i++) {
System.out.println(arr1[i]);
}
copyOf() : 数组扩容方法 , 生成一个新数组
System.out.println("测试copyOf扩容功能");
//copyOf(复制的数组,复制过后的长度)
//生成一个新的数组,长度是指定的复制后的长度,其中还包含原数组中的元素
//复制一个新的数组,指定对一应长度,功能是为数组扩容设计的
int[] intArr = {12, 34, 56};
//将intArr扩容2个元素
int[] intArr2 = Arrays.copyOf(intArr,intArr.length + 2);
for (int i = 0; i < intArr2.length; i++) {
System.out.println(intArr2[i]);
}
toString():将数组元素转换成字符串显示
//toString()
int[] tstArr = {78,65,12,455,65};
String str = Arrays.toString(tstArr);
System.out.println(str);
System.out.println(Arrays.toString(tstArr));
基本数据类型和引用数据类型
引用数据类型:
- 类:class,比如String,Scanner
- 接口
- 数组
在 JVM Java虚拟机的内存 , 分为两个空间 栈内存 : 存储基本数据类型的 , 引用数据类型的地址/引用 堆内存 : 存放引用数据类型的内容
基本数据类型 (四类八种) : byte , short , int ,long , float ,double , char , boolean
基本数据类型存储直接将数值存储 栈内存中 , 如果我们要使用基本数据类型的变量 , 直接使用对应的数值
引用数据类型
引用数据类型的变量在 栈内存 中存放一个地址 , 这个地址直接指向 堆内存中的一个空间 , 引用数据类型的内容实际存储的位置在 堆内存中的
多维数组
之前学习的内容是一维数组 , 多维数组主要是对于 二维数组的学习
一维数组 :
二维数组 :
多维数组的遍历
使用for循环遍历二维数组
//遍历二维数组
int[][] arrA = {
{12,34,56,78,90},//0
{11,22,33,44,55}//1
};
//有几个一维数组
System.out.println("二维数组的长度:" + arrA.length);//2
//二维数组中一维数组的长度
System.out.println("二维数组中一维数组的长度:" + arrA[0].length);//5
//先遍历二维数组
for (int i = 0; i < arrA.length; i++) {
//通过arrA[i]取到一位数组
//遍历一维数组
for (int j = 0; j < arrA[i].length; j++) {
//i:对应维数组的下标
//j:对应一维数组中元素的下标
System.out.print(arrA[i][j] + "\t");
}
System.out.println();
}
冒泡排序
非常重要 , 非常重要 , 非常重要
面向对象编程
类和对象
现有技术是无法实习的 : 不理数据的管理 , 效率很低
解决方案 : 通过类和对象实现
类和对象
在 Java 中万物皆为对象
对象的组成 : **属性和方法**
对象也可以被称作为实例
实例 : 实际存在的例子
创建对象的过程 也可以称作为 , 实例化对象 , 实例化类
问题一 : 类名称一定要大写(大驼峰)
问题二 : 明确你要创建的是哪个类 , 模板别选错了
对象在内存中的表现
类的组成 : 属性 和 方法
属性
属性是类的组成部分之一
别名 : 成员 ( 类中的一个成员 ) 变量 , 字段 field
属性可以不赋值 , 在创建了对象之后 , 通过对象进行赋值的
属性如果不复制 , 程序会规定一个默认值 , 默认值和数组的是相同的
方法
方法的作用
方法的作用就是定义一些特殊的功能代码 , 通过对象来进行调用 , 实现我们想要的功能
如果调用了方法 , 对应着也会执行方法体中的代码
有参数的方法
方法的优势
- 如果没有方法的话 , 我们实现功能的代码要写很多次
- 如果使用了方法的话 , 我们可以将代码直接写到方法中 , 通过对象调用实现功能
方法的可变参数
如果我们没有办法确定方法的参数的数量 , 有可能有1个 , 有可能有多个 , 也可能一个都没有 , 可以通过可变参数实现
可变参数的注意点 :
- 可变参数必须在参数列表 ( 多个参数 ) 的后面
- “ … “ 必须在参数和数据类型之间 , 有无空格皆可 , 但是为了规范要写空格
- 可变参数就是一个数组
方法的返回值
语法 :
返回值可以是任意类型的数据 , 基本数据类型 , 引用数据类型 ( [] , String , Cat …. )
注意 : 方法的返回的数值 , 要和方法的返回值类型相同 , 比如 : 返回值类型是int , 返回的数值内容必须是数字
问题 : 如何判断方法是否该定义返回值 ?
如果我们通过方法计算获得的数据值使用一次 , 而且之后再也不会操作它 , 我们可以不用返回值
如果我们通过计算获得结果会被之后的程序使用 , 我们就需要定义返回值
总结 :
- 方法的返回值分为两种情况 , 第一种有返回值 , 第二种是无返回值
- 如果方法有返回值 , 必须要有 return 关键字
- return 关键字后面数据的数据类型 , 必须和方法的返回值类型一致
- return 必须放在方法的最后一行
- return 只能返回一个值
- 方法如果没有返回值类型 , 返回值类型就是 void , 不用在方法中写 return , 不能有返回值
- 没有返回值类型的方法也可以使用 return 关键字 , 退出方法 , 方法结束
方法之间的调用
如果出现这种情况 , 我们正在编写一个方法 , 但是我们发现这个方法中的某些功能在别的方法中实现过 , 我们就可以不写这个功能的代码 , 直接调用方法来实现这个功能即可
面向对象编程
所谓的面向对象编程就是 , 编写一个类 , 声明他的属性 , 定义方法 , 方法中定义我们需要实现的功能 , 然后实例化这个类的对象 , 通过这个对象调用方法实现我们想要的功能
变量的作用域
通过 for 循环类理解变量的作用域
作用域 : 变量的有效范围 , 就是一个 { } 之间的范围
注意 : 代码编写时的格式 , 代码一定要有缩进的规范 , 为了让开发人员第一时间明确作用域的范围
成员变量 & 局部变量
注意点 :
- 如果在方法中定义一个变量(局部变量) , 必须必须必须赋值之后再使用
- 如果定义一个成员变量 , 可以不赋值直接使用 , 使用的结果就是数据类型的默认值
整数 0 , 浮点数 0.0 , 布尔类型 false , 字符 \u0000
使用方法的特殊情况
现在我们的方法都是在同一个包中使用 , 如果先要使用不同包之中的方法 , 需要 : 导包
之前说学习的关于导包的操作 , 导入 Scanner , Arrays , 是Java开发人员写在 java.util 包中的
String 也是一个类 , 写在 java.lang 包 , 凡是写在 java.lang 包下的类是不需要导包的
return 关键字
在无返回值的方法中 , 使用 return 关键字 , 后面什么都不写直接写分号
如果是这种情况 , 一位这代码走到return这一行的时候 , 方法会停止 , 退出整个方法
break : 终止循环 , 跳出循环
continue : 跳出当次循环 , 直接进行下一次循环
return : 方法停止 , 退出整个方法
面向对象三大特性
封装 : 字面意思将信息隐藏起来 , 不然其他人看到 , 在代码中这种行为就叫做封装
在 Java 中的封装主要是对属性实现的过程进行封装
使用封装 , 通过 private(私有的) 关键字对成员进行封装 分为两部 第一步 : 给属性进行私有化的操作 , 将原来的 public 改成 private 第二步 : 将刚刚私有化的属性 , 一个一个的编写 getter 和 setter 方法
*记住*
*记住*
*记住*
封装的意义 : Java中封装的意义就是对对象的属性和方法进行封装 , 目的在于隐藏属性的实现细节 , 实现数据的安全性
*记住*
*记住*
*记住*
- 封装的注意点
- 所有被 private 修饰的属性 , 无法直接通过对象调用
- 如果在未来的编写类的过程中 , 不管有没有提到封装 , 都要进行封装
面向对象之继承
在现实生活中 , 继承象征这子承父业 , 是父与子的关系
在 Java 程序中 , 也是父与子的关系 , 我们称之为 子类 和 父类
*记住*
*记住*
*记住*
什么是继承 , 以及继承的优势
1, 继承就是子类继承父类的成员 ( 属性 &方法 ) , 私有化无法被使用
2, 优势1 : 实现了代码的重用性 , 生去了很多重复的代码
3, 优势2 : 增强了代码的可维护性
*记住*
*记住*
*记住*
继承的关键字 : **extends 代码实例 被继承的语法 : 子类 extends 父类**
- 使用继承的时候的注意点
- Java 中类之间的继承 , 是单继承 , 一个子类只能有一个父类
一个儿子 一个爹
- 父类可以有多个子类
- 多重继承
什么时候会用到继承
比如我们的项目中需要创建一些类 , 讲师类 , 班主任类 , 学生类
老师 : Teacher
班主任 : Charge
学生 : Student
规定每一个类都有 name , age , gender , 以及 getter 和 setter
如果在没有继承的情况下 , 我们会编写大量的重复性的代码 , 工作量巨大 , 重复的代码非常多 , 代码重用性很低
解决方案 : 使用继承
将这些类中的共性的内容抽取出来 , 形成一个父类 , 让这个父类拥有这些共有的内容 , 让其他的类继承他
父类 : 超类 , 基类
子类 : 派生类
idea快速生成 getter 和 setter : Alt + Insert
- 重用性
将各个类中共性内容抽取出来 , 实现了代码的重用性
- 可维护性
体现在后对代码的修改上 , 比如我们需要给每一个来添加一个属性 “地址” , 如果就没有继承我们需要给每一个类反复的写相同的代码用来添加新的属性 , 工作效率极低 , 容易出现失误
使用继承之后 , 可以将新加入属性写在父类中 , 子类直接继承 , 减少工作量
构造器 / 构造方法
每当我们创建一个对象的时候 , 都会调用这个方法
- 隐式构造方法
- 显示构造方法
如果开发人员不去写这个类的构造方法 , 那么Java会给这个类自动提供一个隐式的构造方法用于创建对象
显示构造方法是我们自己编写的
注意 :
1. **如果我们编写了显示的构造方法 , 那么隐式的构造方法将不在提供
2. 构造方法的 public 可以省略
3. 构造方法可以使用 private 修饰 , 如果构造方法私有化 , 就无法直接创建对象**
问题 : 使用有参的构造方法的时候有可能出现的问题
解决方案
- 写参数
- 再创建一个无参的构造方法
- 构造方法的优势
- 代替了 setter 方法
- 构造方法可以存在多个 , 可以有参数 , 也可以没有参数 , 在可以有部分参数
我们在实例化对象的时候更加的灵活
- 构造方法的注意事项
- 只要存在显示的构造方法 , 隐式的构造方法 Java 将不在提供
- 如果类中没有编写构造方法 , Java 默认会提供一个 无参构造方法
- 构造方法实在创建对象的时候调用 , 当我们使用 new 关键字在创建类对象的时候
在创建对象的时候, 传入的参数和类型, 要和构造方法的参数列表一致
什么时候创建对象构造方法没有返回值的 , 不需要写 void
构造方法不会被继承
构造方法不会被继承
构造方法不会被继承
构造方法不会被继承
构造方法不会被继承
构造方法不会被继承
方法的重载
方法的组成 : 方法名 + (参数) + 方法体
println() 就是一个实现了方法的重载的方法
使用 println() 的时候 , 参数可以是任意类型
方法明名相同 , 方法的参数不同 , 这种行为就是方法的重载
所谓的参数不同指的是
- 参数类型不同
- 参数顺序不同
- 参数数量不同
方法重载的口诀 : 同名不同参 , 与返回值无关
构造方法的重载
构造方法的重载 , 可以是我们在实例化的时候更加的多样性 , 更加的灵活
坑 : 普通方法也可以使用类名命名 , 是不会报错的 类但是无法代替构造方法
this关键字
this : this关键字在 Java 程序中代表 当前类 , 当前对象
this() : 代表调用本类的构造方法
- 使用规范
- this() 要在构造方法中使用
- this() 如果在构造方法中使用 , 必须在第一行
- this() 代表调用本类的无参构造方法 , this(“xxx”) 代表调用本类的有参构造 , 参数是一个字符串
- this() 只能调用本类的构造 , 调用不了其他的方法
重写
如果子类继承父类中的方法,发现父类中的方法无法完成子类中要求的特有功能,子类就可以对父类的方法进行重写
- 方法重写的注意事项
- 必须在继承的基础上
- 在子类重写父类方法时,必须保证方法名称完全相同,参数,返回值一致
- 返回值要么完全相同,要么是父类方法返回值类型的子类
- 子类重写父类方法,访问权限不要小于父类
static 关键字
- 需求
定义 Player 玩家类 , 指定属性用户名 , 密码 , 不断有用户上线 ( 存在实例 ) , 计算用户上线的数量
问题 : count 是一个独立的变量 , 和我们的对象不发生任何关系 , 让 count 和对象产生联系
解决思路 : 将 count 变成对象的属性
静态变量
凡事使用 static 修饰的内容都是静态的成员 , 静态成员被所有的对象共享
如果我们修改一个类的静态成员 , 所有的这个类的对象中这个成员会被一同修改
静态变量 / 类变量 / 静态属性
推荐大家使用第一种
如果静态变量被私有化 , 仍然无法访问
静态变量在使用时的细节
- 类变量是被所有对象拥有的 , 如果修改 , 所有对象一起改
- 类变量和属性的区别
类变量是被所有对象拥有的 , 属性是对象独有的 加上 static 是静态变量 , 如果不加 属性 / 实例变量 / 非静态变量 / 普通变量
类方法的使用注意事项 :
- 类方法中不要出现和对象相关的关键字 , this(当前类的当前对象) 和 super(父类)
- 类方法 / 静态方法可以直接访问静态的成员 ( 静态变量 , 静态方法 ) , 但是不能直接访问非静态成员
如果想要访问静态成员需要通过实例化对访问
- 如果是非静态的方法可以直接访问非静态的成员
记住一个关于静态的口诀 : 静态调用静态 , 非静态可以调用静态和非静态
main()
public : jvm(java虚拟机) 在调用类的主方法的时候 , 必须是public 的不然调用不到
2, static : jvm在调用主方法的时候 , 重来没有创建对象 , 必须是static
3, void : 主方法的作用是用来执行程序的 , 但是并不会给我们一个结果
4, main(String[] args) : 这个字符串数组参数 , 是我们在运行 Java 程序是传入的参数 , 不管我们传入什么都是字符串
代码块
代码块就是 { } 之中的内容
java中的代码块分为2种
- 静态代码块
语法 :static {
//代码
}
- 非静态代码块 (实例块)
语法 :{
//代码
}
代码块的执行的规则 :
- 静态块在实例化的过程中 , 不管你创建了几个对象都只执行一次
- 实例块 , 只要创建对对象就会执行一次
14,1 创建对象的调用顺序
首先调用静态成员
getAge()被执行了 , 说明对象在对静态变量age进行了初始化的操作
静态成员初始化的顺序由编写顺序决定的
再调用普通成员
实例块 , 属性
OO:面向对象
OOA:面向对象分析
OOD:面向对象设计
OOP:面向对象编程
引用转型
- 向上转型(子转父)
- 向下转型(父转子)
向上转型
儿子====>父亲
- 前提条件:继承
- 多态的向上转型
- 子类对象转换成父类引用
向上转型注意事项:父类类型 引用 = new 子类类型();
- 子类对象转换成父类引用
- 可以调用父类中的所有非私有化成员
- 不能调用子类的特有方法(原因这个方法既不是继承父类中方法)
最终运行的效果看子类的具体实现(重写法方法)
//定义一个父类
class Father {
private String name;
public void m() {
System.out.println("父类的m()被执行了......");
}
}
//定义一个子类
class Son extends Father {
//重写父类的方法
@Override
public void m() {
System.out.println("子类的m()执行了......");
}
//定义一个子类的独有方法
public void show() {
System.out.println("子类的show()方法被执行了");
}
}
向下转型
如果我们要在多态的情况下调用子类的特有方法,需要用到向下转型
子类类型 引用 = (子类类型)父类引用();
注意:
- 只能墙砖父类引用,但是不能转换父类的对象
- 要求父类的引用必须指向当前转换的 目标类型对象
当向下转型的时候,可以调用子类的特有方法的
//定义一个父类
class Animal{
String name = "动物";
int age = 10;
public void eat(){
System.out.println("我爱干饭");
}
public void play(){
System.out.println("玩游戏");
}
}
class Dog extends Animal {
public void sleep(){
System.out.println("小狗正在睡觉");
}
}
//父转子
//先创建一个父类的对象
Animal an = new Animal();
Dog dog = (Dog)an;
//要求父类的引用必须指向目标类型的对象
Animal d = new Dog();
Dog dog2 = new (Dog)d;
//通过向下转型的获得的子类调用子类的独有方法
dog2.sleep();
instanceOf
instanceOf是Java的一个保留关键字,左边是对象,右边是类,返回类型是 Boolean 类型。它的具体作用是测试左边的对象是否是右边类或者该类的子类创建的实例对象,是,则返回true,否则返回true
instanceOf使用注意事项
现有继承关系,再由instanceof的使用。
当该测试对象创建时右边的生命类型和左边的类其中的任意一个跟测试类必须得是继承树的同一分支或存在继承关系,否则编译会报错
public class Application {
public static void main(String[] args) {
// Object > Person > teacher
// Object > Person > Student
// Object > String
Object o = new Student(); // 主要看这个对象是什么类型与实例化的类名
// instanceof关键字可以判断左边对象是否是右边类或者子类的一个实例
System.out.println(o instanceof Student); // o 是Student类的一个实例对象 所以判断右边类跟student有无关系 以及显示声明有无关系
System.out.println(o instanceof Person); // true
System.out.println(o instanceof Object); // true
System.out.println(o instanceof String); // false
System.out.println(o instanceof Teacher); // 无关系
System.out.println("========================");
Person person = new Student();
System.out.println(person instanceof Person); // true
System.out.println(person instanceof Object); // true
// System.out.println(person instanceof String); // 编译错误
System.out.println(person instanceof Teacher); // 无关系
}
}
————————————————
版权声明:本文为CSDN博主「yangyssaltedfish」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_37680825/article/details/107580782
instanceof应用场景
需要用到对象的强制类型转换时,需要使用instanceof进行判断
抽象方法
在Animal中有一个eat(),但是方法中的方法体没有意义,因为子类需要重写这个方法实现多态的特性,所以我们不得已在分类中写一个eat()方法
存在的问题:父类的方法无法确定具体实现的功能
解决方法:
当父类中某些方法,需要声明,但是不确定如果实现具体的功能的时候,可以将这个方法定义为抽象方法,那么拥有这个抽象方法的类就是抽象类关键字:abstract
```java //定义一个子类继承抽象的父类 class Cat extends Animal { public Cat(String name, int age) {
super(name, age);
} //子类如果继承抽象的父类,重写父类中的抽象方法 public void eat() {
System.out.println("我爱吃鱼......");
} }
//如果定义抽象方法,类一定要是一个抽象类 abstract class Animal { private String name; private int age; public Animal(String name, int age) { this.name = name; this.age = age; } //定义一个方法动物的行为 //问题: 父类的方法无法确定具体实现的功能 //为方法加入 abstract(抽象) 关键字 //抽象的方法不能拥有方法体 public abstract void eat(); }
总结:<br />当父类一些方法不确定的时候,可以使用 abstract 关键字修饰方法<br />当方法就是抽象方法的时候,有这个抽象方法的类就要是一个抽象类 abstract 修饰的类
<a name="QtgiC"></a>
## 抽象类的介绍
1. 抽象类就是 abstract 修饰的类
1. 使用 abstract 修饰的方法就是抽象方法
1. 抽象类的价值在于设计,设计好了之后,让子类来实现抽象方法
<a name="YsQ68"></a>
## 抽象类的使用细节
> 1. 抽象类不能实例化
> 1. 抽象类不一定要有抽象方法,但是可以有普通方法
> 1. 包含抽象方法的类,一定是抽象类
> 1. abstract 只能用于修饰类和方法,不能修饰属性和其他内容
> 1. 抽象类中可以有任意的成员,因为抽象类的本质就是一个类
>
构造,属性,静态属性,普通方法
> 6. 抽象方法没有方法体
> 6. 如果有一个类继承了抽象类的,子类必须事项抽象类中的所有抽象方法,但是如果是子类是抽象类就不需要重写了
> 6. 抽象方法(abstract)不能和 private,final,static 这个单个关键词一同使用,因为这些关键字和重写违背
> 6. private:子类无法使用 private 的成员,所以无法重写
> 6. final:最终的意思,是不会被重写的
> 6. static:和重写无关
<a name="i3Zvr"></a>
## 抽象方法的例子
```java
public class TestDemo {
public static void main(String[] args) {
//测试实例化抽象类的对象
A a = new A();//报错
}
}
//定义一个抽象类
abstract class A {
}
//定义一个抽象类
abstract class A {
public void hello(){
system.out.println("这是一个普通方法");
}
}
//定义一个抽象类
class A {//报错
public abstract void hello(){//abstract 报错
System.out.println("包含抽象方法的类,一定是抽象类");
}
}
//定义一个和抽象类
abstract class A {
private abstract String name;//abstract报错
public void Aoo(){
System.out.println("abstract 只能用于修饰类和方法 , 不能修饰属性和其他内容");
}
}
//定义一个抽象类
abstract class B {
//属性
private String name;
//静态属性
private static int count;
//构造方法
public B() {
}
//普通方法
public void hello() {
System.out.println("Hello World~~~");
}
}
//定义一个父类
abstract class Father {
public abstract void hello();
//抽象方法除了方法体没有, 参数,返回值都可以有
public abstract int sum(int i, int j);
}
//普通类继承抽象类
//子类继承抽象父类
//需要重写抽象父类的所有抽象方法
class Son extends Father {
@Override
public void hello() {
System.out.println("子类实现的hello()方法....");
}
@Override
public int sum(int i, int j) {
return i + j;
}
}
//抽象类继承抽象类
abstract class Brother extends Father {
@Override
public void hello() {
System.out.println("抽象子类实现的弗雷的方法");
}
public abstract void m();
}
//定义一个子类,继承 Brother 抽象类
class C extends Brother {
@Override
public void m() {
}
@Override
public void hello() {
}
@Override
public int sum(int i, int j) {
return 0;
}
}
接口 interface
在 Java 中 interface 是程序的组成之一
在现实生活中,不断的使用到接口,比如电脑的接口
我们可以将手机,相机,鼠标,键盘…硬件通过 USB 接口连接到电脑上
我们不需要考虑 USB 接口具体链接的是哪个硬件,我们直接连接就可以实现功能
但是 usb 固定了接口的尺寸
通过代码类模拟usb接口,理解接口的特点及作用
//定义一个usb的接口
public interface Usb {
//定义两个方法
//链接接口开始工作
public void start();
public void end();
}
- 使用接口,通过类来使用
关键字 implements (实现) 通过这个关键字创建 实现类
//手机类
//通过implements来创建实现类
//实现了接口,就要实现接口中的方法
public class Phone implements Usb {
@Override
public void start(){
System.out.println("手机连接 usb 开始工作了");
}
public void end(){
System.out.println("手机退出 usb 停止工作了");
}
}
//相机类
public class Camera implements Usb{
@Override
public void start(){
System.out.println("相机链接接口开始工作了");
}
@Override
public void end() {
System.out.println("相机退出接口停止工作了");
}
}
我们已经有一个接口了 , 接口应该方法在哪里 ?
- 创建一个 Computer 类 , 编写 work 方法 , 参数是 Usb 类型 , 通过接口的实现类将手机和相机链接到电脑上
public class Computer {
//定义一个work方法
//实现相机和手机的连接
public void work(Usb usb) {
//通过接口调用方法
usb.start();
usb.end();
}
}
public class Test {
public static void main(String[] args) {
//创建手机,相机,电脑各一个
Phone phone = new Phone();
Camera camera = new Camera();
Computer computer = new Computer();
//通过接口实现手机和相机链接电脑开始工作的操作
//链接手机
//方法参数是Usb接口类型,传入的是实现类的对象
computer.work(phone);//向上转型
//链接相机
computer.work(camera);
}
}
接口的介绍
接口就是给出一些没有实现的方法,封装到一起
到某个地方要去使用的时候,再根据实际情况把这些方法写出来
[访问修饰符] interface 接口名 {
//属性
//方法
}
class 类名 implements 接口名 {
//自己写的属性
//自己写的方法
//必须实现接口的方法
}
- 注意点 ( 需要记住的 )
- 在 JDK1.7前 接口中所有的方法都没有方法体 , 都是抽象方法
- JDK1.8 之后接口中可以有静态方法 , 默认方法 , **接口可以有方法的具体实现
public interface AInterFace {
//写属性
public int n = 100;
//写抽象方法
//在接口中,抽象方法可以省略 abstract 关键字
public void hello();
//在JDK1.8之后,可以有默认方法,需要使用default,来修饰
default public void m1() {
System.out.println("默认方法m1......");
}
//在JDK1.8之后,可以有静态方法
public static void m2() {
System.out.println("静态方法m2......");
}
}
何时使用这个接口
项目经理手下有三位程序员 , 三个人共同开发一个软件
为了控制和管理软件 , 项目经理定义一些接口 , 然后由程序员实现
程序员 A B C 编写类类实现链接数据库的功能 , MySql , DB2 , Oracle
在没有使用接口的情况下
存在的问题 : 如果现在有一个类需要从不同的数据库中取数据 , 在这种情况下 , 通过三者所写的类去链接数据库会发现方法的名字都不一样 , 对于相同的方法管理非常混乱
使用了接口之后
- 优势1 : 定义好接口之后规定每个程序员实现这个接口
实现接口就要重写接口中的方法 , 这样方法的名称就统一了 - 优势2 : 在使用的时候 , 接口会识别不同类型的对象 , 调用链接不同数据库的方法
接口使用的注意事项
细节1 : 接口不能实例化
细节2 : 接口中的方法是 public abstract 修饰的 , 可以不用 abstract
细节3 : 一个普通类实现接口 , 必须将接口中的所有方法实现
//普通类实现接口
class AI implements InterfaceA {
@Override
public void hello() {
System.out.println("重写的hello方法");
}
@Override
public void sleep() {
System.out.println("重写的sleep方法");
}
}
interface InterfaceA {
void hello();
void sleep();
}
细节4 : 抽象类实现接口 , 不需要将所有的抽象方法实现
interface InterfaceA {
void hello();
void sleep();
}
//抽象类实现接口
abstract class AAI implements InterfaceA {
}
细节5 : 类可以实现多个接口
interface IB {
void hello();
}
interface IC {
void sleep();
}
//让 Cat 类实现, IB, IC这两个接口
class Cat implements IB,IC {
@Override
public void hello() {
}
@Override
public void sleep() {
}
}
细节6 : 接口中的属性必须是 **public static final** 静态常量
接口中属性的访问形式 : 接口名.属性名
interface ID {
//等价于public static final int age = 100;
int age = 100;
}
测试
细节7 : 接口不能继承其他类 , 但是可以继承多个接口
interface IAa {}
interface IBb {}
interface ICc extends IAa,IBb {}
细节8 : 接口的访问修饰符只能是 public 和 默认的 , 这一点和类相同
实现接口和继承类的区别
小猴子会爬树 , 继承了老猴子相关的能力
如果小猴想要学会飞行 , 就需要通过学习 , 实现小鸟的能力
如果小猴想要学会游泳 , 就需要通过学习 , 实现鱼的能力
实现和继承的关系 : 实现机制是 Java 对于单继承机制的一种补充
代码实例
父类 : Monkey
姓名
爬树 climbing()
子类 : LittleMonkey 继承 Monkey
class Monkey {
private String name;
public Monkey(String name) {
this.name = name;
}
public void climbing() {
System.out.println(name + "会爬树...");
}
}
接口
//定义两个接口
//小鸟
interface Bird {
void fly();
}
//鱼
interface Fish {
void swimming();
}
继承父类 , 实现接口的 子类
先继承胡实现
class LittleMonkey extends Monkey implements Bird,Fish{
public LittleMonkey(String name) {
super(name);
}
@Override
public void fly() {
System.out.println("通过学习会飞了....");
}
@Override
public void swimming() {
System.out.println("通过学习会游泳了...");
}
}
测试
LittleMonkey wk = new LittleMonkey("悟空");
wk.climbing();
wk.fly();
wk.swimming();
总结 :
继承 , 当子类继承父类的时候 , 就自动拥有了父类的功能
如果子类需要扩展功能 , 可以通过实现接口来进行扩展
可以理解为实现接口是对 Java 单继承的一种补足
继承和实现的不同之处
- 实现和继承解决的问题不同
- 继承的价值 : 解决代码的复用型 , 提高代码的可维护性
- 接口的价值 : 设计 , 设计好各种方法 ( 规范 ) , 让其他的类去实现
- 接口比继承更加灵活
- 继承满足 is - a 关系
比如 : Cat is a Animal - 接口满足 like - a 关系
比如 LittleMonkey like a Bird | 关键字 | abstract 抽象类 | interface 接口 | | —- | —- | —- | | 属性 | 可以 | 普通属性没有, 只能有静态常量 | | 构造方法 | 可以 | 不可以 | | 普通方法 | 可以 | 不可以 , 在 JDK 1.8 版本之后可以有静态方法 , 默认方法 , 这两个方法是可以有方法体的 | | 子类 | 单继承 | Java 中的接口是多继承 , 继承的是接口 , 可以被实现类多实现 |
- 继承满足 is - a 关系
- 看图写结果 , 如果报错请描述修正的方式 , 如果没有报错请描述程序输出的结果
interface A {
int x = 0;//默认是 public final static int x = 0;
}
class B {
int x = 1;
}
class C extends B implements A {
public void pX() {
System.out.println(x);
}
public static void main(String[] args) {
new C().pX();
}
}
报错 , 原因不明确的 x 变量
修改后想要即能取到接口中的静态常量 , 又能拿到父类的属性
拿到接口中的静态常量 : A.x
大道父类中的属性 : super.x
public void pX(){
System.out.println(A.x + " " + super.x);
}
选择题
关于接口的说法 , 正确的是 ( D )
A, 接口中的方法只能在接口的实现类中实现
B, 在接口中可以定义成员变量
C, 接口中不能定义静态常量
D, 以上都不对
编程练习
编写程序,求柱体的体积:
(1)、为柱体的底面设计一个接口Geometry,包含计算面积的方法getArea();
(2)、为柱体设计类pillar,要求:
a)有两个成员变量,底面和高度。底面是任何可以计算面积的几何形状。
b)实现构造方法,对成员变量赋值。
c)包含成员方法,计算柱体pillar的体积。
(3)、编写测试类圆形类、矩形类实现Geometry接口,编写测试类Test,分别用圆形、矩形作为柱体的底面,并计算其体积。
- 接口
//定义一个底面接口,定义一个求面积方法
interface Geometry {
double getArea();
}
- 柱体类
//设计柱体类
class Pillar {
//属性,高度,底面
private double height;
private Geometry geometry;//接口引用
//构造方法为属性赋值
public Pillar(double height, Geometry geometry) {
this.height = height;
this.geometry = geometry;
}
public double tiJi() {
//高度*地步面积
return height * geometry.getArea();
}
}
- 图形类
//编写实现类,圆形,矩形
class Yuan implements Geometry {
private double radius;
//通过构造方法为半径赋值
public Yuan(double radius) {
this.radius = radius;
}
@Override
public double getArea() {
return radius * radius * 3.14;
}
}
class Ju implements Geometry {
private double height;
private double width;
public Ju(double height,double width) {
this.height = height;
this.width = width;
}
@Override
public double getArea() {
return height * width;
}
}
- 测试
public class TestDemo01 {
public static void main(String[] args) {
//获得不同类型的底部
Yuan yuan = new Yuan(10.56);
Ju ju = new Ju(4, 5);
//圆形底部
Pillar pillar1 = new Pillar(10, yuan);
System.out.println("圆形底部柱体体积:" + pillar1.tiJi());
//矩形底部
Pillar pillar2 = new Pillar(20, ju);
System.out.println("矩形底部柱体体积:" + pillar2.tiJi());
//求圆形底部的柱体体积,圆形半径5.5
System.out.println("圆形底部柱体体积:" + new Pillar(10,new Yuan(5.5)).tiJi());
}
}
内部类
内部类的介绍
所谓内部类就是在原有类的基础上,又嵌套了一个类,被嵌套的类就是 内部类(innerclass)
除了嵌套内部类的类之外,被称喂 外部其他类
嵌套内部类的类,被称喂 外部类
class Outer {//外部类
class Inner {//内部类
}
}
class Other {//外部其他类
}
class A {//外部其他类
}
特点:
- 可以直接访问 外部类的私有属性
可以体现类和类的包含关系
String str1 = new String("123456");
String str2 = new String("123456");
sout(str1 == str2);//false
sout(str1.hashCode);
sout(str1.hashCode);
内部类的分类
定义在 局部位置 上的(方法了)
- 局部内部类(有类名)
- 匿名内部类(没有类名,重点)
- 定义在成员位置上的
- 成员内部类(没有 static 修饰的)
- 静态内部类(有 static 修饰)
局部内部类
说明:局部内部类是指定义在外部类的局部位置,如方法中,或者代码块中,并且有类型
- 可以访问外部类的所有成员,包含私有的
- 不可以添加访问修饰符,因为它的地位相当于局部变量,仅在局部作用域中好使,局部变量虽然不能添加访问修饰符,但是可以添加 final 关键字
使用final修饰
- 作用域:仅仅在定义其方法或者代码快中有效,一旦离开就无法使用了
- 局部内部类访问外部类的成员,访问方式:直接访问,直接通过名称调用
- 如果外部类要使用内部类,需要实例化对象
//定义一个外部类
class Outer{
//定义一个私有属性
private int i = 99;
//定义一个方法
public void m1{//方法{}中就是这个类的局部变量
//定义一个局部内部类
final class InnerA {
public void m2(){
System.out.println("外部类的私有属性i:" + i);
//直接调用外部类的成员方法
f2();
}
}
//使用内部类的成员,注意别出作用域
IneerA ineerA = new InnerA();
innerA.m2();
}
//定义一个方法
public void f2(){//定义的是外部类的成员方法,在Outer类的任何一个角落都是有效的
System.out.println("我是 f2()...");
}
}
public class Test {
public static void main(String[] args) {
//创建外部类的对象,通过外部类的方法使用局部内部类
Outer o = new Outer();
o.m1();
}
}
总结:
- 局部内部类定义方法和代码块
- 作用域在方法或者代码块中
- 本质就是一个类
- 外部类是不能直接访问,因为局部内部类相当于局部变量
- 如果外部类和局部内部类成员重名的时候,遵循就近原则
如果直接使用同名成员使用的是内部类的
如果想要调用外部类的同名成员,可以使用语法:外部类名.this.成员
//定义一个外部类
class OuterB{
private String name = "张三";
//定义一个方法,其中定义内部类
public void m(){
class IneerB {
private String name = "张小宝";
//定义内部类的方法
public void f(){
//OuterB.this.name
//属性不是静态的类名无法直接调用,需要对象
//OuterB.this 代表了 OuterB 类的对象
//通过外部类的对象调用外部类的属性
System.out.println("内部类的name" + name + "外部类的name:" + OuterB.this.name);//张小宝
}
}
//使用局部内部类
IneerB ib = new IneerB();
ib.f();//调用 f()
}
}
public class TestDemo01 {
public static void main(String[] args){
//创建外部类的对象,同通过外部类的方法使用局部内部类
OuterB ob = new OuterB();
ob.m();
}
}
匿名内部类(重点)
特点:
- 匿名内部类本质上是一个类
- 内部类
- 该类没有 可视的名字
- 等同于一个类的对象
说明:定义在外部类的局部位置,没有名称
new 类名或者接口 {
//重写方法
}
匿名内部类的使用
代码实例(基于接口的匿名内部类)*
//定义一个接口
interface IA{
//相当于public abstract void eat();
void eat();
}
//如果我们按照传统方式,我们需要实现类
//重写其中的抽象方法
class Cat implements IA {
@Override
public void eat() {//重写eat()
System.out.println("瞄~我爱吃鱼!!");
}
}
public class TestDemo02 {
public static void main(String[] args){
//通过实现类来创建一只小猫
Cat cat = new Cat();
cat.eat();
}
}
问题:
如果我们按照传统的实现类的方式,如果 Cat 只使用一次,单独写一个类很不划算,因为以后不会再用了,而且如果这个时候 Dog类还需要创建一个,但是Dog也只用一次,按照传统的实现类还是很繁琐的
解决方案:通过匿名内部类简化
代码实例
public class TestDemo2 {
public static void main(String[] args) {
//通过实现类来创建一只小猫
AA Cat = new AA(){
@Override
public void eat(){
System.out.println("汪~我爱啃骨头~~");
}
};//注意";"别省
dog.eat():
//通过 getClass() 方法
//作用获得匿名内部类的运行类型,也就是匿名内部类对应的实现类
//底层的实现类TestDemo2
System.out.println("dog引用对应的运行类型:" + dog.getClass());
}
}
在此案例中 dog 对象的 运行类型 和 编译类型
dog的编译类型:IA
dog的运行类型:TestDemo2
dog对象底层的实现
class TestDemo2 implements IA {
@Override
public void eat() {
System.out.println("汪~我爱啃骨头!!");
}
}
注意:匿名内部类使用了一次之后,就不能再使用了,当时通过匿名内部类创建的对象可以多次使用
public class TestDemo03 {
public static void main(String[] args) {
//使用匿名内部类创建一个小猫
AA cat = new AA() {
@Override
public void cry() {
System.out.println("小猫喵喵叫~~~");
}
};
cat.cry();
}
}
//定义一个抽象类
abstract class AA {
public abstract void cry();
}
代码实例 ( 基于普通类的匿名内部类 )
- 定义类
//定义一个普通的Person类
class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public void holle() {
System.out.println("Hello World");
}
public String getName() {
return name;
}
public void show(String name) {
System.out.println("你好我是" + name);
}
}
测试
public class TestDemo04 {
public static void main(String[] args) {
//通过普通类来定义匿名内部类
//new Person() {...}就是Person的对象
new Person() {
public void hello() {
System.out.println("匿名内部类的Hello");
}
}.hello();
new Person() {
@Override
public void show(String name) {
System.out.println("我是匿名内部类:" + name);
}
}.show("张三");
//问:这两个对象的运行类型是Person吗?
Person p = new Person() {};
//TestDemo04$3
System.out.println("对象p的运行类型是:" + p.getClass());
}
}
对象 p 编译类型 和 运行类型
编译类型 : Person
运行类型 : TestDemo04$3
底层 :
class TestDemo04$3 extends Person {
}
4,2 匿名内部类的注意点
- 不能添加访问修饰符 , 应为匿名内部类相当于一个局部成员 , 等同于局部变量
- 作用域 : 仅在在定义其的方法或者代码块中有效
- 匿名内部类访问外部类成员的方式 : 直接访问
如果匿名内部类和外部类的属性重名 , 遵循 就近原则
直接使用 , 使用的是匿名内部类的
如果要使用外部类的重名属性怎么办 , 语法 : 外部类名.this.属性名//定义一个外部类
class OuterC {
private int num = 100;
public void m() {
//基于普通类定义一个匿名内部类
new Person() {
//定义一个重名的属性
private int num = 999;
public void f() {
System.out.println("匿名内部类的num:" + num + ",外部类的num:" + OuterC.this.num);
}
}.f();//通过匿名内部类对象直接调用方法
}
}
public class TestDemo05 {
public static void main(String[] args) {
//测试:实例化外部类对象,调用声明了匿名内部类的方法
OuterC oc = new OuterC();
oc.m();
}
}
外部其他类不能访问匿名内部类 , 因为匿名内部类只是一个局部的变量
匿名内部类的最佳实践
public class TestDemo06 {
//定义一个静态方法,将参数类型只为IB接口类型
//意味着这个方法只能传入 IB 接口的实现类对象
public static void m(IB b) {
b.show();//是指通过实现类对象调用重写的show()方法
}
public static void main(String[] args) {
//通过匿名内部类简化
//匿名内部类实现 IB 接口
//将重写和穿参融为一体
m(new IB() {
@Override
public void show() {
System.out.println("匿名内部类重写的show方法");
}
});
}
}
//定义一个接口
interface IB {
void show();
}
5, 成员内部类
说明 : 成员内部类定义在外部类的成员位置 , 并且没有static 修饰
- 可以直接访问外部类的所有成员, 包含私有
class OuterA {//外部类
//定义一个私有的成员变量
private int num = 100;
//定义一个成员位置的内部类
class InnerA {
//定义一个方法,访问外部类的私有成员
public void m() {
System.out.println(num);
}
}
//通过方法使用成员内部类
public void f() {
InnerA ia = new InnerA();
ia.m();
}
}
测试
public class TestDemo01 {//外部其他类
public static void main(String[] args) {
//创建外部类的对象,调用使用类内部类的方法
OuterA oa = new OuterA();
oa.f();
}
}
- 成员内部类可以添加任意的访问修饰符 : public , protected , 缺省 , private
- 作用域 : 在类中 , 和方法,属性 ( 类成员 ) , 根据访问修饰符而来
如果外部类想要使用内部类的成员 ( 属性,方法 ) , 需要通过实例化对象 - 成员内部类访问外部类的成员 , 方式: 直接访问
外部其他类 访问 成员内部类
方式1 : 通过特殊的语法访问外部类.成员内部类 成员内部类引用 = new 外部类().new 成员内部类();
class OuterB {
class InnerB {
public void hello() {
System.out.println("成员内部类的hello方法~~~~~~");
}
}
//方式2:通过外部类的方法获得成员内部类的对象
public InnerB getInnerB() {
return new InnerB();
}
}
public class TestDemo02 {
public static void main(String[] args) {
//使用方式1对成员内部类进行使用
//语法:外部类.成员内部类 成员内部类引用 = new 外部类().new 成员内部类();
OuterB.InnerB ib = new OuterB().new InnerB();
ib.hello();
//使用方式2获得成员内部类的对象
OuterB ob = new OuterB();
OuterB.InnerB innerB = ob.getInnerB();
innerB.hello();
}
}
外部类属性 和 成员内部类属性重名
直接使用 , 遵循就近原则 , 使用成员内部类的
使用外部类的 , 语法 : 外部类.this.属性名
/**
* 定义外部类,编写和内部类重名的成员变量
*/
class OuterC {
private String name = "张三";
class InnerC {
private String name = "李四";
public void m() {
System.out.println(name);//李四
//使用外部类重名的
//语法:外部类名.this.属性名
System.out.println(OuterC.this.name);//张三
}
}
}
测试
public class TestDemo02 {
public static void main(String[] args) {
OuterC.InnerC ic = new OuterC().new InnerC();
ic.m();
}
}
静态内部类
说明 : 静态内部类定义在外部类的成员位置 , 使用 static 修饰 , 和成员内部类的区别 : 没有static
- 静态内部类可以直接访问外部类的所有成员 , 包括 private 修饰的 , 注意 : 非静态的不能直接访问
- 可以添加访问修饰符 , public , private , protected , 缺省 , 本身就是一个类成员
- 作用域 : 和成员内部类一样, 根据访问修饰符来决定
- 静态的内部类可以直接访问外部类的成员
- 外部类访问内部类的成员需要实例化对象
class OuterD {//外部类
//定义静态和非静态的成员
private String name = "张三";
private static int age = 100;
static class InnerD {//使用静态内部类
//使用外部类的成员
public void m() {
//使用非静态的成员
//System.out.println("外部类的name" + name);//报错:静态调用非静态
System.out.println("外部类的age" + age);//OK:静态可以调用静态的
}
}
//在外部类成员中使用静态内部类的成员,通过实例化对象
public void hello() {
//通过静态内部类对象调用m()
InnerD id = new InnerD();
id.m();
}
}
测试
public class TestDemo03 {
public static void main(String[] args) {
OuterD od = new OuterD();
od.hello();
}
}
外部其他类访问静态内部类 ( 3种 )
方式1 : 语法
外部类.静态内部类 静态内部类的引用 = new 外部类.静态内部类();
方式2 : 通过普通方法获得实例
//方式2:通过方法返回一个静态内部类的对象
public InnerD getInnerD() {
return new InnerD();
}
方式3 : 通过静态方法
//方式3:通过静态方法返回一个InnerD对
//为什么可以使用静态方法获得对象,因为内部类是静态的,静态可以调用静态
public static InnerD getInnerDByStatic() {
return new InnerD();
}
- 编程题
- 有一个铃声的接口 Bell , 里面有一个 ring() 方法
- 定义一个手机类 CellPhone, 具备闹钟的方法 alarmclock() , 参数是 Bell
- 测试手机类的闹钟功能 , 通过匿名内部类作为参数打印 “起床啦 ! ! !”
- 再次传入一个匿名内部类 , 打印 “上课了 ! ! !”
public class LessonTest {
public static void main(String[] args) {
//创建手机类的对象,调用alarmclock()
//通过匿名内部类进行参数的传递
CellPhone cp = new CellPhone();
cp.alarmclock(new Bell() {
@Override
public void ring() {
System.out.println("起床啦~~~~");
}
});
cp.alarmclock(new Bell() {
@Override
public void ring() {
System.out.println("上课了!!!!");
}
});
}
}
//定义一个铃声接口
interface Bell {
void ring();
}
class CellPhone {
public void alarmclock(Bell b) {
b.ring();
}
}
多态
多态:多种形态
在Java代码中实现多态三个条件
- 在继承的状态下
- 方法的重写/覆盖 注意:不是重载
- 引用转型
继承:提高了代码的重用性和可维护性
封装:提高了代码的安全性
多态:提高了代码的扩展性
方法的重写/覆盖
如果子类继承中的方法,发现父类中的方法无法完成子类中要求的特异功能,子类就可以对父类的方法进行重写
public class Human {
private String name;
private int age;
public Human() {
}
public Human(String name, int age) {
this.name = name;
this.age = age;
}
public void work() {
System.out.println("打工人,打工魂,打工都是人上人....");
}
}
public class Teacher extends Human {
public Teacher(String name, int age) {
super(name, age);
}
//重写父类中的work方法
public void work() {
System.out.println("我是老师,我负责授课.....");
}
}
public class TestDemo07 {
public static void main(String[] args) {
//测试一下创建子类对象,调用子类重写父类的方法
Teacher teacher = new Teacher("张三",35);
//最终的结果就是,执行了子类的重写方法
teacher.work();
}
}
- 方法重写的注意事项
- 必须在继承的基础上
- 在子类重写父类方法时,必须保证方法名称完全相同,参数,返回值一致
- 返回值要么完全形同,要么是父类返回值类型的子类
- 子类重写父类方法,访问权限不要小于父类
- public > protected > 缺省 > private
重写和重载的区别
名称 | 发生的范围 | 方法名 | 参数 | 返回值 | 修饰符 |
---|---|---|---|---|---|
重载 | 本类 | 相同 | 不同(数量,类型,顺序) | 无关 | 无关 |
重写 | 继承,子类重写父类 | 相同 | 相同 | 相同,或者是父类返回值类型的子类 | 子类>=父类 |
需求案例
创建一个父类 Animal , 子类 Cat, Dog
父类中属性: name , age
父类中的方法 : eat() { sout(“宠物正在进食”) }
Cat重写 eat() : sout(“小猫爱吃鱼”)
Dog重写 eat() : sout(“小狗啃骨头”)
代码实例
/**
* 父类
*/
public class Animal {
private String name;
private int age;
//定义父类中的方法
public void eat() {
System.out.println("宠物正在进食");
}
//无参构造
public Animal() {
}
//有参数的构造
public Animal(String name, int age) {
this.name = name;
this.age = 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;
}
}
public class Cat extends Animal {
public Cat(String name, int age) {
super(name, age);
}
//子类重写父类的方法
public void eat() {
System.out.println("小猫爱吃鱼");
}
}
public class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
//子类重写父类中的eat()
public void eat() {
System.out.println("小狗啃骨头");
}
}
创建一个主人类 Master
属性 name age
喂食 feed ( Cat cat ) { }
喂食 feed ( Dog dog ) { }
/**
* 主人类
*/
public class Master {
private String name;
private int age;
//编写主人类的喂食方法,喂猫
public void feed(Cat cat) {
//通过小猫的对象调用eat方法
cat.eat();
}
//喂小狗
public void feed(Dog dog) {
dog.eat();
}
public Master(String name, int age) {
this.name = name;
this.age = 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;
}
}
public class TestDemo02 {
public static void main(String[] args) {
//创建一只小猫,创建一只小狗
Cat bai = new Cat("小白", 3);
Dog huang = new Dog("大黄", 5);
//创建主人
Master zs = new Master("张三", 30);
//通过主人对象调用喂食的方法,将要喂食的宠物对象传入方法的参数中
zs.feed(bai);//喂小猫
zs.feed(huang);//喂小狗
}
}
feed() 即可以喂小猫也可以为小狗 , eat()在Animal中输出的内容和在子类中输出的内容不一样 , 都是同一种方法 , 但是执行的代码缺不同 , **体现的事方法的多态
分析问题 :
如果我们还要继续增加宠物 , 我们就需要写更多的重载的feed方法 , 出现了代码重复的现象 , 代码的重用性降低了
解决方案 : 多态 ( 对象的多态 )**
1, 将 feed方法利用对象的多态 , 进行重新编写 2, 创建一个Animal 的子类 , Bird 重写父类eat方法 小鸟吃虫子 , 创建 Bird 对象 , 传入feed
//利用对象的多态,重新编写feed方法
//将原有的子类对象改为父类的对象
public void feed(Animal animal) {
animal.eat();
}
public class Bird extends Animal {
public Bird(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("小鸟吃虫子");
}
}
//创建一只小猫,创建一只小狗
Cat bai = new Cat("小白", 3);
Dog huang = new Dog("大黄", 5);
//创建主人
Master zs = new Master("张三", 30);
//通过主人对象调用喂食的方法,将要喂食的宠物对象传入方法的参数中
zs.feed(bai);//喂小猫
zs.feed(huang);//喂小狗
Bird bird = new Bird("小支", 2);
zs.feed(bird);//为小鸟
多态的核心(对象的多态)
实现对象的多态
//利用对象的多态创建一只小猫
//声明一个 Animal 类的引用 / 对象名
//实际上创建的是子类对象
//通过父类类型创建子类对象
//声明的时候使用父类类型,父类类型不变但是我们创建的对象一直在变
Animal hua = new Cat("小花",2);
Animal wangcai = new Dog("旺财",3);
//虽然我们通过父类的类型创建对象,但是对象本身是子类
//在调用eat()方法的时候,调用的是子类的重写父类的方法
hua.eat();
在Java中创建对象,对象的编译类型和运行类型是可以不一致
Animal hua = new Cat();
编译类型:Animal(父类)
运行类型:Cat(子类)
解释:Animal父类引用指向 Cat 子类的对象编译类型在定义的时候,就确定了,是不可以更改的
运行类型是可以更改
//使用父类类型创建子类 Bird对象
Animal bg = new bird("八哥",1);
bg = new Cat("小黑",2);
编译类型在 “=”左边,运行类型在”=”右边
多态的运用
- 需求:各国人民用餐习惯
- 创建 Person ,属性 name,age,方法 eat(){“我爱干饭”};
- 子类Chinese,继承父类 Person 重写 eat(){“我是中国人,用筷子吃饭”}
- 子类English,继承父类 Person 重写 eat(){“我是英国人,用刀叉吃饭”}
- 创建 Restaurant 餐厅类,属性 name,address, 方法 lunch()
- 当中国人用餐的时候,”我是中国人,用筷子吃饭”
- 当英国人用餐的时候,”我是英国人,用刀叉吃饭”
代码实例
- 创建 Person ,属性 name,age,方法 eat(){“我爱干饭”};
public class Person {
private String name;
private int age;
//定义父类的eat()
public void eat() {
System.out.println("我爱干饭~~~");
}
public Person(String name, int age) {
this.name = name;
this.age = 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;
}
}
public class Chinese extends Person {
public Chinese(String name, int age) {
super(name, age);
}
//重写父类的eat方法
//重写:方法名相同,参数相同,返回值相同(是父类返回值类型的子类),权限>=父类
public void eat() {
System.out.println("我是中国人,我用筷子吃饭!");
}
}
public class English extends Person {
public English(String name, int age) {
super(name, age);
}
//重写eat()
@Override
public void eat() {
System.out.println("我是英国人,我用刀叉吃饭!");
}
}
public class Restaurant {
private String name;
//定义一用午餐的方法
//操作的是用餐的人
//如果使用重载,只要多出一类人就要重载这个方法
//利用多态的特性,将参数改成父类类型,传入子类的参数
public void lunch(Person p) {// Person p = new Chinese("张三",35);
p.eat();
}
public Restaurant(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Test {
public static void main(String[] args) {
//创建Person子类的对象
Chinese zs = new Chinese("张三",35);
English tom = new English("Tom",25);
//创建餐厅的对象
Restaurant restaurant = new Restaurant("大树餐厅");
//邀请人员用餐
restaurant.lunch(zs); // new Chinese("张三",35)
restaurant.lunch(tom);
}
}
如果我们不去使用多态的特性,来编写 lunch(),name多一个人就会重载lunch 方法
- 多态的表现可以理解为子类代替了父类
- 两只之间一定要是继承关系,只有儿子可以代替自己
- 需求 : 定义图形 TuXing 类 , 方法 mianJi() 求面积 , getArea() 获得图形面积
- 子类长方形 ChangFang 属性 chang , kuan , 重写父类的 mianJi() 方法 求自己的面积
- 子类圆型 Yuan 属性 radius , 重写父类的 mianJi() 方法 求自己的面积
- 测试 , 通过图形类的对象 , 调用 getArea() , 传入的的长方形获得长方形面积 , 如果是圆形获得圆形面积
public class TuXing {
//定义父类中求面积的方法
public double mianJi() {
return 0.0;
}
//定义一个获得对应图形面积的的方法
//操作: 图形,利用对象的多态将方法的参数变为父类TuXing类
//由于我们没有必须实例化TuXing类的必要所以将方法定义成
//静态的,使用时直接通过类名调用
public static double getArea(TuXing tx) {
return tx.mianJi();
}
}
/**
* 图形类的子类
*/
public class ChangFang extends TuXing {
//私有的属性
private double chang;
private double kuan;
//定义长方形的构造方法
public ChangFang(double chang, double kuan) {
this.chang = chang;
this.kuan = kuan;
}
@Override
public double mianJi() {
return chang * kuan;
}
}
public class Yuan extends TuXing {
//定义私有属性
private double radius;
//定义构造方法
public Yuan(double radius) {
this.radius = radius;
}
//重写父类中的manJi方法
public double mianJi() {
return 3.14 * radius * radius;
}
}
public class Test {
public static void main(String[] args) {
//声明两个图形
Yuan yuan = new Yuan(10.5);
ChangFang changFang = new ChangFang(5, 6);
//通过类名称调用静态方法,传入图形对象
System.out.println("长方形面积是:" + TuXing.getArea(changFang));
System.out.println("圆形的面积是:" + TuXing.getArea(yuan));
}
}
多态的注意事项
多态中属性的调用
属性是没有重写的,属性的调用取决于编译类型(=左边)
//父类
class Father {
public String name = "张三";
}
//子类
class Son extends Father{
public String name = "张小宝";
}
public class TestDemo2 {
public static void main(String[] args){
//通过向上转型创建 Father 的引用
Father son = new Son();
System.out.println(son.name);//张三,因为编译类型是Fahter
}
}
正则表达式
是用来描述字符串内容格式,使用它通常用于匹配一个字符串的内容是否符合格式要求
正则表达式是由一个字符序列形成的搜索模式。
当你在文本中搜索数据时,你可以用搜索模式来描述你要查询的内容。
正则表达式可以是一个简单的字符,或一个更复杂的模式。
正则表达式可用于所有文本搜索和文本替换的操作。
- . - 除换行符以外的所有字符。
- ^ - 字符串开头。
- $ - 字符串结尾。
- \d,\w,\s - 匹配数字、字符、空格。
- \D,\W,\S - 匹配非数字、非字符、非空格。
- [abc] - 匹配 a、b 或 c 中的一个字母。
- [a-z] - 匹配 a 到 z 中的一个字母。
- [^abc] - 匹配除了 a、b 或 c 中的其他字母。
- aa|bb - 匹配 aa 或 bb。
- ? - 0 次或 1 次匹配。
- * - 匹配 0 次或多次。
- + - 匹配 1 次或多次。
- {n} - 匹配 n次。
- {n,} - 匹配 n次以上。
- {m,n} - 最少 m 次,最多 n 次匹配。
- (expr) - 捕获 expr 子模式,以 \1 使用它。
- (?:expr) - 忽略捕获的子模式。
- (?=expr) - 正向预查模式 expr。
- (?!expr) - 负向预查模式 expr。
一、校验数字的表达式
- 数字:^[0-9]*$
- n位的数字:^\d{n}$
- 至少n位的数字:^\d{n,}$
- m-n位的数字:^\d{m,n}$
- 零和非零开头的数字:^(0|[1-9][0-9]*)$
- 非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$
- 带1-2位小数的正数或负数:^(-)?\d+(.\d{1,2})$
- 正数、负数、和小数:^(-|+)?\d+(.\d+)?$
- 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
- 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
- 非零的正整数:^[1-9]\d$ 或 ^([1-9][0-9]){1,3}$ 或 ^+?[1-9][0-9]*$
- 非零的负整数:^-[1-9][]0-9”$ 或 ^-[1-9]\d$
- 非负整数:^\d+$ 或 ^[1-9]\d*|0$
- 非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
- 非负浮点数:^\d+(.\d+)?$ 或 ^[1-9]\d.\d|0.\d[1-9]\d|0?.0+|0$
- 非正浮点数:^((-\d+(.\d+)?)|(0+(.0+)?))$ 或 ^(-([1-9]\d.\d|0.\d[1-9]\d))|0?.0+|0$
- 正浮点数:^[1-9]\d.\d|0.\d[1-9]\d$ 或 ^(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9]))$
- 负浮点数:^-([1-9]\d.\d|0.\d[1-9]\d)$ 或 ^(-(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9])))$
- 浮点数:^(-?\d+)(.\d+)?$ 或 ^-?([1-9]\d.\d|0.\d[1-9]\d|0?.0+|0)$
校验字符的表达式
- 汉字:^[\u4e00-\u9fa5]{0,}$
- 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
- 长度为3-20的所有字符:^.{3,20}$
- 由26个英文字母组成的字符串:^[A-Za-z]+$
- 由26个大写英文字母组成的字符串:^[A-Z]+$
- 由26个小写英文字母组成的字符串:^[a-z]+$
- 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
- 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
- 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
- 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
- 可以输入含有^%&’,;=?$\”等字符:[^%&’,;=?$\x22]+
- 禁止输入含有~的字符:[^~]+
三、特殊需求表达式
- Email地址:^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$
- 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+.?
- InternetURL:[a-zA-z]+://[^\s] 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=])?$
- 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
- 电话号码(“XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX):^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$
- 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
- 电话号码正则表达式(支持手机号码,3-4位区号,7-8位直播号码,1-4位分机号): ((\d{11})|^((\d{7,8})|(\d{4}|\d{3})-(\d{7,8})|(\d{4}|\d{3})-(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1})|(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1}))$)
- 身份证号(15位、18位数字),最后一位是校验位,可能为数字或字符X:(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)
- 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
- 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
- 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在 8-10 之间):^(?=.\d)(?=.[a-z])(?=.*[A-Z])[a-zA-Z0-9]{8,10}$
- 强密码(必须包含大小写字母和数字的组合,可以使用特殊字符,长度在8-10之间):^(?=.\d)(?=.[a-z])(?=.*[A-Z]).{8,10}$
- 日期格式:^\d{4}-\d{1,2}-\d{1,2}
- 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
- 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
- 钱的输入格式:
- 有四种钱的表示形式我们可以接受:”10000.00” 和 “10,000.00”, 和没有 “分” 的 “10000” 和 “10,000”:^[1-9][0-9]*$
- 这表示任意一个不以0开头的数字,但是,这也意味着一个字符”0”不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$
- 一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$
- 这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧。下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$
- 必须说明的是,小数点后面至少应该有1位数,所以”10.”是不通过的,但是 “10” 和 “10.2” 是通过的:^[0-9]+(.[0-9]{2})?$
- 这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$
- 这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$
- 1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$
- 备注:这就是最终结果了,别忘了”+”可以用”*”替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
- xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\.[x|X][m|M][l|L]$
- 中文字符的正则表达式:[\u4e00-\u9fa5]
- 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
- 空白行的正则表达式:\n\s*\r (可以用来删除空白行)
- HTML标记的正则表达式:<(\S?)[^>]>.?|<.? /> ( 首尾空白字符的正则表达式:^\s|\s$或(^\s)|(\s$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
- 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
- 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
- IPv4地址:((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}
public static void main(String[] args) {
String a = "wangkj@tedu.cn";
String r = "[a-zA-Z0-9_]+@[a-zA-Z0-9]+(\\.[a-zA-Z]+)+ ";
System.out.println(a.matches(r));
}
public static void main(String[] args) {
//String replaceAll(String regex,String str)
//将当前字符串中满足正则表达式的部分给替换为给定的字符串
String l = "abc123def456ghi";
l = l.replaceAll("[0-9]+","#NUMBER#");
System.out.println(l);
}
public static void main(String[] args) {
//String[] split(String regex):拆分字符串
//将当前字符按照满足整则表达式的部分进行拆分,并将拆分出的以String[]形式来返回
String l = "abc123def456ghi";
String[] d = l.split("[0-9]+");
System.out.println(Arrays.toString(d));
l = "123.456.789";
d = l.split("\\.");
System.out.println(Arrays.toString(d));
l = ".123.465..789.487.....";
d = l.split("\\.");
System.out.println(Arrays.toString(d));
l = "name=zhangsan&pwd=123456&";
d = l.split(l);
System.out.println(Arrays.toString(d));
}
String支持与正则表达式相关的方法
- matchers():使用给定的正则表达式验证当前字符串的格式是否符合要求
- split():将当前字符按照满足正则达式的部分进行拆分
- replaceAll():将当前字符串满足正则表达式的部分给替换为给定的字符串
Object:对象/东西
是所有类的鼻祖,所有类都直接或间接继承了Object,万物皆对象,为了多态
包装类
File类
File类
File类的每一个实例可以表示硬盘(文件系统)中的一个文件或目录(实际上表示的是一个抽象路径)
使用File可以做到:
- 访问其表示的文件或目录的属性信息,例如:
- 名字
- 大小
- 修改时间
- 创建和删除文件或目录
- 访问一个目录中的子项
常用构造器
- File(String pathname)
- File(File parent,String name)课参考文档了解
常用方法:
length()
:返回一个long值,表示占用的磁盘空间,单位为字节。canRead()
:File表示的文件或目录是否可读canWirte()
:File表示的文件或目录是否可写isHidden()
:FIle表示的文件或目录是否为隐藏的createNewFile()
:创建一个新文件,如果指定的文件所在的目录不存在会抛弃异常异常java.io.FileNotFountException
mkdir
:创建一个目录mkdirs
:创建一个目录,并且会将所有不存在的父目录一同创建出来,推荐使用delete()
:删除File表示的文件或目录是否真是存在,true
:存在false
:不存在
isFile()
:判断当前File表示的是否为一个文件isDirectory()
判断当前File表示的是否为一个目录listFiles()
:获取File表示目录中的所有子项listFiles(FileFilter Filter)
:获取File表示的目录中满足filter过滤器要求的所有子项
返回值 方法名 | 作用 |
---|---|
boolean exists() |
判断文件是否存在,存在返回true,反之返回false |
boolean isFile() | 判断 File 对象是否是文件,是返回true,反之返回false |
boolean isDirectory() | 判断 File 对象是否是目录,是返回true,反之返回false |
String getName() | 获得文件名字 |
long length() | 获得文件的长度(字节数) |
boolean createNewFile() throws IOException | 创建新的文件,创建成功返回true,否则返回false,有可能抛出 IOExcepation,必须捕捉 |
boolean delete() | 删除文件,删除成功返回 true,否则返回false |
File[] listFile() | 返回子文件和子文件夹的对象数组 |
public class TestDemo01 {
public static void main(String[] args) {
//通过 File 类获得D盘下的 abc.txt 文件
//String pathname 是文件路径
//在Java 中给路径分级可以使用 \\ 或者 /,注意不要用一个\
File file = new File("D:/abc.txt");
System.out.println(file);
//通过 exists() 方法判断文件是否存在
System.out.println("abc.txt是否存在:" + file.exists());
//通过 isFile() 方法判断是否为文件
System.out.println("abc.txt是否是文件:" + file.isFile());
//通过 isDirectory() 方法判断是否为目录
System.out.println("abc.txt是否是目录:" + file.isDirectory());
//通过 getName() 方法获得文件名字: 文件名+后缀
System.out.println(file.getName());
//通过length获得文件的大小(字节数)
System.out.println("abc.txt文件大小为:" + file.length() + "字节");
//使用不存在的文件创建file对象
File hello = new File("D:/hello.txt");
//通过 createNewFile() 创建文件
//返回一个布尔值,创建成功true, 反之为false
if (hello.exists() == false) {//代表文件不存在,需要创建
try {
boolean newFile = hello.createNewFile();
System.out.println("hello.txt是否创建成功:" + newFile);
} catch (IOException e) {
e.printStackTrace();
}
} else {//文件存在无需创建
System.out.println("hello.txt已经存在了,不需要创建了");
}
//在D盘下的创建iodemo文件夹,操作File类在文件夹之下创建xyz.txt文件
File xyz = new File("D:/iodemo/xyz.txt");
try {
boolean newFile = xyz.createNewFile();
System.out.println("xyz.txt是否创建成功:" + newFile);
} catch (IOException e) {
e.printStackTrace();
}
//通过 delete 方法删除一创建的 xyz.txt 文件
boolean delete = xyz.delete();
System.out.println("文件是否成功删除:" + delete);
//获得iodemo文件夹的对象
File iodemo = new File("D:/iodemo");
//通过 listFiles() 获得iodemo文件夹中的所有的文件
File[] files = iodemo.listFiles();
for (File f : files) {
System.out.println(f.getName());
}
}
}
public class TestDemo02 {
public static void main(String[] args) {
//相对路径
//相对于整个项目而言
//src/abc.txt
//在本项目中的src文件夹下创建abc.txt文件
File file = new File("src/abc.txt");
System.out.println(file);
System.out.println("abc.txt是否存在:" + file.exists());
try {
boolean newFile = file.createNewFile();
//创建成功,直接创建在项目路径中,和src平级
System.out.println("abc.txt是否创建成功" + newFile);
} catch (IOException e) {
e.printStackTrace();
}
//通过getAbsolutePath获得src文件夹下的abc.txt的绝对路径
String absolutePath = file.getAbsolutePath();
System.out.println("src文件夹下的abc.txt的绝对路径:" + absolutePath);
//通过getAbsoluteFile获得绝对路径下的file对象
File absoluteFile = file.getAbsoluteFile();
System.out.println(absoluteFile);
}
}
注意
File 类实例化的对象,可以表示文件,也可以表示目录 注意:File 类的对象不能对文件进行读写的操作,只能获得文件的属性
public class FileDemo {
public static void main(String[] args) {
//使用File访问当前项目目录下的demo.txt文件
/*
创建File时要指定路径,而路径通常使用相对路径。
相对路径的好处在于有良好的跨平台性。
"./"是相对路径中使用最多的,表示"当前目录",而当前目录是哪里
取决于程序运行环境而定,在idea中运行java程序时,这里指定的
当前目录就是当前程序所在的项目目录。
*/
// File file = new File("c:/xxx/xxx/xx/xxx.txt");
File file = new File("./demo.txt");
//获取名字
String name = file.getName();
System.out.println(name);
//获取文件大小(单位是字节)
long len = file.length();
System.out.println(len+"字节");
//是否可读可写
boolean cr = file.canRead();
boolean cw = file.canWrite();
System.out.println("是否可读:"+cr);
System.out.println("是否可写:"+cw);
//是否隐藏
boolean ih = file.isHidden();
System.out.println("是否隐藏:"+ih);
}
}
package file;
import java.io.File;
import java.io.IOException;
/**
* 使用File创建一个新文件
*/
public class CreateNewFileDemo {
public static void main(String[] args) throws IOException {
//在当前目录下新建一个文件:test.txt
File file = new File("./test.txt");
//boolean exists()判断当前File表示的位置是否已经实际存在该文件或目录
if(file.exists()){
System.out.println("该文件已存在!");
}else{
file.createNewFile();//将File表示的文件创建出来
System.out.println("文件已创建!");
}
}
}
package file;
import java.io.File;
/**
* 使用File删除一个文件
*/
public class DeleteFileDemo {
public static void main(String[] args) {
//将当前目录下的test.txt文件删除
/*
相对路径中"./"可以忽略不写,默认就是从当前目录开始的。
*/
File file = new File("test.txt");
if(file.exists()){
file.delete();
System.out.println("文件已删除!");
}else{
System.out.println("文件不存在!");
}
}
}
package file;
import java.io.File;
//mkDir():创建当前File表示的目录
//mkDirs():创建当前File表示的目录,同时将所有不存在的父目录一同创建
/**
* 使用File创建目录
*/
public class MkDirDemo {
public static void main(String[] args) {
//在当前目录下新建一个目录:demo
// File dir = new File("demo");
File dir = new File("./a/b/c/d/e/f");
if(dir.exists()){
System.out.println("该目录已存在!");
}else{
// dir.mkdir();//创建目录时要求所在的目录必须存在
dir.mkdirs();//创建目录时会将路径上所有不存在的目录一同创建
System.out.println("目录已创建!");
}
}
}
package file;
import java.io.File;
/**
* 删除一个目录
*/
public class DeleteDirDemo {
public static void main(String[] args) {
//将当前目录下的demo目录删除
File dir = new File("demo");
// File dir = new File("a");
if(dir.exists()){
dir.delete();//delete方法删除目录时只能删除空目录
System.out.println("目录已删除!");
}else{
System.out.println("目录不存在!");
}
}
}
package file;
import java.io.File;
/**
* 访问一个目录中的所有子项
*/
public class ListFilesDemo1 {
public static void main(String[] args) {
//获取当前目录中的所有子项
File dir = new File(".");
/*
boolean isFile()
判断当前File表示的是否为一个文件
boolean isDirectory()
判断当前File表示的是否为一个目录
*/
if(dir.isDirectory()){
/*
File[] listFiles()
将当前目录中的所有子项返回。返回的数组中每个File实例表示其中的一个子项
*/
File[] subs = dir.listFiles();
System.out.println("当前目录包含"+subs.length+"个子项");
for(int i=0;i<subs.length;i++){
File sub = subs[i];
System.out.println(sub.getName());
}
}
}
}
package file;
import java.io.File;
import java.io.FileFilter;
/**
* 重载的listFiles方法,允许我们传入一个文件过滤器从而可以有条件的获取一个目录
* 中的子项。
*/
public class ListFilesDemo2 {
public static void main(String[] args) {
/*
需求:获取当前目录中所有名字以"."开始的子项
*/
File dir = new File(".");
if(dir.isDirectory()){
// FileFilter filter = new FileFilter(){//匿名内部类创建过滤器
// public boolean accept(File file) {
// String name = file.getName();
// boolean starts = name.startsWith(".");//名字是否以"."开始
// System.out.println("过滤器过滤:"+name+",是否符合要求:"+starts);
// return starts;
// }
// };
// File[] subs = dir.listFiles(filter);//方法内部会调用accept方法
File[] subs = dir.listFiles(new FileFilter(){
public boolean accept(File file) {
return file.getName().startsWith(".");
}
});
System.out.println(subs.length);
}
}
}
Stream 流
流是一个输入输出管道,根据管道的 方向 不同,分为输入流和输出流
- 输入流
- 把数据读取到程序中,称之为输入,即 input ,进行数据的 read 操作
- 读文件
- ,
- 输出流
- 从程序中往文件中写数据,称之为输出,即 ,进行数据的 操作
- 写文件
- ,
以上四个类是 关于 的抽象类
根据数据的传输格式不同,分为字节流和字符流,两者都可以对文件进行读写
- 字节流
- 指的是 8 位的通用字节流,读写效率很高,可以传输任意类型的文件,图片,视频……
- 字符流
- 指的是 16 位的通用字符流,读写效率低,主要是针对文字内容
FileInputStream
字节流读文件的方式,字节输入流
字节流在读文件的时候有可能会乱码
- 构造方法,通过 File 对象为构造参数,获得字节输入流对象
参数的 File 文件对象,就要你读取的文件
- 读文件的方法
读取的数据作为返回值,当我们读取到文件的末尾的时候返回-1
代码实例
import java.io.*;
public class test{
public static void mian(String[] args) throws IOException{
//创建src 路径下的abc.txt文件的对象
File f = new File("src/abc.txt");
//创建字节流输入对象
FileinputStream fis = new FileInputStram(f);
//通过循环读取问津的每一个字符
//循环的次数就是文件的长度:length()
for(long i = 0; i < f.length(); i++){
System.out.print(char)fis.read());
}
//非常重要
fis.close();
}
}
FileOutputStram
字节流写文件,字节输入流
- file
- 对应着你要操作的文件
- append
- 是否是向文件内容的末尾添加,代表是添加,代表覆盖
写文件的方法
代码实例
public class TestDemo{
public static void mian(String[] args) throws IOException{
//获得项目中的iotst.txt文件
File f = new File("iotst.txt");
/*
通过File创建字节输出流
FileOutputStream(File args1,boolean args2)
arg1:操作的文件
arg2:是否向问津的内容后追加
true:追加
false:不追加
*/
FileOutputStream fos = new FileOutputStream(f,true);
//定义写入的文件
String str = "Hello,hava a good day!!";
//一个一个写入,通过一个方法根据下标找字符charAt()
//1.遍历字符串
for(int i = 0; i < str.length(); i++){
char c = str.charAt(i);
//write()参数是int类型的所以字符的数据是可以直接使用的
fos.write(c);
}
fos.close();
}
}
通过字节数组写入内容
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;
public class Test {
public static void main(String[] args) throws IOException {
//获得项目中的abc.txt文件
File f = new File("abc.txt");
/*
通过File创建字节流输入
FileOutputStream(File arg1, boolean arg2)
arg1:操作的文件
arg2:是否向文件的内容后追加
true:追加
false:覆盖原有内容
*/
FileOutputStream fos = new FileOutputStream(f,true);
Scanner sc = new Scanner(System.in);
System.out.println("请输入您要输入的内容");
byte[] bytes = sc.next().getBytes();
/*
write(byte[] bytes)
write(byte[] bytes ,int off ,int len)
off:从何处开始,0
len:长度,代表写几个
*/
fos.write(bytes,1,bytes.length-1);
fos.close();
}
}
复制文件
package io;
import java.io.*;
import java.util.Scanner;
/**
* 复制文件案例
*/
public class Test2Demo {
public static void main(String[] args) throws IOException {
copFile("aaa.txt","bbb.txt");
}
/**
* 定义一个复制文件的方法
* 有两个参数
* @param srcPath 资源摁键
* @param tarPath 目标文件
*/
public static void copFile(String srcPath,String tarPath) throws IOException {
//定义两个文件的对象
File src = new File(srcPath);
File tar = new File(tarPath);
if (!tar.exists()){
tar.createNewFile();
}
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(tar);
for (long i = 0; i < src.length(); i++) {
char c = (char)fis.read();
fos.write(c);
}
System.out.println("文件复制成功!!!");
//关闭资源(准则:先关后开,后关先开)
fos.close();
fis.close();
}
}
通过字节数组获得读取的内容,再将字节数组写入目标文件
中的 重载方法
将去读到的内容写入字节数组中,返回字节数组的元素个数
如果读取到文件的结尾会返回-1
package io;
import java.io.*;
import java.util.Scanner;
/**
* 复制文件案例
*/
public class Test2Demo {
public static void main(String[] args) throws IOException {
copFile("aaa.txt","bbb.txt");
}
/**
* 定义一个复制文件的方法
* 有两个参数
* @param srcPath 资源摁键
* @param tarPath 目标文件
*/
public static void copFile(String srcPath,String tarPath) throws IOException {
//定义两个文件的对象
File src = new File(srcPath);
File tar = new File(tarPath);
if (!tar.exists()){
tar.createNewFile();
}
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(tar);
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1){
fos.write(bytes);
}
System.out.println("文件复制成功!!!");
//关闭资源(准则:先关后开,后关先开)
fos.close();
fis.close();
}
}
字符输入流
:字符流读文件
构造方法:
创建一个新的 ,给出 读取
代码实例
public static void main(String[] args) throws IOException {
File f = new File("abc.txt");
FileReader reader = new FileReader(f);
for (long i = 0; i < f.length(); i++) {
System.out.println((char)reader.read());
}
reader.close();
}
字符输出流
FileWriter
:字符流写文件
- 构造
FileWriter(File file)
- 给一个 File 对象构造一个 FileWriter 对象
- 给一个File对象构造一个FileWriter对象
- 写文件的方法
- void
- 写入字符数组的一部分
- void
- 写一个字符
- void
- 写一个字符串一部分
- void
写入内容的方式A:通过字符写入
public class TestDemo06 {
public static void main(String[] args) throws IOException {
//创建本项目中的bbb.txt文件File对象
File file = new File("bbb.txt");
//通过File对象创建字符输出流
FileWriter writer = new FileWriter(file,true);
//通过write()写入内容
String str = "Hello, How are u ? ?";
for (int i = 0; i < str.length(); i++) {
//通过charAt()将字符串中的所有字符取出来
writer.write(str.charAt(i));
}
writer.close();
}
}
写入内容的方式B:通过字符数组写入
public class TestDemo06 {
public static void main(String[] args) throws IOException {
//创建本项目中的bbb.txt文件File对象
File file = new File("bbb.txt");
//通过File对象创建字符输出流
FileWriter writer = new FileWriter(file,true);
//通过write()写入内容
String str = "Hello, How are u ? ?";
//通过字符数组写入
//将字符串转换为字符数组
char[] chars = str.toCharArray();
//writer.write(chars);
//输出str 中的 How are u ? ?
writer.write(chars,7,chars.length - 7);
writer.close();
}
}
写入内容的方式C:通过字符串写入
public class TestDemo06 {
public static void main(String[] args) throws IOException {
//创建本项目中的bbb.txt文件File对象
File file = new File("bbb.txt");
//通过File对象创建字符输出流
FileWriter writer = new FileWriter(file,true);
//通过write()写入内容
String str = "Hello, How are u ? ?";
//通过字符串写入
//writer.write(str);
//写入str中的How are u ? ?
writer.write(str,str.lastIndexOf('H'),str.length() - str.lastIndexOf('H'));
writer.close();
}
}
复制文件
使用方法:
代码实例
复制 z001 文件夹中的内容 , 到 D 盘下的 z002 文件夹
1, 需要两个文件夹的 File 对象
2, 判断目标文件夹是否存在 , 如果不存在需要创建目录
3, 获得资源文件夹中的所有的子文件对象 ( listFiles() )
4, 将资源文件的子文件复制到 z002 文件夹之下
复制 : 参考复制文件的方法
/**
* 复制文件夹中的内容
*/
public class TestDemo07 {
public static void main(String[] args) throws IOException {
//定义两个文件夹File对象
File srcFolder = new File("D:/z001");
File tarFolder = new File("D:/z002");
copyFolder(srcFolder,tarFolder);
}
//定义一个方法赋值文件夹
public static void copyFolder(File src, File tar) throws IOException {
//判断目标文件夹是否存在
if (!tar.exists()) {
tar.mkdir();
}
//获取资源文件夹中的所有的子文件
File[] files = src.listFiles();
//遍历所有的子文件
for (File file : files) {
//获得所有子文件的名字(带后缀的)
String srcName = file.getName();
//创建目标文件夹中的子文件
//tar:父级路径
//srcName:子文件名字
File tarFile = new File(tar,srcName);
copyFile2(file,tarFile);
}
}
//复制文件
public static void copyFile2(File srcFile, File tarFile) throws IOException {
//通过资源文件srcFile创建字节输入流对象
FileInputStream fis = new FileInputStream(srcFile);
//通过资源文件tarFile创建字节输出流对象
FileOutputStream fos = new FileOutputStream(tarFile);
//定义一个字节数组的容器
byte[] bys = new byte[1024];
//通过循环读取资源文件的内容,写入目标文件
int len;
while((len = fis.read(bys)) != -1) {
fos.write(bys,0,len);
}
//关闭资源
fos.close();
fis.close();
}
}
缓冲流
字符输出缓冲流
代码实例
package io;
import java.io.*;
import java.util.Scanner;
/**
* 复制文件案例
*/
public class Test2Demo {
public static void main(String[] args) throws IOException {
//创建abc.txt文件的 File 对象
File f = new File("abc.txt");
//创建字符输出流
FileWriter writer = new FileWriter(f);
//通过字符串输出流的对象创建字符输出缓冲流的对象
BufferedWriter bw = new BufferedWriter(writer);
//通过 write 写入文件
//通过字符写入
char[] chars = "Output By char".toCharArray();
for (int i = 0; i < chars.length; i++) {
bw.write(chars[i]);
}
//换行
bw.newLine();
//直接通过字符数组写入
char[] chars1 = "Output by charArray".toCharArray();
bw.write(chars);
//换行
bw.newLine();
//通过字符串直接写入
bw.write("Output by String");
//关闭资源
bw.close();
writer.close();
}
}
注意:在流的操作结束之后,使用close() 方法进行关闭,如果不关闭,数据会停留在缓冲区,无法写入文件,但是在不关闭的情况下可以使用 flush()方法刷新缓冲区的操作
void flush()
刷新流
字符输入缓冲流
构造方法:
创建使用默认大小的输入缓冲区的缓冲字符输入流
读文件的方法:
返回类型:String
readLine():读一行文字
public class TestDemo09 {
public static void main(String[] args) throws IOException {
//创建aaa.txt文件的File对象
File file = new File("aaa.txt");
//通过File对象创建字符输入流对象
FileReader fr = new FileReader(file);
//通过字符输入流对象创建字符输入缓冲流
BufferedReader br = new BufferedReader(fr);
//定义一个 "" 字符串
String str = "";
//readLine():没有读到数据的时候返回一个null
while((str = br.readLine()) != null) {
System.out.println(str);
}
//关闭资源
br.close();
fr.close();
}
}
字节输入缓冲流
构造方法:创建一个 BufferedInputSteam 并保存其参数,输入流 in ,供以后使用
代码实例
public class TestDemo10 {
public static void main(String[] args) throws IOException {
//通过字节输入流对象创建字节缓冲输入流
BufferedInputStream br = new BufferedInputStream(new FileInputStream("aaa.txt"));
//一次读取一个字节
int len;
while((len = br.read()) != -1) {
System.out.print((char)len);
}
br.close();
}
}
一次读取一个字节数组
public class TestDemo10 {
public static void main(String[] args) throws IOException {
//通过字节输入流对象创建字节缓冲输入流
BufferedInputStream br = new BufferedInputStream(new FileInputStream("aaa.txt"));
//一次读取一个字节数组
byte[] bys = new byte[10000];
int len;
while((len = br.read(bys)) != -1) {
System.out.print(new String(bys,0,len));
}
br.close();
}
}
字节输出缓冲流
常用方法
flush()
刷新缓冲输出流- 返回类型
void
- 返回类型
write(byte[] b, int off, int len)
从指定的字节数组写入len
个自己,从偏移off
开始到缓冲的输出流- 返回类型
void
- 返回类型
write(int b)
将指定的字节写入缓冲的输出流- 返回类型
void
代码实例
public class TestDemo01 {
public static void main(String[] args) throws IOException {
//通过字符串形式的路径创建字节输出流
//通过字节输出创建字节缓冲流
BufferedOutputStream bos = new BufferedOutputStream
(new FileOutputStream("D:/aaa.txt", true));
//通过字节写入内容
String str = "\r\nOutput By byte~~\r\n";
for (int i = 0; i < str.length(); i++) {
bos.write(str.charAt(i));
}
//通过字节数组写入内容
//write(byte[] arr, int off, int len)
//off:从何开始
//len:输出的个数
byte[] bytes = "Output by byte array\r\n".getBytes();
bos.write(bytes);
bos.close();
}
}
- 返回类型
Lambda表达式
jdk8之后,Java支持了lambda表达式这个特性lambda
可以用更精简的代码创建匿名内部类,但是该匿名内部类是宪法的接口只能有一个抽象方法,否则无法使用!lambda
表达式时编译器认可的,最终会将其改为内部类编译到class文件中
package lambda;
import java.io.File;
import java.io.FileFilter;
/**
* JDK8之后java支持了lambda表达式这个特性
* lambda表达式可以用更精简的语法创建匿名内部类,但是实现的接口只能有一个抽象
* 方法,否则无法使用。
* lambda表达式是编译器认可的,最终会被改为内部类形式编译到class文件中。
*
* 语法:
* (参数列表)->{
* 方法体
* }
*/
public class LambdaDemo {
public static void main(String[] args) {
//匿名内部类形式创建FileFilter
FileFilter filter = new FileFilter() {
public boolean accept(File file) {
return file.getName().startsWith(".");
}
};
FileFilter filter2 = (File file)->{
return file.getName().startsWith(".");
};
//lambda表达式中参数的类型可以忽略不写
FileFilter filter3 = (file)->{
return file.getName().startsWith(".");
};
/*
lambda表达式方法体中若只有一句代码,则{}可以省略
如果这句话有return关键字,那么return也要一并省略!
*/
FileFilter filter4 = (file)->file.getName().startsWith(".");
}
}
函数式接口
函数式接口是Java 8 Lambda
最关键的点
- 函数式接口的定义
- 任何一个接口,如果包含唯一一个抽象方法,那么它就是一个函数式接口
比如Runnable
- 对于函数式接口,我们就可以通过Lambda表达式创建接口对象
代码实例
public class LambdaDemo03 {
public static void main(String[] args) {
//1,通过匿名内部类
IA tiger = new IA() {
@Override
public void eat() {
System.out.println("我是一只老虎,爱吃肉");
}
};
tiger.eat();
//2,使用Lambda
// () : 表示的的是 eat方法,因为IA接口中只有一个eat()
// -> : 指向对应的语句,语句就是eat() 中的方法体
//通过Lambda直接将匿名内部类省略的
IA cat = () -> {
System.out.println("我是一只小猫,爱吃鱼");
};
cat.eat();
}
}
//定义函数式接口
//函数式接口:只有一个抽象方法的借口
interface IA {
void eat();
}
代码实例:带参数的
定义函数式接口
//定义一个函数式接口
interface IB {
void info(String name);
}
测试
public class LambdaDemo04 {
public static void main(String[] args) {
//通过Lambda表达式定义匿名内部类,重写info方法
//注意:不是执行,是重写
IB ib = (String name) -> {
System.out.println("我叫" + name);
};
ib.info("张三");//执行
}
}
关于Lambda 表达式的简化操作
//简化1:省去参数数据类型
IB ib2 = (name) -> {
System.out.println("我叫:" + name);
};
ib2.info("王五");
//简化2:省去()
IB ib3 = name -> {
System.out.println("我叫:" + name);
};
ib3.info("找六");
//简化3:省去 {}
IB ib4 = name -> System.out.println("我叫:" + name);
ib4.info("奥利给!");
注意点:
- Lambda表达式只能在有一个语句的情况下进行简化的操作
- 如果有多行代码,必须使用{}将多个语句包括起来
- 格式:参数->{}
- 使用Lamdba的前提条件是,接口一定要说函数式接口,一个接口中只有一个抽象方法
- 多个参数也可以简化数据类型,但式参数一定要加上()
class LambdaTest {
public static void main(String[] args) {
//参数的()不可以省略
IC ic = (name,age) -> {
System.out.println("我叫" + name + ",今年" + age);
};
ic.show("张三",25);
}
}
interface IC {
void show(String name, int age);
}
多线程
线程介绍
- 任务:正在执行的工作,可以多个同时进行
- 比如说边吃饭,边敲代码
- 看似是两个任务同时在进行,但是实际上是某一个时刻做一件事,只不过时间过得太快了感觉像是在做两件事
- 进程:正在进行的程序
- 比如:QQ,微信,idea,LOL
- 线程:一个进程包含着多个线程,线程是由 CPU 进行调度,一个进程必须包含一个线程,不然没有意义
- 比如写的
Java
程序,必须要有一个主线程在跑
- 比如写的
- 多线程:一个进程中有多个线程在执行
- 使用微信和很多个人在聊天
线程核心概念
- 线程是以一个独立的路径
- 在程序运行的时候,即使自己不去创建线程,后台也会有多个线程在跑
- 主线程
- gc线程
mian()
就是 主线程,因为main()
是所有程序的入口- 在进程中开辟多个线程,线程是由 CPU 来进行调度的,顺序不是可以认为改变的。
- 备注:(看 CPU 心情)
- 对一份系统资源进行操作的时候,多个线程去抢夺一个资源,会存在资源抢夺的问题,需要加入并发控制
- 比如春节抢票,100个人抢10张票,会有90个人抢不到,但是我们从数据的角度如果都抢到了,票数最终会变成-90张
线程的创建
- 创建
Thread
子类 - 实现
Runnable
接口,最常用的
Thread 类
Thread
类也实现了 Runnable
接口
通过继承
Thread
类来创建线程 步骤(三步): 第一步:自定义线程类,继承Thread
第二步:重写一个方法,run()
,run()
中的代码就是线程体 第三步:创建线程的对象,调用start()
方法启动线程
代码实例
public class ThreadDemo01 {
public static void main(String[] args) {
//3,创建线程的对象,调用start()启动线程
MyThread mt = new MyThread();
mt.start();//多线程的运行
//在执行自定义线程的同时,主线程也在执行
//主线程就是main(),main()中的代码就是主线程的线程体
for (int i = 0; i < 10000; i++) {
System.out.println("main线程执行第" + i + "次");
}
}
}
//自定义线程类
//1,继承 Thread
class MyThread extends Thread {
//2,重写run方法
@Override
public void run() {
//3,编写线程体
for (int i = 0; i < 10000; i++) {
System.out.println("run()执行第" + i + "次");
}
}
}
普通方法调用和多线程调用的区别
先比较之下第一种的效率低 , 第二种效率高
线程不一定就立即执行 , 看 CPU 的调度
Runnable 接口
步骤
- 第一步:定义一个实现类,实现
Runnable
接口 - 第二步:重写
run()
方法,编写线程体 - 第三步:通过线程类的对象调用
start
方法 ,开启线程
实现类
/**
* 创建Runnable接口的实现类
* 重写run方法
*/
class TestRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("run()执行第" + i + "次");
}
}
}
测试
public class ThreadDemo02 {
public static void main(String[] args) {
/*
执行线程,将Runnable接口的实现对象放入Thread类的构造方法中
通过Thread类的对象调用 start() 方法开启线程
*/
//创建实现类的对象
TestRunnable tr = new TestRunnable();
//创建Thread类的对象,将tr对象传入构造方法中
Thread thread = new Thread(tr);
thread.start();
//主线程
for (int i = 0; i < 10000; i++) {
System.out.println("主线程中执行第" + i + "次");
}
}
}
重要推荐使用 Runnable
接口来实现:避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用
抢票的案例
Thread.sleep()
:延时,休眠Thread.currentThread()
:获得当前线程的对象getName()
:获得线程的名字
public class ThreadDemo{
public static void main(String[] args) {
//打印主线程的对象
//Thread[main,5,main]
//数字5代表着线程的优先级
Thread thread = Thread.currentThread();
System.out.println(thread);
//getName():获得线程的名字
System.out.println(thread.getName());
}
}
sleep方法
class ThreadA extends Thread {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(i);
//休眠一次
//参数是毫秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试
//创建自定义线程类对象,执行start(),开启线程
new ThreadA().start();
抢票代码
线程类
//抢票
class TicketTest implements Runnable {
//定义票数10张
//多个线程对象操作一个资源,资源要是多个对象可以共享
private static int tickets = 10;
@Override
public void run() {//抢票的代码
//循环不停的抢票,while
while(true) {
//如果没有票了循环停止
if(tickets <= 0) {
break;
}
//休眠1秒钟.alt+回车快速的try-catch
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印线程的名字,拿到了第几张票
System.out.println(Thread.currentThread().getName() + "拿到了第" + tickets + "票");
//票数 -1
tickets --;
}
}
}
测试
public class TestDemo01 {
public static void main(String[] args) {
//创建实现类的对象
TicketTest tt = new TicketTest();
//通过 Thead 对象开启线程
//两个参数的构造
//Thread(参数1:执行的线程, 参数2:线程的名字);
new Thread(tt,"张三").start();
new Thread(tt,"李四").start();
new Thread(tt,"王五").start();
}
}
问题:
多个线程操作同一个资源的时候,线程不安全,线程文论,数据异常
三个人同时抢票,票数会减去3张,但是每个线程都认为自己拿到的事第一张票,所以会先出票数相同的情况,这种状况我们称之为线程不安全
案例:龟兔赛跑
- 首先判断赛道的距离
- 判断比赛是否结束
- 打印出胜出者
- 乌龟一直在跑,兔子会睡觉
-
线程类
```java //定义一个线程类 class GuiTu implements Runnable { //1,获胜者 private static String name;
//2,判断比赛是否结束 //条件路程跑了多少米,通过方法来实现判断 public boolean over(int mi) {//是否结束;结束true,没结束false
//判断是否存在获胜者
if (name != null) {
return true;
}
//如果步数跑到了100m位置
if(mi == 100) {
//打印跑到100的线程对象的名字
//并且为获胜者赋值
name = Thread.currentThread().getName();
System.out.println("获胜者: " + name);
return true;
}
return false;
}
@Override
public void run() {
//循环100次
for (int i = 1; i <= 100; i++) {
//模拟兔子睡觉,跑10m,睡10毫秒
if("兔子".equals(Thread.currentThread().getName()) && i % 10 == 0) {
System.out.println("兔子在跑到第" + i + "步的时候睡着了");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boolean flag = over(i);
System.out.println(Thread.currentThread().getName() + "跑了" + i + "米");
if(flag) {
break;
}
}
}
}
<a name="UJpwV"></a>
#### 测试
```java
public class ThreadDemo02 {
public static void main(String[] args) {
GuiTu gt = new GuiTu();
new Thread(gt,"乌龟").start();
new Thread(gt,"兔子").start();
}
}
线程的状态
- 创建状态
new
的时候,线程一但被创建就进入了创建状态,new Thread
的时候
- 就绪状态
- 调用
start()
的时候,线程就进入了状态,并不意味着线程立刻执行,而是等待CPU的调度
- 调用
- 运行状态
- 线程在运行的时候的状态,代表着线程正在执行线程体
- 阻塞状态
- 当线程使用
sleep()
,wait()
的时候,或者synchronized(同步锁)
,线程才会进入阻塞状态- 所谓阻塞状态,就是线程不在往下执行了,这个状态结束后,进入就绪状态,等待CPU调度
- 当线程使用
- 死亡状态
- 线程中断,一但线程死亡了,就不会在被启动了(CPU调度)
| 方法 | |
| —- | —- |
|
setPriority(int newPriority)
| 改线程的优先级 | |static void sleep(long mills)
| 在指定毫秒数中让线程休眠 | |void join()
| 等待线程停止 | |static void yeild()
| 暂停当前线程正在执行的对象,转换为就绪状态 | |boolean isAlive()
| 判断线程是否处于活动状态 | |void interrupt()
| 中断线程 |
- 线程中断,一但线程死亡了,就不会在被启动了(CPU调度)
| 方法 | |
| —- | —- |
|
停止线程
stop()
已弃用,destory()
已弃用- 官方推荐让线程自己停下来
使用一个变量控制,比如
flag = false
的时候,就终止线程public class StopDemo implements Runnable {
//1,定义一个标记位
//停止线程的
private boolean flag = true;
@Override
public void run() {
int i = 0;
while(flag) {
System.out.println("线程正在执行第" + i + "次");
i++;
}
}
//设置一个自定义的停止方法,将标记修改成 false,是线程中断
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
StopDemo sd = new StopDemo();
Thread thread = new Thread(sd);
thread.start();
//主线程开始循环
for (int i = 0; i < 10000; i++) {
System.out.println("main线程执行第" + i + "次");
//当主线程在执行第800次的时候,自定义线程停掉
if(i == 800) {
//thread.stop();
sd.stop();//使用自定义的停止方法
System.out.println("自定义线程停止了");
}
}
}
}
线程休眠sleep
sleep
(时间)指定线程阻塞的时间的毫秒值:1000毫秒 = 1秒sleep
存在异常InterruptedException
sleep
时间达到之后 线程进入就绪状态sleep
可以模拟网络的延迟(CPU执行速度太快我们观察问题所在),我们可以通过sleep
方法问题的发生性- 倒计时
用户输入数字,程序根据用户输入的数字,进行倒计时,到0为止,一秒输出一次倒计时的数字
public class SleepDemo {
//定义一个方法用于倒计时
//参数: 指定倒计时的时间
public static void timeDown(int count) throws InterruptedException {
while(true) {
//休眠一秒钟
Thread.sleep(1000);
System.out.println(count--);
if(count == 0) {
System.out.println("倒计时结束~~");
break;
}
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入倒计时时间:");
try {
timeDown(sc.nextInt());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
每一个线程的对象都有一个锁,sleep方法执行的时候不会释放锁的
线程礼让yield
- 礼让就是让现有线程暂停,但是不会阻塞
- 将线程从运行状态转为就绪状态,等待CPU的调度
- 让CPU重新调度,礼让不一定成功,看系统的调度
方法yield()
public class YieldDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
new Thread(mt,"A").start();
new Thread(mt,"B").start();
}
}
//定义一个线程类
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
Thread.yield();//线程礼让,转换为就绪的状态
System.out.println(Thread.currentThread().getName() + "线程停止执行");
}
}
线程插队join
join
合并线程,等待此线程执行完毕之后,再去执行其他的线程,其他的线程进入阻塞状态-
代码示例
public class JoinDemo implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "插队进来第" + i + "次");
}
}
public static void main(String[] args) throws InterruptedException {
JoinDemo jd = new JoinDemo();
Thread th = new Thread(jd,"A");
th.start();
//主线程
for (int i = 1; i <= 50; i++) {
System.out.println("主线程执行第" + i + "次");
//当主线程执行30次的时候,让th插队进来
//效果: 主线程会等待子线程执行完毕在继续执行
if (i == 30) {
th.join();
}
}
}
}
线程状态
Thread.State(枚举)
- 线程的状态分为一下几种
NEW
- 尚未启动线程
RUNNABLE
- java虚拟机正在执行这个线程
BLOCKED
- 被阻塞的状态
WATTING
- 正在等待另一个线程执行完毕
TIMED_WATTING
- 正在等待休眠时间结束
TERMINATED
- 线程停止了
- 线程的状态分为一下几种
通过线程的对象调用
getState()
方法获得当前线程的状态线程类
//定义一个线程类
class ThreadC implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(i);
//让线程休眠1秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试
public class StateDemo {
public static void main(String[] args) throws InterruptedException {
ThreadC tc = new ThreadC();
Thread thread = new Thread(tc);
//测试线程在创建之初的状态
Thread.State state = thread.getState();
System.out.println("线程未开启时的状态:" + state);
//开启线程之后的状态
thread.start();
state = thread.getState();
System.out.println("线程启动后的状态:" + state);
//只要我们的子线程不停止,就循环主线程
while(state != Thread.State.TERMINATED) {//TERMINATED线程结束
Thread.sleep(2000);
state = thread.getState();
System.out.println(state);
}
}
}
线程的优先级
java会提供一个监视器(线程的调度器),用于监控所有进入就绪状态的线程的
- 线程调度器会根据线程的优先级决定应该先调度那个线程
- 优先级范围
1~10
Thread.NORM_PRIORITY = 5;
默认Thread.MAX_PRIORITY = 10;
最高级别Thread.MIN_PRIORITY = 1;
最低级别
- 使用一下两种方法
getPriority()
setPriority(等级)
代码实例
public class PriorityDemo {
public static void main(String[] args) {
//生成3个线程的对象
// t1, t2, t3,线程名
TestThread tt = new TestThread();
Thread t1 = new Thread(tt,"t1");
Thread t2 = new Thread(tt,"t2");
Thread t3 = new Thread(tt,"t3");
//设置优先级
//t2 < t1 < t3
t1.start();//优先级:5
t2.setPriority(1);
t2.start();
t3.setPriority(10);
t3.start();
}
}
//定义一个线程类,实现Runnable,TestThread
class TestThread implements Runnable {
@Override
public void run() {
//线程的名字,线程的优先级
Thread th = Thread.currentThread();
System.out.println(th.getName() + "优先级:" + th.getPriority());
}
}
线程不安全的案例
买票不安全
UnsafeBuyTickets
线程不安全,会出现负数
public class UnsafeBuyTickets implements Runnable {
//票
private static int tickets = 10;
//线程停止的标记
private boolean flag = true;//没有票的时候更新成false
//买票
public void buy() throws InterruptedException {
//判断是否有票可买
if(tickets <= 0) {
flag = false;
return;//直接退出方法
}
//模拟延时
Thread.sleep(1000);
//购买
//线程名字+买了第+XX+张票
System.out.println(Thread.currentThread().getName() + "拿到了第" + tickets-- + "张票");
}
@Override
public void run() {
while(flag) {
//执行买票的方法
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//创建实现类的对象
UnsafeBuyTickets ubt = new UnsafeBuyTickets();
new Thread(ubt,"张三").start();
new Thread(ubt,"李四").start();
new Thread(ubt,"王武").start();
}
}
不安全的取钱
- 定义一个账户
- 定义一个银行,用来取钱
- 定义两个线程进行取钱的操作
账户类
//定义一个取钱的类
class Drawing extends Thread {
//定义银行中的账户
private Account account;
//定义取出的钱
private int drawingMoney;
//取完后用户手里的钱
private int newMoney;
public Drawing(Account account, int drawingMoney, String name) {//name是线程的名字
this.account = account;
this.drawingMoney = drawingMoney;
super.setName(name);
}
@Override
public void run() {
//判断又没钱可取,储蓄的钱是否足够用户取出
if(account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + "钱不够了不能取了");
return;
}
//模拟延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//取钱:现有账户中余额 = 原有账户的余额 - 取出的钱
account.money -= drawingMoney;
//用户手里有多少钱
newMoney += drawingMoney;
//打印取出的钱的数量,以及用户取完钱后手里钱的数量
System.out.println(account.name + "余额为:" + account.money);
System.out.println(this.getName() + "手里的钱:" + newMoney);
}
}
测试
public class UnsafeBank {
public static void main(String[] args) {
//定义账户
Account accout = new Account("结婚基金", 100);
//定义两个线程取钱
Drawing boy = new Drawing(accout, 50, "小小");
Drawing girl = new Drawing(accout, 100, "小闺女");
boy.start();
girl.start();
}
}
线程同步
- 多个线程操作一个资源
- 并发
- 同一个对象多个操作同时操作,比如抢票
- 线程同步
- 线程排队,一个一个来,程序称之为 队列
- 形成线程同步的条件 和 锁
- 锁
- 可以理解为去WC,进去一个人就要上锁,不让其他人挤进来
- WC
- 资源
人
- 线程
我们只需要对方法提供一套机制,就是
synchronized
关键字
synchronized
方法
public synchronized void 方法名() {
//方法体
}
synchronized
块
synchronized
修饰的方法会控制对象的访问,每一个对象都有一把锁- 每一个
synchronized
方法必须调用该方法的锁才会执行,否则线程会堵塞 - 方法一但被执行,就独占线程资源,直到方法返回释放锁,后面堵塞的线程才有机会获得锁,去执行方法
- 过大的
synchronized
会影响系统的效率 - 方法中存在要修改的内容时,才会使用锁,锁太多会浪费资源
- 同步块
synchronized(obj){
}
- Obj对象,作为共享资源的监视器
- 同步方法中,无需指定监视器,因为同步方法的监视器就是this,就是对象本身
- 同步监视器
1. 第一线程访问的时候,锁同步监视器,执行其中的代码(线程体)
1. 第二线程访问的时候,发现同步监视器被锁定了,无法访问
1. 当第一线程执行完之后,解锁同步监视器
1. 第二线程,发现同步监视器锁解开了,进行访问并锁定
改善买票方法
//买票,加入 synchronized 关键字,修缮成同步方法,保证线程的安全
public synchronized void buy() throws InterruptedException {
//判断是否有票可买
if(tickets <= 0) {
flag = false;
return;//直接推出方法
}
//模拟延时
Thread.sleep(1000);
//购买
//线程名字+买了第+XX+张票
System.out.println(Thread.currentThread().getName() + "拿到了第" + tickets-- + "张票");
}
改善银行取钱
- 此处不适合给 run() 加入 synchronized,因为在这里我们要操作的是账户,而不是 this 取钱
- 所以 synchronized 快,将账户指定为监视器
//定义一个取钱的类
class Drawing extends Thread {
//定义银行中的账户
private Accout accout;
//定义取出的钱
private int drawingMoney;
//取完后用户手里的钱;
private int newMoney
}
网络编程
网络编程概述
计算机网络
是指地理位置不同具有特殊功能的计算机以及其他外部设备,通过同性的线路链接起来,在网络操作系统和网络管理软件以及网络通信协议的管理和协调下,实现资源的共享和信息的传递
网络编程
网络通信协议下,实现网络的互连,不同的计算机上运行的程序间进行数据的交换
简单来说学完网络编程之后,通过编写的网络程序访问其他人电脑上的内容,实现资源的共享
网络编程的三个要素
比如在一个网络环境下,有两台计算机需要进行通信
需要让这两个计算机找到对方,在网络中通过 IP地址
表示设备
每一台电脑上都有一堆程序,如何辨别使用那个程序进行访问,可以通过端口
知道了 **IP**
地址,知道了 **端口号**
,我们的计算机就可以通信了
计算机通信需要遵守一定的规则,规则就是协议
**IP地址**
- 计算机的标识号
- 通过这个标识号来指定接收数据的计算机和发送数据的计算机
- 端口
- 程序的标识号
- 通过端口号来分辨访问的程序是哪个
- 协议
- 同一个网络环境中计算要进行链接通讯时需要遵守一定的规则的,规则就是协议
- 协议规定了数据的传输格式,传输的速率,传输的步骤
- 通信的双方必须同时遵守协议才能完成数据的交互
- 常见的协议:
- UDP协议
- TCP协议
IP
地址
**IP地址**
:在网络中的唯一标识**IP地址**
的分类
**IPv4**
ipconfig
- 给每一个链接网络的计算机分配了一个32
bit
地址,按照TCP
协议的规定,IP
地址用二进制显示的 IP
地址中的每一个数,数字都是 4个字节一组,长度的二进制数字- 二进制的
IP
表现形式:- 1100 0000 1010 1000 0010 0010 0000 0001
- 十进制:
- 192.168.34.1
**IPv6**
- 采用的 128
bit
长度,每16个字节一组,来解决IP
地址分配紧张的问题
- 采用的 128
Dos
命令
ipconfig
:- 查看本机的地址
ping IP地址
:检查网络是否连通
- 特殊的
_IP_
地址:127.0.0.1
回送地址,代表本机的地址,一般是用来测试
InetAddress
为了方便对 IP 地址的获取和操作,Java 提供了一个类 InetAddress
供我们使用InetAddress
:此类代表 Internat
协议地址(IP地址)
需要导包:java.net
包下
- 特点:
- 没有构造器
- 提供一种静态方法获取该类的对象
常用方法
InetAddress getByName(String host)
:确定主机名的IP地址,参数 host,就是主机的名字,指定的主机String getHostAddress()
:返回字符串形式的IP
地址/返回文本显示中的IP
地址字符串String getHostName()
:返回主机名/获取此IP
地址的主机名
代码实例
public class TestDemo {
public static void main(String[] args) throws UnknownHostException {
//通过 getByName() 获得IP地址
//参数是主机名
InetAddress ip1 = InetAddress.getByName("WANGZHAN");
System.out.println(ip1);
//参数还可以是字符串形式的IP地址
InetAddress ip2 = InetAddress.getByName("192.168.34.1");
System.out.println(ip2);
//通过InetAddress对象获取 主机名&IP地址
String hostName = ip1.getHostName();
String hostAddress = ip1.getHostAddress();
System.out.println("主机名:" + hostName);
System.out.println("IP地址:" + hostAddress);
}
}
端口
端口的概述
- 端口:设备上应用程序的唯一标识
- 端口号:
- 协议
- 计算机网络中,链接和通信的规则被称作为协议
UDP协议
- 用户数据协议(User Datagran Protocol)
- UDP是无线的通信协议,数据的发送和接受不建立逻辑链接
- 当一台计算机另一台计算机发送数据的时候,发送端不会确认接收端是否存在
- 接受端接受数据时,一不会想发送端反馈是否接受到数据
- 通常视频会议会使用UDP协议,偶尔丢一两个数据包,但是不会对接收的结果产生太大的影响
优势消耗的资源小,通信的效率高,通常用于视频和音频以及普通数据的传递
TCP协议
- 传输控制协议(Transmission )
java.net.Socket
Socket(套字节)
封装了TCP协议的通讯细节,是的我们使用它可以与服务端建立网络链接,并通过 它获取两个流(一个输入 一个输出),然后使用这两个流的读写操作完成与服务端的数据交互
java.net,ServerSocket
ServerSocket
运行在服务端,作用有两个:
- 向系统深情服务端口,客户端的Socket就是通过这个端口与服务建立连接的
- 监听服务端口,一旦一个客户通过该端口建立连接则会自动创建一个Socket,并通过该Socket与客户端进行数据交互
如果我们把Socket比喻为电话,那么ServerSocket相当于是某客服服务中心的总机
与服务端建立连接案例
package socket;
import java.io.IOException;
import java.net.Socket;
/**
* 聊天室客户端
*/
public class Client {
/*
java.net.Socket 套接字
Socket封装了TCP协议的通讯细节,我们通过它可以与远端计算机建立链接,
并通过它获取两个流(一个输入,一个输出),然后对两个流的数据读写完成
与远端计算机的数据交互工作。
我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克
风(输出流),通过它们就可以与对方交流了。
*/
private Socket socket;
/**
* 构造方法,用来初始化客户端
*/
public Client(){
try {
System.out.println("正在链接服务端...");
/*
实例化Socket时要传入两个参数
参数1:服务端的地址信息
可以是IP地址,如果链接本机可以写"localhost"
参数2:服务端开启的服务端口
我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上
的服务端应用程序。
实例化的过程就是链接的过程,如果链接失败会抛出异常:
java.net.ConnectException: Connection refused: connect
*/
socket = new Socket("localhost",8088);
System.out.println("与服务端建立链接!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 客户端开始工作的方法
*/
public void start(){
}
public static void main(String[] args) {
Client client = new Client();
client.start();
}
}
package socket;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 聊天室服务端
*/
public class Server {
/**
* 运行在服务端的ServerSocket主要完成两个工作:
* 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
* 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
* 就可以和该客户端交互了
*
* 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
* 电话使得服务端与你沟通。
*/
private ServerSocket serverSocket;
/**
* 服务端构造方法,用来初始化
*/
public Server(){
try {
System.out.println("正在启动服务端...");
/*
实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
应用程序占用的端口相同,否则会抛出异常:
java.net.BindException:address already in use
端口是一个数字,取值范围:0-65535之间。
6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
*/
serverSocket = new ServerSocket(8088);
System.out.println("服务端启动完毕!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 服务端开始工作的方法
*/
public void start(){
try {
System.out.println("等待客户端链接...");
/*
ServerSocket提供了接受客户端链接的方法:
Socket accept()
这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
通过这个Socket就可以与客户端进行交互了。
可以理解为此操作是接电话,电话没响时就一直等。
*/
Socket socket = serverSocket.accept();
System.out.println("一个客户端链接了!");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server = new Server();
server.start();
}
}
客户端与服务端完成第一次通讯(发送一行字符串)Socket
提供了两个重要的方法:
OutputStream getOutputStream()
- 该方法会获取一个字节输出流,通过这个输出流写出的字节数据会通过网络发给对方.
InputStream getInputStream()
- 通过该方法获取的字节输入流读取的是远端计算机发送过来的数据
客户端代码
package socket;
import java.io.*;
import java.net.Socket;
/**
* 聊天室客户端
*/
public class Client {
/*
java.net.Socket 套接字
Socket封装了TCP协议的通讯细节,我们通过它可以与远端计算机建立链接,
并通过它获取两个流(一个输入,一个输出),然后对两个流的数据读写完成
与远端计算机的数据交互工作。
我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克
风(输出流),通过它们就可以与对方交流了。
*/
private Socket socket;
/**
* 构造方法,用来初始化客户端
*/
public Client(){
try {
System.out.println("正在链接服务端...");
/*
实例化Socket时要传入两个参数
参数1:服务端的地址信息
可以是IP地址,如果链接本机可以写"localhost"
参数2:服务端开启的服务端口
我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上
的服务端应用程序。
实例化的过程就是链接的过程,如果链接失败会抛出异常:
java.net.ConnectException: Connection refused: connect
*/
socket = new Socket("localhost",8088);
System.out.println("与服务端建立链接!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 客户端开始工作的方法
*/
public void start(){
try {
/*
Socket提供了一个方法:
OutputStream getOutputStream()
该方法获取的字节输出流写出的字节会通过网络发送给对方计算机。
*/
//低级流,将字节通过网络发送给对方
OutputStream out = socket.getOutputStream();
//高级流,负责衔接字节流与字符流,并将写出的字符按指定字符集转字节
OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
//高级流,负责块写文本数据加速
BufferedWriter bw = new BufferedWriter(osw);
//高级流,负责按行写出字符串,自动行刷新
PrintWriter pw = new PrintWriter(bw,true);
pw.println("你好服务端!");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Client client = new Client();
client.start();
}
}