JavaSE基础总结

idea常用快捷键

ctrl + alt + 空格:表示单词提醒功能
ctrl + i:查看当前类可以实现接口的方法有哪些
ctrl + o:查看当前类可以重写的方法有哪些
ctrl + h:查看当前类的继承关系
ctrl + j:查看模板的快捷键
ctrl + n:查看类的源码信息
Ctrl + f:查找单词位置
Shift + Alt + S:截图
Alt + shift + insert + 鼠标:当注释有*在前面不好多行复制,就可以使用此快捷键

常用的DOS命令

dir:列出当前目录下的文件和文件夹
mkdir:创建目录
rmdir:删除目录
cd:进入指定目录
cd /:退回根目录
cd .. :退回上级目录
del:删除文件
Tab键:自动补全代码
cls:清屏
ipconfig:查看IP地址配置
ipconfig /all:查看更详细的IP地址配置
ping IP(或者域名):通信,查看网络是否良好,会停
ping IP/域名 -t:一直ping,不会停

进制转换

计算机当中,二进制最左边的是符号位,0表示正数,1表示负数,八进制0-7 10-17,满8跳,十六进制0-9,a-f,到f跳

十进制转换成二进制方法
在这里插入图片描述
二进制转换成十进制方法
在这里插入图片描述

字符编码

核心:字符编码是人为规定的字典,二进制对应着现实中的文字,最先出现的字符编码是ASCLL码,但只认识英文,最后出现了unicode编码统一了全球所有文字(有多种具体的实现:-UTF-8,-UTF-16,-UTF-32…),Java语言就是用的unicode编码,所以标识符可以用中文,实际开发过程中,统一用的UTF-8比较多.

解码的过程,ASCLL码采用1byte存储
比如 ‘a’ --> 97,对应的二进制是01100001,这个过程是编码.
‘b’ --> 98 ‘c’ --> 99… ‘A’–>65,‘B’–>66…‘0’–>48,‘1’–>49…
把01100001二进制进行解码为’a’
如果编码和解码的不是同一种方式会出现乱码.

原码,反码,补码

计算机在任何情况下只能识别二进制,底层存储数据的形式是二进制的补码,原因是补码形式的效率最高. 二进制有原码,反码,补码,记住对于正数来说,二进制的原码,反码,补码是同一个,完全相同,如果是负数,比如byte b = 1,对应的原码10000001,反码(符号位不变,其他位取反)11111110,补码(反码 + 1)11111111

分析byte b = (byte)150;
第一步列出int类型的150的二进制码是什么
00000000 00000000 00000000 10010110
第二步将int类型强制转换成byte
10010110
第三步计算机存储的是补码,10010110就是补码
补码 = 反码 + 1
反码就是10010101
原码 = 反码取反
原码就是11101010
结果b输出的就是原码的二进制,解码为-106,注意最左边的1只是代表正负号而已.

JVM内存解析

方法区内存:
类加载信息,常量池,静态域,字符串常量池,整数常量池,编译器编译后的代码

堆内存:
存放创建的对象和数组,实例变量

栈内存:
存放局部变量和引用,调用方法时压栈,方法结束弹栈

注释

//是单行注释,用在方法的某个步骤

/*/是多行注释,用在方法的某个步骤

/**/是文档注释,用来注释整个类或者整个方法或者属性,可以被JDK下的javadoc工具解析成帮助文档,具体的DOS命令是

javadoc -d mydoc(任意取文件名) -author -version HelloWorld.java(java源代码)

找到mydoc文件夹下的index.html打开就是文档注释

标识符

程序员能够自己命名的单词就是标识符,由数字,字母,下划线,美元符组成,不能以数字开头,不能使用关键字,不能加入空格,严格区分大小写

包名全部小写
类名,接口名首字母大写
变量名和方法名首字母小写

常量名:全部大写,以_分隔单词
驼峰命名法,见名知意
不建议使用中文

变量

变量是存储数据的容器,保存在内存中,分为成员变量和局部变量

成员变量分为静态变量和实例变量,位置在类里方法外

局部变量分为形参(方法参数列表上和构造器里),方法内局部变量,代码块局部变量
必须赋值,先声明后赋值也可,赋值代码必定执行才可

变量: 数据类型 + 变量名 = 字面值

变量的作用域和重赋值是使用变量的关键点

数据类型

数据类型是JVM根据数据的类型分配合理的内存空间,数据类型分为基本数据类型和引用数据类型

基本数据类型:byte,short,int,long,float,double,boolean,char

引用数据类型:除了八大基本之类之外的类型,比如String

强制类型转换:大容量类型转换成小容量类型,()强转符,会损失精度
自动类型转换:小容量类型转换成大容量类型
如果参与运算,返回的类型的最大的数据类型
byte,short,char参与运算,自动提升为int类型
有小数参与运算,自动提升为double类型,小数默认是double类型

一个byte字节 = 8个比特位

1024B = 1KB,1024KB = 1MB,1024MB = 1GB

\t:制表符,相当于空格

\n:换行

\uxxxx:比如\u000a表示\n

char类型对应unicode码,对应现实中的字典文字

运算符

在这里插入图片描述
取模%:A%B,A是正数结果就是正,A是负数结果就是负数

口诀:++在前先赋值在运算,++在后在赋值在运算,并不会改变数据的类型

byte a = 1; a = a + 1会改变本身的数据类型
a += 1.1111不会改变本身的数据类型,a永远是byte类型,大类型会自动强转成byte

++和+=之类的只能出现在方法里,不能在类体里
在类体的成员变量不能重赋值的操作,a = a + 1;也不行!!!

左移表示乘,右移表示除,注意没有无符号左移
位运算符:<< 1表示左移1位二进制,int a = 10;a<<1结果是20,相当于10 * 2的1次方
位运算符:>> 1表示右移1位二进制,int a = 10;a>>2结果是2,相当于10 / 2的2次方
用二进制的表达运算是最高效的运算方法,位移的次方越小效率越高
2<<3跟8<<1,后者效率更高!!!

三元运算符: 布尔类型?表达式1:表达式2
表达式1和表达式2的数据类型必须一致,返回值的类型是表达式1和2合并的最大类型,比如表达式1是int,表达式2是double就算取的是表达式1的结果,返回的也是double类型数据!!!

