面向对象
0. 初始面向对象
1.面向对象内容的三条主线
Java类及类的成员:(重点)属性、方法、构造器;(熟悉)代码块、内部类
面向对象的特征:封装 、继承、多态、(抽象)
其他关键字的使用:this、super、package、import、static、final、interface、abstract等
2.面向过程编程(POP) VS 面向对象编程(OOP)
2.1 简单的语言描述二者区别
面向过程(Process-Oriented Programming):
-以函数为组织单位
-是一种执行者思维,适合解决简单问题。扩展能力差、后期维护难度较大
面向对象(Object Oriented Programming):
-以类为组织单位。每种事物都具备自己的属性和行为/功能。
-是一种设计者思维。适合解决复杂问题。代码扩展性强、可维护性高
2.2 二者关系
相辅相成
3.
面向对象的两个概念:
类:具有相同特征的事物的抽象描述,是抽象的、概念上的定义。
对象:实际存在的该类事物的每个个体,是具体的,因而也称为实例(instance)。
谈谈对这两个概念的理解?
4. 类的声明和使用
4.1 体会:设计类,其实就是设计类的成员
class person{
}
4.2 类的内部成员一、二
成员之一:属性、成员变量、field(字段、域)
成员之二:(成员)方法、函数、method
4.3 类的实例化
等价描述:类的实例化 <=> 创建类的对象 <=> 创建类的实例
格式:类类型 对象名 = 通过new创建的对象实体
举例:
Phone p1 = new Phone();
Scanner sc = new Scanner(System.in);
String str = new String();
5. 面向对象完成具体功能的操作的三步流程(非常重要)
步骤1.创建类,并设计类的内部成员(属性、方法)
步骤2.创建类的对象。比如:Phone p1 = new Phone();
步骤3.通过对象,调用其内部声明的属性或方法,完成相关功能
类的成员之一:属性
1.变量的分类
角度一:按数据类型来分:基本数据类型(8种)、引用数据类型(数组、类、接口、枚举、注解、记录)
角度二:按照变量在类中声明的位置的不同:成员变量(属性)、局部变量(方法内、方法形参、构造器内、构造器形参、代码块内等)
2.属性的几个称谓:成员变量、属性、field(字段、域)
3.区分成员变量 VS 局部变量
3.1 相同点
变量声明的格式相同:数据类型 变量名 = 变量值
变量都有其作用域。出了作用域就失效了。
变量必须先声明后赋值
3.2 不同点:
①类中声明的位置不同
属性:声明在类内,方法外的变量
局部变量:声明方法、构造器内部的变量
②在内存中分配的位置不同(难点):
属性:随着对象的创建,存储在堆空间
局部变量:存储在栈空间
③生命周期:
属性:随着对象的创建而创建,随着对象的消亡而消亡。
局部变量:随着方法对应的栈针入栈,局部变量会在栈中分配;随着方法对应的栈针出栈,局部变量消亡。
④作用域:
属性:在整个类的内部都是有效的
局部变量:仅限于声明此局部变量所在的方法(或构造器、代码块)中
⑤是否可以有权限修饰符进行修饰:(难)
都有哪些权限修饰符:public、protected、缺省、private。(用于表名所修饰的结构可调用的范围的大小)
属性:可以使用权限修饰符进行修饰
局部变量:不能使用任何权限修饰符进行修饰
⑥是否有默认值:
属性:都有初始化默认值
如果调用之前没有初始化,使用默认初始化值
局部变量:都没有默认初始化值
如果调用之前没有初始化值,直接报错,使用前必须赋值
注意:对于方法的形参,在调用方法时,给形参赋值即可
类的成员之二:方法(method)
1、使用方法的好处
方法的理解:‘方法’是类或对象行为特征的抽象,用来完成某个功能操作
方法的好处:实现代码重用,减少冗余,简化代码
2、使用举例
Math.random()的random()方法
Math.sqrt()的sqrt()方法
System.out.println()println()方法
new Scanner(System.in) .nextInt()方法
Arrays类中的binarySearch()方法、equals()方法
3、声明举例
public void eat()
public void sleep(int hour)
public String interests(String hobby)
4、方法声明格式
权限修饰符[其他修饰符] 返回值类型 方法名(形参列表)[异常类型]{
//方法体
}
注:[]中的不是必须的
5、具体的方法声明的细节
5、1权限修饰符
java规定的修饰符 有四种:private\缺省\protected\public
5、2返回值类型:描述调用完此方法时,是否需要返回一个结果
分类:
>无返回值类型,使用void表示即可
>有返回值类型,使用基本数据类型或引用数据类型表示即可
>需要使用return + 返回值类型的变量或常量
5、3方法名:属于标识符。需要满足标识符的规定和规范。“见名知意”
5、4形参列表:形参,属于局部变量。
格式:(形参类型1,形参1,形参类型2,,形参2,...)
分类:无形参列表,有形参列表
>无形参列表,不能省略()
>有形参列表,根据方法调用需要不确定变量的类型和个数,确定形参的类型和个数
5.5方法体:当我们调用一个方法时,真正执行的代码。体现了此方法的功能
6、注意点
java方法‘不能独立存在’,所有方法必须定义在类里
java方法不调用不执行,每调用一次,执行一次
java方法内可以调用本类中的(其它)方法或属性,方法内不能定义方法
7、关键字:return
7.1return的作用
作用1:结束一个方法
作用2:结束一个方法的同时,可以返回数据给方法的调用者
7.2注意点
return后面不能声明执行语句
方法调用内存解析:
形参:方法在声明时,一对()内声明一个或多个形式参数,简称为形参
实参:方法被调用时,传递给形参的变量或常量,称为实参
过程概述:main方法进栈成栈针 → 生成 类 → 赋值 → 调用方法 方法调用完后出栈 → 执行 main方法 执行完成后 → 垃圾回收
类的成员之三:构造器(constructor)
1.构造器的理解
constructor:建造、构造、建设
2.构造器的作用
作用1:搭配new关键字,创建类对象
作用2:在创建对象的同时,可以给对象的相关属性赋值
3.构造器的使用说明
构造器的声明格式:权限修饰符 类名(形参列表){}
创建类以后,在没有显示提供任何构造器的情况下,系统默认提供一个空参的构造器
,且构造器的权限 与类声明的权限相同。
如果类中显示声明构造器,系统不在提供默认的空参的构造器
一个类中可以声明多个构造器,彼此之间构成重载
1. 封装性
面向对象特征之一:封装性
1.为什么需要封装?
理论上:
高内聚:类的内部数据操作细节自己完成,不允许外部干涉
低耦合:仅暴露少量的方法给外部使用,尽量方便外部调用
通俗的说:把该隐藏的隐藏起来,该暴露的暴露出来。
2.如何实现数据封装?
2.1 权限修饰符
java规定了4种权限修饰符,分别是:private、缺省、protected、public
本类内部(private)、本包内(缺省)、其他包的子类(protected)、其他包非子类(public)
每一个修饰符所在位置,包含前一个的范围
2.2 作用
我们可以使用4种权限修饰符来修饰类及类的内部成员。当这些成员被调用时,体现可见性的大小
2.3 实际案例:
在题目中给Animal的对象legs属性赋值。在实际常识中,legs不能为负数,如果直接调用属性legs
不能加入判断逻辑,解决如下:
将legs属性私有化(private),禁止在Animal类的外部直接调用此属性
提供legs属性赋值的setLegs()方法,在此方法中加入legs属性赋值的判断逻辑if(legs >= 0 && legs % 2 == 0)
将此方法暴露出去,使得在Animal类的外部调用此方法,对legs赋值
提供legs属性获取的getLegs()方法,此方法对外暴露,使得在Animal类的外部还可以调用此属性的值
2.4 4种权限具体使用
类:只能使用public、缺省修饰
类的内部成员:可以使用4种修饰符
2.5 开发中4种权限修饰符的使用情况
比较高:public、private
比较低:缺省、protected
3 封装性的体现
场景一:私有化(private)类的属性,提供公共(public)的get和set方法,对此属性进行获取或修改
场景二:将类中不需要对外暴露的方法,设置private
场景三:单例模式中构造器private,避免在类的外部创建实例
this关键字的使用
1.目前可能出现的问题?以及解决方案?
当声明一个属性对应的SetXxx方法时,通过形参给对应的属性赋值。如果形参名和属性名同名,如何在方法中区分变量?
解决方案:使用this。使用this修饰的变量,表示属性,没有this修饰的变量,表示形参
2.this可以调用的结构:成员变量、方法、构造器
3.this的理解:当前对象(方法调用时)或当前正在创建的对象(在构造器调用时)
4.1 this调用属性和方法
针对于方法内的使用情况(非static修饰的方法):
一般情况:我们通过对象a调用方法,可以在方法内调用当前对象a的属性或其他方法。此时,我们可以在属性和其他方法前使用“this.”,
表示当前属性或方法所属的对象a。但是,一般情况下,我们都选择省略次“this.”结构
特殊情况:如果方法的形参与对象属性同名了,我们必须使用“this.”进行区分。使用this.修饰的变量即为属性(或成员变量)
没有使用this.修饰的变量,即为局部变量
针对于构造器内的使用情况:
一般情况:我们通过构造器创建对象时,可以在构造器内调用当前正在创建的对象的属性或方法。此时,我们可以在属性和方法前使用“this.”,
表示当前属性或方法所属的对象a。但是,一般情况下,我们都选择省略次“this.”结构
特殊情况:如果构造器的形参与正在创建的对象的属性同名了,我们必须使用“this.”进行区分。使用this.修饰的变量即为属性(或成员变量)
没有使用this.修饰的变量,即为局部变量
4.2 this调用构造器
格式:“this(形参列表)”
我们可以在类的构造器中,调用当前类中指定的其它构造器
要求:“this(形参列表)”必须声明在当前构造器的首行
结论:“this(形参列表)”在构造器中最多声明一个
如果一个类中声明了n个构造器,则最多有n-1个构造器可以声明有“this(形参列表)”的结构
2. 继承性
面向对象特征之二:继承性
1.继承性的理解
-生活上:财产、血缘的继承
-代码层面
-自上而下:定义一个A类,在定义另一个B类时,发现B类的功能与A类相似,考虑B类继承于A类
-自下而上:定义了类B、C、C等,发现B、C、D有类似的属性和方法,则可以考虑将相同的属性和方法进行抽取,
封装到A类中,让类B、C、D继承于类A,同时类B、C、D中的相似的功能就可以删除了
2.继承性的好处
-继承的出现减少了代码的冗余,提高了代码的复用性
-有利于功能的扩展
-继承的出现让类与类之间产生了‘is-a’的关系,为多态提供了前提
-继承描述事物之间的所属关系‘is-a',父类更通用、一般,子类更具体
3.继承性的格式
class A{
}
class B extends A{
}
继承中的基本概念
-类A:父类、supperClass、超类、基类
-类b:子类,subClass、派生类
4.有了继承性以后
-子类获取父类中声明的所有属性和方法
-由于封装性的影响,可能子类不能直接调用父类中声明的属性或方法
-子类在继承父类以后,还可以扩展自己特有的功能(体现:增加特有的属性、方法)
extends:延展、扩展,延伸
-继承之前,判断一下是否有is a的关系
-子类和父类,要区分集合和子集
5.默认的父类
java中声明的类,如果没有显示的声明其父类时,则默认继承于java.lang.Object
6.补充说明
-java支持多层继承
-直接父类、间接父类
-java中的子类的概念是相对的
-java中的一个父类可以声明多个子类,一个子类只有一个父类(java的单继承性)
一、super关键字的使用
1.为什么需要super?
子类需要继承父类的属性、方法
如何调用?使用super关键字
2.super的理解:父类的
3.super可以调用的结构:属性、方法、构造器
3.1 super调用属性、方法
-子类继承父类以后,我们就可以在子类的方法或结构中,调用父类中声明的属性或方法(满足封装性的前提下)
-可使用“super.”的结构,表示调用父类的属性或方法
-一般情况下,我们可以考虑省略“super.”的结构,但是,如果出现子类重写父类的方法或子父类中出现相同的属性或方法,
必须使用“super.”声明,显示调用父类被重写的方法或父类中声明的同名的属性
3.2 super调用构造器
①子类继承父类时,不会继承父类的构造器。只能通过“super.(形参列表)”,调用父类指定的构造器
②规定:“super(形参列表)”,必须声明在构造器的首行
③在构造器的首行可以使用“this(形参列表)”,调用本类中重载的构造器
在构造器的首行,“this(形参列表)”和“super(形参列表)”只能二选一
④如果子类构造器的首行既没有调用“this(形参列表)”也没有调用“super(形参列表)”,
则子类构造器默认调用“super(形参列表)”,即调用父类中空参的构造器
⑤子类的任何一个构造器,要么调用本类中重载的构造器,要么调用父类的构造器
⑥一个类声明n个构造器,最多有n-1个构造器中使用“this(形参列表)”
剩下那个一定使用“super(形参列表)”
--> 我们在通过子类的构造器创建对象时,一定在调用子类构造器的过程中,直接或间接调用到父类的构造器
调用父类构造器,才能将父类中声明的属性或方法加载到内存中,供子类对象使用
二、子类对象实例化过程
class Creature{//生物类
//属性、方法、构造器
}
class Animal extends Creature{//动物类
}
class Dog extends Animal{//狗类
}
class DogTest{
public static void main(String args){
Dog dog = new Dog();
}
}
1.从结果角度来看:类的继承性
创建对象后,子类对象就获取了其父类中声明的所有属性和方法,在权限允许的情况下可以直接调用
2.从过程角度来看:
当通过子类的构造器创建对象时,子类的构造器一定会直接或者间接的调用到其父类的构造器,父类也会一层一层往父类调用,
直到调用默认父类Object中的构造器为止
3.创建子类对象的过程中,内存中到底有几个对象
只有一个对象!即为当前new后面构造器对应的类的对象
3. 抽象类与接口
1. abstract的概念:抽象的
2. abstract可以用来修饰:类方法
3. 具体使用:
3.1 abstract修饰类
-此类称为抽象类
-抽象类不能实例化
-抽象类包含构造器,子类对象实例化,需要直接或间接的调用到父类构造器
-抽象类中可以没有抽象方法,有抽象方法所在的类,一定是抽象类
3.2 abstract修饰方法
-此方法称为抽象方法
-抽象方法只有方法的声明,没有方法体
-抽象方法其功能是确定的(方法声明确定),只是不知道如何实现(体现没有方法体)
-子类必须重写父类中的所有抽象方法之后,才可以实例化。否则,子类依然是一个抽象类
4. abstract不能使用的场景:
4.1 abstract不能修饰哪些结构?
属性、构造器、代码块等。
4.2 abstract 不能与哪些关键字共用?(自洽)
私有方法、静态方法、final的方法、final类。
-私有方法不能重写
-避免静态方法使用类进行调用
-final的方法不能被重写
-final修饰的子类不能有子类
接口的使用
1. 接口的理解:接口的本质是契约、标准、规范
2. 定义接口的关键字:interface
3. 接口内部结构的说明:
-可以声明:
属性:必须使用public static final修饰(全局变量)
方法:jdk8之前:只能声明抽象方法,修饰为public static
jdk8:声明静态方法、默认方法
jdk9:声明私有方法
-不可以声明:构造器、代码块等
4. 接口与类的关系:实现关系
5. 格式:class A extends SuperA implements B,C{}
A相较于SuperA叫做子类
A相较于B,C叫做实现类
6. 满足此关系之后,说明:
-类可以实现多个接口
-类针对于接口的多实现,一定程度上弥补了类的单继承性的局限性
-类必须将实现的接口中的所有抽象方法都重写(实现),方可实例化。否则,此实现类必须声明为抽象类
7. 接口与接口的关系:继承关系,可以多继承
8. 接口的多态性:接口名 变量名 = new 实现类对象;
9. 区分抽象类和接口
-共性:都可以声明抽象方法
都不能实例化
-不同:① 抽象类一定有构造器,接口没有构造器
② 类与类之间是继承关系,类与接口之间是实现关系,接口与接口之间是多继承关系
4. 类的多态
面向对象特征之三:多态性
1.如何理解多态性?
理解:一个事物的多种形态
2.java中多态性的体现:
子类对象的多态性:父类的引用指向子类的对象
3.多态性的应用:
虚拟方法调用
在多态的场景下,调用方法时
编译时,认为方法是左边声明的父类的类型的方法(即被重写的方法)
执行时,实际执行的是子类重写父类的方法
简称:编译看左边,运行看右边
4.多态性的使用前提:
① 要有类的继承关系
② 要有方法的重写
5.多态的适用性:
使用于方法,不适用于属性
6.多态的好处与弊端
6.1弊端
在多态的场景下,我们创建子类的对象,也加载了子类特有的属性和方法。但是由于声明为父类的引用,
导致我们没有办法直接调用子类特有的属性和方法
6.2好处
极大的减少了代码的冗余,不需要定义多个重载的方法
7.instanceof的使用
-向下转型使用instanceof进行判断,避免出现转换异常类
-格式 a instanceof A :判断对象a是否是类A的实例
-如果 a instanceof A 返回true,则 a instanceof superA 返回也是true,A 是 super A 的子类
5. 类的高级特性
static关键字的使用
1. static:静态的
声明变量和方法时,加上static修饰,称为类变量和类方法(或静态变量和静态方法),实现成员和实例之间的共享。
2. static用来修饰的结构:属性、方法、代码块、内部类
3. static修饰属性
3.1 复习:变量的分类
方式1:按照数据类型:基本、引用
方式2:按照类中声明的位置:
成员变量:按照是否使用static修饰进行分类
使用static修饰的成员变量:静态变量、类变量
不使用static修饰的成员变量,非静态变量、实例变量
局部变量:方法内、方法形参、构造器内、构造器形参、代码块内等
3.2 静态变量:类中的属性使用static进行修饰
对比静态变量和实例变量:
① 个数
-静态变量:在内存空间中只有一份,被类的多个对象共享
-实例变量:类的每一个实例(或对象)都保存一份实例变量
② 内存位置
-静态变量:jdk6及之前存在方法区(叫做永久代,jdk8及以后叫做元空间),jdk及以后放在堆空间
-实例变量:存放在堆空间的对象实体中
③ 加载时机
-静态变量:随着类的加载而加载,由于类只会加载一次,所以静态变量只有一份
-实例变量:随着对象的创建而加载。每个对象拥有一份实例变量
④ 调用者
-静态变量:可以被类和对象调用
-实例变量:只能被对象调用
⑤ 判断是否可以调用 ---> 从生命周期的角度解释
类变量 实例变量
类 √ ×
对象 √ √
⑥ 消亡时机
-静态变量:随着类的卸载而消亡
-实例变量:随着对象的消亡而消亡
4. static修饰方法:(类方法、静态方法)
-随着类的加载而加载
-可以通过“类.静态方法”的方式直接调用静态方法
-静态方法内可以调用静态的属性或静态的方法
不能调用非静态的结构(比如:属性、方法)
-非静态方法可以调用当前类中的静态结构(属性、方法)或非静态结构(属性、方法)
-static修饰方法内不能使用this和super
类方法 实例方法
类 √ ×
对象 √ √
final关键字的使用
1. final的理解:最终的
2. final可以用来修饰的结构:类、方法、变量
3. 具体说明:
3.1 final修饰类:此类表示不能被继承
比如:String、StringBuffer、StringBuilder类
3.2 final修饰方法:表示方法不能被重写
比如:Object类中的getClass()
3.3 final修饰变量:即可以修饰成员变量,也可以修饰局部变量
此时的“变量”变成了“常量”,意味着一旦赋值,就不可以更改
3.3.1 final修饰成员变量:有哪些位置可以给成员变量赋值?
-显示赋值
-代码块中赋值
-构造器中赋值
3.3.2 final修饰局部变量:一旦赋值就不能修改
-方法内声明的局部变量:在调用局部变量前,一定需要赋值。而且一旦赋值,就不可更改
-方法的形参:在调用此方法时,给形参进行赋值。而且一旦赋值,就不可更改
4. final与static的搭配:修饰成员变量时,次变量称为:全局变量
比如:Math.PI
类的成员之四:代码块
回顾:类中可以声明的结构:属性、方法、构造器、代码块(或初始化块)、内部类
1. 代码块(或初始化块)的作用
用来初始化类或对象的信息(即初始化类或对象的成员变量)
2. 代码块的修饰:
只能使用static
3. 代码块的分类:
静态代码块:使用static
非静态代码块:未使用static
4. 具体使用
4.1 静态代码块:
-随着类的加载而调用
-类加载执行一次,静态代码块也执行一次
-作用:初始化类的信息
-内部可以声明变量、调用属性或方法、编写输出语句等操作。
-静态代码块执行先于非静态代码块
-如果声明多个静态代码块,则按照声明的先后顺序执行
-静态代码块内部只能调用静态的结构(即静态的属性、方法),不能调用非静态的结构(即非静态属性、方法)
4.2 非静态代码块:
-随着对象的创建而执行
-每创建当前类的实例,就会执行一次非静态代码块
-作用:初始化对象的信息
-内部可以声明变量、调用属性或方法、编写输出语句等操作。
-如果声明多个非静态代码块,则按照声明的先后顺序执行
-静态代码块内部可以调用静态的结构(即静态的属性、方法),也可以调用非静态的结构(即非静态属性、方法)
类的成员之五:内部类
1. 什么是内部类?
将一个类A定义在另一个类B里面,里面的那个类A叫做内部类(InnerClass),外面的类B叫做外部类(OuterClass)
2. 为什么需要内部类?
当一个类只服务于另一个类时,推荐定义内部类,遵循高内聚、低耦合的面向对象开发原则
3. 内部类的使用举例
Thread类内部声明了State类,表示线程的生命周期
HashMap类中声明了Node类,表示封装的Key和Value
4. 内部类的分类:
-成员变量:直接声明在外部类的里面
-使用static修饰:静态的成员内部类
-不使用static修饰:非静态的成员内部类
-局部变量:声明在方法内、构造器内、代码块内的内部类
-匿名的局部内部类
-非匿名的局部内部类
5. 内部类知识:
-成员内部类的理解
-如何创建成员内部类的实例
-如何在成员内部类中调用外部类的结构
-局部内部类的基本使用
6. 关于成员内部类的理解:
-从类的角度看:
-内部可以声明属性、方法、构造器、代码块、内部类等
-此内部类可以声明父类,可以实现接口
-可以使用final修饰
-可以使用abstract修饰
-从外部类的成员角度看
-在内部可以调用外部类的结构。比如:属性、方法等
-除了使用public、缺省权限修饰之外,还可以使用private、protected修饰
-可以使用static修饰
6 异常
异常处理概述
1. 什么是异常
指的是程序在执行过程中,出现的非正常情况,如果不处理最终会导致JVM的非正常停止。
2. 异常抛出机制
java中把不同的异常用不同的类表示,一旦发生某种异常,就创建该异常类型的对象,并且抛出(throw)。然后程序员可以捕获(catch)到这个异常对象,并处理;
如果没有捕获(catch)这个异常对象,那么这个异常对象将会导致程序终止。
3. 如何对待异常
对于程序出现的异常,一般有两种解决方法:一是遇到错误就终止程序的运行。另一种方式是程序员在编写程序时,
就充分考虑到各种可能发生的异常和错误,极力预防和避免。实在无法避免的,要编写相应的代码进行异常检测,
以及‘异常的处理’,保证代码的‘健壮性’。
4. 异常的体系结构
java.lang.Throwable:异常体系的根父类
-java.lang.Error:错误。java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况
StackOverFlowerError、OutOfMemoryError
-java.lang.Exception:异常。我们可以编写针对性的代码进行处理
编译时异常:(受检异常)在执行javac.exe命令时,出现的异常。
-ClassNotFoundException
-FileNotFoundException
-IOException
运行时异常:(非受检异常)在执行java.exe命令时,出现的异常。
-ArrayIndexOutOfBoundsException
-NullPointerException
-ClassCastException
-NumberFormatException
-InputMismatchException
-ArithmeticException
1. 方式一(抓抛模型):try-catch-finally
过程1:“抛”
程序在执行的过程中,一旦出现异常,就会在出现异常的代码处,生成对应异常类的对象,并将此对象抛出
一旦抛出,此程序就不在执行其它的代码了
过程2:“抓”
针对于过程1中抛出的异常对象,进行捕获处理。此捕获处理的过程,就称为抓
一旦将异常进行了处理,代码就可以继续执行
2. 基本结构:
try{
...... //可能产生异常的代码
}
catch(异常类型一 e){
...... //当产生异常类型1型异常时的处置措施
}
catch(异常类型二 e){
...... //当产生异常类型2型异常时的处置措施
}
finally{
...... //无论是否发生异常,都无条件执行的语句
}
3. 使用细节:
-将可能出现异常的代码声明在try语句中。一旦代码出现异常,就会自动生成一个对应异常类的对象。并将此对象抛出。
-针对于try中抛出的异常类的对象,使用之后的catch语句进行匹配。一旦匹配上,就进入catch语句块进行处理。
一旦处理接触,代码就可以继续执行下去
-如果声明了多个catch结构,不同的异常类型在不存在子父类关系的情况下,声明没有顺序要求
如果多个异常类型满足子父类的关系,则必须将子类声明在父类结构的上面。否则,报错。
-catch中异常处理的方式:
①自己编写输出的语句。
②printStackTrace():打印异常的详细信息。(推荐)
③getMessage():获取发生异常的原因
-try catch可以嵌套使用
4. 开发体会:
-对于运行时异常:
开发中,通常就不进行显示的处理了
一旦在程序执行中,出现了运行时异常,那么就根据异常的提示信息修改代码即可
-对于编译时异常:
一定要处理。否则编译不通过。
5. finally的使用说明:
5.1 finally的理解
-将一定要被执行的代码声明在finally结构中
-无论在try中或catch中是否存在任未被处理的异常,无论try中或catch中是否存在return语句等
finally中声明的语句都一定要被执行
-finally语句和catch语句是可选的,但finally不能单独使用
5.2 什么样的代码我们一定要声明在finally中呢?
-开发中一些资源(比如:输入流、输出流、数据库连接、Socket连接等资源),在使用完以后,必须显式的进行关闭操作,
否则,GC不会自动的回收这些资源。进而导致内存的泄漏
为了保证这些资源在使用完以后,不管是否出现了未被处理的异常情况下,这些资源能被关闭。我们必须将这些操作声明在finally中
异常处理的方式2:throws
1. 格式:在方法的声明出使用“throws 异常类型1,异常类型2,...”
2. 举例:
public void test() throws 异常类型1,异常类型2,...{
//可能存在编译时异常
}
3. 是否真正处理了异常?
-从编译是否通过的角度看,看成是给出了异常,万一要是出现时候的解决方案。此方案就是,继续往上抛(throws)
-但是,此throws的方式,仅是将可能出现的异常抛给了此方法的调用者。此调用者任然需要考虑如何处理相关异常
从这个角度来看,throws的方式不算是真正意义上处理了异常。
4. 方法重写的要求:(针对于编译异常来说)
子类重写的方法抛出的异常类型可以与父类被重写的方法抛出的异常类型相同,或是父类被重写的方法抛出的异常类型的子类。
7. 常用类
1.String类的理解(以jdk8为例说明)
1.1 类的声明
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
-final:String是不可被继承的
-Serializable:可序列化接口。凡是实现此接口的类的对象都可以通过网络或本地流进行数据的传输。
-Comparable:凡是实现此接口的类,其对象都可以比较大小。
1.2 内部声明的属性
jdk8中:
private final char value[];//储存字符串数据的容器
-final:指明此value数组一旦初始化,其地址就不可变
jdk9开始:为了节省内存空间
private final byte[] value;
2. 字符串常量的储存位置
-字符串常量都储存在字符串常量池(StringTable)中
-字符串常量池不允许存放两个相同的字符串常量
-字符串常量池,在不同的jdk版本中,存放位置不同
jdk7之前,字符串常量池存放在方法区
jdk7及之后,字符串常量池,存放在堆空间
3. String的不可变性的理解
4. String实例化的两种方式
【面试题】
String s2 = new String("hello");在内存中创建了几个对象?
一个是堆空间中new的对象。另一个是在字符串常量池中生成的字面量
5. String的连接操作:+
情况1:常量 + 常量 :结果仍然储存在字符串常量池中,返回此字面量的地址。注:此时的常量可能是字面量,也可能是final修饰的常量
情况2:常量 + 变量 或 变量 + 常量 :都会通过new的方式创建一个新的字符串,返回堆空间中此字符串对象的地址值
情况3:调用字符串的intern() :返回的是字符串常量池中字面量的地址
(了解)情况4:concat():无论是常量调用方法,还是变量调用方法,无论参数是参量还是变量,调用完concat()方法都会返回一个新new的对象
6. String的构造器和常用方法
6.1 构造器
public String():初始化新创建的String对象,以使其表示空字符序列
public String(String original):初始化一个新创建的String对象,使其表示一个与参数相同的字符序列
public String(char value[]):通过当前参数中的字符数组来构造新的String
public String(char value[], int offset, int count):通过字符数组的一部分来构造新的String
public String(byte bytes[]):通过使用平台的默认字符集解码当前参数中的字节数组来构造新的String
public String(byte bytes[], String charsetName):通过使用指定的字符集解码当前参数中的字节数组来构造新的String
针对于StringBuilder来说:
内部属性有:
char[] value;//储存字符序列
int count;//实际储存的字符的个数
StringBuilder sb1 = new StringBuilder();//char[] value = new char[16];
StringBuilder sb1 = new StringBuilder("abc");//char[] value = new char[16+"abc".length()];
sb1.append("ac");//value[0] = 'a';value[1] = 'c';
sb1.append("b");//value[2] = 'b';
当count超过value.length()时,就需要扩容:默认扩容为原容量的2倍+2,并将原有value数组中的元素复制到新的数组中
3. 原码启示
-如果开发中需要频繁的针对于字符串进行增、删、改、查等,建议使用StringBuilder或StringBuffer替换String,因为String效率低
-如果开发中,不涉及 到线程安全问题,建议使用StringBuilder替换StringBuffer。因为StringBuilder效率高
-如果开发中大体确定要操作的字符的个数,建议使用带int capacity参数的构造器,避免底层多次扩容操作,性能降低
4. StringBuffer和StringBuilder中的常用方法
增:
StringBuffer append(xx):提供了很多的append()方法,用于进行字符串追加的方式拼接
删:
StringBuffer delete(int start,int end):删除[start,end]之间的字符
StringBuffer deleteCharAt(int index):删除[index]位置的字符
改:
StringBuffer replace(int start,int end,String str):替换[start,end)范围的字符序列
void setCharAt(int index,char c):替换[index]位置字符
查:
char charAt(int index):查找指定位置index位置上的字符
插:
StringBuffer insert(int index,xx):在[index]位置上插入xx
长度:
int length():返回储存的字符数据的长度
反转:
StringBuffer reverse():反转
5. 对比三者的执行效率
效率从高到低排列:
StringBuilder > StringBuffer > String
/*
注意:Date 日期 和 Data 数据 数据库:Database
Java中针对时间日期类型提供了Date工具类。
使用之前需要进行导包,和Scanner类非常类似: java.util.Date
注意是util下的Date!
导包:大部分的常用包都没有导入 ,原因:Java自带的一个包叫做 Java.lang.*包 该包下得类都是自动导入
例如String System
需要的手动导入:Scanner
*/
/*
常见的获取Date对象方式如下:
Date d = new Date();//获取当前时间
常见的过时方法:···
*/
Date d = new Date();//下面的方法是过时的,但是这个对象不过时
System.out.println(d);//发现:结果是对中国人不友好的
//所以需要通过大量的过时方法来对中国人民友好
//过时方法:仅仅是针对 大量的用户 翻译:如果你的项目很多很多人使用,就不推荐使用下面的方法
//如果用户数量较少,可以使用,但是不推荐。
// int year = d.getYear();
// System.out.println(year+1900);
// int month = d.getMonth();
// System.out.println(month + 1);
// int date = d.getDate();
// System.out.println(date);
// int h = d.getHours();
// int m = d.getMinutes();
// int s = d.getSeconds();
// System.out.println(h+":" + m + ":" + s
/*
由于Date对象的99%的方法都已经过时
备注:Date d = new Date();还是没有过时,依然是获取当前时间的唯一方法。
所以Java提供了SimpleDateFormat类,来替代Date的绝大部分方法,来对时间进行操作。
Simple:简单的
Date:日期
Format:格式
语法如下:
SimpleDateFormat sdf = new SimpleDateFormat(“日期的具体格式”);
日期具体格式如下:
yyyy-MM-dd HH:mm:ss
yyyy/MM/dd HH:mm:ss
yyyy-MM-dd
HH:mm:ss
格式可以随便改,但是字母不能。yyyy表示年 MM表示月 dd日期 HH小时 mm分钟 ss秒
注意:yyyy和MM 和dd和mm和ss 是固定的
但是HH表示24小时制的时间
hh表示12小时的时间
其中字母定死了,符号可以根据场景改变。
字母可以省略,例如只保留年月日或只保留时分秒。
*/
// //如何获取当前时间
// //1.定义时间的格式
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
// //2.获取当前时间
// Date d = new Date();
// //3.格式化时间
// String str = sdf.format(d);
// System.out.println(str);
//某些场景:可能需要过去或未来的时间 方法如下:
String str = "2030/8/8 12:12:12";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date date = sdf.parse(str);
System.out.println(date);
/*
Java提供了Math类来执行基本的数学运算。
该类处于java.lang下,所以使用无需导包。
该类下的所有方法都是静态方法,故无需new对象。
*/
public static void main(String[] args) {
//floor 地板
//ceil 天花板
// doule Math.floor(double x); 向下取整,但是依然是double类型。
System.out.println(Math.floor(1.1));//1.0
System.out.println(Math.floor(1.0));//1.0
// double Math.ceil(double x);向上取整,但是依然是double类型
System.out.println(Math.ceil(1.1));//2.0
System.out.println(Math.ceil(1.0));//1.0
// long Math.round();四舍五入
System.out.println(Math.round(1.1));//1
System.out.println(Math.round(1.5));//2
System.out.println(Math.round(1.9));//2
//总结:四舍五入如果碰到± xxx.5 优先往大的数走
System.out.println(Math.round(-1.1));//-1
System.out.println(Math.round(-1.5));//-1
System.out.println(Math.round(-1.9));//-2
// double Math.random();取一个大于等于0但是小于1的小数 官方概念是这样,但是实际上可以不用考虑0
System.out.println(Math.random());
}
}
/*
学会看API(教学文档,可以了解一些方法的组成以及如何使用):语法如下:
数据类型 方法名(参数列表);
数据类型: 方法的返回值
/*
包装类是专门针对于基本数据类型而言。
由于基本数据类型没有方法或属性。
为了更加方便对基本数据类型的值进行操作,Java提供了八大基本数据类型的包装类。
boolean byte
char short
int float
double long
Boolean Byte
Character Short
Integer Float
Double Long
*/
//包装类如何声明变量 和String一样
Integer a = 123;//直接声明
Integer b = new Integer(123);//通过构造方法声明
//包装类的用途:
//1.作为实体类属性的数据类型,替换基本数据类型。
//以前的写法 见下方的Student类
Student s = new Student(123, "张三", 18,18);
Student s1 = new Student();
System.out.println(s);
System.out.println(s1);
//缺点:基本数据类型的默认值为0 而引用数据类型的默认值为null
//2.将字符串转为对应的基本数据类型
String str = "123445";
int i = Integer.parseInt(str);
System.out.println(i);
double d = Double.parseDouble(str);
/*
包装类的补充:
仅做了解为了面试 实际开发用不到
*/
public static void main(String[] args) {
//自动装箱值得是:将基本数据类型 变成 包装类
// int a = 11;
// Integer b = a;//此过程就是自动装箱
//
// //自动拆箱值得是:将包装类转成基本数据类型
// Integer c = 1;
// int d = c.intValue();
//
//常见面试题:
// String str1 = "你好";
// String str2 = "你好";
// String str3 = new String("你好");
// System.out.println(str1==str2);//true
// System.out.println(str1==str3);//f
//
// System.out.println(str1.equals(str2));//t
// System.out.println(str1.equals(str3));//t
//
// System.out.println(str2==str3);//f
// System.out.println(str2.equals(str3));//t
//Integer和String的类似度高达99%
// Integer str1 = 1;
// Integer str2 = 1;
// Integer str3 = new Integer(1);
// System.out.println(str1==str2);//true
// System.out.println(str1==str3);//f
//
// System.out.println(str1.equals(str2));//t
// System.out.println(str1.equals(str3));//t
//
// System.out.println(str2==str3);//f
// System.out.println(str2.equals(str3));//t
Integer str1 = 1111;
Integer str2 = 1111;
Integer str3 = new Integer(1111);
System.out.println(str1==str2);//false
System.out.println(str1==str3);//f
System.out.println(str1.equals(str2));//t
System.out.println(str1.equals(str3));//t
System.out.println(str2==str3);//f
System.out.println(str2.equals(str3));//t
//Integer直接声明变量和String非常类似 ,都有一个类似缓存池的存在
//String直接声明:先去缓存池找是否存在,如果不存在就new一个,如果存在就直接引用
//Integer直接声明:现在-128~127的缓存池找是否存在,如果不存在就new一个,如果存在就直接引用
8. 集合类
Collection:集合 Array:数组
集合和数组非常类似,都是存储数据的一个容器。
区别:
数组是定长,且只能存储相同类型的数据。
集合是变长,不仅可以存储相同数据而且可以存储不同类型的数据的容器。
Java中的集合可以看成JavaScript的数组。仅仅是概念一样,代码完全不一样。
备注:和集合相关的类都需要导包。注意都是java.util下。
Java中的集合分为两大类:
A.单列集合(类似数组,只需要存值,通过下标访问值)
B.双列集合(类似对象,不仅需要存值,还要存与之对应的属性名,通过属性名访问属性值)
Collection就是所有单列集合的父接口
所有单列集合都实现了该接口,所以:所有单列集合都拥有共同的方法。
所以学会了一种单列集合,剩下的单列集合都能依葫芦画瓢。
Collection又分为:
List子接口(内部元素:有序且可以重复)
Set子接口(内部元素:无序且无法重复)
List接口下主要分为以下两个实现类:
ArrayList 集合:采用数组结构,元素增删慢,查找快,由于日常开发中使用最多的功能就是查询,所以 ArrayList 是最常用的集合之一。没有保证线程安全,但是效率高。
Vector集合: 保证了线程安全,但是效率较低。除此和ArrayList都一样
LinkedList 集合:采用链表结构。元素增删快,但是查找慢。
线程安全:保证多个用户共同使用它也不会出错
数组结构:元素增删慢,查找快
链表结构:元素增删快,查找慢
原因:
数组结构是有序的,每个元素都有一个下标,查找的时候只需要根据下标查找即可, 所以元素的查找快,但是一旦发生了元素的增加或减少, 会引发每个元素的下标发生 变化,导致增删慢。
链表结构是无序的,每个元素仅仅存储了本身的值,和引入下一个元素的地址。
所以查询都是一个一个轮流查询,而不是像数组结构,直接根据下标查询。
但是增删快,因为只需要对增删位置的元素进行修改即可,不会影响别的元素。
//常见的集合1: ArrayList:
//复习: 特点1:采用了数组结构:查询快。增删慢 特点2:线程非安全所以效率快
//特点3:有序且可以重复 特点4:父类是List 祖父类:Collection
//如何创建一个ArrayList :类似于多态: 左边放父类 右边放自类
List list = new ArrayList();
// boolean add(Object o); //往集合尾部添加元素,一般不需要返回值
System.out.println(list.add("张三"));
System.out.println(list.add("李四"));
System.out.println(list.add("wangw"));
System.out.println(list.add("jakc"));
// void add(int index, Object o );//往集合指定位置添加元素
list.add(2,"王五");
System.out.println(list);
//常见的方法:
// int size(); //返回集合的长度
System.out.println(list.size());
// boolean isEmpty(); //判断集合的内部是否为null 注意:并不是判断集合为空
System.out.println(list.isEmpty());
// boolean contains(Object o); //判断集合内部是否存在该值
System.out.println(list.contains("张三1"));
// boolean remove(Object o);//删除集合指定的元素值
System.out.println(list.remove(0));//根据下标删除
System.out.println(list.remove("jakc"));
System.out.println(list);
// int indexOf(Object o);//返回指定元素首次出现的位置。-1表示不存在
System.out.println(list.indexOf("王五123"));
// boolean equals(Object o);//判断两个集合是否 值和顺序都相等
// 也重写了Object的equals方法
//Object get(index) 获取指定位置的元素
System.out.println(list.get(2) + "~~");
List l1 = new ArrayList();
List l2 = new ArrayList();
l1.add(123);
l2.add(123);
System.out.println(l1==l2);
System.out.println(l1.equals(l2));
//复习: 特点1:单列集合 父类是List 祖父类Collection
//特点2:数组结构 特点:有序可重复 线程安全效率低
// List list = new Vector();
// list.add(true);
// list.add("nihao");
// System.out.println(list);
//案例:测试ArrayList和Vector 同时增加100000个元素的效率差
// long l1 = System.currentTimeMillis();
// List list = new ArrayList();
// for(int i = 1; i<=1000000;i++) {
// list.add(i);
// }
// System.out.println(list.size());
// long l2 = System.currentTimeMillis();//16毫秒
// System.out.println(l2-l1);
// long l1 = System.currentTimeMillis();
// List list = new Vector();
// for(int i = 1; i<=1000000;i++) {
// list.add(i);
// }
// System.out.println(list.size());
// long l2 = System.currentTimeMillis();//28毫秒
// System.out.println(l2-l1);
//LinkList:特点: 父类都是List祖父类都是Collection 有序 可重复
//链表结构 增删快。查询慢
// List list = new LinkedList();
//常见的方法如上一样。
//案例:测试ArrayList 和 LinkList 的效率
List list1 = new ArrayList();
List list2 = new LinkedList();
for(int i = 1; i<=10000000;i++) {
list1.add(i);
list2.add(i);
}
long l1 = System.currentTimeMillis();
int i = list1.indexOf(9999999);
System.out.println(i);
long l2 = System.currentTimeMillis();
System.out.println(l2-l1);
//Set集合接口: 特点:单列集合 父类依然是Collection
//特点:无序无法重复。 该接口下有一个常见类:HashSet
Set<Object> set = new HashSet<Object>();
set.add(true);
set.add(123);
set.add("你好");
set.add(123);
System.out.println(set);
//如何遍历集合
//如果List集合 普通for循环即可完成遍历
//但是set集合 没有下标 增强for循环
for(Object o :set ) {
System.out.println(o);
}
//泛型的概念 :就是每个集合后面跟着的 <E> E表示这个集合应该存储什么类型的数据
//集合:可以存放不同类型的。但是即使这样,你也要声明
List<Integer> list = new ArrayList<Integer>();
list.add(12345);
System.out.println(list);
//Map集合双列集合: 分别存储 键 和 值 键理解为属性名 value属性值
//键绝对是独一无二的 一般来说键:String 值:Object
Map<String,Object> map = new HashMap<String,Object>();
map.put("1001", "张三1");//添加元素
map.put("1002", "张三2");
map.put("1003", "张三3");
System.out.println(map);
System.out.println(map.get("1002"));//获取指定键 对应的值
System.out.println(map.size());
//如何遍历一个map集合
//1.将其转成Set集合
Set<String> set = map.keySet();//将map集合中的键 全部转成set集合
System.out.println(set);
for(String s :set) {
System.out.println(map.get(s));
}
//使用3种方式 遍历集合的效率
List<Integer> list = new ArrayList<Integer>();
for(int i = 1; i<=10000000;i++) {
list.add(i);
}
System.out.println("准备完毕!!!!!!!!!!");
// 遍历集合的效率
long l1 = System.currentTimeMillis();
for(int i = 0 ;i<list.size();i++) {
if(i==9999999) {
System.out.println("111222");
}
}
long l2 = System.currentTimeMillis();
System.out.println("普通for循环的遍历效率为" + (l2-l1) );//7
long l3 = System.currentTimeMillis();
for(Integer i : list) {
if(i==9999999) {
System.out.println("111222");
}
}
long l4 = System.currentTimeMillis();
System.out.println("增强for循环的遍历效率为" + (l4-l3) );//35
long l5 = System.currentTimeMillis();
//任何集合都可以通过额外一个知识点: Iterator(迭代器)
Iterator<Integer> it = list.iterator();
while(it.hasNext()) {//是否还有下一个元素
if(it.next()==99999999) {
}
}
long l6 = System.currentTimeMillis();
System.out.println("迭代器循环的遍历效率为" + (l6-l5) );//23
Set<Student> set = new HashSet<Student>();
Student s1 = new Student(1, "jack1", 12);
Student s2 = new Student(2, "jack2", 13);
Student s3 = new Student(3, "jack3", 14);
Student s4 = new Student(1, "jack1", 12);
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
System.out.println(set);
//无序:如果存放的实体类,会出现一个BUG 当某个实体类的属性完全一致,按理来说应该视为相同的值。但是set不会这么做。
//解决办法:重写hashCode方法即可
//hash:哈希 一种编码 每个对象都有一个独一无二的hash值 你就理解为键盘上的26个字母
// Integer a = 123;
// String b = "Nihao";
// System.out.println(a.hashCode());
// System.out.println(b.hashCode());
// //存在的意义: 判断对象是否相等
//一般来说判断对象是否相等都是通过== 但是==获取的内存地址 效率较低
//所以hashCode效率较高、 hashCode相等,则进一步判断内存地址 如果hashCode都不能则内存地址肯定不等。
// String a1 = new String("111");
// String a2 = new String("111");
// System.out.println(a1.hashCode());
// System.out.println(a2.hashCode());
//
// Student s1 = new Student(1, "jack1", 12);
// Student s2 = new Student(1, "jack1", 12);
// System.out.println(s1.hashCode());
// System.out.println(s2.hashCode());
9. IO流
/*
临时存储:变量,数组,集合等,都是存储在内存中,只要停止运行,这些数据都会消失。
数据持久化:将数据保存在硬盘里面,只要不手动删除,则永久生效。
常见的数据持久化技术:1.数据库 2.IO流
数据库:通过Java代码将临时存储的数据存进数据库。
IO流:通过Java代码将临时存储的数据存进指定的文件。
都是双向操作,能存也能取。
注意:文件的相关操作都是导入 java.io 包
构造方法如下:
File file = new File(“要操作或要创建的文件夹或文件的路径”);
常见方法如下:
boolean createNewFile();创建该文件,如果存在就不会创建
boolean mkdir();创建该文件夹,如果存在就不会创建
*/
// 案例1:在项目根目录下创建123.txt文件
// File file = new File("123.txt");//这里是相对路径,表示项目的根目录
// boolean b = file.createNewFile();
// System.out.println(b);
// 案例2:在桌面下创建456.txt(注意路径里面的转义符)
//如果想在非项目路径下创建文件,那么就需要用到绝对路径
// File file = new File("C:\\Users\\Administrator\\Desktop\\456.txt");
// boolean b = file.createNewFile();
// System.out.println(b);
//这里要注意路径的转义符 \是一个转义符。所以需要\\ 来进行转义 以下写法是错误的。
// File file = new File("C:\Users\Administrator\Desktop\456.txt");
// 案例3:在项目根目录下创建test文件夹,然后在内部再创建test.txt文件
//注意:对文件的操作 只能是一层一层的来,不能够直接操作 多层目录。
// File file = new File("test/test.txt");这种写法是错误的.
//一般如果对多层的文件进行处理,一般都是将路径声明在外面。让它成为一个变量。
String str = "test";
File file = new File(str);
file.mkdir();
str = str + "/test.txt";
file = new File(str);
file.createNewFile();
// File f = new File("123.txt");
// boolean exists();返回文件或文件夹是否存在
// System.out.println(f.exists());
// String getAbsolutePath(); 返回文件或文件夹的绝对路径
// String path = f.getAbsolutePath();
// System.out.println(path);
// boolean delete(); 删除空文件夹或文件
// boolean b = f.delete();
// System.out.println(b);
// isFile(); 判断是否为文件
// boolean c = f.isFile();
// System.out.println(c);
// isDirectory();判断是否为文件夹
// getName();返回file对象的文件名字
// System.out.println(f.getName());
// File[] listFiles();返回指定目录下的所有文件或文件夹,返回是file对象
File file = new File("C:\\Users\\Administrator\\Desktop\\chap5");
File[] listFiles = file.listFiles();
for(int i = 0 ;i<listFiles.length;i++) {
System.out.println(listFiles[i]);
}
// 案例1:遍历指定目录下的所有文件夹以及文件。
// File file = new File("C:\\Users\\Administrator\\Desktop\\chap5");
// listFiles(file);
// 案例2:遍历指定目录下是否存在XXXX.xxx文件
File file = new File("E:");
String str = "魔性小人热身舞.mp4";
findFile(file, str);
}
public static void listFiles(File file) {
File[] listFiles = file.listFiles();
for(int i = 0 ;i<listFiles.length;i++) {
if(listFiles[i].isFile()){
System.out.println( "文件名为:" + listFiles[i].getName());
}else {
System.out.println("文件夹名为:" +listFiles[i].getName());
//下一步接着遍历
listFiles(listFiles[i]);
}
}
}
public static void findFile(File file , String fileName) {
File[] listFiles = file.listFiles();
for(int i = 0 ;i<listFiles.length;i++) {
if(listFiles[i].isFile()){
System.out.println( "文件名为:" + listFiles[i].getName());
if(listFiles[i].getName().equals(fileName)) {
System.out.println("找到了!!" + listFiles[i].getAbsolutePath() );
// return;仅仅只能跳出当前方法,无法跳出之前递归的方法,
System.exit(0);//直接结束JVM
}
}else {
System.out.println("文件夹名为:" +listFiles[i].getName());
//下一步接着遍历
findFile(listFiles[i],fileName);
}
}
}
//别拿有用的文件夹删除,因为这是不可撤回的动作
//删除指定文件夹:
String str = "C:\\Users\\Administrator\\Desktop\\chap7";
File file = new File(str);
deleteDir(file);
}
public static void deleteDir(File file) {
File[] files = file.listFiles();
for(int i = 0;i<files.length;i++) {
if(files[i].isFile()) {
files[i].delete();
}else {
deleteDir(files[i]);
}
}
file.delete();
}
/*
之前讲的File 仅仅只能对文件或文件夹进行删除或创建,无法对文件的内部进行修改。
所以就需要额外学习以下知识点:
IO流全称为 : input/output stream
input :输入
output :输出
stream :流
输入流:文件传输给内存,理解为读取文件(获取文件)
输出流:内存传输给文件,理解为写入文件(修改文件)
IO流的分类:
按照流向分:
输入流和输出流
按照类型分:
字节流和字符流
字节流:按照字节为单位传输,适用于所有文件,例如图像,音乐,视频等。
字符流:按照字符为单位传输,仅适用于文本类型文件,例如txt,word等
所以共计4大流:字节输入流 字节输出流 字符输入流 字符输出流
字节 :计算机最小存储单位。 又称byte
位 :计算机最小的单位,又称位 1byte = 8bit
字符 :1个字符 = 2个字节 = 16bit
IO流的分类:
字节输入流 字节输出流 字符输入流 字符输出流
IO流的种类分为差不多50多种,规律如下:
字节输入流: 父类就是 InputStream 凡是:XxxInputStream 的都是字节输入流的实现类
字节输出流: 父类就是 OutputStream 凡是:XxxOutputStream 的都是字节输出流的实现类
字符输入流: 父类就是 Reader 凡是:XxxReader 的都是字符输入流的实现类
字符输出流: 父类就是 Writer 凡是:XxxWriter 的都是字符输出流的实现类
*/
/*
字节输入流: 父类就是 InputStream 凡是:XxxInputStream 的都是字节输入流的实现类
最常见的就是:FileInputStream
构造方法如下:
方法1: FileInputStream f = new FileInputStream("操作的文件的路径");
方法2: FileInputStream f = new FileInputStream( new File("路径") );
*/
// FileInputStream f = new FileInputStream("123.txt");
//读取通过read方法 每次只能读取1个字节 返回的是int类型(ASCII码) 如果返回-1就表示读取结束
// int i = f.read();
// System.out.println(i);
// byte b[] = new byte[100];//创建一个长度为100的数组
// int k = f.read(b);//此时就表示每次都读取100字节。 i返回数组长度 数组的长度是随时改变的:表示读取了多少数据
// //这个数组进入read方法之后,数组内容就会被填充的数据 填充
// for(int i = 0 ;i<b.length;i++) {
// System.out.println(b[i]);
// }
//
//
// f.close();//类似于Scanner;清除内存
FileInputStream f = new FileInputStream("123.txt");
//需求:将文本内容全部读取到控制台上。 每次读取10个字节
byte b[] = new byte[10];
while(true) {
int k = f.read(b);
if(k==-1) {
break;
}else {
for(int i = 0 ;i<b.length;i++) {
System.out.print(b[i]);
}
}
}
f.close();
/*
字节输出流: 父类就是 OutputStream 凡是:XxxOutputStream 的都是字节输出流的实现类
最常见的就是:FileOutputStream
构造方法如下:
方法1: FileOutputStream f = new FileOutputStream("操作的文件的路径");
方法2: FileOutputStream f = new FileOutputStream( new File("路径") );
//如果放了第二个参数 表示每次操作都是追加操作 如如果没放第二个参数或者放了false 就表示每次操作都是清空之后操作
方法3: FileOutputStream f = new FileOutputStream("操作的文件的路径",true);
方法4: FileOutputStream f = new FileOutputStream( new File("路径") ,true);
*/
FileOutputStream f = new FileOutputStream("456.txt");
//注意:每次重新执行,将内部内容清空再操作
f.write(65);
f.write(66);
f.write(67);
String str = "你好";
byte[] bs = str.getBytes();
f.write(bs);
f.close();
//将根路径下的123.txt复制成111.txt
FileInputStream f1 = new FileInputStream("1.jpg");
FileOutputStream f2 = new FileOutputStream("2.jpg");//如果文件不存在会自动帮你创建
byte b[] = new byte[10];
while(true) {
int i = f1.read(b);
if(i==-1) {
System.out.println("操作完成");
break;
}else {
f2.flush();
f2.write(b);
}
}
f1.close();
f2.close();
/*
注意:字符流只能处理文本类型文件 如果处理非文本 将会失真
字符输入流: 父类就是 Reader 凡是:XxxReader 的都是字符输入流的实现类
字符输出流: 父类就是 Writer 凡是:XxxWriter 的都是字符输出流的实现类
*/
FileReader f1 = new FileReader("111.txt");
FileWriter f2 = new FileWriter("333.txt");
char c[] = new char[20];
while(true) {
int i = f1.read(c);
if(i==-1) {
break;
}else {
f2.flush();
f2.write(c);
}
}
f1.close();
f2.close();
// 将a文件夹复制一份b文件夹,假设a文件夹中只有若干个图片。
//文件的复制本质是复制文件 而非文件夹
File f = new File("C:\\Users\\Administrator\\Desktop\\a");
String path = "C:\\Users\\Administrator\\Desktop\\b";
File file = new File(path);
file.mkdir();
File[] files = f.listFiles();
for(int k = 0 ;k<files.length;k++) {
FileInputStream f1 = new FileInputStream( files[k] );
FileOutputStream f2 = new FileOutputStream( path + "\\" + files[k].getName() );
byte b[] = new byte[110];
while(true) {
int i = f1.read(b);
if(i==-1) {
System.out.println("复制完成");
break;
}else {
f2.write(b);
}
}
f1.close();
f2.close();
10. 多线程
1.进程的相关概念:每个独立运行的程序称为进程。例如QQ音乐。腾讯QQ。
2.线程的相关概念:每个进程又可以同时拥有多个线程。并且线程是可以同时执行的。一个进程最少拥有一个线程。
例如运行QQ音乐这个进程。它可以同时听歌,看视频,搜索歌曲。至少在执行3个线程。
当我们运行Java程序,就可以理解为开启了一个进程。
其中该进程下默认的一条进程就是main线程。
可以通过创建多个线程类,实现java的多线程。
如果没有使用多线程,代码的执行顺序就是从上到下依次执行。
例如:先听歌,再吃饭,再看电视,再聊天。如果是单线程,就只能一个一个按顺序执行。
如果是多线程:就可以一边听歌,一边吃饭,一边看电视,一边聊天。
多线程的应用场景:多用户同时使用的场景
例如:春节抢票 、 双十一购物 等 。高并发场景。
高并发:多个事件在同一个时间段发生。 例如同时吃饭和聊天。
高并行:多个事件在同一个时刻发生。 例如同时吃饭和听歌。
解决的最常见方式:利用多线程。
public class Test2 {
//此时 main方法就可以看成一个线程。 此时一个单线程
public static void main(String[] args) {
/*
与线程相关的类和方法都处于: Java.lang.Thread下 所以无需导包。
线程类的创建方法:(先有线程类。再有线程)
1.继承Thread类
2.重写run方法(线程的代码体都放在run方法里面)
3.创建对象
4.调用start方法
备注:线程的执行权是随机的。
由于Java是单继承,所以一般能不继承就不继承,把继承的机会让给更重要的类。
所以一般都是使用下面这种方式来创建线程。
1.实现runnable接口
2.实现run方法(线程的代码体都放在run方法里面)
3.创建上述类对象
4.创建线程对象,然后将第三步的对象作为参数传递。
5.调用start方法
*/
A a = new A();
a.start();
B b = new B();
Thread t = new Thread(b);
t.start();
for(int i = 1;i<=100;i++) {
System.out.println("这是main线程" + i);
}
}
}
class A extends Thread{
public void run() {
for(int i = 1;i<=100;i++) {
System.out.println("这是A线程" + i);
}
}
}
class B implements Runnable{
@Override
public void run() {
for(int i = 1;i<=100;i++) {
System.out.println("这是B线程" + i);
}
}
}
public class Test3 {
public static void main(String[] args) throws Exception {
// Thread.currentThread() 获取当前线程
//
// Thread.currentThread().setName() 给当前线程取一个别名
//
// Thread.currentThread().getName() 获取当前线程的别名
//
// Thread.sleep(long x); 让当前线程暂停x毫秒。让别的线程先执行。
//
// isAlive();获取该线程是否活跃
A1 a = new A1();
Thread t = new Thread(a);
t.start();
System.out.println(Thread.currentThread().isAlive());//true
System.out.println(111);
Thread.sleep(3000);
System.out.println(222);
System.out.println(Thread.currentThread().isAlive());//true
System.out.println("A1线程是否活跃" + t.isAlive());
}
}
class A1 implements Runnable{
@Override
public void run() {
System.out.println(111);
}
}
public class Test4 {
public static void main(String[] args) {
/*
CPU负责:执行线程。 那么存在多个线程,优先级是怎么样的? 随机的
计算机:平均调度。理解为雨露均沾,每个线程都是平等的,都是同等的机会被CPU执行。
Java:抢占时调度,理解为每个线程可以设置一个优先级。可以优先执行该线程。
但是至于抢不抢的过,还是随机的。
Java中每个线程默认优先级都是5,最大为10,最小为1;
Thread.currentThread().setPriority(xxx);
Thread.currentThread().getPriority(xxx);
*/
Thread t = new Thread(new A2());
t.setName("这是A2线程");
Thread.currentThread().setName("main");
t.setPriority(10);
Thread.currentThread().setPriority(1);
t.start();
for(int i = 0 ;i<100;i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
class A2 implements Runnable{
@Override
public void run() {
for(int i = 0 ;i<100;i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
线程的生命周期:
1.新建态:创建了一个线程还没有通过start方法开启。
2.就绪态:调用了start方法,但是还没有开始执行。 或阻塞态结束。
3.运行态:抢到了CPU资源,正在执行中。
4.阻塞态:在运行态的过程中,出现一点意外(例如:被别的线程抢去了cpu资源,调用了sleep,yield等方法)
5.消亡态:线程执行完毕。
public static void main(String[] args) {
upload1();
}
public static void upload1() {
//有会员的网盘下载
for(int i = 1;i<=100;i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("下载进度为:" + i + "%");
}
}
public class Test7 {
public static void main(String[] args) {
/*
创建一个类,该类有3个方法,分别是吃饭睡觉打豆豆。
每个方法需要执行5秒。
*/
// long t1 = System.currentTimeMillis();
// a1();
// a2();
// a3();
// long t2 = System.currentTimeMillis();
// System.out.println(t2-t1);单线程执行时间约等于15秒
long t11 = System.currentTimeMillis();
Thread t1 = new Thread(new A4());
Thread t2 = new Thread(new A5());
Thread t3 = new Thread(new A6());
t1.start();
t2.start();
t3.start();
//下面的写法是不发正确计算结果的。因为多线程的特性,不会等t1t2t3执行完。而是main自己先执行完了
// long t22 = System.currentTimeMillis();
// System.out.println(t22-t11);
//t1 t2 t3都要执行完毕才可以执行下面这行代码
//t1 t2 t3 都处于死亡状态 isAlive返回值都要为false 才可以开始计算时间
while(true) {
if(t1.isAlive()==false && t2.isAlive()==false && t3.isAlive()==false ) {
long t22 = System.currentTimeMillis();
System.out.println(t22-t11);
break;
}
}
}
public static void a1() {
System.out.println("吃饭中");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("吃饭结束");
}
public static void a2() {
System.out.println("睡觉中");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("睡觉结束");
}
public static void a3() {
System.out.println("打豆豆中");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("打豆豆结束");
}
}
class A4 implements Runnable{
@Override
public void run() {
System.out.println("吃饭中");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("吃饭结束");
}
}
class A5 implements Runnable{
@Override
public void run() {
System.out.println("睡觉中");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("睡觉结束");
}
}
class A6 implements Runnable{
@Override
public void run() {
System.out.println("打豆豆中");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("打豆豆结束");
}
}
public class Test8 {
public static void main(String[] args) {
// 创建一个线程类,功能是卖票。初始为100张票。
// 然后定义三个线程对象,同时开启,理解为3个窗口同时卖票。
I i = new I();
Thread t1 = new Thread(i);
Thread t2 = new Thread(i);
Thread t3 = new Thread(i);
t1.setName("窗口1:");
t2.setName("窗口2:");
t3.setName("窗口3:");
t1.start();
t2.start();
t3.start();
//该案例的写法是没有任何问题的 但是结果是不合人意的。
//原因: 由于Java线程是抢占行 如果多个线程同时操作相同的变量 。会出现读写错误。
//如何解决:见test9
}
}
class I implements Runnable{
int num = 100;//表示票的总数
public void run(){
while(true) {
num--;
if(num<0) {
System.out.println("票卖完了");
break;
}else {
System.out.println(Thread.currentThread().getName() + "卖了第" + (100-num) + "张票");
}
}
}
}
public class Test9 {
public static void main(String[] args) {
// 创建一个线程类,功能是卖票。初始为100张票。
// 然后定义三个线程对象,同时开启,理解为3个窗口同时卖票。
I1 i = new I1();
Thread t1 = new Thread(i);
Thread t2 = new Thread(i);
Thread t3 = new Thread(i);
t1.setName("窗口1:");
t2.setName("窗口2:");
t3.setName("窗口3:");
t1.start();
t2.start();
t3.start();
//该案例的写法是没有任何问题的 但是结果是不合人意的。
//原因: 由于Java线程是抢占行 如果多个线程同时操作相同的变量 。会出现读写错误。
//如何解决: 将操作相同变量的代码块 加上一个 锁机制。
//锁机制: 只允许一个线程进入,并且只有当该线程执行结束之后,才允许别的线程进来。
//语法:
/*
synchronized (this) {
操作相同变量的语法代码
}
*/
}
}
class I1 implements Runnable{
int num = 100;//表示票的总数
public void run(){
while(true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (this) {//参数只需要保证是独一无二 都行
num--;
if(num<0) {
System.out.println("票卖完了");
break;
}else {
System.out.println(Thread.currentThread().getName() + "卖了第" + (100-num) + "张票");
}
}
}
}
}
public class Test10 {
public static void main(String[] args) {
Money m = new Money();
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);
t1.setName("支付宝");
t2.setName("微信");
t1.start();
t2.start();
}
}
class Money implements Runnable{
int money = 100;
@Override
public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + "查询到余额为" + money);
if (money >= 100) {
System.out.println(Thread.currentThread().getName() + "开始取钱");
money = money - 100;
System.out.println(Thread.currentThread().getName() + "取钱成功");
}else {
System.out.println(Thread.currentThread().getName() + "取钱失败");
}
}
}
}