代码块
局部代码块
1 、格式:使用大括号括起来的一段代码
2 、位置:类中方法中
3 、作用:
限定变量的生命周期
在局部代码块中,声明的变量,只能在局部代码块范围内进行使用,一旦出了局部代码块的大括号,变量就不能继 续使用了
4 、注意:
如果在局部代码块中修改了局部代码块外声明的变量,那么出了局部代码块之后,仍然保留局部代码块对变量值的 修改。
构造代码块
1 、格式:使用大括号括起来的一段代码
2 、位置:类中方法外
3 、作用:在构造方法之前,完成对成员变量的赋值,可以将构造方法中都要执行的内容,提取到构造代码块中
4 、执行时机
( 1 ) 在创建对象的时候,默认调用一次
( 2 ) 在构造方法执行之前执行
( 3 ) 任意一个构造方法在执行之前,都会执行一次构造代码块中的内容
静态代码块
1 、格式:
static {
静态代码块中的内容;
}
2 、位置:类中方法外
3 、作用:
用于给静态变量初始化赋值
用于在类的加载的时候,进行执行一次的内容,eg:驱动加载
4 、执行特点:
(1 )随着类的加载而执行
(2 )类加载一次,所以静态代码块只执行一次
(3 )执行的时机的最早,早于对象等相关内容
final关键字
1.1 、final 修饰的类无法继承。
1.2 、final 修饰的方法无法覆盖。可以被子类继承。
1.3 、final 修饰的变量只能赋一次值。
1.4 、final 修饰的引用一旦指向某个对象,则不能再重新指向其它对象,但该引用指向的对象内部的数据是可以修改的。
1.5 、final 修饰的实例变量必须手动初始化,不能采用系统默认值。
1.6 、final 修饰的实例变量一般和static 联合使用,称为常量。
public static final double PI = 3.1415926 ;
内部类
使用内部类编写的代码,可读性很差,尽量少用。
根据定义位置的不同;
成员内部类
局部内部类
普通成员内部类
1 、定义在成员位置上的类,就是成员内部类
2 、格式:
class 外部类类名{
class 内部类类名{
}
}
3 、说明:
(1 )内部类可以直接访问外部类的所有成员,包括私有成员
(2 )外部类访问内部类的成员,必须先创建内部类的对象
(3 )在外部类以外,想直接创建内部类的对象,格式:
外部类名. 内部类名 内部类对象名 = new 外部类名( ) . new 内部类名( ) ;
私有成员内部类
1 、也是成员内部类,就是在成员内部类前面加上一个private 关键字
2 、访问方式说明:
(1 ) 在外部类以外,不能直接的访问外部类中的私有的成员内部类
(2 ) 要想对私有的成员内部类进行访问,那么定义一个访问私有成员内部类的公有的方法,让外界可以调用公有 方法,间接的访问私有的成员内部类中的数据。
静态成员内部类
1 、也是成员内部类,在成员内部类的前面加上了一个static 关键字
2 、访问特点:
(1 )静态成员内部类是外部类的静态成员。可以通过外部类类名. 内部类类名的方式直接访问,而不需要创建外部 类对象。
(2 )静态内部类中的非静态成员,需要将所在的内部类对象创建出来之后,才能被使用
(3 )一个类是否需要创建对象,不取决于本类是否是静态,而是取决于本类中的成员是否是静态的
3 、静态成员内部类可以在其他类中创建对象的。格式
外部类名. 内部类名 内部类对象名 = new 外部类类名. 内部类类名( ) ;
局部内部类
1 、局部内部类:定义在方法中的内部类
2 、位置:类中方法中
3 、访问说明:
方法中的局部变量,外界都没有办法访问到
在方法中定义的内部类,外界也是无法访问到的
4 、解决方式:
在方法内部,就创建局部内部类的对象,调用对象的方法
外界调用局部方法,间接的创建对象,完成对局部内部类中内容的访问。
包package和导包import
用于分类存放类文件(. class )文件的文件夹
package 出现在java源文件第一行。
包的声明:
使用package 关键字,声明了当前类所属的包
效果:
(1 )编译这个类,就会将该类的字节码文件放入package 后指定的文件夹路径中
(2 )当前的类的名称也会发生变化,会将类名和包名进行绑定,形成新的类名[ 包名+ 类名] 全类名
带有包名怎么编译?javac - d . xxx. java
运行:怎么运行?java 完整类名
补充:以后说类名的时候,如果带着包名描述,表示完整类名/ 全类名。如果没有带包,描述的话,表示简类名。 访问带包的类的时候,都要使用全类名
使用import 简化
import 什么时候不需要?
java. lang不需要。
同包下不需要。
其它一律都需要。
怎么用?
import 完整类名;
import 包名. *;
注意:
(1 )在一个包中,要用到很多的其他包中的类的时候,不建议使用 import *
(2 )如果在一个文件中,需要使用两个包中的同名类,此时要注意,要么全都使用全类名,要么其中一个使用全类名,一个使用导包语句。
权限修饰符
private 只能在本类中被访问
默认的权限修饰符 在本类中能被访问,
在本包下的其他类中也能被访问
protected 在本类中能被访问,
在本包下的其他类中也能被访问,
可以在其他包中的子类中被继承
public 在本类中能被访问,
在本包下的其他类中也能被访问,
在其他包下的其他类中仍然能被访问
访问控制修饰符 本类 同包 子类 任意位置
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
public 可以 可以 可以 可以
protected 可以 可以 可以 不行
默认 可以 可以 不行 不行
private 可以 不行 不行 不行
范围从大到小排序:public > protected > 默认 > private
抽象类
第一:抽象类怎么定义?在class 前添加abstract 关键字就行了。
第二:抽象类是无法实例化的,无法创建对象的,所以抽象类是用来被子类继承的。
第三:final 和abstract 不能联合使用,这两个关键字是对立的。
第四:抽象类的子类可以是抽象类。也可以是非抽象类。
第五:抽象类虽然无法实例化,但是抽象类有构造方法,这个构造方法是供子类使用的。
子类在创建对象的时候,会super ( ) 父类的无参构造,所以有。
第六:抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中。
第七:抽象方法怎么定义?
public abstract void doSome ( ) ;
第八(* * * * * 五颗星):一个非抽象的类,继承抽象类,必须将抽象类中的抽象方法进行覆盖/ 重写/ 实现。
面试题(判断题):
java语言中凡是没有方法体的方法都是抽象方法。
不对,错误的。
Object类中就有很多方法都没有方法体,都是以“; ”结尾的,但他们都不是抽象方法,例如:
public native int hashCode ( ) ;
这个方法底层调用了C++ 写的动态链接库程序。
前面修饰符列表中没有:abstract 。有一个native 。表示调用JVM本地程序。
接口
接口的基础语法
1 、接口是一种“引用数据类型”。
2 、接口是完全抽象的。
3 、接口怎么定义:
[ 修饰符列表] interface 接口名{ }
4 、接口支持多继承。
5 、接口中只有常量+ 抽象方法。
6 、接口中所有的元素都是public 修饰的
7 、接口中抽象方法的public abstract 可以省略。
8 、接口中常量的public static final 可以省略。
9 、接口中方法不能有方法体。
10 、一个非抽象的类,实现接口的时候,必须将接口中所有方法加以实现。
11 、一个类可以实现多个接口。
12 、extends 和implements 可以共存,extends 在前,implements 在后。
13 、使用接口,写代码的时候,可以使用多态(父类型引用指向子类型对象)。
接口在开发当中的应用
注意:接口在开发中的作用,类似于多态在开发中的作用。
多态:面向抽象编程,不要面向具体编程。降低程序的耦合度。提高程序的扩展力。
public class Master {
public void feed ( Animal a) {
}
}
接口在开发中的作用?
接口是不是完全的?是。而我们以后正好要求,面向抽象编程。面向抽象编程这句话以后可以修改为:面向接口编程。
有了接口就有了可插拔。可插拔表示扩展力很强。不是焊接死的。
总结一句话:三个字“解耦合”
面向接口编程,可以降低程序的耦合度,提高程序的扩展力。符合OCP开发原则。
接口的使用离不开多态机制。(接口+ 多态才可以达到降低耦合度。)
接口可以解耦合,解开的是谁和谁的耦合!!!
任何一个接口都有调用者和实现者。
接口可以将调用者和实现者解耦合。
调用者面向接口调用。
实现者面向接口编写实现。
JAVA8接口特性:
java8中的接口,不仅可以定义抽象方法,还可以定义非抽象方法,但非抽象方法必须加上default 或者static 修饰!
default 默认方法:
加上default 的,实现类可以不用重写,直接调用;
特殊情况1 :
实现类实现了两个都有这个默认方法的,需要重写这个方法以确定实现内容。
特殊情况2 :
在情况1 中,如果想默认方法是某个父类的方法,直接调用“类名. super . 默认方法( ) ”;
特殊情况3 :
如果实现类属于即继承又实现相同,则“类优先”,即使是抽象类也要强制重写。
static :
接口的静态方法可以定义方法体内容
static 不能和abstract 共存
外界只能通过“接口名称. 静态方法名”来访问接口中的静态方法,实现类中不会继承接口中的静态方法( 原因是如果一个类实现了两个具有相同静态方法名的接口,继承之后就不知道应该以哪个为准了)
类型和类型之间的关系
is a(继承)、has a(关联)、like a(实现)
抽象类和接口的区别
在这里我们只说一下抽象类和接口在语法上的区别。
至于以后抽象类和接口应该怎么进行选择,通过后面的项目去体会/ 学习。
抽象类是半抽象的。
接口是完全抽象的。
抽象类中有构造方法。
接口中没有构造方法。
接口和接口之间支持多继承。
类和类之间只能单继承。
一个类可以同时实现多个接口。
一个抽象类只能继承一个类(单继承)。
接口中只允许出现常量和抽象方法。
接口一般都是对“行为”的抽象。
匿名内部类
1 、没有名字的内部类
2 、匿名内部类的使用前提
继承某个类或者实现一个接口
3 、格式:
new 父类类名或者接口名( ) {
父类方法的重写或者接口内容的实现。
}
4 、匿名内部类本质:
(1 )创建了一个该类的子类类型的对象,或者是接口的实现类对象
(2 )更强调的是一种写法
Object
什么是API?
应用程序编程接口。(Application Program Interface)
整个JDK的类库就是一个javase的API。
每一个API都会配置一套API帮助文档。
SUN公司提前写好的这套类库就是API。(一般每一份API都对应一份API帮助文档。)
目前为止我们只需要知道这几个方法即可:
protected Object clone ( )
int hashCode ( )
boolean equals ( Object obj)
String toString ( )
protected void finalize ( )
toString ( ) 方法:
以后所有类的toString ( ) 方法是需要重写的。
重写规则,越简单越明了就好。
System. out. println ( 引用) ; 这里会自动调用“引用”的toString ( ) 方法。
String类是SUN写的,toString方法已经重写了。
1 、源代码长什么样?
public String toString ( ) {
return this . getClass ( ) . getName ( ) + "@" + Integer. toHexString ( hashCode ( ) ) ;
}
源代码上toString ( ) 方法的默认实现是:
类名@对象的内存地址转换为十六进制的形式
2 、SUN公司设计toString ( ) 方法的目的是什么?
通过调用这个方法可以将一个“java对象”转换成“字符串表示形式”
equals ( ) 方法:
以后所有类的equals方法也需要重写,因为Object中的equals方法比较
的是两个对象的内存地址,我们应该比较内容,所以需要重写。
重写规则:自己定,主要看是什么和什么相等时表示两个对象相等。
基本数据类型比较实用:== (引用数据类型也可以用== ,但是比较的是地址值所以不建议)
对象和对象比较:调用equals方法
String类是SUN编写的,所以String类的equals方法重写了。
以后判断两个字符串是否相等,最好不要使用== ,要调用字符串对象的equals方法。
注意:重写equals方法的时候要彻底。
finalize ( ) 方法:
这个方法是protected 修饰的,在Object类中这个方法的源代码是?
protected void finalize ( ) throws Throwable { }
finalize ( ) 只需要重写,重写完将来自动会有程序来调用。
GC:负责调用finalize ( ) 方法。
finalize ( ) 方法的执行时机:
当一个java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用
finalize ( ) 方法。
java中的垃圾回收器不是轻易启动的,
垃圾太少,或者时间没到,种种条件下,有可能启动,也有可能不启动。
hashCode ( ) 方法:
在Object中的hashCode方法是怎样的?
public native int hashCode ( ) ;
这个方法不是抽象方法,带有native 关键字,底层调用C++ 程序。
hashCode ( ) 方法返回的是哈希码:
实际上就是一个java对象的内存地址,经过哈希算法,得出的一个值。
所以hashCode ( ) 方法的执行结果可以等同看做一个java对象的内存地址。
数组
java语言中,数组是一种引用数据类型,它的父类的object
数组是一个数据的集合。
数组中可以存储基本数据类型也可以存储引用数据类型
数组因为是引用类型,所以数组对象是在堆内存中。
数组当中如果存储的是“java对象”的话,实际上存储的是对象的“引用”(内存地址)
数组一旦创建,在java中规定长度是不可变的。
数组的分类包括:一维数组,二维数组,三维数组和多维数组。(一维较多,二维偶尔)
所有数组都有length属性,用来获取数组元素个数(长度)
java中的数组要求数组中元素类型统一
数组在内存方面存储的时候,数组中的元素内存地址是连续的
所有的数组都是拿首元素在数组中的位置的内存地址作为整个数组的内存地址
优点:
查询/ 查找/ 检索某个下标上的元素时效率极高,可以说是查询效率最高的一个数据结构。
为什么效率高?
第一:每个元素的内存地址在空间存储上是连续的
第二: 每个元素类型相同,占用空间大小相同
第三:知道第一个元素内存地址,知道每一个元素占用空间大小,又知道下标,所以通过一个数学表达式就可以计 算出某个元素的内存地址,直接通过内存地址定位该元素,所以它的效率高。
缺点:
第一: 由于为了保证数组中每个元素的内存地址连续,所以在数组中随机增删元素的时候,效率较低,因为随机增删元素 会涉及到后面每个元素统一向前或向后位移的操作(但是对于数组中最后一个元素的操作效率是没有影响的)
第二:数组不能存储大数据量
因为很难在内存空间上找到一块特别大的连续的内存空间。
main方法上“String[ ] args”参数的使用(非重点,了解一下,以后一般都是有界面的,用户可以在界面上输入用户名和密码等参数信息。)
数组的拷贝:System. arraycopy ( ) 方法的使用
数组有一个特点:长度一旦确定,不可变。
所以数组长度不够的时候,需要扩容,扩容的机制是:新建一个大数组,
将小数组中的数据拷贝到大数组,然后小数组对象被垃圾回收。
常见算法
排序算法:
冒泡排序
每次比较每有一次大的就要交换一次位置。
选择排序
比较一组数据中,把最小的数字与最左边的数字进行交换,每次循环都数组都是交换了位置的剩下的元素。虽然比 较次数一样,但是交换次数比冒泡排序少。
查找算法:
二分法查找:
第一:二分法查找建立在排序的基础之上。
第二:二分法查找效率要高于“一个挨着一个”的这种查找方式。
第三:二分法查找原理?
将头索引和尾索引加起来除以2 处的索引与值比较,然后判断大小情况再更改头索引或者尾索引。
Scanner类
Scanner是一个扫描器,可以解析基本数据类型和字符串。
构造方法
Scanner ( File f) -- -- > 扫描指定文件
Scanner ( String path) -- -- > 扫描指定路径
Scanner ( InputStream is) -- -- > 扫描指定的输入流
这第三个就是Scanner ( System. in) , 其中的Sysetm. in就是一个字节输入流,标准输入流,默认关联到键盘。
注意:
如果调用完以空格作为分隔符的方法之后 ,再调用一个以换行符为分隔符的方法时,会有问题,之前的换行符还存在那个字符串中;
解决方式:
1. 调用两次nextLine ( ) ;
2. 创建两个Scanner对象
3. 所以的录入都以字符串形式录入到内存中,再进行数据类型之间的转换。
String类
对String在内存存储方面的理解:
第一:字符串一旦创建不可变。(String s = "abc" ; 不是说变量s不可变,而是"abc" 双引号里面的内容不可变)
为什么?
我看过源代码,String类中有一个byte [ ] 数组,这个byte [ ] 数组采用了final 修饰,数组一旦创建长度就 不可变,并且被final 修饰的引用一旦指向某个对象之后,不可再指向其他引用,所以String是不可变的。
第二:双引号括起来的字符串存储在字符串常量池中。
第三:字符串的比较必须使用equals方法。
第四:String已经重写了toString ( ) 和equals ( ) 方法。
String类的构造方法
String a = "abc" ;
String a = new String ( ) ; 创建一个空串
String a = new String ( "abc" ) ; 创建一个字符串副本对象
String a = new String ( byte [ ] b) ; 将b转换为字符串(ASC码)
String a = new String ( byte [ ] b, int offset, int length) ; 将b字节数组的一部分转换成字符串
String a = new String ( char [ ] b) ; 将b字节数组转换为字符串(拼接)
String a = new String ( char [ ] b, int offset, int length) ; 将b字符数组的一部分转换成字符串
String类型的判断功能
1 、equals ( Object ob) ;
compareTo ( Object ob) 这个方法不仅能看是否相等还能看大小。
2 、equalsIgnoreCase ( String otherStr) ; 忽略大小写判断是否相同
3 、contains ( String str) ;
4 、startsWith ( String prefix) ;
5 、endsWith ( String suffix) ;
6 、isEmty ( ) ;
String类型的获取功能
1 、length ( ) ;
2 、charAt ( int index) ;
3 、substring ( int beginIndex) ;
4 、substring ( int beginIndex , int endIndex) ;
5 、indexOf家族
indexOf ( int ch) ; 采用unicode编码
indexOf ( int ch, int fromIndex) ;
indexOf ( String str) ;
indexOf ( String str, int fromIndex)
6 、lastIndexOf家族(从后面开始)
String类型的转换功能
1 、byte [ ] getBytes ( ) ; 转换成[ 字节] 数组
2 、char [ ] toCharArry ( ) ; 转换成[ 字符] 数组
3 、toUpperCase ( ) ;
4 、toLowerCase ( ) ;
5 、concat ( String str) ; 字符串拼接( 较少用)
6 、valueOf ( ) ; 家族; 些方法为静态方法,用“类名. ”调用将任意数据类型转换为string类型
String类型的其他方法
1 、replace ( String oldStr, String newStr) ;
2 、trim ( ) ; 去掉左右两边的空格以及制表符
3 、String[ ] split ( String regex) ; 以regex将字符串进行拆分
Math类
两个常量
E -- -- > 自然对数的底数,2.718
PI
abs ( 数字类型) ;返回绝对值
System类
常用字段
System. in-- -- > 标准输入流
System. out-- -- > 标准输出流
System. err-- -- > 标准错误输出流,红色。
常用方法
1 、gc ( ) ; 建议垃圾回收器启动
2 、currentTimeMillis ( ) ; 从1970 - 1 - 1 0 : 0 返回毫秒值
应用:1 、将数字转换成时间对象;2 、粗略计算代码运行时间
StringBuffer(1.0)/StringBuilder(1.5)
1 、StringBuffer/ StringBuilder可以看做可变长度字符串。
2 、StringBuffer/ StringBuilder初始化容量16.
3 、StringBuffer/ StringBuilder是完成字符串拼接操作的,方法名:append
4 、频繁进行字符串拼接不建议使用“+ ”, 所以用上面的。
5 、StringBuffer是线程安全的。StringBuilder是非线程安全的。
StringBuffer/ StringBuilder为什么是可变的?
我看过源代码,StringBuffer/ StringBuilder内部实际是一个byte [ ] 数组,而这个byte [ ] 数组没有被final 修饰, StringBuilder/ StringBuffer的初始化容量我记得是16 ,当存满之后会进行扩容,底层会调用数组拷贝的方法 System. arraycopy ( ) 这样扩容的。所以StringBuilder/ StringBuffer适合字符串的频繁拼接操作。
构造方法:
StringBuilder ( ) : 创建一个字符串生成器,初始容量为16 个字符
StringBuilder ( int capacity) 创建一个生成器,初始容量为capacity大小
StringBuilder ( String str) 创建一个字符串生成器,初始值就是str这个字符串,初始大小str + 16
获取容积的方法:
capacity ( ) :返回当前生成器的大小。
length ( ) :返回的是当前生成器中字符的个数。
添加功能:
1 、append ( 任意类型) :可以将任意的数据类型,转成字符串,追到到字符串生成器中
2 、insert ( int index, 任意类型) :可以将任意的数据类型,插入到指定的位置
删除功能:
1 、deleteCharAt ( int index) 删除指定索引处的字符
2 、delete ( int start, int end) 删除指定索引范围内的字符。注意:被删除索引的范围包含头不包含尾
替换和反转:
1 、replace ( int start, int end, String str)
2 、reverse ( ) 将原有的字符串进行反转
八种基本数据类型对应的包装类
Byte Short Integer Long Float Double Boolean Charactor
装箱:
基本数据类型封闭成包装类型的对象
拆箱:
从包装类型的对象中,将包装的基本类型数据取出来。
所有数字的父类型都为Number
Integer
学习Integer,其他模仿,依葫芦画瓢!!!
Integer构造方法:
1 、Integer ( int value) 将一个基本类型int 数据,进行包装,包装为Integer类型的对象
本质:给Integer对象底层中维护的私有的int 类型的变量进行一个赋值的过程
2 、Integer ( String s) 将一个字符串类型的数字,转换成Integer类型的对象
Integer常用方法:
intValue ( value) ; 拆箱
如果value不是“数字”,就会出现NumberFormatException异常
static int parseInt ( String s) ; 静态方法,传入字符串,转化为int
如果s不是“数字”,就会出现NumberFormatException异常
static int parseInt ( String s, radix) ; 静态方法,传入字符串,以指定进制进行转化为十进制int
以下方法了解:
static String toBinaryString ( int i) ; 静态的,将十进制转换成二进制字符串
static String toOctalString ( int i) ; 静态的,将填制转换成八进制字符串
static String toHexString ( int i) ; 静态的,将十进制转换成十六进制字符串
static Integer valueOf ( int i) ; 静态的,将int 转换成Integer
static Integer valueOf ( String s) ; 静态的,将string转换成Integer
static Integer valueOf ( String s, radix) ; 静态的,将string以指定进制进行转换成Integer
toString ( int i, int radix) 使用指定的radix进制,表示数字i(将十进制的数字,转成任意进制的字符串表示形式)
Integer常量:
SIZE : int 类型在内存中所占的位数 32
TYPE :int 类型在方法区中的字节码对象
有9 个预先定义好的Class对象代表8 个基本数据类型和一个void ,它们被java虚拟机创建和基本的数据类型具有相同的名字。
java中为了提高程序执行效率,将[ - 128 到127 ] 之间的所有包装对象提前创建好,放到了一个方法区的“整数型常量池”当中,目的是只要是在这个区间的数据就无需再new ,直接从整数型常量池中取。
public class IntegerTest06 {
public static void main ( String[ ] args) {
Integer a = 128 ;
Integer b = 128 ;
System. out. println ( a == b) ;
Integer x = 127 ;
Integer y = 127 ;
System. out. println ( x == y) ;
}
}
Date
1 、怎么获取系统当前时间
Date d = new Date ( ) ;
2 、日期格式化Date -- - > String
yyyy- MM- dd HH: mm: ss SSS
SimpleDateFormat sdf = new SimpleDateFormat ( "yyyy-MM-dd HH:mm:ss SSS" ) ;
String s = sdf. format ( new Date ( ) ) ;
yyyy 年( 年是4 位)
MM 月(月是2 位)
dd 日
HH 时
mm 分
ss 秒
SSS 毫秒(毫秒3 位,最高999 。1000 毫秒代表1 秒)
注意:在日期格式中,除了y M d H m s S这些字符不能随便写之外,剩下的符号格式自己随意组织。
3 、String -- - > Date
SimpleDateFormat sdf = new SimpleDateFormat ( "yyyy-MM-dd HH:mm:ss" ) ;
Date d = sdf. parse ( "2008-08-08 08:08:08" ) ;
4 、System. currentTimeMillis ( )
获取毫秒数
数字类
BigDecimal
属于大数据,精度极高。不属于基本数据类型,属于java对象(引用数据类型)
这是SUN提供的一个类。专门用在财务软件当中。
DecimalFormat:
数字格式有哪些?
# 代表任意数字
, 代表千分位
. 代表小数点
0 代表不够时补0 eg: ###, ###. ## 表示:加入千分位,保留2 个小数。
Radom
怎么产生int 类型随机数。
Random r = new Random ( ) ;
int i = r. nextInt ( ) ;
怎么产生某个范围之内的int 类型随机数。
Random r = new Random ( ) ;
int i = r. nextInt ( 101 ) ;
枚举
1 、枚举是一种引用数据类型。
2 、枚举编译之后也是class 文件。
3 、枚举类型怎么定义?
enum 枚举类型名{
枚举值, 枚举值2 , 枚举值3
}
4 、当一个方法执行结果超过两种情况,并且是一枚一枚可以列举出来的时候,建议返回值类型设计为枚举类型。
正则表达式
1 、本质就是一个字符串
2 、作用:不仅可以表示该字符串本身,还能表示一类的字符串,或者表示一类字符串的规则和格式
3 、好处:可以使用特别简单的代码,表示非常复杂的规则
4 、坏处:写出正则表达式的正确率不高
字符类:
普通的字符串而言,也是一个正则表达式,但是只能表示的是自己本身这个字符串,无法匹配一类字符串
判断某个字符串是否和某个正则表达式的规则进行匹配,使用String类中matches方法
字符类型:表示的是单个字符,使用的符号是中括号 [ ]
eg:
[ abc] : a或者b或者c中的一个字符
[ ^ abc] : 除了a或者b或者c中的其他的任意一个字符
[ a- zA- Z] : 英文字母的大小写中的一个字符
预定义字符类:
. 表示任意的字符 \. 表示的一个确定的. 这个字符串
\d 表示数字字符
\D 表示非数字字符
\s 表示空格字符
\S 表示非空格字符
\w 表示的[ a- zA- Z0- 9 _]
\W 表示除了[ a- zA- Z0- 9 _] 以外的其他字符
数量词:
模糊的数量词
X? 表示的x这个字符,出现0 次或者1 次
X+ 表示x这个字符,出现1 次或者多次
X* 表示x这个字符,出现0 次,1 次或者多次
精确的数量词
X{ n} 表示x这个字符恰好出现n次
X{ n, } 表示x这个字符至少出现n次
X{ n, m} 表示x这个字符至少出现n次,至多出现m次
字符串中和正则表达式有关的三个方法:
1 、boolean matches ( String regex) 判断当前字符串和参数正则表达式是否匹配
2 、String[ ] split ( String regex) :使用指定的正则表达式完成对字符串的分割。
3 、String replaceAll ( String regex, String replacement) 将调用者字符串中的所有匹配regex正则的子串,全部替换成replacement新串
异常
异常处理机制
java中异常的作用是:增强程序健壮性。
java中异常以类和对象的形式存在。
1 、异常在java中以类和对象的形式存在。那么异常的继承结构是怎样的?
Object
Object下有Throwable(可抛出的)
Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
Exception下有两个分支:
Exception的直接子类:编译时异常
RuntimeException:运行时异常。
2 、编译时异常和运行时异常,都是发生在运行阶段。编译阶段异常是不会发生的。
编译时异常因为什么而得名?
因为编译时异常必须在编译( 编写) 阶段预先处理,如果不处理编译器报错,因此得名。
所有异常都是在运行阶段发生的。因为只有程序运行阶段才可以new 对象。因为异常的发生就是new 异常对象。
3 、编译时异常和运行时异常的区别?
编译时异常一般发生的概率比较高。运行时异常一般发生的概率比较低。
4 、编译时异常还有其他名字:
受检异常:CheckedException
受控异常
5 、运行时异常还有其它名字:
未受检异常:UnCheckedException
非受控异常
6 、再次强调:所有异常都是发生在运行阶段的。
7 、Java语言中对异常的处理包括两种方式:
第一种方式:在方法声明的位置上,使用throws 关键字,抛给上一级。
谁调用我,我就抛给谁。抛给上一级。
第二种方式:使用try . . catch 语句进行异常的捕捉。
这件事发生了,谁也不知道,因为我给抓住了。
注意:
只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。
另外需要注意,try 语句块中的某一行出现异常,该行后面的代码不会执行。
try . . catch 捕捉异常之后,程序的后续代码可以执行。
在以后的开发中,处理编译时异常,应该上报还是捕捉呢,怎么选?
如果希望调用者来处理,选择throws 上报。其它情况使用捕捉的方式。
8 、注意:Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续
向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果。终止java程序的执行。
9 、深入try . . catch
1 、catch 后面的小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型。
2 、catch 可以写多个。建议catch 的时候,精确的一个一个处理。这样有利于程序的调试。
3 、catch 写多个的时候,从上到下,必须遵守从小到大。
4 、在jdk1. 7 之后,可以对异常的类型进行逻辑或表示,多种异常类型,使用相同的处理方式
catch ( 异常的类型1 | 异常的类型2 | 异常的类型3 标识符) {
就是对异常的类型1 和异常的类型2 和异常的类型3 共同的处理方式
}
10 、异常对象有两个非常重要的方法:
获取异常简单的描述信息:
String msg = exception. getMessage ( ) ;
打印异常追踪的堆栈信息(一般使用这个):
exception. printStackTrace ( ) ;
其他方法:getCause ( ) :获取异常对象中的原因异常对象
toString ( ) :返回此 throwable 的简短描述
11 、关于try . . catch 中的finally 子句:
在finally 子句中的代码是最后执行的,并且是【一定会执行】的,即使try 语句块中的代码出现了异常。
finally 子句必须和try 一起出现,不能单独编写。
System. exit ( 0 ) ; 在finally 之前时
finally 语句通常使用在哪些情况下呢?
通常在finally 语句块中完成资源的释放/ 关闭。
因为finally 中的代码比较有保障。
即使try 语句块中的代码出现异常,finally 中代码也会正常执行。
12 、Java中怎么自定义异常呢?
两步:
第一步:编写一个类继承Exception或者RuntimeException.
第二步:提供两个构造方法,一个无参数的,一个带有String参数的。
finally面试题
public class ExceptionTest13 {
public static void main ( String[ ] args) {
int result = m ( ) ;
System. out. println ( result) ;
}
public static int m ( ) {
int i = 100 ;
try {
return i;
} finally {
i++ ;
}
}
}
final finally finalize有什么区别?
final 关键字
final 修饰的类无法继承
final 修饰的方法无法覆盖
final 修饰的变量不能重新赋值。
finally 关键字
和try 一起联合使用。
finally 语句块中的代码是必须执行的。
finalize 标识符
是一个Object类中的方法名。
这个方法是由垃圾回收器GC负责调用的。
泛型(JDK5) - generics
使用:对于有泛型的类型,在这些类型后面跟上尖括号,尖括号里面写上泛型的确定类型;
(在使用某个泛型类创建对象的时候,已经可以确定这个具体的类型了)
泛型的好处:
(1 )提高数据的安全性,将运行时的问题,提前暴露在编译时期。( 元素统一)
(2 )避免向下转型的麻烦
注意:
(1 )前后一致:在创建泛型类对象的时候,赋值符号前面和后面尖括号中的泛型类型要保持一致
(2 )JDK8- 自动类型推断( 又称为钻石表达式或菱形泛型) :如果前面的引用所属类型已写好泛型。后面则可只写一对< > 。
(3 )不能创建一个泛型数组,因为如果能够定义泛型数组,那么就会发生泛型擦除,就失去了泛型的意义。
泛型类的定义:
格式:
class 类名 < 泛型类型1 ,泛型类型2 ,泛型类型3. . . >{
}
说明:
(1 )类名后面跟上的泛型类型,是泛型的声明,一旦声明,这个类型就成为已知类型,就可以在这个类中任意的使用
(2 )泛型的声明名称:只要是一个合法的标识符即可,java源代码中经常出现的是:< E> 和< T> (Element、Type)。
(3 )泛型确定的时机:将来使用这个类,创建对象的时候。
泛型方法:
在方法声明中,带着泛型声明的方法,就是泛型方法
格式
修饰符 < 泛型声明1 ,泛型声明2. . . > 返回值类型 方法名称( 参数列表) {
}
说明:
(1 )在方法上声明的泛型,可以在整个方法中,当做已知的类型来使用
(2 )如果【非静态】的方法上没有任何泛型的声明,那么可以使用类上声明的泛型
(3 )如果【静态】方法上没有任何的泛型的声明,那么就不能使用泛型。连类上声明的泛型都不能够进行使用
因为类上声明的泛型,在创建对象的时候才能确定,【静态】方法想使用泛型,就必须在自己的方法上进行声明。
泛型的通配符:
使用泛型的时候,没有直接使用该泛型,而是使用和该泛型有关的一类类型,就称为泛型的通配符。
第一种形式, 使用? 表示可以是任意的类型:
Collection< E> 接口中的removeAll ( Collection< ? > c) ,此方法表示参数集合c的泛型可以是和调用者集合的泛型( E) 没有任何的关系
第二种形式,使用? extends E 表示泛型类型必须是E类型或者E类型的子类类型
Collection< E> 接口中的addAll ( Collection< ? extends E > c) ,表示参数集合c的泛型必须是调用者集合的泛型E的相同类型或者是E的子类类型。
第三中形式:使用? super E表示泛型类型必须是E类型或者是E类型的父类类型
Arrays中sort ( T[ ] a, Comparator< ? super T> c) T是该方法的泛型,T表示是数组中的元素类型,< ? super T> 表示可以接受泛型类型是数组元素类型或者是数据元素类型的父类类型。
Collection
父接口是Ietrable;
集合中不能直接存储基本数据类型,也不能存java对象,只是存储java对象的内存地址。
java. util. *; 所有的集合类和集合接口都在java. util包下。
集合分类:
单列集合:每个元素都是一个单独的个体。
Collection 单列集合的顶层接口
List 有序可重复的子接口
ArrayList 底层数组,查询修改快,增删慢
LinkedList 双向链表实现,查询慢,增删快
Vector 底层数组
Set 无序不可重复的子接口
HashSet 哈希存储
LinkedHashSet
sortedSet
TreeSet
双列集合:每个操作都是针对一对数据来进行的,一个数据作为一个单位。
Map 双列集合的顶层接口
HashMap 哈希表存储
LinkedHashMap
Collection
Collection是一个接口,不能直接创建对象.
常用方法:
boolean add ( Object obj) 向集合中添加元素
boolean remove ( Object o) 将o元素从集合中删除
( 此方法底层调用了equals方法进行比对)
重点( 1 ) :当集合的结构发生改变时,迭代器必须重新获取,如果还是用以前老的迭代器,会出现
异常:ConcurrentModificationException
重点( 2 ) :在迭代集合元素的过程中,不能调用集合对象的remove方法,删除元素
错:集合. remove ( o) ; 会出现异常:ConcurrentModificationException
( 迭代器不会把自己中的那份删除)
重点( 3 ) :在迭代元素的过程当中,一定要使用迭代器Iterator的remove方法,删除元素
对:迭代器. remove ( o) 。
( 迭代器会把自己中的那份也删除)
boolean contains ( Object o) 判断集合中是否包含o这个元素( 此方法底层调用了equals方法进行比对)
boolean isEmpty ( ) 判断集合是否为空
int size ( ) 返回集合中元素的个数, 不是集合容量。
Collection中带all的方法:
addAll ( Collection c) 将参数c集合中所有的元素,都添加到调用者集合中
containsAll ( Collection c) 判断调用者集合,是否包含参数集合c中所有的元素
removeAll ( Collection c) 将调用者集合中也存在于参数集合c中的元素进行移除
retainAll ( Collection c) 将调用者集合和参数集合c中都有的元素进行保留放入调用者集合中
Collection的第一种遍历方式:
转成数组,通过遍历数据的方式,间接的遍历集合
Object[ ] toArray将调用者集合转成Object类型的数组
集合遍历的第二种方式:迭代器( 在Map集合中不能用。在所有的Collection以及子类中使用)
第一步:获取集合对象的迭代器对象Iterator
Iterator it = 集合. iterator ( ) ;
第二步:通过以上获取的迭代器对象调用Itreator中的方法:
hasNext ( ) ; 判断集合中是否还有下一个元素
next ( ) ; 获取下一个元素
remove ( ) ; 删除正在迭代的那个元素
迭代器使用的注意事项:
next ( ) 方法不仅可以获取下一个元素,同时也会让迭代器对象,向后移动一步。
如果没有下一个元素但调用next ( ) 方法,那么就会出现NoSuchElementException异常
hasNext方法,只能判断时候有没有下一个元素,但是不会移动迭代器指针的位置
Collection - List
List:
是Collection集合的一个有序的子接口
特点:
有序: List集合中的元素有下标,从0 开始,以1 递增。存和取顺序一致。
可重复。
特有方法:
void add ( int index, Object obj) 在指定的索引上,添加指定的元素
Object get ( int index) 获取指定索引位置上的元素
Object set ( int index, E element) 将指定索引位置上的元素进行重新的赋值
int indexOf ( Object o) 获取第一次
int lastIndexOf ( Object o) 获取最后一次
Object remove ( int index) 移除指定索引位置上的元素
Collection的第三种遍历方式:
针对List集合特有的遍历方式可以通过集合的size方法获取list集合索引的范围,
然后根据索引通过get方法得到List集合中的每个元素
并发修改异常ConcurrentModificationException:
原因:在使用【迭代器对象】遍历集合的同时,又使用了【集合对象】增删集合中的元素
避免方式1 :采用迭代器遍历,迭代器增删:
遍历:普通迭代器遍历
迭代器增加:不能用普通的迭代器,普通的迭代器没有增加的方法,需要使用List集合中特有的列表迭代器。
列表迭代器的获取:List中的一个方法 ListIterator listIterator ( ) ;
是Iterator的一个子接口,用于Iterator中所有的方法,还有自己特有的方法
方式2 :集合遍历,集合增删
集合遍历:list特有的方式进行遍历
集合添加:集合的add方法
Collection - List - ArrayList
没有特有的方法
通过物理内存的位置关系,来描述逻辑顺序的相邻;查询快,增删慢
1 、默认初始化容量10 ( 底层先创建了一个长度为0 的数组,当添加第一个元素的时候,初始化容量10 。)
2 、集合底层是一个Object[ ] 数组。
3 、构造方法:
new ArrayList ( ) ;
new ArrayList ( 20 ) ;
4 、ArrayList集合的扩容:
增长到原容量的1.5 倍。
ArrayList集合底层是数组,怎么优化?
尽可能少的扩容。建议在使用ArrayList集合的时候预估计元素的个数,给定一个初始化容量。
5 、数组优点:
检索效率比较高。(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,
然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)
ArrayList之所以检索效率比较高,不是单纯因为下标的原因。是因为底层数组发挥的作用。
6 、数组缺点:
随机增删元素效率比较低。
另外数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间。)
7 、向数组末尾添加元素,效率很高,不受影响。
8 、面试官经常问的一个问题?
这么多的集合中,你用哪个集合最多?
答:ArrayList集合。
因为往数组末尾添加元素,效率不受影响。另外,一般我们检索/ 查找某个元素的操作比较多。
9 、ArrayList集合是非线程安全的。(不是线程安全的集合。)
10 、ArrayList集合不是线程安全的。
List< String> list = new ArrayList < > ( ) ;
调用Collections中的sychronizedList ( ) 方法变成线程安全的:
Collections. synchronizedList ( list) ;
Collection - List - LinkedList
LinkedList集合底层采用了双向链表数据结构;
LinkedList集合有初始化容量吗?没有. 最初这个链表中没有任何元素。first和last引用都是null。
每一个元素都存储在一个节点中,节点中存储了元素本身和下一个节点中元素的地址;查询慢,增删快
特有方法:
addFirst ( Object obj) 在头部添加元素
addLast ( Object obj) 在尾部添加元素
removeFirst ( ) 移除头部元素
removeLast ( ) 移除尾部元素
getFirst ( ) 获取头部元素
getLast ( ) 获取尾部元素
链表的优点:
由于链表上的元素在空间存储上内存地址不连续。
所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议
使用LinkedList。
链表的缺点:
不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头
节点开始遍历,直到找到为止。所以LinkedList集合检索/ 查找的效率
较低。
ArrayList:把检索发挥到极致。(末尾添加元素效率还是很高的。)
LinkedList:把随机增删发挥到极致。
加元素都是往末尾添加,所以ArrayList用的比LinkedList多。
Collection - List - Vector
1 、底层也是一个数组。
2 、初始化容量:10
3 、怎么扩容的?
扩容之后是原容量的2 倍。
10 -- > 20 -- > 40 -- > 80
4 、Vector中所有的方法都是线程同步的,都带有synchronized 关键字,
是线程安全的。效率比较低,使用使用。
Collection - Set
Set是Collection的一个子接口
特点:
无序:存入的顺序和取出的顺序不一定一致。
没有索引:集合中没有任何位置的区分
不能重复:没有位置的区分,相同值的元素也就无法进行区别,所以不能重复
Set集合的遍历:
没有特有的遍历方式,只能使用Collection中定义的方法,Collection中的遍历的方式.
第一种:转成数组,toArray(),不带泛型的数据,得到的是一个Object类型的数组
第二种:转成T类型数组数组, T[ ] toArray ( T[ ] arr) , 带泛型的数组,得到的就是一个T类型的数组
(1 )自己创建的数组的大小,小于集合中元素的个数
在方法中, 就会创建一个新的数组, 使用集合中的元素填充新的数组, 并进行返回
(2 )自己创建的数组的大小,等于集合中元素的个数
在方法中,使用集合中的元素将传入的数组进行填充,将原数组返回
(3 )自己创建的数组的大小,大于集合中元素的个数
在方法中,使用集合中的元素将传入的数组进行填充,多出的位置填充null。将原数组返回
第三种:迭代器
第四种:(JDK5)增强for 循环,foreach语句
格式:for ( 元素的数据类型 元素名称 : 要遍历的集合) {
使用元素名称代表要访问的元素
}
注意事项:
使用增强for ,在完成对集合的同时也不能边遍历边使用集合对象进行增删,否则也会发生并发修改异常
增强for ,没有拿到元素的索引,因此在遍历有索引的集合的时候,如果想要使用索引,是无法进行的。
hashCode方法:
Object类型中的方法, 根据对象的情况,生成一个整数,把这个整数称为对象的哈希码值.
生成数字的原则:
(1 )同一对象多次调用hashCode方法,【必须】返回相同的整数.
(2 )使用equals(Object)方法判断相同的两个对象【必须】返回相同的整数.
(3 )使用equals(Object)方法判断不相同的两个对象,【尽量】返回不同的整数.
(4 )Object类型的hashCode方法【确实】会根据不同的对象,生成不同的整数.
保证元素唯一性的操作:
重写hashCode方法和equals方法,比较的就是各个对象的属性值,是否全部相同
LinkedHashSet:
是hashSet的一个子类,和HashSet保证元素唯一性的原理是相同的
具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现,能够保证存储和取出集合中元素的顺序保持一致
应用:
如果集合中的元素既需要保证唯一性,不能重复,又要保证元素存入的顺序,此时就可以使用LinkedHashSet集合
Collection - Set - HashSet
实际上HashSet集合在new 的时候,底层实际上new 了一个HashMap集合。向HashSet集合中存储元素,实际上是存储到HashMap集合中的键位置上了。
HashSet集合的初始化容量是16 ,初始化容量建议是2 的倍数
扩容:扩容之后是原来容量的2 倍。
Collection - Set - SortedSet
SortedSet由于继承了Set,所以它的特点也是无序不可重复,但是放在SortedSet集合中的元素可以自动排序,称为可排序集合,放到该集合中的元素是自动按照大小顺序排序的。
Collection - Set - SortedSet - TreeSet
无序不可重复的,但是存储的元素可以自动按照大小顺序排序!
无序:这里的无序指的是存进去的顺序和取出来的顺序不同。并且没有下标。
TreeSet集合底层实际上是一个TreeMap, TreeMap集合底层是一个二叉树。
放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。
TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。称为:可排序集合。
最终的结论:
放到TreeSet或者TreeMap集合key部分的元素要想做到排序, 包括两种方式:
第一种:放在集合中的元素实现java. lang. Comparable接口。
第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。比较器实现java. util. Comparator接口
Comparable是java. lang包下的。Comparator是java. util包下的。
Comparable和Comparator怎么选择呢?
当比较规则不会发生改变的时候,或者说当比较规则只有1 个的时候,建议实现Comparable接口。
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。
Comparator接口的设计符合OCP原则。
Map
双列集合的顶层接口
数据结构:描述的就是一个数据(key)到另一个数据(value)的映射关系(对应关系)。
key和value都是引用数据类型。key和value都是存储对象的内存地址。
Map的特点:
key(键) 是唯一的, value ( 值) 不是唯一的, map集合中存放的数据称为键值对, 每个键只能对应唯一的值。
Map中常用的方法:
增加方法
V put ( K key, V value) ; 增加键值对
删除方法
V remove ( Object key) ; 根据给定的键,删除对应的值
void clear ( ) ; 清空集合
获取方法
int size ( ) ; 获取的是键值对的对数
V get ( Object key) ; 根据给定的键,获取对应的值
Set< K> keySet ( ) ; 获取Map中所有的Key
Set< Map. Entry< K, V> > entrySet ( ) ; 将Map集合转换成Set集合
判断方法
boolean containsKey ( Object key) ; 判断集合中是否存在某个键
boolean containsValue ( Object value) ; 判断集合中是否存在某个值
修改方法
v put ( K key, V value) ; 根据对应的键,修改键对应的值;如果已经有这个键就是修改,否则增加;
Map集合的第一种遍历思路【重点】:
1 、获取Map集合中的所有键,放到一个Set集合中,遍历Set集合,获取到键,再运用map集合中的get方法得到值
2 、获取Map集合中所有的键:
Set< E> keySet ( ) ;
3 、遍历Set集合得到每一个键,获取值即可
遍历Set集合使用两种方式:
迭代器或者增强for
4 、根据拿到每一个键,获取对应的值
V get ( K key) ;
Map集合的第二种遍历思路【重点】:
1 、( Entry) 获取map集合中所有的键值对对象,放到一个Set集合中, 遍历Set集合,拿到每个键值对,从这个对象中获取键和值根据键值对对象获取键和值。
2 、根据Mpa集合获取所有的键值对对象,到一个Set集合中
Set< Entry< K, V> > entrySet ( ) ;
Entry< K, V> 就是map集合中的每一个键值对
3 、遍历Set集合,得到是每一个键值对对象
两种方式遍历Set集合
迭代器
增强for
4 、得到每一个键值对之后,获取键和值
Entry中提供了方法
getKey ( ) , 获取当前减值对中的key
getValue ( ) ,获取当前键值对中的value
Entry本质是Map中的一个内部接口,访问的时候,Map. Entry
LinkedHashMap:
是HashMap的一个子类
和HashMap使用是一样的,区别就是LinkedHashMap具有可预知的迭代顺序,键值对的存入和取出顺序是一致的。
Map - HashMap
HashMap集合底层是哈希表/ 散列表的数据结构。非线程安全;
哈希表/ 散列表:一维数组,这个数组中每一个元素是一个单向链表。
在JDK8之后,如果哈希表单向链表中元素超过8 个,单向链表这种数据结构会变成红黑树数据结构,当红黑树上的节点数量小于6 时,会重新把红黑树变成单向链表数据结构。这种方式也是为了提高检索效率,二叉树的检索会再次缩小扫描范围。
HashMap集合的默认初始化容量是16 ,默认加载因子是0.75
这个默认加载因子是当HashMap集合底层数组的容量达到75 % 的时候,数组开始扩容。
扩容后的容量是原来的2 倍。
重点:记住:HashMap集合初始化容量必须是2 的倍数,这也是官方推荐的,
这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。
哈希表是一个怎样的数据结构呢?
哈希表是一个数组和单向链表的结合体。
数组:在查询方面效率很高,随机增删方面效率很低。
单向链表:在随机增删方面效率较高,在查询方面效率很低。
哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。
HashMap集合底层的源代码:
public class HashMap {
Node< K, V> [ ] table;
static class Node < K, V> {
final int hash;
final K key;
V value;
Node< K, V> next;
最主要掌握的是:
map. put ( k, v) 实现原理:
第一步:先将k, v封装到Node对象中
第二步:底层调用k的hashCode ( ) 方法得出hash值,然后通过哈希算法,将hash值转换成数组的下标,下标位置上如果没有元素,就把Node添加到这个位置上。如果说下标对应位置上有链表,此时会拿着k和链表上每一个节点中的k进行equals ( ) ,如果所有的equals ( ) 返回的都是false ,那么这个新节点将被添加到链表的末尾。如果其中有一个equals ( ) 返回了true , 那么这个节点的value将会被覆盖。
v = map. get ( k) 实现原理:
先调用k的hashCode ( ) 方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置,如果这个位置上什么都没有,返回null。如果这个位置上有单向链表,那么会拿着参数k和单向链表上的每个节点中的k进行equals ( ) , 如果所有的equals ( ) 方法返回false ,那么get ( ) 方法返回null。只要其中有一个节点的k和参数k进行equals ( ) 返回的是true ,那么此时这个节点的value就是我们要找的value,get ( ) 方法最终返回这个要找的value。
由上可知:HashMap集合的key会先后调用两个方法,一个是hashCode ( ) ,一个方法是equals ( ) ,这两个方法都需要重写!放在HashMap集合key部分的元素其实就是放到HashSet集合中了。所以HashSet集合中的元素也需要同时重写hashCode ( ) + equals ( ) 方法。
所以说为什么哈希表的随机增删以及查询效率高?
因为增删是在链表上完成的,查询也只需要部分扫描。
HashMap集合的key部分特点:
无序,不可重复。
为什么无序? 因为不一定挂到哪个单向链表上。
不可重复是怎么保证的? equals方法来保证HashMap集合的key不可重复。如果key重复了,value会覆盖。
HashMap集合key部分允许null吗?
允许
但是要注意:HashMap集合的key null值只能有一个。有可能面试的时候遇到这样的问题。
Map - Hashtable
Hashtable集合的底层也是哈希表数据结构,是线程安全的,其中的方法都带有synchoronized关键字,效率较低,现在使用较少了,因为控制线程安全有其他更好的方案。
Hashtable的key和value不允许为null
Hashtable集合的初始化容量是11 它的扩容是:原容量* 2 + 1
Map - Hashtable - properties
目前只需要掌握Properties属性类对象的相关方法即可。
setProperty ( String s) ;
getProperty ( String s) ;
Properties的key和value都是String类型。
Properties被称为属性类对象。
Properties是线程安全的。
Map - SortedMap
无序不可重复,但是放在SortedMap当中key部分的元素会自动按照大小顺序排序,称为可排序集合。
Map - SortedMap - TreeMap
底层数据结构是二叉树
Collections工具类
1 、binarySearch ( List< E> list, T key) 在一个有序的List集合中,通过二分查找寻找元素key在list集合中的索引
2 、frequency ( Collection< ? > c, Object o) 返回元素o在集合c中出现的次数
3 、max,min获取集合中的最大值和最小值
4 、replaceAll ( List< T> list, T oldVal, T newVal) 将list集合中oldVal替换为newVal
5 、reverse ( List< ? > list) 将list集合中的元素进行反转
6 、shuffle ( List< ? > list) 将list集合中元素进行随机的置换
7 、swap ( List< ? > list, int i, int j) 将list集合中指定索引的i和j进行交换
8 、unmodifiableXXX; 方法:将一个可修改的集合传入方法,返回一个不可修改只读的集合
9 、synchronizedXXX方法:将一个线程不安全的集合传入方法,返回一个线程安全的集合
File类
File类型:用于表示一个文件或者文件夹的路径的对象( 抽象路径名)
路径:用于描述文件或者文件夹所在位置的字符串
File类型的构造方法:
1 、File ( String pathname) ;把字符串的路径,封装为一个File类型的对象
2 、File ( String parent, String child) , 将父级路径和子级路径封装成一个file对象,其实描述的就是父级路径和子级路径拼接后的路径
3 、File ( File parent, String child) 将父级File对象路径和子级路径封装一个File对象,其实描述也是父级file对象封装的路径和子级路径拼接后的路径
File类型的创建方法:
1 、boolean createNewFile ( ) 创建当前File对象所描述的路径的文件
2 、boolean mkdir ( ) ; 创建当前File类型所描述的路径的文件夹(如果父级路径不存在,那么不会自动创建父级路径的)
3 、mkdirs ( ) : 创建当前file对象所描述路径的文件夹(如果父级路径不存在,那么会自动创建父级路径的)
File类型的删除方法:
1 、delete ( ) 删除调用者对象所描述的文件夹或者文件
2 、注意:delete方法在删除文件夹的时候,只能删除空文件夹
File类型的重命名方法:
1 、renameTo ( File dest)
调用者是当前的文件或者文件夹的路径对象
参数dest是重命名后的文件或者文件夹的抽象路径
2 、注意:
如果在同一个文件夹下,修改路径,就是重命名
如果发生在不同的文件夹下,修改路径,就是剪切
File类型的判断方法:
1 、exists ( ) : 判断File类型封装的抽象路径是否存在
2 、isFile ( ) : 判断当前调用者File对象,是否是文件
3 、isDirectory ( ) :判断当前调用者File对象,是否是文件夹
File类型的获取功能:
1 、getAbsolutePath ( ) :获取当前file对象的绝对路径
2 、getPath ( ) 获取的就是构造方法中封装的路径
3 、getName ( ) : 获取抽象路径字符串中所描述的最底层中的文件或者文件夹的名称
4 、length ( ) :返回的是调用者file对象所描述的文件的字节个数
注意:该方法如果被( 文件file对象) 调用,返回的就是该文件对应的字节个数
该方法如果是被( 文件夹file对象) 调用,返回的值是不确定的。
5 、String[ ] list ( ) 返回调用者File文件夹对象中所有文件和文件夹的名称到一个字符串数组中
如果此抽象路径名不表示一个目录,那么此方法将返回 null
6 、File[ ] listFiles ( ) ; 返回调用者File文件夹对象中所有文件和文件夹的File对象到一个File数组中
如果此抽象路径名不表示一个目录,那么此方法将返回 null
7 、lastModified ( ) ; 返回最后一次的修改时间
IO
什么是IO?
I : Input
O : Output
通过IO可以完成硬盘文件的读和写。
IO流的分类?
一种方式是按照流的方向进行分类:
以内存作为参照物,
往内存中去,叫做输入( Input) 。或者叫做读( Read) 。
从内存中出来,叫做输出( Output) 。或者叫做写( Write) 。
另一种方式是按照读取数据方式不同进行分类:
有的流是按照字节的方式读取数据,一次读取1 个字节byte ,等同于一次读取8 个二进制位。
这种流是万能的,什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频文件等. . . .
有的流是按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便读取
普通文本文件而存在的,这种流不能读取:图片、声音、视频等文件。只能读取纯
文本文件,连word文件都无法读取。
综上所述:流的分类
输入流、输出流
字节流、字符流
java IO流这块有四大家族:
四大家族的首领( 都是抽象类abstract class ) :
java. io. InputStream-- -- 字节输入流 java. io. Reader-- -- -- - 字符输入流
java. io. OutputStream-- - 字节输出流 java. io. Writer-- -- -- - 字符输出流
注意:在java中只要“类名”以Stream结尾的都是字节流。以“Reader/ Writer”结尾的都是字符流
所有的流都实现了:
java. io. Closeable接口,都是可关闭的,都有close ( ) 方法。用完之后一定要关闭,不然会占用很多资源。
所有的输出流都实现了:
java. io. Flushable接口,都是可刷新的,都有flush ( ) 方法。
养成一个好习惯,输出流在最终输出之后,一定要记得flush ( )
刷新一下。这个刷新表示将通道/ 管道当中剩余未输出的数据
强行输出完(清空管道!)刷新的作用就是清空管道。
注意:如果没有flush ( ) 可能会导致丢失数据。
java. io包下需要掌握的流有16 个:
文件专属:
java. io. FileInputStream(掌握)
java. io. FileOutputStream(掌握)
java. io. FileReader
java. io. FileWriter
转换流:(将字节流转换成字符流)
java. io. InputStreamReader
java. io. OutputStreamWriter
缓冲流专属:
java. io. BufferedReader
java. io. BufferedWriter
java. io. BufferedInputStream
java. io. BufferedOutputStream
数据流专属:
java. io. DataInputStream
java. io. DataOutputStream
标准输出流:
java. io. PrintWriter
java. io. PrintStream(掌握)可以把输出到控制台的信息输出到文件。
对象专属流:
java. io. ObjectInputStream(掌握)
java. io. ObjectOutputStream(掌握)
InputStream&OutPutStream
当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。
外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。
对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭。
InputStream常用方法:
int read();读到就返回值,否则返回-1。
int read(byte[] b);返回值是读到几个数据,-1是没有可读的了。
int available():返回流当中剩余的没有读到的字节数量
long skip(long n):跳过几个字节不读。
实现类:FileInputStream
构造方法:
FileInputStream(File f);
FileInputStream(String path);
这两个构造方法只能用来封闭文件路径,文件夹不行。
OutPutStream常用方法:
void write();
void write(byte[] b);
void write(byte[],int offset,int len);
实现类:FileOutPutStream
FileOutputStream(File f);
FileOutputStream(String path);
会以覆盖的方式添加,用true可不覆盖,慎用。
如果没有没有这个文件会自动新建
以上是字节流,字符流都差不多,只是数组变成了char[],关键词变了。FileReader:文件字符输入流,只能读取普通文本。读取文本内容时,比较方便,快捷。FileWriter:文件字符输出流。写。只能输出普通文本。
BufferedReader:
带有缓冲区的字符输入流。
使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组。自带缓冲。
DataInputStream:数据字节输入流。
java.io.DataOutputStream:数据专属的流。这个流可以将数据连同数据的类型一并写入文件。
注意:这个文件不是普通文本文档。(这个文件使用记事本打不开。)
DataOutputStream写的文件,只能使用DataInputStream去读。并且读的时候你需要提前知道写入的顺序。
读的顺序需要和写的顺序一致。才可以正常取出数据。
InputStreamReader&OutputStreamWriter
可以以指定编码格式进行读和写。
线程的调度
( 这部分内容属于了解)
1.1 、常见的线程调度模型有哪些?
抢占式调度模型:
那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/ 多一些。
java采用的就是抢占式调度模型。
均分式调度模型:
平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。
平均分配,一切平等。
有一些编程语言,线程调度模型采用的是这种方式。
1.2 、java中提供了哪些方法是和线程调度有关系的呢?
实例方法:
void setPriority ( int newPriority) 设置线程的优先级
int getPriority ( ) 获取线程优先级
最低优先级1
默认优先级是5
最高优先级10
优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)
静态方法:
static void yield ( ) 让位方法
暂停当前正在执行的线程对象,并执行其他线程
yield ( ) 方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield ( ) 方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。
实例方法:
void join ( ) ; 合并线程
class MyThread1 extends Thread {
public void doSome ( ) {
MyThread2 t = new MyThread2 ( ) ;
t. join ( ) ;
}
}
class MyThread2 extends Thread {
}
线程
实现线程的第一种方式:
编写一个类,直接继承java. lang. Thread,重写run方法。
实现线程的第二种方式:
编写一个类实现java. lang. Runnable接口
实现线程的第三种方式:实现Callable接口。(JDK8新特性。)
这种方式实现的线程可以获取线程的返回值。这种方式是call不是run;
这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
思考:系统委派一个线程去执行一个任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么能拿到这个执行结果呢?使用第三种方式:实现Callable接口方式。
run ( ) 当中的异常不能throws ,只能try catch
因为run ( ) 方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
方法:
Thread Thread. currentThread ( ) ; 获取当前对象。
String 线程对象. getName ( ) ; 获取线程对象名字。
void 线程对象. setName ( ) ; 修改线程对象名字。
在构造方法时也可以设置名字
当线程没有设置名字的时候,默认的名字有什么规律?(了解一下)
Thread- 0 、Thread- 1 、Thread- 2 、Thread- 3
void 线程对象. interrupt ( ) ; 终止线程的睡眠。
void Thread. yield ( ) ; 静态方法, 让位,当前线程暂停,回到就绪状态,让给其它线程。
void 线程对象. join ( ) ; 线程合并, 当前线程受阻塞,加入的线程执行直到结束。
Thread. State 线程. getState ( ) ; 返回此线程的状态。( NEW, RUNNABLE ( 就绪和运行态) , BLOCKED ( 等待锁、IO) , WAITINT ( 调用了wait方法) , TIME_WAITING ( 调用了有时间限制的wait或者sleep方法) , TERMINATED)
关于线程的sleep方法:
static void sleep ( long millis)
1 、静态方法:Thread. sleep ( 1000 ) ;
2 、参数是毫秒
3 、作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用.
4 、Thread. sleep ( ) 方法,可以做到这种效果:间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
如何合理的终止一个线程的执行?
外部打一个布尔标记,什么时候想线程结束,将布尔标记改一下。
线程池使用:
1 、获取线程池对象
工具类:Executors: 生成线程池的工具类,可以根据需要生成指定大小线程池
ExecutorService Executors. newSingleThreadPool ( ) ; 创建一个单线程池
ExecutorService Executors. newFixedThreadPool ( int nThreads) ; 创建指定数量线程池
2 、创建任务类对象
Runnable类的实现对象,用于定义任务内容
3 、将任务对象提交线程池中
ExecutorService是一个接口,不需要手动创建这个接口的实现类对象,使用方法获取到的就是这个实现类对象。
submit ( Runnable r) ; 将一个任务类对象提交到线程池中,如果没有空闲的线程,则需等待;
shutDown ( ) ; 结束线程池,已经提交的全部保证完成,不准继续提交;
shutDownNow ( ) ; 已经运行的,保证完成;已提交但还未运行的,不给运行,作为返回值范围;未提交的不准提交;
关于多线程并发环境下,数据的安全问题。
为什么这个是重点?
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。
最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点:* * * * * )
什么时候数据在多线程并发的环境下会存在安全问题呢?
三个条件:
条件1 :多线程并发。
条件2 :有共享数据。
条件3 :共享数据有修改的行为。
满足以上3 个条件之后,就会存在线程安全问题。
怎么解决线程安全问题呢?
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在
线程安全问题,怎么解决这个问题?
线程排队执行。(不能并发)。用排队执行解决线程安全问题。这种机制被称为:线程同步机制。
专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。
怎么解决线程安全问题呀?
使用“线程同步机制”。
说到线程同步这块,涉及到这两个专业术语:
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高。)异步就是并发。
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低。线程排队执行。同步就是排队。
Java中有三大变量
实例变量:在堆中。
静态变量:在方法区。
局部变量:在栈中。
以上三大变量中:
局部变量永远都不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈。)
局部变量在栈中。所以局部变量永远都不会共享。
实例变量在堆中,堆只有1 个。静态变量在方法区中,方法区只有1 个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
局部变量+ 常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。
如果使用局部变量的话:建议使用:StringBuilder。
因为局部变量不存在线程安全问题。StringBuffer效率比较低。
ArrayList是非线程安全的。Vector是线程安全的。
HashMap HashSet是非线程安全的。Hashtable是线程安全的。
synchronized
三种写法:
第一种:同步代码块
灵活
synchronized ( 线程共享对象) {
同步代码块;
}
第二种:在实例方法上使用synchronized
表示共享对象一定是this 并且同步代码块是整个方法体。
第三种:在静态方法上使用synchronized
表示找类锁。类锁永远只有1 把。
就算创建了100 个对象,那类锁也只有一把。
对象锁:1 个对象1 把锁,100 个对象100 把锁。
类锁:100 个对象,也可能只是1 把类锁。
怎么解决线程安全问题(解决顺序)
第一种方案:尽量使用局部变量代替“实例变量和静态变量”。
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应1 个对象,100 个线程对应100 个对象,对象不共享,就没有数据安全问题了。)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized 了。线程同步机制。
守护线程
. isDaemon ( ) ; 设置为守护线程
java语言中线程分为两大类:
一类是:用户线程
一类是:守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)。
守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,
守护线程自动结束。
注意:主线程main方法是一个用户线程。
守护线程用在什么地方呢?
每天00 : 00 的时候系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程。一直在那里看着,没到00 : 00 的时候就备份一次。所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。
定时器
定时器的作用:
间隔特定的时间,执行特定的程序。
Timer timer = new Timer ( ) ;
Timer timer = new Timer ( boolean isDaemon) ;
timer. schedule ( TimerTask task, Date firstTime, long period) ;
定时任务:
TimerTask: 是一个Runnable的实现接口,里面有run方法;
在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,
那么在java中其实可以采用多种方式实现:
可以使用sleep方法,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)
在java的类库中已经写好了一个定时器:java. util. Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
wait()和notify()
wait和notify方法不是线程对象的方法,是普通java对象都有的方法。
wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。
wait方法作用:o. wait ( ) 让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。
notify方法作用:o. notify ( ) 让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
Lambda表达式
本质:
表示一个对象,指定接口的实现类对象
前提:
函数式接口
格式:
( 参数列表) - > { 方法体}
情况:
如果有且只有一个参数,( ) 可以省略;
如果Lambda体只有一句,{ } 可以省略;
如果Lambda体只有一句,且有return ,那么可以同时省略{ } 和return ;
函数式接口
Lambda表达式的使用前提
@FunctionalInterface
可以检查是否为函数式接口
常用内置函数式接口:
Consumer< T> : 消费型接口
void accept ( T t) ;
Supplier< T> : 供给型接口
T get ( ) ;
Function< T, R> : 函数型接口
R apply ( T t) ;
Predicate< T> : 断言型接口
boolean test ( T t) ;
方法引用
写一个函数式接口时,方法的实现(lanbda体),已经被某个其他的对象实现了,就不需要在Lambda体中,再次调用这个实现,而可以直接使用那个已经定义好的方法。
格式:
函数式接口 名称 = 对象名: : 方法名称
函数式接口 名称 = 类名: : 静态方法名
作用:
把已经实现的方法,作为一个数据,作为实现类对象,赋值给某个函数式接口的引用
可以把这个引用当做方法的返回值,也可以作为方法的实际参数进行传递
本质:
可以把任意的一个方法,作为函数式接口的一个实现类对象
StreamingAPI
Stream类型数据的获取:
Collection:
集合对象. stream ( ) ;
Map:
keySet ( ) . stream ( ) ;
values ( ) . stream ( ) ;
entrySet ( ) . stream ( ) ;
数组:
Stream. of ( 数组) ;
Stream中的常用方法:
终结方法:
forEach、count
延迟方法:
filter ( 判断) 、limit ( 截取) 、skip ( 跳过) 、map ( 转换)
类加载器
关于JDK中自带的类加载器:(聊一聊,不需要掌握,知道当然最好!)
什么是类加载器?
专门负责加载类的命令/ 工具。
ClassLoader
JDK中自带了3 个类加载器
启动类加载器: rt. jar
扩展类加载器: ext. *. jar
应用类加载器: classpath
代码在开始执行之前,会将所需要类全部加载到JVM当中。
首先通过“启动类加载器”加载。
注意:启动类加载器专门加载:jdk1. 8.0 _101\jre\lib\rt. jar
rt. jar中都是JDK最核心的类库。
如果通过“启动类加载器”加载不到的时候,
会通过"扩展类加载器" 加载。
注意:扩展类加载器专门加载:jdk1. 8.0 _101\jre\lib\ext\* . jar
如果“扩展类加载器”没有加载到,那么
会通过“应用类加载器”加载。
注意:应用类加载器专门加载:classpath中的类。
java中为了保证类加载的安全,使用了双亲委派机制。
优先从启动类加载器中加载,这个称为“父”,“父”无法加载到,再从扩展类加载器中加载,这个称为“母”。双亲委派。如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。
反射机制Reflect
反射机制有什么用?
通过java语言中的反射机制可以操作字节码文件。
有点类似于黑客。(可以读和修改字节码文件。)
通过反射机制可以操作代码片段。(class 文件。)
反射机制的相关类在哪个包下?
java. lang. reflect. *;
反射机制相关的重要的类有哪些?
java. lang. Class:代表整个字节码,代表一个类型,代表整个类。
java. lang. reflect. Method:代表字节码中的方法字节码。代表类中的方法。
java. lang. reflect. Constructor:代表字节码中的构造方法字节码。代表类中的构造方法
java. lang. reflect. Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+ 实例变量)。
通过反射获取Class的三种方式:
方式一:Class c = Class. forName ( "完整类名" ) ;
方式二:Class c = 对象. getClass ( ) ; (java中任何一个对象都有此方法)
方式三:Class c = 任何数据类型. Class; (java中任何一种类型,它都有. class 属性)
通过反射实例化对象:
获取Class之后,可以调用无参构造方法来实例化对象
Object obj = c. newInstance ( ) ;
注意: newInstance ( ) ; 方法会调用调用者类的无参数构造方法,如果没有这个无参构造,那么会出“实例化”异常。
通过读属性配制文件实例化对象:
通过IO流读取属性配制文件
创建类属性对象Map- > Properties pro = new Properties ( ) ;
加载- > ( pro. load ( "流" ) ; )
获取Value- > String s = pro. getProperty ( key) ;
通过反射机制实例化对象- > Class c = Class. forName ( s) ; Object obj = c. newInstance ( ) ;
反射机制的灵活性:
java代码写一遍,在不改变源代码的基础上,可以做到不同对象的实例化。
如果只想要某个类的静态代码块执行,其他一律不执行,可以用forName ( "完整类名" ) ;
获取类路径下文件的绝对路径:
String path = Thread. CurrentThread ( ) . getContextClassLoader ( ) . getResource ( "类路径下的文件" ) . getPath ( ) ;
获取类路径下文件直接以流形式返回:
InputStream is = Thread. CurrentThread ( ) . getContextClassLoader ( ) . getResourceAsStream ( "类路径下的文件" ) ;
资源绑定器( 获取属性配置文件内容) :
ResourceBundle bundle = ResourceBundle. getBundle ( "不带后缀的properties文件" ) ;
String className = bundle. getString ( key) ;
只能绑定类路径下的. properties类型的文件。
反射某类中所有的Field:
1 、获取Class c :
Class. forName ( "..." ) ;
全类名- > c. getName ( ) ;
简类名- > c. getSimpleName ( ) ;
2 、公共的属性获取:
全部- > c. getFields ( ) ;
单个- > c. getField ( ) ;
3 、全部的属性获取:
全部- > Field[ ] f = c. getDeclaredFields ( ) ;
单个- > Field f = getDeclaredField ( "..." ) ;
4 、获取修饰符列表:
修饰符对应编号- > int i = f. getModifiers ( ) ;
转换- > String s = Modifier. toString ( i) ;
5 、获取属性类型:
Class ft = f. getType ( ) ;
typeName = ft. getSimpleName ( ) ;
5 、获取属性名:
f. getName ( ) ;
通过反射机制修改/ 获取对象的属性值( IMPORTANT) :
1 、获取Class c
2 、创建对象:Object obj = c. newInstance ( )
3 、获取属性Field:Field name = c. getDeclaredField ( "name" )
( 三要素:对象obj, 属性name, 值)
4 、修改属性值:name. set ( Object obj, String name)
( 两要素:获取obj对象的name属性的值)
5 、获取属性值:name. get ( Object obj)
如何修改/ 获取私有属性值:
在获取属性之后- > 打破封装- > 进行操作
打破封装:name. setAccessible ( true ) ;
反射Method
1 、获取Class c
2 、获取所有方法( 包括私有) :
Method[ ] methods = c. getDeclaredMethods ( )
获取某个方法:
Method method = c. getDeclaredMethod ( "参数1" , "参数2" , . . . ) ;
3 、通过Modifier的toString ( ) 方法获取修饰服列表:
String s = Modifier. toString ( c. getModifiers ( ) ) ;
4 、获取返回值类型:
Class rt = c. getReturnType ( ) ;
String rtName = rt. getSimpleName ( ) ;
5 、获取方法的参数列表
Class[ ] pts = c. getParameterTypes ( )
for ( Class parametertype : pts) {
parametertype. getSimpleName ( ) ;
}
通过反射机制,调用一个对象的方法( IMPORTANT) :
1 、获取Class c
2 、创建对象: Object obj = c. newInstance ( )
3 、获取方法:Method m = c. getDeclaredMethod ( 方法名, a. class , b. class , . . . )
4 、调用方法:
( 四要素:m- > 方法、obj- > 对象、a值, b值- > 实参、retValue- > 返回值)
Object rtValue = m. invoke ( obj, a值, b值) ;
System. out. println ( rtValue) ;
反射Constructor
1 、获取Class c
2 、获取全部构造方法:Constructor[ ] cs = c. getDeclaredConstructors ( )
3 、遍历cs, 获取每个具体的构造方法:
for ( Constructor con: cs) {
Modifier. toString ( con. getDeclaredConstructor)
cs. getSimpleName ( )
Parameter[ ] ps = con. getParameters ( ) ;
for ( Parameter p : ps) {
p. getTypeParameter ( ) . getSimpleName ( ) ;
}
}
通过反射,实例化对象( IMPORTANT) :
1 、获取Class c
2 、创建对象:先获取到这个有参构造,再调用这个方法创建对象
无参:Constructor con = c. getDeclaredConstructor ( ) ; Object obj = con. newInstance ( ) ;
简化:Object obj = c. newInstance ( ) ;
有参:第一步:Constructor con = c. getDeclaredConstructor ( a. class , b. class , c. class , . . . ) ;
第二步:Object obj = con. newInstance ( a, b, c类型的数据) ;
通过反射,获取一个类的父类和接口( IMPORTANT) :
1 、获取Class c
2 、获取父类:String sName = c. getSuperclass ( ) . getSimpleName ( )
3 、获取接口:Class[ ] in = c. getInterfaces ( )
for ( Class c : in) {
c. getSimpleName ( ) ;
}
注解Annotation
注解
注解,或者叫做注释类型,英文单词是:Annotation
注解Annotation是一种引用数据类型。编译之后也是生成xxx. class 文件。
怎么自定义注解呢?语法格式?
[ 修饰符列表] @interface 注解类型名{
}
注解怎么使用,用在什么地方?
第一:注解使用时的语法格式是:
@注解类型名
第二:注解可以出现在类上、属性上、方法上、变量上、注解还可以出现在注解类型上等。
第三:注解的属性是value且只有一个时,value可以省略。
第四:如果一个注解当中有属性,那么必须给属性赋值。(除非该属性使用default 指定了默认值。)。
第五:如果数组中只有1 个元素,大括号可以省略。
注解当中的属性可以是哪一种类型?
byte short int long float double boolean char String Class 枚举类型以及它们每一种的数组形式。
JDK内置了哪些注解呢?
java. lang包下的注释类型:
掌握:
Deprecated 用 @Deprecated 注释的程序元素,不鼓励程序员,通常是因为它很危险或存在更好的选择。
Override 表示一个方法声明打算重写超类中的另一个方法声明。
不用掌握:
SuppressWarnings 指示应该在注释元素中取消显示指定的编译器警告。
元注解:
用来标注“注解类型”的“注解”,称为元注解。
常见的元注解有哪些?
Target
Retention
关于Target注解:
这是一个元注解,用来标注“注解类型”的“注解”
这个Target注解用来标注“被标注的注解”可以出现在哪些位置上。
@Target ( ElementType. METHOD) :表示“被标注的注解”只能出现在方法上。
@Target ( value= { CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE} ) 表示该注解可以出现在:构造方法上、字段上、局部变量上、方法上. . . . 类上. . . )
关于Retention注解:
这是一个元注解,用来标注“注解类型”的“注解”
这个Retention注解用来标注“被标注的注解”最终保存在哪里。
@Retention ( RetentionPolicy. SOURCE) :表示该注解只被保留在java源文件中。
@Retention ( RetentionPolicy. CLASS) :表示该注解被保存在class 文件中。
@Retention ( RetentionPolicy. RUNTIME) :表示该注解被保存在class 文件中,并且可以被反射机制所读取。
Retention的源代码
public @interface Retention {
RetentionPolicy value ( ) ;
}
RetentionPolicy的源代码:
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
@Retention ( RetentionPolicy. RUNTIME)
public @interface MyAnnotation { }
判断某个类型上面是否有某个注解:
1 、先获取该类型,再用类型. isAnnotationPresent ( "注解.class" ) 进行判断
2 、然后可以用getAnnotation ( 注解. class ) 获取该注解
3 、可以用获取到的注解获取注解当中的属性值eg: 注解. userName ( ) ;
注解在开发中有什么用呢?
起到一个标识的作用,比如说有这个标识要干什么,没有又要干什么。