true取表达式1,false取表达式2
可以嵌套使用,表达式1,2还可以是三元表达式

if-else分支

if(布尔类型):如果if代码块或者else代码块只有一条Java语句,那么{}可以省略不写!Java语句可以是if-else分支
if可以嵌套多层,先分析外层if,慢慢剖析!if-else遵循就近原则匹配

第一种写法:if(布尔类型){}
第二种写法:if(布尔类型){}else{}
第三种写法:if(布尔类型){}else if(){}
第四种写法:if(布尔类型){}else if(){}else{}
只要是单独写的else参与的肯定会执行分支结构,else-if不算!!!

switch-case分支

switch能接收int和String类型数据,枚举类型变量,byte,short,char类型会自动转换成int类型

switch-case搭配break和default,取模%号使用!!!

第一种写法:switch(){case 匹配的数据: 输出语句}
第二种写法:switch(){case 匹配的数据: 输出语句 default: 输出语句}
所以的case都匹配不上才会执行default,等于else
多个case可以合并:写多个case满足其中一个就可以输出语句

三元运算符和switch-case分支运行效率高于if-else分支,优先选择,不同环境下选写法简洁的!!!
三元和switch都能用if写,但if的他们不一定能写

控制语句

break:结束循环语句和switch语句

continue:跳过当前循环,进入下一次循环

return:结束当前方法,或者是返回数据给调用者

所有的语句遵循就近原则!!!

循环结构

循环结构四要素:
初始化条件,布尔条件,更新/迭代条件,循环体
知道循环的次数用for循环,其余用while循环更方便!!!

-----------------------------------------------------------------------------------
for循环:
for1:for(int i = 0;i < x.length;i++){循环体}
for1是循环名,可省略不写
int i = 0是初始化条件,i < x.length是布尔条件,i++是迭代条件
循环的过程是正7和反7,脑中模拟

嵌套多个for循环,外层控制纵向y轴变化,内存控制横向x轴变化!!!
-----------------------------------------------------------------------------------
while循环:
while(布尔条件){循环体}
一直循环,只有当布尔条件为false才结束
注意有时候的更新/迭代条件不要忘了,否则会死循环!!!
---------------------------------------------------------------------------
do-while循环:
do{循环体}while(布尔条件)
至少会执行一次循环体

数组

数组是多个相同类型的数据按照一定顺序排列的容器,并使用一个名字命名,通过编号的方式对这些数据进行统一管理

一维数组的创建:
静态初始化:int[] a = new int[]{…}
动态初始化:int[] b = new int[长度]
静态初始化简写:int[] c = {…}

二维数据的创建:
int[][] a = new int[][]{{一维数组},{一维数组},{…}}
int[][] b = new int[5][]
int[][] c = new int[5][5]
简写:int[][] d = {{一维数组},{…}}
每个一维数组表示引用,保存的地址

数组一旦创建,长度固定,不可修改,数组存储的类型是任意类型!!!
通过下标操作数组的元素,折半发/二分法效率更高(前提数组是有序)
创建数组,遍历赋值是数组的关键
char类型的数组底层源代码是循环遍历输出,如果就只是输出引用,打印的是值,如果拼接了打印的是地址,其他类型的数组底层是转换成String输出的是地址
数组的首元素内存地址代表整个数组的内存地址,通过首元素下标可以计算出偏移量找到其它元素位置,查询效率高,增删效率低并且无法存储大数据量,数组往尾部添加元素效率不受影响

优化数组:
少扩容,提前指定初始化容量
数组初始化容量默认为10,长度0,扩容为原容量的1.5倍

数组工具类Arrays方法
Arrays.equals(int[] a,int[] b);判断两个数组是否相等
Arrays.toString(int[] a);输出数组的信息
Arrays.fill(int[] a,int val);将指定值填入数组
Arrays.sort(int[] a);对数组进行排序
Arrays.binarySearch(int[] a,int key);二分法查找a数组里的key元素,返回下标
Arrays.asList();数组存储元素返回给List集合

面向对象

类:类是程序员根据现实中的事物所虚拟抽象出来的模板

对象:对象是现实中实际存在的个体,如果把世界分成不同的单元,每个单元都是独立的个体,多个个体结合起来形成功能系统

类 = 属性(成员变量) + 行为(方法) + 代码块 + 构造器 + 内部类

思想实现的规则
1.设计类,类的属性,方法,构造器,内部类,代码块
2.创建类的实例对象
3.通过实例调用类的资源

匿名对象
匿名对象是创建对象时没有变量名的对象,只能调用一次

构造器:
作用是创建对象和给实例变量赋值,系统默认提供无参构造,手动写了构造器无参构造会消失!!!一个类至少会有一个构造器

面向对象的专业术语:
面向对象分析: OOA
面向对象设计: OOD
面向对象编程: OOP

方法

方法:是一段代码片段,为了完成特定的功能,可以重复使用
方法声明:
修饰符列表 返回值类型 方法名(形参){方法体}
static修饰的方法:静态方法,方法不能使用实例相关的数据
final修饰的方法:不能被重写
abstract修饰的方法:只有方法声明,没有方法体,就是拿来被重写的
---------------------------------------------------
方法的重载:
方法的重载:在同一个类中,方法名相同,参数的数量或者类型或者顺序不同,这就是方法的重载,作用是程序更加灵活
方法的重载只看方法名和参数类型,跟参数名无关,跟返回值无关,修饰符列表无关,跟方法体无关
-------------------------------------------------------
可变长参数:
String … str等同于String[] str,编译器会认为他们是同一个
可变参数的个数是0-N个,可变形参必须放到形参声明的最后,并且只能声明一个可变参数

-------------------------------------------------------------
方法参数的值传递机制
形参:定义方法时声明小括号里的参数
实参:调用方法时实际传递给形参的数据
如果参数是基本数据类型,实参赋值的是真实存储的数据值
如果参数是引用数据类型,实参赋值的是真实存储的地址值

--------------------------------------------------------
方法的递归:
一个方法体内调用自身
方法的递归包含了隐式循环,它会重复执行某段代码,类似死循环,所以要向已知的方向递归
递归容易撑爆栈内存,少用,就算有出口也可能会撑爆栈内存,因为递归的太深了!!!

