Java基础笔记

文章目录

Java基础

、变量与运算符

1.基本数据类型转换

自动类型转换:容器小的类型转换容器大的数据类型,数据类型按容量大小排序为:(char.byte.short)int->long->float->double。byte,short,char之间不会相互转换,他们三者在计算时首先转换为int类型。

char-2字节

byte-1字节-8bit位

short-2字节

int-4字节

long-8字节

float-4字节

double-8字节

自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型。使用时要加上强制转换符:(),但可能造成精度降低或溢出,格外要注意。

通常,字符串不能直接转换为基本类型,但通过基本类型对应的包装类则可
以实现把字符串转换成基本类型如: String a=“43”;int i =Integer.parseInt(a);

有多种类型的数据混合运算时,系统首先自动将所有数据转换成容量最大的那种数据类型,然后再进行计算。

当把任何基本数据类型的值和字符串(String)进行连接运算时(+),基本数据类 型的值将自动转化为字符串(String)类型。String不是基本数据类型,而是引用数据类型,在作为参数传递的时候不是值传递,指向的地址在方法前后不变,Java 中参数通过值传递传到函数中不会影响原来的值。

**二进制:**Java整数常量默认是int类型,当用二进制定义整数时,其第32位是符号位; 当是long类型时,二进制默认占64位,第64位是符号位
二进制的整数有如下三种形式:
原码:直接将一个数值换成二进制数。最高位是符号位
负数的反码:是对原码按位取反,只是最高位(符号位)确定为1。
负数的补码:其反码加1。

计算机以二进制补码的形式保存所有的整数。
正数的原码、反码、补码都相同
负数的补码是其反码+1

2. 运算符

分类:算术运算符、赋值运算符、比较运算符(关系运算符)、逻辑运算符位运算符、三元运算符。

逻辑运算符

“&”和“&&”的区别:

单&时,左边无论真假,右边都进行运算;

双&时,如果左边为真,右边参与运算,如果左边为假,那么右边不参与运算。

“|”和“||”的区别同理,||表示:当左边为真,右边不参与运算。

异或(^)与或(|)的不同之处是:当左右都为true或false时,结果为false。
理解:异或,追求的是“异”!

算术运算符:

运算符运算范例结果
+正号+33
-负号b=4; -b-4
+5+510
-6-42
*****3*412
/5/51
%取模(取余)7%52
++ ++自增(前):先运算后取值 自增(后):先取值后运算a=2;b=++a; a=2;b=a++;a=3;b=3 a=3;b=2
- - - -自减(前):先运算后取值 自减(后):先取值后运算a=2;b=- -a a=2;b=a- -a=1;b=1 a=1;b=2
+字符串连接“He”+”llo”“Hello”

逻辑运算符:

&—逻辑与| —逻辑或!—逻辑非
&& —短路与|| —短路或^ —逻辑异或
aba&ba&&ba|ba||b!aa^b
truetruetruetruetruetruefalsefalse
truefalsefalsefalsetruetruefalsetrue
falsetruefalsefalsetruetruetruetrue
falsefalsefalsefalsefalsefalsetruefalse

位运算符:

位运算符
运算符运算范例
<<左移3 << 2 = 12 --> 322=12
>>右移3 >> 1 = 1 --> 3/2=1
>>>无符号右移3 >>> 1 = 1 --> 3/2=1
&与运算6 & 3 = 2
|或运算6 | 3 = 7
^异或运算6 ^ 3 = 5
~取反运算~6 = -7
位运算符的细节
<<空位补0,被移除的高位丢弃,空缺位补0。
>>被移位的二进制最高位是0,右移后,空缺位补0; 最高位是1,空缺位补1。
>>>被移位二进制最高位无论是0或者是1,空缺位都用0补。
&二进制位进行&运算,只有1&1时结果是1,否则是0;
|二进制位进行 | 运算,只有0 | 0时结果是0,否则是1;
^相同二进制位进行 ^ 运算,结果是0;1^1=0 , 0^0=0 不相同二进制位 ^ 运算结果是1。1^0=1 , 0^1=1
~正数取反,各二进制码按补码各位取反 负数取反,各二进制码按补码各位取反

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

三元运算符:(条件表达式)?表达式1:表达式2;条件表达式为真结果为表达式1,反之为表达式2。

运算符优先级:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

二、程序流程控制

1. if-else结构

  • 条件表达式必须是布尔表达式(关系表达式或逻辑表达式)、布尔变量
  • 语句块只有一条执行语句时,一对{}可以省略,但建议保留
  • if-else语句结构,根据需要可以嵌套使用
  • 当if-else结构是“多选一”时,最后的else是可选的,根据需要可以省略
  • 当多个条件是“互斥”关系时,条件判断语句及执行语句间顺序无所谓 当多个条件是“包含”关系时,“小上大下 / 子上父下”(父在上 包含了子,下面的子就不走了)。

2. switch-case结构

  • switch(表达式)中表达式的值必须是下述几种类型之一:byte,short,char,int,枚举 (jdk 5.0),String (jdk 7.0);
  • case子句中的值必须是常量,不能是变量名或不确定的表达式值;
  • 同一个switch语句,所有case子句中的常量值互不相同;
  • break语句用来在执行完一个case分支后使程序跳出switch语句块;如 果没有break,程序会顺序执行到switch结尾
  • default子句是可任选的。同时,位置也是灵活的。当没有匹配的case时,执行default

其他:对区间判断,对结果为boolean类型判断,使用if,if的使用范围更广。也就是说,使用switch-case的,都可以改写为if-else。反之不成立。

3. 循环结构

循环的四个组成部分:1.初始化部分、2.循环条件部分、3.循环体部分、4.迭代部分。

3.1 for循环

语法格式:for (①初始化部分; ②循环条件部分**;** ④迭代部分**)**{

​ ③循环体部分**;**

​ }

执行过程:①-②-③-④-、②-③-④-、②-③-④-…-②

说明:

  • 循环条件部分为boolean类型表达式,当值为false时,退出循环
  • 初始化部分可以声明多个变量,但必须是同一个类型,用逗号分隔
  • 可以有多个变量更新,用逗号分隔
3.2 while循环

语法格式:①初始化部分

​ while(②循环条件部分){

​ ③循环体部分**;**

​ ④迭代部分;

​ }

执行过程:①**---④、-②---、②---…-**②

说明:

  • 注意不要忘记声明④迭代部分。否则,循环将不能结束,变成死循环。
  • for循环和while循环可以相互转换。
3.3 do-while循环

语法格式:①初始化部分**;**

​ do{

​ ③循环体部分

​ ④迭代部分

​ }while(②循环条件部分);

执行过程:①**--④-②、-③---、③--…**②

说明:do-while循环至少执行一次循环体。

3.4 循环的嵌套
  • 将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for ,while ,do…while均可以作为外层循环或内层循环。
  • 实质上,嵌套循环就是把内层循环当成外层循环的循环体。当只有内层循环的 循环条件为false时,才会完全跳出内层循环,才可结束外层的当次循环,开 始下一次的循环。
  • 设外层循环次数为m次,内层为n次,则内层循环体实际上需要执行m*n次。
3.5 break和continue

1.break用于终止某个语句块的执行,终止整个循环。

2.continue语句用于跳过其所在循环语句块的一次执行,继续下一次循环,终止本次循环。

另外:与break和continue不同的是,return直接结束整个方法,不管这个return处于多少层循环之内。

注意:

  • break只能用于switch语句和循环语句中。continue只能用于循环语句中。
  • break、continue之后不能有其他的语句,因为程序永远不会执行其后的语句。标号语句必须紧接在循环的头部。标号语句不能用在非循环语句的前面。

三、数组

1. 数组的概述
  • 数组本身是引用数据(默认初始化值为null)类型,而数组中的元素可以是任何数据类型,包括

基本数据类型和引用数据类型。

  • 创建数组对象会在内存中开辟一整块连续的空间,而数组名中引用的是 这块连续空间的首地址。
  • 数组的长度一旦确定,就不能修改。
  • 我们可以直接通过下标(或索引)的方式调用指定位置的元素,速度很快。
  • 数组的分类:按照维度:一维数组、二维数组、三维数组、…按照元素的数据类型分:基本数据类型元素的数组、引用数据类型元素的数组(即对象数组)
2. 数组的使用
  • 定义并用运算符new为之分配空间后,才可以引用数组中的每个元素;
  • 数组元素的引用方式:数组名[数组元素下标]。数组元素下标可以是整型常量或整型表达式。如a[3] , b[i] , c[6*i];数组元素下标从0开始;长度为n的数组合法下标取值范围:0 —>n-1;。
  • 数组是引用类型,它的元素相当于类的成员变量,因此数组一经分配空间,其中的每个元素也被按照成员变量同样的方式被隐式初始化。
  • 多维数组的使用:对于二维数组的理解,我们可以看成是一维数组 array1又作为另一个一维数组array2的元素而存在。
2.1 数组在内存中的存储

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3. 数组中涉及的常见算法
3.1.数组元素的赋值

杨辉三角:

3.2.数组的复制、反转、查找(线性查找、二分法查找)
3.3.数组元素的排序算法

通常来说,排序的目的是快速查找。

  • 衡量排序算法的优劣:

1.时间复杂度:分析关键字的比较次数和记录的移动次数

2.空间复杂度:分析排序算法中需要多少辅助内存

3.稳定性:若两个记录A和B的关键字值相等,但排序后A、B的先后次序保 持不变,则称这种排序算法是稳定的

  • 排序算法分类:内部排序和外部排序。

内部排序:整个排序过程不需要借助于外部存储器(如磁盘等),所有排 序操作都在内存中完成。

外部排序:参与排序的数据非常多,数据量非常大,计算机无法把整个排 序过程放在内存中完成,必须借助于外部存储器(如磁盘)。外部排序最 常见的是多路归并排序。可以认为外部排序是由多次内部排序组成。

十大内部排序算法:

选择排序:①直接选择排序、②堆排序

交换排序:③冒泡排序、④快速排序

插入排序:⑤直接插入排序、⑥折半插入排序、⑦Shell排序

⑧归并排序⑨桶式排序⑩基数排序。

冒泡排序:

冒泡排序的原理非常简单,它重复地走访过要排序的数列,一次比较两个元 素,如果他们的顺序错误就把他们交换过来。

排序思想

1.比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。

2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步 做完后,最后的元素会是最大的数。

3.针对所有的元素重复以上的步骤,除了最后一个。

4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要 比较为止。

//冒泡排序
public class TestMaoPao {
	public static void main(String[] args) {
		int[] arr = {12,56,-3,33,-5};
        //两层循环
		for(int i = 0;i < arr.length-1;i++){
            //之前比较过的不再比较
			for(int j = 0;j < arr.length - i - 1;j++){
                //升序 最大的排到最后
				if(arr[j] < arr[j+1]){
					int temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;
				}
			}
		}
		
		for(int i = 0;i < arr.length;i++){
			System.out.println(arr[i]);
		}
	}
}


快速排序

**
 * 快速排序
 * 通过一趟排序将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分关键字小,
 * 则分别对这两部分继续进行排序,直到整个序列有序。
 */
public class QuickSort {
	private static void swap(int[] data, int i, int j) {
		int temp = data[i];
		data[i] = data[j];
		data[j] = temp;
	}

	private static void subSort(int[] data, int start, int end) {
		if (start < end) {
			int base = data[start];
			int low = start;
			int high = end + 1;
			while (true) {
				while (low < end && data[++low] - base <= 0)
					;
				while (high > start && data[--high] - base >= 0)
					;
				if (low < high) {
					swap(data, low, high);
				} else {
					break;
				}
			}
			swap(data, start, high);
			
			subSort(data, start, high - 1);//递归调用
			subSort(data, high + 1, end);
		}
	}
	public static void quickSort(int[] data){
		subSort(data,0,data.length-1);
	}
	
	
	public static void main(String[] args) {
		int[] data = { 9, -16, 30, 23, -30, -49, 25, 21, 30 };
		System.out.println("排序之前:\n" + java.util.Arrays.toString(data));
		quickSort(data);
		System.out.println("排序之后:\n" + java.util.Arrays.toString(data));
	}
}

/**
 * 数组的工具类的介绍Arrays
 * @author Administrator
 *
 */
public class TestArrays {
	public static void main(String[] args) {
		int[] arr1 = {12,13,14,15};
		int[] arr2 = {-12,13,14,15};
		
		// 1.equals:判断俩个数组是否相等
		System.out.println(Arrays.equals(arr2,arr1));
		
		// 2.toString:显示数组信息
		System.out.println(Arrays.toString(arr1));
		
		// 3.fill:用制定内容填充数组
		// Arrays.fill(arr1, 1);
		System.out.println(Arrays.toString(arr1));
		
		// 4.sort:对数组进行排序,默认是升序排序,底层使用快速排序
		Arrays.sort(arr1);
		
		// 5.binarySearch:使用二分法检索某个元素的索引值,若没找到,返回-1
		System.out.println(Arrays.binarySearch(arr1, 1));
	}
}

数组使用的常见异常(编译时不会报错的)

数组脚标越界异常**(ArrayIndexOutOfBoundsException)**
int[] arr = new int[2]; System.out.println(arr[2]); System.out.println(arr[-1]); 访问到了数组中的不存在的脚标时发生。
空指针异常**(NullPointerException)**
int[] arr = null; System.out.println(arr[0]); arr引用没有指向实体,却在操作实体中的元素时。

四、面向对象编程

面向对象的三大特征:封装、继承和多态

1.对象的创建和使用:

Ø创建对象语法: 类名 对象名 = new 类名**();**

Ø使用“对象名**.**对象成员”的方式访问对象成员(包括属性和方法)

1.1类的访问机制

在一个类中的访问机制:类中的方法可以直接访问类中的成员变量。

(static方法不能访问非static,编译会报错)。

在不同类中的访问机制:先创建要访问类的对象,再用对象访问类中定义的成员。

