第一章:Java语言概述
一、Java编译运行过程与跨平台
二、JDK JRE JVM解释
JDK(Java Development Kit) — Java开发工具包 — JRE+开发工具
开发java程序最小的环境为JDK,所以JDK是JAVA语言的核心
JRE(Java Runtime Environment) — Java运行时环境 — JVM+运行java程序所必需的环境
运行java程序最小的环境为JRE
JVM(Java Virtual Machine)—负责加载.class并运行.class文件
JVM(JAVA虚拟机)将JAVA代码转换为对应的操作系统可以理解的指令,不同的操作系统有不同虚拟机与之对应,同一段代码交给虚拟机之后,虚拟机再转化给操作系统
什么是将java代码翻译成当前操作系统所理解的指令?
这指的就是编译的过程,将.java文件编译成.class字节码文件.编译完成会产生一个.class文件,这个文件称为字节码文件,操作系统看的指令文件就是字节码文件.
第二章:基本语法
标识符
标识符可以简单的理解成一个名字。在Java中,我们需要标识代码的很多元素,包括类名、方法、字段、变量、包名等等。我们选择的那个名称就称为标识符,一个正确的标识符需要遵循以下规则:
-
标识符可以由字母、数字、下划线(_)、美元符($)组成,但不能包含 @、%、空格等其它特殊字符 -
不能以数字开头。如:123name不合法 -
标识符严格区分大小写。如:tmooc 和 Tmooc是两个不同的标识符 -
标识符的命名最好能反映出其作用,做到见名知意。 -
标识符不能是Java的关键字
ASCII编码表
ASCII(American Standard Code for Information Interchange)编码表,美国标准信息交换代码。
在计算机中,所有的数据在存储和运算时都要使用二进制数表示。
a、b、c、d这样的52个字母(包括大写)、以及0、1等数字还有一些常用的符号, 在计算机中存储时也要使用二进制数来表示,而具体用哪些二进制数字表示哪个符号,当然每个人都可以约定自己的一套(这就叫编码),而大家如果要想互相通信而不造成混乱,那么大家就必须使用相同的编码规则。
于是美国有关的标准化组织就出台了ASCII编码,统一规定了上述常用符号用哪些二进制数来表示。
中国 gb2312【字少】 àgbk【识别4万+汉字】à万国码表unicode【ISO制定】
所有码表都兼容ASCALL
附:ASCII码表
关键字
在Java中,有一些单词被赋予了特定的意义,一共有53个关键字。这53个单词都是全小写,其中有两个保留字:const和goto。
注释
几乎所有编程语言都允许程序员在代码中输入注释,因为编译器会忽略注释,所以注释并不会影响程序的运行结果。
注释的真正作用是: 它可以向任何阅读代码的人描述或者解释程序的实现思路,如何使用以及其它任何相关信息,提高代码的可读性,方便后期的维护与复用。Java的注释有3种:
- 单行注释: 注释单行内容.
格式: 每行都以”//”开头.
快捷方式:Ctrl+/添加注释,同样的快捷键,再按一次,取消注释
- 多行注释:注释多行内容,虽然叫多行注释,也可注释单行内容.
格式: 以/* 开头,以*/ 结束.
快捷方式:Ctrl+Shift+/添加注释,Ctrl+Shift+\取消注释,也可以输入”/*”之后按回车添加注释
- 文档注释: 一般用来注释类和方法,通过注释内容来记录类或者方法的信息.
格式: 以 /** 开头。 以 */ 结尾.
快捷方式:输入 /** 之后按回车添加注释
变量
在JAVA中,我们需要记录一些数据,但这些数据的值是不固定的,总在变,我们可以把这些数据理解为变量。
我们通过三个元素来描述变量:变量类型 变量名以及变量值。
int age = 18; //声明int类型的变量并赋值
String tel ; //声明String类型的变量
数据类型
1、基本类型介绍(八种)
变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。
java是一个强类型的语言,会把日常见到的数据,分成好多类型.
内存管理系统根据变量类型来分配存储空间,分配的空间只能用来储存该类型数据。
位 bit,来自英文bit,音译为”比特”,表示二进制位
1Byte = 8 bits (1字节 = 8 位)
1KB = 1024Bytes 1MB = 1024KB 1GB=1024M 变量交换
int t ;
t = a;
a = b;
b = t;
2、 引用类型
引用类型是一个对象类型,值是什么呢?它的值是指向内存空间的引用,就是地址,所指向的内存中保存着变量所表示的一个值或一组值。如:类,接口,数组
3、基本类型的字面值
3.1整数字面值是int类型(byte1 short2 int4 long8 float4 double8)
int x = 99999;
int x = 99999999999;
3.2 byte,short,char三种比int小的整数可以用范围内的值直接赋值
byte b1=127;
byte b2=128;
3.3浮点数的字面值是double类型
double r =3.14;
float r =3.14;
3.4字面值后缀L D F
long x =99999999999L;
float b = 3.0F;
double d = 3D;
3.5进制前缀 0b - 标识这是2进制 ,如:0b0101
0 - 标识这是8进制, 8进制是三位,如: 023
0x - 标识这是16进制,如: 0x0001
\u -标识这是char类型,属于16进制
4 、 基本类型的类型转换
箭头开始的地方是小类型,箭头指向的地方是大类型 4.1 小到大(隐式转换)
byte m = 120;
int n = m;
float f = 3.2f; double d = f; -->可以执行
4.2 大到小(显示转换)
int x = 999;
byte y =(byte)x;
注意: 容量大的类型转换为容量小的类型时必须使用强制类型转换。
- 转换过程中可能导致溢出或损失精度
例如:int i =128; byte b = (byte)i; //打印的结果是-128
因为 byte 类型是 8 位,最大值为127,所以当 int 强制转换为 byte 类型时,值 128 时候就会导致溢出。
- 浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入
例如:float f = 32.7f; int a2 =(int) f; //打印的结果是32
- 不能对boolean类型进行类型转换。
4.3 口诀: 小到大,直接转 大到小,强制转 浮变整,小数没
低 ------------------------------------> 高
byte,short,char—> int —> long—> float —> double
5、 运算规则
5.1 计算结果的数据类型,与最大数据类型一致
System.out.println(3/2);
System.out.println(3/2d);
5.2 byte,short,char三种比int小的整数,运算时会先自动转换成int
byte a = 1;
byte b = 2;
byte c = (byte)(a+b);
5.3 整数运算溢出 整数运算,类似于一个钟表,转到最大时,再转会回到最小。
计算:光速运行一年的长度是多少米?3亿m/s
System.out.println(300000000L*60*60*24*365);
5.4 浮点数运算不精确 使用Java 的 BigDecimal
public static void main(String[] args){
BigDecimal a = new BigDecimal("4.5");
BigDecimal b = new BigDecimal("1.5");
System.out.println("a + b =" + a.add(b));
System.out.println("a - b =" + a.subtract(b));
System.out.println("a * b =" + a.multiply(b));
System.out.println("a / b =" + a.divide(b));
}
5.5浮点数的特殊值
System.out.println(3.14/0);
System.out.println(0/0.0);
System.out.println(0.0/0);
运算符
1.1 概述
运算符 用于连接 表达式 的 操作数,并对操作数执行运算。
例如,表达式num1+num2,其操作数是num1和num2,运算符是”+”。
在java语言中,运算符可分为5种类型:
算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符。
根据操作数的不同,运算符又分为单目运算符、双目运算符和三目运算符。
单目运算符只有一个操作数,双目运算符有两个操作数,三目运算符则有三个操作数。
位运算符涉及到二进制位的运算,在java 程序中运用不是很多。
1.2 运算符速查表
a是操作数,++是自增运算符,–是自减运算符,自增和自减运算符即可以放在变量的前面,也可以放在变量的后面,例如:a++、++a、a–、–a等。 自增(++):将变量的值加1 分前缀式(如++a)和后缀式(如a++)。前缀式是先加1再使用;后缀式是先使用再加1。 自减(–):将变量的值减1 分前缀式(如–a)和后缀式(如a–)。前缀式是先减1再使用;后缀式是先使用再减1。
逻辑运算符连接两个关系表达式或布尔变量,用于解决多个关系表达式的组合判断问题 注意逻辑运算符返回的运算结果为布尔类型 通常,我们用0表示false,用1表示true 与:表示并且的关系 &单与: 1 & 2 ,结果想要是true,要求1和2都必须是true &&双与(短路与):1 && 2 ,当1是false时,2会被短路,提高程序的效率 或:表示或者的关系 |单或: 1 | 2,结果想要是true,要求1和2只要有一个为true就可以 ||双或(短路或):1 || 2,当1是true时,2会被短路,提高程序效率 TIPS:当一个表达式包含多个运算符时,就需要考虑运算符的优先级,优先级高的运算符先参与运算,优先级低的运算符后参与运算。在实际的开发中,不需要特别去记忆运算符的优先级别,也不要刻意的使用运算符的优先级别,对于不清楚优先级的地方使用小括号辅助进行优先级管理。
int max = a > b ? a : b;
分支结构
if(price >= 5000) {
count = price *0.5;
}else if(price >= 2000) {
count = price * 0.8;
}else if(price >= 1000) {
count = price *0.9;
}
switch结构
1、概述:
switch case 语句用来判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。
当一个case成立,从这个case向后穿透所有case,包括default,直到程序结束或者遇到break程序才结束
int a = 5;
switch(a) {
case 1 : System.out.println(1);break;
case 2 : System.out.println(2);break;
case 3 : System.out.println(3);break;
case 4 : System.out.println(4);break;
default : System.out.println(0);
}
2、switch结构的注意事项
- switch 语句中的变量类型可以是: byte、short、int 、char、String(jdk1.5以后支持)
- switch 语句可以拥有多个 case 语句
- 每个 case 后面跟一个要比较的值和冒号,且此值的数据类型必须与变量的数据类型一致
- 当变量值与 case 语句值相等时,开始执行此case 语句的内容,执行完会判断此行代码是否有break,如果有,结束执行,如果没有,会继续向后执行穿透所有case,包括default
- switch 语句可以包含一个 default 分支,该分支一般是写在switch 语句的最后
6)如果在default之前的case有break,则default不会执行
循环结构
for
for(开始条件;循环条件;更改条件){
syso循环体;
}
for(int i = 0;i<=10;i++) {
System.out.println(i);
}
System.out.println("**********************");
for(int i = 10 ;i >= 0;i--) {
System.out.println(i);
}
for(int i =8 ; i <= 8888 ; i=i*10+8) {
System.out.print(i+",");
}
嵌套for循环
概述:
存在至少2层for循环,根据外层的条件,判断里层能否执行 如果能执行,就把里层代码都循环完毕后,再继续判断是否执行外层循环的下一轮循环
for(开始条件;循环条件;更改条件){
for(开始条件;循环条件;更改条件){
循环体;
}
}
for(int i = 1; i<=3 ; i++) {
for(int j = 1; j<=5;j++) {
System.out.print("*");
}
System.out.println();
}
增强for
public static void print(Integer[] a) {
for (int i = 0; i < a.length; i=i+2) {
System.out.println(a[i]);
}
for(Integer i : a) {
System.out.print(i);
}
}
break与continue
break: 直接结束当前循环,跳出循环体,简单粗暴 TIPS: break以后的循环体中的语句不会继续执行,循环体外的会执行 continue: 跳出本轮循环,继续下一轮循环 TIPS:continue后本轮循环体中的语句不会继续执行,但是会继续执行下轮循环,循环体外的也会执行
for(){
代码1
if(条件){
代码3…
break;
}
代码2…
}
continue:跳出本次循环,进入下一轮
for(){
代码1
if(条件){
代码3…
continue;
}
代码2…
}
while
形式(先判断,再执行)
while(执行条件){
循环体;
}
do-while
形式(先执行,再判断,循环体代码保证最少执行一次)
do{
循环体;
} while (执行条件);
三种循环的区别:
三种循环都可以互相代替 1、 for:知道循环次数 2、 while/do while:当循环次数不确定时 3、 while:先判断,不符合规则,不执行代码 4、 do while:代码最少被执行一次,再去判断,符合规则,再次执行代码
变量
概念: 可以改变的数,称为变量。在Java语言中,所有的变量在使用前必须声明。 一般通过“变量类型 变量名 = 变量值 ;”这三部分来描述一个变量。如:int a = 3 ; 变量的使用原则:就近原则,即尽量控制变量的使用范围到最小
局部变量
位置:定义在方法里或者局部代码块中
注意:必须手动初始化来分配内存.如:int i = 5;或者int i; i = 5; 作用域:也就是方法里或者局部代码块中,方法运行完内存就释放了
成员变量
位置:定义在类里方法外 注意:不用初始化,也会自动被初始化成默认值 作用域:整个类中,类消失了,变量才会释放
方法
概述: 被命名的代码块,方法可以含参数可以不含参数,可以提高代码的复用性。 形式:
方法的修饰符 方法的返回值 方法名([参数列表…]){方法体;}
public static void main(String[] args){ …. }
方法调用顺序图:
顺序执行代码,调用指定方法,执行完毕,返回调用位置
方法的重载
方法的重载是指在一个类中定义多个同名的方法,但是每个方法的参数列表不同(也就是指参数的个数和类型不同),程序在调用方法时,可以通过传递给他们的不同个数和类型的参数来决定具体调用哪个方法.
public static void main(String[] args) {
method();
method(20);
method("海绵宝宝",3);
method(100,100);
}
public static void method(int i, int j) {
System.out.println(i+j);
}
public static void method(String s, int i) {
System.out.println(s+"今年"+i+"岁啦");
}
public static void method() {
System.out.println("哈哈哈哈我没有参数");
}
public static void method(int num) {
System.out.println(num*num);
}
第三章:数组
概念:
数组Array,标志是[ ] ,用于储存多个相同类型数据的集合 想要获取数组中的元素值,可以通过脚标(下标)来获取 数组下标是从0开始的,下标的最大值是数组的长度减1
创建数组
数组的创建方式一般分为动态初始化和静态初始化
int[] a = new int[5];
int[] b = new int[]{1,2,3,4,5};
int[] c = {1,2,3,4,5};
创建数组过程分析
程序创建数组 int[] a = new int[5]; 时发生了什么?
-
在内存中开辟连续的空间,用来存放数据,长度是5
-
给数组完成初始化过程,给每个元素赋予默认值,int类型默认值是0
-
数组完成初始化会分配一个唯一的地址值
-
把唯一的地址值交给引用类型的变量a去保存
TIPS: 数组名是个引用类型的变量,它保存着的是数组的地址,不是数组中的数据
向数组中存入数据hello
数组的长度
数组的长度用 length属性来表示,数组一旦创建,长度不可改变,数组的长度允许为0
数组的遍历
遍历:从头到尾,依次访问数组每一个位置,获取每一个位置的元素.形式如下:
我们通过数组的下标操作数组,所以for循环变量操作的也是数组下标
开始:开始下标0 结束:结束下标length-1 如何变化:++
for(从下标为0的位置开始;下标的取值<=数组的长度-1;下标++){
循环体;
}
冒泡排序
概念:
冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素已经排序完成。 这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
形式:
相邻比较,从小到大排序,如果小的就往前换一 代表了从头到尾遍历循环数据
实现冒泡排序
for(int i = 1 ; i <= a.length-1 ; i++) {
System.out.println("第"+i+"轮:");
for(int j=0; j < a.length-1 ; j++) {
if(a[j] > a[j+1]) {
int t = a[j];
a[j] = a[j+1];
a[j+1] = t;
System.out.println("第"+(j+1)+"次比较交换后:"+Arrays.toString(a));
}
}
System.out.println("第"+i+"轮的结果:"+Arrays.toString(a));
}
冒泡排序优化
for(int i = 1 ; i <= a.length-1 ; i++) {
boolean flag = false;
System.out.println("第"+i+"轮:");
for(int j=0; j < a.length-i ; j++) {
if(a[j] > a[j+1]) {
int t = a[j];
a[j] = a[j+1];
a[j+1] = t;
flag = true;
}
}
if(flag == false) {
return a;
}
System.out.println("第"+i+"轮的结果:"+Arrays.toString(a));
}
优化的点有2个 : 第1个就是前面几轮排序产生的最大值不需要参与后面轮的比较,执行过几轮就会产生几个值不参与比较,i轮i个,所以需要-i 第2个优化的点就是我们要设置一个量,这个量用来检测在当前这一轮的相互比较中究竟有没有发生元素的互换位置,如果发生了互换,说明顺序还没排好,flag就改成true,进行下一轮比较,但是如果在当前轮,所有元素都进行了相互比较,并没有互换位置,这就说明顺序已经排好序了,无需下一轮比较,直接return结束方法即可
数组工具类Arrays
Arrays.toString(数组) 把数组里的数据,用逗号连接成一个字符串[值1,值2]
Arrays.sort(数组) 对数组进行排序,对于基本类型的数组使用的是优化后的快速排序算法,效率高 对引用类型数组,使用的是优化后的合并排序算法
Arrays.copyOf(数组,新的长度) 把数组赋值成一个指定长度的新数组 新数组的长度 大于 原数组, 相当于复制,并增加位置 新数组的长度 小于 原数组, 相当于截取一部分数据
二维数组
概念:存放数组的数组,也就是说数组里存的还是数组的数据形式。
创建二维数组
int[][] a = {{3,5},{7,9},{1,2}};
--创建外部数组,长度是3
--给每个外部数组的位置创建内部数组,每个内部数组的长度是2
--给每个内部数组进行数据初始化
--二维数组生成唯一的地址值
--把地址值交给引用类型变量a来保存
遍历二维数组
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < a[i].length; j++) {
System.out.println(a[i][j]);
}
}
第四章:面向对象
概念:
面向对象其实是一种编程思想,通过它可以把生活中复杂的事情变得简单化,从原来的执行者变成了指挥者。 面向对象是基于面向过程而言的。
- 面向过程强调的是过程,比如:
1. 打开冰箱门 2. 把大象放进去 3. 关上冰箱门 - 面向对象强调的是结果,比如:
什么样的冰箱?什么样的大象?谁负责把大象装进去? 而不是关注那个负责的人怎么把大象装冰箱里. 衣服脏了,直接让女盆友去处理,等着穿干净的就可以了。你不关注中间的过程,只要找好对象就可以了~ 再比如.我们想吃一道菜,无需考虑是怎么传菜,怎么做菜的,只需点菜即可.传菜和做菜都有具体的对象来帮我们完成具体的功能.我们不需要关注实现的过程,只需要关注结果就好 这就是我们所说的面向对象的编程实现(OOP,Object Oriented Programming)
面向对象的三大特征
- 封装性,把相关的数据封装成一个“类”组件
- 继承性,是子类自动共享父类属性和方法,这是类之间的一种关系
- 多态,增强软件的灵活性和重用性
封装
概述: 封装是隐藏对象的属性和实现细节,仅仅对外提供公共的访问方式,比如类和方法 好处:
-
提高安全性
-
提高重用性
private关键字: 是一个权限修饰符 ,可以用来修饰成员变量和成员方法.被私有化的成员只能在本类中访问
public class Test4_Private 函数main
public static void main(String[] args) {
Student s = new Student();
System.out.println(s.name);
s.study();
s.name = "程序猿";
s.sno = 666;
System.out.println(s.name);
System.out.println(s.sno);
s.setSubject("JavaCGB");
System.out.println(s.getSubject());
s.study();
}
}
class Student{
String name;
int sno;
private String subject;
public void setSubject(String s) {
subject = s;
}
public String getSubject() {
return subject;
}
public void study() {
System.out.println("正在学习JAVA");
eat();
}
private void eat() {
System.out.println("干饭人 干饭魂");
}
}
TIPS:如何封装?封装后的资源如何访问? 我们可以使用private关键字来封装成员变量与方法 如何访问私有资源? 关于成员变量:
- setXxx – 对外提供公共的设置值方式
- getXxx – 对外提供公共的获取值方式
关于成员方法: 把私有方法放在公共方法里供外界调用即可
继承
概念: 继承是面向对象最显著的一个特征 继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并扩展新的能力. Java继承是会用已存在的类的定义作为基础建立新类的技术 新类的定义可以增加新的数据或者新的功能,也可以使用父类的功能,但不能选择性的继承父类(超类/基类) 这种继承使得复用以前的代码非常容易,能够大大的缩短开发的周期,降低开发费用.
特点:
- 使用extends关键字来表示继承关系
- 相当于子类把父类的功能复制了一份
- Java只支持单继承
- 继承可以传递(爷爷/儿子/孙子这样的关系)
- 不能继承父类的私有成员
- 继承多用于功能的修改,子类可以在拥有父类功能的同时,进行功能拓展
- 像是is a的关系
public class Demo {
public static void main(String[] args) {
Cat c = new Cat();
c.eat();
DingDang d = new DingDang();
d.eat();
System.out.println(d.a);
}
}
class Animal{
public void eat() {
System.out.println("吃啥都行~");
}
}
class Cat extends Animal{
int a = 10;
private int b = 200;
}
class DingDang extends Cat{
}
super
可以通过这个关键字使用父类的内容,Super代表的是父类的一个引用对象 注意:在构造方法里,出现的调用位置必须是第一行
方法重写Override
- 继承以后,子类就拥有了父类的功能
- 在子类中,可以添加子类特有的功能,也可以修改父类的原有功能
- 子类中方法的签名与父类完全一样时,会发生覆盖/复写的现象
- 格式要求:方法的返回值 方法名 参数列表 要完全一致,就方法体是重写的
TIPS: 父类的私有方法不能被重写,子类在重写父类方法时,修饰符 子类重写父类方法时,子类修饰符要大于等于父类修饰符的权限
重载Overload 与重写Override的区别
- 重载: 是指在一个类中的现象,是指一个类中有很多同名的方法,但是方法的参数列表不同
- 重写: 是指发生了继承关系以后(两个类),子类去修改父类原有的功能,子类中有一个方法签名(返回值类型 方法名(参数列表) )和父类的一模一样
- 重载的意义: 是为了方便外界对方法进行调用,什么样的参数程序都可以找到对应的方法来执行,体现的是程序的灵活性
- 重写的意义:是在不修改源码的前提下,进行功能的修改和拓展
(OCP原则:面向修改关闭,面向拓展开放) - 重写要求方法的修饰符: 子类权限 >= 父类的权限
继承的用法
成员变量的使用
public class Test6_Extends1 {
public static void main(String[] args) {
Son s = new Son();
s.eat();
}
}
class Father{
int count = 0;
int sum = 300;
}
class Son extends Father{
int sum = 100;
public void eat() {
int sum = 10;
System.out.println(sum);
System.out.println(this.sum);
System.out.println(count);
System.out.println(super.sum);
}
}
成员方法的使用
public class Test7_Extends2 {
public static void main(String[] args) {
Son2 s = new Son2();
s.eat();
Father2 f = new Father2();
f.eat();
s.study();
}
}
class Father2{
public void eat() {
System.out.println("爸爸爱吃肉");
}
}
class Son2 extends Father2{
public void eat() {
System.out.println("儿子爱吃饺子");
}
public void study() {
System.out.println("来学习JAVA");
}
}
构造方法的使用
public class Test8_Extends3 {
public static void main(String[] args) {
Son3 s = new Son3();
}
}
class Father3{
public Father3() {
System.out.println("我是父类的无参构造");
}
public Father3(String s) {
System.out.println("我是父类的含参构造"+s);
}
}
class Son3 extends Father3{
public Son3() {
super("噜噜噜");
System.out.println("我是子类的无参构造");
}
}
多态
概念: 多态指同一个实体同时具有多种形式 它是面向对象程序设计(OOP)的一个重要特征。 主要是指同一个对象,在不同时刻,代表的对象不一样,指的是对象的多种形态。 好处是:可以把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,统一调用标准。 水果有两种形态:水果和苹果,不关心买回来的是苹果还是西瓜,只要是水果就行
class Animal{
....eat(){syso("吃啥都行")}
}
class Cat extends Animal{
....eat(){syso("吃小鱼干")}
}
class Dog extends Animal{
....eat(){syso("吃肉骨头")}
}
class Pig extends Animal{
....eat(){syso("吃菜叶子")}
}
main(){
Cat c = new Cat();
Dog d = new Dog();
Pig p = new Pig();
Animal a1 = new Cat();
Animal a2 = new Dog();
Animal a3 = new Pig();
}
特点:
- 多态的前提1:是继承
- 多态的前提2:要有方法的重写
- 父类引用指向子类对象,如:Animal a = new Cat();
- 多态中,编译(保存)看左边,运行(测试)看右边
多态的好处: - 多态可以让我们不用关心某个对象到底具体是什么类型,就可以使用该对象的某些方法------可以写出更加通用的代码
- 提高了程序的可扩展性和可维护性
多态的使用
特点: 1) 成员变量: 使用的是父类的 2) 成员方法: 由于存在重写现象,所以使用的是子类的(成员方法使用的是父类的声明,子类的实现) 3) 静态成员: 随着类的加载而加载,谁调用就返回谁的 4)如果父子类都有静态方法,使用的是父类的
public class DuoTai {
public static void main(String[] args) {
Dog2 d = new Dog2();
System.out.println(d.sum);
d.eat();
d.play();
Animal2 a = new Dog2();
System.out.println(a.sum);
a.eat();
a.play();
}
}
class Animal2{
int sum = 10;
public void eat() {
System.out.println("吃啥都行");
}
public static void play() {
System.out.println("玩啥都行");
}
}
class Dog2 extends Animal2{
int sum = 20;
@Override
public void eat() {
System.out.println("小狗要吃肉骨头");
}
public static void play() {
System.out.println("小狗爱打滚儿~~~");
}
}
注意!!!静态资源属于类,不存在重写现象,只是两个类中有同样声明的方法而已,不属于重写
向上转型和向下转型
在JAVA中,继承是一个重要的特征,通过extends关键字,子类可以复用父类的功能,如果父类不能满足当前子类的需求,则子类可以重写父类中的方法来加以扩展。 在应用中就存在着两种转型方式,分别是:向上转型和向下转型。 比如:父类Parent,子类Child 向上转型:父类的引用指向子类对象Parent p=new Child(); 说明:向上转型时,子类对象当成父类对象,只能调用父类的功能,如果子类重写了父类的方法就根据这个引用指向调用子类重写方法。 向下转型(较少):子类的引用的指向子类对象,过程中必须要采取到强制转型。 Parent p = new Child();//向上转型,此时,p是Parent类型 Child c = (Child)p;//此时,把Parent类型的p转成小类型Child //其实,相当于创建了一个子类对象一样,可以用父类的,也可以用自己的 说明:向下转型时,是为了方便使用子类的特殊方法,也就是说当子类方法做了功能拓展,就可以直接使用子类功能。
类和对象
- 类:
- Java语言最基本单位就是类,类似于类型。
- 类是一类事物的抽象。
- 可以理解为模板或者设计图纸。
- 对象:
每个对象具有三个特点:对象的状态,对象的行为和对象的标识。
- 对象的状态用来描述对象的基本特征。
- 对象的行为用来描述对象的功能。
- 对象的标识是指对象在内存中都有一个唯一的地址值用来和其他对象区分开来。
- 类是一类事物的抽象,对象是具体的实现。
- 类和对象的关系:
- 计算机语言来怎么描述现实世界中的事物的? 属性 + 行为
- 那怎么通过java语言来描述呢?通过类来描述一类事物,把事物的属性当做成员变量,把行为当做方法
类的创建使用
分析手机事物: 属性:颜色,尺寸,品牌,价格 功能:打电话,发短信,听音乐 类:手机类,抽取相同的属性和行为 对象:可以按照模板生产很多个手机,比如1号手机对象,包含特有的成员变量和方法 通过class关键字创建类,通过new关键字创建对象。
public class Test3_ClassExec {
public static void main(String[] args) {
Phone p = new Phone();
p.call();
p.message();
p.learn();
System.out.println(p.brand);
System.out.println(p.price);
System.out.println(p.size);
System.out.println(p.color);
}
}
class Phone{
String brand;
double price;
double size;
String color;
public void call() {
System.out.println("正在打电话");
}
public void message() {
System.out.println("正在发短信");
}
public void learn() {
System.out.println("正在看直播");
}
}
对象在内存中的存储
创建对象的流程
Person p = new Person();//短短这行代码发生了很多事情
-
把Person.class文件加载进内存
-
在栈内存中,开辟空间,存放引用变量p
-
在堆内存中,开辟空间,存放Person对象
-
对成员变量进行默认的初始化
-
对成员变量进行显示初始化
-
执行构造方法(如果有构造代码块,就先执行构造代码块再执行构造方法)
-
堆内存完成
-
把堆内存的地址值赋值给变量p ,p就是一个引用变量,引用了Person对象的地址值
Java把内存分成5大区域,我们重点关注栈和堆。
- 一般来讲局部变量存在栈中,方法执行完毕内存就被释放
- 对象(new出来的东西)存在堆中,对象不再被使用时,内存才会被释放
- 每个堆内存的元素都有地址值
- 对象中的属性都是有默认值的
TIPS: 栈与队列指的是一种数据的结构。 栈:先进后出(FILO – First In Last Out) 队列:先进先出(FIFO – First In First Out)
创建多个对象
p2.brand = “HUAWEI”; 就是先到栈内存中找到p2中保存的唯一的地址值 然后根据地址值找到Phone对象,并对其对应的属性值进行修改
p2.learn(); 也是先到栈内存中找到p2中保存的唯一的地址值 然后根据地址值找到Phone对象,执行Phone对象的lean()方法
匿名对象
没有名字的对象,是对象的简化表示形式。 使用场景: 当被调用的对象只调用一次时(多次会创建多个对象浪费内存)
Demo d = new Demo();
d.sleep();
d.game();
new Demo().show();
new Demo().game();
访问控制符
用来控制一个类,或者类中的成员的访问范围。 TIPS:default是表示不写修饰符,默认,如果写default单词来修饰会报错
构造方法
概念:
构造方法是一种特殊的方法,它是一个与类同名且没有返回值类型的方法 对象创建就是通过构造方法完成的,主要功能是完成对象的创建或者对象的初始化 当类创建对象(实例化)时,会自动调用构造方法 构造方法与普通方法一样也可以重载.
形式:
与类同名,且没有返回值类型,可以含参也可以不含参
修饰符 方法名([参数列表]){ 注意:方法名与类名一样 代码…… }
TIPS:关于构造函数怎么记忆 特点:方法名与类名相同,且没有返回值类型 执行时机:创建对象时立即执行 默认会创建无参构造,但是,如果自定了含参构造,默认的无参构造会被覆盖,注意要手动添加哦
构造代码块与局部代码块
构造代码块的特点
- 位置: 在类的内部,在方法的外部
- 作用: 用于抽取构造方法中的共性代码
- 执行时机: 每次调用构造方法前都会调用构造代码块
- 注意事项: 构造代码块优先于构造方法加载
局部代码块
- 位置: 在方法里面的代码块
- 作用: 通常用于控制变量的作用范围,出了花括号就失效
- 注意事项: 变量的作用范围越小越好,成员变量会存在线程安全的问题
代码块的加载顺序
public class Test3_Block {
public static void main(String[] args) {
Teacher t = new Teacher();
Teacher t2 = new Teacher("12312","JAVA");
System.out.println(t2.name);
System.out.println(t2.subject);
t.teach();
t.study();
}
}
class Teacher{
String name;
String subject;
{
subject = "JAVA";
System.out.println("哈哈哈哈要调用构造方法啦");
}
public Teacher() {
System.out.println("无参构造"+subject);
}
public Teacher(String n,String s) {
name = n;
subject = s;
System.out.println("全参构造"+subject);
}
public void study() {
{
int i =10;
System.out.println(i);
}
System.out.println("正在备课ing");
}
public void teach() {
System.out.println("正在上课ing");
}
}
this
概念: this 代表本类对象的一个引用对象 形式: this.name = name ;
class Dog{
String name;
public Dog() {
this("拉拉");
System.out.println("无参构造");
}
public Dog(String s) {
System.out.println("含参构造"+s);
}
}
this与super的区别
- this代表本类对象的引用
class Father3{ this.XXX } //this – Father3 this = new Father3(); - super代表父类对象的引用
class Father3{ super.XXX } //this – Father3 super = new Father3(); 就相当于创建了一个父类对象 - this可以在两个变量名相同时,用于区分成员变量和局部变量
- this 可以在本类的构造方法之间调用,位置必须是第一条语句,注意,不能相互调用,会死循环
- super是发生了继承关系以后,子类如果想用父类的功能,可以通过super调用
- 如果发生了重写,还想用父类的功能,需要使用super来调用
- Super在调用父类构造方法时,必须出现在子类构造方法的第一条语句,而且如果父类中没有提供无参构造,子类可以通过super来调用父类其他的含参构造
static
概念: 是java中的一个关键字,用于修饰成员(成员变量和成员方法)
特点:
- 可以修饰成员变量与成员方法
- 随着类的加载而加载,优先于对象加载
- 只加载一次,就会一直存在,不再开辟新空间, 直到类消失才一起消失
- 静态资源也叫做类资源,全局唯一,被全局所有对象共享
- 可以直接被类名调用
- 静态只能调用静态,非静态可以随意调用
- static不能和this或者super共用,因为有static时可能还没有对象
public class Test_Static1 {
public static void main(String[] args) {
System.out.println(Student.name);
Student.study();
Student s = new Student();
s.name = "黄桃罐头";
System.out.println(s.name);
s.study();
s.speak();
Student s2 = new Student();
System.out.println(s2.name);
System.out.println(Student.name);
}
}
class Student{
int sno;
static String name;
public static void study() {
System.out.println("别闹~学JAVA呢~");
}
public void speak() {
System.out.println("大声说出来~");
}
}
static静态调用关系
1.静态资源只能调用静态资源 2.非静态资源既可以调用静态资源,也可以调用非静态资源
静态代码块、构造代码块、局部代码块
静态代码块格式: static {} 静态资源随着类的加载而加载,并且只被加载一次,一般用于项目的初始化 特点: 被static修饰,位置在类里方法外
三种代码块的比较
- 静态代码块:在类加载时就加载,并且只被加载一次,一般用于项目的初始化
- 构造代码块:在创建对象时会自动调用,每次创建对象都会被调用,提取构造共性
- 局部代码块:方法里的代码块,限制局部变量的范围
几种代码块的关系: 1.代码之间的执行顺序: 静态代码块–>构造代码块–>构造方法–>局部代码块 2.为什么是这样的顺序呢? 静态代码块要优先于对象进行加载 是随着类的加载而加载到内存中的 只加载一次,并且一直存在,直到类消失,它才会消失 3.每个元素的作用: 1)静态代码块:专门用来完成一些需要第一时间加载并且只加载一次的资源 2)构造代码块:创建对象时才会触发,用来提取构造方法中的共性内容 3)构造方法:创建对象时调用,用来创建对象,在构造代码块执行后执行 4)局部代码块:调用所在的方法时才会调用,用来控制变量的作用范围
静态变量和实例变量的区别
在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。 在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。
final
概念:
- 是java提供的一个关键字
- final是最终的意思
- final可以修饰类,方法,成员变量
初衷:java出现继承后,子类可以更改父类的功能,当父类功能不许子类改变时,可以利用final关键字修饰父类。
特点:
- 被final修饰的类,不能被继承
- 被final修饰的方法,不能被重写
- 被final修饰的变量是个常量,值不能被改变
- 常量的定义形式:final 数据类型 常量名 = 值
public class Demo {
public static void main(String[] args) {
Son4 s = new Son4();
s.work();
}
}
class Father4{
final String name = "打工人";
final public void work() {
System.out.println("Father4...打工人,打工魂");
}
}
class Son4 extends Father4{
}
抽象类
概念:
Java中可以定义被abstract关键字修饰的方法,这种方法只有声明,没有方法体,叫做抽象方法. Java中可以定义被abstract关键字修饰的类,被abstract关键字修饰的类叫做抽象类 如果一个类含有抽象方法,那么它一定是抽象类 抽象类中的方法实现交给子类来完成
抽象方法的格式:
权限修饰符 abstract 返回值类型 方法名(参数列表);
特点:
- abstract 可以修饰方法或者类
- 被abstarct修饰的类叫做抽象类,被abstract修饰的方法叫做抽象方法
- 抽象类中可以没有抽象方法
- 如果类中有抽象方法,那么该类必须定义为一个抽象类
- 子类继承了抽象类以后,要么还是一个抽象类,要么就把父类的所有抽象方法都重写
- 多用于多态中
- 抽象类不可以被实例化
抽象类中的构造函数通常在子类对象实例化时使用
public class Test4_Abstract3 {
public static void main(String[] args) {
Fruit f = new Apple();
System.out.println(f.sum);
System.out.println(f.name);
f.eat();
f.eat2();
f.clean();
}
}
abstract class Fruit{
int sum = 10;
final String name ="banana";
public void eat() {
System.out.println("吃啥水果都行");
}
public abstract void eat2() ;
public abstract void clean();
}
class Apple extends Fruit{
@Override
public void eat2() {
System.out.println("水果中最爱吃苹果");
}
@Override
public void clean() {
System.out.println("苹果还是要好好洗洗再吃的");
}
}
abstract注意事项
抽象方法要求子类继承后必须重写。 那么,abstract关键字不可以和哪些关键字一起使用呢?以下关键字,在抽象类中。用是可以用的,只是没有意义了。
- private:被私有化后,子类无法重写,与abstract相违背。
- static:静态优先于对象存在。而abstract是对象间的关系,存在加载顺序问题。
- final:被final修饰后,无法重写,与abstract相违背。
拓展:
递归案例:
接口
概念:
Java里面由于不允许多重继承,所以如果要实现多个类的功能,则可以通过实现多个接口来实现,Java接口和Java抽象类代表的就是抽象类型,就是我们需要提出的抽象层的具体表现 OOP面向对象编程,如果要提高程序的复用率,增加程序的可维护性,可扩展性,就必须是面向接口编程,面向抽象的变成,正确的使用接口/抽象类这些抽象类型作为java结构层次上的顶层.
接口格式:
interface 接口名{ 代码… }
接口的特点:
- 接口中都是抽象方法
- 通过interface关键字来定义接口
- 通过implements让子类来实现接口
- 可以理解成,接口是一个特殊的抽象类(接口里的方法都是抽象方法)
.在JDK1.8以后不仅仅只有常量和抽象方法,还有默认方法和静态方法 默认方法用来直接给子类使用,如果子类想重写也可以自行根据需求重写,不会强制重写 静态方法用来直接通过接口名访问 - 接口突破了java单继承的局限性
- 接口和类之间可以多实现,接口与接口之间可以多继承
- 接口是对外暴露的规则,是一套开发规范
- 接口提高了程序的功能拓展,降低了耦合性
9)常量: 默认使用 public static final 方法: 默认使用 public abstract修饰
public interface Inter {
public abstract void eat();
public abstract void play();
}
public class InterImpl implements Inter{
@Override
public void eat() {
System.out.println("吃火锅");
}
@Override
public void play() {
System.out.println("玩代码");
}
}
public class InterTests {
public static void main(String[] args) {
Inter i = new InterImpl();
i.eat();
i.play();
InterImpl i2 = new InterImpl();
i2.eat();
i2.play();
}
}
接口的用法
public class UserInter {
public static void main(String[] args) {
Inter2 i = new Inter2Impl();
System.out.println(Inter2.age);
}
}
interface Inter2{
int age = 10;
abstract public void eat2();
void eat();
}
class Inter2Impl implements Inter2{
public Inter2Impl() {
super();
System.out.println("我是Inter2Impl的无参构造");
}
@Override
public void eat2() {
}
@Override
public void eat() {
}
}
总结: 接口里是没有构造方法的。在创建实现类的对象时默认的super(),是调用的默认Object的无参构造。 接口里没有成员变量,都是常量。所以,你定义一个变量没有写修饰符时,默认会加上:public static final 接口里的方法,默认就都是抽象的,如果你不写明是abstract的,那会自动补齐。例如:public abstract void save
接口的多继承多实现
public class Test4 {
public static void main(String[] args) {
Inter3Impl i3 = new Inter3Impl();
i3.update();
Inter3 i4 = new Inter3Impl();
i4.find();
}
}
interface Inter1{
void save();
void delete();
}
interface Inter2{
void update();
void find();
}
interface Inter3 extends Inter1,Inter2{
}
class Inter3Impl implements Inter3{
@Override
public void save() {
System.out.println("稍等...正在保存中...");
}
@Override
public void delete() {
System.out.println("稍等...正在删除中....");
}
@Override
public void update() {
System.out.println("客官,马上就更新好啦~~");
}
@Override
public void find() {
System.out.println("小二正在马不停蹄的查询~~~");
}
}
总结
1.类与类的关系
–继承关系,只支持单继承 –比如,A是子类 B是父类,A具备B所有的功能(除了父类的私有资源和构造方法) –子类如果要修改原有功能,需要重写(方法签名与父类一致 + 权限修饰符>=父类修饰符)
2.类和接口的关系 –实现关系.可以单实现,也可以多实现 –class A implements B,C{} –其中A是实现类,B和C是接口,A拥有BC接口的所有功能,只是需要进行方法的重写,否则A就是抽象类
3.接口与接口的关系 –是继承关系,可以单继承,也可以多继承 –interface A extends B,C{} –其中ABC都是接口,A是子接口,具有BC接口的所有功能(抽象方法) –class X implements A{} –X实现类需要重写ABC接口的所有方法,否则就是抽象类 –class A extends B implements C,D{} –其中A是实现类,也是B的子类,同时拥有CD接口的所有功能 –这时A需要重写CD接口里的所有抽象方法
4.抽象类与接口的区别
- 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
- 抽象类要被子类继承,接口要被子类实现。
- 接口只能做方法声明,抽象类中可以做方法声明,也可以做方法实现
- 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
- 抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
- 抽象方法只能声明,不能实现,接口是设计的结果 ,抽象类是重构的结果
- 抽象类里可以没有抽象方法
- 如果一个类里有抽象方法,那么这个类只能是抽象类
- 抽象方法要被实现,所以不能是静态的,也不能是私有的。
- 接口可继承接口,并可多继承接口,但类只能单继承。
拓展
类: 对事物/算法/逻辑/概念等等的抽象,可以把它理解成”模板/图纸” 封装:相关的数据/运算代码封装成一个”类”组件
对象: 从”类”出发创建具体的”实例” 每个对象,占用独立的内存空间,保存各自的属性数据 可以独立控制一个对象,来执行指定的方法代码
软件设计的开闭原则OCP:
开放功能扩展,关闭源码修改。等 开闭原则的英文全称是Open Close Principle,缩写是OCP,它是Java世界里最基础的设计原则,它指导我们如何建立一个稳定的、灵活的系统。 开闭原则的定义是:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的。 开闭原则,是一种设计模式,随着面向对象程序设计的思想,应运而生。 开,指的是可以在源代码的基础上进行扩展,比如继承,接口,抽象类等。在JAVA中,之所以用继承,是在可以直接调用类库的前提下,对其功能进行扩展。不需要应用者去了解封装类的内部逻辑就可以做开发。 闭:指不允许对原有的代码进行修改。以免影响其他现有功能,造成功能瘫痪。
第五章:异常
概述: 用来封装错误信息的对象
异常的继承结构:
Throwable : 顶级父类
--Error : 系统错误,无法修复
--Exception : 可以修复的错误
-- RunTimeException
-- ClassCastException
-- ClassNotFoundException
-- ...
异常处理
当程序中遇到了异常,通常有两种处理方式:捕获或者向上抛出 当一个方法抛出异常,调用位置可以不做处理继续向上抛出,也可以捕获处理异常
- 捕获方式:
try{ 需要捕获的代码 }catch(异常类型 异常名){ 处理方案 } - 抛出方式:
在会发生异常的方法上添加代码:throws 异常类型 例如:public static void main(String[] args) throws Exception{
public class ExceptionDemo {
public static void main(String[] args) throws Exception {
method2();
}
public static void method2() throws Exception{
System.out.println("请输入您要计算的第一个数:");
int a = new Scanner(System.in).nextInt();
System.out.println("请输入您要计算的第二个数:");
int b = new Scanner(System.in).nextInt();
System.out.println( a / b );
}
public static void method1() {
try {
System.out.println("请输入您要计算的第一个数:");
int a = new Scanner(System.in).nextInt();
System.out.println("请输入您要计算的第二个数:");
int b = new Scanner(System.in).nextInt();
System.out.println( a / b );
}catch(InputMismatchException e) {
System.out.println("输入的类型不正确,请输入正确的整数类型");
}catch(ArithmeticException e) {
System.out.println("除数不能为0!!");
}catch(Exception e) {
System.out.println("请输入正确的整数~");
}
}
public static void method() {
System.out.println("请输入您要计算的第一个数:");
int a = new Scanner(System.in).nextInt();
System.out.println("请输入您要计算的第二个数:");
int b = new Scanner(System.in).nextInt();
System.out.println( a / b );
}
}
第六章 :常用API
概念:
API(Application Programming Interface,应用程序接口)是一些预先定义的函数。目的是提供应用程序与开发人员基于某软件可以访问的一些功能集,但又无需访问源码或理解内部工作机制的细节. API是一种通用功能集,有时公司会将API作为其公共开放系统,也就是公司制定自己的系统接口标准,当需要进行系统整合,自定义和程序应用等操作时,公司所有成员都可以通过该接口标准调用源代码.
Java.util包是java中的工具包,包含各种实用工具类/集合类/日期时间工具等各种常用工具包
import java.util.Scanner;
import java.util.Arrays;
java.lang包是java的核心,包含了java基础类,包括基本Object类/Class类/String类/基本数学类等最基本的类
这个包无需导入,默认会自动导入
import java.lang.Object;
import java.lang.String;
import java.lang.StringBuilder/StringBuffer;
正则表达式
包装类等等
Object
概念:
Object类是所有Java类的祖先,也就是说我们所说的”顶级父类” 存在于java.lang.Object,这个包不需要我们手动导包 每个类都使用Object作为超类.所有对象(包括数组)都实现这个类的方法.在不明确给出超类的情况下,Java会自动把Object类作为要定义类的超类.
常用方法介绍:
toString()
本方法用于返回对应对象的字符串表示.
hashCode()
本方法用于返回对应对象的哈希码值 TIPS:哈希码是一种算法,使不同的对象有不同的哈希码值,但是也有相同的情况,我们称之为”哈希碰撞”
equals()
本方法用于指示其他某个对象是否与当前对象”相等”
String
特点:
String是一个封装char[]数组的对象,字符串不可变 通过下图中的底层实现可以看出:被final修饰,是常量 String str = “abc”; 等效于:char data[] = {‘a’, ‘b’, ‘c’};
创建String对象的方式
方式一: String(char[] value) 分配一个新的 String,使其表示字符数组参数中当前包含的字符序列。 方式二: String str = “abc”;
- 如果是第一次使用字符串,java会在字符串常量池创建一个对象。
- 再次使用相同的内容时,会直接访问常量池中存在的对象。
常见方法
length()-查看字符串的长度
charAt()—定位某个字符,返回它的位置
lastIndexOf()-某个字符最后一次出现的位置
substring()-截取子串,如果参数有两个左闭右开[1,5)
equals()-判断两个串是否相等,注意String重写了Object的此方法,所以内容相同就返回true
startsWith()-判断是不是以参数开头
endsWith()--判断是不是以参数结尾
split()—以指定字符分割
trim()-去掉首尾两端的空格
getBytes()-把串转换成数组
toUpperCase()-变成全大写
toLowerCase()-变成全小写
String.valueOf(10)-把int类型的10转换成String类型
StringBuilder/StringBuffer
特点:
- 封装了char[]数组
- 是可变的字符序列
- 提供了一组可以对字符内容修改的方法
- 常用append()来代替字符串做字符串连接”+”
- 内部字符数组默认初始容量是16:super(str.length() + 16);
- 如果大于16会尝试将扩容,新数组大小原来的变成2倍+2,容量如果还不够,直接扩充到需要的容量大小。int newCapacity = value.length * 2 + 2;
- StringBuffer 线程安全,StringBuilder线程不安全
StringBuilder和StringBuffer的区别
1.在线程安全上 : –StringBuffer是旧版本就提供的,线程安全的。@since JDK1.0 –StringBuilder是jdk1.5后产生,线程不安全的。@since 1.5 2. 在执行效率上,StringBuilder > StringBuffer > String 3.源码体现:本质上都是在调用父类抽象类AbstractStringBuilder来干活,只不过Buffer把代码加了同步关键字,使得程序可以保证线程安全问题。 abstract class AbstractStringBuilder implements Appendable, CharSequence {
正则表达式Regex
概述:
正确的字符串格式规则。 常用来判断用户输入的内容是否符合格式的要求,注意是严格区分大小写的。
String提供了支持正则表达式的方法
Matches(正则) : 当前字符串能否匹配正则表达式
replaceAll(正则,子串) : 替换子串
split(正则) : 拆分字符串
public class Test4_Regex {
public static void main(String[] args) {
System.out.println("请您输入您的身份证号:");
String input = new Scanner(System.in).nextLine();
String regex = "[0-9]{17}[0-9xX]";
if( input.matches(regex) ) {
System.out.println("输入正确!");
}else {
System.out.println("输入不正确,请重新输入!");
}
}
}
BigDecimal/BigInteger
概述:
BigDecimal:常用来解决精确的浮点数运算 BigInteger: 常用来解决超大的整数运算
创建对象:
BigDecimal(double val) 将double转换为BigDecimal,后者是double的二进制浮点值十进制表示形式,有坑! BigDecimal(String val) 将String类型字符串的形式转换为BigDecimal
常用方法
Add(BigDecimal bd) : 做加法运算 Subtract(BigDecimal bd) : 做减法运算 Multiply(BigDecimal bd) : 做乘法运算 Divide(BigDecimal bd) : 做除法运算,除不尽时会抛异常 Divide(BigDecimal bd,保留位数,舍入方式) : 除不尽时使用 setScale(保留位数,舍入方式) : 同上 pow(int n) : 求数据的几次幂
public static void main(String[] args) {
method2();
}
public static void method2() {
System.out.println("请输入您要计算的两个小数:");
double a = new Scanner(System.in).nextDouble();
double b = new Scanner(System.in).nextDouble();
BigDecimal bd1 = new BigDecimal(a+"");
BigDecimal bd2 = new BigDecimal(b+"");
BigDecimal bd3;
bd3 = bd1.add(bd2);
System.out.println(bd3);
bd3 = bd1.subtract(bd2);
System.out.println(bd3);
bd3 = bd1.multiply(bd2);
System.out.println(bd3);
bd3 = bd1.divide(bd2, 3, BigDecimal.ROUND_HALF_UP);
System.out.println(bd3);
}
包装类
把基本类型进行包装,提供更加完善的功能。 基本类型是没有任何功能的,只是一个变量,记录值,而包装类可以有更加丰富的功能
Integer
创建对象
1. new Integer(5);
2. Integer.valueOf(5);
在Integer类中,包含256个Integer缓存对象,范围是 -128到127。
使用valueOf()时,如果指定范围内的值,访问缓存对象,而不新建;如果指定范围外的值,直接新建对象。
public static void main(String[] args) {
Integer i0;
Integer i1 = new Integer(5);
Integer i2 = Integer.valueOf(127);
Integer i3 = Integer.valueOf(127);
Integer i4 = Integer.valueOf(300);
Integer i5 = Integer.valueOf(300);
System.out.println(i1==i2);
System.out.println(i2==i3);
System.out.println(i4==i5);
}
Double
创建对象
1. new Double(3.14)
2. Double.valueOf(3.14)
Double d1 = new Double(3.4);
Double d2 = Double.valueOf(3.4);
Double d3 = Double.valueOf(3.4);
System.out.println(d2==d3);
System.out.println(i1.parseInt("8000")+10);
System.out.println(d1.parseDouble("2.2")+1);
自动装箱和自动拆箱
概述:
自动装箱:把 基本类型 包装成对应的 包装类型 的过程 Integer a = 5;//a是引用类型,引用了包装对象的地址。 编译器会完成对象的自动装箱:Integer a = Integer.valueOf(5);
自动拆箱:从包装类型的值,自动变成 基本类型的值 int i = a;//a现在是包装类型,没法给变量赋值,需要把5取出来。 编译器会完成自动拆箱:int i = a.intValue();
public static void main(String[] args) {
Integer i11 = new Integer(127);
Integer i22 = Integer.valueOf(127);
Integer i1 = 5;
int i2 = i1;
}
拓展:
==和equals的区别
1.当使用 = = 比较时,如果相比较的两个变量是引用类型,那么比较的是两者的物理地值(内存地址),如果相比较的两个变量都是数值类型,那么比较的是具体数值是否相等。 2.当使用equals()方法进行比较时,比较的结果实际上取决于equals()方法的具体实现 众所周知,任何类都继承自Object类,因此所有的类均具有Object类的特性,比如String、integer等,他们在自己的类中重写了equals()方法,此时他们进行的是数值的比较,而在Object类的默认实现中,equals()方法的底层是通过 = =来实现的。 System.out.println(p1.name = = p2.name);//true,name是String类型,保存在常量池中,值是同一个
public class Test7 {
public static void main(String[] args) {
Person p1 = new Person("凹凸曼",16);
Person p2 = new Person("凹凸曼",16);
System.out.println(p1 == p2);
System.out.println(p1.name == p2.name);
System.out.println(p1.age == p2.age);
System.out.println(p1.equals(p2));
}
}
class Person {
String name;
int age;
public Person() {
System.out.println("我是手动添加的无参构造");
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
第七章:IO
继承结构
In/out 相对于程序而言的输入(读取)/输出(写出)的过程. 在java中,根据处理的数据单位不同,分为字节流和字符流 字节流 : 针对二进制文件 字符流 : 针对文本文件,读写容易出现乱码的现象,在读写时,最好指定编码集为UTF-8
Java.io包下:
File
字节流:针对二进制文件
InputStream
--FileInputStream
--BufferedInputStream
--ObjectInputStream
OutputStream
--FileOutputStream
--BufferedOutputStream
--ObjectOutputStream
字符流:针对文本文件
Reader
--FileReader
--BufferedReader
--InputStreamReader
Writer
--FileWriter
--BufferedWriter
--OutputStreamWriter
--PrintWriter一行行写出
Stream流的概念
数据的读写可以抽象成数据在管道中流动
- 流只能单方向流动
- 输入流用来读取à in
- 输出流用来写出 àout
- 数据只能从头到尾顺序的读写一次
File文件流
概述:
封装一个磁盘路径字符串,对这个路径可以执行一次操作 可以封装文件路径、文件夹路径、不存在的路径
创建对象:
File(String pathname)通过将给定路径名字符串转换为抽象路径名来创建一个新的File实例
new File(“d:/abc/a.txt”);
new File(“d:/abc”,”a.txt”);
常用方法:
File file = new File("D:\\ready\\1.txt");
System.out.println(file.length());
System.out.println(file.exists());
System.out.println(file.isFile());
System.out.println(file.isDirectory());
System.out.println(file.getName());
System.out.println(file.getParent());
System.out.println(file.getAbsolutePath());
file = new File("D:\\\\ready\\\\2.txt");
System.out.println(file.createNewFile());
file = new File("D:\\ready\\m");
System.out.println(file.mkdir());
file = new File("D:\\ready\\a\\b\\c");
System.out.println(file.mkdirs());
System.out.println(file.delete());
file = new File("D:\\ready\\a");
System.out.println(file.delete());
file = new File("D:\\ready");
String[] list = file.list();
System.out.println(Arrays.toString(list));
File[] listFiles = file.listFiles();
System.out.println(Arrays.toString(listFiles));
System.out.println(listFiles[0].length());
递归求目录总大小
需求:递归求目录的总大小 D:\\ready,步骤分析如下:
1.列出文件夹中的所有资源--listFiles()-->File[]
2.判断,当前资源是文件还是文件夹--文件夹大小为0,文件大小需要累加
--是文件,求文件的字节量大小length(),累加就行
--是文件夹,继续列出文件夹下的所有资源--listFiles()-->File[]
--判断,是文件,求文件的字节量大小length(),累加就行
--判断,是文件夹,再一次列出文件夹下的所有资源
--......重复操作
也就是说,规律就是:只要是文件夹,就需要重复步骤1 2
private static long size(File file) {
File[] fs = file.listFiles();
long sum = 0;
for(int i = 0;i < fs.length ; i++) {
File f = fs[i];
if(f.isFile()) {
sum += f.length();
}else if(f.isDirectory()) {
sum += size(f);
}
}
return sum;
}
字节流读取
字节流是由字节组成的,字符流是由字符组成的. Java里字符由两个字节组成.字节流是基本,主要用在处理二进制数据。 流式传输主要指将整个音频和视频及三维媒体等多媒体文件经过特定的压缩方式解析成一个个压缩包,由视频服务器向用户计算机顺序或实时传送。在采用流式传输方式的系统中,用户不必像采用下载方式那样等到整个文件全部下载完毕,而是只需经过几秒或几十秒的启动延时即可在用户的计算机上利用解压设备对压缩的A/V、3D等多媒体文件解压后进行播放和观看。此时多媒体文件的剩余部分将在后台的服务器内继续下载。
InputStream抽象类
此抽象类是表示字节输入流的所有类的超类/抽象类,不可创建对象 常用方法:
abstract int read() 从输入流中读取数据的下一个字节
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中
int read(byte[] b, int off, int len) 将输入流中最多 len 个数据字节读入 byte 数组,off表示存时的偏移量
void close() 关闭此输入流并释放与该流关联的所有系统资源
FileInputStream子类
直接插在文件上,直接读取文件数据 创建对象 FileInputStream(File file)—直接传文件对象 通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定FileInputStream(String pathname)—传路径 通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定
BufferedInputStream子类
BufferedInputStream 为另一个输入流添加一些功能,即缓冲输入以及支持 mark 和 reset 方法的能力。在创建 BufferedInputStream 时,会创建一个内部缓冲区数组(默认8k大小)。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。 创建对象 BufferedInputStream(InputStream in) 创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
public static void main(String[] args) {
method();
method2();
}
public static void method() {
InputStream in = null;
try {
in = new FileInputStream("D:\\ready\\4.txt");
int b;
while((b = in.read()) != -1) {
System.out.println(b);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void method2() {
BufferedInputStream in = null;
try {
in = new BufferedInputStream(
new FileInputStream("D:\\ready\\4.txt"));
int b;
while((b = in.read()) != -1) {
System.out.println(b);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
字符流读取
常用于处理纯文本数据
Reader抽象类
用于读取字符流的抽象类。 常用方法:
int read() 读取单个字符
int read(char[] cbuf) 将字符读入数组
abstract int read(char[] cbuf, int off, int len) 将字符读入数组的某一部分
int read(CharBuffer target) 试图将字符读入指定的字符缓冲区
abstract void close() 关闭该流并释放与之关联的所有资源
FileReader子类
用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。
创建对象 FileReader(String fileName) 在给定从中读取数据的文件名的情况下创建一个新 FileReader FileReader(File file) 在给定从中读取数据的 File 的情况下创建一个新 FileReader
BufferedReader子类
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。
创建对象 BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流
public static void main(String[] args) {
method2();
}
public static void method() {
try {
Reader in = new FileReader("D:\\ready\\1.txt");
int b;
while( (b = in.read()) != -1) {
System.out.println(b);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void method2() {
try {
Reader in = new BufferedReader(new FileReader("D:\\ready\\1.txt"));
int b;
while( (b = in.read()) != -1) {
System.out.println(b);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
字节流写出
OutputStream抽象类
此抽象类是表示输出字节流的所有类的超类.输出流接受输出字节并将这些字节发送到某个接收器.
常用方法: Void close() 关闭此输出流并释放与此流相关的所有系统资源 Void flush() 刷新此输出流并强制写出所有缓冲的输出字节 Void write(byte[ ] b) 将b.length个字节从指定的byte数组写入此输出流 Void write(byte[ ] b,int off ,int len) 将指定byte数组中从偏移量off开始的len个字节写入输出流 Abstract void write(int b) 将指定的字节写入此输出流
FileOutputStream 子类
直接插在文件上,直接写出文件数据 构造方法(创建对象): FileOutputStream(String name) 创建一个向具有指定名称的文件中写入数据的文件输出流 FileOutStream(File file) 创建一个向指定File对象表示的文件中写入数据的文件输出流 FileOutStream(File file,boolean append)—如果第二个参数为true,表示追加,不覆盖 创建一个向指定File对象表示的文件中写入数据的文件输出流,后面的参数是指是否覆盖原文件内容
BufferedOutputstream 子类
该类实现缓冲的输出流,通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必每次针对字节写出调用底层系统 构造方法(创建对象): BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,用以将数据写入指定的底层输出流
public static void main(String[] args) {
method2();
}
public static void method2() {
OutputStream out = null;
try {
out =new BufferedOutputStream(new FileOutputStream("D:\\ready\\out.txt"));
out.write(100);
out.write(100);
out.write(100);
out.write(100);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void method() {
OutputStream out=null;
try {
out = new FileOutputStream("D:\\ready\\out.txt");
out.write(97);
out.write(98);
out.write(99);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
字符流写出
Writer 抽象类
写入字符流的抽象类 常用方法:
Abstract void close() 关闭此流,但要先刷新它 Void write(char[ ] cbuf) 写入字符数组 Void write(int c) 写入单个字符 Void write(String str) 写入字符串 Void write(String str,int off,int len) 写入字符串的某一部分 Abstract void write(char[] cbuf,int off,int len)写入字符数组的某一部分
FileWriter 子类
用来写入字符文件的便捷类,此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的.如果需要自己自定义这些值,可以先在FileOutputStream上构造一个OutputStreamWriter.
构造方法(创建对象): FileWriter(String filename) 根据给定的文件名构造一个FileWriter对象 FileWriter(String filename,boolean append) 根据给定的文件名以及指示是否附加写入数据的boolean值来构造FileWriter
BufferedWriter子类
将文本写入字符输出流,缓冲各个字符,从而提供单个字符,数组和字符串的高效写入.可以指定缓冲区的大小,或者接受默认的大小,在大多数情况下,默认值就足够大了
构造方法(创建对象): BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流
public static void main(String[] args) {
method1();
method2();
}
public static void method2() {
Writer out = null;
try {
out = new BufferedWriter(new FileWriter("D:\\ready\\out.txt",true));
out.write(97);
out.write("hello");
out.write("io");
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void method1() {
Writer out = null;
try {
out = new FileWriter("D:\\ready\\out.txt",true);
out.write(97);
out.write(98);
out.write(99);
out.write(100);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
文件复制测试
public static void main(String[] args) {
System.out.println("请输入原文件路径:");
String f = new Scanner(System.in).nextLine();
System.out.println("请输入目标文件路径:");
String t = new Scanner(System.in).nextLine();
File from = new File(f);
File to = new File(t);
ZJCopy(from,to);
}
public static void ZJCopy(File from, File to) {
InputStream in = null;
OutputStream out = null;
try {
in = new BufferedInputStream(new FileInputStream(from));
out = new BufferedOutputStream(new FileOutputStream(to));
int b ;
while((b=in.read()) != -1) {
out.write(b);
}
System.out.println("恭喜您!复制成功!");
} catch (IOException e) {
System.out.println("很抱歉!复制失败!");
e.printStackTrace();
}finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void ZFCopy(File from, File to) {
Reader in = null;
Writer out = null;
try {
in = new BufferedReader(new FileReader(from));
out = new BufferedWriter(new FileWriter(to));
int b;
while( (b = in.read()) != -1 ) {
out.write(b);
}
System.out.println("恭喜您!文件复制完成!");
} catch (IOException e) {
System.out.println("很抱歉!文件复制失败!");
e.printStackTrace();
}finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
文件批量读写
public static void ZJcopy(File from, File to) {
InputStream in = null;
OutputStream out = null;
try {
in = new BufferedInputStream(new FileInputStream(from));
out = new BufferedOutputStream(new FileOutputStream(to));
int b = 0;
byte[] bs = new byte[8*1024];
while( (b=in.read(bs)) != -1 ) {
out.write(bs);
}
System.out.println("恭喜您!文件复制成功!");
} catch (IOException e) {
System.out.println("很抱歉!文件复制失败!");
e.printStackTrace();
}finally {与上面相同略…}
}
}
IO的继承结构
1. 主流分类
1) 按照方向进行分类:输入流 输出流(相对于程序而言,从程序写数据到文件中是输出)
2) 按照传输类型进行分类:字节流 字符流
3) 组合: 字节输入流 字节输出流 字符输入流 字符输出流
2. 学习方法:在抽象父类中学习通用的方法,在子类中学习如何创建对象
3.字节输入流:
--InputStream 抽象类,不能new,可以作为超类,学习其所提供的共性方法
--FileInputStream 子类,操作文件的字节输入流,普通类
--BufferedInputStream 子类,缓冲字节输入流,普通类
4.字符输入流
--Reader 抽象类,不能new,可以作为超类,学习其所提供的共性方法
--FileReader,子类,操作文件的字符输入流,普通类
--BufferedReader,子类,缓冲字符输入流,普通类
5.字节输出流:
--OutputStream 抽象类,不能new,可以作为超类,学习其所提供的共性方法
--FileOutputStream 子类,操作文件的字节输出流,普通类
--BufferedOutputStream 子类,缓冲字节输出流,普通类
6.字符输出流
--Writer 抽象类,不能new,可以作为超类,学习其所提供的共性方法
--FileWriter,子类,操作文件的字符输出流,普通类
--BufferedWriter,子类,缓冲字符输出流,普通类
BIO、NIO、AIO的区别
阻塞IO,BIO 就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用时可靠的线性顺序。它的有点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。
非阻塞IO,NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。
异步IO,AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。但目前还不够成熟,应用不多。
序列化/反序列化
概述: 序列化(Serialization)是将对象的状态信息转换为可以存储或传输形式的过程. 在序列化期间,对象将其当前状态写入到临时或持久性存储区.以后可以通过从存储区中读取或者反序列化对象的状态,重新创建该对象. 序列化:利用ObjectOutputStream,把对象的信息,按照固定的格式转成一串字节值输出并持久保存到磁盘 反序列化:利用ObjectInputStream,读取磁盘中之前序列化好的数据,重新恢复成对象 TIPS: 序列化: 对象 à 字节值 反序列化:字节值(之前序列化生成的) à 对象
特点/应用场景
-
需要序列化的文件必须实现Serializable接口,用来启用序列化功能
-
不需要序列化的数据可以修饰成static,原因:static资源属于类资源,不随着对象被序列化输出
-
每一个被序列化的文件都有一个唯一的id,如果没有添加此id,编译器会自动根据类的定义信息计算产生一个
-
在反序列化时,如果和序列化的版本号不一致,无法完成反序列化
-
常用与服务器之间的数据传输,序列化成文件,反序列化读取数据
-
常用使用套接字流在主机之间传递对象
-
不需要序列化的数据也可以被修饰成transient(临时的),只在程序运行期间在内存中存在,不会被序列化持久保存
ObjectOutputStream
ObjectOutputStream将Java对象的基本数据类型和同行写入OutputStream.可以使用ObjectInputStream读取(重构)对象.通过在流中使用文件可以实现对象的持久存储. 如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象.
构造方法:
ObjectOutputStream(OutputStream out)
创建写入指定 OutputStream 的 ObjectOutputStream
方法:
writeObject(Object obj) 将指定的对象写入 ObjectOutputStream
ObjectInputStream
ObjectInputStream对以前使用ObjectOutputStream写入的基本数据和对象进行反序列化
构造方法:
ObjectInputStream(InputStream in)
创建从指定 InputStream 读取的 ObjectInputStream
方法:
readObject()
从 ObjectInputStream 读取对象
public class Student implements Serializable{
private static final long serialVersionUID = -3193364654654535741L;
private String name;
private int age;
private String addr;
private char gender;
}
public static void main(String[] args) {
method2();
}
public static void method2() {
ObjectInputStream in = null;
try {
in = new ObjectInputStream(new FileInputStream("D://ready//1.txt"));
Object o = in.readObject();
System.out.println(o);
System.out.println("恭喜您!反序列化成功!");
} catch (Exception e) {
System.out.println("很抱歉!反序列化失败!");
e.printStackTrace();
}finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void method1() {
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(
new FileOutputStream("D://ready//1.txt"));
Student s = new Student("陈子枢",3,"男生","BeiJing");
out.writeObject(s);
System.out.println("恭喜您!序列化成功");
} catch (IOException e) {
System.out.println("很抱歉!序列化失败!");
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
编码转换流
字节流:针对二进制文件 字符流:针对文本文件,读写容易出现乱码的现象,在读写时,最好指定编码集为UTF-8
概述: 编码转换流(InputStreamReader/OutputStreamWriter)主要进行编码的转换,用来解决字符流读写乱码的问题 api:
OutputStreamWriter :
OutputStreamWriter(OutputStream out)把传入的字节流转成字符流
OutputStreamWriter(OutputStream out ,String charsetName)把Unicode转成其他编码输出
InputStreamReader :
InputStreamReader(InputStream in) 把传入的字节流转成字符流
InputStreamReader(InputStream in,String charsetName)读取其他编码转成Unicode
第八章:泛型
概念:
public class LinkedList extends AbstractSequentialList implements List public interface Deque extends Queue {} public interface Queue extends Collection {} public interface Collection extends Iterable {}
我们上面的代码中出现的<?>是什么东西呢?它叫泛型,常用来和集合对象一同使用,所以在开始学习集合之前,必须先了解下什么是泛型。而且泛型概念非常重要,它是程序的增强器,它是目前主流的开发方式。
泛型是(Generics)JDK1.5 的一个新特性,其实就是一个『语法糖』,本质上就是编译器为了提供更好的可读性而提供的一种小手段,小技巧,虚拟机层面是不存在所谓『泛型』的概念的。是不有点神奇,不知所云,别着急等我讲完你就清楚了。
作用:
通过泛型的语法定义<>,约束集合元素的类型,编译器可以在编译期提供一定的类型安全检查 这样可以避免程序运行时才暴露BUG,代码的通用性也会更强 泛型可以提升程序代码的可读性,但是它只是一个==『语法糖』==(编译后这样的部分会被删除,不出现在最终的源码中),所以不会影响JVM后续运行时的性能.
泛型声明:
泛型可以在接口 类 方法上使用
public interface Collection<E>{}
public class TestStudy<Student>{}
public <E> void print(E e){}
在方法的返回值前声明了一个<E>,表示后面出现的E是泛型,而不是普通的java变量
常用名称:
- E - Element ( 在集合中使用,因为集合中存放的是元素 )
- T - Type ( Java类 )
- K - Key ( 键 )
- V - Value ( 值 )
- N - Number ( 数值类型 )
- ? - 表示不确定的java类型
public static void main(String[] args) {
String[] a = new String[5];
a[2] = "马凯";
a[4] = "义天";
List list = new ArrayList();
list.add("景峰峰");
list.add(1);
list.add(8.8);
list.add('a');
System.out.println(list);
List<String> list2 = new ArrayList<String>();
list2.add("朱斐斐");
List<Integer> list3 = new ArrayList<Integer>();
list3.add(100);
list3.add(200);
System.out.println(list3);
}
public static void main(String[] args) {
Integer[] a = {1,2,3,4,5,6,7,8,9,10};
print(a);
String[] b = {"大哥","二哥","三哥","四哥","五哥","六哥","小弟"};
print(b);
Double[] c = {6.0,6.6,6.66,6.666,6.6666};
print(c);
}
private static <E> void print(E[] e) {
for(E d :e) {
System.out.println(d);
}
}
第九章:Collection接口
前言: Java语言的java.util包中提供了一些集合类,这些集合类又称之为容器 提到容器不难想到数组,集合类与数组最主要的不同之处是,数组的长度是固定的,集合的长度是可变的 数组的访问方式比较单一,插入/删除等操作比较繁琐,而集合的访问方式比较灵活 常用的集合类有List集合,Set集合,Map集合,其中List集合与Set集合继承了Collection接口,各个接口还提供了不同的实现类.
概述:
集合的英文名称是Collection,是用来存放对象的数据结构,而且长度可变,可以存放不同类型的对象,并且还提供了一组操作成批对象的方法.Collection接口层次结构 中的根接口,接口不能直接使用,但是该接口提供了添加元素/删除元素/管理元素的父接口公共方法. 由于List接口与Set接口都继承了Collection接口,因此这些方法对于List集合和Set集合是通用的.
集合的继承结构:
Collection接口 –List 接口 : 数据是有下标的,所以数据是有序的,可以存重复值 –ArrayList子类 –LinkedList子类 –Set 接口 : 数据是没有下标的,所以数据是无序的,不可以存重复的值 –HashSet子类 –Map 接口 : 键值对的方式存数据 –HashMap子类
常用方法
public static void main(String[] args) {
Collection<Integer> c = new ArrayList<Integer>();
c.add(100);
c.add(200);
c.add(300);
c.add(400);
c.add(500);
System.out.println(c);
System.out.println( c.contains(300) );
System.out.println( c.hashCode() );
System.out.println( c.isEmpty() );
System.out.println( c.remove(100) );
System.out.println( c );
System.out.println( c.size() );
System.out.println( c.equals(200) );
Object[] array = c.toArray();
System.out.println(Arrays.toString(array));
Collection<Integer> c2 = new ArrayList<Integer>();
c2.add(2);
c2.add(4);
c2.add(6);
System.out.println(c2);
c.addAll(c2);
System.out.println(c);
System.out.println(c.contains(c2));
System.out.println(c.containsAll(c2));
System.out.println(c.removeAll(c2));
System.out.println(c);
Iterator<Integer> it = c.iterator();
while(it.hasNext()) {
Integer num = it.next();
System.out.println(num);
}
}
List接口
概述:
有序的colletion(也称为序列).此接口的用户可以对列表中的每个元素的插入位置进行精确的控制,用户可以根据元素的整数索引(在列表中的位置)来访问元素,并搜索列表中的元素.
特点:
- 元素都有下标
- 数据是有序的
- 允许存放重复的元素
常用方法:
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("大力娃");
list.add("千顺娃");
list.add("头铁娃");
list.add("喷火娃");
list.add("喷水娃");
list.add("隐身娃");
list.add("小紫娃");
System.out.println(list);
System.out.println("**********我是一个无情的分界线**************");
System.out.println( list.contains("大力娃"));
System.out.println( list.equals("头铁娃"));
System.out.println( list.hashCode());
System.out.println( list.isEmpty());
System.out.println( list.remove("喷水娃"));
System.out.println( list );
System.out.println( list.size());
System.out.println( Arrays.toString(list.toArray()));
list.add("小蝴蝶");
System.out.println(list);
list.add(1,"蝎子精");
System.out.println(list);
System.out.println(list.get(2));
list.add(3,"小蝴蝶");
System.out.println(list);
System.out.println(list.indexOf("小蝴蝶"));
System.out.println(list.lastIndexOf("小蝴蝶"));
System.out.println(list.remove(6));
System.out.println(list.set(0, "妖精蛇"));
System.out.println(list);
List<String> subList = list.subList(2, 6);
System.out.println(subList);
List<String> list2 = new ArrayList<String>();
list2.add("1");
list2.add("2");
list2.add("3");
System.out.println( list.addAll(list2) );
System.out.println( list.addAll(1,list2) );
System.out.println( list );
System.out.println( list.contains(list2) );
System.out.println( list.containsAll(list2) );
System.out.println( list.removeAll(list2) );
System.out.println( list);
}
for(int i = 0 ; i< list.size() ; i++) {
String s = list.get(i);
System.out.println(s);
}
System.out.println("***********我是一个无情的分割线1***************");
for(String s : list) {
System.out.println(s);
}
System.out.println("***********我是一个无情的分割线2***************");
Iterator<String> it = list.iterator();
while(it.hasNext()) {
String s = it.next();
System.out.println(s);
}
System.out.println("***********我是一个无情的分割线3***************");
ListIterator<String> it2 = list.listIterator();
while(it2.hasNext()) {
String s = it2.next();
System.out.println(s);
}
ArrayList
概述:
- 存在java.util包中
- 内部是用数组结构存放数据,封装数组的操作,每个对象都有下标
- 内部数组默认的初始容量是10,如果不够会以1.5倍的容量增长
- 查询快,增删数据效率会低
创建对象: ArrayList() 构造一个初始容量为10的空序列 源码摘抄:int newCapacity = oldCapacity + (oldCapacity >> 1); 解释:数组的新容量 = 旧容量/2的一次方 --相当于原来的1.5倍扩容
ArrayList<Integer> list = new ArrayList();
list.add(100);
list.add(200);
list.add(300);
list.add(400);
list.add(300);
list.add(200);
list.add(0,777);
System.out.println(list);
System.out.println(list.contains(300));
System.out.println(list.get(0));
System.out.println(list.indexOf(200));
System.out.println(list.lastIndexOf(200));
System.out.println(list.isEmpty());
System.out.println(list.remove(1));
System.out.println(list.remove(Integer.valueOf(300)));
System.out.println(list);
System.out.println(list.set(2, 77));
System.out.println(list);
System.out.println(list.size());
System.out.println(Arrays.toString(list.toArray()));
4种迭代方式
System.out.println("方式1");
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i));
}
System.out.println("方式2");
for (Integer i : list) {
System.out.print(i);
}
System.out.println("方式3");
Iterator<Integer> it = list.iterator();
while(it.hasNext()) {
Integer num = it.next();
System.out.print(num);
}
System.out.println("方式4");
ListIterator<Integer> it2 = list.listIterator();
while(it2.hasNext()) {
Integer s2 = it2.next();
System.out.print(s2);
}
ArrayList扩容
ArrayList相当于在没指定initialCapacity时就是会使用延迟分配对象数组空间,当第一次插入元素时才分配10(默认)个对象空间。假如有20个数据需要添加,那么会分别在第一次的时候,将ArrayList的容量变为10;之后扩容会按照1.5倍增长。也就是当添加第11个数据的时候,Arraylist继续扩容变为10*1.5=15;当添加第16个数据时,继续扩容变为15 * 1.5 =22个
ArrayList没有对外暴露其容量个数,查看源码我们可以知道,实际其值存放在elementData对象数组中,那我们只需拿到这个数组的长度,观察其值变化了几次就知道其扩容了多少次。怎么获取呢?只能用反射技术了。
LinkedList
概述:
链表,两端效率高,底层就是链表实现的
总结: ArrayList底层是数组结构,查询快,增删慢,适合查询较多的场景 LinkedList底层是链表结构,查询慢,增删快,适合增删操作较多的场景 注意:LinkedList查询慢是指数据量大时,查询中间要慢,首位操作还是比较快的
创建对象: LinkedList() 构造一个空列表
常用方法
void addFirst(E e) 将指定元素插入此列表的开头
void addLast(E e) 将指定元素添加到此列表的结尾
E getFirst() 返回此列表的第一个元素
E getLast() 返回此列表的最后一个元素
E removeFirst()移除并返回此列表的第一个元素
E removeLast() 移除并返回此列表的最后一个元素
E element() 获取但不移除此列表的头(第一个元素)
boolean offer(E e) 将指定元素添加到此列表的末尾(最后一个元素)
boolean offerFirst(E e) 在此列表的开头插入指定的元素
boolean offerLast(E e) 在此列表末尾插入指定的元素
E peek() 获取但不移除此列表的头(第一个元素)
E peekFirst() 获取但不移除此列表的第一个元素;如果此列表为空,则返回 null
E peekLast() 获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null
E poll()获取并移除此列表的头(第一个元素)
E pollFirst() 获取并移除此列表的第一个元素;如果此列表为空,则返回 null
E pollLast() 获取并移除此列表的最后一个元素;如果此列表为空,则返回 null
LinkedList<String> list = new LinkedList();
list.add("孙悟空");
list.add("猪八戒");
list.add("唐三藏");
list.add("沙师弟");
list.add("白龙马");
System.out.println(list);
list.addFirst("蜘蛛精");
list.addLast("玉兔精");
System.out.println(list);
System.out.println(list.getFirst());
System.out.println(list.getLast());
System.out.println(list.removeFirst());
System.out.println(list);
System.out.println(list.removeLast());
System.out.println(list);
LinkedList<String> list2 = new LinkedList();
list2.add("水浒传");
list2.add("三国演义");
list2.add("西游记");
list2.add("红楼梦");
System.out.println(list2);
System.out.println(list2.element());
System.out.println(list2.peek());
System.out.println(list2.peekFirst());
System.out.println(list2.peekLast());
System.out.println(list2.offer("遮天"));
System.out.println(list2.offerFirst("斗罗大陆"));
System.out.println(list2.offerLast("斗破苍穹"));
System.out.println(list2);
System.out.println(list2.poll());
System.out.println(list2.pollFirst());
System.out.println(list2.pollLast());
System.out.println(list2);
set接口
概述:
-
Set是一个不包含重复数据的Collection
-
Set集合中的数据是无序的(因为Set集合没有下标)
-
Set集合中的元素不可以重复 – 常用来给数据去重
Set集合的特点:
-
数据无序且数据不允许重复
-
HashSet : 底层是哈希表,包装了HashMap,相当于向HashSet中存入数据时,会把数据作为K,存入内部的HashMap中。当然K仍然不许重复。
-
TreeSet : 底层是TreeMap,也是红黑树的形式,便于查找数据
常用方法:
学习Collection接口中的方法即可
Set<String> set = new HashSet<String>();
set.add("牛气冲天");
set.add("牛气冲天");
set.add("牛气冲天");
set.add("虎虎生威");
set.add(null);
System.out.println(set);
System.out.println(set.contains("小兔纸"));
System.out.println(set.equals("牛气冲天"));
System.out.println(set.hashCode());
System.out.println(set.isEmpty());
System.out.println(set.remove("null"));
System.out.println(set.remove(null));
System.out.println(set);
System.out.println(set.size());
Object[] array = set.toArray();
System.out.println(Arrays.toString(array));
Set<String> set2 = new HashSet();
set2.add("小老鼠");
set2.add("小牛犊");
set2.add("小脑斧");
set2.add("小兔纸");
System.out.println(set2);
System.out.println(set.addAll(set2));
System.out.println(set.containsAll(set2));
System.out.println(set.removeAll(set2));
System.out.println(set.containsAll(set2));
System.out.println(set.retainAll(set2));
Iterator<String> it = set2.iterator();
while(it.hasNext()) {
String s = it.next();
System.out.println(s);
}
}
HashSet
概述:
底层是哈希表,包装了HashMap,相当于向HashSet中存入数据时,会把数据作为K存入内部的HashMap中,其中K不允许重复,允许使用null.
HashSet<Integer> set = new HashSet();
set.add(100);
set.add(200);
set.add(300);
set.add(200);
set.add(200);
System.out.println(set);
System.out.println(set.contains(200));
System.out.println(set.isEmpty());
System.out.println(set.remove(100));
System.out.println(set.size());
Iterator<Integer> it = set.iterator();
while(it.hasNext()) {
Integer num = it.next();
System.out.println(num);
}
去重:
//总结:
//1.如果想用set集合给自定义的对象去重,那么需要在自己的类中同时提供重写的hashCode()与equals() //底层源码: if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) //重写hashCode():我们并不想使用自动计算出的哈希值,而是要根据对象的属性值进行计算,如果两个对象的属性值都相同,想生成同一个哈希码 //重写equals():我们想比较的不是对象的地址值(==),而是如果两个对象的属性值都一样,则返回true
Set<Student> set = new HashSet<Student>();
Student s1 = new Student("tony",38,"BeiJing");
Student s2 = new Student("susan",20,"ShangHai");
Student s3 = new Student("Jack",3,"ShenZhen");
Student s4 = new Student("susan",20,"ShangHai");
Student s5 = new Student("Jack",3,"ShenZhen");
System.out.println("s2对象的哈希码:"+s2.hashCode());
System.out.println("s4对象的哈希码:"+s4.hashCode());
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
set.add(s5);
System.out.println(set);
总结: 重复的给set集合添加了属性相同的对象,为什么没有像之前那样去重呢? 翻阅源码,得知:需要保证两个条件: 1.保证对象拥有相同的哈希码值 底层默认使用的是Object提供的hashCode()来计算哈希码值,每次new对象,默认的哈希码值是不同的 解决方案:如果想根据两个对象的属性值来计算哈希值,就需要重写hashCode() 2.保证两个对象的equals()返回true –底层默认使用的是Object提供的逻辑,==比较,也就是说当地址值相同时,才返回true 解决方案:重写equals()
Map接口
概述:
Java.util接口Map<K,V> 类型参数 : K - 表示此映射所维护的键 V – 表示此映射所维护的对应的值 也叫做哈希表、散列表. 常用于键值对结构的数据.其中键不能重复,值可以重复
特点:
- Map可以根据键来提取对应的值
- Map的键不允许重复,如果重复,对应的值会被覆盖
- Map存放的都是无序的数据
- Map的初始容量是16,默认的加载因子是0.75
TIPS:源码摘抄:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
初始容量1<<4,相当于1*(2^4),也就是16
static final float DEFAULT_LOAD_FACTOR = 0.75f;
默认的加载因子是0.75f,也就是存到75%开始扩容,按照2的次幂进行扩容
继承结构
常用方法:
学习Collection接口中的方法即可 void clear() 从此映射中移除所有映射关系(可选操作)。 boolean containsKey(Object key) 如果此映射包含指定键的映射关系,则返回 true。 boolean containsValue(Object value) 如果此映射将一个或多个键映射到指定值,则返回 true。 Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的映射关系的 Set 视图。 boolean equals(Object o) 比较指定的对象与此映射是否相等。 V get(Object key) 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。 int hashCode() 返回此映射的哈希码值。 boolean isEmpty() 如果此映射未包含键-值映射关系,则返回 true。 Set<K> keySet() 返回此映射中包含的键的 Set 视图。 V put(K key, V value) 将指定的值与此映射中的指定键关联(可选操作)。 void putAll(Map<? extends K,? extends V> m)从指定映射中将所有映射关系复制到此映射中(可选操作)。 V remove(Object key) 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。 int size() 返回此映射中的键-值映射关系数。 Collection values() 返回此映射中包含的值的 Collection 视图。
Map<Integer,String> map = new HashMap();
map.put(9527, "白骨精");
map.put(9528, "黑熊精");
map.put(9528, "唐三藏");
map.put(9529, "者行孙");
System.out.println(map);
System.out.println(map.containsKey(9527));
System.out.println(map.containsValue("土地老儿"));
System.out.println(map.equals("者行孙"));
System.out.println(map.get(9529));
System.out.println(map.hashCode());
System.out.println(map.isEmpty());
System.out.println(map.remove(9529));
System.out.println(map.get(9529));
System.out.println(map.size());
Collection<String> values = map.values();
System.out.println(values);
Map集合迭代:
Set<Integer> keySet = map.keySet();
Iterator<Integer> it = keySet.iterator();
while(it.hasNext()) {
Integer key = it.next();
String value = map.get(key);
System.out.println("{" + key + "," + value + "}");
}
Set<Entry<Integer, String>> entrySet = map.entrySet();
Iterator<Entry<Integer, String>> it2 = entrySet.iterator();
while(it2.hasNext()) {
Entry<Integer, String> entry = it2.next();
Integer key = entry.getKey();
String value = entry.getValue();
System.out.println("[" + key + ":" + value + "]");
}
HashMap
前言
HashMap的键要同时重写hashCode()和equlas() hashCode()用来判定二者的hash值是否相同,重写后根据属性生成 equlas()用来判断属性的值是否相同,重写后,根据属性判断 –equlas()判断数据如果相等,hashCode()必须相同 –equlas()判断数据如果不等,hashCode()尽量不同
概述:
HashMap底层是一个Entry[ ]数组,当存放数据时,会根据hash算法来计算数据的存放位置 算法:hash(key)%n , n就是数组的长度,其实也就是集合的容量 当计算的位置没有数据的时候,会直接存放数据 当计算的位置,有数据时,会发生hash冲突/hash碰撞,解决的办法就是采用链表的结构,在对应的数据位置存放链表的头节点,对于这个链表来说,每次新加的节点会从头部位置开始加入,也就是说,数组中的永远是新节点.
HashMap<Integer,String> map = new HashMap();
拓展
加载因子: static final float DEFAULT_LOAD_FACTOR = 0.75f; 前面的讲述已经发现,当你空间只有仅仅为10的时候是很容易造成2个对象的hashcode 所对应的地址是一个位置的情况。这样就造成 2个 对象会形成散列桶(链表)。这时就有一个加载因子的参数,值默认为0.75 ,如果你hashmap的 空间有 100那么当你插入了75个元素的时候 hashmap就需要扩容了,不然的话会形成很长的散列桶结构,对于查询和插入都会增加时间,因为它要一个一个的equals比较。但又不能让加载因子很小,如0.01,这样显然是不合适的,频繁扩容会大大消耗你的内存。这时就存在着一个平衡,jdk中默认是0.75,当然负载因子可以根据自己的实际情况进行调整。
第十章:进程与线程
进程
概念:
进程就是正在运行的程序,它代表了程序所占用的内存区域
特点:
== 独立性== 进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间. == 动态性== 进程与程序的区别在于,程序只是一个静态的指令集合,而进程一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的. 并发性 多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响.
并行和并发
TIPS: HA–高可用 在高并发的环境下,系统如何完成正常功能供给
线程
概念:
线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位. 一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程 我们看到的进程的切换,切换的也是不同进程的主线程 多线程扩展了多进程的概念,使的同一个进程可以同时并发处理多个任务 简而言之,一个程序运行后至少一个进程,一个进程里包含一个线程(单线程)或者多个线程(多线程)
进程与线程的关系
一个操作系统中可以有多个进程,一个进程中可以有多个线程 每个线程有自己独立的内存,每个线程共享一个进程中的内存,每个线程又有自己独立的内存(这个非常重要!!!) 所以想使用线程技术,得先有进程,进程的创建是OS操作系统创建的.我们不能实现,一般都是C或者C++完成
多线程的特性
线程的随机性指的是同一时刻,只有一个程序在执行 我们宏观上觉得这些程序像是同时运行,但是实际上微观时间是因为CPU在高效的切换着,这使得各个程序从表面上看是同时进行的,也就是说,宏观层面上,所有的进程/线程看似同时运行,但是微观层面上,同一时刻,一个CPU只能处理一件事. 时间单位:1/ms甚至更快,切换的速度是纳秒级别的,非常快
CPU分时调度
时间片,即CPU分配给各个线程的一个时间段,称作它的时间片,即该线程被允许运行的时间,如果在时间片用完时线程还在执行,那CPU将被剥夺并分配给另一个线程,将当前线程挂起,如果线程在时间片用完之前阻塞或结束,则CPU当即进行切换,从而避免CPU资源浪费,当再次切换到之前挂起的线程,恢复现场,继续执行。 注意:我们无法控制OS如何选择以及选择哪些线程来执行,OS底层有自己规则: FCFS(First Come First Service 先来先服务算法) SJS(Short Job Service短服务算法)
线程的状态
由于线程状态比较复杂,所以我们由易到难,先学习基础模型,线程的三种状态及其转换,简称”三态模型” l 就绪(可运行)状态:线程已经准备好运行,只要获得CPU,就可立即执行 l 执行(运行)状态:线程已经获得CPU,其程序正在运行的状态 l 阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的暂停状态,即线程执行阻塞。 ? 就绪–>执行:为就绪线程分配CPU即可变为执行状态 ? 执行–>就绪:正在执行的线程由于时间片用完被剥夺CPU暂停执行,就变为就绪状态 ? 执行–>阻塞:由于发生某事件,使正在执行的线程受阻,无法执行,则由执行变为阻塞 (例如线程正在访问临界资源,而资源正在被其他线程访问) 反之,如果获得了之前需要的资源,则由阻塞变为就绪状态,等待分配CPU再次执行 创建状态:线程的创建比较复杂,需要先申请PCB,然后为该线程运行分配必须的资源,并将该线程转为就绪状态插入到就绪队列中 终止状态:等待OS进行善后处理,最后将PCB清零,并将PCB返回给系统
TIPS:PCB(Process Control Block):为了保证参与并发执行的每个线程都能独立运行,OS配置了特有的数据结构PCB来描述线程的基本情况和活动过程,进而控制和管理线程
线程生命周期,主要有五种状态:
- 新建状态(New) : 当线程对象创建后就进入了新建状态.如:Thread t = new MyThread();
- 就绪状态(Runnable):当调用线程对象的start()方法(t.start()😉,线程即为进入就绪状态.
处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()此线程立即就会执行 - 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态
注意:就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态 - 阻塞状态(Blocked):处于运状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行.
- 根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种:
a) 等待阻塞:运行状态中的线程执行wait()方法,本线程进入到等待阻塞状态 b) 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态 c) 其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态.当sleep()状态超时.join()等待线程终止或者超时或者I/O处理完毕时线程重新转入就绪状态 - 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
多线程代码创建方式
继承Thread
概述: Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例 启动线程的唯一方法就是通过Thread类的start()实例方法 start()方法是一native方法,它将通知底层操作系统,.最终由操作系统启动一个新线程,操作系统将执行run() 这种方式实现的多线程很简单,通过自己的类直接extends Thread,并重写run()方法,就可以自动启动新线程并执行自己定义的run()方法 模拟开启多个线程,每个线程调用run()方法. 常用方法: 构造方法:
Thread() 分配新的Thread对象 Thread(String name) 分配新的Thread对象 Thread(Runnable target) 分配新的Thread对象 Thread(Runnable target,String name) 分配新的Thread对象
普通方法:
static Thread currentThread( ) 返回对当前正在执行的线程对象的引用 long getId() 返回该线程的标识 String getName() 返回该线程的名称 void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法 static void sleep(long millions) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) void start() 使该线程开始执行:Java虚拟机调用该线程的run()
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
MyThread t4 = new MyThread();
t1.start();
t2.start();
t3.start();
t4.start();
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i+"="+getName());
}
}
}
实现Runnable接口
概述: 如果自己的类已经extends另一个类,就无法多继承,此时,可以实现一个Runnable接口
常用方法: void run()使用实现接口Runnable的对象创建线程时,启动该线程将导致在独立执行的线程中调用对象的run()方法
public static void main(String[] args) {
MyRunnable target = new MyRunnable();
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
Thread t4 = new Thread(target);
t1.start();
t2.start();
t3.start();
t4.start();
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i+"="+Thread.currentThread().getName());
}
}
}
Callable接口
概述: Callable接口是一种能够返回计算结果并且可以抛出异常的任务。 Callable接口的实现类需要定义一个无参数的方法:call()。 执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
FutureTask<Integer> result = new FutureTask<>(td);
new Thread(result).start();
try {
Integer sum = result.get();
System.out.println(sum);
System.out.println("------------------------------------");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
class ThreadDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
return sum;
}
}
线程创建的其他方式
ExecutorService/Executors ExecutorService:用来存储线程的池子,把新建线程/启动线程/关闭线程的任务都交给池来管理
execute(Runnable任务对象) 把任务丢到线程池
Executors 辅助创建线程池的工具类
newFixedThreadPool(int nThreads) 最多n个线程的线程池 newCachedThreadPool() 足够多的线程,使任务不必等待 newSingleThreadExecutor() 只有一个线程的线程池
public static void main(String[] args) {
TicketR3 target = new TicketR3();
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
pool.execute(target);
}
}
class TicketR3 implements Runnable {
int tickets = 100;
Object o = new Object();
@Override
public void run() {
while (true) {
synchronized (o) {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "=" + tickets--);
}
if (tickets <= 0) break;
}
}
}
}
票超卖
每次创建线程对象,都会生成一个tickets变量值是100,创建4次对象就生成了400张票了。不符合需求,怎么解决呢?能不能把tickets变量在每个对象间共享,就保证多少个对象都是卖这100张票。 解决方案: 用静态修饰 产生超卖,0 张 、-1张、-2张。 产生重卖,同一张票卖给多人。 多线程安全问题是如何出现的?常见情况是由于线程的随机性+访问延迟。 以后如何判断程序有没有线程安全问题?
同步锁
前言
经过前面多线程编程的学习,我们遇到了线程安全的相关问题,比如多线程售票情景下的超卖/重卖现象 在多线程程序中 + 有共享数据 + 多条语句操作共享数据
多线程的场景和共享数据的条件是改变不了的(就像4个窗口一起卖100张票,这个是业务) 所以思路可以从第3点"多条语句操作共享数据"入手,既然是在这多条语句操作数据过程中出现了问题 那我们可以把有可能出现问题的代码都包裹起来,一次只让一个线程来执行
同步与异步
那怎么"把有可能出现问题的代码都包裹起来"呢?我们可以使用synchronized关键字来实现同步效果 也就是说,当多个对象操作共享数据时,可以使用同步锁解决线程安全问题,被锁住的代码就是同步的
接下来介绍下同步与异步的概念: 同步:体现了排队的效果,同一时刻只能有一个线程独占资源,其他没有权利的线程排队。 坏处就是效率会降低,不过保证了安全。 异步:体现了多线程抢占资源的效果,线程间互相不等待,互相抢占资源。 坏处就是有安全隐患,效率要高一些。
synchronized同步关键字
1.写法:
synchronized (锁对象){ 需要同步的代码(也就是可能出现问题的操作共享数据的多条语句); }
2.同步效果的使用有两个前提:
前提1:同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题) 前提2:多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)
3.特点 1)synchronized同步关键字可以用来修饰代码块,称为同步代码块,使用的锁对象类型任意,但注意:必须唯一! 2)synchronized同步关键字可以用来修饰方法,称为同步方法 3)同步的缺点是会降低程序的执行效率,但我们为了保证线程的安全,有些性能是必须要牺牲的 4)但是为了性能,加锁的范围需要控制好,比如我们不需要给整个商场加锁,试衣间加锁就可以了
为什么同步代码块的锁对象可以是任意的同一个对象,但是同步方法使用的是this呢? 因为同步代码块可以保证同一个时刻只有一个线程进入 但同步方法不可以保证同一时刻只能有一个线程调用,所以使用本类代指对象this来确保同步
拓展:线程锁
悲观锁和乐观锁
悲观锁:像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态. 悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。
乐观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态. 乐观锁认为竞争不总是会发生,因此它不需要持有锁,将”比较-替换”这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
两种常见的锁
synchronized 互斥锁(悲观锁,有罪假设) 采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。 每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。
ReentrantLock 排他锁(悲观锁,有罪假设) ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。
ReentrantReadWriteLock 读写锁(乐观锁,无罪假设) 因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。 读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。 读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。
class SaleTicketsV3 implements Runnable{
static int tickets = 100;
static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
@Override
public void run() {
while(true) {
lock.writeLock().lock();
try {
if(tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "=" + tickets--);
}
if(tickets <= 0) break;
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.writeLock().unlock();
}
}
}
}
两种方式的区别
需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁会自动释放,而是用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内! 与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)从理论上讲,与互斥锁定相比,使用读-写锁允许的并发性增强将带来更大的性能提高。
第十一章:注解与枚举类
枚举类
枚举类的实现
- JDK1.5之前需要自定义枚举类
- JDK 1.5 新增的 enum 关键字用于定义枚举类
- 若枚举只有一个对象, 则可以作为一种单例模式的实现方式。
枚举类的属性
- 枚举类对象的属性不应允许被改动, 所以应该使用 private final 修饰
- 枚举类的使用 private final 修饰的属性应该在构造器中为其赋值
- 若枚举类显式的定义了带参数的构造器, 则在列出枚举值时也必须对应的传入参数
自定义枚举类
- 使用 enum 定义的枚举类默认继承了 java.lang.Enum类,因此不能再继承其他类
- 枚举类的构造器只能使用 private 权限修饰符
- 枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾)。列出的实例系统会自动添加 public static final 修饰
- 必须在枚举类的第一行声明枚举类对象
JDK 1.5 中可以在 switch 表达式中使用Enum定义的枚举类的对象 作为表达式, case 子句可以直接使用枚举值的名字, 无需添加枚举 类作为限定。
public enum SeasonEnum {
SPRING("春天","春风又绿江南岸"),
SUMMER("夏天","映日荷花别样红"),
AUTUMN("秋天","秋水共长天一色"),
WINTER("冬天","窗含西岭千秋雪");
private final String seasonName;
private final String seasonDesc;
private SeasonEnum(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc; }
public String getSeasonName() {
return seasonName; }
public String getSeasonDesc() {
return seasonDesc; } }
Enum类的主要方法
1. values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的 枚举值。 2. valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符 串必须是枚举类对象的“名字”。如不是,会有运行时异常: IllegalArgumentException。 3. toString():返回当前枚举类对象常量的名称
实现接口的枚举类
- 和普通 Java 类一样,枚举类可以实现一个或多个接口
- 若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只
要统一实现该方法即可。 - 若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式,
则可以让每个枚举值分别来实现该方法
interface Info {
void describe();
}
public enum EnumImplement implements Info{
SPRING("春天"){
@Override
public void describe() {
System.out.println("春江水阿暖鸭先知");
}
},
SUMMER("夏天"){
@Override
public void describe() {
System.out.println("我爱山中夏,空冥花雨下。");
}
},
AUTUMN("秋天"){
@Override
public void describe() {
System.out.println("停车坐爱枫林晚,霜叶红于二月花。");
}
},
WINTER("冬天"){
@Override
public void describe() {
System.out.println("北国风光,千里冰封,万里雪飘。");
}
};
final String name;
EnumImplement(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
注解(Annotation)
概述:
-
从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是 Annotation(注解) -
Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加 载, 运行时被读取, 并执行相应的处理。通过使用 Annotation, 程序员 可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。代 码分析工具、开发工具和部署工具可以通过这些补充信息进行验证 或者进行部署。 -
Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方 法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在 Annotation 的 “name=value” 对中。 -
在JavaSE中,注解的使用目的比较简单,例如标记过时的功能, 忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如 用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗 代码和XML配置等。 -
未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以 上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的 Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上 可以说:框架 = 注解 + 反射 + 设计模式。
示例:
使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成一个修饰符使用。用于修饰它支持的程序元素
示例一:生成文档相关的注解
@author 标明开发该类模块的作者,多个作者之间使用,分割 @version 标明该类模块的版本 @see 参考转向,也就是相关主题 @since 从哪个版本开始增加的 @param 对方法中某参数的说明,如果没有参数就不能写 @return 对方法返回值的说明,如果方法的返回值类型是void就不能写 @exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写 其中 @param @return 和 @exception 这三个标记都是只用于方法的。 @param的格式要求:@param 形参名 形参类型 形参说明 @return 的格式要求:@return 返回值类型 返回值说明 @exception的格式要求:@exception 异常类型 异常说明 @param和@exception可以并列多个
public class JavadocTest {
public static void main(String[] args) {
}
public static double getArea(double radius){
return Math.PI * radius * radius; } }
示例二:在编译时进行格式检查(JDK内置的三个基本注解)
1.@Override: 限定重写父类方法, 该注解只能用于方法 2.@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为 所修饰的结构危险或存在更好的选择 3.@SuppressWarnings: 抑制编译器警告
public class AnnotationTest{
public static void main(String[] args) {
@SuppressWarnings("unused")
int a = 10;
}
@Deprecated
public void print(){
System.out.println("过时的方法");
}
@Override
public String toString() {
return "重写的toString方法()"; } }
示例三:跟踪代码依赖性,实现替代配置文件功能
Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException { }
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
doGet(request, response);
} }
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
spring框架中关于“事务”的管理
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,readOnly=false,timeout=3)
public void buyBook(String username, String isbn) {
int price = bookShopDao.findBookPriceByIsbn(isbn);
bookShopDao.updateBookStock(isbn);
bookShopDao.updateUserAccount(username, price); }
<tx:advice transaction-manager="dataSourceTransactionManager" id="txAdvice">
<tx:attributes>
<tx:method name="buyBook" propagation="REQUIRES_NEW"
isolation="READ_COMMITTED" read-only="false" timeout="3" />
</tx:attributes>
</tx:advice>
自定义 Annotation
●定义新的 Annotation 类型使用 @interface 关键字 ●自定义注解自动继承了java.lang.annotation.Annotation接口 ●Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其 方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能 是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、 以上所有类型的数组。 ●可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始 值可使用 default 关键字 ●如果只有一个参数成员,建议使用参数名为value ●如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认 值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value, 可以省略“value=” ●没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数 据 Annotation 注意:自定义注解必须配上注解的信息处理流程才有意义。
@MyAnnotation(value="企鹅企鹅")
public class MyAnnotationTest {
public static void main(String[] args) {
Class clazz = MyAnnotationTest.class;
Annotation a = clazz.getAnnotation(MyAnnotation.class);
MyAnnotation m = (MyAnnotation) a;
String info = m.value();
System.out.println(info);
} }
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation{
String value() default "啦啦啦"; }
JDK 中的元注解
●JDK 的元 Annotation 用于修饰其他 Annotation 定义 ●JDK5.0提供了4个标准的meta-annotation类型,分别是: ●Retention ●Target ●Documented ●Inherited 元数据的理解: String name = “啦啦啦”;
● @Retention : 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命 周期, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值: ●RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的 注释 ●RetentionPolicy.CLASS:在class文件中有效(即class保留) , 当运行 Java 程序时, JVM 不会保留注解。 这是默认值 ●RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java 程序时, JVM 会 保留注释。程序可以通过反射获取该注释。 ●==@Target==: 用于修饰 Annotation 定义, 用于指定被修饰的 Annotation 能用于 修饰哪些程序元素。 @Target 也包含一个名为 value 的成员变量。 ●@Documented: 用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。 ●定义为Documented的注解必须设置Retention值为RUNTIME。 ●@Inherited: 被它修饰的 Annotation 将具有继承性。如果某个类使用了被 @Inherited 修饰的 Annotation, 则其子类将自动具有该注解。 ●比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以 继承父类类级别的注解 ●实际应用中,使用较少
JDK8中注解的新特性
Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。此外, 反射也得到了加强,在Java8中能够得到方法参数的名称。这会简化标注在方法 参数上的注解。 ●JDK1.8之后,关于元注解@Target的参数类型ElementType枚举值多了两个: TYPE_PARAMETER,TYPE_USE。 ●在Java 8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用 在任何地方。 ●ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语 句中(如:泛型声明)。 ●ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
public class TestTypeDefine<@TypeDefine() U> {
private U u;
public <@TypeDefine() T> void test(T t){
} }
@Target({ElementType.TYPE_PARAMETER})
@interface TypeDefine{ }
第十二章: 反射
Java反射机制概述
●Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期 借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内 部属性及方法。 ●加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个 类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可 以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看 到类的结构,所以,我们形象的称之为:反射。
动态语言 vs 静态语言
1、动态语言 是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以 被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运 行时代码可以根据某些条件改变自身结构。 主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。 2、静态语言 与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、 C++。
Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动 态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。 Java的动态性让编程的时候更加灵活!
Java反射机制提供的功能
●在运行时判断任意一个对象所属的类 ●在运行时构造任意一个类的对象 ●在运行时判断任意一个类所具有的成员变量和方法 ●在运行时获取泛型信息 ●在运行时调用任意一个对象的成员变量和方法 ●在运行时处理注解 ●生成动态代理
理解Class类并获取Class实例
在Object类中定义了以下的方法,此方法 将被所有子类继承: ● public final Class getClass() 以上的方法返回值的类型是一个Class类, 此类是Java反射的源头,实际上所谓反射 从程序的运行结果来看也很好理解,即: 可以通过对象反射求出类的名称
Class 类
对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接 口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含 了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。 ● Class本身也是一个类 ● Class 对象只能由系统建立对象 ● 一个加载的类在 JVM 中只会有一个Class实例 ● 一个Class对象对应的是一个加载到JVM中的一个.class文件 ● 每个类的实例都会记得自己是由哪个 Class 实例所生成 ● 通过Class可以完整地得到一个类中的所有被加载的结构 ● Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class对象
Class类的常用方法
方法名 | 功能说明 |
---|
static Class forName(String name) | 返回指定类名 name 的 Class 对象 | Object newInstance() | 调用缺省构造函数,返回该Class对象的一个实例 | getName() | 返回此Class对象所表示的实体(类、接口、数组类、基本类型或void)名称 | Class getSuperClass() | 返回当前Class对象的父类的Class对象 | Class [] getInterfaces() | 获取当前Class对象的接口 | ClassLoader getClassLoader() | 返回该类的类加载器 | Class getSuperclass() | 返回表示此Class所表示的实体的超类的Class | Constructor[] getConstructors() | 返回一个包含某些Constructor对象的数组 | Field[] getDeclaredFields() | 返回Field对象的一个数组 | Method getMethod(String name,Class … paramTypes) | 返回一个Method对象,此对象的形参类型为paramType |
应用举例
? String str = “test4.Person”; ? Class clazz = Class.forName(str); ? Object obj = clazz.newInstance(); ? Field field = clazz.getField(“name”); ? field.set(obj, “Peter”); ? Object name = field.get(obj); ? System.out.println(name);
获取Class类的实例(四种方法)
1)前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠, 程序性能最高 实例:Class clazz = String.class; 2)前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象 实例:Class clazz = “www.xx.com”.getClass(); 3)前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方 法forName()获取,可能抛出ClassNotFoundException 实例:Class clazz = Class.forName(“java.lang.String”); 4)其他方式(不做要求) ClassLoader cl = this.getClass().getClassLoader(); Class clazz4 = cl.loadClass(“类的全类名”);
哪些类型可以有Class对象?
(1)class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类 (2)interface:接口 (3)[ ]:数组 (4)enum:枚举 (5)annotation:注解@interface (6)primitive type:基本数据类型 (7)void
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
System.out.println(c10 == c11);
类的加载与ClassLoader的理解
类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化 ●加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时 数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问 入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的 过程需要类加载器参与。 ●链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。 ●验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题 ●准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存 都将在方法区中进行分配。 ●解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。 ●初始化: ●执行类构造器<clinit>()方法的过程。类构造器()方法是由编译期自动收集类中 所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信 息的,不是构造该类对象的构造器)。 ●当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。 ●虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
public class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(A.m);
}
}
class A {
static {
m = 300;
}
static int m = 100;
}
产生,类似于
什么时候会发生类初始化?
● 类的主动引用(一定会发生类的初始化) ●当虚拟机启动,先初始化main方法所在的类 ? new一个类的对象 ●调用类的静态成员(除了final常量)和静态方法 ●使用java.lang.reflect包的方法对类进行反射调用 ●当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类 ●类的被动引用(不会发生类的初始化) ●当访问一个静态域时,只有真正声明这个域的类才会被初始化 ●当通过子类引用父类的静态变量,不会导致子类初始化 ●通过数组定义类引用,不会触发此类的初始化 ●引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常 量池中了)
class Father {
static int b = 2;
static {
System.out.println("父类被加载");
}
}
class A extends Father {
static {
System.out.println("子类被加载");
m = 300;
}
static int m = 100;
static final int M = 1;
}
public class ClassLoadingTest {
public static void main(String[] args) {
A[] array = new A[5];
初始化
Father
Father的初始化
}
static {
System.out.println("main所在的类");
} }
类加载器的作用: ● 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方 法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为 方法区中类数据的访问入口。 ● 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器 中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
了解:ClassLoader
类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类型的类的加载器。
? //1.获取一个系统类加载器 ? ClassLoader classloader = ClassLoader.getSystemClassLoader(); ? System.out.println(classloader); ? //2.获取系统类加载器的父类加载器,即扩展类加载器 ? classloader = classloader.getParent(); ? System.out.println(classloader); ? //3.获取扩展类加载器的父类加载器,即引导类加载器 ? classloader = classloader.getParent(); ? System.out.println(classloader); ? //4.测试当前类由哪个类加载器进行加载 ? classloader = Class.forName(“exer2.ClassloaderDemo”).getClassLoader(); ? System.out.println(classloader); ? //5.测试JDK提供的Object类由哪个类加载器加载 ? classloader = ? Class.forName(“java.lang.Object”).getClassLoader(); ? System.out.println(classloader); ? //*6.关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路 径下的指定文件的输入流 ? InputStream in = null; ? in = this.getClass().getClassLoader().getResourceAsStream(“exer2\test.properties”); ? System.out.println(in);
创建运行时类的对象
有了Class对象,能做什么?
创建类的对象:调用Class对象的newInstance()方法 要 求: 1)类必须有一个无参数的构造器。 2)类的构造器的访问权限需要足够。 难道没有无参的构造器就不能创建对象了吗? 不是!只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。 步骤如下: 1)通过Class类的==getDeclaredConstructor(Class … parameterTypes)==取得本类的指定形参类 型的构造器 2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。 3)通过Constructor实例化对象。
String name = “atguigu.java.Person";
Class clazz = null;
clazz = Class.forName(name);
Constructor con = clazz.getConstructor(String.class,Integer.class);
Person p2 = (Person) con.newInstance("Peter",20);
System.out.println(p2);
获取运行时类的完整结构
使用反射可以取得:
- 实现的全部接口
● public Class<?>[] getInterfaces() 确定此对象所表示的类或接口实现的接口 - 所继承的父类
●public Class<? Super T> getSuperclass()返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的Class - 全部的构造器
●public Constructor<T>[] getConstructors() 返回此 Class 对象所表示的类的所有public构造方法。 ●public Constructor<T>[] getDeclaredConstructors() 返回此 Class 对象表示的类声明的所有构造方法。 ●Constructor类中: ●取得修饰符: public int getModifiers(); ●取得方法名称: public String getName(); ●取得参数的类型:public Class<?>[] getParameterTypes(); - 全部的方法
●public Method[] getDeclaredMethods() 返回此Class对象所表示的类或接口的全部方法 ==●public Method[] getMethods() == 返回此Class对象所表示的类或接口的public的方法 ●Method类中: ●public Class<?> getReturnType()取得全部的返回值 ●public Class<?>[] getParameterTypes()取得全部的参数 ●public int getModifiers()取得修饰符 ●public Class<?>[] getExceptionTypes()取得异常信息 - 全部的Field
●public Field[] getFields() 返回此Class对象所表示的类或接口的public的Field。 ==●public Field[] getDeclaredFields() == 返回此Class对象所表示的类或接口的全部Field。 ●Field方法中: ●public int getModifiers() 以整数形式返回此Field的修饰符 ●public Class<?> getType() 得到Field的属性类型 ●public String getName() 返回Field的名称。 - Annotation相关
●get Annotation(Class<T> annotationClass) ●getDeclaredAnnotations() - 泛型相关
获取父类泛型类型:Type getGenericSuperclass() 泛型类型:ParameterizedType 获取实际的泛型类型参数数组:getActualTypeArguments() - 类所在的包 Package getPackage()
调用运行时类的指定结构
调用指定方法
通过反射,调用类中的方法,通过Method类完成。步骤: 1.通过Class类的 getMethod(String name,Class…parameterTypes) 方法取得 一个Method对象,并设置此方法操作时所需要的参数类型。 2.之后使用 Object invoke(Object obj, Object[] args) 进行调用,并向方法中 传递要设置的obj对象的参数信息。 Object invoke(Object obj, Object … args) 说明: 1.Object 对应原方法的返回值,若原方法无返回值,此时返回null 2.若原方法若为静态方法,此时形参Object obj可为null 3.若原方法形参列表为空,则Object[] args为null 4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用 方法对象的setAccessible(true)方法,将可访问private的方法。
调用指定属性
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和 get()方法就可以完成设置和取得属性内容的操作。 ● public Field getField(String name) 返回此Class对象表示的类或接口的指定的public的Field。 ● public Field getDeclaredField(String name) 返回此Class对象表示的类或接口的指定的Field ●在Field中: ●public Object get(Object obj) 取得指定对象obj上此Field的属性内容 ●public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容
关于setAccessible方法的使用
●Method和Field、Constructor对象都有setAccessible()方法。 ● setAccessible启动和禁用访问安全检查的开关。 ● 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。 ● 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。 ● 使得原本无法访问的私有成员也可以访问 ● 参数值为false则指示反射的对象应该实施Java语言访问检查。
反射的应用:动态代理
Proxy :
专门完成代理的操作类,是所有动态代理类的父类。通过此类为一 个或多个接口动态地生成实现类。 ●提供用于创建动态代理类和动态代理对象的静态方法
动态代理步骤
1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法,以完成代理的具体操作。 2.创建被代理的类以及接口 3.通过Proxy的静态方法 newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 创建一个Subject接口代理 4.通过 Subject代理调用RealSubject实现类的方法
动态代理与AOP(Aspect Orient Programming)
前面介绍的Proxy和InvocationHandler,很难看出这种动态代理的优势,下面介绍一种更实用的动态代理机制 ● 使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有 太大的意义。通常都是为指定的目标对象生成动态代理 ● 这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理 包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异: AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理
第十三章: 网络编程
网络编程概述:
●Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序。 ●Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。 并且 Java 实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境。
网络基础
●计算机网络: 把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规 模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、 共享硬件、软件、数据信息等资源。 ●网络编程的目的: 直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。 ●网络编程中有两个主要的问题: ●如何准确地定位网络上一台或多台主机;定位主机上的特定的应用 ●找到主机后如何可靠高效地进行数据传输
网络通信要素概述
如何实现网络中的主机互相通信
●通信双方地址 ●IP ●端口号 ●一定的规则(即:网络通信协议。有两套参考模型) ●OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广 ●TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。
网络通信协议
通信要素
IP和端口号
-
IP 地址:InetAddress 1)唯一的标识 Internet 上的计算机(通信实体) 2)本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost 3)IP地址分类方式1:IPV4 和 IPV6 IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已 经用尽。以点分十进制表示,如192.168.0.1 IPV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示, 数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984 4) IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。192.168. 开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机 构内部使用 5)特点:不易记忆 -
端口号标识正在计算机上运行的进程(程序) 1) 不同的进程有不同的端口号 2)被规定为一个 16 位的整数 0~65535。 3)端口分类: ① 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23) ② 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占 用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。 ③ 动态/私有端口:49152~65535。 -
端口号与IP地址的组合得出一个网络套接字:Socket
InetAddress类
- Internet上的主机有两种方式表示地址:
① 域名(hostName):www.baidu.com ② IP 地址(hostAddress):202.108.35.210 - InetAddress类主要表示IP地址,两个子类:Inet4Address、Inet6Address。
- InetAddress 类 对 象 含 有 一 个 Internet 主 机 地 址 的 域 名 和 IP 地 址 :
www.baidu.com 和 202.108.35.210。 - 域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)
负责将域名转化成IP地址,这样才能和主机建立连接。== -------域名解析== - InetAddress类没有提供公共的构造器,而是提供了如下几个静态方法来获取InetAddress实例
public static InetAddress getLocalHost()
public static InetAddress getByName(String host)
- InetAddress提供了如下几个常用的方法
public String getHostAddress()
public String getHostName()
public boolean isReachable(int timeout)
网络协议
- 网络通信协议:
计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。 - 问题:网络协议太复杂
计算机网络通信涉及内容很多,比如指定源地址和目标地址,加密解密,压缩 解压缩,差错控制,流量控制,路由控制,如何实现如此复杂的网络协议呢? - 通信协议分层的思想
在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常 用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与 再下一层不发生关系。各层互不影响,利于系统的开发和扩展。
TCP/IP协议簇
- 传输层协议中有两个非常重要的协议:
传输控制协议TCP(Transmission Control Protocol) 用户数据报协议UDP(User Datagram Protocol) - TCP/IP 以其两个主要协议:传输控制协议(TCP)和网络互联协议(IP) 而得
名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。 - IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信
- TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即
物理链路层、IP层、传输层和应用层。
TCP 和 UDP介绍
TCP协议:
1.使用TCP协议前,须先建立TCP连接,形成传输数据通道 2. 传输前,采用“三次握手”方式,点对点通信,是可靠的 3. TCP协议进行通信的两个应用进程:客户端、服务端。 4. 在连接中可进行大数据量的传输 5. 传输完毕,需释放已建立的连接,效率低
UDP协议:
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据报的大小限制在64K内
- 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
- 可以广播发送
- 发送数据结束时无需释放资源,开销小,速度快
Socket
- 利用套接字(Socket)开发网络应用程序早已被广泛的采用,以至于成为事实
上的标准。 2. 网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标 识符套接字。 - 通信的两端都要有Socket,是两台机器间通信的端点。
- 网络通信其实就是Socket间的通信。
- Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
- 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
- Socket分类:
① 流套接字(stream socket):使用TCP提供可依赖的字节流服务 ② 数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务
Socket类的常用构造器:
- public Socket(InetAddress address,int port)创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
- public Socket(String host,int port)创建一个流套接字并将其连接到指定主机上的指定端口号。
Socket类的常用方法:
- public InputStream getInputStream()返回此套接字的输入流。可以用于接收网络消息
- public OutputStream getOutputStream()返回此套接字的输出流。可以用于发送网络消息
- public InetAddress getInetAddress()此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。
- public InetAddress getLocalAddress()获取套接字绑定的本地地址。 即本端的IP地址
- public int getPort()此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0。
- public int getLocalPort()返回此套接字绑定到的本地端口。 如果尚未绑定套接字,则返回 -1。即本端的
端口号。 - public void close()关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接
或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和 OutputStream。 - public void shutdownInput()如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将
返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。 - public void shutdownOutput()禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发
送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流, 则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。
TCP网络编程
Java语言的基于套接字编程分为服务端编程和客户端编程,其通信模 型如图所示:
基于Socket的TCP编程
客户端Socket的工作过程包含以下四个基本的步骤:
- 创建 Socket:根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器端
响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。 - 打开连接到 Socket 的输入/出流: 使用 getInputStream()方法获得输入流,使用
getOutputStream()方法获得输出流,进行数据传输 - 按照一定的协议对 Socket 进行读/写操作:通过输入流读取服务器放入线路的信息
(但不能读取自己放入线路的信息),通过输出流将信息写入线程。 - 关闭 Socket:断开客户端到服务器的连接,释放线路
客户端创建Socket对象
- 客户端程序可以使用Socket类创建对象,创建的同时会自动向服务器方发起连
接。Socket的构造器是: ①Socket(String host,int port)throws UnknownHostException,IOException:向服务器(域名是 host。端口号为port)发起TCP连接,若成功,则创建Socket对象,否则抛出异常。 ②Socket(InetAddress address,int port)throws IOException:根据InetAddress对象所表示的 IP地址以及端口号port发起连接。 - 客户端建立socketAtClient对象的过程就是向服务器发出套接字连接请求
Socket s = new Socket(“192.168.40.165”,9999);
OutputStream out = s.getOutputStream();
out.write(" hello".getBytes());
s.close();
服务器程序的工作过程包含以下四个基本的步骤:
- 调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口
上。用于监听客户端的请求。 - 调用 accept():监听连接请求,如果客户端请求连接,则接受连接,返回通信
套接字对象。 - 调用 该Socket类对象的 getOutputStream() 和 getInputStream ():获取输出
流和输入流,开始网络数据的发送和接收。 - 关闭ServerSocket和Socket对象:客户端访问结束,关闭通信套接字
服务器建立 ServerSocket 对象
- ServerSocket 对象负责等待客户端请求建立套接字连接,类似邮局某个窗口
中的业务员。也就是说,服务器必须事先建立一个等待客户请求建立套接字 连接的ServerSocket对象。 - 所谓“接收”客户的套接字请求,就是accept()方法会返回一个 Socket 对象
ServerSocket ss = new ServerSocket(9999);
Socket s = ss.accept ();
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int num = in.read(buf);
String str = new String(buf,0,num);
System.out.println(s.getInetAddress().toString()+”:”+str);
s.close();
ss.close();
UDP网络编程
- 类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议网络程序。
- UDP数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证
UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。 - DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP
地址和端口号以及接收端的IP地址和端口号。 - UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和
接收方的连接。如同发快递包裹一样
DatagramSocket 类的常用方法
- public DatagramSocket(int port)创建数据报套接字并将其绑定到本地主机上的指定端口。套接字将被
绑定到通配符地址,IP 地址由内核来选择。 - public DatagramSocket(int port,InetAddress laddr)创建数据报套接字,将其绑定到指定的本地地址。
本地端口必须在 0 到 65535 之间(包括两者)。如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地 址,IP 地址由内核选择。 - public void close()关闭此数据报套接字。
- public void send(DatagramPacket p)从此套接字发送数据报包。DatagramPacket 包含的信息指示:将
要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。 - public void receive(DatagramPacket p)从此套接字接收数据报包。当此方法返回时,DatagramPacket
的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法 在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的 长度长,该信息将被截短。 - public InetAddress getLocalAddress()获取套接字绑定的本地地址。
- public int getLocalPort()返回此套接字绑定的本地主机上的端口号。
- public InetAddress getInetAddress()返回此套接字连接的地址。如果套接字未连接,则返回 null。
- public int getPort()返回此套接字的端口。如果套接字未连接,则返回 -1。
DatagramPacket类的常用方法
- public DatagramPacket(byte[] buf,int length)构造 DatagramPacket,用来接收长
度为 length 的数据包。 length 参数必须小于等于 buf.length。 - public DatagramPacket(byte[] buf,int length,InetAddress address,int port)构造数
据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。length 参数必须小于等于 buf.length。 - public InetAddress getAddress()返回某台机器的 IP 地址,此数据报将要发往该
机器或者是从该机器接收到的。 - public int getPort()返回某台远程主机的端口号,此数据报将要发往该主机或
者是从该主机接收到的。 - public byte[] getData()返回数据缓冲区。接收到的或将要发送的数据从缓冲区
中的偏移量 offset 处开始,持续 length 长度。 - public int getLength()返回将要发送或接收到的数据的长度。
UDP网络通信流程:
- DatagramSocket与DatagramPacket
- 建立发送端,接收端
- 建立数据包
- 调用Socket的发送、接收方法
- 关闭Socket
发送端与接收端是两个独立的运行程序
发送端:
DatagramSocket ds = null;
try {
ds = new DatagramSocket();
byte[] by = "hello,baidu.com".getBytes();
DatagramPacket dp = new DatagramPacket(by, 0, by.length,
InetAddress.getByName("127.0.0.1"), 10000);
ds.send(dp);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ds != null)
ds.close();
}
接收端 在接收端,要指定监听的端口。
DatagramSocket ds = null;
try {
ds = new DatagramSocket(10000);
byte[] by = new byte[1024];
DatagramPacket dp = new DatagramPacket(by, by.length);
ds.receive(dp);
String str = new String(dp.getData(), 0, dp.getLength());
System.out.println(str + "--" + dp.getAddress());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ds != null)
ds.close();
}
URL编程
URL类
- == URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一
资源==的地址。 - 它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate
这个资源。 - 通过 URL 我们可以访问 Internet 上的各种网络资源,比如最常见的 www,ftp
站点。浏览器通过解析给定的 URL 可以在网络上查找相应的文件或其他资源。 - URL的基本结构由5部分组成:
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表 ①例如: http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123 ②#片段名:即锚点,例如看小说,直接定位到章节 ③参数列表格式:参数名=参数值&参数名=参数值…
URL类构造器
- 为了表示URL,java.net 中实现了类 URL。我们可以通过下面的构造器来初
始化一个 URL 对象: ①public URL (String spec):通过一个表示URL地址的字符串可以构造一个URL对象。例 如:URL url = new URL (“http://www. baidu.com/”); ②public URL(URL context, String spec):通过基 URL 和相对 URL 构造一个 URL 对象。 例如:URL downloadUrl = new URL(url, “download.html") ③public URL(String protocol, String host, String file); 例如:new URL(“http”, “www.atguigu.com”, “download. html"); ④public URL(String protocol, String host, int port, String file); 例如: URL gamelan = new URL(“http”, “www.baidu.com”, 80, “download.html"); - URL类的构造器都声明抛出非运行时异常,必须要对这一异常进行处理,通
常是用 try-catch 语句进行捕获
URL类常用方法
一个URL对象生成后,其属性是不能被改变的,但可以通过它给定的 方法来获取这些属性:
- public String getProtocol( ) 获取该URL的协议名
- public String getHost( ) 获取该URL的主机名
- public String getPort( ) 获取该URL的端口号
- public String getPath( ) 获取该URL的文件路径
- public String getFile( ) 获取该URL的文件名
- public String getQuery( ) 获取该URL的查询名
URL url = new URL("http://localhost:8080/examples/myTest.txt");
System.out.println("getProtocol() :"+url.getProtocol());
System.out.println("getHost() :"+url.getHost());
System.out.println("getPort() :"+url.getPort());
System.out.println("getPath() :"+url.getPath());
System.out.println("getFile() :"+url.getFile());
System.out.println("getQuery() :"+url.getQuery());
针对HTTP协议的URLConnection类
- URL的方法 openStream():能从网络上读取数据
- 若希望输出数据,例如向服务器端的 CGI (公共网关接口-Common Gateway
Interface-的简称,是用户浏览器和服务器端的应用程序进行连接的接口)程序发送一 些数据,则必须先与URL建立连接,然后才能对其进行读写,此时需要使用 URLConnection 。 - URLConnection:表示到URL所引用的远程对象的连接。当与一个URL建立连接时,
首先要在一个 URL 对象上通过方法 ==openConnection() ==生成对应的URLConnection 对象。如果连接过程失败,将产生IOException.
URL netchinaren = new URL ("http://www.baidu.com/index.shtml");
URLConnectonn u = netchinaren.openConnection( );
- 通过URLConnection对象获取的输入流和输出流,即可以与现有的CGI
程序进行交互
public Object getContent( ) throws IOException
public int getContentLength( )
public String getContentType( )
public long getDate( )
public long getLastModified( )
public InputStream getInputStream( )throws IOException
public OutputSteram getOutputStream( )throws IOException
URI、URL和URN的区别
URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个 资源。 而==URL是uniform resource locator,统一资源定位符,==它是一种具体 的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。 而URN,uniform resource name,统一资源命名,是通过名字来标识资源, 比如mailto:java-net@java.sun.com。也就是说,URI是以一种抽象的,高层 次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL 和URN都是一种URI。
在Java的URI中,一个URI实例可以代表绝对的,也可以是相对的,只要它符 合URI的语法规则。而URL类则不仅符合语义,还包含了定位该资源的信息,因此它不能是相对的
小 结
- 位于网络中的计算机具有唯一的IP地址,这样不同的主机可以互相区分。
2.== 客户端-服务器==是一种最常见的网络应用程序模型。服务器是一个为其客户端提供某种特定 服务的硬件或软件。客户机是一个用户应用程序,用于访问某台服务器提供的服务。端口号 是对一个服务的访问场所,它用于区分同一物理计算机上的多个服务。套接字用于连接客户 端和服务器,客户端和服务器之间的每个通信会话使用一个不同的套接字。TCP协议用于实 现面向连接的会话。 - Java 中有关网络方面的功能都定义在 java.net 程序包中。Java 用 InetAddress 对象表示== IP
地址==,该对象里有两个字段:主机名(String) 和 IP 地址(int)。 - 类 Socket 和 ServerSocket 实现了基于TCP协议的客户端-服务器程序。Socket是客户端
和服务器之间的一个连接,连接创建的细节被隐藏了。这个连接提供了一个安全的数据传输 通道,这是因为 TCP 协议可以解决数据在传送过程中的丢失、损坏、重复、乱序以及网络 拥挤等问题,它保证数据可靠的传送。 - 类 URL 和 URLConnection 提供了最高级网络应用。URL 的网络资源的位置来同一表示
Internet 上各种网络资源。通过URL对象可以创建当前应用程序和 URL 表示的网络资源之 间的连接,这样当前程序就可以读取网络资源数据,或者把自己的数据传送到网络上去。
|