---------------------------------------------------------
方法的重写:
重写的前提是有继承关系,当父类的方法无法满足业务的需求时,子类需要重写父类的方法
重写的规则是:子类的方法名和参数列表必须和父类的一致,子类权限修饰符>=父类,子类返回值类型的继承关系 <= 父类,返回值类型如果是基本数据类型或者void必须一致,子类抛出的异常 <= 父类
重写只针对实例方法,静态方法和被private修饰的方法无法被重写!!!
------------------------------------------------------------------
构造器(构造方法):
所有的类必定都有构造器,系统默认创建无参构造器,如果手动创建了构造器,系统提供的构造器就无效了
构造器的作用是创建对象,给实例变量赋值
子类创建对象时会先调用父类的构造器,在子类构造器的首行,方式是super();

封装

封装是一种信息隐藏技术,隐藏对象内部的复杂性,对外提供简单的接口供外部使用,从而提高系统的安全性,扩展性,可维护性

具体实现:私有化属性,对外提供getter和setter方法,不对外暴露私有的方法,单例模式(私有化构造器),如果不希望类在其他包被调用,类设置成缺省权限

访问控制权限修饰符

在这里插入图片描述
class的权限修饰符只能是public或者是缺省!!!
方法内的变量只能是默认的缺省权限

关键字

this关键字:
this可以用在属性,方法,构造器,代码块,内部类当中,不能用在静态方法中,this代表了当前对象的引用,在堆内存的对象都有一个this指向自身!,this可以省略不写,变量或方法重名必须得写,this()在构造器中使用,表示调用另一个构造器,必须在首行,目的是代码复用!!!
-----------------------------------------------------------
package关键字:
package是声明类或者接口所属的包,必须在源文件的首行

-----------------------------------------------------------
import关键字:
import导入指定的类或者接口,在源文件package之下,第二行开始
*Java.lang是核心基础包,不用导入,用xx.可以导入某包下的所有结构

-----------------------------------------------------------
instanceof关键字:
在任何时候发生向下转型一定要用instanceof运算符判断,避免ClassCastException异常!!!
向下转型时判断是否满足继承关系:(父类引用 instanceof 子类型),运行的结果只能是true或false
Father f = new Son(); if (f instanceof Son){ Son s = (Son)f;}

-----------------------------------------------------------
super关键字:
super关键字用的地方跟this一样,super代表父类型的特征,通过super子类调用父类的属性和方法,如果标识符没有同名super可以省略不写,重名必须要写,在构造器中使用super(),必须在首行,作用是初始化父类型特征,默认调用无参构造器,跟this不能共存
父类构造方法是一定会执行的,Object超类的构造方法是一定会执行的

-----------------------------------------------------------
static关键字:
static可以修饰属性,方法,代码块,内部类
静态变量随着类的加载而加载,可以通过类名.直接调用,本类中类名可省略不写,保存在方法区的静态域当中
属性是多个对象共享时,属性修饰成静态的
操作静态属性和工具类的方法,方法修饰成静态的

-----------------------------------------------------------
final关键字:
final表示最终的,不可变的
被final修饰的类无法被继承,修饰的变量变成常量,必须手动赋值,位置可以是类体,构造器,代码块,修饰的方法不能被重写
修饰的对象引用不能在指向其他对象的内存空间,但是内部的数据可以被修改
final可以修饰局部变量,只能在方法内使用!!!

继承

继承是基于已经存在的类构造一个新类

子类可以继承父类除了构造器外的所有数据,包括私有的,但是因为封装的缘故,子类不能直接使用父类私有的资源
继承只支持单继承(叫父类),间接继承(叫间接父类)
所有类直接或间接继承Object超类

继承的作用是实现代码复用和功能扩展,有了继承才有方法的重写和多态机制

类和类之间使用extends关键字,有is a关系,才满足继承!

继承的缺点是耦合度太高,父类的代码修改后子类会受到牵连

多态

多态是指一个事务的多种形态

父类型引用指向子类对象,编译看左边,运行看右边(编译期调用父类的方法,运行调用子类的方法)!!!
属性不具有多态性,左边是父类型永远调的是父类的属性!!!

多态的前提是继承和方法的重写

向上转型:父类引用指向子类对象
向下转型:前提是发生向上转型,必须先用instanceof关键字判断语句里发生向下转型,以免发生异常!!!

专业术语:
基本数据类型之间:自动类型转换,强制类型转换
引用数据类型之间:向下转型,向上转型

Object类

Object类是所有类的超类,所有类直接或间接继承Object超类

Object超类提供了无参构造器用于子类创建对象

==运算符与equals方法的区别:
==运算符:基本数据类型之间比较的是具体的值,引用数据类型之间比较的是内存地址
equals方法:是Object超类的方法,适用于引用数据类型,没有被重写默认是==,重写equals后比较的是类中的属性是否相等
如果比较基本数据类型用==,引用数据类型用equals方法!!!
SUN公司定义的类基本都重写了equals方法,比如String类,Date,File,包装类…

toString方法:
作用是将对象转换成字符串,如果输出对象的引用,默认是调用了toString方法,一般类都会重写toString方法,返回具体的内容这样更容易理解,易读!!!

finalize方法:
没有引用指向堆内存对象时,对象上的垃圾回收器会调用finalize方法清理内存空间
程序员无法主导垃圾回收器回收,但是可以通过System.gc()提醒它回收!!!

hashCode方法:
获取对象的哈希值,哈希值是一串int类型的数字,可以看成门牌号,房间就是对象的内存空间

wait方法:
释放锁,进入等待,阻塞状态

notify方法:
唤醒被wait的线程,如果有多个,选择优先级高的

notifyAll方法:
唤醒所有被wait的线程

包装类

包装类:让基本数据类型的变量具有类的特征
在这里插入图片描述JDK1.5之后新功能,自动装箱和自动拆箱
自动装箱:基本数据类型转换成引用数据类型的包装类
自动拆箱:包装类转换成基本数据类型

基本数据类型丶包装类丶String类之间的相互转换:
基本数据类型和包装类:自动装箱和拆箱
基本数据类型 ==> String类:1.在""空字符串 2.String.valueOf()方法
String类 ==> 基本数据类型: Integer.parseInt(“123”);
包装类 <==> String类:都是xx.valueOF()方法

单元测试类