1.2 内存解析
  • 堆(Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的对象 实例都在这里分配内存。这一点在 Java虚拟机规范中的描述是:所有的 对象实例以及数组都要在堆上分配。
  • 通常所说的栈(Stack),是指虚拟机栈。虚拟机栈用于存储局部变量等。 局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、 char 、 short 、 int 、 float 、 long 、 double)、对象引用(reference类型, 它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释 放。
  • 方法区(Method Area),用于存储已被虚拟机加载的类信息、常量、静态 变量、即时编译器编译后的代码等数据。
1.3 匿名对象

我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。如:new Person().shout();

使用情况

Ø如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。

Ø我们经常将匿名对象作为实参传递给一个方法调用。

2.类的成员

2.1 类的成员之一:属性

变量的分类:成员变量与局部变量

在方法体外,类体内声明的变量称为成员变量(不以static修饰的是实例变量,一static修饰的是类变量)。

成员变量局部变量
声明的位置直接声明在类中方法形参或内部、代码块内、构造器内等
修饰符private、public、static、final等不能用权限修饰符修饰,可以用final修饰
初始化值有默认初始化值没有默认初始化值,必须显式赋值,方可使用
内存加载位置堆空间 或 静态域内栈空间
2.2 类的成员之二:方法

Ø方法被调用一次,就会执行一次

Ø没有具体返回值的情况,返回值类型用关键字void表示,那么方法体中可 以不必使用return语句。如果使用,仅用来结束方法。

Ø定义方法时,方法的结果应该返回给调用者,交由调用者处理。

Ø方法中只能调用方法或属性,不可以在方法内部定义方法。

*2.2.1 方法的重载(overload)
重载的概念
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数 类型不同即可。
重载的特点
与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类 型)。调用时,根据方法参数列表的不同来区别。
重载示例
//返回两个整数的和 int add(int x,int y){return x+y;} //返回三个整数的和 int add(int x,int y,int z){return x+y+z;} //返回两个小数的和 double add(double x,double y){return x+y;}
可变个数形参:
声明格式:方法名(参数的类型名 …参数名),在一个方法的形参位置,最多只能声明一个可变个数形参
*****2.2.2 方法参数的值传递

方法,必须由其所在类或对象调用才有意义。若方法含有参数:

Ø形参:方法声明时的参数

Ø实参:方法调用时实际传给形参的参数值

Java的实参值如何传入方法呢?

Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本

(复制品)传入方法内,而参数本身不受影响。

Ø形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参

Ø形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参

基本数据类型的参数传递

public static void main(String[] args) {  
    int x = 5;
    System.out.println("修改之前x="+x);//5
    // x是实参 
    change(x);
    System.out.println("修改之后x = " + x);// 5
    //基本数据类型在参数传递时传入的是自己的副本,方法调用后栈空间内基本数据类型地址没有改变,所以值不改变
}

public static void change(int x) {  
    System.out.println("change:修改之前x = " + x);  
    x = 3;
    System.out.println("change:修改之后x = " + x);
}

引用数据类型(String数组)的参数传递

public static void main(String[] args) {
	Person obj = new Person();
	obj.age = 5;
	System.out.println("修改之前age = " + obj.age);// 5
	// x是实参
	change(obj);
	System.out.println("修改之后age = " + obj.age);// 5
}
public static void change(Person obj) {  obj = new Person();  	  System.out.println("change:修改之前age" + obj.age);
 	obj.age = 3;  
 	System.out.println("change:修改之后age" + obj.age);
}
其中Person类定义为:
class Person{
int age;
}
2.2.3 递归方法

递归方法:一个方法体内调用它自身

方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。

递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环

//递归调用计算1-100之间所有自然数的和
public int sum(int num){
if(num == 1){
return 1;
}else{
return num + sum(num - 1);
}
}
2.3.类的成员之三:构造器

构造器的特征

它具有与类相同的名称

它不声明返回值类型。(与声明为void不同)

不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值

构造器的作用:创建对象;给对象进行初始化如:Order o = new Order(); Person p = new Person(“Peter”,15);

注 意

1)Java语言中,每个类都至少有一个构造器

2)默认构造器的修饰符与所属类的修饰符一致

3)一旦显式定义了构造器,则系统不再提供默认构造器

4)一个类可以创建多个重载的构造器

5)父类的构造器不可被子类继承

2.3.1 其他:属性赋值过程

赋值的位置:

① 默认初始化

② 显式初始化

③ 构造器中初始化

④ 通过“对象.属性“或“对象.方法”的方式赋值

赋值的先后顺序:

① - ② - ③ - ④

2.4.类的成员之四:代码块

代码块(或初始化块)的作用:对Java类或对象进行初始化

分类:1)静态代码块(被static修饰的)

①可以有输出语句

②可以对类的属性、类的声明进行初始化操作

③不可以调用非静态的属性和方法

④若有多个静态代码块,则按照从上至下的顺序执行

⑤静态代码块的执行要先于非静态代码块

⑥静态代码块随着类的加载而加载,且只执行一次

static代码块通常用于初始化static的属性
class Person {
public static int total;
static {
total = 100;//为total赋初值
System.out.println("in static block!");

}
…… //其它属性或方法声明
}
public class PersonTest {
public static void main(String[] args) {  System.out.println("total = " + Person.total);  System.out.println("total = " + Person.total);
   }
}
输出:
in static block!  
total=100  
total=100

2)非静态代码块

①可以有输出语句

②可以对类的属性、类的声明进行初始化操作

③除了调用非静态的结构外、还可以调用静态的变量和方法

④若有多个非静态代码块,按照从上到下的顺序执行

⑤每次创建对象的时候,都会执行一次,且先于构造器执行

***总结:程序中成员变量赋值的先后顺序

①声明成员变量的默认初始化

②显式初始化、多个初始化块(代码块,先是静态代码块,而后非静态代码块,同一级别按先后顺序执行)

③构造器再对成员进行初始化操作

④通过“对象.属性”、“对象.方法”的方式,可多次给属性赋值

2.5.类的成员之五:内部类

概念:当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内 部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使 用内部类。

在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者 称为外部类。

Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完 整的名称。Inner class的名字不能与包含它的外部类类名相同;

分类: 成员内部类(static成员内部类和非static成员内部类)

​ 局部内部类(不谈修饰符)、匿名内部类

2.5.1 成员内部类

成员内部类作为类的成员的角色:

①和外部类不同,内部类还可以声明为private或protected②可以调用外部类的结构③可以声明为static的,但此时就不能再调用外层类的非static的成员变量

成员内部类作为类的角色:

①可以定义属性、方法、构造器等结构

②可以声明为abstract类,因此可以被其他内部类继承

③可以声明为final的

④编译以后成成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)

注意:

1.非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类中才可声明static成员。

2.外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式

3.成员内部类可以直接使用外部类的所有成员,包括私有的数据

4.当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的

class Outer {
	private int s;
	public class Inner {  
		public void mb() {
			s = 100;
			System.out.println("在内部类Inner中s=" + s);
		}
	}
	public void ma() {
		Inner i = new Inner();
		i.mb();
	}
}
public class InnerTest {
	public static void main(String args[]) {
		Outer o = new Outer();
		o.ma();
	}
}
public class Outer {  
    private int s = 111;  
    public class Inner {
		private int s = 222;
		public void mb(int s) { 
            System.out.println(s); // 局部变量s
			System.out.println(this.s); // 内部类对象的属性s
			System.out.println(Outer.this.s); // 外部类对象属性s
		}
	}
	public static void main(String args[]) {
		Outer a = new Outer();  Outer.Inner b = a.new Inner();  		b.mb(333);
	}
}
2.5.2 局部内部类

如何声明局部内部类:

class 外部类{  
    方法(){
   	  class 局部内部类{
		}
}
{
class 局部内部类{
	}
 }
}

如何使用局部内部类

Ø只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类

Ø但是它的对象可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类 的父类或父接口类型

局部内部类的特点

Ø内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但 是前面冠以外部类的类名和$符号,以及数字编号。

Ø只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。

Ø局部内部类可以使用外部类的成员,包括私有的。

Ø局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局 部变量的声明周期不同所致。

Ø局部内部类和局部变量地位类似,不能使用public,protected,缺省,private

Ø局部内部类不能使用static修饰,因此也不能包含静态成员

2.5.3匿名内部类

匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。格式:new 父类构造器(实参列表)|实现接口(){

​ **//**匿名内部类的类体部分

}

**特点 ** 1)匿名内部类必须继承父类或实现接口

​ 2)匿名内部类只能有一个对象

​ 3) 匿名内部类对象只能使用多态形式引用

interface A{
public	abstract void fun1();
}
public class Outer{
	public static void main(String[] args) {  
        new Outer().callInner(new A(){
//接口是不能new但此处比较特殊是子类对象实现接口,只不过没有为对象取名
			public void fun1() {
				System.out.println(“implement for fun1");
			}
		});// 两步写成一步了
	}
	public void callInner(A a) {  a.fun1();
	}
}

3.面向对象三大特征

3.1 特征一、封装与隐藏

信息的封装和隐藏:Java中通过将数据声明为私有的(private),再提供公共的(public)方法:**getXxx()setXxx()**实现对该属性的操作,以实现下述目的:

1)隐藏一个类中不需要对外提供的实现细节;

2)使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑, 限制对属性的不合理操作;

3)便于修改,增强代码的可维护性;

3.1.1 四种权限修饰符

Java权限修饰符public、protected、(缺省)、private置于类的成员定义前,用来限定对象对该类成员的访问权限。

对于class的权限修饰只可以用public和default(缺省)。public类可以在任意地方被访问。default类只可以被同一个包内部的类访问

修饰符类内部同一个包不同包的子类同一个工程
privateYes
(缺省)YesYes
protectedYesYesYes
publicYesYesYesYes
3.2 特征二、继承性

为什么要有继承?

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中, 那么多个类无需再定义这些属性和行为,只要继承那个类即可。此处的多个类称为子类**(派生类)**,单独的这个类称为父类(基类 或超类)。可以理解为:“子类 is a 父类”,(学生是一个人)。

类继承语法规则:class Subclass extends SuperClass{ }

3.2.1 作用

1)继承的出现减少了代码冗余,提高了代码的复用性。

2)继承的出现,更有利于功能的扩展。

3)继承的出现让类与类之间产生了关系,提供了多态的前提。

3.2.2 继承的特点

子类继承了父类,就继承了父类的方法和属性。

在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和 方法。

在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”。

关于继承的规则:子类不能直接访问父类中私有的**(private)**的成员变量和方法。(使用setter和getter)

Java只支持单继承和多层继承,不允许多重继承。一个子类只能有一个父类,一个父类可以派生出多个子类。

*3.2.3 方法的重写(override/overwrite)

**定义:**在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置和覆盖。在程序执行的时候,子类的方法将覆盖父类的方法。

要求:

1)子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表。

2)子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型。

3)子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限;子类不能重写父类中声明为private权限的方法。

4)子类方法抛出的异常不能大于父类被重写方法的异常。

注意:子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。

3.3 特征三、多态性
3.3.1 概念

多态性,是面向对象中最重要的概念,在Java中的体现:

对象的多态性: 父类的引用指向子类的对象

  • 可以直接应用在抽象类和接口上

Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明 该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简 称:编译时,看左边;运行时,看右边。

  • 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
  • 多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法)“看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)

对象的多态 :在Java中,子类的对象可以替代父类的对象使用

一个变量只能有一种确定的数据类型

一个引用类型变量可能指向(引用)多种不同类型的对象

Object o = new Person();//Object类型的变量o,指向Person的对象Object o = new Student();//Object的变量o,指向Student的对象

子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型**(upcasting)**。

一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法

方法声明的形参类型为父类类型,可以使用子类的对象作为实参调用该方法

从编译和运行的角度看方法的重载与重写:

**重载,**是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不 同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了 不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类 和子类的,即子类可以重载父类的同名不同参数的方法。所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法, 这称为“早绑定”或“静态绑定”;

而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体 方法,这称为“晚绑定”或“动态绑定”。

引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”

3.3.2 多态小结

多态作用:提高了代码的通用性,长称作接口重用

前提:1)需要存在继承或者实现关系

​ 2)有方法的重写

成员方法

1)编译时:要查看引用变量所声明的类中是否有所调用的方法。

2)运行时:调用实际new的对象所属的类中的重写方法。

成员变量:不具备多态性,只看引用变量所声明的类。

4.抽象类与抽象方法

4.1 为什么会有抽象类?

随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一 般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。

4.2 什么是抽象类和抽象方法?

用abstract关键字来修饰一个类,这个类叫做抽象类

用abstract来修饰一个方法,该方法叫做抽象方法

抽象方法:只有方法的声明,没有方法的实现。以分号结束:

比如:public abstract void talk();

含有抽象方法的类必须被声明为抽象类,抽象类可以没有抽象方法。

抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重 写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。

不能用abstract修饰变量、代码块、构造器;

不能用abstract修饰私有方法、静态方法、final的方法、final的类。

  • 问题1:为什么抽象类不可以使用final关键字声明?答:因为被final修饰的类不可以被继承,而抽象类设计出来就是为了子类继承的。
  • 问题2:一个抽象类中可以定义构造器吗?答:可以有,抽象类可以声明并定义构造函数。因为你不可以创建抽象类的实例,所以构造函数只能通过构造函数链调用(Java中构造函数链指的是从其他构造函数调用一个构造函数),例如,当你创建具体的实现类。现在一些面试官问,如果你不能对抽象类实例化那么构造函数的作用是什么?好吧,他**可以用来初始化抽象类内部声明的通用变量,并被各种实现使用。**另外,即使你没有提供任何构造函数,编译器将为抽象类添加默认的无参数的构造函数,没有的话你的子类将无法编译,因为在任何构造函数中的第一条语句隐式调用super(),Java中默认超类的构造函数。
  • 问题3:是否可以这样理解:抽象类就是比普通类多定义了抽象方 法,除了不能直接进行类的实例化操作之外,并没有任何的不同?答:错误,普通类方法可以有方法体,有jdk8.0之前抽象类只有方法的声明没有实现过程。(jdk8.0之后接口里也可以写方法体了)

5.接口

5.1 什么是接口?

接口(interface)是抽象方法和常量值定义的集合。

1)一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方 法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
2)另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又 没有is-a的关系,仅仅是具有相同的行为特征而已。

3)接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则 必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是 “能不能” 的关系。
4)接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都
要遵守。

5.2 接口的特点

1)用interface来定义。
2)接口中的所有成员变量都默认是由public static final修饰的。
3)接口中的所有抽象方法都默认是由public abstract修饰的。
4)接口中没有构造器。
5)接口采用多继承机制

public interface Runner {  
    int ID = 1;
	void start(); 
    public void run();  
    void stop();
}
以上相当于:(方法省略了public abstract,常量的修饰符也被省略)
public interface Runner {
	public static final int ID = 1;  
	public abstract void start();  
	public abstract void run();  
	public abstract void stop();
}

*****定义Java类的语法格式:先写extends,后写implements
class SubClass extends SuperClass implements InterfaceA{ }
*****一个类可以实现多个接口,接口也可以继承其它接口。
*****实现接口的类中必须提供接口中所有方法的具体实现内容,方可实
例化。否则,仍为抽象类。
*****接口的主要用途就是被实现类实现。(面向接口编程)
*****与继承关系类似,接口与实现类之间存在多态性
*****接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲, 接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义 (JDK7.0及之前),而没有变量和方法的实现

5.3 接口的应用——代理模式

代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其
他对象提供一种代理以控制对这个对象的访问。

应用场景:安全代理:屏蔽对真实角色的直接访问。
远程代理:通过代理类处理远程方法调用(RMI)
延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有 100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理 模式,当需要查看图片时,用proxy来进行大图片的打开。

