admin管理员组文章数量:1646252
计算机硬件介绍, 内存:
- 比特 bit:0/1
- 字节 byte:1 byte = 8 bit;
- CPU 从内存中读取数据,而不是从硬盘直接读取;内存比银盘块十倍;内存存储断电就会丢失,容量有限;作用:保存CPU的临时计算结果,保存硬盘部分数据给CPU;
- 访问网页架构:
- B/S架构,browser sever
- C/S架构, client sever
Java语言
JDK-开发工具包
JRE-运行环境
JVM-虚拟机
JDK = JRE + 开发工具集(Java编译工具等)
JRE = JVM + Java SE标准类库API
44.标识符
- 英文字母大小写,_ $等组成
- 不能以数字开头
- 区分大小写,不能包含空格
- public class的名字必须和文件名相同。
45.命名规范
- 包名:所有单词都小写
- 类名、接口名:大驼峰
- 变量名、方法名:小驼峰
- 常量名:所有字母都大写,单词之间下划线连接
48.数据类型
- 基本数据类型:
- 数值型
- 整数类型(byte, short, int, long) 1字节=8byte
- 字符型(char)
- 浮点类型(float, double)
- 布尔型
- 数值型
- 引用数据类型
- 类(字符串在这里)class
- 接口 interface
- 数组 []
54.自动类型转换
- 自动类型提升: byte->short->int->long->float->double
byte, short, char之间作运算时,结果是int - 强制类型转换,使用强转符()
- float类型定义的时候必须加上F
58.String类型
- String属于引用数据类型,字符串
- char 型定义必须放字符, String定义可以为空字符串
- char 与int 相加会变成int, ‘a’+1 = 98,对char使用+会变成数字;
- String 与其他类型相加都为String;
78.运算符
- & ,|会继续执行后面的操作
- &&, ||,有时候会产生短路, 不执行后面
- 三元运算符 (条件)?表达式1:表达式2
79.switch case
- 每次进入case执行之后,还会继续向下接着判断case
- switch 中的表达式只能是byte, short, char, int,枚举,String这六种类型;
- break在siwtch case是可选的
123.指定标签的break;
直接跳出两层循环
label:for(;;){
for(;;){
break label;
}
}
139.数组
- 静态初始化
int[] ids;//声明
ids = new int[]{1,2,3,4};//数组的初始化和数组元素的赋值同时进行
int[] arr = new int[][] {{1,2,3},{1,2},{3,4}};
- 动态初始化
String[] names = new String[5];//数组的初始化和数组元素的赋值分开进行
String[][] arr = new String[3][2];
String[][] arr = new String[3][];//不写列可以,长度和赋值不能同时有
145.内存结构
数组的默认初始化值:
方式一:int[][] arr = new int[4][3];
外层元素初始化值:地址值;
内层元素初始化值:默认值
方式二:int[][] arr = new int[4][];
外层元素初始化null;
内层元素初始值不能调用;
154.数据结构
- 线性表:顺序表(数组),链表,栈, 队列
- 树形结构:二叉树
- 图形结构
156.多维数组
int[] x, y[];
其中x为int类型的一维数组, y为int类型的二维数组;
161.数组的复制
int[] arr1, arr2;
arr1 = new int[]{1,2,3};
arr2 = new int[arr1.length];//使用new创建一个新的数组,进行相同的赋值操作
165.排序算法
- 时间复杂度、空间度杂度、稳定性
- 十大内部排序算法
- 选择排序:直接选择排序,堆排序
- 交换排序:冒泡排序、快速排序O(nlogn)
- 插入排序:直接插入排序、折半插入排序、Shell排序
- 归并排序
- 桶排序
- 基数排序
174.类
- Java类以及类的成员:属性、方法、构造器; 代码块、内部类;
- 三大特征:封装、继承、多态
- 关键字:
177.类以及类的成员
- 属性 = 成员变量 = field = 域、字段
- 方法 = 成员方法 = 函数 = method
181.对象的创建与使用:内存解析
- 堆( Heap) , 此内存区域的唯一目的就是存放
对象实例
, 几乎所有的对象实例都在这里分配内存。 这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。 - 栈(Stack),通常所说的栈( Stack) , 是指虚拟机栈。 虚拟机栈用于存储
局部变量
等。局部变量表存放了编译期可知长度的各种基本数据类型( boolean、 byte、char 、 short 、 int 、 float 、 long 、double) 、 对象引用( reference类型,它不等同于对象本身, 是对象在堆内存的首地址) 。 方法执行完, 自动释放。 - 方法区(Method Area) , 用于存储已被虚拟机加载的
类信息、 常量、 静态变量
、 即时编译器编译后的代码等数据。
196.面向过程与面向对象
- 面向过程:强调的是功能的行为,以函数为最小的单位,考虑怎么做;
- 面向对象:强调具备了功能的对象,以类/对象为最小单位,考虑谁来做;
207.可变个数形参
- 定义:数据类型 … 变量名
- 可以取0,1,2…个形参
- 可变个数形参的方法与本类方法中方法名相同,形参不同的方法之间构成重载;
public void show(String ... strs){
System.out.println("xxxxxx");
}
public void show(String[] strs){
System.out.println("xxxxxx");
}
- 可变个数形参定义和本类中方法名相同,形参类型也相同的数组之间不构成重载,两者重复,不能共存。
- 可变个数形参在声明的时候只能申明在末尾,最多只能申明一个可变形参。
208.值传递机制
- 如果变量是基本数据类型,此时的赋值是变量所保存的数据值;
- 如果变量是引用数据类型, 此时的赋值是变量所保存的数据的地址;
212.char[]字符数组的输出
char[] arr = new char[]{'a', 'b', 'c'};
System.out.println(arr);//s输出abc,而不是地址值
219.匿名对象:在创建的对象没有显式的赋给一个变量名,即为匿名对象;
特点:匿名对象只能调用一次
new Phone();
218.String 引用数据类型
String类型是存储在字符串常量池当中的,而不是堆中。字符串常量是不可修改的。
String s1 = "hello";
test.change(s1);
System.ou.println(s1);//其实打印的是hello,但是String是一个引用类型的值
...
public void change(String s){
s = "hi~~~~";
}
220.封装性的体现
将类的属性私有化Private, 提供公共的Public方法去获得get和设置set私有属性。
221.权限修饰符
- 四种权限:private, 缺省, protected, public
- 四种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
- 修饰类只能使用缺省和public
225.构造器
- 如果没有构造器,系统有一个默认无参数构造器;
- 定义构造器格式: 权限修饰符 类名(形参列表){};
- 一个类中定义了多个构造器,彼此构成重载;
- 一旦我们显示定义了类的构造器,系统就不再提供默认的空参构造器;
- 一个类中至少会有一个构造器
- 属性的赋值过程: 默认初始化->显式初始化->构造器中赋值->对象.方法赋值
229.JavaBean:一种由Java语言写成的可重用组件
- 类是公共的
- 有一个无参的公共构造器
- 有属性,且有对应的get, set方法
230.UML类图
231.局部变量就近原则
232.this调用
- this 可以用来修饰、调用:属性、方法、构造器
- this修饰属性和方法:this理解为当前对象 或 当前正在创建的对象
- 在类的方法中:我们可以使用this.属性或者this.方法,调用当前对象属性或方法,但是通常情况下,选择省略this; 特殊情况:如果方法的形参和类的属性同名时,我们必须显式使用 "this.变量"的方式,表明此变量是属性,而非形参;
- 在类的构造器中, 我们可以使用"this.属性"或"this.方法",调用当前正在创建的对象的属性或方法,但是通常情况下,选择省略this; 特殊情况:如果构造器的形参和类的属性同名时,我们必须显式使用 "this.变量"的方式,表明此变量是属性,而非形参;
- this修饰、调用构造器。一个构造器内部调用另一个构造器, 使用this(); this(形参列表)调用其他的构造器。
- 注意:构造器中不可以使用this调用自己的构造器,造成循环。且不能相互调用形成环;this构造器调用必须放在构造器的首行,且只有一个this调用
236.package关键字
- 为了更好的实现项目中类的管理,提供包的概念
- 使用package声明类所属的包, 放在首行
- 包的命名遵循规范,每"."一次,就是一层文件目录
- 同一个包下不能命名同名的接口和类。不同包可以;
237.MVC设计模式
- 视图模型层 View:主要显示数据。相关工具类、自定义view
- 控制器层 Controller:主要处理业务逻辑。应用界面相关、服务相关。。。
- 数据模型层 Model:主要处理数据。数据对象封装; 数据库操作类; 数据库;
238.import - java.lang包下定义的,则可以省略import.
- 不同包下同名的类使用方法, 至少有一个类需要全类名的方法去写
com.qin.java.Account account = new com.qin.java.Account(xx, xx, xx);
- 使用"xxx.*"表明可以调用xxx包下的所有结构, 但是如果使用的是xxx子包下的结构,则仍需要显式导入子包;
- import static:导入指定类或接口中的静态结构:属性、方法。
243.快捷键
功能 | 快捷键 |
---|---|
1. 自动补全代码或者提示代码 | alt+/ |
2. 快速修复 | ctrl+1 |
3. 批量导包 | ctrl+shift+o |
4. 单行注释 | ctrl+/ |
5. 多行注释 | ctrl+shift+\ |
6. 退回前一个编辑页面 | alt+left |
7. 进入下一个编辑页面 | alt+right |
8. 光标选中指定类,查看继承树结构 | ctrl+t |
9. 格式化代码 | ctrl+shift+f |
10.当前类中搜索指定方法 | ctrl+o |
11.小写变大写 | ctrl+shift+x |
12.大写变小写 | ctrl+shift+y |
13.快速查找,定位到下一个 | ctrl+k |
14.补全对象 | ctrl+1 |
247.文档注释快捷键:alt+shift+j
261.继承 extends
继承格式:
class A extends B{
}//A 是子类, B是父类
一旦子类继承父类之后,子类就获得了父类中声明的结构:属性、方法。私有属性也可以继承,但是由于封装性的作用,可能访问由限制,使得子类不能直接调用父类的结构。
262.继承性的使用
- 一个子类只能有一个父类:单继承
- 一个类可以被多个类继承,一个父类对应多个子类;
- 类可以多层继承[间接父类];子类直接继承的父类叫做直接父类;
- 所有的类都直接或者间接继承于java.lang.Object类
270.方法重写
当创建子类对象之后,通过子类调用子父类中同名同参的方法时,实际执行的是子类重写父类的方法。
- 区分方法的重载与重写
- 重载:重载的方法是在同一个类中的,方法的名称相同,但是参数的个数和类型不同
- 重写:
- 子类和父类的方法是同名同参数,
- 子类重写的方法的权限修饰符是大于等于父类的权限。
- 特殊情况:子类中不能重写父类中声明为private权限的方法。
- 父类被重写的方法的返回值类型是A类型,子类重写的返回值类型可以是A类型和A类的子类。
- 父类被重写的方法的返回值类型是void类型,子类重写的返回值类型void类型。
- 父类被重写的方法的返回值类型是基本数据类型(double),子类重写的返回值类型必须是相同的基本数据类型(double)
- 子类重写的方法抛出的异常类型不大于父类被重写方法抛出的异常类型
- 子类和父类中同名同参数的方法要么都声明为static(不是重写),要么都声明为非static;
274.super关键字:可以理解为父类的
- 用来调用属性、方法、构造器,super.属性、 super.方法
- 当子类的属性或者方法和父类的名称相同时,使用super调用父类
- 可以在子类的构造器中使用super()显式调用父类的构造器,super()必须在首行。
- 在构造器首行没有显示声明调用什么构造器的时候,默认调用super()构造器。
- 在一个类的多个构造器中,至少有一个构造器使用super()调用父类的构造器;创建子类对象时,会一直向上调用父类的构造器,直到java.lang.Object()的空参构造器,正因为调用过父类的构造器,所以可以看到所有父类的结构。
278.多态性
- 对象的多态性:父类的引用指向子类的对象(子类对象赋给父类的引用)
Person p1 = new Man();
Person p2 = new Woman();
-
当调用子类父类都的同名同参的方法时,实际执行的是子类中重写的方法–虚拟方法调用或者动态绑定,不能直接调用子类里面特有的方法、属性,因为构造的是一个父类类型;
-
多态的使用:虚拟方法调用,父类的申明,子类的调用;编辑期调用父类,运行调用子类;
-
多态的使用前提:
- 具有继承关系
- 子类中方法被重写
- 对象的多态性:只适用于方法、不适用于属性(编译运行都看右边)
- 多态是一个运行时行为
-
重载不表现为多态性、重写表现为多态性;
285.向下转型
Person p1 = new Man();
Person p2 = new Woman();//Man 里面的特有的属性和方法不能被调用
- 有了对象的多态之后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时只能调用父类声明的属性和方法,子类特有的属性和方法不能调用。
- 如何调用子类特有的属性和方法?向下转型,使用强制类型转换符。类似基本数据类型的强制类型转换。
Man m1 = (Man)p2;
- isinstanceof判断对象是否是某一个类的子类,方便进行强制向下转型,防止出现异常;
a isinstanceof A:判断对象a是否是类A的实例,如果是,返回true;如果不是,返回false;
//问题代码1
Person p4 = new Person();//建立的类是person,没有加载过子类的属性方法
Man m4 = (Man)p4; //强制转化为子类,会报错
//上中下转换
Object obj = new Woman();//定义上,下
Person p = (Person)obj;//转换为中
多态-内存方法理解,使用Person p1 = new Man();
在内存空间表示为 Person[地址值],但是对象内部包含有Person和Man,可以使用向下转型,让Person变成Man,从而在内存空间中表示为Man[地址值];
//例子
class Base {
int count = 10;
public void display() {
System.out.println(this.count);
}
}
class Sub extends Base {
int count = 20;
public void display() {
System.out.println(this.count);
}
}
public class FieldMethodTest {
public static void main(String[] args) {
Sub s = new Sub();//s是sub类型
System.out.println(s.count);//调用的sub的属性
s.display();//调用的sub的方法
Base b = s;//此时地址值一样,但是类的名称变为了Base
System.out.println(b == s);//true
System.out.println(b.count);//Base的属性10
b.display();//Base地址值,但是由于display方法被重写,所以调用的子类的显示方法20
}
}
- 若子类重写了父类的方法,意味着子类定义的方法彻底覆盖了父类里同名的方法;系统不可能把父类里的方法转移到子类中。编译看左边,运行看右边;
- 对于实例变量则不存在这样的情况,即使子类中定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量。编译运行都看左边;
package com.atguigu.test;
//考查多态的笔试题目:
public class InterviewTest1 {
public static void main(String[] args) {
Base base = new Sub(); //父类引用指向子类对象
base.add(1, 2, 3);//此时父类和子类中都有相同的add函数,构成重写,调用子类的add
Sub s = (Sub)base;//强制向下转换
s.add(1,2,3);//由于子类中的add(int a, int b, int c)不与父类add构成重写,所以直接调用子类的sub_2;
}
}
class Base {
public void add(int a, int... arr) {
System.out.println("base");
}
}
class Sub extends Base {
public void add(int a, int[] arr) {
System.out.println("sub_1");
}
public void add(int a, int b, int c) {
System.out.println("sub_2");
}
}
292.Object类的使用
- Object类是所有java类的根父类
- 如果在类的声明中没有使用extends关键字指明其父类,则默认父类为java.lang.Object类
- Object类中的功能属性、方法具有通用性;
- clone();克隆一个对象
- finalize();在垃圾回收机制回收任何对象之前,垃圾回收自动调用;如果该方法被覆盖,让一个新的变量重新引用该对象,则会重新激活对象;
- getclass()获取当前对象的所属类
- hashcode()当前对象的hash值
- == 与 equals()的区别
- ==使用在基本数据类型和引用数据类型中
- 基本数据类型判断的是数据是否相同
- 引用数u类型判断的是数据地址是否相同
- euqals()方法的使用:是一个方法,需要对象去调用 。只适用于引用数据类型
- 对引用数据类型比较的是对象的地址
- 对String、Date、File、包装类等重写了equals方法,比较的是对象的实体内容。
295.重写equals()
- 1.首先判断两者对象地址是否相同
- 2.判断要比较的对象是否是一样的类
- 3.对比两个对象的每个属性是否相同,需要先强制转型成一样
- 还可在Source里面选择生产equals和hashcode
299.toString()
- 当输出一个对象的引用时, 实际上就是调用的当前对象的toString() --类@哈希地址
- String、Date、File、包装类都重写了Object类中的toString方法,使得调用对象的toString方法时,返回的是"实体内容";
- 自定义类也可以重写toString()方法,当调用此方法时,返回对象的实体内容。
public String toString(){}
- 还可以在Source里面自动生成一个重写的toString(), 默认的toString方法只能把当前类的非静态变量进行输出,静态变量不能输出。
301.单元测试-JUnit测试
- 1.选中当前工程,点击右键,BuildPath-Add libraries-JUnit4
- 2.创建Java类,进行单元测试,此类时public的,提供公共的无参构造器
- 3.此类中声明单元测试方法,方法权限时public,没有返回值,没有形参;
- 4.此单元测试方法上需要声明注解@Test, 并在单元测试类中导入
- 5.左键双击方法名,右键单击测试运行。执行结果异常出现红条。
- 可以直接在项目中加入测试函数,上面注释好@Test,点击加入JUnit包即可。
302.包装类:使得基本数据类型变量具有类的特征
包装类 | 基本数据类型 |
---|---|
Boolean | boolean |
Byte | byte |
Short | short |
Integer | int |
Long | long |
Character | char |
Float | float |
Double | double |
掌握基本数据类型、包装类、String三者之间的相互转换
- 基本数据类型—>包装类:调用包装类的构造器
int num1 = 10;
Integer in1 = new Integer(num1);
- 包装类—>基本数据类型:xxxxValue()
Integer in1 = new Integer(12);
int i1 = in1.intValue();
-
自动装箱与拆箱
- 装箱:基本数据类型变成包装类
- 拆箱:包装类转换为集本数据类型
-
基本数据类型、包装类—>String类型 valueOf(XXX xxx)
Double d1 =new Double(12.4);
String str = String.valueOf(d1);// 浮点型转换为字符串
- String类转换为基本数据类型、包装类 pasreXxx(String s)
String str1 = "123";
int num2 = Integer.parseInt(str1);
307.面试
package com.atguigu.java2;
import org.junit.Test;
/*
* 关于包装类使用的面试题
*
*
*/
public class InterviewTest {
@Test
public void test1() {
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1);// 1.0; 三元运算符会提升类型
}
@Test
public void test2() {
Object o2;
if (true)
o2 = new Integer(1);
else
o2 = new Double(2.0);
System.out.println(o2);// 1;
}
@Test
public void test3() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);//false
//Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],
//保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在
//-128~127范围内时,可以直接使用数组中的元素,不用再去new了。目的:提高效率
Integer m = 1;//自动装箱。[-128,127]
Integer n = 1;
System.out.println(m == n);//true
Integer x = 128;//相当于new了一个Integer对象
Integer y = 128;//相当于new了一个Integer对象
System.out.println(x == y);//false
}
}
314.static关键字
- static可以用来修饰:属性、方法、代码块、内部类
- 使用static修饰属性:静态变量(类变量):同一类中的所有变量共享同一个静态变量;
- 可以使用 “类.静态变量” 的方式去加载。
- 由于类只会加载一次,则静态变量在内存中一直会存在一份,存在方法区的静态域中。
- 静态属性举例:Math.PI、
- 类可以调用静态方法,但是不能调用非静态方法。对象既可以调用静态方法,也可以调用非静态方法。
区别 | 静态方法 | 非静态方法 |
---|---|---|
类 | yes | no |
对象 | yes | yes |
- 静态方法中:只能调用静态的方法和属性
- 非静态方法中:既可以调用非静态的方法和属性,也可以调用静态的方法和属性
- 静态方法内,不能使用this关键字,super关键字;
- 注意:
- 属性是可以被多个对象所共享的,不会随着对象的不同而不同;
- 类中的常量也申明为static
- 操作静态属性的方法,通常设置为static
- 工具类方法中,习惯声明为static的: Math、Arrays、Collections
322.设计模式
设计模式是在大量的实践总结和理论优化之后优选的代码结构、编程风格、以及解决问题的思考方式。
设计模式避免了我们自己再思考和摸索,就像是经典的棋谱,不同的棋局。套路
- 单例设计模式:采取一定的方法保证在整个软件系统中,对某个类只存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让一个类在虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样就不能用new操作符在类的外部产生类的对象,只能调用该类的某一个静态方法以返回类内部创建的对象。
静态方法只能访问类中的静态成员变量,所以指向类内部产生的该类对象的变量也必须定义为静态的
。
模式分类 | 设计模式 |
---|---|
创建型模式 | 共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式 |
结构型模式 | 共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式 |
行为型模式 | 共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介模式、解释器模式 |
- 懒汉式
package com.qin.custom.test;
//调用的时候才创建对象
public class Singleton1 {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);
}
}
class Bank {
//1.私有化类的构造器
private Bank(){
}
// 2.声明当前类对象,没有初始化,对象也必须声明为static
private static Bank instance = null;
//3.声明public,static的返回当前对象的方法
public static Bank getInstance() {
if (instance == null) {
instance = new Bank();
}
return instance;
}
}
- 饿汉式
package com.qin.custom.test;
//开始就把对象造好了
public class Singleton {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);
}
}
class Bank {
//1.私有化类的构造器
private Bank() {
}
// 2.内部创建类的对象,此对象的声明也是静态的
private static Bank instance = new Bank();
//3.提供公共的静态方法,返回类的对象
public static Bank getInstance() {
return instance;
}
}
-
区分饿汉式和懒汉式
饿汉式- 坏处:饿汉式对象加载时间长
- 好处:饿汉式是线程安全的
懒汉式
- 好处:延迟对象加载
- 坏处:目前写法线程不安全
-
单例设计模式-应用场景
- 网站计数器
- 应用程序的日志应用
- 数据库连接池
- 读取配置文件的类
328.代码块
- 代码块的作用:用来初始化类、对象
- 代码块有修饰的话,只能是static, 即只有静态代码块和非静态代码块两种;
- 静态代码块:
- 静态代码块随着类的加载而执行,只执行一次;
- 初始类的信息;
- 静态代码块优先于非静态代码块执行;
- 静态代码块只能调用静态的属性、静态的方法、不能调用非静态的结构
- 非静态代码块:非静态代码块随着对象的创建而执行,
- 每创建一个对象就执行一次;可以在创建对象时,
- 可以对对象的属性进行初始化。
- 非静态代码块可以调用静态属性、静态方法、非静态属性,非静态方法
330.静态代码块、构造器举例
- 代码块先于构造器执行
- 静态代码块在整个先加载,由父及子
package com.atguigu.java3;
//总结:由父及子,静态先行
class Root{
static{
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root(){
super();
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid(){
super();
System.out.println("Mid的无参数的构造器");
}
public Mid(String msg){
//通过this调用同一类中重载的构造器
this();
System.out.println("Mid的带参数构造器,其参数值:"
+ msg);
}
}
class Leaf extends Mid{
static{
System.out.println("Leaf的静态初始化块");
}
{
System.out.println("Leaf的普通初始化块");
}
public Leaf(){
//通过super调用父类中有一个字符串参数的构造器
super("尚硅谷");
System.out.println("Leaf的构造器");
}
}
public class LeafTest{
public static void main(String[] args){
new Leaf(); //输出一
System.out.println();
new Leaf();
}
}
方法一:
Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器
main()方法也是一个静态方法,所以进入main会先调用其他的静态函数
331.属性赋值的执行顺序
- 默认初始化
- 显示初始化/代码块中执行
- 构造器初始化
- 有对象之后,可以通过"对象.属性",或者"对象.方法"的方式进行赋值
332.final关键字
-
final可以用来修饰类、方法、变量
-
final修饰类:则该类不能继承,但是该类创建的对象的某些属性可以改变;
-
final修饰方法:此方法不能被重写
-
final修饰变量:此时的"变量"成为常量
- 可以赋值的位置:显示初始化 final int WIDTH = 0;
- 代码块中初始化;
- 构造器中初始化;多个构造器都需要赋值;
-
static final用来修饰属性:全局常量
341.抽象类abstract
- abstract可以用来修饰类、方法
- abstract类:不能实例化
- abstratc方法:只有方法的声明,没有方法体;抽象方法的所属类必须是抽象类,不能造对象。反之,抽象类中不一定有抽象方法。
- 若子类重写了父类中所有的抽象方法,则此子类也是一个抽象类。重写直接父类、间接父类的所有方法。
343.abstract使用
- abstract不能用来修饰:属性、构造器等结构
- abstract不能用来修饰私有方法、静态方法、final的方法、final的类
345.匿名对象
- 抽象类的匿名子类
- 非匿名的类非匿名的对象
- 非匿名的类匿名的对象
- 匿名子类的匿名对象
package com.atguigu.java;
/*
* 抽象类的匿名子类
*
*/
public class PersonTest {
public static void main(String[] args) {
method(new Student());//匿名对象
Worker worker = new Worker();
method1(worker);//非匿名的类非匿名的对象
method1(new Worker());//非匿名的类匿名的对象
System.out.println("********************");
//创建了一匿名子类的对象:p
Person p = new Person(){
@Override
public void eat() {
System.out.println("吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸");
}
};
method1(p);
System.out.println("********************");
//创建匿名子类的匿名对象
method1(new Person(){
@Override
public void eat() {
System.out.println("吃好吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸新鲜空气");
}
});
}
public static void method1(Person p){
p.eat();
p.breath();
}
public static void method(Student s){
}
}
class Worker extends Person{
@Override
public void eat() {
}
@Override
public void breath() {
}
}
346.模板方法的设计模式
抽象类的应用:模板方法的设计模式
- 例如:重写抽象类的抽象方法,其他功能齐全。
348.接口,一个类可以有多个接口,然后获得多继承的效果。
- 1.接口的使用interface来定义
- 2.接口和类是并列的两个结构
- 3.JDK7之前:只能定义全局常量和抽象方法
- 全局常量:public、static、final
- 抽象方法:public、abstract
- 4.JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法;
- 5.接口中不能定义构造器,意味着接口不能实例化。
- 6.接口通过让类去实现接口:
- 6.1如果实现类覆盖了接口中所有的抽象方法,那么此实现类就可以造对象
- 6.2如果实现类没有覆盖接口中所有的抽象方法,那么该类就只能是一个抽象类
- 7 .可以实现多个接口->弥补了Java单继承的缺陷:
格式:class AA extends BB implements CC,DD,EE - 8.接口和接口之间可以多继承
- 9.接口的使用,体现多态性
351.接口的使用
- 接口的多态性
- 接口实际上就是定义了一种规范
- 在开发中体会面向接口编程。
353.代理模式
354.工厂模式
357.接口 JDK8
- 1.接口中定义的静态方法,在类中实现接口后拿不到,只能通过接口来调用。
- 2.通过实现类的对象可以调用接口中的方法,调用时,仍然调用的是重写后的方法。
- 3.如果子类在没有重写此方法的情况下,
默认是调用的父类的同名同参数的方法-->类优先原则
。 - 4.如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,并且在实现类中没有重写此方法,报错->接口冲突。
- 5.如何在子类或者实现类中调用父类、接口中被重写的方法
调用接口原本的方法:接口名称.super.method()
//调用接口中的默认defualt方法
CompareA.super.method3();
CompareB.super.method3();
359.内部类
- 1.允许将一个类A声明在另一个类B中,则类A就是内部类,类B就是外部类
- 2.内部类的分类:成员内部类(静态、非静态) vs 局部内部类(方法块、代码块、构造器)
- 3.成员内部类:
- 一方面作为外部类的成员:
- 调用外部类的属性、结构
- 可以被四种权限修饰符修饰
- 可以被static修饰
- 类内可以定义属性、方法、构造器等;
- 另一方面:作为类,可以定义属性、方法、构造器等
- 可以被final修饰,表示此类不能被继承
- 可以被abstract修饰
- 一方面作为外部类的成员:
361.如何实例化成员内部类的对象
-
静态成员内部类:
Person.Dog dog = new Person.Dog(); -
非静态成员内部类:首先要创建一个外部对象,通过外部对象创建内部对象
Person p = new Person();
Person.Bird bird = p.new.Bird(); -
重名的时候调用属性
- name 形参的属性
- this.name内部类的属性
- Person.this.name外部类的属性
public class InnerClassTest {
public static void main(String[] args) {
//创建Dog实例(静态的成员内部类):
Person.Dog dog = new Person.Dog();
dog.show();
//创建Bird实例(非静态的成员内部类):
// Person.Bird bird = new Person.Bird();//错误的
Person p = new Person();
Person.Bird bird = p.new Bird();
bird.sing();
System.out.println();
bird.display("黄鹂");
}
}
class Person{
String name = "小明";
int age;
public void eat(){
System.out.println("人:吃饭");
}
//静态成员内部类
static class Dog{
String name;
int age;
public void show(){
System.out.println("卡拉是条狗");
// eat();
}
}
//非静态成员内部类
class Bird{
String name = "杜鹃";
public Bird(){
}
public void sing(){
System.out.println("我是一只小小鸟");
Person.this.eat();//调用外部类的非静态属性
eat();
System.out.println(age);
}
public void display(String name){
System.out.println(name);//方法的形参
System.out.println(this.name);//内部类的属性
System.out.println(Person.this.name);//外部类的属性
}
}
public void method(){
//局部内部类
class AA{
}
}
{
//局部内部类
class BB{
}
}
public Person(){
//局部内部类
class CC{
}
}
}
局部内部类和成员内部类在编译后都会生成字节码文件。
- 成员内部类: 外部类$内部类名.class
- 局部内部类:外部类名$数字 内部类名.class
371.异常概述
- Error:Java虚拟机无法解决的严重问题:JVM系统内部错误,资源耗尽等严重情况,栈溢出,内存溢出;没办法编写针对的代码进行处理。
- Exception:因编程错误或者偶然的外在错误导致的一般性问题;
红色:编译时异常;
蓝色:运行时异常;
java.lang.Throwable
|-----java.lang.Error:一般不编写针对性的代码进行处理。
|-----java.lang.Exception:可以进行异常的处理
|------编译时异常(checked)
|-----IOException
|-----FileNotFoundException
|-----ClassNotFoundException
|------运行时异常(unchecked,RuntimeException)
|-----NullPointerException //空指针异常
|-----ArrayIndexOutOfBoundsException //角标越界
|-----ClassCastException //类型转换异常
|-----NumberFormatException //数值格式异常
|-----InputMismatchException //输入不匹配异常
|-----ArithmeticException //算术异常
372.异常处理方式:
异常处理:抓抛模型
- 过程一:“抛”:程序在正常执行的过程中,一旦出现异常,就会在代码出生成一个异常类的对象,并将此对象抛出,一旦抛出对象,其后的代码就不会执行;
- 过程二:“抓”
- 方式一:try-catch-finally
- 方式二: throws + 异常类型
try{
//可能出现异常的代码
} catch(异常类型1 变量名1){ //一旦异常对象匹配到catch 时,就会进行相应的处理。处理完成就会跳出当前的try/catch结构
//处理异常的方式1
} catch(异常类型2 变量名2){
//处理异常的方式2
}
373.try-catch-finally使用
- finally是可选的。
- 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
- 一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的 try-catch结构(在没有写finally的情况)。继续执行其后的代码
- catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错
- 常用的异常对象处理的方式: ① String getMessage() ② printStackTrace()
- 在try结构中声明的变量,再出了try结构以后,就不能再被调用,属于局部变量
- try-catch-finally结构可以嵌套
- 体会1:使用try-catch-finally处理编译时异常,是得程序在编译时就不再报错,但是运行时仍可能报错。相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
- 体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。
374.finally 使用
- finally是可选的
- finally放在代码中一定会被执行,在return 之前执行;
- 像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动回收的,需要手动进行资源的释放,此时的资源释放就需要声明在finally中。
- 快捷操作:选中需要try-catch的代码块,右键Surround with增加try-catch代码块;
376.throws+异常类型
1."throws + 异常类型"写在方法的声明处,指明此方法执行时,可能会抛出异常类型,一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws异常类型时, 就会被抛出。
开发中如何选择使用try-catch-finally 还是使用throws?
- 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
- 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
379.throw手动抛出异常
throw的是一个异常的对象:throw new MyException(“不能输入负数”);
- 如何自定义异常类?
-
- 继承于现有的异常结构:RuntimeException 、Exception
-
- 提供全局常量:serialVersionUID
-
- 提供重载的构造器
-
408.idea
名称 | 快捷键 |
---|---|
主函数 | psvm 或者 main 加上回车 |
输出 | sout 加上回车,或者"xxx".out加上回车 |
补全对象 | alt+enter |
构造器 | alt+insert |
查找类 | ctrl+n |
查找方法 | ctrl+o |
代码移动 | ctrl+shift+up/dowm |
代码补全提示 | ctrl+space |
继承关系 | F4 |
格式化代码 | ctrl+shift+L |
try-catch | ctrl+shift+win+t |
try-catch… | ctrl+alt+t |
创建测试 | ctrl+shift+t |
416.多线程创建
java.lang.Thread
- 方式一:继承于Thread类
- 1.创建一个继承于Thread类的子类
- 2.重写Thread类的run(),此线程要执行的操作
- 3.创建Thread类的子类对象
- 通过此对象调用start():启动当前线程、调用当前线程的run()
- 问题一:不能通过直接调用run()方法的方法启动线程
- 问题二:不可以让已经start的线程再次启动start
创建Thread类的匿名子类对象:
//创建Thread类的匿名子类对象
new Thread(){
public void run(){
System.out.printlin(Thread.currentThread().getName() + "hello");
}
}.start();
- start():启动当前线程;调用当前线程的run()
- run():通常需要重写Thread类中的方法,将创建的线程要执行的操作声明在此方法中
- currentThread():静态方法,返回当前线程的名字;
- getName():获取当前线程的名字;
- setName():设置当前线程的名字
- yield():释放当前cpu的执行权
- join():在线程A中调用线程B的join(),此时线程A就进入阻塞状态,直到线程B执行完;
- sleep():休息当前线程,单位是毫秒,指定时间内,当前线程是阻塞状态。sleep需要try-catch
420.线程优先级
- 高优先级的线程抢占CPU
- 同优先级线程先进先出,高优先级抢占式策略
- 最大优先级MAX_PRIORITY10,最小MIN_PRIORITY1,NORM_PRIORITY普通优先级5
- 高优先级线程要抢占线程,只是高概率被执行,不是绝对的执行。
422.实现Runnable接口
- 1.创建一个实现了Runnable接口的类
- 2.实现类去实现Runnable中的抽象方法run()
- 3.创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start();
424.比较创建线程的两种方式:优先选择Runnable
- 1.实现的方式没有类的单继承的局限性
- 2.实现的方法更适合用来处理多个线程有共享数据的情况
- 联系:public class Thread implements Runnable
- 相同点:两种方式都需要重写run()
426.基本概念:程序、进程、线程
- 程序:为完成特定任务用某种语言编写的一组指令的集合,即指
一段静态的代码
- 进程:正在运行的一个程序,动态的过程:产生、存在、消亡–生命周期
- 线程:线程作为调度和执行的单位,每一个线程拥有独立的运行栈和程序计数器。一个进程中的多个线程共享相同的内存单元/内存地址空间:它们从同一堆中分配对象,可以访问相同的变量和对象。但是有可能带来安全隐患。
- 并行:多个CPU同时执行多个任务
- 并发:一个CPU同时执行多个任务
428.线程的生命周期
- 新建:一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪:
- 运行
- 阻塞:sleep(long time); join(); 等待同步锁;wait(); suspend()挂起
- 死亡
430.卖票过程中出现重票和错票
- 1.问题: 卖票过程中出现重票和错票
- 2.原因:由于多线程中,当前线程还未操作完,其他线程参与进来了,也操作了车票数量
- 3.解决:当一个线程a在操作车票的时候,其他线程不能参与进来,直到线程a出现阻塞,也不能被改变。
- 4.在Java中:使用同步机制,来解决线程安全问题
- 方式一: 同步代码块
-说明:synchronized(同步监视器){ //需要被同步的代码 }
1.操作共享数据的代码,即为需要被同步的代码;
2.共享数据:多个线程共同操作的数据
3.同步监视器:任何一个类的对象都可以充当锁。要求:多个线程必须要共用同一把锁。
4.好处:解决了线程安全问题;但是相当于是单线程,效率低;
5.在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类(Window.class)充当同步监视器。- 方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步。
1.同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。public synchronized void show(){ //同步代码块 } private static synchronized void show(){ //同步代码块 }
2.非静态的同步方法,同步监视器是:this
3.静态的同步方法,同步监视器是:当前类本身
435.线程安全的单例模式
方式一:
class Bank{
private Bank(){
}
private static Bank instance = null;
private static synchronized Bank getInstance(){
if (instance == null){
//此处可能有多个线程同时进入
instance = new Bank();
}
return instance;
}
}
方式二:
class Bank{
private Bank(){
}
private static Bank instance = null;
private static Bank getInstance() {
synchronized(Bank.class){
if (instance == null) {
//此处可能有多个线程同时进入
instance = new Bank();
}
return instance;
}
}
}
方式三:
class Bank {
private Bank() {
}
private static Bank instance = null;
private static Bank getInstance() {
if (instance == null) {
synchronized (Bank.class) {
if (instance == null) {
//此处可能有多个线程同时进入
instance = new Bank();
}
}
}
return instance;
}
}
436.线程的死锁
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
- 出现死锁后,不会出现异常,所有线程都处于阻塞状态;
437.显示定义同步锁Lock
- 解决线程安全问题的方式三:Lock锁:JDK5.0新特性
- Lock的子类:ReentrantLock对象
synchronized和Lock方式的异同:都可以解决代码同步问题
- synchronized 在执行完相应的代码逻辑后,自动释放同步监视器
- Lock需要手动的启动同步(Lock()), 同时结束同步也需要手动实现
使用顺序:
Lock --> 同步代码块 —>同步方法
439.线程通信
- wait():一旦执行此方法,当前线程就会进入阻塞状态,并释放同步监视器
- notify():一旦执行此方法,就会唤醒被wait()的一个线程。如果有多个线程被wait(),就唤醒优先级高的那个。
- notifyall():唤醒所有的wait();
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while(true){
synchronized(this){
notify();//唤醒在wait()的线程
if (number < 100){
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使得调用wait()方法的线程进入阻塞状态
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
说明:
- wait()、notify()、notifyall()就只能在同步方法或者同步代码块中调用。
- wait()、notify()、notifyall()的调用者就只能是同步方法或者同步代码块的同步监视器。
- wait()、notify()、notifyall()三个方法是定义在java.lang.Object类中
面试题 sleep()和wait()的异同:
- 相同点:一旦执行,都可以使当前的线程进入阻塞状态
- 不同点:
- 两个方法声明的位置不同,Thread类中声明sleep(),Object类中声明wait()
- 调用要求不同:sleep()可以在任何场景下调用, wait()必须在同步代码块中调用
- 关于是否释放同步监视器:如果两个方法都在同步代码块或者同步方法中,sleep()不会释放锁,wait()会释放锁。
442.新建线程的Callable方法
- 相比run()方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助Future Task类, 比如获取返回结果
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此方法需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;//int 装箱
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类对象作为传递到FutureTask构造器中,创建FutureTask对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable的返回值
Object sum = futureTask.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
443.线程池(创建线程的方式)
提前创建好多个线程、放入线程池中,使用的时候直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用。
- 好处:1.提高相应速度; 2.降低资源消耗;3.便于线程管理;
- API:ExecutorService 和 Executors
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);//线程数为10
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
// service1.setCorePoolSize(5);
// service1.setKeepAliveTime(5000, TimeUnit.SECONDS);
// service1.setMaximumPoolSize(5);
//设置线程池的属性
//2.执行指定的线程操作,需要提供实现Runnable接口或者Callable接口
service.execute(new NumberThread());//适合使用Ruunable
service.execute(new NumberThread1());//适合使用Ruunable
// service.submit();//适合使用Callable
//3.关闭连接池
service.shutdown();
}
}
449.String
- String 声明为final,不可被继承
- String实现了Serializable接口:表示字符串是支持序列化的
- String实现了Comparable接口:表示String可以比较大小
- String内部定义了final char[] value 用于存储字符串数据
- 不可变的字符序列
- 字符串常量池是不会存储内容相同的字符串
- 体现:
- 1.当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
- 2.对字符串进行连接操作时,也需要重新指定内存区域赋值
- 3.当调用String的replace方法时,也必须指定新的内存区域赋值
451.String的实例化
- 方式一:通过字面量定义
- 方式二:通过new + 构造器的方式
public class StringTest {
@Test
public void test1(){
//字面量定义,此时的s1和s2是在方法区中的字符串常量池中
String s1 = "JavaEE";
String s2 = "JavaEE";
//通过 new + 构造器的方法,此时s3,s4保存的地址值,是数据在堆空间开辟空间后对应的地址值
String s3 = new String("JavaEE");
String s4 = new String("JavaEE");
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s3 == s4);
}
}
面试题:String s = new String(“abc”)方式创建对象,在内存中创建了几个对象? 答:2个。在堆空间中有new结构,在常量池中有"abc";
452.String拼接
1.常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
2.只要其中有一个是变量,结果就在堆中。
3.如果拼接的结果调用intern()方法,返回值就在常量池中
public void test3(){
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
System.out.println(s3 == s8);//true
}
478.SimpleDateFormat
格式化:日期—>文本
解析:文本----->日期
格式化:
- SimpleDateFormat() :默认的模式和语言环境创建对象
- public SimpleDateFormat(String pattern): 该构造方法可以用参数pattern
指定的格式创建一个对象,该对象调用: - public String format(Date date): 方法格式化时间对象date
解析: public Date parse(String source): 从给定字符串的开始解析文本,以生成
一个日期。解析可能会抛异常。要求字符串必须是SimpleDateFormat识别的格式;通过构造器参数实现;
SimpleDateFormat formater2 = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
481.Calendar日历类的使用
日历类是一个抽象类,实例化方式
1.创建其子类(GregorianCalendar)的对象
483.新的日期API
- 本地日期( LocalDate )、
- 本地时间(LocalTime )、
- 本地日期时间 LocalDateTime )、
- 时区 ZonedDateTime
- 持续时间( Duration )的类
LocalTime time= LocalTime.now();
LocalDate date = LocalDate.now();
484.Instant
时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间戳
名称 | 方法 |
---|---|
now() | 静态方法,返回默认 UTC 时区的 Instant 类的对象 |
ofEpochMilli (long epochMilli) | 静态方法返回在 1970 01 01 00 00 00 基础上加上指定毫秒数之后的 Instant 类的对象 |
atOffset(ZoneOffset offset) | 结合即时的偏移来创建一个OffsetDateTime |
toEpochMilli() | 返回1970 01 01 00 00 00 到当前时间的毫秒数 即为时间戳 |
488.比较器
Java 实现对象排序的方式有两种:
- 自然排序: java.lang.Comparable
- 定制排序: java.util.Comparator
Comparable接口的使用举例:
1.像String包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方法。
2.重写compareTo(obj)的规则:
- 如果当前对象 this 大于形参对象 obj 则返回正整数,
- 如果当前对象 this 小于 形参对象 obj 则返回负整数,
- 如果当前对象 this 等于 形参对象 obj 则返回零
自定义类需要实现Comparable接口,并重写comparaTo方法,说明按照什么进行按照什么进行比较排序的。
重写步骤:
1.判断是否是相同的父类,进行强转
2.将对象的属性进行比较,返回不同的值[正负零]
Comparator:
当元素的类型没有实现 java.lang.Comparable 接口而又不方便修改代码,或者实现了 java.lang.Comparable 接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来 排序 强行对 多个 对象进行 整体 排序的比较。
public void test3(){
String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};
Arrays.sort(arr,new Comparator(){
//按照字符串从大到小的顺序排列
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof String && o2 instanceof String){
String s1 = (String) o1;
String s2 = (String) o2;
return -s1.compareTo(s2);
}
// return 0;
throw new RuntimeException("输入的数据类型不一致");
}
});
System.out.println(Arrays.toString(arr));
}
496.枚举类
类的对象只有有限个,确定的。当需要定义一组常量时,强烈建议使用枚举类。
jdk5.0之前自定义枚举类
jdk5.0之后,使用enum关键字定义枚举类
注意:这里定义属性的时候需要声明为private final。
//自定义枚举类
class Season{
//1.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//2.私有化类的构造器,并给对象属性赋值
private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//3.提供当前枚举类的多个对象:public static final的
public static final Season SPRING = new Season("春天","春暖花开");
public static final Season SUMMER = new Season("夏天","夏日炎炎");
public static final Season AUTUMN = new Season("秋天","秋高气爽");
public static final Season WINTER = new Season("冬天","冰天雪地");
//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//4.其他诉求1:提供toString()
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
Enum类中的常用方法:
- values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
- valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
- toString():返回当前枚举类对象常量的名称
使用enum关键字定义的枚举类实现接口的情况
- 情况一:实现接口,在enum类中实现抽象方法
- 情况二:让枚举类的对象分别实现接口中的抽象方法
enum Season{
//1.提供当前枚举类的多个对象
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","冰天雪地");
//2.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//2.私有化类的构造器,并给对象属性赋值
private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
}
//简单的枚举可以这样执行
public enum Status{
BUSY,VOCATION,FREE;
}
502.注解
框架 = 注解 + 反射 + 设计模式;
如何自定义注解:参照@SuppressWarnings定义
- 注解声明为:@interface
- 内部定义成员,通常使用value表示
- 可以指定成员的默认值,使用default定义
如果自定义注解没有成员时,表示是一个表示作用
如果注解有成员,在使用注解时,需要指明成员的值。
自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
自定义注解通过都会指明两个元注解:Retention、Target
jdk 提供的4种元注解
元注解:对现有的注解进行解释说明的注解
- Retention:指定所修饰的 Annotation 的生命周期:SOURCE\CLASS(默认行为)\RUNTIME,只有声明为RUNTIME生命周期的注解,才能通过反射获取。
- Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素
- 出现的频率较低
Documented:表示所修饰的注解在被javadoc解析时,保留下来。
Inherited:被它修饰的 Annotation 将具有继承性。
511.集合
1.集合、数组都是对多个数据进行存储操作的结构,简称Java容器;内存层面的存储。
2.数组确定之后,可存储的元素类型、元素长度已经被指定。
- 缺点:长度确定之后无法修改
- 可以操作的方法较少
- 获取数组中实际元素个数,没有现成的方法
- 数组中的存储数据:有序、可重复。
512.Collection 和 Map两种体系
Collection接口: 单列数据, 定义了存取一组对象的方法的集合
- List: 元素有序、可重复的集合
- Set: 元素无序、不可重复的集合
Map接口: 双列数据,保存具有映射关系“key-value对”的集合
Collection接口继承树
Collection接口:单列集合,用来存储一个一个对象
- list接口:存储有序的、可重复的数据 --------->动态数组
- Set接口:存储无序的、不可重复的数据 ------>实际的集合
Map接口:双列集合,用来存储一对(key-value), 一对数据
521.iterator接口
迭代器:提供一种方法访问一个容器中的各个元素,而又不暴露该对象的内部细节。迭代器模式,就是为容器而生的。主要用于遍历。其主要的方法:
- hasNext():判断下一个位置是否有元素
- next():指针右移,返回集合上下一个位置的元素
- remove()方法:删除集合中的元素,可以按照索引删除,也可以按照对象删除;
public class IteratorTest {
@Test
public void test(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add("abc");
coll.add("def");
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
for-each循环遍历集合、数组:for(集合元素的类型 局部变量: 集合对象)
for(Object obj: objs){}
526.List接口:存储有序的,可重复的数据。 动态数组,替换原有的数组
- ArrayList:线程不安全,效率高。底层使用Object[]
- LinkedList:底层使用的双向链表,频繁的插入、删除操作使用效率高;
- Vector:线程安全、效率低。底层使用Object[]
三者储存数据的特点相同:存储有序的、可重复的数据
ArrayList
- 在JDK7中:创建长度为10的Object[] 数组 elementData, 每次扩容1.5倍,将旧数组复制到新数组。类似单例中的饿汉式。
- JDK8中:初始化中ArrayList并没有创建长度为10的数组,调用add的时候才创建长度为10的数组。类似单例中的懒汉式
LinkedList: 无扩容机制
Vector:初始化长度为10,扩容为原来的2倍;
533.Set结构
- HashSet:Set的主要实现类,线程不安全的,可以储存null值
- LinkedHashSet:作为HashSet的子类,加入前后指针,线程不安全,可以按照添加的顺序遍历;
- TreeSet:同一个类的数据,可以按照对象的指定属性进行排序
Set中都是Collection
535.Set 中的hashCode();
add的时候加入的元素每一个进行哈希值计算,选一个位置(Set初始长度16)存放,然后如果有哈希冲突,JDK7头插法,JDK8尾插法。拉链法
- 先判断哈希,再判断equals()
添加元素的过程:以HashSet为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
数组此位置上是否已经有元素:
如果此位置上没有其他元素,则元素a添加成功。 --->情况1
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
如果hash值不相同,则元素a添加成功。--->情况2
如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功。--->情况2
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下
HashSet底层:数组+链表的结构。
要求:
1.在往Set中添加元素的时候,其所在的类一定要重写hashCode() 和equals().
2.重写的hashCode()和equals()尽可能保持一致:对象的散列码相同
537.LinkedHashSet
LinkedHashSet作为HashSet的子类,在添加数据的同时,还维护了两个引用,记录此数据前后数据。
538.TreeSet:同一个类的数据,可以按照对象的指定属性进行排序
- 向TreeSet中添加数据,要求是相同的类的对象。
- 两种排序方法:自然排序Coparable 和 自定义排序Comparator
- 自然排序中,比较两个对象是否相等的标准为:compareTo()返回0,而不是equals();
- 自定义排序中,比较两个对象是否相等的标准为:compare()返回0、不再是equals()
- 采用红黑树作为底层结构,特点:有序、查询速度比List快。
545.过滤List重复数据
HashSet set = new Hashset();
set,add(list);
return new ArrayList(set);
面试题目:对Set的理解,首先hashCode(),再equals();
@Test
public void test3(){
HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");
set.add(p1);
set.add(p2);
System.out.println(set); //[Person{id=1002, name='BB'}, Person{id=1001, name='AA'}]
p1.name = "CC"; //此时的p1名称被修改为CC, 但是实际存储位置没变,hashcode没有变
set.remove(p1); //删除的时候是按照CC寻找hashCode删除的,所以没有找到,集合中还有AA,CC
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
set.add(new Person(1001,"CC"));//这里就是算得CC的hashcode,之前分别是AA,BB所以还可以添加到集合里面
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
set.add(new Person(1001,"AA"));//这里的new的AA计算hashCode()的时候会和之前的AA计算到同一位置,但是此时两者的euqals不相等
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
}
547.HashMap
key — value,
- Map中的key是无序的、不可重复的,使用Set存储所有的value —>
key所处的类需要重写equals和hashCode()
- Map中的value是无序的、可重复的,使用Collection存储所有的value
一个键值对: key-value 构成了一个Entry对象;
Map中的entry:无序的、不可重复的,使用Set存储所有的entry
JDK7中:数组+链表
JDK8中:数组+链表;当数组上的某一索引位置上的元素以链表的形式存在的数据个数>8,且当前数组的长度>64时,此时索引位置上的所有数据改为使用红黑树存储。JDK8中的底层数组是Node[ ],而非Entry[ ];首次调用put方法就会创建一个长度为16的数组。
557.Collections工具类:操作Collection、Map的工具类
面试题:Collection 和Collections的区别:Collection是存储单列集合List、Set、Map的接口,Collections是操作它们的工具类
- reverse(List): 反转 List 中元素的顺序
- shuffle(List): 对 List 集合元素进行随机排序
- sort(List): 根据元素的自然顺序对指定 List 集合元素按升序排序
- sort(List, Comparator): 根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
- swap(List, int, int): 将指定 list 集合中的 i 处元素和 j 处元素进行交换
- 线程安全:Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。synchronizedList、SynchronizedMap等
564.泛型
问题:1.之前的类型不安全,什么类型都可以添加; 2.强转时,可能出现ClassCastException
泛型声明的数据应该为类,不能为基本数据类型。如果没有指定类型,默认为Object
如何自定义泛型类、泛型接口、泛型方法:使用< T >或者E去表示泛型
- 泛型不同的引用不能相互赋值
- 静态方法不能使用类的泛型:因为泛型是在创建对象的时候赋值的
- 异常类不能声明为泛型类
- 泛型方法可以声明为静态的,泛型参数是在调用方法时确定的,并非在实例化的时后确定的。
570.泛型继承
- 泛型在继承方面的体现
虽然类A是类B的父类,但是G< A> 和G< B>二者不具备子父类关系,二者是并列关系。
补充:类A是类B的父类,A< G> 是 B< G> 的父类
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;
//此时的list1和list2的类型不具有子父类关系
List<Object> list1 = null;
List<String> list2 = new ArrayList<String>();
571.通配符的使用; 通配符 :?
类A是类B的父类,G< A>和G< B>是没有关系的,二者共同的父类是:G< ? >
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;
list = list1;
list = list2;
//添加(写入):对于List<?>就不能向其内部添加数据。
//除了添加null之外。
// list.add("DD");
// list.add('?');
list.add(null);
573.有限制条件的通配符使用
? extends A: 可以看作小于等与
G<? extends A> 可以作为G< A>和G< B>的父类,其中B是A的子类
? super A: 可以看作大于等于
G<? super A> 可以作为G< A>和G< B>的父类,其中B是A的父类
List<? extends Person> list1 = null;
List<? super Person> list2 = null;
575.IO流
File类的使用:
1.File类的一个对象,代表一个文件或者一个文件目录(俗称文件夹)
2.File类声明在java.io包下面
3. File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
4. 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点".
1.如何创建File类的实例
File(String filePath)
File(String parentPath,String childPath)
File(File parentFile,String childPath)
2.
相对路径:相较于某个路径下,指明的路径。
绝对路径:包含盘符在内的文件或文件目录的路径
3.路径分隔符
windows:\\
unix:/
public String getAbsolutePath():获取绝对路径
public String getPath() :获取路径
public String getName() :获取名称
public String getParent():获取上层文件目录路径。若无,返回null
public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified() :获取最后一次的修改时间,毫秒值
如下的两个方法适用于文件目录:
public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
注意:public boolean renameTo(File dest):把文件重命名为指定的文件路径
比如:file1.renameTo(file2)为例:
要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。
文件的判断:
public boolean isDirectory():判断是否是文件目录
public boolean isFile() :判断是否是文件
public boolean exists() :判断是否存在
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏
创建硬盘中对应的文件或文件目录
public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs() :创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建
删除磁盘中的文件或文件目录
public boolean delete():删除文件或者文件夹
删除注意事项:Java中的删除不走回收站。
583.IO流原理及流的分类
分类:
- 按照数据单位不同分为:字节流(8位)、字符流(16位)
- 按照数据流的流向不同分为:输入流、输出流
- 按照角色不同分为:节点流、处理流
586.IO流的体系结构
流的操作步骤:
1.File类的实例化
2.FileReader流的实例化
3.读入操作
4.资源关闭
流的体系结构
抽象基类 | 节点流(或文件流) | 缓冲流(处理流的一种) |
---|---|---|
InputStream | FileInputStream (read(byte[] buffer)) | BufferedInputStream (read(byte[] buffer)) |
OutputStream | FileOutputStream (write(byte[] buffer,0,len) | BufferedOutputStream (write(byte[] buffer,0,len) / flush() |
Reader | FileReader (read(char[] cbuf)) | BufferedReader (read(char[] cbuf) / readLine()) |
Writer | FileWriter (write(char[] cbuf,0,len) | BufferedWriter (write(char[] cbuf,0,len) / flush() |
@Test
public void testFileReader() {
//1.实例化数据
FileReader fr = null;
try {
File file = new File("hello.txt");
//2.获得具体的流
fr = new FileReader(file);
//3.数据的读取,返回读入的一个字符,如果到达文件末尾,返回-1
int data = fr.read();
while (data != -1){
System.out.print((char)data);
data = fr.read();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.流的关闭操作
try {
if (fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
对read()操作升级:使用read的重载方法, 一次读取多个字符,批量读取。
587.文件写入
从内存中写出数据到硬盘的文件里。
说明:
1. 输出操作,对应的File可以不存在的。并不会报异常
2.
File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
File对应的硬盘中的文件如果存在:
如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):对原有文件的覆盖
如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容
public void testFileWriter() throws IOException {
File file = new File("hello1.txt");
FileWriter fw = new FileWriter(file, false);//false会直接覆盖, true会接着写
fw.write("I have a dream!\n");
fw.write("happy birthday\n");
fw.close();
}
590.IO流 字节流
测试FileInputStream和FileOutputStream的使用
结论:
- 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
- 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,…),使用字节流处理
593.缓冲流
处理流之一:缓冲流的使用
1.缓冲流:
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
2.作用:提供流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区
3.处理流,就是“套接”在已有的流的基础上。
@Test
public void BufferedStreamTest() throws FileNotFoundException {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//1.造文件
File srcFile = new File("爱情与友情.jpg");
File destFile = new File("爱情与友情3.jpg");
//2.造流
//2.1 造节点流
FileInputStream fis = new FileInputStream((srcFile));
FileOutputStream fos = new FileOutputStream(destFile);
//2.2 造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3.复制的细节:读取、写入
byte[] buffer = new byte[10];
int len;
while((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
// bos.flush();//刷新缓冲区
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源关闭
//要求:先关闭外层的流,再关闭内层的流
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
// fos.close();
// fis.close();
}
598.转换流
转换流提供了在字节流和字符流之间的转换,属于字符流。
Java API提供了两个转换流:
- InputStreamReader:将InputStream转换为Reader,将一个字节的输入流转换为一个字符的输入流;
- OutputStreamWriter:将Writer转换为OutputStream,将一个字符的输出流转换为一个字节的输出流;
- 字节流中的数据都是字符时,转成字符流操作更高效。
603.
-
输入流、输出流:
- System.in和System.out分别代表了系统标准的输入和输出设备
- 默认输入设备是: 键盘, 输出设备是:显示器
- System.in的类型是InputStream
- System.out的类型是PrintStream,其是OutputStream的子类FilterOutputStream 的子类
-
打印流
实现将基本数据类型的数据格式转化为字符串输出- 打印流: PrintStream和PrintWriter
- 提供了一系列重载的print()和println()方法,用于多种数据类型的输出
- PrintStream和PrintWriter的输出不会抛出IOException异常
- PrintStream和PrintWriter有自动flush功能
- PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
- System.out返回的是PrintStream的实例
-
数据流
为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流。- 数据流有两个类: (用于读取和写出基本数据类型、 String类的数据)
- DataInputStream 和 DataOutputStream
- 分别“套接”在 InputStream 和 OutputStream 子类的流上
609.对象流
ObjectInputStream和OjbectOutputSteam
- 用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
- 序列化: 用ObjectOutputStream类保存基本类型数据或对象的机制,将内存中的java对象保存到磁盘中或者通过网络传输出去
- 反序列化: 用ObjectInputStream类读取基本类型数据或对象的机制
- ObjectOutputStream和ObjectInputStream不能序列化static和transient修
饰的成员变量
序列化与反序列化:
public class ObjectInputOutputStream {
@Test
public void testObjectOutputStream1() {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
oos.writeObject(new String("我爱北京天安门"));
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void testobjectInputStream(){
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("object.dat"));
Object obj = ois.readObject();
String str = (String) obj;
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
自定义类的序列化
- 实现Serializable标识接口
- 当前类提供一个全局常量:serialVersionUID = xxxxxxxxxxxL;
- 除了当前类需要依靠Serializable接口序列化,其内部所有属性也需要序列化(默认情况下,集本数据类型是可以序列化的)
- 序列化 和 反序列化的顺序是固定的,不能乱序;
- 不能序列化static和transient修饰的成员变量。
614.随机存取文件流
RandomAccessFile的使用
- 1.RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口
- 2.RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
- 3.如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。 如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
- 4.可以通过相关的操作,实现RandomAccessFile“插入”数据的效果
618.网络编程
通信要素一:IP和端口号
1. IP:唯一的标识 Internet 上的计算机(通信实体)
2. 在Java中使用InetAddress类代表IP
3. IP分类:IPv4 和 IPv6 ; 万维网 和 局域网
4. 域名: www.baidu www.mi www.sina www.jd www.vip
5. 本地回路地址:127.0.0.1 对应着:localhost
6. 如何实例化InetAddress:两个方法:getByName(String host) 、 getLocalHost();两个常用方法:getHostName() / getHostAddress()
7. 端口号:正在计算机上运行的进程。
要求:不同的进程有不同的端口号
范围:被规定为一个 16 位的整数 0~65535。
公认端口: 0~1023。被预先定义的服务通信占用(如: HTTP占用端口
80, FTP占用端口21, Telnet占用端口23)
注册端口: 1024~49151。分配给用户进程或应用程序。(如: Tomcat占
用端口8080, MySQL占用端口3306, Oracle占用端口1521等) 。
动态/私有端口: 49152~65535。
8. 端口号与IP地址的组合得出一个网络套接字:Socket
621.TCP/UDP
TCP协议:
- 使用TCP协议前,须先建立TCP连接,形成传输数据通道
- 传输前,采用“三次握手” 方式,点对点通信, 是可靠的
- TCP协议进行通信的两个应用进程:客户端、 服务端。
- 在连接中可进行大数据量的传输
- 传输完毕,需释放已建立的连接, 效率低
UDP协议:
- 将数据、源、目的封装成数据包, 不需要建立连接
- 每个数据报的大小限制在64K内
- 发送不管对方是否准备好,接收方收到也不确认, 故是不可靠的可以广播发送
- 发送数据结束时无需释放资源,开销小,速度快
/**
* 实现TCP的网络编程
* 例子1:客户端发送信息给服务端,服务端将数据显示在控制台上
*
* @author shkstart
* @create 2019 下午 3:30
*/
public class TCPTest1 {
//客户端
@Test
public void client() {
Socket socket = null;
OutputStream os = null;
try {
//1.创建Socket对象,指明服务器端的ip和端口号
InetAddress inet = InetAddress.getByName("192.168.14.100");
socket = new Socket(inet,8899);
//2.获取一个输出流,用于输出数据
os = socket.getOutputStream();
//3.写出数据的操作
os.write("你好,我是客户端mm".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源的关闭
if(os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//服务端
@Test
public void server() {
ServerSocket ss = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
//1.创建服务器端的ServerSocket,指明自己的端口号
ss = new ServerSocket(8899);
//2.调用accept()表示接收来自于客户端的socket
socket = ss.accept();
//3.获取输入流
is = socket.getInputStream();
//不建议这样写,可能会有乱码
// byte[] buffer = new byte[1024];
// int len;
// while((len = is.read(buffer)) != -1){
// String str = new String(buffer,0,len);
// System.out.print(str);
// }
//4.读取输入流中的数据
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len;
while((len = is.read(buffer)) != -1){
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + "的数据");
} catch (IOException e) {
e.printStackTrace();
} finally {
if(baos != null){
//5.关闭资源
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(ss != null){
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
627.URL类
- URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一
资源的地址。 - 它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate
这个资源。 - 通过 URL 我们可以访问 Internet 上的各种网络资源,比如最常见的 www, ftp
站点。浏览器通过解析给定的 URL 可以在网络上查找相应的文件或其他资源。 - URL的基本结构由5部分组成:
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表 - 例如:
http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123 - #片段名:即锚点,例如看小说,直接定位到章节
- 参数列表格式:参数名=参数值&参数名=参数值…
634.Java的反射机制
Reflection(反射)是被视为动态语言
的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
Java反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
@Test
public void test1(){
//1.创建Person类对象
Person p1 = new Person("Tom", 12);
//2.通过对象调用其内部属性
p1.age = 10;
System.out.println(p1);
p1.show();
//3.在Person内外部,不可以通过Person类的对象调用其内部私有结构
//比如 name, showNation等
}
@Test
public void test2() throws Exception {
Class clazz = Person.class;
//1.通过反射创建Person类的对象
Constructor cons = clazz.getConstructor(String.class, int.class);
Object obj = cons.newInstance("Tom", 12);
Person p = (Person) obj;
System.out.println(p.toString());
//2.通过反射调用对象指定的属性、方法
Field age = clazz.getDeclaredField("age");
age.set(p, 10);
System.out.println(p.toString());
//3.调用方法
Method show = clazz.getDeclaredMethod("show");
show.invoke(p);
//4.通过反射,是可以调用Person类的私有结构的,比如私有的构造器,方法,属性等
//调用私有属性和方法
Constructor cons1 = clazz.getDeclaredConstructor(String.class);
cons1.setAccessible(true);
Person p1 = (Person) cons1.newInstance("Jerry");
System.out.println(p1);
//私有属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1, "HanMeiMei");
System.out.println(p1.toString());
//调用私有方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
showNation.invoke(p1, "中国");
}
//疑问1:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用那个?
//建议:直接new的方式。
//什么时候会使用:反射的方式。 反射的特征:动态性
//疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?
//不矛盾。封装性:建议你调用什么。反射性:能够调
/*
关于java.lang.Class类的理解
1.类的加载过程:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件
加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此
运行时类,就作为Class的一个实例。
2.换句话说,Class的实例就对应着一个运行时类。
3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式
来获取此运行时类。
640.获取Class的实例
//获取Class的实例对象
@Test
public void test3() throws ClassNotFoundException {
//方式一:调用运行时类的属性: .class
Class<Person> clazz1 = Person.class;
System.out.println(clazz1);
//方式二:通过运行时类的对象
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
//方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("com.qin.java.Person");
System.out.println(clazz3);
}
643.类的加载与ClassLoader
@Test
public void test1(){
//对于自定义类,使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//调用系统类加载器的getParent():获取扩展类加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
//调用扩展类加载器的getParent():无法获取引导类加载器
//引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2);
ClassLoader classLoader3 = String.class.getClassLoader();
System.out.println(classLoader3);
}
645.通过反射创建运行时类的对象
@Test
public void test4() throws IllegalAccessException, InstantiationException {
Class clazz = Person.class;
/*
* newInstance():调用此方法,创建对应的运行时类的对象
* 调用的是运行时类的空参构造器
* 要想此方法正常创建:
* 1.运行时类必须提供空参构造器
* 2.空参构造器的访问权限够,通常设置为public
*
* 在Javabean中要求提供一个空参构造器,原因
* 1.便于通过反射,创建运行时类对象
* 2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
* */
Person obj = (Person) clazz.newInstance();
System.out.println(obj);
}
647.通过反射获取运行时类的完整结构
全部的方法:
public Method[] getDeclaredMethods()
返回此Class对象所表示的类(当前类,不包含父类)或接口的全部方法
public Method[] getMethods()
返回此Class对象所表示的类(包含父类)或接口的public的方法
全部的Field
public Field[] getFields()
返回此Class对象所表示的类(包含父类)或接口的public的Field。
public Field[] getDeclaredFields()
返回此Class对象所表示的类(只有当前类)或接口的全部Field
657.调用运行类的指定方法
/*
如何操作运行时类中的指定的属性 -- 需要掌握
*/
@Test
public void testField1() throws Exception {
Class clazz = Person.class;
//创建运行时类的对象
Person p = (Person) clazz.newInstance();
//1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
Field name = clazz.getDeclaredField("name");
//2.保证当前属性是可访问的
name.setAccessible(true);
//3.获取、设置指定对象的此属性值
name.set(p,"Tom");
System.out.println(name.get(p));
}
/*
如何操作运行时类中的指定的方法 -- 需要掌握
*/
@Test
public void testMethod() throws Exception {
Class clazz = Person.class;
//创建运行时类的对象
Person p = (Person) clazz.newInstance();
/*
1.获取指定的某个方法
getDeclaredMethod():参数1 :指明获取的方法的名称 参数2:指明获取的方法的形参列表
*/
Method show = clazz.getDeclaredMethod("show", String.class);
//2.保证当前方法是可访问的
show.setAccessible(true);
/*
3. 调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参
invoke()的返回值即为对应类中调用的方法的返回值。
*/
Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN");
System.out.println(returnValue);
System.out.println("*************如何调用静态方法*****************");
// private static void showDesc()
Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
//如果调用的运行时类中的方法没有返回值,则此invoke()返回null
// Object returnVal = showDesc.invoke(null);
Object returnVal = showDesc.invoke(Person.class);
System.out.println(returnVal);//null
}
/*
如何调用运行时类中的指定的构造器
*/
@Test
public void testConstructor() throws Exception {
Class clazz = Person.class;
//private Person(String name)
/*
1.获取指定的构造器
getDeclaredConstructor():参数:指明构造器的参数列表
*/
Constructor constructor = clazz.getDeclaredConstructor(String.class);
//2.保证此构造器是可访问的
constructor.setAccessible(true);
//3.调用此构造器创建运行时类的对象
Person per = (Person) constructor.newInstance("Tom");
System.out.println(per);
}
661.静态代理
package com.qim.java;
/**
* 静态代理举例:代理类和被代理类在编译期间被写死
* @author qin
* @create 2021-01-28-下午9:04
*/
interface ClothFactory{
void produceCloth();
}
//代理类
class ProxyClothFactory implements ClothFactory{
private ClothFactory factory;//就拿被代理对象进行实例化
public ProxyClothFactory(ClothFactory factory){
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println("代理工厂做一些准备工作");
factory.produceCloth();
System.out.println("代理工厂做一些后续的工作");
}
}
//被代理类
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("NIKE工厂生产衣服");
}
}
public class StaticProxyTest {
public static void main(String[] args) {
//创建被代理类对象
ClothFactory nike = new NikeClothFactory();
//创建代理类对象
ClothFactory proxyClothFactory = new ProxyClothFactory(nike);
proxyClothFactory.produceCloth();
}
}
662.动态代理的举例
package com.qim.java;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author qin
* @create 2021-01-28-下午9:33
*/
//接口
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}
/*
* 要想实现动态代理,需要解决的问题?
* 问题一:如何根据加载到内存中的被代理类,动态创建一个代理类及其对象
* 问题二:当通过代理类的对象调用方法时,如何动态调用被代理类中同名的方法
*/
// 动态代理
class ProxyFactory{
//调用此方法,返回一个代理类的对象;解决问题一
public static Object getProxyInstance(Object obj){
//obj:被代理类的对象,根据被代理类对象,动态创建一个代理类对象
//返回值是 Object, 不能写死
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
//被代理类的 类名, 接口, 调用的方法
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),handler);
}
}
class MyInvocationHandler implements InvocationHandler{
/**
*
* 当我们通过代理类的对象,调用方法a时,就会自动的调用如下方法: invoke()
* 将被代理类要执行的方法a的功能声明在invoke()方法中
* proxy代理类对象, method代理类方法,
*
*/
private Object obj; //赋值时,需要使用被代理类的对象进行赋值
public void bind(Object obj){
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method:即为代理类对象调用的方法:此方法也就作为了被代理类对象调用的方法
//obj:被代理类的对象
Object returnValue = method.invoke(obj, args);
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//通过代理类对象调用方法时, 会自动调用被代理类中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("火锅");
//只需要提供被代理类 和 接口 ,就可以实现动态代理
NikeClothFactory nikeClothFactory = new NikeClothFactory();
ClothFactory proxyInstance1 = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
proxyInstance1.produceCloth();
}
}
666.Lambda表达式
Java8的新特性
@Test
public void test2(){
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
int compare1 = com1.compare(12, 21);
System.out.println(compare1);
//Lambda表达式
System.out.println("##############");
Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1,o2);
int compare2 = com2.compare(32, 21);
System.out.println(compare2);
//方法引用
System.out.println("##############");
Comparator<Integer> com3 = Integer::compare;
int compare3 = com3.compare(32, 21);
System.out.println(compare3);
}
1.举例: (o1, o2) -> Integerpare(o1, o2)
2.格式:
-> : lambda操作符 或 箭头操作符
->左边: lambda形参列表
->右边: 指定了 Lambda 体, 是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能
Lambda表达式:作为接口的实例
670.函数式接口
- 方法引用的使用
1.使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
2.方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以
方法引用,也是函数式接口的实例。
3. 使用格式: 类(或对象) :: 方法名
4. 具体分为如下的三种情况:
情况1 对象 :: 非静态方法
情况2 类 :: 静态方法
情况3 类 :: 非静态方法
5. 方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的
形参列表和返回值类型相同!(针对于情况1和情况2)
675.Stream
/*
* 1. Stream关注的是对数据的运算,与CPU打交道
* 集合关注的是数据的存储,与内存打交道
*
* 2.
* ①Stream 自己不会存储元素。
* ②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
* ③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
*
* 3.Stream 执行流程
* ① Stream的实例化
* ② 一系列的中间操作(过滤、映射、...)
* ③ 终止操作
*
* 4.说明:
* 4.1 一个中间操作链,对数据源的数据进行处理
* 4.2 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
*/
//创建 Stream方式一:通过集合
@Test
public void test1(){
List<Employee> employees = EmployeeData.getEmployees();
// default Stream<E> stream() : 返回一个顺序流
Stream<Employee> stream = employees.stream();
// default Stream<E> parallelStream() : 返回一个并行流
Stream<Employee> parallelStream = employees.parallelStream();
}
//创建 Stream方式二:通过数组
@Test
public void test2(){
int[] arr = new int[]{1,2,3,4,5,6};
//调用Arrays类的static <T> Stream<T> stream(T[] array): 返回一个流
IntStream stream = Arrays.stream(arr);
Employee e1 = new Employee(1001,"Tom");
Employee e2 = new Employee(1002,"Jerry");
Employee[] arr1 = new Employee[]{e1,e2};
Stream<Employee> stream1 = Arrays.stream(arr1);
}
//创建 Stream方式三:通过Stream的of()
@Test
public void test3(){
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
}
//创建 Stream方式四:创建无限流
@Test
public void test4(){
// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
//遍历前10个偶数
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);
// 生成
// public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
677.筛选与切片、映射、排序
- filter(Predicate p)——接收 Lambda , 从流中排除某些元素。
- limit(n)——截断流,使其元素不超过给定数量。
- skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
- distinct()——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
public class StreamAPITest1 {
//1-筛选与切片
@Test
public void test1(){
List<Employee> list = EmployeeData.getEmployees();
// filter(Predicate p)——接收 Lambda , 从流中排除某些元素。
Stream<Employee> stream = list.stream();
//练习:查询员工表中薪资大于7000的员工信息
stream.filter(e -> e.getSalary() > 7000).forEach(System.out::println);
System.out.println();
// limit(n)——截断流,使其元素不超过给定数量。
list.stream().limit(3).forEach(System.out::println);
System.out.println();
// skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
list.stream().skip(3).forEach(System.out::println);
System.out.println();
// distinct()——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
list.add(new Employee(1010,"刘强东",40,8000));
list.add(new Employee(1010,"刘强东",41,8000));
list.add(new Employee(1010,"刘强东",40,8000));
list.add(new Employee(1010,"刘强东",40,8000));
list.add(new Employee(1010,"刘强东",40,8000));
// System.out.println(list);
list.stream().distinct().forEach(System.out::println);
}
//映射
@Test
public void test2(){
// map(Function f)——接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。
List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
// 练习1:获取员工姓名长度大于3的员工的姓名。
List<Employee> employees = EmployeeData.getEmployees();
Stream<String> namesStream = employees.stream().map(Employee::getName);
namesStream.filter(name -> name.length() > 3).forEach(System.out::println);
System.out.println();
//练习2:
Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest1::fromStringToStream);
streamStream.forEach(s ->{
s.forEach(System.out::println);
});
System.out.println();
// flatMap(Function f)——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
Stream<Character> characterStream = list.stream().flatMap(StreamAPITest1::fromStringToStream);
characterStream.forEach(System.out::println);
}
//将字符串中的多个字符构成的集合转换为对应的Stream的实例
public static Stream<Character> fromStringToStream(String str){//aa
ArrayList<Character> list = new ArrayList<>();
for(Character c : str.toCharArray()){
list.add(c);
}
return list.stream();
}
@Test
public void test3(){
ArrayList list1 = new ArrayList();
list1.add(1);
list1.add(2);
list1.add(3);
ArrayList list2 = new ArrayList();
list2.add(4);
list2.add(5);
list2.add(6);
// list1.add(list2);
list1.addAll(list2);
System.out.println(list1);
}
//3-排序
@Test
public void test4(){
// sorted()——自然排序
List<Integer> list = Arrays.asList(12, 43, 65, 34, 87, 0, -98, 7);
list.stream().sorted().forEach(System.out::println);
//抛异常,原因:Employee没有实现Comparable接口
// List<Employee> employees = EmployeeData.getEmployees();
// employees.stream().sorted().forEach(System.out::println);
// sorted(Comparator com)——定制排序
List<Employee> employees = EmployeeData.getEmployees();
employees.stream().sorted( (e1,e2) -> {
int ageValue = Integer.compare(e1.getAge(),e2.getAge());
if(ageValue != 0){
return ageValue;
}else{
return -Double.compare(e1.getSalary(),e2.getSalary());
}
}).forEach(System.out::println);
}
}
678.匹配与查找、归约、收集
public class StreamAPITest2 {
//1-匹配与查找
@Test
public void test1(){
List<Employee> employees = EmployeeData.getEmployees();
// allMatch(Predicate p)——检查是否匹配所有元素。
// 练习:是否所有的员工的年龄都大于18
boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
System.out.println(allMatch);
// anyMatch(Predicate p)——检查是否至少匹配一个元素。
// 练习:是否存在员工的工资大于 10000
boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
System.out.println(anyMatch);
// noneMatch(Predicate p)——检查是否没有匹配的元素。
// 练习:是否存在员工姓“雷”
boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
System.out.println(noneMatch);
// findFirst——返回第一个元素
Optional<Employee> employee = employees.stream().findFirst();
System.out.println(employee);
// findAny——返回当前流中的任意元素
Optional<Employee> employee1 = employees.parallelStream().findAny();
System.out.println(employee1);
}
@Test
public void test2(){
List<Employee> employees = EmployeeData.getEmployees();
// count——返回流中元素的总个数
long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
System.out.println(count);
// max(Comparator c)——返回流中最大值
// 练习:返回最高的工资:
Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
Optional<Double> maxSalary = salaryStream.max(Double::compare);
System.out.println(maxSalary);
// min(Comparator c)——返回流中最小值
// 练习:返回最低工资的员工
Optional<Employee> employee = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(employee);
System.out.println();
// forEach(Consumer c)——内部迭代
employees.stream().forEach(System.out::println);
//使用集合的遍历操作
employees.forEach(System.out::println);
}
//2-归约
@Test
public void test3(){
// reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值。返回 T
// 练习1:计算1-10的自然数的和
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream().reduce(0, Integer::sum);
System.out.println(sum);
// reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
// 练习2:计算公司所有员工工资的总和
List<Employee> employees = EmployeeData.getEmployees();
Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
// Optional<Double> sumMoney = salaryStream.reduce(Double::sum);
Optional<Double> sumMoney = salaryStream.reduce((d1,d2) -> d1 + d2);
System.out.println(sumMoney.get());
}
//3-收集
@Test
public void test4(){
// collect(Collector c)——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
// 练习1:查找工资大于6000的员工,结果返回为一个List或Set
List<Employee> employees = EmployeeData.getEmployees();
List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
employeeList.forEach(System.out::println);
System.out.println();
Set<Employee> employeeSet = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
employeeSet.forEach(System.out::println);
}
}
本文标签: 硅谷
版权声明:本文标题:尚硅谷学习 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dianzi/1729424263a1200756.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论