右键选中模块 --> build path–>add libraries -->JUnit 4

要求:方法必须是public,返回值为void,方法名随意,参数列表为空,类必须提供无参构造器

设计模式

设计模式:可以重复利用的解决方案,一种模板设计思想

设计模式之一:单例模式
所谓单例模式就是保证创建的对象只有一个!!!

单例模式核心三要素:
1.私有化构造方法
2.私有化静态创建的对象
3.提供一个公共静态的方法返回对象

饿汉式的设计思想:

//饿汉式,不管用不用对象都先创建出来
class Singleton01 {
    //私有化构造方法
    private Singleton01() {
    }

    //私有化对象
    private static Singleton01 singleton = new Singleton01();

    //提供获取对象的方法
    public static Singleton01 getSingleton() {
        return singleton;
    }
}

懒汉式的设计思想:

//懒汉式,需要用对象的时候才创建
//多线程环境下共享了singleton资源,有安全隐患,需要加锁.
class Singleton02 {
    //私有化构造方法
    private Singleton02() {
    }

    //私有化对象
    private static Singleton02 singleton;

    //同步代码块使用的唯一锁对象
    static Object o = new Object();
    //提供获取对象的方法
    public static Singleton02 getSingleton() {
        synchronized (o) {
            //如果对象为空,就创建对象
            if (singleton == null) {
                singleton = new Singleton02();
            }
            return singleton;
        }
    }
}

代码块

静态代码块:
用来初始化类的信息,随着类加载而加载
比如注册数据库驱动,就可以写在静态代码块里

实例代码块:
每创建一次对象就执行一次,用在对属性的赋值

局部代码块:(忽略)
控制变量的范围

执行顺序:父类静态代码块 > 子类静态代码块 > main方法 > 父类实例代码块 > 父类构造器代码块 > 子类实例代码块 > 子类构造器代码块
静态代码块只会执行一次!!!

抽象类

类和类之间具有共同特征,把这些共有特征抽取出来形成的类就是抽象类

abstract:表示抽象的,用来修饰类,方法
不能修饰属性,构造器,私有方法,静态方法,final修饰的类和方法!!!

抽象类无法实例化,有构造器,用来给子类创建对象

抽象方法只有方法声明,没有方法体

抽象类可以没有抽象方法,有抽象方法的一定是抽象类

子类必须重写抽象类的所有抽象方法,不然还是抽象类!!!抽象类就是被重写的

接口

接口是一种规范,定义接口就是定义规范

接口使用interface关键字定义,接口只能定义常量和抽象方法
属性固定写法:public static final 数据类型 属性名 = 值;
方法固定写法:public abstract 返回值类型 方法名(参数列表);
public static final默认隐藏,public abstract默认隐藏,可忽略不写!!!

JDK1.8接口新增了可以写静态方法,默认方法:
静态方法通过接口名直接调用
default默认方法(default void mehtod(){…},可以加public修饰符),通过实现类对象调用!!!

接口的作用是松耦合提高程序扩展性

接口不能实例化,也没有构造器!!!

接口之间可以多继承,接口之间不能实现,类可以实现多个接口,同时还可以再继承类
优先选择接口方式,扩展性更强,如果继承和实现同时存在,继承写在前面,如果只有实现,其实实现接口的类先继承了Object超类,然后再实现的接口!!!

匿名接口:
接口无法实例化,但是可以通过new 接口(){重写方法}的方式,相当于少写了实现类,缺点是无法重复利用!!!

类,接口的关系

is a:继承关系,狗是一个动物
实线空三角箭头指向父

like a:实现关系,厨师像一个菜单
子类虚线空三角箭头指向接口

has a:关联关系,老公和老婆关联起来
双向关联是大小于符号描述的实线双箭头,单向关联是引入类的所在类指向引入的类,(比如学生类包含了教室类,学生用实线单箭头指向教室)

聚合关系:关联关系的一种,是较强的关联关系,是整体和部分的关系,比如汽车和轮子
部分的类用实线空菱形状箭头指向整体类

合成关系:关联关系的一种,比聚合关系还强的关联关系,比如人和四肢不能分开
部分实线实心菱形状箭头指向整体类

依赖关系:依赖关系是比关联关系弱的关系
类用虚线大小于符号指向另一个类

内部类

内部类分为:静态内部类,实例内部类,局部内部类,匿名内部类

内部类可以直接访问外部类的所有资源,包括私有的,外部类想访问内部类的资源需要创建内部类对象
静态内部类:外部类名.静态内部类 x = 外部类名.new 内部类对象
实例内部类:先创建外部类对象,外部类对象引用.new 内部类对象

内部类可以被static修饰,4种权限修饰符修饰,final,abstract修饰!!!

局部内部类的变量必须是final修饰

异常

异常是指:程序在执行过程中发生错误,不正常的情况叫异常
在这里插入图片描述编译时异常:是Exception类的直接子类
运行时异常:是RuntimeException类的子类

处理异常的两种方式:
1.声明抛出:throws
2.捕捉处理:try{}catch(){}finally{}
如果希望调用者处理就声明抛出,其他情况就自己处理

手动抛出异常:throw

打印异常信息:
e.getMessage();该方法简单打印出异常类参数列表信息
e.printStackTrace();打印异常堆栈追踪信息,可读性更好,打印的方式是采用异步线程
找到自己写的包名,异常信息看第一行

JDK8新特性:
可以同时catch多个异常类,满足其一就能执行代码块.
try{}catch(FileNotFoundException | ArithmeticException e){…};

finally代码块:
finally代码块是一定会执行的,即使try出现了异常,除非有System.exit(0),退出了JVM,Finally才不会执行,finally必须和try一起出现,不能单独写

自定义异常:两步固定写法
第一步:编写一个类继承Exception或者RuntimeException.
第二步:提供两个构造方法,一个无参,一个带有String类型的参数,带参数的调用父类含参super(s);

自定义类一般是手动抛出异常,然后声明抛出,作用是提醒调用者

public class MyException extends Exception{
    //无参构造
    public MyException() {
    }
    //带有String类型的构造方法
    public MyException(String message) {
        super(message);
    }
}

多线程

进程是一个应用程序,进程之间的内存相互独立,互不干扰
线程是一个进程中的一个执行场景,一个进程可以启动多个线程,线程之间栈内存相互独立,内存和方法区内存是共享的
如果进程是公司,那线程就是员工!!!