**分类:**静态代理(静态定义代理类)
动态代理(动态生成代理类)JDK自带的动态代理,需要反射等知识

interface Network {  
    public void browse();
}

// 被代理类
class RealServer implements	Network {

	@Override
	public void browse() {
		System.out.println("真实服务器上 网浏览信息");
	}
}
// 代理类
class ProxyServer implements Network {  
    private Network network;
	public ProxyServer(Network network) {  
        this.network = network;
	}
	public void check() {
		System.out.println("检查网络连接等操作");
	}
	public void browse() {  
    	check();  
        network.browse();
	}
}
public class ProxyDemo {
	public static void main(String[] args) {
		Network net = new ProxyServer(new  RealServer());
		net.browse();
}


5.4 接口和抽象类的对比
No.区别点抽象类接口
1定义包含抽象方法的类主要是抽象方法和全局常量的集合
2组成构造方法、抽象方法、普通方法、 常量、变量常量、抽象方法、(jdk8.0:默认方法、静态方法)
3使用子类继承抽象类(extends)子类实现接口(implements)
4关系抽象类可以实现多个接口接口不能继承抽象类,但允许继承多个接口
5常见设计模式模板方法简单工厂、工厂方法、代理模式
6对象都通过对象的多态性产生实例化对象
7局限抽象类有单继承的局限接口没有此局限
8实际作为一个模板是作为一个标准或是表示一种能力
9选择如果抽象类和接口都可以使用的话,优先使用接口,因为避免单继承的局限
5.5 接口的默认方法

若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同 参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接 口时,会出现:接口冲突。
解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突。

若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非 抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则。接口中具有 相同名称和参数的默认方法会被忽略。

6.重要关键字

6.1 this关键字

它在方法内部使用,即这个方法所属对象的引用;

它在构造器内部使用,表示该构造器正在初始化的对象

this可以调用类的属性、方法和构造器,当在方法内需要用到调用该方法的对象时,就用this。

class Person{	// 定义Person类
    private String name ;  
    private int age ;
public Person(String name,int age){  
    this.name = name ;  
    this.age = age ;	}
public void getInfo(){
System.out.println("姓名:" + name) ;
    this.speak();
}
public void speak(){
    System.out.println("年龄:"+ this.age);
  }
}
public class PersonTest{
	public static void main(String args[]){
		Person per1 = new Person("张三",23) ;
		Person per2 = new Person("李四",14) ;
		per1.getInfo() ; // 当前调用getInfo()方法的对象是per1
		per2.getInfo() ;// 当前调用getInfo()方法的对象是per2
} }
//输出:
姓名:张三
年龄:23

姓名:李四
年龄:14

注意:

①可以在类的构造器中使用"this(形参列表)"的方式,调用本类中重载的其他的构造器。

②构造器中不能通过"this(形参列表)"的方式调用自身构造器

③this(形参列表)"必须声明在类的构造器的首行

④在类的一个构造器中,最多只能声明一个"this(形参列表)"

6.2 super关键字

在Java中使用super来调用父类中的指定操作:

1)super可以用于访问父类中定义的属性

2)super可以用于调用父类中定义的成员方法

3)super可以用于在子类构造器中调用父类的构造器

注意:

①尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员

②super的追溯不仅限于直接父类

③super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识

调用父类的构造器

子类中所有的构造器默认都会访问父类中空参数的构造器

当父类中没有空参数的构造器时,子类的构造器必须通过**this(参数列表)或者super(参数列表)**语句指定调用本类或者父类中相应的 构造器。同时,只能”二选一”,且必须放在构造器的首行

如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错

6.3 this和super的区别
No.区别点thissuper
1访问属性访问本类中的属性,如果本类没 有此属性则从父类中继续查找直接访问父类中的属性
2调用方法访问本类中的方法,如果本类没 有此方法则从父类中继续查找直接访问父类中的方法
3调用构造器调用本类构造器,必须放在构造 器的首行调用父类构造器,必须 放在子类构造器的首行
6.4 static关键字

为什么要有static关键字?

有时候我们希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份(每个对象都共有一个相同属性,不必每次都重新声明一下)。

类方法和类属性:

类属性作为该类各个对象之间共享的变量。在设计类时**,**分析哪 些属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法。

如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。

使用范围

在Java类中,可用static修饰属性、方法、代码块、内部类

被修饰后的成员具备以下特点

1)随着类的加载而加载

2)优先于对象存在

3)修饰的成员,被所有对象所共享

4)访问权限允许时,可不创建对象,直接被类调用

注意:

1)在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构。

2)因为不需要实例就可以访问static方法,因此static方法内部不能有this(也不能有super )

3)static修饰的方法不能被重写

class Person {
	private int id;
	public static int total = 0;  
    public Person() {
		total++;  
        id = total;
}
public static void main(String args[]){  
    Person Tom=new Person();  
    Tom.id=0;
	total=100; // 不用创建对象就可以访问静态成员
	}
}
public class StaticDemo {
public static void main(String args[]) {
	Person.total = 100; // 不用创建对象就可以访问静态成员
    //访问方式:类名.类属性,类名.类方法  
    System.out.println(Person.total);  Person c = new Person();  	       	 System.out.println(c.total); //输出101
}}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

6.4.1 单例模式(singleton)

概念:所谓单例模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。

如果我们要让类在一个虚拟机中只能产生一个对象,那就必须将类的构造器设为private。这样就不能通过new关键字产生对象,但是在该类的内部还是可以产生该类的对象。因为在内的外部一开始还无法得到该类的对象,只能调用该类的某个静态方法返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类的内部产生的该类对象的变量也必须定义成静态的。

单例模式的优点:由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的
产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。

单例设计模式——饿汉式

class Singleton {
	// 1.私有化构造器
	private Singleton() {
	}

	// 2.内部提供一个当前类的实例
	// 4.此实例也必须静态化
	private static Singleton single = new Singleton();

	// 3.提供公共的静态的方法,返回当前类的对象
	public static Singleton getInstance() {
	return single;
	}
}

单例设计模式——懒汉式

class Singleton {
	// 1.私有化构造器
	private Singleton() {
	}
	// 2.内部提供一个当前类的实例
	// 4.此实例也必须静态化
	private static Singleton single;
	// 3.提供公共的静态的方法,返回当前类的对象
	public static Singleton getInstance() {  
        if(single == null) {
			single = new Singleton();
		}
		return single;
	}
}
//懒汉式暂时还存在线程安全问题,讲到多线程时,可修复


单例模式应用场景:

1)网站的计数器,一般也是单例模式实现,否则难以同步。

2)应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志 文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。

3)数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。

4)项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置

文件数据,都生成一个对象去读取。

5)Application 也是单例的典型应用

6)Windows的Task Manager (任务管理器)就是很典型的单例模式

7)Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程 中,回收站一直维护着仅有的一个实例。

6.5 final关键字

在Java中声明类、变量和方法时,可使用关键字final来修饰,表示“最终的”。

1.final标记的类不能被继承(提高安全性,提高程序的可读性)——String类、System类、StringBuffer类都是final修饰的,不能被继承

2.final标记的方法不能被子类重写——如Object类的getClass()

3.final标记的变量(成员变量或局部变量)**即称为常量。名称大写,且只能被赋值一次。**final标记的成员变量必须在声明时或在每个构造器中或代码块中显式赋 值,然后才能使用。如:final double MY_PI =3.14

7.Object类的使用

Object类是所有Java类的根父类

Object类中的主要结构

NO.方法名称类型描述
1public Object()构造构造器
2public boolean equals(Object obj)普通对象比较
3public int hashCode()普通取得Hash码
4public String toString()普通对象打印时调用
7.1 ==操作符与equals方法

1.==操作符

1)基本类型比较:只要两个变量的值相等就为true

2)引用类型比较引用:只有指向同一个对象时才返回true

用==比较时,两边的数据类型必须兼容(或是可以自动转换的类型)

2.equals方法

所有类都继承了Object,也就可以重写equals方法

equals方法只能比较引用类型,比较是否指向同一个对象

特例:当用equals() 方法进行比较时, 对类FileStringDate 及包装类来说,是比较类型及内容而不考虑引用的是否是同一个对 象;

原因:在这些类中重写了Object类的**equals()**方法。

当自定义使用**equals()**时,可以重写。用于比较两个对象的“内容”是否都相等.

重写equals方法的原则:

1)对称性:如果x.equals(y)返回是“true ”, 那么y.equals(x) 也应该返回是“true”。

2)自反性:x.equals(x)必须返回是“true”。

3)传递性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。

4)一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你
重复x.equals(y)多少次,返回都是“true”。

任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。

7.1.1==和equals的区别

1.== 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型 就是比较内存地址

2.equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是**==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals**是比较值的错误观点。

3.具体要看自定义类里有没有重写Objectequals方法来判断。

4.通常情况下,重写equals方法,会比较类中的相应属性是否都相等。

7.2toString方法
  • toString()方法在Object类中定义,其返回值是String类型,返回类名和它的引用地址。
  • 在进行String与其它类型数据的连接操作时,自动调用toString()方法
    Date now=new Date();
    System.out.println(“now=”+now); 相当于
    System.out.println(“now=”+now.toString());
  • 可以根据需要在用户自定义类型中重写toString()方法 如String 类重写了toString()方法,返回字符串的值。 s1=“hello”;
    System.out.println(s1);//相当于System.out.println(s1.toString());
  • 基本类型数据转换为String类型时,调用了对应包装类的toString()方法
    int a=10; System.out.println(“a=”+a);
public void test() {
char[] arr = new char[] { 'a', 'b', 'c' };
System.out.println(arr);//abc

int[] arr1 = new int[] { 1, 2, 3 };
System.out.println(arr1);//[I@4554617c地址

double[] arr2 = new double[] { 1.1, 2.2, 3.3 };
System.out.println(arr2);//[D@74a14482地址
}
//为什么?
//因为char数组重写过toString方法

8.包装器类的使用

针对八种基本数据类型定义相应的引用类型—包装类(封装类)
有了类的特点,就可以调用类中的方法,Java才是真正的面向对象

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean
charCharacter

其中:Byte ,Short,Integer,Long,Float ,Double继承于父类Number。

8.1 装箱和拆箱
8.1.1 基本数据类型和包装器类型转换

a) 装箱基本数据类型转包装器类型

①通过包装类的构造器实现:int i = 500; Integer t = new Integer(i);、

②通过字符串参数构造包装类对象:Float f = new Float(“4.56”);Long l = new Long(“asdf”); //NumberFormatException

b) 拆箱包装器类转基本数据类型

​ ①调用包装类的.xxxValue()方法:boolean b = Obj.booleanValue();

jdk 1.5 之后支持自动装箱和拆箱,但是类型必须匹配

8.1.2 字符串转换成基本数据类型

a)字符串转换成基本数据类型
①通过包装类的构造器实现:
int i = new Integer(“12”);
②通过包装类的parseXxx(String s)静态方法Float f = Float.parseFloat(“12.1”);

b)基本数据类型转换成字符串
①调用字符串重载的valueOf()方法:
String fstr = String.valueOf(2.34f);
更直接的方式:
String intStr = 5 + “”

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

五、异常

1.异常概述与异常体系结构

1.1 异常概念及分类

异常:在Java语言中,将程序执行中发生的不正常情况称为“异常”。
(开发过程中的语法错误和逻辑错误不是异常)
Java程序在执行过程中所发生的异常事件可分为两类:
1)Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。一般不编写针对性 的代码进行处理。
2)Exception: 其它因编程错误或偶然的外在因素导致的一般性问题可以使 用针对性的代码进行处理。例如:空指针访问//试图读取不存在的文件//网络连接中断//数组角标越界。

1.运行时异常
是指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常。
对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对 程序的可读性和运行效率产生影响。

2.编译时异常
是指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一 般性异常。编译器要求Java程序必须捕获或声明所有编译时异常。
对于这类异常,如果程序不处理,可能会带来意想不到的结果。

2.常见异常

*java.lang.RuntimeException

ØClassCastException

ØArrayIndexOutOfBoundsException(数组角标越界)

ØNullPointerException(空指针)

ØArithmeticException

ØNumberFormatException(数据格式转换错误)

ØInputMismatchException

*java.io.IOExeption

ØFileNotFoundException

ØEOFException

*java.lang.ClassNotFoundException

*java.lang.InterruptedException

*java.io.FileNotFoundException

*java.sql.SQLException

3.异常处理机制一:try-catch-finally

Java提供的是异常处理的抓抛模型。

Java程序的执行过程中如出现异常,会生成一个异常类对象, 该异常对象将被提交给Java运行时系统,这个过程称为抛出 (throw)异常。

异常对象的生成

Ø由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,如果在当 前代码中没有找到相应的处理程序,就会在后台自动创建一个对应异常类的实例 对象并抛出——自动抛出

Ø由开发人员手动创建:Exception exception = new ClassCastException();——创 建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样

异常处理是通过try-catch-finally语句实现的。
try{
......	//可能产生异常的代码
}
catch( ExceptionName1 e ){
......	//当产生ExceptionName1型异常时的处置措施
//父类异常不能放在前面,不然后面的就捕获不到了,编译会报错
//如果明确知道产生的是何种异常,可以用该异常类作为catch的参数;也可 以用其父类作为catch的参数。捕获异常的有关信息:
//与其它对象一样,可以访问一个异常对象的成员变量或调用它的方法。
//getMessage()	获取异常信息,返回字符串
//printStackTrace()	获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。


}
catch( ExceptionName2 e ){
......	//当产生ExceptionName2型异常时的处置措施
}
[ finally{
......	//无论是否发生异常,都无条件执行的语句,不论在try代码块中是否发生了异常事件,catch语句是否执 行,catch语句是否有异常,catch语句中是否有return,  finally块中的语句都会被执行。
//finally语句和catch语句是任选的,
} ]


4.异常处理机制二:throws

声明抛出异常是Java中处理异常的第二种方式

Ø如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这 种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理, 而由该方法的调用者负责处理。

Ø在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可 以是方法中产生的异常类型,也可以是它的父类。

声明抛出异常举例:

public void readFile(String file)  throws FileNotFoundException {

……

**//** 读文件的操作可能产生**FileNotFoundException**类型的异常

FileInputStream fis = new FileInputStream(file);

..……

}

public class ThrowsTest {
	public static void main(String[] args) {
		ThrowsTest t = new ThrowsTest();  
        try {
			t.readFile();
		} catch (IOException e) {
			e.printStackTrace();
			}
		}
	public void readFile() throws IOException {
		FileInputStream in = new FileInputStream("atguigushk.txt");
		int b;
		b = in.read();
		while (b != -1) {  
       	 	System.out.print((char) b);  
            b = in.read();
		}
		in.close();
	}
}

重写方法声明抛出异常的原则:

//重写方法不能抛出比被重写方法范围更大的异常类型。在多态的情况下, 对methodA()方法的调用-异常的捕获按父类声明的异常处理。

