文章目录
- java 语言概述(1)
- java语言基础(2)
- 选择与循环(3)
- 类和对象(4)
- 数组(5)
- 字符串(6)
- 类的继承与多态(7)
- 子类引用与父类引用之间的转换
- Java 常用核心类(8)
- 内部类、枚举、注释(9)
- 接口与Lambda 表达式(10)(详细)(解答题)
- 泛型与集合(11)(详细)(程序填空)
- 异常处理(12)(解答题)
- 输入输出(13)(程序填空题)
- 并发编程基础(17)(判断选择)
java 语言概述(1)
- java 语言是面向对象的,支持面向对象的全部特征
- java 语言的优势是
平台的独立性
,只要系统安装了 java 虚拟机(JVM),程序无需修改,就可以运行,实现“一次编写,多处运行”- 编写和运行java 程序需要使用JDK
- 开发java 程序的步骤:
(1)使用编辑器编写源文件,保存为 .java 文件
(2)使用java 编译器 javac.exe 将源程序编译成 .class 字节码文件javac xxxx.java
(3)最后用java 解释器 java.exe 执行程序java xxxx
注意没有.class
- java 程序是类的集合,关键字class 引入类,
每一个可以执行的 java 程序 必须有一个 main()方法
,该方法具有严格的形式,不得修改,,且是 程序开始执行的入口点
- 注释分为行注释 // 和块注释 /* / 文档注释 /* */
- 目前最流行的集成开发环境(IDE) 为 NetBeans 和 Eclipse
JVM
:java 虚拟机 (运行字节码的程序),JVM 运行在操作系统上,与操作系统无关
JRE
: java 运行环境(包含 JVM 和 java 类库)JDK
:java 开发工具集 (包含JRE)public static 修饰 main() ,必须是公共的,确保main 能被其他类调用,static 因为开始的时候并没有创造实例,static 确保main 方法不用创造实例也能调用
Java 中不一定有public 类,但是最多也只能在一个程序中有一个public 类
java语言基础(2)
- java 的关键字不能被定义为标识符
- java 的标识符必须以
字母、下划线、美元符号
开头,后面可以为数字、字母、下划线、美元符号,
长度没有限制- java 的数据类型可以分为基本数据类型(byte ,short,int,long,float,double,char,boolean)以及引用数据类型(数组,接口,类,枚举,注释)
- 变量存储数据值。
使用变量前必须声明
,有基本数据类型和引用数据类型- 使用java.util.Scanner 类可以读取
除char 以外
的各种数据类型(包括字符串)- 自动类型转换(加宽转换):具有
少位数的数据类型转化多位数
的数据类型。强制数据类型转化相反- 表达式计算的最后的结果一般是最多位数的
- swith(i) i 可以为 整形(byte,short,int ,char)、枚举(enum)、字符串(String) ,但是不能为
long 、float、double 类型 (要求是离散的)
- boolean 类型中
false 与true 不对应 0 和非0
- 科学计数法 321e-3 ,e的前面必须为整数
- float 类型的值 必须加上
f 或者 F
- char 类型可以直接转化为int 类型,但是反过来就要强制转化
int a = 1 + ' b' ; //合法
- 注意 字符串 是
""
,字符是‘’
,容易误导- 声明变量的时候,如果没有赋初值,那么就不能直接输出,
系统不会初始化,这与python 不同
- 大数 java.math.BigInteger 准确计算误差 java.math.Decimal
- java 中有复合赋值符 -= +=
- 负数的补码就是正数的补码的按位取反,再加一
选择与循环(3)
- java 选择语句:单分支 if 语句、双分支 if-else 语句、嵌套if 语句、多分支 if -else 语句、switch 语句、条件表达式。
- 条件运算符(condition ? expression 1 : expression 2)是java
唯一的三元运算符
,可以用 if- else 来代替- switch 语句根据
char,byte,short ,int ,String 和 enum
类型进行选择控制。- 在switch语句中,break 语句可选,通常在每一个语句的分支的结尾,来终止switch 语句的剩余的语句,
如果没有break ,那么就会执行剩下的全部语句
。default 语句是可以选择的,表示当表达式的值与全部的case 不匹配时,就执行default 语句。- 在循环体中,break 立即终止包含该 break 语句的最内层循环,continue 只是终止当前的循环
- do-while 语句和while 语句的区别就是 do-while 语句 至少会执行一次
- 在for循环中,初始化部分要
定义并赋初值,该初始化过程不能在循环外部
.- for 循环中的一部分可以为空也可以全部为空,但是分号不能省略
- 增强型的for循环:for(Type var : collection) 主要用于数组和集合元素迭代
注意for 循环 的第三个部分是迭代完之后才执行的,上面的例题选B
类和对象(4)
- 面向对象的基本概念:对象、类、消息。对象:自身的状态特征以及可以对这些状态施加的操作的实体。类:具有相似特征和行为的对象的集合 消息: 对象与对象之间的联系
- 三个特性:封装性、继承性、多态性。
(1)封装性
:把对象的状态和行为结合成一个独立的系统单位,并尽可能隐藏对象的内部细节;封装使得对象形成两个部分:接口部分和实现部分;封装提供两种保护:保护对象和保护客户端
(2)继承性
:一个对象获得另一个对象的属性的过程。支持层次结构,体现类之间(IS-A) 关系
(3)多态性
:在一个程序中相同的名字表示不同的含义的情况。多态分为静态多态:方法的重载:同一个类中定义多个名称相同的方法;动态多态:方法覆盖:子类定义与父类方法同名的方法- Employee employee; //创建一个引用变量(相当于指针) employee = new Employee(); //创建一个变量,实例化
构造方法
:名字与类名相同,没有返回值,创建对象时用new 调用。一旦创建带参构造方法,系统就不会提供默认构造方法。
错误点,没有默认的无参构造方法
- 当用this 在一个构造方法中调用另一个构造方法时,this 语句必须是第一句。
- this 关键字引用:解决成员变量与方法参数、局部变量重名,调用另一个构造方法。
- 静态变量与实例变量:用static 修饰就是静态变量,否则就是实例变量。
静态变量共用一个内存空间,该类的对象共享同名的静态变量,修改同步,实例变量每一个都有自己的空间
- 静态方法与实例方法:用static 修饰就是静态方法,否则就是实例方法
`静态方法只能访问静态变量和调用静态方法,实例方法可以对实例变量和静态变量进行访问,对实例方法和静态方法进行调用通常使用类名来调用静态方法与静态变量
类的成员变量,系统会自动赋初值,但是方法和代码块中的变量,系统不会赋初值,使用前必须自己赋初值,数值都是0,boolean 是 false
初始化块在调用构造方法之前被调用
初始化顺序
:使用默认值(只有实例变量)或者自行的初始值->初始化块-> 执行构造方法- 包是相关类的和接口的集合
一个源程序只能有一个package 语句,该语句必须是为源程序中的第一个非注释语句
- 成员变量默认值: byte 、 short 、 int 都是0,boolean 是
false
- 参数传递分为:值传递:调用方法时将实际参数值的一个副本传递给方法中得形式参数,方法调用之后实际参数的值不会改变;引用类型作为参数时,实际传递的是引用值,在方法内部有可能改变原来的值
创建包:使用带-d 的编译命令 javac -d D:\study Employee.java
-d 后面是上一级目录, 编译后的 Employee.class 会放在 创建的包的目录中
- 可以使用import static 导入类或者接口中的静态变量以及静态方法,这样就不用在使用的时候加上类名
- 使用java.lang 包以及当前目录中的类不用导入
- 参数匹配感觉是按顺序匹配
- “* ”表示导入该包中的全部的类,否则就具体指示
每一个源程序通常被称为一个编译单元,每一个编译单元可以包含一个包多个import 以及类接口和枚举,
一个编译单元最多只能有一个public 类(或者接口、枚举),并且文件名与该类的类名相同
- 类是引用数据类型,用来声明对象引用变量。
对象引用变量只是对该对象的引用
,对象实际存储在内存堆中。- 对象是类的实例,可以使用
new
运算符来创建,使用点运算符.来通过对象的引用变量来访问该对象的成员- 方法头指定方法的修饰符,返回值类型,参数列表。方法可以返回一个值,如果不返回,则用void ,有返回值就用 return ,无返回值也可以用return 。
参数列表包含参数的类型,个数和次序
。方法名和参数列表构成方法标签
。参数可以选。- 传递给方法的实际参数应该与方法签名中的形式参数具有相同的个数,类型和顺序。
方法重载
:两个方法有相同的方法名,但是参数列表不同即可。(没有规定返回值)- 在方法中声明的变量为局部变量,作用域从声明的地方开始,到结束的作用块。
局部变量在使用前必须声明和初始化。
构造方法与普通方法都可以重载。重载是方法名称相同、参数个数或者参数类型不同的方法。不能通过返回值来判断方法重载
A,C,D
A,C
默认的构造方法会与类的修饰符相同,类的构造方法的修饰符不能低于类的修饰符
注意构造方法是没有返回值的
- this 关键字 用来引用当前的对象,可以在普通的方法中引用
实例变量
,也可以在构造方法中调用同一个类中的另一个构造方法
- 实例变量和方法属于类的一个实例。静态static 变量是被一个类中全部的实例所共享的,可以在不使用实例的情况下调用静态方法
第一个代码错误在用类名来访问成员变量,这是不对的,第二个静态方法不能访问实例变量,都可以将data 改成 static int data = 10;
为什么不能用类名来访问实例变量?类的每一个对象的实例方法的内存不一样,只用静态变量才可以通过类名来访问
- 类的每一个实例都可以访问这个类的静态变量与静态方法。一般情况下,用
"类名.变量" 和“类名.方法”来访问静态变量与静态方法
- 当一个变量不再被使用时,系统自动调用后台的垃圾回收器销毁对象,也可以调用
System.gc() 方法或 Runtime 实例 的gc() 方法强制执行垃圾回收(不能保证立即回收)
- 包用import 来回收
&& 会短路,当左边的值为false 时,右边不执行,& 不会短路,两边同时算出来,再计算
- A
- 包的创建
如果在一个带返回值的方法中,不写return语句会发生编译错误;在返回值类型为void的方法中可以不写return语句
在一个类中不可以定义两个名称和参数列表相同,但返回值不同或修饰符不同的方法
数组(5)
- 使用elemType[ ] arrayName 和 elemType arrayName[ ] 声明一个数组类型都可以,都合法
声明数组变量并不给数组分配任何空间,数组变量是引用类型的变量。数组变量包含的是对数组的引用
- 只有创建数组后才能给数组赋值,使用new 来创建数组,语法为 : new elemType[arraySize]
- 创建数组之后,
大小不能改变
,使用arrayName.length
来得到数组的长度,下标是从0开始的,防止发生数组越界- 创建一个数组,如果其中的元素的基本类型是数值型,那么赋默认值
0
.字符类型的数组是’\u0000’,布尔类型是false
,引用类型是 null
- 数组初始化器:数组的声明、创建、初始化合并:
elemType [ ] arrayName = { value1,value 2····valuen};数组的声明不能指定元素个数,数组是引用类型,是对象,继承Object 所有方法,只有用new 来创建才可以指定大小
- 语句 int a[10]; 声明了一个有 10 个元素的数组,每个元素都是 int 类型。
错误,声明不能指定大小
A ,C ,D (只有B 错误,声明时不能指定大小)
byte [ ] a, b[ ] ; 其中a 为一维, b 为二维
- 数组作为参数传递的时候,是数组的引用传递,被调用的方法可以改变原来数组的元素
- 可以使用增强型for 来访问数组每一个元素
- 定义可变参数的方法,可变参数必须是方法的最后一个参数。可以将数组作为参数传递给可变参数。
可变参数就是能够接受任意数量的该类型的数据
- 可以使用
java.util.Arrays
类中的静态方法对数组进行查找,排序,复制,比较等- Arrays 的
sort
排序是稳定的,对于基本数据类型是升序排序,不能对布尔类型排序- 使用
Arrays.binary
进行查找,数组必须已经排序,对整数时,当查找成功,则返回下标,否则返回(-应该出现的下标-1)- 二维数组:
elemType [ ] [ ] arrayName = new elemType[rowsize] [columnsize];- 初始化器:elemType [ ] [ ] arrayName = { { } ,{ } ,{ } };
数组名字是引用,直接令一个数组名等于另一个,只是让两个数组名指向同一个数组
二维数组可以只指定一维,另一维大小不等
字符串(6)
- 字符串是一个字符序列,字符串用
“”
,字符用‘’
- 字符串是对象
- 字符串的长度是
不可以变化
的,replace(),substring(),toLowerCase() 等方法都是操作之后创建一个新的字符串,原来的字符串不变
。- replace( char oldchar,char new char) //字符替换
- substring( int start ,int end) //返回一个
[ start , end )
的子字符串- charAt(int index) //返回指定下标的字符
- indexOf(int ch) //查找字符ch 第一次出现的位置,找不到则返回-1
- 使用equals() 方法比较两个字符串是否等,使用compareTo() 比较两个字符串的大小,使用
split ()
来拆分字符串,使用``matches() 方法实现字符串与正则表达式匹配,join()
方法用来按照指定分隔符连接字符串- Java 的main方法带一个
字符串数组参数
,称为命令行参数,当调用时,可以为他传递若干参数,并存在args 参数中StringBuilder 类以及 StringBuffer (线程安全)类 创建的是可变的字符串
- 字符串的比较不能用
==和!=,这样比较的只是字符串的引用是否相同,并不是比较两个字符串的内容是否相同,只能用compareTo 进行比较
第一个是true 第二个是false
- 注意转义字符的影响
选B ,实际上 s 的字符就是 "Hello,World!"
结果还是ABCDE
区分:
类的继承与多态(7)
- 可以使用extends 通过现有的类定义新类,新类被称为子类或者派生类,现类被称为父类、超类、基类
子类继承父类中非private 成员变量和成员方法,子类可以覆盖父类中的实例方法,子类不能继承父类的构造方法
*静态方法属于类,不能被继承,父类的静态方法若在子类中重新定义的话,那么父类的静态方法被隐藏。要覆盖一个方法,必须要有相同的标签名(方法名字与参数列表)相同,私有方法不能被覆盖,静态方法不能被覆盖
- super 可以用来访问父类的构造方法,调用必须是构造方法第一句
- java 的每一个类都是Object 类的子类
- 抽象类abstract ,抽象方法只有方法的申明,没有方法的实现。抽象类不能被实例化,只能被继承,在非抽象类中,抽象方法必须实现
子类的实例也是父类的实例(用instanceof 判断时要注意),可以将子类的实例转化为父类的实例
,但是反过来就要强制转化- 可以用instanceof 运算符测试某个对象是否为某种类型的实例
多态:若一个方法的参数是父类,可以向该方法参数传递任何子类
- 动态绑定:变量的实际类型决定在调用实例时哪一个方法被实现
- 类的继承不支持多重继承
- 非private 方法才可以被覆盖
覆盖的方法要求,子类中的方法的名字,参数列表,返回类型与父类相同
方法的重载是在一个类中定义方法名字相同,但是参数列表不同的方法
- 要是在子类中定义了与父类名字相同但是参数列表不同的方法,那么这是属于方法的重载(到时调用的时候会根据参数,进行自动的一个选择),但是要是子类覆盖了父类的方法,在一般情况下,不会调用父类被覆盖的方法(可以用super.方法名来调用)
- super 关键字,可以调用父类的变量(super.变量名),构造器(super(参数列表)),方法(super.方法名),一般父类中的变量没有被子类的变量覆盖的时候,在子类中是可以直接访问使用的,(方法也一样),变量与方法被覆盖之后要用super 才能调用父类中的变量以及方法
子类是不能继承父类的构造方法
的,在子类的构造方法,若没有使用super 来调用父类的构造方法,系统会默认调用,其中this 可以用来调用本类的构造方法,不论,super 还是this 只能出现在构造方法的第一句话
,并且只能有一句(this 与super 一共只能出现一次
)- 封装性通过包以及类以及类的成员访问权限实行封装性
- final 来修饰类、方法、变量,
final 修饰的类为最终类,不允许被继承
,final 修饰的方法不能被覆盖,但是可以被继承和重载,final 修饰的变量为常值变量,一旦赋值则不能被修改
- 抽象类是不能实例化的,但是可以通过实例化该抽象方法的子类来实现其中的抽象方法
- 抽象方法只有方法的申明,没有方法的实现,
抽象方法必须在抽象类
中- 由于final 类不能被继承,可是抽象类必须被继承,所以
final 与abstract 不能在定义类时同时实现
- 子类对象可以自动转换为父类的对象,但是父类对象要强制转换才能转换为子类对象(
要求父类对象是运用子类的构造方法生成的
)- 区别方法的多态与重载与重写(覆盖),静态多态通过方法的重载实现,动态多态通过方法的覆盖实现
D ,注意覆盖。当父类的方法被子类的方法覆盖(或称为重写)时,如果使用父类引用变量调用该方法,实际上会调用子类中覆盖的方法
如果你使用父类引用变量引用子类对象,那么只能访问父类中定义的成员。即使实际上引用的是子类对象,只能调用父类中存在的方法和属性,而不能调用子类中特有的方法和属性。
运行时会根据实际对象的类型调用相应的方法。即使引用变量是父类类型,实际上会调用子类的方法,这被称为运行时多态性
抽象类不能实例化
输出 Inside BB's callme 和 Inside BB's metoo
抽象方法被子类实现,当然还是用子类的哇,至于被子类覆盖的方法,就只能用子类的啦
A 是正确的,对于B ,stuff 的实际类型是 Employee ,强制转化成 Manager 类型会报错
B, B 既不构成重载也不构成重写,要是重写的话,返回类型应该与父类相同,要是想构成重载的话,又与A 冲突.由于A 合法,但是 B 与 A 并不与 重载(在重载的意义上 ,为重复)
子类引用与父类引用之间的转换
在Java中,子类对象和父类对象之间的转换涉及到两种主要概念:向上转型(Upcasting)和向下转型(Downcasting)。这两者都是用来处理继承关系中的不同类型对象之间的转换问题。
- 向上转型(Upcasting):
向上转型是指将一个子类的对象赋值给一个父类的引用变量。这是自动的,不需要显式的类型转换。
// 父类
class Animal {
// ...
}
// 子类
class Dog extends Animal {
// ...
}
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
// 向上转型
Animal animal = myDog;
}
}
在这个例子中,myDog
是 Dog
类的一个实例,但它也可以被赋值给 Animal
类型的引用变量 animal
,这是因为 Dog
是 Animal
的子类。这种转型是隐式的,不需要额外的操作。
- 向下转型(Downcasting):
向下转型是指将一个父类的引用变量转换为其子类的引用变量。这需要显式的类型转换,并且在运行时可能会引发ClassCastException
异常,因此在进行向下转型之前最好使用instanceof
运算符进行类型检查。
// 父类
class Animal {
// ...
}
// 子类
class Dog extends Animal {
// ...
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();//注意这里不是Animal animal = new Animal();
// 向下转型
if (animal instanceof Dog) {
Dog myDog = (Dog) animal;
// 现在可以使用 myDog 对象
} else {
// 处理无法转型的情况
}
}
}
在这个例子中,animal
是 Animal
类型的引用,但在运行时,我们通过 instanceof
检查确保它是 Dog
类型的实例,然后进行向下转型。这样做可以避免 ClassCastException
异常。
总的来说,向上转型是安全的,因为子类对象可以被视为父类对象,而向下转型需要谨慎处理,因为它可能会引发异常。在进行向下转型时,最好使用 instanceof
运算符进行类型检查,确保对象的类型是正确的。
Java 常用核心类(8)
- Object 类是java 语言中全部类的根类,定义类的时候若没有用extends 指明继承哪一个类,则编译器会自动加上 extends Object
- Object 类定义了 toString(),equals() 等方法,这些方法可以被子类继承,子类也可以覆盖这些方法
- 如果不覆盖Object 类的toString 方法,那么调用该方法会返回
类名@ 十六进制数
- 如果不覆盖Object 类的equals 方法,那么就是比较两个对象的
引用
,equals 的参数是 Object 类对象
- 一个对象想要被克隆,就要实现
Cloneable 接口
,clone 只是简单的拷贝,如果是基本数据类型还好(不变),否则有可能原来的对象与克隆对象共享
- java 在Math 中 提供数学方法,用于执行数学函数
- 使用 Math 类的 random () 方法可以随机生成 0.0-1.0 (不包含1.0)的一个double 型 的数,在此基础上可以编写简单的表达式,生成任意范围的随机数
- 每一种基本的数据类型都会有一个对应的包装类型,包装类型提供常用的方法对包装的对象进行操作。基本的数据类型与包装类型可以自动转化,称为自动装箱与自动拆箱。
每一种包装类型的对象中所包装的值是不可变的,要改变,只能重新生成
- 使用BigInteger 类 和BigDecimal 类可以对大整数以及大浮点数进行相关计算
- 使用LocalData 类和LocalTime 类可以分别对本地日期、时间进行操作,二LocalDateTime 则是两者的结合
- Math 类是
final
修饰的,其构造方法是private
的,所以不能被实例化- 自动装箱:基本数据类型的数据自动转化为包装类的实例
- 自动拆箱:包装类的实例自动转化为基本数据类型
- 对于public Boolean (String s):用一个字符串来创建Boolean 对象,如果字符串s 不为null,且s 为’true’,那么就创建一个true 值,否则就为 false
内部类、枚举、注释(9)
成员内部类声明在类体中,是外层类的成员(所以叫做成员内部类),成员内部类也独自生成一个类文件
。因此可以使用访问修饰符(如 private 或者 public ),也可以使用abstract 或者 final 来修饰。- 使用内部类优点:
对于只在一处使用的类进行分组,提高封装性
- 在包含类的内部可以直接实例化成员内部类,如果在包含内部类的外部实例化成员内部类,必须有一个外部类实例。
OuterClass.InnerClass inner = new OuterClass().new InnerClass();
- 在成员内部类中 this 表示内部类实例的引用
局部内部类定义在外层类的方法中,要使用局部内部类,必须在定义它的方法中实例化,并且在局部内部类声明之后。
局部内部类就像局部变量一样,作用域仅限于该方法,所以不能有任何修饰符修饰(不能用private,protected,public,static,可以用final 和abstract),它的块之外不能使用。所以在方法体的外部不能创建该类的对象,但是局部内部类可以访问外层类的实例变量以及方法的final 的局部的变量
- 一个局部内部类不能使用其方法的声明的变量(包括参数),除非这些变量是 final 修饰的
- 匿名内部类没有名称,它要么实现一个接口,要么是一个类的子类
匿名内部类就是在定义一个类的时候创建一个实例,匿名内部类继承一个接口不使用extends 和 implements 关键字
匿名内部类没有名称,所以匿名内部类没有构造方法,又不知道类名,所以只能用new 来创建实例,并且没有修饰符
匿名内部类可以实现一个接口或者拓展一个类,但是不能同时实现接口和拓展类,也不能实现多个接口。
静态内部类就是在类体中,定义static 修饰的类
静态内部类和成员内部类的区别:静态内部类可以定义静态成员,成员内部类不行;静态内部类只能访问静态变量,成员内部类访问静态变量和实例变量;创建静态内部类的实例不需要先创建一个外部类实例,但是创建一个成员内部类就要先创建一个外部类实例
//创建静态内部类对象Outer.Inner snc = new Outer.Inner();//创建成员内部类对象 Outer.Inner iner = new Outer().new Inner();//接口Runnable ,其中有一个 void run() 方法Runnable task = new Runnable{ public void run(){ //重写 } }
- 静态内部类使用static 修饰定义在类体中的类,它实际是一种顶层镶嵌类。创建内部类对象不需要外部类的实例,但需要同时
指定外层类以及内部类名OuterClass.InnerClass inner = new OuterClass.InnerClass()。
- 嵌套类实例不能访问外部类的this 引用
- 枚举类型是一个枚举值的列表,每个值是一个
标识符
,使用enum 定义枚举。它也被作为一种特殊的类型对待。枚举值可以出现在switch 结构中
- 可以给java 包、类型(类、接口、枚举)、构造方法、方法、成员变量、参数7局部变量 进行标注
- 3种注解类型:在 java.lang 包中,
@Override ,@Deprecated ,@SuppressWarnings
- 注解类型的定义使用 ·
interface
关键字,前面加上@
符号
匿名内部类的隐式的修饰符是final 和 abstract
接口与Lambda 表达式(10)(详细)(解答题)
interface 在java 中的作用:interface 实现多继承,java 语言通过接口使得处于不同层次的、甚至互不相关的类具有相同的行为
- 接口可以实现多继承
- 接口用interface 关键字来定义,[public ] interface InterfaceName[extends SuperInterface] ,其中public 表示该接口可以被所有的类继承,否则就只能被同一个包中的类继承
- 接口可以被作为引用变量的数据类型或者类型转换的结果,与抽象类一样,
不能被new 进行实例化
- 接口是常量、抽象方法、默认方法、静态方法的集合,
实现接口,就是实现接口中定义的抽象方法
,类实现接口用implements 来实现,若实现多个接口,就用逗号进行分隔。由于接口中的抽象方法只有定义没有实现,要是继承接口的类不是abstract 类型,那么该类就要实现接口中的全部的抽象方法(非abstract 类型的类不能存在abstract 方法
)- 类在实现接口的抽象方法的时候,必须使用与抽象方法完全相同的方法标签,否则只是对方法的重载而不是实现
- 在接口中,
定义
抽象方法时就算不加上修饰符,编译时也会自动加上public
abstract
,其中接口中的方法都是public 类的,那么在类中实现方法时,就必须显示使用public
来修饰(否则就缩小了访问控制范围)- 接口可以多继承,除了本身定义的常量与方法,子接口将继承超接口的全部的常量与方法
不能继承静态方法
- 一个类可以同时实现多个接口,但是只能同时继承一个类,一个接口能够继承多个接口
- 一个类实现多个接口,就要实现每一个接口中的抽象方法,接口中的常量以及默认方法都被实现类继承,但是
接口中的静态方法不被继承
(可以直接用对象.默认方法名字来调用接口中的默认方法)- 接口类型的使用:
接口是一种引用类型,任何实现该接口的实例都可以被储存在该接口类型的变量中
,当通过接口对象调用某一个方法时,java 运行时系统确定该调用哪个类中的方法.
例如 AA aa = new DD(); //其中 DD 类实现了 AA 接口,aa 为 AA 接口对象 ,存储了实现AA 接口的变量,但是通过aa 来调用只能调用自身的方法(或者自身继承的方法)- 定义在接口中的变量,都会自动加上public 、final 、static 属性,因此它们都是常量,常量的定义可以省略修饰符
int abc = 100;
public int abc = 100;
public final static int abc = 100;//这三行等价- 接口中的常量一般都用全部大写,由于接口的多继承,可能会造成常量的冲突,如果常量名不冲突,则子接口可以继承父接口中的常量,如果冲突,则子接口不能继承常量,但是可以在子接口定义一个新的同名常量,
常量应该通过接口名来调用
- 静态方法:在一个类中可以定义静态方法,该静态方法被该类的所有的实例共享
- 在java 早期的版本,接口中只能有抽象方法,在Java SE 8 开始增加 静态方法与默认方法
- 在接口中定义静态方法:与接口有关的静态方法可以在接口中定义,用static 关键字,默认的访问修饰符为public ,接口的静态方法的使用用
"接口名.方法名()"
的形式访问,接口的静态方法不能被子接口所继承,也不能被实现类继承
(那就只有"接口名.方法名()"这种使用方法)- 默认方法:可以给接口中的任何方法都提供一个默认实现,这称为默认方法(default mothod),默认方法的定义用default 关键字,默认方法的使用要通过
引用变量调用
。默认方法可以被子接口以及实现类继承
,但是子接口中要是定义了相同的默认方法,父接口中的默认方法被隐藏
。- 如果实现类继承多个接口,造成默认方法的冲突:可以在实现类中定义冲突默认方法的一个新的实现(另起炉灶),或者委托其中一个接口的默认方法来实现(二选一)
接口小结:常量与静态方法可以说是一伙的,它们都通过接口名来调用,因为它们都是 public static 修饰,但是它们也有不同,就是常量是可以被继承的,但是静态方法是不能被继承的,对于默认方法,其实就是相当于类中的一般方法,而对于抽象方法,由于在接口中定义的抽象方法都是没有方法体的,都要靠实现类来实现,有一个重要点要注意,那就是实现抽象方法时的方法必须是public 类型的
- java 类库中定义了许多接口,有些接口没有定义任何方法,这些接口被称为
标识接口
- Comparable 接口:要比较两个同类对象的大小可以使用Comparable 中的compareTo ()方法,Comparable接口的定义如下:
public interface Comparable{
int compareTo(T other );
}- Comparable 接口是泛型接口,实现该接口的时候,将泛型类型T 替换成一种具体的类型。如果希望一个类的对象能够比较大小,
类必须实现Comparable <T> 接口中 的 compareTo() 方法
。该方法实现当前对象与参数对象进行比较的功能,返回一个整数值。当调用的对象小于、等于、大于参数对象时,该方法分别会返回负整数、0、正整数
。按照该方法比较出的对象顺序称为自然顺序
。- Comparator 接口与Comparable 接口相似,但是Comparator 接口的抽象方法compare 有两个 T 类型的参数,而Comparable 有一个T 类型的参数
- 匿名内部类:匿名内部类是一种没有显式定义类名称的局部内部类的特殊形式。它通常用于创建一个
只需使用一次的简单类或接口的实例
。匿名内部类的语法形式相对简洁,通常在创建对象或实现接口时使用- Lambda 表达式:lambda表达式是可以传递给方法的一段代码,可以是一个语句,也可以是一个代码块。对于函数体只有一行代码的,可以去除大括号{ }以及return 关键字,由于java 编译器会自动推导出参数的类型,所以还可以省略参数类型的指定。
- 函数式接口:是指
仅包含一个抽象方法的接口
,又被称为单抽象方法接口
。每一个lambda 表达式都对应一个函数式接口类型。可以将Lambda 表达式看作函数式接口的类的一个实例。默认方法不是抽象方法,所以在函数式接口中可以定义默认方法。可以在定义函数式接口时加上@FunctionInterface 语句,这样,当你定义多于一个抽象方法,编译会报错。- 函数式接口之所以十分重要:可以使用Lambda 表达式创建一个与匿名内部类等价的对象
- Lambda 表达式的语法:使用Lambda 表达式将代码传递给方法,有两种方式指定Lambda 表达式:
(1)(参数1,参数2,···) -> 表达式;
(2)(参数1,参数2,···) -> {/代码块/};Lambda 表达式是用于实现 接口中的抽象方法的,Lambda 表达式的内容就是对抽象方法的创建,对于接口的默认方法,可以通过接口对象来引用
。
Lambda 表达式以参数列表开头(就算没有参数,也要有一个括号)
,参数用括号定界,然后是一个箭头 -> ,最后是表达式主体。括号中的参数传递给表达式。如果表达式只有一个语句,那么语句块的大括号可以省略。如果参数的类型是可以推导的,则可以省略类型,如果只有一个参数,且该参数的类型可以推导,那么参数括号可以省略。- 预定义的函数式接口:
(1)Function<T,R> 表示带一个参数且返回一个结果的函数,结果类型可以与参数类型不同,需要覆盖 R apply(T)方法import java.util.function.*;public class FunctionDemo { public static void main(String[]args) { Function<Integer,Double> milesTokms = (input) -> 1.6*input; int miles = 3; double kms = milesTokms.apply(miles); System.out.printf("%d miles is %3.2f kilometers \n", miles,kms); } }
(2)BiFunction<T,U,R> 表示带两个参数且返回一个结果的函数,结果类型可以与参数类型不同,需要覆盖 R apply (T,U) 方法
import java.util.function.*;public class BiFunctionDemo { public static void main(String[] args) { BiFunction<Float,Float,Float> area = (width,height)-> width*height; float width = 2.5F; float height = 2.5F; float realarea = area.apply(width, height); System.out.println("the area is " + realarea); } }
(3)UnaryOperator 表示一种有一个操作数的运算且返回一个结果,结果类型与操作类型一样。UnaryOperator 是Function 的子接口(用法与Function 类似)
(4)BinaryOperator 表示一种有两个操作数的运算,结果类型必须与操作类型一样,是BiFunction<T,U,R> 的子接口(用法与BiFunction 类似)
(5)Predicate 带一个参数的函数,它基于参数值返回 true 或者 false 值,需要覆盖 test 方法public interface Predicate<T>{ boolean test(T t);}//判断字符串是否全部为数字import java.util.function.*;public class PredicateDemo { public static void main(String[] args) { Predicate<String> numbersonly = (input)->{ for(int i= 0;i<input.length();i++) { char c = input.charAt(i); if("0123456789".indexOf(c)==-1) return false; } return true; }; System.out.println(numbersonly.test("123456")); System.out.println(numbersonly.test("100o1")); } }
(6)Supplier 表示结果的提供者 ,需要覆盖 get 方法
//随机生成随机数import java.util.Random;import java.util.function.*;public class SupplierDemo { public static void main(String[]args) { Supplier<Integer> onerandom = ()->{ Random random = new Random(); return random.nextInt(10); }; for(int i = 1;i<=5;i++) System.out.println(onerandom.get()); } }
(7)Comsumer 带一个参数且不返回结果的操作,需要覆盖 accept 方法
import java.util.function.*;//带一个字符串参数,并居中对齐输出public class ConsumerDemo { public static void main(String[]args) { Function<Integer,String> space = (count)->{ StringBuilder sb = new StringBuilder(count); for(int i =1;i<=count;i++) sb.append(" "); return sb.toString(); }; int lineLength = 60; Consumer<String> printCenter = (input)->{ int length = input.length(); String spaces = space.apply((lineLength-length)/2); System.out.println(spaces + input); }; printCenter.accept("A Lambda expression a day"); printCenter.accept("makes you"); printCenter.accept("look smarter"); } }
- 方法引用:java 中许多方法带一个函数式接口对象作为参数,如果传递的表达式有实现的方法,可以使用一种特殊的语法,方法引用来代替Lambda 表达式。
Arrays.sort(names,(x,y)->compareToIgnoreCase(y));//names 为字符串数组//等价于方法引用Arrays.sort(names,String::compareToIgnoreCase);
- 方法引用就是类名或者对象引用,后面跟着::,然后就是方法名。
- 方法引用有一下三种使用方式:
(1)对象::实例方法名
(2)类名::静态方法名
(3)类名::实例方法名- 构造方法引用:构造方法引用与方法引用类似。不同的是构造方法引用需要使用 new 运算符,例如
Employee ::new
泛型与集合(11)(详细)(程序填空)
- 泛型:类和接口的一种拓展机制,主要是实现
参数化类型(泛型又被叫做参数化类型)
机制。简单来说,泛型就是带一个或者多个类型参数的类或者接口
。- 泛型的T
不能使用基本的数据类型
,如 int ,float,double,boolean ,long,这些基本类型只能由相对应的包装类来代替,分别是Integer,Float,Double,Boolean,Long。T 为类型变量,可以为任何的类或者接口。- 泛型的使用与方法的调用类似:方法的调用需要传递参数;使用泛型需要传递一个类型参数,即用某个具体的类型来代替T.
Node<Integer> node = new Node<Integer>(10);//调用构造方法
- 注意有
两个地方
要补充 的具体值,如果输入不是预先设定的 类型参数的数据,将会发生编译错误
- 由于编译器能够从上下文中推断泛型参数的类型,所以在创建泛型类型时,可以直接使用菱形<>语法,即仅用一对尖括号.则上面的代码可以写成:
Node<Integer> node = new Node<>();
- 类型参数名字使用大写字母表示。常见的类型参数名有 E(表示元素),K(表示键),N(表示数字),T(表示类型),V(表示值)等,注意
泛型可能会有多个类型参数,但是在类或者接口的声明中,每个参数名必须是唯一的
- 当你的类继承接口的时候,你的相对应的类型参数也要保持一致
- 泛型方法:
带类型参数的方法
。类的成员方法以及构造方法都可以定义为泛型方法。泛型方法的定义与泛型类型相似,但是参数类型的作用域仅限于声明的方法和构造方法中。泛型的方法可以定义为静态的和非静态的
。- 通配符(?) 的使用:泛型类型本身是一个java 类型,将不同的类型参数传递给泛型类型会产生不同的类型。
List<Object> list1 = new ArrayList<Object>();List<String> list2 = new ArrayList<String>();//List 是泛型接口,ArrayList 是泛型类
虽然String 是Object 的子类,但是List<Object> 与List<String>没有关系,不存在子类型的关系
- 但是你使用通配符的话就可以匹配任何类型,
List<?> list 可以匹配 List <Object> list 与List <String> list 类型
- 有界类型参数:需要限制传递类型参数的类型种类。
(1) 有界类型参数分为上界与下界,上界用extends 指定,下界用super 来指定
(2)<? extends 上界类型> 表示匹配上界类型或者其子类型
(3)增加类型用&
<? extends 上界类型 & 其他类型>
(4)<? super 下界类型> 表示匹配包含下界类型以及其父类型的类型//例如Integer 与 Double 是 Number 的子类List<? extends Number> numberlist 能够匹配List<Integer> 与 List<Double>
- 类型擦除:在实例化泛型类型的时候,编译器会使用一种叫类型擦除的技术转化这些类型。在编译时,会
清除类和方法中所有与类型参数有关的信息
。类型擦除可以让使用泛型的java 程序与不使用泛型类型的Java程序兼容
Node 被转换为 Node (泛型类型 -> 源类型)源类型
是不对类型参数的泛型类或者接口,这意味着在运行时找不到泛型类使用什么类型就不能用isinstanceOf 来判断
- 集合框架:
(1)背景:需要使用一组类型相同的对象,数组可以完成,但是数组一经定义便不能改变大小
(2)集合框架:定义了一组接口和类,使得处理对象组更加容易
(3) 集合是指存放一组对象的一个对象。
(4)集合框架由两类类型构成:Collection 与Map。Collection 对象来存放一组对象,Map 对象来存放一组“关键字/值”的对象。Collection 与 Map 都是最基本的接口,它们还有子接口- Collection 接口是所有
集合类型的根接口
,继承 Iterable 接口。它又三个子接口,Set 接口,List 接口,Queue 接口
。- Collection 接口定义了集合操作的常用方法:
基本操作、批量操作、数组操作、流操作
。
(1)基本操作:添加元素,删除指定元素,返回元素的个数,返回集合的迭代器对象boolean add(E e)boolean remove(Object o)int size()Iterator iterator()//返回包含全部元素的迭代器对象
(2)批量操作:
boolean addAll(Collection <? extends E> c) //将集合c 中全部的元素添加到当前的集合中boolean removeAll(Collection<?>c) //从当前集合中删除c 中的全部的元素default boolean removeIf(Predicate <? super E> filter) //从当前集合中删除满足谓词的全部元素boolean containsAll(Collection<?>c)//当前集合是否包含c 中的全部元素 boolean retainAll(Collection<?>c) //当前集合中只保留指定集合c 中的元素,其他元素删除 void clear() //将集合清空
*(3)数组操作:
Object[] toArray() //返回包含集合中全部元素的对象数组
T[ ] toArray(T[ ] a) //返回包含集合中全部元素的数组,返回数组的元素的类型是指定的数组类型//设 c 为一个Collection 对象,下面将 c 转化为一个新的Object 数组,数组的长度与c 的元素的个数相同Object [ ] a = c.toArray();//设 c 中只包含String 对象,下面将c 转化为String 数组,长度与 c 中的元素的个数相同String [ ] a = c.toArray(new String[0]); //参数new String[0]是创建一个长度为0 的数组,但是方法会动态返回一个合适的数组
- List 接口以及实现类:是Collection 接口的子接口,实现一种
线性表
的数据结构。存放在List 中的元素都有一个下标(从0开始)
,可以通过下标来访问List 中的元素。List 中可以包含重复
的元素。List 接口的实现类有ArrayList
、LinkedList
、Vector
、Stack
。- List 接口除了继承Collection 中的方法,还有自己的方法:
E get(int index) //返回指定下标处的元素E set(int index,E element) //修改指定下标处的元素void add(int index,E element) //将元素插入到指定的下标处,要是直接是元素则从表尾进行插入E remove(int index) //删除指定下标处的元素 abstract boolean addAll(int index,Collection<? extends E>c) //在指定的下标处插入集合c 的全部的元素 int indexOf(Object o) //查找指定对象第一次出现的位置 int lastIndexOf(Object o) //查找元素最后一次出现的位置//实现两个线性表的合并list1.addAll(list2);//要是想不破坏list1 List<String> list3 = new ArrayList<>(list1); list3.addAll(list2);
- ArrayList 类:是最常用的线性表实现类,通过数组实现的集合对象。ArrayList 类实际上实现了一个变长的对象数组,其元素可以动态地增加与删除,它的定位访问时间是常量时间。
ArrayList 地构造方法如下:
ArrayList() :创建一个空地数组线性表对象,默认初始大小为10.线性表的大小会自动增加
ArrayList(Collection c) : 用集合 c 中的元素创建一个数组线性表对象
ArrayList(int initialCapacity) : 创建一个空的数组线性表对象,并指定初始容量
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo {
public static void main(String[] args) {
List<String> bigcity = new ArrayList<String>();
bigcity.add("shanghai");
bigcity.add("guangzhou");
bigcity.add("beijing");
System.out.println(bigcity.size());
bigcity.add(1,"zhanjiang");
System.out.println(bigcity.size());
System.out.println(bigcity.contains("beijing"));
System.out.println(bigcity.indexOf("guangzhou"));
System.out.println(bigcity.remove(2));
System.out.println(bigcity.get(1));
System.out.println(bigcity.size());
}
}
- 遍历集合元素:
(1)简单的for 循环
for(int i= 1;i<=bigcity.size();i++) System.out.print(bigcity.get(i)+" ");
(2) 使用增强的for 循环
for(String city:bigcity) System.out.print(city + " ");
含义:将bigcity集合中每个对象存储到city ,然后打印输出,这种方法只能按顺序输出,不能修改
(3)使用迭代器:迭代器是一个可以遍历集合中每一个元素的对象。调用集合对象的iterator()方法得到一个iterator 对象,再调用 Iterator 对象就可以遍历集合中的每一个元素boolean hasNext() // 返回迭代器是否有对象E next() // 返回迭代器中的下一个对象void remove () //删除迭代器中的当前对象
package demo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ArrayListDemo {
public static void main(String[] args) {
List<String> bigcity = new ArrayList<String>();
bigcity.add("shanghai");
bigcity.add("guangzhou");
bigcity.add("beijing");
Iterator iterator = bigcity.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
}
}
- 分析:Iterator 使用一个
内部指针,开始时指向第一个元素的前面
。如果在指针的后面还有元素,HasNext()就返回 true 。调用next() 方法,指针移动到下一个元素,并返回该元素
。remove() 方法将删除指针所指向的元素,该方法将同时从集合中删除对象
- 将数组转化为List 对象:Arrays.asList() 方法
String[] str = {"one","two","three"};List <String> list = Arrays.asList(str);//转化为List 列表
- 注意
Arrays.asList() 方法返回的 List 对象是不可以变化的,如果想要变化,可以将其作为参数传递给ArraysList 的一个构造方法
List <string> list = new ArrayList<>(Arrays.asList(str));
- Set 接口以及实现类:Set 接口对象类似于
集合
,不允许有重复的元素
。Set 接口没有定义新的方法
,只包含从Collection 接口继承的方法。Set 的常见的实现类:HashSet 类
、TreeSet
类、LinkedHashSet
类。- HashSet 类:运用散列的方法存储元素,具有最好的存储性能,但是元素没有顺序。
构造方法:
(1)HashSet() //创建一个空的散列集合
(2)HashSet(Collection c) //用指定的集合元素来创建一个散列集合
(3)HashSet(int initialCapacity) //创建一个指定初始容量的散列集合
(4)HashSet(int initialCapacity,float loadFactor) //创建一个有指定初始容量以及装填因子的散列集合- 用Set 对象实现标准的集合运算
s1.addAll(s2);//并运算s1.retainAll(s2);//交运算s1.removeAll(s2);//差运算s1.containAll(s2);//判断是否为自己
- TreeSet 类:实现树运算,使用红黑树为元素排序,添加到TreeSet 中的元素必须是可以比较的,即元素必须实现Comparable 接口。
- Queue 接口以及实现类:队列,实现先进先出的方式排序。Queue 接口 有
Deque
和PriorityQueue
子接口,Deque 有ArrayDeque 和 LinkedList 实现类,- Queue 接口的常用方法:
每一种操作都有两种形式,下面的前者会在操作失败抛出异常,后者会在操作失败返回一个特定的值 (offer 返回 false , poll 与 peek 返回 null)
boolean add(E e) //插入元素 等价于 boolean offer(E e)E remove() //返回队列头元素,同时删除 等价于 E poll() E element() //返回队列头元素,但不将它删除 等价于 E peek()
- Deque 接口实现
双端队列
,支持从两端进行插入与删除元素,同时实现了Stack 与 Queue
的功能。Deque 的常见的实现类包括ArrayDeque 和 LinkedList ,前者是可变数组的实现,后者是线性表的实现。ArrayDeque 不能存储 null ,但是 LinkedList 可以存储 null
- 集合转换:集合实现类的构造方法一般都接受一个Collection 对象,可以将Collection 转换成不同类型的集合。
- Map 接口以及实现类:Map 是用来存储
“键/值”
对的对象。在Map 中存储的关键字和值都必须是对象,并且关键字是唯一的,但是值可以重复
- Collection 类:提供了若干 static 对集合对象的操作主要有排序、重排、查找、求极值以及常规操作。
排序:- Stream API:
(1)流可以分为 顺序流 和并行流。
(2)流不是存储对象的数据结构,仅用来移动数据,因此不能像集合一样向流中添加元素。
(3)使用流的原因是它支持顺序和并行的聚集操作
(4)Stream 对象可以用来传输任何类型的对象- 创建和获取流:
用Stream 的 of() 静态方法
Stream stream = Stream.of(100,200,300); //创建了一个包含三个Integer 元素的流- 连接流和限制流:
用concat() 静态方法 将两个流连接起来,该操作是延迟的。结果是返回一个新流,结果是第一个流的后面接第二个流的全部元素。- 按照自然顺序排序流(返回一个新流) 用sorted
- 在流的每一个元素上执行一次操作
forEach()
//括号里面可以使用Lambda 表达式- 过滤流:按照某种规则选择流中的元素,返回一个包含选择元素的新流,调用filter() 方法过滤,传递一个Predicate 对象,Predicate 对象决定元素是否包含在新流中。
List<String> words = ;Stream<String> longwords = words.stream().filter(w->w.length()>12);//将一个字符串流转换成一个只包含长单词的流
- 流规约:规约就是终止操作。Stream 接口中 的规约方法,count() ,max(),min() ,分别返回流中的元素的个数,最大值,最小值
- 收集结果:可以使用iterator () 来生成一个能够用来访问元素的传统迭代器。可以调用toArray() 方法来获取一个含流中全部元素的数组。但是stream.toArray() 返回一个Object[]类型的数组,所以想要获取相对应的参数类型的数组,那么就传递给数组的构造方法。
String [ ] result = stream.toArray(String [ ] ::new);- 并行流:使用Collection 的parallelStream() 方法
可以从集合获取一个并行流
Stream parallelwords =words.parallelStream();
- 使用
palell()
方法可以将顺序流转换成并行流。
Stream parallelwords =Stream.of(words).parallel();
- 在多核计算机上,
并行流的运算速度一般快过顺序流,但是不是绝对的
利用map 对流上的数据进行运算
异常处理(12)(解答题)
- 所谓的异常:就是在程序运行过程中产生的使程序终止正常运行的错误对象
- 常见的报错:
(1)NullPointerException : 当某个对象的引用为null 时,调用该对象的方法或者对象时就会产生该异常- Java 的异常类都是Throwable 类的子类,都是Object 类的直接子类,定义在java.lang 包中。
- Throwable 类有两个子类:Error类 与 Exception 类。
(1) Error 描述的是系统内部的错误,发生概率小,除了通知用户以及终止程序,什么都不用做,程序一般不处理该异常
(2)Exception 类分为:非检查异常与检查异常。
(3)非检查异常(运行时异常):是RuntimeException 类以及子类。非检查异常在程序运行时检测,可能发生在任何位置且数量较大,所以系统不对非检测异常进行处理。系统不处理的异常:Error 类的子类,RuntimeException 类以及子类 ,但是RuntimeException 类以及子类被称为免检异常
- 常见的非检查异常:
(1)NullPointerException : 空指针异常,但某个对象为null 时调用该对象的方法或者使用该对象
(2)ArithmeticException :算数异常,除法运算时除数为0
(3)ClassCastException : 对象转化异常,不符合对象类型的转换
(4)ArrayIndexOutOfBoundsException :数组下标越界异常
(5)NumberFormatException : 数字格式错误异常 (字符串转化为数值时,不能正确转换)- 尽管编译器不对非检查异常进行处理,但是有非检查异常,程序也不能正常结束。
检查异常(必检异常):除RuntimeException 类以及子类以外的异常类。该类异常必须捕获或者声明,否则编译不通过
- 异常处理:
(1)使用try-catch-finally 来捕获并处理异常
(2)通过throws 子句声明抛出异常
(3)使用try-with-resources 管理资源- 抛出异常:异常都是在方法中产生的,在方法运行的过程中,如果产生异常,在这个方法中就会产生一个代表该异常类的对象,并交给系统,运行时,系统会寻找相应的代码来处理该异常。
- try-catch-finally :
try{//可能产生异常的代码块}catch(ExceptionType1 E){//异常代码的处理 }catch(ExceptionType2 E){ //异常代码的处理 }finally{ //最后的处理代码 }
- try 中的代码可能产生一种或者多种异常,catch 可以有多个,多个catch 存在,则异常类型的排序顺序必须是按照
从特殊到一般
,即子类异常放前面,父类异常放后面,否则编译出错- finally 是可选项,
不论是否发生异常,即使使用return 语句,finally 代码块都会被执行
,除非catch 中使用 System.exit() 使程序终止。finally 块 与catch 块不能单独使用
,必须结合try- 异常处理类的根类方法:(用来查看错误信息)
(1)public void printStackTrace() // 在标准错误输出流上输出异常调用栈的轨迹
(2)public String getMessage() //返回细节描述
(3)public void prnitStackTrace(printWriter s)
(4)public String toString() //返回异常的简短描述- catch 中的内容可以为空,只有当真的出现异常才会调用catch 内容,一个catch 也可以捕捉多个异常,用
|
分开即可- 可以throws 抛出异常,将异常传递给调用方法处理
就算有多个异常,一般按照顺序在第一个就抛出,剩下的不被抛出
- 子类抛出的异常必须是父类抛出异常或者子异常
- 可以在被调用的方法中使用
throw
来抛出异常,然后在调用方法中再次捕捉,再处理,在方法层面,使用的是throws
- 使用try-with-resources 结构可以打开实现了
java.lang.AutoCloseable
接口(可以继承)的资源,资源使用后会自动关闭,从而避免程序员忘记关闭资源的错误(使用I/O 流
时可以用)- 自定义异常类:需要继承Exception 类或者子类
public class CustomException extends Exception{ public CustomException( ){ } public CustomeException(String message){ super(message);//传递给超类,覆盖toString 的描述 } }
- 断言:断言就是一个java 语句,其中指定一个布尔表达式,程序员认为在执行程序的时候该布尔表达式的值为true ,系统通过计算该布尔表达式执行断言,若该表达式的值为false ,系统会报告一个错误,通过验证断言是true ,来确信程序的正确。
- 断言的形式:
(1) assert expression ;
(2) assert expression : detailMessage ;
当expression 为true ,什么也不用做。如果为false,则会抛出 AssertionError 异常- 编译带有断言的程序:
javac AssertionDemo.java (与一般的程序相同)
打开断言 java -ea ,关闭断言 java -da
输入输出(13)(程序填空题)
- 二进制 I/O 流:
java 支持 文件 I/O 和流式 I/O 。流式 I/O 分为 输入流与输出流。所有的数据流都是单向的。使用输入流只能从中读取数据;使用输入流,只能向其写入数据。数据流分为 二进制流(字节流) 和文本流(字符流),处理信息的基本单位是 字节和字符
。- 顺序读写数据:
(1)从外界获取数据:建立一个输入流对象,然后从输入流中读取数据
(2)将数据输出:建立一个流对象,然后向输出流中写出数据- File 类应用: 用来表示物理磁盘上的实际文件或者目录,但是它不表示文件中的数据(仅仅创建一个表示文件的对象)
File file = new File("hello.txt");//此时文件不存在file.createNewFile();//创建一个文件System.out.println(file.exists());//判断文件是否存在
- 常用的方法:
public boolean exists()//判断文件是否存在public long length() //返回文件的字节长度public boolean createNewFile()//当文件不存在就创建一个空文件public boolean renameTo(File newName) //重命名文件 public boolean delete() //删除文件,若为目录,则目录为空才可以删除 public long lastModified()//返回文件最后被修改的日期和时间
文件分为:文本文件和二进制文件,但是计算机不区分文件类型,都是按照二进制文件存储
- InputStream 类是二进制输入类的根类:
public int read()//从输入流读取下一个字节并返回一个0-255的整数值public int read(byte[] b)//读取多个字节,存入b 中public int available()//返回输入流中可以读取或者可以跳过的字节数public void close()//关闭输入流
- OutputStream 类是输出流的根类:
public void write(int b)//把指定整数b的低位8字节写入输出流public void write(byte[] b)//把指定字节数组的b 的b.length 个字节写入输出流public void flush()//刷新输出流,输出全部缓存内容public void close()//关闭输入流
- DataInputSream 类 和 DataOutputStream 类 分别为 数据输入流和数据输出流
- 二进制I/O 流:以字节为单位
package homework;
import java.io.*;
public class homework13_4 {
public static void main(String[]args) {
//向文件中写数据
try (
// FileOutputStream output = new FileOutputStream("data.txt");
// DataOutputStream dataOutStream = new DataOutputStream(
// new BufferedOutputStream(output));
//等价于
//都是先创建一个文件输出流,转化成一个缓存输出流,最后包装为一个数据输出流
DataOutputStream dataOutStream = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream("data.txt")));
){
for(int i = 0;i<10;i++)
{
int number = (int )(Math.random()* 1001)+1000;
System.out.println("number is " + number);
dataOutStream.writeInt(number);
//wirte相关数据类型
}
}catch(IOException e) {
e.printStackTrace();
}
System.out.println("data sended to file");
//从文件中读取数据
try(
DataInputStream dataInputStream = new DataInputStream(
new BufferedInputStream(new FileInputStream("data.txt")));
){
while(dataInputStream.available()>0) {
int getnumber = dataInputStream.readInt();
System.out.printf("get the number %d\n",getnumber);
}
}catch(IOException e) {
e.printStackTrace();
}
}
}
区别:InputStream 和 OutputStream 分别为 二进制输入流 和二进制输出流的 根类,Reader 和 Writer 分别为文本输入流和文本输出流的根类
- 文本I/O 流:以
字符
为基本单位- FileReader 类是文件输入流,FileWriter 是文件输出流
package homework;
import java.util.*;
import java.io.*;
public class homework13_12 {
public static void main(String[]args) {
Set<String> more = new HashSet<>();
Set<String> one = new HashSet<>();
String filename = "data.txt";
try(
FileReader freader = new FileReader(filename);
BufferedReader input = new BufferedReader(freader);
//等价于 BufferedReader input = new BufferedReader(
// new FileReader("data.txt"));
){
String[] words = null;
String line = null;
while((line = input.readLine())!=null) {
words = line.split("{ ,.}");
for(String word:words) {
if(!one.add(word));//集合要求不重复
more.add(word);
}
}
one.removeAll(more);
System.out.println("only one word :"+ one);
System.out.println("more than one :" + more);
}catch(IOException e) {
e.printStackTrace();
}
}
}
"input.txt",OutputStreamWriter,s.length ,IOException
并发编程基础(17)(判断选择)
- java 语言支持多线程的程序设计。
线程是进程中一个独立的顺序控制流,多线程是指单个程序内可以同时运行多个线程
。- 创建多线程:一是
继承Thread 类并覆盖其run() 方法
;二是实现 Runnable 接口并实现其 run() 方法
- 线程从创建、运行到结束总是处于下面某个状态:
新建状态
,可运行状态
,等待状态
,组塞状态
以及结束状态
。
- 每一个线程都有一个
优先级
,当有多个线程处于可以运行状态的时候,线程调度会根据线程的优先级进行调度。优先级高的不一定先执行
- 当多个线程在
没有同步的情况下操作共享数据时,其结果是不可预知的
。- 在许多情况下,多个线程需要共享数据资源,这就是线程的同步与资源共享的问题。可以通过对象
锁
实现线程的同步
,可以使用关键字synchronized
来实现方法同步和对象同步
。锁
可以确保同一时刻只有一个线程执行临界区
。- 使用Executor 可以将Runnable 实例列入执行计划,Callable 会描述一个产生结果的任务。