第一章 JAVA基础知识
1解释java跨平台的含义
答:java程序是一次编译,到处运行。Java程序→java编译器编译成.Class字节码文件,然后.class文件在java虚拟机上运行,与平台无关,只需要在不同的平台上安装java虚拟机即可。
2.JDK、JRE、JVM区别和J2SE、J2EE区别
答: 简单来说,JRE可以支撑Java程序的运行,包括JVM虚拟机(java.exe等)和基本的类库(rt.jar等),JDK可以支持Java程序的开发,包括编译器(javac.exe)、开发工具(javadoc.exe等)和更多的类库(如tools.jar)等。
JRE=JVM+核心类库(运行时必须的类),可以使java程序运行。
JDK=JRE+开发工具+基础类库(Java API 包括rt.jar),可以开发java程序。
J2SE是标准版,是我们通常用的一个版本,从JDK 5.0开始,改名为Java SE。
J2EE(enterprise edition),企业版,使用这种JDK开发J2EE应用程序,从JDK 5.0开始,改名为Java EE。
3.为什么要配置classPath环境变量和path变量
答:设置classpath环境变量,JVM就会根据设置的路径寻找class字节码文件。
设置PATH变量是为了让操作系统找到指定的工具命令程序。(配置javac的路径等)
4.标识符与关键字
标识符就是java程序中自定义的类名、变量名、方法名等。
规则是:
1)必须由字母、数字、下划线和美元组成。
2)开头不可以是数字,并且区分大小写
3)关键字和保留字不可以用作自定义标识符
约定俗称的规范:
1)包名全部小写。Eg:cn.bob.dao
2)类名和接口名所有单词首字母都大写。Eg:BobDemo1.java
3)方法名和变量名首单词小写,别的单词的第一个字母大写。Eg:doFilter()、int customName;
4)常量都大写
关键字:是有特殊含义的标识符。比如long、private等
5.数据类型的种类和如何类型转换
答:5.1Java语言提供了八种基本类型和引用数据类型包括:
八种基本类型:
整数类型:byte(8位)、short(16位)、int(32位)、long(64位),默认为int型
浮点类型(float(32)、double(64)),默认为double型,
字符类型:char(16)
布尔:boolen。声明变量占32位,声明数组,每个数组元素占8位
注意:小数默认为double型,若表示成float型,需要在后边加F或者f。比如float a = 3f;整数同理
引用数据类型:类似指针,引用类型指向一个对象,指向对象的变量是引用变量。对象、数组、字符串都是引用数据类型。所有引用类型的默认值都是null。
5.2类型转换:小数据->大数据,自动类型转换
大数据->小数据,使用强制类型转换
强制类型转换格式为:
long b=3l;
int a =(int)b;
6.区分算术运算符/和%、+的区别。
%为取余数,结果的正负号取决于第一个数(被除数)。Eg:-10%-3等于-1;
/为除数,10/3等于3;
+:表示连接符:当有字符串的到时候,+就是连接符
+:作加法运算。
Eg:systemprintln(1+2+3+”hello”+4+5); 结果是”6hello45”
7.区分>>和>>>移位运算符
>>:右移位,相当于乘2;若操作数为整数,左边用0补;若操作数为负数,左边用1补
>>>:无符号右移位,相当于乘2;统一用0补
8解释以下逻辑运算符
&:与,都相同为true
&&:短路与,当左边为false,右边不用运算。
|:或,都不同才为false
||:短路或,当左边为true,右边不用算了。
^:异或,不同为1,相同为0.一个数A异或B两次,结果还是A。
面试题:交换a、b的值,不用第三方变量。
答: a=a^b;
b=a^b;
a=a^b;
9转义字符
用“\”转换为字符本身输出。比如:打印“hello”bob”,system.out.println(“hello\”bob)
常见的有:\n:换行,在windows系统上换行是\r\n。
\r:光标移到一行的首位置上,比如:
system.out.println(“hello\rbob);结果为boblo;
10三目运算符
布尔表达式?值1:值2;如果布尔表达式为true,则为值1,为false,为值2;
比如:True?false:true的结果为false。
11switch -case语句的停止条件
10.1格式: Switch(){ Case 常量1: 代码; break; Case 常量2: 代码; break; default; 代码; break; } | 注意点: 1、case后边必须跟常量 2、一旦匹配上其中一个case语句,就会依次执行,后边不在判断case语句了,直到遇到break为止。 3、default不管位置如何,永远都是最后执行。(前提是没有遇到break语句) |
12break、continue、return的区别
Break;用于终止当前循环语句
Continue:用于跳过本次循环语句,执行下一次循环语句。
Return:结束本次函数,若函数有返回值,格式为:return 返回值;
13break在多层循环中,如果不仅结束内循环,还要终止外循环,怎么做
答:加标签。在C/C++中,goto语句用于跳出多层循环,在java中,可以用标签形式跳出多层循环。
程序代码:
标签;标识符和冒号组成。
Out:
for(inti=1;i<5;i++){
for(int j =0;j<6;j++){
if(j>2){
break out;
}
system.out.println(j);
}
}
结果为:0,1
14函数(方法)和数组的定义格式
14.1函数:修饰符 返回值类型函数名(形参)
{
代码;
return 结果;
}
14.2一维数组:数据类型[] 数组变量名 = new 数据类型[数组长度];//动态初始化
Int[] arr = new int[20];
数据类型[] 数组变量名 = {元素1,元素2};//静态初始化
int[] arr={1,2,3};
14.3二维数组:数据类型[][] 数组变量名 = new 数据类型[数组长度]数组长度];//动态初始化
Int[][] arr=newarr[2][3];
数据类型[][] 数组变量名 = {{元素1,元素2},{元素3,元素4}};//静态初始化
Int[][] arr= {{1,2,3},{5,6}};两行三列
14.4数组的特点:数组一旦初始化,长度就固定。
数组元素与元素地址连续的。
存储同一种数据类型。
第二章JAVA面向对象
1谈谈成员变量与局部变量的区别
从生命周期讲,成员变量是在创建对象时候存在,在对象销毁时候消失。在堆内存中。
局部变量是在方法被调用时执行到创建变量语句时候存在,一旦出了作用域就消失,在栈内存中
从初始值上讲,成员变量有初始值,而局部变量在使用之前必须赋值。
数据类型初始值:Int->0; float->0.0f; double->0.0; char->’’; Boolean->false;string等引用类型为null
从定义上讲:成员变量定义在方法之外,类之内。
局部变量定义在方法之内。
2谈谈static关键字、静态成员变量与非静态成员变量、静态函数与非静态函数的区别
2.1Static修饰的变量或者方法是共享的,每次创建对象对static修饰的数据改动的话就会影响到下一个对象的使用。
2.2静态成员变量与非静态成员变量
访问方式:静态成员变量可以用类名.变量名或者对象名.变量名访问。
非静态成员变量只能用对象名.变量名访问。
生命周期:静态成员变量随类被加载而存在。非静态成员变量随对象创建而存在。
存储位置:静态成员变量是存储在方发区中,非静态成员变量存储在堆内存中。
作用:静态成员变量是共享的,非静态成员变量不是共享的
2.3静态函数和非静态函数:
从访问方式:静态函数可以用类名.函数名()或者对象.函数名调用,
非静态成员函数只能用对象名.函数名访问。
注意:
1)文件加载时,静态方法和非静态方法都会加载到方法区中,只不过要调用到非静态方法时需要先实例化 一个对象
2)静态函数可以访问静态成员,不可以访问(可以定义)非静态成员,不可以定义静态成员。因为非静态成员随对象存在而存在,而静态成员是全局的,不可以定义在方法中。
3)静态函数不可以出现this、super关键字。因为this关键字是指调用该函数的对象,有静态函数时候可能没有对象。
疑问:在静态函数中,可以定义非静态变量吗?答:可以的。注意,定义的非静态变量是局部的。
3谈谈this关键字与super关键字
3.1this代表了所属函数的调用者对象,与调用该函数的对象地址是一样的。
this的作用:
1)在方法内部,当存在同名的成员变量和局部变量时候,this可以在方法内部访问成员变量的数据。
如果不存在同名,在方法里边访问成员变量,java编译器会自动在变量前边加上this关键字。
2)在构造方法中可以调用其他构造函数。格式为this(参数);
注意:this必须位于构造函数的第一句。
3.2super代表了父类资源的引用。
super的作用:
1)当父类和子类存在同名的成员变量时,在子类中用super访问父类的成员,super.变量。
2)在子类的构造方法中,用super(参数);调用父类的构造方法。如果不写,java编译器会自动在第一行添加super()构造方法,默认调用父类的无参的构造方法。
3.3this与super的区别:
1)使用前提不一样,super必须要有继承,用在子类中。
2)调用构造函数不同,this调用的是本类的构造函数,super是调用父类的构造函数。两者不可以一起用,因为都要处在构造函数第一行。
4final关键字、finally块和finalize方法
final:修饰基本数据类型,不能重新赋值,所以第一次就要赋值。
修饰引用类型变量,改变量不可以重新指向新的对象
修饰函数,不可以被重写(子类不能用了),但可以被子类使用。
修饰一个类,不能被继承。比如String等类是用final修饰的。
finally:使用前提:存在try块才可以使用,表示这段语句一定被执行,通常用来释放内存
格式:try{
代码
}catch(Exception e)finally{
代码
}
finalize():是Object的一个方法,用于垃圾回收器调用。可以重写该方法,用于释放内存
5关键字instance of是什么作用
使用条件:判断的对象必须存在继承或者实现接口。
作用是判断对象是否属于指定类别
格式:对象 instance of 类;如果对象是这个类的实例,就返回true。
6abstract和implements区别
6.1abstract特点:
1)若一个函数只有声明,要用abstract修饰,该函数为抽象函数
2)若类中有抽象函数,必须有abstrat修饰该类,抽象类也可以没有抽象函数。
3)抽象类自身不可以创建对象,他的构造函数是给子类创建对象初始化用的
4)非抽象类继承(extends)抽象类时,要实现父类所有抽象方法。
6.2abstract的非法组合。
1)不可以和private共同修饰一个方法。因为abstract修饰的方法子类必须要实现,而private修饰的方法子类不可以实现。
2)不可以与static共同修饰这个方法,因为用类名调用该方法毫无意义。
3)不可以与final修饰一个方法。因为final修饰的方法不可以被重写。
6.3Implements:是用来继承接口的。接口用
interface 接口名{}表示。
6.4接口类的特点:
1)接口的成员变量默认public staticfinal修饰(常量),接口的方法默认publicabstract修饰
2)接口没有构造方法,自身不可以创造对象
3)当非抽象类继承接口时,要实现全部方法。
格式 class 类名 implements 接口名{
}
总结:
接口和抽象类的相同点:
1)都不能被实例化
2)子类继承抽象类或者接口时候都要实现里边的抽象方法
7接口和普通的类的关系
A、抽象类实现接口时,可以不实现接口中的方法。但非抽象类实现接口,一定要实现接口中的方法。
B、一个类可以实现多接口
class A implements B,C{}
c接口可以继承多个接口,类不可以。(因为接口中的方法不具体)
8什么是构造函数,他和普通函数有什么区别?
构造函数是用来在对象实例化时初始化对象的成员变量。
构造函数与普通函数的区别:
函数名:构造函数必须与类名一样。而普通函数没有要求。
返回值类型:构造函数没有返回值,而普通函数有返回值。
调用形式:构造函数在创建对象实例化的时候由JVM调用的,而普通函数是用对象手动调用的
构造函数的注意点:
1、若一个类没有写构造函数,java编译器在编译成字节码文件时候会自动为该类加上无参的构造方法,修饰符类型和类的修饰符类型一样。
9构造代码块、静态代码块和局部代码块
代码块的作用是:给对象进行初始化,创建多少个对象执行多少次。
构造代码块:
格式:{代码},位于成员变量上
运行时间和顺序:当创建对象时,构造函数最后运行,成员变量和构造代码块按顺序运行。
静态代码块:
格式;static{代码},位于成员变量上
运行时间:在字节码文件加载到内存中运行,只会执行一次,通常用来初始化静态变量
局部代码块:放在方法中,目的是缩短局部变量的生命周期,意义不大。
10为什么需要Main函数
public static void main(String args[]){}
public:表示任何类和对象都可以访问
static;表示main()方法是一个静态方法
main:JVM识别的特殊方法名,是程序的入口。
args[]:是开发人员在命令行状态下与程序提供的一种手段。
11什么叫匿名对象,它的作用是什么
没有引用类型变量指向的对象叫匿名对象。
好处是:简化书写,JVM优先回收匿名对象
应用场景:当一个对象调用一个方法一次的时候,就不在使用。就可以使用匿名对象
注意事项:匿名对象不可以赋值,因为两个匿名对象不会是同一个。
12方法的重写与重载
12.1重写:一定要有继承关系,子类的函数和父类函数名一样,并且形参列表也一样。称重写。
注意:子类的修饰符必须大于或者等于父类的修饰符。(public等)
子类的返回值类型必须大于或者等于父类的返回值类型
子类抛出的异常必须比小于或者等于父类抛出的异常
12.2重载:一个类中出现同名的函数,并且形参列表不一致,称重载
注意:与返回值类型无关。
13什么是封装,什么是继承,什么是多态
A、封装是面向对象的一大特征,他的目的是保护成员属性,提高安全性。
实现过程是:用private修饰被封装的属性。
提供公共的方法set/get()方法设置/获取类中私有的属性
B、继承是通过extends关键字实现的。
格式: class A extends B{}
注意:1)父类中的私有化成员priate不能继承。
2)创建子类对象时候,肯定先实例化父类。所以一般父类一定要有无参的构造函数,在创建子类对象时,默认调用父类无参的构造方法。如果创建子类有参数的对象时,在子类的构造函数中第一行一定要写super(参数)。
C、多态:父类的引用类型变量指向了子类的对象或者接口的引用类型变量指向了接口的实现类对象。
使用多态的前提是必须要有继承或者实现。
格式:父类 对象名 = new 子类();
接口 对象名 = new 实现类();//调用的都是子类的方法,因为都是非静态的
注意:多态中,子父类出现同名的非静态成员函数,访问的是子类的函数。
多态中,子父类出现同名成员变量和静态成员函数,访问的是父类函数。
多态中,创建的对不可以访问子类特有的成员,要想访问,需要强制类型转换
14修饰符的比较
public:用在不同包中都OK;
protected:用在同一个包中的不同类或者不同包的子父类中
default:默认类型,可以用再同一个包中
private:只能用到同一个类中。
15成员内部类、局部内部类和匿名内部类的比较
15.1内部类:定义在一个类里边的类叫内部类,根据位置不同,可以分为成员内部类和局部内部类。内部类的字节码文件名:外部类$内部类
15.2成员内部类:在成员变量的位置上
1)如果成员内部类是静态的,访问格式为:外部类.内部类 变量 = new 外部类.内部类();
2)如果成员内部类是非静态的,访问格式为:外部类.内部类 变量 = new 外部类().内部类();
成员内部类注意事项:
A、可以直接访问外部类的成员变量,如果存在同名的成员变量,默认访问内部类的成员变量,可以通过“外部类.this.成员变量名”来访问。
B、如果成员内部类的是私有的类,则在外部类中提供方法去访问和设置内部类。在其他类中就不可以访问了。(javabean思想)
C、如果成员内部类中一旦出现静态的成员变量,则成员内部类也要用static修饰,就是静态内部类了。因为,如果成员内部类没有static修饰,那么就访问不到静态的成员变量了。这里和外部类无关。注意:非静态的类也可以存在静态变量。
15.3局部内部类:在方法里
访问格式为:正常调用外部类的方法。外部类.方法名();
注意:在局部内部类中访问方法里边的局部变量,这个局部变量必须是final修饰。因为,如果不是final修饰,那么方法执行完毕,就会消失,那么此时的局部内部类的对象还没有消失,就会跟尴尬。
15.4匿名内部类(本质是一个子类对象)
只能使用一次,它通常用来简化代码编写
使用前提是:一定要有继承关系或者与接口有实现关系。
访问格式;new 父类名/接口名(){
}
注意点:
a·匿名内部类不能有构造方法。
b·匿名内部类不能定义任何静态成员、方法和类。//只使用一次
c·匿名内部类中只能重写父类方法,不可以自己写方法(因为出现一次,访问不到)
d·因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
16什么时候用import语句
当两个类不在同一个包中需要使用。其中,java.lang是默认导入的
第三章 异常与常规的API类
1异常类的种类有哪些,列举一两个主要的方法
1.1java中提供了异常的基类Throwable类(不是接口),它有两个子类,为Error类和Exception类,代表的是错误和异常。异常类都以Exception结尾。
1.2Throwable三个常用的方法:
(String)toString():返回当前异常对象的完整类名和病态信息。
(String)getMessage():返回创建Throwable类传入的字符串信息(有参构造方法中的参数)
(String)printStackTrace():打印异常的栈信息:告诉用户哪一行错误和错误的类等
2异常分为运行时异常和编译时异常,他们的区别是什么
Ø 从继承的类上讲,,运行时异常都是RuntimeException类以及其子类,其余的都是编译时异常,比如IOException(读文件异常)
Ø 从方法抛出处理上讲,如果方法内部抛出一个运行时异常,则在该方法上不用声明异常类型,调用这个方法时候也可以不处理运行时异常。如果方法内部抛出编译时异常,在该方法上必须要声明异常类型,调用这个方法的时候必须要处理或者继续抛出这个编译时异常。
运行时异常为什么待遇好,因为运行时异常可以避免。比如除数是0,空指针等等
3异常的两种处理方式
方式一:在方法中捕获异常
处理格式:
try{
可能发生的异常代码;
}catch(捕获的异常类型变量名){
处理的代码;
}
注意事项:
1、如果try中出现异常代码,则try里边的代码不会执行,直接执行catch里边的内容
2、try块后边可以跟多个catch块,catch块处理的异常必须从小到大。或者(小->小->大)
3、因为try-catch块只可以处理一个try中的异常,所以必须在catch写第一个错误的捕获异常对象和其父类。
方式二:在方法中抛出异常
用throw和throws关键字,throw用于抛出,throws用于声明。
格式:throw new 异常类;
注意事项:
1、方法内部抛出异常对象,则throw语句后边不执行。所以一个方法只可以抛出一个异常。
2、若方法抛出的是运行时异常,该方法不用用throws声明,调用者也不用处理;若方法抛出的是编译时异常,则调用者必须处理或者继续抛出。
3、如果一个编译时异常的方法在main()函数里,main函数也可以抛出,那么就给JVM了,JVM会调用这个异常对象的printStackTrace()方法。
4如何定义一个异常类
出现原因:因为生活中很多不正常的情况,需要自定义异常类。
做法:该类继承Exception类就可以了
class 类名 extends Exception{}
5finally块的作用
finally:使用前提:存在try块才可以使用,表示这段语句一定被执行,通常用来释放内存
格式:try{
代码
}catch(Exception e)finally{
代码
}
6Object类的经典方法
Object是所有类的超级父类
(String)toString()方法:返回一个字符串,格式为完整类型@对象的哈希码(理解为地址);一般可重写。
注意:system.out.println(对象名)等价于system.out.println(对象名.toString())
(boolean)equals(Object a):比较的是两个对象的地址,一般都重写。
(int)hashCode():返回该对象的哈希值。(理解为地址)
一般如果重写equals()方法必须要重写hashCode()方法
7String类字符串创建的内存分析(存储机制)
情况1:String string1=”hello”
检查字符串常量池中存不存在这个字符串对象,如果不存在就创建,然后将字符串“hello”的地址传送到栈内存中的引用类型变量string1中,如果存在,就直接将字符串“hello”的地址传送到栈内存中的引用类型变量string1中。
情况2:String string2 = new String(“hello”);
检查字符串常量池中存不存在这个字符串对象,如果不存在就创建,然后在堆内存中在创建一个字符串对象(copy),吧堆内存中的字符串对象的地址传给栈内存中的引用类型变量string2中。如果存在,就直接在堆内存中在创建一个字符串对象(copy),吧堆内存中的字符串对象的地址传给栈内存中的引用类型变量string2中
8String类中的equals()方法和==比较
String类吧equals()方法重写了,比较的是内容。而==号是比较的是对象的地址。==两边的对象必须是同类型的。
9Sting类和StringBuffer类的区别
Sting是不可变的类,创建后不可以更改,内容一旦发生改变,就会马上创建新的对象。
StringBuffer类是字符串缓冲类,它充当字符串容器,String字符串修改如下;
publicstaticvoid main(String[] args) { String a = "hello"; StringBuffer string1 = new StringBuffer(a); StringBuffer string2 = string1.append("bob"); System.out.println(string1.hashCode()); System.out.println(string2.hashCode()); } 结果是一样的。说明地址一样 |
10比较StringBuffer类和StringBuilder类
相同点:两个类的方法都一样,都是字符串缓冲类,当用类的无参的构造函数创建对象时候,默认初始容量是16字节,底层是依赖字符数组存储,如果长度不够就增加一倍加2;
不同点:StringBuffer是线程安全的,效率不高
StringBuilder是线程不安全的,效率高。一般用这个,除非大量操作字符串,且是多线程的。
11列举System类的常用方法。
系统类,没有构造方法,所以不能实例化。里边有静态方法可以获取对象
常用的方法:
(long)currentTimeMillis();获取系统的时间
(void)exit(int a);退出jvm,参数为0表示正常退出。为1表示异常退出。
(void)gc():建议垃圾回收期回收。只是建议。
(void)arrayCopy(a,1,b,2,3);吧a数组的从第一个元素开始复制三个元素给b数组的第二个位置到第四个位置
(properties)getProperties():获取系统的所有属性
12列举日期类Date、Calendar和日期格式化类SimpleDateFormat类
1、Date类访问年月日已经过时。现在用Canlendar类,Canlendar类的构造方法是protected修饰的,不同包可以用它的方法访问。
(Canlendar)getInstance():获取Canlendar对象
(long)get(常量),用于获取年月日时分秒。
get(Canlendar.YEAR);等等
缺点是:不可以按照指定的格式输入年月日。
2、SimpleDateFormat类
无参的构造方法SimpleDateFormat()使用默认的格式创建对象
有参的构造方法,使用指定的格式SimpleDateFormat(“yyyy年MM月dd日 HH:mm:ss”)
(String)format(Dataa)将Data类转换为指定格式
(Data)parse(String a)将指定格式的字符串转化为Data类对象,字符串应该和构造方法里边的字符串一样
13解释Math类的方法
14解释Runtime类
exec(String a)字符串是执行文件的路径,这个方法调用者一定要抛出对象
第四章 集合和线程
1说说集合与数组的不同点
集合是可以存储任意类型的对象数据的集合容器,并且长度可以变化的。
数组只能存储同一种类型的数据,而且一旦初始化,长度就固定。元素地址直接是连续的。
2说说集合的类的框架
---|Collection:(接口) 单列集合
---|List(接口): 有序, 可重复
---|ArrayList: 数组实现, 查找快, 增删慢
由于是数组实现, 在增和删的时候会牵扯到数组
增容, 以及拷贝元素. 所以慢。数组是可以直接
按索引查找, 所以查找时较快
---|LinkedList: 链表实现, 增删快, 查找慢
由于链表实现, 增加时只要让前一个元素记住自
己就可以, 删除时让前一个元素记住后一个元
素, 后一个元素记住前一个元素. 这样的增删效
率较高但查询时需要一个一个的遍历, 所以效率
较低
---|Vector: 和ArrayList原理相同, 但线程安全, 效率略低
和ArrayList实现方式相同, 但考虑了线程安全问
题, 所以效率略低
---|Set(接口): 无序, 不可重复
---|HashSet:线程不安全,存取速度快。底层是以hash表实现的。
---|TreeSet:红-黑树的数据结构(左小右大),默认对元素进行自然排序。如果 没 I 有自然排序,要处理
---| Map: 键值对(双例集合,键不可以重复)
---|HashMap采用哈希表实现,所以无序
---|TreeMap可以对键进行排序
---|HashTable
使用各种单列集合的条件:
List
| 如果我们有序, 可重复, 使用List. 如果查询较多, 那么使用ArrayList 如果存取较多, 那么使用LinkedList 如果需要线程安全, 那么使用Vector |
Set
| 如果我们无序,不可重复, 使用Set. 如果我们需要将元素排序, 那么使用TreeSet 如果我们不需要排序, 使用HashSet, HashSet比 TreeSet效率高. 如果我们需要有序, 又要不可重复, 那么使用LinkedHashSet |
3collection接口的基本方法,这是所有集合都可以用的方法。
注意:list接口有特有方法,而set接口没有特有方法。
3.1增加
(boolean)add(Object a):增加对象到集合末尾处
eg:add(1)是错误的,必须写成add(Integer(1));
(boolean)addAll(Collection c)把集合C中的元素添加到调用该方法的集合中
3.2删除
Ø (void)clear():清空集合中的元素。
Ø (boolean)remove(Objecto);删除集合中的元素
Ø (void)removeAll(Collection c)删除集合中与c集合一样的元素。
Ø (void)retainAll(Collection c)只保留集合中与c集合一样的元素。
3.3查看
(int)size():查看元素的个数
3.4判断
Ø (boolean)isEmpty()集合为空,则为true。
Ø (boolean)contains(Objecto)判断集合中是否存在指定的元素,依赖的是equals()方法比较
Ø (boolean)containsAll(Collectionc)判断集合中是否存在集合C中所有元素,依赖equals()方法比较
3.5迭代
Ø (Object[] o)toArray():将集合中的元素存储到Object数组中返回。
Object[]里边的元素只可以使用Object[]数组接收,如果用其他类型接收,就要类型转换。
Ø (Iterator)iterator():返回的是Iterator接口的实现类,该实现类可以对调用该方法的集合操作,创建一个迭代器,有一个指针指向集合中的第一个元素。
Iterator接口实现类里边的方法有:
Ø (boolean)hasNext():如果有元素可以遍历,没有就会返回false。(判断作用)
Ø (E e)next():获取元素,按索引值从低到高获取,返回值是集合中的元素。将集合中的元素放到迭代器Iterator中,先取元素,在指针下移(加1)。
Ø (void)remove():移除迭代器中最后一次返回的元素。对集合有影响。
4List接口方法
List接口肯定包括collection的全部方法,因为List类是有序的,所以很多特有的方法都是带有索引值的
4.1、list特有的添加方法
(void)add(int index, E e):将集合元素e添加到指定位置。
(void)addAll(int index,Collectionc)将集合C中的元素放入调用方法的集合的index位置后。原来的元素会被覆盖掉
4.2特有的获取方法
(E e)get(int index)根据索引值获取集合元素
(int)indexof(E e)找出第一次元素出现的索引值。
(int)lastIndexof(E e)找出元素最后一次出现的索引值。
(List list)subList(int index1,int index2)从原集合中取[index1,index2)的元素给新的集合list。包头不包尾
4.3修改
(void)set(int index,E e),把index位置的元素变为e。
4.4迭代
(ListIterator)listIterator():返回 ListIterator接口的实现类。
ListIterator特有的方法:
(boolean)hasPrevious():判断是否存在上一个元素。
(E e)Previous():指针先上移(减一),在取指针中的元素。遍历的时候不可以从第一个遍历了,
(void)add(E e):元素插入当前指针的位置,然后指针加1.
(void)set(E e):替换迭代器中最后一次返回的元素。
5说说迭代器的注意事项
1、迭代器一旦创建,到不在使用,都称为遍历元素,一旦创建对象,就会有一个指针指向调用者的集合中的第一个元素,迭代器类的添加和删除都会改变集合的元素。
2、迭代器在遍历元素的时候,不可以用集合对象改变元素的个数,但是可以替代元素。如果要增加和删除,只能用迭代器的方法操作。
6用三种方式遍历List集合的元素。
方法一:用get()方法 |
for(int i=0;i<list.size();i++){ system.out.ln(list.get(i)+”,”) } |
方法二:用hasNext()和next()方法 |
publicstaticvoid main(String[] args) { ArrayList<Integer> a = new ArrayList<Integer>(); for(int i=0;i<9;i++){ a.add(i); } Iterator iterator = a.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()+","); } } //结果是012345678 |
方式三:使用hasPrevious()和previous()逆序排序 |
publicstaticvoid main(String[] args) { ArrayList<Integer> a = new ArrayList<Integer>(); for(int i=0;i<9;i++){ a.add(i); } ListIterator iterator = a.listIterator(); while(iterator.hasNext()){ System.out.println(iterator.next()+","); } while(iterator.hasPrevious()){ System.out.println(iterator.previous()+","); } } //结果是01234567887654321 |
7比较List接口下的ArrayList、LinkedList、Vector的特点与区别
7.1、ArrayList:特点是查询速度快,增删慢,并且线程不同步。操作效率高
查询快:因为ArrayList实现类底层维护了Object数组(默认容量是10字节)存储数据的,所以元素是连续的,查询时候只需要快速改变指针就可以
增删慢:因为增加一个元素时,会创建一个新的ArrayList对象,先判断数组长度,不够自动加半倍,然后把旧的元素全部复制过来,在添加新的元素。删除某个元素时,要把后边的元素全部补齐。
7.2、Vector;和ArrayList存储原理一样,只是是线程同步的(安全),操作效率低。既然被同步了,多个线程就不可能同时访问vector中的数据,只能一个一个地访问,所以不会出现数据混乱的情况,所以是线程安全的。但不推荐使用。效率太低,现在可以通过Collections.synchronizedListt方法拿同步的List,于是Vector就废了
7.3LinkedList:特点是查询速度慢,增删快
因为底层是链表结构实现,每个元素都是由一个元素加下一个元素的内存地址组成,所以查询的时候只能一个个查,删除快是因为如果删除一个元素时候,只需要上一个元素的访问内存地址改变就可以了。
LinkedList特有的方法:
(void)addFirst()添加元素到第一个位置。
(void)addLast()添加元素到最后一个位置。//和Collection接口的add()一样
(E e)getFirst():获取第一个元素
(E e)getLast():获取最后一个元素
(E e)removeFirst():删除第一个元素
(E e)removeLast():删除最后一个元素
(void)push(E e)添加元素到首位置。(和addFirst()一样)模拟栈的先进后出
(E e)pop():将元素的首位置移出(和removeFirst()一样)模拟栈的先进后出
(boolean)offer(E e)添加最后一个元素(和addLast()一样)模拟堆的先进先出
(E e)poll():获取元素第一个元素(和addLast()一样)模拟堆的先进先出
8比较set接口下的HashSet类和TreeSet类的原理
8.1HashSet底层使用哈希表来支持,特点是存取速度快。
存储原理是当添加元素时,调用元素的hashCode()方法计算该元素在哈希表中的存储值,
如果该存储地址有其他元素,那么在比较equals()方法,如果equals()方法(不重写是比较地址)返回的是false,那么允许添加。否则不允许添加。
如果该存储地址没有其他元素,那么添加。
注意:集合元素可以是null,但只能放入一个null
HashSet要求放入的对象必须实现HashCode()方法,放入的对象,是以hashcode码作为标识的,而具有相同内容的String对象,hashcode是一样,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例。
8.2TreeSet底层实现了红黑树排序。(元素左小右大)
往TreeSet里边存储数据的时候,如果元素本身具备了自然顺序的特性,那么按元素的自然特性排序,如果不具备,不可以存储两个,因为无法比较,运行时候会出错,需要处理。处理方式如下:
方式一:
S1先定义一个比较器类(实现Comparator接口),然后重写(int)compare()方法,吧比较规则写在这个方法里.如果返回正数,则大于指定的对象,如果返回负数,则小于指定的对象。
S2在创建TreeSet的时候传入这个定义好的比较器类(构造方法中有)。
方式二:吧添加的元素实现Comparator接口,然后重写(int)compareTo(E e)方法,吧比较规则写在这个方法里.如果返回正数,则大于指定的对象,如果返回负数,则小于指定的对象。元素的自然排列是自己这么做了
问题:为什么使用TreeSet存入字符串,字符串默认输出是按升序排列的?
因为字符串实现了一个接口,叫做Comparable 接口.字符串重写了该接口的compareTo 方法,所以String对象具备了比较性.
9Map接口的方法
Map接口的存储数据的形式是键值对形式存在的。键是不可以重复的,值可以重复。
9.1、添加:
1、Vput(K key, V value)
(可以相同的key值,但是添加的value值会覆盖前面的,返回值是前一个,如果没有就返回null)
2、putAll(Map<?extends K,? extends V> m)
从指定映射中将所有映射关系复制到此映射中。
9.2、删除
1、remove() 删除关联对象,指定key对象
2、clear() 清空集合对象
9.3、获取
1:value get(key); 可以用于判断键是否存在的情况。当指定的键不存在的时候,返
回的是null。
9.3、判断:
1、booleanisEmpty() 长度为0返回true否则false
2、booleancontainsKey(Object key) 判断集合中是否包含指定的key
3、booleancontainsValue(Object value) 判断集合中是否包含指定的value
9.4、长度:
Int size()
9.5、迭代:
方式一:Set<K>keySet()方法
将Map转成Set集合(keySet()),通过Set的迭代器取出Set集合中的每一个元素(Iterator)就是Map集合中的所有的键,再通过Map中的get方法获取键对应的值。
Set<Integer> ks = map.keySet(); Iterator<Integer> it = ks.iterator(); while (it.hasNext()) { Integer key = it.next(); String value = map.get(key); System.out.println("key=" + key + " value=" + value); } } |
方式二:Collection<V> values()
通过values 获取所有值,不能获取到key对象。然后迭代。
Collection<String> vs = map.values(); Iterator<String> it = vs.iterator(); while (it.hasNext()) { String value = it.next(); System.out.println(" value=" + value);
|
方式三:Set<Map.Entry<K,V>>entrySet()
将map集合中的键和值映射关系打包为一个对象,就是Map.Entry,将该对象存入Set集合,Map.Entry是一个对象,那么该对象具备的getKey,getValue获得键和值。
Set<Map.Entry<Integer, String>> es = map.entrySet();//es就是set类 Iterator<Map.Entry<Integer, String>> it = es.iterator(); while (it.hasNext()) { // 返回的是封装了key和value对象的Map.Entry对象 Map.Entry<Integer, String> en = it.next(); // 获取Map.Entry对象中封装的key和value对象 Integer key = en.getKey(); String value = en.getValue(); System.out.println("key=" + key + " value=" + value); }
|
10HashMap和TreeMap和HashTable的原理
HashMap的哈希表是键值对存储的。添加的原理会调用键的hashCode()方法,比较规则和hashSet一样
值得注意的是,当调用键的equal()方法比较的时候,如果是true,那么值被替换。键还是不变的。可以放入空的键和值。只能放入一个。
HashTable底层也是依赖哈希表实现的,线程安全,每个方法基本都是安全的,当然效率低
TreeMap添加原理以键为标准,原理和TreeSet一样
11集合工具类Collections(了解)
11.1Collections和Collection的区别
Collections是一个工具类,用来操作集合对象的。Collection是单列集合的跟接口
11.2工具类提供的方法有:
Collections:常见方法:
1, 对list进行二分查找: 前提该集合一定要有序。 int binarySearch(list,key); //必须根据元素自然顺序对列表进行升级排序 //要求list 集合中的元素都是Comparable 的子类。 int binarySearch(list,key,Comparator); 2,对list集合进行排序。 sort(list); //对list进行排序,其实使用的事list容器中的对象的compareTo方法 sort(list,comaprator); //按照指定比较器进行排序 3,对集合取最大值或者最小值。 max(Collection) max(Collection,comparator) min(Collection) min(Collection,comparator) reverse(list); 5,对比较方式进行强行逆转。 Comparator reverseOrder(); Comparator reverseOrder(Comparator); 6,对list集合中的元素进行位置的置换。 swap(list,x,y); 7,对list集合进行元素的替换。如果被替换的元素不存在,那么原集合不变。 replaceAll(list,old,new); 8,可以将不同步的集合变成同步的集合。这是为什么HashTable和Vector淘汰的原因 Set synchronizedSet(Set<T> s) Map synchronizedMap(Map<K,V> m) List synchronizedList(List<T> list) 9. 如果想要将集合变数组: 可以使用Collection 中的toArray 方法。注意:是Collection不是Collections工具类 传入指定的类型数组即可,该数组的长度最好为集合的size。
|
12什么叫线程和进程
进程是正在执行的程序,而线程是程序在执行过程中,执行程序代码的一个执行最小单元。线程负责代码的执行。
13java应用程序最少有几个线程?
最少有两个线程,分别是主线程,负责main()方法的代码执行;垃圾回收器线程,负责回收垃圾。
14实现多线程有两种方式,请简要说明
方式一:继承Thread类
首先定义一个类继承Thread类,然后重写(void)run()方法。最后创建对象调用该类的start()方法
注意:
1、重写run()方法的目的是:
每个线程都有自己的任务代码,自定义线程的任务代码就是run()方法中的代码。主线程的代码就是main方法中的所有代码。
2、线程一旦用start()方法调用,就会开启线程,执行run()方法,因此run()方法不可以直接调用。如果调用run()方法,就相当于普通方法。
方式二:实现Runnable实现类。(推荐)
首先定义一个类实现Runnable接口,并重写run()方法,然后创建这个实现类的对象。
然后创建一个Thread对象,把实现类的对象作为实参传入传递(构造方法有参数),调用Thread类的start方法开启线程
注意:
1、实现Runnable接口的实现类对象不是线程对象,只有继承Thread类才是线程类。之所以这样做,是因为java是单继承,多实现的。
方式二: publicclass Demo1 { publicstaticvoid main(String[] args) { MyRun my = new MyRun(); Thread t1 = new Thread(my); t1.start(); for (int i = 0; i < 200; i++) { System.out.println("main:" + i); } } } class MyRun implements Runnable { publicvoid run() { for (int i = 0; i < 200; i++) { System.err.println("MyRun:" + i); } } } |
15多线程的好处有哪些?
好处:解决了进程可以同时执行多个任务的问题。提高了资源的利用率,但没有提高效率。
坏处:增加CPU负担,降低一个进程中线程的执行效率。
16线程类Thread的常用方法
Ø Thread(String name) 初始化线程的名字
Ø getName() 返回线程的名字
Ø setName(String name) 设置线程对象名
Ø sleep() 是静态方法,用父类调用。Thread.sleep();
线程睡眠指定的毫秒数。sleep()使用的方法要捕获处理异常,不可以抛出,因为sleep()在run()里边,Thread中的run()方法没有抛出异常,所以子类也不行
Ø getPriority() 返回当前线程对象的优先级 默认线程的优先级是5
Ø setPriority(intnewPriority) 设置线程的优先级 虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现(最大的优先级是10 ,最小的1 ,默认是5)。
Ø currentThread() 返回CPU正在执行的线程的对象
17什么叫锁?线程安全的解决方法?这些方法有弊端吗?
17.1锁:任何对象都可以成为锁对象,锁对象必须共享唯一的。否则无效。
什么是锁对象?
每个java对象都有一个锁对象.而且只有一把钥匙.
因为一个对象只有一个锁,所有如果一个线程获得了这个锁,其他线程就不能获得了,直到这个线程释放(或者返回)锁。也就是说在锁释放之前,任何其他线程都不能进入同步代码(不可以进入该对象的任何同步方法,但可以进入非同步方法)。释放锁指的是持有该锁的线程退出同步方法,此时,其他线程可以进入该对象上的同步方法。
1:只能同步方法(代码块),不能同步变量或者类
2:每个对象只有一个锁
3:不必同步类中的所有方法,类可以同时具有同步方法和非同步方法
4:如果两个线程要执行一个类中的一个同步方法,并且他们使用的是了类的同一个实例(对象)来调用方法,那么一次只有一个线程能够执行该方法,另一个线程需要等待,直到第一个线程完成方法调用。
5:如果类同时具有同步方法和非同步方法,那么多个线程仍然可以访问该类的非同步方法。
同步会影响性能(甚至死锁),优先考虑同步代码块。
6:如果线程进入sleep() 睡眠状态,该线程会继续持有锁,不会释放。
17.2方式一:同步代码块:(推荐使用,灵活)
synchronized(锁对象){
同步的代码;
}
方式二:同步函数
使用synchronized修饰该函数
要点:如果是非静态函数同步函数的锁对象是this对象(自动获取当前实例的锁);
如果是静态同步函数,锁对象为所属类的字节码文件(类名.class或者对象.getClass()获取
同步函数的锁对象是固定的,不是自己指定的
17.3会造成死锁现象,无法避免
出现死锁的根本原因是存在两个或两个以上的线程共享资源,资源之间互相约束。
18线程之间的通信(通讯)
通讯:当一个线程完成任务时候,通知其他线程去执行别的任务。
以下三个方法都是锁对象的方法。
wait():告诉当前线程放弃监视器(锁)并进入阻塞状态,直到其他线程持持有了相同的监视器(锁)并调用notify()才可以唤醒。
notify():唤醒持有同一个监视器(锁)中调用wait的第一个线程,注意:被唤醒的线程是进入了可运行状态。等待cpu执行权。
notifyAll():唤醒持有同一监视器中调用wait的所有的线程。
注意:
A、wait(),notify(),notifyAll()都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。
B、这些方法一定要定义在Object类中
因为这些方法在操作线程时,都必须要标识他们所操作线程持有的锁,只有同一个锁上的被等待线程,可以被统一锁上notify唤醒,不可以对不同锁中的线程进行唤醒,就是等待和唤醒必须是同一个锁。而锁由于可以使任意对象,所以可以被任意对象调用的方法定义在Object类中
C、wait()和sleep()有什么区别?
wait():释放资源,释放锁。是Object的方法
sleep():释放资源,不释放锁。是Thread的方法
D、定义了notify为什么还要定义notifyAll,因为只用notify容易出现只唤醒本方线程情况,导致程序中的所有线程都在等待。
可以通过设置一个标记,表示数据的(存储空间的状态)例如,当消费者读取了(消费了一次)一次数据之后可以将标记改为false,当生产者生产了一个数据,将标记改为true。 ,也就是只有标记为true的时候,消费者才能取走数据,标记为false时候生产者才生产数据。 消费者类:一直运行Person中的read()方法。 生产者类: publicclass Demo10 { publicstaticvoid main(String[] args) { Person p = new Person(); Producer pro = new Producer(p);//生产者 Consumer con = new Consumer(p);//顾客 Thread t1 = new Thread(pro, "生产者"); Thread t2 = new Thread(con, "消费者"); t1.start(); t2.start(); } }
// 使用Person作为数据存储空间 class Person { String name; String gender; booleanflag = false; publicsynchronizedvoid set(String name, String gender) { if (flag) { try { wait(); } catch (InterruptedException e) {
e.printStackTrace(); } } this.name = name; this.gender = gender; flag = true; notify(); } publicsynchronizedvoid read() { if (!flag) { try { wait(); } catch (InterruptedException e) {
e.printStackTrace(); } } System.out.println("name:" + this.name + "----gender:" + this.gender); flag = false; notify(); } }
// 生产者 class Producer implements Runnable { Person p; public Producer() { }
public Producer(Person p) { this.p = p; }
@Override publicvoid run() { int i = 0; while (true) { if (i % 2 == 0) { p.set("jack", "man"); } else { p.set("小丽", "女"); } i++; } } }
// 消费者 class Consumer implements Runnable { Person p; public Consumer() { } public Consumer(Person p) { this.p = p; } @Override publicvoid run() { while (true) { p.read(); } }
} 运行结果: name:jack----gender:man name:小丽----gender:女 name:jack----gender:man name:小丽----gender:女 name:jack----gender:man name:小丽----gender:女 |
19线程的生命周期
创建(new 线程对象)->可运行状态->运行状态->死亡状态
注意:运行状态下的线程一旦执行了sleep()或者wait()方法,线程就会进入临时阻塞状态。如果是调用sleep()方法是进入临时睡眠,如果线程超过临时睡眠时间,进入可运行状态。如果调用wait()方法,必须要由其他线程用notify()方法唤醒线程才可以重新进入可运行状态。
20什么叫守护线程
守护线程又叫后台线程,通俗讲,守护线程是非守护线程的保姆,和非守护线程基本一样,
如果用户线程退出的话,守护线程自动退出,
守护线程的设置,在用户线程调用start()方法之前调用线程对象的setDaemon(boolean a)方法,如果是true,这个进程就是守护线程,如果是false,就不是。默认不是。
21如何停止线程
停止线程的最好方式就是让他自动运行结束。run()方法结束
一般都用标志位去控制。
如果线程处于等阻塞状态,通过notify()方法和标志位控制。
22join()方法的作用
当A线程执行到了B线程Join方法时A就会等待,等B线程都执行完A才会执行,Join可以用来临时加入线程执行。使A、B线程顺序执行,而不是并序执行
第五章IO文件管理和网络编程
1管理文件和目录的类是什么
File类用来管理文件和文件夹。
常用方法如下:
1、构造方法(3个): 1.1new File(String pathname); 通过将给定路径来创建一个新File实例。 1.2new File(String parent, String child); 根据parent路径名字符串和child路径名创建一个新File实例。 parent是指上级目录的路径,完整的路径为parent+child. 1.3new File(File parent, String child); 根据parent抽象路径名和child路径名创建一个新File实例。 parent是指上级目录的路径,完整的路径为parent.getPath()+child. 说明:
2、创建方法(4个) Ø (boolean)createNewFile() //需要抛出异常, 在指定位置创建一个空文件,成功就返回true,如果已存在就不创建然后返回 false 说明: File file = new File(“F:\\a.txt”);//此时还没有a.txt File. createNewFile();//创建成功 Ø (boolean)mkdir() 在指定位置创建文件夹,这只会创建最后一级目录,如果上级目录不存在就抛异常。 说明: File file = new File(“F:\\a.txt”);//此时还没有a.txt File.mkdir();//创建成功,创建出a.txt的文件夹
Ø (boolean)mkdirs() 在指定位置创建多个文件夹 Ø renameTo(File dest) 重命名文件或文件夹,也可以操作非空的文件夹,文件不在同一个路径时相当于文件的剪切,剪切时候不能操作非空的文件夹。移动/重命名成功则返回true,失败则返回false。
3、删除方法(2个) Ø delete() 删除文件或一个空文件夹,如果是文件夹且不为空,则不能删除,成功返回true,失败返回false。 Ø deleteOnExit() //JVM虚拟机退出时才删除 在虚拟机终止时,删除此抽象路径名表示的文件或目录, 4、判断方法(5个) exists() 文件或文件夹是否存在。 isFile() 是否是一个文件,如果不存在,则始终为false。 isDirectory() 是否是一个目录,如果不存在,则始终为false。 isHidden() 是否是一个隐藏的文件或是否是隐藏的目录。 isAbsolute() 测试此抽象路径名是否为绝对路径名。 5、获取方法() Ø (String)getName() 获取文件或文件夹的名称,不包含上级路径。 Ø (String)getPath() 返回绝对路径,可以是相对路径,但是目录要指定 Ø (String)getAbsolutePath() 获取文件的绝对路径,与文件是否存在没关系 Ø (int)length() 获取文件的大小(字节数),如果文件不存在则返回0L,如果是文件夹也返回0L。 Ø (String)getParent() 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回null。 (long)lastModified() 获取最后一次被修改的时间。 6、文件夹相关:(5) Ø staic File[] listRoots() 列出所有的根目录(Window中就是所有系统的盘符) Ø (String)list() 返回目录下的文件或者目录名,包含隐藏文件。对于文件这样操作会返回null。 Ø (String)list(FilenameFilter filter) 返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。 Ø (File[] file)listFiles() 返回目录下的文件或者目录对象(File类实例),包含隐藏文件。对于文件这样操作会返回null。 Ø (File[] file)listFiles(FilenameFilter filter) 返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。 |
2IO流的实现机制是什么
当我们需要对文件进行读取操作时候,需要用到IO流,按照处理单位分,分为字节流和字符流,按数据留下分,分为输入流和输出流(相对程序而言)、共有四种流,加上缓冲的话,共八种。
2.1输入字节流
A、输入字节流的基类是InputStream(抽象类),子类有FileInputStream
B、输入字节流的方法:
Ø (int)read():读取一个字节数据,把读到的数据返回,如果读完,则返回-1;
Ø (int)read(byte[] b)读b.length个字节数据放到byte数组中,返回读到的个数。若读完,返回-1
Ø (void)close():关闭资源,如果不关闭,那么别的程序无法对资源文件进行其他操作
C、读取文件数据的步骤
先找到目标文件然后建立目标通道
读取文件数据,最后关闭资源
privatestaticvoid showContent5()throws IOException { File file = new File(“f:\\a.txt”) FileInputStream fis = new FileInputStream(file); byte[] byt = newbyte[1024]; int length = 0; while(length=fis.read(byt)!=-1){ String content = new String(byt,0,length); system.out.println(content); } Fis.close(); } 本次采用缓冲数组+循环类的方式读取,读取a.txt的文件内容
|
2.1.1缓冲输入字节流
A、因为读取文件时,用缓冲数组读取效率高,而输入字节流自定义缓冲数组,所以可以吧输入字节流转换为缓冲输入字节流BufferedInputStream类,内部维持了8KB字节的数组。
方法名:
|
|
|
|
B、读取文件的步骤
先找到目标文件然后建立目标通道
读取文件数据,最后关闭资源
privatestaticvoid showContent5()throws IOException { File file = new File(“f:\\a.txt”) FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream (fis); int content = 0; while((content=bis.read())!=-1){ system.out.println((char)content); } bis.close(); } |
2.2输出字节流
A、输出字节流的基类是OutputStream(抽象类),子类有FileOutputStream
B、输出字节流的方法:
Ø (void)wirte(int b):写出一个字节(低八位)数据
Ø (void)write(byte[] b) 将b.length个字节写出
Ø (void)write(byte[] b,intindex,int length):将b这个字节数组从index开始,写出length长度
Ø (void)close():关闭资源,如果不关闭,那么别的程序无法对资源文件进行其他操作
C、FileOutStream类的注意事项
如果目标文件不存在,就会自动创建目标对象。如果存在,会先清空目标对象的数据,然后在写入
如果想不清空目标文件的原有数据,那么在构造FileOutStream类的时候用FileOutStream(File file,true)
D、写出文件数据的步骤
先找到目标文件(自动创建,)然后建立数据通道
写出数据,关闭资源
privatestaticvoid showContent5()throws IOException { File file = new File(“f:\\a.txt”) FileOutputStream fos = new FileOutputStream(file); Byte[] buf = “java”.getBytes(); fos.write(buf); fos.close(); } |
2.2.1缓冲输出字节流
A、因为写出文件时,FileOutputStream类的write方法需要字节数组,所以把FileOutputStream类转换BufferedOutputStream类,不需要数组了。内部维持了8KB字节的数组。
B、注意事项:
使用BufferedOutputStream类写数据,write()方法吧数据写到内部的字节数组中,在硬盘上看不到,需要刷新,或者close()方法,或者内部的字节数组填满为止。
C方法
|
|
|
|
|
|
C、写出文件的步骤
先找到目标文件然后建立目标通道
写入文件数据,最后关闭资源
privatestaticvoid showContent5()throws IOException { File file = new File(“f:\\a.txt”) FileOutputStream fos = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream (fos); bos.write(“java”.getBytes()); bos.flush();//刷新数据 bos.close(); } |
2.3输入字符流
因为字节流只能处理字节,如果是汉语占两个字节就不好处理了。所以用字符流。
A、输入字符流的基类是Reader(抽象类),一个子类是FileReader。
B、方法:
Ø (int) read():
读取一个字符。返回的是读到的那个字符。如果读到流的末尾,返回-1.
Ø int read(char[]):
将读到的字符存入指定的数组char[]中,返回的是读到的字符个数,也就是往数组里装的元素的个数。如果读到流的末尾,返回-1.
Ø close()
进行资源的释放
C、读取文件的步骤
找到目标文件,然后建立数据通道
读取数据,关闭资源
publicstaticvoid readFileByReader()throws Exception { File file = new File(“f:\\a.txt”); Reader reader = new FileReader(file); char[] buf = new char[1024]; int len = 0; while ((len = reader.read(buf)) != -1) {//读的字符给buf。 String content = new String(buf,0,len); System.out.print(content); } reader.close(); } |
2.3.1缓冲输入字符流(很重要)
A、BufferedReader类,可以一行一行读。特殊的方法是:
|
B、读取文件的步骤
找到目标文件,然后建立数据通道
读取数据,关闭资源
privatestaticvoid readFile() throws IOException { File file = new File(“f:\\a.txt”); Reader read = new FileReader(file); BufferedReader br = new BufferedReader(read); String line = null; while ((line = br.readLine()) != null) { System.out.println(line); }
|
2.4输出字符流
Write类是所以输出字符流的基类(抽象类)。比如子类FileWriter:向文件数据写的输出字符流。
注意:
A、FileWriter类内部已经维持了一个1024个字符数组,写数据时先写入内部的字符数组,把数据写到硬盘上,要用flush()方法或者close()方法。
B、如果目标文件不存在,就会自动创建,写的时候是覆盖原来数据,如果不想覆盖,则构造方法里边用true。
方法
Ø write(ch): 将一个字符写入到流中。
Ø write(char[]): 将一个字符数组写入到流中。
Ø write(String): 将一个字符串写入到流中。
Ø flush():刷新流,将流中的数据刷新到目的地中,流还存在。
Ø close():关闭资源:在关闭前会先调用flush(),刷新流中的数据去目的地。然流关闭。
发现基本方法和OutputStream 类似,有write方法,功能更多一些。可以接收字符串。
C写入文件的步骤:
privatestaticvoid writeFileByWriter() throws IOException { File file = new File(“f:\\a.txt”); FileWriter fw = new FileWriter(file); fw.write('新'); fw.flush(); fw.write("中国".toCharArray()); fw.write("明天"); // 关闭流资源 fw.close(); } |
2.4.1缓冲输出字符流
A、BufferedWriter:内部维持一个8192个长度的字符。
B、写入文件的步骤
privatestaticvoid writeFileByWriter() throws IOException { File file = new File(“f:\\a.txt”); FileWriter fw = new FileWriter(file); BufferedWriter bw =n new BufferedWriter(fw); bw.write('新'); bw.flush(); // 关闭流资源 bw.close(); } |
3什么是序列流(合并流)
序列流用SequenceInputStream类表示, 是其他输入流的逻辑串联。它从输入流的有序集合开始,从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
面试题:将a.txt和b.txt的内容写到c.txt中 privatestaticvoid testSequenceInputStream() throws IOException { FileInputStream fis1 = new FileInputStream("c:\\a.txt");//找到文件并建立连接 FileInputStream fis2 = new FileInputStream("c:\\b.txt");//找到文件并建立连接
SequenceInputStream s1 = new SequenceInputStream(fis1, fis2); int len = 0; byte[] byt = newbyte[1024];
FileOutputStream fos = new FileOutputStream("c:\\z.txt");//找到文件并建立连接
while ((len = s1.read(byt)) != -1) { fos.write(byt, 0, len); } s1.close(); } |
4什么是对象流
4.1对象输入输出流的作用是用于写对象的信息到文件中或者从文件中读取对象的信息。
4.2对象输出流:ObjectOutputStream;(把程序中对象写到文件中,序列化)
对象输入流:ObjectInputStream(把文件中对象读到程序中,反序列化)
4.3注意点:
A、对象读取文件和写入文件的前提是必须实现Serializable接口,这个接口没有任何方法。
B、SerialVersionUID用于记录class文件的版本信息,它是通过类的工程名、包名、类名、成员名算出的数字。
C、使用ObjectInputStream反序列化时,ObjectInputStream先读取文件中的SerialVersionUID,然后与本地的class文件的SerialVersionUID对比,若不一样,就反序列化失败。
D、在一开始给类指定一个SerialVersionUID,这样在认为的修改类中的成员,JVM也不会计算这个class的SerialVersionUID了。
E、如果一个对象中的某一个成员不想被序列化到硬盘中,可以使用transient修饰。
F、如果一个类里边有另一个类引用,那么另一个类也需要实现Serializable接口。
对象序列化; publicstaticvoid writeObj() throws IOException { User user = new User(); File file = new File(“F:\\obj.txt”); FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(user); oos.close(); } //对象反序列化 publicstaticvoid readObj() throws IOException { FileInputStream fis = new FileInputStream(new File(“F:\\obj.txt”)); ObjectInputStream ois = new ObjectInputStream(fis); Object readObject = ois.readObject(); User user= (User) readObject; System.out.println(user); fis.close(); }
|
4什么叫java序列化:
当创建对象时,程序运行时它就会存在,但是程序停止时,对象也就消失了.但是如果希望对象在程序不运行的情况下仍能存在并保存其信息,将会非常有用,对象将被重建并且拥有与程序上次运行时拥有的信息相同。可以使用对象的序列化。序列化可以将对象的状态写在流里去进行网络传输、保存文件、存放在数据库中,必要时把该流读取出来。
类序列化的实现方式:对象流
5什么是转换流
转换流的作用是字节->字符,可以在构造方法中写编码方式
输入字节转换流InputStreamReader:把输入字节流转换为字符流。
输出字节转换流OutputStreamWriter:把输出字节流转换为字符流
输入字节流转换关键步骤 FileInputStream fis = new FileInputStream(file); InputStreamReader ins = new InputStreamReader(fis, “utf-8”);
|
6什么是打印流
打印流为PrintStream类,属于输出流,打印之前会把数据转换为字符串在打印,输出对象为控制台,主要用于收集日常日志信息
7相对路径和绝对路径
绝对路径:在硬盘上的完整路径。
相对路径:资源文件相对于程序所在的路径。
注意:“.”和“..”分别代表当前路径和上一级路径。
如果程序所在路径和资源路径不在同一个盘里,是无法写相对路径。
eg:File file = new File(“.”);//程序当前路径
8处理文件的异常
8.1对文件处理一般要抛异常,若不抛,则处理。
8.2抛出try{
文件相关代码
}catch(IOException e){
throw new RuntimeException(e)//阻止后边代码的运行
}finally{
}
9什么叫Properties配置文件
9.1、Properties类主要用于生产配置文件和读取配置文件的信息。它是Hashtable的子类(存储键值对)。该集合类不需要泛型,因为该集合中的键值对都是String类型。
Map
|--Hashtable
|--Properties
9.2方法:
1、存入键值对:(void)setProperty(String key,String value);
2、value getProperty(key);根据键获取值
3、如果配置文件的配置信息使用的时候中文,在使用方法store(输出流对象,string a)存储的时候要写字符流对象,如果写字节流对象的话,字节流对象默认iso8859-1编码
4、load(流对象):加载配置文件信息
在f盘下边写bob.properties的文件,里边存一些键值对并读取 public void createProperties()throws IOException{ Properties pro = new Properties(); Pro.setProperty(“鲍珀”,”男”); Pro.setProperty(“吉彩云”,”女”); pro.store(new FileOutputStream(“f:\\bob.properties”),”人员统计”) } //读取 public void read()throws IOException{ Properties pro = new Properties(); pro.load(new FileReader(“f:\\bob.properties”)); // 遍历该集合 Set<Entry<Object, Object>> entrySet = pro.entrySet(); for(Entry<Object, Object> entry:entrys){ system.out.ln(“键:”entry.getKey()+”值:”+entry.getValue()); }
}
|
10、网络编程和网页编程的区别
网络编程:主要用于解决计算机和计算机直接的数据传输问题。三要素:IP、端口号、通讯协议
网页编程:基于html页面的基础进行和服务器的数据交互。
11、什么是IP地址、描述IP地址的类
11.1IP地址是32位二进制组成,可把32位分为4份,IP地址=网络号+主机号
11.2IP地址:A类地址 1个网络号加3个主机号(政府单位)
B类地址 2个网络号加2个主机号(事业单位)
C类地址 3个网络号加1个主机号(个人,网吧)
11.3IP类是InetAddress类,无构造方法,必须通过静态方法获取对象。
主要方法如下:
staticInetAddress getLocalHost():返回本地主机的IP地址的对象
staticInetAddress getByName():获取别人IP地址的对象
StringgetHostAddress():返回本地IP地址的字符串表达形式。
StringgetHostName():返回主机名
12UDP与TCP通讯协议的特点:
UDP通讯协议特点:
1.、将数据封装为数据包,大小限制在64K中,面向无连接,所以不可靠,速度快,
2、UDP是不分服务端与客户端的,只分发送端与接收端
TCP通讯协议特点:
1.tcp是基于IO流进行数据的传输的,传输数据大小没有限制,面向连接,基于三次握手协议,所以可靠,传输速度慢。
2.tcp是区分客户端与服务端的。
13UDP编程类和编写基于UDP的发送端和接收端
DatagramSocket类(UDP的Socket服务)。
DatagramSocket类(数据包类)
方法:DatagramPacket(buf, length, address, port)
buf: 发送的数据内容
length : 发送数据内容的大小。
address : 发送的目的IP地址对象
port : 端口号。
面试题1:编写一个收发程序 发送端的使用步骤: 1. 建立udp的服务。 2. 准备数据,把数据封装到数据包中发送。 发送端的数据包要带上ip地址与端口号。 3. 调用udp的服务,发送数据。 4. 关闭资源。 publicclass Demo1Send{ publicstaticvoid main(String[] args) throws IOException { //建立udp的服务 DatagramSocket datagramSocket = new DatagramSocket(); //准备数据,把数据封装到数据包中。 String data = "这个是我第一个udp的例子.."; //创建了一个数据包 DatagramPacket packet = new DatagramPacket(data.getBytes(), data.getBytes().length,InetAddress.getLocalHost() , 9090); //调用udp的服务发送数据包 datagramSocket.send(packet); //关闭资源 ---实际上就是释放占用的端口号 datagramSocket.close();
} }
接收端的使用步骤 1. 建立udp的服务 2. 准备空 的数据 包接收数据。 3. 调用udp的服务接收数据。 4. 关闭资源 publicclass Demo1Receive { publicstaticvoid main(String[] args) throws IOException { //建立udp的服务,并且要监听一个端口。 DatagramSocket socket = new DatagramSocket(9090); //准备空的数据包用于存放数据。 byte[] buf = newbyte[1024]; DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length); // 1024 //调用udp的服务接收数据 socket.receive(datagramPacket); //receive是一个阻塞型的方法,没有接收到数据包之前会一直等待。数据实际上就是存储到了byte的自己数组中了。 System.out.println("接收端接收到的数据:"+ new String(buf,0,datagramPacket.getLength())); // getLength() 获取数据包存储了几个字节。 //关闭资源 socket.close();
} } 先运行接收端,后运行发送端。 |
14TCP编程类和编写基于TCP的客户端服务器端程序
Socket(客户端类) , tcp的客户端一旦启动马上要与服务端进行连接。
ServerSocket(服务端类)
tcp的客户端使用步骤: 1. 建立tcp的客户端服务。 2. 获取到对应的流对象。 3.写出或读取数据 4. 关闭资源。 publicclass Demo1Clinet { publicstaticvoid main(String[] args) throws IOException{ //建立tcp的服务 Socket socket = new Socket(InetAddress.getLocalHost(),9090); //获取到Socket的输出流对象 OutputStream outputStream = socket.getOutputStream(); //利用输出流对象把数据写出即可。 outputStream.write("服务端你好".getBytes());
//获取到输入流对象,读取服务端回送的数据。 InputStream inputStream = socket.getInputStream(); byte[] buf = newbyte[1024]; int length = inputStream.read(buf); System.out.println("客户端接收到的数据:"+ new String(buf,0,length));
//关闭资源 socket.close(); } } Tcp服务端的使用 步骤 1. 建立tcp服务端 的服务。 2. 接受客户端的连接产生一个Socket. 3. 获取对应的流对象读取或者写出数据。 4. 关闭资源。 publicclass Demo1Server {
publicstaticvoid main(String[] args) throws Exception { //建立Tcp的服务端,并且监听一个端口。 ServerSocket serverSocket = new ServerSocket(9091); //接受客户端的连接 Socket socket = serverSocket.accept(); //accept() 接受客户端的连接该方法也是一个阻塞型的方法,没有客户端与其连接时,会一直等待下去。 //获取输入流对象,读取客户端发送的内容。 InputStream inputStream = socket.getInputStream(); byte[] buf = newbyte[1024]; int length = 0; length = inputStream.read(buf); System.out.println("服务端接收:"+ new String(buf,0,length));
//获取socket输出流对象,想客户端发送数据 OutputStream outputStream = socket.getOutputStream(); outputStream.write("客户端你好啊!".getBytes()); //关闭资源 serverSocket.close(); } } |
第六章 java进阶知识点串讲和JVM虚拟机
1什么是泛型
泛型为容器贴标签,指明容器的存储类型。写泛型时候,左右两边都要写。
泛型的好处:
1)可以减少手动类型转换的工作
2)可以把程序运行时错误提前到编译时报错!!
3)使用泛型可以让开发者写出更加通用的代码!!!!!!!
1.1函数上的泛型
A、定义:当函数中使用了一个不明确的数据类型,那么在函数上就可以进行泛型的定义。
修饰符 <泛型的声明>返回值类型 函数名( 泛型 变量名 ){
}
eg:public <T>T getChar(T t){
代码块
}
B、在方法上自定义泛型,这个自定义泛型的具体类型是调用该方法的时候传入的。
1.2类上的泛型
A、应用场景:如果一个类上的方法有很多,每个方法如果都需要自定义泛型,那么可以在类上定义泛型
B、格式
修饰符 class 类名<泛型>{
}
C、
1)在类上自定义泛型,具体数据类型是在创建对象时候指定的。如果在创建对象也没指定,默认为Object型。
2)类上的自定义泛型不可以用在静态方法上。如果需要,在静态方法上声明使用。(因为类的泛型在创建对象时候确定的,而静态方法可以通过类名访问)
1.3泛型接口
格式:interface 接口名<声明自定义类型>{}
注意事项:A接口自定义泛型在实现接口时候指定。如果没有指定,默认为Object型。也可以声明,不指定,在创建对象时候指定。
interface Dao<T>{} public class Demo1 implements Dao<String>{}//指定类型 public class Demo1 implements Dao{}//不指定,默认Object型
public class Demo1<T> implements Dao<T>{}//先声明 Demo1<String> a = new Demo1<String>(); |
1.4上下限
限定通配符的上边界:
extends
接收Number 类型或者Number的子类型
正确:Vector<? extends Number> x = new Vector<Integer>(); 错误:Vector<? extends Number> x = new Vector<String>();
|
限定通配符的下边界
super
接收Integer 或者Integer的父类型
正确:Vector<? super Integer> x = new Vector<Number>(); 错误:Vector<? super Integer> x = new Vector<Byte>(); |
2什么是正则表达式
正则表达式是操作字符串的规则
需求:只能输入数字
public class Demo2{
public static void main(String[] args) { //只能输入数字 String str = "124354232"; char[] arr = str.toCharArray(); boolean flag = true; for(int i = 0 ; i< arr.length ; i++){ if(!(arr[i]>=48&&arr[i]<=57)){ flag = false; } } System.out.println(flag?"输入正确":"输出只能是数字"); }
} |
使用正则表达式:
public class Demo2{
public static void main(String[] args) { //只能输入数字 String str = "12435423a2"; boolean flag = str.matches("[0-9]+"); System.out.println(flag?"输入正确":"只能输入数字");
} } |
正则表达式的符号
预定义字符类
. | 任何字符(与行结束符可能匹配也可能不匹配) |
|
\d | 数字:[0-9] |
|
\D | 非数字: [^0-9] |
|
\s | 空白字符:[ \t\n\x0B\f\r] |
|
\S | 非空白字符:[^\s] |
|
\w | 单词字符:[a-zA-Z_0-9] |
|
\W | 非单词字符:[^\w] |
|
System.out.println("a".matches(".")); System.out.println("1".matches("\\d")); System.out.println("%".matches("\\D")); System.out.println("\r".matches("\\s")); System.out.println("^".matches("\\S")); System.out.println("a".matches("\\w")); |
Greedy 数量词
X? | X,一次或一次也没有 |
X* | X,零次或多次 |
X+ | X,一次或多次 |
X{n} | X,恰好n次 |
X{n,} | X,至少n次 |
X{n,m} | X,至少n次,但是不超过m次 |
System.out.println( "a".matches(".") ); System.out.println( "a".matches("a") ); System.out.println("a".matches("a?") ); System.out.println( "aaa".matches("a*") ); System.out.println( "".matches("a+") ); System.out.println( "aaaaa".matches("a{5}") ); System.out.println( "aaaaaaaaa".matches("a{5,8}") ); System.out.println( "aaa".matches("a{5,}") ); System.out.println( "aaaaab".matches("a{5,}") ); |
范围表示
[abc] | a、b 或 c(简单类) | |
[^abc] | 任何字符,除了 a、b 或 c(否定) | |
[a-zA-Z] | a 到 z 或 A 到 Z,两头的字母包括在内(范围) | |
[a-d[m-p]] | a 到 d 或 m 到 p:[a-dm-p](并集) | |
[a-z&&[def]] | d、e 或 f(交集) | |
[a-z&&[^bc]] | a 到 z,除了 b 和 c:[ad-z](减去) | |
[a-z&&[^m-p]] | a 到 z,而非 m 到 p:[a-lq-z](减去) | |
|
| |
System.out.println( "a".matches("[a]") ); System.out.println( "aa".matches("[a]+") ); System.out.println( "abc".matches("[abc]{3,}") ); System.out.println( "abc".matches("[abc]+") ); System.out.println( "dshfshfu1".matches("[^abc]+") ); System.out.println( "abcdsaA".matches("[a-z]{5,}") ); System.out.println( "abcdsaA12".matches("[a-zA-Z]{5,}") ); System.out.println( "abcdsaA12".matches("[a-zA-Z0-9]{5,}") ); System.out.println( "abdxyz".matches("[a-c[x-z]]+")); System.out.println( "bcbcbc".matches("[a-z&&[b-c]]{5,}")); System.out.println( "tretrt".matches("[a-z&&[^b-c]]{5,}"));
| ||
^ | 行的开头 | |
$ | 行的结尾 | |
\b | 单词边界 | |
\B | 非单词边界 | |
\A | 输入的开头 | |
\G | 上一个匹配的结尾 | |
\Z | 输入的结尾,仅用于最后的结束符(如果有的话) | |
\z | 输入的结尾 |
System.out.println("45678".matches("^[^0]\\d+"));
System.out.println("demo.java".matches("\\w+\\.java$"));
匹配功能
需求:校验QQ号,要求:必须是5~15位数字,0不能开头。没有正则表达式之前
public static void checkQQ(String qq) { int len = qq.length(); if(len>=5 && len <=15) { if(!qq.startsWith("0")) { try { long l = Long.parseLong(qq); System.out.println("qq:"+l); } catch (NumberFormatException e) { System.out.println("出现非法字符"); } } else System.out.println("不可以0开头"); } else System.out.println("QQ号长度错误"); } |
有了正则表达式之后:
[1-9][0-9]{4,14} [1-9]表示是第一位数字是会出现1-9范围之间的其中一个,下来的数字范围会出现在0-9之间,至少出现4次,最多出现14次。
public static void checkQQ2() { String qq = "12345"; String reg = "[1-9][0-9]{4,14}"; boolean b = qq.matches(reg); System.out.println("b="+b); } |
需求:匹配是否为一个合法的手机号码。
public static void checkTel() { String tel = "25800001111"; String reg = "1[35]\\d{9}";//在字符串中,定义正则出现\ 要一对出现。 boolean b= tel.matches(reg); System.out.println(tel+":"+b); } |
切割功能
需求1:根据空格对一段字符串进行切割。
public static void splitDemo() { String str = "aa.bb.cc"; str = "-1 99 4 23"; String[] arr = str.split(" +"); for(String s : arr) { System.out.println(s); } } |
需求2 :根据重叠词进行切割。
public static void splitDemo2() { String str = "sdqqfgkkkhjppppkl"; String[] arr = str.split("(.)\\1+"); for(String s : arr) { System.out.println(s); }
} |
注意:为了提高规则复用,用()进行封装,每一个括号都有一个编号,从1开始,为了复用这个规则。可以通过编号来完成该规则的调用。需要对编号数字进行转义。\\1就代表获取1组规则。
替换功能
需求:把字符串中的重叠字替换成单个单词。
public static void replaceDemo() { String str = "sdaaafghcccjkqqqqqql"; String s = str.replaceAll("(.)\\1+","$1");//$ 可以获取到该方法中正则实际参数中的某一个存在的组 $组编号即可。 System.out.println(str+":"+s); String nums = "wser127372tyuiopd6226178909876789fghjk"; String s1 = nums.replaceAll("\\d+","*"); System.out.println(nums+":"+s1); } |
3什么是正则对象—查找正则表达式
步骤:
1,先将正则表达式编译成正则对象。使用的是Pattern类一个静态的方法。compile(regex);
2,让正则对象和要操作的字符串相关联,通过matcher方法完成,并返回匹配器对象。
3,通过匹配器对象的方法将正则模式作用到字符串上对字符串进行针对性的功能操作
范例:
Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher("aaaaab");
boolean b = m.matches();
Matcher类的常用方法
(boolean)find():通知匹配器匹配字符串,查找符合规则的字符串
(string)group()获得符合规则的子串
获取
获取需要使用到正则的两个对象:使用的是用正则对象Pattern和匹配器Matcher。
需求:获取由3个字母组成的单词。
public static void getDemo() { String str = "da jia zhu yi le,ming tian bu fang jia,xie xie!"; //想要获取由3个字母组成的单词。 //刚才的功能返回的都是一个结果,只有split返回的是数组,但是它是把规则作为分隔符,不会获取符合规则的内容。 //这时我们要用到一些正则对象。 String reg = "\\b[a-z]{3}\\b"; Pattern p = Pattern.compile(reg); Matcher m = p.matcher(str); while(m.find()) { System.out.println(m.start()+"...."+m.end()); System.out.println("sub:"+str.substring(m.start(),m.end())); System.out.println(m.group());//在使用group方法之前,必须要先找,找到了才可以取。 } |
练习二:我我....我...我.要...要要...要学....学学..学.编..编编.编.程.程.程..程
将字符串还原成 我要学编程。
练习3:校验邮件
public static void checkMail() { String mail = "abc123@sina.com.cn"; mail = "1@1.1"; String reg = "[a-zA-Z_0-9]+@[a-zA-Z0-9]+(\\.[a-zA-Z]+)+"; reg = "\\w+@\\w+(\\.\\w+)+";//简化的规则。笼统的匹配。 boolean b = mail.matches(reg); System.out.println(mail+":"+b); } |
练习4:网络爬虫
class GetMailList { public static void main(String[] args) throws Exception { String reg = "\\w+@[a-zA-Z]+(\\.[a-zA-Z]+)+"; getMailsByWeb(reg); }
public static void getMailsByWeb(String regex)throws Exception { URL url = new URL("http://localhost:8080/myweb/mail.html");
URLConnection conn = url.openConnection(); BufferedReader bufIn = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line = null; Pattern p = Pattern.compile(regex); while((line=bufIn.readLine())!=null) { //System.out.println(line); Matcher m = p.matcher(line); while(m.find()) { System.out.println(m.group()); } } bufIn.close(); } public static void getMails(String regex)throws Exception { BufferedReader bufr = new BufferedReader(new FileReader("mail.txt")); String line = null; Pattern p = Pattern.compile(regex); while((line=bufr.readLine())!=null) { //System.out.println(line); Matcher m = p.matcher(line); while(m.find()) { System.out.println(m.group()); } } bufr.close(); } } |
4什么是增强for循环
4.1目的是为了简化迭代器的书写。
4.2适用范围:实现了Iterable接口的对象和数组对象适合
4.3格式for(数据类型 变量名:遍历目标){
}
注意;
A、for循环对所有单例都适合,对双列不适合。因为Map集合没有实现Iterable。如果Map集合想实现,则双列变单列。
B、在遍历过程中,不可以使用集合对象对元素个数修改。也无法调用迭代器的方法(修改等)。因为获取不到对象。而迭代器遍历时候可以用迭代器对象对集合删除、添加。
int[] arr = {12,5,41,42,44,2,5}; for(int a:arr){ System.out.println(“元素是”+a); } |
5什么是静态导入
调用静态函数时候用类名访问,如果你想省略类名,可以在程序中导入静态方法包,可以简化书写。
eg:
importstatic java.lang.System.out; importstatic java.lang.Math.*;
publicclass Demo {
publicstaticvoid main(String[] args) { // 普通写法 System.out.println("hello world"); int max = Math.max(100, 200); System.out.println(max);
// 静态导入 out.println("hello world"); int max2 = max(100, 200); System.out.println(max2); }
} |
6什么是可变参数
当不确定几个参数的时候,可以用可变参数表示。
格式:数据类型…变量名
注意点:可变参数必须位于函数形参中的最后一个,所以一个函数只可以有一个可变参数。调用时候可以传参数也可以不传。可变参数实际上是一个数据对象
7什么是自动装箱和自动拆箱
7.1八种基本数据类型也属于类,把这八个类叫包装类。
包装类 基本数据类型
Byte | byte |
Short | short |
Integer | int |
Long | long |
Boolean | boolean |
Float | float |
Double | double |
Character | char |
对象变基本数据类型:拆箱 | 基本数据类型包装为对象:装箱 |
7.2自动装箱:自动吧java的基本数据类型转换为对象类型数据
8什么是枚举
8.1当数据有固定范围的时候,可以用枚举。
8.2格式:
enum 类名{
枚举值
成员变量;
成员函数;
}
8.3注意点:
A、枚举类的枚举值在编译时默认为public static final 类名 枚举值。也就是说枚举值是枚举类的对象。
B、枚举值在枚举类中必须是第一行,枚举类的构造函数必须是private修饰。
9什么叫批处理文件
9.1定义:批处理文件又叫bat处理文件,是一次性可以执行多个命令的文件。
9.2bat文件的创建:吧文本文件后缀名改为bat即可。吧执行的命令写在bay处理文件中
常见批处理文件的命令: echo 表示显示此命令后的字符 tiltle 设置窗口的标题。 echo off 表示在此语句后所有运行的命令都不显示命令行本身 color 设置窗体的字体颜色。 @与echo off相象,但它是加在每个命令行的最前面,表示运行时不显示这一行的命令行(只能影响当前行)。 pause 运行此句会暂停批处理的执行并在屏幕上显示Press any key to continue...的提示,等待用户按任意键后继续 rem 表示此命令后的字符为解释行(注释),不执行,只是给自己今后参考用的(相当于程序中的注释) 或者%注释的内容% %[1-9]表示参数,参数是指在运行批处理文件时在文件名后加的以空格(或者Tab)分隔的字符串 |
10java中的路径
绝对路径:是一个文件的完整路径,一般包括盘符
相对路径:相对于当前程序所在的路径而言。在Eclipse中,当前路径是工程的根目录。
类文件路径:使用了classpath路径找对应的资源
11什么是junit测试框架
A、用途: junit测试框架主要是用于对程序进行测试,不用都在main()方法上测试。测试的结果也可以用断言类(assert类),不需要人工对比。
B、junit测试框架的使用
S1、搭建环境:
导入junit.jar包(junit4)
S2、写测试类:
0,一般一个类要对应一个测试类。
1,测试类与被测试类最好是放到同一个包中(可以是不同的源文件夹)
2,测试类的名字是被测试类的名字加Test后缀。
S3:写测试方法:
0,一般一个方法对应一个单元测试方法。
1,测试方法的名字为test前缀加被测试方法的名字,如testAddPerson()。
2,单元测试方法上面要加上@Test注解(org.junit.Test)!
3,单元测试方法不能有参数,也不能有返回值(返回void)!测试的方法不能是静态的方法。
S4、测试方法的基本使用:
1,可以单独执行一个测试方法,也可以一次执行所有的、一个包的、一个类中所有的测试方法。
2,执行完后,显示绿色表示测试成功;显示红色表示测试失败(抛异常后会测试失败)。
注意点:
1、投机取巧的测试办法是在需要测试的方法上加入@Test,然后运行测试方法,看看结果,然后在去掉
2、@Test测试的方法不可以是static修饰的,不可以带参数。
3、如果在测试方法时候需要准备工作或者后序工作可以在别的地方用这些注释。
@Test
表示单元测试方法。
@Before
所修饰的方法应是非static的(且没有参数,返回值为void)。
表示这个方法会在本类中的每个单元测试方法之前都执行一次。
@After
所修饰的方法应是非static的(且没有参数,返回值为void)。
表示这个方法会在本类中的每个单元测试方法之后都执行一次。
@BeforeClass
所修饰的方法应是static的(且没有参数,返回值为void)。
表示这个方法会在本类中的所有单元测试方法之前执行,只执行一次。
@AfterClass
所修饰的方法应是static的(且没有参数,返回值为void)。
表示这个方法会在本类中的所有单元测试方法之后执行,只执行一次。
C对测试的结果可以用断言类(Assert)判断是否达到预期值。
eg:(boolean)assertSame(参数1,参数2);可以判断两个值是否相等用个(==比较),可以一个预设的,一个是程序的结果
(boolean)assertEquals(参数1,参数2)可以判断两个值是否相等用个(equals()方法比较),可以一个预设的,一个是程序的结果
12什么是对象的浅克隆和深克隆
12.1为什么克隆:克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠克隆了。
12.2浅克隆:如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象。
浅克隆的类要实现Cloneable接口并重写Object类中的clone()方法;具体步骤如下:
S1. 被复制的类需要实现Clonenable接口
S2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)
12.3深克隆:无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
可以利用对象输入输出流先把对象写到文件里,然后在读取对象(序列化)
深克隆的类要实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
13什么是反射
13.1反射的概念
当字节码文件加载到内存时,JVM创建一个对应的字节码对象的class对象,把字节码文件的信息全部存储到该class对象中,
13.2反射中用到的类
Class类:代表类
Ø static Class forName("String完整包名加类名"):得到类的对象
eg: Class clazz =Class.forName("cn.itcast.reflect.Person");
Ø (String)getName():得到类的全名:包名加类名
Ø (Class)getSuperclass();得到父类的对象
Ø Constructor[] getConstructors():得到所有公共的构造方法类的对象
Ø Constructor[] getDeclaredConstructors():得到所有的构造方法类的对象
Ø Constructor<T> getConstructor(Class<?>...parameterTypes):得到一个构造方法
eg: Constructorconstructor = clazz.getConstructor(int.class,String.class)
Ø (Method)getMethod(“方法名”,方法返回类型):得到public方法的对象
Ø (Field)getDeclaredField(“成员变量名”);得到任意属性
Ø (Field)getField(“成员变量名”);得到public修饰的属性
Constructor类:代表类的构造方法
u (Object )newInstance(根据不同的参数):获取不同构造方法的类的对象。
u setAccessible(true);//使用反射对象不对java访问检查
Constructor constructor = clazz.getConstructor(int.class,String.class); // getConstructor获取单个指定的构造方法。 Person p = (Person) constructor.newInstance(999,"小城"); // newInstance()创建一个对象 |
Method类:代表类的方法
u invoke(方法被调用的对象,方法需要的参数)
u setAccessible(true);设置访问权限允许访问,针对私有方法
Person p = new Person(110,"狗娃"); Method m = clazz.getMethod("eat", int.class); m.invoke(p, 3); //等价于p.eat(3); |
Field类:代表类的属性。
u setAccessible(true);得到私有成员的访问权限,暴力反射
u (String)getName():得到属性名
u (String)getType():得到属性类型
u (void)set(成员被调用的对象,赋值)
Person p = new Person(); Field field = clazz.getDeclaredField("id"); field.setAccessible(true); field.set(p, 110); //p.id=110 |
14什么是内省,什么是BeanUtils工具
使用内省的原因:开发框架时,经常需要使用java对象的属性来封装程序的数据,每次都使用反射技术(工厂模式)完成此类操作过于麻烦,所以sun公司开发了一套API,专门用于操作java对象的属性。
内省(Introspector类)是用于操作java对象的属性的,其实内省是变态的反射,本质上还是反射。
//获取Person类所有属性的get方法 publicvoid getAllProperty() throws IntrospectionException{ //Introspector 内省类 BeanInfo beanInfo = Introspector.getBeanInfo(Person.class); //通过BeanInfo获取所有的属性描述器 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); //获取一个类中的所有属性描述器 for(PropertyDescriptor p : descriptors){ System.out.println(p.getReadMethod()); //get方法 }
@Test//对一个属性加入对象中 public void testProperty() throws Exception{ Person p = new Person(); //下边的代码吧属性id写到对象P中。比较繁琐,写一大推代码只是描述了一个属性 //属性描述器,操作Person类的id属性 PropertyDescriptor descriptor = new PropertyDescriptor("id", Person.class); //获取属性对应的get或者是set方法设置或者获取属性了。 Method m = descriptor.getWriteMethod(); //获取属性id的set方法。 //执行该方法设置属性值 m.invoke(p,110); Method readMethod = descriptor.getReadMethod(); //是获取属性的get方法 System.out.println(readMethod.invoke(p, null)); } |
因为内省一个属性描述器只能操作一个属性,也是比较麻烦的,所以开发出一套对象获取属性的工具。
BeanUtils:方法
Ø setProperty(对象, “属性名”, id);
Ø copyProperty(s2, "id", "2");参数一:拷贝到的对象。参数二:拷贝的属性参数三:拷贝的值
1)拷贝的方法能够自动将非String的类型转换对应的类型的属性.例如: "2"(String)-》 int(String->int) "97.23"(String) -> double (String->double)
2)如果要支持从String转为Date,那么需要手动注册一个日期转换器
BeanUtils工具就是方便开发者操作javabean对象。
作用: 1)拷贝一个javabean对象的属性
2)从一个javabean拷贝到另一个javabean对象(所有属性)
3)从一个map集合中拷贝到javabean对象中。
BeanUtils的好处:
1. BeanUtils设置属性值的时候,如果属性是基本数据类型,BeanUtils会自动帮我转换数据类型。
2. BeanUtils设置属性值的时候底层也是依赖于get或者Set方法设置以及获取属性值的。
3. BeanUtils设置属性值,如果设置的属性是其他的引用类型数据,那么这时候必须要注册一个类型转换器。
BeanUtils使用:(别人开发的工具)
导包commons-logging.jar 、commons-beanutils-1.8.0.jar
publicclass Demo3 { publicstaticvoid main(String[] args) throws Exception { //从文件中读取到的数据都是字符串的数据,或者是表单提交的数据获取到的时候也是字符串的数据。 String id ="110"; String name="陈其"; String salary = "1000.0"; String birthday = "2013-12-10";
Emp e = new Emp(); BeanUtils.setProperty(e, "id", id); BeanUtils.setProperty(e, "name",name); BeanUtils.setProperty(e, "salary",salary);
//注册一个类型转换器,converter是接口。 //register(Converter a,Class b)当我遇到b的引用类型时就用a的转换器。 ConvertUtils.register(new Converter() { @Override public Object convert(Class type, Object value) { // type : 目前所遇到的数据类型。 ,这里就是吧Data.class创给type。value :目前参数的值。convert()有两个形参 Date date = null; try{ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); date = dateFormat.parse((String)value);//吧字符串的值变成date类 }catch(Exception e){ e.printStackTrace(); } return date; }
}, Date.class);
BeanUtils.setProperty(e, "birthday",birthday); System.out.println(e); } } |
15什么是反射泛型(重要)
用反射技术得到的泛型。
public class TeacherDao extends BaseDao<Teacher>{}
//具体的dao上面的泛型类型 private Class targetClass; //表名 private String tableName;
Class clazz = this.getClass(); //得到当前运行类的Class对象 Type type = clazz.getGenericSuperclass();// 得到TeacherDao的父类BaseDao<Teacher> ParameterizedType param = ( ParameterizedType)type; //强制成参数化子类 Type[] types = param.getActualTypeArguments();得到参数化类型上面的泛型类型列表 //6)取出泛型类型列表中的第一个泛型类型 Type target = types[0]; // Teacher //7)强转成Class类型 targetClass = (Class)target;//Teacher //数据库的表名也要是teacher,都是小写。 tableName = targetClass.getSimpleName().toLowerCase();//teather } 这段代码就是得到BaseDao<teacher>中的teacher,在CRM系统的项目中用到过。 |
16什么叫反射注解
场景:用反射技术得到类、方法、属性上的注解,因为用反射泛型的时候数据库中的表名要和类名一样。如果不一样,怎么办呢?我们可以使用注解绑定类和表名,属性和字段名。
/** * 表的注解 * @author APPle * */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public@interfaceTable { String name();//数据的表名 } 在BaseDao类中 Table table = (Table)targetClass.getAnnotation(Table.class); tableName = table.name();//类名对应的表名
|
17注解和注释的区别,说明什么叫注解(Annotation)
注释:提高程序的可读性,对程序运行没有影响!!!
注解:给程序带上一些标记,从而影响编译器运行程序的结果!!!
17.1注解的作用:
1)可以在程序上(类,方法,属性)携带信息
2)注解简化(取代)配置文件(xml或properties)
17.2自定义注解:
@Target({ElementType.METHOD,ElementType.FIELD})//元注解 @Retention(RetentionPolicy.RUNTIME)//元注解 public@interfaceAuthor { //声明属性 String name(); String modifyTime() default"2015-06-25";//给属性带上默认值 String[] address();//带有数组类型的属性 //如果注解的属性名称为value(类型不限制),那么在使用注解的时候可以不写value=,如果不写,value的值要在注解的最前边 String[] value(); //String[] names(); //String value(); }
publicclass Demo2 { @Author(name = "eric", address = { "广州天河","广东韶关" }, value = {"传智"}) private String name;
|
17.3元注解(注解类里边的注解)
A、@Target({TYPE, FIELD,METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
声明注解的使用范围.
TYPE: 注解可以用在类上
FIELD:注解可以用在属性上
METHOD:用在方法上
PARAMETER:用在参数声明上面
CONSTRUCTOR:用在构造方法上面
LOCAL_VARIABLE:用在本地变量上面
B、 @Retention(RetentionPolicy.SOURCE)
声明注解的有效范围
RetentionPolicy.SOURCE: 该注解只在源码中有效!
RetentionPolicy.CLASS: 该注解在源码中,和字节码中有效!(默认)
RetentionPolicy.RUNTIME:该注解在源码中,和字节码中有效,运行字节码的时候有效!这样才可以用反射拿注解(要运行)
18什么叫类加载器
类加载器是实现吧.java变成.class的工具
1)系统提供给我们三个类加载器:
BootStrap加载器:加载的jdk/jre/lib/rt.jar(开发的时候用的核心类)
ExtClassLoad加载器:加载jdk/jre/lib/ext/*.jar (扩展包)
AppClassLoad加载器:加载CLASSPATH中的jar包和class文件
注意:加载器可以扩展,tomcat服务器就对加载器扩展了
2) 这三个类加载器是树状结构.
3) 类加载的过程:
3.1一个类A是有一个类加载器加载的,如果类A中使用到了类B,类B也是由类A的类加载器加载
4)委托机制:
4.1发起者类加载器 去加载类的时候,先委托其父类(没有继承,只是树状的结构)加载, 如果还有父类加载器,则继续委托上去,直接没有父加载器为止。
最顶层的类加载就需要真正地去加载指定类,如果在其类目录中找不到这个类,继续往下找,找到发起者类加载器为止!!!
委托机制的好处:
可以让代码加载更加安全和高效!保证核心类的字节码只有一份在内存中。
一个web的servlet加载顺序:
1)WebappClassLoader(tomcat自定义的类加载器) :
加载web/WEB-INF/classes/ 类
加载web/WEB-INF/lib/*.jar jar包
1.1WebappClassLoader设计的目录为了分离服务器中每个web应用(每个项目),让每个web应用互不干扰的。每个项目都有servletContext类
1.2WebappClassLoader打破了委托机制。 为了保持优先加载当前web应用的所有资源
2)StandardClassLoader(tomcat自定义的类加载器):加载tomcat/lib/*.jar
解释:StandardClassLoader用于加载所有web用到的jar包或类。
3)AppClassLoader : jdk的CLASSPATH
4)ExtClassLoader: jre/lib/ext/*.jar
5)BootStrap :jar/lib/rt.jar
类加载器找的顺序是5,43,1,2。因为不同项目可能出现同名的jar包。如果先加载2,会出现同名
19什么叫动态代理
如果对某个接口中的某个指定的方法的功能进行扩展,而不想实现接口里所有方法,可以使用(动态)代理模式! Java中代理模式:静态/动态/Cglib代理(spring) 。使用动态代理,可以监测接口中方法的执行!
|--Proxy
staticObject newProxyInstance(
ClassLoader loader, 当前使用的类加载器
Class<?>[] interfaces, 目标对象(Connection)实现的接口类型
InvocationHandler h 事件处理器:当执行上面接口中的方法的时候,就会自动触发事件处理器代码,把当前执行的方法(method)作为参数传入。
// 对con创建其代理对象,这里实现的功能是当调用con的close方法就执行pool.addLast(con);操作 Connection proxy = (Connection) Proxy.newProxyInstance(
con.getClass().getClassLoader(), // 第一个参数:类加载器,目标为接口 new Class[]{Connection.class}, // 第二个参数:目标对象实现的接口
new InvocationHandler() { // 第三个参数:当调用con对象方法的时候,自动触发事务处理器 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 方法返回值 Object result = null; // 当前执行的方法的方法名 String methodName = method.getName(); // 判断当执行了close方法的时候,把连接放入连接池 if ("close".equals(methodName)) { System.out.println("begin:当前执行close方法开始!"); // 连接放入连接池 pool.addLast(con); System.out.println("end: 当前连接已经放入连接池了!"); } else { // 调用目标对象方法 result = method.invoke(con, args); } return result; } |
19设计类单例还是多类的原则
判断一个类应该设计是单例还是多例,主要看有没有维护成员变量、且对象成员变量进行修改! 如果有,这个类就应该是多例!
20什么叫代理,代理的种类
proxy:这个单词表示代理,代理提供了目标对象的一种访问方式,通过代理访问目标对象。
代理的好处是:目标对象没有直接暴露给用户,代理可以在目标对象的基础上增加额外的验证和功能等。
20.1静态代理
特点:
1.目标对象必须要实现接口
2.代理对象,要实现与目标对象一样的接口
总结:就是自创一个对象过渡一下
缺点:
1.代理对象,需要依赖目标对象的接口!
如果接口功能变化(多一个),目标对象变化,会引起代理对象的变化!
2.对每一个目标对象,都要分别写一个代理类,麻烦!
IUserDao.Java接口类 |
publicinterface IUserDao { void save(); void find(); } |
UserDao.Java,目标类 |
// 目标对象 publicclass UserDao implements IUserDao{ publicvoid save() { System.out.println("模拟:保存用户!"); } publicvoid find() { System.out.println("查询"); } } |
UserDaoProxy.Java |
/** * 静态代理 特点: 1. 目标对象必须要实现接口 2. 代理对象,要实现与目标对象一样的接口 * */ publicclass UserDaoProxy implements IUserDao{
// 代理对象,需要维护一个目标对象 private IUserDao target = new UserDao(); @Override publicvoid save() { System.out.println("代理操作:开启事务..."); target.save(); // 执行目标对象的方法 System.out.println("代理操作:提交事务..."); } @Override publicvoid find() { target.find(); } } |
App.Java |
package cn.itcast.a_static; publicclass App { publicstaticvoid main(String[] args) { // 代理对象 IUserDao proxy = new UserDaoProxy(); // 执行代理方法 proxy.save();
} } |
20.2动态代理
动态代理:
1.通常说的动态代理,就是指jdk代理!
因为是通过jdk的api在运行时期,动态的生成代理对象的!
2. 目标对象一定要实现接口, 代理对象不用实现接口!
JDK 生成代理对象的Api
|-- Proxy类
static Object | newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) |
参数loader :
当前目标对象使用的类加载器!
参数interfaces:
当前目标对象实现的接口
参数 h:
接口类型,事件处理器.
原理:当执行目标对象方法的时候,会触发事件;把当前执行的方法(method对象),传入事件处理器方法参数中, 这样就可以根据业务逻辑,判断是否执行目标对象方法或扩展功能!
IUserDao.java |
package cn.itcast.b_dynamic; // 接口 publicinterface IUserDao { void save();
void find(); } |
UserDao.java |
package cn.itcast.b_dynamic; // 目标对象 publicclass UserDao implements IUserDao{ @Override publicvoid save() { System.out.println("模拟:保存用户!"); }
@Override publicvoid find() { System.out.println("查询"); } } |
ProxyFactory.java |
package cn.itcast.b_dynamic; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 动态代理: * 代理工厂,给多个目标对象生成代理对象! * @author AdminTH * */ publicclass ProxyFactory { // 接收一个目标对象 private Object target; public ProxyFactory(Object target) { this.target = target; }
// 返回对目标对象(target)代理后的对象(proxy) public Object getProxyInstance() { Object proxy = Proxy.newProxyInstance( target.getClass().getClassLoader(), // 目标对象使用的类加载器 target.getClass().getInterfaces(), // 目标对象实现的所有接口 new InvocationHandler() { // 执行代理对象方法时候触发 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取当前执行的方法的方法名 String methodName = method.getName(); // 方法返回值 Object result = null;
// 判断 if ("find".equals(methodName)) { // 直接调用目标对象方法 result = method.invoke(target, args); } else { System.out.println("开启事务..."); // 执行目标对象方法 result = method.invoke(target, args); System.out.println("提交事务..."); } return result; } } ); return proxy; } } |
App.Java |
package cn.itcast.b_dynamic;
publicclass App { publicstaticvoid main(String[] args) {
// 创建目标对象 IUserDao target = new UserDao(); //结果 class cn.itcast.b_dynamic.UserDao System.out.println("目标对象:" + target.getClass());
// 代理对象 IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance(); System.out.println("代理对象: " + proxy.getClass()); // 结果: class $Proxy0
// 执行代理对象的方法 proxy.find(); } } |
20.3Cglib代理(子类代理)
当目标对象没有实现接口,就不能使用jdk提供的代理,可以以子类的方式实现!
在运行时期动态在内存中构建一个子类对象的方法,从而对目标对象扩展,这种就是cglib代理!
UserDao.Java |
package cn.itcast.c_cglib; // 目标对象 publicclass UserDao{ publicstaticvoid save() { System.out.println("模拟:保存用户!"); } publicvoid find() { System.out.println("查询"); } } |
ProxyFactory.java |
package cn.itcast.c_cglib; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; /** * cglib代理: * 代理工厂,给多个目标对象生成代理对象! * @author AdminTH * */ publicclass ProxyFactory implements MethodInterceptor{ // 接收一个目标对象 private Object target; public ProxyFactory(Object target) { this.target = target; }
// 返回目标对象代理后的子类对象 public Object getProxyInstance() { // 对target生成子类对象 // 字节码生成工具类 Enhancer en = new Enhancer(); // 设置父类 en.setSuperclass(target.getClass()); // 设置回调函数 en.setCallback(this); // 创建子类对象 return en.create(); } // 事件处理器,执行目标方法时候触发 @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("开启事务..."); Object result = method.invoke(target, args); System.out.println("提交事务..."); return result; } } |
App.java |
package cn.itcast.c_cglib; publicclass App { publicstaticvoid main(String[] args) { // 创建目标对象 UserDao target = new UserDao(); System.out.println("目标对象:" + target.getClass()); // class cn.itcast.c_cglib.UserDao // 代理对象 UserDao proxy = (UserDao) new ProxyFactory(target).getProxyInstance(); System.out.println("代理对象: " + proxy.getClass()); // class cn.itcast.c_cglib.UserDao$$EnhancerByCGLIB$$6ecf51fe 代理类 //UserDao$$EnhancerByCGLIB$$6ecf51fe继承UserDao类 // 执行代理对象的方法 proxy.save(); } } |