public class A {
	public void methodA() throws IOException {
		//……
	} }
public class B1 extends A {
	public void methodA() throws FileNotFoundException {
		//……
} }
public class B2 extends A {
	public void methodA() throws Exception {//报错
		//……
} }

5.手动抛出异常:throw

Java异常类对象除在程序执行过程中出现异常时由系统自动生成并 抛出,也可根据需要使用人工创建并抛出。
1)首先要生成异常类对象,然后通过throw语句实现抛出操作(提交给Java运 行环境)。
IOException e = new IOException(); throw e;
2)可以抛出的异常必须是Throwable或其子类的实例。下面的语句在编译时将 会产生语法错误:
throw new String(“want to throw”);

6.用户自定义异常类

一般地,用户自定义异常类都是RuntimeException的子类。
自定义异常类通常需要编写几个重载的构造器。
自定义异常需要提供serialVersionUID
自定义的异常通过throw抛出。
自定义异常最重要的是异常类的名字,当异常出现时,可以根据
名字判断异常类型。

//用户自定义异常类MyException,用于描述数据取值范围错误信息。用户 自己的异常类必须继承现有的异常类。

class MyException extends Exception {
	static final long serialVersionUID = 13465653435L;
	private int idnumber;

	public MyException(String message, int id) { 	 			super(message);
		this.idnumber = id;
	}

	public  int getId() {
		return idnumber;
	}
}
public class MyExpTest {
	public void regist(int num) throws MyException { 
        if (num < 0)
			throw new MyException("人数为负值,不合理", 3);
		else
			System.out.println("登记人数" + num);
	}
	public void manager() {
		try {
			regist(100);
		} catch (MyException e) {
			System.out.print("登记失败,出错种类" + e.getId());
		}
		System.out.print("本次登记操作结束");
	}
	public static void main(String args[]) {  
        MyExpTest t = new MyExpTest();  t.manager();
	}
}

6.1 小结 异常处理5个关键字

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

六、多线程

1.基本概念:程序、进程、线程

1)程序**(program)**是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码,静态对象。

2)进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期

Ø如:运行中的QQ,运行中的MP3播放器

Ø程序是静态的,进程是动态的

Ø进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

3)线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。

Ø若一个进程同一时间并行执行多个线程,就是支持多线程的

Ø线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小

Ø一个进程中的多个线程共享相同的内存单元/内存地址空间->它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资 源可能就会带来安全的隐患。

a.单核cpu与多核cpu、并行与并发

单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程 的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费 才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以 把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时 间单元特别短,因此感觉不出来。
如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
并行与并发
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

**使用多线程的优点:**1)提高应用程序的响应,对图形化界面更有意义,增强用户体验。2)提高计算机系统cpu的利用率。3)改善程序结构将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。

**何时需要多线程:**1)程序需要同时执行两个或多个任务。

2)程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。

3)需要一些后台运行的程序时

2.线程的创建和使用(四种创建方式)

2.1 继承Thread类的方式

1)Thread类的特性:Ø每个线程都是通过某个特定Thread对象的run()方法来 完成操作的,经常 把run()方法的主体称为线程体;通过该Thread对象的start()方法来启动这个线程,而非直接调用run()。

2)Thread类构造器:

**Thread():**创建新的Thread对象
Thread(String threadname):创建线程并指定线程实例名
Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
**Thread(Runnable target, String name):**创建新的Thread对象

方式一:继承Thread类
①定义子类继承Thread类。
②子类中重写Thread类中的run方法。
③创建Thread子类对象,即创建了线程对象。
④调用线程对象start方法:启动线程,调用run方法。
注意点
1.如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
2.run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
3.想要启动多线程,必须调用start方法。
4.一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”。
a. Thread类的相关方法
void start(): 启动线程,并执行对象的run()方法
run(): 线程在被调度时执行的操作
String getName(): 返回线程的名称
void setName(String name):设置该线程名称
static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
static void yield():线程让步:暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法
join() :当某个程序执行流中调用其他线程的 join()方法时,调用线程被阻塞,直到join()方法加入的 join线程执行完为止(Ø低优先级的线程也可以获得执行)
static void sleep(long millis):(指定时间:毫秒)
令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后 重排队
stop(): 强制线程生命期结束,不推荐使用
boolean isAlive():返回boolean,判断线程是否还活着
2.2 实现Runnable接口的方式
方式二:实现Runnable接口
1)定义子类,实现Runnable接口。
2)子类中重写Runnable接口中的run方法。
1)通过Thread类含参构造器创建线程对象。
4)将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
5)调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
a. 继承方式和实现方式的联系与区别

public class Thread extends Object implements Runnable

Thread类其实是Runnable接口的一个实现类****

两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。

实现方式的好处:

避免了单继承的局限性

多个线程可以共享同一个接口实现类的对象,非常适合多个相同线 程来处理同一份资源。

继承方式:实现方式:
1.Java中类是单继承的,如果继承了Thread了,该类就不能再被其他类继承了1.Java类中可以多实现接口,此时该类可以继承其他类,并且可以实现其他接口。设计上更优雅。
2.从操作上分析,继承方式更简单,获取线程名字也简单2.从操作上分析,实现方式稍微复杂,获取线程名字也比较复杂,得使用Thread.currentThread()来获取当前线程的引用。
3.从多线程共享同一个资源上分析,继承方式不能做到3.从多线程共享同一个资源上分析,实现方式可以做到。
2.3 实现Callable接口的方式

与使用Runnable相比, Callable功能更强大些

Ø相比run()方法,可以有返回值

Ø方法可以抛出异常

Ø支持泛型的返回值

Ø需要借助FutureTask类,比如获取返回结果

lFuture接口

Ø可以对具体Runnable、Callable任务的执行结果进行取消、查询是 否完成、获取结果等。

ØFutrueTask是Futrue接口的唯一的实现类

ØFutureTask 同时实现了Runnable, Future接口。它既可以作为

Runnable被线程执行,又可以作为Future得到Callable的返回值

2.4 使用线程池

经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止

线程池相关API:ExecutorServiceExecutors

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
**线程的调度

1)调度策略:①时间片;②抢占式(高优先级的线程抢占cpu)

2)Java的调度方法:①同优先级线程组成先进先出队列(先到先服务),使用时间片策略;②对高优先级,使用优先调度的抢占式策略。

**线程的优先级

线程的优先级等级
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5
涉及的方法
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级
说明
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

**补充 线程的分类

Java中的线程分为两类:一种是守护线程,一种是用户线程。
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
守护线程是用来服务用户线程的,通过在start()方法前调用
thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
Java垃圾回收就是一个典型的守护线程。
若JVM中都是守护线程,当前JVM将退出。

3.线程的生命周期

JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类 及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态
1)新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建 状态
2)就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已
具备了运行的条件,只是没分配到CPU资源
3)运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线 程的操作和功能
4)阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中
止自己的执行,进入阻塞状态
5)死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.线程的同步

​ 举个栗子:火车站三个窗口售票(三个线程的同步)

class Ticket implements Runnable {  
    private int tick = 100;

	public void run() {  
        while (true) {
			if (tick > 0) {  
    				System.out.println(Thread.currentThread  ().getName() + "售出车票,tick号为:" +  tick--);
			} else
				break;
		}
	}
}

class TicketDemo {
	public static void main(String[] args) {

		Ticket t = new Ticket();

        Thread t1 = new Thread(t);  
        Thread t2 = new Thread(t);  
        Thread t3 = new Thread(t);  
        t1.setName("t1窗口");
        t2.setName("t2窗口");
        t3.setName("t3窗口");
        t1.start();
        t2.start();
        t3.start();
     }
}
//以上会出现最后三个线程抢一个票的情况,导致打印车票为1、0、-1的情况

private int tick = 100;  
public void run(){
	while(true){
		if(tick>0){  
            try{
				Thread.sleep(10);
			}catch(InterruptedException e){ 
                e.printStackTrace();
            }
		System.out.println(Thread.currentThread().getName()+"售出车票,tick号为:"+tick--);

} } }

1)多线程出现了安全问题
2)问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有 执行完,另一个线程参与进来执行。导致共享数据的错误。
3)解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以 参与执行。

Java对于多线程的安全问题提供了专业的解决方式:同步机制

//同步代码块:
synchronized (对象){
// 需要被同步的代码;
}
synchronized还可以放在方法声明中,表示整个方法为同步方法。
例如:
public synchronized void show (String name){.
}

4.1 线程的锁synchronized

同步锁机制:对于并发工作,你需要某种方式来防 止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。

synchronized的锁是什么?:

1)任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。

2)同步方法的锁:静态方法(类名.class)、非静态方法(this)

3)同步代码块:自己指定,很多时候也是指定为this或类名.class

注意:1)必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全。

2)一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方 法共用同一把锁(this),同步代码块(指定需谨慎)。

a>释放锁的操作

1)当前线程的同步方法、同步代码块执行结束。
2)当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、 该方法的继续执行。
3)当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
4)当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线 程暂停,并释放锁。

b>不会释放锁的操作

1)线程执行同步代码块或同步方法时,程序调用Thread.sleep()、
Thread.yield()方法暂停当前线程的执行

2)线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
应尽量避免使用suspend()和resume()来控制线程

4.2 单例设计模式懒汉式(线程安全)
class Singleton {
	private static Singleton instance = null;
	private Singleton(){}
	public static Singleton getInstance(){  
        if(instance==null){
			synchronized(Singleton.class){
				if(instance == null){
						instance=new Singleton();
				}	
            }	
        }
        return instance;
	}
}

public class SingletonTest{
	public static void main(String[] args){
		Singleton s1=Singleton.getInstance(); 
        Singleton s2=Singleton.getInstance();  
        System.out.println(s1==s2);
	}	
}


4.3 线程的死锁问题

死锁
->不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃
自己需要的同步资源,就形成了线程的死锁
->出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续

解决方法
—专门的算法、原则
—尽量减少同步资源的定义
—尽量避免嵌套同步

4.4 Lock锁

从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同
步锁对象来实现同步。同步锁使用Lock对象充当。

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象 加锁,线程开始访问共享资源之前应先获得Lock对象。

ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和 内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以 显式加锁、释放锁。

class A{
	private final ReentrantLock lock = new ReenTrantLock();  	 public void m(){
		lock.lock();
		try{
			//保证线程安全的代码;
		}
		finally{
			lock.unlock();
		}
	}
}
注意:如果同步代码有异常,要将unlock()写入finally语句块


4.5 synchronizedLock 的对比

1)Lock是显式锁手动开启和关闭锁,别忘记关闭锁),

​ synchronized是 隐式锁,出了作用域自动释放

2)Lock只有代码块锁,synchronized有代码块锁和方法锁

3)使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有
更好的扩展性(提供更多的子类)

优先使用顺序:
Lock --> 同步代码块(已经进入了方法体,分配了相应资源)–> 同步方法
(在方法体之外)

5.线程的通信

举个栗子:使用两个线程打印1-100**。线程1, 线程2 交替打印(后期代码放上)

5.1 wait() 与 notify() 和 notifyAll()

1)wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当 前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有 权后才能继续执行。
2)notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
3)notifyAll ():唤醒正在排队等待资源的所有线程结束等待。

这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁, 因此这三个方法只能在Object类中声明。

wait()方法

①在当前线程中调用方法: 对象名.wait()
②使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify
(或notifyAll) 为止。
③调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
④调用此方法后,当前线程将释放对象监控权 ,然后进入等待
⑤在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。

notify()/notifyAll()

①在当前线程中调用方法: 对象名.notify()

②功能:唤醒等待该对象监控权的一个/所有线程。

③调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)

七、常用类

1.字符串相关类(String、StringBuffer、StringBuilder)

1.1 String

String类:代表字符串。Java 程序中的所有字符串字面值(如 “abc” )都作 为此类的实例实现。
String是一个final类,代表不可变的字符序列。
–>字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改。
–>String对象的字符内容是存储在一个**字符数组value[]**中的。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
	/** The value is used for character storage. */
	private final char value[];

	/** Cache the hash code for the string */
	private int hash; // Default to 0


String字符串的内存存储过程:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

String对象的创建:

String str = "hello";

//本质上this.value = new char[0];
String	s1 = new String();

//this.value = original.value;
String	s2 = new String(String original);

//this.value = Arrays.copyOf(value, value.length);
String	s3 = new String(char[] a);

String	s4 = new String(char[] a,int startIndex,int count);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

String  s1  = "javaEE";
String  s2  = "javaEE";
String  s3  =  new String("javaEE");
String  s4  =  new String("javaEE");

System.out.println(s1 == s2);//true  
System.out.println(s1 == s3);//false  
System.out.println(s1 == s4);//false  
System.out.println(s3 == s4);//false


外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

**String字符串特性

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

结论:

常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。

只要其中有一个是变量,结果就在堆中

如果拼接的结果调用intern()方法,返回值就在常量池中

1.1.1 String常用方法
int length():返回字符串的长度: return value.length
char charAt(int index): 返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
int **compareTo(**String anotherString):比较两个字符串的大小
String substring(int beginIndex) : 返回一个新的字符串, 它是此字符串的从beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字 符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的
子字符串是否以指定前缀开始
boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列 时,返回 true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出 现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后 一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.1.2 String与基本数据类型转换

字符串 --> 基本数据类型、包装类
1)Integer包装类的public static int parseInt(String s):可以将由“数字”字 符组成的字符串转换为整型。
2)类似地,使用java.lang包中的Byte、Short、Long、Float、Double类调相应的类方法可以将由“数字”字符组成的字符串,转化为相应的基本数据类型。
基本数据类型、包装类–> 字符串
1)调用String类的public String valueOf(int n)可将int型转换为字符串
2)相应的valueOf(byte b)、valueOf(long l)、valueOf(float f)、valueOf(doubled)、valueOf(boolean b)可由参数的相应类型到字符串的转换

1.1.3 String与字符数组char[]转换

字符数组 -->字符串
1)String 类的构造器:String(char[]) 和 String(char[],int offset,int length) 分别用字符数组中的全部字符和部分字符创建字符串对象。

字符串 --> 字符数组
1)public char[] toCharArray()*:将字符串中的全部字符存放在一个字符数组中的方法。
2)public void **getChars(**int srcBegin, int srcEnd, char[] dst,
int dstBegin):提供了将指定索引范围内的字符串存放到数组中的方法。

1.1.4 String与字节数组byte[]转换

字节数组 -->字符串
1)String(byte[]):通过使用平台的默认字符集解码指定的 byte 数组,构 造一个新的 String。
2)String(byte[],int offset,int length) :用指定的字节数组的一部分, 即从数组起始位置offset开始取length个字节构造一个字符串对象。

