(涉及到图片的资源因为在电脑本地,挨个挨个找太浪费时间就不找了)
**字节:**每逢8位是一个字节,这是数据存储的最小单位。
计算机中的数据转换:
1 Byte = 8 bit
1 KB = 1024 Byte
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB
1 PB = 1024 TB
1 EB = 1024 PB
1 ZB = 1024 EB
MS-DOS(Microsoft Disk Operationg System)磁盘操作系统
磁盘切换[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vrQB31CZ-1636071520894)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210102202035418.png)]
进入磁盘下面的文件夹:cd + 文件夹的名称
返回该文件夹上一级:cd + … {cd + \}
显示当前文件夹下面的文件:dir
清空DOS界面: cls
退出: exit
是运行所有java程序的假象计算机,是java的运行环境,具有跨平台性
是java程序运行时环境,包含JVM和运行时所需要的核心类库
是java程序开发工具包,包含JRE和开发人员使用的工具
命名规则(软性要求):
数据类型 | 关键字 | 内存占用 | 取值范围 |
---|---|---|---|
字节型 | byte | 1个字节 | -128~127 |
短整型 | short | 2个字节 | -32768~32767 |
整型 | int(默认) | 4个字节 | -231~231-1 |
长整型 | long | 8个字节 | -263~263-1 |
单精度浮点数 | float | 4个字节 | 1.4E-45 ~3.4028E+38 |
双精度浮点数 | double(默认) | 8个字节 | 4.9E-324~1.797E+308 |
字符型 | char | 2个字节 | 0-65535 |
布尔类型 | boolean | 1个字节 | true false |
注意定义long类型的数值的时候需要在后面加一个L,float类型的数值赋值也需要在后面加一个F,不然默认的是double类型
//例如
long num = 30000000L;
float num1 = 2.5F;
格式:范围小的类型 范围小的变量名 = (范围小的类型)原本范围大的数据;
注意事项:
强制类型转换类型一般不推荐使用,因为有可能发生精度损失、数据溢出。
byte/short/char这三种类型都可以发生数学运算,例如加法“+”。
byte/short/char这三种类型在运算的时候,都会被首先提升成为int类型,然后再计算。
//比如byte+byte
byte num1 = 40;
byte num2 = 50;
byte result = num1 + num2;//此时这一行会报错,因为byte和byte的相加会提升为int和int相加,最后返回的一个结果就是int,正确的写法如下
int result = num1 + num2;//当然也可以用byte将结果强制类型转换
boolean类型不能发生数据类型转换
运算:
JShell是JDK9新出的轻量级编译,相当于脚本一句一句进行编译,使用方法也非常简单,直接在dos窗口中输入jshell,就可以进入,注意退出的时候使用【/exit】进行退出,这是固定的退出格式。
常量优化
/*
在给变量进行赋值的时候,如果右侧的表达式当中全部都是常量,没有任何变量,
那么编译器javac将会直接将若干个常量表达式计算得到结果。
short result = 5+8; //等号右边全都是常量,没有任何变量参与运算
编译之后,得到的.class字节码文件当中相当于【直接就是】:short result = 13;
这称为“编译器的常量优化”
但是注意:一旦表达式当中有变量参与,那么就不能进行这种优化了。
*/
Public class DemoNotice{
public static void main(String[] args){
short a = 5;
short b = 8;
short result = a + b; //错误的写法,这是不被允许的,a和b进行运算的时候会自动提升为int和int进行计算,导致左右两边的类型不匹配
short result = 5 + 8;
}
}
switch语句特点:
注意事项:
死循环的标准格式
//死循环的标准格式
while(true){
循环语句;
}
//随便写一条语句
System.out.println("hello world!"); //此时整个程序都会报错,表示识别不到这个语句,因为前面那个是死循环,所以程序根本不能运行到这一句,所以识别不到
Ctrl+Alt+L //格式化代码
Alt+Enter //导入包
Ctrl+shift+↑↓ //将当前代码行上下移动
Alt+Insert //自动生成toString、get、set等方法
定义方法的注意事项:
**方法重载(OverLoad):**方法名相同,但是参数列表不同(参数个数,参数类型,参数类型顺序)。
动态初始化(长度):在创建数组的时候,直接指定数组当中的数据元素个数
//固定格式:数据类型[] 数组名称 = new 数据类型[数组长度];
int[] arrayA = new int[300];
double[] arrayB = new double[300];
String[] arrayC = new String[300];
/*
1、动态初始化也可以拆分成为两个步骤。
*/
静态初始化:在创建数组的时候,不直接指定数据个数多少,而是直接将具体的数据内容进行指定
//基本格式:数据类型[] 数组名称 = new 数据类型[]{元素1,元素2,.....}
int[] arrayA = new int[]{1, 2, 3, 4, 5};
String[] arrayB = new String[]{"Hello", "World", "Java"};
//省略格式:数据类型[] 数组名称 = {元素1,元素2,......};
int[] arrayA = {1, 2, 3, 4, 5};
/*
注意事项:
1、静态初始化没有直接指定长度,但是仍然会自动推算得到长度。
2、静态初始化标准格式剋拆分成为两个步骤。
3、静态初始化一旦使用省略格式,就不能拆分成为两个步骤了。
*/
数组的遍历
//不能直接打印数组名称,这样输出的结果是数组对应的内存地址哈希值
//访问数组元素的时候,可以通过数组+索引进行访问:数组名[索引值]
/*
注意事项:
静态数组初始化其实也有默认值的过程,只不过系统自动马上将默认值替换成为了大括号当中的具体数值。
*/
数组作为方法参数
//数组作为方法参数传入的时候
public static void printArray(int[] array){
for(int i = 0; i < array,length; i++)
System.out.println(array[i]);
}
数组作为返回值
//数组作为返回值时
class demo{
public static void main(String[] args){
int[] result = calulate(int 10, int 20, int 30);
}
public staic int[] calulate(int a, int b, int c){
int sum = a + b + c;
int avg = sum/3;
int[] array = new int[2];
array[0] = sum;
array[1] = avg;
return array;
}
}
Java的内存需要划分成为5个部分:
1、栈(Stack):存放的都是方法中的局部变量。方法的运行一定要在栈当中。
局部变量:方法的参数,或者是方法体内部的变量
作用域:一旦超出作用域,立刻从栈内存 当中消失
2、堆(Heap):凡是new出来的东西,都放在堆中。
堆内存里面的东西都有一个地址值:16进制
堆内存里面的数据,都有默认值。规则:
如果是整数 | 默认为0 |
---|---|
如果是浮点数 | 默认为0.0 |
如果是字符 | 默认为’\u0000’ |
如果是布尔 | 默认为false |
如果是引用类型 | 默认为null |
3、方法区(Method Area):存储.class相关信息,包含方法的信息。
4、本地方法栈(Native Method Stack):与操作系统相关。
5、寄存器(pc Register):与CPU相关。
一个数组内存图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aDDaa0Bi-1636071520898)(D:\photo\一位数组内存图.jpg)]
二个数组内存图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jx6Mojgq-1636071520900)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210115163040808.png)]
有关数组的函数:
//获取数组的长度
int[] arr = new int[]{1,2,3,4,5,6,7,8,9,10};
int len = arr.length; //这样就可以直接获取数组的长度
面向对象:
/*
面向对象的思想:
可以不考虑细节的过程,而直接看整体
*/
**类的定义:**是一组相关属性和行为的集合。可以看成是一类事物的模板,使用事物的属性特征和行为特征来描述该类事物。
**对象的定义:**是一类事物的具体体现。对象时类的一个实例,必然具备该类事物的属性和行为。
类是对象的模板,对象时类的实例。
类中的成员变量默认的情况下是protected。
调用对象的内存图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rAU4vsiS-1636071520902)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210116100748053.png)]
两个对象适用一个方法的内存图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AvHzgSu9-1636071520904)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210116101613457.png)]
当一个对象作为参数,传递到方法当中时,实际上传递进去的是对象的地址值,同理当一个对象作为返回值返回时,返回的也是对象的地址值
局部变量和成员变量:
this关键字
为了区别于成员变量同名的参数,通过谁调用的方法,谁就是this
一个标准的类通常要拥有下面四个组成部分:
这样标准的类也叫做Java Bean。
概述:API(Application Programming Interface),应用程序编程接口。Java API是一本程序员的字典,是JDK中提供给我们使用的类的说明文档。这些类降低成的代码实现封装了起来,我们不需要关心这些类是如果实现的,只需要学习这些类如何使用即可。所以我们可以通过查询API的方式,来学习Java提供的类,并得知如何使用它们。
注意:只用java.lang下面的内容不需要导入包,其他的都需要导入包。
Scanner类的使用
//基本使用格式
Scanner sc = new Scanner(System.in);
//这里的System.in在我们学习的前期基本是固定格式,代表从键盘输入的意思
/*
使用方法:
对象名.成员方法名()
*/
//获取键盘输入一个int数字:
int num = sc.nextInt();
//获取键盘输入的一个字符串:
String str = sc.next();
匿名对象就是只有右边的对象,没有左边的名字和赋值运算符。
/*
匿名对象的格式
new 类名称();
*/
new Person().name;
//注意事项:匿名对象只能使用唯一的一次,下次再用不得不再创建一个新对象。
//使用建议:如果确定有一个对象只需要使用唯一的一次,就可以用匿名对象。
//匿名对象作为方法的参数和返回值
//普通使用方式
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
//匿名对象的方式
int num = new Scanner(System.in);
//使用一般写法传入参数
Scanner sc = new Scanner(System.in);
methodParam(sc);
//使用匿名对象来进行传参
methodParam(new Scanner(System.in));
public static void methodParam(Scanner sc){
int num = sc.nextInt();
System.out.println("输入的是:" + num);
}
//Scanner作为返回值
Scanner sc = methodReturn();
int num = sc.nextInt();
System.out.println("输入的是:" + num);
public static Scanner methodReturn(){
//一般写法
Scanner sc = new Scanner(System.in);
return sc;
//匿名对象写法
return new Scanner(System.in);
}
//用法也和Scanner类相似
Random r = new Random();
int num = r.nextInt();
int count = r.nextInt(10); //取到0-9的随机数
//数组的长度不可以发生改变,但是ArrayList集合的长度是可以随意变化的
/*
对于ArrayList来说,有一个尖括号代表泛型
泛型:也就是装在集合当中的所有元素,全都是统一的什么类型
注意:泛型只能是引用类型,不能是基本类型
*/
ArrayList<String> list = new ArrayList<>();//此时就创建了一个字符串数组
/*
注意事项:
对于ArrayList集合来说,直接打印得到的不是地址值,而是内容
如果内容是空,得到的是空的中括号:[]
向集合中添加一些数据,需要用到add方法
*/
/*
如果希望向集合ArrayList当中存储基本类型数据,必须使用基本类型对应的“包装类”
基本类型 包装类(引用类型,包装类都位于java.lang包下)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
从JDK1.5开始,支持自动装箱、自动拆箱
自动装箱:基本类型 --> 包装类型
自动拆箱:包装类型 --> 基本类型
*/
ArrayList当中常用的方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zZgj0AMw-1636071520906)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210119175146257.png)]
概述:java.lang.String类代表字符串。
API当中说:java程序中的所有字符串字面值(如“abc”)都作为此类的实例实现。
其实就是说:程序当中所有的双引号字符串,都是String类的对象。(就算没有new,也照样是。)
特点:
创建字符串的常见3种构造方法:
public String():创建一个空白字符串,不含有任何内容。
public String(char[] array):根据字符串数组的内容,来创建对应的字符串。
public String(byte[] array):根据字节数组的内容,来创建对应的字符串。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AznHw7y8-1636071520907)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210121121225089.png)]
字符串常量池:
/*
字符串常量池:程序当中直接写上的双引号字符串,就在字符串常量池中。
对于基本类型来说, ==是进行数值的比较。
对于引用类型来说, ==是进行地址值的比较。
*/
public static void main(String[] args){
String str1 = "abc";
String str2 = "abc";
char[] charArray = {'a', 'b', 'c'};
String str3 = new String(charArray);
System.out.println(str1 == str2); //true
System.out.println(str1 == str3); //false
System.out.println(str2 == str3); //false
}
字符串常量池[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FLLrcHjt-1636071520908)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210121200038939.png)]
字符串中与比较常用的方法
/*
public boolean equals(Object obj):参数可以是任何对象,只有参数是一个字符串并且内容相同的才会给true;否则返回false。
注意事项:
1、任何对象都能用Object进行接受。
2、equals方法具有对称性,也就是a.equals(b)和b.equals(a)效果一样。
3、如果比较双方一个常量一个变量,推荐把常量字符串写在前面。
推荐:"abc".equals(str) 不推荐:str.equals("abc")
*/
public static void main(String[] args){
String str1 = "Hello";
String str2 = "Hello";
char[] charArray = {'H', 'e', 'l', 'l', 'o'};
String str3 = new String(charArray);
System.out.println(str1.equals(str2)); //true
System.out.println(str2.equals(str3)); //true
System.out.println(str3.equals("Hello")); //true
System.out.println("Hello".equals(str1)); //true
}
/*
public boolean equalsIgnoreCase(String str):忽略大小写,进行内容比较
*/
public static void main(String[] args){
String str = "abc";
System.out.println("Abc".equalsIgnoreCase(str));
}
字符串中与获取相关的常用方法
/*
String当中与获取相关的常用方法有:
public int length():获取字符串当中含有的字符个数,拿到字符串长度。
public String concat(String str):将当前字符串和参数字符串拼接成为返回值新的字符串。
public char charAt(int index):获取指定索引位置的单个字符。(索引从0开始)
public int indexOf(String str):查找参数字符串在本字符串当中首次出现的索引位置,如果没有返回-1值。
*/
字符串中与截取相关的常用方法
/*
字符串的截取方法:
public String substring(int index):截取从参数位置一直到字符串末尾,返回新字符串。
public String substring(int begin,int end):截取从begin开始,一直到end结束,中间的字符串。
备注:[begin,end),包含左边,不包含右边。
*/
public static void main(String[] args){
String str1 = "HelloWorld";
String str2 = str1.substring(5);
System.out.println(str2); //World
String str3 = str1.substring(4,7); //oWo
}
字符串与转换相关的常用方法
/*
String当中与转换相关的常用方法:
public char[] toCharArray():将当前字符串拆分成为字符数组作为返回值。
public byte[] getBytes():获得当前字符串的字节数组。
public String replace(CharSequence oldString, CharSquence newString):将所有出现的老字符串替换成为新的字符串,返回替换之后的结果新字符串
备注:CharSequence意思就是说可以接受字符串类型。
*/
字符串与分割相关的常用方法
/*
分割字符串的方法:
public String[] split(String regex):按照参数的规则,将字符串切分成为若干部分。
注意事项:
split方法的参数其实是一个”正则表法式“,要注意,如果按照英文句点”.“进行切分,必须写”\\."(两个反斜杠)
*/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DHjtRKM1-1636071520909)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210123135134290.png)]
含义:一旦用了static关键字,那么这也难怪的内容不再属于对象自己,而是属于类的,所有凡是本类的对象,都共享同一份。
/*
static修饰成员变量的时候,一旦对象给该成员变量赋值,所有对象都会共享这个值。
*/
class Student{
private int id;
private String name;
private int age;
private static int idCounter = 0;
static String room;
public Student(){
this.id = ++idCounter;
}
public Student(String name, int age){
this.name = name;
this.age = age;
this.id = ++idCounter;
}
//成员变量的get和set方法
...
}
class Test{
public static void main(String[] args){
Student one = new Student("郭靖","19");
one.room = "内江六中";
System.out.println(one.getName() + " " + one.getAge() + " " + one.getRoom() + " " + one.getId());
Student two = new Studnet("黄蓉", "20");
System.out.println(two.getName() + " " + two.getAge() + " " + two.getRoom() + " " + two.getId()); //此时的two的room依然会显示内江六中,因为数据是共享的
}
}
用static修饰成员方法
/*
一旦用static修饰成员方法,那么这就成为了静态方法。静态方法不属于对象,而是属于类的。
如果没有static关键字,那么必须首先创建对象,然后通过对象才能使用它。
如果有了static关键字,那么不需要创建对象,直接就能通过类名称进行调用。
并且静态方法推荐使用类名称进行直接调用
无论是成员变量,还是成员方法。如果有了static,都推荐使用类名称进行调用。
静态变量:类名称.静态变量
静态方法:类名称.静态方法()
注意事项:
1、静态不能直接访问非静态。
原因:因为在内存当中是【先】有的静态内容,【后】有的非静态内容。
“先人不知道后人,但是后人知道先人。“
2、静态方法当中不能用this。
原因:this代表当前对象,通过谁调用的方法,谁就是当前对象。
*/
静态static内存图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0E7HdfI-1636071520910)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210123155550259.png)]
静态代码块
/*
静态代码块的格式:
public class 类名称{
static{
//静态代码块的内容
}
}
特点:当第一次用到本类时,静态代码块执行唯一的一次。
静态代码块的典型用途:
用来一次性的堆静态成员变量进行赋值。
*/
public class Demo04Static{
public static void main(String[] args){
Person one = new Person();
Person two = new Person();
//此时静态代码块只会运行一次,不会因为对象的再次创建而再次执行
}
}
public class Person{
static {
System.out.println("静态代码块的执行");
}
public Person(){
System.out.println("构造方法的执行");
}
}
数组工具类Arrays
/*
java.util.Arrays是一个与数组相关的工具类,里面提供了大量静态方法,用来实现数组常见的操作。
public static String toString(数组):将参数数组变成字符串(按照默认的格式[元素1,元素2,...])
public static void sort(数组):按照默认升序(从小到大)对数组元素进行排序。
注意事项:
1、如果是数值,sort默认按照升序从小到大
2、如果是字符串,sort默认按照字母升序
3、如果是自定义的类型,那么这个自定义的类需要有Comparable或者Comparator接口的支持。(今后学习)
*/
public static void main(String[] args){
int[] intArray = {10, 20, 30};
String intstr = Arrays.toString(intArray);
System.out.println(intstr);
int[] array1 = {2, 1, 3, 10, 6};
Arrays.sort(array1);
System.out.println(Arrays.toString(array1));
}
数学工具类Math
/*
java.util.Math类是数学相关的工具类,里面提供了大量的静态方法,完成与数学运算相关的操作。
public static double abs(double num):获取绝对值。
public static double ceil(double num):向上取整
public static double floor(double num):向下取整
public static long round(double num):四舍五入
*/
继承主要解决的问题就是:共性抽取
继承的特点:
继承是多态的前提
/*
当父类和子类的成员变量重名的时候
并且在子类中还有重名的局部变量
子类中访问这些变量的区别:
局部变量: 直接写成员变量名
本类的成员变量: this.成员变量名
父类的成员变量: super.成员变量名
*/
/*
重写(Override)
概念:在继承关系当中,方法的名称一样,参数列表一样。
重写(Override):方法的名称一样,参数列表【也一样】。
重载(Override):方法的名称一样,参数列表【不一样】。
注意事项:
1、必须保证父子类之间方法的名称相同,参数列表也相同。
@Override:写在方法前面,用来检测是不是有效的正确覆盖重写。
这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。
2、子类方法的返回值必须【小于等于】父类方法的返回值范围。
前提:java.lang.Object类是所有类的公共最高父类(祖宗类),java.lang.String就是Object的子类。
3、子类方法的权限必须【大于等于】父类方法的权限修饰符。
小扩展提示:public > protected > (default) > private
备注:(default)不是关键字,而是什么都不写。(如下)
*/
public int age;
protected int age;
int age;
private int age;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vByUb10y-1636071520912)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210125120207068.png)]
重写父类方法的时候,可以不用重新写父类方法中的重复代码,直接用super关键字在重写方法中调用该父类方法,然后添加新的内容。
/*
继承关系中,父子类构造方法的访问特点:
1、子类构造方法当中有一个默认的“super()"调用,所以一定是先调用的父类构造,后执行的子类构造。
2、子类构造可以通过super关键字来调用父类重载构造。
3、super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造。
*/
public class Fu{
public Fu(){
System.out.println("父类构造方法执行!");
}
}
public class Zi extends Fu{
public Zi(){
//不管这里super()写没写,都会默认给送一个,要是写了就不送,不写就送。
//super();
System.out.println("子类构造方法执行!");
}
}
public class Test{
public static void main(String[] args){
Zi zi = new Zi();
/*
这里的执行结果:
父类构造方法执行!
子类构造方法执行!
*/
}
}
/*
super关键字用来访问父类内容,而this关键字用来访问本类内容。用法也有三种:
1、在本类的成员方法中,访问本类的成员变量。
2、在本类的成员方法中,访问本类的另一个成员方法。
3、在本类的构造方法中,访问本类的另一个构造方法。
在第三种用法当中要注意:
A、this(...)调用也必须是构造方法的第一个语句,唯一一个。
B、super和this两种构造调用,不能同时使用。(尽管不写super(...),父类的构造方法在子类创建对象的时候仍然能够调用)
*/
super和this关键字图解[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fZ7KLtTg-1636071520912)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210125124554056.png)]
抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。
抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract。
如何使用抽象类和抽象方法:
不能直接创建new抽象对象。
必须用一个子类来继承抽象父类。
子类必须覆盖重写抽象父类当中所有的抽象方法。
覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号。
public abstract class Animal{
public abstract void eat();
}
public class cat extends Animal{
@Override
public void eat(){
}
}
创建子类对象进行使用。
一个抽象类不一定含有抽象方法,只要保证抽象方法所在的类是抽象类,即可。
这样没有抽象方法的抽象类,也不能直接创建对象,在一些特殊场景下有用途。
/*
接口就是多个类的公共规范。
接口是一种引用数据类型,最重要的内容就是其中的抽象方法。
如何定义一个接口的格式:
public interface 接口名称{
//接口内容
}
如果是Java 7,那么接口中可以包含的内容有:
1、常量
2、抽象方法
如果是Java 8,还可以额外包含有:
3、默认方法
4、静态方法
如果是Java 9,还可以额外包含有:
5、私有方法
*/
public interface Demo{
}
/*
在任何版本的java中,接口都能定义抽象方法。
格式:
public abstract 返回值类型 方法名称(参数列表);
注意事项:
1、接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract
2、这两个关键字修饰符,可以选择性的省略。
3、方法的三要素,可以随意定义。
*/
public interface Demo{
//这是一个抽象方法
public abstract void method1();
//这也是一个抽象方法
abstract void method2();
//这也是一个抽象方法
public void method3();
//这也是一个抽象方法
void method4();
}
/*
接口使用步骤:
1、接口不能直接使用,必须有一个“实现类”来实现接口。
格式:
public class 实现类名称 implements 接口名称{
//....
}
2、接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。
实现:去掉abstract关键字,加上方法体大括号。
3、创建实现类的对象,进行使用。
*/
/*
从Java 8开始,接口里允许定义默认方法。
格式:public default 返回值类型 方法名称(参数列表){
//方法体
}
备注:接口当中的默认方法,可以解决接口升级的问题。
*/
public interface DemoInterfaceDefault {
public abstract void method();
public default void methodAbs(){
System.out.println("make this method run ...");
}
}
public class DemoInterfaceDefaultImplA implements DemoInterfaceDefault {
@Override
public void method() {
System.out.println("A is running ...");
}
}
public class DemoInterfaceDefaultImplB implements DemoInterfaceDefault {
@Override
public void method() {
System.out.println("B is running ...");
}
@Override
public void methodAbs(){
System.out.println("B中重写默认方法");
}
}
public class DemoMainTest {
public static void main(String[] args) {
DemoInterfaceDefaultImplA a = new DemoInterfaceDefaultImplA();
a.method();
a.methodAbs();
System.out.println("==========");
DemoInterfaceDefaultImplB b = new DemoInterfaceDefaultImplB();
b.method();
b.methodAbs();
}
}
/*
接口的实现类实现接口,尽管接口的默认方法没有在实现类中写,但是仍然可以进行调用,并且可以在实现类中重写接口中的默认方法。
*/
接口的静态方法使用
/*
从Java 8 开始,接口当中允许定义静态方法。
格式:
public static 返回值类型 方法名称(参数列表){
方法体
}
注意:不能通过接口实现类的对象来调用接口当中的静态方法。
正确方法:通过接口名称,直接调用其中的静态方法。
格式:
接口名称.静态方法名(参数)
*/
public interface MyInterface {
public static void runMethod(){
System.out.println("接口静态方法执行");
}
}
public class MyMain{
public static void main(String[] args){
MyInterface.runMethod();
}
}
接口的私有方法定义
/*
问题描述:
我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题。
但是这个共有方法不应该让实现类使用,应该是私有化的。
解决方案:
从Java 9开始,接口当中允许定义私有方法。
1、普通私有方法,解决多个默认方法之间重复代码问题。
格式:
private 返回值类型 方法名称(参数列表){
方法体
}
2、静态私有方法,解决多个静态方法之间重复代码问题
格式:
private static 返回值类型 方法名称(参数列表){
方法体
}
*/
public interface MyInterface {
public void method();
public default void methodA(){
System.out.println("A is running ...");
methodC();
}
public default void methodB(){
System.out.println("B is running ...");
methodC();
}
private void methodC(){
System.out.println("111");
System.out.println("222");
System.out.println("333");
}
}
/*
此时在该接口的实现类中是不能够对methodC方法进行访问的,只有该接口中的默认方法能够对其进行访问,同样的静态私有方法的抽取也是只能在在接口的静态方法中进行访问,实现类是不能够进行访问的。
*/
接口的常量定义和使用
/*
接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰。
从效果上看,这其实就是接口的【常量】。
格式:
public static final 数据类型 常量名称 = 数据值;
注意:
1、在接口中就算定义“成员变量”的时候不加上这三个修饰词,Java也会默认给加上,不写也照样是常量。
2、接口当中的常量,必须进行赋值,不能不赋值。
3、接口中常量的名称,使用完全大写的字母,用下划线进行分割。(推荐的命名规则)
*/
public interface MyInterfaceConst {
//这其实就是一个常量,一旦赋值,不可以修改
public static final int NUM_OF_MYCLASS = 10;
}
public class MyInterfaceMain {
public static void main(String[] args) {
System.out.println(MyInterfaceConst.NUM_OF_MYCLASS);
}
}
在Java 9+版本中,接口的内容可以有:(用“[]”括起来的修饰符可以省略)
1、成员变量其实是常量,格式:
[public] [static] [final] 数据类型 常量名称 = 数据值;(也等于==》 数据类型 常量名称 = 数据值)
注意:常量必须进行赋值,而且一旦赋值不能改变。
常量名称完全大写,用下划线进行分割。
2、接口中最重要的就是抽象方法,格式:
[public] [abstract] 返回值类型 方法名称(参数列表);
3、从Java 8开始,接口里允许定义默认方法,格式:
[public] default 返回值类型 方法名称(参数列表){ 方法体 }
4、从Java 8开始,接口里允许定义静态方法,格式:
[public] static 返回值类型 方法名称(参数列表){ 方法体 }
注意:应该通过接口名称进行调用,不能通过实现类对象调用接口静态方法
5、从Java 9开始,接口里允许定义私有方法,格式:
普通私有方法:private 返回值类型 方法名称(参数列表){ 方法体 }
静态私有方法:private static 返回值类型 方法名称(参数列表){ 方法体 }
注意:private的方法只有接口自己才可以调用,不能被实现类或别人调用。
继承父类并实现多个接口
使用接口的时候,不要注意:
1、接口是没有静态代码块或者构造方法的。
2、一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。
格式:
public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
//覆盖重写所有抽象方法
}
3、如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
4、如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类。
5、如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行重写。
6、一个类如果直接父类当中的方法和接口当中的默认方法产生了冲突,优先使用父类当中的方法。
/*
1、类和类之间是单继承的。直接父类只有一个。
2、类与接口之间是多实现的,一个类可以实现多个接口。
3、接口与接口之间是多继承的。
注意事项:
1、多个父接口当中的抽象方法如果重复,没关系。
2、多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,【而且带着default关键字】。
*/
(继承是多态的前提:extends继承或者implements实现,是多态性的前提)
举例说明:
小明是一个对象,这个对象既有学生形态,也有人类形态。
一个对象拥有多种形态,这就是:
==对象的多态性==
(多态性是形容对象的,不是形容类的)
/*
代码当中体现多态性,其实就是一句话:父类引用指向子类对象
格式:
父类名称 对象名 = new 子类名称();
或者:
接口名称 对象名 = new 实现类名称();
*/
public class Fu {
public void method(){
System.out.println("父类方法!");
}
public void methodFu(){
System.out.println("父类特有方法!");
}
}
public class Zi extends Fu {
@Override
public void method() {
System.out.println("子类方法!");
}
}
public class DemoMultiMain {
public static void main(String[] args) {
Fu obj = new Zi();
obj.method();
obj.methodFu();
}
}
/*
在多态的代码当中,成员方法的访问规则是:
看new的是谁,就优先用谁,没有则向上找。
口诀:
1、成员变量:编译看左边,运行还看左边。
2、成员方法:编译看左边,运行看右边。
有关成员方法口诀解释一下:编译的时候看用父类里边有没有该方法,如果没有的话就报错,有的话就正常运行,运行的时候,就看子类是否重写了该方法,要是子类重写了就优先用子类的,要是没有的话,就用父类的。
当然如果父类未定义,子类定义了该成员方法,也可以进行调用,需要进行强制转换
格式:((子类) 对象名).成员方法;
*/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ch3OMLVS-1636071520914)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210201165829452.png)]
对象的向上向下转换
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iFQRytMQ-1636071520915)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210201173441440.png)]
/*
转换过程怎么样才能知道一个父类引用的对象,本来是什么子类?
格式:
对象 instanceof 类名称
这将会得到一个boolean值结果,也就是判断前面的对象能不能当作后面类型的实例。
*/
public static void main(String[] args){
Animal animal = new Cat();
animal.eat();
if(animal instanceof Dog){
Dog dog = (Dog) animal;
dog.watchHouse();
}
if(animal instanceof Cat){
Cat cat = (Cat) animal;
cat.catchMouse();
}
}
分析图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M214KQti-1636071520916)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210201174947341.png)]
**项目代码:**Demo06下面的democomputer
final关键字代表最终、不可改变的。
/*
常见四种用法:
1、可以用来修饰一个类
当final关键字用来修饰一个类的时候,格式:
public final class 类名称 {
//...
}
含义:当前这个类不能有任何的子类。
注意:一个类如果是final的,那么其中的所有成员方法都不能被覆盖重写(因为没有子类)。
2、可以用来修饰一个方法
当final关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重写。
格式:
修饰符 final 返回值类型 方法名称(参数列表){
//...
}
注意事项:对于类、方法来说,abstract关键字和final关键字不能同时使用,因为矛盾。(因为用了 abstract修饰的方法必须进行覆盖重写,而final修饰的方法不能被覆盖重写,所以矛盾)
3、还可以用来修饰一个局部变量
一旦使用final用来修饰局部变量,那么这个变量就不能进行更改。
“一次赋值,终生不变”(只要保证有唯一一次赋值就是正确的)
注意:
1、对于基本类型,不可变说的是变量当中的数据不可变
2、对于引用类型来说,不可变说的是变量当中的地址值不可变
例如:图片例1
4、还可以用来修饰一个成员变量
对于成员变量来说,如果使用final关键字修饰,那么这个变量也照样是不可变的。
1、由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了。
2、对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一。
3、必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值。
*/
图片例1[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uXNDJ5cB-1636071520917)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210202085850234.png)]
public > protected > (default) > private
public | protected | (default) | private | |
---|---|---|---|---|
同一个类 | YES | YES | YES | YES |
同一个包 | YES | YES | YES | NO |
不同包子类 | YES | YES | NO | NO |
不同包非子类 | YES | NO | NO | NO |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aFi7EBdu-1636071520919)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210202093802242.png)]
内部类:
如果一个事物的内部包含另一个事物,那么这就是一个类内包含另一个类。
例如:身体和心脏的关系。又如:汽车和发动机的关系。
分类:
/*
成员内部类
如果一个类是定义在一个类里面,那么这么一个类就是局部内部类。
修饰符 class 类名称 {
修饰符 class 类名称 {
//...
}
//...
}
注意:内用外,随意访问;外用内,需要内部类对象。
===================================================
如何使用成员内部类?有两种方式:
1、间接方式:在外部类的方法当中,使用内部类;然后main只是调用外部类的方法。
2、直接方式,公式:
【外部类名称.内部类名称 对象名 = new 外部类名称().内部类名称();】
当成员变量出现了重名现象,想要在内部类调用外部类的成员变量,那么格式是:外部类.this.外部类成员变 量名
*/
//成员变量重名的情况
public class Outer {
int num = 10;
public class Inner{
int num = 20;
public void methodInner(){
int num = 30;
System.out.println(num);
System.out.println(this.num);
System.out.println(Outer.this.num);
}
}
}
内部类(inner class)是定义在另一个类中的类,内部类中的方法可以访问创建该内部类的类(outer class)的域中所有数据(包括私有/private数据)。并且,内部类可以对同一个包中的其他类隐藏起来。
/*
局部内部类:
如果一个类是定义在一个方法内部的,那么这就是一个局部内部类
“局部”:只有当前所属的方法才能使用它,出了这个方法外面就不能用了。
定义格式:
修饰符 class 外部类名称 {
修饰符 返回值类型 外部类方法名称(参数列表){
class 局部内部类名称 {
//...
}
}
}
小结一下类的权限修饰符:
public > protected > (default) > private
定义一个类的时候,权限修饰符规则:
1、外部类:public / (default)
2、成员内部类:public / protected / (default) /private
3、局部内部类:什么都不能写(虽然都是不写,与default其实是不一样)
*/
public class Outer{
public void methodOuter(){
class Inner{
int num = 0;
public void methodInner(){
System.out.println(num);
}
}
Inner inner = new Inner();
inner.methodInner();
}
}
当某一个类只被唯一一个方法调用时,就可以采用局部内部类来定义它(该类定义在某方法内部)。局部类不能使用public或者private访问说明符进行声明,它的作用域被限定在声明这个局部类的方法(或代码块)中。并且,局部类对外部世界是完全隐藏起来的,及时定义它的方法所在的外围类也不可以访问它。
由于局部内部类对于外围类不可见,所以不存在用过其他手段调用该局部内部类的危险,也就极大的保证了安全性。
局部内部类的final问题
/*
局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是【有效的final的】。
备注:从Java 8+开始,只要局部变量事实不变,那么final关键字可以省略
原因:
1、new出来的对象在堆内存当中。
2、局部变量是跟着方法走的,在栈内存当中。
3、方法运行结束之后,立刻出栈,局部变量就是立刻消失。
4、但是new出来的对象会在堆当中持续存在,直到垃圾回收消失。
(生命周期的问题)
*/
假如一个局部内部类只被用一次(只用它构建一个对象),就可以不用对其命名了,这种没有名字的类被称为匿名内部类(anonymous inner class),其代码格式通常为:
new SuperType( ){
Inner class methods and data //匿名内部类的书写和数据
}
其中SuperType可以是一个接口(匿名内部类将要实现的接口),也可以是一个类(匿名内部类将要扩展它)。匿名内部类的可见域与局部内部类相同。
/*
如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,
那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】
匿名内部类的定义格式:
接口名称 对象吗 = new 接口名称() {
//覆盖重写所有抽象方法
}
对格式进行解析“new 接口名称(){...}进行解析:
1、new代表创建对象的动作
2、接口名称就是匿名内部类需要实现哪个接口
3、{...}这才是匿名内部类的内容
另外还要注意几点问题:
1、匿名内部类,在【创建对象】的时候,只能使用唯一一次
如果希望多次创建对象,而且类的内容一样的话,那么就必须使用单独定义的实现类了。
2、匿名对象,在【调用方法】的时候,只能调用唯一一次
如果希望同一个对象,调用多次方法,那么必须给对象起个名字。
3、匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】
强调:匿名内部类和匿名对象不是一回事!!!
*/
public class MyDemoMain {
public static void main(String[] args) {
MyInterface my = new MyInterface() {
@Override
public void method() {
System.out.println("使用匿名内部类实现方法!");
}
};
my.method();
new MyInterface() {
@Override
public void method() {
System.out.println("使用匿名内部类实现方法!");
}
}.method();
}
}
/*
java.lang.Object类
类Object是类层次结构的根(最顶层)类,每个类都使用Object作为超类。
所有对象(包括数组)都实现了这个类的方法。
1、当子类没有重写Object的toString方法时,直接打印对象的名字,其实就是调用toString方法
2、equals在进行比较的时候,需要注意:
基本数据类型:比较的是内容
引用数据类型:比较的是地址值
Object类的equals方法默认比较的是两个对象的地址值,没有意义
所以我们需要重写equals方法,比较两个对象的属性值(...)
对象的属性值一样,返回true;否则返回false;
问题:
隐含这一个多态的问题
Object obj = new Student(...);
多态的弊端:无法使用子类特有的内容(属性,方法)
解决:可以使用向下转型(强转)把Object类型转换为Student
*/
//自动生成的
@Override
public boolean equals(Object o) {
if (this == o) return true;
/*
* 下面的条件判断和obj instanceof student是一样的
* 这里利用了反射技术,判断o是否为Student类型
* */
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
if (name != null ? !name.equals(student.name) : student.name != null) return false;
return sex != null ? sex.equals(student.sex) : student.sex == null;
}
//自动生成的高级版本(推荐使用这个)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name) &&
Objects.equals(sex, student.sex);
}
//自己写的
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj == this)
return true;
if (obj instanceof Student){
Student stu = (Student) obj;
if (this.name == stu.getName() && this.age == stu.getAge() && this.sex == stu.getSex()){
return true;
}else {
return false;
}
}
return false;
// return super.equals(obj);
}
/*
这里说明一下Objects.equals方法:
Objects类的equals方法:对两个对象进行比较,防止空指针异常
public static boolean equals(Object a, Object b){
return (a == b) || (a != null && a.equlas(b));
}
这样处理之后就不会因为null值出现空指针异常。
*/
/*
java.util.Date:表示日期和时间的类
类Date表示特定的瞬间,精确到毫秒。
毫秒:千分之一 1000毫秒 = 1秒
特定的瞬间:一个时间点,一刹那时间
2088-08-08 09:55:33:333 瞬间
2088-08-08 09:55:33:334 瞬间
2088-08-08 09:55:33:334 瞬间
...
毫秒值的作用:可以对时间和日期进行计算
2099-01-03 到 2088-01-01 中间一共有多少天
可以日期转换为毫秒进行计算,计算完毕,在把毫秒转换为日期
把日期转换为毫秒:
当前的日期:2088-01-01
时间远点(0毫秒):1970年1月1日 00:00:00(英国格林威治)
就是计算当前日期到时间远点之间一共经历了多少毫秒
注意:
中国属于东八区,会把时间增加8个小时
1970年1月1日 08:00:00
把毫秒转换为日期:
1天 = 24 x 60 x 60 = 86400秒 = 86400 x 1000 = 86400000毫秒
*/
public class Demo01Date {
public static void main(String[] args) {
//获取当前系统时间到1970 年 1 月 1 日 00:00:00经历了多少毫秒
System.out.println(System.currentTimeMillis());
System.out.println("=======================");
demo01();
System.out.println("=======================");
demo02();
System.out.println("========================");
demo03();
}
/*
* Date 类的成员方法
* long getTime() 把日期转换为毫秒(相当于System.currentTimeMillis())
* 返回自 1970 年 1月 1 日 00:00:00 GMT以来此 Date 对象表示的毫秒数。
* */
private static void demo03() {
Date date = new Date();
long time = date.getTime();
System.out.println(time);
}
/*
* Date 类的带参构造方法:
* Date(long date):传递毫秒值,把毫秒转换为Date日期
* */
private static void demo02() {
//此时传入的是0毫秒,以为这从1970 年1月1日过了0毫秒的日期
Date date = new Date(11112312323L);
System.out.println(date);
}
/*
* Date 类的空参数构造方法:
* Date()获取的就是当前系统的日期和时间
* */
private static void demo01() {
Date date = new Date();
System.out.println(date);
}
}
/*
java.text.DateFormat是日期/时间格式化子类的抽象类,我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来回转换。
成员方法:
String format(Date date) 按照指定的模式,把Date日期,格式化符合模式的字符串
Date parse(String source)把符合模式的字符串,解析为Date日期
DateFormat类是一个抽象类,无法直接创建对象使用,可以使用DateFormat的子类
java.text.SimpleDateFormat extends DateFormat
构造方法:
SimpleDateFormat(String pattern) 用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat。
参数:
String pattern:传递指定的模式
模式:区分大小写的
写对应的模式,会把模式替换为对应的日期和时间
”yyyy-MM-dd HH:mm:ss“
注意:
模式中的字母不能更改,连接模式的符号可以改变
*/
public class Demo01DateFormat {
public static void main(String[] args) {
demo01();
System.out.println("=====================");
demo02();
}
private static void demo02() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
try {
Date date = sdf.parse("2017年12月11日 14时06分12秒");
System.out.println(date);
} catch (ParseException e) {
e.printStackTrace();
}
}
private static void demo01() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String text = sdf.format(date);
System.out.println(date);
System.out.println("============");
System.out.println(text);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CXwRyiDr-1636071520920)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210204172143448.png)]
常用方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2KYbQ70l-1636071520921)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210204172324009.png)]
/*
java.lang.System类中提供了大量的静态方法,可以获取与系统相关的信息或系统级操作,在System类的API文档中,常用的方法有:
public static long currentTimeMills():返回以毫秒为单位的当前时间。
public static void arraycopy(Object src, int srcPos, Obeject dest, int destPos, int length):将数组中指定的数据拷贝到另一个数组中。
*/
/*
StringBuilder类
字符串缓冲区,可以提高字符串的操作效率(看成一个长度可以变化的字符串)
底层也是一个数组,但是没有被final修饰,可以改变
StringBuilder在内存中始终是一个数组,占用空间少,效率高
如果超出了StringBuilder的容量,会自动扩容。
构造方法:
public StringBuilder():构造一个空的StringBuilder容器。
pubic StringBuilder(String str):构造一个StringBuilder容器,并将字符串加进去。
*/
public static void main(String[] args) {
StringBuilder bu1 = new StringBuilder();
System.out.println(bu1);
StringBuilder bu2 = new StringBuilder("abc");
System.out.println(bu2);
}
/*
StringBuilder常用的方法有2个:
public StringBuiler append(...):添加任意类型数据的字符串形式,并返回当前对象自身。
public String toString():将当前StringBuilder对象转换为String对象。
Public StringBuilder reverse(...):将参数字符串的内容反转,返回的仍然是对象自身。
*/
public static void main(String[] args) {
StringBuilder bu1 = new StringBuilder();
System.out.println(bu1);
StringBuilder bu2 = new StringBuilder("abc");
System.out.println(bu2);
bu1.append("sfdasdfa");
System.out.println(bu1);
System.out.println("=================");
bu1.reverse();
System.out.println(bu1);
}
/*
StringBuilder和String可以互相转化:
String->StringBuilder:可以使用StringBuilder的构造方法
StringBuilder(String str) 构造一个字符串生成器,并初始化为指定的字符串内容。
StringBuilder->String:可以使用StringBuilder中的toString方法
public String toString():将当前StringBuilder对象转化成String
*/
String str = "hello";
System.out.println("str:" + str);
//String -> StringBuilder
StringBuilder sb = new StringBuilder(str);
sb.append(" world");
System.out.println(sb);
//StringBuilder -> String
String s = sb.toString();
System.out.println(s);
/*
就是把基本数据类型包装起来,并添加一些方便处理基本数据内容的方法,这样的类就是包装类
装箱:将基本数据类型包装(基本数据类型 -> 包装类)
拆箱:从包装类对象转换为对应的基本类型
自动装箱与自动拆箱:
基本类型的数据和包装类之间可以自动的相互转化
JDK1.5之后出现的新特性
自动装箱:直接把int类型的整数赋值给包装类
自动拆箱:包装类无法直接参与计算,可以转化为基本类型进行计算
*/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jrESeJUW-1636071520922)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210205104426473.png)]
数组:
1、长度不可变
2、可以存储基本数据类型,也可以存储对象
集合:
1、长度可变
2、不能存储基本数据类型,只能存储对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2qjwfG7k-1636071520923)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210205110525321.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f1PDCMZI-1636071520925)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210205110757124.png)]
boolean add(E e); 向集合中添加元素
boolean remove(E e); 删除集合中的某个元素
void clear(); 清空集合所有的元素
boolean contains(E e); 判断集合中是否包含某个元素
boolean isEmpty(); 判断集合是否为空
int size(); 获取集合长度
Object[] toArray(); 将集合转成一个数组
/*
迭代:即Collection集合元素的通用获取方式。
java.util.Iterator接口:迭代器(对集合进行遍历)
有两个常用的方法
boolean hasNext() 如果仍有元素可以迭代,则返回true。
判断集合中还有没有下一个元素,有就返回true,没有就返回false
E next() 返回迭代的下一个元素
取出集合中的下一个元素
Iterator迭代器是一个接口,我们无法直接使用,需要使用Iterator接口的实现类对象
获取实现类的方法比较特殊,Collection接口中有一个方法,叫iterator(),这个方法返回的就是
迭代器的实现类对象。
迭代器的使用步骤(重点):
1、使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
2、使用Iterator接口中的方法hasNext判断还有没有下一个元素
3、使用Iterator接口中的方法next取出集合中的下一个元素
*/
public static void main(String[] args) {
Collection<String> coll = new ArrayList<>();
((ArrayList<String>) coll).add("姚明");
((ArrayList<String>) coll).add("科比");
((ArrayList<String>) coll).add("乔丹");
((ArrayList<String>) coll).add("库里");
Iterator<String> it = coll.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r7ItrIsc-1636071520926)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210206102338674.png)]
/*
增强for循环:
内部的实现原理其实是个Iterator迭代器(底层使用的也是迭代器,使用for循环的格式,简化了迭代器的书写
是JDK1.5之后出现的新特性
Collection extends Iterable:所有的单列集合都可以使用foreach
public interface Iterable实现这个接口允许对象成为“foreach”语句的目标。
foreach:用来遍历集合和数组
格式:
for(集合/数组的数据类型 变量名 : 集合名/数组名){
sout(变量名);
}
*/
public static void main(String[] args) {
demo01();
demo02();
}
private static void demo02() {
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
for (String s: list) {
System.out.println(s);
}
}
private static void demo01() {
int[] arr = {1,2,3,4,5};
for (int a: arr) {
System.out.println(a);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vgxaoaFj-1636071520927)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210206174921635.png)]
/*
创建集合对象,使用泛型
好处:
1、避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型。
2、把运行期异常(代码运行之后会抛出的异常),提升到了编译期(写代码的时候会报错)
弊端:
泛型是什么类型,只能存储什么类型的数据
创建集合对象,不使用泛型
好处:
集合不使用泛型,默认的类型就是Object类型,可以存储任意类型的数据
弊端:
不安全,会引发异常
*/
定义格式:
修饰符 class 类名<代表泛型的变量>{}
/*
* 定义一个含有泛型的类,模拟ArrayList集合
* */
public class GenericClass<ElemType> {
private ElemType name;
public ElemType getName() {
return name;
}
public void setName(ElemType name) {
this.name = name;
}
}
public static void main(String[] args) {
GenericClass<String> gc = new GenericClass<>();
gc.setName("hrg");
System.out.println(gc.getName());
System.out.println("=================================");
GenericClass<Integer> gs = new GenericClass<>();
gs.setName(1);
System.out.println(gs.getName());
}
/*
* 定义含有泛型的方法:泛型定义在方法的修饰符和返回值类型之间
*
* 格式:
* 修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)){
* 方法体;
* }
*
* 含有泛型的方法,在调用方法的时候确定泛型的数据类型
* 传递什么类型的参数,泛型就是什么类型
* */
public class GenericMethod {
public <M> void method01(M m){
System.out.println(m);
}
}
public static void main(String[] args) {
GenericMethod gm = new GenericMethod();
gm.method01(5);
gm.method01(true);
gm.method01(8.8);
}
/*
* 定义含有泛型的接口
* */
public interface GenericInterface<I> {
public abstract void method(I i);
}
public class GenericInterfaceImpl implements GenericInterface<String> {
@Override
public void method(String s) {
System.out.println(s);
}
}
public class GenericInterfaceImpl2<I> implements GenericInterface<I> {
@Override
public void method(I i) {
System.out.println(i);
}
}
public class Demo02GenericInterface {
public static void main(String[] args) {
GenericInterfaceImpl gf = new GenericInterfaceImpl();
gf.method("字符串");
GenericInterfaceImpl2<Integer> gfm = new GenericInterfaceImpl2<>();
gfm.method(10);
GenericInterfaceImpl2<String> gfs = new GenericInterfaceImpl2<>();
gfs.method("my love");
}
}
/*
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身无法使用。
通配符基本使用
泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。
此时只能接受数据,不能往集合中存储数据
*/
public static void main(String[] args){
Collection<Integer> list1 = new ArrayList<>();
getElement(list1);
Collection<String> list2 = new ArrayList<>();
getElement(list2);
}
public static void getElement(Collection<?> coll){}
//?代表可以接收任意类型
/*
泛型的通配符:
?:代表任意的数据类型
使用方法:
不能创建对象使用
只能作为方法的参数使用
*/
容器类有两个类型:
容器的类型以及元素的类型
ArrayList notes = new ArrayList;
查询快,增删慢
原因:每次增加或删除都会重新创建一个新的数组
int[] arr = new int[];
特点:
趋近于平衡树,查询速度非常的快,查询叶子节点最大次数和最小次数不能超过2倍
约束:
自己查完之后再进行总结
java.util.List接口 extends Collection接口
List接口的特点:
1.有序的集合,存储元素和取出元素的顺序是一致的(存储123取出123)
2.有索引,包含了一些带索引的方法
3.允许存储重复的元素
List接口中带索引的方法(特有):
- public void add(int index, E element):将指定的元素,添加到该集合中的指定位置上
- public E get(int index):返回集合中指定位置的元素。
- public E remove(int index):移除列表中指定位置的元素,返回的是被移除的元素
- public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素
注意:
操作索引的时候,一定要防止索引越界异常
该集合不是同步的,是多线程的,这样访问速度快
java.util.ArrayList集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合
许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。
java.util.LinkedList集合 implements List接口
LinkedList集合的特点:
1、底层是一个链表结构:查询慢,增删快
2、里边包含了大量操作首尾元素的方法
注意:使用LinedList集合特有的方法,不能使用多态
- public void addFirst(E e):将指定元素插入此列表的开头
- public void addLast(E e):将指定元素添加到此列表的结尾
- public void push(E e):将元素推入此列表所表示的堆栈
- public E getFirst():返回此列表的第一个元素
- public E getLast():返回此列表的最后一个元素
- public E removeFirst():移除并返回此列表的第一个元素
- public E removeLast():移除并返回此列表的最后一个元素
- public E pop():从此列表所表示的堆栈处弹出一个元素
- public boolean isEmpty():如果列表不包含元素,则返回true
Vector类可以实现可增长的对象数组。
与新collection实现不同,Vector是同步(单线程)的。
Java.util.Set接口 extends Collection接口
Set接口的特点:
1、不允许存储重复的元素
2、没有索引,没有带索引的方法,也不能使用普通的for循环遍历
/*
java.util.HashSet集合 implements Set接口
HashSet特点:
1、不允许存储重复的元素
2、没有索引,没有带索引的方法,也不能使用普通的for循环遍历
3、是一个无需的集合,存储的元素和取出元素的顺序有可能不一致
4、底层是一个哈希表结构(查询的速度非常的快)
哈希值:是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来得到地址,不是数据实际存储的物理地址
在Object类有一个方法,可以获取对象的哈希值
int hashCode() 返回该对象的哈希码值
hashCode方法的源码:
public native int hashCode();
native:代表该方法调用的是本地操作系统的方法
*/
public class Person extends Object {
//Object类的hashCode方法是可以重写的
@Override
public int hashCode() {
return 1;
}
}
public class Demo03HashCode {
public static void main(String[] args) {
//Person类继承了Object类,所以可以调用hashCode方法
Person p1 = new Person();
int num1 = p1.hashCode();
System.out.println(num1);//356573597
Person p2 = new Person();
int num2 = p2.hashCode();
System.out.println(num2);//1735600054
System.out.println("========================");
System.out.println(p1);//com.hrg.demo.day03Set.Person@ 1540e19d (== 356573597)
System.out.println(p2);//com.hrg.demo.day03Set.Person@ 677327b6 (== 1735600054)
//哈希值相等,但是new出来的对象是不一定相等,因为哈希值不代表对象的物理地址,只有物理地址相等,对象才相等
System.out.println(p1 == p2);//false
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s1 == s2);//false
System.out.println(s1.equals(s2));//true
}
}
哈希表结构[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H6Nxd7YM-1636071520929)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210215113122474.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uKasmQYZ-1636071520930)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210215114949696.png)]
HashSet存储自定义类型元素
/*
set集合报错元素唯一:
存储的元素(String,Integer,...,Student,Person),必须重写hashCode方法和equals方法
要求:
同名同年龄的人,视为同一个人,只能存储一次
*/
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Demo03HashSetSaveStudent {
public static void main(String[] args) {
HashSet<Student> hset = new HashSet<>();
Student t1 = new Student("鲁迅",18);
Student t2 = new Student("鲁迅",18);
Student t3 = new Student("鲁迅",20);
hset.add(t1);
hset.add(t2);
hset.add(t3);
System.out.println(hset);
}
}
/*
java.util.LinkedHashSet集合 extends HashSet集合
LinkedHashSet集合特点:
底层是一个哈希表(数组+链表/红黑树)+链表:多了一条链表(记录元素的存储顺序),保证元素有序
输出的元素和输入的元素顺序是一样的
*/
/*
可变参数:是JDK1.5之后出现的新特性
使用前提:
当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数。
使用格式:定义方法时使用
修饰符 返回值类型 方法名(数据类型...变量名){}
可变参数的原理:
可变参数底层就是一个数组,根据传递参数个数不同,会创建不同长度的数组,来存储这些参数
传递的参数个数,可以是0个(不传递),1,2,...多个
*/
public class Demo03VarArgs {
public static void main(String[] args) {
// int i = add();
// int i = add(10);
// int i = add(10,20);
int i = add(10,20,30,40,50,60);
System.out.println(i);
}
/*
* 定义计算(0-n)整数和的方法
* 已知:计算整数的和,数据类型已经确定int
* 但是参数的个数不确定,不知道要计算几个整数的和,就可以使用可变参数
* add():就会创建一个长度为0的数组,new int[0]
* add(10):就会创建一个长度为1的数组,new int[]{10};
* add(10,20):就会创建一个长度为2的数组,new int[]{10,20};
* ...
* */
public static int add(int ... arr){
// System.out.println(arr);
// System.out.println(arr.length);
int sum = 0;
for (int i : arr) {
sum += i;
}
return sum;
}
/*public static int add(int a,int b,int c){
return a+b+c;
}
public static int add(int a,int b){
return a+b;
}*/
}
/*
可变参数的注意事项
1、一个方法的参数列表,只能有一个可变参数
2、如果方法的参数有多个,那么可变参数必须写在参数列表的末尾
*/
public static void method(String a,double b,int...c){
//方法体
}
//可变参数的特殊(终极)写法
public static void method(Object...obj){
//方法体
}
/*
java.utils.Collections是集合工具类,用来对集合进行操作。部分方法如下:
-public static boolean allAll(Collection c,T... elements):往集合中添加一些元素。
-public static void shuffle(List> list) 打乱顺序:打乱集合顺序。
*/
public class Demo03CollectionsSet {
public static void main(String[] args) {
ArrayList<String> arr = new ArrayList<>();
/*arr.add("a");
arr.add("b");
arr.add("c");
arr.add("d");
arr.add("e");
System.out.println(arr);*/
//往集合中添加一些元素
Collections.addAll(arr, "a", "b", "c", "d", "e");
System.out.println(arr);
//打乱集合中元素的顺序
Collections.shuffle(arr);
System.out.println(arr);
}
}
/*
public static void sort(List list):将集合中元素按照默认规则排序。
public static void sort(List list, Comparator super T>):将集合中元素按照指定规则排序
注意:
sort(List list)使用前提
被排序的集合里边存储的元素,必须实现Comparable,重写接口中的方法compareTo定义排序的规则
sort(List list, Comparator super T>):将集合中元素按照指定规则排序
Comparator和Comparable的区别:
Comparable:自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的规则compareTo方法
Comparator:相当于找一个第三方的裁判,比较两个
*/
public class Demo03CollectionsSort {
public static void main(String[] args) {
ArrayList<Employee> list = new ArrayList<>();
list.add(new Employee("张三",18));
list.add(new Employee("李四",16));
list.add(new Employee("王五",19));
System.out.println(list);
//使用带一个参数的
/*Collections.sort(list);
System.out.println(list);*/
//使用带两个参数的
/*
* o1 - o2 : 升序
* o2 - o1 : 降序
* */
Collections.sort(list, new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
return o1.getAge() - o2.getAge();
}
});
System.out.println(list);
}
}
/*
java。util.Map集合(实现不同步,多线程,速度快)
Map集合的特点:
1、Map集合是一个双列集合,一个元素包含两个值(一个key,一个value)
2、Map集合中的元素,key和value的数据类型可以相同也可以不同
3、Map集合中的元素,key是不允许重复的,value是可以重复的
4、Map集合中的元素,key和value是一一对应的
java.util.HashMap集合 implements Map接口
HashMap集合的特点:
1、HashMap集合底层是哈希表:查询的速度特别的快
JDK1.8之前:数组+单向链表
JDK1.8之后:数组+单向链表/红黑树(链表的长度超过8):提高查询速度
2、HahshMap集合是一个无序的集合,存储元素和取出元素的顺序有可能不一致
java.util.LinkedHashMap集合 extends HashMap集合
LinkedHashMap的特点:
1、LinkedHahshMap集合底层是哈希表+链表
2、LinkedHashMap集合是一个有序的集合,存储元素和取出元素的顺序是一致的
*/
/*
public V put(K key,V value):把指定的健与指定的值添加到Map集合中
public V remove(Object key):把指定的键所对应的键值对元素在Map集合中删除,返回被删除元素的值
public V get(Object key):根据指定的键,在Map集合中获取对应的值
boolean containsKey(Object key):判断集合中是否包含指定的键
public Set keySet():获取Map集合中所有的键,存储到Set集合中
public Set> entrySet():获取到Map集合中所有的键值对对象的集合(Set集合)
*/
public class Demo04Map {
public static void main(String[] args) {
// show01();
// show02();
// show03();
show04();
}
/*
*boolean containsKey(Object key):判断集合中是否包含指定的键
* 包含返回true,不包含返回false
* */
private static void show04() {
Map<String,Integer> map = new HashMap<>();
map.put("hrg",22);
map.put("hrh",15);
map.put("zh",45);
boolean hrg = map.containsKey("hrg");
System.out.println(hrg);
boolean hhh = map.containsKey("hhh");
System.out.println(hhh);
}
/*
* public V get(Object key):根据指定的键,在Map集合中获取对应的值
* 返回值:v
* key存在,返回对应的value
* key不存在,返回null
* */
private static void show03() {
Map<String,Integer> map = new HashMap<>();
map.put("hrg",22);
map.put("hrh",15);
map.put("zh",45);
map.put("hbd",47);
Integer hrg = map.get("hrg");
System.out.println(hrg);
}
/*
* public V remove(Object key):把指定的键所对应的键值对元素在Map集合中删除,返回被删除元素的值。
* 返回值:v
* key存在,v返回被删除的value值
* key不存在,v返回null值
* */
private static void show02() {
Map<String,Integer> map = new HashMap<>();
map.put("hrg",22);
map.put("hrh",15);
map.put("zh",45);
map.put("hbd",47);
System.out.println(map);
System.out.println("==========================");
Integer hrg = map.remove("hrg");
System.out.println(hrg);
System.out.println(map);
System.out.println("===========================");
Integer hhh = map.remove("hhh");
System.out.println(hhh);
System.out.println(map);
}
/*
* public V put(K key,V value):把指定的健与指定的值添加到Map集合中
* 返回值:v
* 存储键值对的时候,key不重复,返回值v是null
* 存储键值对的时候,key重复,会使用新的value替换map中之前的value,返回被替换的value值
* */
private static void show01() {
Map<String,String> map = new HashMap<>();
String v1 = map.put("hrg", "帅哥");
System.out.println(v1);
map.put("hrh","丑逼");
map.put("zh","妈妈");
map.put("hbd","老爸");
System.out.println(map);
}
}
//keySet()方法的使用:
public class Demo04KeySet {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
map.put("hrg",22);
map.put("hrh",16);
map.put("hbd",48);
System.out.println(map);
Set<String> str = map.keySet();
Iterator<String> it = str.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(map.get(s));
}
//和上面的while是一样的效果
for (String s : str) {
Integer num = map.get(s);
System.out.println(num);
}
}
}
//entrySet()方法
/*
* Map集合中的方法:
* Set> entrySet() 返回此映射中包含的映射关系的set视图
*
* 实现步骤:
* 1、使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中
* 2、遍历Set集合,获取每一个Entry对象
* 3、使用Entry对象中的方法getKey()和getValue()获取键与值
* */
public class Demo04entrySet {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
map.put("hrg",22);
map.put("hrh",16);
map.put("hbd",48);
map.put("zh",46);
System.out.println(map);
Set<Map.Entry<String, Integer>> entries = map.entrySet();
Iterator<Map.Entry<String, Integer>> it = entries.iterator();
while(it.hasNext()){
// System.out.println(it.next());
Map.Entry<String, Integer> en = it.next();
String key = en.getKey();
System.out.print(key);
Integer va = map.get(key);
System.out.println(" " + va);
}
//上面的while和下面的for效果是一样的
for(Map.Entry<String,Integer> entry : entries){
String key = entry.getKey();
System.out.print(key);
Integer va = map.get(key);
System.out.println(" " + va);
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4BlWzp7M-1636071520931)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210220111424192.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7VhDklxh-1636071520933)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210220114519155.png)]
/*
* HashMap存储自定义类型键值:
* Map集合保证key是唯一的:
* 作为key的元素,必须重写hashCode方法和equals方法,以保证key唯一
* */
public class Demo04HashMapSavePerson {
public static void main(String[] args) {
// show01();
show02();
}
/*
* HashMap存储自定义类型键值
* key:Person类型
* Person类必须重写hashCode()和equals()方法,以保证key唯一
* value:String类型
* 可以重复
* */
private static void show02() {
HashMap<Person,String> map = new HashMap<>();
map.put(new Person("丑逼",18), "美国");
map.put(new Person("帅逼",18), "中国");
map.put(new Person("垃圾",18), "印度");
map.put(new Person("丑逼",18), "英国");
Set<Map.Entry<Person, String>> set = map.entrySet();
for (Map.Entry<Person, String> entry : set) {
Person key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"-->"+value);
}
}
/*
* HashMap存储自定义类型键值
* key:String类型
* String类型重写hashCode()和equals()方法,保证key的唯一
* value:Person类型
* value可以重复(同名同年龄的人视为同一个)
* */
private static void show01() {
Map<String,Person> map = new HashMap<>();
map.put("北京", new Person("张三",18));
map.put("上海", new Person("李四",19));
map.put("广州", new Person("王五",20));
map.put("北京", new Person("赵六",18));
Set<String> strings = map.keySet();
for (String s : strings) {
Person value = map.get(s);
System.out.println(s + "-->" + value);
}
}
}
/*
java.util.LinkedHashMap extends HashMap
Map 接口的哈希表和链表列表实现,具有可预知的迭代顺序。
底层原理:
哈希表+链表(记录元素的顺序)
*/
public class Demo04LinkedHashMap {
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
map.put("a","a");
map.put("c","c");
map.put("b","b");
map.put("a","d");
System.out.println(map);//key不允许重复,无序
LinkedHashMap<String,String> linkedmap = new LinkedHashMap<>();
linkedmap.put("a","a");
linkedmap.put("c","c");
linkedmap.put("b","b");
linkedmap.put("a","d");
System.out.println(linkedmap);//key不允许重复,有序
}
}
/*
java.util.Hashtable集合 implements Map接口
Hashtable:底层也是一个哈希表,是一个线程安全的集合,是单线程集合,速度慢
HashMap:底层是一个哈希表,是一个线程不安全的集合,是多线程的集合,速度快
HashMap集合(之前学的所有的集合):可以存储null值,null键
Hashtable集合,不能存储null值,null键
Hashtable和Vector集合一样,在jdk1.2版本之后被更先进的集合(HashMap,ArrayList)取代了
Hashtable的子类Properties依然活跃在历史舞台
Properties集合是一个唯一和IO流相结合的集合
*/
public class Demo04HashTable {
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
map.put(null, "a");
map.put("b", null);
map.put(null, null);
System.out.println(map);
Hashtable<String,String> table = new Hashtable<>();
//table.put("a",null);//错误,会报空指针异常
//table.put(null,"a");//错误,会报空指针异常
//table.put(null,null);//错误,会报空指针异常
table.put("a","a");
System.out.println(table);
}
}
/*
JDK9的新特性:
List接口,Set接口,Map接口:里边增加了一个静态的方法of,可以给集合一次性添加多个元素
static List of (E... elements)
使用前提:
当集合中存储的元素的个数已经确定了,不在改变时使用
注意:
1、of方法只适用于List接口,Set接口,Map接口,不适用于接口的实现类(意味着不适用于HashMap,HashSet)
2、of方法的放回置是一个不能改变的集合,集合不能再使用add,put方法添加元素,会抛出异常
3、Set接口和Map接口在调用of方法的时候,不能有重复的元素,否则会抛出异常
*/
Debug调试程序:
可以让代码逐行执行,查看代码执行的过程,调试程序中出现的bug
使用方式:
在行号的右边,鼠标左键单击,添加断点(每个方法的第一行,哪里有bug添加到哪里)
右键,选择Debug执行程序
程序就会停留在添加的第一个断点处
执行程序:
Step Over:逐行执行程序
Step into:进入到方法中
Step out:跳出方法
Resume Program:跳到下一个断点,如果没有下一个断点,那么就结束程序
Console:切换到控制台
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6OiLiiFW-1636071520934)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210224175506542.png)]
异常的根类是java.lang.Throwable,其下有两个子类:
1、java.lang.Error:错误(必须修改源代码)
2、java.lang.Exception:编译期异常,进行编译(写代码)java程序出现的问题
RuntimeException:运行期异常,Java程序运行过程中出现的问题,是Exception下面的子类,
平时所指的异常是java.lang,Exception
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2iFyn45H-1636071520935)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210224181255383.png)]
/*
throw关键字
作用:
可以使用throw关键字在指定的方法中抛出指定的异常
使用格式:
throw new xxxException("异常产生的原因")
注意:
1、throw关键字必须写在方法的内部
2、throw关键字后边new的对象必须是Exception或者Exception的子类对象
3、throw关键字抛出指定的异常对象,我们就必须处理这个异常对象
throw关键字后边创建的是RuntimeException或者是RuntimeException的子类对象,我们可以不处理,默认交给JVM处理(打印异常对象,中断程序)
throw关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么throws,要么try...catch
*/
/*
Objects类中的静态方法
public static T requireNonNull(T obj):查看指定引用对象是不是null.
*/
/*
throws关键字
在方法声明的时候使用,抛出异常
*/
/*
Throwable类中定义了3个异常处理的方法
String getMessage() 返回此throwable的简短描述
String toString() 返回此throwable的详细消息字符串
Void printStackTrace() JVM打印异常对象,默认此方法,打印的异常信息是最全面的
*/
/*
finally关键字
1、finally不能单独使用,必须和try一起使用
2、finally一般用于资源释放(资源回收),无论程序是否出现异常,最后都要资源释放(IO)
*/
/*
多个异常使用捕获又该如何处理呢?
1、多个异常分别处理
2、多个异常一次捕获,多次处理
3、多个异常一次捕获一次处理
*/
/*
子父类的异常:
- 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
- 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
*/
public class Fu {
public void show01() throws NullPointerException,IndexOutOfBoundsException{};
public void show02() throws NullPointerException,IndexOutOfBoundsException{};
public void show03() throws NullPointerException,IndexOutOfBoundsException{};
public void show04(){};
}
class Zi extends Fu{
//子类重写父类方法时,抛出父类异常的子类
public void show01() throws NullPointerException,IndexOutOfBoundsException{};
//子类重写父类方法时,抛出父类异常的子类
public void show02() throws ArrayIndexOutOfBoundsException{};
//子类重写父类方法时,不抛出异常
public void show03(){};
//父类没有抛出异常,子类重写父类方法时也不可抛出异常
public void show04(){
try {
throw new Exception("编译期运行异常");
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*
自定义异常类:
java提供的异常类,不够我们使用,需要自己定义一些异常类
格式:
public class xxxException extends Exception | RuntimeException{
添加一个空参数的构造方法
添加一个带异常信息的构造方法
}
注意:
1、自定义异常类一般都是以Exception结尾,说明该类是一个异常类
2、自定义异常类,必须继承Exception或者RuntimeException
继承Exception:那么自定义的异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try...catch
继承RuntimeException:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)
*/
并发与并行
并发:程序交替执行
并行:程序同时执行
线程与进程:
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个进程可以包含多个线程
线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aWnAExKu-1636071520936)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210225172912158.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OrmZJpzv-1636071520937)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210228190135138.png)]
public static void main(String[] args){
MyThead mt = new MyThead();
mt.run();
mt.start();
}
/*
注意:
如果调用mt.run()方法执行,此时是单线程执行,是由main方法调用的run()方法
而mt.start()方法则不同,调用该方法可以重新开辟一个栈空间用来执行run()方法,因此此时是多线程执行
*/
/*
构造方法:
public Thread():分配一个新的线程对象
public Thread(String name):分配一个指定名字的新的线程对象
public Thread(Runnable target):分配一个带有指定目标新的线程对象
public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字
*/
/*
常用方法:
public String getName():获取当前线程名称
public void start():导致此线程开始执行;Java虚拟机调用此线程的run方法
public void run():此线程要执行的任务在此处定义代码
public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)
public static Thread currentThread():返回对当前正在执行的线程对象的引用
*/
/*
创建线程的方法:
1、继承Thread类方式
2、实现Runnable接口方式
实现步骤:
1、创建一个Runnable接口的实现类
2、在实现类中重写Runnable接口的run方法,设置线程任务
3、创建一个Runnable接口的实现类对象
4、创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5、调用Thread类中的start方法,开启新的线程执行run方法
*/
/*
实现Runnable接口创建多线程程序的好处:
1、避免了单继承的局限性
一个类只能继承一个类,类继承了Thread类就不能继承其他的类
实现了Runnable接口,还可以继承其他的类,实现其他的接口
2、增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:用来开启新线程
*/
public class RunnableImpl implements Runnable {
private static int ticket = 100;
//定义一个锁对象,利用synchronized来进行线程同步
Object obj = new Object();
@Override
public void run() {
while(true){
//这是第一种方法,利用锁对象,将共享资源同步
/*synchronized (obj){
if (ticket > 0){
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "号票");
ticket--;
}
else{
break;
}
}*/
payTicket();
}
}
//第二种,使用同步代码块,
public synchronized void payTicket(){
// synchronized (this){
if (ticket > 0){
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "号票");
ticket--;
}
// }
}
/*
第三种,使用静态同步代码块,此时ticket也必须是静态变量
静态的同步对象就不嫩是this了,this是创建对象之后产生的,静态方法优于对象
静态方法的锁对象是本类的class属性--》class文件对象(反射)
* */
public static /*synchronized*/ void payTicketStatic(){
synchronized (RunnableImpl.class){
if (ticket > 0){
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "号票");
ticket--;
}
}
}
}
public class Demo06Ticket {
public static void main(String[] args) {
RunnableImpl able = new RunnableImpl();
Thread t1 = new Thread(able);
Thread t2 = new Thread(able);
Thread t3 = new Thread(able);
t1.start();
t2.start();
t3.start();
}
}
/*
解决线程安全问题的第三种方案:Lock锁
java.util.concurrent.locks.Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作
Lock接口中的方法:
void lock() 获取锁
void unlock() 释放锁
java.util.concurrent.locks.ReentrantLock implements Lock接口
使用步骤:
1、在成员位置创建一个ReentrantLock对象
2、在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
3、在可能会出现安全问题的代码后调用Lock接口中的方法unlock获取锁
*/
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class RunnableLock implements Runnable{
private int ticket = 100;
//创建一个ReentrantLock锁对象
Lock l = new ReentrantLock();
@Override
public void run() {
while (true){
l.lock();
if (ticket > 0){
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "号票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
l.unlock();
}
}
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7WZQLWFI-1636071520938)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210302135306251.png)]
/*
等待唤醒案例:线程之间的通信
创建一个顾客线程:告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITTING状态(无限等待)
创建一个老板线程:花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
注意:
顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
同步使用的锁对象必须保证唯一
只有锁对象才能调用wait和notify方法
Object类中的方法
void wait() 在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待。
void notify() 唤醒在此对象监视器上等待的单个线程
会继续执行wait之后的代码
* */
public class Demo06WaitAndNotify {
public static void main(String[] args) {
//创建一个锁对象,保证唯一
Object obj = new Object();
//创建一个顾客线程
new Thread(){
@Override
public void run() {
//必须保证线程同步,保证等待和唤醒的线程只能有一个执行
synchronized (obj){
System.out.println("顾客付钱给老板等待老板的包子");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后的代码
System.out.println("顾客开始吃包子");
}
}
}.start();
new Thread(){
@Override
public void run() {
//设置做包子的时间
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//必须保证线程同步,保证等待和唤醒的线程只能有一个执行
synchronized (obj){
System.out.println("老板5秒钟之后做好包子给顾客");
obj.notify();
}
}
}.start();
}
}
/*
进入到TimeWaiting(计时等待)有两种方式
1、使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2、使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
*/
/*
调用wait和notify方法需要注意的细节
1、wait方法和notify方法必须要由同一个锁对象调用
2、wait方法和notify方法式属于Object类。
3、wait方法与notify方法必须要在同步代码块或者式同步函数中使用。
*/
线程池:容器——>集合(ArrayList,HashSet,LinkedList,HashMap)
线程池的原理:
当程序第一启动的时候,创建多个线程,保存到一个集合中
当我们想要使用线程的时候,就可以从集合中去出来线程使用
Thread t = list.remove(0);
Thread t = linked.removeFirst();
当我们使用完线程,需要将线程归还给线程池
list.add(t);
linked.addLast(t);
在JDK1.5之后,JDK内置了线程池,我们可以直接使用;
/*
线程池:JDK1.5之后提供的
java.util.concurrent.Executors:线程池的工厂类,用来生产线程池
Executors类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
参数:
int nThreads:创建线程池中包含的线程数量
返回值:
ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
java.util.concurrent.ExecutorService:线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task) 提交一个 Runnable 任务用于执行
关闭/销毁线程的方法
void shutdown()
线程池的使用步骤:
1、使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定数量的线程池
2、创建一个类,实现Runnable接口,重写run方法,设置线程任务
3、调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
*/
public class Demo06ThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
// MyThreadImpl mti = new MyThreadImpl();
//线程池会一直开启,使用完了之后,会自动把线程归还给线程池,线程可以继续使用
// executorService.submit(mti);
executorService.submit(new MyThreadImpl());
executorService.submit(new MyThreadImpl());
executorService.submit(new MyThreadImpl());
}
}
public class MyThreadImpl implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+"这是利用线程池设置的线程任务");
}
}
只要能获得到结果,谁去做的,怎么做的都不重要,重视的是结果
/*
使用Runnable接口的方式实现多线程程序和使用lambda表达式
*/
public class Demo06Runnable {
public static void main(String[] args) {
RunnableImpl able = new RunnableImpl();
new Thread(able).start();
Runnable a = new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"设置的线程任务");
}
};
new Thread(a).start();
//lambda表达式简化代码
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"设置的线程任务");
}).start();
}
}
/*
lambda表达式的标准格式:
由三部分组成:
a、一些参数
b、一个箭头
c、一段代码
格式:
(参数列表) -> {一些重写方法的代码}
*/
public class Demo06Cook {
public static void main(String[] args) {
//调用invokeCook方法,参数是Cook接口的匿名内部类对象
invokeCook(new Cook() {
@Override
public void makeFood() {
System.out.println("吃饭了");
}
});
//使用lambda表达式,简化匿名内部类的书写
invokeCook(()->{
System.out.println("吃屎了");
});
}
//定义一个方法,参数传递Cook接口,方法内部调用Cook接口中的方法makeFood
public static void invokeCook(Cook cook){
cook.makeFood();
}
}
//带参数的lambda表达式
public class Demo06Calculator {
public static void main(String[] args) {
//使用匿名内部类调用的方式
/*mycalc(10, 20, new Calculator() {
@Override
public int calc(int a, int b) {
return a+b;
}
});
*/
//使用lambda表达式
mycalc(3, 4, (int a, int b)->{
return a+b;
});
}
public static void mycalc(int a, int b, Calculator ca){ //这里的Calculator是自己设置的接口,并且定义了一个calc抽象方法
int sum = ca.calc(a,b);
System.out.println(sum);
}
}
/*
lambda表达式:可推导,可以省略
凡是根据上下文推导出来的内容,可以省略不写
可以省略的内容:
1、(参数列表):括号中参数列表的数据类型,可以省略不写
2、(参数列表):括号中的参数如果只有一个,那么类型和()都可以省略
3、(一些代码):如果()中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)
注意:要省略{},return,分号必须一起省略
*/
/*
lambda的语法简洁,但是仍有需要注意的地方:
1、使用lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
无论是JDK内置的Runnable、Comparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。
2、使用Lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为lambda对应的而接口类型,才能使用Lambda作为该接口的实例。
备注:有且仅有一个抽象方法的接口,称为“函数式接口”。
*/
/*
java.io.File类
文件和目录路径名的抽象表示形式。
java把电脑中的文件和文件夹(目录)封装为一个File类,我们可以使用File类对文件和文件夹进行操作
我们可以使用File类的方法
创建一个文件/文件夹
删除文件/文件夹
获取文件/文件夹
判断文件/文件夹是否存在
对文件夹进行遍历
获取文件的大小
File类是一个与系统无关的类,任何的操作系统都可以使用这个类中的方法
重点:记住三个单词
file:文件
directory:文件夹/目录
path:路径
*/
import java.io.File;
public class Demo07File {
/*
static String pathSeparator 与系统有关的路径分隔符,为了方便,他被表示为一个字符串
static char pathSeparatorChar 与系统有关的路径分隔符
static String separator 与系统有关的默认名称分隔符,
static char separatorChar 与系统有关的默认名称分隔符
*/
public static void main(String[] args) {
String pathSeparator = File.pathSeparator;
System.out.println(pathSeparator);
char pathSeparatorChar = File.pathSeparatorChar;
System.out.println(pathSeparatorChar);
String separator = File.separator;
System.out.println(separator);
char separatorChar = File.separatorChar;
System.out.println(separatorChar);
}
}
/*
File类的构造方法有四个:可以查阅API文档找到其定义和使用方法
*/
import java.io.File;
public class Demo07FileMethod {
/*
常用方法:
public String getAbsolutePath():返回此File的绝对路径名字符串
public String getPath():将此File转换为路径名字符串
public String getName():返回由此File表示的文件或目录的名称
public long length():返回由此File表示的文件的长度(文件的大小),以字节为单位
*/
public static void main(String[] args) {
File file = new File("a.txt");
String absolutePath = file.getAbsolutePath();
System.out.println(absolutePath);//D:\idea\InterlliJ IDEA project\exercisedemo\a.txt
System.out.println("========================");
String path = file.getPath();
System.out.println(path);//a.txt
System.out.println("========================");
String name = file.getName();
System.out.println(name);//a.txt
System.out.println("========================");
long length = file.length();
System.out.println(length);//0
}
}
/*
判断功能的方法:
public boolean exists():此File表示的文件或者目录是否实际存在
public boolean isDirectory:此File表示的是否为目录
public boolean isFile:此File表示的是否为文件
*/
/*
File的创建和删除方法:
public boolean createNewFile():当且仅当具有该名称的文件尚不存在时,创建一个新的空文件;
public boolean delete():删除由此File表示的文件或者目录
public boolean mkdir():创建由此File表示的目录(创建单级文件夹)
public boolean mkdirs():创建由此File表示的目录,包括任何必需但不存在的父目录(创建单级或多级文件夹)
*/
/*
File类遍历(文件夹)目录功能:
public String[] list():返回一个String数组,表示该File目录中的所有子文件或目录
public File[] listFile():返回一个File数组,表示该File目录中的所有的子文件或目录
*/
分类:
注意事项:
package com.hrg.demo.day08digui;
import java.io.File;
/*
* 遍历目录文件及其子文件目录(只要以.txt结尾的)
*
* 我们可以使用过滤器来实现
* 在File类中有两个和ListFiles重载的方法,方法的参数传递就是过滤器
* File[] listFiles(FileFilter filter)
* java.io.FileFilter接口:用于抽象路径名(File对象)的过滤器
* 作用:用来过滤文件(File对象)
* 抽象方法:用来过滤文件的方法
* boolean accept(File pathname) 测试指定抽象路径名是否应该包含在某个路径名列表中
* 参数:
* File pathname:使用listFiles方法遍历目录,得到的一个文件对象
*
* File[] listFiles(FilenameFilter filter)
* java.io.FilenameFilter接口:实现此接口的类实例可用于过滤器文件名
* 作用:用来过滤文件名称
* 抽象方法:用来过滤文件的方法
* boolean accept(File dir, String name) 测试指定文件是否应该包含在某一文件列表中
* 参数:
* File dir:构造方法中传递的被遍历的目录
* String name:使用ListFiles方法遍历目录,获取的每一个文件/文件夹的名称
*
* 注意:
* 两个过滤器接口是没有实现类的,需要我们自己写实现类,重写过滤的方法accept,在方法中自己定义过滤的规则
*
* */
public class Demo08Filter {
public static void main(String[] args) {
File file = new File("D:\\testforidea");
getAllFile(file);
}
/*
定义一个方法,参数传递File类型的目录
方法中对目录进行遍历
*/
public static void getAllFile(File dir){
// System.out.println(dir);
File[] files = dir.listFiles(new FileFilterImpl());
for (File file : files) {
if (!file.isDirectory()){
System.out.println(file);
}
else {
getAllFile(file);
}
}
}
}
//过滤器的实现类
public class FileFilterImpl implements FileFilter {
@Override
public boolean accept(File pathname) {
if (pathname.isDirectory()){
return true;
}
return pathname.toString().toLowerCase().endsWith(".txt");
}
}
概念:
i: input
o:output
流:数据(字符,字节) 1个字符=2个字节 1个字节=8个二进制位
输入:把硬盘中的数据读取到内存中使用
输出:把内存中数据写入到硬盘中保护
输入流 | 输出流 | |
---|---|---|
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
import java.io.FileOutputStream;
import java.io.IOException;
/*
java.io.OutputStream:此抽象类是表示输出字节流的所有类的超类
定义了一些子类共性的成员方法:
public void close(): 关闭此输出流并释放与此流相关联的任何系统资源
public void flush(): 刷新此输出并强制任何缓冲的字节被写出
public void write(byte[] b): 将b.length字节从指定的字节数组写入此输处流
public void write(byte[] b,int off,int len): 从指定的字节数组写入len字节,从偏移量off开始输出到此输出流
public abstract void write(int b): 将指定的字节输出流
java.io.FileOutputStream extends OutputStream
FileOutputStream: 文件字节输出流
作用:把内存中的数据写入到硬盘的文件中
构造方法:
FileOutputStream(String name) 创建一个向具有指定文件中写入数据的输出文件夹
FileOutputStream(File file) 创建一个向指定File对象表示的文件中写入数据的文件输出流。
参数:
String name : 目的地是一个文件的路径
File file :目的地是一个文件
构造方法的作用:
1、创建一个FileOutputStream对象
2、会根据构造方法中传递的文件/文件路径,创建一个空的文件
3、会把FileOutputStream对象指向创建好的文件
写入数据的原理(内存——>硬盘)
Java程序-->JVM(Java虚拟机)-->OS(操作系统)-->OS调用写数据的方法-->把数据写入到文件中
字节输出流的使用步骤:
1、创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
2、调用FileOutputStream对象中的方法write,把数据写入到文件中
3、释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提供程序的效率)
*/
public class Demo09OutputStream {
public static void main(String[] args) throws IOException {
//1、创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
FileOutputStream fos = new FileOutputStream("D:\\testforidea\\c.txt");
//2、调用FileOutputStream对象中的方法write,把数据写入到文件中
fos.write(97);
//3、释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提供程序的效率)
fos.close();
}
}
/*
一次写多个字节的方法:
*/
public class Demo09OutputStreamMore {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("D:\\testforidea\\e.txt");
/*
public void write(byte[] b): 将b.length字节从指定的字节数组写入此输出流
一次写多个字节:
如果写的第一个字节是正数(0-127),那么显示的时候会查询ASCII码表
如果写的第一个字节是负数,那么第一个字节会和第二个字节,两个字节组成一个中文显示,查询系统默认码表(GBK)
*/
//byte[] bytes = {49, 48, 48};
//byte[] bytes = {-65, -49, -48, 48}; //-65和-49会组成一个中文,-48和48会组成一个中文
byte[] bytes = {69, 78, 79, 76, 67, 66};
/*
public void write(byte[] b,int off, int len): 将字节数组的部分写入此输出流
off:byte数组的开始位置
len:从off位置开始多长
*/
fos.write(bytes,1,2);
byte[] bytes1 = "你好".getBytes();
/*
在GBK中,中文是两个字节来表示
在UTF-8中,中文是用三个字节来表示
*/
System.out.println(Arrays.toString(bytes1));
fos.write(bytes1);
fos.close();
}
}
/*
文件按数据的追加/续写:使用两个参数的构造方法
FileOutputStream(String name, boolean append) 创建一个向具有指定name的文件中写入数据的输出文件流
FileOutputStream(File file, boolean append) 创建一个向指定File对象表示的文件中写入数据的文件输出流
参数:
String name,File file:写入数据的目的地
boolean append: 追加写开关
true:创建对象不会覆盖原文件,继续在文件的末尾追加写数据
false:创建一个新文件,覆盖源文件
文件中写入的数据进行换行:
不同系统的换行符:
Windows:\r\n
Linux:/n
mac:/n
*/
public class Demo09OutputStreamAdd {
public static void main(String[] args) throws IOException {
/*
当开启true了之后,上一次程序运行的结果就一直保留在里边了,但是如果重新置为false的话,就会覆盖源文件
*/
FileOutputStream fos = new FileOutputStream("D:\\testforidea\\d.txt",true);
byte[] bytes = "你好啊".getBytes();
for (int i = 0; i < 10; i++) {
fos.write(bytes);
fos.write("\r\n".getBytes());
}
byte[] bytes1 = {47, 67, 78, 90, 12};
fos.write(bytes1);
fos.close();
}
}
package com.hrg.demo.day09IO;
import java.io.FileInputStream;
import java.io.IOException;
/*
java.io.InputStream:字节输入流
此抽象类是表示输入流的所有类的超类。
定义了所有子类共性的方法:
int read() 从输入流中读取数据的下一个字节
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
void close() 关闭此输入流并释放与该流关联的所有系统资源。
java.io.FileInputStream extends InputStream
FileInputStream: 文件字节输入流
作用:把硬盘文件中的数据,读取到内存中使用
构造方法:
FileInputStream(String name)
FileinputStream(File file)
参数:读取文件的数据源
String name:文件的路径
File file:文件
构造方法的作用:
1、会创建一个FileInputStream对象
2、会把FileInputStream对象指定构造方法中要读取的文件
读取数据的原理(硬盘-->内存)
java程序-->JVM-->OS-->OS读取数据的方法-->读取文件
字节输入流的使用步骤:
1、创建FileInputStream对象,构造方法中绑定主要读取的数据源
2、使用FileInputStream对象中的方法read,读取文件
3、释放资源
*/
public class Demo09InputStream {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\testforidea\\c.txt");
/*
当继续往下读的时候,下一次读就会读取下一个字节,而如果没有字节了,就返回-1
*/
/*int one = fis.read();
System.out.println(one);
int two = fis.read();
System.out.println(two);
int three = fis.read();
System.out.println(three);
int four = fis.read();
System.out.println(four);*/
/*
这里必须要用一个变量(len)来接收fis.read,不能直接写成
while(fis.read() != -1){
System.out.println(fis.read());
}
这样会导致最后的结果出问题
*/
int len = 0;
while ((len=fis.read()) != -1){
System.out.println(len);
System.out.println((char) len);
}
fis.close();
}
}
/*
字节输入流一次读取多个字节的方法:
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储到缓冲区数组b中
明确两件事情:
1、方法的参数byte[]的作用
起到缓冲作用,存储每次读取到的多个字节
数组的长度一般定义为1024(1kb)或者1024的整数倍
2、方法的返回值int是什么
每次读取的有效字节个数
String类的构造方法
String(byte[] bytes):把字节数组转换为字符串
String(byte[] bytes,int offset,int length):把字节数组的一部分转换为字符串,offset:数组索引的开始位置
*/
public class Demo09InputStreamMore {
public static void main(String[] args) throws IOException {
//创建FileInputStream对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("D:\\testforidea\\c.txt");
//使用FileInputStream对象中的方法read读取文件
/*byte[] bytes = new byte[2];
int len = fis.read(bytes);
System.out.println(len);
// System.out.println(Arrays.toString(bytes));
System.out.println(new String(bytes));
len = fis.read(bytes);
System.out.println(new String(bytes));
System.out.println(len);
len = fis.read(bytes);
System.out.println(new String(bytes));
System.out.println(len);*/
byte[] bytes = new byte[1024];
int len = 0;
while ((len = fis.read(bytes)) != -1){
System.out.println(new String(bytes,0,len));
}
//释放资源
fis.close();
}
}
//复制文件
/*
文件复制练习:一读一写
明确:
数据源:c:\\xxx.txt
数据目的地:d:\\xxx.txt
文件复制的步骤:
1、创建一个字节输入流对象,构造方法中绑定要读取的数据源
2、创建一个字节输出流对象,构造方法中绑定要写入的目的地
3、使用字节输入流对象中的方法read读取文件
4、使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
5、释放资源
*/
public class Demo09CopyFile {
public static void main(String[] args) throws IOException {
//1、创建一个字节输入流对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("d:\\testforidea\\c.txt");
//2、创建一个字节输出流对象,构造方法中绑定要写入的目的地
FileOutputStream fos = new FileOutputStream("d:\\testforidea\\a\\a.txt");
/*//一次读入一个字节的方式
int len = 0;
while ((len = fis.read()) != -1){
fos.write(len);
}*/
//优化:使用数组缓冲读取多个字节,写入多个字节的方式
byte[] bytes = new byte[1024];
//使用字节输入流对象中的方法read读取文件
int len = 0;
while ((len = fis.read(bytes)) != -1){
fos.write(bytes,0,len);
}
//释放资源,先释放写的资源,再释放读的资源,如果写完了,那么一定是读完了
fos.close();
fis.close();
}
}
//JDK7
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo10JDK7 {
/*
JDK7新特性:
在try的后边可以增加一个(),在括号中可以定义流对象
那么这个流对象的作用域就在try中有效
try中的代码执行完毕,会自动把流对象释放,不用写finally
格式:
try(定义流对象;定义流对象.....){
可能会产生异常的代码块
}catch(异常类变量 变量名){
异常的处理逻辑
}
* */
public static void main(String[] args) {
try(//1、创建一个字节输入流对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("d:\\testforidea\\c.txt");
//2、创建一个字节输出流对象,构造方法中绑定要写入的目的地
FileOutputStream fos = new FileOutputStream("d:\\testforidea\\a\\a.txt");){
//一次读入一个字节的方式
int len = 0;
while ((len = fis.read()) != -1){
fos.write(len);
}
}catch (IOException e){
System.out.println(e);
}
}
}
//JDK9
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo11JDK9 {
/*
JDK9的新特性:
try的前面可以定义流对象
在try后边的()中可以直接引入流对象的名称(变量名)
在try代码执行完毕之后,流对象也可以释放掉,不用写finally
格式:
A a = new A();
B b = new B();
try(a;b){
}catch(){
}
* */
public static void main(String[] args) throws FileNotFoundException {
//1、创建一个字节输入流对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("d:\\testforidea\\c.txt");
//2、创建一个字节输出流对象,构造方法中绑定要写入的目的地
FileOutputStream fos = new FileOutputStream("d:\\testforidea\\a\\a.txt");
try(fis;fos){
//一次读入一个字节的方式
int len = 0;
while ((len = fis.read()) != -1){
fos.write(len);
}
}catch (IOException e){
System.out.println(e);
}
}
}
/*java.util.Properties集合 extends Hashtable implements Map
Properties 类表示一个持久的属性集。Properties 可保存在流中或从流中加载。
Properties 集合是一个唯一和IO流相结合的集合
可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
属性列表中每个键及其对应值都是一个字符串
Properties集合是一个双列集合,key和value默认都是字符串*/
public static void main(String[] args) {
/*
使用Properties集合存储数据,遍历取出Properties集合中的数据
Properties集合是一个双列集合,key和value默认都是字符串
Properties集合有一些操作字符串的特有方法
Object setProperty(String key, String value) 调用Hashtable的方法put。
String getProperty(String key) 通过key找到value值,此方法相当于Map集合中的get(key)方法
set stringPropertyNames() 返回此属性列表中的键集,其中该键及其对应值是字符串,此方法相当于Map集合中的keySet方法
*/
show01();
}
public static void show01(){
//创建Properties对象
Properties prop = new Properties();
//使用setProperty方法,往集合中添加数据
prop.setProperty("大小姐","167");
prop.setProperty("小矮人","168");
prop.setProperty("毒蘑菇","169");
prop.setProperty("皇后","170");
//使用stringPropertyNames方法将键以set集合的方式取出来
Set<String> str = prop.stringPropertyNames();
for (String s : str){
System.out.print(s + " ");
// prop.getProperty(s);
System.out.println(prop.getProperty(s));
}
}
//store方法
public static void show02() throws IOException {
/*
可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
void store(OutputStream out, String comments)
void store(Writer writer, String comments)
参数:
OutputStream out:字节流输出,不能写中文
Writer writer:字符流输出,可以写中文
String comments:注释,用来解释说明保存的文件是做什么用的,不能使用中文,会产生乱码,默认是Unicode编码,一般使用""空字符串
使用步骤:
1.创建Properties对象,添加数据
2.创建字节流或者字符流对象,在构造方法中绑定输出目的地
3.使用store方法,把集合中的临时数据,持久化写入到硬盘中存储
4.释放资源
*/
//创建Properties对象
Properties prop = new Properties();
//使用setProperty方法,往集合中添加数据
prop.setProperty("大小姐","167");
prop.setProperty("小矮人","168");
prop.setProperty("毒蘑菇","169");
//创建字节流或者字符流对象,在构造方法中绑定输出目的地
FileWriter fw = new FileWriter("demotwo//prop.txt");
//使用store方法,把集合中的临时数据,持久化写入到硬盘中存储
prop.store(fw, "sava data");
//释放资源
fw.close();
}
public static void show03() throws IOException {
//创建Properties对象
Properties prop = new Properties();
//使用setProperty方法,往集合中添加数据
prop.setProperty("大小姐","167");
prop.setProperty("小矮人","168");
prop.setProperty("毒蘑菇","169");
prop.store(new FileOutputStream("demotwo//prop2.txt"),"sava data");
}
//load方法
public static void show04() throws IOException {
/*
可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
void loac(InputStream input)
void load(Reader reader)
参数:
InputStream input:字节输入流,不能读取含有中文的键值对
Reader Reader:字符输入流,能读取含有中文的键值对
使用步骤:
1.创建Properties对象
2.使用Properties集合对象中的方法load读取保存键值对的文件
3.遍历Properties集合
注意:
1.存储键值对的文件中,键与值默认的连接符号可以使用=,或者空格
2.存储键值对的文件中,可以用#进行注释,被注释的键值对不会被读取
3.存储键值对的文件中,默认读取时就是字符串形式,所以不用加上引号
*/
//创建Properties对象
Properties prop = new Properties();
prop.load(new FileReader("demotwo//prop.txt"));
Set<String> set = prop.stringPropertyNames();
for (String key : set){
System.out.print(key + " ");
System.out.println(prop.getProperty(key));
}
}
public static void show05() throws IOException {
Properties prop = new Properties();
prop.load(new FileInputStream("demotwo//prop2.txt"));
Set<String> set = prop.stringPropertyNames();
for (String key : set){
System.out.print(key + " ");
System.out.println(prop.getProperty(key));
}
}
缓冲流,也叫高效流,是对4个基本的FileXxx流的增强,所以也是4个流,按照数据类型分类:
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
public class Demo01OutputStream {
/*
java.io.BufferedOutputStream extends OutputStream
BufferedOutputStream:字节缓冲输出流
继承自父类的共性成员方法:
public void close(): 关闭此输出流并释放与此流相关联的任何系统资源
public void flush(): 刷新此输出并强制任何缓冲的字节被写出
public void write(byte[] b): 将b.length字节从指定的字节数组写入此输处流
public void write(byte[] b,int off,int len): 从指定的字节数组写入len字节,从偏移量off开始输出到此输出流
public abstract void write(int b): 将指定的字节输出流
构造方法:
BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入到指定的 底层输出流。
BufferedOutputStream(OutputStream out, int size) 创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层
参数:
OutputStream:字节输出流
我们可以传递FileOouputStream,缓冲流会给FileOutputStream增加一个缓冲区,提高FileOutputStream的写入效率
int size:指定缓冲流内部缓冲区的大小,不指定默认
使用步骤(重点):
1、创建FileOutputStream对象,构造方法中绑定要输出的目的地
2、创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象效率
3、使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区
4、使用BufferedOutputStream对象中的flush,把内部缓冲区中的数据,刷新到文件中
5、释放资源(会先调用flush方法刷新数据,第4部可以省略)
*/
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("demotwo//a.txt");
BufferedOutputStream bfos = new BufferedOutputStream(fos);
bfos.write("崔崔是猪".getBytes());
// bfos.flush();
bfos.close();
}
}
public class Demo01InputStream {
/*
java.io.BufferedInputStream extends InputStream
BufferedInputStream:字节缓冲输入流
继承自父类的共性成员方法:
int read() 从输入流中读取数据的下一个字节
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
void close() 关闭此输入流并释放与该流关联的所有系统资源。
构造方法:
BufferedInputStream(InputStream input) 创建一个BufferedInputStream并保存其参数,即输入流in,以便将来使用。
BufferedInputStream(InputStream input, int size) 创建具有指定缓冲区大小的BufferedInputStream并保存其参数,即输入流
参数:
InputStream:字节输入流
我们可以传递FileInputStream,缓冲流会给FileInputStream增加一个缓冲区,提高FileInputStream的读取效率
int size:指定缓冲流内部缓冲区的大小,不指定默认
使用步骤(重点):
1、创建FileInputStream对象,构造方法中绑定要输出的目的地
2、创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象效率
3、使用BufferedInputStream对象中的方法read,读取文件
4、释放资源
*/
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("demotwo//a.txt");
BufferedInputStream bfis = new BufferedInputStream(fis);
byte[] bytes = new byte[1024];
int len = 0;
while ((len = bfis.read(bytes)) != -1){
System.out.println(new String(bytes, 0 ,len));
}
/*int len = 0;
while (len != -1){
len = bfis.read();
System.out.println();
}*/
bfis.close();
}
}
public class Demo02BufferedWrite {
/*
java.io.BufferedWriter extends Writer
BufferedWriter:字符缓冲输出流
继承自父类的共性成员方法:
- void write(int c) 写入单个字符
- void writer(char[] cbuf) 写入字符数组
- abstract void write(char[] cbuf, int off , int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
- void write(String str) 写入字符串
- void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
- void flush() 刷新该流的缓冲
- void close() 关闭此流,但要先刷新它
构造方法:
BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲字符输出流
BufferedWriter(Writer out, int sz) 创建一个使用给定大小输出缓冲区的新缓冲字符输出流
参数:
Writer out:字符输出流
我们可以传递FileWriter,缓冲流会给FileWriter增加一个缓冲区,提高FileWriter的写入效率
int sz:指定缓冲区的大小,不写默认大小
特有成员方法:
void newLine() 写入一个行分隔符,会根据不同的操作系统,获取不同的行分隔符
换行:换行符号
使用步骤:
1、创建字符缓冲输出流对象,构造方法中传递字符输出流
2、调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
3、调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件中
4、释放资源
*/
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("demotwo//a.txt"));
for (int i = 0; i < 10; i++) {
bw.write("崔崔是猪");
bw.newLine();
}
bw.close();
}
}
public class Demo02BufferedReader {
/*
这里痛字符输入流一样,所以不做过多赘述
*/
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("demotwo//a.txt"));
// byte[] bytes = new byte[1024];
String line = null;
while ((line = br.readLine()) != null){
System.out.println(line);
}
br.close();
}
}
编码:字符(能看懂的)-> 字节(看不懂的,比如0101…)
解码:字节(看不懂的,比如0101…)-> 字符(能看懂的)
static关键字:静态关键字
静态优先于非静态加载到内存中(静态优先于对象进入到内存中)
被static修饰的成员变量不能被序列化,序列化的都是对象
transient关键字:瞬态关键字
被transient修饰成员变量,不能被序列化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XAZcYoad-1636071520939)(C:\Users\13488\AppData\Roaming\Typora\typora-user-images\image-20210925141539722.png)]
当JVM反序列化对象时,能找到class文件,但是当class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出InvalidClassException异常。
端口号是一个逻辑端口,我们无法直接看到端口号,但是可以通过一些软件或者命令进行查看
当我们使用网络软件时,操作系统就会为网络软件随机分配一个端口号
或者网络软件在打开时和系统要指定的端口号
端口号是由两个字节组成,取值范围是在0~65535之间
注意:
1024之前的端口号不能使用,已经被系统分配给已知的网络软件
网络软件的端口号不能重复
我们使用ip地址加上端口号,就可以保证数据准确无误的发送到对方计算机的指定软件上
常用的端口号:
1、80端口
2、数据库端口{ MySQL:3306 Oracle:1521}
3、Tomcat服务器端口:8080
通信的步骤:
服务器端先启动
服务器端不会主动的请求客户端
必须使用客户端请求服务器端
客户端和服务器端就会建立一个逻辑链接
而这个连接中包含一个对象
这个对象就是IO对象
客户端和服务器端就可以使用
IO对象进行通信
通信的数据不仅仅是字符
所以IO对象是字节流对象
服务器端进行通信时必须明确的两件事情:
1、多个客户端同时和服务器进行交互,服务器必须明确和哪个客户端进行的交互
在服务器端有一个方法,叫accept客户端获取到请求的客户端对象
(Socket s1 = server.accept();)
2、多个客户端同时和服务器进行交互,就需要使用多个IO流对象
服务器是没有IO流的,服务器可以获取到请求的客户端对象Socket,使用每个客户端Socket中提供的IO流和客户端进行交互
(服务器端使用客户端的字节输入流读取客户端发送的数据,服务器端使用客户端的字节输出流给客户端回写数据)
package com.hrg.demo.day12net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/*
Tcp通信的客户端:向服务器端发送连接请求,给服务器发送数据,读取服务器回写的数据
表示客户端的类:
java.net.Socket:此类实现客户端套接字。套接字是两台机器间通信的端点。
套接字:包含了IP地址和端口号的网络单位
构造方法:API文档中查询
Socket(String host, int port) Creates a stream socket and connects it to the specified port number on the named host.
成员方法:
outputStream getOutputStream() 返回此套接字的输出流
InputStream getInputStream() 返回此套接字的输入流
void close() 关闭此套接字
实现步骤:
1、创建一个客户端对象Socket,构造方法绑定服务器的ip地址和端口号
2、使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
3、使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
4、使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
5、使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
6、释放资源(Socket)
注意:
1、客户端和服务器端进行交互,必须使用Socket中提供的网路流, 不能使用自己创建的流对象
2、当我们创建客户端socket的时候,就会去请求服务器和服务器经过3次握手建立连接通路(这时服务器没有启动,那么就会抛出异常;如果服务器已经启动,那么就可以进行交互了)
*/
public class Demo01TcpClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
OutputStream os = socket.getOutputStream();
os.write("你好服务器".getBytes());
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new java.lang.String(bytes, 0, len));
socket.close();
}
}
package com.hrg.demo.day12net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*
Tcp通信的服务器端:接收客户端的请求,读取客户端发送的数据,给客户端回写数据
表示服务器的类:
java.net.ServerSocket:此类实现服务器套接字
构造方法:
ServerSocket(int port) 创建绑定到特定端口的服务器套接字
服务器端必须明确一件事情,必须得知道哪个客户端请求的服务器
所以可以使用accept方法获取到请求的客户端对象Socket
成员方法:
Socket accept() 侦听并接收到此套接字的连接
服务器的实现步骤:
1、创建服务器ServerSocket对象和系统要指定的端口号
2、使用ServerSocket对象中的方法accept,获取到请求的客户端对象Socket
3、使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
4、使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
5、使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream
6、使用网络字节输出流OutputStream对象中的方法write,给客户端回写数据
7、释放资源(Socket,ServerSocket)
*/
public class Demo02TcpServer {
public static void main(String[] args) throws IOException {
ServerSocket soc = new ServerSocket(8888);
Socket socket = soc.accept();
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new java.lang.String(bytes, 0, len));
OutputStream os = socket.getOutputStream();
os.write("收到谢谢".getBytes());
socket.close();
soc.close();
}
}
Tcp通信的文件上传案例
原理:客户端读取本地的文件,把文件上传到服务器,服务器在把上传的文件保存到服务器的硬盘上
步骤:
1、客户端使用本地的字节输入流,读取要上传的文件
2、客户端使用网络字节输出流,把读取到的文件上传到服务器
3、服务器使用网络字节输入流,读取客户端上传的文件
4、服务器使用本地字节输出流,把读取到的文件,保存到服务器的硬盘上
5、服务器使用网络字节输出流,给客户端回写一个“上传成功”
6、客户端使用网络字节输入流,读取服务器回写的数据
7、释放资源
注意 :
客户端和服务器与本地硬盘进行读写,需要使用自己创建的字节流对象(本地流)
客户端和服务端之间进行读写,必须使用Socket中提供的字节流对象(网络流)
package com.hrg.demo.day12net.Fileupload;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/*
文件上传案例的客户端
*/
public class TcpClient {
public static void main(String[] args) throws IOException {
//1、创建一个FileInputStream对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("D:\\photo\\msh.jpg");
//2、创建一个Socket客户端对象,并绑定对应的服务器以及端口号
Socket socket = new Socket("127.0.0.1", 8888);
//3、使用socket对象中的getOutputStream方法,获取网络字节输出流
OutputStream os = socket.getOutputStream();
//4、使用FileInputStream中的read方法读取本地的文件,同时利用OutputStream中的write向服务器发送数据
int len = 0;
byte[] bytes = new byte[1024];
//这里需要注意,read方法当没有东西读入的时候,不会读入-1,而是进入阻塞
while((len = fis.read(bytes)) != -1){
os.write(bytes, 0, len);
}
//这里需要使用shutdownOutput方法,用来中断客户端的输入流,防止客户端与服务器端陷入阻塞状态
socket.shutdownOutput();
//5、读取从服务器端回写的数据
InputStream is = socket.getInputStream();
while((len = is.read(bytes)) != -1){
System.out.println(new String(bytes, 0, len));
}
//释放资源
fis.close();
socket.close();
}
}
package com.hrg.demo.day12net.Fileupload;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/*
文件上传案例的服务器端
*/
public class TcpServer {
public static void main(String[] args) throws IOException {
//1、首先创建一个ServerSocket服务器对象,获取对应的客户端建立连接
ServerSocket serverSocket = new ServerSocket(8888);
//2、通过ServerSocket方法的accept()方法,获取对应客户端
Socket socket = serverSocket.accept();
//3、通过获取到的socket对象,通过网络字节输入流读取数据
InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream("D:\\photo\\otherphoto\\first.jpg");
byte[] bytes = new byte[1024];
int len = 0;
while ((len = is.read(bytes)) != -1){
fos.write(bytes, 0, len);
}
//4、读取完成之后,通过Socket对象的getOutputStream方法给客户端回写数据
OutputStream os = socket.getOutputStream();
os.write("文件上传成功".getBytes());
//5、释放资源
fos.close();
socket.close();
serverSocket.close();
}
}
//B/S模式
//以下是浏览器和服务器进行传递必须要写的三行
//写入HTTP协议响应头,固定写法
out.write("HTTP/1.1 200 OK\r\n".getBytes());
out.write("Content-Type:text/html\r\n".getBytes());
//必须要写入空行,否则浏览器不解析
out.write("\r\n".getBytes());
一个简单的B/案例:
通过浏览器访问项目下面的index.html页面
package com.hrg.demo.day12net.Tcp;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Demo03TcpForBS {
public static void main(String[] args) throws IOException {
//1、创建serverSocket对象
ServerSocket serverSocket = new ServerSocket(8080);
while (true){
//通过ServerSocket对象中accept方法获取socket对象
Socket socket = serverSocket.accept();
new Thread(new Runnable() {
@Override
public void run() {
try{
//通过socket对象得到网络输入流,读取浏览器端传输的数据
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str = br.readLine();
String[] arr = str.split(" ");
String htmlpath = arr[1].substring(1);
System.out.println(htmlpath);
//创建一个FileInputStream本地输入流,构造方法中绑定要读取的html路径
FileInputStream fis = new FileInputStream(htmlpath);
//使用socket中getOutputStream方法获取网络字节输出流
OutputStream out = socket.getOutputStream();
//写入HTTP协议响应头,固定写法
out.write("HTTP/1.1 200 OK\r\n".getBytes());
out.write("Content-Type:text/html\r\n".getBytes());
//必须要写入空行,否则浏览器不解析
out.write("\r\n".getBytes());
//一读一写复制文件,把服务器读取的文件回写到客户端
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes)) != -1){
out.write(bytes, 0, len);
}
//1、释放资源
fis.close();
socket.close();
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
// serverSocket.close();
}
}
函数式接口在java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
@FunctionalInterface
public interface MyFunctionalInterface {
public abstract void method();
}
/*
函数式接口的使用:一般可以作为方法的参数和返回值
*/
public class Demo01test {
//定义一个方法,参数使用函数式接口MyFunctionInterface
public static void show(MyFunctionalInterface myInter){
myInter.method();
}
public static void main(String[] args) {
//调用show方法,方法的参数是一个接口,所以可以传递接口的实现类
show(new MyFunctionalInterfaceImpl());
//调用show方法,方法的参数是一个接口,所以可以传递接口的匿名内部类
show(new MyFunctionalInterface() {
@Override
public void method() {
System.out.println("使用匿名内部类重写接口中的方法");
}
});
//调用show方法,方法的参数是一个函数式接口,所以可以传递lambda表达式
show(() -> {
System.out.println("使用lambda表达式");
});
//简化lambda表达式
show(() -> System.out.println("使用简化后的lambda表达式"));
}
}
函数式编程是种编程方式,它将电脑运算视为函数的计算。(Lambda表达式就属于函数式编程)
package com.hrg.demo.day13FunctionInterface.com.hrg.Lambda;
/*
使用lambda优化日志案例
lambda的特点:延迟加载(减少性能浪费)
Lambda的使用前提,必须存在函数式接口
*/
public class Demo02Lambda {
public static void showLog(int level, MessageBuilder mb){
if (level == 1){
System.out.println(mb.builderMessage());
}
}
public static void main(String[] args) {
//定义三个日志信息
String msg1 = "Hello";
String msg2 = "World";
String msg3 = "Java";
//调用showLog方法,参数MessageBuilder是一个函数式接口,所以可以传递Lambda表达式
showLog(1, () -> {
return msg1 + msg2 + msg3;
});
/*
使用lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中
只有满足条件,日志的等级是1
才会调用接口MessageBuilder中的方法
*/
}
}
//用函数式接口作为参数传递
/*
例如java.lang.Runnable接口就是一个函数式接口,
假设有一个startThread方法使用该接口作为参数,那么就可以使用Lambda进行传参。
这种情况其实和Thread类的构造方法参数为Runnable没有本质区别
*/
public class Demo01Runnable {
//定义一个方法StartThread,方法的参数使用Runnable进行传参
public static void startThread(Runnable run){
//开启一个线程
new Thread(run).start();
}
public static void main(String[] args) {
//调用startThread方法,方法的参数是一个接口,那么我们可以传递这个接口的匿名内部类
startThread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + "线程启动了");
}
});
//调用startThread方法,方法的参数是一个函数式接口,所以传递lambda表达式
startThread(()->{
System.out.println(Thread.currentThread().getName() + "-->" + "线程启动了");
});
//优化lambda表达式
startThread(() -> System.out.println(Thread.currentThread().getName() + "-->" + "线程启动了"));
}
}
类似得,如果用一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。
package com.hrg.demo.day13FunctionInterface.com.hrg.LambdaTest;
import java.util.Arrays;
import java.util.Comparator;
public class Demo02Comparator {
public static Comparator<String> getComparator(){
//方法的返回值是接口,所以可以返回一个匿名内部类
/*return new Comparator() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
};*/
//由于方法的返回值是函数式接口,可以返回lambda表达式
/*return (String o1, String o2)->{
return o1.length() - o2.length();
};*/
//同时,我们可以进一步简化lambda表达式
return (o1, o2) -> o1.length() - o2.length();
}
public static void main(String[] args) {
String[] arr = {"aaaa","bb","ccccc","dddddddd"};
System.out.println(Arrays.toString(arr));
//调用Arrays中sort方法,对字符串数组进行排序
Arrays.sort(arr, getComparator());
//输出排序后的结果
System.out.println(Arrays.toString(arr));
}
}
java.util.function.Supplier接口仅包含也给无参的方法:T get()。用来获取一个泛型参数指定类型的对象数据。
package com.hrg.demo.day13FunctionInterface.com.hrg.Supplier;
import java.util.Arrays;
import java.util.function.Supplier;
/*
求数组元素的最大值
*/
public class Demo02Test {
public static int getMax(Supplier<Integer> sup){
return sup.get();
}
public static void main(String[] args) {
int[] arr = {1, 3, 5, 12, 41, 36, 77, 99};
int max = getMax(() -> {
int temp = arr[0];
for (int i : arr) {
if (temp < i) {
temp = i;
}
}
return temp;
});
System.out.println(max);
}
}
java.util.function.Consumer接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。
package com.hrg.demo.day13FunctionInterface.com.hrg.Comsumer;
import java.util.function.Consumer;
public class Demo01Consumer {
/*
定义一个方法,
方法的参数传递一个字符串,
方法的参数传递Consumer接口,泛型使用String,
可以使用Consumer接口消费字符串
*/
public static void method(String name, Consumer<String> con){
con.accept(name);
}
public static void main(String[] args) {
//定义一个字符串
String name = "崔馨月";
//调用method方法
// method(name, (str) -> System.out.println(str + "是猪"));
method(name, (str) -> {
String restr = new StringBuffer(str).reverse().toString();
System.out.println(restr);
});
}
}
如果一个方法的参数和返回值全都是Consumer类型,那么就可以实现鲜果:消费数据的时候,实现组合。这个方法就是Consumer接口中的default方法andThen。
package com.hrg.demo.day13FunctionInterface.com.hrg.Comsumer;
import java.util.function.Consumer;
public class Demo02andThen {
//使用一下Consumer的默认方法andThen方法
/*
Consumer con1
Consumer con2
String s = "hello"
con1.accept(s)
con2.accept(s)
连接两个接口 再进行消费
con1.andThen(con2).accept(s) 这里注意,谁写前面,谁先对s进行消费
*/
public static void method(String name, Consumer<String> con1, Consumer<String> con2){
// con1.accept(name);
// con2.accept(name);
/*
使用andThen方法,把两个连接在一起,然后一起使用
这种写法与上面的写法所达到的效果一样
*/
con1.andThen(con2).accept(name);
}
public static void main(String[] args) {
method("I am CC's father", (str)->{
System.out.println(str.toUpperCase());
}, (str)->{
System.out.println(str + ",i love she.");
});
}
}
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果,这时可以使用java.util.function.Predicate接口
package com.hrg.demo.day13FunctionInterface.com.hrg.Predicate;
import java.util.function.Predicate;
/*
Predicate接口中包含一个抽象方法:boolean test(T t)。用于条件判断的场景:
*/
public class Demo01PredicateTest {
public static void method(String name, Predicate<String> pre){
System.out.println(pre.test(name));
}
public static void main(String[] args) {
method("hello world", (str)->{
return str.length() > 5;
});
}
}
package com.hrg.demo.day13FunctionInterface.com.hrg.Predicate;
import java.util.function.Predicate;
/*
利用and方法可以判断当多个predicate都为true时,结果才返回true
*/
public class Demo02Predicate_and {
public static boolean getBoolean(String s, Predicate<String> pre1, Predicate<String> pre2){
return pre1.and(pre2).test(s);
}
public static void main(String[] args) {
boolean bb = getBoolean("cc is a pig", (str) -> {
return str.length() > 5;
}, (str) -> {
return str.contains("a");
});
System.out.println(bb);
}
}
package com.hrg.demo.day13FunctionInterface.com.hrg.Predicate;
import java.util.function.Predicate;
//negate:将Predicate所得结果取反
public class Demo03Predicate_negate {
public static void method(String s, Predicate<String> pre){
System.out.println(pre.negate().test(s));
}
public static void main(String[] args) {
method("abc", (str) -> {
return str.length() > 5;
});
}
}
package com.hrg.demo.day13FunctionInterface.com.hrg.Predicate;
import java.util.function.Predicate;
//or:即当有多个Predicate接口预测结果时,当有一个为真即为真
public class Demo04Predicate_or {
public static void method(String s, Predicate<String> pre1, Predicate<String> pre2){
System.out.println(pre1.or(pre2).test(s));
}
public static void main(String[] args) {
method("cuicui",(s)->{
return s.length() > 5;
},(s)->{
return s.contains("a");
});
}
}
package com.hrg.demo.day13FunctionInterface.com.hrg.Predicate;
import java.util.ArrayList;
import java.util.function.Predicate;
/*
集合信息的筛选:
数组当中由多名成员信息,通过predicate接口将符合条件的的信息存储到ArrayList中
*/
public class Demo05PredicateText {
public static ArrayList<String> method(String[] ss, Predicate<String> pre){
ArrayList<String> arrayList = new ArrayList<>();
for (String s : ss) {
if (pre.test(s)){
arrayList.add(s);
}
}
return arrayList;
}
public static void main(String[] args) {
String[] arr = {"迪丽热巴,19", "马儿扎哈,21", "崔馨月,18"};
ArrayList<String> list = method(arr,(s)->{
return Integer.parseInt(s.split(",")[1]) < 20;
});
for (String temp : list) {
System.out.println("姓名:" + temp.split(",")[0] + ",年龄:" + temp.split(",")[1]);
}
}
}
java.util.function.Function
/*
抽象方法:apply
Function接口中最主要的抽象方法:R apply(T t),根据类型T的参数获取类型R的结果
*/
package com.hrg.demo.day13FunctionInterface.com.hrg.Function;
import java.util.function.Function;
public class Demo01FunctionApply {
private static void method(Function<String, Integer> function){
int num = function.apply("10");
System.out.println(num + 20);
}
public static void main(String[] args) {
method((str)->{
return Integer.parseInt(str);
});
}
}
/*
Function接口中的默认方法andThen:用来进行组合操作
需求:
把String类型的"123",转换为Integer类型,把转换后的结果加10
把增加之后的Integer类型的数据,转换为String类型
*/
package com.hrg.demo.day13FunctionInterface.com.hrg.Function;
import java.util.function.Function;
public class Demo02FunctionAndThen {
public static void change(String s, Function<String, Integer> function1, Function<Integer, String> function2){
System.out.println(function1.andThen(function2).apply(s));
// Integer num = function1.apply(s);
// System.out.println(function2.apply(num));
}
public static void main(String[] args) {
String s = "123";
change(s, (ss)->{
return Integer.parseInt(ss) + 10;
},(ss)->{
return Integer.toString(ss);
});
}
}
训练:
/*
String str = "赵丽颖,20"
1、将字符串截取数字年龄部分,得到字符串;
2、将上一步的字符串转换成为int类型的数字;
3、将上一步的int数字累加100,得到结果int数字。
*/
在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
package com.hrg.demo.day14Stream.my.Stream;
import java.util.ArrayList;
import java.util.List;
/*
使用Stream流的方式,遍历集合,对集合中的数据进行过滤
Stream流时JDK1.8之后出现的
关注的是做什么,而不是怎么做
*/
public class Demo02StreamFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("张三丰");
list.add("张九龄");
list.add("赵敏");
list.add("周芷若");
list.add("不周山");
list.add("张六");
//对集合中的元素按条件进行过滤
list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
}
}
这张图展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。
这里的filter、map、skip都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法count执行的时候,整个模型才会按照指定策略进行操作。而这得益于Lambda的延迟执行特性。
Stream(流)是一个来自数据源的元素队列
和以前的Collection操作不同,Stream操作还有两个基础的特征:
获取一个流非常简单,有以下几种常用的方法:
package com.hrg.demo.day14Stream.my.Stream;
import java.util.*;
import java.util.stream.Stream;
public class Demo03GetStream {
/*
获取一个流的方法:
*/
public static void main(String[] args) {
//**第一种:创建一个集合,把集合转换为Stream流
List<String> list = new ArrayList<>();
Stream<String> stream01 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream02 = set.stream();
Map<String,String> map = new HashMap<>();
//获取键,存储到一个set集合种
Set<String> keySet = map.keySet();
Stream<String> stream03 = keySet.stream();
//或者获取值
Collection<String> values = map.values();
Stream<String> stream04 = values.stream();
//或者获取键值对(键与值的映射关系 entrySet)
Set<Map.Entry<String, String>> entries = map.entrySet();
Stream<Map.Entry<String, String>> stream05 = entries.stream();
//**第二种:把数组转换为Stream流
Stream<Integer> stream06 = Stream.of(1, 2, 3, 4, 5, 6);
//可变参数可以传递数组
Integer[] arr = {1, 2, 3, 4, 5};
Stream<Integer> stream07 = Stream.of(arr);
}
}
package com.hrg.demo.day14Stream.my.Stream;
import java.util.stream.Stream;
/*
Stream流中的常用方法_forEach
void forEach(Consumer super T> action);
该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理
Consumer接口是一个消费型的函数式接口,可以传递lambda表达式,消费数据
简单记:
forEach方法,用来遍历流中的数据
是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
*/
public class Demo04Stream_forEach {
public static void main(String[] args) {
//获取一个流
Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七");
//使用Stream流中的方法forEach对流中的数据进行遍历
stream.forEach((s)->{
System.out.println(s);
});
}
}
package com.hrg.demo.day14Stream.my.Stream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/*
Stream流中的常用方法_filter:用于对Stream流中的数据进行过滤
Stream filter predicate);
filter方法的参数predicate是一个函数式接口,可以传递Lambda表达式,对数据进行过滤
Predicate中的抽象方法:
boolean Test(T t);
*/
public class Demo05Stream_Filter {
public static void main(String[] args) {
//创建一个Stream流
String[] str = {"张一山,22","刘德华,33","古天乐,44","胡歌,39","周星驰,60"};
Stream<String> stream = Stream.of(str);
//这里不用传入一个字符串数组,stream.filter会自动对字符串数组中每一个字符串进行判断
Stream<String> stream2 = stream.filter((String ss) -> {
String[] strings = ss.split(",");
if (Integer.parseInt(strings[1]) >= 40) {
return true;
}
return false;
});
stream2.forEach(s -> System.out.println(s));
}
}
如果需要将流中的元素映射到另一个流中,可以使用map方法。
package com.hrg.demo.day14Stream.my.Stream;
import java.util.stream.Stream;
/*
如果需要将流中的元素映射到另一个流中,可以使用map方法。
Stream map(Function super T,? extends R> mapper);
该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
Function中的抽象方法:
R apply(T t);
*/
public class Demo06Stream_Map {
public static void main(String[] args) {
//获取一个String类型的Stream流
Stream<String> stream = Stream.of("1", "2", "3", "4", "5");
//使用map方法,把字符串类型的整数,转换(映射)为Integer类型的整数
Stream<Integer> stream1 = stream.map((String s) -> {
return Integer.parseInt(s);
});
stream1.forEach(s -> System.out.println(s));
}
}
package com.hrg.demo.day14Stream.my.Stream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/*
Stream流中的常用方法_count:用于统计Stream流中元素的个数
Long count();
count方法是一个终结方法,返回值是一个long类型的整数
所以不能再继续调用Stream流中的其他方法了
*/
public class Demo07Stream_Count {
public static void main(String[] args) {
//获取一个Stream流
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
Stream<Integer> stream = list.stream();
//计算流中元素个数
System.out.println(stream.count());
}
}
package com.hrg.demo.day14Stream.my.Stream;
import java.util.stream.Stream;
/*
Stream流中常用的方法_limit:用于截取流中的元素
limit方法可以对流进行截取,只取用前n个。
Stream limit(long maxSize);
参数是一个long类型,如果集合当前长度大于参数则进行截取;否则不进行操作
limit方法是一个延迟方法,只是对流中的元素进行截取,返回的是一个新的流,可以继续调用Stream流中的其他方法
*/
public class Demo08Stream_Limit {
public static void main(String[] args) {
//获取一个Stream流
String[] arr = {"美羊羊","喜羊羊","懒洋洋","沸羊羊","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr);
//使用limit对Stream流中的元素进行截取,只要前4个元素
Stream<String> stream2 = stream.limit(4);
//遍历流
stream2.forEach(s -> System.out.println(s));
}
}
package com.hrg.demo.day14Stream.my.Stream;
import java.util.stream.Stream;
/*
Stream流中常用的方法_skip:用于跳过元素
Stream skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到 一个长度为0的空流;
*/
public class Demo09Stream_Skip {
public static void main(String[] args) {
//获取一个Stream流
String[] arr = {"美羊羊","喜羊羊","懒洋洋","沸羊羊","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr);
//跳过前4个元素
Stream<String> stream1 = stream.skip(4);
//遍历流
stream1.forEach(s -> System.out.println(s));
}
}
package com.hrg.demo.day14Stream.my.Stream;
import java.util.stream.Stream;
/*
Stream流中常用的方法_concat:将两个流合并称为一个流(Stream流中的静态方法)
Stream concat(Stream extends T> a,Stream extends T> b);
*/
public class Demo10Stream_Concat {
public static void main(String[] args) {
//获取一个Stream流
String[] arr = {"美羊羊","喜羊羊","懒洋洋","沸羊羊","灰太狼","红太狼"};
Stream<String> streamA = Stream.of(arr);
//获取一个String类型的Stream流
Stream<String> streamB = Stream.of("1", "2", "3", "4", "5");
Stream<String> concatStream = Stream.concat(streamA, streamB);
concatStream.forEach(s-> System.out.println(s));
}
}
双冒号**’::’**为引用运算符,而他所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么可以通过双冒号来引用该方法作为Lambda的替代者。
package com.hrg.demo.day14Stream.my.reference;
/*
定义一个打印的函数式接口
*/
@FunctionalInterface
public interface Printable {
public abstract void print(String s);
}
package com.hrg.demo.day14Stream.my.reference;
public class Demo01Printable {
public static void printString(Printable p){
p.print("hello world!");
}
public static void main(String[] args) {
//调用该方法
printString(s -> System.out.println(s));
/*
分析:
Lambda表达式的目的,打印参数传递的字符串
把参数s,传递给了System.out对象,调用out对象中的方法println对字符串进行了输出
注意:
1、System.out对象是已经存在
2、println方法也是已经存在
所以我们可以使用方法引用来优化lambda表达式
可以使用System.out方法直接引用println方法
*/
printString(System.out::println);
}
}
package com.hrg.demo.day14Stream.my.reference;
public class MethodRerObject {
//定义一个成员方法,传递字符串,把字符串按照大写输出
public void printUpperCase(String s){
System.out.println(s.toUpperCase());
}
}
package com.hrg.demo.day14Stream.my.reference;
import java.lang.invoke.MethodHandle;
/*
通过对象名引用成员方法
使用前提式对象名是已经存在的,成员方法也是已经存在的
就可以使用对象名引用成员方法
*/
public class Demo02ObjectMethodRer {
//定义一个方法,方法的参数传递Printable接口
public static void printStringAll(Printable p){
p.print("Hello");
}
public static void main(String[] args) {
//调用PrintString方法
printStringAll(s -> System.out.println(s.toUpperCase()));
//进行方法引用
//如果引用成员的是静态方法,可以直接使用类名加成员方法引用
// printStringAll(MethodRerObject::printUpperCase);
/*
如果不是静态方法,可以通过创建对象来使用
注意:如果是静态方法,这里通过创建对象来使用是不正确的
*/
MethodRerObject obj = new MethodRerObject();
printStringAll(obj::printUpperCase);
}
}
package com.hrg.demo.day14Stream.my.reference;
public class Man extends Human {
//子类重写父类方法
public void sayHello(){
System.out.println("hello i'm Man.");
}
public void method(Greetable b){
b.Greet();
}
public void show(){
/*method(()->{
//创建父类
Human h = new Human();
//调用父类的sayHello方法
h.sayHello();
});*/
//因为子父类关系,所以存在一个关键字super,代表父类,所以我们可以直接调用父类的成员方法
method(()->{
// super.sayHello();
this.sayHello();
});
}
public static void main(String[] args) {
new Man().show();
}
}
package com.hrg.demo.day14Stream.my.reference.DemoThis;
@FunctionalInterface
public interface Richable {
public abstract void buy();
}
package com.hrg.demo.day14Stream.my.reference.DemoThis;
public class Husband {
//定义一个买房子的方法
public void buyHouse(){
System.out.println("四川买一栋别墅");
}
//定义一个结婚的方法
public void marry(Richable r){
r.buy();
}
//定义一个高兴的方法
public void soHappy(){
/*marry(()->{
this.buyHouse();
});*/
/*
* 使用方法引用优化Lambda表达式
* this是已经存在的
* 本类的成员方法buyHouse也是已经存在的
* 所以我们可以通过this引用本类方法
* */
marry(this::buyHouse);
}
public static void main(String[] args) {
new Husband().soHappy();
}
}
package com.hrg.demo.day14Stream.my.reference.DemoConstrutor;
/*
定义一个创建Person对象的函数式接口
* */
@FunctionalInterface
public interface PersonBuilder {
//定义一个方法,根据传递的姓名,创建Person对象并返回
public abstract Person builderPerson(String name);
}
package com.hrg.demo.day14Stream.my.reference.DemoConstrutor;
/*
* 类的构造器(构造方法)引用,Person是一个只有name属性的类
* */
public class Demo {
//定义一个方法,参数传递姓名和PersonBuilder接口,方法中通过姓名创建Person对象
public static void printName(String name,PersonBuilder pb){
Person person = pb.builderPerson(name);
System.out.println(person);
}
public static void main(String[] args) {
//调用printName方法,方法的参数PersonBuilder接口是一个函数式接口,可以传递lambda
printName("ccc",(String name)->{
return new Person(name);
});
//使用方法引用
printName("hrg",Person::new);
}
}
package com.hrg.demo.day14Stream.my.reference.DemoArray;
/*
* 数组的构造器引用
* */
public class Demo {
/*
定义一个方法,方法参数传递创建数组的长度和ArrayBuilder接口
方法内部根据传递的长度使用ArrayBuilder中的方法创建数组返回
*/
public static int[] createArray(int length, ArrayBuilder ab){
return ab.builderArray(length);
}
public static void main(String[] args) {
//调用createArray方法,传递数组长度和lambda表达式
int[] array1 = createArray(10, (len) -> {
return new int[len];
});
System.out.println(array1.length);
//方法引用
int[] array2 = createArray(12, int[]::new);
System.out.println(array2.length);
}
}