首页 > 代码库 > JAVA基础总结
JAVA基础总结
Java 编程基础............................................................................................................ - 3 -
一、 前言....................................................................................................... - 3 -
1. JAVA 软件工程师的岗位要求................................................................. - 3 -
二、 Java概述:............................................................................................ - 4 -
1. Java的三种技术架构.............................................................................. - 4 -
2. JDK、JRE、JVM三者间的关系............................................................. - 4 -
3. Java运行环境配置................................................................................. - 5 -
4. Java与Javac命令................................................................................. - 6 -
5. Java编译过程........................................................................................ - 7 -
6. Java内存处理机制................................................................................. - 7 -
7. 常用DOS命令....................................................................................... - 7 -
三、 Java编程风格与命名规范....................................................................... - 8 -
1. 格式规范................................................................................................ - 8 -
2. 命名规范................................................................................................ - 8 -
四、 语法基本元素......................................................................................... - 9 -
1. 关键字................................................................................................... - 9 -
2. 标识符................................................................................................... - 9 -
3. 数据类型、常量和变量......................................................................... - 10 -
4. 运算符和表达式.................................................................................... - 11 -
五、 流程控制.............................................................................................. - 14 -
1. 循环语句.............................................................................................. - 14 -
2. 判断语句.............................................................................................. - 14 -
3. 跳转语句.............................................................................................. - 14 -
4. flag 或index标记的用法..................................................................... - 15 -
六、 数组..................................................................................................... - 16 -
1. 数组的常用操作................................................................................... - 16 -
2. 二维数组.............................................................................................. - 17 -
Java 面向对象.......................................................................................................... - 19 -
一、 前言..................................................................................................... - 19 -
二、 类和对象.............................................................................................. - 19 -
1. 类和对象的定义................................................................................... - 19 -
2. 类的创建和使用................................................................................... - 19 -
3. 对象的创建和使用................................................................................ - 20 -
4. 成员变量VS局部变量.......................................................................... - 20 -
5. 成员方法VS方法................................................................................. - 21 -
6. 构造方法.............................................................................................. - 22 -
7. 补充几点.............................................................................................. - 22 -
三、 封装、继承和多态................................................................................ - 25 -
1. 封装..................................................................................................... - 25 -
2. 继承..................................................................................................... - 26 -
3. 多态..................................................................................................... - 27 -
四、 抽象类和接口....................................................................................... - 30 -
1. 抽象类(abstract).............................................................................. - 30 -
2. 接口(interface)................................................................................ - 31 -
3. 抽象类和接口的对比............................................................................ - 32 -
五、 异常..................................................................................................... - 33 -
1. JAVA异常............................................................................................ - 33 -
2. 处理异常机制....................................................................................... - 34 -
3. 捕获异常try / catch 和 finally.............................................................. - 34 -
4. 抛出异常 throws和throw.................................................................... - 35 -
5. Java常见异常...................................................................................... - 35 -
6. 自定义异常.......................................................................................... - 36 -
7. Java异常处理的原则和技巧................................................................. - 36 -
8. 补充几点.............................................................................................. - 36 -
六、 面向对象的几点猜想............................................................................ - 36 -
Java 高级特性.......................................................................................................... - 37 -
一、 集合框架和泛型................................................................................... - 37 -
1. 集合 框架 API..................................................................................... - 37 -
2. Iterator 迭代器.................................................................................... - 40 -
3. Collections包装类............................................................................... - 41 -
4. 补充几点.............................................................................................. - 42 -
5. 泛型..................................................................................................... - 42 -
二、 实用类................................................................................................. - 42 -
1. 枚举..................................................................................................... - 43 -
2. 包装类................................................................................................. - 43 -
3. Math类................................................................................................ - 44 -
4. String类.............................................................................................. - 44 -
5. StringBufffer类和StringBulider类...................................................... - 45 -
6. 日期时间类.......................................................................................... - 45 -
7. Random类........................................................................................... - 46 -
三、 输入/输出、序列化和反射.................................................................... - 46 -
1. File类的操作....................................................................................... - 46 -
2. Java的流............................................................................................. - 47 -
3. 读写文本文档....................................................................................... - 47 -
4. 读写二进制文档................................................................................... - 50 -
5. 重定向标准 I/O.................................................................................... - 51 -
6. 序列化与反序列化................................................................................ - 51 -
7. 反射机制.............................................................................................. - 52 -
8. 补充几点.............................................................................................. - 52 -
四、 注解和多线程....................................................................................... - 53 -
1. 注解..................................................................................................... - 53 -
2. 多线程................................................................................................. - 54 -
五、 网络编程.............................................................................................. - 57 -
1. 网络基础知识....................................................................................... - 57 -
2. InetAddress......................................................................................... - 58 -
3. URL类................................................................................................. - 58 -
4. TCP编程............................................................................................. - 59 -
5. UDP编程............................................................................................. - 63 -
6. 注意问题.............................................................................................. - 65 -
六、 XML..................................................................................................... - 65 -
1. XML 基础............................................................................................ - 65 -
2. XML 注释............................................................................................ - 65 -
3. XML 解析............................................................................................ - 65 -
七、 GUI...................................................................................................... - 68 -
八、 正则表达式.......................................................................................... - 73 -
Java 编程基础
一、 前言
1. JAVA 软件工程师的岗位要求
- 基础扎实,具备良好面向对象设计能力和范化的编程风格;熟悉MVC设计模式;
- 熟悉CSS/HTML, JavaScript, XML/XSL, Ajax,Jquery等Web脚本语言技术
- 熟练掌握Oracle/MySQL等主流数据库,了解数据库内部机制和原理;
- 熟练基于J2EE的相关开源技术以及框架(Spring,SpringMVC,MyBatis,Hibernate);
- 熟练使用Tomcat、Apache,Nginx,servlet等应用和服务器配置;
- 熟练使用Junit编写测试代码,Maven代码管理工具;
- 了解UML等建模语言、PowerDesigner等设计常用工具;
- 项目工作经验
二、 Java概述:
1991 年Sun公司的James Gosling等人开始开发名称为 Oak 的语言,希望用于控制嵌入在有线电视交换盒、PDA等的微处理器;1994年将Oak语言更名为Java;
1. Java的三种技术架构
a) JAVAEE
Java Platform Enterprise Edition,开发企业环境下的应用程序,主要针对web开发。
a) JAVASE
Java Platform Standard Edition,完成桌面应用程序的开发,是其它两者的基础。
b) JAVAME
Java Platform Micro Edition,开发电子消费产品和嵌入式设备,如手机中的程序。
2. JDK、JRE、JVM三者间的关系
a) JDK:Java Development Kit
JDK是整个JAVA的核心,包括了Java运行环境JRE(Java Runtime Envirnment)、一堆Java工具(Javac/Java/jdb等)和Java基础的类库(即Java API 包括rt.jar)。
在JDK的安装目录下有一个名为jre的目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib和起来就称为jre。
b) JRE:Java Runtime Environment
Java程序的运行环境,Java运行的所需的类库+JVM(Java虚拟机)。与JDK不同,JRE是Java运行环境,并不是一个开发环境,所以没有包含任何开发工具(如编译器和调试器),只是针对于使用Java程序的用户。
c) JVM:Java virtual machine
Java虚拟机的缩写,是整个Java实现跨平台的最核心的部分,所有的Java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行。
也就是说class并不直接与机器的操作系统相对应,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释给本地系统执行。
只有JVM还不能成class的执行,因为在解释class的时候JVM需要调用解释所需要的类库lib,而jre包含lib类库。
配置环境变量是为了让Java jdk\bin目录下的工具,可以在任意目录下运行,原因是,将该工具所在目录告诉了系统,当使用该工具时,由系统帮我们去找指定的目录。JAVA_HOME---
Java运行环境配置
b) PATH环境变量
作用是指定命令搜索路径,在shell下面执行命令时,它会到PATH变量所指定的路径中查找看是否能找到相应的命令程序。我们需要把 jdk安装目录下的bin目录增加到现有的PATH变量中,bin目录中包含经常要用到的可执行文件如Javac/Java/Javadoc等,设置好 PATH变量后,就可以在任何目录下执行Javac/Java等工具了。
c) CLASSPATH环境变量
作用是指定类搜索路径,要使用已经编写好的类,前提当然是能够找到它们了,JVM就是通过CLASSPTH来寻找类的。我们 需要把jdk安装目录下的lib子目录中的dt.jar和tools.jar设置到CLASSPATH中,当然,当前目录“.”也必须加入到该变量中。
d) JAVA_HOME环境变量
它指向jdk的安装目录,Eclipse/NetBeans/Tomcat等软件就是通过搜索JAVA_HOME变量来找到并使用安装好的jdk。
e) 永久配置方式
Java_Home:C:\Program Files\Java\jdk1.7.0_07
Path: %Java_Home%\bin;%Java_Home%\jre\bin;
ClassPath:. ; %Java_Home%\bin;%Java_Home%\lib\dt.jar;%Java_Home%\lib\tools.jar
特点:系统默认先去当前路径下找要执行的程序,如果没有,再去path中设置的路径下找
f) 临时配置方式(DOS)
set path=%path%;C:\Program Files\Java\jdk\bin set classpath=XXX
注释:环境变量的配置已经完成,DOS命令下输入Java/Javac/Java –version检测。
g) 补充几点
如果没有定义环境变量classpath,Java启动JVM后,会在当前目录下查找要运行的类文件;如果指定了classpath,那么会在指定的目录下查找要运行的类文件。
还会在当前目录找吗?两种情况:
- ? 如果classpath的值结尾处有分号,在具体路径中没有找到运行的类,会默认在当前目录再找一次。
- ? 如果classpath的值结果出没有分号,在具体的路径中没有找到运行的类,不会再当前目录找。
- ? 一般不指定分号,如果没有在指定目录下找到要运行的类文件,就报错,这样可以调试程序。
4. Java与Javac命令
Java是分两部分的:一个是编译,一个是运行。
- Javac:负责的是编译的部分,当执行Javac时,会启动Java的编译器程序。对指定扩展名的.Java文件进行编译。 生成了jvm可以识别的字节码文件。也就是class文件,也就是Java的运行程序。
- Java: 负责运行的部分.会启动JVM加载运行时所需的类库,并对class文件进行执行。
- javac MyInfo.Java
- java MyInfo
5. Java编译过程
6. Java内存处理机制
待补充~
7. 常用DOS命令
md |
创建文件夹 |
cd |
进入文件夹 |
del |
删除文件夹 |
copy |
复制文件 |
rd |
删除文件夹 |
cd.. |
后退一级 |
cls |
清屏 |
help |
帮助 |
dir |
列出目录 |
cd\ |
进入根目录 |
ren |
重命名 |
|
|
详见常用DOS命令大全及其用法
三、 Java编程风格与命名规范
1. 格式规范
a) 缩进嵌套的代码
b) 断开很长的句子
c) 使用空白
d) 不要直接使用Tab控制符
2. 命名规范
a) JAVA源文件的命名
JAVA源文件名必须和源文件中所定义的类的类名相同。且不能为关键字
b) Package的命名
Package名的第一部分应是小写ASCII字符,并且是顶级域名之一,通常是com、edu、gov、mil、net、org或由ISO标准3166、1981定义的国家唯一标志码。Package名的后续部分由各组织内部命名规则决定,内部命名规则指定了各组件的目录名,所属部门名、项目名等。
c) Class/Interface的命名
Class名应是首字母大写的名词, 而且大写中间单词的首字母。命名时应该使其简洁而又具有描述性。异常类的命名,应以Exception结尾。Interface的命名规则与Class相同。
d) 常量的命名
常量名的字母应全部大写,不同单词之间通过下划线连接,并且名字组合应该赋予含义。
e) 变量的命名
- 普通变量
普通变量名的首字母小写,其它每个单词的首字母大写。命名时应该使其简短而又有特定含义,简洁明了的向使用者展示其使用意图。
- 约定变量
所谓约定变量,是指那些使用后即可抛弃(throwaway)的临时变量。通常i、j、k、m和n代表整型变量;c、d和e代表字符型变量。
f) 方法的命名
方法名的第一个单词应是动词,并且首字母小写,其它每个单词首字母大写。
g) 方法参数的命名
选择有意义的名称作为方法的参数名。可能的话,选择和需要赋值的字段一样的名字。
详见Java编程风格与命名规范-ZZ
四、 语法基本元素
1. 关键字
a) 定义:被JAVA语言赋予了特殊含义的单词。
b) 特点:关键字中所有字母都为小写。
c) 注意:goto和const作为保留字存在,目前并不使用。
2.
标识符
a) 含义:标识符就是给类,接口,方法,变量等起名字时使用的连续字符序列。
b) 组成规则:① 英文大小写字母A~Z和a~z ② 数字字符0~9 ③ $ 和 _
c) 注意事项:① 不能以数字开头 ② 不能是Java中的关键字 ③ 区分大小写
④ 不能有空格
3. 数据类型、常量和变量
a) 基本数据类型
byte、short、int、long、float、double、char、boolean
b) 引用数据类型
数组、类、接口。
分类 |
不同类型 |
关键字 |
字节数 |
取值范围 |
默认值 |
整数型 |
字节型 |
byte |
1个字节 |
-27~27-1(-128~127) |
0 |
短整型 |
short |
2个字节 |
-215~215-1(-32768~32767) |
0 |
|
整型 |
Int (默认) |
4个字节 |
-231~231-1 |
0 |
|
长整型 |
long |
8个字节 |
-263~263-1 |
0 |
|
文本型 |
字符型 |
char |
2个字节 |
0~216-1(从0~65536) |
‘\u0000’ |
浮点型 |
单精度浮点型 |
float |
4个字节 |
大约±3.40282347E+38F (有效位数为6~7位) |
0.0f |
双精度浮点型 |
Double (默认) |
8个字节 |
大约±1.79769313486231570E+308 (有效位数为15位) |
0.0d |
|
逻辑型 |
布尔型 |
boolean |
1个字节 |
True/false |
false |
级别从低到高为:(byte--> short),char (这两个平级)--> int--> float--> long--> double
补充: 不同的字符所占的字节是不同的。详见输入输出章节之字符编码。
0x 0
c) 数据类型转换
不同的基本数据类型之间进行运算时需要进行类型转换,除boolean类型外所有的基本数据类型都需要考虑类型转换,其主要应用在算术运算和赋值运算时。
- 自动类型转换:除boolean型外,其他数据类型系统自动从低级别到高级别转换;
- 强制类型转换:把一个高级别的数赋给一个低级别的的变量。
- ? 溢出处理:待补充
- 补充:同种类型之间才可以进行强制装换。错误举例:String a = (String)2;
正确举例:String a = integer.toString(2)或int i = integer.parseInt(s);
d) 变量
所有局部变量在使用前都得初始化。详见类与对象部分。
e) 常量
在程序执行的过程中其值不可以发生改变的量,Java中常量可分为字面值常量和自定义常量。 eg. final double PI = 3.14;
- 字符串常量 用双引号括起来的内容
- 整数常量 所有整数 12,23,默认为int,长整型加字母‘L ’
- 小数常量 所有小数 12.34,56.7,默认为都double,float加字母‘F’
- ? 字符常量 用单引号括起来的内容‘a’,‘A’,‘0’和转义字符常量”\n”
- 布尔常量 较为特殊,只有true和false
- 空常量 null (数组部分讲解)
- 符号常量 用一个标识符来表示的常量,修饰符final,eg. #define PI 3.14
f)
|
整型常量的三种进制表示形式
- 十进制整数,如12, -314, 0。
- 八进制整数,要求以0开头,如012
- 十六进制数,要求0x或0X开头,如0x12
- 十进制数形式,必须含有小数点,例如:
f) 浮点类型常量两种表示形式
3.14 314.0 时 .314
- 科学记数法形式,如
3.14e2 3.14E2 314E2
g) 转义字符的用法
- System.out.println(“Hello\nWorld”);
- System.out.println(“Hello”+”\n”+”World”);
- 十进制和二进制的快速转换利用8421码;
- 二进制和八进制、十六进制之间转换以十进制作为桥梁;
- 二进制到八进制每3位一组合,二进制到十六进制,每4位一组合。
h) 进制转换诀窍
i) 有符号数据表示方法
在计算机内,有符号数有3种表示法:原码、反码和补码。所有数据的运算都是采用补码进行的。
- 原码:就是二进制定点表示法,即最高位为符号位,“0”表示正,“1”表示负,其余位表示数值的大小。
- 反码:正数的反码与其原码相同;负数的反码是对其原码逐位取反,符号位除外
- 补码:正数的补码与其原码相同;负数的补码是在其反码的末位加1
u [+1] = [00000001]原 = [00000001]反 = [00000001]补
u [-1] = [10000001]原 = [11111110]反 = [11111111]补
参考:
4. 运算符和表达式
表达式是由运算符与操作数组合而成的,运算符指明对操作数的运算方式。组成表达式的Java操作符有很多种。运算符按照其要求的操作数数目来分,可以有单目运算符、双目运算符和三目运算符,它们分别对应于1个、2个、3个操作数。运算符按其功能来分,有算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符和其他运算符。
优先级 |
运 算 符 |
含义描述 |
1 |
( ) ,. , [ ] |
强制运算、方法调用 |
2 |
++ , -- , + , ~(位取反) , !(逻辑取反) |
一元运算、字符串相加 |
3 |
New eg.Type a=new Type() |
创建运算符 |
4 |
* , / , % |
算术运算 乘除、取余 |
5 |
+ , - |
算术运算 加减(正负) |
6 |
<< , >> , >>>(无符号) |
位运算 |
7 |
< , > , <= , >= , instanceof |
比较运算、类运算 |
8 |
= = , ! = , .equals() |
比较运算 |
9 |
& |
逻辑(位)运算 与 |
10 |
^ |
位运算 异或 |
11 |
| |
逻辑(位)运算 或 |
12 |
&& |
逻辑 与(短路) |
13 |
|| |
逻辑 或(短路) |
14 |
?: eg. (关系表达式)?表达式1:表达式2; |
三元 条件运算 |
15 |
= , *= , /= , %= , += , -= |
简单赋值运算 |
16 |
<<= , >>= , >>>= , &= , ^= , |= |
复杂赋值运算 |
a) 算术运算符
(* , / , % , + , - )
- %:任何整数模2不是0就是1,所以只要改变被模数就可以实现开关运算。
- +:加法运算符,连接符。举例:123+321+hello=444hello;hello+123+321=hello123321;hello+(123+321)=hello444;
- ++,--:自增自减运算。举例:int a=5;print(4++);//5 print(++4);//7
- * , /:注意小数类型计算问题。1/2*2=0,1/2*2.0=0,1/2.0*2=1.0。整型与整型运算得整型,整形与浮点型运算得浮点型,结果取取决于第一次遇到浮点型的位置。
b) 赋值运算符
(= , *= , /= , %= , += , -=)
c) 比较运算符
(< , > , <= , >= ,= = , ! =) boolean bo=(i==j);
特点:该运算符的特点是:运算完的结果,要么是true,要么是false。
待补充:==与.equals()区别:
d) 逻辑运算符
(& , | , ^ , ! , && , ||)
逻辑运算符除了 ! 外都是用于连接两个boolean类型表达式。
- &:只有两边都为true结果是true。否则就是false。
- |:只要两边都为false结果是false,否则就是true。
- ^:异或:和或有点不一样。
u 两边结果一样,就为false;
u 两边结果不一样,就为true。
- & 和 &&区别:
u &:无论左边结果是什么,右边都参与运算(建议短路与);
u &&: 短路与,如果左边为false,那么右边不参数与运算。
- | 和|| 区别:
u |:两边都运算(建议短路或);
u ||:短路或,如果左边为true,那么右边不参与运算。
e) 位运算符
用于操作二进制位的运算符;& , | , ^ ,<< , >> , >>>(无符号)
- << :空位补0,被移除的高位丢弃。
- >> :最高位是零,右移空位补0;最高位是1,最高位补1,其他空位补0。
- >>> :最高位无论是0或1,右移空位都补0。
- & :任何二进制和0的 & 运算,结果都是0;和1的 & 运算,结果都是原值。
- | :任何二进制和0的 | 运算,结果都是原值;和1的 | 运算,结果都是1。
- ^ :任何相同二进制的 ^ 运算,结果都是0;不同的 ^ 运算,结果都是1。
- 赋值表达式本身也是一个值,其值等于左值。举例:int b = (a=6);得出b=6。
f) 表达式
待补充~
五、 流程控制
在Java中,流程控制分为三种基本结构:顺序结构、分支结构和循环结构。
If 判断: |
else If 判断: |
If 判断(嵌套): |
switch 判断: |
||
if (表达式) { //语句1; } else { //语句2; } 类比: (关系表达式)? 表达式1:表达式2; 注:else语句可用 Continue代替; |
if (表达式1) { //语句1; } else if (表达式2) { //语句2; … } else { //语句3; } |
If (表达式1) { if(表达式2) { //语句1; } else { //语句2; } } else { //语句3; } |
switch (表达式) { case 常量1: 语句1; break; case 常量2: 语句2; break; … default: 语句3; } |
||
while循环: |
do-while循环: |
for循环: |
|||
while(表达式) { //循环体; } |
do{ //循环体; } while(表达式); |
for(初始化;条件;迭代) { //循环体; } |
|||
1. 循环语句
a) 循环是相同动作的集合.
b) 循环次数确定的情况,通常选用for循环;
循环次数不确定的情况,通常选用while或do-while循环。
c) 初始情况不满足循环条件时,while循环一次都不会执行;
do-while循环不管任何情况都至少执行一次。
d) for 循环,若变量在判断条件内初始化,则作用域为循环体(大括号)内。
e) 嵌套循环判断条件:内层循环一圈,外层循环一次。
2. 判断语句
a) 多重if与switch选择结构比较
- 相同点:都是用来处理多分支条件的结构
- 不同点:switch选择结构只能处理等值条件判断的情况;多重if选择结构没有switch选择结构的限制,特别适合某个变量处于某个连续区间时的情况。
b) else If 顺序进行判断,当前面条件不满足时才进行下一步,条件满足,跳过剩余结构,结束判断语句。
c) switch 后面的小括号中的变量应该是int、short、byte、char、枚举类型、String。
d) switch 可进行语句合并,以进行连续区间判断。(省去语句和break)
3. 跳转语句
a) break:结束判断语句,执行完判断体后的语句后,终止离自己最近的外层循环。
do { String title = input.next(); if (title.equals("exit")) { break; } } while (true); // 跳出死循环 |
b) continue:结束判断语句,终止本次循环,不执行判断体后面语句,进入下一次循环。
public static void main(String[] args) { int i = 0; do { i++; if (i == 3) { // break; // 输出12 continue; // 输出1245 } System.out.println(i); } while (i < 5); } |
c) return:结束当前方法的执行并退出,并返回值到调用该方法的语句处。
for (int i = 0; i < 10; i++) { if (i % 2 != 0) return; System.out.print(i); } // 运行结果:0 |
4. flag 或index标记的用法
- 循环内判断用flag标记返回判断结果。
- 返回函数返回值时,用flag传递返回值。
- 需要交互输入的函数最好放在测试类的main中,否者传递返回值会很麻烦。
六、 数组
数组是一个变量,存储相同数据类型的一组数据
步骤 |
举例 |
解释 |
声明 |
int[ ] a; |
告诉计算机数据类型是什么 |
分配空间 |
a = new int[5]; |
告诉计算机分配几个连续的空间 |
赋值 |
a[0] = 8; |
向分配的格子里放数据 |
引用 |
a[0] = a[0] * 10; |
引用数据进行计算 |
1. 数组的常用操作
a) 声明数组并分配空间
数据类型[ ] 数组名 = new 数据类型[大小];
b) 数组的初始化(声明的同时赋值)
- 方法1: 边声明边赋值。
int[ ] scores = {89, 79, 76};
int[ ] scores = new int[ ]{89, 79, 76};
- 方法2:动态地从键盘录入信息并赋值。
Scanner input = new Scanner (System.in);
for (int i = 0; i < 30; i ++) {
scores[i] = input.nextInt ();}
- 创建数组并赋值的方式在一条语句中完成。
- 所有的数组下标默认从0开始,而且访问时不可超出定义的上限,否则会产生越界错误。
- for 遍历 (逆序输出)
c) 数组的遍历
for (int k = array.length - 1; k >= 0; k--) {// 逆序输出 System.out.print(array[k] + "\t"); } |
- for增强型函数
for (int temp : array) {// 用for增强型函数对数组进行顺序输出 System.out.println(temp); } |
- 对数组进行增添,修改,删除
数组的增添举例: String[] phones = new String[] {"iPones4S", "iPones5", null}; // null 不用加双引号 int index = -1;// 增加监视器 for (int i = 0; i < phones.length; i++) {// 1.增加iPones6 if (phones[i] == null) { index = i; break; } } if (index != -1) { phones[index] = "iPones6"; } else { System.out.println("数组已满"); } System.out.println(Arrays.toString(phones)); |
注释:数组的修改与删除类似
d) 数组的排序
- 冒泡排序
for (int m = 0; m < array.length - 1; m++) { // 冒泡排序 for (int n = 0; n < array.length - 1 - m; n++) { if (array[n] > array[n + 1]) { int temp = array[n]; array[n] = array[n + 1]; array[n + 1] = temp; } } } |
e) 使用Arrays 类操作数组
方法名称 |
说明 |
|
Arrays. |
boolean equals(array1,array2) |
比较array1和array2两个数组是否相等 |
sort(array) |
对数组array的元素进行升序排列 |
|
String toString(array) |
将一个数组array转换成一个字符串 |
|
void fill(array,val) |
把数组array所有元素都赋值为val |
|
copyOf(array,length) |
把数组array复制成一个长度为length 的新数组,返回类型与复制的数组一致 |
|
int binarySearch(array, val) |
查询元素值val在数组array中的下标 (要求数组中元素已经按升序排列 |
注释:import java.util.*;
2. 二维数组
一、 数组的初始化
String[][] str = new String[5][]; int[][] scores = new int[][] { { 90, 85, 54 }, { 76, 80 },{ 87 } }; int[][] scores = { { 90, 85, 54 }, { 76, 80 }, { 87 } }; |
二、 数组的遍历
public class Case5 { // 二维数组的遍历 public static void main(String[] args) { int[][] scores = new int[][] { { 90, 78, 54 }, { 76 , 80 }, { 87 } }; for (int i = 0; i < scores.length; i++) { //外层数组长度 for (int j = 0; j < scores[i].length; j++) {//内层数组长度 System.out.println(scores[i][j]); } } } } |
Java 面向对象
一、 前言
1. 理解面向对象
- 面向对象是相对面向过程而言
- 面向过程强调的是功能行为
- 面向对象将功能封装进对象,强调具备了功能的对象。
- 开发的过程:其实就是不断的创建对象,使用对象,指挥对象做事情。
- 设计的过程:其实就是在管理和维护对象之间的关系。
- 封装(encapsulation)
- 继承(inheritance)
- 多态(polymorphism)
- 类:是具有相同属性和方法的一组对象的集合。
- 对象:用来描述客观事物的一个实体,由属性和方法构成。
- 类是对象的抽象,对象是类的具体实例。
- 类是抽象的,不占用内存,而对象是具体的,占有内存空间。
2. 面向对象的特点
二、 类和对象
1. 类和对象的定义
public class ConstructorMethod { String name; int age; // 成员变量 public ConstructorMethod(String name, int age) { //有参构造方法,构造方法无返回类型 this.name = name; this.age = age; //实例的属性 = 实参; } public static int Case1() { //成员方法 System.out.println("我是Case1调用"); return 0; } } |
2. 类的创建和使用
a) 类的定义
- 访问修饰符 class 类名 {成员变量;成员方法} // 详见上例
- 引用类的属性:对象名.属性
b) 类的使用
举例:center.name = "北京中心"; //给name属性赋值
- 引用类的方法:对象名.方法名()
举例:center.showCenter(); //调用showCenter()方法
c) 补充说明
- 在Java中,类是按须加载,只有当需要用到这个类的时候,才会加载这个类,并且只会加载一次。
- 一个类文件中Public类只有一个,且和类文件同名。且类的访问权限只有Public和Default,包内均可见,故包内类不可同名。
- 类名 对象名= new 类名();
3. 对象的创建和使用
a) 对象的创建
举例:School center = new School();
b) 对象的使用
- 用对象名引用类的属性和方法。参见类的使用。
- 类名 对象名= new 类名(); //对象的声明与初始化
- 数据类型 变量名 = new 数据类型(值); //变量的声明与初始化
- 对象名和变量名都是一种引用。数据类型可以当做一种特殊的类。在此概念下,变量和对象的特性可部分通用。
- 成员变量:在类的范围内声明,在类的整个范围内都能用,类似C语言中全局变量
- 局部变量:在局部(方法体内)声明,大括号(或循环体)限制局部变量的作用域。
- 数据类型 变量名 = 值;或 数据类型 变量名 = new 数据类型(值);
- 第二种声明方式一般不用,本文引入主要是为了与对象的定义进行对比。
- 局部变量在使用前必须赋初始值。成员变量即使没有赋初始值,系统会自动初始化,
d) 对象和变量的几点思考
4. 成员变量VS局部变量
a) 定义
b) 变量的声明与初始化
c) 使用规则
基本数据类型为初始化为0,布尔类型为false,引用类型为null。
- 局部变量和成员变量可以同名,同名时,就近使用。
- Java没有全局变量的概念。概念相近的有静态变量。
public class Test { public static void main(String[] args) { Cat cat = null; //局部变量需要初始化或传实参 Pet pet = new Cat(); cat = (Cat)cat; } }
class Pet { }
class Cat extends Pet { } |
5. 成员方法VS方法
a) 定义
- 所谓方法,就是可以重复使用的一段代码的组合,是一个功能模块。成员方法是定义在类内部的方法。
一般格式:访问修复符 返回值类型 方法名(参数列表){方法体};
- 访问修饰符:方法允许被访问的权限范围, 可以是 public、protected、private 甚至可以省略。
- 返回值类型:方法返回值的类型。如果方法不返回任何值,则returnValueType的值为 void ;如果方法返回值不为void,则需要指定返回值的类型,并且在方法体中使用 return 语句返回值。
- 方法名:定义的方法的名字,必须使用合法的标识符
- 参数列表:传递给方法的参数列表,参数可以有多个,多个参数间以逗号隔开,每个参数由参数类型和参数名组成,以空格隔开。定义时的参数成为形参,须定义数据类型。调用时的参数称为实参,实参不用定义数据类型。
- 方法体:方法体包含定义哪些方法语句的集合。
- 语句:语句必须写在方法体内,除初始化语句。
b) 访问修饰符
类的访问权限有Public和Default两种,类中成员的访问权限有下列四种:
- Public: 能访问该类的任何地方可以访问Public成员。
- Protected:(子类修饰符)包内,和包外自身从父类继承而来的受保护成员。
- Default:(包修饰符)包内成员可相互访问。
- Private:(私有修饰符)只有自己类内的成员可以访问。
- ? 访问权限由大到小:public>protected>default>private
补充:继承权限测试. Package 。
继承权限测试(类之间) & 访问权限测试(属性和方法)测试结果:
- ? 访问修饰符是限制要访问(继承)自己的对象权限的符号
- ? 应用范围:
u 类的访问修饰符只有public和默认两种。
u 类中成员 public(公共修饰符) --> protected(子类) --> default(包)--> private(私有)
u 局部变量作用域确定,故没有访问修饰符,访问修饰符仅针对成员变量和方法。
- ? 访问方式:
u 子类对父类的访问,直接调用属性或方法(其实隐含this.调用)。
u 非继承关系或父类对子类的访问的访问方式,需用对象或类名访问。
- ? 应用原理:
u 访问权限是基于继承决定的,继承权限也是一种访问权限。
u 继承则属性和方法归父类和子类共有,但除私有成员不可以访问。
u 子类可以选择重写父类的属性和方法的访问修饰符,权限不能小于父类
c) 方法的调用
- 在同一个包里,普通方法需要用对象名调用,静态(类)方法可以直接类名调用(同一类甚至不用写类名)。
d) 方法的重载
方法重载,就是在同一个类里中可以创建多个方法,方法名要一样,参数列表不同,和访问修饰符 、返回值无关。调用方法时通过传递给它们的不同参数个数和参数类型来决定具体使用哪个方法,是一个类中多态性的一种表现。
方法重写,在子类和父类之间对,父类的函数进行重新定义。子类名称和参数别表与父类相同,方法体重写。
6. 构造方法
所谓构造方法,就是构造对象的方法,通过new + 构造方法, 这样就创建了一个新的对象。
- 构造方法要与类名相同,无返回类型。在创建对象的时候,对象的属性进行初始化,减少初始化语句。
- 每一个类都有构造方法,在没有定义时,系统默认创建一个无参构造方法。
- 构造方法是一类特殊的方法,在定义和调用具有普通方法的特性。
- 成员变量包括实例变量和类(静态)变量;而成员方法包括实例方法、类(静态)方法。
- 类变量、类方法是属于类的变量、方法,必须是静态的,要加static;故其又称静态变量、静态方法。可以不用实例,直接用类就可以访问或调用类变量或类方法。
- 实例变量和实例方法在一个类中没有static修饰,使用变量或方法时候先实例化(创建对象)才能访问或调用实例变量或实例方法;
- 方法与变量是个总体性概念,定义在类内部的被称为成员变量与成员方法,定义在main方法或方法体内部的变量被称为局部变量。
- 实例方法引用this指向正在执行方法的类的实例,构造方法引用this指正在的构造对象,静态方法不能使用this和supper关键字,构造方法根据不同参数指向类的实例。
- super()或者this()都是调用构造函数,构造函数用于初始化,所以初始化的动作要先完成。
- super() 或 super. 只能在子类里使用。
7. 补充几点
a) 变量几个概念的解析
b) this和supper的用法
public class Test { private String name; public Test(){ this.name = "小新"; } public Test(String name) { System.out.println("我是" +this.name); } public static void main(String[] args) { Test test = new Test("小强"); } } //运行结果:我是null(实参没有该成员变量赋值) |
c) main 方法
public class Test { public static void main(String[] args) { for (int i = 0; i < args.length; i++) { System.out.println("args" + i + "=" + args[i]); } } } 输出结果: args0=args1 args1=args2 |
- Public:表示的这个程序的访问权限,表示的是任何的场合可以被引用,这样java虚拟机就可以找到main()方法,从而来运行javac程序。
- Static:表明方法是静态的,不依赖类的对象的,是属于类的,在类加载的时候main()方法也随着加载到内存中去。如果主函数设置成非静态,则无法运行。或实例化主方法。
- Void:main()方法是不需要返回值的。
- Main:作为程序的入口,有main方法程序才能运行。
- args[]:是用来从控制台接收参数。
- 静态方法内不可以调用非静态属性,若要调用非静态成员属性,则需要把属性设置成静态属性。或者把把非静态成员单独封装成方法,再通过方法调用
- 方法体内部只可定义语句,不可再定义方法。
- 参数的声明
- 接收实参的几种形式
d) 方法传参
u int month = 4; u int day = input.nextInt(); 注:作为形参的变量的初始化便是传入实参。 |
待补充~
e) 内部类
内部类将相关的类组织在一起,从而降低了命名空间的混乱。一个内部类可以定义在另一个类里,可以定义在函数里,甚至可以作为一个表达式的一部分。内部类可以直接访问外部类中的成员。而外部类想要访问内部类,必须要建立内部类的对象。
- Java 内部类种类及使用解析 http://www.cnblogs.com/mengdd/archive/2013/02/08/2909307.html
http://blog.sina.com.cn/s/blog_accab4a90101gzh2.html
① 匿名内部类
匿名内部类也就是没有名字的内部类,正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写。但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口。
abstract class Person { public abstract void eat(); }
public class Demo { public static void main(String[] args) { Person p = new Person() { public void eat() { System.out.println("eat something"); } }; p.eat(); } } |
h) Static关键字
- 想要实现对象中的共性数据的对象共享。可以将这个数据进行静态修饰。
- 被静态修饰的成员,可以直接被类名所调用。也就是说,静态的成员多了一种调用方式。类名.静态方式。
- 静态随着类的加载而加载。而且优先于对象存在。
① 静态代码块
一个类可以使用不包含在任何方法体中的静态代码块,当类被载入时,静态代码块被执行,且只被执行一次,静态块常用来执行类属性的初始化。
static { System.out.println ("Hello Everyone"); } |
方法里不可以定义static
待补充~
i) final关键字
这个关键字是一个修饰符,可以修饰类,方法,变量。
- 被final修饰的类是一个最终类,不可以被继承。
- 被final修饰的方法是一个最终方法,不可以被覆盖(重写)。
- ? 被final修饰的变量是一个常量,只能赋值一次。
三、 封装、继承和多态
1. 封装
public class Test { private String name; //将类的属性进行封装 public String getName() { //并提供可带判断条件的访问方法 return name; }
public void setName(String name) { this.name = name; } } |
a) 定义
将类的属性隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。
b) 步骤
修改属性的可见性(设为private,防止错误的修改)à创建公有的getter/setter方法(用于属性的读写)à在getter/setter方法中加入属性控制语句(对属性值的合法性进行判断)。
c) 优点
- 封装确实可以使我们容易地修改类的内部实现,而无需修改使用了该类的客户代码。
- 可以对成员变量进行更精确的控制。
- 限制对属性的不合理操作;
- 结合权限控制,限制客户端的操作权限。
2. 继承
class Person { // 父类 private String name; public static int age; public static String sex;
public Person() { // 会自动生成无参构造器 System.out.println("父类构造器"); }; // 但是考虑继承的可靠性,建议写上。
public void setName(String name) { this.name = name; }
public String getName() { return this.name; } }
class Student extends Person { // 子类 private String school;
public Student() { // 子类student中构造 super(); //super只可在子类中使用 System.out.println("子类构造器"); } } public class TestExtendDemo { // 测试类 public static void main(String[] args) { Person.age = 18; Student.age = 19; // 继承父类的成员属性和方法可直接调用 Person.sex = "男"; // 静态变量或方法用类调用 System.out.println(Student.age+Person.sex); Scanner input = new Scanner(System.in); Student someone = new Student(); //创建子类对象的过程 someone.setName(input.next()); // 实例变量和方法用实例调用 System.out.println(someone.getName()); } } |
a) 定义
继承是面向对象最显著的一个特性。多个类中存在相同属性和方法时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和方法,只要继承那个类即可。继承是代码重用的一种方式。Java通过extends关键字来实现单继承。(Is a)
b) 继承的基本原则
- 能够继承除构造方法外所有成员,但private成员不可以访问;
- 可以同通过super()调用类的构造方法,已提高代码重用性。同样super()可以调用父类的成员属性和成员方法。
- Java具有单根继承性,只能继承一个父类。
- Java继承具有传递性,支持多级继承。
- Object类是所有类的父类。
- 声明:[访问修饰符] class Dog extendsPet { //子类特有的属性和方法}
- 调用父类构造方法:super(); super(name);
c) 格式与引用
访问父类属性:super.name;
调用父类方法:super.print();
d) 补充几点
- 父类没有无参构造器,则需在子类构造器的第一行显式调用父类的有参构造器。
原因:子类的所有构造器中的第一行,其实都有一条隐身的语句super(), super()表示调用父类的无参构造器。子类继承父类中的属性,在new 子类的时候,相应的父类的属性也就拿到了,这就是子类的实例化过程。
然而父类有有参构造器,则无参构造器就不存在了,所以在子类中须在第一行显式调用super(参数),或者在父类中添加一个无参构造器。之所以须在第一行调用,是因为父类的构造器调用以及初始化过程一定在子类的前面。
并且,父类也被new一个对象出来的说法是错误的,举例抽象类可以被继承的,但抽象类不能被实例化。
- 隐藏和覆盖的区别:
参考:Java中属性的隐藏与方法的覆盖的区别
参考:静态绑定与动态绑定
- 方法重写的原则:
u 子类方法重写时方法名、参数、返回值、返回类型均须相同。访问权限不小于父类。
解析:确保子类可以重写父类。父类不能覆盖子类。
u 重写是父子类要么都静态,要么都不静态。
u 子类可以定义与父类同名的静态方法,以便在子类中隐藏父类的静态方法。
u 父类的私有方法不可访问,不能被子类重写。
3. 多态
abstract class Animal { // 因为本身实例化没有意义,所以一般申明为抽象类 Animal() { // 会自动生成无参构造器 }; // 但是考虑继承的可靠性,建议写上。
abstract void eat();// 抽象方法 } class Cat extends Animal { // 1、继承父类 public void eat() { // 2、子类方法的重写 System.out.println("吃鱼"); }
public void catchMouse() { //子类私有方法,只能用子类对象调用 System.out.println("抓老鼠"); } }
class Dog extends Animal { // 1、继承父类 public void eat() { // 2、子类方法的重写 System.out.println("吃骨头"); }
public void kanJia() { System.out.println("看家"); } }
class DuoTaiDemo { // 类名与源文件相同,否则无法运行 public static void main(String[] args) { function(new Cat()); // new Cat() 表示构造一个猫的对象 function(new Dog());
//Animal a; //对象可像变量一样声明和传递参数 //a= new Cat(); //父类为抽象类,不能实例化,但能创建引用 Animal a=new Cat(); // 向上转型 // 类似变量传参,new Cat()是参数 a.eat(); //3.父类对象引用指向子类对象 PS:须先满足前两个条件 }
public static void function(Animal a) { //对象可作变量传递参数 a.eat(); //3.父类对象引用指向子类对象 PS:须先满足前两个条件
if (a instanceof Cat) {// 用于判断对象是否为指定类的实例 Cat c = (Cat) a; // 向下转型,由Animal(父)类转为Cat(子)类 c.catchMouse(); // 向下转型,调用子类特有属性 } else if (a instanceof Dog) { Dog c = (Dog) a; c.kanJia(); } } |
public class Polymorphism { // 多态传参 ,取例 Think in Java void doStuff(Shape s) { // 形参 s.erase(); // ... s.draw(); }
public static void main(String[] args) {
Circle c = new Circle(); // Circle/Triangle/Line是Shape的子类 Triangle t = new Triangle(); Line l = new Line(); doStuff(c); // 实参 doStuff(t); doStuff(l); } } |
a) 定义
指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是方法调用),与方法不同的是对象的属性则不具有多态性。
b) 实现技术
动态绑定:是指在程序运行时判断所引用对象的实际类型,根据其实际的类型调用其相应的方法,是多态实现的具体形式。执行动态绑定的非静态方法
补充知识:静态绑定:又称为前期绑定在程序编译时进行了绑定,即在还没运行时,就已经加载到内存。执行静态绑定的有变量和静态方法
c) 必要条件
- 要有继承(实现多态的前提);
- 要有重写(实现多态的基础);
- 父类引用指向子类对象
d) 实现方式
接口实现,继承父类进行方法重写,同一个类中进行方法重载。当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的该同名方法。
e) 作用
消除类型之间的耦合关系
f) 补充几点
- 多态在子父类中的成员上的体现的特点:(待进一步完善)
u 成员变量:在多态中,子父类成员变量同名。
在编译时期:参考的是引用型变量所属的类中是否有调用的成员。(编译时不产生对象,只检查语法错误)
运行时期:也是参考引用型变量所属的类中是否有调用的成员。
简单一句话:无论编译和运行,成员变量参考的都是引用变量所属的类中的成员变量。再说的更容易记忆一些:成员变量à编译运行都看 = 左边。
u 静态函数
编译时期:参考的是引用型变量所属的类中是否有调用的成员。
运行时期:也是参考引用型变量所属的类中是否有调用的成员。
为什么是这样的呢?因为静态方法,其实不属于对象,而是所属于该方法所在的类。
调用静态的方法引用是哪个类的引用调用的就是哪个类中的静态方法。
简单说:静态函数à编译运行都看 = 左边。
u 成员函数
编译时期:参考引用型变量所属的类中是否有调用的方法。
运行事情:参考的是对象所属的类中是否有调用的方法。
为什么是这样的呢?因为在子父类中,对于一模一样的成员函数,有一个特性:覆盖。
简单一句:成员函数,编译看引用型变量所属的类,运行看对象所属的类。
更简单:成员函数à编译看 = 左边,运行看 = 右边。
四、 抽象类和接口
1. 抽象类(abstract)
public abstract class ClassName { int name; abstract void fun(); } |
a) 定义
- 抽象类:被定义为抽象的类,它用来集合子类的通用特性的。不能被实例化,只能被用作子类的超类。
- 抽象方法:它只有声明,而没有具体的实现,如果一个类含有抽象方法,则称这个类为抽象类。抽象方法声明格式为:abstract void fun();因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。
- 抽象类就是为了继承而存在的,如果定义一个抽象类,不去继承它,没有任何意义。
- 抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
- 抽象类可以没有抽象方法,也可以全部是抽象方法,如果子类继承的父类是抽象类,则继承父类所有非私有方法,又由于存在抽象类的方法为抽象方法,故子类必须重写父类里所有的抽象的方法(除非父类抽象类没有抽象方法),才能被实例化,也就是创建对象,要不然子类也将是个abstract类,不能被实例化。
- 抽象类中可以有非抽象的构造方法,创建子类的实例时可能调用。
b) 特点
2. 接口(interface)
public interface InterfaceName { public abstract void cry(); } class ClassName implements Interface1,Interface2....{ } |
a) 定义
在软件工程中,接口泛指供别人调用的方法或者函数。从这里,我们可以体会到Java语言设计者的初衷,它是对行为的抽象。
接口是抽象方法的集合。如果一个类实现了某个接口,那么它就继承了这个接口的抽象方法。这就像契约模式,如果实现了这个接口,那么就必须确保使用这些方法。
接口主要用来描述类具有什么功能,自身不能做任何事情。具体实现交由实现接口的那个类来完成。
b) 特点
- 接口中可以声明变量,且被隐式地指定为public static final变量。可当做全局常量用。
- 接口是对方法的抽象,定义的方法必须是抽象方法。系统默认都是public abstract
- 接口不可以实例化。
- 实现类且必须实现所有的接口。如果不能全部实现,则定义两个接口。
- 接口也可以extends接口
- 接口回调:是指可以把使用某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。实际上,当接口变量调用被类实现的接口中的方法时,就是通知相应的对象调用接口的方法,这一过程称为对象功能的接口回调。 参考:http://blog.csdn.net/hack_bug/article/details/7625646
- 如果需要从子类向基类进行向上转型,则使用基类,否则可使用组合关联。
- 如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。
- 如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。
c) 其他几点补充
d) 什么时候使用抽象类和接口
3. 抽象类和接口的对比
a) 语法层面的区别
参数 |
抽象类 |
接口 |
默认的方法实现 |
它可以有默认的方法实现 |
所有的方法都是抽象的。不存在方法的实现 |
实现 |
子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 |
子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现 |
构造器 |
抽象类有构造器,用于子类初始化 |
接口不能有构造器 |
普通类 |
除了不能实例化抽象类之外,它和普通Java类没有任何区别 |
接口是完全不同的类型 |
访问修饰符 |
抽象方法可以有public、protected和default这些修饰符 |
接口方法默认修饰符是public。不可以使用其它修饰符。 |
main方法 |
抽象方法可以有main方法并且我们可以运行它 |
接口没有main方法,因此我们不能运行它。 |
多继承 |
抽象方法可以继承一个类和实现多个接口 |
接口只可以继承一个或多个其它接口 |
速度 |
它比接口速度要快 |
接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。 |
添加新方法 |
如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 |
如果你往接口中添加方法,那么你必须改变实现该接口的类。 |
b) 设计层面上的区别
抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。
五、
异常
1. JAVA异常
Java异常处理的目的是提高程序的健壮性。你可以在catch和finally代码块中给程序一个修正机会,使得程序不因不可控制的异常而影响程序的流程。同时,通过获取Java异常信息,也为程序的开发维护提供了方便。
a) 概念解析
- Java中的异常用对象来表示。
- Java异常处理通过5个关键字try、catch、throw、throws、finally进行管理。
- 异常是针对方法来说的,抛出、声明抛出、捕获和处理异常都是在方法中进行的。
- Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。
- Error(错误):表示仅靠程序本身无法恢复的严重错误。
- Exception(异常):表示程序本身可以处理的异常。
- RuntimeException:是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。Java编译器不去检查它。这种异常可以通过改进代码实现来避免。
- ThreadDeath:
- 运行时异常:
b) 运行时异常和受检查异常
RuntimeException类及其子类都被称为运行时异常,这种异常的特点是Java编译器不去检查它,也就是说,当程序中可能出现这类异常时,即使没有用try...catch语句捕获它,也没有用throws字句声明抛出它,还是会编译通过。
- 受检查异常:
除了RuntimeException类及其子类外,其他的Exception类及其子类都属于受检查异常,这种异常的特点是要么用try...catch捕获处理,要么用throws语句声明抛出,否则编译不会通过。
- 两者的区别:
运行时异常表示无法让程序恢复运行的异常,导致这种异常的原因通常是由于执行了错误的操作。
受检查异常表示程序可以处理的异常。受检查异常表示程序可以处理的异常。如果抛出异常的方法本身不处理或者不能处理它,那么方法的调用者就必须去处理该异常,否则调用会出错,连编译也无法通过。
当然,这两种异常都是可以通过程序来捕获并处理的,比如除数为零的运行时异常。
2. 处理异常机制
异常处理机制为:抛出异常,捕捉异常。
- 捕捉异常:在方法中用try...catch语句捕获并处理异常,catach语句可以有多个,用来匹配多个异常。
- 抛出异常:对于处理不了的异常或者要转型的异常,在方法的声明处通过throws语句抛出异常。
- Java规定:对于所有的可查异常,一个方法必须捕捉,或者声明抛出方法之外。
3. 捕获异常try / catch 和 finally
import java.util.InputMismatchException; import java.util.Scanner;
public class TryCatch { public static void main(String[] args) { Scanner input = new Scanner(System.in);
try {// 可能会发生异常的程序代码 System.out.print("请输入一个数字:"); int a = input.nextInt(); } catch (InputMismatchException e) {// 捕捉异常 System.err.println("数据类型不符!"); e.printStackTrace(); System.err.println(e.getMessage()); // return; 若捕捉到异常会先执行finally, 再return。 } catch (Exception e) {// catch 先写子类,再写父类 System.out.println("再捕捉一次!"); System.exit(1); } finally {// 除非执行System.exit(1),否则都会执行 System.out.println("finally 被执行!"); // 应用举例:确保关闭数据库,关闭流 }
System.out.println("我还是被执行了!"); // 如果提前return,则不执行了 } } |
4. 抛出异常 throws和throw
public class Throws {
public static void main(String[] args) throws Exception{ System.out.println(10 / 0); throw new Exception("抛出异常"); //System.out.println("throw后面的代码不再执行"); } } |
5. Java常见异常
a) runtimeException子类:
- ? java.lang.ArrayIndexOutOfBoundsException:
数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
- ? java.lang.ArithmeticException:
算术条件异常。譬如:整数除零等。
- ? java.lang.NullPointerException:
空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等
- ? java.lang.ClassNotFoundException:
找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
- java.lang.NegativeArraySizeException:数组长度为负异常
- java.lang.ArrayStoreException:数组中包含不兼容的值抛出的异常
- java.lang.SecurityException:安全性异常
- java.lang.IllegalArgumentException:非法参数异常
- ? IOException:操作输入流和输出流时可能出现的异常。
- EOFException:文件已结束异常
- FileNotFoundException:文件未找到异常
- ClassCastException:类型转换异常类
- ArrayStoreException:数组中包含不兼容的值抛出的异常
- SQLException:操作数据库异常类
- NoSuchFieldException:字段未找到异常
- NoSuchMethodException:方法未找到抛出的异常
- NumberFormatException:字符串转换为数字抛出的异常
- StringIndexOutOfBoundsException:字符串索引超出范围抛出的异常
- IllegalAccessException:不允许访问某类异常
- InstantiationException:当应用程序试图使用Class类中的newInstance()方法创建一个类的实例,而指定的类对象无法被实例化时,抛出该异常
b) IOException
c) 其他
6. 自定义异常
待补充~
7. Java异常处理的原则和技巧
- 避免过大的try块。
- 不要把自己能处理的异常抛给别人。
- catch块尽量保持一个块捕获一类异常
- 细化异常的类型,不要不管什么类型的异常都写成Excetpion。
- 不要用try...catch参与控制程序流程,异常控制的根本目的是处理程序的非正常情况。
8. 补充几点
如果出现异常的线程为主线程,则整个程序运行终止;如果非主线程,则终止该线程,其他线程继续运行。
如果父类或者接口中的方法没有抛出过异常,那么子类是不可以抛出异常的,如果子类的覆盖的方法中出现了异常,只能try不能throws。
如果这个异常子类无法处理,已经影响了子类方法的具体运算,这时可以在子类方法中,通过throw抛出RuntimeException异常或者其子类,这样,子类的方法上是不需要throws声明的。
越早处理异常消耗的资源和时间越小,产生影响的范围也越小。
六、
面向对象的几点猜想
Java 高级特性
一、 集合框架和泛型
1. 集合 框架 API
大多集合能够处理任何类型的数据,因为这些集合定义的处理对象使用了Object类型。
所有的Java集合都在java.util包中。
上述类图中,实线边框的是实现类,比如ArrayList,LinkedList,HashMap等,折线边框的是抽象类,比如AbstractCollection,AbstractList,AbstractMap等,而点线边框的是接口,比如Collection,Iterator,List等。 |
Java 集合框架 |
|||||
接口 |
实现类 |
是否有序 |
是否允许元素重复 |
特点 |
|
Collection |
Collection |
无序 |
不唯一 |
父接口 |
|
List |
ArraysList |
有序 |
不唯一 |
遍历访问效率高[Z1] |
|
LinkedList |
有序 |
不唯一 |
插入删除效率高 |
||
Set |
AbstractSet |
无序 |
唯一 |
|
|
HashSet |
查找效率高 |
||||
Map |
TreeSet |
二叉排序树 |
key必须唯一 |
|
|
AbstractMap |
否 |
|
|||
HashMap |
查询指定元素效率高 |
||||
TreeMap |
二叉排序树 |
|
a) Collection 接口抽象方法:(List、Set 、Map实现接口)
返回类型 |
方法 |
解释 |
boolean |
add(E e) |
确保此 collection 包含指定的元素(可选操作)。 |
void |
clear() |
移除此 collection 中的所有元素(可选操作)。 |
boolean |
contains(Object o) |
如果此 collection 包含指定的元素,则返回 true。 |
boolean |
isEmpty() |
如果此 collection 不包含元素,则返回 true。 |
boolean |
remove(Object o) |
从此 collection 中移除指定元素的单个实例。 |
int |
size() |
返回此 collection 中的元素数。 |
boolean |
equals(Object o) |
比较此 collection 与指定对象是否相等。 |
Object[] |
toArray() |
返回包含此 collection 中所有元素的数组。 |
Iterator<E> |
iterator() |
返回在此 collection 的元素上的迭代器 (接口)。 |
Ps:1.传入均为形参,不须类型修饰。2.接口不可实例化。方法全部交由实现类实现。
class Arrays { // 除特殊表明,为 Collection接口所有实现类所的方法 ArrayList<String> list = new ArrayList<String>();
void OwMethod() { list.add("且听风吟_z"); list.add("山多拉之灯"); list.add(0, "Jake"); // 使用下标确定添加位置
System.out.println(list.get(1)); // list 特有 System.out.println("下标为:" + list.indexOf("Jake")); // list 特有 System.out.println(list.set(0, "Captain")); // 替换元素, list 特有 System.out.println("判断包含元素:" + list.contains("Captain"));
System.out.println("该集合有元素数:" + list.size()); System.out.println("移除元素:" + list.remove("山多拉之灯"));
System.out.println("字符串型式返回元素:" + list.toString()); System.out.println("这是什么鬼:" + list.toArray() + "\n");
list.clear(); // 没有返回值 System.out.println("\n判断是否为空:" + list.isEmpty()); } } |
Ps:注意元素时对象时的区别
b) List 接口特有方法(ArraysList、LintkedList 实现接口)
<E> |
get(int index) |
返回列表中指定位置的元素。 |
<E> |
set(int index, E element) |
用指定元素替换列表中指定位置的元素(可选操作)。 |
c) LinkedList 类特有方法
<E> |
getFirst() |
返回此列表的第一个元素。 |
<E> |
getLast() |
返回此列表的最后一个元素。 |
<E> |
removeFirst() |
移除并返回此列表的第一个元素。 |
class Link { // LinkedList 方法举例
LinkedList<String> list2 = new LinkedList<String>();
void OwMethod() { list2.add("No.1"); list2.addFirst("我是第一个!"); list2.add("No.2");
System.out.println("LinkedList 返回元素:" + list2.getFirst()); list2.removeFirst(); System.out.println("LinkedList 返回元素:" + list2.toString()); } } |
Link补充:1. 删除元素后,index后面的元素会自动向前补齐
2.索引若没有元素,返回-1。
d) Set接口(HashSet 实现接口)
class Set { // HashSet 方法举例 HashSet<String> list6 = new HashSet<String>();
void OwMethod() { list6.add("cat"); list6.add("dog"); System.out.println(list6.getClass()); } } |
e) Map接口 <K,V> (HashMap 实现接口)
返回类型 |
方法 |
解释 |
boolean |
containsKey(Object key) |
确保此 collection 包含指定的元素。 |
<V> |
get(Object key) |
返回指定键所映射的值。 |
<V> |
put(K key, V value) |
将指定的值与此映射中的指定键关联。 |
Collection<V> |
values() |
返回此映射中包含的值的 Collection集合。 |
Set<K> |
keySet() |
返回此映射中所包含的键的 Set集合。 |
class Map { HashMap<Integer, String> list7 = new HashMap<Integer, String>(); // Map要得是对象,不能放int void OwMethod() {
list7.put(1, "car"); // key 不可以重复 list7.put(2, "truck"); // Value 的值可以是普通数据也可以是对象 list7.put(3, "bus"); System.out.println("是否包含key = 1:" + list7.containsKey(1)); System.out.println("key所对应的值:" + list7.get(1)); System.out.println("包含Value的所有元素:" + list7.values()); System.out.println("包含Key的所有元素:" + list7.keySet());
System.out.println("HashMap返回元素:" + list7.toString() + "\n"); } } |
2. Iterator 迭代器
boolean |
hasNext() |
如果仍有元素可以迭代,则返回 true。 |
Set<K> |
next() |
返回迭代的下一个元素。 |
void |
remove() |
移除迭代器返回的最后一个元素。 |
补充:遍历集合元素的几种方法
Collections.sort(list); // Ps:不是必须的 Iterator<String> iter = list.iterator(); //1.调用迭代器,遍历集合元素 while (iter.hasNext()) { String name = iter.next(); System.out.println(name); }
for (String str : list) { // 2.使用增强for循环,遍历集合元素 System.out.println(str); } // 3.转换成字符串,遍历集合元素 System.out.println("HashMap返回元素:" + list.toString() + "\n"); |
3. Collections包装类
Java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
Void |
sort(List<T> list) |
根据自然顺序 对指定列表按升序进行排序。 |
<T> |
Max |
根据自然顺序,返回给定 collection 的最大元素。 |
<T> |
Min |
根据自然顺序,返回给定 collection 的最小元素。 |
<T> |
Max (Comparator) |
重写比较器,返回给定 collection 的最大元素。 |
<T> |
Fill(list,obj) |
使用指定元素替换指定列表中的所有元素。 |
<T> |
binarySearch |
使用二分搜索法搜索指定列表,以获得指定对象。 |
注:<T>的正式名称为类型参数,也可通俗的称为占位符,符号读作of T。
Collections.sort(list); Iterator<Student> it = list.iterator(); while (it.hasNext()) { Student stu = (Student) it.next(); System.out.println(stu.getNumber()); }
int index = Collections.binarySearch(list, student1); System.out.println("student1的下标是:" + index);
Student stu1 = Collections.max(list); System.out.println("最大值的下标是:" + stu1.getNumber());
// Collections.fill(list, "我被替换了"); // 替换的是什么东西?? |
a) 重写比较器
接口:public interface Comparable<T>
此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。
实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort)
public int compareTo(Object obj) { Student student = (Student) obj; if (this.number == student.number) { // 用对象还是get都可以 return 0; } else if (this.number > student.getNumber()) { return 1; } else { return -1; } } |
4. 补充几点
a) 注意API中参数是对象还是普通数据类型。
b) 集合中的元素可以是普通数据也可以是对象。
c) 泛型执行定义了类型,避免了装箱和拆箱操作,性能有很大提高了。详见 高级特性à包装类à装箱/拆箱。
5. 泛型
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
为什么使用泛型? 先来看使用非泛型集合的两个问题:性能问题和类型安全问题。
性能问题:不管选择哪个类型来保存,都不能避免使用非泛型容器带来的装箱问题,所以提出了泛型。
类型安全问题:在泛型之前,要解决类型安全问题的唯一方法是手工创建自定义(强类型的)集合类。
a) 泛型类
b) 泛型方法
c) 泛型接口
二、 实用类
API(Application Programming Interface)应用程序编程接口,它是运行库的集合,预先定义了一些借口和类。API 提供如下常用的包:
- Java.lang:java应用最广发的包,自动导入,提供了包装类、Math类、String类等java的基础类和接口。还提供了关于系统管理操作的类。
- Java.util:包含了系统辅助类,特别是集合类。
- Java.io:包含了输入输出类,用于文件操作。
- Java.net:包含了网络相关的类,如Socket、ServerSocker等
- Java.sql:包含了与数据库相关的类,如Connection、Statement等
1. 枚举
public enum Week { MON, TUE, WED, THU, FRI, SAT, SUN; } // 调用 Week. MON |
public enum FORMAT { YYYY_MM_DD_FORMAT("yyyy-MM-dd"), // 为枚举元素赋值先定义元素,再定义属性 DD_MM_YYYY_FORMAT("dd-mm-yyyy");
private String stringValue;
private FORMAT(String stringValue) { this.stringValue = http://www.mamicode.com/stringValue; } public String getStringValue() { return stringValue; } } // 调用 FORMAT.DD_MM_YYYY_FORMAT.getStringValue() |
2. 包装类
基本数据类型 |
包装类 |
包装类 |
包装类 |
byte |
Byte |
Number |
Object |
short |
Short |
||
int |
Integer |
||
long |
Long |
||
float |
Float |
||
double |
Double |
||
char |
Character |
|
|
boolean |
Boolean |
|
a) 拆箱与装箱
大多集合能够处理任何类型的数据,因为这些集合定义的处理对象使用了Object类型。当集合处理的数据是JAVA中的基本类型时,比如int、float、double类型,这些类型在JAVA中属于值类型,而这些数据进入集合中时,集合能够处理的是Object这样的引用类型,所以在值类型的数据进入集合的时候,需要将值类型包装成引用类型,这个过程就是装箱的过程,从集合中取出数据时,取出的是引用类型,同样需要把引用类型还原为值类型,这个过程就是拆箱的过程。
- 泛型执行定义了类型,避免了装箱和拆箱操作,性能有很大提高了。
- JDK1.5后,允许基本数据类型和包装类型进行混合数学运算。
- 包装类并不是用来取代基本数据类型的,在基本数据类型需要用对象表示时使用。
b) 基本数据类型转换成包装型
Integer num1 = new Integer(123); // 手动装箱方式,构造方法 Integer num2 = new Integer("123"); // 除Character类外的基本类型 Character num3 = new Character(‘a‘); Integer num4 = Integer.valueOf(123); Integer num5 = 1; // 自动装箱 |
c) 包装型转换成基本数据类型
int num1_1 = num1.intValue(); // 手动拆箱方式 int num5_1 = num5; // 自动拆箱 |
d) toString()和parseInt()
- toString():以字符串形式返回包装对象表示的基本类型(基本类型->字符串)
- parseXXX():把字符串转换为基本数据类型(Character除外)(字符串->基本类型)
String sex=Character.toString(‘男‘); // 基本类型->字符串 int num=Integer.parseInt("36"); // 字符串->基本类型 //可用于校验字符是否合法 sex=‘男‘+""; // 合并转换字符串的一种方式 |
3. Math类
//获取随机数的两种random()方法: int show1 = (int) (Math.random() * 5.0D) + 1; int show2 = (int) (Math.random() * 10.0D) % 5 + 1; System.out.println(Math.max(show1, show2)); System.out.println(Math.abs(-10)); // 绝对值运算 System.out.println(Math.E + "\n" + Math.PI); // 两个常量 //Ps:random()返回一个大于等于0小于1的随机数。 |
4. String类
在Java中,字符串被作为String类型的对象来处理。String类位于java.lang包中。字符串是一个字符序列,事实上也是一个字符数组。可用下标索引。
String s1 = "-Java-"; String s1_1 = s1.toLowerCase(); // 转为小写 String s1_2 = s1.toUpperCase(); // 转为大写
String s2 = "渺千山暮雪,"; String s3 = "万里层云,"; String s4 = "只影向谁去?";
String s5 = s2.concat(s3.concat(s4)); // 连接字符串 String[] s6 = s5.split("。|,|?"); // 字符串拆分,用数组接收 String s7 = s5.substring(1, 5); // 提取下标区间字段
System.out.println(s1.equals(s1_2)); // 比较字符串,详见运算符章节 System.out.println(s1_1.equalsIgnoreCase(s1_2)); // 比较字符串,忽略大小写 System.out.println(s5); for (String temp : s6) { System.out.println(temp); } System.out.println(s7); // indexOf()查询若不存在返回index = -1 System.out.println(s7.length()); //字符串长度 System.out.println("第一次出现逗号位置的下标:" + s5.indexOf(",")); System.out.println("最后出现逗号位置的下标:" + s5.lastIndexOf(",")); |
5. StringBufffer类和StringBulider类
StringBuffer s1 = new StringBuffer("仰天大笑出门去"); StringBuffer s2 = s1.append("无人知是荔枝来!");// 效率比concat()高 StringBuffer s3 = s2.insert(7, ","); // 返回StringBuffer,不能用String接收。 int s4 = s3.hashCode(); //StringBuffer 好多方法可以用 System.out.println(s3); System.out.println(s4); |
注释:StringBufffer类和StringBulider类处理字符串的方法基本一致。
a) 补充几点:
- String是不可变对象。每次对String类型改变实际上等同与生成了一个String对象。对经常改变内容的字符串最好不用String类型。String字符串用“+“连接实际上是通过建立一个StringBuffer对象,调用appent()方法,然后再转换成String。
- StringBuffer改变是改变的对象的引用,效率比String要高。
- StringBulider是单线程的,不提供同步,理论上效率更高。
6. 日期时间类
Date date = new Date(); // 创建日期对象 SimpleDateFormat formater // HH表示24时制 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("当前时间为:" + formater.format(date));
Calendar t = Calendar.getInstance(); // 抽象类,得到子类对象 System.out.println("今天是" + t.get(Calendar.YEAR) + "年" + (t.get(Calendar.MONTH) + 1) + "月" + t.get(Calendar.DAY_OF_MONTH) + "日"); // Calendar.DAY_OF_WEEK 中 Sunday是 1 System.out.println("今天是星期" + (t.get(Calendar.DAY_OF_WEEK) - 1)); } |
7. Random类
Random rand = new Random(47); int show1 = rand.nextInt(10); //产生一个大于等于0,小于10的随机数 System.out.println(show1); //Math.random()方法底层是使用Random类实现的 |
三、 输入/输出、序列化和反射
1. File类的操作
@SuppressWarnings("static-access") public static void main(String[] args) throws IOException { SimpleDateFormat formater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
File file1 = new File("I:/myDoc"); // 创建新 File 实例,创建的是引用 file1.mkdir(); // 创建文件夹 file.mkdirs() file1.createTempFile("temp", ".txt", file1); // 创建临时文件 System.out.println("是目录:" + file1.isDirectory());
File file2 = new File("I:/myDoc/temp.txt"); // 可是文件,也可是目录 file2.createNewFile(); //创建文件 System.out.println("是文件:" + file2.isFile());
System.out.println("文件是否存在:" + file2.exists()); System.out.println("名称:" + file2.getName()); System.out.println("相对路径: " + file2.getPath()); System.out.println("绝对路径: " + file2.getAbsolutePath()); System.out.println("文件大小:" + file2.length() + " 字节"); System.out.println("最后修改日期: " + formater.format(file2.lastModified())); } System.out.println("文件是否删除:" + file2.delete()); |
2. Java的流
流,是指一连串流动的字符,是以先进先出的方式发送和接收数据的通道。流分为输入和输出流。输入/输出时相对于内存来说的(往里读,往外写)。Java的输出流主要有OutputStream和White作为抽象基类,而输入流主要由InputStream和Reader作为抽象基类。
字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好。
如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点。
所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列。
字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串;字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。
字节流在操作的时候是直接与文件本身关联,不使用缓冲区,字节直接存到文件中;字符流在操作的时候是通过缓冲区与文件操作,字符到缓冲区然后再到文件中,所以字符流中存在一个flush()方法来刷新缓冲区。
3. 读写文本文档
//******** 问题:下列步骤数据存储和转换的过程是怎么样的? 编码:UTF-8 ******* // 1. 把一个字符串 "苏A" 输入缓冲区 (33487)10000010 11001111 (65)01000001 UTF-16 // 1. 在缓冲区加高位转换成UTF=8 11101000/10001011/10001111/01000001 // 2. 转换成byte[] //用长度为4的字节数组接受 Byte1 = 11101000 Byte2 = ... // 3 .然后存入文本 // 把四个字节数组存入文本(阅读文本时,解码器解码) // 4. 然后从文本读取 //把文档内容以字节(符)型式读入内存,并转换成char // 注:苏A == (\u82cf--16进制表达) ((232) 11101000 (139) 10001011 (143) 10001111) (65)01000001 // 如果不知道编码方式,可能同样的二进制数,解码方式有误,导致解码失败。 // IO操作的是序号,通过字库表,对应显示正确的字符。 public class Keep_Stream_Test {
public static void main(String[] args) throws IOException {
int date, temp; String line; String str = "苏A"; //UTF-8 汉字一个字符约三个字节 char a = ‘C‘; // 一个整型代表一个字符,故可以互相转换 byte[] words = str.getBytes(); // 把字符串内容写进字节数组 System.out.println(System.getProperty("file.encoding")); // 获得本地平台的字符编码类型 /** * 字节流写入数据 * */ FileOutputStream fos1 = new FileOutputStream("I:/text1.txt"); fos1.write(552); // 读写的是序号 // 往里读,往外写 // 非正确演示 fos1.write(words, 0, words.length);// 把字节数组存储的内容写进流 /** * 字符流写入数据 * */ FileWriter fw = new FileWriter("I:/text1.txt", true);//不覆盖原文件 BufferedWriter bw = new BufferedWriter(fw); // 装饰者模式 bw.write("-南京"); bw.write(‘)‘); bw.flush(); // 往外写时才用 /** * 字节流读取数据 * */ FileInputStream fis1 = new FileInputStream("I:/text1.txt"); BufferedInputStream bis1 = new BufferedInputStream(fis1); System.out.println("文件大小:" + bis1.available() + "字节\n"); while ((date = fis1.read()) != -1) { // 把文本内容以字节型式读入内存 System.out.print(date); System.out.print((char) date); System.out.println("\t" + Integer.toBinaryString(date)); } // 此处对应关系与编码方式有关,多字节解码方式有误 // 没有缓冲区,读一个字节,转一个。so如果汉字是单字节就不会出错鸟~ System.out.println(‘\n‘); /** * 字符流读取数据 * */ FileReader fr1 = new FileReader("I:/text1.txt"); BufferedReader br1 = new BufferedReader(fr1); while ((date = br1.read()) != -1) { // 把文档内容以字符型式读入内存 System.out.print(date); System.out.print((char) date); System.out.println("\t" + Integer.toBinaryString(date)); } while ((line = br1.readLine()) != null) { // 把文档内容以字符型式(每次一行)读入内存,返回字符串。这一段读不出流 只可用一次。 System.out.print(line); // 由于字符流有缓冲区,所以2个字符读(Unicode)与一行行读都可以,最终按 编码方式处理。 } /** * 字符流读取数据 InputStreamReader 是字节流通向字符流的桥梁 * */ InputStreamReader isr = new InputStreamReader(fis1, "UTF-8"); // 转换流 BufferedReader br2 = new BufferedReader(isr);// 缓冲流 while ((temp = br2.read()) != -1) { System.out.print((char) temp); // 流使用一次就不能再用了?? } System.out.println(‘\u82cf‘);// 十六进制表示一个字符 System.out.println((byte) a); } } |
a) 节点流与处理流
- IO中的使用到了一个设计模式:装饰设计模式。写一个类(包装类)对被包装对象进行包装,对一组类进行功能的增强。
- 节点流:节点流从一个特定的数据源读写数据。即节点流是直接操作文件,网络等的流,他们直接从文件中读取或往文件中写入字节流。对文件操作的字符流有FileReader/ FileWriter,字节流有FileInputStream/FileOutputStream。
- 处理流:“连接”在已存在的流(节点流或处理流)之上通过对数据的处理为程序提供更为强大的读写功能。过滤流是使用一个已经存在的输入流或输出流连接创建的,过滤流就是对节点流进行一系列的包装。
u 缓冲流:缓冲流要“套接”在相应的节点流之上,对读写的数据提供了缓冲的功能。
2 字节缓冲流有BufferedInputStream/ BufferedOutputStream,字符缓冲流有BufferedReader/BufferedWriter,字符缓冲流分别提供了读取和写入一行的方法ReadLine和NewLine方法。使用字符缓冲流的时候,一定先flush,再close。
u 转换流:用于字节数据到字符数据之间的转换。
2 字符流InputStreamReader/OutputStreamWriter。其中,InputStreamReader需要与InputStream“套接”,OutputStreamWriter要与OutputStream“套接”。
u 数据流:提供了读写Java中的基本数据类型的功能。
2 DataInputStream和DataOutputStream分别继承自InputStream和OutputStream,须“套接”在InputStream和OutputStream类型的节点流上。
u 对象流:用于直接将对象写入写出。
2 对象流类有ObjectInputStream和ObjectOutputStream,本身这两个方法没什么,但是其要写出的对象有要求,该对象必须实现Serializable接口,来声明其是可以序列化的。否则,不能用对象流读写。
u 还有一个关键字比较重要,transient,由于修饰实现了Serializable接口的类内的属性,被该修饰符修饰的属性,在以对象流的方式输出的时候,该字段会被忽略。
b) 补充几点:
① FileInputStream和FileReader的区别
两个类的构造方法的形式和参数都是相同的,参数为File对象或表示路径的String。
FileInputStream以字节流方式读取,FileReader把文件转换为字符流读入。
② BufferedWriter和PrintWriter的区别
PrintWriter提供的print/println/printf等方法方便使用,并封装了字节流与字符流之间的转换。BufferedWriter提供缓冲。详见TCP编程章节例程。
③ 构造方法的形式和参数的区别
File流以文件作为参数,处理流以节点流或其子类作为参数。
- ? 参考资料:
Java输入、输入、IO流 类层次关系梳理 http://www.cnblogs.com/LittleHann/p/3678685.html
4. 读写二进制文档
/** * 二进制文件读取操作 * */ FileInputStream fis3 = new FileInputStream("I:/text1.txt"); DataInputStream dis1 = new DataInputStream(fis3); /** * 二进制文件写入操作 * */ FileOutputStream fos2 = new FileOutputStream("I:/text2.txt"); DataOutputStream dos1 = new DataOutputStream(fos2); int temp2; while ((temp2 = dis1.read()) != -1) { // 读取文件并写入文件 dos1.write(temp2); } dos1.writeUTF("Hello"); // 将一个字符串写入文件 dos1.flush(); |
5. 重定向标准 I/O
Java的标准输入/输出分别通过System.in和System.out来代表,在默认的情况下分别代表键盘和显示器,当程序通过System.in来获得输入时,实际上是通过键盘获得输入。当程序通过System.out执行输出时,程序总是输出到屏幕。
/** * System.out 重定向向文件输出 * */ PrintStream ps = new PrintStream(new FileOutputStream("work")); System.setOut(ps); System.out.println("Hello World!"); /** * System.in 重定向从文件输入 * */ FileInputStream fis = new FileInputStream("work"); System.setIn(fis);
Scanner sc = new Scanner(System.in); while (sc.hasNextLine()) { System.out.println(sc.nextLine()); } |
6. 序列化与反序列化
序列化是将对象的状态写入到特定的流中的过程,反序列化则是从特定的流中获取数据重新构建对象的过程。
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("c:\\myDoc\\stu.txt")); Student stu = new Student("安娜", 30, "女"); // 对象序列化 oos.writeObject(stu);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream( "c:\\myDoc\\stu.txt")); // 对象反序列化 Student stu1 = (Student) ois.readObject(); System.out.println("姓名为:" + stu1.getName()); System.out.println("年龄为:" + stu1.getAge()); System.out.println("性别为:" + stu1.getGender()); Ps:class Student implements java.io.Serializable |
补充:静态数据不能被序列化,因为静态数据不在堆内存中,是存储在静态方法区中。用transient 关键字修饰非静态数据也不会被序列化。
7. 反射机制
其实就是动态加载一个指定的类,并获取该类中的所有的内容。而且将字节码文件封装成对象,并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个类进行解剖。反射技术大大的增强了程序的扩展性。
a) 反射的基本步骤:
- 获得Class对象,就是获取到指定的名称的字节码文件对象。
- 实例化对象,获得类的属性、方法或构造函数。
- 访问属性、调用方法、调用构造函数创建对象。
b) 获取这个Class对象的三种方式
c) 创建对象的两种方式
参考资料:
- Java 反射机制深入研究 http://lavasoft.blog.51cto.com/62575/43218
- 一个例子让你了解Java反射机制 http://my.oschina.net/zc741520/blog/195460
- Java 反射机制 http://www.cnblogs.com/mengdd/archive/2013/01/26/2877972.html
8. 补充几点
a) 字符集和字符编码
我们在计算机屏幕上看到的是实体化的文字,而在计算机存储介质中存放的实际是二进制的比特流。那么在这两者之间的转换规则就需要一个统一的标准。
字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。均占一个字符,但字节长度由于编码方式的不同而不同。
字符集(Charset):是字符的集合。常见字符集有:ASCII字符集、ISO-8859-1字符集(兼容ASCII) 、GB2312字符集(兼容ASCII) 、GBK字符集(兼容GB2312)、BIG5字符集、GB18030字符集(兼容GBK)、Unicode(兼容ISO-8859-1)字符集等。
字符不一定适合作传输、储存等处理,有时须经编码(encode)后才能应用。如Unicode(UTF-8、UTF-16、UTF-32)、GBK等方式编码。
对于一个字符集来说要正确编码转码一个字符需要三个关键元素:字库表(character repertoire)、编码值(coded character set)、字符编码(character encoding form)。其中字库表是一个相当于所有可读或者可显示字符的数据库,字库表决定了整个字符集能够展现表示的所有字符的范围。用一个编码值(数值代码)来对应字库中的每一个字符。字符编码,将编码值(数值代码)以一定编码方式转化成二进制数,以便与进行传输、储存等处理。过程:字符à编码值à二进制值。
苏A 的 存 储 形 式 为 : ASCII 码: (33487) 10000010 11001111 (65) 01000001 UTF-8: (\u82cf)( (232) 11101000 (139) 10001011 (143) 10001111) (65) 01000001 |
- ? ASCII码:一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间。一个二进制数字序列,在计算机中作为一个数字单元,一般为8位二进制数,换算为十进制。最小值0,最大值255。如一个ASCII码就是一个字节。
- ? Unicode编码:一个英文等于两个字节,一个中文(含繁体)等于两个字节。
- ? UTF-8编码:一个英文字符等于一个字节,一个中文(含繁体)≈三个字节。
符号:英文标点占一个字节,全角两个字节。中文标点占两个字节,全角四个字节。
- ? Java中的字符流处理的最基本的单元是Unicode码元。由于字符流在输出前实际上是要完成Unicode码元序列到相应编码方式的字节序列的转换,所以它会使用内存缓冲区来存放转换后得到的字节序列,等待都转换完毕再一同写入磁盘文件中
参考文章:十分钟搞清字符集和字符编码,字符集和字符编码(Charset & Encoding)
字符编码详解——彻底理解掌握编码知识,“乱码”不复存在
b) 文本文件与二进制文件的区别
所有的文件在内存和磁盘中都是二进制格式的,所以文本文件与二进制文件的区别并不是物理上的,而是逻辑上的。这两者只是在编码层次上有差异。
文本文件是基于字符编码的文件,常见的编码有ASCII(双字节字符集)、Unicode(可变字节字符集)编码等。二进制文件是基于值编码的文件,二进制读取只可正对二进制文件。
用ASCII方式处理文件,一个字节(8 Bite)放一个ASCII字符,因而便于对字符进行逐个处理,也便于输出字符,但一般占储空间较多,而且要花费较多的转换时间。二进制文件是把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放,用二进制形式输出数值,可以节省外存空间和转换时间。
文本文件的读取需要按照相应的编码方式进行解码。
5678 的 存 储 形 式 为 : ASCII 码: 00110101 00110110 00110111 00111000 (四个字节) 二进制: 00010110 00101110 (两个字节) |
注释:ASCII码是8 Bite的编码,Unicode码元一般占16 Bite。
c) 数据结构相关知识
待补充~
四、 注解和多线程
1. 注解
注解一种标记
Annotation //注解
@Deprecated
@SuppressWarnings("deprecation") 消除警告
@Override 重写
自定义注解
比如A注解
public @interface A {
}
元注解
@Retention
@Target
待补充~
2. 多线程
进程:正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。
线程:其实就是进程中一个程序执行控制单元,一条执行路径。进程负责的是应用程序的空间的标示。线程负责的是应用程序的执行顺序。
a) 创建线程
public class Thread_Demo {
public static void main(String[] args) { Demo1 d1 = new Demo1(); // Runnable实现类,只有run()方法 Thread t = new Thread(d1); // Thread对象调用start()方法 t.start();
Demo2 d2 = new Demo2(); d2.start(); //Thread对象调用start()方法 } }
class Demo1 implements Runnable { // 实现Runnable接口方式实现多线程
public void run() { System.out.println("Runnable"); } }
class Demo2 extends Thread { // 继承Thread类实现多线程 public void run() { System.out.println("Thread"); } |
b) 线程的生命周期
- 新建状态(New):新创建了一个线程对象。
- 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
- 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
u 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
u 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
u 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
c) 多线程安全问题
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不执行。
同步代码块: public class ThreadDemo { public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t, "窗口一"); Thread t2 = new Thread(t, "窗口二"); Thread t3 = new Thread(t, "窗口三"); Thread t4 = new Thread(t, "窗口四"); t1.start(); t2.start(); t3.start(); t4.start(); } }
class Ticket implements Runnable { private int ticket = 100;
public void run() { while (true) { synchronized (new Object()) { // synchronized关键字,用来修饰方法或代码块的时,保证同时最多只有一个线程执行 该段代码。 if (ticket <= 0) break; System.out.println(Thread.currentThread().getName() + "---卖出"+ ticket--); } } } } |
同步函数: public class ThreadDemo { public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t, "窗口一"); Thread t2 = new Thread(t, "窗口二"); Thread t3 = new Thread(t, "窗口三"); Thread t4 = new Thread(t, "窗口四"); t1.start(); t2.start(); t3.start(); t4.start(); } }
class Ticket implements Runnable { private int ticket = 100;
public synchronized void saleTicket() { if (ticket > 0) System.out.println(Thread.currentThread().getName() + "卖出了" + ticket--); }
public void run() { while (true) { saleTicket(); } } } |
等待唤醒机制: |
生产消费机制: |
参考资料:
- 最全面的Java多线程用法解析 http://www.codeceo.com/article/java-mult-thread.html
- JAVA多线程实现的三种方式 http://blog.csdn.net/aboy123/article/details/38307539
五、 网络编程
网络编程的目的就是指直接或间接地通过网络协议与其他计算机进行通讯。网络编程中有两个主要的问题,一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输。在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机。而TCP层则提供面向应用的可靠的或非可靠的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的。
目前较为流行的网络编程模型是客户机/服务器(C/S)结构。
1. 网络基础知识
a) TCP/IP协议
是目前世界上应用最为广泛的协议,是以TCP和IP为基础的不同层次上多个协议的集合,也成TCP/IP协议族、或TCP/IP协议栈
- TCP:Transmission Control Protocol 传输控制协议
- IP:Internet Protocol 互联网协议
- 应用层:HTTP、FTP、SMTP、Telnet等
- 传输层:TCP/IP
- 网络层:
- 数据链路层:
- 物理层:网线、双绞线、网卡等
b) TCP/IP五层模型
c) IP地址
IP网络中每台主机都必须有一个唯一的IP地址,IP地址是一个逻辑地址。
d) 协议
为进行网络中的数据交换(通信)而建立的规则、标准或约定。(=语义+语法+规则)。
不同层具有各自不同的协议。
e) 端口
IP地址用来标识网络上的计算机,而端口号用来指明该计算机上的应用程序。
f) 数据封装
一台计算机要发送数据到另一台计算机,数据首先必须打包,打包的过程称为封装。
封装就是在数据前面加上特定的协议头部。
g) Socket套接字
网络上具有唯一标识的IP地址和端口组合在一起才能构成唯一能识别的标识符套接字。
- Socket原理机制:
u 通信的两端都有Socket。
u 网络通信其实就是Socket间的通信。
u 数据在两个Socket间通过IO传输。
h) Java中的网络支持
针对网络通信的不同层次,Java提供了不同的API,其提供的网络功能有四大类:
- InetAddress:用于标识网络上的硬件资源,主要是IP地址。
- URL:统一资源定位符,通过URL可以直接读取或写入网络上的数据。
- Sockets:使用TCP协议实现的网络通信Socket相关的类。
- Datagram:使用UDP协议,将数据保存在用户数据报中,通过网络进行通信。
- InetAddress类用于标识网络上的硬件资源,标识互联网协议(IP)地址。
2. InetAddress
// 使用域名创建对象 InetAddress inet1 = InetAddress.getByName("www.163.com"); System.out.println(inet1); // 使用IP创建对象 InetAddress inet2 = InetAddress.getByName("127.0.0.1"); // 127.0.0.1是一个回送地址,指本地机,一般用来测试使用 System.out.println(inet2); // 获得本机地址对象 InetAddress inet3 = InetAddress.getLocalHost(); System.out.println(inet3); // 获得对象中存储的域名 String host = inet3.getHostName(); System.out.println("域名:" + host); // 获得对象中存储的IP String ip = inet3.getHostAddress(); System.out.println("IP:" + ip); |
3. URL类
// 创建一个URL的实例 URL baidu = new URL("http://www.baidu.com"); URL url = new URL(baidu, "/index.html?username=tom#test"); // ?表示参数,#表示锚点 System.out.println(url.getProtocol()); // 获取协议 System.out.println(url.getHost()); // 获取主机 System.out.println(url.getPort()); // 如果没有指定端口号,根据协议不同使用默认端口。getPort()方法的返回值为 -1 System.out.println(url.getPath()); // 获取文件路径 System.out.println(url.getFile()); // 文件名,包括文件路径+参数 System.out.println(url.getRef()); // 相对路径,就是锚点,即#号后面的内容 System.out.println(url.getQuery()); // 查询字符串,即参数 |
- 通过URL对象的openStream()方法可以得到指定资源的输入流,通过流能够读取或访问网页上的资源
URL url_2 = new URL("http://www.baidu.com"); InputStream is = (InputStream) url_2.openStream(); // 通过openStream方法获取资源的字节输入流 InputStreamReader isr = new InputStreamReader(is, "UTF-8"); // 将字节输入流转换为字符输入流,如果不指定编码,中文可能会出现乱码 BufferedReader br = new BufferedReader(isr); // 为字符输入流添加缓冲,提高读取效率 String data = http://www.mamicode.com/br.readLine();// 读取数据 while (data != null) { System.out.println(data);// 输出数据 data = http://www.mamicode.com/br.readLine(); } br.close(); isr.close(); is.close(); |
4. TCP编程
TCP协议是面向连接的、可靠的、有序的、以字节流的方式发送数据,通过三次握手方式建立连接,形成传输数据的通道,在连接中进行大量数据的传输,效率会稍低。
a) Java中基于TCP协议实现网络通信的类
- 客户端的Socket类
- 服务器端的ServerSocket类
|
b) Socket通信的步骤
① 创建ServerSocket和Socket
② 打开连接到Socket的输入/输出流
③ 按照协议对Socket进行读/写操作
④ 关闭输入输出流、关闭Socket
c) 服务器端
① 创建ServerSocket对象,绑定监听端口
② 通过accept()方法监听客户端请求
③ 连接建立后,通过输入流读取客户端发送的请求信息
④ 通过输出流向客户端发送乡音信息
⑤ 关闭相关资源
public class DemoTCP_Server { public static void main(String[] args) throws IOException { /** * 基于TCP协议的Socket通信,实现用户登录,服务端 */ // 1、创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口 ServerSocket serverSocket = new ServerSocket(10086); // 2、调用accept()方法开始监听,等待客户端的连接 Socket socket = serverSocket.accept(); // 3、getInputStream()方法获取输入流,并读取客户端信息 InputStream is = socket.getInputStream(); // 返回InputStream InputStreamReader isr = new InputStreamReader(is); // 转化流处理 BufferedReader br = new BufferedReader(isr); String info = null; while ((info = br.readLine()) != null) { System.out.println("我是服务器,客户端说:" + info); } socket.shutdownInput(); // 关闭输入流 // 4、获取输出流,响应客户端的请求 OutputStream os = socket.getOutputStream(); PrintWriter pw = new PrintWriter(os); pw.write("欢迎您!"); pw.flush();
// 5、关闭资源,注意顺序 pw.close(); os.close(); br.close(); isr.close(); is.close(); socket.close(); serverSocket.close(); } } |
d) 客户端
① 创建Socket对象,指明需要连接的服务器的地址和端口号
② 连接建立后,通过输出流想服务器端发送请求信息
③ 通过输入流获取服务器响应的信息
④ 关闭响应资源
public class DemoTCP_Clinet { // 客户端 public static void main(String[] args) throws IOException {
// 1、创建客户端Socket,指定服务器地址和端口 Socket socket = new Socket("localhost", 10086); // 2、获取输出流,向服务器端发送信息 OutputStream os = socket.getOutputStream();// 字节输出流 PrintWriter pw = new PrintWriter(os);// 将输出流包装成打印流 pw.write("用户名:admin;密码:123"); pw.flush(); socket.shutdownOutput(); // 关闭客户端的输出流。相当于给流中加入一个结束标记-1 // 3、获取输入流,并接收服务器端的反馈信息 InputStream is = socket.getInputStream(); // BufferedReader br = new BufferedReader(new InputStreamReader(is)); Scanner input = new Scanner(new InputStreamReader(is)); while (input.hasNextLine()) { //用Scanner接收输入流,对比服务端方法优劣 System.out.println("我是客户端,服务器说:" + input.nextLine()); }
// 4、关闭资源 input.close(); is.close(); pw.close(); os.close(); socket.close(); } } |
e) 例程小结
① 上例中几种读写方式的对比
PrintWriter作为一个便捷的字节流与字符流之间的转换工具,已经封装了转换的方法,直接使用它回写的时候,不用再使用getBytes()转换成字节流。
如用byte数组接收,将得到的字节流写入数组后,得把它转化为一个String的对象(用String(数组名,第一个索引,长度))。
如用缓冲流接收时,需要用InputStreamReader转化。综合来说,还是PrintWriter比较方便。
② Socke的shutdownInput()与close的区别
shutdownInput():关闭Socket的输入流,程序还可以通过该Socket的输出流输出数据。
客户端连接到服务器后,开始发送数据,发送完成后无须再次发送数据,只需要读取服务器响应数据即可,当读取响应完成后,该Socket连接也被关闭了。
③ 用Scanner接收输入流,对比服务端方法优劣
Scanner接收InputSteam对象,调用nextLine()方法读取流文件。
f) 应用多线程实现服务器与多客户端之间的通信
① 服务器端创建ServerSocket,循环调用accept()等待客户端连接
② 客户端创建一个socket并请求和服务器端连接
③ 服务器端接受客户端请求,创建socket与该客户建立专线连接
④ 建立连接的两个socket在一个单独的线程上对话
⑤ 服务器端继续等待新的连接
public class DemoThread_Clinet { // 客户端 public static void main(String[] args) throws IOException { Socket socket = new Socket("localhost",10086); socket.close(); } } |
public class DemoThread_Server extends Thread {// 服务器端 Socket socket = null; public DemoThread_Server(Socket socket) { // 以Sockeet为参的构造方法 this.socket = socket; }
public void run() { }
public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(10086); Socket socket = null; int count = 0;// 记录客户端的数量 while (true) { socket = serverSocket.accept(); // 阻塞状态,接收客户端请求,然后往下执行,新增线程 DemoThread_Server serverThread = new DemoThread_Server(socket); serverThread.start(); count++; System.out.println("客户端连接的数量:" + count); } } } |
5. UDP编程
UDP协议(用户数据报协议)是无连接的、不可靠的、无序的,速度快。进行数据传输时,首先将要传输的数据定义成数据报(Datagram),大小限制在64k,在数据报中指明数据索要达到的Socket(主机地址和端口号),然后再将数据报发送出去。
a) Java中基于TCP协议实现网络通信的类
- DatagramPacket类:表示数据报包
- DatagramSocket类:进行端到端通信的类
b) 服务器端实现步骤
① 创建DatagramSocket,指定端口号
② 创建DatagramPacket
③ 接受客户端发送的数据信息
④ 读取数据
public class DemoUDP_Server { /** * 服务器端,实现基于UDP的用户登录 * */ public static void main(String[] args) throws IOException {
// 1、创建服务器端DatagramSocket,指定端口 DatagramSocket socket = new DatagramSocket(8888); // 2、创建数据报,用于接受客户端发送的数据 byte[] data = http://www.mamicode.com/new byte[1024]; DatagramPacket packet = new DatagramPacket(data, data.length); // 3、接受客户端发送的数据 socket.receive(packet);// 此方法在接受数据报之前会一直阻塞 // 4、读取数据 String info = new String(data, 0, data.length); System.out.println("我是服务器,客户端告诉我" + info);
// 1、定义客户端的地址、端口号、数据 InetAddress address = packet.getAddress(); int port = packet.getPort(); byte[] data2 = "欢迎您!".getBytes(); // 2、创建数据包,包含响应的数据信息 DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address, port); // 3、响应客户端` socket.send(packet2); // 4、关闭资源 socket.close(); } } |
c) 客户端实现步骤
① 定义发送信息
② 创建DatagramPacket,包含将要发送的信息
③ 创建DatagramSocket
④ 发送数据
public class DemoUDP_Clinet { // 客户端
public static void main(String[] args) throws IOException {
// 1、定义服务器的地址、端口号、数据 InetAddress address = InetAddress.getByName("localhost"); int port = 8888; byte[] data = "http://www.mamicode.com/用户名:admin;密码:123".getBytes(); // 2、封包,包含发送的数据信息 DatagramPacket packet = new DatagramPacket(data, data.length, address, port); // 3、创建DatagramSocket对象 DatagramSocket socket = new DatagramSocket(); // 4、向服务器发包 socket.send(packet);
// 1、创建数据包,用于接受服务器端响应数据 byte[] data2 = new byte[1024]; DatagramPacket packet2 = new DatagramPacket(data2, data2.length); // 2、接受服务器响应的数据 socket.receive(packet2); String reply = new String(data2, 0, packet2.getLength()); System.out.println("我是客户端,服务器说:" + reply); // 4、关闭资源 socket.close(); } } |
6. 注意问题
a) 多线程的优先级问题
参考资料:
- Java Socket编程 http://developer.51cto.com/art/201509/490775.htm
- Java网络编程 http://www.cnblogs.com/linjiqin/archive/2011/06/10/2077237.html
六、 XML
1. XML 基础
2. XML 注释
3. XML 解析
<?xml version = "1.0" encoding="UTF-8"?> <PhoneInfo> <Brand name="华为"> <Type name="U8650"></Type> <Type name="HW123"></Type> <Type name="HW321"></Type> </Brand> <Brand name="苹果"> <Type name="Iphone5"></Type> <Type name="Iphone6"></Type> <Type name="Iphone6s"></Type> </Brand> </PhoneInfo> |
public class ParseXMLDemo { // Document、NoteList、Note、Element 接口 private Document document;
public static void main(String[] args) throws Exception { ParseXMLDemo pd = new ParseXMLDemo(); pd.getDocument(); pd.showInfo(); System.out.println("------------------"); pd.add(); pd.delete(); pd.update(); pd.showInfo(); pd.saveXML("E:/JAVA/U1/U2-1/src/chapter06/new.xml"); }
public void getDocument() throws Exception { // 获得DOM树 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.parse("E:/JAVA/U1/U2-1/src/chapter06/XMLDemo.xml"); // 1、创建解析器工厂 2、创建解析器 3、操作xml对象 }
public void showInfo() {// 获取手机品牌和属性 NodeList brands = document.getElementsByTagName("Brand"); for (int i = 0; i < brands.getLength(); i++) { Node node = brands.item(i); Element eleBrand = (Element) node;// 节点强转元素 System.out.println(eleBrand.getAttribute("name"));
NodeList types = node.getChildNodes(); for (int j = 1; j < types.getLength(); j++) { Node typeNode = types.item(j); if (typeNode.getNodeType() == node.ELEMENT_NODE) {//元素节点 Element eleType = (Element) typeNode; System.out.println("\t" + eleType.getAttribute("name")); } } } }
public void saveXML(String path) throws Exception { TransformerFactory factory = TransformerFactory.newInstance(); factory.setAttribute("indent-number", "4");// 设置缩进 Transformer transformer = factory.newTransformer(); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");//转换器 transformer.setOutputProperty(OutputKeys.INDENT, "yes");//开启缩进 // StreamResult result=new StreamResult(new FileOutputStream(path)); DOMSource source = new DOMSource(document);// 设置源 StreamResult result = new StreamResult(new OutputStreamWriter( new FileOutputStream(path), "UTF-8"));// 设置目标 transformer.transform(source, result);// 转换 }
public void add() throws Exception {// 添加 Element element1 = document.createElement("Brand"); element1.setAttribute("name", "三星"); Element element2 = document.createElement("Type"); element2.setAttribute("name", "Note3"); element1.appendChild(element2); document.getElementsByTagName("PhoneInfo").item(0) .appendChild(element1); this.saveXML("E:/JAVA/U1/U2-1/src/chapter06/new.xml"); }
public void update() throws Exception {// 修改 NodeList brands = document.getElementsByTagName("Brand"); for (int i = 0; i < brands.getLength(); i++) { Node brand = brands.item(i); Element eleBrand = (Element) brand; // 修改的是元素的属性 eleBrand.setAttribute("id", i + ""); // i转为String this.saveXML("E:/JAVA/U1/U2-1/src/chapter06/new.xml"); } }
public void delete() throws Exception {// 删除 NodeList brands = document.getElementsByTagName("Brand"); for (int i = 0; i < brands.getLength(); i++) { Node brand = brands.item(i); Element eleBrand = (Element) brand; if ("华为".equals(eleBrand.getAttribute("name"))) { eleBrand.getParentNode().removeChild(eleBrand); } this.saveXML("E:/JAVA/U1/U2-1/src/chapter06/new.xml"); } } } |
|
在内存中分配连续的空间,
遍历访问效率高,但是在添加和删除
非尾部元素时会导致后面的所有元素的移动,
导致需要重新判断后面元素的下标,
这就造成ArrayList对插入、删除、等操作性能低下。
JAVA基础总结