字符串 --> 字节数组
1)public byte[] getBytes() :使用平台的默认字符集将此 String 编码为
byte 序列,并将结果存储到一个新的 byte 数组中。
1)public byte[] getBytes(String charsetName) :使用指定的字符集将 此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。

1.2 StringBuffer类

java.lang.StringBuffer代表可变的字符序列,JDK1.0中声明,可以对字符 串内容进行增删,此时不会产生新的对象。很多方法与String相同。
作为参数传递时,方法内部可以改变值

StringBuffer类不同于String,其对象必须使用构造器生成。有三个构造器:
StringBuffer():初始容量为16的字符串缓冲区
StringBuffer(int size):构造指定容量的字符串缓冲区
StringBuffer(String str):将内容初始化为指定字符串内容

StringBuffer常用方法:
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
StringBuffer delete(int start,int end):删除指定位置的内容
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
StringBuffer insert(int offset, xxx):在指定位置插入xxx StringBuffer reverse() :把当前字符序列逆转
public int indexOf(String str)
public String substring(int start,int end)
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)
1.3 StringBuilder类(三者对比)

lStringBuilderStringBuffer 非常类似,均代表可变的字符序列,而且 提供相关功能的方法也一样

l面试题:对比StringStringBufferStringBuilder

ØString(JDK1.0):不可变字符序列

ØStringBuffer(JDK1.0):可变字符序列、效率低、线程安全

ØStringBuilder(JDK 5.0):可变字符序列、效率高、线程不安全

注意:作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder 会改变其值。

2.jdk 8 之前的日期时间API

2.1 java.lang.System

System类提供的public static long currentTimeMillis()用来返回当前时 间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
此方法适于计算时间差。

计算世界时间的主要标准有:
UTC(Coordinated Universal Time)
GMT(Greenwich Mean Time)
CST(Central Standard Time)

2.2 java.util.Date类

表示特定的瞬间,精确到毫秒
构造器:
Date():使用无参构造器创建的对象可以获取本地当前时间。
Date(long date)
常用方法
getTime():返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象
表示的毫秒数。
toString():把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz是时间标准。其它很多方法都过时了。

2.3 java.text.SimpleDateFormate类

Date类的API不易于国际化,大部分被废弃了,java.text.SimpleDateFormat
类是一个不与语言环境有关的方式来格式化和解析日期的具体类。
它允许进行格式化:日期文本、解析:文本日期
格式化:
SimpleDateFormat() :默认的模式和语言环境创建对象
public SimpleDateFormat(String pattern):该构造方法可以用参数pattern
指定的格式创建一个对象,该对象调用:
public String format(Date date):方法格式化时间对象date
解析:
public Date parse(String source):从给定字符串的开始解析文本,以生成
一个日期。

Date date = new Date(); // 产生一个Date实例
// 产生一个formater格式化的实例
SimpleDateFormat formater = new SimpleDateFormat();  System.out.println(formater.format(date));// 打印输出默认的格式
SimpleDateFormat formater2 = new SimpleDateFormat("yyyy 年 MM月 dd 日 EEE  HH:mm:ss");
System.out.println(formater2.format(date)); 
try {
// 实例化一个指定的格式对象
Date date2 = formater2.parse("2008年08月08日 星期一 08:08:08");
// 将指定的日期解析后格式化按指定的格式输出
System.out.println(date2.toString());
} catch (ParseException e) {
e.printStackTrace();
}

2.4 java.util.Calendar类

Calendar是一个抽象基类,主用用于完成日期字段之间相互操作的功能。
获取Calendar实例的方法
使用Calendar.getInstance()方法
调用它的子类GregorianCalendar的构造器。
一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想 要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、 MINUTE、SECOND
public void set(int field,int value)
public void add(int field,int amount)
public final Date getTime()
public final void setTime(Date date)
注意:
获取月份时:一月是0,二月是1,以此类推,12月是11
获取星期时:周日是1,周二是2 , 。。。。周六是7

3.jdk 8 新的日期时间API

JDK 1.0中包含了 一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用 了。而Calendar并不比Date好多少。它们面临的问题是:
1)可变性:像日期和时间这样的类应该是不可变的。
2)偏移性:Date中的年份是从1900开始的,而月份都从0开始。

3)格式化:格式化只对Date有用,Calendar则不行。
此外,它们也不是线程安全的;不能处理闰秒等。

总结:对日期和时间的操作一直是Java程序员最痛苦的地方之一。

LocalDate、LocalTime、LocalDateTime 类是其中较重要的几个类,它们的实例 是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。 它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区 相关的信息。
LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储 生日、纪念日等日期。
LocalTime表示一个时间,而不是日期。
LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一。

注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示 法,也就是公历。

4.Java比较器(Comparable接口、Comparator)

4.1 自然排序Comparable

1)Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称 为类的自然排序。
2)实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即 通过 compareTo(Object obj) 方法的返回值来比较大小。如果当前对象this大 于形参对象obj,则返回正整数,如果当前对象this小于形参对象obj,则返回 负整数,如果当前对象this等于形参对象obj,则返回零。
3)实现Comparable接口的对象列表(和数组)可以通过 Collections.sort 或 Arrays.sort进行自动排序。实现此接口的对象可以用作有序映射中的键或有 序集合中的元素,无需指定比较器。
4)对于类 C 的每一个 e1 和 e2 来说,当且仅当 e1.compareTo(e2) == 0 与 e1.equals(e2) 具有相同的 boolean 值时,类 C 的自然排序才叫做与 equals 一致。建议(虽然不是必需的)最好使自然排序与 equals 一致。

Comparable 的典型实现:(默认都是从小到大排列的)
String:按照字符串中字符的Unicode值进行比较
Character:按照字符的Unicode值来进行比较
数值类型对应的包装类以及BigInteger、BigDecimal:按照它们对应的数值
大小进行比较
Boolean:true 对应的包装类实例大于 false 对应的包装类实例
Date、Time等:后面的日期时间比前面的日期时间大
class Goods implements Comparable {  private String name;
private double price;

//按照价格,比较商品的大小  @Override
public int compareTo(Object o) {
if(o instanceof Goods) {
Goods other = (Goods) o;
if (this.price > other.price) {  return 1;
} else if (this.price < other.price) {
return -1;
}
return 0;
}
throw new RuntimeException("输入的数据类型不一致");
}
//构造器、getter、setter、toString()方法略
}

public class ComparableTest{
public static void main(String[] args) {

Goods[] all = new Goods[3];
Arrays.sort(all);
all[0]=new Coods("《红楼梦》",100);
all[1]=new Coods("《西游记》",50);
all[2]=new Coods("《三国演义》",120);
System.out.println(Arrays.toString(all));

}



4.2 定制排序Comparator
当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码, 或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那 么可以考虑使用 Comparator 的对象来排序,强行对多个对象进行整体排 序的比较。
重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返 回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示 o1小于o2。
可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort), 从而允许在排序顺序上实现精确控制。
还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的
顺序,或者为那些没有自然顺序的对象 collection 提供排序。
Goods[] all = new Goods[3];
all[0]=new Coods("《红楼梦》",100);
all[1]=new Coods("《西游记》",50);
all[2]=new Coods("《三国演义》",120);
Arrays.sort(all, new Comparator() {

@Override
public int compare(Object o1, Object o2) {
    Goods g1 = (Goods) o1;  
    Goods g2 = (Goods) o2;

return g1.getName().compareTo(g2.getName());
}
});

System.out.println(Arrays.toString(all));


5.System类

System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。 该类位于java.lang包。
由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实 例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便 的进行调用。
成员变量
System类内部包含in、out和err三个成员变量,分别代表标准输入流
(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
成员方法
native long currentTimeMillis():
该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时
间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。
void exit(int status):
该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表
异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。

void gc():
该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则 取决于系统中垃圾回收算法的实现以及系统执行时的情况。
String getProperty(String key):
该方法的作用是获得系统中属性名为key的属性对应的值。

6.Math类

java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回
值类型一般为double型。
abs 绝对值 acos,asin,atan,cos,sin,tan 三角函数 sqrt 平方根
pow(double a,doble b) a的b次幂 log 自然对数
exp e为底指数
max(double a,double b)
min(double a,double b)
random() 返回0.0到1.0的随机数
long round(double a) double型数据a转换为long型(四舍五入)

toDegrees(double angrad):弧度**—>角度
toRadians(double angdeg):角度
—>**弧度

7.BigInteger与BigDecimal

Integer类作为int的包装类,能存储的最大整型值为231-1,Long类也是有限的, 最大为263-1。如果要表示再大的整数,不管是基本数据类型还是他们的包装类 都无能为力,更不用说进行运算了。

java.math包的BigInteger可以表示不可变的任意精度的整数。BigInteger 提供
所有 Java 的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法。 另外,BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、 位操作以及一些其他操作。

构造器
BigInteger(String val):根据字符串构建BigInteger对象

八、泛型、枚举与注解

1.枚举类的使用

使用情况:1)类的对象只有有限个,确定的。

​ 2)当需要定义一组常量时,强烈建议使用枚举类。

枚举类的实现:
JDK1.5之前需要自定义枚举类。
JDK 1.5 新增的 enum 关键字用于定义枚举类。
若枚举只有一个对象, 则可以作为一种单例模式的实现方式。

枚举类的属性:
1)枚举类对象的属性不应允许被改动, 所以应该使用 private final 修饰。
2)枚举类的使用 private final 修饰的属性应该在构造器中为其赋值。
3)若枚举类显式的定义了带参数的构造器, 则在列出枚举值时也必须对应的
传入参数。

1.1 自定义枚举类:

1)私有化类的构造器,保证不能在类的外部创建其对象
2)在类的内部创建枚举类的实例。声明为:public static final
3)对象如果有实例变量,应该声明为private final,并在构造器中初始化

class Season{
	private final String SEASONNAME;//季节的名称 
    private final String SEASONDESC;//季节的描述
	private Season(String seasonName,String seasonDesc){  			this.SEASONNAME = seasonName;
		this.SEASONDESC  = seasonDesc;
	}
	public static final Season SPRING = new Season("春天", "春暖花开");  
    public static final Season SUMMER = new Season("夏天", "夏日炎炎");  
    public static final Season AUTUMN = new Season("秋天", "秋高气爽");  
    public static final Season WINTER = new Season("冬天", "白雪皑皑");
}


1.2 使用enum定义枚举类

使用说明
使用 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类的主要方法:

values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的
枚举值。
valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符 串必须是枚举类对象的“名字”。如不是,会有运行时异常: IllegalArgumentException。
toString():返回当前枚举类对象常量的名称

2.注解(Annotation)

从jdk5.0开始,Java增加了对元数据(MetaData) 的支持, 也就是注解。

Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加 载, 运行时被读取, 并执行相应的处理。Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方 法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在 Annotation 的 “name=value” 对中。

框架 = 注解 + 反射 + 设计模式。

常见注解使用:

示例一:生成文档相关的注解

示例二:在编译时进行格式检查(JDK内置的三个基本注解)。

@Override: 限定重写父类方法, 该注解只能用于方法。@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为
所修饰的结构危险或存在更好的选择。@SuppressWarnings: 抑制编译器警告

示例三:跟踪代码依赖性,实现替代配置文件功能

自定义注解:

定义新的Annotation 类型使用 @interface 关键字
自定义注解自动继承了java.lang.annotation.Annotation接口
Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其
方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能 是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、 以上所有类型的数组。
可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始 值可使用 default 关键字
如果只有一个参数成员,建议使用参数名为value
如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认 值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value, 可以省略“value=”
没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数
据Annotation
注意:自定义注解必须配上注解的信息处理流程才有意义

3.泛型

3.1 泛型的概念

所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类 型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如, 继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实 际的类型参数,也称为类型实参)。

在集合中有泛型时,只有指定类型才可以添加到集合中:保证类型安全,读取出来的对象不需要强转:便捷。

3.2 在集合在使用泛型
ArrayList<Integer> list = new ArrayList<>();//类型推断

list.add(78);
list.add(88);
list.add(77);
list.add(66);

//遍历方式一:
//for(Integer i : list){
//不需要强转
//System.out.println(i);
//}

//遍历方式二:
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}

Map<String,Integer> map = new HashMap<String,Integer>();

map.put("Tom1",34);
map.put("Tom2",44);
map.put("Tom3",33);
map.put("Tom4",32);
//添加失败
//map.put(33, "Tom");

Set<Entry<String,Integer>> entrySet = map.entrySet();

Iterator<Entry<String,Integer>> iterator =	entrySet.iterator();

while(iterator.hasNext()){
Entry<String,Integer> entry = iterator.next();
System.out.println(entry.getKey() + "--->" + entry.getValue());
}


3.3 自定义泛型结构

1.泛型的声明:interface List 和 class GenTest<K,V>
其中,T,K,V不代表值,而是表示类型。这里使用任意字母都可以。常用T表示,是Type的缩写。

2.泛型的实例化:

一定要在类名后面指定类型参数的值(类型)。如: List strList = new ArrayList(); Iterator iterator = customers.iterator();
T只能是类,不能用基本数据类型填充。但可以使用包装类填充
把一个集合中的内容限制为一个特定的数据类型,这就是generics背后
的核心思想。

泛型类和泛型接口泛型类和泛型接口
1.泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>7.jdk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();
2.泛型类的构造器如下:public GenericClass(){}。 而下面是错误的:public GenericClass(){}8.泛型的指定中不能使用基本数据类型,可以使用包装类替换。
3.实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致9.在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态 属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法 中不能使用类的泛型。
4.泛型不同的引用不能相互赋值。
>尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。
10.异常类不能是泛型的
5.泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价 于Object。经验:泛型要使用一路都用。要不用,一路都不要用。11.不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];
参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
6.如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。12.父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
子类不保留父类的泛型:按需实现
没有类型 擦除
具体类型
子类保留父类的泛型:泛型子类
全部保留
部分保留
结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自 己的泛型

泛型方法