多线程的作用:是为了提高程序的执行效率,提高CPU使用率

创建线程的两种方式:
1.继承Thread类,重写run方法
2.实现Runnable接口,重写run方法

public class CodeTest {
    public static void main(String[] args) throws Exception{
       //创建线程对象
        Thread thread01 = new Thread01();
        //设置线程名
        thread01.setName("t1");
        //开启分支线程
        thread01.start();
        //主线程
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程 ==> " + i);
        }
    }
}
class Thread01 extends Thread{
    //重写run方法
    public void run(){
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "线程\t" + i);
        }
    }
}
-----------------------------------------------
public class CodeTest {
    public static void main(String[] args) {
        //创建线程
        Thread thread = new Thread(new Thread01());
        //设置线程名
        thread.setName("t1");
        //开启分支线程
        thread.start();
        //给主线程改名
        Thread.currentThread().setName("t2");
        //这是主线程
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "主线程\t" + i);
        }
    }
}
class Thread01 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "线程\t" + i);
        }
    }
}

JDK1.5新增的创建线程两种方式:
1.实现Callable接口,需要借助FutureTask类
2.使用线程池,开发使用!!!

public class Test {
    public static void main(String[] args) {
        //1.创建线程池,指定线程数量
        ExecutorService service = Executors.newFixedThreadPool(10);
        //2.执行指定线程,传入Callable类型或者Runnable类型
        service.execute(new MyRunnable());
        //3.关闭线程池
        service.shutdown();
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        int sum = 0;
        for (int i = 0; i < 101; i++) {
            if (i % 2 == 0){
                sum += i;
            }
        }
        System.out.println("sum = " + sum);
    }
}
----------------------------------------------------------------------------
public class Test {
    public static void main(String[] args) {
        //2.创建实现类
        MyCallable myCallable = new MyCallable();
        //3.创建闭锁FutureTask类,传入实现类
        FutureTask futureTask = new FutureTask(myCallable);
        //4.创建线程传入闭锁对象,启动线程
        new Thread(futureTask).start();
        try {
            //5.获取线程返回的值,处理异常
            Object o = futureTask.get();
            System.out.println("100以内的偶数和为:" + o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
//1.创建实现类实现Callable接口
class MyCallable implements Callable{
    @Override
    public Object call() throws Exception {
        //求100以内的偶数和
        int sum = 0;
        for (int i = 0; i < 101; i++) {
            if (i % 2 == 0){
                sum += i;
            }
        }
        return sum;
    }
}

线程的生命周期:
在这里插入图片描述线程的常用方法:
start();启动一个分支线程,在JVM开辟新的空间,完成后方法瞬间结束,自动调用run方法
currentThread();静态方法,代表当前线程对象引用
getName();获取线程名
setName();设置线程名
yield();释放当前CPU时间片,作用是让位,从运行状态回到就绪状态抢CPU时间片
sleep(毫秒数);静态方法,作用是让当前线程进入休眠状态,进入阻塞状态
interrupt();作用是终止线程的休眠
isAlive();判断当前线程是否存活
stop();作用是强制终止线程,已过时不建议使用
join();作用是合并线程,当前线程进入阻塞状态,其他线程开始插队执行,执行结束了当前线程才能执行

线程优先级:
MAX_PRIORITY(最高级) ,MIN_PRIORITY(最低级) ,NORM_PRIORITY(标准)默认
setPriority();作用是设置线程的优先级,最低1,最高10,默认5
getPriority();作用是获取线程的优先级

锁机制

判断数据不安全的因素:多线程环境下对共享数据进行了修改!!!
解决办法:加锁

同步:排队现象
异步:多线程并发

同步锁synchronized:
synchronized (对象锁/类锁) {线程同步代码块}
synchronized加在方法体里:对象是唯一的,多线程共享的就行
synchronized加在实例方法上/里:对象锁必须是this
synchronized加在静态方法上/里:获取的是类锁,全局唯一,类锁必须是类名.class
推荐使用加在方法里的,效率更高,更灵活!!!
对象锁需要满足:1.多线程共享2.唯一锁

对象锁执行原理:
多线程并发下,第一个线程执行程序遇到synchronized(对象锁),获取到对象锁,第二个线程也遇到synchronized(对象锁),但是发现没锁,进入等待,直到第一个线程归还对象锁,第二个线程才能获取对象锁继续执行!!!

程序在运行状态遇到synchronized关键字,会先释放CPU的时间片,然后到锁池去找对象锁,如果没找到会进入锁池等待,如果找到了会进入就绪状态抢CPU时间片

--------------------------------------------------------------
Lock锁:手动开启和关闭锁,性能更好,建议使用
1.在实现类体创建对象new ReentrantLock();必须保证多线程共享一个Lock锁
2.调用方法lock,开启锁机制
3.最后调用unlock方法,解锁
--------------------------------------------------
死锁:线程嵌套,相互调用等待

public class CodeTest {
    public static void main(String[] args) {
        //创建object对象
        Object o1 = new Object();
        Object o2 = new Object();
        //创建线程对象,共享o1,o2
        Thread t1 = new Thread01(o1,o2);
        Thread t2 = new Thread02(o1,o2);
        //启动线程
        t1.start();
        t2.start();
    }
}
//创建线程1
class Thread01 extends Thread{
    Object o1;
    Object o2;

    public Thread01(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    public void run(){
        synchronized (o1){//有两把对象锁,这里先获取o1的对象锁
            try {
                Thread.sleep(1000);//这里休眠1秒,把o2的对象锁让给t2线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2){//o2的对象锁在t2线程,所以在锁池等待对象锁的归还

            }
        }
    }
}
//创建线程2
class Thread02 extends Thread{
    Object o1;
    Object o2;

    public Thread02(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    public void run(){
        synchronized (o2){//有两把对象锁,这里先获取o2的对象锁
            try {
                Thread.sleep(1000);//这里休眠1秒,把o1的对象锁让给t1线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){//o1的对象锁在t1线程,所以在锁池等待对象锁的归还

            }
        }
    }
}

String类

String在JDK1.8之前底层是char数组,1.8之后是byte数据,在内存上比原来节省了1个字节,String是不可变序列,一旦创建之后不可改变,保存在方法区的字符串常量池中,所谓一旦创建不可改变,就是你定义了一个字符串,后面又对它进行了重赋值,原来的字符串也还是存在的,并没有被改变,所以它不适合做大量字符串拼接操作

常量和常量拼接结果在常量池,常量池不会存在相同的常量
拼接的只要其中有变量,结果在堆中,如果有final修饰,结果在常量池
如果拼接的结果调用了intern方法,结果就在常量池中

String类的常用方法:
charAt(数组下标);获取下标对应的字符
contains(“子字符串”);判断前面字符串是否包含后面的子字符串
endWith(“子字符串”);判断字符串是否以子字符串结尾
equalsIgnoreCase(" ");判断两个字符串内容是否相等并忽略大小写
getBytes();将字符串转换成字节数组
indexOf(“子字符串”);获取子字符串在字符串中第一次出现的下标
isEmpty();判断字符串是否为空
length();获取字符串的长度
lastIndex(“子字符串”);获取子字符串最后一次出现在字符串的下标
replace(“1”,“2”);将1变成2
split(“-”);以-来拆分字符串
startsWith(“子字符串”);判断字符串是否以子字符串开始
substring(元素下标);从下标位置开始截取字符串
substring(下标起始位,下标结束位);截取一段字符串内容,含头不含尾
toCharArray();将字符串转换成char[]
toLowerCase();将字符串转换成小写
toUpperCase();将字符串转换成大写
trim();去掉字符串前后的空格
compareTo(" ");比较两个字符串的大小,字母越靠后越大.如果相等输出0,前小后大输出-1,前大后小输出1
String.valueOf(“任意类型数据”);将非字符串数据转换成字符串

-----------------------------------------------
StringBuffer丶Stringbuilder
StringBuffer:可变字符序列,线程安全,效率低
Stringbuilder:可变序列,线程不安全,效率高
默认数组长度是0,容量是16,扩容是2倍 + 2
创建对象的时候尽可能给一个初始化容量,减少扩容次数,提高程序的执行效率.比如new StringBuffer(100);

StringBuffer丶Stringbuilder常用方法:
append();追加数据,拼接字符串
delete(int start,int end);删除指定位置的内容
replace(int start,int end,String str);将区间位置左闭右开的元素替换成str
insert(int index,xxx);指定位置插入xxx
reverse();将当前字符序列逆转
setCharAt(int index,char xx);将指定位置字符改为xx

Date类(过时)

System.currentTimeMillis();获取自1970年1月1日 00:00:00 000到当前时间的总毫秒值
Date date = new Date();获取当前时间对象
date.getTime();获取当前时间的毫秒值
SimpDateFormat sdf = new SimpDateFormat(“yyyy-MM-dd HH:mm:ss SSS”);获取日期格式化对象
sdf .format(date);格式化当前日期
sdf.parse(“字符串”);解析字符串成Date类型

Calender类(偏移)

Calender类是抽象类
Calender.getInstance();获取当前系统的日历对象
get(Calendar.DAY_OF_MONTH);获取当前月的天数
set(Calendar.DAY_OF_MONTH,22);设置当前月的天数
add(Calendar.DAY_OF_MONTH,-3);当前月的天数减3
getTime();将日历类转换成Date类
setTime();将Date类转换成日历类

获取月份时,一月是0,12月是11
获取星期时,周日是1,周六是7

LocalDateTime(推荐)

LocalDate.now();获取当前日期
LocalTime.now();获取当前时间
LocalDateTime.now();获取当前日期 + 时间
LocalDateTime.of(…);设置指定的年月日小时分秒,没有偏移,最常用!!!
getXXX();获取时间结构
withXXX();设置时间结构
plusXXX();加操作
minusXXX();减操作

Instant类

Instant.now();获取本初子母线标准时间,北京少8小时
atOffset(ZoneOffset.ofHours(8));添加时间的偏移量
toEpochMilli();获取1970.1.1到至今的毫秒数
ofEpochMilli(…L);通过毫秒值获取Instant实例

DateTimeFormatter日期格式化对象,类似于SimpleDateFormat
自定义:DateTimeFormatter.ofPattern(“yyyy-MM-dd HH:mm:ss SSS”)

Scanner类

扫描接收用户输入的各种类型数据

next()和nextLine()的区别:
虽然都是接收字符串,但是next通用

nextLine只能在对象后面连着用才会生效

Random类

产生随机数,默认范围是0.0-1.0,左闭右开!!!

System类

System.out:out是System类的静态变量
System.gc();建议垃圾回收器启动
System.currentMillis();获取自1970年1月1日到系统当前时间的总毫秒值
System.exit(0);退出JVM虚拟机
System.getProperty(key);根据K获取V

Comparable和Comparator接口

Comparable和Comparator接口是比较器,String类,包装类等默认实现了比较器

Comparable接口叫自然排序:this跟obj比较,如果this大于obj返回1,小于返回-1,相等0
实现类实现Comparable接口,重写compareTo方法编写排序规则

Comparator接口是定制排序:重写compare方法,o1跟o2比较

当比较规则只有一条的时候,建议用Comparable接口.
当比较规则有多条,需要来回切换规则,建议用Comparator接口

BigDecimal类

BigDecimal类精度极高,专门用在财务做计算,加减乘除

bigDecimal1.add(bigDecimal2);加
bigDecimal1.subtract(bigDecimal2);减
bigDecimal1.multiply(bigDecimal2);乘
bigDecimal1.divide(bigDecimal2);除

DecimalFormat类

#表示数字,逗号代表千分位,小数点代表小数点,0表示不够时补0

DecimalFormat decimalFormat = new DecimalFormat(“#,###,###.000”);

//格式化数字
String format = decimalFormat.format(1111234.56);

System.out.println(format);//1,111,234.560

枚举类

类对象的数量是确定有限的类叫枚举类
定义一组常量,返回结果超过2种以上用枚举!!!两种建议用布尔类型
枚举类对象都是public static final修饰

enum 类名{枚举类对象,枚举类对象;}逗号隔开,分号结束!!!

public enum Result {
//四季值
SPRING, SUMMER, AUTUMN, WINTER;
//或者
SPRING(“春天”,“春暖”){重写接口的方法},SUMMER(…);
}

常用方法:
values();获取所有属性值
valueOf(“属性值”);获取名为属性值的对象
soString();获取枚举类常量的名字

实现接口,跟类使用方式一样用implements
让枚举类对象都实现接口的方法

正则表达式

一种字符模型,专门做字符串格式匹配

^开始,$结束
\d 代表数字
\D 非数字
\w 英文字母
\W 非英文

“^a(2)$” ,表示两个a字符,相当于aa

注解

注解是标记提示,规范要求

JDK内置注解:
@Override:重写方法
@Deprecated:表示已经过时了不建议用,很危险或有更好的选择
@SuppressWarnings:抑制编译器警告,变量是灰色,加注解变成显色

元注解:
对注解解释说明的注解称为元注解
@Target:指定注解作用的位置,比如类上,方法上…
@Retention:指定注解保存的位置(生命周期)默认是CLASS,SOURCE保存在源文件,CLASS保存在字节码文件,RUNTIME保存在字节码文件并且可以被反射机制读取到
@Documented:修饰的注解被javadoc解析时会保留下来
@Inherited:修饰的注解具有继承性

自定义注解:修饰符列表 @interface 类名{}
注解没有属性,表明是一个标识作用,JVM会特殊对待
有属性必须赋值,用value定义成员,用default指定默认值
属性的类型是:八大基本类型,String,Class,枚举类对象,数组

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
@interface MyAnnotation{
    //看着像方法,实际上是属性name
    String name() default "null";
    int age();//没有默认值,使用注解时必须赋值.
    String value();
}

Collection集合

Collection集合是单列集合,存储对象的引用!!!
在这里插入图片描述Collection集合常用方法:
add();添加元素到集合
addAll();添加集合所有元素到另一个集合
size();获取集合元素的个数
clear();清空集合所有元素
contains();判断集合是否包含元素
containsAll();判断集合所有元素是否在另一个集合中
remove();删除集合的元素
removeAll();删除当前集合里,另一个集合所有元素
retainAll();获取集合和另一集合的共有元素,非共有的被删除
remove()和cotains方法底层调用了equals方法
isEmpty();判断集合是否为空
toArray();将集合转换成数组
Arrays.asList();将数组转换成List集合

迭代器遍历集合:
迭代器Iterator搭配while循环使用,Collection类型专用

获取的是当前集合状态下的迭代器,如果后面集合结构发生了变化,迭代器需要重新获取
iterator();获取迭代器
hasNext();判断集合下一个位置是否有元素
next();移到下一个元素位置并返回元素
remove();删除迭代中的元素,会自动更新集合的结构,不要使用集合的remove方法,这样集合结构会发生改变,需要重新获取迭代器

增强for循环遍历集合:只能遍历单列集合,没有下标概念
foreach用于快速遍历集合和数组,搭配泛型使用

for(数据类型 变量名 : 引用){输出变量名}

---------------------------------------------------------------
List接口:
List接口是有序可重复的,有三个实现类ArrayList,LinkedList,Vector

ArrayList:
底层是Object数组,效率高,线程不安全,调用add方法才会创建长度10的数组,扩容为原数组1.5倍

LinkedList:
底层是双向链表,增删效率高,查询效率低,每次查找都是从头节点开始遍历

Vector:
底层是Object数组,效率低,线程安全,极少使用,底层创建长度为10的数组,扩容为原数组的2倍

List接口常用方法:
add();指定位置插入元素
addAll();指定位置插入另一集合所有元素
get();获取指定位置元素
indexOf();获取元素第一次出现的位置
lastIndexOf();获取元素最后出现的位置
remove();删除指定位置的元素
set();设置指定位置的元素为xx
subList();获取区间位置的元素

----------------------------------------------------------
Set接口:
Set接口是无序不可重复的,有三个实现类,HashSet,TreeSet,LinkedHashSet,无序指根据哈希值存元素

HashSet:
线程不安全,可以存null值,底层是HashMap,存入HashSet的元素等于存在HashMap的key部分

LinkedHashSet:
是HashSet的子类,遍历数据可以按照添加的顺序遍历,频繁遍历LinkedHashSet效率高于HashSet

TreeSet:
SortedSet实现类是TreeSet

按照对象指定的属性排序,实现了Comparable接口(比较器)

Set接口没有定义额外的方法,使用的是Collection集合的方法

HashSet底层是HashMap,HashMap底层是哈希表,哈希表是数组加链表加红黑树结构,向HashSet接口添加元素,会先调用该类的HashCode方法计算哈希值,如果哈希值对应的地址有值,会再调用equals方法比较为false添加成功,true失败,如果地址没有值,直接添加成功!!!
所以向Set接口添加数据,规定一定要重写HashCode和equals方法
重写equals方法也必须重写HashCode,互相绑定,保持一致性!!!

TreeSet的底层是TreeMap,TreeMap的底层是二叉树
在这里插入图片描述

Map集合

Map是双列结构,存储Key-Value键值对,是无序不可重复的!!!
HashMap底层是数组 + 链表 + 红黑树结构,数组方面,调用put方法时才创建长度16的数组,加载因子0.75,扩容为原数组的两倍,两倍的原因是通过取模和位运算的结果,优化性能最好,链表方面,存储元素时,会调用hashcode方法计算哈希值,在哈希算法获取数组下标,如果下标上没有元素则直接存储,如果有元素,会在调用equals方法比较,如果为false则直接存储,如果为true,则覆盖元素,在红黑树方面,当数组长度 > 64,链表的个数 > 8,整体结构会变成红黑树,当节点的个数 < 6,会再次变成链表>

有三个实现类:HashMap,TreeMap,Hashtable(Properties)

HashMap:线程不安全,效率高,可以存null的key和value
衍生LinkedHashMap:按照添加元素的顺序遍历,频繁遍历效率高于HashMap

TreeMap:实现比较器排序遍历,考虑key的自然排序或定制排序,底层是红黑树

Hashtable:线程安全,效率低,不能存储null的key和value
初始化容量是11,默认加载因子是0.75f,扩容是原容量 * 2 + 1
衍生Properties:常用来处理配置文件,key和value必须是字符串
setProperty(“K”,“V”);存储字符串类型数据
getProperty(“K”);通过K获取V

Map常用方法:
put(K,V);添加键值对
putAll();将map集合所有键值对添加到另一个集合
remove();删除指定key的键值对,返回value
clear();清空map所有键值对
get(K);通过K获取V
containsKey(K);判断集合是否包含K
containsValue(V);判断集合是否包含V
isEmpty();判断集合个数是否为0
keySet();获取Map集合所有的K返回给Set集合
size();获取集合个数
values();获取Map集合所有V,返回Collection
entrySet();获取集合的键值对返回给Set集合,元素的类型是Map.Entry,有自己特有的方法,比如getKey();获取K,getValue();获取V

遍历Map集合的方式:
第一种:通过ketSet方法,获取所有key存储到Set集合在遍历
第二种:通过entrySet方法,获取所有键值对存储到Set集合在遍历
第三种:通过values方法,获取所有值存储到Collection集合在遍历

Collection工具类

常用方法:
synchronizedList(list类型);将list集合转成线程安全
sort(list);对list集合进行升序排序
reverse(list);反转list中的元素顺序
shuffle(list);对list集合进行随机排序
swap(list,1,2);将集合的1位置元素和2位置元素换位
max(Collection);获取集合最大元素
max(Collectiong,Comparator);根据Comparator规则获取集合最大值
min();参考max
frequency(Collection,Object);获取集合指定元素出现的个数
copy(list dest,list src);将集合src内容复制到集合dest
replaceAll(list,oldVal,newVal):将list的旧值改为新值

泛型

泛型在编译阶段有用,是给编译器参考的
优点:不需要向下转型,要调用子类特有的方法才向下转型
缺点:集合的元素缺乏多样性

泛型的类型必须是类,如果泛型没有指明类型,默认Object
<>里随便写只是标识符,但是经常写成< E >表示element元素和< T >type类型
静态方法,异常类不能使用泛型

自定义泛型类丶接口:
情况1:class Test01<E>{}
创建泛型类对象需指定泛型类型!!!
情况二:class Test02<T> extends Object<T>{}

自定义泛型方法:
public <E> List<E> method(E[ ] arr){…}
和类的泛型没有任何关系,方法可以是静态的
public static <E> List<E> method(E[ ] arr){…}

通配符:?
可以匹配任意类型数据

File类

File类和流无关系,不能完成文件读与写
File对象只是文件或目录的抽象形式,操作的是文件或目录

File类常用方法:
exists();判断文件或目录是否存在
createNewFile();创建新文件
mkdir();创建目录
mkdirs();创建多重目录
getParent();获取父文件路径
getParentFile();也是获取父文件路径,返回类型不同
getAbsolutePath();获取文件或目录的全路径
getAbsoluteFile();也是获取文件或目录的全路径,返回类型不同
getName();获取当前文件或目录的名字
isFile();判断是否为文件
isDirectory();判断是否为目录
canRead();判断是否可读
canWrite();判断是否可写
isHidden();判断是否隐藏
lastModified();获取文件最后一次修改时间,返回毫秒值是1970到修改时间毫秒值
length();获取文件大小
listFiles();获取当前目录下所有子文件或目录
delete();删除文件或目录

IO流

IO流操作的是磁盘和内存,对文件或目录的读和写
文件是字节或字符构成,流分为字节流和字符流
内存为中心,硬盘到内存是输入流(读),内存到硬盘是输出流(写)
所有流都实现java.io.Closeable接口,都可关闭,都有close方法,流是占资源的,记得关闭
所有输出流都实现了java.io.Flushable接口,都可刷新,都有flush方法,输出后记得刷新以免数据丢失
Stream结尾的类都是字节流,Reader/writer结尾的都是字符流
节点流没有读到字节或字符,返回-1,包装流没有读到字节或字符,返回null
在这里插入图片描述在这里插入图片描述流根据常用IO流:
FileInputStream:文件字节输入流
FileOutputStream:文件字节输出流
FileReader:文件字符输入流
FileWriter:文件字符输出流
InputStreamReader:字节输入流转换成字符输入流
OutputStreamWriter:字节输出流转换成字符输出流
BufferedReader:带缓冲区的字符输入流
BufferedWriter:带缓冲区的字符输出流
BufferedInputStream:带缓冲区的字节输入流
BufferedOutputStream:带缓冲区的字节输出流
DataInputStream:数据字节输入流
DataOutputStream:数据字节输出流
只能通过DataInputStream来读,并且读的顺序必须和写的一致
PrintWriter:字符输出流
PrintStream:字节输出流
默认输出到控制台,可改变输出方向,不指向控制台输出,指向文件输出,用方法System.setOut(PrintStream类型)
ObjectInputStream:反序列化,磁盘为中心,磁盘到内存的过程
ObjectOutputStream:对象字符输出流 序列化

序列化和反序列化:
序列化对象必须实现Serializable接口,接口没有任何代码,是标识性接口,JVM会给标识接口特殊待遇,自动生成一个序列化版本号,用来区分类,Java区分类一靠类名,二靠序列号
transient修饰的属性不参与序列化

反射

通过反射操作类字节码,不修改源代码,程序灵活,符合OCP原则:对扩展开放,对修改关闭
性能慢于正常代码,比正常代码复杂,建议灵活地方使用

获取类字节码:
Class.forName(“类全路径”);包名加类名
引用.getClass();
类名.class();
通过字节码操作类所有结构!!!

网络编程

IP:定位主机,唯一标识,Java类InetAddress代表IP
端口号:应用程序
IP + 端口号 = Socket节点,定位主机上的应用程序

TCP,UDP协议 + IP + 端口号 = 数据传输

URL:统一资源定位符,定位某个资源地址
协议 + IP + 端口号 + 资源地址 + 参数列表 = http://localhost:8080/userLogin?username=zs&password=123

IDEA-Debug

Step Over(F8):进入下一步
Resume Program(F9):进入下一个Debug,直接跳到下一个红圆点
Step into:进入方法
Forcs Step into:强制进入方法
Step Out:回到上一步

JDK新特性

Lambda:
->:Lambda操作符
操作符左边:Lambda形参列表(抽象方法的形参)
右边:Lambda体(抽象方法的方法体)
接口类型 x = (形参1,形参2) -> {方法体;};
要求:是接口,方法1个

方法引用:
是Lambda另外写法形式
类或引用 :: 方法名

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值