目录
Java简介
Java三个方向
JDK
一、第一个Java程序
二、JVM跨平台
三、IDEA的使用
四、命名规范
五、类文件解读
六、智能纠错
七、八种数据基本类型
八、自动转换类型
九、强制类型转换
十、运算符
十一、运算自动提升
十二、流程控制
十三、隐式类型转换
十四、局部变量的作用域
十五、Java中的类和对象
15.1 对象的作用
15.2 java对象创建与数据的存取
15.3 Java中的函数
15.5 函数中的参数与返回值
总结:参数与返回值的语法
十六、set函数
16.1 第一步 我们需要再给年龄赋值的时候进行条件判断 如果数据符合要求 再进行赋值
16.2 第二步通过set函数来解决问题
十七、权限修饰符
十八、get函数
十九、数据模型类
二十、toString函数的作用
二十一、构造函数
构造函数的作用
构造函数的重载
二十二、继承
内存结构
二十三、多态和抽象类
一、多态
二、抽象类
二十四、接口
一、接口的简介
二、 接口的语法
三 、对比
四 、static关键字
五 、final最终的
二十五、数组和封装
一、Java中的数组
二、Java中的数据存取
三、Java中数组越界异常
四、数据工具类封装
五、Java数组扩容
六、动态数组封装
七 、封装
二十六、异常机制
一、为什么需要异常
二、异常抛出[执行方]
三、异常处理[调用方]
四 、多异常捕获
五、异常的分类
六、finally代码块
面试题
七、自定义异常
二十七 、File和IO流
一、File的介绍
二、File 创建对象
三、File创建文件,文件夹
四、File 删除文件
五、File 修改文件
六、File 查询内容
七、递归算法
IO流的介绍
一、字节流
1.1 OutputStream 字符输出流
1.2 字节输入流--InputStream
二、字符流
2.1 Write字符输出流
2.2 Reader字符输入流
三、包装流-缓冲字节包装流
四、对象序列换
4.1 为什么需要对象包装流
4.2 对象包装流的操作
4.3 序列化
4.4 读取对象流
4.5 序列化版本ID
Java简介
1995年 SUN(太阳)公司 詹姆斯·高瑟林 java之父
被Oracle公司收购: Oracle数据库 MySQL数据库 Java
Java三个方向
JavaSE:窗口应用程序 【电脑软件】 Java不擅长的区域
JavaEE:Web应用程序 【服务器软件】 Java的生存之道
JavaME:嵌入式程序 【手机软件程序】
JDK
之前学习 HTML CSS JAVASCRIPT 的时候没有安装运行环境 那是因为 htmlcssjs的运行环境就是浏览器
同样我们学习java 如果没有运行环境 可以在电脑上面写代码 但是不能执行
JDK:开发者工具包
JRE: Java运营环境
JVM: 虚拟机
SUN公司--研发java语言
Eclipse【日食】 -- java语言开发工具 免费 30%
MyEclipse【我的日食】 -- java语言开发工具 和eclipse一模一样但是安装好之后就自带一群插件 收费
STS -- java语言开发工具 和MyEclipse一模一样 运行更快更稳定 免费
IDEA 丰富的插件市场 收费 70%
建议:使用IDEA 但是快捷键使用Eclipse
一、第一个Java程序
在电脑某个文件夹中创建一个Demo.txt文件
在文件中编写代码
/**
* Created by Intellij IDEA
*
* @author 王俊凯
* @Date: 2022/8/1 20:41
* @Version 1.0
*/
package com.wjk;
public class Demo {
public static void main(String[] args) {
System.out.println("hello Word");
}
}
将Demo.txt的后缀名改为java
在地址栏进行cmd
编译我们的java代码变成字节码: javac Demo.java
运行我们的java字节码文件到JVM: java Demo
二、JVM跨平台
Demo.java[源文件:写代码的文件]
|
| javac Demo.java 编译命令
|
Demo.class[字节码文件]
|
| java Demo 运行命令
|
JVM平台: windows Linux Mac
每个平台底层实现是不一样[可以理解成 IOS和安卓 底层是不一样]所以识别的软件程序也是不一样
此时就会对我们开发的时候造成一个困扰:需要基于不同的平台开发该平台能识别的软件
例如:爱奇艺客户端 需要造windows版本 mac版本跨平台: 开发一套程序 能在不同的平台运行
java语言支持跨平台,原理:
我们只需要开发一次 java源码 ,将其编译成 字节码。
然后在不同的平台安装不同版本的虚拟机,然后使用jvm运行字节码
三、IDEA的使用
1 创建工程:
File--new---project--java---名字和存储路径
.idea idea配置文件[idea项目特有别]
src 源代码文件夹 我们创建的.java文件都放到src下
.iml 配置文件
2 创建包 【package】
思想和前端项目一样,分包管理: img css js 等文件夹
java中的package 包其实也是一个文件夹 不仅是不同的包存放不同的代码 并且还有权限访问的作用
如何创建包:
选择src--new---package--起名字--完成3 创建源文件 【Xxxx.java】
选中包--new--java class -- 起名字--完成
如何删除包 删除类 重命名
四、命名规范
工程名:英文
包名:反域名 全小写 例如: com.aaa.day01.test
com 代表公司企业组织 org 代表公益组织 java 代表官方源代码
aaa 代表公司企业名称
day01 代表项目名称
test 代表包的作用
类名:英文 有意义 大驼峰:每个单词的首字母都要大写
五、类文件解读
我们使用IDEA创建 Xxxx.java文件夹的时候 IDEA会自动帮我们创建一个同名的公共类
包声明 表明当前文件所在的包 面试题:请问在一个源文件中 最有可能出现在第一行的代码是什么?A package B import C haha D hehe
package com.aaa.day01.test;类声明
public class Dog {}格式: 【权限修饰符】 关键字 名字 {}
在java中 写代码必须写到类中
权限修饰符 修饰符是java中特有的用来控制访问级别
关键字class class代表声明的是一个类 abstract class 抽象类 interface 接口 enum 枚举 @interface 注解
名字 遵循命名规范
{} class body
六、智能纠错
七、八种数据基本类型
public class Demo {
public static void main(String[] args) {
int a = 99;
}
}
java中变量的声明:
数据类型 名字 = 初始值;
数据类型 在java中 数据类型规定了当前变量只能存储什么类型的数据 因为java遵循类型匹配原则
数据类型分类: 基本数据类型[八种] 和 对象类型/引用类型[n种]名字 变量名的命名规范遵循小驼峰;首字母小写 如果有多个单词从第二个单词开始首字母大写
= 赋值符号
初始值 java中的数据初始值要和 数据类型匹配
public class Demo {
public static void main(String[] args) {
//整数类型
byte a = 10;
short b = 20;
int c = 30;
long d = 40L;
//浮点类型
float e = 1.9F;//单精度浮点类型
double f = 9.9;//双精度浮点类型
//字符类型
char g = 'a';
//布尔类型
boolean l = true;
//字符串类型
String i = "打倒小日本";
}
}
八、自动转换类型
public static void main(String[] args) {
int a = 20;
// Incompatible types.Required:byte Found:int
byte b = a;
}
此时我们如果还需要赋值 就可以使用强制类型转换: 在需要转换的类型前面添加 (需要转成的类型)
public static void main(String[] args) {
int a = 20;
byte b = (byte) a;
}
但是注意 强制类型转换其实就是欺骗编译器,运行的时候可能会出现数据溢出的情况
public static void main(String[] args) {
int a = 129;
byte b = (byte)a;
System.out.println(b);
}
此时数据溢出 输出-127 为啥是-127原因是经过了二进制的原码 反码 补码等运算
九、强制类型转换
public static void main(String[] args) {
int a = 20;
// Incompatible types.Required:byte Found:int
byte b = a;
}
此时我们如果还需要赋值 就可以使用强制类型转换: 在需要转换的类型前面添加 (需要转成的类型)
public static void main(String[] args) {
int a = 20;
byte b = (byte) a;
}
但是注意 强制类型转换其实就是欺骗编译器,运行的时候可能会出现数据溢出的情况
public static void main(String[] args) {
int a = 129;
byte b = (byte)a;
System.out.println(b);
}
此时数据溢出 输出-127 为啥是-127原因是经过了二进制的原码 反码 补码等运算
十、运算符
一目运算符: ++ --
public static void main(String[] args) {
int a = 10;
int b = a++; // 先执行a++ 然后将a++之前的值赋值给b
int c = ++a; // 先执行++a 然后将++a之后的值赋值给c
System.out.println(a);12
System.out.println(b);10
System.out.println(c);12
}
二目运算符: + - * / % += -= *= /= > < >= <= == !=
三目运算符: (条件表达式)?(真):(非真)
十一、运算自动提升
public static void main(String[] args) {
int a = 5;
int b = 10;
int c = a/b;
System.out.println(c);
}
首先c的类型就是整型 所以即使有小数也会取整。 然后 a/b 的结果是 0.5 【真实结果】 但是 a的类型是int b的类型也是int
在java中 运算结果的类型 取决于参与运算的变量 并且取更精确的 -- 运算的自动提升
所以 结果的类型也是int 所以0.5 【真实结果】就变了 0 ,在将0赋值给 c
所以我们要真实结果的时候 还需要将 a 或者 b 其中之一改成 float 即可
十二、流程控制
public static void main(String[] args) {
int a = 20;
if(a>10){
System.out.println("你好世界");
}
for (int i=1;i<10;i++){
for(int j=1;j<20;j++){
System.out.print("*");
}
System.out.println();
}
}
十三、隐式类型转换
public static void main(String[] args) {
// Incompatible types. Required:byte Found:int
byte a = 128;
// Incompatible types. Required:float Found:double
float b = 20.5;
}
说明 直接写的常量数字 也是有数据类型的 , 10 数据类型是 int 20.5数据类型是double
但是 我们这样赋值就不会出问题
public static void main(String[] args) {
byte a = 127;
}
此时127还是int 但是在byte的范围之内 所以发生了隐式类型转换
public static void main(String[] args) {
float a = 20.5F;
long b = 99999999999L;
}
还有一种情况,也是一道经典的面试题:请问以下代码有问题吗?为什么?
public static void main(String[] args) {
short a = 10;
a = a +5;
short b = 20;
b+=5;
}
十四、局部变量的作用域
在函数内部声明的变量 称之为 局部变量
在函数外面类的里面声明的变量 称之为 全局变量/字段/成员变量/属性
public class People {
/**
* 字段 成员变量 属性 全局变量
*/
int a;
public static void main(String[] args) {
// 局部变量
int b;
}
}
局部变量的作用域指的是 在哪里可以使用
从定义开始 到所在的结束大括号
并且在java中 同一作用域内 变量名不能重复
public static void main(String[] args) {
int a = 20;
// Variable 'a' is already defined in the scope
int a = 20;
for (int i=0;i<66;i++){
}
for (int i=0;i<66;i++){
}
}
十五、Java中的类和对象
15.1 对象的作用
编程中有很多能存储数据的形式:
js中有五种数据类型 java中有两类:基本数据类型 和 对象类型
1 变量
2 数组
3 对象 能存储一组数据并且有语义化
js对象的使用:
var obj = {
"name":"张三",
"age" : 18 ,
"address":"北京"
};
15.2 java对象创建与数据的存取
张三 18 北京 java工程师
李四 19 南京 国家电网
王五 20 开封 导游创建了一个 JavaTest 类 这是一个测试类 里面写了main函数 用来测试我们的代码
创建一个类 【类是对象的模板 对象是类的实例】
public class People { }
在类中添加成员变量
public class People {
String name;
int age;
String address;
String job;
}
在测试类中创建对象 存取数据
public class JavaTest {
public static void main(String[] args) {
// java中创建对象
People p1 = new People();
p1.name = "张三";
p1.age = 18;
p1.address = "北京";
p1.job = "java工程师";
People p2 = new People();
p2.name = "李四";
p2.age = 19;
p2.address = "南京";
p2.job = "国家电网";
// 获取信息
System.out.println( p1.age );
System.out.println( p2.job );
}
}
15.3 Java中的函数
函数的作用:其实就是给代码块起个名字 需要使用这段代码的时候 只需要通过调用函数的形式让其执行
java中函数的语法格式:
[修饰符] 返回值类型 函数名(参数列表){
代码块
}
静态调用 : 类名.函数名();
非静态调用: 对象名.函数名();
15.4 实际练习
public class Haha {
public void aaa(){
System.out.println("打沉台湾");
System.out.println("打沉台湾");
System.out.println("打沉台湾");
System.out.println("打沉台湾");
}
}
public class Test01 {
public static void main(String[] args) {
Haha haha = new Haha();
haha.aaa();
}
}
15.5 函数中的参数与返回值
参数和返回值 都是java中数据传递的方式 只不过传递的方向不一样
import java.util.Scanner;
public class Test01 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("输入身高");
double v = scanner.nextDouble();
System.out.println("输入体重");
double w = scanner.nextDouble();
Haha haha = new Haha();
String people = haha.people(v, w);
System.out.println(people);
}
}
public class Haha {
public String people(double height,double weight){
double temp = height-105;
String str="";
if (weight>temp+5){
System.out.println("胖");
}else if (weight<temp-5){
System.out.println("瘦");
}else {
System.out.println("正常");
}
return str;
}
}
总结:参数与返回值的语法
1.没有参数 小括号里什么都不写 没有返回值 就写void
2.参数添加的形式是(参数类型 参数名1 , 参数类型 参数名2)
3.有参数的函数调用的时候 需要传值 并且传的值的个数、类型、顺序都要匹配
4.有返回值的函数 需要返回什么类型的数据 返回值类型就写什么类型
5.有返回值的函数 一定需要有一个能执行的return
6.void函数也可以写return语句
7.return语句执行之后 方法体就结束 后面的代码语句就不执行了
8.有返回值的函数在调用的时候 可以赋
十六、set函数
为什么会使用set函数
public class Test01 {
public static void main(String[] args) {
People people = new People();
people.name="罗丽莉";
people.age=-18;
}
}
我们创建了People类,可以通过new People对象的形式 给People对象数据完成存取。
但是会有问题 p1.age = -18; 从语法的角度讲 没有问题 因为 age是 int类型 -18在int的取值范围之内 所以语法不报错。
但是从实际需求的角度讲一个人的年龄是-18显然有问题不符合常理
[注意点 别写错不就行了 注意我们写的是程序 数据来源于用户 我们的程序存在不合理的地方 用户可以写违规数据 ]
我们可以通过set函数来解决问题
16.1 第一步 我们需要再给年龄赋值的时候进行条件判断 如果数据符合要求 再进行赋值
public class Test01 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int i = scanner.nextInt();
People p1 = new People();
if (i>0){
p1.age=i;
}else{
p1.age=0;
}
System.out.println(p1.age);
People p2 = new People();
int i1 = scanner.nextInt();
if (i1>0){
p2.age=i1;
}else {
p2.age=0;
}
System.out.println(p2.age);
}
}
16.2 第二步通过set函数来解决问题
public class People {
String name;
int age;
public void setAge(int age){
if (age>0){
this.age=age;
}else {
this.age=0;
}
}
}
十七、权限修饰符
权限修饰符
public 公共权限。修饰的内容当前类中任意包都可以访问
修饰的内容: 成员变量 函数 类
protected 包权限和继承权限。修饰的内容只能在同一个包中访问,如果不在同一个包中但是有继承关系也可以访问。
[受保护的] 修饰的内容: 成员变量 函数
default 包权限,修饰的内容只能在同一个包中访问,其他包无法访问。
friendly 注意 不是添加default关键字代表包权限 而是什么都不写代表包权限
修饰的内容: 成员变量 函数 类
private 私有权限,修饰的内容只能在当前类中使用,外部无法调用[反射不考虑]
修饰的内容: 成员变量 函数
十八、get函数
为什么需要get函数
当我们将age私有化之后,此时只能通过set函数对其进行赋值。但是对象是用来进行数据存取的,此时只能存 不能取了!通过get函数解决问题
public int getAge(){
return age;
}
十九、数据模型类
在漫长的开发中,java对象用来存取数据,但是期间经历了很多问题,后来又解决问题,再写代码都会规避问题,最后总结出一套java存储对象类 创建的方式:
public class 类名 {
私有化成员变量
set和get函数
toString()
}
类 : 测试类 JavaTest
Util
数据模型类 People
数据模型类的作用就是来存取数据,开发工具都自带模型类生成功能
public class People {
private String name;
private int age;
private String address;
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
二十、toString函数的作用
public class Test01 {
public static void main(String[] args) {
People people = new People();
people.setAge(18);
people.setName("买买买");
people.setAddress("中国");
System.out.println(people);
}
}
当我们使用 System.out.println 输出对象的时候,如果当前对象重写了toString函数则按照重写的格式输出
如果没有重写 则按照Object的toString函数输出: com.wjk.test04.People @4554617c
全限定名 hashcode值
当我们在People中添加toString的时候
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
此时输出 People{name='买买买', age=18, address='中国'}
二十一、构造函数
构造函数,是一种特殊的方法
构造函数的作用:主要是用来创建对象时初始化对象,即为对象成员变量赋初始值
构造函数的调用:总与new运算符一起使用在创建对象的语句中
构造函数的重载:特别的一个类可以有多个构造函数,可根据其参数个数的不同或者参数类型的不同来区分他们 这就是函数的重载
当我们创建 People people = new People(); 此时 new People()的People()就是构造函数
也就是说 创建对象的语法格式 应该是:new 构造函数;
构造函数特点:A 一个类如果不写 在编译之后就会生成一个 公共无参数的构造函数public People() {}B 构造函数不写返回值类型 如果写了就不再是构造函数 虽然不会报错 但是会有黄色警告
C 构造函数的名字要和类名相同 如果不相同则会报错:Invalid method declaration; return type required
public people() { } 名字小写了 就不符合构造函数要求 系统会当成一个普通函数 一个普通函数必须有返回值类型
D 如果自定义了有参数的构造函数 此时就不会自动生成公共无参数的构造函数了
构造函数的作用
public class Test01 {
public static void main(String[] args) {
People p1 = new People();
p1.setName("王俊凯");
p1.setAge(20);
p1.setAddress("河南");
People p2 = new People();
p2.setName("王源");
p2.setAge(19);
p2.setAddress("中国");
}
}
我们创建对象的时候 需要给成员变量赋值 一个一个的写很麻烦
所以我们可以借助构造函数 在创建对象的时候 顺便给对象的字段赋值
public class Test01 {
public static void main(String[] args) {
People people = new People("王俊凯",20,"河南");
System.out.println(people);
People people1 = new People("王源",19,"中国");
System.out.println(people1);
}
}
public class People {
private String name;
private int age;
private String address;
public People() {
}
public People(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
构造函数的重载
当我们的类 只添加了有参数的构造函数(目的创建对象的时候 同时完成数据的赋值) 有的时候 我们需要创建对象 但是暂时还没有数据
此时我们的代码就会有问题:
public static void main(String[] args) {
People p1 = new People();
// .... 经历了很多代码 之后才有数据
p1.setName("张三");
}
所以我们在一个类中 添加有参数构造函数的同时 也需要添加一个公共无参数的
public class People {
private String name;
private int age;
private String address;
public People(){}
public People(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
....
}
此时我们就发现 俩构造函数 函数的名字一样相同 但是两个却能共存
此时就发生了 函数的重载 overload
在java中 同一个类中 函数名相同 参数列表不同 则函数可以共存 这个特点就成为 函数的重载
参数列表不同具体值 参数的个数 参数的类型 参数的顺序
为什么需要这个语法/什么时候使用函数重载语法?
此时我们其实天天在使用函数的重载 系统的输出语句 就很神奇 :相同的函数 居然能传递不同的参数
public static void main(String[] args) {
System.out.println(10);
System.out.println(10.5);
System.out.println(false);
System.out.println("haha");
}
此时我们查看源代码 ctrl+左键
public void println(boolean x) {
synchronized (this) {
print(x);
newLine();
}
}
public void println(char x) {
synchronized (this) {
print(x);
newLine();
}
}
public void println(int x) {
synchronized (this) {
print(x);
newLine();
}
}
public void println(long x) {
synchronized (this) {
print(x);
newLine();
}
}
public void println(float x) {
synchronized (this) {
print(x);
newLine();
}
}
public void println(double x) {
synchronized (this) {
print(x);
newLine();
}
}
public void println(char x[]) {
synchronized (this) {
print(x);
newLine();
}
}
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
使用场景:1 同一个函数 同样的功能 但是需要传递不同类型的参数
2 同一个函数 根据不同的参数 实现不同的功能
二十二、继承
22.1 为什么需要继承
我们的类 有一种情况是 数据模型类。在数据模型类中 创建 私有化成员变量 添加set和get函数。
很多时候 我们需要创建大量的数据模型类。此时就会出现 重复字段问题
例如 我们帮助动物园写项目 每种动物都需要有一个类与其对应
class Dog{ String name;int age; double price;}
class Cat{ String name;int age; double price;}
class Pig{ String name;int age; int height;int wight;}
class Tiger{ String name; int age; String kind; String address; }我们之前讲解类和对象的时候 一个类是对象的模板 对象是类的实例
为了解决这样的问题 所以就有一个 面向对象的特征--继承
public class JavaTest {
public static void main(String[] args) {
Dog d1 = new Dog();
d1.name = "旺财";
d1.age = 18;
d1.price = 666;
Cat c1 = new Cat();
c1.name = "小菊";
c1.age = 66;
c1.price = 132;
Pig p1 = new Pig();
p1.name = "八戒";
p1.age = 16;
p1.height = 25;
p1.wight = 50;
}
}
class Haha{ String name;int age; }
class Hehe extends Haha { double price; }
class Dog extends Hehe { byte aByte; }
class Cat extends Hehe { boolean aBoolean; }
class Pig extends Haha { int height;int wight;}
class Tiger extends Haha { String kind; String address; }
22.2 继承的语法规则
1. 在java中建立继承关系的格式是:
class 类名A extends 类名B {}
例如: class Dog extends Hehe { ... }
其中 类名A 称之为 子类或者 本类
类名B 称之为 父类
2. 在java中 类和类之间的关系 是继承关系 并且是单继承
//Class cannot extend multiple classes
class Dog extends Hehe,Haha { byte aByte; }这也是java区别于c++的地方:java是基于c++ 并且摒弃了 c++中晦涩难懂的 多继承 多指针等概念
但是java中有很多的类型 不一样的类型会遵循不一样的关系面试题:请问 java是单继承还是多继承?
类 类 继承关系 单继承
接口 类 实现关系 多现实
接口 接口 继承关系 多继承
3.在java中 类和类之间 建立了继承关系 ,子类就可以调用父类的属性和方法
注意权限修饰符达不到的情况下 无法调用
面试题:请问 java中私有的能否被继承?
能 但是不能被调用 / 不能 因为无法调用4.在java中 如果没有写 extends 关键字 并不是没有父类 而是默认继承 Object 【基类 超类/父类 祖宗类】也就是说 所有的类 都是 Object 的子类
5. 在java中 建立继承关系之后,调用函数的时候 如果当前类有 则先调用当前类的 ,如果当前类没有 则调用父类的
public class JavaTest {
public static void main(String[] args) {
Cat cat = new Cat();
cat.eat();
}
}
class Haha {
public void eat(){
System.out.println("动物都要吃饭");
}
}
class Cat extends Haha {
public void eat(){
System.out.println("猫爱吃鱼");
}
}
总结一句话: 父类的函数 满足不了子类的需求 所以子类重写父类的函数
有一个注解 @Override 是用来验证子类是否对父类函数重写成功的标记 如果重写失败就会报错:Method does not override method from its superclass. 并且阿里规范明确要求 所有的重写都需要 添加 @Override
6.在java中 支持 父类的引用 引用子类对象的写法
public class JavaTest {
public static void main(String[] args) {
Cat cat = new Cat();
Haha haha = new Cat();
haha.hello();
}
}
class Haha {
public void hello(){
System.out.println("动物都要吃饭");
}
}
class Cat extends Haha {
@Override
public void hello(){
System.out.println("猫爱吃鱼");
}
}
内存结构
<script>
var c = 0x70; // 16进制 0x
var a = 70; // 10进制
var b = 070; // 8进制 0
var d = 0B11; // 2进制 0B
console.log(a,b,c,d)
</script>
public static void main(String[] args) {
int a = 10;
int b = a;
a = 20;
System.out.println(b);
}
public static void main(String[] args) {
Cat c1 = new Cat();
c1.age = 18;
Cat c2 = c1;
c2.age = 20;
System.out.println(c1.age);
}
public class JavaTest {
public static void main(String[] args) {
Cat c1 = new Cat();
c1.age = 18;
c1.name = "张三";
}
}
class Haha {
String name;
}
class Cat extends Haha{
int age;
}
此时我们查看构造函数
public class JavaTest {
public static void main(String[] args) {
Cat c1 = new Cat();
c1.age = 18;
c1.name = "张三";
}
}
class Haha {
String name;
public Haha(){
System.out.println("Haha被创建了");
}
}
class Cat extends Haha{
public Cat(){
//Call to 'super()' must be first statement in constructor body
super();//在每个类的构造函数中 都会有一个 super() 代码 代表调用父类公共个无参数的构造函数 如果不写 默认会执行 写必须在第一句
System.out.println("猫被创建了");
}
int age;
}
public class JavaTest {
public static void main(String[] args) {
Haha cat = new Cat();
cat.eat();
}
}
class Haha {
public void eat(){
System.out.println("动物要吃饭");
}
}
class Cat extends Haha {
@Override
public void eat(){
System.out.println("猫爱吃鱼");
}
}
二十三、多态和抽象类
一、多态
需求A
动物园拜托我们写一套程序:
有一个狗类 类中有一个eat函数。再有一个人类 ,类中有一个feed函数。
其中人类的feed函数能喂狗。
public class JavaTest {
public static void main(String[] args) {
Dog wangcai = new Dog();
People zhangsan = new People();
zhangsan.feed(wangcai);
}
}
class Dog {
public void eat(){
System.out.println("狗吃肉");
}
}
class People {
public void feed(Dog d){
d.eat();
}
}
问题1:狗为什么不自己吃?
public static void main(String[] args) {
Dog wangcai = new Dog();
wangcai.eat();
}
从代码的角度讲 确实这样写比较方便。但是我们这个案例 模拟的是后期我们要遇到的一个场景:在一个类的函数中 需要使用到另一个类的对象
问题2:对象传参
需求B
动物园又来了一个新动物Cat,也有eat函数,人类也需要喂养猫咪。
public class JavaTest {
public static void main(String[] args) {
Dog wangcai = new Dog();
Cat tom = new Cat();
People zhangsan = new People();
zhangsan.feed(wangcai);
zhangsan.feed(tom);
}
}
class Cat {
public void eat(){
System.out.println("猫吃猫粮");
}
}
class Dog {
public void eat(){
System.out.println("狗吃肉");
}
}
class People {
public void feed(Dog d){
d.eat();
}
public void feed(Cat c){
c.eat();
}
}
需求C
动物园又来一个新动物:Tiger 也有eat函数 人类也需要喂养tiger。
此时就产生了问题: 我们需要创建Tiger 人类需要再次重载一个 feed(Tiger t) 函数。
我们发现 出现一种新动物 人类就需要重载一次。这就是我们代码中的 高耦合。
我们的代码要朝着:低耦合 高内聚 的方向发展。
也就是说 我们需要解决一个问题: 无论添加多少新动物 人类都不需要修改。(解耦)
所以java中就造了一种语法: 父类的引用 名字 = new 子类对象(); 就能解决我们现在的问题
public class JavaTest {
public static void main(String[] args) {
Haha wangcai = new Dog();
Haha tom = new Cat();
Haha taige = new Tiger();
People zhangsan = new People();
zhangsan.feed( wangcai );
zhangsan.feed( tom );
zhangsan.feed( taige );
}
}
class Haha {
public void eat(){}
}
class Tiger extends Haha {
public void eat(){
System.out.println("老虎吃狗粮");
}
}
class Cat extends Haha {
public void eat(){
System.out.println("猫吃猫粮");
}
}
class Dog extends Haha {
public void eat(){
System.out.println("狗吃肉");
}
}
class People {
public void feed(Haha h){
h.eat();
}
}
后来将这个语法 起了个好听的名字: 多态
多态(Polymorphism)按字面的意思就是“多种状态”。
在面向对象语言中,接口的多种不同的实现方式即为多态。引用Charlie Calverts对多态的描述——
**多态性是允许你将父类引用设置成为一个或更多的他的子对象相等的技术
[父类的引用 可以赋值成 不同的 子类对象形式 ]
赋值之后,父类引用就可以根据当前赋值给它的子对象的特性以不同的方式运作**。
【赋值之后 赋值成什么对象就调用什么对象的函数】
多态的语法: 继承多态 父类类型 名字 = new 子类对象();
接口多态 接口类型 名字 = new 实现类对象();
多态的作用: 解耦
二、抽象类
我们为了多态而创建了
class Haha {
public void eat(){}
}问题1: Haha能不能被创建对象?如果能 合理吗?
public static void main(String[] args) {
Haha haha = new Haha();
People zhangsan = new People();
zhangsan.feed( haha );
}
Haha这个类可以创建对象,但是不合理,因为创建对象之后 人类就能喂养,但是动物园没有Haha这种动物。
Haha这个类出现的原因是 为了产生继承关系 从而产生多态 解除程序耦合。
也就是说 有的类 存在并不是为了穿件对象 而是为了 让其他类继承。这种类 不能创建对象问题2: Haha中的eat 有存在的意义吗?
有存在的意义 因为不写 程序编译不通过。但是写了又不会调用。
所以不能说没意义 只能说 我们只需要函数的声明 【函数声明 】
不需要函数的实现 【函数方法体{} 】
问题3: Dog Cat 等类可以不写eat吗?如果能 合理吗?
可以不写,但是不合理。因为不写就会调用父类的,但是需求明确指明 每个动物类 都需要有各自的eat函数
Dog Cat 等 类 必须重写父类的eat函数
所以官方急需要造一个新语法 来完成大家的愿望
2.1 解决问题
public abstract class Haha {
public abstract void eat();
}
2.2 抽象类和抽象函数
1 class 前面添加 abstract关键字 此时这个类就是 抽象类
2 抽象类不能 new 对象
'Haha' is abstract; cannot be instantiated
3 抽象类有构造函数
[因为抽象类 存在的意义就是为了让别的类继承 建立继承关系之后 子类构造函数第一句代码 super() ]
4 抽象类可以继承其他的类 也可以被其他类继承
5 抽象类也可以写成员变量 普通函数
6 抽象类可以写抽象函数
---总结 抽象类和普通类的区别: 抽象类不能new对象 可以有抽象函数
7 一个函数返回值前面添加abstract 代表抽象函数
8 抽象函数必须在抽象类中
9 抽象函数不写也不能写方法体
10 抽象函数必须由子类实现 除非子类也是抽象的
11 抽象函数不能和 private final static 等连用
二十四、接口
一、接口的简介
接口(硬件类接口)是指同一计算机不同功能层之间的通信规则称为接口。
接口(软件类接口)是指对协定进行定义的引用类型。其他类型实现接口,以保证它们支持某些操作。接口指定必须由类提供的成员或实现它的其他接口。与类相似,接口可以包含方法、属性、索引器和事件作为成员。Java里面由于不允许多重继承,所以如果要实现多个类的功能,则可以通过实现多个接口来实现。
接口就是一个特殊的抽象类
接口的作用:
1 实现多态
2 定制规则
3 添加标记
二、 接口的语法
1 接口的声明 使用 interface 关键字
public interface Hehe {}
2 接口中的函数 默认都是公共抽象的 public abstract 修饰
interface Hehe {
void run();
}
3 接口中的变量都是 静态常量 public static final 修饰的
4 接口中只有 抽象函数 静态常量 默认实现 静态函数(需要写方法体) 其他的都没有
[接口没有构造函数 不能创建对象 没有普通函数 不能和一些关键字连用 ]
5 接口可以继承接口 并且是多继承
interface Hehe extends Foo,Bar {}
6 接口和类之间的关系是 实现关系 并且是多实现
class Dog implements Haha,Hehe{
public void eat(){
System.out.println("狗要吃肉");
}
public void run(){
System.out.println("狗儿跑");
}
}
三 、对比
普通类: 成员变量 静态变量 静态常量 构造函数 普通函数 静态函数 继承父类 实现接口 可以new对象
抽象类: 成员变量 静态变量 静态常量 构造函数 普通函数 静态函数 抽象函数 继承父类 实现接口 不能new对象
接口 : 静态常量 静态函数 抽象函数 继承接口 不能new对象
枚举 :
注解 :
四 、static关键字
static 关键字修饰的内容就是静态的。
可以修饰 成员变量---静态变量
可以修饰 普通函数---静态函数
如果使用static进行修饰 调用规则全部变成 类调用。所以有的地方就会和你说:
成员的内容属于对象级别 静态的内容属于类级别
成员变量和静态变量
如果遇到 一群学生 每个学生都需要年龄 地址 姓名 班级 这一些使用成员变量
如果遇到 最大年龄 多个对象共享数据 这使用静态变量
普通函数和静态函数
普通函数 : 函数内部涉及到当前对象操作的时候 只能使用普通函数 例如 set get toString
静态函数 : 能使用静态函数的地方 基本上都可以使用普通函数
public class JavaTest {
public static void main(String[] args){
People p1 = new People();
p1.eat();
People.sleep();
}
}
class People {
public void eat(){
}
public static void sleep(){
}
}
五 、final最终的
final 关键字修饰的内容代表最终的。
final可以修饰 类 成员变量 静态变量 函数 局部变量
A final 修饰类的时候代表 最终类 [该类不能被其他类继承]
面试题: 请问String能否被继承 ? 为什么?为什么这样架构?
不能 public final class String implements java.io.Serializable, Comparable<String>, CharSequence { }
B final 修饰的函数 可以被调用 但是不能被子类重写
有一些核心算法函数 不允许重写
C final 修饰变量 代表常量 只能赋值一次 不能再改
成员常量: 初始化的时候 或者 构造函数
class Haha {
final int a = 20;
}
class Haha {
final int a;
public Haha(){
a = 20;
}
}
静态常量: 初始化的时候 或者 静态代码块
class Haha {
static final int a = 20;
}
class Haha {
static final int a ;
/**
* 当类加载到方法区的时候 会调用静态代码块里面的代码
*/
static {
a = 30;
}
}
注意 final修饰的变量 命名规范 全大写下划线
包名: 全小写 反域名
类名:大驼峰 有意义
函数名 成员变量 静态变量 参数名:小驼峰 有意义
静态常量:全大写 下划线
最常用的就是静态常量 作为代替代码中的魔法值使用
private final static int MIN_AGE = 18;
public void eat(){
int a = 20;
if( a > MIN_AGE){
}
}
二十五、数组和封装
一、Java中的数组
数组是用来存取一组数据。并且通过 索引值/下标 对数组中的数据进行操作。索引值从0开始。
JavaScript中的数组 var arr = [];
public static void main(String[] args) {
// 1 java中创建有初始容量的数组
// 创建一个 int类型的数组 并且初始容量是5
// java中的数组 声明的是什么类型 只能存储该类型的数据 ------ 定型
int[] arr1 = new int[5];
// 这种声明方式不推荐大家使用 但是有一些书面材料有这样的写法 所以大家需要掌握
int arr2[] = new int[5];
People[] arr3 = new People[10];
// 2 java中创建有初始值的数组
// 创建带有三个初始值的String字符串数组 并且容量就是3 ------- 定容
String[] arr4 = new String[]{"张三" , "李四" ,"王五"};
People p1 = new People();
p1.id = 1;
p1.name = "张三";
People p2 = new People();
p2.id = 12;
p2.name = "李四";
People[] arr5 = new People[]{ p1, p2 };
// 3 字面量创建数组
double[] arr6 = {20.5 , 30.6};
String[] arr7 = {"黄金", "科技", "白酒" , "石油" ,"电动车"};
}
格式: 数据类型[] 名字 = new 数据类型[容量]; --- 定容 定型
数据类型[] 名字 = new 数据类型[]{初始数据,...}
数据类型[] 名字 = {}
二、Java中的数据存取
public static void main(String[] args) {
// [0,0,0,0,0]
int[] arr = new int[5];
//存
arr[0] = 20;
arr[1] = 30;
//取
int a = arr[0];
System.out.println(a);
// 遍历数组
for(int i=0;i<arr.length;i++){
int b = arr[i];
System.out.println(b);
}
}
三、Java中数组越界异常
public static void main(String[] args) {
int[] arr = new int[5];
//存
arr[6] = 20;
}
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 6
at com.aaa.test.JavaTest.main(JavaTest.java:11)
数组越界异常,java中的数据创建之后都是定容的 ,此时索引值存取数据都需要在容量允许的范围之内 ,如果超出在报异常
四、数据工具类封装
Java中的数组有一个问题
public static void main(String[] args) {
int[] arr = {10,30,50,80};
System.out.println(arr);
}
当我们输出数组的时候 ,打印的是:[I@1540e19d 全限定名@hashcode码
因为输出的时候 默认会调用 对象的toString函数的返回值 。此时 int[] arr 自己没有toString
所以就调用Object中的toString函数。所以打印出来 [I@1540e19d
这样的输出 不满足我们对数组的输出要求 所以我们都是直接在当前类中重写toString函数
但是!!!!数组不是我们自己写的类 所以我们需要自己用逻辑实现其输出
public static void main(String[] args) {
int[] arr = {123,51,456,1324,123,4,125,1243,532,45};
String str = "[";
for(int i=0;i<arr.length;i++){
str += arr[i];
if( i == arr.length-1){
str += "]";
}else{
str += ",";
}
}
System.out.println(str);
}
此时我们就能格式化输出数组的内容。这个代码太棒了!!!! 但是 如果在别的地方 还有一个数组 也要进行格式化输出 我们是不是需要进行cv大法呢?
我们每一次都进行CV太麻烦了 ,可以 将这段功能代码 写到一个函数中 哪里需要哪里调用
public class ArrayTool {
/**
* 格式化数组
* @param arr
* @return
*/
public static String xx(int[] arr){
String str = "[";
for(int i=0;i<arr.length;i++){
str += arr[i];
if( i == arr.length-1){
str += "]";
}else{
str += ",";
}
}
return str;
}
/**
* return the max value
*/
public static int getMax(int[] arr){
int max = arr[0];
for(int i=1;i<arr.length;i++){
if( arr[i] > max){
max = arr[i];
}
}
return max;
}
/**
* 首次出现的索引值
*/
public static int indexOf(int[]arr,int data){
if(arr.length==0){
return -1;
}
for(int i=0;i<arr.length;i++){
if(arr[i] == data){
return i;
}
}
return -1;
}
/**
* 两个数组是否完全相同
*/
public static boolean equals(int[] haha,int[] hehe){
if(haha == hehe){
return true;
}
if(haha.length != hehe.length){
return false;
}
for(int i=0;i<haha.length;i++){
if(haha[i] != hehe[i]){
return false;
}
}
return true;
}
/**
* 排序
*/
public static void sort(int[] arr){
for(int i=1;i<arr.length;i++){
for (int j=0;j<arr.length-i;j++){
if(arr[j]>arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
}
五、Java数组扩容
java中数组是定容的,此时使用起来非常不方便。假如我们创建了一个数组 容量是5 ,存储数据的时候发现不够用了,如果强行寸就会出现数组越界异常。所以我们java中的数组需要动态扩容
六、动态数组封装
public class MyArray {
private int[] element;
private int size;
public MyArray(){}
public MyArray(int count){
element = new int[count];
}
public void add(int data){
if( element.length == size ){
element = Arrays.copyOf(element, size + (size >> 1));
}
element[size++] = data;
}
public int get(int index){
return element[index];
}
public int size(){
return size;
}
}
七 、封装
封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。
我们做过三种封装:
A 数据模型类
B 工具类
C 功能对象封装面向对象思想源于封装
面向对象思想: 开发的时候 不仅要考虑到功能的实现 还要考虑到后期的优化和维护
二十六、异常机制
一、为什么需要异常
我们上面封装的工具类,其实是有问题的
public static int getMax(int[] arr){
int max = arr[0];
for(int i=1;i<arr.length;i++){
if( arr[i] > max){
max = arr[i];
}
}
return max;
}
这个函数的封装有不严谨的地方,当一个数组里面没有数据的时候,此时就没有最大值一说
所以我们需要在代码中进行安全判断
public static int getMax(int[] arr){
if(arr.length == 0){
return '错';
}
int max = arr[0];
for(int i=1;i<arr.length;i++){
if( arr[i] > max){
max = arr[i];
}
}
return max;
}
此时我们就发现 当我们添加安全判断之后 条件成立 终止程序的时候 无法正常return
return null; return "没有最大值"; return -1;return '错'; 都不行
要么有歧义 要么类型不匹配,所以就无法正常 return此时有返回值的函数 急需一种非正常操作下的返回形式 所以java就推出了异常机制
封装的函数:
能正常执行的 就正常 return
非正常执行的 就异常 throw
那么从今天开始我们就要推翻之前的语法: 有返回值的函数 一定要有一个可执行的return或throw
二、异常抛出[执行方]
public static int getMax(int[] arr){
if(arr.length == 0){
throw new RuntimeException("对不起 数组不能为null");
}
int max = arr[0];
for(int i=1;i<arr.length;i++){
if( arr[i] > max){
max = arr[i];
}
}
return max;
}
throw 异常对象
RuntimeException 运行时异常
三、异常处理[调用方]
A 异常转移【默认处理方式】
public static void main(String[] args) throws RuntimeException, IOException {}在函数的参数后面 添加 throws 异常类 ,异常类
当我们进行异常转移的时候 如果函数中出现这类异常 此时异常就会转移到当前函数的调用方,如果调用方继续转移
最终会转移到JVM,此时JVM就会负责去处理这个异常,并且处理的方式是:终止JVM并且打印异常跟踪栈信息
Exception in thread "main" java.lang.RuntimeException: 对不起 数组不能为null
at com.aaa.test01.ArrayTool.getMax(ArrayTool.java:9)
at com.aaa.test01.JavaTest.main(JavaTest.java:13)
B 异常捕获
public static void main(String[] args) {
int[] arr = {};
try {
int max = ArrayTool.getMax(arr);
System.out.println(max);
}catch (RuntimeException e){
// 出现异常之后的 处理方案
String message = e.getMessage();
System.out.println(message);
e.printStackTrace();
}
System.out.println("你好世界");
}
通过
try{
可能出现异常的代码
}catch(异常类型 名字){
出现该种异常的解决方案
}
语句块在捕获异常 并进行处理
四 、多异常捕获
如果我们需要不同的异常类型 使用不同的解决处理方案 此时就需要 分类捕获异常
public static void main(String[] args) {
int[] arr = {};
try {
int max = ArrayTool.getMax(arr);
System.out.println(max);
.....
}catch (NullPointerException e){
e.printStackTrace();
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}catch (RuntimeException e){
e.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("你好世界");
}
执行流程是:1 当try中代码出现异常之后 就会直接跳到catch中
2 会一个catch一个catch的寻找 当前异常是否匹配捕获内容
所以分类捕获的时候 大异常不能写上面
如果所有的异常 解决方案都是一样的 此时只需要捕获一种总异常类型
public static void main(String[] args) {
int[] arr = {};
try {
int max = ArrayTool.getMax(arr);
System.out.println(max);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("你好世界");
}
五、异常的分类
Throwable
|
|-- Error 错误 我们程序出现的错误,一旦出现也无法用代码去解决
| OutOfMemoryError 内存溢出错误 AWTError
|
|-- Exception 异常 我们代码出现的数据参数等问题
|
|--其他 |
|--ReflectiveOperationException | 非运行时异常
|--IOExcetion | 编码阶段就会出现的异常 并且需要给出解决方案
|
|
|---RuntimeException 运行时异常 程序运行到jvm之后才会出现的异常 [杜绝]
ArithmeticException 算术异常
NullPointerException 空指针异常 使用null 调用java函数
IndexOutOfBoundsException 索引越界异常
IllegalArgumentException 非法参数异常
ClassCastException 类型转换异常
....
六、finally代码块
finally代码块 无论有没有异常 都会执行finally中的代码
所以经常用来关闭资源等场景
public static void main(String[] args) {
try {
System.out.println("打开银行保险柜大门");
System.out.println("从里面拿些钱");
int i = 1/0;
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("关闭银行保险柜大门");
}
}
面试题
1 final 和 finally 的区别?
2 finally 可以和谁连用
try {}finally {}
try {}catch (Exception e){ e.printStackTrace(); }finally {}
3 请问以下代码输出什么?
public class JavaTest {
public static void main(String[] args) {
int haha = haha();
System.out.println(haha);
}
public static int haha(){
int a = 20;
try {
return a;
}finally {
a = 30;
}
}
}
4 try中有return finally中也有return 请问执行哪一个?
public class JavaTest {
public static void main(String[] args) {
int haha = haha();
System.out.println(haha);
}
public static int haha(){
try {
return 20;
}finally {
return 30;
}
}
}
七、自定义异常
Throwable 我们平时在异常Exception中常用的函数 其实都是父类Throwable的
public String getMessage() {
return detailMessage;
}
public void printStackTrace() {
printStackTrace(System.err);
}
我们又发现一个神奇的问题 Exception 没有自己的函数 构造函数也是调用父类的构造函数 仅此而已
public class Exception extends Throwable {
static final long serialVersionUID = -3387516993124229948L;
public Exception() {
super();
}
public Exception(String message) {
super(message);
}
public Exception(String message, Throwable cause) {
super(message, cause);
}
public Exception(Throwable cause) {
super(cause);
}
protected Exception(String message, Throwable cause,
boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
public class RuntimeException extends Exception {
static final long serialVersionUID = -7034897190745766939L;
public RuntimeException() {
super();
}
public RuntimeException(String message) {
super(message);
}
public RuntimeException(String message, Throwable cause) {
super(message, cause);
}
public RuntimeException(Throwable cause) {
super(cause);
}
protected RuntimeException(String message, Throwable cause,
boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
我们在查看其子类
public class NullPointerException extends RuntimeException {
private static final long serialVersionUID = 5162710183389028792L;
public NullPointerException() {
super();
}
public NullPointerException(String s) {
super(s);
}
}
public class IndexOutOfBoundsException extends RuntimeException {
private static final long serialVersionUID = 234122996006267687L;
public IndexOutOfBoundsException() {
super();
}
public IndexOutOfBoundsException(String s) {
super(s);
}
}
此时我产生了一个疑问 写了几百个异常类 但是类中什么都没有 全是调用父类构造函数
既然这些子类什么用都没有 为什么还要写那么多呢?为了有语义化。其实实质上是一样的,但是在使用的时候
throw new Exception();
如果我们写成
throw new IndexOutOfBoundsException()java虽然写了很多很多的异常 目的是为了将我们可能出现的情况全部包括,但是随着时代的发展 有一些情况肯定是官方没有定义过。所以我们可以自定义异常
public class UsernameBuPiPeiException extends RuntimeException {
private static final long serialVersionUID = 5162710183389028792L;
/**
* Constructs a {@code NullPointerException} with no detail message.
*/
public UsernameBuPiPeiException() {
super();
}
/**
* Constructs a {@code NullPointerException} with the specified
* detail message.
*
* @param s the detail message.
*/
public UsernameBuPiPeiException(String s) {
super(s);
}
}
二十七 、File和IO流
一、File的介绍
File 类 就是当前系统中 文件或者文件夹的抽象表示
通俗的讲 就是 使用File对象 才操作我们电脑系统中的文件或者文件夹学习File类 其实就是学习 如何通过file对象 对系统中的文件/文件夹进行增删改查
二、File 创建对象
1 前期准备在非c盘下创建一个文件夹 例如 G:/aaa
2 File对象的创建
File f1 = new File("G:\\aaa");
A 导包 import java.io.File;
B 路径是从电脑上面复制过来的 G:\aaa 但是复制到代码中就变成 "G:\\aaa"
原因是 \ 代表转义符 而我们真正的需要使用 \本身 所以就 \\ 前边代表转义符 转移后面的斜线成本身含义3 为什么要转义成 \ 斜线呢?
因为在windows操作系统中 路径分隔符 就是 \ 但是java代码是跨平台的 其他平台的路径分隔符是 /
为了让我们的代码有跨平台特征 ,所以我们以后写路径分隔符的时候 需要动态获取 File.separator
File f1 = new File("G:"+File.separator+"aaa");
或者
File f1 = new File("G:/aaa"); // window / \ 都行
三、File创建文件,文件夹
@Test
public void test01()throws Exception{
File file1 = new File("C:/aaa/999.txt");
//创建新文档
file1.createNewFile();
//设置为只读 不可更改
file1.setReadOnly();
//修改
file1.renameTo(new File("C:/aaa/666.txt"));
File f2 = new File("C:/aaa/baga");
//创建一个文件
f2.mkdir();
File f3 = new File("C:/aaa/1/2/3/4/5/6/7/8/9");
//创建多个文件
f3.mkdirs();
}
四、File 删除文件
@Test
public void test01() throws Exception {
File f1 = new File("G:/aaa/123.txt");
f1.delete();
File f2 = new File("G:/aaa/haha");
f2.delete();
//注意 删除不了有内容的文件夹
File f3 = new File("G:/aaa/1");
f3.delete();
}
五、File 修改文件
@Test
public void test01() throws Exception {
File f1 = new File("G:/aaa/123.txt");
// 修改文件的 读写可执行权限 这是在Linux中特有的权限
f1.setWritable(true);
f1.setReadable(true);
f1.setExecutable(true);
f1.setReadOnly();
//f1.renameTo( new File("G:/789.txt") );
f1.setLastModified(1000*60*60*24);
}
六、File 查询内容
can 系列
get 系列
is 系列
@Test
public void test02()throws Exception{
File file1 = new File("C:/aaa/555.txt");
file1.createNewFile();
//获取文件名字
String name = file1.getName();
System.out.println(name);
//获取路径的上局目录
String parent = file1.getParent();
System.out.println(parent);
//获取路径
String path = file1.getPath();
System.out.println(path);
//是否为一个文件 除了文件夹
boolean file = file1.isFile();
System.out.println(file);
//是否有文件夹
boolean directory = file1.isDirectory();
System.out.println(directory);
//是否存在
boolean exists = file1.exists();
System.out.println(exists);
//内容长度
long length = file1.length();
System.out.println(length);
//获取所有子目录的名字
File file2 = new File("C:/aaa");
String[] list = file2.list();
System.out.println(Arrays.toString(list));
//获取所有子目录的文件对象
File[] files = file2.listFiles();
System.out.println(Arrays.toString(files));
}
七、递归算法
请计算 高斯累加 1+2+3..+100 = 5050
当时我们写过for循环版本 这一次我们使用 递归完成
<script>
function sum(n) {
if (n == 1) {
return 1;
}
return n + sum(n - 1);
}
var a = sum(3);
alert(a);
</script>
@Test
public void test02(){
haha("C:/Program Files (x86)");
}
public void haha(String path){
File f1 = new File(path);
if( f1.isDirectory() ){
File[] files = f1.listFiles();
for (File file : files) {
if(file.isDirectory()){
haha( file.getPath() );
}else{
System.out.println(file);
}
}
}else {
System.out.println(f1);
}
}
IO流的介绍
IO都是全大写 说明肯定是两个单词的首字母
I inputstream 输入流 O outputstream 输出流
IO 称之为 java的输入输出流
其实学习IO 就是学习 如何通过java代码 对文件内容 进行 读(输入流) 写(输出流)
所以有一话: 读进来 写出去Java流的分类
按流向分:
输入流: 程序可以从中读取数据的流。
输出流: 程序能向其中写入数据的流。
按数据传输单位分:
字节流: 以字节为单位传输数据的流
字符流: 以字符为单位传输数据的流
按功能分:
节点流: 用于直接操作目标设备的流 ---- 四大基流
过滤流: 是对一个已存在流的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能。 ---- 包装流
四大基流:
字节输入流
字节输出流
字符输入流
字符输出流
一、字节流
1.1 OutputStream 字符输出流
它可以对任意文件进行操作,对文件进行输出操作。以字节为单位。 它是所有字节输出流的父类,
FileOutputStream
第一种方式 输出Int类型的码值
@Test
public void test03() throws Exception{
FileOutputStream out = new FileOutputStream("G:/aaa/456.txt");
out.write(100);
out.write(100);
out.write(100);
out.close();
}
第二种方式 byte数组的方式
@Test
public void test03() throws Exception{
FileOutputStream out = new FileOutputStream("G:/aaa/456.txt");
byte[] data = "hello world".getBytes();
out.write(data);
out.close();
}
第三种方式 byte数据局部写出
@Test
public void test03() throws Exception{
FileOutputStream out = new FileOutputStream("G:/aaa/456.txt");
byte[] data = "abcdefghijklmn".getBytes();
out.write(data,3,5);
out.close();
}
文件追加形式:
@Test
public void test03() throws Exception{
FileOutputStream out = new FileOutputStream("G:/aaa/456.txt",true);
byte[] data = "abcdefghijklmn".getBytes();
out.write(data,3,5);
out.close();
}
1.2 字节输入流--InputStream
它可以对任意文件进行读操作 ,以字节为单位,它是所有字节输入流的父类,子类有FileInputStream
第一种方式: 每次读取一个字节 返回int类型的码值
@Test
public void test04() throws Exception{
FileInputStream in = new FileInputStream("G:/aaa/456.txt");
int d1 = in.read();
System.out.println(d1);
int d2 = in.read();
System.out.println(d2);
in.close();
}
第二种方式 byte数组读取
@Test
public void test04() throws Exception{
FileInputStream in = new FileInputStream("G:/aaa/456.txt");
byte[] data = new byte[50];
int read = in.read(data);
System.out.println(read);
System.out.println(Arrays.toString(data));
String s = new String(data, 0, read);
System.out.println(s);
in.close();
}
第三种方式 byte数据局部获取
@Test
public void test04() throws Exception{
FileInputStream in = new FileInputStream("G:/aaa/456.txt");
byte[] data = new byte[50];
int read = in.read(data,3,5);
System.out.println(read);
System.out.println(Arrays.toString(data));
String s = new String(data, 0, read);
System.out.println(s);
in.close();
}
循环读取文件内容
@Test
public void test04() throws Exception{
FileInputStream in = new FileInputStream("G:/aaa/456.txt");
byte[] data = new byte[50];
int len = 0;
StringBuilder sb = new StringBuilder();
while( (len = in.read(data)) > 0 ){
String s = new String(data, 0, len);
sb.append(s);
}
System.out.println(sb);
in.close();
}
1.3 使用字节输出和字节输入完成复制功能
@Test
public void testCopy() throws Exception{
//1.创建字节输入流
InputStream is = new FileInputStream("D:/AAA0420/666.jpg");
//2.字节输出流
OutputStream fos = new FileOutputStream("C:/AAA0420/66.jpg");
byte[] bytes = new byte[10];
int c=0;
while ((c=is.read(bytes)) !=-1 ){
fos.write(bytes,0,c);
}
is.close();
fos.close();
}
二、字符流
2.1 Write字符输出流
它是所有字符输出流的跟类。---FileWriter类
@Test
//字符输出流
public void test01() throws Exception{
//字符输出流 ---指定对那个文件 (路径) 进行写操作
FileWriter writer = new FileWriter("C:/aaa/111.txt");
writer.write("你好 世界");
writer.flush();//刷新流
writer.close();//关闭流
}
上面每次往文件中写内容时 就会把原来的内容覆盖了。 如何追加内容
public void test01() throws Exception{
//字符输出流 ---指定对哪个文件(路径)进行写操作
//true:表示允许追加内容到文件中
Writer writer = new FileWriter("D:/0419AAA/S.txt",true);
String str ="明天是晴天";
writer.write(str);
writer.flush();//刷新流
writer.close();//关闭流资源
}
2.2 Reader字符输入流
它是所有字符输入流的根类 它的实现类有很多,我们使用FileReader实现类
@Test
//字符输入流
public void test05()throws Exception{
FileReader reader = new FileReader("C:/aaa/111.txt");
StringBuffer stringBuffer = new StringBuffer();
char[] chars = new char[50];
int count;
while ((count=reader.read(chars))>0){
String s = new String(chars, 0, count);
stringBuffer.append(s);
}
System.out.println(stringBuffer);
reader.close();
}
三、包装流-缓冲字节包装流
缓存流是在基础流[InputStream OutputStream Reader Writer]之上 添加了一个缓存池功能.
BufferInutStream BufferOutputStream BufferReader BufferWriter 提高IO的效率,降低IO的次数。
缓存输出
@Test
public void TestBuffer() throws Exception{
OutputStream out = new FileOutputStream("C:/aaa/777.txt");
BufferedOutputStream bos = new BufferedOutputStream(out);//缓存流要作用在基础流上
String str ="nihao shijie nihao shijie";
byte[] bytes = str.getBytes();
bos.write(bytes); //因为你写的内容 暂时放入缓存池中 并没有直接放入文件中。 所以文件中没有你的内容。
//bos.flush();//刷新缓存池---把池子中的内容输入到文件上
bos.close(); //关闭----先刷新缓冲池 再关闭流资源
}
缓存输入
@Test
//缓冲输入流
public void test07()throws Exception{
FileInputStream in = new FileInputStream("C:/aaa/777.txt");
BufferedInputStream inputStream = new BufferedInputStream(in);
int count;
byte[] bytes = new byte[50];
while ((count=inputStream.read(bytes))>0){
String s = new String(bytes, 0, count);
System.out.println(s);
}
inputStream.close();
}
四、对象序列换
4.1 为什么需要对象包装流
我们之前的IO操作都是将字符串内容在文件中进行读写
我们java中存储数据使用对象存储,我们能不能将一个存储了数据的对象写入到文件中并且读取呢?
可以的 要是用的就是对象包装流
我们为什么要把对象写入到文件中呢?
例如我们单机游戏的存档 其实就是对象写入到文件中
【
内存:
硬盘:
】
4.2 对象包装流的操作
/**
* Created by Intellij IDEA
*
* @author 王俊凯
* @Date: 2022/8/16 16:20
* @Version 1.0
*/
package com.wjk.test05;
import java.io.Serializable;
public class People {
private int id;
private int money;
private String name;
private String address;
@Override
public String toString(){
return "People{" +
"id=" + id +
", money=" + money +
", name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
public People(int id, int money, String name, String address) {
this.id = id;
this.money = money;
this.name = name;
this.address = address;
}
}
@Test
public void test08()throws Exception{
People people = new People(1,13000,"八嘎","河南");
FileOutputStream out = new FileOutputStream("C:/aaa/People.txt");
ObjectOutputStream outputStream = new ObjectOutputStream(out);
outputStream.writeObject(people);
outputStream.close();
}
此时我们运行代码发现出现异常:
java.io.NotSerializableException: com.aaa.test03.People
不能序列化异常
4.3 序列化
序列化: 将 java对象转 换成 二进制数据流 过程 称之为 对象序列化
反序列化: 将 二进制数据流 转换成 java对象的过程 称之为 反序列化序列化平时都是需要的 例如 文件上传
序列化操作是一个很复杂的功能,所以java底层已经写好了,
但是java不确定你是否需要使用序列化操作或者说 你是否允许你的对象进行序列化
所以java就想出来一套方案: 有一个接口 Serializable 如果你的对象需要使用java底层序列化操作
那么你就实现这个接口 java底层就会帮我们进行序列化 如果不需要则不实现这就是接口三大作用之一: 添加标记
我们查看这个接口的源码
public interface Serializable {}
有人就问 为什么 String 我们当时直接操作了 很开心
那是因为public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
引申一下 public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
4.4 读取对象流
读取对象流可以使用Object类
@Test
public void test08()throws Exception{
People people = new People(1,13000,"八嘎","河南");
FileOutputStream out = new FileOutputStream("C:/aaa/People.txt");
ObjectOutputStream outputStream = new ObjectOutputStream(out);
outputStream.writeObject(people);
outputStream.close();
}
4.5 序列化版本ID
ava.io.InvalidClassException: com.aaa.test03.People;
local class incompatible: stream classdesc serialVersionUID =6263378312512288776
local class serialVersionUID = -8718843698792840422
当我们写入对象之后 修改了原有对象类的内容 再次读取报异常 序列化版本不匹配
public class People implements Serializable {
private int id;
private int money;
private String name;
private String address;
@Override
public String toString(){
return "People{" +
"id=" + id +
", money=" + money +
", name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
public People(int id, int money, String name, String address) {
this.id = id;
this.money = money;
this.name = name;
this.address = address;
}
}
面试题: 1 final修饰的静态常量命名规范?
2 为什么java官方的不这样命名? private static final long serialVersionUID = 666L;
因为历史遗留问题