方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型 方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
泛型方法的格式:
[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
泛型方法声明泛型时也可以指定上限(在12.5中讲)

public static <T> void fromArrayToCollection(T[] a, Collection<T> c) {        for (T o : a) {
		c.add(o);
	 }
}

public static void main(String[] args) {
	Object[] ao = new Object[100];  
    Collection<Object> co = new ArrayList<Object>();  					     fromArrayToCollection(ao, co);

	String[] sa = new String[20];  
    Collection<String> cs = new ArrayList<>();  	          			     fromArrayToCollection(sa, cs);

	Collection<Double> cd = new ArrayList<>();
	// 下面代码中T是Double类,但sa是String类型,编译错误。
	// fromArrayToCollection(sa, cd);
	// 下面代码中T是Object类型,sa是String类型,可以赋值成功。
	fromArrayToCollection(sa, co);
}

//..............................................................
class Creature{}
class Person extends Creature{}  
class Man extends Person{}  class PersonTest {
	public static <T extends Person> void test(T t){
	System.out.println(t);
	}

	public static void main(String[] args) {
		test(new Person());
		test(new Man());
		//The method test(T) in the type PersonTest is not
		//applicable for the arguments (Creature)  
        test(new Creature());
	}
}


3.4 泛型在继承上的体现

如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的 类或接口,G并不是G的子类型!

比如:String是Object的子类,但是List并不是List
的子类。

public void testGenericAndSubClass() {
Person[] persons = null;
Man[] mans = null;
//  而  Person[]	是  Man[]	的父类.  persons = mans;

Person p = mans[0];

// 在泛型的集合上
List<Person> personList = null;
List<Man> manList = null;
// personList	= manList;(报错)
}


3.5 通配符的使用

使用类型通配符:?
比如:List<?> ,Map<?,?>
List<?>是List、List等各种泛型List的父类。

读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型 是什么,它包含的都是Object。

写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中 添加对象。
唯一的例外是null,它是所有类型的成员。

将任意元素加入到其中不是类型安全的: Collection<?> c = new ArrayList(); c.add(new Object()); // 编译时错误
因为我们不知道c的元素类型,我们不能向其中添加对象。add方法有类型参数E作为集 合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知
道那是什么类型,所以我们无法传任何东西进去。

唯一的例外的是null,它是所有类型的成员。

另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的 类型,但是我们知道,它总是一个Object。

public static void main(String[] args) {
	List<?> list = null;
	list =  new   ArrayList<String>();
	list =  new   ArrayList<Double>();
	// list.add(3);//编译不通过 list.add(null);

	List<String> l1 = new ArrayList<String>();  			     List<Integer> l2 = new ArrayList<Integer>();  l1.add("尚硅谷");
	l2.add(15);
	read(l1);
	read(l2);
}

public static void read(List<?> list) {  
    for (Object o : list) {
	System.out.println(o);
	}
}

//注意点1:编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?
public static <?> void test(ArrayList<?> list){

}
//注意点2:编译错误:不能用在泛型类的声明上
class GenericTypeClass<?>{

}
//注意点3:编译错误:不能用在创建对象上,右边属于创建集合对象  ArrayList<?> list2 = new ArrayList<?>();


有限制的通配符:

<?>

允许所有泛型的引用调用
1)通配符指定上限
上限extends:使用时指定的类型必须是继承某个类,或实现某个接口,即<=
2)通配符指定下限
下限super:使用时指定的类型不能小于操作的类,即>=
举例:
①<? extends Number> (无穷小 , Number]
只允许泛型为Number及Number子类的引用调用)

②<? super Number> [Number , 无穷大)
只允许泛型为Number及Number父类的引用调用

③<? extends Comparable>
只允许泛型为实现Comparable接口的实现类的引用调用

泛型嵌套:

public static void main(String[] args) {
HashMap<String, ArrayList<Citizen>> map = new HashMap<String, ArrayList<Citizen>>();
ArrayList<Citizen> list = new ArrayList<Citizen>();
list.add(new Citizen("刘恺威"));  list.add(new Citizen(" 杨 幂 "));  list.add(new Citizen("小糯米"));  map.put("刘恺威", list);

Set<Entry<String, ArrayList<Citizen>>> entrySet = map.entrySet();  Iterator<Entry<String, ArrayList<Citizen>>> iterator = entrySet.iterator();  while (iterator.hasNext()) {
Entry<String, ArrayList<Citizen>> entry = iterator.next();  String key = entry.getKey();
ArrayList<Citizen> value = entry.getValue();  System.out.println(" 户 主 :" + key);  System.out.println("家庭成员:" + value);
}
}

九、集合

1.Java集合概述

为什么要有集合?一方面, 面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用Array存储对象方面具有一些弊端,而Java集合就像一种容器,可以动态地把多个对象的引用放入容器中。

Java 集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组
数组在内存存储方面的特点:
数组初始化以后,长度就确定了。
数组声明的类型,就决定了进行元素初始化时的类型 Object[]
数组在存储数据方面的弊端:
数组初始化以后,长度就不可变了,不便于扩展
数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。
同时无法直接获取存储元素的个数
数组存储的数据是有序的、可以重复的。---->存储数据的特点单一

Java 集合可分为 Collection 和 Map 两种体系
1)Collection接口:单列数据,定义了存取一组对象的方法的集合
List:元素有序、可重复的集合
Set:元素无序、不可重复的集合
2)Map接口:双列数据,保存具有映射关系“key-value对”的集合)

2.Collection接口方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法 既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。

JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)
实现。在 Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都 当成 Object 类型处理;从 JDK 5.0 增加了泛型以后,Java 集合可以记住容 器中对象的数据类型。

接口方法:
1、添加:add(Object obj)
addAll(Collection coll)
2、获取有效元素的个数:int size()
3、清空集合:void clear()
4、是否是空集合:boolean isEmpty()
5、是否包含某个元素:
boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象
boolean containsAll(Collection c):也是调用元素的equals方法来比 较的。拿两个集合的元素挨个比较。
6、删除
boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
boolean removeAll(Collection coll):取当前集合的差集
7、取两个集合的交集:boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c
8、集合是否相等:boolean equals(Object obj)
9、转成对象数组:Object[] toArray()
10、获取集合对象的哈希值:hashCode()
11、遍历:iterator():返回迭代器对象,用于集合遍历

3.Iterator迭代器接口

Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection集合中的元素。

Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所 有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了 Iterator接口的对象。
Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建Iterator 对象,则必须有一个被迭代的集合。
集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合 的第一个元素之前。

//hasNext():判断是否还有下一个元素
while(iterator.hasNext()){
//next():①指针下移 ②将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}
//删除方法   
Iterator iter = coll.iterator();//回到起点
while(iter.hasNext()){
Object obj = iter.next();
if(obj.equals("Tom")){
iter.remove();
}
}
//注意:
//Iterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方 法,不是集合对象的remove方法。
//如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。


使用foreach循环遍历集合元素

Java 5.0 提供了 foreach 循环迭代访问 Collection和数组。
遍历操作不需获取Collection或数组的长度,无需使用索引访问元素。
遍历集合的底层调用Iterator完成操作。
foreach还可以用来遍历数组。

list<person> persons=.....;
for(Person p:persons){
    //........
}

4.Collection子接口一:List

List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据 序号存取容器中的元素。
List接口的实现类常用的有:①ArrayList、②LinkedList、③Vector

List接口方法
List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引来操作集合元素的方法。
void add(int index, Object ele):在index位置插入ele元素
boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
Object get(int index):获取指定index位置的元素
int indexOf(Object obj):返回obj在集合中首次出现的位置
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置Object remove(int index):移除指定index位置的元素,并返回此元素Object set(int index, Object ele):设置指定index位置元素为ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
4.1 List实现类之一:ArrayList

ArrayList 是 List 接口的典型实现类、主要实现类
本质上,ArrayList是对象引用的一个”变长”数组
ArrayList的JDK1.8之前与之后的实现区别?
JDK1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组
JDK1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元 素时再创建一个始容量为10的数组
Arrays.asList(…) 方法返回的 List 集合,既不是 ArrayList 实例,也不是
Vector 实例。 Arrays.asList(…) 返回值是一个固定长度的 List 集合

public void testListRemove() {  
    List list = new ArrayList();  list.add(1);
	list.add(2);
	list.add(3);  
    updateList(list);  
    System.out.println(list);//
}

private static void updateList(List list) {
	list.remove(2);
}


4.2 List实现类之二:LinkedList

LinkedList:双向链表,内部没有声明数组,而是定义了Node类型的first和last, 用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基
本结构。Node除了保存数据,还定义了两个变量:prev变量记录前一个元素的位置,next变量记录下一个元素的位置

对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高

新增方法:
void addFirst(Object obj)
void addLast(Object obj)
Object getFirst()
Object getLast()
Object removeFirst()
Object removeLast()

4.3 List实现类之三:Vector

Vector 是一个古老的集合,JDK1.0就有了。大多数操作与ArrayList
相同,区别之处在于Vector是线程安全的。
在各种list中,最好把ArrayList作为缺省选择。当插入、删除频繁时,
使用LinkedList;Vector总是比ArrayList慢,所以尽量避免使用。
新增方法:
void addElement(Object obj)
void insertElementAt(Object obj,int index)
void setElementAt(Object obj,int index)
void removeElement(Object obj)
void removeAllElements()

题:
请问ArrayList/LinkedList/Vector的异同?谈谈你的理解?ArrayList底层是什么?扩容机制?Vector和ArrayList的最大区别?
ArrayList和LinkedList的异同
二者都线程不安全,相对线程安全的Vector,执行效率高。<br/>此外,ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于 随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。对于新增 和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。
ArrayList和Vector的区别
Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用 ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大 小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。

5.Collection子接口之二:Set

Set接口是Collection的子接口,set接口没有提供额外的方法
Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个
Set 集合中,则添加操作失败。
Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法

5.1 Set实现类之一:HashSet

HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。
HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
HashSet 具有以下特点
1)不能保证元素的排列顺序
2)HashSet 不是线程安全的
3)集合元素可以是 null
**@)**HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。
**@
)**对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。

向HashSet中添加元素的过程

当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法 来得到该对象的 hashCode 值,然后根据 hashCode 值,通过某种散列函数决定该对象 在 HashSet 底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在 数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布, 该散列函数设计的越好)
如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果 为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了, 那么会通过链表的方式继续链接。
如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功

重写hashCode()方法的基本原则

1)在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
2)当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode()
方法的返回值也应相等。
3)对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

重写equals()方法的基本原则

以自定义的Customer类为例,何时需要重写equals()?

1)当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候,总是
要改写hashCode(),根据一个类的equals方法(改写后),两个截然不
同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode()方法, 它们仅仅是两个对象。

2)因此,违反了“相等的对象必须具有相等的散列码”。

3)结论:复写equals方法的时候一般都需要同时复写hashCode方法。通
常参与计算hashCode的对象的属性也应该参与到equals()中进行计算。


了解:为什么用Eclipse/IDEA复写hashCode方法,有31这个数字?

选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)

并且31只占用5bits,相乘造成数据溢出的概率较小。

31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化。(提高算法效率)

31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结 果只能被素数本身和被乘数还有1来整除!(减少冲突)


5.2 Set实现类之二:LinkedHashSet

LinkedHashSet 是 HashSet 的子类
LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置, 但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入 顺序保存的。
LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全 部元素时有很好的性能。
LinkedHashSet 不允许集合元素重复。

5.3 Set实现类之三:TreeSet

TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。
TreeSet底层使用红黑树结构存储数据
新增的方法如下: (了解)
Comparator comparator()
Object first()
Object last()
Object lower(Object e)
Object higher(Object e)
SortedSet subSet(fromElement, toElement)
SortedSet headSet(toElement)
SortedSet tailSet(fromElement)
TreeSet 两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序

TreeSet和后面要讲的TreeMap采用红黑树的存储结构,特点:有序,查询速度比List快。

5.3.1 排序一:自然排序
自然排序:TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元 素之间的大小关系,然后将集合元素按升序(默认情况)排列
如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable接口
实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过compareTo(Object obj) 方法的返回值来比较大小。
Comparable 的典型实现:
BigDecimal、BigInteger 以及所有的数值型对应的包装类:按它们对应的数值大小 进行比较
Character:按字符的 unicode值来进行比较
Boolean:true 对应的包装类实例大于 false 对应的包装类实例
String:按字符串中字符的 unicode 值进行比较
Date、Time:后边的时间、日期比前面的时间、日期大
**1)向TreeSet 中添加元素时,**只有第一个元素无须比较compareTo()方法,后面添 加的所有元素都会调用compareTo()方法进行比较。
2)因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同 一个类的对象
**3)**对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通 过 compareTo(Object obj) 方法比较返回值。
**4)**当需要把一个对象放入 TreeSet 中,重写该对象对应的 equals() 方法时,应保 证该方法与 compareTo(Object obj) 方法有一致的结果:如果两个对象通过 equals() 方法比较返回 true,则通过 compareTo(Object obj) 方法比较应返回 0。 否则,让人难以理解。
5.3.2 排序二:定制排序
1)TreeSet的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没 有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照 其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator接口来 实现。需要重写compare(T o1,T o2)方法。
2)利用int compare(T o1,T o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
3)要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构 造器。
4)此时,仍然只能向TreeSet中添加类型相同的对象。否则发生ClassCastException异 常。
5)使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。

6.Map接口

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

6.1 Map接口概述

①Map与Collection并列存在。用于保存具有映射关系的数据:key-value
②Map 中 的 key 和 value 都可以是任何引用类型的数据
③Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应
的类,须重写hashCode()和equals()方法
④常用String类作为Map的“键”
⑤key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到
唯一的、确定的 value
⑥Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和 Properties。其中,HashMap是 Map 接口使用频率最高的实现类。


外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

6.2 Map接口常用方法
添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元视图操作的方法
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
Map map = new HashMap();
//map.put(..,..)省略 
System.out.println("map的所有key:");  
Set keys = map.keySet();// HashSet  
for (Object key : keys) {
	System.out.println(key + "->" + map.get(key));
}
System.out.println("map的所有的value:");  
Collection values = map.values();  
Iterator iter = values.iterator();  
while (iter.hasNext()) {
	System.out.println(iter.next());
}
System.out.println("map所有的映射关系:");
// 映射关系的类型是Map.Entry类型,它是Map接口的内部接口
Set mappings = map.entrySet();
for (Object mapping : mappings) {
	Map.Entry entry = (Map.Entry) mapping;
	System.out.println("key 是 :" + entry.getKey() + ",value 是 :" + entry.getValue());
}


6.3 Map实现类之一:HashMap

》HashMap是 Map 接口使用频率最高的实现类。
》允许使用null键和null值,与HashSet一样,不保证映射的顺序。
》所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和hashCode()
》所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()
》一个key-value构成一个entry
》所有的entry构成的集合是Set:无序的、不可重复的
》HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true, hashCode 值也相等。
》HashMap 判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true。

HashMap的存储结构

JDK 7及以前版本:HashMap是数组+链表结构(即为链地址法)

JDK 8版本发布以后:HashMap是数组+链表+红黑树实现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

关于两个版本具体存储结构描述在第十一章PPT,这里不想写了。。。

6.4 Map实现类之二:LinkedHashMap

1)LinkedHashMap 是 HashMap 的子类
2)在HashMap存储结构的基础上,使用了一对双向链表来记录添加 元素的顺序
3)与LinkedHashSet类似,LinkedHashMap 可以维护 Map 的迭代 顺序:迭代顺序与 Key-Value 对的插入顺序一致

HashMap中的内部类:Node

static class Node<K,V> implements Map.Entry<K,V> {
	final int hash;
	final K key;
	V value;  
    Node<K,V> next;
}


LinkedHashMap中的内部类:Entry

static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
	Entry(int hash, K key, V value, Node<K,V> next) {
		super(hash, key, value, next);
	}
}

