前言
How to study?
- 需求
- 工作需要
- 跳槽,对方需要
- 技术控
- 看看传统技术能否解决
- 能解决,不完美
- 解决不了
- 问清楚新技术到底有什么优异
- 引出我们学习的新技术和知识点
- 学习新技术或者知识点的基本原理和基本语法(先不要考虑细节)
- 快速入门(基本程序,curd)
- 开始研究技术的注意事项,使用细节,使用规范,如何优化(人话:推倒重来)
JAVA诞生故事
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0G1gJPU5-1678672773976)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220712172429817.png)]
- 震惊世界的功能:在网页上加载动态的图片
- LTS版本:long term support,长期支持版
- 目前最广泛使用JAVA8、JAVA11
JAVA技术平台体系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aaPqzpC3-1678672773978)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220712172924460.png)]
- 目标:贯通标准版,学会企业版
JAVA重要特点
-
JAVA语言是面向对象的(oop)
-
JAVA语言是见状的。JAVA的强类型机制、异常处理、垃圾的自动手机是JAVA程序健壮性的重要保证
-
JAVA是跨平台性质的。【.class文件通过解释器可在多个系统下运行】
-
JAVA语言是解释型的[了解]
解释性语言:JavaScript,PHP,Java
编译型语言:c、c++
区别:解释性语言,编译后的代码不能被机器直接执行,需要解释器来执行,编译型语言,编译后的代码已经是二进制码可以直接被机器直接运行
JAVA运行概述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HNPNHfBw-1678672773979)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220712174645511.png)]
JVM:Java虚拟机,实现在不同系统解释统一代码
JAAVA运行机制以及运行过程
- JAVA核心机制-JAVA虚拟机[JVM java virtual machine]
- 基本介绍
- JVM是一个虚拟计算器,具有指令集并使用不同的存储区域。负责执行指令,管理数据、内存、寄存器,包含在JDK中
- 对于不同平台,有不同的虚拟机
- JAVA虚拟机机制屏蔽了底层运行平台的差别,实现了”一次编译,到处运行“(hhh还没有解释)
JDK基本介绍
- JDK全称:JAVA Development Kit [java开发工具包]
- JDK = JRE + java的开发工具[java,javac,javadoc,javap等]
JRE基本介绍
-
JRE(JAVA Runtime Envirnment JAVA运行环境)
JRE = JVM + JAVA的核心类库【类】
-
包括Java虚拟机和JAVA程序所需的核心类库等,如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可
执行流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HUhn2VCT-1678672773980)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220712193421703.png)]
什么是编译
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fAhqugD5-1678672773982)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220712193511933.png)]
什么是运行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ct08jOP-1678672773983)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220712193641270.png)]
JAVA开发注意事项
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7MA3Z3S6-1678672773985)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220712193746905.png)]
8.Public类的名称需要与源文件名称相同
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vMu97UHe-1678672773986)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220712194121604.png)]
每一个类编译后,都对应一个.class(JVM是加载机制,加载每个类)
安装JAVA
配置环境变量PATH
- 进入环境变量
- 增加JAVA_HOME环境变量,只想jdk安装目录d:\xxxx
- 编辑path环境变量,增加%JAVA_HOME%\bin
- 打开DOS命令行,任意目录下敲入javac/java,javadoc。如果出现java的参数则配置成功
Javadoc注释
一.Java注释方式
Java 支持三种注释方式。分别是
-
单号注释 //
-
多行注释 /* */
-
文档注释,它以 /** 开始,以 */结束。
可以使用 javadoc 工具软件来生成信息,并输出到HTML文件中。
二.JavaDoc注释用法
在开始的 /**
之后,第一行或几行是关于类、变量和方法的主要描述
。之后,你可以包含一个或多个各种各样的 @ 标签
。每一个 @ 标签必须在一个新行的开始或者在一行的开始紧跟星号(*).
- 多个相同类型的标签应该放成一组。例如,如果你有三个 @see 标签,可以将它们一个接一个的放在一起。
下面是一个类的说明注释的实例:
/*** 这个类绘制一个条形图
* @author runoob
* @version 1.2
*/
三.JavaDoc注释会输出什么
javadoc 工具将你 Java 程序的源代码作为输入,输出一些包含你程序注释的HTML文件。
每一个类的信息将在独自的HTML文件里。javadoc 也可以输出继承的树形结构和索引。
由于 javadoc 的实现不同,工作也可能不同,你需要检查你的 Java 开发系统的版本等细节,选择合适的 Javadoc 版本。
四.JavaDoc注释常用标签
1.@see
- 使用@see时应注意 写在每行注释的开头。
- 用法:@see 类#属性 / 方法
四.JavaDoc注释常用标签
1.@see
使用@see时应注意 写在每行注释的开头。
用法:@see 类#属性 / 方法
JavaDoc注解中的{@link}与@see
,他可以链接类或者方法,方便自己或别人看的时候,可以直接找到你关联的代码类或者啥的。
在编辑器中,按住
ctrl+鼠标左键
,就能直接跳转
2.{@link}
- {@link}的使用与@see有一点区别就是可以写在注释的任意位置
- 用法:{@link 类#属性 / 方法}
/**
* {@link ClassA#属性}
* {@link ClassA#方法}
* {@link com.joh.demo.ClassB#方法}
*
* 可不用开头写 {@link ClassA#属性}
* 可不用开头写 {@link ClassA#方法}
* 可不用开头写 {@link com.joh.demo.ClassB#方法}
*/
JavaDoc注解中的{@link}与@see
,他可以链接类或者方法,方便自己或别人看的时候,可以直接找到你关联的代码类或者啥的。
在编辑器中,按住
ctrl+鼠标左键
,就能直接跳转
3.其他JavaDoc标签
标签 | 描述 | 示例 |
---|---|---|
@author | 标识一个类的作者 | @author description |
@deprecated | 指名一个过期的类或成员 | @deprecated description |
{@docRoot} | 指明当前文档根目录的路径 | Directory Path |
@exception | 标志一个类抛出的异常 | @exception exception-name explanation |
{@inheritDoc} | 从直接父类继承的注释 | Inherits a comment from the immediate surperclass. |
{@link} | 插入一个到另一个方法或者代码的链接,但是该链接显示链接样式 | Inserts an in-line link to another topic. |
{@linkplain} | 插入一个到另一个方法或者代码的链接,但是该链接显示纯文本字体 | Inserts an in-line link to another topic. |
@param | 说明一个方法的参数 | @param parameter-name explanation |
@return | 说明返回值类型 | @return explanation |
@serial | 说明一个序列化属性 | @serial description |
@serialData | 说明通过writeObject( ) 和 writeExternal( )方法写的数据 | @serialData description |
@serialField | 说明一个ObjectStreamField组件 | @serialField name type description |
@throws | 和 @exception标签一样.,说明要抛出的异常 | @throws tag has the same meaning as the @exception tag. |
{@value} | 显示常量的值,该常量必须是static属性。 | Displays the value of a constant, which must be a static field. |
@version | 指定类的版本 | @version info |
五.更多详细
(29条消息) 【Java基础】JavaDoc注释标签大全_墩墩分墩的博客-CSDN博客_java注释标签
Java代码规范
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dNjavjSM-1678672773988)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220712235502004.png)]
JAVA API文档
-
API(Application Programming Interface,应用程序编程接口)是JAVA提供的基本编程接口(java提供的类还有相关方法)。
中文在线文档:https://www.matools -
Java语言提供了大量基础类,因此Oracle公司也为这些基础类提供了相应的API文档,用于告诉开发者如何使用这些类,以及这些类李包含的方法。
-
Java类的组织形式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JTic2UV9-1678672773989)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220713141610471.png)]
-
举例说明如何使用ArrayList类有哪些方法
- 找方法:包 -> 类 -> 方法(如math)
- 通过索引找(math)
标识符命名规则和规范
规则:必须要做
规范:最好这样做
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h59lHSlo-1678672773990)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220715200504867.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J5C8uDAN-1678672773991)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220715200807421.png)]
基础
HelloWorld,JAVA
//1.public class hello表示hello是一个类,是一个public公有的类
//2.hello{ }表示一个类的开始和结束
//3.public static void main(string[]args)表示一个主方法,即程序入口
public class Hello {
//编写一个主方法
public static void main(String[] args){
System.out.println("hello,world_JAVA");
}
}
转义字符
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PgskIzwi-1678672773993)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220712200215383.png)]
public class ChangeChar{
public static void main(String[] args){
System.out.println("北京\t上海\t换行\n 符号\\ 一个\" 一个\' 回车\r回车完毕");
//解读
//1.输出 回车
//2.\r表示回车
//3.将后面的字符一个个替换成前面的字符
}
}
class Homework{
public static void main(String[] args){
System.out.println("书名\t作者\t价格\t销量\n三国\t罗贯中\t120\t1000");
}
}
”+“号运算
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S74iIkO6-1678672773994)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220713113144123.png)]
JAVA数据类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dhn7Modn-1678672773995)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220713113427422.png)]
整数类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ddNvjOh0-1678672773996)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220713114432925.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t99WbG11-1678672773997)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220713122822166.png)]
浮点类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-abX8fry9-1678672773998)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220713140553571.png)]
字符类型
- 使用unicode码进行转义,char占2个字节
- 字符类型“+”号运算不是拼串而是进行加法运算
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XEu4XD9q-1678672774000)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220713151322638.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FPKSrFc1-1678672774001)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220713152512264.png)]
布尔类型
细节说明
- 不可以0或非0的整数代替false和true,这点和c语言不同
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v8HXGI3U-1678672774002)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220713154829300.png)]
基本数据类型的转换
自动类型转换
简介
当java程序在进行赋值或者运算时,精度小的类型回自动转换为精度大的数据类型
这个就是自动类型转换**(注意是精度噢)**
数据类型按精度(容量)大小排序如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NCZ5hly3-1678672774003)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220713182119602.png)]
例子:
int a='c';//对
double d=80;//对
需要注意的地方:
- 有多种类型的数据混合运算时,系统首先自动将数据转换成容量最大的那种数据类型,然后计算。
- 当我们把精度(容量)大的数据类型赋值给精度(容量)小的数据类型时,就会报错,反之就会进行自动类型转换。
- (byte,short)和char之间不会进行转换。
- byte,short,char他们三者可以计算,在计算时首先转换为int类型(因此只能用来赋值等)
- boolean不参与转换
- 自动提升原则:表达式结果的类型自动提升为操作数中最大的类型
- 整型直接量在没有加后缀的时候默认为int,但是当它被赋值给某个变量(这个变量是short型,int型,byte型中的一种)时,则会自动转化成相对应的类型。
强制类型转换
介绍:
自动类型转换的逆过程,将精度大的数据类型转换为精度小的数据类型。使用时要加上强制转换符(int/byte/float/…),但可能造成精度降低或溢出,格外要注意。
案例演示:
public class ForceConvert {
public static void main(String[] args){
int n1 = (int)1.9;
System.out.println("n1="+n1);//造成精度损失
//输出n1=1
int n2 = 2000;
byte b1 = (byte)n2;
System.out.println("b1="+b1);//造成数据溢出
//输出b1=-48
}
}
需要注意的细节:
-
当进行数据的精度从大—>小,就需要使用强制转换
-
强转换符号只针对最近的操作数有效,玩玩会使用小括号提升优先级
public class ForceConvertDetail { public static void main(String[] args){ //演示强制类型转换 //强转符号只针对最近的操作数有效,往往回使用小括号提升优先级 // int x = (int)10 * 3.5 + 6 * 1.5; //double -> int 错误,强制转换()只对10有效 int y = (int)(10 * 3.5 + 6 * 1.5); //使用小括号提升优先级计算进行强制转换 System.out.println(y); } }
-
char类型可以保存int类型的常量值,但不能保存int的变量值,需要强转
char c1=100;//ok int m=100;//ok char c2 = m;//false char c3 = (char)m;//ok System.out.println(c3);
-
byte和short,char类型在进行运算时,当作int类型处理。
基本数据类型和String类型转换
-
介绍
在程序开发中,我们经常需要将基本数据类型转换成String类型。或者将String类型转换成基本数据类型。
-
基本数据类型转String类型
语法:将基本类型的值+""即可
演示:
-
String类型转基本数据类型
语法:通过基本类型的包装类调用parseXX方法即可
演示:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NrbYR63y-1678672774005)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220713204437534.png)]
注意:Char转换则使用ch.charAt(0)取出其中一个字符
-
注意事项:
- 在将String类型转换成基本数据类型时,要确保String类型能够转成有效的数据,比如:我们可以把“123”,转成一个整数,但不能把“hello”转换成一个整数
- 如果格式不正确,就会抛出异常,程序就会终止,这个问题在异常处理章节中,会讲解
运算符
介绍
运算符是一种特殊符号,用来表示数据的运算、赋值和比较等…
- 算术运算符
- 赋值运算符
- 关系运算符
- 逻辑运算符
- 位运算符
- 三元运算符
算术运算符
除法
public class ArithmeticOperator {
public static void main(String[] args){
System.out.println(10/4);//从数学角度来讲是2.5,java运算结果是2
System.out.println(10.0/4);//java运算结果是2.5
double d = 10 / 4;//java钟10 / 4 = 2,2=>2.0
System.out.println(d);//结果是2.0
}
}
模运算
%的本质,公式:a % b = a - a / b * b
-10 % 3 = -10 - (-10)/3 * 3 = -10 - (-3) * 3 = -10 + 9 = -1
public class ArithmeticOperator {
public static void main(String[] args){
System.out.println(10 % 3);//1
System.out.println(-10 % 3);//-1
System.out.println(10 % -3);//
}
}
自增自减
作为表达式使用
前++:++i先自增后赋值
后++:i++先赋值后自增此外还有**等
面试题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PLr328cK-1678672774006)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220715193140160.png)]
//原理如下
int i = 1;
k = i++;
/*
内部规则:
temp = i;
i = i + 1;
k = temp;👈
*/
//如果k变量换成i,则最后一步手指指着的k也改成i,所以结果仍未未自增前
k = ++i;
/*
内部规则:
i = i + 1;
temp = i;
k = temp;
*/
//与上同理
关系运算符
关系运算符的结果都是boolean,要么true,要么false
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lfffmlaz-1678672774008)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220715194231467.png)]
逻辑运算符
- 短路与&&,短路或||,取反!
- 逻辑与&,逻辑或|,逻辑异或^
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZtyuCIpX-1678672774009)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220715194623760.png)]
短路与逻辑的区别:
- &&(短路与):从优先级最高开始,只要出现false,则后面的条件均不会判断,最终结果直接判定为false
- &(逻辑与):表达式内所有条件都需要判断
赋值运算符
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vmRi5Eoo-1678672774012)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220715195624684.png)]
三元运算符
条件表达式?表达式1:表达式2
- 如果条件表达式为true,则返回表达式1
- 如果条件表达式为false,则返回表达式2
键盘输入
-
介绍:
在编程钟,需要接收用户输入的数据,就可以使用键盘输入语句来获取。
使用一个扫描器(对象),就是Scanner -
步骤:
- 导入该类的所在包,java.util.*
- 创建该类的对象(即,声明变量)
- 调用里面的功能
-
演示
//1.引入Scanner类所在的包 import java.util.Scanner;//表示把java.util下的Scanner类导入 public class Input { public static void main(String[] args){ //2.创建一个Scanner 对象,myScanner即对象 Scanner myScanner = new Scanner(System.in); //3.接收用户的输入需要使用相关方法,如下的next(),nextInt(),nextDouble() System.out.println("请输入名字"); String name = myScanner.next(); System.out.println("请输入年龄"); int age = myScanner.nextInt(); System.out.println("请输入薪水"); double sal = myScanner.nextDouble(); System.out.println("人的信息如下:"); System.out.println("name=" + name + " age=" + age + " salary=" + sal); } }
位运算
- Java没有无符号数,换言之,Java钟的数都是有符号的
- 在计算机运算的时候,都是以补码的方式运算的
public static void main(String[] args){
//1 =》00000000 00000000 00000000 00000001
//=》00000000 00000000 00000000 00000000
//本质1/2/2 = 0
int a = 1 >> 2;//1向右位移2位
//-1 =》 10000001 =》11111110(反码) =》 11111111(补码) =》 操作11111111 =》反码11111110 =》 原码10000001 ->-1
int b = -1 >> 2;
//1 = 》00000001 =》 00000100 本质1*2*2
int c = 1 << 2;
int e = 3 >>> 2;
//a,b,c,d,e结果是多少
}
- 算术右移
>>
:低位溢出,符号位不变,并用符号位补溢出高位 - 算术左移
<<
:符号位不变,低位补0 >>>
逻辑右移也叫无符号右移,规则:低位溢出,高位补0- 特别说明:没有
<<<
符号
public static void main(String[] args){
//得到2的补码,2的原码:00000000 00000000 00000000 00000010
//2的补码:00000000 00000000 00000000 00000000 00000010
//3的原码:00000000 00000000 00000000 00000000 00000011
//3的补码:00000000 00000000 00000000 00000000 00000011
//按位与&:
//00000000 00000000 00000000 00000010 <- 2
//00000000 00000000 00000000 00000011 <- 3
//00000000 00000000 00000000 00000010 <- result = 2
System.out.println(2&3);//2
//-2的原码:10000000 00000000 00000000 00000010
//-2的反码:11111111 11111111 11111111 11111101
//-2的补码:11111111 11111111 11111111 11111111
//~-2操作:00000000 00000000 00000000 00000001 -< result = 1
System.out.println(~-2);//1
//2的原码:00000000 00000000 00000000 00000010
//2的补码:00000000 00000000 00000000 00000010
//~2操作:11111111 11111111 11111111 11111101 <-result(补码)
//因为不是正数,所以本身并不是三码合一,需要由补码转原码
//运算后的反码:11111111 11111111 11111111 11111100
//运算后的原码:10000000 00000000 00000000 00000011 <-result = -3
System.out.println(~2);//-3
}
switch分支
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aOIfjy5L-1678672774013)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220717123127845.png)]
用法:
推荐在单个值进行选择的情况下使用switch,在多个区间的情况下使用if
import java.util.Scanner;
public class Switch01 {
public static void main(String[] args) {
Scanner myScanner = new Scanner(System.in);
System.out.println("请输入一个字符(a-g)");
char c1 = myScanner.next().charAt(0);
switch (c1) {
case 'a':
System.out.println("今天星期1,猴子穿新衣");
break;
case 'b':
System.out.println("今天星期2,猴子穿新衣");
break;
case 'c':
System.out.println("今天星期3,猴子穿新衣");
break;
default:
System.out.println("你输入的字符不正确,没有匹配");
break;//可以没有!
}
System.out.println("退出switch,继续执行程序");
}
}
需要注意的细节:
- 表达式switch()的数据类型,应和case后的常量类型一致
或者是可以自动转成可以互相比较的类型[常量的类型 -> 表达式内结果的类型] - switch(表达式)中表达式的返回值必须是:(byte,short,int,char,enum,string)
注意:浮点型和boolean不支持噢 - case内的值不能是变量
- 没有default的话,匹配不到case,直接退出switch
- 如果没有写break,程序会顺序执行到switch结尾,除非执行到break
- 推荐在单个值进行选择的情况下使用switch,在多个区间的情况下使用if
break与label标签
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9fMlyM5W-1678672774014)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220717173445163.png)]
字符串的比较
说明:
使用String类中的equals()方法
数组
如何定义与创建?
方式1-动态初始化:
数据类型 数组名[] = new 数据类型[大小]
int a[] = new int[5];//创建了一个数组,名字a,存放5个int
方式2-动态初始化:
-
先声明数组
语法:数据类型 数组名[];
也可以 数据类型[] 数组名;
此时并没有开辟内存空间,a指向null,会报错(空指针异常)
int a[]; int[] a;
-
创建数组
语法:数组名 = new 数据类型[大小];
a = new int[10]
方式3-静态初始化:
-
初始化数组
语法:数据类型 数组名[]={元素值,元素值}
int a[]={2,5,6,7,8,89,90,34,56}
数组细节
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gBVj5fCI-1678672774016)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220720145847701.png)]
数组赋值的机制(引用传递)
-
基本数据类型赋值,这个值就是具体的数据,而且相互不影响。(值拷贝)
int n1 = 2; int n2 = n1;//n2是新开辟的空间,里面的值是具体数值
-
数组在默认情况下是引用传递,赋的值的是地址。(引用传递)
int arr1[] = {1,2,3}; int arr2[] = arr1; //系统分配内存,存入数据{1,2,3},并返回地址给arr1 //第二句其实是引用了arr1的地址,如果修改,修改的是同一片内存的值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NcfjkisJ-1678672774017)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220720171318930.png)]
垃圾回收
若arr1与arr2指向堆内存内不同两个数据空间,将arr1指向arr2,此时arr1原来的数据空间就没有变量引用,会被当做垃圾,销毁。
排序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wFHRnOnC-1678672774018)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220720183833832.png)]
二维数组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yzifQhCG-1678672774019)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220720193442339.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-00dIpy12-1678672774021)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220720190156458.png)]
- 与C语言不同,C语言是开辟一片连续的内存将其看作二维形式,JAVA是给每个二维形式分配不同的空间,通过一维形式进行调用。
因此,C定义数组时一维可不写,二维必写;JAVA一维必写,二维可不写。
相对与C语言,JAVA还能实现二维的长度不同。
OOP类与对象
简介
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zeXFOj0S-1678672774022)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220721095710143.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l5a3i5Qb-1678672774023)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220721101146561.png)]
对象在内存中的存在形式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ns7svcGu-1678672774024)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220721101921388.png)]
字符串放在方法区的常量池中,在部分版本中,常量池在堆里(不在方法区里)
属性/成员变量
简介
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VkT9UoTo-1678672774025)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220721102738054.png)]
细节
- 属性的定义语法同变量–访问修饰符:控制属性的访问方位(public,protected,默认,private)
- 属性的定义类型可以为任意类型,包含基本类型或引用类型
- 属性如果不赋值,有默认值,规则和数组一致。
创建
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VBNjDOZ7-1678672774026)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220721104353676.png)]
声明未创建则cat = null
对象创建过程
JAVA内存的结构分析
- 栈:一般存放基本数据类型(局部变量)
- 堆:存放对象(Cat cat,数组等)
- 方法区:常量池(常量,比如字符串),类加载信息
- 示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z0UIWJ7Q-1678672774027)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220721110848547.png)]
分析执行步骤
Person p = new Person();
p.name = "jack";
p.age = 10;
- 先加载Person类信息(属性和方法信息,只会加载一次)
- 在堆中分配空间,进行默认初始化(int->0)
- 把地址返回并赋给p,p指向对象
- 进行指定初始化:p.name = “jack”
成员方法
基本介绍
在某些情况下,我们需要定义成员方法(简称方法)。
比如人类:出了有一些属性外(年龄,姓名…),我们人类还有一些行为(说话、跑步、通过学习还能算术)
这时就要用成员方法来完成对类的抽象
public class Method01 {
public static void main(String[] args){
//方法使用
//1.方法写好后,如果不去调用(使用),不会输出
//2.先创建一个对象,然后调用方法即可
Person p1 = new Person();
p1.speak();//调用方法
p1.cal01();
p1.cal02(2);
// 调用getSum方法,同时num1=10,num2=20
// 把方法getSum返回的值,赋给变量returnRes
int returnRes = p1.getSum(10, 5);
System.out.println("getSum返回值:"+returnRes);
}
}
class Person {
String name;
int age;
//方法
//1.public:表示这个方法是公开的
//2.void:表示方法没有返回值
//3.speak():speak是方法名,()是形参列表
//4.{}方法体,可以写我们要执行的代码
public void speak(){
System.out.println("我是一个好人");
}
public void cal01(){
int res = 0;
for(int i = 1;i <= 1000;i++){
res += i;
}
System.out.println("计算结果="+res);
}
// 1.(int n)形参列表,表示当前有一个形参n,可以接收用户输入
public void cal02(int n){
int res=0;
for(int i = 1;i <= n ; i++){
res += i;
}
System.out.println("计算结果="+res);
}
// 1.public表示方法是公开的
// 2.int 表示方法执行后表示返回一个int值
// 3.getSum方法名
// 4.(int num1,int num2)形参列表,两个形参,可以接受用户传入的两个数
// 5.return res;表示把res的值,返回
public int getSum(int num1,int num2){
int res = num1 + num2;
return res;
}
}
方法的调用机制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lWC3MHyR-1678672774028)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220721133328314.png)]
使用细节
-
访问修饰符(作用是控制方法的使用范围)
如果不写默认访问,[又四种:public,protected,默认,private],后续讲解
-
返回数据类型
- 一个方法最多又一个返回值[使用数组或对象返回多个值]
- 返回类型可以任意类型,包含基本类型或引用类型(数组,对象)
- 如果方法要求又返回数据类型,则方法中最后执行的语句必须为return 值;
且要求返回值类型必须和return的值类型一致或兼容 - 如果方法是void,则方法中可以没有return语句,或者只写return;
-
命名方法:遵循驼峰命名法,最好见名知意
-
方法调用细节说明:
-
同一个类中的方法调用:直接调用即可。比如print(参数);
class A{ public void print(int n){ System.out.println("print()被调用 n = "+n); } public void sayOK(){ print(10); System.out.println("执行了sayOK()~~~"); } } { A = a new A(); a.sayOK(); } ------------------------------ print()方法被调用 n = 10 执行了sayOK()~~~
-
跨类中的方法A类调用B类方法:
需要通过对象名调用。比如:对象名.方法名(参数)
class A{ public void m1(){ System.out.println("m1()开始执行~~~"); //创建B对象,然后调用方法即可 B b = new B(); b.hi(); System.out.println("m1()继续执行~~~"); } } class B{ public void hi(){ System.out.println("B类中的hi()被执行") } } { A = a new A(); a.m1(); } -------------------------- m1()开始执行~~~ B类中的hi()被执行 m1()继续执行~~~
-
方法重载OverLoad
-
基本介绍
java中允许同一个类中,多个同名方法的存在,但要求形参列表不一致!
比如:
System.out.println();
out是oruntStrean类型System.out.println(100); System.out.println("hello,java"); System.out.println(1.1); System.out.println(true);
-
重载的好处
- 减轻了起名的麻烦
- 减轻了记名的麻烦
-
注意细节
- 方法名:必须一致
- 形参列表:必须不同(形参类型或个数或顺序,至少有一个不同,参数名无要求)
- 返回类型:无要求
可变参数VaribleParameters
-
基本概念
java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法,就可以通过可变参数实现
-
基本语法
访问修饰符 返回类型 方法名(数据类型... 形参名){//这三个点注意 }
-
案例
-
- int…表示的是可变参数,类型是int,即可以接受多个int(0-多)
- 使用可变参数时,可以当作数组来使用,即 parameters 可当作数组
public class VariableParameter { public static void main(String[] args){ Method m = new Method(); m.sum(1,2,3,4,5,6); } } class Method{ // 1.int...表示的是可变参数,类型是int,即可以接受多个int(0-多) // 2.使用可变参数时,可以当作数组来使用,即 parameters 可当作数组 public int sum(int... parameters){ System.out.println("接收参数个数为"+parameters.length); int res = 0; for(int i = 0;i < parameters.length; i++){ res += parameters[i]; } System.out.println(res); return res; } }
-
-
注意事项和使用细节
-
可变参数的实参可以为0个或任意多个
-
可变参数的实参可以为数组
-
可变参数的本质就是数组
-
可变参数可以和普通数字类型的参数一起放在形参列表,但必须保证可变参数在最后
-
一个形参列表只能出现一个可变参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xwRPp30p-1678672774029)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220726181818990.png)]
-
作用域Scope
-
基本使用
面向对象中,变量作用域是非常重要的知识点,相对来说不是特别号理解,要求认真思考,要求深刻掌握变量作用域。
-
在java编程中,主要的变量就是属性(成员变量)和局部变量。
-
我们说的局部变量一般是指在成员方法中定义的变量。
-
java中作用域的分类
全局变量:也就是属性,作用域为整个类体内(如Cat类:cry eat等方法的使用属性)
局部变量:也就是除了属性以外的其它变量,作用域为定义它的代码块中!
-
全局变量可以默认不赋值,可以直接使用,因为有默认值;
局部变量必须赋值后才能使用,因为没有默认值。
class Cat{ // 全局变量:也就是属性,作用域为整个类体 Cat类:在cry eat等方法使用的属性 int age = 10; public void say(){ // 1.局部变量一般是指在成员方法中定义的变量,没有默认值。 // n 和 name 就是局部变量 // n 和 name 的作用域在cry方法中 int n = 10; String name = "jack"; System.out.println("在cry中使用全局属性 age=:" + age); System.out.println("在cry中使用局部属性 name=:" + name + " n=:" + n); } public void eat(){ System.out.println("在eat中使用全局属性 age=:" + age); // System.out.println("在eat中使用局部属性 name=:" + name); //错误 } }
-
-
注意事项和细节使用
-
属性和局部变量可以崇明,访问时遵循就近原则。
-
在同一个作用域中,比如在同一个成员方法中不能重名
-
属性生命周期较长,伴随着对象的创建而创建,伴随着对象的死亡而死亡。局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而死亡。即在一次方法调用过程中。
-
作用域范围不同
全局变量/属性:可以被本类使用,或其他类使用(通过在方法内new对象调用,也可以通过参数传入)
局部变量:只能在本类中对应的方法中使用
-
修饰符不同(protected public private default)
全局变量/属性可以加修饰符
局部变量不可以加修饰符
-
构造方法/构造器Constructor
-
看一个需求
前面我们在创建人类的对象时,是先把一个对象创建好后,再给他的年龄姓名属性赋值,如果现在我要求,在创建人类的对象时,就直接指定这个对象的年龄和姓名,该怎么做?这是就可以使用构造器。
-
基本语法
[修饰符] 方法名(形参列表){ 方法体 }
- 构造器的修饰符可以默认(default、protected、private、public)
- 构造器没有返回值
- 方法名和类名必须一致
- 构造器的调用,由系统完成
-
基本介绍
构造方法又叫构造器,是类的一种特殊方法,他的主要作用是完成对新对象的初始化。他有几个特点:
- 方法名和类名相同
- 没有返回值
- 在创建对象时,系统会自动的调用该类的构造器完成对对象的初始化
public class Constructor01 { public static void main(String[] args){ Person p1 = new Person("Smith", 80); System.out.println("p1.name="+p1.name+" p1.age="+p1.age); } } class Person{ String name; int age; //1.构造器没有返回值,也不能写void //2.构造器的名称和类person一样 //3.(String pName,int pAge)是构造器的形参列表,规则和成员方法一样 public Person(String pName,int pAge){ System.out.println("构造器被调用"); name = pName; age = pAge; } }
-
注意事项和使用细节
-
一个类可以定义多个不同构造器,即构造器重载(方法重载)
比如:我们可以再给Person类定义一个构造器,用来创建对象的时手,只指定人名,不需要指定年龄
-
构造器名和类名要相同
-
构造器没有返回值
-
构造器是完成对象初始化,并不是创建对象
-
在创建对象时,系统自动调用该类的构造方法
-
如果没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器),比如Person(){},使用javap指令反编译看看
-
一旦定义了自己的构造器,默认构造器就被覆盖了,就不能再使用默认的无参数构造器,除非显示定义一下
-
this关键字
-
简介
java虚拟机会给每个对象分配this,代表当前对象。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pcq5yHNV-1678672774030)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220729104142889.png)]
-
用法
哪个对象调用了方法,方法内this就代表哪个对象
-
this的注意事项和使用细节
- this关键字可以用来访问本类的属性、方法、构造器
- this用于区分当前类的属性和局部变量
- 访问成员方法的语法:this.方法名(参数);
- 访问构造器语法:this(参数);注意只能在构造器中使用(即只能在构造器中访问另外一个构造器)
- this不能再类定义的外部使用,只能在类定义的方法中使用
this()关键字
用于调用本类下的另一个构造器,在构造器内使用。
大作业(猜拳游戏)
import java.util.Random;
import java.util.Scanner;
public class Homeword14 {
public static void main(String[] args){
int ch;
Scanner myScanner;
GameList gameList = new GameList();
do{
System.out.println("========游戏面板========");
System.out.println(" 1.开始游戏 2.游戏记录 ");
System.out.println(" 3.退出游戏");
myScanner = new Scanner(System.in);
ch = myScanner.nextInt();
switch(ch){
case 1:
while(!(new MoraGame(gameList).gamer.hand == -1)){};
break;
case 2:
gameList.showGameList();
break;
}
}while(ch != 3);
System.out.println("Bye~");
}
}
class GameList{
String gameList[];
public void updateGameList(String result){
if(gameList == null){
gameList = new String[1];
}else{
String newGameList[] = new String[gameList.length+1];
for(int i = 0 ; i < gameList.length ; i++){
newGameList[i] = gameList[i];
}
gameList = newGameList;
}
gameList[gameList.length-1] = result;
}
public void showGameList(){
if(gameList == null){
System.out.println("暂无游戏记录~");
return;
}
System.out.println("局数\t玩家\t电脑\t结局");
for(int i = 0 ; i < gameList.length ; i++){
System.out.println((i+1)+"\t"+gameList[i]);
}
}
}
class MoraGame{
String gameResult;
Gamer gamer = new Gamer();
Robot robot = new Robot();
public MoraGame(GameList gameList){
if(gamer.hand == -1){
return;
}
int result = judge(gamer.hand, robot.hand);
gameResult = translator(gamer.hand) + "\t" + translator(robot.hand) + "\t" + translator(result);
storeGameList(gameList);
System.out.println("========比赛结果========");
System.out.println("玩家\t电脑\t结局");
System.out.println(gameResult);
}
/**
* @param gamerHand 玩家的手势
* @param robotHand 电脑的手势
* @return 输:-1 赢:1 平局:0
*/
public int judge(int gamerHand,int robotHand){
if((gamerHand == 0 && robotHand == 1) || (gamerHand == 1 && robotHand == 2) || (gamerHand == 2 && robotHand == 0)){
return 5;
}else if(gamerHand == robotHand){
return 4;
}else{
return 3;
}
}
public void storeGameList(GameList gameList){
gameList.updateGameList(gameResult);
}
public String translator(int s){
String res = null;
switch(s){
case 0:
res = "石头";
break;
case 1:
res = "剪刀";
break;
case 2:
res = "布";
break;
case 3:
res = "输了";
break;
case 4:
res = "平局";
break;
case 5:
res = "赢了";
break;
}
return res;
}
}
class Gamer{
int hand;
/**
* 构造器
*/
public Gamer(){
hand = getHand();
}
/**
* @return res返回一个0-2的整数 0-石头 1-剪刀 2-布
*/
public int getHand(){
System.out.println("请输入你要出的拳头(0-拳头,1-剪刀,2-布,-1-退出游戏)");
Scanner myScanner = new Scanner(System.in);
int res = myScanner.nextInt();
if(res == -1){
return res;
}
while(!(res < 3 && res >=0)){
System.out.println("数字输入错误");
myScanner = new Scanner(System.in);
res = myScanner.nextInt();
}
return res;
}
}
class Robot{
int hand;
/**
* 构造器
*/
public Robot(){
hand = getHand();
}
/**
* @return res返回一个0-2的整数 0-石头 1-剪刀 2-布
*/
public int getHand(){
Random r1 = new Random();
int res = r1.nextInt(3);
return res;
}
}
OOP进阶
Idea使用技巧
-
常用快捷键
- 删除当前行 ctrl+ X
- 复制当前行 crtl + D
- 补全代码 alt + /
- 单行注释 ctrl + /
- 格式化代码 ctrl + alt + L
- 鼠标右键菜单+生成,可快速生成构造器
- 查看一个类的层级关系 crtl + H
- 自动分配变量名 alt + Enter
-
自定义模板
file -> settings -> editor -> live templates ->可以查看/增加模板快捷键
public class TestTemplate { //main + tab public static void main(String[] args) { //sout + tab System.out.println(); //fori + tab for (int i = 0; i < 1; i++) { } } }
包Package
-
应用场景(相当于文件夹)
现在有两个程序员共同开发一个java项目,程序员xiaoming希望定义一个类取名Dog,程序员xiaoqiang也想定义一个类叫Dog。两个程序员为此还吵了起来,怎么办?
-
包的三大作用
- 区分相同名字的类
- 当类很多的时候,可以很好的管理类[看JAVA API文档]
- 控制访问范围
-
包基本语法
package com.xuejava;
- package 关键字,表示打包
- com.xuejava 表示包名
-
包的本质分析(原理)
包的本质 实际上就是创建不同的文件夹/目录来保存类文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KRFRM0SF-1678672774031)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220730094604528.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cg8iCRRC-1678672774033)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220730102103105.png)]
-
快速入门
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Syp7rkIF-1678672774034)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220730103453009.png)]
-
包的命名
-
命名规则
只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字
-
命名规范
一般是小写字母+小圆点
com.公司名.项目名.业务模块名
比如:
com.sina.crm.user //用户模块 com.sina.crm.order //订单模块 com.sina.crm.utils //工具模块
-
-
常用的包
一个包下,包含很多类,java中常用的包有:
java.lang.* //lang包是基本包,默认引入,不需要再引入 java.util.* //util包,系统提供的工具包,工具类,如Scanner java.* //网络包,网络开发 java.awt.* //是左java的界面开发,GUI
-
如何引入包
我们引入一个包的主要目的是使用包下面的类,
比如:import java.util.Scanner;就知识引入一个类Scanner
import java.util.*;表示将java.util包中所有类引入
案例:使用系统提供Arrays完成数组排序
package com.hspedu.pkg; import java.util.Arrays; public class Import01 { public static void main(String[] args) { int arr[] = {-1, 20, 2, 13, 3}; Arrays.sort(arr); for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + "\t"); } } }
-
注意事项和使用细节
- package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package
- import指令位置放在package的下面,在类定义前面,可以有多句且没有顺序
⚪面向对象三大特征(封装、继承、多态)
访问修饰符
基本介绍
java提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
- 公开级别:用public修饰,对外公开
- 受保护级别:用protected修饰,对子类和同一个包中的类公开
- 默认级别:没有修饰符号(或default),向同一个包的类公开
- 私有级别:用private修饰,只有类本身可以访问,不对外公开
注意事项
- 修饰符可以用来修饰类中的属性,成员方法以及类
- **只有默认和public才能修饰类!**并且遵循上图访问权限的特点。
- 因为没有学习继承,因此关于在子类中的访问权限,我们讲完子类后,再回头
- 子类继承了所有属性和方法(包括static),但私有(private)属性和方法在同一包中不能在子类直接访问,要通过公共的方法去访问
- 成员法规范的访问规则和属性完全一致
封装Encapsulation
简介
封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作[方法],才能对数据进行操作。
举例:对电视机的操作就是典型封装
打开电视,关闭电视在电视内部是一个复杂的过程,但是遥控器可以轻松一个按键进行操作
- 封装的理解和好处
- 隐藏实现细节:方法(连接数据库)<–调用(传入参数…)
- 可以对数据进行验证,保证安全合理
封装的实现步骤
-
将属性进行私有化private【不能直接修改属性】
-
提供一个公共的set方法,用于对属性判断并赋值
public void setXxx(类型 参数名){ //加入数据验证的业务逻辑 属性 = 参数名; }
-
提供一个公共的get方法,用于获取属性的值
public XX getXxx(类型 参数名){ //权限判断,Xxx某个属性 return Xxx; }
快速入门案例
package com.hspedu.encap;
public class Encapsulation01 {
//如果要使用快捷键运行,需要先配置主类
//第一次,我们使用鼠标点击形式运行程序,后面就可以用
public static void main(String[] args) {
Person person = new Person();
person.setName("邓圣君");
person.setAge(30);
person.setSalary(30000);
System.out.println(person.info());
//如果我们自己使用构造器指定属性
Person smith = new Person("smith", 2000, 50000);
System.out.println("====smith信息====");
System.out.println(smith.info());
}
}
class Person{
public String name;
private int age;
private double salary;
public Person() {
}
//有三个属性的构造器
public Person(String name, int age, double salary) {
setName(name);
setAge(age);
setSalary(salary);
}
//自己写set和get方法太慢,我们可以使用快捷键(右键->生成->Getter & Setter)
public String getName() {
return name;
}
public void setName(String name) {
if(name.length()>=2&&name.length()<=6){
this.name = name;
}else{
System.out.println("名字长度不对,需要2-6个字符,赋值默认名字");
this.name = "NoName";
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age >=1&&age<=120){
this.age = age;
}else {
System.out.println("年龄范围错误!已赋值默认18");
this.age = 18;
}
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String info(){
return "信息为 name="+name+" age="+age+" 薪水="+salary;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UiEXywAw-1678672774036)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220730155747617.png)]
继承Extends
简介
-
为什么需要继承?
我们编写了两个类,一个是pupil类(小学生),一个是Graduate(研究生)
问题:两个类的属性和方法有很多是相同的,怎么办?
=>答:继承(代码复用)
-
基本介绍和示意图
继承可以解决代码复用,让我们的编程更加靠近人类思维。当多个类存在相同变量的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cA0raTwG-1678672774037)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220730172138900.png)]
-
继承的基本语法
class 子类 extends 父类{ }
- 子类会自动拥有父类定义的属性和方法
- 父类又叫超类,基类
- 子类又叫派生类
深入细节
-
子类继承了所有属性和方法(包括static),但私有(private)属性和方法在同一包中不能在子类直接访问,要通过公共的方法去访问
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oR1LvzLJ-1678672774038)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220730173806854.png)]
-
子类必定会调用父类的构造器,即super(),完成父类参数的初始化
public class Sub extends Base{ public Sub(){ super();//默认调用父类的无参构造器,不会实例化父类 System.out.println("子类构造器sub()..."); } public void sayOk(){ //我们发现,父类的非private属性和方法,都可以访问 System.out.println(n1+" "+n2+" "+" "+n3+" "+"n4不能访问"+getN4()); } }
-
当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用
super()
去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。(人话:如果父类不实例化,子类拿不到父类特有的值,所以必须用父类构造器初始化这些值,并不会实例化父类)【实例化详解可以看小知识】 -
如果希望指定去调用父类的某个构造器,则显式的调用一下
-
super()在使用时,需要放在构造器第一行
-
super()和this()都只能放在构造器第一行,因此这俩个方法不能共存在一个构造器。
-
java所有类都是Object类的子类,Object是所有类的基类。
-
父类构造器的调用不限于直接父类!将一致往上追溯到Object类(顶级父类)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WR3KoOut-1678672774039)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220731103140835.png)]
-
子类最多只能继承一个父类(即直接继承),即java中是单继承机制。
思考:如何让A类继承B类和C类?
答:让A类继承B类,再让B类继承C类。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y7inWADG-1678672774041)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220731103543324.png)]
-
不能滥用继承,子类和父类必须满足is-a的逻辑关系
举例:
Person is a Music?错
Cat is a animal?对
super()关键字
-
基本介绍
super代表父类引用,用于访问父类的属性、方法、构造器
-
基本语法
-
访问父类的属性,但不能访问父类的private属性
super.属性名;
-
访问父类的方法,不能访问父类的private方法
super.方法名(参数列表);
-
访问父类的构造器:
super(参数列表);只能放在构造器的第一句,只能出现一句!
-
-
细节
- 调用父类构造器的好处:分工明确,父类属性由父类初始化,子类的属性由子类初始化
- 当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super、this、直接访问是一样的效果
- super的访问不限于直接父类,如果父类的上级有同名的成员,也可以使用super去访问爷爷类;如果多个积累(上级类)中都有同名方法,使用super访问遵循就近原则
继承的本质
-
案例
我们看一个案例来分析当子类继承父类,创建子类对象时,内存中到底发生了什么?
重点:当子类对象创建好后,建立了怎样的查找关系- 首先看子类是否有该属性
- 如果子类有这个属性,并可以访问(还有private情况),则返回信息
- 如果子类没有这个属性,就看父类有没有这个属性
- 如果父类没有就按照(3)规则,继续向上一级查找,直到Object
PS:如果父类有这个属性,但不能访问(private),则报错,并不会再向上查找。(但可以通过父类的公共方法去访问)
虽然说是向上查找的机制,但可以简单概括为覆盖的意思
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-36E4Dtp5-1678672774042)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220731121231727.png)]
步骤:
- 加载类信息,由object到grandpa到father再到son
- 在堆创建son对象空间,首先(object在此跳过)分配grandpa属性,再分配father属性,最后分配son属性(分配属性并不代表创建一个新对象)
- 将Son对象空间地址赋值给栈中的son变量。
方法的重写/覆盖Override
-
基本介绍
简单的说:方法覆盖(重写)就是子类有一个方法,和父类的某个方法名称、返回类型、参数一致,那么我们就是说子类的这个方法覆盖了父类的那个方法。
-
细节
- 子类的方法的参数,方法名称,要和父类方法的参数,方法名称完全一样【类似于方法重载】
- 子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类,比如:
父类的方法返回类型是Object,子类的方法返回类型是String【区别于方法重载】 - 子类方法不能缩小父类方法的访问权限【区别于方法重载】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TzF2aBAj-1678672774043)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220802100817670.png)]
多态Polymorphic
简介
-
先看一个问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q0PyRme9-1678672774045)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220802111939194.png)]
-
基本介绍
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
多态的具体体现
-
方法的多态:重写和重载就体现多态
//方法重载体现多态 A a = new A(); //我们传入不同参数就会调用不同sum方法 System.out.println(a.sum(1,2)); System.out.println(a.sum(1,3,2));
//方法重写体现多态 B b = new B(); a.say(); b.say();
-
对象的多态【重点】
(1)一个对象的编译类型和运行类型可以不一致
(2)编译类型在定义对象时,就确定了,不能改变
(3)运行类型是可以变化的
(4)编译类型看定义时 ‘=’ 号的左边,运行类型看 ‘=’ 号的右边
Animal animal = new Dog();//[animal编译类型是Animal,运行类型是Dog] animal = new Cat();//[animal的运行类型变成了Cat,编译类型仍然是Animal] //Cat is Animal思想
即父类可引用子类对象
编译类型是约束,运行类型是行为,行为必须遵循约束
//主人给小狗喂食骨头
public void feed(Dog dog,Bone bone){
System.out.println("主人 "+name+" 给 "+dog.getName()+" 吃 "+bone.getName());
}
public void feed(Cat cat,Fish fish){
System.out.println("主人 "+name+" 给 "+cat.getName()+" 吃 "+fish.getName());
}
//如果动物很多,食物很多
//---》feed方法很多,不利于管理和维护
//pig --》 rice
// tiger --》meat...
//使用多态思想
public void feed(Animal animal,Food food){
System.out.println("主人 "+name+" 给 "+animal.getName()+" 吃 "+food.getName());
}
//animal 编译类型Animal,可以指向Animal子类对象
//food 编译类型是Food,可以指向Food子类对象
多态的注意事项和细节讨论(内含向上转型)
多态的前提是:两个对象(类)存在继承关系
多态的向上转型
本质:父类的引用指向了子类的对象(对象不会变,变的是地址),但是编译器认为成员只有父类的成员,运行遵循继承规则
[有点类似于基础篇的数据类型的转换]
特点:
- 可以调用父类中所有成员(需遵守访问权限)
- 不能调用子类中特有成员
- 最终运行效果看子类的具体实现(仅方法,若构造器修改了属性,则规则也生效)
Animal animal = new Cat();
//可以调用父类中所有成员(需遵循访问权限)
//但是不能调用子类的特有成员
//因为在编译阶段,能调用哪些成员,是由编译类型决定的
animal.catchMouse();//错误
//遵守访问权限
animal.show();//错误
//最终运行效果看子类的具体实现
animal.eat();
//输出:猫吃鱼,而不是 吃.
多态向下转型
- 语法: 子类类型 引用名 = (子类类型)父类引用;
- 只能强转父类的引用,不能强转父类的对象;
- 要求父类的引用必须指向的是当前目标类型的对象(就是上面animal = new Cat()的情况)
- 当向下转型后,可以调用子类类型中所有成员
Animal animal = new Cat();
//可以调用父类中所有成员(需遵循访问权限)
//但是不能调用子类的特有成员
//因为在编译阶段,能调用哪些成员,是由编译类型决定的
Cat cat = (Cat)animal;
cat.catchMouse();
多态属性下的细节
-
属性没有重写之说!属性的值看编译类型
package com.hspedu.poly_.detail; public class PolyDetail02 { public static void main(String[] args) { Base base = new Sub(); System.out.println(base.count);//输出10 } } class Base{ int count = 10; } class Sub extends Base{ int count = 20; }
-
instanceOf比较符,用于判断对象的类型是否为XX类型或XX类型的子类
子类型对象 instanceof 父类/本类 结果为true;
父类型对象 instanceof 子类 结果为false;[即true表示 对象 是 指定类型的子类或者本类 , false表示 对象 是 指定类型的父类 或者 不属于该类型]
[如果instanceof为true,则 该对象 必定能使用Student类方法]多态数组详解
package com.hspedu.poly_.detail; public class PolyDetail03 { public static void main(String[] args) { BB bb = new BB(); System.out.println(bb instanceof BB);// true System.out.println(bb instanceof AA);// true AA aa = new BB(); System.out.println(aa instanceof AA);// true System.out.println(aa instanceof BB);// true } } class AA{}//父类 class BB extends AA{}//子类
动态绑定机制
package com.hspedu.poly_.dynamic_;
public class DynamicBinding {
public static void main(String[] args) {
A a = new B();
System.out.println(a.sum());
System.out.println(a.sum1());
}
}
class B extends A{
public int i = 20;
// public int sum(){
// return i+20;
// }
public int sum1(){
return i+10;
}
public int getI(){
return i+10;
}
}
class A {
public int i = 10;
public int sum(){
return getI()+10;
}
public int sum1(){
return i+10;
}
public int getI(){
return i;
}
}
- 当调用对象方法的时候,该方法会和该对象的内存地址(运行类型)绑定
即:父类的方法需要调用父类子类同名方法的时候,会优先选择与内存绑定了的方法,例子如上 - 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用,就是调用哪里的属性[P314] [P358]
- 上述两点归纳:方法从运行类型处找,属性在哪个方法调用用的就是哪个方法里的。
琐碎知识
- 直接访问属性就是看编译类型,直接访问方法就是看运行类型.(属性没有重写!)
- getClass()方法可以查看运行类型
多态数组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1fB6TsOQ-1678672774047)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220802195836017.png)]
public class PolyArray {
public static void main(String[] args) {
Person person[] = new Person[5];
person[0] = new Person("jack",20);
person[1] = new Student("jack",18,100);
person[2] = new Student("smith",19,30.1);
person[3] = new Teacher("scott",30,20000);
person[4] = new Teacher("king",50,25000);
for (int i = 0; i < person.length; i++) {
//提示:person[i]编译类型是Person,运行类型是根据实际情况由JVM来判断
System.out.println(person[i].say());//动态绑定机制
}
}
}
public class Person {
private String name;
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 Person(String name, int age) {
this.name = name;
this.age = age;
}
private int age;
public String say() {
return name + " " + age;
}
}
public class Student extends Person {
private double score;
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public String say() {
return super.say() + " " + score;
}
}
public class Teacher extends Person {
private double salary;
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String say() {
return super.say() + " " + salary;
}
}
public class PolyArray {
public static void main(String[] args) {
Person person[] = new Person[5];
person[0] = new Person("jack",20);
person[1] = new Student("jack",18,100);
person[2] = new Student("smith",19,30.1);
person[3] = new Teacher("scott",30,20000);
person[4] = new Teacher("king",50,25000);
for (int i = 0; i < person.length; i++) {
//提示:person[i]编译类型是Person,运行类型是根据实际情况由JVM来判断
System.out.println(person[i].say());//动态绑定机制
//这里大家就要动脑筋。如果instanceof为true,则 该对象 必定能使用Student类方法
if(person[i] instanceof Student){
Student student = (Student)person[i];
student.study();
} else if (person[i] instanceof Teacher) {
Teacher teacher = (Teacher)person[i];
teacher.teach();
}else {
System.out.println("你的类型有误,请自己检查");
}
}
}
}
Object类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SlWrlXsG-1678672774048)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220803100129272.png)]
equals方法
-
==
(1)==:如果判断基本类型,判断的是值是否相等.
(2)==:如果判断的是引用类型,判断的是地址是否相等,即判定是不是同一个对象
-
equals源码查看
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
-
equals默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等.
比如Integer,String[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sA2H1WFc-1678672774049)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220803114657503.png)]
-
重写equals方法
hashCode方法
- 提高具有哈希结构的容器效率
- 两个引用,如果指定的是同一个对象,则哈希值肯定是一样的!
- 两个引用,如果指向的是不同的对象,则哈希值不一样
- 哈希值主要根据地址号得出,不能完全将哈希值等价于地址
- 后面在集合中,hashCode如果需要的话,也会重写
toString
-
基本介绍
默认返回 : 全类名(包名+类名) + @ + 哈希值的十六进制
子类往往重写toString方法,用于返回对象的属性信息[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bwhTg3pq-1678672774049)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220803134459025.png)]
-
重写toString方法,打印对或拼接对象时,都会自动调用该对象的toString形式
演示:public String toString() { return "Monster{" + "name='" + name + '\'' + ", job='" + job + '\'' + ", sal=" + sal + '}'; }
-
当直接输出一个对象时,toString方法会被默认调用.
finalize(类似于c++的析构函数)
- 当对象被回收时,系统自动调用该对象的finalize方法. 子类可以重写此方法 , 做一些释放资源的操作
- 什么时候被回收 : 当某个对象没有任何引用时 , 则jvm就认为这个对象是个垃圾对象 , 就会使用垃圾回收机制来销毁该对象,在销毁前,会先调用finalize方法.
- 垃圾回收机制的调用,是由系统来决定(即有自己的gc算法) , 也可以通过System.gc()主动触发垃圾回收机制.
断点调试
简介
-
一个需求
- 在开发中,查找错误时,需要一步步查看源代码的执行过程,从而发现错误所在
- 重要提示 : 在点点调试过程中,是运行状态,是以对象的运行类型来执行的
-
断点调试介绍
- 断点调试是指程序的某一行设置一个断电,调试时候,程序运行到这一行就会停住,然后你可以一步步往下调试,调试的过程中可以看到哥哥变量当前的值 , 出错的话 , 调试到出错的代码行即显示错误 , 停下 . 进而分析从而找到bug
- 断点调试是程序员必须掌握的技能
- 断点调试也能帮助我们查看java底层源代码的执行过程,提尕程序员的Java水平
-
断点调试的快捷键
- F7(跳入)跳入方法内
- F8(跳过)逐行执行代码
- shift + F8(跳出)跳出方法
- F9(resume,执行到下一个断点)
-
idea如何进入jdk原码?
使用强迫进入
房屋出租系统
项目需求说明
实现基于文本界面的《房屋出租软件》
能够实现对房屋信息的添加、修改和删除(用数组实现),并能够打印房屋明细表
项目界面
-
主菜单
-
新增房源
-
查找房源
-
删除房源
-
修改房源
项目设计—程序框架图
分层模式=>当软件比较复杂,需要管理模式(mcv–model control view)
实现
准备工具类Utility,提高开发效率
在实际开发中,公司都会提高相应的工具类和开发库,可以提高开发效率,程序员也需要能够看懂别人写的代码,并能够正确调用
- 了解Utility类的使用
- 测试Utility类
完成House类
编号 房主 电话 地址 月租 状态(未出租/已出租)
package com.hspedu.houserent.domain;
public class House {
private int id;//编号
private String name;//房主
private String phone;//电话
private String address;//地址
private double rent;//月租
private String state;//状态(未出租/已出租)
//构造器和setter,getter
public House(int id, String name, String phone, String address, double rent, String state) {
this.id = id;
this.name = name;
this.phone = phone;
this.address = address;
this.rent = rent;
this.state = state;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public double getRent() {
return rent;
}
public void setRent(double rent) {
this.rent = rent;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//为了方便我们查看对象,重写toString
@Override
public String toString() {
return "House{" +
"id=" + id +
", name='" + name + '\'' +
", phone='" + phone + '\'' +
", address='" + address + '\'' +
", rent=" + rent +
", state='" + state + '\'' +
'}';
}
}
显示主菜单和退出软件功能
化繁为简:一个一个功能实现
说明:实现功能的三部曲[明确功能->思路分析->代码实现]
-
功能说明:
用户打开软件,可以看到主菜单,可以退出软件。
-
思路分析:
在HourseView.java中,编写一个方法mainMenu,显示菜单
-
代码实现:
package com.hspedu.houserent.view;
import com.hspedu.houserent.utils.Utility;
/*
* 1.显示界面
* 2.接收用户的输入
* 3.调用HouseService完成对房屋信息的各种操作
* */
public class HouseView {
private boolean loop = true;//控制显示菜单循环
private char key = ' ';//接收用户选择
// 显示主菜单
public void mainMenu(){
do{
System.out.println("===============房屋出租系统菜单===============");
System.out.println("\t\t\t1.新 增 房 屋");
System.out.println("\t\t\t2.查 找 房 屋");
System.out.println("\t\t\t3.删 除 房 屋 信 息");
System.out.println("\t\t\t4.修 改 房 屋 信 息");
System.out.println("\t\t\t5.房 屋 列 表");
System.out.println("\t\t\t6.退出");
System.out.print("请输入你的选择(1-6):");
key = Utility.readChar();
switch (key){
case '1':
System.out.println("新 增 房 屋");
break;
case '2':
System.out.println("查 找 房 屋");
break;
case '3':
System.out.println("删 除 房 屋 信 息");
break;
case '4':
System.out.println("修 改 房 屋 信 息");
break;
case '5':
System.out.println("房 屋 列 表");
break;
case '6':
System.out.println("退 出");
loop = false;
break;
}
}while(loop);
}
}
显示房屋列表功能
-
功能说明:
-
思路分析:
需要编写HouseView.java和HouseService.java
-
代码实现:
添加房屋信息功能
-
功能说明:
-
思路分析:
-
代码实现:
规定:新添加的房屋id按照自增长来
删除房屋信息功能
- 思路分析:
- 房屋id是否存在
- 代码实现:
OOP高级
目录
- 类变量和类方法
- 理解main方法语法
- 代码块
- 单例设计模式
- final关键字
- 抽象类
- 接口
- 内部类
类变量和类方法(静态static)
没有静态类的说法
类变量/静态变量
-
提出问题
有一群小孩再堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?编写程序解决
-
快速入门
思考:
如果设计一个int count 表示总人数,我们再创建一个小孩时,就把count加1,并且count是所有对象共享的就ok了!
-
类变量内存布局
- 有的说法是在堆空间中
- 有的说法是在方法区的静态区中(jdk7以及之前)
可以肯定的是,类变量是被对象共享的,因此类变量其实是引用类型,地址被引用了
Java static变量保存在哪?CSDN博客
java中的静态变量和Class对象究竟存放在哪个区域? - 知乎 (zhihu)
共识:
- static变量是同一个类所有对象共享
- static变量是在类加载的时候就生成,且只加载一次!
-
注意事项
-
什么时候需要用类变量(使用场景)
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如:定义学生类,统计所有学生共交多少钱。Student(name,fee)
-
类变量与实例变量(普通属性)区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
-
加上static称为类变量或静态类型,否则称为实例变量/普通变量/非静态变量
-
类变量可以通过 类名.类变量名 或者 对象名.类变量名 来访问,但java设计者推荐我们使用 类名.类变量名 方式访问。【前提是满足修饰符的访问权限和访问】
-
实例变量不能通过 类名.类变量名 方式访问
-
类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要加载类了,就可以使用类变量了。
-
类变量的生命周期是随类的家宅开始,随类的消亡而销毁
-
类方法/静态方法
-
类方法景点的使用场景
-
当方法中不涉及到任何和对象相关的成员,则可以将方法涉及成静态方法,提高开发效率
比如:工具类中的方法utils,Math类,Arrays类,Collections集合类
-
小结
在程序实际开发中,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一维数组,冒泡排序,完成某个计算任务等…
-
-
注意细节
-
类方法和普通方法都是随着类的加载而加载,将构造信息存储在方法区
⭐类方法中无this的参数
⭐普通方法中隐含着this参数
这个细节使得静态方法的使用场景比较独特
-
类方法可以通过类名调用,也可以通过对象名调用
-
普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用
-
main方法语法
-
深入理解main方法
解释main方法的形式:public static void main(String[] args){}
-
java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
-
java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
-
该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
java 执行的程序 参数1 参数2 参数3
-
-
特别提示
- 在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性
- 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
-
如何在idea中传递参数
代码块
-
基本介绍
代码化块又称为初始化块,属于类中的成员[即 是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{}包围起来
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用勇敢对象或者类显式调用,而是加载类时或创建对象时,隐式调用
-
基本语法
[修饰符]{
代码
};
注意:
- 修饰符可选,要写的话,也只能写static
- 代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块
- 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断)
- ;可以写上,也可以省略
-
解说
- 相当于另一种形式的构造器(对构造器的补充机制),可以做初始化的操作
- 场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的复用性
-
细节
-
static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。
如果是普通代码块,每创建一个对象,就执行。 -
类声明时候被加载?⭐
- 创建对象实例时(new)
- 创建子类对象实例,父类也会被加载
- 使用类的静态成员时(静态属性、方法)
-
普通的代码块,在创建对象实例时,会被隐式的调用。
被创建一次,就会调用一次
如果只是使用类的静态成员时,普通代码块并不会执行。
-
创建一个对象时,在一个类调用顺序是:⭐
- 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果又多个静态代码块和多个静态变量初始化,则按他们的定义的顺序调用)
- 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
- 调用构造函数
-
构造器的最前面其实隐含了super()和调用普通代码块
静态相关的代码块,属性初始化,在类加载时就执行完毕,因此是优先于构造器的普通代码块执行的 -
我们看一下创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:⭐
- 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 父类的构造方法
- 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 子类的构造方法 //面试题
-
静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员
-
单例设计模式
什么是设计模式
- 静态方法和属性的经典使用
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,不同的棋局,我们就用不同棋谱,免得我们自己再思考和摸索。
什么是单例模式
- 所谓类的单例设计模式,就是采取一定的方法保证再整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
- 单例模式有两种方法:①饿汉式②懒汉式
单例模式应用实例
演示饿汉式和懒汉式单例模式的实现
步骤如下:
-
构造器私有化(防止直接new一个新对象)
-
类的内部创建对象
-
向外暴露一个静态的公共方法。getInstance()
-
代码实现:(饿汉式)-只要类被加载,对象就生成了(急不急啊,急死你得了)【还没用到这个对象,就已经生成了对象】
public class SingleTon01 { public static void main(String[] args) { GirlFriend gf = GirlFriend.getInstance(); } } //有一个类,GirlFriend //只能有一个女朋友 class GirlFriend{ private String name; //如何保证我们只能创建一个 girlFriend对象 //步骤【单例模式-饿汉式】 //1.将构造器私有化 private GirlFriend(String name) { this.name = name; } //2.在类的内部直接创建静态对象 private static GirlFriend gf = new GirlFriend("小红"); //3.提供一个公共static方法 返回gf对象(静态方法只能调用静态成员) public static GirlFriend getInstance(){ return gf; } //总结:通过类的加载只会加载一次的特性,当静态方法被调用,类对象加载就只会开辟唯一一个空间, //生成唯一一个对象,并返回。 }
-
代码实现:(懒汉式)-使用的时候才会创建实例
package com.hspedu.single_; import com.sun.org.apache.bcel.internal.generic.IF_ACMPEQ; /* * 演示懒汉式的单例模式 * */ public static void main(String[] args) { Cat cat = Cat.getInstance(); } //希望程序运行过程中只能养一只cat class Cat{ private String name; //步骤 //1.仍然把构造器私有化(防止在外部再new一个对象) private Cat(String name) { this.name = name; } //2.定义一个static静态属性对象(区别于饿汉式) private static Cat cat; //3.提供一个public的static,可以返回一个cat对象(区别于饿汉式),在调用方法的时候才生成对象, // 即使类被加载,也不会立即生成对象 public static Cat getInstance(){ if(cat == null){ cat = new Cat("小咪"); } return cat; } //总结:通过类的加载只会加载一次的特性,当静态方法被调用,类对象加载就只会开辟唯一一个空间, //生成唯一一个对象,并返回。 //于饿汉式区别: //3.提供一个public的static,可以返回一个cat对象(区别于饿汉式),在调用方法的时候才生成对象, // 即使类被加载,也不会立即生成对象 }
-
懒汉式VS饿汉式
-
二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建
-
饿汉式不存在线程安全,懒汉式存在线程安全问题(后面学习线程后完善)
同时执行存在创建3个对象的情况
-
**饿汉式存在浪费资源的可能。**因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉hi是使用时才创建,就不存在这个问题。
-
在外面javaSE标准类中,java.lang.Runtime就是经典的单例模式-饿汉式
-
final关键字
- 基本介绍
final可以修饰类、属性、方法和局部变量。
在某些情况下,程序员可能有以下需求,就会使用到final:
- 当不希望类被继承时,可以使用final修饰
- 当不希望父类的否个方法被子类覆盖/重写(override)时,可以用final关键字修饰。
- 当不希望类的某个属性的值被修改,可以用final修饰【类似于JS声明常量】
- 当不希望某个局部变量被修改,可以使用final修饰【类似于JS声明常量】
-
食用细节
-
final修饰的属性一般又叫常量,一般用 XX_XX_XX来命名 如:税率 TAX_RATE
-
final修饰的属性在定义时,必须赋初值,并且以后不能再修改,复制可以再如下位置【选择一个位置赋初值即可】:
- 定义时:如public final double TAX_RATE = 0.08;
- 在构造器中
- 在代码块中
-
如果final修饰的属性是静态的,则初始化的位置只能是
- 定义时
- 静态代码块中
不能在构造器中赋值,因为类加载的时候就需要初始化了,不能等到实例后才初始化。
-
final类不能继承,但是可以实例化对象。
-
如果类不是final类,但是含有final方法,则该方法虽然不能重写但是可以被继承
-
一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。(不能继承自然也不能重写里面的方法了)
-
final不能修饰构造方法(即构造器)
-
final和static往往搭配使用,效率更高,底层编译器做了优化处理**(即,不会导致类加载!!!⭐)**
-
包装类(Integer,Double,Float,Boolean等都是final),String也是final类
-
⚪抽象类/abstract关键字
-
提出问题
一个小问题,看个程序
父类方法的不确定性:这是个动物,但是动物分荤食和素食,里面又分很多种食物,eat()具有不确定性
小结:当父类的某些方法,需要声明,但是有不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类
-
简介
当一个父类的一些方法不能确定时,可以用abstract关键字来修司该方法,这个方法就是抽象方法,用abstract来修饰该类就是抽象类
-
用abstract关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名{ }
-
用abstract关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体
-
抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类
-
抽象类,是考官比较爱问的知识点,在框架和设计模式使用比较多
-
-
细节
-
抽象类不能被实例化
-
抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
-
一旦类包含了abstract方法,则这个类必须声明为abstract
-
abstract只能修饰类和方法,不能修饰属性和其它的。
-
抽象类可以拥有任意成员【抽象类本质还是类】,比如:非抽象方法,构造器,静态属性等等…
-
抽象方法不能有主体(代码块)
-
如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,触发它自己也声明为abstract类
-
抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的(且自带public)
private:重写不能提高访问权限
final:无法继承或重写
static:静态仅在类加载时执行,无法重写或继承
-
-
案例
我们看看如何把Animal做成抽象类,并让子类Cat类实现
-
实践-模板设计模式
需求:
- 有多个类,完成不同的任务job
- 要求统计得到各自完成任务的实践
实践:
设计一个抽象类(Template),能完成如下功能:
- 编写方法calculateTime(),可以计算某段代码的耗时时间
- 编写抽象方法job()
- 编写一个子类Sub,继承抽象类Template,并实现code方法。
- 编写一个测试类TestTemplate,看看是否好用
⚪接口Interface
-
为什么有接口?(场景)
usb插槽就是显示生活中的接口
你可以把手机、相机、u盘都插在usb插槽上,而不用担心哪个插槽是专门插哪个,原因是做usb插槽的厂家和做各种设备的厂家都遵守了统一的规定包括尺寸,排线等等…
-
快速入门
这样的设计需求在java编程/php//go中也是会大量存在的,我曾经说过,一个程序就是一个世界,在显示世界存在的情况,在程序中也会出现。我们用程序来模拟一下。
接口:规定需要有某个功能,但是功能的具体实现交给厂家(USER)实现
基本介绍
- 接口默认普通方法都是抽象方法,没有代码块,因此每个方法implements之后都需要完成这些方法
- JDK8.0之后,可以有默认实现方法,需要使用default关键字来修饰
- JDK8.0之后,可以有静态方法
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。语法:
interface 接口名{
//属性
//方法,在接口中,可以省略abstract关键字
}
class 类名 implements 接口{
//自己的属性
//自己的方法
//必须实现的接口的抽象方法
}
小结:
- 在JDK7.0以前,接口里的所有方法都没有方法体,即都是抽象方法
- JDK8.0之后,接口类可以有静态方法,默认方法(default),也就是说接口中可以有方法的具体实现
深入讨论
对初学者来讲,理解接口的概念不算太难,难的是不知道声明时候使用接口,下面我例举几个应用场景:
-
说现在要制造战斗机,武装直升机。专家只需把飞机需要的功能/规格定下来即可,然后让别的人具体实现即可。
-
现在有一个项目经理,管理三个程序员,开发一个软件,为了控制和管理软件,项目经理可以定义一些接口,然后由程序员具体实现。
实际要求:三个程序员,编写三个类,分别完成对MYSQL,Oracle,DB2数据库的连接connect,close…
注意事项
-
接口不能被实例化
-
⭐接口中方法默认修饰符:public abstract,接口中抽象方法,可以不用abstract修饰符和public修饰符 图示:
-
一个普通类实现接口,就必须将该接口的所有方法都实现,可以使用alt+enter或alt+i来实现重写
-
抽象类implements接口,可以不用实现接口的方法
-
一个类可以同时实现多个接口【A extends B,A就不能再去继承C类了,但是接口可以多实现(implements),因此不能将接口和抽象类一样简单看成是类的另一种形式】
-
接口中的属性,只能是final的,而且是public static final修饰符。比如:int a=1;实际上是public static final int a=1;(因此必须初始化)
-
接口中属性的访问形式:接口名.属性名[不是简单的继承,是多实现的基础]
-
⭐接口不能继承其它的类,但是可以继承多个别的接口【接口可以继承接口】
interface A extends B,C{}
-
接口的修饰符只能是public和默认,这点和类的修饰符是一样的
实现接口VS继承类
小猴子继承老猴子,但是游泳和飞翔,小猴并没有鸟的翅膀和鱼的筛,小猴学会飞翔和游泳需要自己的方式去实现。
-
接口和继承的解决问题不同
继承的价值主要在于:解决代码的复用性和可维护性
接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。即更加灵活 -
接口比继承更加灵活【单继承,多实现】
接口比继承更加灵活,继承是满足is-a关系,而接口只需满足like-a关系
-
接口再一定成都上实现代码解耦[即:接口规范性+动态绑定]
接口的多态性
-
多态参数(前面案例体现)
再前面USB接口案例,USB usb,既可以接收手机对象,又可以接收相机对象,就体现了接口的多态(接口引用可以指向实现了接口的类的对象)
-
多态数组
演示一个案例:给USB数组中,存放Phone 和 Camera对象,Phone类还有一个特有方法call(),遍历数组,如果是Phone对象,除了调用USB接口定义的方法外,还需要调用Phone特有方法call()
-
接口存在多态传递现象(即C继承B后也实现了接口AInterface1)【接口也能继承】
多实现的属性访问
interface A{
int x = 0;//public static int x = 0;
}
class B{
int x = 1;
}
interface C{
int x = 2;//public static int x = 0;
}
public class D extends B implements A,C{
public static void main(String[] args){
System.out.println(A.x + super.x + C.x);//不能直接用x输出,会报错
}
}
内部类
基本介绍
一个类的内部又完整的嵌套了另一个类结构。被嵌套的累被称为内部类(inner class),嵌套其它类的类称为外部类(outer calss)。是我们类的第五大成员【思考:类的五大成员是哪些?[属性、方法、构造器、代码块、内部类]】,内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。
注意:内部类是学习的难点,同时也是重点,后面看底层原码时,有大量的内部类
基本语法:
class Outer{//外部类
class Inner{//内部类
}
}
class Other{//外部其它类
}
内部类的分类
- 定义在外部类局部位置上(比如方法、代码块内):
- 局部内部类(有类名)
- 匿名内部类(没有类名,⭐)
- 定义在外部类的成员位置上(作为成员):
- 成员内部类(没用static修饰)
- 静态内部类(使用static修饰)
局部内部类的使用
说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
- 可以直接访问外部类的所有成员,包括私有的
- 不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final
- 作用域:仅仅在定义它的方法或代码块中
- 局部内部类可以被继承,final修饰除外
- 局部内部类–访问–》外部类的成员[访问方式:直接访问]
- 外部类–访问–》局部内部类的成员**[访问方式:创建对象,再访问(注意:必须再作用域内)]**
- 外部其它类—不能访问–》局部内部类(因为局部内部类的本质是一个局部变量)
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则(如果想访问外部类的成员,则可以使用(外部类名.this.成员)去间接访问【因为不是静态的,所以需要使用this】
⭐匿名内部类的使用
说明:匿名内部类是定义再外部类的局部变量,比如:方法、代码块中,并且没用类名
基本语法:
new 类或接口(参数列表){
//类体
};
- 场景
需求:想使用一个接口并创建对象,但是这个对象的类只使用了一次,后面不再使用
传统方式方式:写一个类,实现接口,并创建对象
匿名内部类方式的方式简化了传统方式的书写。
- 系统分配的名字
在外部类的命名基础上,增加了$1的编号,如:
class Outer{
Outer o1 = new Outer(){}
}
//上面的对象的类名字为Outer$1
- 匿名内部类的运行类型判断
Father f1 = new Father("jack"){
//类体
};//@情况1
Father f1 = new Father("jack");//@情况2
情况1的匿名内部类的运行类型是 Father$1,而情况2的运行类型则是Father
两者的区别仅是有无类体。
原因:
情况1有类体,说明你在父类Father的基础上做了修改(方法的重写等…),而情况2没有类体则说明没有做修改(说明只是简单的继承),所以两者运行类型有所不同。
- 本质
实际上是一个立即创建对象过程的类,有点类似与单调模式的饿汉式,该类只使用一次,执行则立即创建唯一对象,其继承关系由参数括号左边的名字决定继承与实现。(因为其匿名特性,无法重写构造器)
- 特殊的调用成员的方式
new Person(){
public int n1 = 10;
}.n1=20;
-
细节
- 可以直接访问外部类的所有成员,包括私有的
- 不能添加访问修饰符,因为它的地位只是一个局部变量【与局部内部类一致】
- 作用域:仅仅在定义它的方法或代码块中【与局部内部类一致】
- 如果外部类和内部类的成员重名时,内部类访问的化,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问【同局部内部类一致】
- 外部其它类不能访问匿名内部类成员。【与局部内部类一致】
- 外部类通过对象来访问匿名内部类成员【与局部内部类一致】
- 在内部类中不可以用static声明成员。
-
⭐匿名内部类的最佳实践
当作实参直接传递,简洁高效【个人认为:有点像js的回调函数(即,传入一个方法作为实参,在另外的方法调用该参数(方法),可以灵活变化),java更复杂,需要创建一个匿名内部类以及对应接口达到方法的传入的目的】
interface AA{ public void cry(); } main类中://形参是接口类型AA public static void show(AA a){ a.cry(); } main方法中: show(new AA(){ public void cry(){ System.out.println("AA cry"); } });
成员内部类
说明:成员内部类是定义在外部类的成员位置,并且没用static修饰【局部内部类、匿名内部类在方法、代码块、形参中使用】
-
可以直接访问外部类的所有成员,包含私有的
-
可以添加任意访问修饰符(public、protected、默认、private)因为它的低位就是一个成员
-
作用域
和外部类的其它成员一样,为整个类体比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法。
-
成员内部类访问外部类(比如属性)【访问方式:直接访问】
-
外部类访问内部类【范围方式:创建对象】
-
其他外部类访问成员内部类
new了外部类再去new成员内部类,或通过方法返回一个对象
-
如果外部类和内部类的成员重名时,内部类访问的化,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
静态内部类
说明:静态内部类是定义在外部类的成员位置,可参考类变量知识(可通过外部类名直接访问,不需要new外部类)
-
可以直接访问外部类的所有静态成员,包括私有的,但不能直接访问非静态成员
-
可以添加任意访问修饰符(public、protected、default、private)因为它的低位就是一个成员
-
作用域:同其它的成员,为一个整体的类体
-
静态内部类-访问-外部类(比如:静态属性)【访问方式:直接访问所有静态成员】
-
外部类-访问-静态内部类【访问方式:创建对象,再访问】
-
外部其它类-访问-静态内部类【访问方式:①可通过外部类名直接访问,不需要new外部类,但是不可以通过对象名来调用。②编写一个方法,可以返回静态内部类的对象实例】
-
如果外部类和静态内部类的成员重名时,静态内部类访问,遵循就近原则,如果访问外部类的成员,则可以使用(外部类名.成员)去访问【与成员内部类差别是不使用this,因为可以直接访问的也只能是静态成员】
枚举、注解、异常、泛型
枚举Enumeration
形式1-原理
-
先看需求
要求创建季节(Season)对象,请设计完成。
class Season{ private String name; private String desc;//描述 //构造器 //getter&&setter } //因为对于季节而已,它的对象(具体值),是固定的四个,不会有更多 //按照传统设计类的思路,不能体现季节是有限的四个对象 //因此,这样的设计不好====》枚举类
-
解决方案-枚举
- 枚举是一组常量的集合
- 可以这样理解:枚举属于一种特殊的类,里面只包含一组优先的特定的对象。
-
演示
- 将构造器私有化private,目的:防止直接new
- 去掉setter相关方法:防止属性被修改
- 在Season内部,直接创建固定的对象
- 优化,类可以加入final修饰符,里面的变量也使用final
final class Season{ private String name; private String desc;//描述 //定义了四个对象 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("冬天", "寒冷"); //1.将构造器私有化private,目的:防止直接new //2.去掉setter相关方法:防止属性被修改 //3.在Season内部,直接创建固定的对象 //4.优化,对象可以加入final修饰符 private Season(String name, String desc) { this.name = name; this.desc = desc; } //2.去掉setter相关方法:防止属性被修改 public String getName() { return name; } public String getDesc() { return desc; } @Override public String toString() { return "Season{" + "name='" + name + '\'' + ", desc='" + desc + '\'' + '}'; } }
-
规则
- 不需要提供setter方法,因为枚举对象通常只读
- 对枚举对象/属性使用final+static共同修饰,实现底层优化(之前有提到final+static在jvm有特殊buff,详见OOP高级final关键字)
- 枚举对象名通常使用全部大写,常量的命名规范
- 枚举对象根据需要,也可以有多个属性
形式2-枚举类
-
演示
//如果使用了enum来实现枚举类 //1.使用关键字enum替代class //2.public static final Season SPRING = new Season("春天","温暖") 用 SPRING("春天", "温暖") 替代 // SPRING("春天", "温暖")解读 常量名(实参列表) //3.⭐⭐枚举属性必须前置⭐⭐ //4.如果有多个常量(对象),使用,号间隔即可 enum Season2{ //定义了四个对象 SPRING("春天", "温暖"),SUMMER("夏天", "炎热"),AUTUMN("秋天", "凉爽"),WINTER("冬天", "寒冷"); private String name; private String desc;//描述 //1.将构造器私有化private,目的:防止直接new //2.去掉setter相关方法:防止属性被修改 //3.在Season内部,直接创建固定的对象 private Season2(String name, String desc) { this.name = name; this.desc = desc; } //2.去掉setter相关方法:防止属性被修改 public String getName() { return name; } public String getDesc() { return desc; } @Override public String toString() { return "Season{" + "name='" + name + '\'' + ", desc='" + desc + '\'' + '}'; } }
-
注意事项
-
当我们使用enum关键字开发一个枚举类时候,默认会继承Enum类
-
传统public static final Season SPRING = new Season(“春天”,“温暖”) 用 SPRING(“春天”, “温暖”) 替代
-
如果使用无参构造器创建枚举对象,则实参列表和小括号可以省略
-
当有多个枚举对象时,使用,间隔,最后有一个分号结尾
-
枚举对象必须放在枚举类的行首
-
SPRING(“春天”, “温暖”),实际上是调用构造器,其需要创建对应的构造器
-
如果我们使用的是无参构造器创建常量对象,则可以省略括号,如下图what
-
我们称上图的成员为枚举对象
-
enum原型对象
values:返回枚举对象数组
enum使用switch语句
Color green = Color.GREEN;
switch (green){
case RED:
System.out.println("匹配到红色");
break;
default:
System.out.println("others");
}
switch()中,放入枚举对象
在每个case后,直接写上在枚举类中,定义的枚举对象即可。
enum实现接口
-
使用enum关键字后,就不能再继承其它类了,因为enum会隐式继承Enum,而java是单继承机制
-
枚举类和普通类一样,可以实现接口,如下形式。
enum 类名 implements 接口1,接口2{ }
注解Annotation
简介
- 注解也被称为元数据(metadata),用于修饰解释包、类、方法、属性、构造器、局部变量等数据信息。
- 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。
- 在JavaSE中,注解的使用目的比较简单,例如:标记过时的功能,忽略警告等。在JavaEE中注解占据了更重要的角色,例如:用来配置应用程序的任何切面,代替JavaEE旧版中所一遛的繁冗代码和XML配置等。
使用
使用Annotation时要在其面前增加@符号,并把该Annotation当成一个修饰符使用。用于修饰它支持的程序元素。
-
三个基本的Annotation
- @Override:限定某个方法,是重写父类方法,该注解只能用于方法
- @Deprecated:用于表示某个程序元素(类、方法等)【表示已过时】
- @SuppressWarnings:抑制编译器警告
-
@Override
如果写了@Override注解,编译器就会去检查该方法是否真的重写了父类的方法,如果的确重写了,则编译通过,如果没用构成重写,则编译错误。@Override其实本质上是一个注解类。
- @Override表示指定重写父类的方法(从编译层面验证),如果父类没有对应方法则报错
- 如果子类的重写方法不写@Override注解,而父类仍有重写的方法,则仍然构成子类的重写
- @Override只能修饰方法,不能修饰其它类、包、属性等等
- 查看@Override注解原码为@Target(ElementType.METHOD),说明只能修饰方法
- @Target是修饰注解的注解,称为元注解,记住这个概念
-
@Deprecated
- @Deprecated 修饰某个元素,表示该元素已经过时,不可用
- 虽然java官方不再推荐使用,但是仍然可以使用
- 查看@Deprecated的源码
- 可以修饰方法,类,字段,包,参数等等
- @Deprecated 可以做版本升级的兼容过渡
-
@SuppressWarnings
-
当我们不希望在代码上看到黄色警告信息的时候,可以用SuppressWarnings注解来抑制警告信息
-
在{“”}中,可以写入你希望抑制(不显示)警告信息
-
关于SupressWarnings作用访问是和你放置的位置相关
比如@SuppressWarnings放置在main方法,那么抑制警告的访问就在main方法,如上图。
-
看看@SuppressWarnings源码
-
放置的位置就是TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIBLE
-
该注解类有数组String[] values() 设置一个数组比如{“rawtypes”,“unchecked”}
-
-
可以指定的警告类型有如下:(用于排查bug用处极大)
关键字 用途 all 抑制所有警告 boxing to suppress warnings relative to boxing/unboxing operations/抑制装箱、拆箱操作时候的警告 cast to suppress warnings relative to cast operations/抑制映射相关的警告 dep-ann to suppress warnings relative to deprecated annotation/抑制启用注释的警告 deprecation to suppress warnings relative to deprecation/抑制过期方法警告 fallthrough to suppress warnings relative to missing breaks in switch statements/抑制确在switch中缺失breaks的警告 finally to suppress warnings relative to finally block that don’t return/抑制finally模块没有返回的警告 hiding to suppress warnings relative to locals that hide variable incomplete-switch to suppress warnings relative to missing entries in a switch statement (enum case)/忽略没有完整的switch语句 nls to suppress warnings relative to non-nls string literals/忽略非nls格式的字符 null to suppress warnings relative to null analysis/忽略对null的操作 rawtypes to suppress warnings relative to un-specific types when using generics on class params/使用generics时忽略没有指定相应的类型 restriction to suppress warnings relative to usage of discouraged or forbidden references serial to suppress warnings relative to missing serialVersionUID field for a serializable class/忽略在serializable类中没有声明serialVersionUID变量 static-access to suppress warnings relative to incorrect static access/抑制不正确的静态访问方式警告 synthetic-access to suppress warnings relative to unoptimized access from inner classes/抑制子类没有按最优方法访问内部类的警告 unchecked to suppress warnings relative to unchecked operations/抑制没有进行类型检查操作的警告 unqualified-field-access to suppress warnings relative to field access unqualified/抑制没有权限访问的域的警告 unused to suppress warnings relative to unused code/抑制没被使用过的代码的警告 -
-
补充说明:@interface的说明
- @interface不是接口,是注解类,在jdk1.5之后加入的,独有标识。
- (30条消息) @SuppressWarnings注解用法详解_zyhan1的博客-CSDN博客_suppresswarnings
JDK的元注解(了解)
-
元注解基本介绍
JDK的元Annotation用于修饰其它Annotation
元注解:本身作用不大,讲这个原因希望同学们,看源码时,可以知道他是干什么的
-
元注解种类(使用不多,了解,不用深入研究,想深入可以看源码)
-
Retention //指定注解的作用范围,三种SOURCE,CLASS,RUNTIME
-
Target //指定注解可以在哪些地方使用
-
Documented //指定该注解是否会在javadoc体现
-
Inherited //子类会继承父类注解
-
异常Exception
简介
所谓异常就是程序运行时可能出现的一些错误,比如试图打开一个根本不存在的文件灯,异常处理将会改变程序中的控制流程,让程序有机会对程序做处理。
场景
- num1/num2 =>10/0
- 当执行到num1/num2因为num2=0,程序就会出现异常ArithmeticException
- 当抛出异常后,程序就退出,崩溃了,下面的代码就不再执行
- 大家想一想这样的程序好吗?不好,不应该出现一个不算致命的问题,就导致整个系统崩溃
- java设计者提供了异常处理机制来解决该问题
解决
如果程序员认为一段代码可能出现异常/问题,可以使用try-catch异常处理机制来解决
从而保证程序的健壮性
将代码块->选中->快捷键ctrl+alt+t ->选中try-catch
如果进行异常处理,那么即使程序异常,依然可以运行
try{
int res = num1/num2;
}catch(Exception e){
e.printStackTrace();
System.out.println("出现异常的原因="+e.getMessage());
}
System.out.println("程序继续运行");
基本概念
Java语言中,将程序执行中发生的不正常情况称为”异常“。(开发过程中的语法错误和逻辑错误不是异常)
执行过程中所发生的异常事件可分为两类
- Error(错误):Java虚拟机无法解决严重的问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError[栈溢出]和OOM(out of memory),Error是严重错误,程序会崩溃。
- Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等,Exception分为两大类:运行时异常[程序运行时候,发生的异常]和编译时异常[编译时,编译器检查出的异常]
异常体系图一览(简洁版)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tBuqm5Hv-1678672774052)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220829175425328.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VtO1K2ie-1678672774053)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220829180346949.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-azTRqOb5-1678672774053)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220829180637652.png)]
异常体系图小结
- 异常分为两大类,运行时异常和编译时异常
- 运行时异常,编译器检查不出错误。一般是编程时的逻辑错误,是程序员应该避免出现的异常。Java.lang.RuntimeException类以及它的子类都是运行时异常
- 对于运行时异常,可以不做处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响【或使用全局异常捕获】
- 编译时异常,是编译器要求必须处置的异常。
运行时异常
-
NullPhinterException空指针异常
当应用程序试图在需要对象的地方使用null时,抛出该异常,看案例演示。
public class NullPointerException { public static void main(String[] args) { String name = null; System.out.println(name.length()); } } Exception in thread "main" java.lang.NullPointerException at exception_.NullPointerException.main(NullPointerException.java:10)
-
ArithmeticException数字运算异常
当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例,案例演示:
public class ArithmeticException { public static void main(String[] args) { int num = 1/0; System.out.println(num); } } Exception in thread "main" java.lang.ArithmeticException: / by zero at exception_.ArithmeticException.main(ArithmeticException.java:9)
-
ArrayIndexOutOfBoundsException数组下标越界异常
用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引
public class ArrayIndexOutOfBoundsException { public static void main(String[] args) { int[] arr = {1,2,4}; for (int i = 0; i < arr.length + 1; i++) { System.out.println(arr[i]); } } } Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3 at exception_.ArrayIndexOutOfBoundsException.main(ArrayIndexOutOfBoundsException.java:11)
-
ClassCastException类型转换异常
当试图将对象强制转换为不是实例的子类时,抛出该异常。案例演示:
public class ClassCastExption { public static void main(String[] args) { A a = new B(); B b = (B)a; C c = (C)a; } } class A{ } class B extends A{ } class C extends A{ } Exception in thread "main" java.lang.ClassCastException: exception_.B cannot be cast to exception_.C at exception_.ClassCastExption.main(ClassCastExption.java:11)
-
NumberFormatException数字格式不正确异常[]
当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常=>使用异常我们可以确保输入是数字的条件
public class NumberFormatException { public static void main(String[] args) { String name = "dsj"; System.out.println(Integer.parseInt(name)); } } Exception in thread "main" java.lang.NumberFormatException: For input string: "dsj" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Integer.parseInt(Integer.java:580) at java.lang.Integer.parseInt(Integer.java:615) at exception_.NumberFormatException.main(NumberFormatException.java:10)
编译异常
-
介绍
编译异常是指在编译期间,就必须处理的异常,否则代码不能通过编译。
-
常见的编译异常
-
SQLException
操作数据库时,查询表可能发生异常
-
IOException
操作文件时,发生的异常
-
FileNotFoundException
当操作一个不存在的文件时,发生异常
-
ClassNotFoundException
加载类,而该类不存在时,发生异常
-
illegalArguementException
参数异常
-
异常处理介绍
-
基本介绍
异常处理就是当异常发生时,对异常处理的方式
-
异常处理
-
try-catch-finally
程序员在代码中捕获发生的异常,自行处理
-
throws
将发生的异常抛出,交给调用者(方法)来进行处理,最顶级处理者就是JVM
如果程序员没有进行异常处理,程序默认使用throws
-
try-catch异常处理
-
Java提供try和catch块来处理异常。try块用于包含可能出错的代码。catch用于处理try块中的异常。可根据需要嵌入多个try-catch块
-
如果异常发生了,则异常后面的代码不会执行,直接进入到catch块。
-
如果异常没用发生,则顺序执行try的代码块,不会进入到catch。
-
如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等)则使用finally{ }
-
可以有多个catch语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前,比如(Exception在后,NullPointerException在前),如果发生异常,只会匹配一个catch,案例演示:
try{ }catch(NullPointerException e){ }catch(Exception e){ }finally{ }
-
可以进行try-finally配合使用,这种用法相当于没用捕获异常,因此程序会直接崩掉。
应用场景:执行一段代码,不管是否发生异常,都必须执行某个业务逻辑。 -
若存在finally,即使前面的代码已经return,一定会执行finally中的代码,其过程相当于递归调用
throws异常处理
-
基本介绍
-
如果一个方法中的语句执行时,可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
-
在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
public class Throws01 { public static void main(String[] args) { } public void f1() throws FileNotFoundException, java.lang.NullPointerException, java.lang.ArithmeticException { //创建了一个文件流对象 //老韩解读: //1.这里的异常是一个FileNotFoundException编译异常 //2.使用前面讲过的try-catch-finally //3.使用throws,抛出异常,让调用f1方法的调用者(方法)处理 //4.throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父亲 //5.throws关键字后也可以是异常列表,即可以抛出多个异常 FileInputStream fis = new FileInputStream(("d://aa.txt")); } }
-
-
注意事项
- 对于编程异常,程序中必须处理,比如try-catch或者throws
- 对于运行时异常,程序中如果没有处理,默认就是throws的方式处理
- 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型要么和父类抛出的异常类型一致,要么为父类抛出的异常的类型的子类型
- 在throws过程中,如果有方法try-catch,就相当于处理异常,就可以不必throws
自定义异常
-
基本概念
当程序种出现了某些“错误”,但该错误信息并没有再Throwable子类种描述处理,这个时候可以自己设计异常类,用于描述该错误信息。
-
自定义异常的步骤
- 定义类:定义异常类名(程序员自己写)继承Exception或RuntimeException
- 如果继承Exception,属于编译异常
- 如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException,好处是:我们可以使用默认的处理机制【即在方法后面自动throws】,比较方便)
- 通常使用构造器传送错误信息(参数message)
throw和throws的区别
throws:
public void f1() throws Exception{
...
}
throw:
if(age>=200){
throw new AgeException("年龄需要小于200");
}
泛型
看一个需求
- 请编写程序,在ArrayList中,添加3个Dog对象
- Dog对象含有name和age,并输出name和age(需要使用get())
先使用传统的方法来解决 -> 引出泛型
package com.dsj.generic_;
import java.util.ArrayList;
/*
* @author 远空_
* @version 1.0
* */
public class Generic01 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(new Dog("旺财",10));
list.add(new Dog("发财",1));
list.add(new Dog("小白",20));
//加入我们程序员不小心添加了一只猫
list.add(new Cat("招财",1));
//遍历
for (Object o : list) {
Dog dog = (Dog) o;
System.out.println(dog.getName() + "-" + dog.getAge());
}//ClassCastException
}
}
class Dog{
public String name;
public int age;
public Dog(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;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class Cat{
public String name;
public int age;
public Cat(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;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
泛型的理解和好处
- 不能对加入到集合ArrayList中的数据类型进行约束(不安全)
- 遍历的时候,需要进行类型转换,如果集合中的数据量比较大,对效率有影响
初试泛型
package com.dsj.generic_.imporve;
import java.util.ArrayList;
/*
* @author 远空_
* @version 1.0
* */
public class Generic02 {
public static void main(String[] args) {
//1.当我们ArrayList<Dog>存放到ArrayList集合中的元素是Dog类型
//2.如果编译器发现添加的类型不满足要求,就会报错
//3.在遍历的时候,可以直接取Dog类型而不是Object
ArrayList<Dog> list = new ArrayList<Dog>();
list.add(new Dog("旺财",10));
list.add(new Dog("发财",1));
list.add(new Dog("小白",20));
//加入我们程序员不小心添加了一只猫
// list.add(new Cat("招财",1));
//遍历
for (Dog dog : list) {
System.out.println(dog.getName() + "-" + dog.getAge());
}//ClassCastException
}
}
class Dog{
public String name;
public int age;
public Dog(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;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class Cat{
public String name;
public int age;
public Cat(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;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
优势
- 编译时,检查添加的元素类型,提高了安全性
- 减少了类型转换的次数,提高效率
-
不使用泛型
Dog -> Object ->Dog//放入到ArrayList会先转成Objcet,在取出时,还需要转换成Dog
-
使用泛型
Dog->Dog->Dog//放入时,和取出时,不需要类型转换提高效率
3.不再提示编译警告
泛型介绍
如同变量:int a = 10;
泛型=>Integer,Sting,Dog
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mSFg0BYj-1678672774054)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221012104838139.png)]
-
泛型又称参数化类型,是JDK5出现的新特性,解决数据类型的安全问题
-
在类声明或实例化时只需要制定好需要的类型即可
-
JAVA泛型可以保证如果程序在编译时没有法储警告,运行时就不会产生ClassCastException异常。同时,代码更简介、健壮
-
泛型的作用是:可以在类声明时通过一个表示表示类中某个属性的类型,或者是某个方法的返回值类型,或者是参数类型。
package com.dsj.generic_; /* * @author 远空_ * @version 1.0 * */ public class Generic03 { public static void main(String[] args) { //特别强调:E具体的数据类型在定义Person对象的时候指定,在编译期间,就确定E是什么类型 Person<String> dsj = new Person<>("dsj"); Person<Integer> integerPerson = new Person<Integer>(100); } } class Person<E>{ E s;//E表示s的数据类型,该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么 public Person(E s) {//E也可以是参数类型 this.s = s; } public E f(){//返回类使用E return s; } }
泛型的语法
-
泛型的声明
interface 接口<T>和class 类<K,V>{} //比如:List,ArrayList //说明:1)其中,T,K,V不代表正常值,而是表示类型 // 2)任意字母都可以。常用T表示,是Type的缩写
-
泛型的实例化
- List strList = new ArrayList ();
- Itertor iterator = costomers.iterator();
-
注意事项
-
interface List{},public class HashSet{}…等等
说明:T,E只能是引用类型,不能是基本数据类型
-
在指定泛型具体类型后,可以传入该类型或者其子类型类型
-
泛型简写使用形式(推荐第二种)
List list1 = new ArrayList();
List list2 = new ArrayList<>();
-
若默认不写,则E默认传入Object
-
自定义泛型类
-
基本语法
class 类名<T,R,...){ 成员 }
-
注意细节
-
普通成员可以使用泛型(属性、方法)
-
使用泛型的数组,不能初始化
T[] array = new T[8]//false,因为数组在new的时候不能确定T的类型,无法确定空间大小,无法进行空间开辟
-
静态方法不能使用类的泛型(泛型是在new对象的时候定义的)
public static void m1(T t){}//false,因为静态是和类相关的,在类加载时,对象还没有创建,所以,如果静态方法和静态属性使用了泛型,JVM就无法完成初始化
-
泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定类型)
-
如果在创建类对象时,没有指定类型,默认为Object
-
自定义泛型接口
-
基本语法
interface 接口名<T,R...>{ }
-
注意细节
- 接口中,静态成员也不能使用泛型(这个和泛型类规定一样)
- 泛型接口的类型,在继承接口或者实现接口时确定
- 没有指定类型,默认为Object
自定义泛型方法
-
基本语义
修饰符 <T,R..> 返回类型 方法名(参数列表){ }
-
注意细节
-
泛型方法,可以定义在普通类中,也可以定义在泛型类中
class Car{ public void run(){ } //说明 //1.<T,R>就是泛型 //2.提供给fly使用的 public <T,R> void fly(T t,R r){ } } class Fish<T,R>{//泛型类 public void run(){ } public <U,M> void eat(U u,M m){ } }
-
当泛型方法被调用时,类型会确定。
-
public void eat(E e){},修饰符后没有<T,R…> eat方法不是泛型方法,而是使用了泛型变量
泛型方法,可以使用类声明的泛型,也可以使用自己声明的泛型
class Fish<T,R>{//泛型类 public void run(){ } public <U,M> void eat(U u,M m){ } //1.下面的hi()并不是泛型方法 //2.是hi()使用了类声明的泛型变量 public void hi(T t){ } //3.泛型方法,可以使用类声明的泛型,也可以使用自己声明的泛型 public <K> void hello(R r,K k){ } }
-
泛型的继承和通配符说明
-
泛型不具备继承性
List <Object> list = new ArrayList<String> ();//false
- <?>:支持任意泛型类型
public static void printList(List<?> list) { for (Object o : list) { System.out.println(o); } } public static void main(String[] args) { List<String> l1 = new ArrayList<>(); l1.add("aa"); l1.add("bb"); l1.add("cc"); printList(l1); List<Integer> l2 = new ArrayList<>(); l2.add(11); l2.add(22); l2.add(33); printList(l2); }
- <? extends A>:支持A类以及A类的子类,规定了泛型的上限
- <? super A>:支持A类以及A类的父亲,不限于直接父类,规定了泛型的下限
-
(32条消息) 泛型(T)、通配符(?)理解和区别_华海的bk的博客-CSDN博客_泛型通配符?和泛型t区别
说明:
通配符其实是用于传入实参的时候,对实参类型的限制。
JUnit
-
为什么需要Junit
- 一个类有很多功能代码需要测试,为了测试,就需要写入到main方法中
- 如果有很多个功能代码测试,就需要来回注销,切换很麻烦
- 如果可以直接运行一个方法,就方便很多,并且可以给出相关信息,就好了 -> JUnit
-
基本介绍
- JUnit是一个JAVA语言的单元测试框架
- 多数JAVA的开发环节都已经继承了JUnit作为单元测试的工具
常用类
八大包装类Wrapper
包装类的分类
- 针对八种基本数据类型相应的引用类型-包装类
- 有了类的特点,就可以调用类种的方法
除了前两个,后面的都会继承Number类【可通过结构图进行查看】
包装类和基本数据类型转换
- jdk5前的手动装箱和拆箱方式,装箱:基本类型->包装类型,拆箱:包装类型->基本类型
- jdk5以后(含jdk5)的自动装箱和拆箱方式[底层走的是Integer.valueOf()和intValue]
- 自动装箱底层调用的是valueOf方法,比如Integer.valeOf()
//手动装箱 int -> Integer
int n1 = 100;
Integer integer = new Integer(n1);
System.out.println(integer);//100
Integer integer1 = Integer.valueOf(n1);
System.out.println(integer1);//100
System.out.println(integer==integer1);//false,不是同一个对象
//手动拆箱 Integer -> int
Integer integer2 = new Integer(200);
int i = integer2.intValue();
System.out.println(i);//200
关于Integer的一道题
public static void main(String[] args) {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);
Integer m = Integer.valueOf(1);
Integer n = 1;
System.out.println(m == n);
Integer x = 128;
Integer y = 128;
System.out.println(x == y);
}
String类
一些tips
-
String对象用于保存字符串,也就是一组字符串序列
-
字符串常量对象是用双引号阔气的字符序列。例如:“你好”、“12.97”、“boy”等
-
字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字节
-
String类较常用构造方法(其它看手册):
String s1 = new String(); String s2 = new String(String original); String s3 = new String(char[] a); String s4 = new String(char[] a,int startIndex,int count);
-
String类实现了接口Serializable[String可以串行化:可以在网络传输]
-
String是final类,不能被其它的类继承
-
String 有属性private final char value[];用于存放字符串的内容【⭐一定要注意:value是一个final类型,不可以修改!即value不能指向新的地址,但是单个字符内容是可以变化的!】
创建String对象的两种方式
方式1:直接赋值String s = “dsj”;
方式2:调用构造器 String s = new String(“dsj”);
- 方式一:先从常量池查看是否有“dsj”数据空间,如果有,直接指向;如果没有则重新创建,然后指向。s最终指向的是常量池的空间地址
- 方式二:先在堆中创建空间,里面维护了value属性,指向常量池的dsj空间。如果常量池没有“hsp”,重新创建,如果有,直接通过value指向char[]数组空间。最终指向的是堆种的空间地址。
- 画出两种方式的内存分布图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qekSw2pf-1678672774056)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220903195221184.png)]
String类特性
- String是一个final类,代表不可变的字符序列
- 字符串是不可改变的。一个字符串对象一旦被分配,其内容是不可变的。
//以下语句创建了几个对象?画出内存布局图
String s1 = "hello";
s1 = "haha";
//创建了两个对象
//以下语句创建了几个对象?
String a = "hello" + "abc";
//一个对象
//解读:编译器会进行优化,判断创建的常量池对象,是否有引用指向
//等价于 String a = "helloabc";
//创建了几个对象?画出内存图。
String a = "hello";
String b = "abc";
String c = a + b;
//解读
//1.先创建一个StringBuilder sb = new 先创建一个StringBuilder()
//2.执行sb.append("hello");
//3.sb.append("abc");
//4.String c = sb.toString();
// public String toString() {
// // Create a copy, don't share the array
// return new String(value, 0, count);
// }
//最后其实是c指向堆种的对象(String) value[] 再指向 池中 "helloabc"
//一共创建3个对象,如下图
//小结:
//底层是StringBuilder sb = new StringBuilder();sb.append(a);sb.append(b);sb是在堆种,并且append是在原来字符串的基 础上追加的。
//重要规则:String c1 ="ab" + "cd";常量相加,看的是池。String c1 = a + b;变量相加,是在堆中
//下面代码输出说明,并说明原因
public class StringExercise09 {
public static void main(String[] args) {
Test1 ex = new Test1();
ex.change(ex.str,ex.ch);
System.out.print(ex.str+" and ");
System.out.println(ex.ch);
}
}
class Test1{
String str = new String("dsj");
final char ch[] = {'j','a','v','a'};
public void change(String str,char ch[]){
str = "java";
ch[0] = 'h';
}
}
//输出 dsj and hava
String类常见方法
-
说明
String类是保存字符串变量的。每次更新都需要重新开辟空间,效率较低,因此java设计者还提供了StringBuilder和StringBuff来增强String功能,并提高效率。【后面外面还会详细介绍StringBuilder和StringBuffer】
String s = new String(""); for(int i = 0; i < 80000; i++){ s += "hello"; }
-
equals // 区分大小写,判断内容是否相等
-
equalsIgnoreCase //忽略大小写的判断内容是否相等
-
length //获取字符的个数,字符串的长度
-
indexOf //获取字符在字符串中第一次出现的索引,索引从0开始,如果找不到,返回-1
-
lastIndexOf //获取字符在字符串中最后一次出现的索引,索引从0开始,如果找不到,返回-1
-
substring //截取指定范围的子串
-
trim //去前后空格
-
charAt //获取索引某处的字符,注意不能使用Str[index]这种方式
-
toUpperCase //转换成大写
-
toLowerCase //转换成小写
-
concat //拼接,将字符串拼接起来
s.concat("林黛玉").concat("薛宝钗").concat("together");
-
replace //替换,替换字符串中的字符,并返回一个新的字符串对象的引用
s = "宝玉 and 林黛玉 薛宝钗 薛宝钗" s1 = s.replace("林黛玉","薛宝钗"); //将s中所有“林黛玉”替换成“薛宝钗”,赋值给s1
-
split 分割字符串,对于某些分割字符,外面需要转义 比如 | 和\ \等,并返回一个数组对象的引用【在对字符串进行分割时,如果有特殊字符,需要加入转义符\】
String poem = "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦"; String split[] = poem.split(","); //以,为标准,将字符串进行分割,返回一个数组
String poem = "E:\\aaa\\bbb"; String split[] = poem.split("\\\\");
-
toCharArray 转换成字符数组
String s = "happy"; char chs[] = s.toCharArray();
-
compareTo比较两个字符串的大小,如果前者大,则返回整数,后者大,则返回负数,如果相等,则返回0[建议看源码]
- 如果长度相同,并且每个字符也相同,就返回0
- 如果长度相同或不相同,但是再进行比较时,可以区分大小就返回c1 - c2
- 如果前面的部分都相同,长度不相同,则返回 str1.len - str2.len
String a = "john"; String b = "jack";
-
format 格式化字符串
占位符有:
%s 字符串 %c 字符 %d 整形 %.2f 浮点型
String name = "john"; int age = 10; double score = 98.3 / 3; char gender = '男'; //传统写法 String info = "我的名字是" + name + ",年龄是" + age + ",成绩是" + score + ",性别是" + gender +"。希望等级喜欢我!"; //占位符写法 String info = String.format("我的名字是%s,年龄是%d,成绩是%.2f,性别是%c。希望大家喜欢我",name,age,score,gender);
StringBuffer类
-
基本介绍
java.lang.StringBuffer代表可变的字符序列,可以堆字符串内容进行增删。很多方法与String相同,但StringBuffer是可变长度的。StringBuffer是一个容器。
- StringBuffer是final类,不能被继承
- 实现了Serializable接口,可以保存到文件,或网络传输【串行化】
- 继承了抽象类AbstractStringBuilder
- AbstractStringBuilder属性char[] value,不是final,存放字符序列【也就是说真正存放的内容在父类】,引出存放在堆中【不是常量池】
-
StringBuffer VS String
- String保存的是字符串常量,里面的值不能更改,每次String类的更新实际上就是更改地址,效率较低。
- StringBuffer保存的是字符串变量,里面的值可以更改,每次StringBuffer的更新实际上可以更新内容,不用每次更新地址,效率较高,char[] value这个放在堆
-
构造器详解
StringBuffer() //构造一个其中不带字符的字符串缓冲区,其初始容量为16个字符。
StringBuffer(CharSequence seq) //构造一个字符串缓冲区,它包含与指定的CharSequence相同的字符。
StringBuffer(int capacity) //构造一个不带字符,但具有指定初始容量的字符串缓冲区。即对char[]带下进行指定
StringBuffer(String str) //构造一个字符串缓冲区,并将其内容初始化为指定的字符串内容。
-
String和StringBuffer互相转换
在开发中,我们经常需要将String和StringBuffer进行转换
//String->StringBuffer String s = "hello"; //方式1 StringBuffer b1 = new StringBuffer(s); //方式2 StringBuffer b2 = new StringBuffer(); b2.append(s);
//StringBuffer -> String //方式1 String s2 = b1.toString();//指向常量池 //方式2 String s3 = new String(b1);//指向value[]
-
StringBuffer类方法
- 曾append
- 删delete(start,end)【区间:[start,end)】
- 改replace(start,end,String)
- 查indexOf 查找子串在字符串第一次出现的索引,如果找不到返回-1
- 插insert
- 获取长度length
StringBuffer s = new StringBuffer("hello"); //增 s.append(','); s.append("张三丰"); s.append("赵敏").append(100).append(true).append(10.5); System.out.println(s);//hello,张三丰赵敏100true10.5 //删 //解读:删除[11,14)范围的字符 s.delete(11,14); System.out.println(s);//hello,张三丰赵敏true10.5 //改 //范围[9,11) s.replace(9,11,"周芷若"); System.out.println(s);//hello,张三丰周芷若true10.5 //查 int indexOf = s.indexOf("张三丰"); System.out.println(indexOf);//6 //插入 //索引为9的位置插入"赵敏",原先索引为9的内容自动后移 s.insert(9,"赵敏"); System.out.println(s);//hello,张三丰赵敏周芷若true10.5 //长度 System.out.println(s.length());//22
StringBuilder
- 基本介绍
- 一个可变的字符序列。此类提供一个与StringBuffer兼容的API【你可以看到源码中,两者有相同的类图】,但不保证同步()。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer要快。
- 在StringBuilder上的主要操作是append和insert方法,可重载这些方法,以接收任意类型的数据。
- 详细介绍
- StringBuilder继承AbstractStringBuilder类
- 实现了Serializable,说明StringBuilder对象是可以串行化【序列化】
- StringBuilder是final类,不能被继承
- StringBuilder字符序列仍然是存放在其父类AbstractStringBuilder的char[] value;因此,字符存在堆中。
- StringBuilder的方法,没有做互斥的处理,即没有synchronized关键字,因此只建议在单线程的情况下使用StringBuilder。
String、StringBuffer和StringBuilder
比较
-
StringBuilder和StringBuffer非常类似,均代表可变的字符序列,而且方法也一样
-
String:不可变字符序列,效率很低,但是复用率高
-
StringBuffer:可变字符序列、效率较高(增删)、线程安全
-
StringBuilder:可变字符序列、效率最高、线程不安全
-
String使用注意说明:
String s = "a";//创建了一个字符串 s += "b";//实际上原来的"a"字符串对象已经丢弃了,现在又产生了一个字符串s+"b"(也就是"ab")如果多次执行这些改变串内容的操作,会导致大量副本字符串留在内存中,降低效率。如果这样的操作放到循环中会极大地影响程序的性能=>结论:如果我们堆String做大量修改,不要使用String
选择
- 如果字符串存在大量修改操作,一般使用StringBuffer或StringBuilder
- 如果字符串存在大量修改操作,并在单线程的情况,使用StringBuilder
- 如果字符串存在大量修改操作,并在多线程的情况,使用StringBuffer
- 如果我们字符串很少修改,被多个对象引用,使用String,比如配置信息等
StringBuilder的方法使用和StringBuffer一样,不再说。
Math类
基本介绍
Math类包含用于执行基本数学运算的方法,如初等值数、对数、平方根和三角函数。
方法一览
常见方法
-
abs绝对值
int abs = Math.abs(-9);
-
pow求幂
double pow = Math.pow(2,4);//2的4次方
-
ceil取整,向上取整,返回>=该参数的最小整数
double ceil = Math.ceil(-3.0001);//-3
-
floor 向下取整,返回<=该参数的最大整数
double floor = Math.floor(-4.999);//-5
-
round 四舍五入,相当于给原数+0.5
long round = Math.round(-5.001);//-4
-
sqrt求开方
double sqrt = Math.sqrt(-9);//不存在返回null double sqrt = Math.sqrt(9);//3
-
random求随机数
//random 返回的是0 <= x <1 之间的一个随机小数 //思考,如何返回一个a-b之间的一个随机整数,a,b俊伟整数,比如a = 2;b = 7; //(int)(a) <= x <= (int)(a + Math.random()*(b-a+1)),加1是因为向下取整
-
max、min返回最大最小值
int min = Math.min(1,9);//1 int max = Math.max(45,90);//45
Arrays类
简介
Arrays里面包含了一系列静态方法,用于管理或操作数组(比如排序和搜索)
常用方法
-
toString返回数组的字符串形式
Arrays.toString(arr);
-
sort排序(自然排序和定制排序)
Integer arr[] = {1,-1,7,0,89}; Arrays.sort(arr); //因为数组是引用类型,所以通过sort排序后,会直接影响到实参arr //sort有重载的方法,也可以通过传入 一个接口Comparator实现 定制排序 Arrays.sort(arr,new Comparator(){ @Override public int compare(Object o1,Object o2){ Integer i1 = (Integer)o1; Integer i2 = (Integer)o2; return i2 - i1;//这里体现接口编程,看看源码,就明白 } }); //调用 定制排序 时,传入两个参数(1)排序的数组arr(2)实现了Comparator接口的匿名内部类
-
binarySearch 通过二分查找法进行查找,要求必须排好序
//1.使用binarySearch 二叉查找 //2.要求该数组是有序的。如果该数组是无需的,不能使用binarySearch //3.如果数组中不存在该元素,就返回-1 int index = Arrays.binarySearch(arr,3);
-
copyOf数组元素的复制
//1.从arr数组中,拷贝arr.length个元素到newArr数组中 //2.如果拷贝的长度>arr.length就在新数组后面增加null值 //3.如果拷贝长度<0就抛出异常NegativeArraySizeException Integer newArr[] = Arrays.copyOf(arr,arr.length);
-
fill数组元素的填充
Integer num[] = new Integer[]{9,3,2}; //1.使用99去填充num数组,可以理解成是替换原来的元素 Arrays.fill(num,99);
-
equals比较两个数组元素内容是否完全一致
Integer arr[] = {1,-1,7,0,89}; Integer arr2[] = {1,2,90,123,567}; //1.如果arr和arr2数组的元素一样,则返回true boolean equals = Arrays.equals(arr,arr2);
-
asList 将一组值,转换成list
//1.asList方法,会将(2,3,4,5,6,1)数据转成一个List集合 //2.返回的 asList 编译类型 List(接口) //3.asList 运行类型:class java.util.Arrays#ArrayList,是Array的一个静态内部类 List asList = Array.asList(2,3,4,5,6,1); sout("asList="+asList);//asList=[2,3,4,5,6,1]
System类
常见方法
-
exit退出当前程序
sout("ok1"); //1.exit(0)表示程序退出 //2.0表示一个状态,正常的状态 System.exit(0); sout("ok2");
-
arraycopy:复制数组元素,比较适合底层调用,一般使用Arrays.copyOf完成复制数组。
int src[] = {1,2,3}; int dest[] = new int[4]; //1.主要是搞清楚5个参数的含义 //* @param src the source array.源数组 //* @param srcPos starting position in the source array.从源数组的哪个索引开始拷贝 //* @param dest the destination array.目标数组 //* @param destPos starting position in the destination data.把源数组的数据拷贝到目标数组的哪个开始索引 //* @param length the number of array elements to be copied.被拷贝的个数 System.arraycopy(src,0,dest,1,3); System.out.println("dest="+ Arrays.toString(dest));//dest=[0,1,2,3]
-
currentTimeMillens:返回当前事件距离1970-1-1的毫秒数
System.out.println(System.currentTimeMillis());
-
gc:运行垃圾回收机制System.gc()
不作演示
BigInteger和BigDecimal类
BigInteger
应用场景
- BigInteger适合保存比较大的整型
- BigDecimal适合保存精度更高的浮点数(小数)
演示
public static void main(String[] args) {
long l = 23788888899l;
System.out.println("l="+l);
BigInteger bigInteger = new BigInteger("23333333333333333333333333333333333333333333333");
BigInteger bigInteger1 = new BigInteger("100");
System.out.println(bigInteger);//23333333333333333333333333333333333333333333333
//1.在对BigInteger进行加减乘除的时候,需要使用对应的方法,不能直接进行加减乘除
//2.可以创建一个要操作的BigInteger然后进行相应操作
BigInteger add= bigInteger.add(bigInteger1);
System.out.println(add);//23333333333333333333333333333333333333333333433
BigInteger subtract = bigInteger.subtract(bigInteger1);//减
System.out.println(subtract);//23333333333333333333333333333333333333333333233
BigInteger multiply = bigInteger.multiply(bigInteger1);//乘
System.out.println(multiply);//2333333333333333333333333333333333333333333333300
BigInteger divide = bigInteger.divide(bigInteger1);//除
System.out.println(divide);//233333333333333333333333333333333333333333333
}
Bigdecimal
应用演示
- BigInteger适合保存比较大的整型
- BigDecimal适合保存精度更高的浮点数(小数)
日期类
第一代日期类Date
-
Date类:精确到毫秒,代表特定的瞬间
-
SimpleDateFormat:格式和解析日期的具体类。它允许进行格式化(日期 -> 文本)、解析(文本 -> 日期)和规范化。
-
应用实例
public static void main(String[] args) throws ParseException { //1.获取当前系统事件 //2.这里的Date类是在java.util包 //3.默认输出的日期格式是国外的方式,因此通常需要对格式进行转换 Date d1 = new Date();//获取当前系统事件 System.out.println("当前日期="+d1); Date d2 = new Date(99999);//通过指定毫秒数得到时间 System.out.println(d1.getTime());//获取某个时间对应的毫秒数 //1.创建 SimpleDateFormat对象,可以指定相应的格式 //2.这里的格式使用的字母是规定好的 SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E"); String format = sdf.format(d1);//format():将日期转换成指定格式的字符串 //1.可以把一个格式化的String转换成对应的Date //2.得到Date仍然在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换 //3.在把String -> Date,使用的sdf格式需要和你给的String的格式一样,否则会抛出转换异常 String s = "1996年01月01日 10:20:30 星期一"; Date parse = sdf.parse(s); System.out.println(parse); System.out.println(sdf.format(parse)); }
第二代日期类Calendar类(日历)
-
第二代日历类,主要就是Calendar类(日历)
public abstract class Calendar extends Object implments Serializable,Cloneable,Comparable<Calendar>
-
Calendar类是一个抽象类,它为特定瞬间与一组注入YEAR、MONTH、DAY_OFMONTH、HOUR等日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。
-
实例演示
public static void main(String[] args) { //1.Calendar是一个抽象类,并且构造器是private,所以new不出来,通过静态属性和方法来使用 //2.可以通过getInstance()来获取实例 //3.提供大量的方法和字段提供给程序员 //4.Calendar没有提供对应的格式化的累,因此需要程序员自己组合来输出 //5.⭐如果我们需要按照24小时进制来获取时间,Calendar.Hour ==>Calendar.HOUR_OF_DAY Calendar c = Calendar.getInstance();//创建日历类对象,比较自由简单 System.out.println(c); //获取日历对象的某个日历字段 System.out.println("年:"+c.get(Calendar.YEAR)); System.out.println("月:"+c.get(Calendar.MONTH)+1); System.out.println("日:"+c.get(Calendar.DAY_OF_MONTH)); System.out.println("小时:"+c.get(Calendar.HOUR)); System.out.println("分钟:"+c.get(Calendar.MINUTE)); System.out.println("秒:"+c.get(Calendar.SECOND)); //Calendar没有专门的格式化方法,所以需要程序员自己来组合显示 System.out.println(c.get(Calendar.YEAR)+"年"+(c.get(Calendar.MONTH)+1)+"月"+c.get(Calendar.DAY_OF_MONTH)+"日"); }
第三代日期类
-
前面两代日期类的不足分析
JDK1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK1.1引入Calendar类之后被弃用了。而Calendar也存在的问题是:
- 可变性:像日期和时间这样的类应该是不可变的。
- 便宜些:Date中年份是从1970开始的,而月份都是从0开始
- 格式化:格式化只对Date有用,Calendar则不行
- 此外,他们也不是线程安全的;
- 不能处理闰秒等(每隔两天,多出1s。)
-
第三代日期类
- LocalDate(日期/年月日)、LocalTime(时间/时分秒)、LocalDateTime(日期时间/年月日时分秒)JDK8加入
- LocalDate只包含日期,可以获取日期字段
- LocalTime只包含时间,可以获取时间字段
- LocalDateTime包含日期+时间,可以获取日期和时间字段
-
案例演示
public static void main(String[] args) { //1.使用now()返回表示当前日期时间的对象 LocalDateTime ldt = LocalDateTime.now(); System.out.println(ldt); System.out.println("年="+ldt.getYear()); System.out.println("月(英文)="+ldt.getMonth()); System.out.println("月(数字)="+ldt.getMonthValue()); System.out.println("日="+ldt.getDayOfMonth()); System.out.println("时="+ldt.getHour()); System.out.println("分="+ldt.getMinute()); System.out.println("分="+ldt.getSecond()); //只关心年月日 LocalDate now = LocalDate.now(); System.out.println(now); }
第三代日期类格式化
DateTimeFormatter格式日期类
类似于SimpleDateFormat
DateTimeFormat dtf = DateTimeFormatter.ofPattern(格式);
String str = dtf.format(日期对象);
案例演示:
public static void main(String[] args) {
//1.使用now()返回表示当前日期的对象
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
//2.使用DateTimeFormatter对象来进行格式化
//创建 DateTimeFormatter对象
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH小时mm分钟ss秒 E");
String format = dtf.format(ldt);
System.out.println(format);
}
Instant时间戳(第三代)
类似于Date提供了一系列和Date类转换的方式
Instant–>Date:
Date date = Date.from(instant);
Date–>Instant:
Instant instant = date.toInstant();
案例演示
public static void main(String[] args) {
//1.通过 静态方法 Instant.now() 返回Instant时间戳对象
Instant now = Instant.now();
System.out.println(now);
//2.通过Date.from()可以把Instant转成Date对象
Date date = Date.from(now);
//3.通过Instant.toInstant()可以把Date转成Instant对象
Instant instant = date.toInstant();
}
集合
集合的理解
-
数组
- 长度最开始时必须指定,而且一旦指定,不能更改
- 保存的必须为同一类型是元素
- 使用数组进行增加元素的示意代码–比较麻烦
-
集合
- 可以动态保存任意多个对象,使用比较方便
- 提供了一系列方便操作对象的方法:add、remove、set、get等
- 使用集合添加,删除新元素的代码很简洁
-
⭐集合的框架体系图
- 集合主要分两组(单列集合,双列集合)
- Collection 接口有两个重要的子接口List和Set,他们的实现子类都是单列集合
- Map 接口的实现子类 是双列集合,存放的是K-V两个元素
- 记住这张图
Collection接口和常用方法
-
Collection接口实现类的特点
public interface Collection<E> extends Iterable<E>
- collection实现子类可以存放多个元素,每个元素可以是Object
- 有些Collection的实现类,可以存放重复的元素,有些不可以
- 有些Collection的实现类,有些是有序的(List),有些是无序的(Set)
- Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的
-
常用方法,以实现子类ArrayList来演示
-
add:添加单个元素(返回布尔值)
list.add("jack"); list.add(10);//list.add(new Inerger(10)) list.add(true);//会自动装包 System.out.println("list = "+list);//list = [jack, 10, true]
-
remove:删除指定元素(返回布尔值)
list.remove(1);//删除第二个元素 System.out.println("list = "+list);//list = [jack, true] list.remove("jack");//删除指定某个元素 System.out.println("list = "+list);//list = [true]
-
contains:查找元素是否存在(返回布尔值)
System.out.println(list.contains("jack"));//false System.out.println(list.contains(true));//true
-
size:获取元素个数(返回int)
System.out.println(list.size());//返回1
-
isEmpty:判断是否为空(返回布尔值)
System.out.println(list.isEmpty());//false
-
clear:清空(void)
list.clear(); System.out.println("list = "+list);//list = []
-
addAll:添加多个元素(boolean)
List list2 = new ArrayList(); list2.add("红楼梦"); list2.add("三国演义"); list.addAll(list2);//传入实现Collection的集合 System.out.println("list = "+list);//list = [true, 红楼梦, 三国演义]
-
containsAll:查找多个元素是否都存在(boolean)
System.out.println(list.containsAll(list2));//true
-
removeAll:删除多个元素(boolean)
list.add("聊斋"); list.removeAll(list2); System.out.println("list = "+list);//list = [true, 聊斋]
-
List与Set的区别
- List、Set都继承自Collection接口;List的特点:元素有放入顺序,且可重复;Set的特点:元素无放入顺序,且不可重复(注意:元素虽然无放入顺序,但是元素在Set中的位置是由该元素的HashCode决定的,其位置是固定的)。List支持for循环,也就是通过下标来遍历,也可以用迭代器,但是Set只能用迭代器,因为他无序,无法使用下标取值;
- List接口有三个实现类:LinkedList,ArrayList,Vector。Set接口有两个实现类:HashSet(底层由HashMap实现),LinkedHashSet
- Set:检索元素效率低,删除和插入效率高,插入和删除不会引起元素位置改变。List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。
遍历
使用Iterator(迭代器)
-
基本介绍
-
Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
-
所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器
-
Iterator的结构:
-
Iterator仅用于遍历集合,Iterator本身并不存放对象
-
使用for循环增强
-
基本介绍
增强for循环,可以代替iterator迭代器,特点:增强for就是简化版的iterator,本质一样。只能用于遍历集合或数组。
-
基本语法
for(元素类型 元素名 : 鸡和马或数组名){ 访问元素 }
普通for循环
因为Set接口是无序的,不提供get方法通过索引获取,因此无法使用普通for进行遍历
for(int i = 0; i< list.size();i++){
Object object = list.get(i);
System.out.println(object);
}
List接口和常用方法
-
List接口基本介绍
List接口是Collection接口的子接口
-
List集合类中元素有序(即添加顺序和去除顺序一致)、且可重复
List list = new ArrayList(); list.add("tom"); list.add("jack"); list.add("mary"); list.add("mary"); list.add("smith"); System.out.println(list);//[tom, jack, mary, mary, smith]顺序一致,可重复
-
List集合中的每个元素都有其对应的顺序索引,即支持索引
System.out.println(list.get(3));//索引从0开始,返回mary
-
List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
-
JDK API中List接口的实现类有:
-
-
List接口的常用方法
List集合里添加了一些根据索引来操作集合元素的方法
-
void add(int index,Object ele):在index位置插入ele元素
List list = new ArrayList(); list.add("张三丰"); list.add("贾宝玉"); list.add(1,"邓圣君");
-
boolean addAll(int index,Collection eles):从index位置开始将eles中的所有元素添加进来
List list2 = new ArrayList(); list2.add("jack"); list2.add("tom"); list.addAll(1,list2);//在dsj后面加入list2的元素 System.out.println(list);//[张三丰, jack, tom, 邓圣君, 贾宝玉]
-
Object get(int index):获取指定index位置的元素
不解释
-
int indexOf(Object obj):返回obj在集合中首次出现的位置
System.out.println(list.indexOf("tom"));//2
-
int lastIndexOf(Object obj):返回obj在集合中末次出现的位置
list.add("邓圣君"); System.out.println(list.lastIndexOf("邓圣君"));//5
-
Object remove(int index):移除指定index位置的元素,并返回此元素
System.out.println(list.remove(0));//张三丰 System.out.println(list);//[jack, tom, 邓圣君, 贾宝玉, 邓圣君]
-
Object set(int index,Object ele):设定指定index位置的元素为ele,相当于是替换
list.set(1,"玛丽");//索引必须存在,因为是“替换” System.out.println(list);//[jack, 玛丽, 邓圣君, 贾宝玉, 邓圣君]
-
List subList(int fromIndex,int toIndex):返回从fromIndex到toIndex位置的子集合
List res = list.subList(0,2);//注意,返回的子集合是[0,2)并非闭区间 System.out.println(list); System.out.println(res);
-
ArrayList底层结构和源码分析
ArrayList的注意事项
- ArrayList可以加入NULL,并且可以是多个
- ArrayList是由数组来实现数据存储的(线性表)
- ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高),多线程下不建议使用ArrayList
ArrayList的底层操作机制源码分析
-
ArrayList中维护了一个Object类型的数组elementDate
transient Object[] elementDate;
-
当创建对象时,如果使用的是无参构造器,则初始elementDate容量为0(jdk7是10)
-
当添加元素时:先判断是否需要扩容,如果需要扩容,则调用grow()方法,否则直接添加元素到合适位置
-
如果使用的是无参构造器,如果第一次添加,需要扩容的话,则扩容elementData为10,如果需要再次扩容的话,则扩容elementData为1.5倍
-
如果使用的是指定容量capacity的构造器,则初始elementData容量为capacity
-
如果使用的是指定容量capacity的构造器,如果需要扩容,则直接扩容为elementData为其1.5倍
-
无参构造器过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zox0EZDr-1678672774057)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220916132015869.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ez6EWUDK-1678672774058)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220916132031919.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jdfszUVs-1678672774059)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220916132042455.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X5F50ece-1678672774060)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220916132109475.png)]
-
有参构造器过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M1jtnx1H-1678672774061)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220916184146802.png)]
Vector底层结构和源码分析
Vector基本介绍
-
Vector类的定义说明
-
Vector底层也是一个对象数组,protected Object[] elementData;
-
Vector是线程同步的,即线程安全,Vector类的操作方法有synchronized
public synchronized E get(int index){ if(index >= elementCount) throw new ArrayIndexOutOfBoundsExecption(index); return elementData(index); }
-
在开发中,需要线程同步安全时,考虑使用Vector
Vector和ArrayList的比较
源码分析
自己看jdk
LinkedList底层结构
说明
-
LinkedList实现了双向链表和双端队列特点
图1-双向链表
-
可以添加任意元素(元素可以重复),包括null
-
线程不安全,没有实现同步
双向链表
- LinkedList底层维护了一个双向链表
- LinkedList中维护了两个属性first和last分别指向首节点和尾节点
- 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点,prev指向后一个节点,最终实现双向链表
- 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
- 模拟一个简单的双向链表【走代码】<数据结构>
public class LinkList01 {
public static void main(String[] args) {
Node jack = new Node("jack");
Node tom = new Node("tom");
Node dsj = new Node("dsj");
// 连接3个节点,形成双向链表
//jack -> tom -> dsj
jack.next = tom;
tom.next = dsj;
//dsj -> tom ->jack
dsj.prev = tom;
tom.prev = jack;
Node first = jack;//让first引用指向jack,就是双向链表的头节点
Node last = dsj;//让last引用指向dsj,就是双向链表的尾节点
//演示,从头到尾遍历
while (first != null){
System.out.println(first);
first = first.next;
}
//演示,从尾到头遍历
while (last != null){
System.out.println(last);
last = last.prev;
}
//链表插入数据
first = jack;
first = Node.insert(2,new Node("sora"),first);//插入"sora"到第二个节点
while (first != null){
System.out.println(first);
first = first.next;
}
}
}
//定义一个Node类,Node对象表示双向链表的一个节点
class Node{
public Object item;//存放数据
public Node next;//指向下一个节点
public Node prev;//指向上一个节点
public Node(Object data){
this.item = data;
}
@Override
public String toString() {
return "Node{" +
"item=" + item +
'}';
}
public static Node insert(int index,Node newNode,Node first){
Node flag = first;
for (int i = 0; i < index; i++) {
if(first == null){
throw new RuntimeException("first 节点不能为 NULL");
}
first = first.next;
}
if(first.prev != null){
newNode.prev = first.prev;
first.prev.next = newNode;
}
newNode.next = first;
first.prev = newNode;
if(index == 0){
flag = flag.prev;
}
return flag;
}
}
底层结构
自己看jdk
Set接口
Set接口基本介绍
-
无序(添加和取出的顺序不一致),没有索引,虽然取出的顺序不是添加的顺序,但是取出的顺序是固定的
-
不允许重复元素,所以最多包含一个null(不会覆盖原来的元素,只会返回布尔值)
set.add("jack");//return true; set.add("jack");//return false;
-
JDK API中Set接口的实现类有:
HashSet
HashSet的全面说明
-
HashSet实现了Set接口
-
HashSet实际上是HashMap,看下源码。
-
可以存放null值,但是只能有一个null,不能重复
-
HashSet不保证元素是有序的,取决于hash后,再确定索引结果
-
不能有重复元素/对象。在前面Set接口使用已经讲过。
-
仅存放Key值,因为底层使用HashMap,Value值是一个常量,仅Key值是有用的
HashSet底层模拟
HashSet底层是HashMap,HashMap底层是(数组+链表+红黑树)
public class HashSetStructure {
public static void main(String[] args) {
//模拟一个HashSet的底层
//1.创建一个数组,数组的类型是Node[]
//2.有些人,直接把 Node[] 数组称为 表table
Node table[] = new Node[16];
System.out.println("table = "+table);
//3.创建节点
Node john = new Node("john", null);
table[2] = john;
System.out.println("table = "+table);
Node jack = new Node("jack", null);
john.next = jack;//将jack节点挂载到john
System.out.println("table = "+table);
}
}
class Node{
Object item;//存放数据
Node next;//指向下一个节点
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
@Override
public String toString() {
return "Node{" +
"item=" + item +
", next=" + next +
'}';
}
}
- 当节点大于64,数组长度大于16时,数据结构转为红黑树
HashSet底层JDK
- HashSet底层HashMap
- 添加一个元素时,先得到hash值,会转成->索引值
- 找到存储数据表table,看这个索引位置是否已经存放有元素
- 如果没有,直接加入
- 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
- 在JAVA8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
添加元素过程
public class HashSet_ {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
/*
* final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
* table就是HashMap 的一组数组,类型是Node[]
* if语句表示如果当前table是null或者大小=0
* 就是第一次扩容,到16个空间,
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
* (1)根据key,得到hash 去计算该 key 应该存放到table表的哪个索引位置
* 并且把这个位置对象,赋给 p
* (2)判断p是否为null
* (2.1)如果p为null,表示还没有存放元素,就创建一个Node(key="java",value=PRESENT)
* (2.2)就放在该位置tab[i] = newNode(hash,key,value,null)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
* 如果size>限额,就扩容
* ⭐size就是我们每加入一个节点Node(K,V,hash,next)
* 也就是当我们给hashset增加一个元素时候,table大小也会增加
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
* */
hashSet.add("java");
}
}
扩容和转换红黑树过程
- HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子
(loadFactor)是0.75 = 12 - 如果table数组使用到了临界值12,就会扩容到16 * 2 = 32,新的临界值就是32 * 0.75 = 24
- 在JAVA8中,如果一条链表的元素个数大于默认值TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制。
后续学完红黑树再回来分析代码
LinkedHashSet
基本知识
-
LinkedHashSet 是 HashSet的子类
-
Linked是链表的意思
-
LinkedHashSet底层是LinkedHashMap,底层维护了一个数组+链表+双向链表
-
LinkedHashSet根据元素的hashCode值来决定元素的存储位置(数组索引),同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kGpauHts-1678672774062)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220922161705104.png)]
-
LinkedHashSet不允许添加重复元素。
添加元素
- 添加第一次时候直接将 数组table 扩容到 16,存放的节点类型是 LinkedHashMap$Entry(内部类)
- 数组是
HashMap$Node[]
存放的元素/数据是LinkedHashMap$Entry
- 继承关系是再内部类完成的,内部类直接的继承
TreeSet(排序)
public class TreeSet_ {
public static void main(String[] args) {
// TreeSet treeSet = new TreeSet();
//添加数据
//1.当我们使用无参构造器,创建TreeSet时,元素是无序的
//2.当希望添加的元素,按照字符串大小来排序
//3.使用TreeSet提供的一个构造器,可以传入一个比较器接口(匿名内部类)
// 并指定排序规则
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String) o1).compareTo((String) o2);
}
});
//源码解读
//1.构造器把传入的比较器对象,赋给了TreeSet的底层TreeMap的属性thisparator
//
treeSet.add("jack");
treeSet.add("tom");
treeSet.add("sp");
treeSet.add("a");
System.out.println("treeSet" + treeSet);//treeSet[a, jack, sp, tom] 是无序的
}
}
HashSet与HashMap对比
Hashset和hashmap的区别🔗
Map
Map接口实现类的特点[实用]
注意:这里将的是JDK8的Map接口特点
-
Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
-
Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
-
Map中的key不允许重复,原因和HashSet一样,通过Hash检验,前面分析过源码
-
Map中的value可以重复
-
Map的Key可以为null,value也可以为null
-
常用String类作为Map的key
-
key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
-
Map存放数据的Key-Value示意图,一堆k-v是放在一个Node中的,又因为Node实现了Entry接口,有一些书上也说 一对k-v 就是一个Entry
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oCrRUMEq-1678672774063)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220925111954226.png)]
public class Map_ {
public static void main(String[] args) {
//Map接口实现类的特点
//1.Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
//2.Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
//3.Map中的key不允许重复,原因和HashSet一样,通过Hash检验,前面分析过源码
//4.Map中的value可以重复
Map map = new HashMap();
map.put("no1","邓圣君");//k-v
map.put("no2","张无忌");//k-v
map.put("no1","张三丰");//当有相同的k时,就进行v的替换
map.put("no3","张三丰");//v可以重复
System.out.println(map);//{no2=张无忌, no1=邓圣君} 可看出无序输出
System.out.println(map.get("no1"));//张三丰
}
}
public class MapSource {
public static void main(String[] args) {
Map map = new HashMap();
map.put("no1","dsj");
map.put("no2","张无忌");
//1.k-v 最后是HashMap$Node node = newNode(hash,key,value,null)
//2.k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合(只有collection实现了迭代器,所以需要转换成set) ,该集合存放的元素的类型是Entry
// 而一个Entry对象就有k,v EntrySet<Entry<k,v>>
//3.在entrySet中,定义的类型是Map.Entry,但是实际上存放的还是HashMap$Node
// 这时因为 static class NOde<K,V> implements Map.Entry<K,V>
//4.当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历,因为 Map.Entry 提供了重要方法
// K getKey(); V getValue();
Set set = map.entrySet();
System.out.println(set);
for (Object obj : set) {
// System.out.println(obj.getClass());//HashMap$Node
//为了从HashMap$Node取出k-v
//1.先做一个向下转型
Map.Entry entry = (Map.Entry)obj;
System.out.println(entry.getKey() + "-" + entry.getValue());
}
//将k-v键值对分别转换成集合
Set set1 = map.keySet();
Collection values = map.values();
}
}
Map接口和常用方法
- put:添加
- remove:根据键删除映射关系
- get:根据键获取值
- size:获取元素个数
- isEmpry:判断个数是否为0
- clear:清楚
- containsKey:查找键是否存在
Map接口的遍历
-
Map接口遍历方法
- containsKey:查找键是否存在
- keySet:获取所有的键的set
- Values:获取所有的值的collection
- entrySet:获取所有键值对
public class MapFor { public static void main(String[] args) { Map map = new HashMap(); map.put("邓超","孙俪"); map.put("王宝强","马蓉"); map.put("宋","马蓉"); map.put("刘令博",null); map.put(null,"刘亦菲"); map.put("鹿晗","关晓彤"); //第一组:先取出所有Key,通过Key取出对应的value Set keyset = map.keySet(); //(1)增强for System.out.println("---增强for---"); for (Object key : keyset) { System.out.println(key + "-" + map.get(key)); } //(2)迭代器 System.out.println("---迭代器---"); Iterator iterator = keyset.iterator(); System.out.println(iterator); while (iterator.hasNext()) { Object next = iterator.next(); System.out.println(next + "-" + map.get(next)); } //第二组,把所有的values取出 Collection values = map.values(); //这里可以使用Collections使用的遍历方法 //(1)增强for System.out.println("---取出所有的value 增强for---"); for (Object value : values) { System.out.println(value); } //(2)迭代器 System.out.println("---取出所有value 迭代器---"); Iterator iterator1 = values.iterator(); while (iterator1.hasNext()) { Object next = iterator1.next(); System.out.println(next); } //第三组:通过EntrySet 来获取k-v //⭐Entry是接口,Nodes实现,但是Node访问权限为默认,我们无法引用,因此使用Entry类型 System.out.println("---使用EntrySet的for增强(第三种)---"); Set entrySet = map.entrySet();//EntrySet<Map.Entry<K,V>> //(1)增强for for (Object entry : entrySet) { //将entry转成Map.Entry Map.Entry m = (Map.Entry)entry; System.out.println(m.getKey() + "-" +m.getValue()); } //(2)迭代器 Iterator iterator2 = entrySet.iterator(); System.out.println("---使用EntrySet的迭代器(第三种)---"); while (iterator2.hasNext()) { Map.Entry entry = (Map.Entry)iterator2.next(); System.out.println(entry.getKey() + "-" +entry.getValue()); } } }
##HashMap底层机制
HashMap类中的Node<K,V>类里有一个Node<K,V> next。那以Node<K,V>.next.next.next这种结构形式存储元素就是所说的链表,而Node<K,V>[ ] tab就是数组,tab所存储元素为每个链表的第一个元素。
每个数组的位置就是一个哈希值,如果两个值哈希值一样,就会占用一个位置,他们就成了一个链表
Java8优化了这个地方,如果相同哈希值,链表的长度超过8,就从链表转换成红黑树
Node类是HashMap的一个静态内部类,实现了 Map.Entry<K,V>接口。在调用put方法创建一个新的键值对时,会调用newNode方法来创建Node对象
二、数据结构
HashMap内部结构是数组(Node[] table)和链表结合组成的复合结构,数组被分成一个个桶(bucket),通过哈希值决定键值对在这个数组的寻址;哈希值相同的键值对,则以链表形式存储。需注意的是:链表大小超过阈值(TREEIFY_THRESHOLD = 8)时,链表就会被改造成树形结构。 下面示意图为一个简单的HashMap示意图:
HashMap底层机制和源码剖析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bt1Osy6P-1678672774064)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220926105014312.png)]
- (K,V)是一个Node实现了Map.Entry<K,V>,查看HashMap源码可以看到
- jdk7的hashMap底层实现[数组+链表],jdk8底层[数组+链表+红黑树]
- 扩容机制
- HashMap底层维护了Node类型的数组table,默认为null
- 当创建对象时,将加载因子(loadfactor)初始化为0.75
- 当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应添加处理。如果添加时发现容量不够,则需要扩容。
- 第一次添加,则需要扩容table容量为16,临界值(threshold)为12
- 以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的两倍,即24,依次类推
- 在JAVA8种,如果一条链表的元素个数超过THREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化
Hashtable
-
存放的元素是键值对:即k-v
-
Hashtable的键和值都不能为null
-
Hashtable使用方法基本和HashMap一样
-
Hashtable是线程安全的(synchronized),hashMap是线程不安全的
-
简单看下底层结构
public class HashTableExercise { public static void main(String[] args) { Hashtable table = new Hashtable();//ok table.put("john",100);//ok table.put(null,100);//异常 table.put("john",null);//异常 table.put("lucy",100);//ok table.put("lic",100);//ok table.put("lic",88);//替换 table.put("hello1",1); table.put("hello2",1); table.put("hello3",1); table.put("hello4",1); table.put("hello5",1); table.put("hello6",1); System.out.println(table); //1.底层有数组Hashtable$Entry[] 初始化大小为 11 //2.临界值 threshold = 8(11 * 0.75) //3.扩容:按照自己的扩容机制来进行 //4.执行方法addEntry(hash,key,value,index);添加K-V 封装Entry //5.当if(count>=threshold)满足时,就进行扩容 //6.按照int newCapacity = (oldCapacity << 1) + 1;的规则进行扩容 } }
Hashtable 和 HashMap对比
TreeMap
Properties(Map接口实现类)
基本介绍
-
Propertier类继承自Hashtable类并且实现了Map接口也是使用一种键值对K-V的形式来保存数据
-
他的使用特点和Hashtable类似
-
Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象并进行读取和修改
-
说明:工作后 xxx.properties文件通常作为配置文件,这个知识点在IO流举例,有兴趣可以先看文章
https://wwwblogs/xudong-bupt/p/3758136.html
基本使用
集合选型以及小结
在开发中选择说明集合实现类,主要取决于业务特点,然后根据集合实现类特征进行选择,分析如下:
-
先判断存储的类型(一组对象[单列]或一组键值对[双列])
-
一组对象[单列]:Collection接口
允许重复:List
增删多:LinkedList[底层维护了一个双向链表]
查改多:ArrayList[地城维护Object类型的可变数组]、Vector[线程安全,效率太低面临被淘汰的威胁]
不允许重复:Set
无序:HashSet[底层是HashMap,维护一个哈希表 即(数组+链表+红黑树)]
排序:TreeSet
插入和取出顺序一致:LinkedHashSet[维护数组+链表+双向链表]
-
一组键值对[双列]:Map接口
键无序:HashMap[底层是:哈希表 jdk7:数组+链表,jdk8:数组+链表+红黑树]、Hashtable[线程安全]
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMap
读取文件:Properties
Collections工具类
-
Collections工具类介绍
- Collections是一个操作Set、List和Map等集合的工具类
- Collections中提供了一系列静态方法对集合元素进行排序、查询和修改等操作
-
排序操作
- void reverse(List):反转List中元素的顺序
- void shuffle(List):对List集合元素进行随机排序
- void sort(List,Comparator):根据指定的Comparator产生的顺序对List集合元素进行排列
- void sort(List):根据元素的自然顺序对指定的List集合元素按升序排序
- void swap(List,int,int):将指定list集合中的i处元素和j处元素进行交换
-
查找、替换
- Object max(Collection):根据元素的自然顺序,返回给定
- Object max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
- Object min(Collection,Comparator)
- Object min(Collection)
- int frequency(Collection,Object):返回指定集合中指定元素的出现次数
- boolean replaceAll(List list, Object oldVal,Object newVal):使用新值替换List对象的所有旧值
多线程基础
进程相关概念
-
进程
- 进程是指运行中的程序,比如我们使用的QQ,就启动了一个进程,操作系统会为该进程分配内存空间。当我们使用迅雷,有启动了一个进程,操作系统将为迅雷分配新的内存空间。
- 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程。
-
线程
- 单线程:同一时刻,只允许执行一个线程
- 多线程:同一时刻,可以执行多个线程,比如:一个QQ进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
-
其它
-
并发:同一时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多任务就是并发
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-26J2DXaz-1678672774065)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221018191239074.png)]
-
并行:同一时刻,多个任务同时执行。多喝cpu可以实现并行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rPeMs1VX-1678672774066)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221018191254163.png)]
-
线程的基本使用
-
创建线程的两种方式
在java中线程来使用有两种方法
- 继承Thread类从,重写run方法
- 实现Runable接口,重写run方法
package com.threaduse; /* * @author 远空_ * @version 1.0 * */ public class Thread01 { public static void main(String[] args) { Cat cat = new Cat(); cat.start(); } } //1.当一个类继承了Thread类,该类就可以当作线程使用 //2.我们会重写run方法,写上自己的业务代码 //3.run Thread类实现了Runnable接口的run方法 class Cat extends Thread{ int times = 0; public void run(){ //Java中实现真正的多线程是start中的start()方法,run知识一个普通的方法,多线程高并发专题剖析 while(true){ System.out.println("喵喵"+(++times)); try { Thread.sleep(1000); }catch (InterruptedException e) { e.printStackTrace(); } if(times==10){ break; } } } }
-
使用JConsole监控线程执行情况,并画出示意图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wqj8tGd9-1678672774067)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221019094537881.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zX5VCbdV-1678672774069)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230103000128684.png)]
package com.threaduse; /* * @author 远空_ * @version 1.0 * */ public class Thread01 { public static void main(String[] args) throws InterruptedException { Cat cat = new Cat(); cat.start();//启动子线程thread0 //当main线程启动一个子线程Thread-0,主线程不会阻塞,会继续执行 //这时,主线程和子线程是交替执行的 System.out.println("主线程继续执行"+Thread.currentThread().getName()); for (int i = 0; i < 60; i++) { System.out.println("主线程 i="+i); //让主线程休眠 Thread.sleep(1000); } } } //1.当一个类继承了Thread类,该类就可以当作线程使用 //2.我们会重写run方法,写上自己的业务代码 //3.run Thread类实现了Runnable接口的run方法 class Cat extends Thread{ int times = 0; public void run(){ //Java中实现真正的多线程是start中的start()方法,run知识一个普通的方法,多线程高并发专题剖析 while(true){ System.out.println("喵喵"+(++times)+" 线程名:"+Thread.currentThread().getName()); try { Thread.sleep(1000); }catch (InterruptedException e) { e.printStackTrace(); } if(times==120){ break; } } } }
-
源码解读
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-teMDQ3az-1678672774070)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221019095609583.png)]
//源码解读 /* (1)public synchronized void start() { start0() } //start0()是本地方法,是JVM调用,底层是c/c++实现 //真正实现多线程的效果,是start0(),而不是run (2)private native void start0(); * */
案例2-实现Runnable接口
说明:
- java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了
- java设计者们提供了另外一个方式创建进程,就是通过实现Runnable接口来实现创建进程
使用:
package com.threaduse;
/*
* @author 远空_
* @version 1.0
* 通过实现Runnable来开发线程
* */
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
//dog.start();这里不能调用start
//因此我们创建Thread对象,把dog对象(实现Runnable),放入Thread对象
Thread thread = new Thread(dog);
thread.start();
}
}
class Dog implements Runnable{
int count = 0;
@Override
public void run() {
while(true){
System.out.println("小狗汪汪叫~"+(++count)+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
补充:
-
这里底层使用了设计模式[代理模式-静态代理]
//模拟一个Thread架构 class proxy implements Runnable{//你可以把Proxy类当作ThreadProxy private Runnable target = null; @Override public void run() { if(target != null){ target.run(); } } public proxy(Runnable target) { this.target = target; } public void start(){ start0(); } public void start0(){ } }
继承Thread vs 实现Runable的区别
- 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
- 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable
线程终止
-
基本说明
- 当线程完成任务后,会自动退出
- 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
-
代码演示
package com.threaduse; /* * @author 远空_ * @version 1.0 * */ public class ThreadExit_ { public static void main(String[] args) throws InterruptedException { T t = new T(); t.start(); //如果希望主线程去控制子线程的终止,必须可以修改loop变量 //让t1退出run放到,从而终止t1线程->通知方式 //让主线程休眠10秒后终止子线程 Thread.sleep(5000); t.setLoop(false); } } class T extends Thread{ private int count = 0; //设置一个控制变量 private boolean loop = true; @Override public void run() { while (loop){ try { Thread.sleep(50); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("hello"+(++count)); } } public void setLoop(boolean loop) { this.loop = loop; } }
常用Thread类方法
- 第一组
- setName //设置线程名称
- getName //返回该线程的名称
- start //使该线程开始执行;java虚拟机底层调用该线程的start0方法
- run //调用线程对象run方法;
- setPriority //更改线程的优先级
- getPriority //获取线程优先级
- sleep //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
- interrupt //中断线程
-
第一组注意细节
- start底层会创建一个新的线程,调用run,run就是一个简单的方法调用,不会启动线程
- 线程优先级的范围
- interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断线程休眠
- sleep:线程的静态方法,使当前线程休眠
package com.threaduse.method; /* * @author 远空_ * @version 1.0 * */ public class ThreadMethod01 { public static void main(String[] args) throws InterruptedException { //测试相关的方法 T t = new T(); t.setName("dsj"); t.setPriority(Thread.MIN_PRIORITY); t.start(); //主线程输出打印5个hi,然后我就中断 子线程的休眠 for (int i = 0; i < 5; i++) { Thread.sleep(1000); System.out.println("hi "+i); } System.out.println(t.getName()+" 优先级:"+t.getPriority()); t.interrupt();//当执行到这里,就会中断t线程的休眠 Thread.sleep(1000); System.out.println("主线程结束"); } } class T extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { try { Thread.sleep(50); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName()+" 吃包子~~~~"+i); } try { System.out.println(Thread.currentThread().getName()+" 休眠中~~~~"); Thread.sleep(20000); } catch (InterruptedException e) { //当该线程执行到一个interrupt方法时,就会catch一个异常,可以加入自己的业务代码 //InterruptException是捕获到一个中断异常 System.out.println(Thread.currentThread().getName()+"被interrupt了"); } } }
-
第二组
-
yield:线程的礼让。让出cpu,让其它线程执行,但礼让的时间不确定,所以也不一定礼让成功
-
join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
package com.threaduse.method; import com.threaduse.Thread02; /* * @author 远空_ * @version 1.0 * */ public class ThreadMethod02 { public static void main(String[] args) { T1 t1 = new T1(); t1.setName("T1"); t1.start(); for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+" 在吃第"+i+"个包子"); if(i==4){ Thread.yield();//礼让 try { System.out.println("让子线程插队."); t1.join();//插队 } catch (InterruptedException e) { throw new RuntimeException(e); } } try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } class T1 extends Thread{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+" 在吃第"+i+"个包子"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }
-
用户线程和守护线程
-
用户线程:也叫工作线程,当线程的任务执行完或以通知方式结束
myDaemonThread.setDaemon(true);
-
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
-
常见的守护线程:垃圾回收机制
package com.threaduse.method;
/*
* @author 远空_
* @version 1.0
* */
public class ThreadMethodExercise {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
myDaemonThread.setDaemon(true);
myDaemonThread.start();
//如果外面希望当main线程结束后,子线程自动结束
//秩序将子进程设置为线程守护即可
for (int i = 0; i < 10; i++) {
System.out.println("宝强在辛苦的工作"+i);
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread{
@Override
public void run() {
for (;;){
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("马蓉在外面快乐聊天,哈哈哈~~~");
}
}
}
线程的生命周期
-
JDK钟用Thread.State枚举表示了线程的几种状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cjNBBPkQ-1678672774071)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221020101044087.png)]
-
线程状态转换图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mPv9dtdu-1678672774072)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221020101347390.png)]
Synchronized线程同步机制
-
简介
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
- 也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其它线程都不可以对这个内存地址进行操作,直到该线程完成操作,其它线程才能对该内存地址进行操作。
-
同步具体方法-Synchronized
-
同步代码块
synchronized(对象){//得到对象的锁,才能操作同步代码 //需要被同步代码; }
-
synchronized还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m(String name){ //需要被同步的代码 }
-
如何理解:
就好像某小伙伴上厕所前先把门关上(上锁),完事后再出来(解锁),那么其它小伙伴就可在使用测试了,如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aeh0LK0P-1678672774073)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221020230600013.png)]
-
使用synchronized解决售票问题
-
分析同步原理
互斥锁
-
基本介绍
- Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
- 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
- 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
- 同步的局限性:导致程序的执行效率要降低
- 同步方法(非静态)的锁可以是this,也可以是其它对象(要求是同一个对象)
- 同步方法(静态)的锁为当前类本身
-
注意细节
-
当静态方法同步时,锁为当前类本身
synchronized(类名.class){//得到对象的锁,才能操作同步代码 //需要被同步代码; }
-
同步方法(非静态)的锁可以是this,也可以是其它对象(要求是同一个对象)
每个对象都有一把锁,通过别的对象也可以实现同步访问(不一定锁本对象)
-
实现的落地步骤:
- 需要先分析上锁代码
- 选择同步代码块(尽量选择,锁范围越小,效率越高)或同步方法
- 要求多个线程的锁对象为同一个即可!
-
不使用继承Thread的原因:其为一个对象一个线程,无需同步(或者说通过静态static属性锁定一个对象,也可以实现多线程上锁)
-
线程的死锁
-
基本介绍
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生
-
应用案例
妈妈:你先完成作业,才让你玩手机
小明:你先让我玩手机,我才完成作业
package com.synchronized_;
/*
* @author 远空_
* @version 1.0
死锁模拟
* */
public class DeadLock_ {
public static void main(String[] args) {
DeadLockDemo A = new DeadLockDemo(true);
DeadLockDemo B = new DeadLockDemo(false);
A.start();
B.start();
}
}
class DeadLockDemo extends Thread{
static Object o1 = new Object();
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
//1.如果flag为T,线程就会先得到/持有o1对象锁,然后尝试获取o2对象锁
//2.如果线程A得不到o2对象锁,就会进行Blocked
//3.如果flag为F,线程B就会先得到/持有o2对象锁,然后就回去获取o1对象锁
//4.如果线程B得不到o1对象锁,就会blocked
if(flag){
synchronized (o1){//对象互斥锁,下面就是同步代码
System.out.println(Thread.currentThread().getName()+"进入了1");
synchronized (o2){//这里获得li对象的监视权
System.out.println(Thread.currentThread().getName()+"进入了2");
}
}
}else {
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"进入了3");
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"进入了4");
}
}
}
}
}
释放锁
-
下面操作会释放锁
-
当线程的同步方法、同步代码块执行结束
案例:上厕所,完事出来
-
当前线程在同步代码块、同步方法中遇到break、return
案例:没有正常的完事,经理叫他修改bug,不得已出来
-
当前线程在同步代码块、同步方法出现了未处理的ERROR或Exception,导致异常结束
案例:没有正常完事,发现没带纸巾,不得已出来
-
当前线程在同步到代码块、同步方法中执行了线程对象的wait()方法,当钱线程暂停,并释放锁。
案例:没有正常完事,觉得需要酝酿一下,所以出来等会再进去
-
-
下面操作不会释放锁
-
线程执行同步代码块或同步方法时候,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
案例:上厕所,太困了,在坑位上眯了一会
-
线程执行同步代码块时,其它线程调用了该线程的suspend()方法讲该线程挂起,该线程不会释放锁
提示:应尽量避免实验suspend和re’sume方法来控制线程,方法不再推荐使用
-
IO流
文件
什么是文件
文件,对我们并不模式,文件是保存数据的地方,比如大家经常使用的word文档,txt文件,excel文件…都是文件。它既可以保存一张图片,也可以保持视频,声音…
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m6Eusq6E-1678672774074)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221204161231602.png)]
文件流
文件再程序中是以流的形式来操作的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UeQHumhi-1678672774075)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221204161438960.png)]
流:数据再数据源(文件)和程序(内存)之间经理的路径
输入流:数据从数据源(文件)到程序(内存)的路径
输出流:数据从程序(Neicun)到数据源(文件)的路径
常见的文件操作
创建文件对象相关构造器和方法
-
相关方法
new File(String pathname)//根据路径构建一个File对象 new File(File parent,String child)//根据文件对象目录+子路径构建 new File(String parent,String child)//根据父目录+子路径构建 createNewFile() //创建新文件 //File对象通常是文件路径
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-62AFGbTd-1678672774076)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221204170101347.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MaC6qIIB-1678672774077)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221204170114299.png)]
获取文件的相关信息
- getName文件名字
- getAbsolutepath文件绝对路径
- getParent文件父级目录
- length文件大小(字节)
- exists文件是否存在
- isFile是不是一个文件
- isDiretory是不是一个目录
//先创建文件对象
File file = new File("e:\\news1.txt");
//调用相应的方法,得到对应信息
System.out.println("文件名字="+file.getName());
System.out.println("文件绝对路径"+file.getAbsoluteFile());
System.out.println("文件父级目录"+file.getParent());
System.out.println("文件大小(字节)"+file.length());
System.out.println("文件是否存在"+file.exists());
System.out.println("是不是一个文件"+file.isFile());
System.out.println("是不是一个目录"+file.isDirectory());
目录的操作和文件删除
-
mkdir创建一级目录
//值得注意的是,在java中,目录也会被当作文件 String filePath = "e:\\a"; File file = new File(filePath); if(file.exists()){ System.out.println("文件存在"); }else { if (file.mkdir()){ System.out.println("创建成功"); }else { System.out.println("创建失败"); } }
-
mkdirs创建多级目录
//值得注意的是,在java中,目录也会被当作文件 String filePath = "e:\\a\\b\\c"; File file = new File(filePath); if(file.exists()){ System.out.println("文件存在"); }else { if (file.mkdirs()){ System.out.println("创建成功"); }else { System.out.println("创建失败"); } }
-
delete删除空目录或文件
//值得注意的是,在java中,目录也会被当作文件 String filePath = "e:\\news1.txt"; File file = new File(filePath); if(file.exists()){ if(file.delete()){ System.out.println("删除成功"); }else { System.out.println("删除失败"); } }else { System.out.println("文件不存在"); }
IO流原理以及流的分类
Java IO流原理
-
I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理数据传输。
如读写文件,网络通讯等。 -
Java程序中,对于数据的输入/输出操作以“流(stream)”的方式进行。
-
java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过方法输入或输出数据。
-
输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
-
输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中
流的分类
- 按操作数据单位不同分为:字节流(8bit)二进制文件、字符流(按字符,与文件编码有关)
- 按数据流的流向分类:输入流、输出流
- 按流的角色的不同分为:节点流、处理流/包装流
- 1. java的io流共涉及40多个累,实际上非常规则,都是如上四个抽象基类派生的。 2. 由着四个类派生出来的子类名称都是以其父类名作为子类后缀。
FileIutputStream介绍
package inputstream_;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
* @author 远空_
* @version 1.0
* */
public class FileInputStream_ {
public static void main(String[] args) {
}
@Test
public void readFile01(){
String filePath = "e:\\hello.txt";
int readData = 0;
FileInputStream fileInputStream = null;
try {
//创建FileInputStream
fileInputStream = new FileInputStream(filePath);
//从该输入流读取一个字节的数据。如果没有输入可用,此方法将阻止。
//如果返回-1,表示读取完毕
while((readData = fileInputStream.read()) != -1){
System.out.print((char)readData);//转成char显示
//一个汉字三个字节,而这只会读入一个字节,因此会造成乱码
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//关闭文件流,释放资源
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/*
* 使用read(byte[] b)进行读取文件
* */
@Test
public void readFile02(){
String filePath = "e:\\hello.txt";
int readData = 0;
byte[] buf = new byte[8];//一次读取8个字节
int readLen = 0;
FileInputStream fileInputStream = null;
try {
//创建FileInputStream
fileInputStream = new FileInputStream(filePath);
//从该输入流读取最多b.length字节的数据到字节数组。如果没有输入可用,此方法将阻止。
//如果返回-1,表示读取完毕
//如果读取正常,返回实际读取的字节数
while((readLen = fileInputStream.read(buf)) != -1){
System.out.print(new String(buf,0,readLen));//转成char显示
//一个汉字三个字节,而这只会读入一个字节,因此会造成乱码
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//关闭文件流,释放资源
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
FileOutputStream介绍
public class FileOutputStream_ {
/*演示使用FileOutputStream将数据写到文件中,
* 如果该文件不存在,则创建该文件
* */
@Test
public void writeFile(){
//创建对象
String filePath = "e:\\a.txt";
FileOutputStream fileOutputStream = null;
try {
//得到FileOutputStream对象
//说明:
//1.new FileOutputStream(FilePath)创建,当写入内容时,会覆盖原先内容
//2.new FileOutputStream(FilePath,true)创建,写入内容时,不会覆盖,在最后追加内容
fileOutputStream = new FileOutputStream(filePath,true);
//写入一个字节
fileOutputStream.write('H');//
//写入字符串
//str.getBytes()可用把字符串->字节数组
String str = "hello,world";
fileOutputStream.write(str.getBytes());
//write(byte[] b,int off,int len)将len字节从位于偏移量off的指定字节数组写入此文件输出流
fileOutputStream.write(str.getBytes(),0,str.length());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
作业:复制文件
/*
* @author 远空_
* @version 1.0
* */
public class FileCopy {
public static void main(String[] args) {
//完成文件拷贝,将e:\\head.jpg
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
String sourcePath = "E:\\head.jpg";
String newPath = "D:\\head.jpg";
try {
fileInputStream = new FileInputStream(sourcePath);
fileOutputStream = new FileOutputStream(newPath);
//定义一个字节数组,提高读取效率
byte[] buf = new byte[512];
int readLen = 0;
while ((readLen = fileInputStream.read(buf))!=-1){
//读取之后,就写入到文件,通过fileOutputStream
//即一遍读一边写
fileOutputStream.write(buf,0,readLen);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//关闭输入流和输出流
try {
if(fileOutputStream!=null){
fileOutputStream.close();
}
if (fileInputStream != null){
fileInputStream.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
FileReader和FileWriter介绍
FileReader和FileWriter是字符流,即按照字符来操作io
-
FileReader相关方法:
- new FileReader(File/String)
- read:每次读取单个字符,返回该字符,如果到文件末尾返回-1
- read(char[]):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1
相关API:
- new String(char[])将char[]转换成String
- new String(char[],off,len):将char[]的指定部分转换成String
public class FileReader_ { public static void main(String[] args) { String filePath = "e:\\story.txt"; FileReader fileReader = null; int data = 0; //1.创建 FILEreader对象 try { fileReader = new FileReader(filePath); //循环读取使用read while((data=fileReader.read())!=-1){ System.out.print((char) data); } } catch (IOException e) { throw new RuntimeException(e); }finally { if(filePath!=null){ try { fileReader.close(); } catch (IOException e) { throw new RuntimeException(e); } } } } @Test public void readFile02(){ String filePath = "e:\\story.txt"; FileReader fileReader = null; char[] buf = new char[8]; int readLen = 0; //1.创建 FILEreader对象 try { fileReader = new FileReader(filePath); //循环读取使用read while((readLen=fileReader.read(buf))!=-1){ System.out.print(new String(buf,0,readLen)); } } catch (IOException e) { throw new RuntimeException(e); }finally { if(filePath!=null){ try { fileReader.close(); } catch (IOException e) { throw new RuntimeException(e); } } } } }
-
FileWriter常用方法
- new FileWriter(File/String):覆盖模式,相当于流的指针在首端
- new FileWriter(File/String,true):追加模式,相当于流的指针在位段
- write(int):写入单个字符
- write(char[]):写入指定数组
- write(char[],off,len):写入指定属猪的指定部分
- write(String):写入整个字符串
- write(String,off,len):写入字符串的指定部分
相关API:String类:toCharArray:将String转换成char[]
注意:FileWriter使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件!(因为还在内存,并没有保存到外存中)
处理流的基本概念
基本介绍
-
节点可以从一个特定的数据源读写数据,如:FileReader、FileWriter[源码]
-
处理流(也叫包装流)是“连接”在已存在的流(节点流或处理流)之上,为程序提高更为强大的读写功能,如BufferedReader、BufferedWriter[源码]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LFE9HEIq-1678672774078)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221220183411824.png)]
节点流和处理流的区别和联系
- 节点流是底层流/低级流,直接跟数据源相接
- 处理流包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。【源码理解】
- 处理流(也叫包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连[模拟修饰器设计模式]
处理流的功能主要体现在下面两个方面:
- 性能的提高:主要以增加缓冲的方式来提高输入输出的效率
- 操作的便捷:处理流可能提供了一些了便捷的方法来一次输入输出大批量的数据,使用更加灵活
BufferedReader和BufferedWriter(处理流)
- BufferedReader和BufferedWriter属于字符流,是按照字符来读取数据的
- 关闭时,只需要关闭外层流即可[看后面源码] [会自动将封装的节点流给关闭]
package reader_;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/*
* @author 远空_
* @version 1.0
* */
public class BufferedReader_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\\note.txt";
//创建BufferedReader
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
//读取
String line;//按行读取效率高
//说明
//1.bufferedReader.readLine()是按行读取
//2.当返回null时,表示文件读取完毕
while ((line = bufferedReader.readLine())!=null){
System.out.println(line);
}
//关闭流,这里注意,只需要关闭BufferedReader,因为底层会去自动的去关闭节点流FileReader。
bufferedReader.close();
}
}
public class BufferedWriter_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\\ok.txt";
//创建BufferWriter
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath,true));
bufferedWriter.write("hello,韩顺平教育");
bufferedWriter.newLine();//插入一个换行,与系统相关
bufferedWriter.write("hello,韩顺平教育");
bufferedWriter.newLine();//插入一个换行,与系统相关
bufferedWriter.write("hello,韩顺平教育");
//插入一个换行
//说明:关闭外层流即可
bufferedWriter.close();
}
}
对象流(序列化)
- 看一个需求
- 将int num = 100这个int数据保存到文件中,注意不是100数字,而是int 100,并且能够从文件中直接回复int 100
- 将Dog dog = new Dog(“小黄”,3)这个Dog对象保存到文件中,并且从文件回复
- 上面的要求,就是能够将基本数据类型或对象进行序列化和反序列化的操作
- 序列化和反序列化
- 序列化就是保存数据时,保存数据的值和数据类型
- 反序列化就是在回复数据时,回复护具的值和数据类型
- 需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,改类必须实现如下两个接口之一:
- Serialzable //这是一个标记接口,没有方法
- Externalizable //该接口有方法需要实现,因此我们一般实现上面的Serializable接口
- 基本介绍
- 功能:提供了对基本类型或对象类型的序列化和反序列化的方法
- ObjectOutputStream提供序列化的功能
- ObjectInputStream 提供反序列化功能
- 注意事项
- 读写顺序要一致
- 要求实现序列化和反序列化对象,即Serializable
- 序列化的类中建议添加SerialVersionUID为了提高版本的兼容性
- 序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员(该修饰符使得java可以通过添加transient来拒绝被写入序列化的文件)
- 序列化对象时候,要求里面属性的类型也需要实现序列胡接口
- 序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类叶已经默认实现了序列化
public class ObjectInputStream_ {
public static void main(String[] args) {
String file = "e:\\data.dat";
try {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
Integer n1 = (Integer) objectInputStream.readInt();
Boolean n2 = (Boolean) objectInputStream.readBoolean();
System.out.println(""+n1+n2);
//读取
//解读:
//1.读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致
//2.否则会出现异常
System.out.println(objectInputStream.readChar());
System.out.println(objectInputStream.readDouble());
System.out.println(objectInputStream.readUTF());
Object o = objectInputStream.readObject();
System.out.println("运行类型"+o.getClass());
System.out.println("dog信息"+o);
objectInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
public class ObjectOutputStream_ {
public static void main(String[] args) throws IOException {
//序列化后,保存的文件格式不是纯文本,而是按照他的格式来保存
String file = "e:\\data.dat";
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
//序列化数据到e:\\data.dat
objectOutputStream.writeInt(100);//int -> Integer(实现了Serializable)
objectOutputStream.writeBoolean(true);//boolean -> Boolean(实现了Serializable)
objectOutputStream.writeChar('a');//char -> Character(实现了Serializable)
objectOutputStream.writeDouble(9.5);//double -> Double(实现了Serializable)
objectOutputStream.writeUTF("邓圣君");//String(实现了Serializable)
//保存一个dog对象
objectOutputStream.writeObject(new Dog("旺财",10));
objectOutputStream.close();
System.out.println("保存完毕");
}
}
标准输入输出流
//System 类里的 public final static InputStream in = null;
//System.in的编译类型InputStream
//System.in的运行类型BufferedInputStream
//表示的是标准输入 键盘
System.out.println(System.in.getClass());
//System 类里的 public final static PrintStream out = null;
//编译类型 PrintStream
//运行类型 PrintStream
//表示标准输出 显示器
System.out.println(System.out.getClass());
-
应用案例
传统方法System.out.println(“”)是使用out对象将数据输出到显示器
-
应用案例2
传统的方法,Scanner是从标准输入键盘接受数据
转换流InputStreamReader和OutputStreamWriter
-
先看一个文件乱码问题,引出学习转换流的必要性
使用ANSI编码保存文件后,用java默认的bufferedreader读出的数据乱码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UDN8KCJZ-1678672774079)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221221165021581.png)]
-
介绍
-
InputStreamReader:Reader的子类,可以将InputStream(字节流)包装成Reader(字符流)
-
OutputStreamWriter:Writer的子类,可以实现OutputStream(字节流)包装成Writer(字符流)
-
当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
-
可以在使用时指定编码格式(比如utf-8,gbk,gb2312等)
-
PrintStream和PrintWriter打印流
package printstream_;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
/*
* @author 远空_
* @version 1.0
* */
public class PrintStream_ {
public static void main(String[] args) throws FileNotFoundException {
PrintStream out = System.out;
//在默认情况下,PrintStream输出数据的位置是 标准输出,即显示器
out.print("邓圣君,你好");
//因为print底层调用了write,所以我们可以直接使用write进行输出
try {
out.write("java".getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
out.close();
//我们可以去修改打印流输出的位置/设备
//1.修改成 输出到 "e:\\f1.txt
//2."hello,java native!"就会输出到文件
System.setOut(new PrintStream("e:\\f1.txt"));
System.out.println("hello,java native!");
}
}
package printstream_;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
/*
* @author 远空_
* @version 1.0
* */
public class PrintWriter_ {
public static void main(String[] args) throws IOException {
PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt"));
printWriter.print("hi,evesky!");
printWriter.close();
}
}
Propertier类
-
看一个需求
如下一个配置文件 mysql.properties
ip=127.0.0.1
user=root
pwd=123456
请问编程读取ip、user和pwd的值是多少
-
分析
-
用传统方法
public class Properties01 { public static void main(String[] args) throws IOException { //读取mysql.properties文件,并得到ip、user、pwd BufferedReader bufferedReader = new BufferedReader(new FileReader("src/properties_/mysql.properties")); String line = ""; while((line = bufferedReader.readLine())!=null){ String[] split = line.split("="); //如果我们要求指定的ip值 if ("ip".equals(split[0])){ System.out.println("获取到ip值"+split[1]); } System.out.println(split[0]+"值是:"+split[1]); } bufferedReader.close(); } }
-
使用Properties类可以方便实现
-
-
基本介绍
-
专门用于读写配置文件的集合类
配置文件的格式:
键=值
键=值
-
注意:键值对不需要有空格,值不需要用引号引起来。默认类型是String
-
Properties常见方法
- load:加载配置文件的键值对到Properties对象
- list:将数据显示到指定设备
- getProperty(key):根据键获取对应值
- setProperty(key,value):设置键值对到Properties对象
- store:将Properties的键值对存储到配置文件,在idea中,保存信息到配置文件如果含有中文,会存储为unicode码
-
-
应用案例
1.使用Properties类完成对 mysal.properties 的读取
public class Properties02 { public static void main(String[] args) throws IOException { //使用Properties类完成对 mysal.properties 的读取 //1.创建Properties对象 Properties properties = new Properties(); //2.加载指定的文件 properties.load(new FileReader("src/properties_/mysql.properties")); //3.吧k-v显示到控制台 properties.list(System.out); //4.根据key值得到value String ip = properties.getProperty("ip"); System.out.println("获取ip:"+ip); } }
2.使用Properties类添加key-val 到新文件 mysql2.properties 中
public class Properties03 { public static void main(String[] args) throws IOException { //使用Properties类添加key-val 到新文件 mysql2.properties 中 Properties properties = new Properties(); //创建 //1.如果该文件没有key就是创建 //2.如果该文件有key,就是修改 //Properties父类是Hash她不可,底层就是Hashtable核心方法 properties.setProperty("charset","utf8"); properties.setProperty("user","邓圣君"); properties.setProperty("pwd","123456"); //将k-v存储到文件中即可 properties.store(new FileOutputStream("src/properties_/mysql.properties"),"123"); System.out.println("保存配置文件成功!"); } }
3.使用Properties类完成对 mysql.properties 的读取, 并修改某个key-val
public class Properties03 { public static void main(String[] args) throws IOException { //使用Properties类添加key-val 到新文件 mysql2.properties 中 Properties properties = new Properties(); //创建 //1.如果该文件没有key就是创建 //2.如果该文件有key,就是修改 //Properties父类是Hash她不可,底层就是Hashtable核心方法 properties.setProperty("charset","utf8"); properties.setProperty("user","邓圣君"); properties.setProperty("pwd","123456"); //将k-v存储到文件中即可 properties.store(new FileOutputStream("src/properties_/mysql.properties"),"123"); System.out.println("保存配置文件成功!"); } }
Swing
##Swing是什么?
1)AWT
在早期JDK1.0发布时,Sun公司就为GUI开发提供了一套基础类库,这套类库被称为AWT(Abstract Window Toolkit),即抽象窗口工具包。AWT的起初设想就是为了统一实现不同操作系统的图像界面,但问题是,不同操作系统图形库的功能可能不一样(比如按钮,在不同系统的就表现不一样),在一个平台上存在的功能在另外一个平台上则可能不存在,为此AWT不得不通过牺牲功能来实现平台无关性。不仅如此,AWT还是一个重量级组件,使用比较麻烦,且设计出的图形界面不够美观功能也非常有限。为此,Sun公司对AWT进行改进,提出了Swing组件,提供了更加丰富的组件和功能,来满足GUI设计的一切需求。
2)Swing
Swing是一个用于开发Java应用程序用户界面的开发工具包。使用 Swing 来开发图形界面比 AWT 更加优秀,因为 Swing 是一种轻量级组件,它采用纯 Java 实现,不再依赖于本地平台的图形界面,所以可以在所有平台上保持相同的运行效果,对跨平台支持比较出色。除此之外,Swing 提供了比 AWT 更多的图形界面组件,Swing开发人员只用很少的代码就可以利用Swing丰富、灵活的功能和模块化组件来创建优雅的用户界面。
为了和 AWT 组件区分,Swing 组件在javax.swing.*包下,类名均以 J 开头,例如: JFrame、JLabel、JButton等,而在AWT中叫Frame、Label等。
Swing能够干什么?
1)做系统,比如医院的软件。
2)做各种小游戏,提升逼格。
3)Intellij IDEA就是java swing开发的
4)毕业设计用的挺多。
Swing容器
Swing 中容器可以分为两类:顶层容器和中间容器,容器类都是继承自 Container 类。
顶层容器:
是进行图形编程的基础,一切图形化的东西都必须包括在顶层容器中。Swing中有三种顶层容器,分别是JFrame、JDialog 和 JApplet。
中间容器:
是容器组件的一种,也可以承载其他组件,但中间容器不能独立显示,必须依附于其他的顶层容器。常见的中间容器有 JPanel、JScrollPane、JTabbedPane 和 JToolBar。
Swing布局
1)FlowLayout(流式布局)
流式布局管理器:按水平方向依次排列放置组件,排满一行,换下一行继续排列。
2)GridLayout(网格布局)
网格布局管理器:按指定行列数分隔出若干网格,每一个网格按顺序放置一个控件。
3)GridBagLayout(网格袋布局)
网格袋布局管理器:每个组件可占用一个或多个网格,可将组件垂直、水平或沿它们的基线对齐。
4)BoxLayout(箱式布局)
箱式布局管理器:它把若干组件按水平或垂直方向依次排列放置。
5)GroupLayout(分组布局)
分组布局管理器:将组件按层次分组(串行 或 并行),分别确定 组件组 在 水平 和 垂直 方向上的位置。
6)CardLayout(卡片布局)
卡片布局管理器:它将容器中的每个组件看作一张卡片,一次只能看到一张卡片,其他卡片被遮住。
7)BorderLayout(边界布局)
边界布局管理器:它把 Container 按方位分为 5 个区域(东、西、南、北、中),每个区域放置一个组件。
8)SpringLayout(弹性布局)
弹性布局管理器:通过定义组件四条边的坐标位置来实现布局。
9)null(空布局)
空布局:也叫绝对布局,通过设置组件的坐标和宽高来布置组件。
看起来有很多是不是很吓人,其实不然,我们主要掌握以下4种
1、边界布局(BorderLayout)
2、流式布局(FlowLayout)
3、网格布局(GridLayout)
4、null(空布局)
Swing常用组件
如何创建Swing窗体程序
1.实例化JFrame对象,也就是创建一个窗体。
2.设置窗体的相关属性。
3.获取一个容器。
4.创建组件。
5.向容器添加组件。
6.使窗体可视。
import java.awt.Container;
import javax.swing.JButton;
import javax.swing.JFrame;
public class SwingLearn {
public static void main(String[] args) {
//实例化 JFrame
JFrame frame = new JFrame();
//设置相关属性
frame.setTitle("Swing学习");//标题
frame.setSize(300,300);//窗体大小
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//点击关闭按钮是关闭程序
frame.setLocationRelativeTo(null); //设置居中
frame.setResizable(false); //不允许修改界面大小
//获取容器
Container container = frame.getContentPane();
frame.setLayout(null);
//创建按钮
JButton jButton = new JButton("我是按钮");
jButton.setBounds(100, 100, 100, 40);
//按钮添加到容器中
container.add(jButton);
//设置显示
frame.setVisible(true);
}
}
Swing为什么发展不好?
1)运行Swing程序,用户的电脑上必须有java运行环境,这个不太现实也很不方便。
2)本身用Swing开发出来的客户端本身比较大,客户端也需安装到用户的电脑上。
3)如果遇到不同的操作系统,需要为不同的操作系统开发一套客户端。
4)B/S系统的强势崛起,因为B/S架构多用WEB网页进行开发,不需要安装客户端,在浏览器上打开,一旦代码发生变更,客户端不需要进行升级。
坦克大战
基本介绍
1.程序概述
本程序是一个简单的坦克游戏程序,使用java语言编写,在jdk环境下运行。游戏开始时,用户通过键盘操纵坦克移动,转弯和射击,与敌人坦克进行交战,知道消灭所有敌人就可以过关。本程序包括23个类,2800多行代码,和三个gif图片。
2.功能介绍
2.1开始游戏
本程序运行的主函数
3.涉及技术
- java面向对象编程
- 多线程
- 文件i/o操作
- 数据库
思考----编程-----思考----编程
java绘图坐标体系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UDEyUaZv-1678672774083)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221017083251603.png)]
-
绘图还必须该清楚一个非常重要的概念-像素
一个像素等于多少厘米
-
计算机屏幕上显示的内容都是由屏幕上的每一个像素组成的。例如,计算机显示器分辨率是800x600,表示计算机屏幕上每一行都由900个点组成,共有600行,整个计算机屏幕共有480000个像素。像素是一个密度单位,而厘米是一个长度单位,两者无法比较
绘图
- Component类提供了两个和绘图相关的重要方法
- paint(Graphics g)绘图组件的外观
- repaint()刷新组件的外观
- 当组件第一次在屏幕显示的时候,程序会自动的调用paint()方法来绘制组件。
- 在以下情况paint()将会被调用:
- 窗口最小化,再最大化
- 窗口的大小发生变化
- repaint函数被调用
Graphics类
Graphics 类你可以理解为画笔,为我们提供了各种绘制图形的方法:
-
画直线drawLine(int x1,int y1,int x2,int y2)
-
画矩形变矿 drawRect(int x,int y,int width,int height)
-
画椭圆边框 drawOval(int x,int y,int width,int height)
-
填充矩形 fillRect(int x,int y,int width,int height)
-
填充椭圆 fillOval(int x,int y,int width,int height)
-
画图片 drawImage(Image img,int x,int y,…)
//画图片 //1.获取图片资源 // Image image = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bg.jpg")); // g.drawImage(image,10,10,200,200,this);
-
画字符串 drawString(String str,int x,int y)
//画字符串 //设置画笔字体 g.setColor(Color.red); g.setFont(new Font("微软雅黑",Font.BOLD,50)); //这里设置的坐标,原点在左下角 g.drawString("北京你好",10,10);
-
设置画笔的字体 setFont(Font font)
-
设置画笔的颜色 setColor(Color c)
Java事件处理机制
-
事件处理机制-看一个问题
BallMove.java–让小球收到键盘的控制,上下左右移动
package com.event; import javax.swing.*; import java.awt.*; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; /* * @author 远空_ * @version 1.0 * 演示小球通过键盘控制上下左右的移动->讲解Java的事件控制 * */ public class BallMove extends JFrame{ MyPanel mp = null; public static void main(String[] args) { new BallMove(); } public BallMove(){ mp = new MyPanel(); this.add(mp); this.setSize(400,300); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.addKeyListener(mp); this.setVisible(true); } } class MyPanel extends JPanel implements KeyListener { private int x=10,y=10; //为了让小球可以移动,把它的坐标设置成变量 public void paint(Graphics g){ super.paint(g); g.fillOval(x,y,20,20);//默认黑色 } //有字符输出时,该方法就会触发 @Override public void keyTyped(KeyEvent e) { } //当某个键按下,该方法触发 @Override public void keyPressed(KeyEvent e) { //根据用户按下的不同键,来处理小球移动 //在java中,会给每个键分配一个值 switch (e.getKeyCode()){ case KeyEvent.VK_W: y--; break; case KeyEvent.VK_S: y++; break; case KeyEvent.VK_A: x--; break; case KeyEvent.VK_D: x++; break; } //让面板重绘 /*在以下情况paint()将会被调用: 1. 窗口最小化,再最大化 2. 窗口的大小发生变化 3. repaint函数被调用*/ this.repaint(); } //当某个键松开,该方法触发 @Override public void keyReleased(KeyEvent e) { System.out.println(e.getKeyCode()); } }
-
介绍
-
KeyLinstener接口(需要实现)
//有字符输出时,该方法就会触发 @Override public void keyTyped(KeyEvent e) { } //当某个键按下,该方法触发 @Override public void keyPressed(KeyEvent e) { } //当某个键松开,该方法触发 @Override public void keyReleased(KeyEvent e) { System.out.println(e.getKeyCode());//输出松开的按键 }
-
JFrame的方法
this.addKeyListener(mp);//窗口JFrame对象可以监听键盘事件,即可以监听到面板发生的键盘事件。
-
基本说明
java事件处理是采取“委派事件模型”。当事件发生时,产生事件的对象就会把此“信息”传递给“事件的监听者”处理,这里说的“信息”实际上就是java.awt.event事件类库里某个类所创建的对象,把它称为“事件的对象”。
-
前面我们提到几个重要的概念事件源,事件,事件监听器我们下面来全面的介绍它们
-
事件源:事件源是一个产生事件的对象,比如按钮,窗口等。
-
事件:事件就是承载事件源状态改变时的对象,比如当键盘事件、鼠标事件、窗口事件等等,会生成一个事件对象,该对象保存着当前事件很多信息,比如KeyEvent对象有含义被按下键的Code值。java.awt.event包和javax.swing.event包中定义了各种事件类型
-
事件类型:查阅jjdk文档
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FlTpfE2Y-1678672774084)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20221017170648885.png)]
坦克大战0.3
添加功能:当玩家按下j键,发射一颗子弹。
- 思路:
- 当发射一颗子弹后,就相当于启动了一个线程√
- Hero有子弹的对象,当按下J时,我们就启动一个发射行为(线程),让子弹不停移动,形成一个射击的效果√
- 我们My Panel 需要不停重绘才能出现该效果√
- 当子弹移动到面板的边界时,就应该销毁。(把启动的子弹的线程销毁)√
坦克大战0.4
- 添加功能
- 让敌人的坦克也能够发射子弹(可以有多颗子弹)√
- 当我方坦克击中敌人坦克时,敌人的坦克就消失,如果能做出爆炸效果更好。√
- 让敌人的坦克也可以自由随机的上下左右移动√
- 控制我方的坦克和敌人的坦克在规定的范围移动√
- 多个子弹的绘制√
- 防止坦克重叠√
- 思路Myself
- 在子弹类使用Vector集合保存所有发射的子弹对象
- 子弹对象线程中,当移动后判断是否碰撞敌方坦克,碰撞后敌人坦克从集合中移除
- 创建一个爆炸类,以及建立其集合,保存爆炸位置的数据,以及实现动画效果
- 随机移动用Random实现
- 每次坦克移动均检测是否碰撞实体
- 遍历子弹类Vector集合
坦克大战0.5
-
添加功能
-
防止敌人坦克重叠运动
-
记录玩家的成绩,存盘退出
-
记录当时敌人的坦克坐标,存盘退出
-
玩游戏时,可选择是开新游戏还是继续上局游戏
-
Net
网络相关概念
- 概念:两台设备之间通过网络实现数据传输
- 网络通信:将数据通过网络从一台设备传输到另一台设备
- java包下提供了一系列类或接口,供程序员使用,完成网络通讯
网络
- 概念:两台或多台设备通过一定物理设备连接起来就构成了网络
- 根据网络的覆盖范围不同,对网络进行分类:
- 局域网:覆盖范围最小,仅仅覆盖一个教师或一个机房
- 城域网:覆盖范围较大,可以覆盖一个城市
- 广域网:覆盖范围最大,可以覆盖全国,甚至全球,万维网是广域网的代表。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FEDL8BMf-1678672774085)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230103121554217.png)]
ip协议
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ar0L0q4w-1678672774085)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230103131804615.png)]
域名和端口号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2xmckCNE-1678672774086)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230103194909539.png)]
网络协议
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2mGLgkYz-1678672774087)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230103195212890.png)]
TCP UDP
- TCP协议: 传输控制协议
- 使用TCP协议前,须先建立TCP连接,形成传输数据通道
- 传输前,采用”三次握手"方式,是可靠的(数据准确无误传输)
- TCP协议进行通信的两个应用进程: 客户端、服务端
- 在连接中可进行大数据量的传输
- 传输完毕,需释放已建立的连接,效率低
- UDP协议
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据报的大小限制在64K内
- 因无需连接,故是不可靠的
- 发送数据结束时无需释放资源(因为不是面向连接的)速度快
- 举例: 厕所通知: 发短信
InetAddress类
-
相关方法
- 获取本材lnetAddress对象getLocalHost
- 根据指定主机名/域名获取ip地址对象 getByName
- 获取InetAddress对象的主机名getHostName
- 获取InetAddress对象的地址getHostAddress
public class API_ { public static void main(String[] args) throws UnknownHostException { //获取本机的InetAddress对线 InetAddress localhost = InetAddress.getLocalHost(); System.out.println(localhost);//小新14AIR/192.168.0.6 //2.根据指定主机名获取InetAddress 对象 InetAddress host = InetAddress.getByName("小新14AIR"); System.out.println(host);//小新14AIR/192.168.0.6 //3.根据域名返回InetAddress对象,比如www.baidu InetAddress host2 = InetAddress.getByName("www.baidu"); System.out.println(host2);//www.baidu/14.215.177.39 //4.通过 InetAddress对象,获取相应ip地址和域名 String ip = host2.getHostAddress(); String name = host2.getHostName(); System.out.println(ip+" "+name); } }
Socket(套接字,直译:插座)
-
基本介绍
- 套接字(Socket)开发网络应用程序被广泛采用,以至于成为事实上的标准。
- 通信的两端都要有Socket,是两台机器间通信的端点
- .网络通信其实就是Socket间的通信。
- Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
- 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端
TCP编程
基本介绍
- 基于客户端—服务端的网络通信
- 底层使用的是TCP/IP协议
- 应用场景举例:客户端发送数据,服务端接受并显示
- 基于Socket的TCP编程
案例演示1(字节流)
public class SocketTCP01Server {
public static void main(String[] args) throws IOException {
//细节,要求9999端口未被占用
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("等待连接,阻塞进程");
//2.当没有客户端连接999端口时候,程序阻塞,等待连接
//如果有客户端连接从,则会返回一个Socket对象,程序继续
//细节:如果ServerSocket 可以通过accept()返回多个Socket[多个客户端连接服务器的并发]
Socket socket = serverSocket.accept();
System.out.println("socket = "+socket);
//socket = Socket[addr=/192.168.0.6,port=1404,localport=9999]
//3.socket.getInputStream()读取客户端写入到数据通道的数据
InputStream inputStream = socket.getInputStream();
//4.IO读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf))!=-1){
System.out.println(new String(buf,0,readLen));
}
//5.关闭流和socket
inputStream.close();
socket.close();
}
}
public class SocketTCP01Client {
public static void main(String[] args) throws IOException {
//1.连接服务器
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端socket="+socket);
//客户端socket=Socket[addr=小新14AIR/192.168.0.6,port=9999,localport=1404]
//2.连接上后,生成Socket,通过socket.getOutputStream()
//得到和socket关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3.通过输出流。写入数据到数据通道
outputStream.write("hello,server".getBytes());
//4.关闭流对象和socket,必须关闭,节省资源
outputStream.close();
socket.close();
System.out.println("客户端退出");
}
}
注意:如果客户端没有发消息write(),服务端会进行等待
案例演示2(字节流)
public class SocketTCP02Client {
public static void main(String[] args) throws IOException {
//1.连接服务器
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端socket="+socket);
//客户端socket=Socket[addr=小新14AIR/192.168.0.6,port=9999,localport=1404]
//2.连接上后,生成Socket,通过socket.getOutputStream()
//得到和socket关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3.通过输出流。写入数据到数据通道
outputStream.write("hello,server".getBytes());
socket.shutdownOutput();
//4.获取和socket相关联的输入流,读取数据并显示
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen=inputStream.read(buf))!=-1){
System.out.println(new String(buf,0,readLen));
}
socket.shutdownInput();
//5.关闭流对象和socket,必须关闭,节省资源
outputStream.close();
socket.close();
System.out.println("客户端退出");
}
}
public class SocketTCP02Server {
public static void main(String[] args) throws IOException {
//细节,要求9999端口未被占用
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("等待连接,阻塞进程");
//2.当没有客户端连接999端口时候,程序阻塞,等待连接
//如果有客户端连接从,则会返回一个Socket对象,程序继续
//细节:如果ServerSocket 可以通过accept()返回多个Socket[多个客户端连接服务器的并发]
Socket socket = serverSocket.accept();
System.out.println("socket = "+socket);
//socket = Socket[addr=/192.168.0.6,port=1404,localport=9999]
//3.socket.getInputStream()读取客户端写入到数据通道的数据
InputStream inputStream = socket.getInputStream();
//4.IO读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf))!=-1){
System.out.println(new String(buf,0,readLen));
}
socket.shutdownInput();
//5.获取socket相关联的输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello,client".getBytes());
socket.shutdownOutput();
//6.关闭流和socket
inputStream.close();
outputStream.close();
socket.close();
}
}
案例演示3(字符流)
注意:如果使用字符流,需要手动刷新flush(),否则数据不会写入数据通道
public class SocketTCPClient {
public static void main(String[] args) throws IOException {
//1.连接服务器
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端socket="+socket);
//客户端socket=Socket[addr=小新14AIR/192.168.0.6,port=9999,localport=1404]
//2.连接上后,生成Socket,通过socket.getOutputStream()
//得到和socket关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream,"utf8");
BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
//3.通过输出流。写入数据到数据通道
bufferedWriter.write("hello,server");
//如果使用字符流,需要手动刷新flush(),否则数据不会写入数据通道
bufferedWriter.flush();
socket.shutdownOutput();
//4.获取和socket相关联的输入流,读取数据并显示
InputStream inputStream = socket.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"utf8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String s = null;
while ((s=bufferedReader.readLine())!=null){
System.out.println(s);
}
socket.shutdownInput();
//5.关闭流对象和socket,必须关闭,节省资源
bufferedReader.close();
socket.close();
System.out.println("客户端退出");
}
}
public class SocketTCPServer {
public static void main(String[] args) throws IOException {
//细节,要求9999端口未被占用
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("等待连接,阻塞进程");
//2.当没有客户端连接999端口时候,程序阻塞,等待连接
//如果有客户端连接从,则会返回一个Socket对象,程序继续
//细节:如果ServerSocket 可以通过accept()返回多个Socket[多个客户端连接服务器的并发]
Socket socket = serverSocket.accept();
System.out.println("socket = "+socket);
//socket = Socket[addr=/192.168.0.6,port=1404,localport=9999]
//3.socket.getInputStream()读取客户端写入到数据通道的数据
InputStream inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
//4.IO读取
String s = null;
while ((s=br.readLine())!=null){
System.out.println(s);
}
socket.shutdownInput();
//5.获取socket相关联的输出流
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));
bw.write("hello,client!");
//如果使用字符流,需要手动刷新,否则数据不会写入数据通道
bw.flush();
socket.shutdownOutput();
//6.关闭流和socket
br.close();
socket.close();
}
}
案例演示4(传输文件)
UDP编程
-
基本介绍
- 类 DatagramSocket 和 DatagramPacket[数据报] 实现了基于UDP 协议网络程序。
- .UDP数据报通过数据报套接字DatagramSocket 发送和接收,系统不保证UDP数据表一定能安全送到目的地,也不能确定什么时候可以抵达。
- DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接受端的IP地址和端口号
- UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方
-
基本流程
- 核心的两个类/对象 DatagramSocket与DatagramPacket
- 建立发送端,接收端(没有服务端、接受端概念
- 建立数据包 DatagramPacket对象
- 调用DatagramSocket的发送、接收方法
- 关闭DatagramSocket
应用案例1
//B端,服务器
package socket.udpcopy;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/*
* @author 远空_
* @version 1.0
* */
public class B {
public static void main(String[] args) throws IOException, InterruptedException {
DatagramSocket socket = new DatagramSocket(9999);
byte[] bytes = new byte[63 * 1024];
DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
socket.receive(packet);
byte[] data = packet.getData();
int len = packet.getLength();
String fileName = new String(data, 0, len);
System.out.println("客户端要求获取文件:"+fileName);
File file = new File("E:\\下载", fileName);
if(file.exists()){
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
int readLen = 0;
DatagramPacket sendPocket = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 8888);
String fileSize = Long.toString(file.length());
System.out.println("文件大小:"+fileSize);
sendPocket.setData(fileSize.getBytes(),0,fileSize.getBytes().length);
socket.send(sendPocket);
while ((readLen = bis.read(bytes))!=-1){
sendPocket.setData(bytes,0,readLen);
socket.send(sendPocket);
Thread.sleep(4);
System.out.println("传输");
}
}else {
System.out.println("文件不存在");
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("e:\\下载\\TankWar-master.zip"));
int readLen = 0;
DatagramPacket sendPocket = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 8888);
String fileSize = Long.toString(file.length());
sendPocket.setData(fileSize.getBytes(),0,fileSize.getBytes().length);
socket.send(sendPocket);
while ((readLen = bis.read(bytes))!=-1){
sendPocket.setData(bytes,0,readLen);
socket.send(sendPocket);
Thread.sleep(4);
System.out.println("传输");
}
}
socket.close();
}
}
//A端,客户端
import java.io.*;
import java.net.*;
import java.util.Scanner;
/*
* @author 远空_
* @version 1.0
* */
public class A {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
String s = scanner.next();
DatagramSocket socket = new DatagramSocket(8888);
DatagramPacket packet =
new DatagramPacket(s.getBytes(),s.getBytes().length, InetAddress.getLocalHost(), 9999);
socket.send(packet);
byte[] bytes = new byte[63 * 1024];
DatagramPacket recevicePacket = new DatagramPacket(bytes, bytes.length);
File file = new File("e:\\",s);
file.createNewFile();
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
socket.receive(recevicePacket);
String fileSize = new String(recevicePacket.getData(),0,recevicePacket.getLength());
System.out.println("文件大小:"+fileSize);
while (Long.parseLong(fileSize) > file.length()){
socket.receive(recevicePacket);
byte[] data = recevicePacket.getData();
int len = recevicePacket.getLength();
bos.write(data,0,len);
bos.flush();
System.out.println("文件传输进度:"+(100*file.length()/Integer.parseInt(fileSize))+"%");
}
System.out.println("文件传输完成");
socket.close();
}
}
多用户通信系统
需求分析
- 用户登录
- 拉取在线用户列表
- 无异常退出(服务器、客户端)
- 私聊
- 群聊
- 发文件
- 服务器新闻推送
多用户即时通讯系统 章节🔗
反射refluction
需求引出
-
根据配置文件 re.properties 指定信息,创建对象并调用方法
classfullpath=com.hspedu.Cat
method=hi
思考: 使用现有技术,你能做的吗?
-
这样的需求在学习框架时特别多,即通过外部文件配置,在不修改源码情况下,来控制程序,也符合设计模式的 ocp原则(开闭原则:不修改源码,来扩展功能)
基本介绍
-
反射机制允许程序在执行期借助于ReflectionAP取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到
-
加载完类之后,在堆中就产生了一个Class类型的对象 (一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射
-
p对象 --》类型 Person类
对象 cls —>类型 Class类
这个对象的类型是 Class
反射原理与用途
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HA6rsxwe-1678672774088)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230107230644304.png)]
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
反射相关的主要类
- java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
- java.lang.reflect.Method: 代表类的方法
- java.lang.reflect.Field: 代表类的成员变量
- java.lang.reflect.Constructor: 代表类的构造方法
这些类在 java.lang.reflection
/*
* @author 远空_
* @version 1.0
* */
public class Reflection01 {
public static void main(String[] args) throws Exception{
Cat cat = new Cat();
cat.hi();
Properties properties = new Properties();
properties.load(new FileInputStream("src/re.properties"));
String classfullpath=properties.getProperty("classfullpath");
String methodName = properties.getProperty("method");
//使用反射机制加载
//1.加载类,返回Class类型对象
Class aClass = Class.forName(classfullpath);
//2.通过aclass获取你加载的类的实例对象
Object o = aClass.newInstance();
System.out.println("o的运行类型"+o.getClass());
//(3)通过aclass得到你加载的类 Cat 的 methodName 的方法对象
// 即:在反射中,可以把方法视为对象(万物皆对象)
Method method = aClass.getMethod(methodName);
//(4)通过method来调用方法:即通过方法对象来实现调用方法
method.invoke(o);//传统方法 对象.方法() ,反射机制 方法.invoke(对象)
//java.lang.reflect.Field: 代表类的成员变量
//得到name字段
//getField不能得到私有的属性
Field nameField = aClass.getField("age");
//传统 对象.成员变量 ,反射:成员变量对象.get(对象)
System.out.println(nameField.get(o));//0
//java.lang.reflect.Constructor: 代表类的构造方法
Constructor constructor = aClass.getConstructor();//()中指定构造函数参数类型,这里返回无参构造器
System.out.println(constructor);//public question.Cat()
Constructor constructor1 = aClass.getConstructor(String.class);//这里传入String.class就是String类的Class对象
System.out.println(constructor1);//public question.Cat(java.lang.String)
}
}
反射的优缺点
- 优点: 可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
- 缺点: 使用反射基本是解释执行,对执行速度有影响
//反射缺点体现
package question;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/*
* @author 远空_
* @version 1.0
* */
public class Relection02 {
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
m1();
m2();
// 传统功夫,耗时:6
// 船新版本,耗时:1351
}
//传统方法来调用hi
public static void m1(){
Cat cat = new Cat();
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
cat.hi();
}
long end = System.currentTimeMillis();
System.out.println("传统功夫,耗时:"+ (end - start));
}
//反射机制调用方法
public static void m2() throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
Class<?> aClass = Class.forName("question.Cat");
Object o = aClass.newInstance();
Method hi = aClass.getMethod("hi");
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
hi.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("船新版本,耗时:"+ (end - start));
}
}
反射调用优化
关闭访问检查
- Method和Field、Constructor对象都有setAccessible0方法
- setAccessible作用是启动和禁用访问安全检查的开关
- 参数值为true表示 反射的对象在使用时取消访问检查提高反射的效率。参数值为false则表示反射的对象执行访问检查
关闭后耗时:传统功夫,耗时:4
船新版本,耗时:695
Class类
- 基本介绍
- Class也是类,因此也继承Object类 [类图]
- Class类对象不是new出来的,而是系统创建的[演示]
- 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次[演示1]
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过Class可以完整地得到一个类的完整结构,通过一系列API(下图常用方法)
- Class对象是存放在堆的
- 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括 方法代码16变量名,方法名,访问权限等等) https://www.zhihu/question/38496907示意图]
Class类的常用方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YL3qih4d-1678672774089)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230107234251092.png)]
获取类信息
- getName:获取全类名
- getSimpleName:获取简单类名
- getFields:获取所有public修饰的属性,包含本类以及父类的
- getDeclaredFields:获取本类中所有属性
- getMethods:获取所有public修饰的方法,包含本类以及父类的
- .getDeclaredMethods:获取本类中所有方法
- getConstructors: 获取所有public修饰的构造器,包含本类的(构造器不会继承通过super访问)
- getDeclaredConstructors:获取本类中所有构造器
- getPackage:以Package形式返回 包信息
- getSuperClass:以Class形式返回父类信息
- getInterfaces:以Class[]形式返回接口信息
- getAnnotations:以Annotation[] 形式返回注解信息
Field
- getModifiers: 以int形式返回修饰符[说明: 默认修饰符 是0,public 是1 ,private 是 2,protected 是 4,static 是8 ,final 是 16] , public(1) + static 8) = 9,本质是转换二进制读取位数
- getType:以Class形式返回类型
- getName:返回属性名
Method
- getModifiers:以int形式返回修饰符[说明: 默认修饰符 是0,public 是1 ,private 是 2 ,protected 是4static 是8 ,final是 161
- getReturnType:以Class形式获取 返回类型
- getName:返回方法名
- getParameterTypes:以Class[]返回参数类型数组
Constructor
- getModifiers: 以int形式返回修饰符
- getName:返回构造器名 (全类名)
- getParameterTypes:以Class[]返回参数类型数组
获取Class类对象的6种方法
package class_;
import question.Cat;
/*
* @author 远空_
* @version 1.0
* */
public class GetClass_ {
public static void main(String[] args) throws ClassNotFoundException {
//1.Class.forName
Class<?> aClass = Class.forName("question.Cat");
//2.类名.class,应用场景:用于参数传递
Class<Cat> catClass = Cat.class;
System.out.println(catClass);
//3.对象.getClass(),应用场景,有对象实例
Cat cat = new Cat();
Class aClass1 = cat.getClass();
System.out.println(aClass1);
//4.通过类加载器【4种】来获取到类的Class对象
//(1)先得到类加载器 car
ClassLoader classLoader = cat.getClass().getClassLoader();
//(2)通过类加载器得到Class对象
Class<?> aClass2 = classLoader.loadClass("question.Cat");
System.out.println(aClass2);
}
}
特殊的两种:
//5.基本数据类型,按如下方式得到Class对象
Class<Integer> integerClass = int.class;
Class<Character> characterClass = char.class;
Class<Boolean> booleanClass = boolean.class;
System.out.println();
//6.基本数据类型对应的包装类,可以通过.TYPE得到Class类对象
Class<Integer> type = Integer.TYPE;
Class<Character> type1 = Character.TYPE;
System.out.println(integerClass == type);//true
那些类型有Class对象
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- interface : 接口
- 数组
- enum :枚举
- annotation :注解
- 基本数据类型
- void
动态加载和静态加载
-
基本说明
反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
- 动态加载:运行时加载需要的类如果运行时不用该类,则不报错,降低了依赖性
-
类加载时机
- 当创建对象时 (new)//静态
- 当子类被加载时//静态
- 调用类中的静态成员时//静态
- 通过反射//动态
类加载(面试)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D9sOf9AP-1678672774090)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230108122247800.png)]
加载阶段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wVcYvnVe-1678672774091)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230108122714881.png)]
验证阶段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OAi5a7R7-1678672774092)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230108123213872.png)]
准备阶段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JoWLytaA-1678672774093)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230108123618675.png)]
解析阶段
类似逻辑地址转换物理地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e8qsT5Ja-1678672774094)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230108123705579.png)]
Init初始化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xXLqGHJF-1678672774094)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230108123904996.png)]
通过反射创建对象
-
方式一:调用类中的public修饰的无参构造器
-
方式二:调用类中的指定构造器
-
Class类相关方法
newlnstance : 调用类中的无参构造器,获取对应类的对象
getConstructor(Class…clazz):根据参数列表,获取对应的public构造器对象
getDecalaredConstructor(Class…clazz):根据参数列表,获取对应的所有构造器对象
-
Constructor类相关方法
setAccessible:暴破
newlnstance(Object…obj):调用构造器
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/*
* @author 远空_
* @version 1.0
* */
public class ReflecCreateInstance {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//1先获取User的Class对象
Class<?> user = Class.forName("User");
//2通过public的无参构造器创建实例
Object o = user.newInstance();
System.out.println(o);
//3.通过public的有参构造器创建实例
Constructor<?> constructor = user.getConstructor(int.class);
Object o1 = constructor.newInstance(18);
System.out.println(o1);
//4.通过非public的有参构造器创建实例
Constructor<?> declaredConstructor = user.getDeclaredConstructor(int.class, String.class);
//爆破,使用反射可以访问private构造器
declaredConstructor.setAccessible(true);
Object dsj = declaredConstructor.newInstance(18, "dsj");
System.out.println(dsj);
}
}
class User{//User类
private int age;
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
private String name;
public User(String name) {
this.name = name;
}
private User(int age, String name) {
this.age = age;
this.name = name;
}
public User() {
}
public User(int age) {
this.age = age;
}
}
通过反射访问类种的成员
-
根据属性名获取Field对象
Field f = clazz对象.getDeclaredField(属性名)
-
暴破 : f.setAccessible(true); //f 是Field
-
访问
f.set(o,值);
syso(f.get(o));
-
如果是静态属性,则set和get中的参数o,可以写成null
import java.lang.reflect.Field;
/*
* @author 远空_
* @version 1.0
* */
public class ReflecAccessProperty {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//1.得到Student对应Class对象
Class<?> student = Class.forName("Student");
//2.创建对象
Object o = student.newInstance();
System.out.println(o.getClass());
//3.使用反射得到age属性
Field age = student.getField("age");
age.set(o,88);
System.out.println(o);//传统功夫
System.out.println(age.get(o));//由反射获取
//4.使用反射操作name属性
Field name = student.getDeclaredField("name");
name.setAccessible(true);
//name.set(o,"dsj");
name.set(null,"dsj");//因为name是static属性,因此o也可以写出null
System.out.println(name.get(null));
}
}
class Student{
public int age;
private static String name;
public Student() {
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
'}';
}
}
通过反射访问方法
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/*
* @author 远空_
* @version 1.0
* */
public class ReflecAccessMethod {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class<?> boss = Class.forName("Boss");
Object o = boss.newInstance();
//3.调用public的hi方法
Method hi = boss.getMethod("hi",String.class);
Object invoke = hi.invoke(o,"dsj");
//4.调用private static方法,私有:declared+爆破;static:可用null
Method say = boss.getDeclaredMethod("say", int.class, String.class, char.class);
say.setAccessible(true);
Object invoke1 = say.invoke(null, 100, "dsj", 'm');
}
}
class Boss{
public int age;
private static String name;
private static String say(int n,String s,char c){
return n+" "+s+" "+ c;
}
public void hi(String s){
System.out.println("hi~"+s);
}
}
JDBC
JDBC概述
-
JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题
-
Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。
-
JDBC的基本原理图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rAbI4Ofe-1678672774096)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230109202041985.png)]
-
JDBC API是一系列的接口,它统一和规范了应用程序与数据库的连接、执行SQL语句,并到得到返回结果等各类操作,相关类和接口在 java.sql与javax.sql包中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rOs3USe1-1678672774097)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230109203455138.png)]
快速入门
-
JDBC程序编写步骤
-
注册驱动 - 加载Driver 类
-
获取连接 - 得到Connection
-
执行增删改查 - 发送SQL 给mysql执行
-
释放资源 - 关闭相关连接
import com.mysql.cj.jdbc.Driver; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; /* * @author 远空_ * @version 1.0 * */ public class JDBC01 { public static void main(String[] args) throws SQLException { // 1. 注册驱动 - 加载Driver 类 Driver driver = new Driver(); // 2. 获取连接 - 得到Connection //解读: //(1)jdbc:mysql:// 表示规定好的协议,通过jdbc的方式,连接mysql //(2)localhost 主机,可以是ip地址 //(3)3306表示mysql监听的端口号 //(4)hsp_db02表示连接到mysqldb的哪个数据库 //(5)mysql的连接本质就是前面学过的socket连接 String url = "jdbc:mysql://localhost:3306/hsp_db02"; //将用户名和密码放入到Properties对象 Properties properties = new Properties(); properties.setProperty("user","root"); properties.setProperty("password","123456"); Connection connect = driver.connect(url, properties); // 3. 执行增删改查 - 发送SQL 给mysql执行 String s = "insert into actor values(null,'刘德华','男','1970-11-11','110')"; //statement 用于执行静态sql语句并返回 Statement statement = connect.createStatement(); int rows = statement.executeUpdate(s);//如果是dml语句,返回的就是影响的行数 System.out.println(rows > 0 ? "成功":"失败"); // 4. 释放资源 - 关闭相关连接 statement.close(); connect.close(); } }
-
连接jdbc的五种方式
方式1
public void conn01() throws SQLException {
Driver driver = new Driver();
String url = "jdbc:mysql://localhost/hsp_db02";
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","123456");
Connection connect = driver.connect(url, properties);
System.out.println(connect);
}
方式2
public void conn02() throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
//使用反射加载Driver类,动态加载,更加灵活,减少依赖性
Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
Object o = aClass.newInstance();
Driver driver = (Driver) o;
String url = "jdbc:mysql://localhost/hsp_db02";
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","123456");
Connection connect = driver.connect(url, properties);
System.out.println(connect);
}
方式3
//方式3 使用DriverManager 代替 Driver 进行统一管理,扩展性更好
public void conn03() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
//使用反射加载dirver
Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();
//创建url 、user 、password
String url = "jdbc:mysql://localhost/hsp_db02";
String user = "root";
String password = "123456";
DriverManager.registerDriver(driver);//注册Dirver驱动
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
方式4(常用)
// 方式4:使用Class.fotName 自动完成驱动注册,简化代码
public void conn04() throws ClassNotFoundException, SQLException {
//使用放射加载Driver类
//在加载Driver类时,会自动完成注册
/*底层会在加载类时候进行注册
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}*/
Class.forName("com.mysql.cj.jdbc.Driver");
//创建url 、user 、password
String url = "jdbc:mysql://localhost/hsp_db02";
String user = "root";
String password = "123456";
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
-
mysqL驱动5.1.6可以无需CLass.forName(“com.mysgljdbc.Driver”);
-
从idk1.5以后使用了jdbc4,不再需要显示调用class.forName()注册驱动
而是自动调用驱动jar包下META-INF\services\java .sql.Driver文本中的类名称去注册
-
建议还是协商Class.forName(“”)更加明确
方式5(最好)
新建properties文件
user=root
password=123456
url=jdbc:mysql://localhost/hsp_db02
driver=com.mysql.cj.jdbc.Driver
代码
@Test
//方式5,在方式4的基础上改进,增加配置问,让mysql更加灵活
public void conn05() throws IOException, ClassNotFoundException, SQLException {
//通过properties获取文件信息
Properties properties = new Properties();
properties.load(new FileInputStream("src/main/java/mysql.properties"));
//获取相关值
String user = properties.getProperty("user");
String pwd = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);//建议写上
Connection connection = DriverManager.getConnection(url,user,pwd);
System.out.println(connection);
}
ResultSet结果集
- 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
- ResultSet对象保持一个光标指向其当前的数据行。 最初,光标位于第一行之前
- next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false ,因此可以在while循环中使用循环来遍历结果集
package resultset_;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/*
* @author 远空_
* @version 1.0
* */
public class ResultSet_ {
public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException {
//通过properties获取文件信息
Properties properties = new Properties();
properties.load(new FileInputStream("src/main/java/mysql.properties"));
//获取相关值
String user = properties.getProperty("user");
String pwd = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);//建议写上
Connection connection = DriverManager.getConnection(url,user,pwd);
//得到statement
Statement statement = connection.createStatement();
//组织sql语句
String sql = "select id,name,sex,borndate from actor";
/*解读源码
*
* */
//执行sql语句,得到结果集
ResultSet resultSet = statement.executeQuery(sql);
//使用while取出数据
while (resultSet.next()){//让光标向后移动,如果没用更多行,则返回false
int id = resultSet.getInt(1);//获取该行的第1列数据
String name = resultSet.getString(2);//获取该行第2列数据
String sex = resultSet.getString(3);//获取该行第3列数据
Date date = resultSet.getDate(4);
System.out.println(id + "\t" + name + "\t" + sex + "\t" + date);
}
/*1 刘德华 男 1970-11-11
2 刘德华 男 1970-11-11
3 刘德华 男 1970-11-11
4 jack 男 1990-11-11*/
//关闭连接
resultSet.close();
statement.close();
connection.close();
}
}
Statement(面试)
- .Statement对象 用于执行静态SQL语句并返回其生成的结果的对象
- 在连接建立后,需要对数据库进行访问,执行 命名或是SQL 语句,可以通过
Statement(存在sql注入危险)
PreparedStatement(预处理)
CallableStatement(调用存储过程) - Statement对象执行SQL 语句,存在SQL注入风险
- SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,恶意攻击数据库。(代码演示)
- 要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来)取代 Statement 就可以了
mysql> select * from admin where name = '1' OR' and pwd = 'OR '1'='1';
+------+-----+
| NAME | pwd |
+------+-----+
| tom | 123 |
+------+-----+
sql注入
package statement_;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
/*
* @author 远空_
* @version 1.0
* */
public class Statement_ {
public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException {
Scanner scanner = new Scanner(System.in);
//让用户输入用户名和密码
System.out.println("请输入管理员名字:");//next()当接收到空格或者'就表示结束
String admin = scanner.nextLine();//nextLine()则不会
System.out.println("输入密码:");
String user_pwd = scanner.nextLine();
//通过properties获取文件信息
Properties properties = new Properties();
properties.load(new FileInputStream("src/main/java/mysql.properties"));
//获取相关值
String user = properties.getProperty("user");
String pwd = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);//建议写上
Connection connection = DriverManager.getConnection(url,user,pwd);
//得到statement
Statement statement = connection.createStatement();
//组织sql语句
String sql = "select name,pwd from admin where name = '"+ admin +"' and pwd = '" + user_pwd + "'";
//执行sql语句,得到结果集
ResultSet resultSet = statement.executeQuery(sql);
if(resultSet.next()){//如果查询到一条记录
System.out.println("登录成功");
}else{
System.out.println("登录失败");
}
}
}
PrepareStatement
-
PreparedStatement 执行的 SQL 语句中的参数用问号(3)来表示,调用PreparedStatement 对象的 setXxx0 方法来设置这些参数.setXxx0 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fGP5yeRZ-1678672774098)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230110174736269.png)]
-
调用 executeQuery0,返回 ResultSet 对象
-
调用 executeUpdate0: 执行更新,包括增、删、修改
- 预处理好处
- 不再使用+ 拼接sql语句,减少语法错误
- 有效的解决了sql注入问题!
- 大大减少了编译次数,效率较高
package statement_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
/*
* @author 远空_
* @version 1.0
* */
public class PreparedStatement_ {
public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
Scanner scanner = new Scanner(System.in);
//让用户输入用户名和密码
System.out.println("请输入管理员名字:");//next()当接收到空格或者'就表示结束
String admin = scanner.nextLine();//nextLine()则不会
System.out.println("输入密码:");
String user_pwd = scanner.nextLine();
//通过properties获取文件信息
Properties properties = new Properties();
properties.load(new FileInputStream("src/main/java/mysql.properties"));
//获取相关值
String user = properties.getProperty("user");
String pwd = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);//建议写上
Connection connection = DriverManager.getConnection(url,user,pwd);
//组织sql语句
String sql = "select name,pwd from admin where name = ? and pwd = ?";
//得到prepared statement
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//给?问号赋值
preparedStatement.setString(1,admin);
preparedStatement.setString(2,user_pwd);
//执行查询语句
ResultSet resultSet = preparedStatement.executeQuery();
//执行sql语句,得到结果集
if(resultSet.next()){//如果查询到一条记录
System.out.println("登录成功");
}else{
System.out.println("登录失败");
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8D7zyDe0-1678672774099)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230110205546435.png)]
封装JDBCUtils
说明:
在jdbc 操作中,获取连接和 释放资源 是经常使用到可以将其封装JDBC连接的工具类 JDBCUtils
package utils;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/*
* @author 远空_
* @version 1.0
* */
public class JDBCUtils {
//定义相关属性,因为只需要一份,因此,我们做出static
private static String user;
private static String password;
private static String url;
private static String driver;
//在static代码块去初始化
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src/main/java/utils/mysql.properties"));
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
driver = properties.getProperty("driver");
Class.forName(driver);
} catch (IOException e) {
//在实际开发中,我们可以这样处理异常
//1.将编译异常转成运行异常
//2.这时调用者可以悬着捕获该异常,也可以选择默认处理该异常,比较方便
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
//连接数据库,返回Connection
public static Connection getConnection(){
try {
return DriverManager.getConnection(url,user,password);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//关闭相关资源
//1.ResultSet
//2.Statement 或者 PrepareStatement
//3.Connection
//4.如果需要关闭资源就传入对象,否则传入null
public static void close(ResultSet set, Statement statement, PreparedStatement preparedStatement,Connection connection){
try {
if (set != null){
set.close();
}
if (statement != null){
set.close();
}
if (preparedStatement != null){
set.close();
}
if (connection != null){
set.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
package utils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/*
* @author 远空_
* @version 1.0
* */
public class UseJDBCUtils {
public static void main(String[] args) {
//1.得到连接
Connection connection = JDBCUtils.getConnection();
//2.组织一个sql
String sql = "update actor set name = ? where id = ?";
PreparedStatement preparedStatement = null;
try {
//创建PrepareStatement对象
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"周星驰");
preparedStatement.setInt(2,2);
//执行
int i = preparedStatement.executeUpdate();
if (i!=0){
System.out.println("ok"+i);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
JDBCUtils.close(null,null,preparedStatement,connection);
}
}
}
Servlet
线程安全问题
这个问题如果不深究原理的话,还是很简单理解的。但是安全从业者岂能不深究原理,加倍努力吧!
##条件竞争漏洞
条件竞争漏洞(Race codition
),官方的概念是”发生在多个线程同时访问一个文件,一块代码,一个变量等没有进行锁操作或者同步操作的场景中“。这个漏洞存在于操作系统,数据库,web等多个层面
- 脏牛漏洞
php
中的session.upload_progress
选项servlet
线程安全问题
…
##Servlet
实例化
我们在写一个servlet
的时候写的是xxxservlet
,这表示的是一个类。当第一个请求过来的时候会将xxxservlet
类进行一次实例化,等到第二个或者更多的请求过来的时候用的依旧是xxxservlet
初次实例化的对象,并不会在实例化新的xxxservlet
对象。这样的话是不是就会存在条件竞争问题?
##漏洞分析
看一看下面这一个butlerServelt
,看似矛盾的处理,能不能尝试绕过。
package com;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class butlerServlet extends HttpServlet {
public boolean status;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String cmd = req.getParameter("cmd");
status = true;
if (cmd.equals("shell")) {
status = false;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (!status) {
//resp.getWriter().println("you are worng");
return;
}
if(cmd.equals("shell")) {
resp.getWriter().println("you are execute successful!");
}
}
}
使用python脚本请求来绕过,我们需要俩个python请求
a.py
:
import requests
url = "http://localhost:8080/sec?cmd=shell"
while True:
content = requests.get(url)
if "successful" in content.text:
print(content.text)
b.py
:
import requests
url = "http://localhost:8080/sec?cmd=test"
while True:
content = requests.get(url)
if "successful" in content.text:
print(content.text)
执行成功:
##漏洞修复
**漏洞原因:**因为servlet
只初始化一次,不同的请求对servlet
中的成员变量同时进行操作,将可能出现非法的操作。
那么如何解决线程安全问题呢?
- 实现
singleThreadModel
- 避免使用成员变量
- 同步共享数据
###1.实现SingleThreadModel
接口
SingleThreadModel
接口,以确保servlet
一次只能处理一个请求。但是从Servlet API
开始,该接口目前已经被弃用,因为它没有解决所有线程安全问题,例如静态变量和会话属性可以被多个线程同时访问,即使我们已经实现了 SingleThreadModel
接口。因此建议使用其他方式来解决这些线程安全问题,例如同步块等。
package com;
import javax.servlet.SingleThreadModel;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class butlerServlet extends HttpServlet implements SingleThreadModel {
public boolean status;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String cmd = req.getParameter("cmd");
status = true;
if (cmd.equals("shell")) {
status = false;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (!status) {
//resp.getWriter().println("you are worng");
return;
}
if(cmd.equals("shell")) {
resp.getWriter().println("you are execute successful!");
}
}
}
最终效果:
###2.避免使用成员变量
因为问题出现在多个线程操作成员变量,去掉该成员变量就OK了
package com;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class butlerServlet extends HttpServlet {
//public boolean status;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
boolean status;
String cmd = req.getParameter("cmd");
status = true;
if (cmd.equals("shell")) {
status = false;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (!status) {
//resp.getWriter().println("you are worng");
return;
}
if (cmd.equals("shell")) {
resp.getWriter().println("you are execute successful!");
}
}
}
最终效果:
###3.synchronized
同步锁
Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍可以访问该object中的非加锁代码块。synchroized
有三种用法:
-
修饰实例方法
-
修饰静态方法
-
修饰代码块
synchronized(this){ //这里的this指的是执行这段代码的对象 //互斥代码 }
处理方式:
package com;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class butlerServlet extends HttpServlet {
public boolean status;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String cmd = req.getParameter("cmd");
synchronized (this) {
status = true;
if (cmd.equals("shell")) {
status = false;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (!status) {
//resp.getWriter().println("you are worng");
return;
}
if (cmd.equals("shell")) {
resp.getWriter().println("you are execute successful!");
}
}
}
}
最终效果:
##思考与分析
那么上面的漏洞修复方法就是最好的解决办法吗?
实现SingleThreadModel
接口,为每一个请求都创建一个servlet
实例,这样无疑加重了系统的负担。而synchronized
修饰词取保了同一时间只有一个线程才可以访问这片代码块,那么在高并发的时候这有是一个无法解决的问题。
##参考链接
Java的synchronized
:
y4tacker
的文章:https://blog.csdn/solitudi/article/details/122781947?spm=1001.2014.3001.5501
小知识
进制转换
十进制转二进制
- 规则:讲二进制数不断除以2,直到商为0为止,然后将每步得到的余数倒过来,就是对应的二进制。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oVlbTDLu-1678672774105)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220715230955658.png)]
十进制转八进制
- 规则:讲八进制数不断除以8,直到商为0为止,然后将每步得到的余数倒过来,就是对应的八进制。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bFFR0Kbw-1678672774105)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220715231138112.png)]
十进制转十六进制
- 规则:讲十六进制数不断除以16,直到商为0为止,然后将每步得到的余数倒过来,就是对应的十六进制。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-34pAzZDB-1678672774106)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220715231321427.png)]
二进制转八进制
- 规则:从最低位开始,将二进制数每三位一组,转换成对应的八进制数即可。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XM0gSPC6-1678672774107)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220715231448650.png)]
二进制转十六进制
-
规则:从最低位开始,将二进制数每四位一组,转成对应的十六进制数即可。
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DzuLnn3V-1678672774108)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20220715231611048.png)]
琐碎知识
-
new 其实相当于在内存中开辟空间
-
对象信息加载的时候会创建原始对象在堆空间
-
类的加载与实例化_RiKing_1的博客-CSDN博客_类加载和实例化
-
加载子类对象时候,父类对象也会被加载,而且父类先被加载
-
类的加载完成后才能进行调用的操作
-
串行化:可以通过网络传输。
-
public class Homework01a { public static void main(String[] args) { String str = "abcdef"; System.out.println(Homework01a.reverse(str,1,4)); } /* * (1)将字符串中指定的部分进行反转。 * (2)编写方法public static String reverse(String str,int start,int end)搞定 * 思路分析 * (1)先把方法定义确定 * (2)把String转成char[],因为char的元素是可以交换的 * (3)画出分析示意图*/ public static String reverse(String str,int start,int end){ if(!(str != null && start >=0 && end > start && end < str.length())){ throw new RuntimeException("参数不正确"); } char chars[] = str.toCharArray(); char temp; while (start < end){ temp = chars[start]; chars[start] = chars[end]; chars[end] = temp; start++;end--; } return new String(chars); } }
-
31是质数而且31 * i = i << 5 - i
-
试分析HashSet和TreeSet分别是如何实现去重的
(1)HashSet去重机制:hashCode()+equals(),底层先通过存入对象,进行运算得到一个hash值,通过hash值得到对应的索引,如果发现table索引所在的位置,没有数据,就直接存放数据;如果有数据,就进行equals比较【遍历比较】,如果比较后不相同,就加入,否则就不加入。
(2)TreeSet的去重机制:如果你传入了一个Comparator匿名对象,就使用实现的compare()去重,如果方法返回0,就认为是相同的元素,就不添加,如果没有传入Comparator匿名对象,则以你添加的对象实现的Compareable接口的compareTo去重。
-
试看是否会抛出异常
public class Homework04 { public static void main(String[] args) { TreeSet treeSet = new TreeSet(); treeSet.add(new Person()); } } class Person{}
由于Person没有实现Comparable,而treeSet在没有传入比较器的情况下,会默认调用Comparable接口的compareTo
实例化
java的实例化就是java加载一个类并在堆内存中开辟出来一个内存空间
举个例子:
Canteen canteen = new Canteen();
- 声明对象的类型
- 在栈内存生成一个canteen对象引用,并且该对象指向后面生成的在堆内存的对象
- 将栈内存的对象指向堆内存的对象
- java执行堆内存的对象的生成创建和开辟空间
- 要生成的class类对象
- 执行该类得初始化方法初始化对象;
注意点:
①因为2该对象的引用是存在于栈内存的,所以它会随着执行方法的出栈而消失。
② 堆内存的对象会被java回收机制所管理
类方法
static声明的方法称为类方法,通过类名可以直接调用类方法
当一个方法是static时,就是一个静态方法
注意:静态方法可以直接通过类名调用。
java中类何时被加载
- 创建父类的实例
- 创建子类的实例
- 访问类的静态方法
- 访问类的静态变量
- 反射 Class.forName()
java类在以上五种情况下会被加载。
在jvm生命周期中每个类如果存在,则不会重复加载。
在加载子类的时候会优先加载其父类。
类被加载的时候,其中的静态代码块、静态方法及静态变量也会被加载。
在初始化某个类时,如果这个类的静态代码块、静态方法或静态变量引用到了另一个类,则这个类也会被加载。
————————————————
原文链接:https://blog.csdn/weixin_30233335/article/details/114390832
static方法的继承问题
在JAVA中,如果父类中含有一个静态方法,且在子类中也含有一个返回类型,方法名,参数列表相同的静态方法,那么该子类实际上是隐藏了父类的同名静态方法,而非重写。
父类和子类中含有的其实是两个没有关系的方法,它们的行为也不具有多态性。
增强型for循环
for(Season season : values){
//将values数组中的值按顺序赋值给season
}
TCP详解
一:引出
客户端与服务器之间数据的发送和返回的过程当中需要创建一个叫TCP connection的东西;由于TCP不存在连接的概念,只存在请求和响应,请求和响应都是数据包,它们之间都是经过由TCP创建的一个从客户端发起,服务器接收的类似连接的通道,这个连接可以一直保持,http请求是在这个连接的基础上发送的;在一个TCP的连接上是可以发送多个http请求的
二:TCP的报文格式
1:TCP在传输层和网络层数据传输的过程
TCP协议是在传输层端到端的传输信息,既然说到传输层协议,那么就讲一下传输层协议在数据传输过程中的位置:
左边家庭要给右边家庭通信中的(应用层和传输层和网络层)
左边家庭的孩子小红(应用层中 应用进程)写了一封信(应用消息),然后小红将信交给了哥哥李雷(传输层),李雷将信放入到家门口的信箱里,邮递员来了将信取走放到了邮政汽车上,然后邮政汽车肯定不是直达的,他们到了转运中心(路由器)转到另一辆汽车上根据时间成本,路线成本等选择一条路继续运输(网络层)。
2:TCP报文
其中比较重要的字段
a:序号(sequence number):
Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
对序号的补充:
应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分区成适当长度的 报文段 (通常受该计算机连接的网络的 数据链路层 的 最大传输单元 ( MTU )的限制)。之后TCP把结果包传给IP层,由它来通过网络将包传送给接收端实体的TCP层**。TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。**然后接收端实体对已成功收到的包发回一个相应的确认( ACK );如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。TCP用一个 校验和 函数来检验数据是否有错误;在发送和接收时都要计算校验和。
b:确认序号(acknowledgement number):
Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。
c:标志位(Flags):
共6个,即URG、ACK、PSH、RST、SYN、FIN等。具体含义如下
URG:紧急指针(urgent pointer)有效。
ACK:确认序号有效,确认接收到消息;
PSH:接收方应该尽快将这个报文交给应用层。
RST:重置连接。
SYN:发起一个新连接。
FIN:释放一个连接。
三:TCP的三次握手的图示
1:图示
所谓的三次握手即TCP连接的建立。这个连接必须是一方主动打开,另一方被动打开的。
以下为客户端主动发起连接的图解:
2:客户端各个状态:
CLOSED状态:为关闭状态
SYN_SENT状态:为请求连接状态, 当你要访问其它的计算机的服务时首先要发个同步信号给该端口,此时状态为SYN_SENT,如果连接成功了就变为ESTABLISHED,此时SYN_SENT状态非常短暂。
ESTABLISHED状态:连接成功
3:服务端的各个状态:
LISTENING状态:监听状态, State显示是LISTENING时表示处于侦听状态,就是说该端口是开放的,等待连接,但还没有被连接。就像你房子的门已经敞开的,但还没有人进来。
SYN-RCVD状态:收到和发送一个连接请求后等待对方对连接请求的确认。
ESTABLISHED状态:连接成功
补充:SYN-RCVD状态
当服务器收到客户端发送的同步信号时,将标志位ACK和SYN置1发送给客户端,此时服务器端处于SYN_RCVD状态,
如果连接成功了就变为ESTABLISHED,正常情况下SYN_RCVD状态非常短暂。如果发现有很多SYN_RCVD状态,那你的机器有可能被SYN Flood的DoS(拒绝服务攻击)攻击了。
SYN Flood的攻击原理是:
在进行三次握手时,攻击软件向被攻击的服务器发送SYN连接请求(握手的第一步),
但是这个地址是伪造的,如攻击软件随机伪造了51.133.163.104、65.158.99.152等等地址。
服务器 在收到连接请求时将标志位 ACK和 SYN 置1发送给客户端(握手的第二步),
但是这些客户端的IP地址都是伪造的,服务器根本找不到客户机,也就是说握手的第三步不可能
完成。这种情况下服务器端一般会重试(再次发送SYN+ACK给客户端)并等待一段时间后丢弃这个
未完成的连接,这段时间的长度我们称为SYN Timeout,一般来说这个时间是分钟的数量级
(大约为30秒-2分钟);一个用户出现异常导致服务器的一个线程等待1分钟并不是什么很大的问题,
但如果有一个恶意的攻击者大量模拟这种情况,服务器端将为了维护一个非常大的半连接列表而
消耗非常多的资源----数以万计的半连接,即使是简单的保存并遍历也会消耗非常多的
CPU 时间和内存,何况还要不断对这个列表中的IP进行SYN+ACK的重试。
此时从正常客户的角度看来,服务器失去响应,这种情况我们称做: 服务器端受到了
SYN Flood攻击(SYN洪水攻击 )
百度百科
4:TCP三次握手的过程
握手之前主动打开连接的客户端结束CLOSED阶段,被动打开的服务器端也结束CLOSED阶段,并进入LISTEN阶段。随后开始“三次握手”:
a:首先客户端先向服务器端发送一个TCP报文
标记位为SYN,表示“请求建立新连接”;
序号为Seq=X(X一般为1)(传输信息的时候每个数据包的序号);
随后客户端进入SYN-SENT阶段(请求连接的阶段)。
b:服务器端收到来自客户端的TCP报文之后,结束LISTEN阶段。并返回一段报文
标志位为SYN和ACK,表示“确认客户端的报文Seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”(即告诉客户端,服务器收到了你的数据);
序号为Seq=y;(返回一个收到信息的数据包 并给其标序号为y)
确认号为Ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值(两端配对 接收到消息 并反馈的过程;随后服务器端进入SYN-RCVD阶段。
ACK:代表确认收到消息
c:客户端接收到来自服务器确认收到数据的TCP报文后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段,并返回一段TCP报文
标志位为ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了);
序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;
确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值;
随后客户端进入ESTABLISHED阶段。(即成功建立了连接)
d:关于确认号Ack和数据包的序号Seq值得变化
Ack确认号:就是确认收到消息后 返回给 发送端的 序号(Ack = 发起方的Seq + 1) 即就是下次发送的端的seq序号
ACK确认序号(Seq)有效:确认发送的数据包的成功到达
Seq:序号:给每个数据包一个序号,保证接受端可以按序收到数据包(首次握手的时候 Seq = 上次握手的时候的Ack值,如果没有 则可以是任意值)
在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续"握手",以此确保了"三次握手"的顺利完成
5:为甚要三次握手
a:为了防止服务器端开启一些无用的连接增加服务器开销
第一次握手客户端发送的TCP报文,服务端成功接收;然后第二次握手,服务端返回一个确认收到消息的TCP报文,但这个报文因为某些原因丢失了,那么客户端就一直收不到这个TCP报文的
1
,客户端设置了一个超时时间,超过了就重新发送一个TCP连接请求,那么如果没有第三次握手的话,服务端是不知道客户端是否收到服务返回的信息的,这样没有给服务器端一个创建还是关闭连接端口的请求,服务器端的端口就一直开着,等到客户端因超时重新发出请求时,服务器就会重新开启一个端口连接。那么服务器端上没有接收到请求数据的上一个端口就一直开着,长此以往,这样的端口多了,就会造成服务器端开销的严重浪费。
b:防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
已经失效的客户端发出的请求信息,由于某种原因传输到了服务器端,服务器端以为是客户端发出的有效请求,接收后产生错误。
c:总结
也可以这样理解:“第三次握手”是客户端向服务器端发送数据,这个数据就是要告诉服务器,客户端有没有收到服务器“第二次握手”时传过去的数据。若发送的这个数据是“收到了”的信息,接收后服务器就正常建立TCP连接,否则建立TCP连接失败,服务器关闭连接端口。由此减少服务器开销和接收到失效请求发生的错误。(如果第三次握手失败的话,那服务端就关闭连接)
socket.shutdownOutput
了解更多🔗
看某神视频的时候,发现不用socket.shutdownOutput()服务端就会被阻塞,阻塞在如下代码块中:
后面想了下,其实在客户端代码中不调用socket.close()也会导致阻塞。
说回到客户端,如果不加如下代码就会阻塞。因为服务器会一直等待客户端的输出。既然服务器阻塞了,客户端等待着服务器的输出,也会被阻塞,所以导致客户端和服务端都被阻塞。
调用Socket.shutdownOutput()方法后,客户端输出的数据都将被发送,并加上 TCP 的正常连接终止序列(-1,也就是服务端终止循环的判断条件),这样服务端读取数据时就不会被阻塞了。
想必socket.close()也做了类似的事情。
但是本代码需要接收服务器的响应,所以不能关闭socket,只能单向关闭客户端输出流。
客户端代码如下:
public static void main(String[] args) {
try {
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9000);
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream(new File("D:\\2021\\ksnet\\src\\捕获.JPG"));
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1) {
os.write(buffer,0,len);
}
//不加则会阻塞
socket.shutdownOutput();
//accept server message
InputStream inputStream = socket.getInputStream();
byte[] buffer2 = new byte[1024];
int len2;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while((len2 = inputStream.read(buffer2)) != -1) {
baos.write(buffer2);
}
System.out.println(baos.toString());
baos.close();
inputStream.close();
fis.close();
更多推荐
JavaSe学习日记
发布评论