🍉🍍🍒🥕🍇🥥
- 🍉java基础概述
- 🍉java的基础语法
- 🍉数组
- 🍉方法
- 🍉debug
- 🍉类和对象
- 🍉字符串
- 🍉集合
- 🍉继承
- 🍉包
- 🍉修饰符
- 🍉多态
- 🍉抽象类
- 🍉接口
- 🍉形参和返回值
- 🍉内部类
- 🍉一些常用的API
- 🍉异常
- 🍉集合的进阶
- 🍉程序的复用性
- 🍉IO流
- 🍉多线程
- 🍉网络编程
- 🍉Lambda表达式
- 🍉接口组成更新
- 🍉方法引用
- 🍉函数式接口
- 🍉Stream流
- 🍉反射
- 🍉模块化
🍉java基础概述
🍍java跨平台原理
jre是java虚拟机,相当于一个翻译,只要保证在需要运行java应用程序的操作系统上安装一个与该操作系统对用的jre就能保证java能在该操作系统上运行
🍍jdk,jre,jvm的联系
jdk:java开发环境
jvm:java虚拟机
jre:运行时环境
🍍用控制台编译输出hello word
1.首先现在一个目录下新建“文件名.java文件”,再在控制台中进入该目录
2.编译生成.class文件:javac 文件名.java
3.运行:java 文件名
public class helloword{
public static void main(String [] args){
System.out.println("hello world");
}
}
🍉java的基础语法
🍍注释
单行注释://注释信息
多行注释:/星号 注释信息 星号/
🍍常量
除了空常量其余都可以用println直接输出
🍍数据类型
🍍变量
输出s:
public class helloword{
public static void main(String [] args){
char a='s';
System.out.println(a);
}
}
🍒变量的定义注意事项
1.整数默认为int类型
当我们要定义一个long类型的变量的时候,必须在末尾加一个L,否则会默认为int类型导致溢出
小数默认为float类型
同理13.14默认为double类型,赋给float类型变量的时候会出现不兼容的现象,所以需要加一个F
🍍标识符规则
1.由数字,字母,美元符,下划线组成
2.不能以数字开头
3.不能是关键字
4.区分大小写
🍍类型转换
🍒自动类型转换(从小到大)
比如将10这个int类型的值赋给double,就会进行自动转化,最后输出10.0
public class helloword{
public static void main(String [] args){
double a=10;
System.out.println(a);
}
}
🍒强制类型转换(从大到小,有可能会导致数据的丢失)
输出10
public class helloword{
public static void main(String [] args){
int a=(int)10.99;
System.out.println(a);
}
}
🍍变量运算的类型优先级
1.如果使用int类型来接收的话会出现不兼容(从double到int会损失精度)
public class helloword{
public static void main(String [] args){
int a=10.99+'a'+10;
System.out.println(a);
}
}
2.所以用double接受即可
public class helloword{
public static void main(String [] args){
double a=10.99+'a'+10;
System.out.println(a);
}
}
🍍字符串的+操作(拼接)
只要有字符串的+操作都是拼接
注意:运算顺序为从左到右,如果+的两端中有一个为字符串的话为拼接操作,否则为加法操作
输出黑马666,100黑马
🍍赋值运算符注意问题
+=(或者-=)隐含了强制类型转换
下图中,如果用s=s+20的话,因为s是short类型,但是20是int类型,根据优先级关系会将其转换成int类型但是s是short类型的,即发生了不兼容,所以需要强制类型转换
🍉数组
🍍数组的定义
1.推荐使用:int[ ] arr
定义一个数组,数组名称是arr
2.int arr[ ]
定义一个变量,变量名是arr数组
🍍数组的初始化
java中数组必须经过初始化,即分配内存并赋值
🍒动态初始化
初始化只需要指定数组长度,然后系统为数组分配初始值
eg:int[ ] arr = new int[3]
new:申请内存空间
整数默认值为0
浮点数为0.0
布尔值为false
🍒静态初始化
初始化的时候指定初始值,然后由系统决定数组的长度
eg:int[ ] arr = {0,1,2}
🍍内存分配
int[ ] arr = new int[3]
new int[3]表示在内存中申请三个内存,这三个内存为一个内存块,arr只想这个内存块号,即这个内存块的存储地址
输出存储地址:[I@179d3b25
System.out.println(arr);
🍒栈内存和堆内存
栈内存存放基本类型变量(int double之类的)和引用变量(类,接口,数组)
堆内存存储对象
🍍两个数组指向同一个内存
任意一个数组进行了修改,另一个数组访问时其值都会发生改变
输出200,200
public class helloworld {
public static void main(String[] args) {
int [] arr=new int[3];
arr[0]=100;
指向同一个内存空间
int [] arr2=arr;
arr2[0]=200;
System.out.println(arr[0]);
System.out.println(arr2[0]);
}
}
🍉方法
🍍方法的定义和调用
import java.util.Scanner;
public class helloworld {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int a=sc.nextInt();
int b=sc.nextInt();
System.out.println(max(a,b));
}
定义方法
public static int max (int a,int b){
if (a>b){
return a;
}
else return b;
}
}
🍍方法的注意事项
方法不能嵌套
🍍方法的重载
🍒构成重载的条件
1.看方法名是否相同
2.看参数是否相同(类型,数量)
3.看是否在同一个类中
如果在同一个类中,方法名相同,且参数不同,满足这三个条件才构成重载(与方法的返回值无关)
🍒如何调用同名方法
在调用的时候java虚拟机会根据传入参数的不同来区分同名的方法,所以直接在传递参数的时候,根据该函数的形参类型进行传递即可
🍍传参
🍒基本型传参
a的值还是1
int a=1;
max(a);
public static void max (int a){
a++;
}
🍒引用型传参
形参传递的是引用类型的数据(数组),所以形参会改变实参的值
package com.itheima;
import java.util.Scanner;
public class helloworld {
public static void main(String[] args) {
int[] arr = new int[3];
arr[0] = 1;
change(arr);
System.out.println(arr[0]);
}
public static void change(int arr[]) {
arr[0]=2;
}
}
🍉debug
🍍步骤
1.加断点
2.选择调试选项
3.在控制台和debugger上查看即可
(按F7步入)
🍉类和对象
🍍类和对象的关系以及类的属性行为
🍒类
类是一个模板,它描述一类对象的行为和属性
eg:
如果以汽车为类(class),而具体的每辆车为该汽车类的对象(object),对象包含了汽车的颜色、品牌、名称等
🍒对象
对象是类的一个实例,有状态和行为
类是对象的抽象,对象是类的实体
🍒属性
比如一台手机,由内存,大小,像素这些属性
🍒行为
一台手机能打电话,发短信这些行为,即对象能干什么
🍍类的定义
1.定义类(类名必须一致)
2.定义成员变量
3.定义成员方法
🍍对象的使用
1.在类2中创建类1的对象
类名 对象名 = new 类名();
eg:phone p = new phone();
2.使用类1对象中的值或者方法
p.call();
p.price;
🍍多个对象
🍒指向不同的地址
s1和s2是不同的地址空间,相当于两个不同的变量,所以不会产生覆盖
🍒指向相同的地址
和数组一样,对象也可以指向同一个内存空间
student s1 = new student();
student s2=s1;
其中一个修改了另一个也会修改
🍍成员变量和局部变量
成员变量:类中方法外的变量
局部变量:类中方法中的变量
比如name是成员变量,i是局部变量,j是局部变量
🍒区别
栈内存存放基本类型变量(int double之类的)和引用变量(类,接口,数组)
堆内存存储对象
🍍private
🍒为什么要使用private
因为在实际应用中,如果将age设置为public的话,那么其他的测试类调用时,就可以随便设置值,无法保证数据的安全性,所以先用private进行判断数据是否安全在进行置值
🍒使用方法
1.private修饰的变量只能在本类中被访问
2.如果想在别的类中使用,就要提供get和set方法,且两者都要用public修饰
被引用的类
public class helloworld {
private int x = 100;
int y = 200;
public int get(){
return x;
}
public void set(int a){
if(a>0){
this.x=a;
}
else{
System.out.println("你给的数据有误");
}
}
}
测试类
输出
100
你给的数据有误
100
public class 引用helloworld类 {
public static void main(String[] args) {
helloworld h = new helloworld();
System.out.println(h.get());
h.set(-1);
System.out.println(h.get());
}
}
🍍this
经过this修饰的变量为成员变量,否则为局部变量
🍍封装
概述:
原则:
好处:
🍍构造方法
什么是构造方法:
是一种用于创建对象的特殊的方法(构造方法名必须和类名相同)
格式:
🍒无参构造方法
被调用类
package com.itheima;
public class helloworld {
private int x = 100;
int y = 200;
public helloworld(){
System.out.println("构造成功");
}
}
测试类:构造一个对象的同时输出”测试成功“
package com.itheima;
public class 引用helloworld类 {
public static void main(String[] args) {
helloworld h = new helloworld();
}
}
🥕注意事项
1.当没有构造方法的时候系统会默认给出无参构造方法,但是如果有构造方法的话,系统就不会给默认无参构造方法
2.多个构造方法满足重载机制
🍒带参构造方法
public helloworld(int x){
if(x>0){
this.x=x;
}
else {
System.out.println("你给出的数据有误");
}
}
🍉字符串
🍍API
application program interface(应用程序接口)
具体API使用方法可以在帮助文档里面查询
🍍String类
🍒概述
String类代表字符串,java中所有用""括起来的都是String类的对象
🍒使用构造方法来建立String对象
🥕无参构造方法
String s1 = new String();
此字符串为空
🥕带参构造方法
1.传入字符数组
char 【】 arr = {‘a','b','c'};
String s1 = new String(arr);
2.传入字节数组
char 【】 arr = {97,98,99};
String s1 = new String(arr);
🍒直接赋值(推荐使用)
String s1 = “abc”
🍒二者区别
直接赋值得到的对象,如果值相同的话,其地址也是相同的
new出来的对象就算值相同,其地址也不相同
使用==比较的是地址是否相同
所以下面输出false false true
使用equals方法比较的是字符串的值是否相同
所以下面输出true true true
🍍字符串遍历和拼接
🍒遍历
s.charAt(i)就是字符串的第i个字母
for(int i=0;i<s.length;i++){
sout(s.charAt(i));
}
🍒拼接
字符串之间可以直接相加
public class 引用helloworld类 {
public static void main(String[] args) {
String s1 = "";
for (int i = 1; i <= 3; i++) {
s1 += i;
}
System.out.println(s1);
}
}
🍍StringBuilder类
🍒为什么要用StringBuilder类
因为在字符串拼接的时候,会造成地址空间和时间的浪费,比如hello和world拼接就需要三个地址空间
🍒二者的区别以及相互转换
区别
String内容是不可变的,StingBuilder内容是可变的
相互转换
🥕Sting到StringBuilder
🥕StringBuilder到String
🍒StringBuilder常用方法
append方法,返回对象本身
reverse方法,反转
🍒例
拼接两个字符串(123,234拼接成【1,2,3,2,3,4】)
package com.itheima;
import java.util.Scanner;
public class 引用helloworld类 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s1 = sc.nextLine();
String s2 = sc.nextLine();
String s3 = rever(s1,s2);
System.out.println(s3);
}
public static String rever(String s1,String s2){
StringBuilder sb = new StringBuilder("[");
for(int i=0;i<s1.length();i++){
sb.append(s1.charAt(i)).append(',');
}
for(int i=0;i<s2.length()-1;i++){
sb.append(s2.charAt(i)).append(',');
}
sb.append(s2.charAt(s2.length()-1)).append(']');
return sb.toString();
}
}
🍉集合
🍍ArrayList
🍒概述
🍒常用方法
🥕添加:2种,一种最后加,一种指定位置加
红色部分为两种添加元素的方法,蓝色部分会发生越界报错
🥕删除:2种,一种返回删除是否成功,一种返回被删除值
1.返回删除是否成功,如果有该元素就返回true否则false
2.返回被删除的元素
🥕修改:1种,返回被修改的元素
🥕求长度:1种
array.size
🥕返回指定索引值:1种
🥕eg:存储helloworld对象并遍历输出
package com.itheima;
import java.util.ArrayList;
import java.util.Scanner;
public class 引用helloworld类 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int x = sc.nextInt();
ArrayList<helloworld> array = new ArrayList<helloworld>();
//存储x个对象:
for (int i = 0; i < x; i++) {
adddata(array);
}
//遍历x个对象
for(int i=0;i<x;i++){
System.out.println("第"+(i+1)+"个学生的年龄为:"+array.get(i).getX());
}
}
//定义添加函数
public static void adddata(ArrayList<helloworld> array) {
Scanner sc = new Scanner(System.in);
int s1 =sc.nextInt();
helloworld h1=new helloworld(s1);
array.add(h1);
}
}
🍒eg:学生管理系统
idea里面
🍉继承
面向对象的三大特征:继承,多态,封装
🍍继承的优缺点
优点:提高了代码的复用性和维护性
缺点:降低了代码的独立性
🍍变量的访问特点
如果要访问子类方法中x这个变量,首先会在方法内部寻找给变量是否存在,如果存在的话就会用方法里面的值,如果不存在的话就会到本类中找,本类中也不存在的话就回去父类找,所以在子类方法中访问变量次序为方法内-本类-父类
具体可以用this.和super.来表示
注意:
子类和父类中的成员变量的访问优先级也是本类-父类
🍍构造方法的访问特点
因为子类会继承父类的所有方法和成员变量,所以初始化子类之前,系统会默认给父类也进行初始化,即调用父类的无参构造方法super(),如果想用父类的带参构造方法进行初始化的话就直接super(参数,参数…)即可(如果已经自己手动添加 父类的构造方法,系统就不会在默认添加)
用this和super访问构造方法
🍍成员方法的访问特点
同理先本类再父类
🍒方法重写
🍍重写和继承的注意事项
1.使用注释信息Override
可以检查是否符合重写规则正确(直接再子类中写父类中的方法名直接弹出)
2.父类中的私有方法不能重写,且私有变量不能继承,但是可以通过get和set方法来使用
3.子类方法的访问权限符不能比父类的更低
🍍例子
定义一个子类一个父类,父类中有完整的get,set方法和构造方法(无参,带参)
1.子类如果要使用继承于父类中的private变量的话必须使用get和set方法
2.子类如果想要定义带参构造方法的话必须使用super
public zi (String name ,String age){
super(name,age);
}
这时候就有疑问了,为什么不能这样呢:
public zi (String name , String age){
this.name=name;
this.age=age;
}
原因是子类继承的父类中的private变量,子类只具有拥有权,但是没有使用权,所以必须调用父类中的get,set或者是其他给予权限访问父类私有变量的方法才能使子类使用私有变量
🍉包
🍍概述和使用
概述和作用
实际上就是文件夹,作用就是方便对类进行分类管理
格式
包名用.分开,范例就是个二级包
🍍如何创建包
不用idea的情况下怎么用记事本建包
🍒手动
1.先将编写好的java文件编译生成.class文件(要在代码中申明建包)
2.手动建包,也就是建立文件夹,比如上例中,先建立com文件夹,再在com文件夹中建立itheima文件夹
3.将.class文件剪切放入com.itheima文件夹中
4.再控制台带包执行(因为可能会重名):java com.itheima.helloworld
🍒自动
1.编译的时候javac -d. helloworld.java即可自动建包
2.带包运行即可
🍍导包
格式:import cn.itcast.teacher;
🍉修饰符
🍍权限修饰符
public,protected,默认,private,访问权限逐渐递增
private:只能在本类中才能被访问
默认:同一个包中的子类,或者是无关类能被访问
protected:不同包的子类能被访问
public:不同包的无关类,子类都可以被访问
🍍final关键字
🍒修饰成员变量
1.被final修饰的方法叫最终方法,不能被子类重写(private的方法也不能被重写)
2.被final修饰的变量就成为了常量不能被再次赋值(但是被private修饰的变量可以通过getset重新赋值)
3.被final修饰的类不能作为父类被子类继承
🍒修饰局部变量
4.被final修饰的对象里面的值可以改变,但是地址不能改变,因为对象的本质是一个地址,所以对象里面的属性是可以改变的
🍍static关键字
一般用来修饰成员变量或函数
表示被所用对象共享,只要一个变量对其进行了赋值,其他变量都会随之改变,这样定义后,可以直接在别的类中用下列方法来声明
Student.university="传智大学"
🍒访问特点
1.static修饰的成员方法是静态的,只能访问静态的成员变量和成员方法
2.不用static修饰的成员方法是非静态的,可以访问静态,非静态的成员变量,方法
ps:因为main函数是静态的,所以定义方法的时候也要用static修饰才能被main方法调用
🍉多态
同一个对象的不同形态
🍍条件
1.有继承关系
2.有方法重写
3.有父类引用指向子类对象
Animal cat = new cat();
🍍访问特点
🍒访问成员变量时
运行看左边
用多态的形式访问成员变量其实访问的是父类中的成员变量
public class fu {
int age = 10;
}
public class zi extends fu {
int age = 20;
}
public class fuzi {
public static void main(String[] args) {
fu z = new zi();
System.out.println(z.age);
}
}
输出10
编译看左边
如果父类中没有name的话,报错
public class fu {
int age = 10;
}
public class zi extends fu {
int age = 20;
}
public class fuzi {
public static void main(String[] args) {
fu z = new zi();
System.out.println(z.name);
}
}
🍒访问成员方法时
编译看左边,运行看右边(子类中重写了方法的话会按照子类来)
🍍多态的利弊
🍒优点:提高了方法的扩展性
即使用父类型参数进行传参,而具体操作使用的子类型
eg:
我已经定义好了3个类:儿子,父亲,爷爷,三个类都继承于person类,并重写了person中的p1方法
再定义一个操作类和一个测试类
操作类:将子类型的参数传入,再调用子类的方法,需要将三个类对应的方法全部写出
public class demo {
public void print(fu f){
f.p1();
}
public void print(zi z){
z.p1();
}
public void print(yeye y){
y.p1();
}
}
测试类
public class 测试 {
public static void main(String[] args) {
demo d = new demo();
fu f = new fu();
zi z = new zi();
yeye y = new yeye();
d.print(f);
d.print(z);
d.print(y);
}
}
输出:我是儿子 我是父亲 我是爷爷
但是以这样的方式我每定义一个继承于同一个父类的子类都要在操作类中多加一个函数,为了简便可以用多态的方法编写操作类,只需要一个print函数即可
操作类:
public class demo {
public void print(person p){
p.p1();
}
}
测试类
public class 测试 {
public static void main(String[] args) {
demo d = new demo();
fu f = new fu();
zi z = new zi();
d.print(f);
d.print(z);
}
}
🍒弊端:不能访问子类特有的方法
编译看左边,运行看右边,如果父类中没有子类方法的话,编译就会报错
🍍多态的转型:解决多态的弊端
向上转型:
Alimal a = new cat();
即普通多态的定义就叫向上转型
向下转型:
向上转型无法使用子类中特有的方法,所以需要向下转型,将其转换为子类对象
cat c = (cat) a;
🍉抽象类
比如动物类就是抽象类,一个没有方法体的方法称为抽象方法,该类一定是一个抽象类,抽象类不是具体的事物,所以抽象类不能创建对象
🍍抽象类使用特点
🍒如何创建抽象类的对象
使用多态的方法:1.继承 2.重写 3.父类指向子类
抽象类:
public abstract class person {
public abstract void p1();
public void p2() {
System.out.println("我是地球人");
}
}
子类:
public class zi extends person {
@Override
public void p1() {
System.out.println("我是儿子");
}
}
测试类:
public class 测试 {
public static void main(String[] args) {
zi z =new zi();
z.p2();
}
}
注意:
1.要使一个非抽象类继承抽象类,必须要重写抽象类中的抽象方法,否则只能是一个抽象类继承一个抽象类
2.抽象类不能实例化,但是可以通过子类实例化
3.抽象类中不一定有抽象方法(不常见),但是由抽象方法的一定是抽象类
🍍抽象类的成员特点
1.抽象类中可以有构造方法
不是说抽象类不能实例化吗?
因为抽象类可以使用多态的方法进行实例化, 且因为子类会继承父类的所有方法和成员变量,所以初始化子类之前,系统会默认给父类也进行初始化,所以抽象类中的构造方法是为了初始化抽象类中的变量,常量之类的
2.抽象类的抽象方法的作用
使子类必须要重写,也就是限定子类必须要有某些动作
🍉接口
🍍定义及使用
定义接口
public interface 接口名{}
定义类并实现接口
public class 类名 interface 接口名{}
接口的实例化
还是通过多态的形式
1.先用implement实现接口
2.再对接口里的抽象方法进行重写
如果不重写的话+abstract(接口的实现类要么是实现接口内的所有抽象方法,要么是个抽象类)
3.向下转型
🍍接口的成员特点
1.接口内的成员变量默认是被final修饰的常量
ps:被静态修饰的变量可以用接口名.xxx或者是类名.xxx来表示
比如static int school;
2.接口内没有构造方法,因为接口是行为进行抽象
但是为什么实现接口的类中有父类的构造方法呢?
Object类
当一个类没有父类的时候,object类就是他的父类
所以上述的代码等价于
3.接口内成员方法只能是抽象方法
🍍例子
1.可以将一个抽象类用来实现一个接口(但是实际上不这么用,因为抽象类是对事物的抽象,接口是对行为的抽象),这个抽象类的实现类必须重写接口和这个抽象类中的所有抽象方法
2.具体实现中有这样的情况:
抽象类1:有一个eat()抽象方法
对其进行实例化
Animal a = new cat();
接口1:有一个jump()抽象方法
对其进行实例化
jumpinter j = new cat();
当我们想调用eat时就必须用对象a,但是使用a的话无法调用jump方法,同理调用对象j也只是能调用jump方法而无法调用eat方法。所以我们直接将实现类实例化即可
🍍类和接口的关系
类和类
可以多层继承,但是不能多继承
类和接口
可以多实现
接口和接口
接口和接口可以多继承
implements inter2,inter2,inter3
🍍抽象类和接口的区别
抽象类是对事物的抽象
比如一个门,有关闭和打开的功能
接口是对行为的抽象
比如一各门,有报警功能,就可以将报警功能作为一个接口
🍉形参和返回值
🍍类名和抽象类名作为形参或者返回值
类名作为形参
其实是该类的对象作为形参
类名作为返回值
返回值是该类的对象
抽象类名作为形参
抽象类无法实例化,所以需要创建一个该抽象类的子类对象传递进来(多态)
抽象类作为返回值
也需要多态的形式返回其子类的对象
🍍接口名作为形参或返回值
和抽象类一样,都是接口的实例化对象
🍉内部类
内部类可以直接访问外部类的所有成员(包括私有)
外部类要访问内部类则必须要创建对象
🍍成员内部类
内部类在类的成员位置
🍒public修饰的成员内部类
成员内部类:
如何创建对象
相当于先初始化outer再初始化inner
🍒private修饰的内部类
内部类的适用范围就是将此类隐藏起来,不让外界直接访问
外界无法访问inner,但是本类里面是可以的,所以类似于getset方法,我们为inner创建一个“get”方法,通过这个方法来简介调用y.eat();
public class fu {
private class yeye {
public void eat() {
System.out.println("爷爷其实什么也不吃");
}
}
public void method() {
yeye y = new yeye();
y.eat();
}
}
🥕疑问
为什么再外界申明了内部类的对象不能调内部类的方法
public class fu {
private class yeye {
public void eat() {
System.out.println("爷爷其实什么也不吃");
}
}
public yeye method() {
yeye y = new yeye();
y.eat();
return y;
}
}
🍍局部内部类
顾名思义就是定义在方法里的内部类
如何调用呢?
因为内部类已经在方法里面了,所以只要调用该方法即可
public void method(){
private class inner(){
public void eat(){
sout("吃东西");
}
}
inner i = new inner();
i.eat();
}
🍍匿名内部类
是局部内部类的特殊形式
用new关键字来修饰表示其实是一个匿名的对象(因为没有对象名)
本质:是一个继承了该类或者实现了该接口的子类匿名对象
Outer类:
匿名内部类如果是针对接口的话中必须重写该接口的全部抽象方法,针对抽象类的话也要
我们可以看出new Inter是一个Inter的实现类对象,所以可以用Inter i来接收 (多态)
如果Inter是一个类的话,则表示i是Inter的子类对象(多态)
Inter i = new Inter(){
public void show(){
sout();
}
}
Inner接口(也可以是类)
测试类:
🍒应用
如果我们要用三个类(cat,dog,pig)实现一个jumpinterface接口,就需要定义三个类
eg猫类:
public class cat implements jumpinterface{
public void jump(){
sout();
}
}
操作类
public void operate(jumpinterface j){
j.jump
}
测试类
操作类 demo = new 操作类();
Inter i = new cat();
demo.operate(i)
同理dog类,pig类都是这样,但是这样过于繁琐了,我们可以不用定义猫类和狗类,🐖类
使用匿名内部类
因为匿名内部类的本质就是一个实例化对象,所以传递参数的时候可以直接在形参里写匿名内部类
测试类:
操作类 c = new 操作类();
c.operate(new jumpinterface(){
public jump(){
sout("猫跳高");
}
});
c.operate(new jumpinterface(){
public jump(){
sout("狗跳高");
}
});
🍉一些常用的API
前面学的String,StringBuilder都是常用的API
🍍Math类
🍍System类
注意java还是和C语言一样整数➗整数只能为整数,所以要想其位小数的话要×1.0
🍒用currentTimeMills()求程序运行时间
currentTimeMills表示的是现在的时间到1970年之间的时间(以ms为单位)
🍍Object类
🍒toString方法言简意赅的输出对象
以后在写一个类的时候还是按alt+insert就可以添加一个toString方法(就是重写父类Object中的toString方法),然后在测试类中输出对象名就不会输出一串地址码,而是言简意赅的形式
@Override
public String toString() {
return "fu{" +
"age=" + age +
", name=" + name +
'}';
}
测试类:
fu f =new fu(1,2);
System.out.println(f);
输出:
fu{age=1, name=2}
否则输出:
com.itheima.fu@254989ff
🍒equals方法比较两个对象是否相同
还是在类中按alt+insert选择
equals and hashcode
然后下一步下一步下一步,然后删除hashcode部分代码即可得到
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass())
return false;
fu fu = (fu) o;
return age == fu.age && name == fu.name;
}
测试类:
public class 测试 {
public static void main(String[] args) {
fu f =new fu(1,2);
System.out.println(f);
fu f2 =new fu(2,2);
System.out.println(f.equals(f2));
}
}
🍍Arrays类
🍒toString方法
Arrays.toString(数组名)
输出默认为下面的格式
🍒sort方法
实现数组从小到大排序
🍍基本类型包装类
1.有了基本类型为什么还要有包装类型呢?
我们知道Java是一个面相对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型),它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。
另外,当需要往ArrayList,HashMap中放东西时,像int,double这种基本类型是放不进去的,因为容器都是装object的,这是就需要这些基本类型的包装器类了
方便各种基本类型和String类型的转换
2.为什么工具类都不用声明对象直接就可以调方法?
因为工具类的特点是:
🍒Integer基本类型包装类
如何得到Integer对象
使用valueof方法(valueof方法是静态方法,所以可以直接用类名.方法名调用)
Integer和int的互相转换
Integer ii = Integer.valueOf(100);
int i = ii.intValue();
🍒int和String之间的转换
🥕int----String
1.字符串直接相加
String s = "";
int a=100;
s+=a;
2.使用基本包装类
String s = String.valueOf(100);
🥕String----int
int y = Integer.parseInt("101010");
🍒基本类型包装排序
🥕字符串中数据排序
1.用split方法将字符串转换成字符串数组
可以将字符串中的数据以”xxx“的形式分开存进一个字符数组
String s = "91 27 46 38 50"
String [] s = s.split(" ");
2.用parseInt方法直接将String数组内的每一个元素转换成int数组里的元素
3.用arrays.sort(arr)对int型数组进行排序
4.因为涉及到字符串的拼接,所以用StringBuilder的add方法
5.将StringBuilder转换成String类型
🍒自动装箱和拆箱
自动装箱:
想将int类型赋值给Integer类型的话,完整的应该这样写
Integer i = Integer.valueOf(1000);
但是jdk5之后简化了,可以直接将int赋值给Integer,但是内部还是执行了上述过程,只不过我们看不到而已
Integer i = 100;
自动拆箱:
如果想要将Integer的值加上100的话,完整写的话应该是先要将Integer转换成int,即拆箱
Integer i = Integer.valueOf("100");
int j= i.intValue();
将int类型的值+100后再装箱转换成Integer
j+=100;
i=Integer.valueOf(j);
但是自动拆箱可以将其简化成:(包含两步,一步拆,一步装)
Integer i = Integer.valueOf("100");
i+=100;
注意:
🍍Date类
构造方法
Date d1 = new Date();
System.out.println(d1);
输出现在的日期时间,精确到毫秒
Tue Jul 19 19:25:18 GMT+08:00 2022
🍍 SimpleDateFormat类
🍒format方法(将date转化为String)
将日期字符串(data的构造方法)以默认的格式输出
默认输出:
如果不用无参构造方法而用带参构造方法(形参传入格式化字符串,试了只加一个y,一个M一个d也可以代表具体年月日)可以改变格式
public class 测试 {
public static void main(String[] args) {
Date d1 = new Date();
SimpleDateFormat s1 =
new SimpleDateFormat("y年M月d日");
System.out.println(s1.format(d1));
}
}
🍒parse方法(将String转化为Date)
注意M表示月,m表示分,且符号一定要相对应
public class 测试 {
public static void main(String[] args) throws ParseException {
String s = "2048年08月09日 11:11:11";
SimpleDateFormat s1 =
new SimpleDateFormat("y年M月d日 h:m:s");
Date d1 = s1.parse(s);
System.out.println(d1);
}
}
🍍日期工具类
1.传入data类,表示默认的日期格式
2.传入String类,表示日期的格式化格式,即要将date类型转换成String类型的何种形式
1.传入一个字符串,代表要转换成date的String类型
2.传入一个字符串,代表如何解析上一个字符串,注意符号一定要相符
🍍calendar类
没看
🍉异常
🍍error和异常
java中所有异常和错误的超类时Throwable类
Throwable类又可以划分为两个子类:error和异常
error:
error问题表示程序的比较严重的问题,合理的应用程序不应该试图捕获
🍍异常体系
🍍JVM对问题的默认处理方案
🍍异常处理
为什么要异常处理
因为异常出现的时候,默认会结束程序,但是在实际开发中不需要结束程序,处理了异常即可
类别
throws和try catch
🍒try catch处理异常
格式:
eg:
public class 测试 {
public static void main(String[] args) {
int [] arr= new int[3];
System.out.println(arr[3]);
System.out.println("结束");
}
}
当我们执行一段有异常的代码的时候,控制台出现了一下的异常,可以得到异常类:ArrayIndexOutOfBoundsException
然后使用try catch方法让程序出现了异常也能继续向下执行(catch里面的异常类直接用控制台里面显示的,然后再随便起一个对象名即可)
public static void main(String[] args) {
int [] arr= new int[3];
try{
/*System.out.println(arr[3]);
这一步相当于new了一个异常给java运行系统
eg:ArrayIndexOutOfBoundsException
然后java就会在下面的catch中找是否有这个异常出现
如果找到了的话就进入catch语句执行其中的语句
*/
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("数组访问越界");
}
System.out.println("结束");
}
🍒throwable类
throwable是所有异常和错误的祖宗类,所以所有异常类都可以使用throwable中的方法
1.getMessage方法:返回异常产生的原因
public class 测试 {
public static void main(String[] args) {
int [] arr= new int[3];
try{
System.out.println(arr[3]);
}catch (ArrayIndexOutOfBoundsException e){
System.out.println(e.getMessage());
}
System.out.println("结束");
}
}
Index 3 out of bounds for length 3
2.toString()抛回简短描述
java.lang.ArrayIndexOutOfBoundsException:
Index 3 out of bounds for length 3
3. e.printStackTrace();输出比较全面的异常信息
🍒编译时异常和运行时异常的区别
所有的RuntimeException称为运行时异常,其他成为编译时异常
运行时异常:
前面访问数组越界的异常代码是可以不写的,因为是运行时异常(可以把异常类名赋值下来到帮助文档里搜,看他的父类是runtime…还是非运行时异常)但是但是写了的话也行
编译时异常:
前面写String转date的时候parse会报错,这就是编译时异常
找到该异常类并用try catch处理:
public class 测试 {
public static void main(String[] args){
String s = "2022年7月20日 17:28";
SimpleDateFormat si =
new SimpleDateFormat("y年M月d日h:m");
Date d = new Date();
try{
d=si.parse(s);
}catch(ParseException p){
System.out.println("异常处理");
}
System.out.println(d);
}
}
这时控制台输出:
Wed Jul 20 17:28:00 GMT+08:00 2022
可见并没有异常产生,所以编译器提醒的时候不一定会有异常(比如上例的只要格式相对应就不会出现异常) 相当于告诉你:我可能有问题,你不要掉以轻心
🍒throws处理异常
为什么需要用throws+异常类名 处理异常
当一个方法内的权限不够时,就需要将异常抛出,以后谁调用这个方法谁来处理这个异常
eg:
方法内出现异常的时候将异常抛出
调用该方法的时候用try catch处理
🍒自定义异常
只要继承自Exception或者是RunTimeException的话就可以是异常类
重点:1.手动抛出异常类对象 2.调用异常方法的时候记得用try catch处理
定义一个异常类,方便后续抛出异常对象
public class 异常类 extends Exception{
public 异常类(){}
public 异常类(String message){
super(message);
//为什么要将一个字符串传递给super?
//因为父类为Throwable类,其中有一个detailMessage变量
//传递给该变量之后就可以用Throwable的成员方法方便查看该异常
//比如printStackTrace
}
}
定义一个异常抛出类,根据条件抛出上述的异常对象
public class 异常操作类 {
public void dafen(int grade) throws 异常类 {
//因为下面的异常不一定要抛出,所以这里相当于提醒程序“我可能会抛出异常”
if (grade < 0 || grade > 100) {
throw new 异常类("你给的分数有误,应该在0-100之间");
//抛出异常类对象,注意这里要手动抛出
} else {
System.out.println("打分正常");
}
}
}
测试类
public class 异常测试类 {
public static void main(String[] args) {
异常操作类 y = new 异常操作类();
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
//因为异常操作类将异常抛出,所以需要调用的时候进行处理
try {
y.dafen(a);
} catch (异常类 e) {
e.printStackTrace();
}
}
}
🥕throw和throws的区别
🍉集合的进阶
🍍集合体系
蓝色的表示接口,红色的表示实现类
🍍Collection集合
🍒概述
如何创建Collection对象?
使用多态的方法
Collection<> c = new ArrayList<>();
Collection是否重写了toString方法
sout(c)的话发现是这样的,所以重写了
🍒常用方法
注意:
1.Collect中没有Array中的add(int index , Object x)方法
2.Collect中也没有Array中的remove(int index)方法
🍒Collection集合的遍历方式
🥕Iterator的使用
因为Collection中没有ArrayList中的get(int index)方法,所以必须使用迭代器Iterator来遍历
Iterator<String> i = c.iterator();
1.Iterator是一个接口,需要通过集合的iterator()方法来得到,所以它是依赖于集合的
2.Iterator是一个接口,c.iterator()是这个接口的实现类,所以相当于通过多态的方式实现Iterator接口
🥕遍历所需要的两种方法
next方法:返回下一个元素
hasNext方法:判断遍历到的位置的是否还有元素
🥕遍历
while (i.hasNext()) {
String s = i.next();
System.out.println(s);
}
🍒List集合
🥕概述
再Collection基础上增加了两个功能
🥕常用方法
儿子ArrayList有,但是父亲Collection没有
🥕Listlterator(List集合特有的迭代器)
ListIterator继承自Iterator,所以有next和hasnext方法
Listlterator可以任何方向遍历元素,并且可以向集合添加元素(Iterator就不行,会有并发修改异常)
🥕子类特点
两种集合的用法大都是一样的,因为都继承自List类,只是查询速度或者是增删速度不一样,可以根据代码主要面向功能进行取舍
ArrayList
底层的数据结构是数组,查询快,增删慢
LinkedList
底层的数据结构是链表,查询慢,增删快
🥕LinkedList集合
🍇特有方法
可以对列表的第一个和最后一个元素进行增删查改
(链表的特点)
🍒哈希值
根据字符串或者数字算出来的int类型的数值
Object类中有一个hashCode方法可以求哈希值,以后因为所有类都继承了Object类,所以所有对象都可以调用hashCode方法
System.out.println(x.hashCode());
x可以是字符串,数字,对象,基本所有东西都有hash值(不知道我说的对不我猜的)
注意:
默认情况下,不同对象的hash值是不同的
输出:true
String s1 = "1";
String s2 = "1";
System.out.println(s1.hashCode()==s2.hashCode());
输出false:
zi z1 = new zi();
zi z2 = new zi();
System.out.println(z1.hashCode()==z2.hashCode());
但是通过alt+enter对hashcode方法的重写可以实现不同对象的hash值相同
🍒Set集合
🥕特点
1.和Collection一样没有索引,所以只能用迭代器和增强for遍历
2.不包含重复元素
3.遍历的时候不能保证顺序
🥕hashset集合
特点和Set一样,就多了一个底层的数据结构是hash表
🍇hash表是怎样保证元素唯一性的
比如给了几个字符串,每个字符串算出哈希值对16取余的值(因为数组长度默认为16),这个值就是要存储在数组的位置
存储元素的时候,先要根据算出来的存储位置将字符串放进该位置,这时会进行三步判断
1.先看该位置有没有其他元素,如果没有则存入,否则进入步骤2
2.看该位置的所有元素的哈希值是否有和要存入的元素哈希值相同的,如果有则进入步骤3,否则存入
3.比较字符串的值是否相同,相同的话则重复,否则存入
🍇使用hashset存储对象时记得重写equals和hashcode方法
不然当两个对象的内容相同时依然无法去重(因为对象是new出来的)
所以按alt+enter即可(前面写了)
🍇为什么不直接equals比较值是否相同,而先要计算hashCode?
答:hash算法是二进制算法,计算式本质是二进制,所以hash算法速度很快。如若hashCode不同则可直接存储不用equlas比较。所以先计算hashCode大大加快了存储速率
🥥LinkedHashSet集合
使用链表和哈希表为底层数据
可以实现去重,有序遍历,但是也只有迭代器一种遍历方法
🥕TreeSet集合
特点:
1.可以按照指定的顺序排序
2.没有带索引方法
3.不包含重复的元素
4.默认为自然排序,或者用比较器排序指定
具体是哪一种通过构造方法是无参(自然排序)还是带参(比较器)来决定来确定
🍇实现对一个字符串或者是数字的自然排序
不需要重写compareTo方法,直接遍历就能按序输出
数字从小到大
字符串比首字母
🍇自然排序Comparable实现对一个类的排序
要想对一个类使用Comparable排序的话,必须要该类 Implement Comparable<>() 接口,并重写其中的compareTo(该类的对象)方法
注意:
如果返回0,表示元素重复,不存储
如果返回正数,表示按照顺序存储
如果返回负数,表示按照逆序存储
先比年龄:
this在前面就是升序,在后面就是降序
先按年龄排序,年龄相同再按姓名首字母从小到大排序
public void compareTo(Student s){
int num = this.age-s.age;
if(num==0){
return this.name.compareTo(s.name);
//compare<>()中自己带有字符串的比较方法
//所以直接用即可
}
else return num;
}
🍇用比较器Comparator排序实现对一个类的排序
可见形参传入的是一个接口的实例化对象,所以可以用匿名内部类简便实现,重写comparator中的compare方法即可(不是comparable中的compareTo)
public class 测试 {
public static void main(String[] args) {
TreeSet<学生类> t = new TreeSet<学生类>(new Comparator<学生类>(){
public int compare(学生类 s1 , 学生类 s2){
int num=s1.getAge()-s2.getAge();
if(num==0){
return s1.getName().compareTo(s2.getName());
}
else return num;
}
});
学生类 s2 = new 学生类(1, "sadf", "2", "2");
学生类 s1 = new 学生类(1,"werw","1","1");
学生类 s3 = new 学生类(1,"xcvb","3","3");
t.add(s1);
t.add(s3);
t.add(s2);
for(学生类 x:t){
System.out.println(x.getAge()+" "+x.getName());
}
}
}
输出:
1 sadf
1 werw
1 xcvb
🍇总结一下两种的特点
1.comparable<>接口用于自然排序,需要在类里面重写其中的comparTo方法,this在前面就是升序,在后面就是降序)(comparable——comparTo)
2.comparator<>接口用于比较器排序,需要在TreeSet创建对象的时候传入comparator接口的实例化对象,实例化对象需要重写compare方法,形参传入两个需要排序的类的实例s1,s2,s1-s2就是升序,s2-s1就是降序(comparator——compare)
3.一定不要忘了在两个接口的后面+泛型
4.comparTo方法默认就是对数字和字母的自然排序,格式为Object s1.compareTo(Object s2),前面比后面大返回正数,小则负数,相等返回零
🥕如何使用Set集合添加不重复随机数
Random r = new Random();
int x = r.nexInt(20);
上述代码产生[0,19]以内的随机数给x
🍍并发修改异常
简言之就是Iterator不能改变集合元素个数,但是可以改变集合中元素的值以及集合的遍历,如果想要改变集合元素个数的话还是用for循环
🍍增强for循环
目的:简化数组和Collection集合的遍历
本质:Iterator迭代器,所以也不能改变集合个数
🍍三种集合遍历方法
定义集合
List<String> c = new ArrayList<String>();
c.add("1");
c.add("2");
c.add("3");
增强for:
for(String s2 : c){
System.out.println(s2);
}
普通for:(适用于有get(int index)方法的集合)
for(int j = 0 ; j<c.size();j++){
System.out.println(c.get(j));
}
迭代器:
Iterator<String> i = c.iterator();
while (i.hasNext()) {
String s = i.next();
System.out.println(s);
}
循环中不能用两次(含)的next()方法,否则会出现异常
解决方法:将next()取出的数据先传给一个新的对象,用的时候再想方法取出来。
🍍Map集合
🍒HashMap
🥕创建
创建方法和Collection一样都是需要多态来创建对象
下面即创建了一个键值都是String类型的集合
🥕基本功能
Map中没有迭代器,所以输出Map的话直接输出集合名即可
1.put
如果有键重复出现了的话,那么其对应的值就会把之前的值给替代掉,比如下面的王祖贤就没了
2.keySet获取所有键的集合,因为键的集合是不重复的所以用Set来接收(只能用Set不能用HashSet)
3.values获取所有值的集合,所以用Collection来接收(只能用Collection来接收,不能用ArrayList或List)
🥕两种遍历方法
思路1:先将所有的键用一个Set集合来存储,在遍历键的同时用get方法找到键对应的值即可
思路2:🥕直接返回Map对象(键值对)集合
Map.entrySet方法可以返回一个Map键值对的集合,用Set集合接收:(Set里面的元素是键值对,所以应该是Map.Entry<Object,Object>)
接收到了之后,Map.Entry<Object,Object>对象可以调用getKey和getValue方法获取键值,然后用增强for遍历即可
(注意增强for遍历的是Set集合s而不是Map集合m)
🥕例子:HashMap存储Stu为值的元素
public class 测试 {
public static void main(String[] args) {
Map<Integer, 学生类> m = new HashMap<Integer, 学生类>();
学生类 s1 = new 学生类("zwy", 19, 100);
学生类 s2 = new 学生类("jjy", 22, 110);
学生类 s3 = new 学生类("yh", 20, 90);
m.put(1, s1);
m.put(2, s2);
m.put(3, s3);
Set<Map.Entry<Integer, 学生类>> s = m.entrySet();
for (Map.Entry<Integer, 学生类> ss : s) {
System.out.println(ss.getValue().getName()+":"+ss.getValue().getGrade());
}
}
}
🥕例子:存储以Stu为键的元素
其他都一样但是记得一点:如果不重写Stu里面的Hashcode和equals方法的话,Map集合中可能会出现键的重复,所以如果有以对象名的方式判断对象的内容是否相等的时候需要重写hashcode和equals方法
🍒集合嵌套
🥕ArrayList集合存储HashMap集合
🥕HashMap存储ArrayList
🍒统计字符串中每个字符出现的个数
public class 测试 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
Map<Character, Integer> m = new HashMap<Character, Integer>();
for (int i = 0; i < s.length(); i++) {
char a = s.charAt(i);
//如果没有该字母就加进去
if (m.get(a) == null) {
m.put(a, 1);
} else {//如果有的话就将键对应的值取出来+1
int x = m.get(a);
x += 1;
m.put(a, x);
}
}
System.out.println(m);
}
}
输出是无序的:
{a=3, s=4, d=4, f=3, g=1}
如果要进行有序排序的话就用TreeMap即可对键进行排序(和TreeSet类似,无参就是自然排序)
输出是有序的:
{a=2, d=4, e=1, f=3, g=1, q=1, s=4, w=1}
🍍面向集合的工具类Collections
对Collection集合有三种工具方法:sort排序,reverse反转,shuffle随即置换(每次运行都会随机对集合内的元素进行随机排序,每次都不一样 )
🍒用ArrayList实现对Stu的排序
之前学了能对类进行排序的只有TreeSet集合,但是有了Collections之后,别的Collecitions也能对类进行排序
🥕比较器排序
sort(array,Comparator c)
重写Comparator中的compare方法就可以了
🥕自然排序
重写compareTO方法
🍒模拟斗地主
🥕装牌
这时顺序的,想要实现随机装牌的话还要调用
🥕洗牌
Collections.shuffle(array);
🥕发牌
创建四个集合,其中三个集合为玩家手牌集合,另一个是底牌集合,使用对n取余可以实现为n个人发牌>=array.size()-3
指的是最后三张牌底牌
🥕看牌
🍒斗地主升级版(对拿到的牌进行排序)
1.使用HashMap集合存储牌,键为1-54,值对应具体哪张牌
2.使用ArrayList集合存储牌的编号(键),方便洗牌
3.用TreeSet集合存储值,方便对牌进行排序
🥕装牌
🥕洗牌
🥕发牌
定义三个TreeSet集合进行接收,接收之后自动将编号变成有序
🥕看牌
先从TreeSet集合中找到键,再对应在HashMap集合中找到值(写了个方法方便点)
🍉程序的复用性
🍍泛型
如果想要在一个集合中存储不同类型的元素
public class 测试 {
public static void main(String[] args) {
List c =new ArrayList();
//默认为Object类
c.add("123");
c.add(123);
c.add(123.123);
for(int i=0;i<c.size();i++){
System.out.println(c.get(i));
}
}
}
🍒泛型类
要使不同类型的元素使用同一个类,就可以使用泛型类
public class 学生类<T> {
private T yuwen;
private T shuxue;
//然后生成构造方法和getset方法即可
学生类<String> s1 = new 学生类<String>("1", "1");
System.out.println(s1.getYuwen());
学生类<Integer> s2 = new 学生类<Integer>(1, 1);
System.out.println(s2.getYuwen());
🍒泛型方法
🍒泛型接口
类和接口的泛型都写在类名/就扣名后面,泛型方法的泛型写在public后面
接口:
实现类:
记得实现接口的类也是一个泛型类
操作类:
🍍可变参数(参数个数可变)
传递形参的时候用…a来传递,其中a实质上是一个数组,所以遍历的时候就可以用增强for来遍历
调用:
sout (sum.(10,20,30,40,50));
注意:如果参数里面包含了可变参数的话,应该将可变参数放在后面
🍒可变参数的使用
了解一下吧
🍉IO流
🍍File类
File是文件路径名的抽象表示
🍒常用构造方法
三个输出都是一样的,都是文件的路径名
🍒File类的创建方法
1.createNewFile(File f)
如果文件不存在就创建该文件并返回true,如果文件存在的话就返回false(有异常直接抛出即可)
2.mkdir(File f)
创建的是目录(单级目录,比如下例中的JavaSE),和上一个方法类似
3.mkdirs(File f)
创建多级目录
注意:不能根据file的名称来判断创建的是文件还是目录,要看具体掉的哪个方法,比如下面创建了名为javase.txt的目录
🍒File的判断和获取功能
说一下最后两个方法:
第一个方法返回的是一个该目录下所有的文件,目录的字符串数组
第二个方法放回的是该目录下的所有file类对象的数组,可以用来输出该目录下所有文件名,或者是目录名之类的
🍒FIle类删除功能
相对路径和绝对路径
1.myFile相对路径:即再当前模块目录下创建java.txt文件(左边)
删除文件
删除目录的时候要保证目录下面没有内容才能被删除
🥕递归获取该目录下的所有文件名
public class 测试 {
public static void main(String[] args) throws IOException {
File f = new File("C:\\WeGameApps");
getall(f);
}
public static void getall(File f) {
File[] f1 = f.listFiles();
if (f1 != null) {
for (File f2 : f1) {
if (f2.isDirectory()) {
getall(f2);
}
if (f2.isFile()) {
System.out.println(f2.getName());
}
}
}
}
}
🍍字节流
输出流用于将数据写入,输入流用于将数据写出
🍒IO流的分类
按照数据类型类分类
1.字节流
2.字符流
使用上的区别
🍒字节流写数据
字节流抽象类
InputStream和OutputStream这两个类是表示字节输出流的所有类的超类
OutputStream的子类FileOutputStream用于将数据写入File
注意:创建文件输出流对象的同时也创建好了文件
1.创建字符串输出流
2.想File输入数据(字节类型)
打开该txt就是a97
3.最后释放资源
🥕写数据的三种方法
第一个方法传入的形参是一个字节,一次性只能写入一个字节的数据,前面已经讲过了,第二个方法一次性可以写入一个字节数组,如果我们要写入一个字符串的话,字符串类型自身有一个方法可以返回字符串对应的字节数组
String s;
s.getBytes();
所以如果要写入一个String类型的数组的话可以这样
byte[] b = "zwy".getBytes();
file.write(b);
🥕字节流写数据如何换行
加换行符即可:
“\n”(linux)
“\r”(mac)
“\r\n”(window)
🥕字节流写数据如何实现追加写入
创建对象的时候传入第二个参数true,这样写入数据的时候就是在末尾追加,而不是在开头插入
🥕字节流写数据异常处理
异常处理中的finally
public class 测试 {
public static void main(String[] args){
FileOutputStream f = null;
try {
f=new FileOutputStream("C:\\Users\\mangder\\zwy.txt");
f.write("zwy".getBytes());
} catch (IOException i) {
i.printStackTrace();
}
finally {
if(f!=null){
try{
f.close();
}catch(IOException i){
i.printStackTrace();
}
}
}
}
}
🍒字节流读数据
🥕一个一个读
read()方法一次性只能读出一个数据(字节),如果要转化成字符的话加一个强制类型转换即可
使用循环读取
如果文件中没有数据了就返回-1
注意输出的时候不加println中的ln(不换行)
public class 测试 {
public static void main(String[] args) throws IOException {
FileInputStream f = new FileInputStream("");
int x;
while((x=f.read())!=-1){
System.out.println((char)x);
}
f.close();
}
}
🍇复制文本文件
public class 测试 {
public static void main(String[] args) throws IOException {
FileInputStream f1 = new FileInputStream("C:\\Users\\mangder\\Pictures\\Saved Pictures\\QQ图片20220215091318.jpg");
FileOutputStream f2 = new FileOutputStream("C:\\Users\\mangder\\pic.jpg");
int x;
while((x=f1.read())!=-1){
f2.write(x);
}
f1.close();
f2.close();
}
}
🥕一次读一个字节数组
String类中的方法String(byte数组)可以将字节数组转换为对应的字符串
比如有一个txt文件里面是
hello
world
第一次读取的时候输出:
5
hello
第二次读取数据的时候输出
5
wor
因为再文件中helloworld中间有一个换行符\n\r所以读取进来的换行符要算两个
第三次读取的时候输出
2
ld
因为返回值是这次读取实际上读取到的数据,而不是数组的长度
注意:换行符在统计字符总数的时候记得算
eg:简便写法(一个中文字符占三个字节)
当读取不到数据的时候还是返回-1
🍇复制文本文件
🍒字节缓冲流
优点:可以加快文件的读写速度
构造方法:
在里面传入一个FileInpute或者是FileOutPut对象
BufferedInputStream b1 =
new BufferedInputStream(new FileInputStream(""));
BufferedOutputStream b2 =
new BufferedOutputStream(new FileOutputStream(""));
复制文件并计算时间
发现时间是远远小于字节流的
字节流:一个一个字节的读取需要30000ms,一次读取一个字节数组的话,数组越大,时间越少,比如数组大小为1000时时间只需要52ms)
缓冲字节流:一个字节一个字节的读需要134ms,数组的话需要21ms
long x = System.currentTimeMillis();
BufferedInputStream b1 = new BufferedInputStream(new FileInputStream("C:\\Users\\mangder\\Pictures\\Saved Pictures\\QQ图片20220215092521.png"));
BufferedOutputStream b2 = new BufferedOutputStream(new FileOutputStream("C:\\Users\\mangder\\picture.png"));
int bb1;
while ((bb1 = b1.read()) != -1) {
b2.write(bb1);
}
long y = System.currentTimeMillis();
System.out.println(y - x);
🍉字符流
🍒为什么出现字符流
🍒编码表
🍒字符串中的编码解码问题
如果指定字符集默认为UTF-8
编码: 前面说过就是getbyte方法,传入参数可以传入编码格式
解码: 使用String的构造方法传入byte数组转换成字符串
String ss = new String(byte数组,"UTF-8");
🍒字符流中的编码解码问题
和字符缓冲流一样,参数都要传入一个FileInputStream或者是outputStream
OutputStreamWriter将字符转换为字节,常用于输入字符(记得加flush刷新才能写进去,但是为了防止以往,执行close之前会有执行一次flush(),所以flush可以省略)
OutputStreamWriter f2 =
new OutputStreamWriter(new FileOutputStream("",true));
f2.write("牛逼123");
f2.flush();
f2.close();
InputStreamWriter将字节转换为字符,常用于读出字符
InputStreamReader f1 =
new InputStreamReader(new FileInputStream(""));
int flag;
while((flag=f1.read())!=-1){
System.out.print((char) flag);
}
🍒字符流写数据的5种方式
🍒读数据的两种方式
🍒字符流简洁版
可以用FileReader和FileWriter来代替OutputStreamWriter和InputStreamReader(而且还不用匿名内部类)
🍒字符缓冲流(BufferedReader/BufferedWriter)
🥕字符缓冲流的特有功能
1.行分隔符,主要解决不同的系统换行符不一样的问题
2.读一行的数据(不包括任何行终止符,如果没了就返回null)
可以用来遍历数据
也可以用来复制
🍍总结
字符流只能复制文本类型的数据
🍍集合和文件的相互写入
🍒集合到文件
(记得+flush)
🍒文件到集合
🍍点名器
🍍复制单级文件夹
详细代码:
public class 测试 {
public static void main(String[] args) throws IOException {
//第一个File目的是得到文件夹的名字
File f1 = new File("C:\\Users\\mangder\\picpic");
String s1 = f1.getName();
//第二个File目的是判断目的复制目录下有没有该文件夹,没有的话就创建
File f2 = new File("C:\\Users\\mangder\\Videos","picpic");
if(!f2.exists()){
f2.mkdir();
}
//创建File数组存储源文件的所有对象(为什么不用String数组?)
//因为用String数组不方便将地址拼接起来传入方法
File [] ff = f1.listFiles();
for(File f3:ff){
File yuan = new File("f3");
File mudi = new File(f2,f3.getName());
//定义复制方法
copy(yuan ,mudi);
}
}
public static void copy(File yuan,File mudi) throws IOException {
//new FileOutputStream(可以传入File也可以传入String)
BufferedOutputStream b1 = new BufferedOutputStream(new FileOutputStream(mudi));
BufferedInputStream b2 =new BufferedInputStream(new FileInputStream(yuan));
byte [] b = new byte[1024];
int len;
while((len=b2.read(b))!=-1){
b1.write(b,0,len);
b1.flush();
}
b1.close();
b2.close();
}
}
🍍复制多级文件夹(报错)
自己写的没按照网壳写,报错:拒绝访问
public class 测试 {
public static void main(String[] args) throws IOException {
File yuan = new File("C:\\Users\\mangder\\picpic");
File mudi = new File("C:\\Users\\mangder\\picpic2");
copy(yuan,mudi);
}
public static void copy(File yuan,File mudi) throws IOException {
BufferedOutputStream b2 = new BufferedOutputStream(new FileOutputStream(mudi));
BufferedInputStream b1 = new BufferedInputStream(new FileInputStream(yuan));
byte[] b = new byte[1024];
int len;
File [] ff = yuan.listFiles();
if(ff!=null){
for (File f3 : ff) {
if (f3.isFile()) {
while ((len = b1.read(b)) != 0) {
b2.write(b, 0, len);
}
} else {
File exitt = new File(mudi, f3.getName());
if (!exitt.exists()) {
exitt.mkdir();
}
copy(f3, exitt);
}
}
}
}
}
🍍特殊操作流
🍒标准输入输出流
🥕标准输入流
即从键盘录入数据
🍒标准输出流
sout
🍒对象序列化流
也就是写入文件,但是从文件中看是一串乱码只有一部分看得懂,这时候就需要反序列化将乱码读取出来
🍍对象反序列化
读出来是一个对象,还需要向下转型成学生类
🍍Properties
🍒作为集合使用
没有泛型方法,所以不能用泛型来定义
🥕特有方法
🍒Properties和IO流结合的方法
1.load:将数据从文件写入集合
2.store:将数据从集合写入文件
🍒要想每次输入的数据时,之前输入的数据不被清零的话记得在形参后面加true
🍒如果一个文件数据有变化的时候,尽量新建一个流来读取,如果接着用以前的流的话会出现文件里面有值,但是输出却是null
🍒参数后面有true的,初始话会保存文件内容,没有true的初始化会清空文件内容
🍉多线程
🍍Thread实现多线程
线程:进程的执行路径
1.定义一个类MyThread继承Thread类
2.重写Thread类中的run方法
3.创建MyThread对象
4.启动线程(不能用对象名.方法)
🍍设置获取线程名称
🍒设置名称
线程名称默认为Thread-0开始一直到1,2,3…
1.带参构造方法(alt+enter)
2.setName方法
🍒获取名称
1.使用继承自Thread类中的getName()方法(在主函数中不能直接使用getName方法,因为只有线程才能调用)
2.在主函数中获取名称,使用currentThread方法获得现在正在执行的线程(不能对象名.getName()获得,因为线程的执行具有随机性)
🍍线程优先级
🍍两种线程的调度模型
java用的是抢占式的调度模型,所以执行具有随机性
🍒获取线程优先级
🍒设置线程优先级(1——10)
🍍线程控制
🍒sleep方法(阻塞)
效果是每隔1s输出3个人(下面的alt enter捕获异常即可)
🍒join方法
tj1执行完了之后才执行添加tj2和tj3
🍒setDaemon方法
设置守护线程,当主线程(当Java程序启动时,一个线程立刻运行,该线程通常叫做程序的主线程,所以在线程执行之前用currentThread获取该线程就是主线程)结束的时候,守护线程应该很快也结束
🍍线程的生命周期
🍍Runnable实现多线程
1.实现Runnable接口
1.测试类
实现Runnable接口
Thread t1 = new Thread(my,name:"");
Thread t2 = new Thread(my,name:"");
继承Thread类
Thread t1 = new Thread(name:"");
Thread t2 = new Thread(name:"");
🍒Runnable相比于Thread的好处
1.因为接口可以多继承,但是类只能单继承,所以方便实现类继承别的类
2.创建多线程的时候,是吧myRunnab的同一个对象作为参数传递的,可以看作是同一个资源由多个线程使用
🍍同步线程
在满足以下条件的情况下可能会出现安全问题
1.多个线程共享数据
解决方案就是同步代码块:当一个线程进入时,可以将该代码块锁起来,保证任意时刻只能有一个线程执行即可
1.实现Runnable接口
public class 线程类 implements Runnable {
int ticket = 100000;
Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");
ticket--;
}
}
}
}
2.测试类
public class 线程测试类 {
public static void main(String[] args) {
线程类 ss = new 线程类();
Thread t1 = new Thread(ss,"第一个窗口");
Thread t2 = new Thread(ss,"第二个窗口");
Thread t3 = new Thread(ss,"第三个窗口");
t1.start();
t2.start();
t3.start();
}
}
🍍同步方法
注意:
1.好像synchronized在给代码块和方法加锁的时候都可以用this为参数,就不用再前面定义一个Object了(因为Object代表任意对象当然兼容this或者是其他的类型。。。)
2.记得在方法那里加上synchronized
3.同步静态方法的锁对象是类名.class
package com.itheima;
public class 线程类 implements Runnable {
int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (this){
if (ticket > 0) {
sell();
}
}
}
}
private synchronized void sell() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");
ticket--;
}
}
🍍线程安全类
也就是类里面的方法被synchronized修饰
后两种不常用,因为由将线程不安全集合转换为线程安全类的方法
🍍Lock锁
之所以用try和finally是因为要如果catch里面的代码出问题了导致锁不能释放的话会导致程序不会继续运行,所以加一个finally来保证锁一定被释放
lock on lock off
🍍生产者消费者案例
box类:在测试类中new一个box对象而不是在每个生产者消费者内new,因为会被初始化
public class box {
private int summilk=0;
private int getmilk=1;
boolean have = false;
public synchronized void put(int milk){
this.summilk=milk;
System.out.println("将第"+this.summilk+"瓶奶放入奶箱");
if(summilk>=getmilk){
have=true;
}
}
public synchronized void get(){
if(getmilk>summilk){
have=false;
}
if(have==true){
System.out.println("用户拿到了第"+this.getmilk+"瓶奶");
this.getmilk+=1;
}
}
}
测试类:
public class boxdemo {
public static void main(String[] args) {
box b = new box();
producer p = new producer(b);
customer c = new customer(b);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
t2.start();
t1.start();
}
}
生产者:
public class producer implements Runnable{
private box b;
public producer(box b){
this.b=b;
}
@Override
public void run() {
for(int i=1;i<=10000;i++){
b.put(i);
}
}
}
消费者:
public class customer implements Runnable{
private box b;
public customer(box b){
this.b=b;
}
@Override
public void run() {
while(true){
b.get();
}
}
}
🍉网络编程
🍍三要素
1.ip地址
给每台计算机的标识号,通过这个标识号来指定要接受的数据的计算机和识别要发送的计算机
2.端口
应用程序的标识
3.协议
🍒ip地址
🍒InetAddress类实现对ip地址的获取和操作
传入的形参可以是ip地址也可以是主机名,传入的主机名的话getAddress就是输出对应ip地址,传入的ip地址getName就输出对应的主机名
🍍端口和协议
🍒二者特点
1.UDP发送时用的是字节数组的形式发送,send里面接的是字节数组,TCP发送的时候需要获取该套接字对应的输入流s.getOutputStream(),输出端获取时用s.getInputStream()
2.UDP接收的时候用的是getData()和getData来返回字节数组的值和大小,TCP用的是先将客户端转换成Socket再调用其中的io流方法来遍历字节数组
3.UDP发送和的时候需要两个类一个DatagramSocket负责发送和接收,一个DatagramPacket 负责传递各种参数和调用 getData和getData方法。TCP发送的时候接收方使用ServerSocket接收,发送发使用Socket 类发送
4.发送方是DatagramPacket时 要传递4个参数(数组名,数组长度,IP地址,端口号),Socket只需要两个(IP地址,端口号),接收方DatagramPacket和ServerSocket都是一个参数(端口号)
🍒UDP
send(传入端口号用InetAddress.getByName(“10.13.130.164”))
public class send {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
DatagramSocket d = new DatagramSocket();
String s=null;
while(!((s=sc.nextLine()).equals("886"))){
byte [] b = new byte[1024];
DatagramPacket d2 = new DatagramPacket(b,b.length, InetAddress.getByName("10.13.130.164"),1900);
d.send(d2);
}
d.close();
}
}
receive
public class receive {
public static void main(String[] args) throws IOException {
while(true){
DatagramSocket d = new DatagramSocket(1900);
byte [] b = new byte[1024];
DatagramPacket d2 = new DatagramPacket(b,b.length);
d.receive(d2);
System.out.println("接收到的数据是:"+new String(d2.getData(),0,d2.getLength()));
}
}
}
🍒TCP
🥕发送数据
1.这里的传递ip地址就可以不用IneAddress了,直接传递ip地址系统会默认给你转换
2.TCP发送端会提供字节流来帮助写入和读出,但是接收端没有
public class send {
public static void main(String[] args) throws IOException {
Socket s = new Socket("192.168.137.1",10000);
OutputStream os = s.getOutputStream();
os.write("TCP".getBytes());
s.close();
}
}
🥕接收数据
先将接收端的ServerSocket 类转换成Socket 才能调用字节流方法,然后按照遍历字节流的方式输出信息即可
public class receive {
public static void main(String[] args) throws IOException {
ServerSocket s = new ServerSocket(10000);
Socket s2 = s.accept();
InputStream i = s2.getInputStream();
byte [] b = new byte[1024];
int len;
while((len=i.read(b))!=-1){
System.out.println(new String(b,0,len));
}
s.close();
}
}
🥕练习1:将获取的输出流转换成BufferedReader和BufferedWriter
🥕练习2:服务器数据写入文本文件
客户端:
服务器:
🥕练习3:客户端数据来自于文本文件
客户端:
服务端:
🥕练习4: 接收服务器反馈
客户机写完之后才读数据,服务器读完之后再写数据,所以反馈信息和要传输的数据不会混淆
客户端:
服务器:
🥕练习5:从文件中取内容上传给服务器,服务器将数据写入文件并给出反馈
服务器:不知道客户端写完了没有,所以这一步服务器一直在阻塞状态,一直在等客户端发来数据
line=br.readline()是先执行右边,后执行赋值,而readline的对象的根源是accept,是阻塞的,如果没有新内容就一直等,根本不会执行到赋值那一步
所以之前停止while的原理是客户端socket关闭了,导致line为空了才退出的,并不是直接因为文件读完了赋值为null的
在客户端加一个标志写入结束了的方法即可
服务器:
🥕多线程实现文件上传
🍉Lambda表达式
🍍Lambda表达式标准格式
🍒练习1
要实现一个接口,如果不使用匿名内部类,则需要有另外一个类来实现该该接口,再用多态…
🍍注意事项
1.Lambda表达式只适用于接口中有且只有一个抽象方法的情况
2.必须有上下文环境 ,单独使用是没有意义的(大多是代表的是该接口的实现类对象)
3.不能用于抽象类
🍍和匿名内部类的区别
🍉接口组成更新
🍍默认方法
有时候接口中的方法都需要重写的话会比较麻烦,比如要在一个接口新增一些方法的话,那么每一个接口的实现类都需要重写该方法,但是如果使用默认方法的话就不强制(但是也可以在实现类中重写)重写了
🍍静态方法
只能被接口(名)调用,因为如果有一个实现类实现了两个接口的话,再用实现类对象去调用的话就不知道调那个方法了(public可以省略)
🍍私有方法
适用情况:当接口里面的方法有相同的部分时,为了简化代码,可以将相同的部分提取出来放在私有方法里面
(注意:静态方法只能调静态方法,而且非静态方法也能调用静态方法,所以将私有方法设置为静态会方便很多)
🍉方法引用
方法引用可以认为是Lambda表达式的一种特殊形式,Lambda表达式可以让开发者自定义抽象方法的实现代码,方法引用则可以让开发者直接引用已存在的实现方法,作为Lambda表达式的Lambda体(参数列表得一致)
🍍引用类方法(引用类的静态方法)
方法引用和Lambda方法是等效的,Lambda的所有形式参数都默认传递给了引用类方法的形式参数
🍍引用对象的实例方法(引用对象中的成员方法)
当Lambda表达式被对象的实例化方法替代的时候,Lambda表达式的所有形参都会被传递给该对象的实例化方法
🍍引用类的实例方法(引用类的成员方法)
第一个参数作为调用者(调用该类的成员方法),后面的参数全部作为形参传递给方法调用
🍍引用构造器(引用构造方法)
🍉函数式接口
有且只有一个抽象方法的接口
java提供了FunctionalInterface注解(建议加上)来验证是否为一个函数式接口
🍍函数式接口作为方法的参数
1.可以用匿名内部类作为参数的传递
2.可以用Lambda表达式作为参数的传递
🍍作为方法的返回值
前面的Collections类中,对ArrayList排序的方法是:
sort(array,Comparator c)
1.匿名内部类作为结果返回:
2.用Lambda表达式作为结果返回
🍍常用的函数式接口
🍒Supplier< T >接口(按照一定逻辑产生某个类型的数据)
🥕获取最大值
🍒Consumer接口(按照某种逻辑消费一个数据)
🥕accept方法
对给定的参数执行给定的操作
对“林青霞”进行Lambda表达式:sout操作
🥕操作1.andThen(操作2).accpet(需要进行操作的形参)
🥕练习(从字符串中提取并打印信息)
printInfo方法中的形参中,字符数组strArray表示该方法要对这个字符数组进行操作,后面的for循环遍历accept(str)表示每一步需要对str这个字符串进行操作
🍒Predicate接口(判断给定的参数是否符合某种要求并返回布尔值)
🥕test方法:对给定的参数进行判断并返回一个布尔值
可以在返回值那儿加一个!表示输出的布尔值与判断相反
🥕and,or方法
or方法同理
🥕练习:筛选满足条件的数据
🍒Function<T,R>接口(接受一个参数并通过一定的逻辑得到一个指定类型的结果)
T表示输入的类型,R表示函数结果的类型
🥕apply方法:将函数应用于给定的参数
🥕andThen方法
同理第三个方法可以改成这样
🥕例子
🍉Stream流
例子:将名字是“张”开头的存储到一个新集合,再将名字的长度为3的存储到另一个新集合
这样子写的代码十分冗余,使用Stream流用更精简的方式来过滤
🍍使用步骤
🍍Stream流的生成方式
🍒Collections体系集合生成流
直接调用默认方法default Stream< E > stream();
🍒Map生成流
map集合不能直接生成流但是可以间接生成流,比如调用keySet和values方法生成键,值的集合(还有entrySet生成Map.Entry<类型1,类型2>的集合),然后再用该集合生成流
🍒数组生成流
of(T…values)方法
🍍中间操作
🍒filter方法
🥕例1
stream中的方法(用Lambda重写),能对流中的数据进行过滤
先创建流,再对数据进行判断如果这个数据是以“张”开头的话,就终结操作并输出
简化版:
🥕例2
fileter的返回值也是一个Stream流,所以可以层级调用
🍒limit方法
返回前几个数据
🍒skip方法
跳过前几个元素输出
🍒concat方法(静态的)
可以再后面加一个distinct()去重
🍒sort自然排序和比较器排序
🍒map和mapToInt方法
sum是mapToint里面的特有的方法可以统机流里面的元素个数,mapToint和map能是想相同的功能都是传入Funtion接口
🍍终结操作
🍒forEach和count方法
🍍综合案例
🍍收集操作
collectors方法能返回collector接口
1.收集到List集合
2.收集到Set集合
3.转换成map集合
🍉反射
🍍获取Class对象
1.使用类的class属性来获取
输出的就是类的位置
2.调用对象的getClass方法
3.使用class类中的静态方法获取
参数传递的是类在包中的地址
🍍反射获取构造方法并使用
class对象的getConstructors方法
返回值是一个Constructor对象数组,相当于把一个类中的成员变量,方法等看成了一个对象,Contructor对象反映了由该Class对象表示的类的所有公共构造函数
即数组中有两个构造函数,一个带参的一个无参的(公共的)
class对象的getDeclaredConstructors方法
可以得到所有的构造方法(Collections对象数组)
class中的getConstructor方法获取单个对象(公共)
如果想要取到代餐的话,就是c.getConstructor(String.class,int.class);
class中的getDeclaredConsructor方法
和上面的类似
Constructor的newInstance方法
🍒如何使用私有构造方法
使用暴力反射
🍍反射获取成员变量并使用
获取class对象——获取成员变量对象addressField(用Field对象接收)——赋值(给类对象obj中的成员变量对象addressField赋值)
🍒处理私有的成员变量
同理也要暴力的取消访问检查