6.5 Map实现类之三:TreeMap

1)TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。
TreeMap 可以保证所有的 Key-Value 对处于有序状态。
2)TreeSet底层使用红黑树结构存储数据
3)TreeMap 的 Key 的排序:
自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有 的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
4)TreeMap判断两个key相等的标准:两个key通过compareTo()方法或
者compare()方法返回0。

6.6 Map实现类之五:Properties

1)Properties 类是 Hashtable 的子类,该对象用于处理属性文件
2)由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key
和 value 都是字符串类型
3)存取数据时,建议使用setProperty(String key,String value)方法和
getProperty(String key)方法。

Properties pros = new Properties();
pros.load(new FileInputStream("jdbc.properties"));  
String user = pros.getProperty("user");  System.out.println(user);


7.Collections工具类

1)Collections 是一个操作 Set、List 和 Map 等集合的工具类(操作数组的工具类是Arrays)
2)Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
排序操作:(均为static方法)
reverse(List):反转 List 中元素的顺序
**shuffle(List):**对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
**swap(List,int, int):**将指定 list 集合中的 i 处元素和 j 处元素进行交换

查找、替换
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值

Collections类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集 合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全
问题

十、IO流

1.File类的使用

java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关
File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。
如果需要访问文件内容本身,则需要使用输入/输出流。
想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对 象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
File对象可以作为参数传递给流的构造器.

常用构造器:
public File(String pathname)
以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。
绝对路径:是一个固定的路径,从盘符开始
相对路径:是相对于某个位置开始
public File(String parent,String child)
以parent为父路径,child为子路径创建File对象。
public File(File parent,String child)
根据一个父File对象和子文件路径创建File对象

路径分隔符:路径中的每级目录之间用一个路径分隔符隔开。
路径分隔符和系统有关:
windows和DOS系统默认使用“\”来表示
UNIX和URL使用“/”来表示
Java程序支持跨平台运行,因此路径分隔符要慎用。
为了解决这个隐患,File类提供了一个常量:
public static final String separator。根据操作系统,动态的提供分隔符。
举例:

File file1 = new File("d:\\atguigu\\info.txt");
File file2 = new File("d:" + File.separator + "atguigu" + File.separator + "info.txt");
File file3 = new File("d:/atguigu");

常用方法:


File类的获取功能
public String getAbsolutePath():获取绝对路径
public String getPath() :获取路径
public String getName() :获取名称
public String getParent():获取上层文件目录路径。若无,返回null
public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified() :获取最后一次的修改时间,毫秒值

public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
File类的重命名功能
public boolean renameTo(File dest):把文件重命名为指定的文件路径

File类的判断功能
public boolean isDirectory():判断是否是文件目录
public boolean isFile() :判断是否是文件
public boolean exists() :判断是否存在
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏

File类的创建功能
public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。 如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建
注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目 路径下。

File类的删除功能
public boolean delete():删除文件或者文件夹
删除注意事项:
Java中的删除不走回收站。
要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录


File dir1 = new File("D:/IOTest/dir1");
if (!dir1.exists()) { // 如果D:/IOTest/dir1不存在,就创建为目录
dir1.mkdir();
}
// 创建以dir1为父目录,名为"dir2"的File对象
File dir2 = new File(dir1, "dir2");
if (!dir2.exists()) { // 如果还不存在,就创建为目录
dir2.mkdirs();
}
File dir4 = new File(dir1, "dir3/dir4");  if (!dir4.exists()) {
dir4.mkdirs();
}
// 创建以dir2为父目录,名为"test.txt"的File对象
File file = new File(dir2, "test.txt");
if (!file.exists()) { // 如果还不存在,就创建为文件
file.createNewFile();
}

2.IO流原理以及流的分类

2.1 IO原理

I/O是Input/Output的缩写, I/O技术是非常实用的技术,用于
处理设备之间的数据传输。如读/写文件,网络通讯等。

Java程序中,对于数据的输入/输出操作以“流(stream)” 的
方式进行。

java.io包下提供了各种“流”类和接口,用以获取不同种类的 数据,并通过标准的方法输入或输出数据。

输入input:读取外部数据(磁 盘、光盘等存储设备的数据)到 程序(内存)中。输出output:将程序(内存) 数据输出到磁盘、光盘等存储设 备中。

2.2 流的分类

按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
按数据流的流向不同分为:输入流,输出流
按流的角色的不同分为:节点流,处理流

Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个 抽象基类派生的。由这四个类派生出来的子类名称都是以其父类名作为子类名后缀

(抽象基类)字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.3 节点流和处理流

节点流:直接从数据源或目的地读写数据

处理流:不直接连接到数据源或目的地,而是“连接”在已存 在的流(节点流或处理流)之上,通过对数据的处理为程序提 供更为强大的读写功能。

2.4 输入流InputStream & Reader

●InputStream 和 Reader 是所有输入流的基类。

