#Java基础
目录:
1:基本知识
4:常量与变量
5:数据类型
7:方法
8:流程控制
9:数组(array)
10:Java内存划分
11:面向对象
12:API
13.抽象:也属于面向对象
14.接口(Interface
15.集合(Collection)
16.数据结构
17.异常
18.多线程
20.File类
22.网络编程入门
23.函数式接口
24.方法引用
25.测试
26:反射:框架设计的灵魂(用于写框架
27:注解:说明程序的(给计算机看的
===============================================
####1:基本知识:
1:进制转换:
2:命令提示符:
切换盘符: 盘符名称:
进入文件夹: cd文件夹名称
返回上一级: cd..
返回根目录: cd \
查看当前内容: dir
清屏: cls
退出: exit
3:JDK包含JRE包含JVM
1:JDK:开发工具
2:JRE:运行
3:JVM:虚拟机
4:Java的开发平台:
JAVA SE:主要用在客户端开发
JAVA EE:主要用在web应用程序开发
JAVA ME:主要用在嵌入式应用程序开发
5:程序开发的步骤:
1:写源程序(xxx.java)
2:编译源程序(xxx.javac) javac xxx.java
3:运行() java xxx
####2:关键字
特点:完全小写的字母,在工具中有特殊颜色
####3:标识符
自定义的内容(如类名、方法名、变量名)
####4:常量与变量
1:常量
在程序运行时,固定不变的量。
*分类:
1:字符串常量:凡是用双引号引起来的。如"abc"、"123"
2:整数常量:直接写上的数字,没有小数点 如 100、 200
3:浮点数:有小数点。 如2.5、-3.1
4:字符常量:用单引号,如'A'、'中'
5:布尔变量:true 、false
6:空常量: null,不能直接打印输出
2:变量(variable)
*在程序运行期间,内容可以发生改变的量
*创建: 数据类型 变量名称; //创建一个变量
变量名称=数据值; //赋值
or:
数据类型 变量名称 = 数据值
*注意事项:
1:多个变量名称不可以重复,
2:对于flaot和long类型来说,字母后缀F和L不能丢
3:
####5:数据类型
1:基本数据类型(四类八种)
1:整数型:
byte(-128~127)、
short(-32768~32767)、
int(-2*31次方~2*31次方-1)、(默认)
long(-2*63次方~2*63次方-1)
2:浮点型:
float(1.4013E-45~3.4028E+38)、
double(4.9E-324~1.7977E+308)(默认)
3:字符型:
char(0~65535)
4:布尔型:
boolean:true、false
*byte<short<char<int<long<float<double
2:引用数据类型
字符串、数组、类、接口、lambda
3:数据类型转换
1:自动转换(隐式):将取值范围小的类型自动提升为取值范围大的类型
2:强制转换(显式):将取值范围大的类型自动提升为取值范围小的类型
*格式:范围小的类型 范围小的变量名 = (范围小的类型) 原本范围大的数据;
*注意事项:强转有可能发生精度损失、数据溢出
BigDecimal类:专门对浮点型数据的计算,
*用来对超过16位有bai效位的数进行精确的运算
*由于浮点数采用二进制系bai统表示du,而二进制无法精确的表示1/10,好比十zhi进制无法精确表示1/3一样。因此,dao对于很多值浮点数都是采用其能够表示的离目标值近的数来表示,这有可能会在计算中带来不易察觉的误差。为了解决基本数据类型浮点数不能进行精确计算的问题,Java中专门提供了java.math.BigDecimal类,其提供浮点数的精确计算功能。
三元运算
*格式:数据类型 变量名称 = 条件判断?表达式A:表达式B
(如:int max = a>b? a:b )
####7:方法
*格式:
修饰符 返回值类型 方法名称(参数类型 参数名称,...){
方法体;
return 返回值;
}
如:
public class Demo01Method {
public static void main(String[] args) {
sum(1,20); //单独调用
sysytem.out.println(sum(1,20)); //打印调用
int number = sum(1,20); //赋值调用
}
public static int sum(int a, int b){
int result = a+b;
return result;
}
}
*调用方法 格式:方法名称(参数列表);
*注意事项:
1:方法定义的先后顺序无所谓
2:方法的定义不能产生嵌套包含关系
3:方法定义好了之后,不会执行的。如果想执行,一定要进行方法的调用。
4:void:是无返回值,void类用final修饰的。
*方法有参数与无参数
*方法有返回值与无返回值
1:有返回值:可用单独、打印、赋值调用
2: 无返回值:只能用单独调用
*方法重载(overload):多个方法的名称一样,就可以实现多个类似的功能。
1:与下列因素有关:参数个数不同、参数类型不同、参数的多类型顺序不同
2:与下列因素无关:参数名称、方法返回值类型、
####8:流程控制
1:顺序结构
2:判断语句
1:单if语句:
*if(关系表达式){
语句体;
}
2:if..else语句:
*if(关系表达式){
语句体1;
}else{
语句体2;
}
*注意:条件满足执行语句1,反之执行语句2。
3:复合的、扩展的if...else语句
*if(判断条件){
执行语句1;
}else if(判断条件2){
执行语句2;
}
...
}else if(判n){
执行n;
}else{
执行语句n+1;
}
3:选择语句switch
*switch(表达式){
case 常量值1;
语句体1;
break;
case 常量值1;
语句体1;
break;
...
default;
语句体n+1;
break;
}
*注意:1:多个case后面的数值不可以重复
2:switch后面小括号只能是下列数据类型:基本:byte/short/ char/int 、引用:string字符串/enum枚举
3:前后顺序可颠倒,break语句可省略,去掉break语句会穿透。
4:循环语句
*循环结构:1:初始化语句
2:条件判断
3:循环体
4:步进语句
1:for循环
*for(初始化表达式;布尔表达式;步进表达式){
循环体
}
2:while循环
1:标准格式:
*while(条件判断){
循环体
}
2:扩展格式:
*初始化语句;
while(条件判断){
循环体;
步进语句;
}
3:do-while循环
1:标准格式:
*do{
循环体
}while(条件判断);
2:扩展格式:
*初始化语句;
do{
循环体;
步进语句;
}while(条件判断);
4:注意:次数确定的场景多用for循环,否则用while循环。
5:循环控制:
1:break语句 打断
2:continue语句 立即跳过当前剩余内容,开始下一次循环
6:死循环:
*while(true){
循环体
}
7:循环嵌套
*总共循环次数=外循环次数*内循环次数
####9:数组(array)
*概念:是一种容器,可以同时存放多个数据值
*特点:1:是一种引用数据类型
2:当中的多个数据,类型必须统一
3:数组长度在程序运行中不可改变
*两种初始化方式:
1:动态初始化(指定长度)
*格式:数据类型[] 数组名称 = new 数据类型[数组长度];
2:静态初始化(指定内容)
*格式:
1:标准格式:数据类型[] 数组名 =new 数据类型{元素1,元素2...}; 2:省略格式:数据类型[] 数组名 ={元素1,元素2,...};
*访问数组元素进行获取
*直接打印数组名称,得到的是数据对应的:内存地址哈希值
*格式:数组名称[索引值]
//索引值是int数字,代表数组当中元素的编号。从0开始
*访问数组元素进行赋值
*1:使用动态初始化数组时,其中的元素将会自动拥有一个默认值。规则如下:
如果是整数类型,默认为0
如果是浮点类型,默认为0.0
如果是字符类型,默认为'\u0000'
如果是布尔类型,默认为false
如果是引用类型,默认为null
*2:静态也有初始值,只是马上替换成了大括号里的值
*获取数组的长度
*格式:数组名称.length
数组创建后,程序运行期间,长度不可改变。
*遍历数组:对数组中每一个元素进行逐个处理,默认方式是打印输出。
(使用for循环,次数就是长度)
*数组反转: 对称位置的元素交换。
*for (int min = 0,max = array.length - 1;min<max;min++,max--){
int temp = array[min];
array[min] = array[max];
array[max] = temp;
}
*数组作为方法的参数或返回值,传递或返回的都是地址值。
####10:Java内存划分
1:栈(Stack):存放的都是方法中的局部变量(局部变量即只能在作用域内有效)。
2:堆(Heap): 凡是new出来的都在堆当中
3:方法区(Method Area):存储.class
4:本地方法栈(Native Method Stack):与操作系统相关
5:寄存器( pc Register):与cpu相关
*所有的引用类型变量,都可以赋值为一个null值
*数组必须进行new初始化才能使用其中元素
####11:面向对象
*三大基本特征:封装、继承、多态
1:封装:
*封装就是将一些细节隐藏起来,对于外界不可见。
*方法就是一种封装
*关键字private也是一种封装:
*被private修饰的,在本类中仍然可以随意访问,但超出本类范围就不再能被访问了。
*间接访问private成员变量,就是定义一对Getter/Setter。
*对于Getter来说,不能有参数,返回值类型和成员变量对应
*对于Setter来说,不能有返回值,参数类型和成员变量对应
//如:使用private关键字定义学生类,Getter/Setter。
public class Student {
private String name;
private int age;
private boolean male;
public void setName(String str){
name = str;
}
public String getName(){
return name;
}
public void setAge(int num){
age = num;
}
public int getAge(){
return age;
}
public void setMale(boolean b ){
male = b;
}
public boolean isMale(){
return male;
}
}
*注意事项:Boolean值的getter方法写成isXxx的形式。
//使用学生类
public class Demo01Student {
public static void main(String[] args ) {
Student stu = new Student();
stu.setName("那托");
stu.setAge(20);
setMale(true);
System.out.println("姓名:" +stu.getName());
System.out.println("年龄:" +stu.getAge());
System.out.println("男女:" +stu.isMale());
}
}
2:继承性:
*继承是多态的前提,如果没有继承就没有多态。
*继承的三个特点:
1:Java语言是单继承的
2:Java语言可以多级继承
3:一个父类可以拥有多个子类
*父类:基类、超类
*子类:派生类
*继承关系中的特点:
1:子类可以拥有父类的“内容”
2:子类还可以拥有自己专有的内容
*继承的格式:
*定义父类(普通的类定义):
public class 父类名称 {
//...
}
*定义子类:
public class 子类名称 extends 父类名称 {
//...
}
*区分子类方法中重名的三种变量:
1:局部变量:直接写成员变量名
2:本类成员变量:this.成员变量名
3:父类成员变量:super.成员变量名
*继承中成员方法的访问特点:
创建的对象是谁,就优先用谁,如没有则向上找
*重写与重载:
1:重写(Override):在继承关系中,方法的名称一样,参数列表也一样。
*特点:创建的是子类对象,则优先用子类方法。
*注意事项:
1: 必须保证父子类方法名称相同,参数列表相同。
2:@Override:写在方法前,用来检测是否正确覆盖重写。
3:子类方法的返回值必须小于等于父类方法的返回值范围。(Object是所有类的公共最高父类)
4:子类方法的权限必须大于等于父类方法的权限修饰符。 public > protected > (default):代表什么都不写,留空 > private
2:重载(Overload):方法名称一样,参数列表不一样。
*继承应用场景:设计原则:对于已经投入使用的类,尽量不要进行修改。推荐定义一个新的类,来重复利用其中共性内容,并且添加改动新内容。
*继承中构造方法的访问:
1:子类构造方法中有一个默认隐含的"super()"调用,所以一定是先调用父类构造,后执行子类构造。
2:可以通过super关键字来子类构造调用父类重载构造
3:super的父类构造调用,必须是子类构造方法的第一个语句,不能一个子类构造调用多次super构造。
4:总结:子类必须调用父类构造方法,不写则赠送super();写了则用写的指定的super调用,super只能有一个,还必须是第一个。
*super关键字的三种用法:(用来访问父类内容的)
1:在子类的成员方法中,访问父类的成员变量
2:在子类的成员方法中,访问父类的成员方法
3:在子类的构造方法中,访问父类的构造方法
*this关键字的三种用法:(用来访问本类内容的)
1:在本类的成员方法中,访问本类的成员变量
2:在本类的成员方法中,访问本类的另一个成员方法
3:在本类的构造方法中,访问本类的另一个构造方法。(在第三种中:this(...) 调用必须是构造方法的第一个语句)
*super和this不能同时使用
3:多态性(Multi)
*继承是多态的前提,即extends继承或者implements实现,是多态性的前提。
*一个对象拥有多种形态,这就是对象的多态性。(如:父类:人类 子类:学生 小明是一个对象,小明既有学生形态,也有人类形态)
*多态的格式:代码中体现多态性,就是:父类引用指向子类对象
父类名称 对象名 = new 子类名称();
或者:接口名称 对象名 = new 实现类名称();
*使用多态的好处:无论右边new的时候换成哪个子类对象,等号左边调用方法都不会变化。
*对象的向上转型(其实就是多态写法):
父类名称 对象名 = new 子类名称();
*含义:右侧创建一个子类对象,把它当作父类来看待使用
*注意事项:1:向上转型一定是安全的,从小范围到大范围
2:对象一旦向上转型为父类,那么就无法调用子类原本特有的内容。
如:
//1:Animal.java
public abstract class Animal{
public abstract void eat();
}
//2:Cat
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼!")
}
}
//3:Demo01Main.java
public class Demo01Main {
public static void main(String[] args) {
Animal animal = new Cat();
animal.eat();
}
}
*对象的向下转型:
*类与对象:
1:类:是一组相关属性和行为的集合。可以看成是一类事物的模板,使用事物的属性特征和行为特征来描述该类事物。
*属性:就是该事物的状态信息
*行为:就是该事物能够做什么
2:对象:是一类事物的具体体现。对象是类的一个实例,必然具备该类事物的属性和行为。
3:*类是对一类事物的描述,是抽象的。对象是一类事物的实例,是具体的。
*类是对象的模板,对象是类的实体。
*类:(类的首字母都是大写,关键字都是小写)
格式:public class ClassName{
成员变量
成员方法
}
如:
public class Student{
//成员变量
String name;
int age;
//成员方法
public void eat(){
System.out.println("吃饭!");
}
public void sleep(){
System.out.println("睡觉!");
}
public void study(){
System.out.println("学习!");
}
}
*注意:1:成员变量是直接定义在类当中的,在方法外面
2:成员方法不要写static关键字
***通常情况下,一个类并不能直接使用,需要根据类创建一个对象,才能使用。**
1:导包:也就是指出需要使用的类在什么位置。
import 包名称.类名称;
*对于和当前类属于同一个包的可以省略导包语句。
2:创建 //根据类创建对象
格式:类名称 对象名 = new 类名称();
3:使用,分两种情况:
1:使用成员变量:对象名.成员变量名
2:使用成员方法:对象名.成员方法名(参数)
*将一个对象作为参数/返回值 传递到方法当中时,实际上传递进去的是对象的地址值。
*成员变量和局部变量的区别:
1:定义的位置不一样
局部变量:在方法内部,成员变量:在方法外部,类当中。
2:作用范围不一样
局部变量:只有方法中才能用 成员变量:整个类通用
3:默认值不一样
局部变量:没有 成员变量:有,同数组一样。
*当方法的局部变量和类的成员变量重名时,根据“就近原则”,优先使用局部变量
*如果需要访问本类当中的成员变量,需要使用格式:
this.成员变量名
*“通过谁调用的方法,谁就是this。”
*构造方法:(专门用来创建对象的方法)
*格式:
public 类名称(参数类型 参数名称) {
方法体
}
*注意事项:构造方法的名称必须和所在的类名称完全一样。不写返回值类型,连void都不写,不能return一个具体的返回值,
如果没有写任何构造方法,编译器会默认一个构造方法,没有参数、方法体。
构造方法也可以重载:方法名称相同,参数不同。
*定义一个标准类:(包括四个组成部分)
1:所有的成员变量都要使用private关键字修饰
2:为每一个成员变量编写一对Getter/Setter方法
3:编写一个无参数构造方法
4:编写一个全参数构造方法
//创建一个标准的Student类
public class Student {
//成员变量
private String name;
private int age;
//无参构造方法
public Student(){
}
//全参构造方法
public Student(String name,int age){
this.name = name;
this.age = age;
}
//Getter/Setter
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int num){
this.age = age;
}
public int getAge(){
return age;
}
}
//Student类的调用
public class Demo02Student {
public static void main(String[] args ){
Student stu1 = new Student(); //无参构造
Student stu2 = new Studnet(name :"卡布达",age:17); //全参构造
System.out.println("姓名:" +stu2.getName() + ",年龄:" +stu2.getAge());
//如果需要改变对象当中的成员变量数据内容,仍然还需要使用setXxx方法
stu2.setAge(21);
System.out.println("姓名:" +stu2.getName() + ",年龄:" +stu2.getAge());
}
}
*final关键字:代表最终的、不可改变的
*常见四种用法:
1:可以用来修饰一个类
格式:public final class 类名称 {
}
含义:这个类不能有任何子类。
注意:final类,其中的成员方法都无法覆盖重写。
2:可以用来修饰一个方法
格式: 修饰符 final 返回值类型 方法名称(参数列表){
//方法体
}
注意:
1:当final关键字修饰一个方法时,这个方法就是最终方法,也就是不能被覆盖重写。
2:对于类和方法来说,abstract关键字和final关键字不能同时使用,因为矛盾。
3:可以用来修饰一个局部变量
格式:
注意:使用final修饰的局部变量,那么这个变量就不能进行更改。“一次赋值,终生不变”。
//不可变:
1:对于基本类型来说,不可变说的是变量当中的数据不可改变
2:对于引用类型来说,不可变说的是变量当中的地址值不可改变
4:可以用来修饰一个成员变量
*成员变量如果使用final关键字修饰,那么这个变量也照样不可变
1:由于成员变量具有默认值,用了final后必须手动赋值,不会再给默认值。
2:对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。
3:必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值。
*Java中的四种权限修饰符
public > protected > (default) > private
同一个类 yes yes yes yes
同一个包 yes yes yes no
不同包子类 yes yes no no
不同包非子类 yes no no no
*内部类(一个类包含另一个类)
*分类:
1:成员内部类:定义在方法外
定义格式: 修饰符 class 外部类名称 {
修饰符 class 内部类名称 {
//...
}
//...
}
注意:内用外,随意访问;外用内,需要内部类对象
使用:
1:直接:公式:外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
2:间接:在外部类的方法当中,使用内部类,然后main只是调用外部类的方法。
*内部类的同名变量访问:重名情况下,访问外部类成员变量:格式:外部类名称.this.外部类成员变量名
2:局部内部类:(包含匿名内部类)定义在一个方法内部的。
“局部”:只有当前所属的方法才能使用它,除了这个方法外面就不能用了。
定义格式:
修饰符 class 外部类名称 {
修饰符 返回值类型 外部类方法名称(参数列表){
class 局部内部类名称{
//...
}
}
}
*局部内部类的final问题:局部内部类,如果希望访问所在方法的局部 变量,那么这个局部变量必须是【有效final的】。从Java8+,只有局部变量事实不变,那么可省略final关键字。
原因:与生命周期有关
1:new出来的对象在堆内存当中
2:局部变量是跟着方法走的,在栈内存当中
3:方法运行结束后,立刻出栈,局部变量就会消失
4:但是new出来的对象会在堆当中持续存在,直到垃圾回收消失。
*匿名内部类:如果接口的实现类(或者是父类的子类)只需要使用唯一的一次。那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】
定义格式:
接口名称 对象名 = new 接口名称(){
//覆盖重写所有抽象方法
};
注意事项:
1:匿名内部类,在【创建对象】的时候,只能使用唯一一次。
如果希望多次创建对象,而且类的内容一样的话,那么就必须单独定义实现类了。
2:匿名对象,在【调用方法】的时候,只能调用一次。
如果希望同一个对象,调用多次方法,那么必须给对象起个名字。
3:匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】。
4:强调:匿名内部类和匿名对象不是一回事!
*小结一下类的权限修饰符:
定义一个类的时候,权限修饰符规则:
1:外部类:public / (default)
2:成员内部类:public / protected / (default) /private
3:局部内部类:什么都不写
*类作为成员变量类型:
如:
类作为成员变量类型
//1:Hero.java
//1:Hero.java
public class Hero {
private String name;
private int age;
private Weapon weapon;
public Hero(){
}
public Hero(String name,int age, Weapon weapon){
this.name = name;
this.age = name;
this.weapon = weapon;
}
public void attack(){
System.out.println("年龄为" + age + "的" +name + "用" +weapon.getCode()+ "攻击敌方。" );
}
public String getName(){
return name;
}
public void setName(String name ){
this.name = name;
}
public String getAge(){
return age;
}
public void setAge(int age ){
this.age = age;
}
public String getWeapon(){
return weapon;
}
public void setWeapon(Weapon weapon ){
this.weapon = weapon;
}
}
//2:Weapon.java
//2:Weapon.java
public class Weapon{
private String code;
public Weapon(){
}
public Weapon(String code){
this.code = code;
}
public String getCode(){
return code;
}
public void setCode(String code){
this.code = code;
}
}
//3:main.java
//3:main.java
public class DemoMain {
public static void main(String [] args)
Hero hero = new Hero();
hero.setName("剑魔");
hero.setAge(20);
Weapon weapon = new Weapon("多兰剑");
hero.setWeapon(weapon);
hero.attack();
}
*接口作为成员变量类型:
####12:API :
*概述:应用程序编程接口。Java API 是一本程序员的字典,是JDK中提供给我们使用的类的说明文档,这些类将底层的代码实现了封装,只需要学习这些类的使用。
*只有java.lang下的类不需要导包,其他都需要导包。
*引用类型的一般使用步骤:
1:导包:
import 包路径.类名称;
2:创建:
类名称 对象名 = new 类名称();//(跟创建一个对象一样)
3:使用:
对象名.成员方法名()
1:Scanner类:一个可以解析基本类型和字符串的简单文本扫描器
*System.in 系统输入指的是:通过键盘录入数据
如:Scanner sc = new Scanner(System.in); //创建
String str = sc.next(); //使用
int num = sc.netInt(); //使用
2:匿名对象:只有右边的,没有左边的名字和赋值运算符。
*new 类名称();
*注意事项: 匿名对象只能使用一次,喜下次再用得重新创建一个新得。
*使用建议:如果确定有一个对象只需要使用唯一一次,就可以用匿名对象。 、
3:Random类:用来生成随机数字
*使用中:如:方法nextInt(3),括号里的参数范围为[0,3)。
4:ArrayList<E> 类:对象数组,长度可随意改变,与数组不同(因为集合里存的都是地址值,基本类型没有地址值)
*E:代表泛型,只能是引用类型。JDK1.7后,等号右边的E可以不写,即<E>
*创建如:ArrayList<String> list = new ArrayList<>();
*注意:1:直接打印得到的不是地址值,而是内容.
2:向集合ArrayList中存基本类型数据,必须用对应的包装类。
*基本类型对应的其包装类:
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
*从JDK1.5后,自动装箱、拆箱:基本类型自动变成包装类,包装自动转成基本类型。
*向集合添加数据,add方法:list.add("","",""...)
5:包装类:
*如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类
*自动装箱/拆箱
*基本类型与字符串之间的转换
基本类型转换为String:
最简单方式:基本类型直接与""相连接即可;如:34+""
6:String类:字符串,程序中所有的双引号字符串,都是String类的对象
*特点:内容不可改变,所以可以共享。字符串效果上相当于是char[]字符数组,但是底层原理是bate[]字节数组。
*直接创建: String str = "...";
*字符串的常量池:程序中直接写上双引号字符串,就在池中。
*注意事项:
1:==:对于基本类型:是数值比较;对于引用类型:是地址值比较
2:双引号直接写的字符串在常量池当中,new的不在池当中。
3:字符串内容比较用方法equals:如str1.equals(str2); 推荐把常量(str1)写在前面,变量(str2)写在后面。
7:StringBuilder类:字符串缓冲区
*可以提高字符串的操作效率(看成一个长度可以变化的字符串)
*底层是一个数组,但是没有被final修饰,可以改变长度
StringBuilder在内存中始终是一个数组,占用空间少,效率高,如果超出了StringBuilder的容量,会自动的扩容。
*构造方法:
public StringBuilder():构造一个空的StringBuilder容器
public StringBuilder(String str):构造一个StringBuider容器,并将字符串添加进去
*StringBuilder和String可以相互转换:
String --> StringBuilder:可以使用StringBuilder的构造方法
StringBuilder(String str):构造一个字符串生成器,并初始化为指定的字符串内容
StringBuilder --> String :可以使用StringBuilder中的toString方法
public String toString():将当前StringBuilder对象转换为String对象
8:System类:工具类,
CurrentTimeMillis()方法:获取当前系统毫秒值时间
9:链式编程:
方法的返回值是一个对象,可以根据对象继续调用方法。
如:"avd".append(1).append(true).append(8.8)
10:静态static关键字:使用了static关键字,就属于类了,多个对象就可以共享
*static修饰成员变量:那么这个变量不再属于自己,而是属于类,多个对象共享同一份数据。
*计数器: private static int idCounter = 0; 每当new了一个新对象,计数器+1。
*static修饰成员方法:那么这个方法不再属于自己,而是属于类
*如果没有static关键字,那么必须首先创建对象,然后通过对象才能使用它。(普通方法)
*对于静态方法,可以通过对象名进行调用,也可以直接通过类名称(推荐)调用。
*总结:成员变量/成员方法,有了static,推荐使用类名称进行调用。
1:类名称.静态变量
2:类名称.静态方法()
*静态代码块:格式: public class 类名称{
static {
//静态代码块的内容
}
}
*特点: 1:当第一次用到本类时,静态代码块执行唯一的一次。
2:静态内容总是优先于非静态。
*典型用途:用来一次性地对静态成员变量进行赋值。
11:数组工具类Arrays:是一个与数组相关的工具类,提供了大量静态方法,用来实现数组常见的操作。
*方法:1:toString:将参数数组变成字符串
*如:int[] intArray = {10,20,30};
String intStr = Arrays.toString(intArray);
2:sort:对数组进行排序 : 如:Arrays.sort(数组名);
*注意事项:如果是数值、字符串,sort默认按照升序从大到小。如果时自定义的类型,那么这个自定义类型需要有Comparable或者Comparator接口的支持。
12:数学工具类Math:数学相关的工具类,提供了大量静态方法,完成与数学运算相关的操作。
*使用:Math.方法(参数) 如:Math.abs(3.12):获取绝对值。
13:Object类:所有类的父类,超类。所有对象(包括数组)都实现这个类的方法。
*Object类中的toString方法:
*String toString() :返回该对象的字符串表示。(直接打印对象的名字,其实就 是调用对象的toString方法)。
*重写toString()方法,打印对象的属性。(alt+enter)
*看一个类是否重写了toString方法,直接打印这个类对应对象的名字即可。
如果没有重写,那么打印的就是对象的地址值。如果重写了,就按照重写的方式打印。
*作用:打印对象信息
1:重写前:打印的是包名类名@地址值
2:重写后:打印的是对象中的属性值
*Object类中的equals方法:
*boolean equals(Object obj) :指示其他某个对象是否与此对象“相等”。
如:boolean b = p1.equals(p2);
*equals方法的源码:
public boolean equals(Object obj) {
return (this == obj);
}
参数:
Object obj :可以传递任意对象
方法体:
==:比较运算符,返回的是一个布尔值
基本数据类型:比较的是值
引用数据类型:比较的是两个对象的地址值
this:哪个对象调用的方法,方法中的this就是那个对象,如p1就是this。
obj:传递过来的参数p2
this == boj --> p1 == p2
*Object类的equals方法默认比较的是两个对象的地址值,没有意义。所有我们重写equals方法,比较两个对象的属性值(name,age)
*问题:
隐含着一个多态
Object obj = p2 = new Person("古力娜扎",18);
多态弊端:无法使用子类特有的内容(属性,方法)
解决:可以使用向下转型(强转)把Object类型转换为Person
**作用:比较两个对象的
1:重写前:比较的是对象地址值
2:重写后:比较的是对象中的属性值
*Objects类中的equals方法:比较两个对象是否相同,但是加了一些健壮性的判断。
如:boolean result = Objects.equals(s1,s2);
14:Date类:表示日期和时间的类(毫秒值)
*毫秒:千分之一秒
*毫秒值的作用:可以对时间和日期进行计算。
*可以日期转换为毫秒值计算,计算后,再把毫秒值转换为日期
*把日期转换为毫秒:
当前的日期:2088-01-01
时间原点:1970年1月1日 00:00:00
*就是计算当前日期到时间原点之间一共经历了多少毫秒?
*注意:中国属于东八区,会把时间增加八个小时
*把毫秒值转换为日期:
1 天 = 24 × 60 × 60 = 86400 秒 = 86400 × 1000 = 86400000毫秒
*获取当前系统时间到时间原点经历了多少毫秒:
public class Demo01Date {
public static void main(String[] args) {
System.out.println(System.currentTimeMillis());
}
}
*Date类的构造方法和成员方法:
1:构造方法:
1:空参构造方法:Date() 获取当前系统的日期和时间 如 Date date = new Date();
2: 带参数构造方法: Date(long date):传递毫秒值,把毫秒值转换为Date日期 如:Date date = new Date(1234343131L);
2:成员方法:
long getTime() :把日期转换为毫秒,返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
如:
Date date = new Date();
long time = date.getTime();
15:DateFormat类:
*日期/时间格式化子类的抽象类,通过这个类可以帮助我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来回转换。
*格式化:按照指定的格式,从Date对象转换为String对象
*解析:按照指定的格式,从String对象转换为Date对象
*因为DateFormat为抽象类,不能直接使用,所以需要用其子类SimplDateFormat。
*成员方法:
String format(Date date):按照指定的模式,把Date日期格式化为符合模式的字符串
Date parse(String soure):把符合模式的字符串解析为Date日期
如:
/*需求:计算一个人出生了多少天?
分析: 1:输入出生时间,转换为毫秒值
2:获取系统时间,转换为毫秒值
3:当前时间毫秒值-出生时间毫秒值
4:差值转换为天数
*/
public class Demo02Birthday {
public static void main(String[] args) {
//1:获取出生日期
Scanner sc = new Scanner(System.in);
System.out.println("请输入你的出生日期,格式为yyyy-MM-dd");
String birthdayDateString = sc.next();
//2:使用DateFormat类中的方法parse。把字符串的出生日期解析为Date格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date birthdayDate = sdf.parse(birthdayDateString);
//3:把Date格式的出生日期转换为毫秒值
long birthdayDateTime = birthdayDate.getTime();
//4:获取当前时间,转换为毫秒值
long todayTime = new Date().getTime();
//5:使用当前日期的毫秒值-出生日期的毫秒值
long time = todayTime - birthdayDateTime;
//6:把毫秒值的差值转换为天(s/1000/60/60/24)
System.out.println(time/1000/60/60/24);
}
}
16:Calender类:日历类
*是一个抽象类,里面提供了很多操作日历字段的方法(YEAR、MONTH、DAY_OF_MONTH、HOUR)
*Calender类无法直接创建对象使用,里边有一个静态方法叫getInstance(),该方法返回了Calender类的子类对象(Calender.getInstance())
17:File类:
*在IO包下。Java把电脑中的文件和文件夹(目录)封装为了一个File类。
*File类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作
*是一个与系统无关的类,任何的操作系统都可以使用这个类中的方法
*file:文件
directory:文件夹/目录
path:路径
####13.抽象:也属于面向对象
*如:父类:动物吃东西 子类1:猫吃鱼 子类2:狗吃你
*抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。如:public abstract void eat();
*抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可。
*如:public abstract class Animal {
public abstract void eat();
}
*使用 抽象类和抽象方法:
1:不能直接创建(new)抽象对象。
2:必须用一个子类来继承抽象父类。
3:子类必须覆盖重写抽象父类当中所有的抽象方法。
*覆盖重写(实现):去掉抽象方法的abstract关键字,然后补上方法体大括号。
4:创建子类对象进行使用
*抽象方法和抽象类的注意事项:
1:抽象类不能创建对象。
2:抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
3:抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
4:抽象类的子类,必须重写抽象父类中所有抽象方法。否则,编译无法通过而报错。除非该子类也是抽象类。
####14.接口(Interface)
*接口就是一种公共的规范标准。只要符合规范标准,就可以通用。接口就是多个类的公共规范。接口是一种引用数据类型
*接口的定义格式:
public interface 接口名称 {
//接口内容
}
*Java9接口可以包含的内容有:常量、抽象方法、默认方法、静态方法、私有方法
1:接口的抽象方法定义:
*格式:public abstract 返回值类型 方法名称(参数列表);
*注意事项:1:接口中的抽象方法,修饰符必须是两个固定的关键字,public abstract ,可以选择性省略
*使用步骤:
1:接口不能直接使用,必须有一个实现类来实现该接口
格式:
public class 实现类名称 implements 接口名称 {
//...
}
2:接口的实现类必须覆盖重写接口中所有的抽象方法。去掉abstract关键字,加上方法体大括号
3:创建实现类的对象,进行使用。(创建实现类的对象:类名后面+Impl)
如:
//接口的抽象方法:
//MyiterfaceAbstract.java(interface接口)
public interface MyInterfaceAbstract {
public abstract void methodAbs();
}
//MyinterfaceAbstractimpl.java
public class MyInterfaceimpl implements MyInterfaceAbstract{
@Override
public void methodAbs(){
}
}
//Demo01Interface.java
public class Demo01Interface {
public static void main(String[] args){
MyinterfaceAbstractimpl impl = new MyinterfaceAbstractimpl();
impl.methodAbs();
}
}
2:接口的默认方法 可以解决接口升级的问题。
*格式:
public default 返回值类型 方法名称(参数列表){
//方法体
};
*使用:接口的默认方法,可以通过接口实现类对象,直接调用
接口的默认方法,也可以被接口实现类覆盖重写。
3:接口的静态方法 (静态方法与类有关,与对象无关系)
*格式:
public static 返回值类型 方法名称(参数列表) {
//方法体
}
*使用:
不能通过接口实现类的对象来调用接口中的静态方法。
正确使用:通过接口名称,直接调用其中的静态方法。
格式:接口名称.静态方法名(参数);
4:接口的私有方法(用来解决多个默认方法之间重复代码问题)
1:普通私有方法:
*格式:
private 返回值类型 方法名称(参数列表) {
//方法体
}
2:静态私有方法:
*格式:
private static 返回值类型 方法名称(参数列表) {
//方法体
}
*使用:私有方法设定后,只有本接口内可以访问到私有方法。
5:接口中的常量:(接口当中也可以定义“成员变量”,但必须用pubc static final 三个关键字修饰,从效果上看,这就是接口的常量)
*格式:
public static final 数据类型 常量名称 = 数据值;
*注意事项:
1:接口中的常量,可以省略public static final ,注意:不写也照样是这样。
2:接口中的常量,必须进行赋值,不能不赋值。
3:接口中常量的名称,使用完全大写的字母,用下划线进行分隔
*接口小结:
1:成员变量其实是常量,
格式:[public] [static] [final] 数据类型 常量名称 = 数据值;
注意:
常量必须赋值,一旦赋值不可改变
常量名称完全大写,用下划线进行分隔
2:接口中最重要的就是抽象方法
格式:[public] [abstract] 返回值类型 方法名称(参数列表);
注意:实现类必须覆盖重写接口中所有的抽象方法,除非实现类是抽象类
3:从Java8开始,接口里允许定义默认方法
格式:[public] default 返回值类型 方法名称(参数列表) {方法体}
注意:默认方法也可以被覆盖重写
4:从Java8开始,接口里允许定义静态方法
格式:[public] static 返回值类型 方法名称(参数列表) {方法体}
注意:应该通过接口名称进行调用,不能通过实现类对象调用接口静态方法
5:从Java9开始,接口里允许定义私有方法
格式:
1:普通私有方法 :private 返回值类型 方法名称(参数列表) {方法体}
2:静态私有方法:private static 返回值类型 方法名称(参数列表) {方法体}
注意:private的方法只有接口自己才能调用,不能被实现类或别人使用。
*接口使用的注意事项:
1:接口是没有静态代码块或者构造方法的
2:一个类的直接父类是唯一的,但是一个类可以同时实现多个接口
*格式:
public class MyIterfaceImpl implements MyInterfaceA,MyInterfaceB {
//覆盖重写所有抽象方法
}
3:如果实现类所实现的多个接口中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
4:如果实现类没有覆盖重写所有接口中的所有抽象方法,那么实现类就必须是一个抽象类
5:如果实现类实现的多个接口中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写
6:一个类如果直接父类中的方法,和接口中的默认方法产生了冲突,优先用父类中的方法。
*类和接口相关的几种关系:
1:类与类之间是单继承的。直接父类只有一个
2:类与接口之间是多实现的。一个类可以实现多个接口
3:接口与接口之间是多继承的。
*注意事项:
1:多个父接口中的抽象方法如果重复,没关系
2:多个父接口中默认方法重复,那么子接口必须进行默认方法的覆盖重写,【而且带着default关键字】
####15.集合(Collection)
1:集合框架
Collection接口
(定义的是所有单列集合中共性的方法
所有的单列集合都可以使用共性的方法
没有带索引的方法)
/ \
【list接口】 【set接口】
1:有序的集合(存储和取出元素顺序相同) 1:不允许存储重复元素
2:允许存储重复的元素 2:没有索引(不能使用普通的for循环遍历)
3:有索引,可以使用普通的for循环遍历
/ | \ / \
/ | \ / \
/ | \ / \
/ | \ / \
【Vector集合】 【ArrayList集合】 【LinkedList集合】 【TreeSet集合】 【HashSet集合】
无序 无序
|
|
|
【LinkedHashSet集合】
有序
*学习集合的目标:
1:掌握每种集合的特性
2:会遍历集合,把数据取出来
3:掌握每种集合的特性
*集合框架的学习方式:
1:学习顶层:学习顶层接口/抽象类中共性的方法,所有的子类都可以使用。
2:使用底层:顶层不是接口就是抽象类,无法创建对象使用,需要使用底层的子类创建对象使用。
2:Collection集合:单列集合
*单列集合的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。JDK 不提供此接口的任何直接 实现:它提供更具体的子接口(如 Set 和 List)实现。此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。
*里面定义了所有单列集合共性的方法,任意的单列集合都可以使用Collection接口中的方法
*共性方法:
boolean add(E e) :把给定的对象添加到当前集合中,确保此collection包含指定的元素(可选操作)。
void clear() :移除此 collection 中的所有元素(可选操作)。
boolean remove(Object o) :从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。
boolean contains(Object o) :如果此 collection 包含指定的元素,则返回 true。
boolean isEmpty() :如果此 collection 不包含元素,则返回 true。
int size() :返回此 collection 中的元素数。
Object[] toArray():返回包含此 collection 中所有元素的数组。
1:List集合
*特点:
1:有序的集合(存储和取出元素顺序一致的)
2:允许存储重复的元素
3:有索引,可以使用普通的for循环遍历
*List接口中带索引的方法(特有)
*void add(int index, E element) :在列表的指定位置插入指定元素(可选操作)。
把指定的元素,添加到该集合中的指定位置上
*E get(int index) :返回列表中指定位置的元素。
返回集合中指定位置的元素
*E remove(int index) : 移除列表中指定位置的元素(可选操作),返回的是被移除的元素
*E set(int index, E element) :用指定元素替换列表中指定位置的元素(可选操作),返回值的更新前的元素。
*注意:操作索引的时候,一定要防止索引越界异常
*遍历List集合有三种方式:
1:for循环
2:迭代器
3:增强for
1:ArrayList<E>:List集合的子类/实现类,其数据存储的结构是数组结构。元素增删慢,查找快。由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。
2:LinkdeList集合:List集合的子类/实现类,其存储数据的结构是链表结构,方便元素添加、删除。查询慢,增删快
*是一个双向链表。
*实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。
3:Voctor集合:可以实现可增长的对象数组。与Collection不同,Voctor是同步的,单线程的,速度慢。
2:Set集合:
特点:
1:不允许存储重复元素
2:没有索引,没有带索引的方法,也不能使用普通的for循环遍历
1:HashSet集合:
个性特点:是一个无序集合,存储元素和取出元素的顺序有可能不一致
底层是一个哈希表结构(查询速度非常快)
*使用迭代器/增强for遍历
*哈希值:是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来得到地址,不是数据实际存储的物理地址)
在Object类有一个方法,可以获取对象的哈希值
int hashCode():返回该对象的哈希码值
*hashCode方法的源码:
public native int hashCode();
native:代表该方法调用的是本地操作系统的方法
*String重写了hashCode方法。
*哈希表:JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值得链表都存储在一个链表里。但是当位于一个桶中得元素较多,即hash值相等得元素较多时,通过key值依次查找得效率较低。
JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
*特点:速度快
*数组结构:把元素进行了分组(相同哈希值的元素是一组) 链表/红黑树结构:把哈希值的元素连接到一起。
*哈希冲突:两个元素不同,但是哈希值相同。如:重地、通话
*如果链表的长度超过了8位,那么就会把链表转换为红黑树(提高查询速度)
*Set集合存储元素不重复的原理
*HashSet存储自定义类型元素:
set集合保证元素唯一:必须重写hashCode方法和equals方法
2:LinkedHashSet集合:
底层是一个哈希表(数组+链表/红黑树)+链表。多了一条链表(记录元素的存储顺序),保证元素有序。
可变参数:是JDK1.5后出现的新特性
使用前提:当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数。
使用格式:定义方法时使用
修饰符 返回值类型 方法名(数据类型...变量名){}
可变参数的原理:可变参数底层就是一个数组,根据传递参数个数不同,会创建不同长度的数组,来存储这些参数
传递的参数个数,可以是0个(不传递),1,2...多个
如:
定义一个方法,计算几个Int类型整数的和
public static int adb(int...arr) {
//
return 0;
}
注意事项:1:一个方法的参数列表,只能有一个可变参数
2:如果方法的参数有多个,那么可变参数必须写在参数列表的末尾
可变参数的特殊写法(终极):
public static void method(Object...obj) {
}
3:Collections类:集合工具类,用来对集合进行操作。
*常用方法:
static <T> boolean addAll(Collection<? super T> c, T... elements) :将所有指定元素添加到指定 collection 中。
如:其是静态方法,可直接使用:Collections.addAll(list,"a","s","f","v");
static void shuffle(List<?> list) :使用默认随机源对指定列表进行置换。打乱顺序,打乱集合顺序。
如:Collections.shuffle(list);
static <T> void sort(List<T> list) :根据元素的自然顺序 对指定列表按升序进行排序。
注意:sort(List<T> list)使用前提:被排序的集合里边存储的元素,必须实现Comparable,重写接口中的方法ComparableTo定义排序规则
static <T> void sort(List<T> list, Comparator<? super T> c) :根据指定比较器产生的顺序对指定列表进行排序。
Comparator和Comparable的区别:
Comparable:自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的规则CompareTo方法
Comparator:相当于找一个第三方的裁判,比较两个
3:Iterator迭代器
*Iterator接口:用于遍历集合中的元素
*迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续再判断,如果还有就再取出来。一直把集合中的所有元素全部取出来。这种取出方式专业术语称为迭代。
*常用方法:
E next() :返回迭代的下一个元素。 判断集合中还有没有下一个元素,有就返回true,没有返回false
boolean hasNext() :如果仍有元素可以迭代,则返回 true。取出集合中的下一个元素
*Iterator迭代器,是一个接口,无法直接使用,需要使用Iterator接口的实现类对象,获取实现类的方式比较特殊,Collection接口中有一个方法,叫Iterator(),这个方法返回的就是迭代器的实现类对象。
Iterator<E> iterator() 返回在此collection的元素上进行迭代的迭代器。
*迭代器的泛型跟着集合走。
*迭代器的使用步骤:
1:使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)。
2:使用Iterator接口中的方法hasNext判断还有没有下一个元素
3:使用Iterator接口中的方法next取出集合中的下一个元素
如:
迭代器的代码实现:
public class Demo01Iterator{
public static void main(String[] args){
//创建一个集合对象
Collection<String> coll = new ArrayList<>();
//往集合里添加元素
coll.add("姚明");
coll.add("科比");
coll.add("乔丹");
coll.add("詹姆斯");
coll.add("韦德");
//1:使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)。
Iterator<String> it = coll.iterator();
/*发现使用迭代器取出集合中的元素的代码,是一个重复的过程
所以我们可以使用循环优化
不知道集合中有多少元素,使用while循环
循环结束的条件,hasNext方法返回false*/
while(it.hasNext()){
String e = it.next();
System.out.println(e);
}
/*//2:使用Iterator接口中的方法hasNext判断还有没有下一个元素
boolean b = it.hasNext();
//3:使用Iterator接口中的方法next取出集合中的下一个元素
b = it.hasNext();
s = it.next();
b = it.hasNext();
s = it.next();
b = it.hasNext();
s = it.next();
b = it.hasNext();
s = it.next();
b = it.hasNext();
s = it.next();
*/
}
}
4:增强for:(也称for each循环),专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。所有的单列集合都可以使用增强for
格式:
for(集合/数组的数据类型 变量名:集合/数组名) {
sout(变量名);
}
如:
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("vvv");
list.add("asss");
list.add("xxxc");
for(String st:list){
System.out.println(st);
}
5:泛型:是一种未知的数据类型,当我们不知道使用什么数据类型的时候,可以使用泛型
泛型也可以看成是一个变量,用来接收数据类型
E e :Element 元素
T t :Type 类型
*创建集合对象的时候,就会确定泛型的数据类型
*使用泛型和不使用泛型:
1:创建集合对象不使用泛型
*好处:集合不使用泛型,默认的类型就是Object类型,可以存储任意类型的数据
*弊端:不安全,会引发异常
2:创建集合对象使用泛型
*好处: 1:避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型
2:把运行期异常(代码运行之后会抛出的异常),提升到了编译期(写代码的时候会报错)
*弊端:泛型是什么类型,只能存储什么类型的数据
*作用:用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。
*定义含有泛型的类
格式:修饰符 class 类名<代表泛型的变量> {}
注意:创建对象的时候确定泛型
*定义含有泛型的方法
格式:修饰符 <代表泛型的变量> 返回值类型 方法名(参数列表(使用泛型)){ }
注意:含有泛型的方法,在调用方法的时候确定泛型的数据类型
传递什么类型的参数,泛型就是什么类型
*定义含有泛型的接口
使用方式:第一种:定义接口的实现类,实现接口,指定接口的泛型
第二种:接口使用什么泛型,实现类就使用什么泛型
*泛型通配符:
*不知道使用什么类型来接收的时候,就可以使用 ? 表示未知通配符
此时只能接收数据,不能往该集合中存储数据
*使用方式:不能创建对象使用
只能作为方法的参数使用
*高级使用:受限泛型:
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。Java的泛型中可以指定一个泛型的上限和下限:
上限:格式:类型名称 <? extends 类 > 对象名称
意义:只能接收该类型及其子类
下限:格式:类型名称 <? super 类 > 对象名称
意义:只能接收该类型及其父类型
6:Map集合:双列集合
*Map<K,V>:将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。
*Map集合的特点:
1:Map集合是一个双列集合,一个元素包含两个值(一个key,一个value)
2:Map集合中的元素,key和value的数据类型可以相同,也可以不同
3:Map集合中的元素,key是不允许重复的,value是可以重复的
4:Map集合中的元素,key和value是一一对应
*Map集合中不能包含重复的键,值可以重复;每个键只能对应一个值。
*Map常用的子类:
*HashMap<K,V>集合:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hsahCode()方法、equals()方法。不同步的
*LinkedHashMap<K,V>集合: implements HashMap<K,V>存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
*HashTable<K,V>集合:map的实现类早期的双列集合,单线程的,被HashMap取代了,被淘汰了
*子类:
Properties集合:唯一和IO流相结合的集合,依然活跃在历史舞台。
Properties:类表示了一个持久的属性集。Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串(是一个双列集合)。
方法:
可以使用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:使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
4:释放资源
可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
void load(InputStream instream)
void load(Reader reader)
参数:InputStream instream:字节输入流,不能读取含有中文的键值对
Reader reader:字符输入流,能读取含有中文的键值对
使用步骤:
1:创建Properties集合对象
2:使用Properties集合对象中的方法load读取保存键值对的文件
3:遍历Properties集合
注意:
1:存储键值对的文件中,键与值默认的连接符号可以使用=,空格(其他符号)
2:存储键值对的文件中,可以使用#进行注释,被注释的键值对不会再被读取
3:存储键值对的文件中,键与值默认都是字符串,不用再加引号
有一些操作字符串的特有方法:
Object setProperty(String key,String value):调用HashTable的方法put
string getproperty(String key):通过key找到value值,此方法相当于Map集合中的get(key)方法
Set<String> stringPropertyNames():返回此属性列表中的链表,其中该键及其对应值是字符串,此方法相当于Map集合中的keySet方法
*Map常用的方法:
public V put(K key, V value) :将指定的值与此映射中的指定键关联(可选操作)。将指定的键和指定的值添加到Map集合中。
返回值:V
存储键值对的时候,key不重复,返回值v是null
存储键值对的时候,key重复,会使用新的value替换map中重复的value,返回被替换的value值
V remove(Object key) : 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
V get(Object key) :返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。 根据指定的键,在Map集合中获取对应的值
*Map集合中有一个内部接口Entry<K,V>:
*作用:当Map集合一创建,那么就会在Map集合中创建一个Entry对象,用来记录键与值(键值对对象,键与值的映射关系)
*方法:Set<Map.entry<K,V>> entrySet()
把Map集合内部的多个Entry对象取出来存储到一个Set集合中
getKey():获取key
getValue():获取value
*Map集合遍历:
*第一种方式:通过键找值的方式
Set<K> keySte():返回此映射中包含的键的Set视图
实现步骤:
1:使用Map集合中的方法keySet(),把Map集合所有的key取出来,存储到一个Set集合中
2:遍历set集合,获取Map集合中的每一个key
3:通过Map集合中的方法get(key),通过key找到value
如:
public class Demo01KeySet {
public static void main(String[] args ){
//创建Map集合对象
Map<String,Integer> map = new HashMap<>();
map.put("杨丽颖",161);
map.put("杨颖",162);
map.put("林志玲",124);
//使用Map集合中的方法keySet(),把Map集合所有的key取出来,存储到一个set集合中
Set<String> set = map.keySet();
//遍历Set集合,获取Map集合中的每一个key
//使用迭代器遍历
Iterator<String> it = set.iterator();
while(it.hasNext()){
String key = it.next();
//通过Map集合中的方法get(key),通过key找到value
Iterator value = map.get(key);
system.out.println(key + "=" + value);
}
}
}
*第二种方式:使用Entry对象遍历
实现步骤:
1:使用Map集合中的方法EntrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中
2:遍历set集合,获取每一个Entry对象
3:通过Entry对象中的方法getkey()和getvalue()获取键与值
*HashMap存储自定义类型键值:
Map集合保证key是唯一的:作为key的元素,必须重写hashCode方法和equals方法,以保证key唯一。
*LinkedHashMap<K,V>集合:
implements HashMap<K,V>
*Hashtable<K,V>集合:
implements Map<K,V>
*底层是一个哈希表,是一个线程安全的集合,同步的,是单线程集合,速度慢
*Hashtable集合和HashMap集合的区别:
HashMap集合可以存储null值,null键
Hashtable集合不能存储null值,null键
*Hashtable和Vector集合一样,在JDK1.2版本之后被更先进的集合(HashMap和ArrayList)取代了,Hashtable的子类properties依然活跃在历史舞台。
*properties集合是一个唯一和IO流相结合的集合。
7:JDK9对集合添加的优化:
*添加了几种集合工厂方法,更方便创建少量元素的集合、map实例。新的List、Set、Map的静态工厂方法,可以更方便地创建集合的不可变实例。
list接口,Set接口,Map接口:里边增加了一个静态的方法of,可以给集合一次性添加多个元素。
static <E> list<E> of {E... elements}
//斗地主代码实现:
public class DouDiZhu {
public static void main(String[] args){
//准备牌
//创建一个Map集合,存储牌的索引和组装好的牌
HashMap<Integer,String> poker = new HashMap<>();
//创建一个list集合,存储牌的索引
ArrayList<Integer> pokerIndex = new ArrayList<>();
//定义两个集合,存储花色和牌的序号
List<String> colors = List.of("♥","♠","♣","♦");
List<String> numbers = List.of("2","A","K","Q","J","10","9","8","7","6","5","4","3");
//把大小王存储到集合中
//定义一个牌的索引
int index = 0;
poker.put(index,"大王");
pokerIndex.add(index);
index++;
poker.put(index,"小王");
pokerIndex.add(index);
index++;
//循环嵌套遍历两个集合,组装52张牌,存储到集合中,
for(String number:numbers){
for(String color : colors){
poker.put(index,color+number);
pokerIndex.add(index);
index++;
}
}
//洗牌
//使用Collections中的方法shuffle(List)
Collection.shuffle(pokerIndex);
//发牌
//定义4个集合,存储玩家的索引,和底牌的索引
ArrayList<Integer> player01 = new ArrayList<>();
ArrayList<Integer> player02 = new ArrayList<>();
ArrayList<Integer> player03 = new ArrayList<>();
ArrayList<Integer> diPai = new ArrayList<>();
//遍历存储牌索引的List集合,获取每一个牌的索引
for(int i = 0;i < pokerIndex.size();i++){
Integer in = pokerIndex.get(i);
//先判断底牌
if(i>51){
diPai.add(in);
}else if (i%3==0){
player01.add(in);
}else if (i%3==1){
player02.add(in);
}else if (i%3==2){
player03.add(in);
}
}
//排序
Collections.sort(player01);
Collections.sort(player02);
Collections.sort(player03);
Collections.sort(diPai);
//看牌
LookPoker("刘德华",poker,player01);
LookPoker("周润发",poker,player02);
LookPoker("周星驰",poker,player03);
LookPoker("底牌",poker,diPai);
}
public static void lookPoker(String name,HashMap<Integer,String> poker,ArrayList<Integer> list) {
System.out.print(name+":");
for (Integer key :list){
String value = poker.get(key);
System.out.print(value+"");
}
System.out.println();
}
}
####16.数据结构
*数据存储的常用结构:栈、队列、数组、链表和红黑树。
1:栈
*先进后出 如:入栈123 出栈321
*最先进去的在栈底,最后进去的在栈顶。进入叫入栈,出来叫出栈。
*运算受限的线性表,只允许在表的一端进行插入删除。
2:队列:
*先进先出
* 入口和出口在集合的两侧 如存储:123 取出:123
3:数组:
*查询快,增删慢
*查询快:数组的地址是连续的,我们通过数组的首地址可以找到数组,通过数组的索引可以快速查找某一个元素
*增删慢:数组的长度是固定的,我们想要增加/删除一个元素,必须创建一个新数组,把源数组的数据复制过来。
*数组的元素增删:
如:
要把数组中索引是3的元素删除,必须创建一个新的数组,长度是源数组的长度-1。把源数组的其他元素复制到新数组中,把新数组的地址赋值给变量arr,源数组会在内存中被销毁(垃圾回收)
4:链表
*查询慢,增删快
*查询慢:链表中地址不是连续的,每次查询元素,都必须从头开始查询
*增删快:链表结构,增加/删除一个元素,对链表的整体结构没有影响,所以增删快。
*链表中的每一个元素也称为一个节点,一个节点包含了一个数据源(存储数组),两个指针域(存储地址)
*一个节点:【自己的地址 数据 下一个节点的地址】
*单向链表:无序,链表中只有一条链子,不能保证元素的顺序(存储元素和取出元素的顺序有可能不一致)
*双向链表:有序,链表中有两条链子,有一条链子是专门记录元素的顺序,是一个有序的集合。
5:红黑树(树都是倒立的)
*二叉树:分支不能超过两个
*平衡树:左子树和右子树相等
*不平衡树:左子树!=右子树
*红黑树:
特点:趋近于平衡树,查询的速度非常快,查询叶子节点最大次数和最小次数不能超过2倍
约束:
1:节点可以是红色的或者是黑色的
2:根节点是黑色的
3:叶子节点(空节点)是黑色的
4:每个红色的节点的子节点都是黑色的
5:任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
####17.异常
*异常:指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止
*在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象,Java处理异常的方式是中断处理。
【Throwable】
/ \
/ \
【Error】 【Exception】
(不能处理,只能尽量避免) (由于使用不当导致,可以避免的)
*Throwable类:异常的根类。
*子类:
1:Exception:编译期异常,进行编译(写代码)Java程序出现的问题
*子类:RuntimeException:运行期异常程序运行过程中出现的问题
2:Error:错误
*必须修改源代码,程序才能继续执行
*异常的处理:
1:throw关键字
*可以使用throw关键字在指定的方法中抛出指定的异常
*使用格式:throw new xxxException("异常产生的原因");
*注意:
1:throw关键字必须写在方法的内部
2:throw关键字后边new的对象必须是Exception或者Exception的子类对象
3:throw关键字抛出指定的异常对象,我们就必须处理这个异常对象
throw关键字后边创建的是RuntimeException或者是RuntimeException的子类对象,我们可以不处理,默认交给JVM处理(打印异常对象,中断程序)
throw关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么throws,要么try...catch
2:Object非空判断:
*其源码中对对象为null的值进行了抛出异常操作
3:异常处理的方式:
1:throw关键字
2:捕获异常:try...catch
4:Throwable类中3个异常处理的方式:
1:String getMessage() :返回此throwable的简短描述
2:String toString():返回此throwable的详细信息字符串
3:void printStackTrace():JVM打印异常对象,默认此方法,打印的异常信息是最全面的。
5:finally代码块:里面的代码一定执行
*不能单独使用,必须和try一起使用
*一般用于资源释放(资源释放),无论程序是否出现异常,最后都要资源释放(IO)。
6:子父类异常:父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出。
如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
7:自定义异常类:
*Java提供的异常类,不够使用时,需要自己定义一些异常类
*格式:
public class xxxxException extends Exception(或者RuntiomeException){
添加一个空参数的构造方法
添加一个带异常信息的构造方法
}
*注意:
1:自定义异常类一般都是以Exception结尾,说明该类是一个异常类
2:自定义异常类,必须得继承Exception或者RuntimeException
继承Exception:那么自定义得异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try...catch
继承RuntimeException:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)
如:
1//自定义一个异常类RegisterException.java
public class RegisterException extends Exception{
//添加一个空参数的构造方法
public RegisterException(){
super();
}
//添加一个带异常信息的构造方法
//查看源码发现,所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常信息
public RegisterException(String message){
super(message);
}
}
2//使用RegisterException自定义异常类Demo01RegisterException.java
//要求:模拟注册操作,如果用户已存在,则抛出异常并提示:亲,该用户已经被注册
//分析:
/*1:使用数组保存以及注册过的用户名(数据库)
2:使用Scanner获取用户输入的注册的用户名(前端,页面)
3:定义一个方法,对用户输入的注册的用户名进行判断
遍历获取到的用户名和用户输入的用户名比较
true:
用户名已经存在,抛出RegisterException异常,告知用户:亲,该用户已经被注册
false:
继续遍历比较
如果循环结束了,还没有找到重复的用户名,提示用户:恭喜您,注册成功
*/
public class Demo01RegisterException {
//1:使用数组保存以及注册过的用户名(数据库)
static String[] usernames = {"张三","李四","王五"};
public static void main(String[] args){
//使用Scanner获取用户输入的注册的用户名(前端,页面)
Scanner sc = new Scanner(System.in);
System.out.println("请输入您要注册的用户名");
String usernames sc.next();
checkUsername(username);
}
//定义一个方法,对用户输入的注册的用户名进行判断
public static void checkUsername(String username) {
//遍历存储已经注册过用户名的数组,获取每一个用户名
for(String name : username){
//使用获取到的用户名和用户输入的用户名比较
if(name.equals(username)){
//true:用户名已经存在,抛出RegisterException异常,告知用户:亲,该用户已经被注册
throw new RegisterException("亲,该用户已经被注册");
}
}
//如果循环结束了,还没有找到重复的用户名,提示用户“恭喜您,注册成功”
System.out.println("恭喜您,注册成功");
}
}
####18.多线程
并发:多个事件在同一时间段内发生(交替执行)
并行:多个事件在同一时刻发生(同时发生)(同时执行)
进程:一个内存中运行的应用程序。一个进程可以有多个线程,这个应用程序也可以称为多线程程序
线程:是进程中的一个执行单元
*一个程序运行后至少有一个进程,一个进程中可以包含多个线程
线程调度:1:分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
2:抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的是抢占式调度
主线程:执行主(main)方法的线程
单线程程序:Java程序中只有一个线程,
执行从main方法开始,从上到下依次执行
*Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类
*创建多线程程序的第一种方式:创建Thread类的子类
*实现步骤:
1:创建一个Thread类的子类
2:在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程做什么?)
3:创建Thread类的子类对象
4:调用Thread类中的方法start方法,开启新的线程,执行run方法
void start() :使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
结果是两个线程并发地运行;当前线程(main线程)(从调用返回给 start 方法)和另一个线程(创建的新线程,执行其 run 方法)。
多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
如:
//多线程第一种方式:创建Thread类的子类
1//MyThread.java
//创建一个Thread类的子类
public class MyThread extends Thread{
//在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程做什么?)
@Override
public void run(){
for(int i = 0; i < 20 ; i++){
System.out.println("run" + i);
}
}
}
2//Demo01Thread.java
public class Demo01Thread{
public static void main(String[] args){
//创建Thread类的子类对象
MyThread mt = new MyThread();
//调用Thread类中的方法start方法,开启新的线程,执行run方法
mt.start();
for(int i = 0; i <20 ; i++){
System.out.println("main" + i );
}
}
}
*随机打印结果的原因:两个线程,一个main线程,一个新线程一起抢夺cpu的执行权(执行时间),谁抢到了就执行对应的代码
*多线程好处:多个线程之间互不影响(因为在不同的栈空间)
*创建多线程程序的第二种方式:实现 Runnable 接口
*Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
*实现步骤:
1:创建一个Runable接口的实现类
2:在实现类中重写Runable接口的run方法,设置线程任务
3:创建一个Runable接口的实现类对象
4:创建Thread类对象,构造方法中传递Runable接口的实现类对象
5:调用Thread类中的start方法,开启新的线程执行run方法
如:
Demo01Runable.java
public class Demo01Runable{
public static void main(String[] args){
//3:创建一个Runable接口的实现类对象
RunableImpl run = new RunableImpl();
//4:创建Thread类对象,构造方法中传递Runable接口的实现类对象
Thread t = new Thread(run):
//5:调用Thread类中的start方法,开启新的线程执行run方法
t.start();
for (int i = 0; i <20; i++){
System.out.println(Thread.currentThread().getName()+"-->"i);
}
}
}
RunableImpl.java
//1:创建一个Runable接口的实现类
public class RunableImpl implements Runable{
//2:在实现类中重写Runable接口的run方法,设置线程任务
@Override
public void run(){
for (int i = 0; i <20; i++){
System.out.println(Thread.currentThread().getName()+"-->"i);
}
}
}
*Thread和Runable的区别
实现Runable接口比继承Thread类所具有的优势:
1:适合多个相同的程序代码的线程去共享同一个资源
2:可以避免Java中的单继承的局限性
3:增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立
4:线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类
扩充:在Java中,每次程序运行至少启动2个线程,一个是main线程,一个是垃圾收集线程。因为每次使用Java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实就是在操作系统中启动了一个进程
*匿名内部类实现线程的创建:
匿名:没有名字
内部类:写在其他类内部的类
匿名内部类作用:简化代码
把子类继承父类,重写父类的方法,创建子类对象集合一步完成
或把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
格式:
new 父类/接口(){
重写父类/接口中的方法
};
如://线程的父类是Thread
public class Demo01InnerClassThread{
public static void main(String[] args ){
//线程的父类是Thread
new Thread(){
//重写run方法,设置线程任务
@Override
public void run(){
for (int i = 0; i <20; i++){
System.out.println(Thread.currentThread().getName()+"-->"+"猿");
}
}
}.start();
}
}
如:
//线程的接口Runable
public class Demo01InnerClassThread{
public static void main(String[] args ){
//线程的接口Runable
Runable r = new Runable(){
//重写run方法,设置线程任务
@Override
public void run(){
for (int i = 0; i <20; i++){
System.out.println(Thread.currentThread().getName()+"-->"+"猿");
}
}
};
new Thread(r).start();
//简化接口的方式
new Thread(new Runable(){
//重写run方法,设置线程任务
@Override
public void run(){
for (int i = 0; i <20; i++){
System.out.println(Thread.currentThread().getName()+"-->"+"猿");
}
}
}).start();
}
}
*Thread类的常用方法:
1:获取线程名称:
方法1:使用Thread类中的方法getName()
String getName() :返回该线程的名称
方法2:可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
static Thread currentThread() :返回对当前正在执行的线程对象的引用
*可以使用链式编程:如:System.out.println(Thread.currentThread().getName());
2:设置线程名称(了解):
方法1:使用Thread类中的方法setName(名字)
void setName(String name) :改变线程名称,使之与参数 name 相同。
方法2:创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参数构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
Thread(String name) :分配新的 Thread 对象。
3:sleep方法:
static void sleep(long millis) :在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响
如:
public class Demo01Sleep{
public static void main(String[] args) {
//模拟秒表
for (int i = 0;i < 20; i++){
System.out.println(i);
//使用Thread类的sleep方法让程序睡眠2秒钟
try{
Thread.sleep(2000);
}catch(InterruptedException){
e.printStackTrace();
}
}
}
}
*线程安全问题:
多线程访问了共享的数据,会产生线程安全问题(如:电影院多个窗口卖同一百张票)
注意:线程安全问题是不能产生的,我们可以让一个线程在访问共享数据的时候,无论是否失去cpu的执行权,都让其他线程只能等待,等待当前线程执行完代码后,其他线程再执行其代码。(如:线程安全问题:三个窗口卖票,出现重复票和不存在的票)
问题如:
问题如:
//卖票案例
1//Demo01Ticket.java
//模拟卖票案例
//创建3个线程,同时开启,对共享的票进行出售
public class Demo01Ticket{
public static void main(String[] args) {
//创建Runable接口的实现类对象
RunableImpl run = new RunableImpl();
//创建Thread类对象,构造方法中传递Runable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
2//RunableImpl.java
public class RunableImpl implements Runable{
//定义一个多线程共享资源
private int ticket = 100;
//设置线程任务,卖票
@Override
public void run(){
while(true){
//先判断票是否存在
if (ticket >0){
//提高安全问题出现的概率,让程序睡眠
try{
Thread.sleep(10);
}catch(InterruptedException){
e.printStackTrace();
}
//票存在,卖票
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
问题:会出现重复的票和不存在的票
*线程同步机制(synchronized):来解决多线程访问同一资源时出现的线程安全问题。
三种方式完成同步操作:
1:同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问
格式:
synchronized(锁对象){
访问量共享数据的代码(可能出现线程安全问题的代码)
}
注意:
1:通过代码块中的锁对象,可以使用任意的对象
2:但是必须保证多个线程使用的锁对象是同一个
3:锁对象的作用:
把同步代码块锁住,只让一个线程再同步代码块中执行
如:
//解决:
//部分代码:
//RunableImpl.java
public class RunableImpl implements Runable{
//定义一个多线程共享资源
private int ticket = 100;
//创建一个锁对象
Object obj = new Object();
//设置线程任务,卖票
@Override
public void run(){
while(true){
//同步代码块
synchronized(obj){
//先判断票是否存在
if (ticket >0){
//提高安全问题出现的概率,让程序睡眠
try{
Thread.sleep(10);
}catch(InterruptedException){
e.printStackTrace();
}
//票存在,卖票
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
总结:同步中的线程,没有执行完毕是不会释放锁的,同步外的线程没有锁进不去同步。
2:同步方法:使用synchronized修饰的方法,就叫同步方法。保证A线程执行该方法的时候,其他线程只能在方法外等着。
格式:
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步锁是谁:
对于非static(静态)方法,同步锁就是this
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
使用步骤:
1:把访问了共享数据的代码抽取出来,放在一个方法中
2:在方法上添加synchronized修饰符
//解决2.1:
//部分代码
//解决2
//非静态方法
//解决2.1:
//部分代码
//解决2
//非静态方法
//RunableImpl.java
public class RunableImpl implements Runable{
//定义一个多线程共享资源
private int ticket = 100;
//设置线程任务,卖票
@Override
public void run(){
while(true){
playTicket();
}
}
pubic synchronized void playTicket(){
//先判断票是否存在
if (ticket >0){
//提高安全问题出现的概率,让程序睡眠
try{
Thread.sleep(10);
}catch(InterruptedException){
e.printStackTrace();
}
//票存在,卖票
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
//解决2.2 静态方法
//RunableImpl.java
public class RunableImpl implements Runable{
//定义一个多线程共享资源
private static int ticket = 100;
//设置线程任务,卖票
@Override
public void run(){
while(true){
playTicketStatic();
}
}
pubic static synchronized void playTicketStatic(){
//先判断票是否存在
if (ticket >0){
//提高安全问题出现的概率,让程序睡眠
try{
Thread.sleep(10);
}catch(InterruptedException){
e.printStackTrace();
}
//票存在,卖票
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
或者:
//解决2.3 静态方法
//RunableImpl.java
public class RunableImpl implements Runable{
//定义一个多线程共享资源
private static int ticket = 100;
//设置线程任务,卖票
@Override
public void run(){
while(true){
playTicketStatic();
}
}
pubic static void playTicketStatic(){
synchronized(RunableImpl.class){
//先判断票是否存在
if (ticket >0){
//提高安全问题出现的概率,让程序睡眠
try{
Thread.sleep(10);
}catch(InterruptedException){
e.printStackTrace();
}
//票存在,卖票
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
3:锁机制:Lock锁
*提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
*Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock():加同步锁
public void unlock():释放同步锁
*ReentrantLock接口 implements Lock接口
*使用步骤:
1:在成员位置创建一个ReentrantLock对象
2:在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
3:在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
//解决3
//解决3
//RunableImpl.java
public class RunableImpl implements Runable{
//定义一个多线程共享资源
private static int ticket = 100;
//1:在成员位置创建一个ReentrantLock对象
LOck l = new ReetrantLock();
//设置线程任务,卖票
@Override
public void run(){
while(true){
//2:在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
l.lock();
//先判断票是否存在
if (ticket >0){
//提高安全问题出现的概率,让程序睡眠
try{
Thread.sleep(10);
//票存在,卖票
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}catch(InterruptedException){
e.printStackTrace();
}finally{
//3:在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
l.unlock();//无论程序是否异常都会把锁释放掉
}
}
}
}
}
*线程状态:
*线程可以处于下列状态之一:
NEW:新建状态。至今尚未启动的线程处于这种状态。
RUNNABLE:运行状态。正在 Java 虚拟机中执行的线程处于这种状态。
BLOCKED:阻塞状态。受阻塞并等待某个监视器锁的线程处于这种状态。
WAITING:无限等待状态。无限期地等待另一个线程来执行某一特定操作的线程处于这种状态
需要Object.notify()来唤醒。
TIMED_WAITING:计时等待状态(使用sleep()进入休眠状态)。等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
计时等待,可以自己醒来。
TERMINATED:死亡状态。已退出的线程处于这种状态。
*在给定时间点上,一个线程只能处于一种状态。这些状态是虚拟机状态,它们并没有反映所有操作系统线程状态。
*等待唤醒:即对TIMED_WAITING:计时等待状态操作。
等待唤醒机制:也称为线程之间的通信:使各个线程能有效的利用资源的手段。
*Object类中wait方法和notify方法
*void wait() :在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
*void wait(long timeout) :在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。 如果在毫秒值结束之后,还没有被notify()唤醒,会自动醒来
*void notify() : 唤醒在此对象监视器上等待的单个线程。
*void notifyAll() :唤醒在此对象监视器上等待的所有线程。
*线程池:容纳多个线程的容器,线程可以反复使用。
如:LinkedList<Thread>
*在JDK1.5后,JDK内置了线程池,我们可以直接使用
*生产线程池的工厂类:Executors类
方法:
static ExecutorService newFixedThreadPool(int nThreads) :创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
*参数:int nThreads :创建线程池中包含的线程数量
*返回值:ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
ExecutorService接口:线程程接口。用来从线程池中获取线程,调用start方法,执行线程任务
方法:
submit(Runable task):提交一个Runable任务用于执行
shutdown():关闭/销毁线程池的方法
*线程池的使用步骤:
1:使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程量的线程池。
2:创建一个类,实现Runable接口,重写run方法,设置线程任务
3:调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
4:调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
####19.Lambda表达式
*函数式编程思想 :强调做什么,而不是以什么方式去做。重视结果,不重视过程
面向对象:过分强调必须通过对象的形式来做事情
*冗余的Runable代码 (简化省去实现类Impl)--->使用匿名内部类 (简化)--->使用Lambda表达式
如:
public class Demo02Lambda {
public static void main(String[] args) {
//使用匿名内部类的方式,实现多线程
new Thread(new Runable(){
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"新线程创建了");
}
}).start();
//使用Lambda表达式,实现多线程
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"新线程创建了")
}.start();
}
}
*Lambda表达式标准格式:
(参数列表) -> {一些重写方法的代码}
解释格式:():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔
->:传递的意思,把参数传递给方法{}
():重写接口的抽象方法的方法体
*Lambda表达式省略格式:
可以省略的内容:
1.{参数列表}:括号中参数列表的数据类型,可以省略不写
2.{参数列表}:括号中的参数如果只有一个,那么类型和()都可以省略
3.{一些代码}:如果{}中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)
注意:要省略{},return,分号必须一起省略
*Lambda的使用前提:
1:使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法
2:使用Lambda必须具有"上下文判断"。也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
备注:有且仅有一个抽象方法,称为“函数式接口”。
####20.File类:
是文件和目录路径名的抽象表示。主要用于文件和目录的创建、查找、删除。
file:文件
directory:文件夹/目录
path:路径
静态成员方法:
static String pathSeparator :与系统有关的路径分隔符,为了方便,它被表示为一个字符串。
static char pathSeparatorChar :与系统有关的路径分隔符。
static String separator :与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。
static char separatorChar :与系统有关的默认名称分隔符。
*路径分隔符: Windows:分号; Linux:冒号:
*文件分隔符: Windows:反斜杠\ Linux:正斜杠/
*操作路径:路径不能写死了;
如:Windows: C:\develop\a\a.txt
Linux: C:/develop/a/a.txt
写法:"C:"+File.separator+"develop"+File.separator+"a"+File.separator+"a.txt"
*即用File.separator代替分隔符
*路径:
1:绝对路径:是一个完整路径。以盘符开始的路径
如: C:\\develop\\a\\a.txt
2:相对路径:
*相对指的是相对于当前项目的根目录
*如果使用当前项目的根目录,路径可以简化书写:
如:a.txt
*注意:
1:路径是不区分大小写
2:路径中的文件名称分隔符Windows使用反斜杠,反斜杠是转义字符,两个反斜杠代表一个普通的反斜杠
*构造方法:
File(String pathname) :通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。
*参数
String pathname:字符串的路径名称
路径可以是以文件结尾,也可以是以文件夹结尾
路径可以是相对路径,也可以是绝对路径
路径可以是存在,也可以是不存在
创建File对象,只是把字符串路径封装为File对象,不考虑路径的真假情况
File(String parent, String child) :根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
*参数:把路径分成了两部分
String parent:父路径
String child:子路径
*好处:父路径和子路径,可以单独书写,使用起来非常灵活;父路径和子路径都可以变化
如:
public class Demo01File{
public static void main(Sting[] args) {
show02(parent:"c:\\",child:"a.txt");
}
private static void show01(String parent,String child){
File file = new File(parent,child);
//sout.....
}
}
File(File parent, String child) :根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
参数:File parent:父路径
String child:子路径
好处:
父路径是File类型,可以使用File的方法对路径进行一些操作,再使用路径创建对象。
*常用方法:
1:file类获取功能的方法:
String getAbsolutePath() :返回此抽象路径名的绝对路径名字符串。
获取的构造方法中传递的路径
无论路径是绝对的还是相对的,getAbsolutePath方法返回的都是绝对路径
如:
public class Demo01File{
public static void main(Sting[] args) {
show01();//这个方法返回的都是绝对路径
}
private static void show01(String parent,String child){
//使用的绝对路径
File f1 = new File("C:\\develop\\a\\a.txt");
String absolutePath1 = f1.getAbsolutePath();
//sout.....
//使用的相对路径
File f2 = new File("a.txt");
String absolutePath2 = f2.getAbsolutePath();
//sout....
}
}
String getPath() :将此抽象路径名转换为一个路径名字符串。
返回的:绝对的就是绝对的,相对的就是相对的
*toString方法就是调用的getPath()方法
String getName() :返回由此抽象路径名表示的文件或目录的名称。
获取的就是构造方法传递路径的结尾部分(文件/文件夹)
long length() :返回由此抽象路径名表示的文件的长度。
获取的是构造方法指定的文件的大小,以字节为单位
注意:
文件夹是没有大小概念的,不能获取文件夹的大小
如果构造方法中给出的路径不存在,那么length方法返回0
2:file类判断功能的方法:
boolean exists() :测试此抽象路径名表示的文件或目录是否存在。
此File表示的文件或目录是否实际存在
boolean isDirectory() :测试此抽象路径名表示的文件是否是一个目录。
boolean isFile() :测试此抽象路径名表示的文件是否是一个标准文件。
3:file类创建删除功能的方法:
boolean createNewFile() :当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。
创建文件的路径和名称在构造方法中给出(构造方法的参数)
返回值:布尔值
true:文件不存在,创建文件,返回true
false:文件存在,不会创建,返回false
注意:
1:此方法只能创建文件,不能创建文件夹
2:创建文件的路径必须存在,否则会抛出异常
*createNewFile方法声明抛出了IOException,我们调用这个方法,必须处理这个异常,要么throws,要么try catch。
boolean mkdir() :创建此抽象路径名指定的目录。
只能创建单级文件夹
boolean mkdirs() :创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。
既能创建单级文件夹,也可以创建多级文件夹
boolean delete() :删除此抽象路径名表示的文件或目录。
此方法可以删除构造方法路径中给出的文件/文件夹
注意:此方法是直接在硬盘删除文件/文件夹,不走回收站,删除要谨慎
4:file类遍历(文件夹)目录功能的方法:
String[] list() : 返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。
返回一个String数组,表示该File目录中的所有子文件或目录
File[] listFiles() :返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。
返回一个File数组,表示该File目录中的所有的子文件或目录
注意:list方法和listFiles方法遍历的是构造方法中给出的目录
如果构造方法中给出的目录的路径不存在,会抛出空指针异常
如果构造方法中给出的路径不是一个目录,也会抛出空指针异常
*递归:
*递归:值在当前方法内调用自己的这种现象
main(){
a();
}
a(){
a();
}
b(){
b();
}
//间接递归
b(){
c();
}
c(){
b();
}
*分类:
直接递归:方法自己调用自己
间接递归:A方法调用B方法,B方法调用C方法,C方法调用A方法。
*注意事项:
递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出(可以加个if指定次数)
在递归中虽然有限定条件,但是递归次数不能太多
构造方法,禁止递归
*递归使用前提:
当调用方法的时候,方法的主体不变,每次调用方法的参数不同,可以使用递归
如:
//使用递归计算1~n之间的和
public class Demo02Recurison{
public static void main(Sting[] args) {
sum(100);
}
public static int sum(int n){
if(n==1){
return 1;
}
return n + sum(n-1);
}
}
//综合案例
//综合案例
//文件搜素:搜索D:\aaa目录中的.java文件
public class Demo05Recurison{
public static void main(String[] args ){
File file = new Flie("D:\\abc");
getAllFile(file);
}
public static void getAllFile(File dir){
File[] files = dir.listFiles();
for (File f: files){
if(f.isDirectory()){
getAllFile(f);
}else{
//1把File对象f,转为字符串对象
//String name = f.getName();
//String path = f.getPath();
String s = f.toString("java");
//2调用String类中的方法endWith判断字符串是否是以.java结尾
boolean b = s.endWith(".java");
//3如果是以.java结尾的文件,则输出
if(b){
System.out.println(f);
}
}
}
}
}
*FileFilter过滤器:
在File类中有两个和ListFiles重载的方法,方法的参数传递的就是过滤器。
1:File[] ListFiles(FileFilter filter)
FileFilter接口:用于抽象路径名(File对象)的过滤器
作用:用来过滤文件(File对象)
抽象方法:用来过滤文件的方法
boolean accept(File pathname):测试指定抽象路径名是否应该包含在某个路径名列表中
参数:File pathname:使用ListFiles方法遍历目录,得到的每一个文件对象
2:File[] ListFiles(FilenameFilter filter)
FilenameFilter接口:实现此接口的类实例可用于过滤器文件名
作用:用于过滤文件名称
抽象方法:用来过滤文件的方法
boolean accept(File dir ,String name):测试指定文件是否应该包含在某一文件列表中
参数:File dir:构造方法中传递的被遍历的目录
String name:使用ListFiles方法遍历目录,获取的每一个文件/文件夹的名称
*两个两个过滤器是没有实现类的,需要我们自己写实现类,重写过滤的方法accept,在方法中自己定义过滤的规则。
创建过滤器FileFilter的实现类,重写过滤方法accept,定义过滤规则
如:
//第一种:
//1:
public class Demo01Filter{
public static void main(String[] args){
//遍历C:\\abc目录下的文件名称
File file = new File("C:\\abc");
getAllFile(file);
}
public static void getAllFile(File dir){
File[] files = dir.listFiles(new FileFilterImpl());//传递过滤器对象
for(File f : files){
if(f.isDirectory){
getAllFile(f);
}
else{
System.out.println(f);
}
}
}
}
//2.FileFilterImpl.java
public class FileFilterImpl implements FileFilter{
@Override
public boolean accept(File pathname){
/*
过滤的规则:
在accept方法中,判断File对象是否是以.java结尾
是就返回true
不是就返回false
*/
if(pathname.isDirectory()){
return true;
}
return pathname.getName().tolowerCase().endsWith(".java");
}
}
//第二种:传递过滤器对象 使用匿名内部类
public class Demo01Filter{
public static void main(String[] args){
//遍历C:\\abc目录下的文件名称
File file = new File("C:\\abc");
getAllFile(file);
}
public static void getAllFile(File dir){
//传递过滤器对象 使用匿名内部类
File[] files = dir.listFiles(new FileFilter(){
@Override
public boolean accept(File pathname) {
return pathname.isDirectory() || pathname.getName().toLowerCase().endsWith(".java");
}
};//传递过滤器对象
for(File f : files){
if(f.isDirectory){
getAllFile(f);
}
else{
System.out.println(f);
}
}
}
}
//第三种
//使用Lambda表达式优化匿名内部类(接口中只有一个抽象方法)
public class Demo01Filter{
public static void main(String[] args){
//遍历C:\\abc目录下的文件名称
File file = new File("C:\\abc");
getAllFile(file);
}
public static void getAllFile(File dir){
//传递过滤器对象 使用匿名内部类
File[] files = dir.listFiles((dir,name)->new File(d,name).isDirectory()||name.tolowerCase().endsWith(".java");
for(File f : files){
if(f.isDirectory){
getAllFile(f);
}
else{
System.out.println(f);
}
}
}
}
####21.IO:
数据存到键盘、内存、硬盘、外接设备和读取到内存中。我们把这种数据的传输,可以看作是一种数据的流动,按照流动的方向,以内存为基准,分为输入input和输出output,即流向内存的是输入流,流出内存的是输出流。
Java中I/O操作主要是指使用Java.io包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做写出数据。
IO的分类:
根据数据的流动分为:
输入流:把数据从其他设备山读取到内存中的流
输出流:把数据从内存中写到其他设备上的流
根据数据的类型分为:
字节流:
字符流:
InputStream:字节输入流
OutputStream字节输出流:
Reader:字符输入流
Writer:字符输出流
字节流:
*一切皆为字节:一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。(0和1)
*OutputStream字节输出流:
此抽象类是表示输出字节流的所有类的超类。输出流接受输出字节并将这些字节发送到某个接收器。
需要定义 OutputStream 子类的应用程序必须始终提供至少一种可写入一个输出字节的方法。
定义了一些共性的成员方法:
void close() :关闭此输出流并释放与此流有关的所有系统资源。
void flush() :刷新此输出流并强制写出所有缓冲的输出字节。
void write(byte[] b) :将 b.length 个字节从指定的 byte 数组写入此输出流。
void write(byte[] b, int off, int len) :将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
abstract void write(int b) :将指定的字节写入此输出流。
子类:
FileOutputStream:文件字节输出流
作用:把内存中的数据写入到硬盘的文件中
构造方法:
FileOutputStream(String name):创建一个向具有指定名称的文件中写入数据的输出文件流
FileOutputStream(File file) :创建一个向指定File对象表示的文件中写入数据的文件输出流
参数:写入数据的目的
String name:目的地是一个文件的路径
File file:目的地是一个文件
构造方法的作用:
1:创建一个FileOutputStream对象
2:会根据构造方法中传递的文件/文件路径,创建一个空的文件
3:会把FileOutputStream对象指向创建好的文件
写入数据的原理(内存-->硬盘):
Java程序-->JVM(java虚拟机)-->OS(操作系统)-->OS调用写数据的方法-->把数据写入到文件中
字节输出流的使用步骤:
1:创建一个OutputStream字节输出流的子类(如FileOutputStream)的对象,构造方法中传递写入数据的目的地
2:调用FileOutputStream对象中的方法write,把数据写入到方法中
3:释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提高程序的效率)
*InputStream字节输入流:
此抽象类是表示输入字节流的所有类的超类。
定义了一些共性的成员方法:
int read():从输入流中读取数据的下一个字节
int read(byte[] b):从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中。
子类:
FileItputStream:文件字节输入流
作用:把硬盘文件中的数据,读取到内存中使用
构造方法:
FileInputStream(String name)
FileInputStream(File file)
参数:读取文件的数据源
String name:文件的路径
File file:文件
构造方法的作用:
1:会创建一个FileInputstream对象
2:会把FileInputstream对象指向构造方法中要读取的文件
读取数据的原理(硬盘-->内存):
Java程序-->JVM-->OS-->OS读取数据方法-->读取文件
字节输入流的使用步骤:
1:创建FileInputStream对象,构造方法中绑定要读取的数据源
2:使用FileInputStream对象中的方法read,读取文件
3:释放资源
如:
//文件复制
//原理:一读一写
/*
步骤:
1:创建一个字节输入流对象,构造方法中绑定要读取的数据源
2:创建一个字节输出流对象,构造方法中绑定要写入的目的地
3:使用字节输入流对象中的方法read读取文件
4:使用字节输出流中的方法write,把读取到的字节写到目的地文件中
5:释放资源
*/
public class DemoCopyFile{
public static void main(String[] args){
long s = System.currentTimeMillis();//检测程序执行复制用时
//1:创建一个字节输入流对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("C:\\1.jpg");
//2:创建一个字节输出流对象,构造方法中绑定要写入的目的地
FileOutputStream fos = new FileOutputStream("D:\\1.jpg");
//使用数组缓冲读取多个字节,写入多个字节
byte[] bytes = new byte[1024];
//3:使用字节输入流对象中的方法read读取文件
int len = 0;//每次读取的有效字节个数
while((len = fis.read(bytes))!=-1){
//4:使用字节输出流中的方法write,把读取到的字节写入到目的地
fos.while(bytes,0,len);
}
fos.close();
fis.close();
long e = System.currentTimeMillis();//检测程序执行复制用时
System.out.println("复制文件共耗时:"+(e-s)+"毫秒");
}
}
使用字节流读取中文的问题:遇到中文字符时,可能不会显示完整的字符,因为一个中文符可能占用多个字节存储。
1个中文
GBK:占用2个字节
UTF-8:占用3个字节
字符流:
*所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件
*Reader字符输入流:
此抽象类是表示输入字符流的所有类的超类。是一个抽象类
定义了一些共性的成员方法:
int read():读取单个字符并返回
int read(char[] cbuf):一次读取多个字符,将字符读入数组
void close():关闭该流并释放与之关联的所有资源
子类:
FileReader: extends InputStreamReader extends Reader
FileReader:文件字符输入流
作用:把硬盘文件中的数据以字符的方式读取到内存中
构造方法:
FileReader(String fileName)
FileReader(File file)
参数:读取文件的数据源
String fileName:文件的路径
File file:一个文件
构造方法的作用:
1:创建一个FileReader对象
2:会把FileReader对象指向要读取的文件
字符输入流的使用步骤:
1:创建FileReader对象,构造方法中绑定要读取的数据源
2:使用FileReader对象中的方法read读取文件
3:释放资源
*Writer字符输出流:
此抽象类是表示输出字符流的所有类的超类。是一个抽象类
定义了一些共性的成员方法:
abstract void close() :关闭此流,但要先刷新它。
abstract void flush() :刷新该流的缓冲。
void write(char[] cbuf) :写入字符数组。
abstract void write(char[] cbuf, int off, int len) :写入字符数组的某一部分。
void write(int c) :写入单个字符。
void write(String str) :写入字符串。
void write(String str, int off, int len) :写入字符串的某一部分。
子类:
FileWriter extends OutputStreamWriter extends Writer
FileWriter:文件字符输出流
作用:把内存中字符数据写入到文件中
构造方法:
FileWriter(File file):根据指定的File对象构造一个FileWriter对象
FileWriter(String fileName):根据给定的文件名构造一个FileWriter对象
参数:写入数据的目的地
String fileName:文件的路径
File file:是一个文件
构造方法的作用:
1:会创建一个FileWriter对象
2:会根据构造方法中传递的文件/文件的路径,创建文件
3:会把FileWriter对象指向创建好的文件
关闭close和刷新flush的区别:
*因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中,但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush方法了。
flush:刷新缓冲区,流对象可以继续使用
close:先刷新缓冲区,然后通知系统释放资源,流对象不可以再被使用了
缓冲流:
也叫高效流,高效读写。是对四个基本的FileXxx流的增强
缓冲流的基本原理:是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
字节缓冲流: BufferedInputStream、BufferedOutputStream
字符缓冲流:BufferedReader、BufferedWriter
*BufferedOutputStream字节缓存输出流:extends OutputStream
继承父类共性成员方法
构造方法:
BufferedOutputStream(OutputStream out) :创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
BufferedOutputStream(OutputStream out, int size) :创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。
参数:
OutputStream out:字节输出流
我们可以传递FileOutputStream,缓冲流会给FileOutputStream增加一个缓冲区,提高FileOutputStream的写入效率
int size:指定缓冲流内部缓冲区的大小,不指定默认
使用步骤:
1:创建FileOutputStream对象,构造方法中绑定要输出的目的地
2:创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象对象,提高FileOutputStream对象效率
3:使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
4:使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
5:释放资源(会先调用flush方法刷新数据,第4步可以省略)
*BufferedInputStream字节缓存输入流:extends inputStream
继承父类共性成员方法
构造方法:
BufferedInputStream(InputStream in) :创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
BufferedInputStream(InputStream in, int size) :创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
参数:
InputStream in:字节输入流
int size:指定缓冲流内部缓冲区的大小,不指定默认
使用步骤:
1:创建FileOutInStream对象,构造方法中绑定要读取的数据源
2:创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
3:使用BufferedInputStream对象中的方法read,读取文件
4:释放资源
//文件复制
//原理:一读一写
//使用缓冲流
/*
步骤:
1:创建一个字节缓冲输入流对象,构造方法中传递字节输入流
2:创建一个字节缓冲输出流对象,构造方法中传递字节输出流
3:使用字节缓冲输入流对象中的方法read,读取文件
4:使用字节缓冲输出流中的方法write,把读取到的字节写入到内部缓冲区中
5:释放资源
*/
public class DemoCopyFile{
public static void main(String[] args){
long s = System.currentTimeMillis();//检测程序执行复制用时
//1:创建一个字节缓冲输入流对象,构造方法中传递字节输入流
BUfferedInputStream bis = new BufferedInputStream(new FileInputStream("c:\\1.jpg"));
//2:创建一个字节缓冲输出流对象,构造方法中传递字节输出流
BUfferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("d:\\1.jpg"));
//3:使用字节缓冲输入流对象中的方法read,读取文件
//使用数组缓冲读取多个字节,写入多个字节
byte[] bytes = new byte[1024];
int len = 0;
while((len = bis.read(bytes))!=-1){
bos.write(bytes,0,len);
}
bos.close();
bis.close();
long e = System.currentTimeMillis();//检测程序执行复制用时
System.out.println("复制文件共耗时:"+(e-s)+"毫秒");
}
}
*字符缓冲输出流BufferedWriter:
继承父类共性成员方法
构造方法:
BufferedWriter(Writer out) :创建一个使用默认大小输出缓冲区的缓冲字符输出流。
BufferedWriter(Writer out, int sz) :创建一个使用给定大小输出缓冲区的新缓冲字符输出流。
参数:Writer out:字符输出流
我们可以传递FileWriter,缓冲流会给FileWriter增加一个缓冲区,提高FileWriter的写入效率
int sz:指定缓冲区的大小,不写默认大小
特有的成员方法:
void newLine():写入一个行分隔符。会根据不同的操作系统,获取不同的行分隔符
使用步骤:
1:创建字符缓冲输出流对象,构造方法中传递字符输出流
2:调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
3:调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件中
4:释放资源
*字符缓冲输入流BufferedReader:
继承父类共性成员方法
构造方法:
BufferedReader(Reader in) :创建一个使用默认大小输入缓冲区的缓冲字符输入流。
BufferedReader(Reader in, int sz) :创建一个使用指定大小输入缓冲区的缓冲字符输入流。
参数:Reader in:字符输入流
我们可以传递FileReader,缓冲流会给FileReader增加一个缓冲区,提高FileReader的读取效率
特有的成员方法:
String readLine():读取一个文本行。读取一行数据
行的终止符:通过下列字符之一即可认为某行已终止:换行('\n')、回车('\r')或者回车后直接跟着换行(\r\n)。
返回值:包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回null
使用步骤:
1:创建字符缓冲输入流对象,构造方法中传递字符输入流
2:使用字符缓冲输入流对象中的方法read/readLine读取文本
3:释放资源
转换流:
字符编码:就是一套自然语言的字符与二进制之间的对应规则
编码:字符(能看懂的)-->字节(看不懂的)
解码:字节(看不懂的)-->字符(能看懂的)
编码表:生活中文字和计算机中二进制的对应规则
字符集:也叫编码表。是一个系统支持的所有字符的集合
常见字符集:ASCII字符集 GBK字符集 Unicode字符集
*OutputStreamWriter:字符流通向字节流的桥梁
使用步骤:
1:创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
2:使用OutputStreamWriter对象中的方法write,把字符转成字节存在缓冲区
3:使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中
*InputStreamReader:字节流通向字符流的桥梁
序列化流和反序列化流:
把对象以流的方式,写入到文件中保存,叫写对象,也叫对象的序列化
注意:进行序列化和反序列化的类,必须实现Serializable标记接口
如:
public class Person implements Serializable{
}
*ObjectOutputStream序列化:把对象以流的方式写入到文件中保存,对象的序列化。对象中包含的不仅仅是字符,使用字节流,存:writeObject(p)
构造方法:
特有成员方法:
void writeObject(Obect obj):将指定对象写入ObjectOutputStream
使用步骤:
1:创建ObjectOutputStream对象,构造方法中传递字节输出流
2:使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
3:释放资源
*ObjectInputStream反序列化:把文件中保存的对象,以流的方式读取出来 读取方法:readObject()
特有成员方法:
Object readObject():从硬盘读取对象
前提:类实现Serializable接口,必须存在类对应的class文件
*序列化操作:
一个对象要想序列化,必须满足两个条件:
1:该类必须实现Serializable接口
2:该类的所有属性必须是可序列化的
如果有一个属性不需要可序列化,则该属性必须说明时瞬态的,使用transient修饰
static关键字:静态关键字
静态优先于非静态加载到内存中
被static修饰的成员变量不能被序列化,能被序列化的是非静态,即对象
*反序列化操作:
当jvm反序列化对象时,能找到class文件,但是class文件在序列化对象后发生了修改,那么反序列化操作也会失败,抛出Invalidclass异常
打印流PrintStream:
PrintStream类:
构造方法:public PrintStream(String fileName)
使用指定的文件名创建一个新的打印流
####22.网络编程入门:
软件结构:c/s结构,客户端和服务器结构
b/s结构,浏览器和服务器结构
网络编程:在一定的协议下,实现两台计算机的通信的程序
网络通信协议:规则(连接和通信的规则)
网络协议:
TCP:传输控制协议
FTP:文件传输协议
UDP:用户数据报协议
SMPT:简单邮件协议
TELNET:远程终端协议
POP3:邮件读取协议
网络协议为计算机网dao络中进行数据交换而建立的规则、标准或约定的集合。
网络协议是由三个要素组成:
1、语义
语义是解释控制信息每个部分的意义。它规定了需要发出何种控制信息,以及完成的动作与做出什么样的响应。
2、语法
语法是用户数据与控制信息的结构与格式,以及数据出现的顺序。
3、时序
时序是对事件发生顺序的详细说明。
网络协议的层次结构如下:
1、结构中的每一层都规定有明确的服务及接口标准。
2、把用户的应用程序作为最高层
3、除了最高层外,中间的每一层都向上一层提供服务,同时又是下一层的用户。
4、把物理通信线路作为最低层,它使用从最高层传送来的参数,是提供服务的基础。
TCP/IP:传输控制协议/因特网互联协议
4层:
应用层:HTTP协议
传输层:TCP、UDP
网络层:IP,核心,将传输的数据分组发送到目标计算机或网络
数据结构层(物理层):
UDP:用户数据报协议,无连接通信,不能保证数据完整性
特点:被限制64kb以内,耗资少,效率高
TCP:传输控制协议,面向连接通信,安全
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
第一次握手:客户端向服务器发出连接请求,等待服务器确认
第二次握手:服务器向客户端回送一个响应,通知客户端收到了连接请求
第三次握手:客户端再次向服务器发送确认信息,确认连接
网络编程三要素:
1:协议
2:IP地址:(0~255)
IPV4:32位二进制 命令:本机IP:ipconfig
IPV6:128位十六进制 检查网络连通:ping Ip地址
本机IP:ping 127.0.0.1或localhost
3:端口号:逻辑端口,两个字节组成(0~65535之间)
系统分配给网络软件指定或随机的端口号
常用端口号:
1:80端口,网络端口
2:数据库:mysql:3306 oracle:1521
3:tomcat服务器:8080
使用IP和端口号,就可以保证数据准确无误发送到对方计算机的指定软件上
面向连接的通信,服务器和客户端经过了3次握手,建立逻辑连接,才能通信
TCP通信程序: 客户端Client、服务器Server
两端通信步骤:1:服务器先启动,等待客户端连接
2:客户端主动连接服务器,连接成功才能通信;服务器不可以主动连接客户端
在Java中,提供了两个类用于实现TCP通信程序:
1:客户端:java.net.Socket类:创建Socket对象,向服务器发出连接请求
*Socket类:
实现客户端套接字
套接字:是两台机器之间通信的端点。包含了IP和端口号的网络单位
构造方法:Socket(String host,int port):创建一个流套接字并将其连接到指定主机上的指定端口号
参数:String host:服务器主机名/IP地址
int port:服务器端口号
成员方法
实现步骤:
1:创建一个客户端Socket,构造方法绑定服务器的IP地址和端口号
2:使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
3:使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
4:使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
5:使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
6:释放资源
2:服务端 :java.net.ServerSocket类:创建ServerSocket对象,相当于开启一个服务,等待客户端连接,服务器使用客户端的流和客户端交互
*ServerSocket类:
实现服务器套接字
构造方法:ServerSOcket(int port):创建绑定到特定端口的服务器套接字
成员方法
Socket accept():侦听并接受到此套接字的连接
实现步骤:
1:创建服务器ServerSocket对象和系统要指定的端口号
2:使用ServerSocket对象中的方法accept,获取到请求的客户端对象Socket
3:使用Socket方法getInputStream()获取网络字节输入流InputStream
4:使用网络字节输入流中的方法read,读取客户端发送的数据
5:使用Socket对象中的方法getOutputStream()获取网络字节输出流
6:使用网络字节输出流中的方法write,给客户端回写
7:释放资源
注意:read()读本地文件,结束标记是读取到-1结束。
while循环不会读取到-1,也就不会把结束标记写给服务器
进入阻塞状态,
解决:上传完文件,给服务器写一个结束标记。
void shutdownOutput():禁用此套接字的输出流
对于TCP套接字,任何以前写入的数据都将被发送,并且后跟TCP的正常连接终止序列,即在客户端while后+shutdowOutput()方法
文件上传案例优化:
1:文件名称写死的问题(防止文件的文件覆盖,保证文件唯一性)
2:循环接收的问题(不关服务器,可多次上传)
3:效率问题(使用多线程技术)
####23.函数式接口:
指有且仅有一个抽象方法的接口,可包括其他方法:静态、默认、私有
Java中的函数式编程体现是:Lambda
适用于Lambda的接口,即是函数式接口
/*备注:语法糖:是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实底层的实现原理仍然时迭代器,这便是语法糖。从应用层面来讲,Java中的Lambda被当做是匿名内部类的语法糖,但是二者在原理上是不同的。*/
*格式:修饰符 interface 接口名称{
public abstract 返回值类型 方法名称(可选参数信息);
//其他非抽象方法内容
}
注意:public abstract是可以省略的,所以定义一个函数式接口很简单
*@FunctionalInterface注解:检测接口是否是一个函数式接口
是:编译成功;否:编译失败
*@Override注解:检测方法是否为重写的方法
是:编译成功;否:编译失败
*函数式接口的使用:一般可以作为方法的参数和返回值类型
*函数式编程:
Lambda的延迟执行
使用Lambda作为参数和返回值
*常用函数式接口:
Supplier接口:
supplier<T>接口仅包含一个无参的方法:T get(),用来获取一个泛型参数指定类型的对象数据
被称为生产型接口,指定接口泛型是什么类型,那么接口中的get方法就生产什么类型的数据
Consumer接口:
与Supplier接口相反,是一个消费型接口,泛型执行什么类型。就可以使用accept方法消费说明类型的数据
自定义消费
默认方法:andThen:把两个Consumer接口连接组合在一起再进行消费
con1.andThen(con2).accept(s)
Predicate接口:
对某类型的数据进行判断,从而得到一个Boolean值结果
抽象方法:boolean test(T t):用来对指定数据类型进行判断的方法
默认方法:1:and:表示并且关系,也是用&&连接
2:or:表示或,用||连接
3:negate:表示非,用!连接
逻辑表达式:
&&:与,有false则false
||:或,有true则true
!:非,非真则假,非假则真
Function接口:
用来根据一个类型数据得到另一个类型的数据
Function<T,R>:根据T的参数获取R的结果
抽象方法:apply(T t): T-->R
默认方法:andThen:把两个Funtion组合
*Stream流:得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端
获取流:几种常用方式:
1:所有的Collection集合都可以通过Stream默认方式获取流
default Stream<E> stream()
2:Stream接口的静态方法of,可以获取数组对应的流
static <T> Stream<T> of (t...values)
参数:是一个可变参数,可传递一个数组
常用方法:
延迟方法:返回值仍是Stream接口自身方法,支持链式调用
终结方法:返回值不是Stream接口自身方法,包括:1:count方法 2:forEach方法
备注:forEach 与增强for不同:
forEach:逐一处理,用来遍历流中的数据,是终结方法,遍历后不能再用Stream流中的其他方法
方法filter:过滤,将一个流转换成另一个子集流
*Stream流属于管道流,只能被消费一次。第一次Stream流调用完毕,数据流向下一个Stream流
常用方法:
map(映射):需要将流中的元素映射到另一个流中使用,把T转R,这种动作叫映射
count(统计个数):返回long类型 终结方法
limit(取用前几个):long类型整数 延迟方法
skip(跳过前几个):
concat(组合):两个流合并为一个流
####24.方法引用
优化Lambda表达式
引用符:"::"
通过对象名引用成员方法:(对象、成员方法存在,就可以使用对象名来引用成员方法)
如:printString(obj::printUpperCaseString);
通过类名称引用静态成员方法(类、静态成员方法存在)
通过super引用成员方法(子父类关系、所有有super关键字,代表父类)
通过this引用本类成员方法
类的构造器(构造方法)引用:
数组的构造器(构造方法)引用:
####25.测试
测试分类:
1:黑盒测试
不需写代码,给输入值,看程序是否能够输出期望值
2:白盒测试
需要写代码,关注程序具体的执行流程
Junit单元测试(白盒测试的一种):
步骤:
1:定义一个测试类(测试用例)
建议:1:测试类名:被测试类名+Test
2:包含:xxx.xxx.xx.test
2:定义测试方法,可以独立运行
建议:1:方法名:test+测试方法名
2:返回值:void(不返)
3:参数列表:空参
3:给方法加@Test
4:导入junit依赖环境,即(import ...)
判定结果:红色失败,绿色测试成功
一般我们会使用断言操作处理结果:Assert.assertEquals(期望结果,运算结果)
初始化方法:用于资源申请,所有测试方法在执行之前都会先执行该方法,在方法前+@Before
释放资源方法:在所有测试方法执行完后,都会自动执行该方法,在方法前+@After
####26:反射:框架设计的灵魂(用于写框架)
框架:半成品软件,可在框架基础上进行软件开发,简化编码。
反射机制:将类的各个组成部分封装为其他对象
好处:1:在程序运行中,操作这些对象
2:解耦,提高程序可扩展性
Java代码在计算机中经历的阶段:1:Source源代码阶段
2:Class类对象阶段
3:Runtime运行时阶段
获取class对象的三种方式:
1:Class.forName("全类名"):将字节码文件加载进内存,返回class对象
多用于配置文件。将类名定义在配置文件中。读取文件,加载类
2:类名.class:通过类名的属性class获取
多用于参数的传递
3:对象.getClass():getClass()方法在Object类中定义
多用于对象的获取字节码的方式
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论哪种方式获取的class对象都是同一个
class对象的功能:
1:获取功能:
获取成员变量们
Field[] getFields
Field getField(String name)
获取构造方法们
Constructor<?>[] getConstructors()
Construtor<T> getConstructor(类?>...parameterTypes)
获取成员方法们
Method
获取类名
String getName()
忽略访问权限修饰符的安全检查:setAccessiable(true):暴力反射
####27:注解:说明程序的(给计算机看的)
JDK1.5后
作用:1:编写文档:通过代码里标识的注解生成文档(生成API文档)
2:代码分析:通过代码里标识的注解对代码进行分析(使用反射)
3:通过代码里标识的注解让编译器能够实现基本编译检查(Override)
1:JDk中预定义的一些注解:
@Override:检测该注解标注的方法是否继承父类(接口)的
@Deprecated:该注解标注的内容,表示已过时
@SupperssWarnings:压制警告
2:自定义注解:
格式:元注解
public @interface 注解名称{}
其中,元注解是用于描述注解的注解:
@Target:用于描述注解能够作用的位置
@Retention:用于描述注解被保留的阶段
@Documented:用于描述注解是否被抽取到API文档中
@Inherited:用于描述注解是否被子类继承
在程序中使用(解析)注解:即获取注解中定义的属性值
三步:
1:获取注解定义的位置的对象(class、Method、Field)
2:获取指定的注解
*getAnnotation(Class)
3:调用注解中的抽象方法获取配置的属性值