InputStream(典型实现:FileInputStream

int read(): 从输入流中读取数据的下一个字节。返回 0 到 255 范围内的int 字节值。如果因 为已经到达流末尾而没有可用的字节,则返回值-1。
i**nt read(byte[] b)😗*从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已 经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取 的字节数。
**int read(byte[] b, int off, int len)😗*将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取
的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于 文件末尾而没有可用的字节,则返回值-1。

Reader(典型实现:FileReader
int read(): 读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个
字节的Unicode码),如果已到达流的末尾,则返回 -1

int read(char [] c) : 将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数
int read(char [] c, int off, int len): 将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字 符。如果已到达流的末尾,则返回-1。否则返回本次读取的字符数。
●程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资 源,所以应该显式关闭文件 IO 资源
●FileInputStream 从文件系统中的某个文件中获得输入字节。FileInputStream用于读取非文本数据之类的原始字节流。要读取字符流,需要使用 FileReader

2.5 输出流OutputStream&Writer

●OutputStream 和 Writer 也非常相似:
void write(int b/int c);
void write(byte[] b/char[] cbuf);
void write(byte[] b/char[] buff, int off, int len);
void flush();
void close(); 需要先刷新,再关闭此流
●因为字符流直接以字符作为操作单位,所以 Writer 可以用字符串来替换字符数组, 即以 String 对象作为参数
void write(String str);
void write(String str, int off, int len);
●FileOutputStream 从文件系统中的某个文件中获得输出字节。FileOutputStream 用于写出非文本数据之类的原始字节流。要写出字符流,需要使用 FileWriter

OutputStream

●void write(int b):将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写 入的字节是参数b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255范围的。
●void write(byte[] b)
将b.length 个字节从指定的byte 数组写入此输出流。write(b) 的常规协定是:应该 与调用write(b, 0, b.length) 的效果完全相同。
●void write(byte[] b,int off,int len)
将指定byte 数组中从偏移量 off 开始的len 个字节写入此输出流。
●public void flush()throws IOException
刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立即写入它们预期的目标。
●public void close() throws IOException
关闭此输出流并释放与该流关联的所有系统资源。

⭕Writer

●void write(int c)
写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即写入0 到 65535 之间的Unicode码。
●void write(char[] cbuf)
写入字符数组。
●void write(char[] cbuf,int off,int len)
写入字符数组的某一部分。从off开始,写入len个字符
●void write(String str)
写入字符串。
●void write(String str,int off,int len)
写入字符串的某一部分。
●void flush()
刷新该流的缓冲,则立即将它们写入预期目标。
●public void close() throws IOException
关闭此输出流并释放与该流关联的所有系统资源

3.节点流(文件流)

读取文件:


1.建立一个流对象,将已存在的一个文件加载进流。
FileReader fr = new FileReader(new File(“Test.txt”));

2.创建一个临时存放数据的数组。
char[] ch = new char[1024];

3.调用流对象的读取方法将流中的数据读入到数组中。
fr.read(ch);

4.关闭资源。
fr.close();


FileReader fr = null;  
try {
	fr = new FileReader(new File("c:\\test.txt"));
	char[] buf = new char[1024];  
    int len;
	while ((len = fr.read(buf)) != -1) {
		System.out.print(new String(buf, 0, len));
	}
} catch (IOException e) {
	System.out.println("read-Exception :" + e.getMessage());
} finally {
	if (fr != null) {  
        try {
			fr.close();
		} catch (IOException e) {
			System.out.println("close-Exception :" + e.getMessage());
		}
    }
}

写入文件:


1.创建流对象,建立数据存放文件
FileWriter fw = new FileWriter(new File(“Test.txt”));

2.调用流对象的写入方法,将数据写入流
fw.write(“atguigu-songhongkang”);

3.关闭流资源,并将流中的数据清空到文件中。
fw.close();

FileWriter fw = null;  
try {
	fw = new FileWriter(new File("Test.txt"));
	fw.write("atguigu-songhongkang");
} catch (IOException e) {
	e.printStackTrace();
} finally {
	if (fw != null)  try {
	fw.close();
} catch (IOException e) {
	e.printStackTrace();
}
}

注意点:

定义文件路径时,注意:可以用“/”或者“\”。

在写入一个文件时,如果使用构造器FileOutputStream(file),则目录下有同名文
件将被覆盖。
如果使用构造器FileOutputStream(file,true),则目录下的同名文件不会被覆盖, 在文件内容末尾追加内容。

在读取文件时,必须保证该文件已存在,否则报异常。

字节流操作字节,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt

字符流操作字符,只能操作普通文本文件。最常见的文本文
件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意.doc,excel,ppt这些不是文 本文件。

4.缓冲流

为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类 时,会创建一个内部缓冲区数组,缺省使用8192个字节(8Kb)的缓冲区

●缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:
BufferedInputStream 和 BufferedOutputStream
BufferedReader 和 BufferedWriter

●当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区
●当使用BufferedInputStream读取字节文件时,BufferedInputStream会一次性从 文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中 读取下一个8192个字节数组。
●向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满, BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。使用方法 flush()可以强制将缓冲区的内容全部写入输出流
●关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也 会相应关闭内层节点流
●flush()方法的使用:手动将buffer中内容写入文件
●如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷 新缓冲区,关闭后不能再写出

BufferedReader br = null;  
BufferedWriter bw = null; 
try {
	// 创建缓冲流对象:它是处理流,是对节点流的包装
	br = new BufferedReader(new 	FileReader("d:\\IOTest\\source.txt"));  
    bw = new BufferedWriter(new FileWriter("d:\\IOTest\\dest.txt")); 
    String str;
	while ((str = br.readLine()) != null) { 
        // 一次读取字符文本文件的一行字符
		bw.write(str); // 一次写入一行字符串
		bw.newLine(); // 写入行分隔符
	}
	bw.flush(); // 刷新缓冲区
} catch (IOException e) {
	e.printStackTrace();
} finally {
	// 关闭IO流对象
	try {
		if (bw != null) {
			bw.close(); // 关闭过滤流时,会自动关闭它所包装的底层节点流
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
	try {
		if (br != null) {
			br.close();
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
}

5.转换流

转换流提供了在字节流和字符流之间的转换

Java API提供了两个转换流:
InputStreamReader:将InputStream转换为Reader
OutputStreamWriter:将Writer转换为OutputStream

字节流中的数据都是字符时,转成字符流操作更高效。

很多时候我们使用转换流来处理文件乱码问题。实现编码和 解码的功能。

InputStreamReader

●实现将字节的输入流按指定字符集转换为字符的输入流。
●需要和InputStream“套接”。

●构造器
public InputStreamReader(InputStream in)
public InputSreamReader(InputStream in,String charsetName)
如 : Reader isr = new InputStreamReader(System.in,”gbk”);

OutputStreamWriter
●实现将字符的输出流按指定字符集转换为字节的输出流。
●需要和OutputStream“套接”。

●构造器
public OutputStreamWriter(OutputStream out)
public OutputSreamWriter(OutputStream out,String charsetName)

如何转换?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

public void testMyInput() throws Exception {
	FileInputStream fis = new FileInputStream("dbcp.txt");
	FileOutputStream fos = new FileOutputStream("dbcp5.txt");

	InputStreamReader isr = new InputStreamReader(fis, "GBK");
	OutputStreamWriter osw = new OutputStreamWriter(fos, "GBK");

	BufferedReader  br  = new BufferedReader(isr);
	BufferedWriter  bw   =  new  BufferedWriter(osw);

	String str = null;
	while ((str = br.readLine()) != null) {
		bw.write(str);  bw.newLine();
		bw.flush();
	}
	bw.close();
	br.close();
}


补充:编码解码

编码:字符串–>字节数组
解码:字节数组–>字符串

转换流的编码应用
可以将字符按指定编码格式存储
可以对文本数据按指定编码格式来解读
指定编码表的动作由构造器完成

6.标准输入流、输出流

●System.in和System.out分别代表了系统标准的输入和输出设备
●默认输入设备是:键盘,输出设备是:显示器
System.in的类型是InputStream
System.out的类型是PrintStream,其是OutputStream的子类 FilterOutputStream 的子类
重定向:通过System类的setIn,setOut方法对默认设备进行改变。
public static void setIn(InputStream in)
public static void setOut(PrintStream out)

System.out.println("请输入信息(退出输入e或exit):");
// 把"标准"输入流(键盘输入)这个字节流包装成字符流,再包装成缓冲流 
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));  
String s = null;
try {
	while ((s = br.readLine()) != null) { 
        // 读取用户输入的一行数据 --> 阻塞程序
		if ("e".equalsIgnoreCase(s) || "exit".equalsIgnoreCase(s)) { 
            System.out.println("安全退出!!");
			break;
		}
		// 将读取到的整行字符串转成大写输出 
        System.out.println("-->:" + s.toUpperCase());  		         System.out.println("继续输入信息");
	}
} catch (IOException e) {
	e.printStackTrace();
} finally {
	try {
		if (br != null) {
			br.close(); // 关闭过滤流时,会自动关闭它包装的底层节点流
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
}

7.打印流

●实现将基本数据类型的数据格式转化为字符串输出

●打印流:PrintStream和PrintWriter
提供了一系列重载的print()和println()方法,用于多种数据类型的输出
PrintStream和PrintWriter的输出不会抛出IOException异常
PrintStream和PrintWriter有自动flush功能
PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。
在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
System.out返回的是PrintStream的实例

PrintStream ps = null;  
try {
	FileOutputStream fos = new FileOutputStream(new 		File("D:\\IO\\text.txt"));
// 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)  
    ps = new PrintStream(fos, true);
	if (ps != null) {// 把标准输出流(控制台输出)改成文件
		System.setOut(ps);
	}
	for (int i = 0; i <= 255; i++) { // 输 出 ASCII 字 符
			System.out.print((char) i);
			if (i % 50 == 0) { // 每50个数据一行
					System.out.println(); // 换 行
			}
	}
} catch (FileNotFoundException e) {
	e.printStackTrace();
} finally {
	if (ps != null) {
		ps.close();
	}
}

8.数据流

●为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流。
●数据流有两个类:(用于读取和写出基本数据类型、String类的数据)
DataInputStream 和 DataOutputStream
分别“套接”在 InputStream 和 OutputStream 子类的流上
●DataInputStream中的方法:boolean readBoolean(), char readChar(), double readDouble() , long readLong(),String readUTF(),byte readByte(), float readFloat() , short readShort() , int readInt(),void readFully(byte[] b).

●DataOutputStream中的方法
将上述的方法的read改为相应的write即可

DataOutputStream dos = null;
try { // 创建连接到指定文件的数据输出流对象
	dos = new DataOutputStream(new 	FileOutputStream("destData.dat"));  
    dos.writeUTF("我爱北京天安门"); // 写UTF字符串 
    dos.writeBoolean(false); // 写入布尔值
	dos.writeLong(1234567890L); // 写入长整数
	System.out.println("写文件成功!");
} catch (IOException e) {  
    e.printStackTrace();
} finally { // 关闭流对象
	try {
		if (dos != null) {
			// 关闭过滤流时,会自动关闭它包装的底层节点流
			dos.close();
		}
	} catch (IOException e) { 
        e.printStackTrace();
	}
}

//-----------------------------------------------------
DataInputStream dis = null;
try {
	dis = new DataInputStream(new 	FileInputStream("destData.dat"));  
    String info = dis.readUTF();
	boolean flag = dis.readBoolean();  
    long time = dis.readLong();  
    System.out.println(info);
	System.out.println(flag);  
    System.out.println(time);
} catch (Exception e) { 
    e.printStackTrace();
} finally {
	if (dis != null) {  
        try {
			dis.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

9.对象流

●ObjectInputStream和OjbectOutputSteam
用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可 以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

●序列化:用ObjectOutputStream类保存基本类型数据或对象的机制
●反序列化:用ObjectInputStream类读取基本类型数据或对象的机制

●ObjectOutputStream和ObjectInputStream不能序列化statictransient
饰的成员变量

对象的序列化

●对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从 而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传 输到另一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原 来的Java对象
●序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据, 使其在保存和传输时可被还原
●序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返 回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础
●如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可 序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。 否则,会抛出NotSerializableException异常
Serializable
Externalizable

●凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
-->private static final long serialVersionUID;
–>serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象 进行版本控制,有关各版本反序列化时是否兼容。
–>如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自 动生成的。 若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议, 显式声明。

●简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验 证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的 serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同 就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异 常。(InvalidCastException)


使用对象流序列化对象

●若某个类实现了 Serializable 接口,该类的对象就是可序列化的:
–>创建一个 ObjectOutputStream
–>调用 ObjectOutputStream 对象的 writeObject(对象) 方法输出可序列化对象
–>注意写出一次,操作flush()一次
●反序列化
–>创建一个 ObjectInputStream
–>调用 readObject() 方法读取流中的对象
●强调:如果某个类的属性不是基本数据类型或 String 类型,而是另一个 引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的 Field 的类也不能序列化

//序列化:将对象写入到磁盘或者进行网络传输。
//要求对象必须实现序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(“data.txt"));
Person p = new Person("韩梅梅", 18, "中华大街", new Pet());
oos.writeObject(p);  oos.flush();
oos.close();
                                                                     
//反序列化:将磁盘中的对象数据源读出。
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(“data.txt"));
Person p1 = (Person)ois.readObject();
System.out.println(p1.toString());
ois.close();


10.随机存取文件流

RandomAccessFile 类
●RandomAccessFile 声明在java.io包下,但直接继承于java.lang.Object类。并 且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也 可以写。
●RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意
地方来读、写文件
–>支持只访问文件的部分内容
–>可以向已存在的文件后追加内容
●RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。
RandomAccessFile 类对象可以自由移动记录指针:
–>long getFilePointer():获取文件记录指针的当前位置
–>void seek(long pos):将文件记录指针定位到 pos 位置

●构造器
public RandomAccessFile(File file, String mode)
public RandomAccessFile(String name, String mode)

●创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指 定 RandomAccessFile 的访问模式:
–>r: 以只读方式打开
–>rw:打开以便读取和写入
–>rwd:打开以便读取和写入;同步文件内容的更新
–>rws:打开以便读取和写入;同步文件内容和元数据的更新
●如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件, 如果读取的文件不存在则会出现异常。 如果模式为rw读写。如果文件不 存在则会去创建文件,如果存在则不会创建。

我们可以用RandomAccessFile这个类,来实现一个

11. NIO中Path、Paths、Files类的使用

NIO与原来的IO有同样的作用和目 的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于 通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网
络编程NIO。

●早期的Java只提供了一个File类来访问文件系统,但File类的功能比较有限,所 提供的方法性能也不高。而且,大多数方法在出错时仅返回失败,并不会提供异 常信息。
●NIO. 2为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可以看成是File类的升级版本,实际引用的资 源也可以不存在。
●在以前IO操作都是这样写的:
import java.io.File;
File file = new File(“index.html”);
●但在Java7 中,我们可以这样写:
import java.nio.file.Path; import java.nio.file.Paths;
Path path = Paths.get(“index.html”);

Path接口


lPath 常用方法:

String toString() : 返回调用 Path 对象的字符串表示形式
boolean startsWith(String path) : 判断是否以 path 路径开始
boolean endsWith(String path) : 判断是否以 path 路径结束
boolean isAbsolute() : 判断是否是绝对路径
Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
Path getRoot() :返回调用 Path 对象的根路径
Path getFileName() : 返回与调用 Path 对象关联的文件名
int getNameCount() : 返回Path 根目录后面元素的数量
Path getName(int idx) : 返回指定索引位置 idx 的路径名称
Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
Path resolve(Path p) :合并两个路径,返回合并后的路径对应的Path对象
File toFile(): 将Path转化为File类的对象


Files类

java.nio.file.Files 用于操作文件或目录的工具类。
Files常用方法:
Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录 Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错
void deleteIfExists(Path path) : Path对应的文件/目录如果存在,执行删除
Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
long size(Path path) : 返回 path 指定文件的大小

Files常用方法:用于判断
boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件
boolean isHidden(Path path) : 判断是否是隐藏文件
boolean isReadable(Path path) : 判断文件是否可读
boolean isWritable(Path path) : 判断文件是否可写
boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在

Files常用方法:用于操作内容
SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连 接,how 指定打开方式。
DirectoryStream newDirectoryStream(Path path) : 打开 path 指定的目录
InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象


十一、反射机制

1.Java反射机制概述

⭕Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期 借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内 部属性及方法。
⭕加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个 类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可 以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看 到类的结构,所以,我们形象的称之为:反射

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


补充:动态语言、静态语言

动态语言是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以 被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运 行时代码可以根据某些条件改变自身结构

主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。

静态语言与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。

Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动 态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活.


Java反射机制提供的功能

–>在运行时判断任意一个对象所属的类
–>在运行时构造任意一个类的对象
–>在运行时判断任意一个类所具有的成员变量和方法
–>在运行时获取泛型信息
–>在运行时调用任意一个对象的成员变量和方法
–>在运行时处理注解
–>生成动态代理

●主要API

java.lang.Class**😗*代表一个类

**java.lang.reflect.Method:**代表类的方法

**java.lang.reflect.Field:**代表类的成员变量

**java.lang.reflect.Constructor:**代表类的构造器

2.理解Class类并获取Class实例

Class类:描述类的类

在Object类中定义了以下的方法,此方法
将被所有子类继承:
public final Class getClass()
以上的方法返回值的类型是一个Class类, 此类是Java反射的源头,实际上所谓反射 从程序的运行结果来看也很好理解,即: 可以通过对象反射求出类的名称。

ØClass本身也是一个类

ØClass 对象只能由系统建立对象

Ø一个加载的类在 JVM 中只会有一个Class实例

Ø一个Class对象对应的是一个加载到JVM中的一个.class文件

Ø每个类的实例都会记得自己是由哪个 Class 实例所生成

Ø通过Class可以完整地得到一个类中的所有被加载的结构

ØClass类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

2.1 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
2.2 获取Class类的实例的四种方法

⭕1.前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠, 程序性能最高
实例:Class clazz = String.class;
⭕2.前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
实例:Class clazz = “www.icss.com”.getClass();
⭕3.前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方 法forName()获取,可能抛出ClassNotFoundException
实例:Class clazz = Class.forName(“java.lang.String”);
⭕4.其他方式(不做要求)类加载器
ClassLoader cl = this.getClass().getClassLoader(); Class clazz4 = cl.loadClass(“类的全类名”);

2.3 哪些类型可以有Class对象

1.class:

外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类

2.interface:接口

3.[]:数组

4.enum:枚举

5.annotation:注解@interface

6.primitive type:基本数据类型

7.void

3.类的加载与ClassLoader的理解

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过
如下三个步骤来对该类进行初始化。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时 数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问 入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的 过程需要类加载器参与。
链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存 都将在方法区中进行分配。
解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
初始化:
执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中 所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信 息的,不是构造该类对象的构造器)。
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。

public class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(A.m);
}
}

class A {
static {
m = 300;
}
static int m = 100;
}
//第二步:链接结束后m=0
//第三步:初始化后,m的值由<clinit>()方法执行决定
//	这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并产生,类似于
//<clinit>(){
//m =  300;
//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和Father的初始化
	// A a = new A();
	// System.out.println(A.m);
	// Class.forName("com.icss.java2.A");
	// 被动引用
	A[] array = new A[5];//不会导致A和Father的初始化
	// System.out.println(A.b);//只会初始化 Father
	// System.out.println(A.M);//不会导致A和 Father的初始化
}
    static {
		System.out.println("main所在的类");
	}

}


外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

类加载器的作用

●类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为 方法区中类数据的访问入口。

●类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器 中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。

ClassLoader

类加载器作用是用来把类(class)装载进内存的,有引导类加载器、扩展类加载器、系统类加载器。

●//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);

4.创建运行时类的对象

有了Class对象,能做什么?
创建类的对象:调用Class对象的newInstance()方法
要 求: 1)类必须有一个无参数的构造器。

​ 2)类的构造器的访问权限需要足够。

难道没有无参的构造器就不能创建对象了吗?
不是!只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。
步骤如下:
1)通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类 型的构造器

2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。

3)通过Constructor实例化对象。

//1.根据全类名获取对应的Class对象 
String name =icss.java.Person";  
Class clazz = null;
clazz = Class.forName(name);
//2.调用指定参数结构的构造器,生成Constructor的实例
Constructor con = clazz.getConstructor(String.class,Integer.class);
//3.通过Constructor的实例创建对应类的对象,并初始化类属性  
Person p2 = (Person) con.newInstance("Peter",20); System.out.println(p2);


5.获取运行时类的完整结构

通过反射获取运行时类的完整结构:Field、Method、Constructor、Superclass、Interface、Annotation。

使用反射可以取得:
●1.实现的全部接口
public Class<?>[] getInterfaces()
确定此对象所表示的类或接口实现的接口。

●2.所继承的父类
public Class<? Super T> getSuperclass()
返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的
Class。

●3.全部的构造器
public Constructor[] getConstructors()
返回此 Class 对象所表示的类的所有public构造方法。
public Constructor[] getDeclaredConstructors()
返回此 Class 对象表示的类声明的所有构造方法。

Constructor类中:
取得修饰符: public int getModifiers();
取得方法名称: public String getName();
取得参数的类型:public Class<?>[] getParameterTypes();

●4.全部的方法
public Method[] getDeclaredMethods()
返回此Class对象所表示的类或接口的全部方法
public Method[] getMethods()
返回此Class对象所表示的类或接口的public的方法

Method类中:
public Class<?> getReturnType()取得全部的返回值 public Class<?>[] getParameterTypes()取得全部的参数
public int getModifiers()取得修饰符
public Class<?>[] getExceptionTypes()取得异常信息。

●5.全部的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的名称。

●6.Annotation相关

get Annotation(Class annotationClass)
getDeclaredAnnotations()
●7.泛型相关
获取父类泛型类型:Type getGenericSuperclass()
泛型类型:ParameterizedType
获取实际的泛型类型参数数组:getActualTypeArguments()

●8.类所在的包Package getPackage()

6.调用运行时类的指定结构

1.调用指定方法
通过反射,调用类中的方法,通过Method类完成。步骤:
1)通过Class类的getMethod(String name,Class…parameterTypes)方法取得 一个Method对象,并设置此方法操作时所需要的参数类型。
2)之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中
传递要设置的obj对象的参数信息。

Object invoke(Object obj, Object … args)
说明:
Object 对应原方法的返回值,若原方法无返回值,此时返回null
若原方法若为静态方法,此时形参Object obj可为null
若原方法形参列表为空,则Object[] args为null
若原方法声明为private,则需要在调用此invoke()方法前,显式调用 方法对象的setAccessible(true)方法,将可访问private的方法。

2.调用指定属性

在反射机制中,可以直接通过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语言访问检查。

7.反射的应用:动态代理

●代理设计模式的原理:
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原 始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原 始对象上。
●之前为大家讲解过代理机制的操作,属于静态代理,特征是代理类和目标 对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代 理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能。

●动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时 根据需要动态创建目标类的代理对象。
●动态代理使用场合:
->调试
->远程方法调用
●动态代理相比于静态代理的优点:
抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中 处理,这样,我们可以更加灵活和统一的处理众多的方法。

7.1 Java动态代理相关API

●Proxy :专门完成代理的操作类,是所有动态代理类的父类。通过此类为一
个或多个接口动态地生成实现类。
●提供用于创建动态代理类和动态代理对象的静态方法
static Class <?> getProxyClass(ClassLoader loader, Class<?>... interfaces) 创建 一个动态代理类所对应的Class对象 static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
InvocationHandler h)
直接创建一个动态代理对象

7.2 动态代理步骤

1.创建一个实现接口InvocationHandler的类,它必须实现invoke方 法,以完成代理的具体操作。

//Object theProxy:代理类的对象
//Method method:要调用的方法
//Object[] params:方法调用时所需要的参数
public Object invoke(Object theProxy, Method method, Object[] params)
throws Throwable{
try{
Object retval = method.invoke(targetObj, params);
// Print out the result
System.out.println(retval);
return retval;
}catch (Exception exc){}
}


2.创建被代理的类以及接口

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 创建 一个Subject接口代理。

RealSubject target = new RealSubject();
// Create a proxy to wrap the original implementation  
DebugProxy proxy = new DebugProxy(target);
// Get a reference to the proxy through the Subject interface
Subject sub = (Subject) Proxy.newProxyInstance(  Subject.class.getClassLoader(),new Class[] { Subject.class }, proxy);


4.通过 Subject代理调用RealSubject实现类的方法

String info = sub.say(Peter", 24);
System.out.println(info);


7.3 动态代理与AOP

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

●使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有 太大的意义。通常都是为指定的目标对象生成动态代理
●这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理 包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异: AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值