java 第二部分

java

三目运算符

三目运算符 (表达式1)?(表达式2):(表达式3),计算方法是这样的:
表达式1是一个逻辑表达式,如果其值为true,则整个表达式的值为表达式2的值,否则为表达式3的值。


在一个表达式中可能包含多个有不同运算符连接起来的、具有不同数据类型的数据对象;由于表达式有多种运算,不同的结合顺序可能得出不同结果甚至出现错误运算错误,因为当表达式中含多种运算时,必须按一定顺序进行结合,才能保证运算的合理性和结果的正确性、唯一性。

优先级

1.优先级与求值顺序无关。如a+b && b_c,虽然_优先级最高,但这个表达式求值顺序是从左到右。
2.优先级从上到下依次递减,最上面具有最高的优先级,逗号操作符具有最低的优先级。
3.相同优先级中,按结合性进行结合。大多数运算符结合性是从左到右,只有三个优先级是从右至左结合的,它们是单目运算符、条件运算符、赋值运算符。
4.基本的优先级需要记住:
指针最优,单目运算优于双目运算。如正负号
先算术运算,后移位运算,最后位运算。请特别注意:1 << 3 + 2 & 7等价于 (1 << (3 + 2))&7. 
逻辑运算最后结合


JAVA语言的优先级

运算符结合性
. ( ) (方法调用)从左向右
! ~ ++ – +(一元运算) -(一元运算)从右向左
* / %从左向右
+ -从左向右
<< >> >>>从左向右
< <= > >= instanceof从左向右
== !=从左向右
&从左向右
^从左向右
|从左向右
&&从左向右
||从左向右
?:从右向左
=从右向左

2. 流程控制

程序在执行的时候,遵循以下的三种最基础的执行结构

  • 顺序结构:

代码从上向下逐行执行
顺序结构是程序执行的基本结构

  • 分支结构:

程序在运行到某一个节点时,遇到了多种分支,根据指定的条件选择其中的一种分支继续向下执行

  • 循环结构:

某一个代码段中的内容需要重复执行,重复的次数由循环条件来控制


程序默认采用的是顺序结构, 我们也可以通过一些语句来修改程序的执行结构, 这样的语句叫做流程控制语句, 按照修改的执行结构不同, 可以分为 分支流程控制 和 循环流程控制

2.2 分支流程控制

2.2.1 分支流程控制–if

基础语法

if (condition) {
    // 代码段1
}
else {
    // 代码段2
}

逻辑: condition是一个boolean类型的变量, 或者一个boolean结果的表达式. 如果condition的值为true, 则代码段1执行, 否则, 代码段2执行
进阶

if (condition1) {
    // 代码段1
}
else if (condition2) {
    // 代码段2
}
else {
    // 代码段3
}

逻辑: 先判断condition1, 如果condition1成立, 执行代码段1; 如果condition1不成立, 再判断condition2, 如果condition2成立, 执行代码段2, 否则执行代码段3
特殊说明
在if-else语句中, 如果某一个代码段中只有一句语句, 则大括号可以省略; 但是从语法的规范来说, 一般情况下, 我们是不省略的

2.2.2 分支流程控制–switch

基础语法

switch (variable) {
    case value1:
    case value2:
    ...
    default:
}

逻辑: variable是一个变量, switch语句捕获这个变量的值; 如果变量的值和某一个case后面的值相同了, 就会执行这个case后面的语句; 如果变量的值没有和任意的一个value相同, 则执行default后面的值
注意事项
在switch-case语句中有穿透性, 即: 如果variable的值和某一个value匹配了, 会执行从这个case开始, 一直到switch语句结束, 后面所有的代码, 且跳过后面的case匹配; 为了避免穿透, 可以使用关键字break来跳出switch语句

2.3  循环流程控制

2.3.1 循环流程控制–while

基础语法

while (condition) {
    // 循环体
}

逻辑: 每次循环线判断condition, 如果为true, 循环体执行, 然后再判断condition; 直到condition为false, 循环结束
do-while

do {
    // 循环体
} while (condition);

逻辑: 先执行一次循环体, 然后再判断循环条件是否成立
关键字 break, continue 与 循环标签
break
用在循环中, 表示跳出循环; 如果用在多层循环中, 只会跳出当前的一层循环
continue
用在循环中, 表示继续循环, 从continue开始, 后面所有的代码不执行, 直接进入下次循环
循环标签
一般用在多层循环中, 可以给每一个循环都添加一个标签; 可以配合break语句, 直接结束指定的循环. 或者配合continue语句, 直接终止指定循环的执行, 立即进入下次循环

2.3.2 循环流程控制–for

基础语法

for (循环起点; 循环条件; 循环步长) {
    循环体
}

逻辑:
先执行循环起点, 一般用与定义一个控制循环的变量, 或者给这个变量一个初始值
再判断循环条件是否成立, 如果循环条件成立, 执行循环体, 再执行循环步长, 然后重新判断循环条件
如果循环条件不成立, 循环结束
特殊说明

  • 关于语法省略
    • for循环的小括号中由三部分组成, 这三个部分都可以省略:
    • 循环起点: 变量的定义可以放在循环外
    • 循环条件: 如果省略, 默认是true, 可以在循环体中控制跳出循环
    • 循环步长: 可以在循环体中控制步长
  • 关于大括号
    • 如果循环体中只有一句语句, 循环体大括号可以省略; 但是出于规范, 一般我们也不省略

3. 方法

3.1. 方法的定义

什么是方法?


方法,其实就是一个代码段。我们可以将一些需要频繁执行的逻辑,用代码段包装起来。


方法的定义

[访问权限修饰符] [其他的修饰符] 返回值类型 方法名([参数列表]) {
    
// 方法体,也叫做方法实现
}
// 方法名是一个标识符,遵循小驼峰命名法

在Java中,方法和函数可以视为相同。


方法的定义需要注意:方法只能写在类中,并且,方法不允许嵌套方法。

3.2. 方法的调用

一个方法定义完成后,不会被自动的调用。也就意味着,这个方法中的代码不会自动执行。


如果需要执行某一个方法中的代码,需要手动调用这个方法。


**方法的调用:**直接使用方法的名字来访问即可。

3.3. 方法的参数

参数,其实就是一个变量。这个变量是声明在参数列表中的。


方法中参数变量和普通变量的区别


1)方法中参数变量的声明,不允许赋初始值。
2)每一个参数的类型都必须添加,不能省略。
3)在方法中使用参数,这个参数一定是有值的。


有参数的方法调用


如果一个方法有参数,则这个方法在调用的时候,一定要对参数进行赋值。


在方法声明的小括号中的内容,叫做形式参数,简称 形参


在方法调用的小括号中的内容,叫做实际参数,简称** 实参**


在方法调用的时候,用实参给形参赋值。这个过程叫 传参


方法参数的实际意义


实现了方法与方法之间的通信


实现了方法之间的数据传递。

3.4. 方法的返回值

返回值:
就是一个方法中的逻辑执行的结果。也可以实现数据在不同的方法之间进行传递。这个数据传递的方向和参数正好相反。


void: 也是一种数据类型,空类型。只能用在方法的返回值部分,代表这个方法没有返回值。


return:

  1. 在后面跟上一个具体的值,作为这个方法的返回值。
  2. 结束一个方法的执行。


返回值注意事项:


如果一个方法的返回值类型不是void,那么这个方法在结束之前,必须要有结果返回。
如果一个方法中出现了分支结构,则需要保证每一个分支上都有结果返回。


Q: 在一个返回值类型为void的方法中,可以使用return吗?
A: 可以!此时,return后面什么都不要添加,仅仅代表结束方法的执行。

3.5. 方法的重载(Overload)

在同一个类中的方法,如果满足以下的要求,则这些方法之间的关系,就是重载关系。


1)方法名相同
2)参数不同(类型不同、数量不同)


方法的重载,只跟方法名、参数有关系,与返回值没有关系!


使用重载的方法,可以方便调用。

3.6. 方法的递归【优先级最低】

什么是递归?
递归,其实就是方法调用自己。或者多个方法之间进行循环调用。
递归容易出现的问题。
StackOverflowError
因为方法的执行需要将方法压栈。而死递归的情况下,只有方法压栈,没有方法执行结束出栈。因此栈中积压的方法越来越多,直到超出了栈的容量。出现 栈溢出错误
合理的使用递归,可以在一定程度上提高我们的编码效率。其实,在使用递归的时候,只需要考虑好递归的条件即可。


4. 数组

4.1. 数组的定义


什么是数组?


数组是一个数据容器,里面存储了若干个相兼容的数据类型的数据。


长度:


长度在数组中,代表数组的容量。表示,这个数组中可以存储多少个数据。


数组是一个定长的容器,一旦实例化完成,长度不能改变。


元素:


数组中存储的各个数据,称作是数组的元素。


实例化数组:给数组开辟内存空间,并存储一些初始值

// 实例化了一个长度为5的数组,并填充了默认的初始值。
int[] array = new int[5];
// 实例化了一个数组,并填充了指定的初始值。
// 此时,这个数组的长度,由初始值的数量来确定
int[] array = new int[] { 1, 2, 3, 4, 5, 6, 7 };
// 可以省略 new int[] ,实例化了一个数组,并填充了指定的初始值。
// 数组的长度由初始值的数量来确定。
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8 };


默认初始值:
int、byte、short、long -> 0
float、double -> 0.0
boolean -> false
char -> ‘\u0000’(体现出的是一个空格)
引用数据类型 -> null

4.2. 数组中的元素访问

数组中的每一个元素,都有一个位置索引。这个位置索引,称作一个元素的下标


在数组中,元素的下标是从0开始的!


访问数组中的元素,通过元素的下标来实现访问。


下标访问,使用中括号,将下标写道中括号里面。


注意!!!


使用下标访问数组中的元素的时候,切记!不能越界!!!如果越界,将出现ArrayIndexOutOfBoundsException!!!


数组的遍历

// 1. 遍历下标(array.length获取数组的长度)
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
// 2. 增强for循环
//    依次从数组array中获取每一个元素,并将获取到的元素给ele进行赋值
for (int ele : array) {
System.out.println(ele);
}


两种遍历方式的对比:


1)下标遍历,可以在遍历的过程中获取到元素的下标。而增强for循环没有下标的获取。
2)下标遍历的效率比增强for循环低。
3)下标遍历中,可以对数组中的内容进行修改,增强for循环则不可以。

4.3. 数组的内存分析


内存分区:内存可以分为4个区域,


堆Heap。


数组进行实例化的时候,需要使用到关键字new。new代表的含义,就是在堆上开辟空间。


int[] array = new int[5];


这一句话,代表在堆上开辟空间。开辟了5个连续的4字节空间。


将这些空间的首元素地址给左侧的数组类型的变量进行赋值。

4.4. 数组的排序

排序:将数组中的元素,按照一定的规则,进行重新排列。


数组排序的算法非常多,但是常见的算法:选择排序、冒泡排序、快速排序、归并排序。


选择排序


思路:固定一个元素的下标,用这个下标的元素,依次和后面的每一个元素进行大小比较。当满足交换条件的时候,将这两个元素进行交换。

static void sort(int[] array) {
	for (int i = 0; i < array.length - 1; i++) {
		int swapIndex = i;
		for (int j = i + 1; j < array.length; j++) {
			if (array[swapIndex] > array[j]) {
				swapIndex = j;
			}
		}
		if (swapIndex != i) {
			int temp = array[i];
			array[i] = array[swapIndex];
			array[swapIndex] = temp;
		}
	}
}


冒泡排序


思路:从第0位开始,依次比较两个相邻的元素。将满足交换条件的元素进行交换。

static void sort(int[] array) {
	// 1. 循环进行每一趟的比较
	for (int i = 0; i < array.length - 1; i++) {
		// 2. 每一趟的比较,都从第0位开始
		for (int j = 0; j < array.length - 1 - i; j++) {
			// 3. 比较两个相邻的元素
			if (array[j] > array[j + 1]) {
				int temp = array[j];
				array[j] = array[j + 1];
				array[j + 1] = temp;
			}
		}
	}
}

4.5. 数组中元素的查询

查询数组中的某一个元素出现的下标。


顺序查询


遍历数组,依次用每一位的元素和要查询的元素进行比较,如果相同,则返回这个元素下标。


二分查询


从一个范围中,查询指定的数字,取这个范围的中间值进行比较。这种查询方式是二分查询。


二分查询对数组是有要求的:要求数组需要时排序的。


使用场景:


如果数组不是排序状态的,用顺序查询。
如果数组是排序状态的,视情况而定。
如果数组的元素数量多,用二分查询 (20)
如果数组的元素数量少,用顺序查询

4.6. 数组作为参数以及…语法




只能用在方法的形参中,int… 实际上代表的是一个 int[]


这种参数,其实是不定长度参数列表。在调用方法传参的时候,可以直接将数组中的每一个元素写到方法的参数列表中。


注意事项:
不定长度参数列表(…),在使用的时候,必须放到形参列表的最后位。

4.7. Arrays工具类

Arrays.sort()//排序
Arrays.copyof(int[] original,int newLength)//copy原数组中长度为newLength的字符串并返回
int[] array = new int[] {1,2,3,4,5,6};
		int[] array1 = Arrays.copyOf(array, 3);
		for(int e:array1) {
			System.out.print(e+" ");
		}

4.8. 二维数组

5. 面向对象

5.1. 面向对象的简介

面向过程
是一种看待问题、解决问题的思维方式。着眼点在于,问题是怎样一步步的解决的,然后亲力亲为的去解决这个问题。
面向对象
是一种看待问题、解决问题的思维方式。着眼点在于,找到一个能够帮助解决问题的实体,然后委托这个实体帮助解决问题。


Java是一种面向对象的语言。
可以使用Java这门编程语言,更容易的写出具有面向对象编程思想的代码。

5.2. 类的设计与对象的实例化

对象: 可以帮助解决问题的实体,就是一个对象。
: 是由若干个具有相同的特征和行为的对象组成的一个集合。
类和对象的关系: 类是对象的集合 , 对象是类的个体
备注: 在程序设计中,一定是现有的类,再有的对象。

5.2.1. 类的设计

语法

[访问权限修饰符] class 类名 {
    // 类体
    // 类的所有的对象共有的特征
    // 类的所有的对象共有的行为
}

语法说明:

  1. 类名: 是一个标识符, 遵循大驼峰命名法。
  2. 特征: 又叫属性, 在类中的体现是一个全局变量。
  3. 行为: 在类中的体现,是一个方法。

类是一种自定义的数据类型

5.2.2. 对象的实例化

对象的实例化,需要使用到关键字new,也就是说,类是一种自定义的引用数据类型。

5.3. 构造方法


是一个方法,这个方法和普通方法相比,有不一样的地方


1)构造方法没有除了访问权限修饰符以外的其他修饰符。
2)构造方法没有返回值。并不是说—返回值类型是void。而是不要写返回值类型部分。
3)构造方法的名字必须和类名相同。
4)构造方法不能被显式调用。构造方法是在实例化一个对象的时候被自动调用


构造方法默认的提供原则


1)如果一个类中没有写任何的构造方法,此时,这个类中会包含一个系统自动提供的,public权限的无参的构造方法。
2)如果一个类中写构造方法了,此时,系统将不再自动提供任何的构造方法。


构造方法是可以重载的,如何区分调用重载的构造方法?
通过参数区分。

构造方法的实际使用场景
构造方法,作为一个对象生命周期中的第一个方法,一般会在构造方法中,对对象的某一些属性进行初始化的赋值

5.4. 成员访问

访问类中的成员(属性、方法)

5.4.1. 成员分类

类中的成员,大致可以分为两种:静态成员、非静态成员。

5.4.1.2 非静态成员

没有使用关键字 static 修饰的成员,就是非静态成员。也叫作实例成员。


非静态的成员,是属于对象的。在访问的时候需要使用对象来访问。而对象也叫作一个类的实例(instance),因此非静态成员,也叫作–实例成员。

5.4.1.2 静态成员

使用关键字 static 修饰的成员,就是静态成员。也叫作 类成员。


静态的成员是属于类的。在访问的时候,需要用类来访问。
其实,静态成员也可以用对象访问。只是不推荐。

5.4.5. 静态成员与非静态成员的内存分析

  1. 非静态属性:空间的开辟,是发生在对象的实例化的时候。


非静态属性的空间,在堆空间中,随着对象的实例化开辟,随着对象的销毁而销毁。

  1. 静态属性:空间的开辟,是发生在类第一次被加载到jvm内存。(当程序第一次使用到这个类的时候)。


静态属性的空间,是在静态空间中,随着类的加载被开辟,程序运行的过程中,不会销毁。常驻内存。

5.4.6. 总结

  1. 静态成员,需要用类来访问。
  2. 非静态成员,需要用对象来访问。
  3. 在非静态方法中,可以直接访问非静态成员和静态成员。
  4. 在静态方法中,只能直接访问静态成员。

5.5. this关键字


this在程序中,可以用在非静态的方法中,和构造方法中。

在一个类的方法中,允许出现参数的名字和属性的名字重复。并且这样是合法的。
因为属性的空间开辟在堆上,而参数作为局部变量,空间开辟在栈中。他们在不同的空间中。
在这个方法中,直接写变量的名字,使用的是参数,而不是属性。


this表示对当前对象的引用
1. 如果用在一个非静态方法中,谁调用这个方法,this就代表谁。
2. 在构造方法中,this表示刚刚被实例化的对象。


在一个类中,访问当前类的属性和方法的时候,在某些情况下,this是可以省略的。
如果省略掉this之后,对程序没有任何的影响,没有任何的歧义,此时的this就是可以省略的。

杀手锏:如果不知道哪些this可以省略,哪些this不能省略。那么全部不要省略。


this()调用当前类中的其他构造方法


在构造方法中,可以使用this()调用当前类中的其他的构造方法,具体调用哪一个构造方法,由小括号中的实参区分


注意事项:


在构造方法中,使用this关键字调用其他的构造方法,这一句话必须写在构造方法中的第一行。也就是说,在构造方法调用的代码之前,不能有任何的语句,包括输出语句。
不要循环调用。

5.6. 封装

是面向对象三大特性之一【封装、继承、多态】

封装,是一个比较抽象的概念。可以将一个代码段、一个功能包装起来,方便维护、方便在其他的地方去使用这部分功能。
封装可以分为 广义的封装 和 狭义的封装。
广义的封装:将一个功能封装成一个方法、将一个大的功能集封装成一个模块、…、将某些具有固定格式的数据,封装成一个类的对象…
狭义的封装:将一个类中的某些属性私有化起来,不让外界直接访问。同时提供对应的set和get方法。


某一些属性,如果直接让外界访问。外界对其赋的值可能不是我们想要的。可能这些值从语法讲,没有问题;但是从逻辑上讲,是不可以的。这种情况下,我们就需要将属性私有化起来,不让外界直接访问。
从JavaBean规范出发,所有的属性,都需要私有化。

5.6.1. 单例设计模式

设计模式:由前人总结的,用来解决特定问题的一种解题思路。
单例设计模式:在项目的任意模块、任意位置,某个类的对象只能是唯一的一个。


如果每次需要获取到的类的对象需要时同一个对象,此时,对象的获取不能使用new的方式。因为new代表了开辟一块新的空间。两次new出来的空间一定是不相同的。


实现单例设计模式的步骤

  1. 把类的构造方法私有化,杜绝从类外通过new的方式实例化对象的可能性。
  2. 给类添加一个方法,返回一个当前类的对象。
  3. 单例分为懒汉式单例 和 饿汉式单例
  4. 声明一个私有的、静态的、当前类的对象
  5. 饿汉式:直接在声明的同时,对这个静态属性进行实例化
public class Chairman {
	
	private static Chairman Instance = new Chairman();
	
	private Chairman() {
		System.out.println("一个新的Chairman对象被实例化了");
	}
	
	public static Chairman getChairman() {
		return Instance;
	}
}

c. 懒汉式:声明好静态的当前类对象后,不去实例化。当第一次调用getChairman方法的时候实例化。

public class Chairman {
	
	private static Chairman Instance;
	
	private Chairman() {
		System.out.println("一个新的Chairman对象被实例化了");
	}
	
	public static Chairman getChairman() {
		if (Instance == null) {
			Instance = new Chairman();
		}
		return Instance;
	}
}

5.7. 包 Package


包,起到了组织代码、组织文件。类似于文件夹。

package: 声明当前文件属于哪一个包,为了给编译后的.class文件找路径。


在程序中,如果需要在一个类中,使用到另外一个包里面的类,有两种方式可以实现:


1)使用类的全限定名:从这个类最外层的包开始,一层一层的往里面找。
2)导包:使用关键字 import 导入指定的包(类)。

5.8. 继承

是面向对象三大特性之一【封装、继承、多态】

5.8.1. 什么是继承

如果有多个相关联的类,具有相同的属性和方法,那么,可以将这些相同的部分提取出来,单独做一个类。


这个被提取出来的,具有公共部分的类,叫做 – 父类,基类,超类(Super Class)


被提取的具有相同的属性方法的类,叫做 – 子类,派生类


他们之间的关系,叫做 – 继承。
子类 继承自 父类

5.8.2. 继承的语法

extends:用来描述继承
class 子类类名 extends 父类类名 {}

5.8.3. 继承的特点

  1. Java是单继承的。一个类只能有一个父类,但是一个类可以有多个子类。
  2. 一个类在继承了父类的同时,还可以被其他的类继承。
  3. 子类可以访问父类中看得到的成员。父类可以将属性、方法继承给子类。

所谓的"看得到的",跟成员的访问权限有关。

  1. 子类在拥有父类成员的基础之上,还可以有自己的成员,而且自己特有的成员,只能被自己和自己的子类访问。

5.8.4. 访问权限修饰符

访问权限:指的是某一个类、属性、方法 可以被访问的范围。

访问权限修饰符可以修饰访问级别
公共权限public类、属性、方法在当前项目的任意位置都可以访问
保护权限protected属性、方法在当前包中,和跨包的子类中可以访问
包权限-类、属性、方法在当前包中可以访问
私有权限private属性、方法在当前类中可以访问

5.8.5. 继承中的构造方法

一个对象在实例化的时候,会自动的调用构造方法。


一个对象,在实例化,在堆上分配内存空间的时候,会先实例化父类部分。会优先给从父类继承的属性分配空间。在实例化父类部分的时候,会自动的调用父类中的无参构造方法。


因此,如果父类中没有无参构造,会对子类造成影响,导致子类对象无法正常实例化。

关键字super: 代表对父类对象的引用,类似于this关键字
但是,对于super,一般情况下,我们只在两种场景下使用:

  1. 在构造方法中,使用super(),调用父类中的构造方法。
  2. 在非静态方法中、构造方法中,使用super调用父类中的方法。


如何解决这个问题:

  1. 给父类添加一个无参构造。
  2. 在子类的构造方法中,手动使用super()调用父类中可见的构造方法。

5.8.6. 方法的重写

方法重写Override:
在子类中,对从父类继承到的方法进行重新实现。
重写,又叫做覆写。在子类中,用子类的实现方式覆盖掉父类的实现方式。
子类从父类中继承到某一个方法,将父类的实现方式抛弃,改成自己的实现方式。
@Override
是系统内置的一个注解,用于方法前。
作用:检测下面的一个方法,是不是一个重写的方法。
注意:这个注解只是起到一个验证的作用,并不意味着没有添加这个注解,就不是一个重写方法了。但是出于规范,一般情况下,重写的方法都需要添加一个@Override


重写对方法的返回值、访问权限的要求

  1. 重写的方法,访问权限要大于等于父类中方法的访问权限。

public > protected > 包权限 > private

  1. 重写的方法,返回值类型要小于等于父类方法的返回值类型。


final关键字

可以修饰含义
变量表示值不可以发生改变,是一个常量
表示最终类,这个类无法被继承
方法表示最终方法,这个方法无法被重写

5.8.7. Object类


Object是Java的根类,在Java中,所有的类都直接或者间接的继承自Object类。


Object类中的几个方法:

  • getClass


获取用来描述当前类的Class对象,可以获取一个类型。

  • equals


由于 == 比较,对于对象来说,只能比较地址。因此,在进行比较的时候,很多时候不符合逻辑需求。


equals就是一个用来比较两个对象的方法。默认实现,比较的是地址。如果需要自定义比较的规则,可以重写这个方法。

  • 注意事项:

虽然我们可以在equals方法中,进行自定义的规则比较。但是,在制定比较规则的时候,还是要遵循一定的规范的。
如果obj是null,一定要返回false
如果this == obj,一定要返回true
如果两个对象的类型不同,一定要返回false
a.equals(b)如果是true,则 b.equals(a) 也一定要是true
a.equals(b) == true && b.equals© == true, 那么 a.equals© 一定也得是true

  • hashCode

获取一个对象的在一个散列集合中的索引。(哈希表)(HashSet、HashMap)

  • toString

可以通过重写这个方法,实现自定义的对象的字符串表示形式。

5.9. 多态

是面向对象三大特性之一【封装、继承、多态】

5.9.1. 对象的转型

和数据类型转换比较像。


父类的引用可以指向子类的对象。

  • 向上转型

由子类类型转型为父类类型。
向上转型一定会成功,是一个隐式转换。
向上转型后的对象,将只能访问父类中公有的方法和属性。

  • 向下转型

由父类类型转型为子类类型。
向下转型可能会失败,需要显式转换。
向下转型后的对象,将可以访问子类中特有的方法和属性。


向下转型失败:
如果在做向下转型的时候,这个对象正好是要转型的类型,则此时会转型成功。
但是如果这个对象不是要转型的类型,此时会转型失败。如果转型失败,会出现 ClassCastException。

5.9.2. instanceof关键字

向下转型可能会失败,如果转型失败,就会出现类型转化异常 ClassCastException,因此,在做向下转型的时候,一定要判断要转型的对象是不是指定的类型。


可以使用关键字 instanceof 判断对象是否是要转型的类型。

对象 instanceof 类

判断一个对象是否是指定的类型,判断结果是一个boolean。

5.9.3. 多态

多态:向上转型后的对象,调用父类中的方法,最终的实现是子类的重写实现。

5.10. 抽象类

5.10.1. 抽象类和抽象方法

  1. 抽象方法:使用关键字abstract修饰的方法,叫做抽象方法


抽象方法,只有方法声明,没有方法实现。
抽象方法,只能被定义在抽象类中。不允许在一个非抽象类中定义抽象方法。

  1. 抽象类:使用关键字abstract修饰的类,叫做抽象类

抽象类除了可以包含以前所有的成员之外,还可以包含抽象方法。
抽象类不能实例化对象。
非抽象子类,在继承一个抽象父类的时候,必须实现父类中的抽象方法。(通过重写)

5.10.2. 抽象类的使用场景

结合抽象类和抽象方法的特点:抽象方法,必须写在抽象类中。


得出抽象类的使用场景:


可以通过抽象类和抽象方法,实现某些简单规则的制定


可以在抽象类中,定义若干抽象方法,让所有的非抽象子类必须实现。这些抽象方法,其实都是可以被当做规则。

5.10.2. 抽象类

可以使用抽象类,进行简单的规则制定。约束所有的子类。约束一个规范,让所有的子类都实现这个规范,方便对子类的统一管理。


但是,抽象类的规则制定,是通过继承实现的。而在Java中,继承是单继承,父类只能有一个。因此,如果使用抽象类进行规则的制定,可能会对一个类原有的继承结构造成影响。


另外,因为单继承的存在,一个类最多只能遵守一种规则约束。


为了让一个类可以遵守多个规则约束,引入接口的概念。

5.11. 接口


接口,也是一种自定义的引用数据类型,跟类比较相似。可以使用接口进行规则的指定,对所有的实现类进行统一的约束,使其具有相同的对外的方法和功能。

Java是单继承的。其实很多面向对象的语言,都是单继承的。很多语言在抛弃了多继承的同时,会使用其他的方法间接的实现多继承。而Java就是通过接口间接的实现多继承的。

5.11.1. 接口的定义


接口的定义,需要使用关键字 interface


接口中成员的定义:

  1. 可以写方法:

接口中的方法,默认都是用 public abstract 修饰的。
即便接口中的方法没有任何的修饰符,他们依然是 public abstract。

  1. 可以写属性:

接口中的属性,默认的修饰符是 public static final。
接口中的属性定义,必须同时赋初始值。

5.11.2. 接口的实现


接口不是类,不能实例化对象。接口写完之后,是要给某一个类去实现的。


类,在实现接口的时候,需要使用关键字 implements

  1. 接口的实现,对于继承没有影响。一个类可以在继承父类的同时,实现接口。
  2. 如果一个类既有继承,又有实现。在类的定义部分,先写继承,再写实现。

public class TestClass extends Object implements Test

  1. 一个类可以实现多个接口,一个接口也可以被多个类实现。

如果一个类实现了一个接口。那么这个类被称为这个接口的 实现

  1. 如果一个实现类实现了接口,那么这个实现类中可以 “继承” 到接口中的成员。

**
实现接口注意事项

  1. 如果接口方法,在父类中已经被实现了。子类是可以继承到这些方法的,因此,在子类中,可以不去重写实现
  2. 如果一个类实现的多个接口中,有相同的方法。实现类中只需要实现一次即可


Java8的新特性


1. static


在Java8中,可以使用static修饰一个接口中的方法。接口中静态的方法,需要添加实现。


接口中的静态方法,只能用接口自己调用。


2. default


在Java8中,可以使用default修饰一个接口中的方法。default方法,也需要添加实现。


使用default修饰的方法,需要用接口的实现类对象来调用。


其实,default就是给接口方法添加一个默认的实现方式,实现类在实现这个接口的时候,可以重写这个方法,也可以不重写这个方法。

5.11.3. 接口的继承

接口之间是有继承的。并且接口之间的继承是多继承。
子接口可以继承到父接口中所有的方法。

5.11.4. 接口的多态

对象转型
接口的引用,可以指向实现类的对象。

  1. 实现类类型转型为接口类型,是一个向上转型。
  2. 接口类型转型为实现类类型,是一个向下转型。


多态
向上转型后的对象,去调用接口中的方法,最终的实现是实现类中的实现方式。

5.11.5. 接口在案例

5.11.6. 函数式接口


如果一个接口中,有且只有一个方法,是实现类必须要实现的。这样的接口就叫做函数式接口。
@FunctionalInterface 是一个用来修饰接口的注解。可以去验证一个接口是不是一个函数式接口。

5.12. 内部类

5.12.1. 成员内部类

5.12.2. 静态内部类

5.12.3. 局部内部类

5.12.4. 匿名内部类

匿名内部类,作为内部类部分的重点,也是必须要掌握的。


匿名内部类就是没有名字的内部类。


匿名内部类,一般是配合其他类一块使用的。表示这个类的子类。


实际应用中,匿名内部类,一般情况下,是配合抽象类或者接口使用的

5.13. lambda表达式

5.13.1. 什么是lambda表达式

lambda表达式是Java8的新特性之一。其实本质上来讲,lambda表达式是一个匿名函数。


实际应用中,lambda表达式一般是配合抽象类或者接口使用的。配合接口的使用场景更多。


可以使用lambda表达式对一个接口进行非常简洁的实现。
Lambda表达式可以简化接口的实现。但是并不是所有的接口都可以使用lambda表达式实现,lambda表达式对接口是有要求的。要求接口是一个函数式接口。

5.13.2. lambda表达式的基础语法

lambda表达式是对接口中的方法进行简单的实现,本质是一个匿名函数。
对于lambda表达式的语法部分,需要重点关注的是:方法的参数列表、方法体
关键的符号: -> lambda运算符,读作 “goes to”

也就是说,一个完整的lambda表达式,由三部分组成:参数列表、lambda运算符 和 方法体
其中,lambda运算符的作用是分隔> 参数列表 和 > 方法体
使用lambda表达式来实现接口中的抽象方法,因此,在使用接口引用调用接口中的方法的时候,实际执行的是lambda表达式中的实现方式。

5.13.3. lambda表达式的语法进阶


方法体部分的精简:
如果一个lambda表达式的方法体中,只有一句代码,则大括号可以省略。
如果lambda表达式方法体中唯一的一句代码,是一个返回语句。在省略了大括号的同时,必须省略return。


参数部分精简:
可以省略掉参数的类型,因为参数的类型在接口的方法声明中已经明确了。
如果有多个参数,那么要么都省略,要么都不省略。不要出现有的省略了,有的没有省略。
如果参数列表中有且只有一个参数,则小括号可以省略。

5.13.4. 函数引用

如果一个lambda表达式的方法体中,只是在调用其他的方法。这种情况下,对于方法的参数、方法体、lambda运算符。都可以省略。直接引用这个要调用的方法即可。


方法引用,分为两部分:


第一部分:引用的方法调用方。


第二部分:引用方方法。


中间使用两个冒号分隔。


方法引用,其实就是用一个已经存在的方法来实现指定的接口中的抽象方法。因此:引用的方法参数和返回值必须和接口中的方法保持一致。

6. 包装类

6.1. 包装类是什么

包装类:是对基本数据类型的包装,基本数据类型都是值类型,可以通过包装类,使其具有引用类型的一些特性。

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

6.2. 装箱与拆箱

装箱: 将基本数据类型转型为包装类型

  1. 可以通过包装类的构造方法来实现装箱
    Integer a = new Integer(10);
  2. 可以通过包装类提供的静态方法valueof完成装箱
    Integer a = Integer.valueOf(10);

拆箱: 将包装类型转型为基本数据类型
使用包装类中的 xxxValue() 方法 Integer: int a = aa.intValue();
在JDK 1.4 之后,装箱与拆箱是自动完成的!

6.3. 包装类中享元原则

6.4. 字符串与基本数据类型的转换


1. 字符串转换为数字


java.lang中的Integer类调用其类方法public static int paeseInt(String s)可以将由“数字”字符组成的字符串,比如“876”转换为int数据类型,例如:


 int x;


 String s = “876”;


 x = Integer.paeseInt(s);


 类似地,使用java.lang包中的Byte、Short、Long、Float、Double类调用相应的类方法


public static byte parseByte(String s) throws NumberFormatException 


public static short parseShort(String s) throws NumberFormatException 


public static long parseLong(String s) throws NumberFormatException 


public static double parseDouble(String s) throws NumberFormatException 


2. 可以使用下面的方法将数字转化为相应的字符串对象:


① valueOf方法


 public static String valueOf(byte n)


 public static String valueOf(int n)


 public static String valueOf(long n)


 public static String valueOf(float n)


 public static String valueOf(double n)


 例如:将形如123,12345.8等数值转换为字符串


 String str  =  String.valueOf(123)


 String str  =  String.valueOf(12345.8)


② 可以使用toString方法(一个对象通过调用该方法可以获取该对象的字符串表示)


使用java.lang包中的Byte、Integer、Short、Long、Float、Double类调用相应的类方法,Integer.toString、Double.toString,


等等


比较常用的是Integer.toString("要进行转换的数字“”),方法得到的是一个字符串的对象


例子:


int n = 123;


String m = Integer.toString(n);


System.out.println(m + 1);






所以我们可以使用上面的方法来将数字型的字符串转化为一个字符串


Date date = new Date();
System.out.println(date.toString());






3. 字符串与字符数组


① 将字符数组转换为字符串对象


使用String类的构造方法:String(char a[])和String(char a[],int offset,int length)


分别用数组a中的全部字符和部分字符构造字符串对象


例子:


char a[] = {‘1’,‘2’,‘3’};


System.out.println(new String(a).length());






② String类也提供了将字符串存放到数组中的办法: public void getChars(int start,int end,char c[],int offset);


例子:


char a[];


String s = “abcdefghijklmnopqrst”;


s.getChars(11,15,a,0);


还有一个更加简练的方法将字符串中的全部字符存放到一个字符数组的办法:public char [] toCharArray();


例子:


int num = 10;


char s[] = new char[10];
s = Integer.toString(num).toCharArray();

7. 常用类

7.1. Math类

是一个数学类,里面封装了若干个数学公式。当使用到数学运算的时候,可以直接使用这个类。

7.2. Random类

是一个用来产生随机数的类

7.3. BigInteger、BigDecimal类

BigInteger: 用来描述一个非常大的整型数字


BigDecimal: 用来描述一个非常大的浮点型数字

7.4. Date、SimpleDateFormat类

Date: 是一个日期类,用来描述一个日期、时间。

SimpleDateFormat: 日期格式化的类,可以实现将一个Date对象转成指定格式的字符串,或者将一个指定格式的字符串转成Date对象。

7.5. Calendar类

这个类,也是一个用来表示日期、时间的类。在Date中,很多的时间操作的方法都废弃掉了,用这个类中的方法进行替代。

8. 枚举

8.1. 什么是枚举?为什么要用枚举?


枚举,也是一种自定义的数据类型。这种类型和类很像,但是比类多个一种成员。枚举中可以写枚举元素。


使用枚举,可以限定某些变量的取值范围。


例如:在前面的需求中,我们常把"性别"设置为char类型。但是,如果将性别设置为字符型,那么,这个变量可以取的值,将会非常多。


char gender = ‘男’; gender = ‘女’;


gender = ‘a’; gender = ’ '; gender = ‘l’; gender = ‘.’;


再例如:如果需要定义一个变量,来表示月份,这个变量的类型应该是?


int month = 1000;


什么情况下使用枚举


当某些变量的取值范围,在有限的几个值中去取。这种情况下,就可以设置为枚举类型。


使用枚举的好处


可以大幅度的提高代码的可读性。

8.2. 枚举的定义与基本的使用


定义枚举,需要使用关键字 enum ,其他的语法,与类完全相同。


在枚举中,可以写出这个枚举类型的变量,可以取到的每一个值。这些值,就是枚举的元素。

9. 异常

9.1. 异常的体系


什么是异常?


异常,指的是程序在编译、运行的过程中遇到的种种不正常的情况,导致程序无法正常的编译、运行。


异常,在Java中,是有专门的类来表示的。


Throwable : 是错误和异常的根类


Error : 错误信息,是发生在jvm虚拟机上的,程序无法处理的非正常情况。


Exception : 异常,可以在程序中进行处理。

9.2. 异常的分类


异常,可以分为编译时异常和运行时异常。


其实,异常的分类,分两种:


Runtime Exception 和 Non-Runtime Exception


准确来说,应该是运行时异常, 和非运行时异常。


但是,因为非运行时异常,是发生在程序编译的时候,如果不去处理,会导致程序无法正常编译。因此,习惯上来讲,会将非运行时异常,称为编译时异常。


一个未经处理的运行时异常,会导致程序运行终止。


一个未经处理的编译时异常,会导致程序编译失败。

9.3. 异常的基本处理方式 try-catch


基础语法

try {
	// 可能会出现异常的代码
}
catch (可能出现的异常类型 标识符) {


执行逻辑:


如果try中的代码出现了异常,则会和catch进行匹配。


如果可以匹配上,表示这个异常已经被捕获处理了,此时,将不再终止程序的运行。同时,catch代码段中的逻辑会执行。


如果匹配不上,此时这个异常依然没有被处理,可以继续终止程序。同时catch代码段中的逻辑将不会执行。


语法注意事项:


try代码段不能独立存在,后面必须跟上catch或者finally


可以使用多个catch,捕获多种异常


如果多个catch中捕获的异常类型没有继承关系,则先后顺序无所谓。


如果多个catch中捕获的异常类型有继承关系,则子类异常在前,父类异常在后。


可以在一个catch中,使用 | 将多种异常拼接在一起,这时候多个异常的先后顺序无所谓,这种情况下,不允许出现父类异常。


如果一个try代码段中出现了很多种异常,并且,多种异常的处理方式完全相同。则此时,可以直接catch父类异常。

9.4. finally


finally: 用在try或者catch后面。无论try中的代码是否出现了异常,finally中的语句始终会执行。


在finally中,常常会做 资源释放、关闭流… 等工作

9.5. throw和throws关键字


我们可以在程序中实例化一个异常类的对象,但是一个异常对象被实例化完成后,没有任何意义。只有当抛出这个异常的时候,这个异常才会生效,会终止程序运行。


throw: 配合异常对象使用,使这个异常生效。


throws: 写在方法的声明部分,在参数列表之后,方法体之前。用来声明这个方法中有什么异常。


最重要的作用:表示这个方法中出现的异常,可以不去处理。由调用方处理。

9.6. 自定义异常


当我们需要使用异常的时候,系统提供的异常类已经不能满足我们的需求,那么我们就需要自定义异常。


其实,自定义异常,就是继承自Exception类或者RuntimeException类,做一个子类。


继承自Exception类的异常,是编译时异常。
继承自RuntimeException类的异常,是运行时异常。

10. 字符串

10.1. 字符串类String

10.1.1. 字符串类型的内存分析



字符串,其实是由若干个字符组成的一个有序序列。在Java中,使用String类来描述一个字符串。

在String类中,维护了一个字符数组。其实字符串的存储,底层实现就是使用一个字符数组来实现的。

字符串是一个类,也是一个引用数据类型。但是这个引用类型,和前面讲的类、接口都不一样。字符串的空间开辟不在堆上。字符串是在常量池中开辟的空间。在字符串中,依然有享元原则。当我们第一次使用到某一个字符串的时候,在常量池中会将字符进行排列组成,组成一个我们需要的字符串,将地址给引用赋值。第二次再使用到完全相同的字符串,此时会将上次组合好的字符串地址直接给新的引用赋值。

可以通过构造方法,实例化一个字符串对象。通过构造方法实例化的字符串,和直接使用双引号写的字符串有什么区别?

**字符串是在常量池开辟的空间,遵循享元原则。**
** ### 10.1.2. 字符串常用的方法 构造方法 "", new String, new char[] {}, 详见jdk1.8文档 ```java // 0. 因为字符串的空间开辟是在常量池,也就是说,字符串的内容是不允许发生改变的。 // 操作字符串的方法,都不会直接修改这个字符串,而是将操作结果作为方法的返回值,返回一个新的字符串

// 1. 字符串拼接
System.out.println(“hello” + “world”);
System.out.println(“hello”.concat(“world”));

// 2. 字符串截取(截取字符串中的一部分)
// 从第3位开始,一直到字符串的结尾
System.out.println(“hello world”.substring(3));
// 截取指定范围的字符串 [start, end)
System.out.println(“hello world”.substring(3, 7));

// 3. 字符串替换
// 将一个字符串中的指定字符替换成其他字符
System.out.println(“hello world”.replace(‘l’, ‘L’));
// 将一个字符串中指定的字符序列替换成新的字符序列
System.out.println(“hello world”.replace(“el”, “ELLLL”));

// 4. 将一个字符串转成一个字符数组
char[] array = “hello world”.toCharArray();
System.out.println(Arrays.toString(array));

// 5. 将一个字符串转成字节数组
byte[] array1 = “字节”.getBytes();
System.out.println(Arrays.toString(array1));
try {
byte[] array2 = “字节”.getBytes(“utf8”);
System.out.println(Arrays.toString(array2));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}

// 6. 查询下标(如果找不到要查询的内容,则查询结果返回-1)
// 6.1. 查询一个字符,在字符串中第一次出现的下标
System.out.println(“hello world”.indexOf(‘l’));
// 6.2. 查询一个字符,在字符串中最后一次出现的下标
System.out.println(“hello world”.lastIndexOf(‘l’));
// 6.3. 查询一个字符串,在一个字符串中第一次出现的下标
System.out.println(“hello world”.indexOf(“ll”));
// 6.4. 查询一个字符串,在一个字符串中最后一次出现的下标
System.out.println(“hello world”.lastIndexOf(“ll”));
// 6.5. 查询一个字符,在指定的范围内第一次出现的下标
System.out.println(“hello world”.indexOf(‘l’, 5));
// 6.6. 查询一个字符串,在在一个字符串的指定范围内出现的下标
System.out.println(“hello world”.indexOf(“rl”, 5));
// 6.7. 查询一个字符,在指定的范围内最后一次出现的下标
System.out.println(“hello world”.lastIndexOf(‘l’, 5));
// 6.8. 查询一个字符串,在指定的范围内最后一次出现的下标
System.out.println(“hello world”.lastIndexOf(“ll”, 5));

// 7. 大小写转换
// 7.1. 将一个字符串中的小写字母转成大写字母
System.out.println(“hello WORLD”.toUpperCase());
// 7.2. 将一个字符串中的大写字母转成小写字母
System.out.println(“hello WORLD”.toLowerCase());

// 8. 获取字符串的长度
System.out.println(“hello world”.length());

// 9. 判断一个字符串是否是空字符串
System.out.println("".isEmpty());

// 10. 判断一个字符串的前后缀
// 10.1. 判断一个字符串是否以指定的字符串开头
System.out.println(“hello world”.startsWith(“he”));
// 10.2. 判断一个字符串是否以指定的字符串结尾
System.out.println(“hello world”.endsWith(“ld”));

// 11. 判断一个字符串是否包含指定的字符串
System.out.println(“hello world”.contains(“lo”));

// 12. 判断两个字符串中的内容是否相同
System.out.println(“hello world”.equals(“hello world”));
System.out.println(“hello world”.equalsIgnoreCase(“HELLO WORLD”));

// 13. 去除一个字符串前后两端的空格
System.out.println(" hello world ".trim());

// 14. 字符串大小比较
// 字符串比较的结果:不看具体的数值,看正负。
// 正数: “hello” > “world”
// 负数: “hello” < “world”
// 0: “hello” == “world”
// 字符串比较规则:
// 依次比较两个字符串中的每一个字符,直到某一个字符的比较可以分出大小。
int ret = “hello”.compareTo(“world”);
System.out.println(ret);

int ret2 = “hello”.compareToIgnoreCase(“HELLO”);
System.out.println(ret2);

// 15. 字符串格式化
// 将若干个变量的内容,按照一定的格式拼接到一个字符串中。
String name = “xiaoming”;
int age = 10;
char gender = ‘男’;
// 姓名: xiaoming, 性别: 男, 年龄: 10岁
/**

  • 在fotmat方法中,可以定制一个字符串的格式,在格式字符串中,可以使用指定的符号进行占位。
  • %s : 字符串
  • %c : 字符类型
  • %d : 整型 %03d: 占用长度为3位,如果不够3位,往前补0
  • %f : 浮点型 %.2d: 小数点后面保留两位
    */
    String retStr = String.format(“姓名: %s, 性别: %c, 年龄: %03d岁”, name, gender, age);
    System.out.println(retStr);

// 16. 字符串切割
// 按照指定的字符,切割字符串
String[] names = “xiaoming, xiaohong, xiaoli, xiaoming, xiaolan, xiaobai, xiaohei”.split(", ");
for (String n : names) {
System.out.println(n);
}

<a name="2eP4m"></a>
## 10.2. StringBuffer、StringBuilder
StringBuffer: 字符串缓冲池,可以用来操作一个字符串。<br />StringBuilder: 字符串构建器,是使用工厂模式实现的字符串的常见操作。
```java
// StringBuffer: 使用StringBuffer来操作一个字符串
// 实例化一个StringBuffer对象,这种方式实例化的是一个空字符串
// StringBuffer sb = new StringBuffer();
// 通过一个字符串字面量,实例化一个StringBuffer对象
StringBuffer sb = new StringBuffer("hello world");

// 1. 增:在一个字符串的尾部拼接一个新的字符串
sb.append("!");

// 2. 增:在指定的位置插入一个新的子串
sb.insert(5, '@');

// 3. 删:删除指定范围的字符 [start, end)
sb.delete(5, 12);

// 4. 删:删除指定的下标的字符
sb.deleteCharAt(5);

// 5. 改:将一个字符串中指定范围的字符替换成指定的字符串
sb.replace(2, 4, "你好,师妹");

// 6. 改:修改指定位的字符
sb.setCharAt(4, '。');

// 7. 字符串反转  abcd -> dcba
sb.reverse();

// 8. 转成字符串
sb.toString();

System.out.println(sb);

StringBuilder的方法,与StringBuffer完全相同


StringBuffer是线程安全的,StringBuilder是线程不安全的。


如果不考虑线程安全的情况(例如,在单线程中),推荐使用StringBuilder,因为StringBuilder是线程不安全的,没有做代码同步。因此,效率高。

10.3. 正则表达式

正则表达式,不是Java特有的内容。是一种独立的表达式。主要的作用是来做字符串的校验。正则是来制定一些字符串应该遵循的规则,可以去判断一个字符串是否满足这样的规则。

11. 集合

11.1. 泛型https://www.cnblogs.com/Tractors/p/11247664.html

11.1.1. 泛型的定义

泛型,就是泛指的类型。可以将类型在类与类、方法、接口之间进行传递。类似与方法的传参

可以在类、接口、方法中声明一个类型,这个类型,仅可以在当前的类、接口、方法中使用。

如果一个类中定义了泛型,这个类就是一个泛型类
如果一个接口中定义了泛型,这个类就是一个泛型接口
如果一个方法中定义了泛型,这个类就是一个泛型方法


当使用到泛型类、泛型接口、泛型方法的时候,需要指定泛型的类型。如果不指定类型,泛型默认为Object类型


类似于,调用方法的时候,需要传实参

泛型在类中的定义:
在类名的后面,添加一对尖括号<>,在尖括号中,写上泛型的类型
泛型的类型名字,遵循大驼峰命名法。但是,一般情况下,泛型只用一个大写字母表示。
一个类中,可以定义多个泛型,多个泛型,都写在一个<>中,使用,逗号做分隔

public class Box<T> {
   
  private T t;
 
  public void add(T t) {
    this.t = t;
  }
 
  public T get() {
    return t;
  }
 
  public static void main(String[] args) {
    Box<Integer> integerBox = new Box<Integer>();
    Box<String> stringBox = new Box<String>();
 
    integerBox.add(new Integer(10));
    stringBox.add(new String("菜鸟教程"));
 
    System.out.printf("整型值为 :%d\n\n", integerBox.get());
    System.out.printf("字符串为 :%s\n", stringBox.get());
  }
}

泛型在接口中的定义同在类中的定义


泛型在方法中的定义:
在返回值的前边写<>,也就是写在void前面写上<>

11.1.2. 泛型的使用

下面的例子演示了"extends"如何使用在一般意义上的意思"extends"(类)或者"implements"(接口)。该例子中的泛型方法返回三个可比较对象的最大值。

public class MaximumTest
{
   // 比较三个值并返回最大值
   public static <T extends Comparable<T>> T maximum(T x, T y, T z)
   {                     
      T max = x; // 假设x是初始最大值
      if ( y.compareTo( max ) > 0 ){
         max = y; //y 更大
      }
      if ( z.compareTo( max ) > 0 ){
         max = z; // 现在 z 更大           
      }
      return max; // 返回最大对象
   }
   public static void main( String args[] )
   {
      System.out.printf( "%d, %d 和 %d 中最大的数为 %d\n\n",
                   3, 4, 5, maximum( 3, 4, 5 ) );
 
      System.out.printf( "%.1f, %.1f 和 %.1f 中最大的数为 %.1f\n\n",
                   6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) );
 
      System.out.printf( "%s, %s 和 %s 中最大的数为 %s\n","pear",
         "apple", "orange", maximum( "pear", "apple", "orange" ) );
   }
}


编译以上代码,运行结果如下所示:

3, 45 中最大的数为 5

6.6, 8.87.7 中最大的数为 8.8

pear, apple 和 orange 中最大的数为 pear


接口泛型

interface Inter<T>{
    void show(T t);
}
class InterImpl implements Inter<String> {
    @Override
    public void show(String s) {
        System.out.println(s);
    }
}
public class Test {
    public static void main(String[] args) {
        InterImpl inter = new InterImpl();
        inter.show("s");
    }
}


**泛型方法:**修饰符  返回值类型 方法名(T t){};

class Demo {
    public <T> void show(T t){
        System.out.println(t);
    }
}
public class Test {
    public static void main(String[] args) {
        Demo demo = new Demo();
        demo.show("string");
        demo.show(123);
    }
}

注意事项:

  1. 如果在使用到类的时候,不对泛型指定类型,则泛型默认为Object类型
  2. 泛型,只能使用引用数据类型
  3. 在一个类中定义的泛型,只能在当前类中使用,不能继承给子类

11.1.3. 泛型限定

使用一个泛型类对象,给另一个泛型类型引用进行赋值的时候,对泛型的约束
 1、通配符:?;也可以理解为占位符;
  2、用法:

**    1) ? extends E:上限;可以接收E类型或者E的子类型;
    2) ? super E:**下限;可以接收E类型或者E的父类型;

import java.util.*;

class Person{

    private String name;

    public Person(String name) {this.name = name;}

    public String getName() { return name;}

  }

class Student extends Person{

    public Student(String name) {super(name); }

}

public class Test {

    public static void main(String[] args) {

        ArrayList<Person> al1=new ArrayList<Person>();

        al1.add(new Person("张三"));

        al1.add(new Person("李四"));

        al1.add(new Person("王五"));

 

        ArrayList<Student> al2=new ArrayList<Student>();

        al2.add(new Student("刘一"));

        al2.add(new Student("刘二"));

        al2.add(new Student("刘三"));

        

        printAll(al1);

        printAll(al2);

    }

    public static void printAll(ArrayList<? extends Person> al){

        Iterator<? extends Person> it=al.iterator();

        while (it.hasNext()){

            System.out.println(it.next().getName());

        }}}

 

11.2. 集合类(Collection):

1)用于存储对象;长度可变;存储不同类型对象;
  2)集合类和数组的区别:
    (1)数组长度不变;集合类可变;
    (2)数组中数据都是同类型;集合类可以不同;
(3)操作数组的方式比较单一
集合提供了大量的方法操作容器中的数据

二、集合框架的构成和分类:
1651677-20190723233043192-558254939[1].png

11.3. Collection接口

是集合框架中,单列集合的顶级接口。这个接口中,定义了对单列集合进行的常规操作。所有的子接口都可以继承到这些接口,所有的实现类,都需要实现这些方法。
add
addAll
remove
removeAll删除交集
retainAll保留交集
removeIf删除符合要求的元素
clear清空

(1)List(列表):

可存放重复元素,元素存取是有序的;该集合体系有索引;
List中常用类:
   1.ArrayList:数组结构;线程不同步,增删速度慢,查询速度快。1.2jdk
   2.LinkedList:链表结构;增删速度快,查询速度慢。
   3.Vector:数组结构;线程同步;已被ArrayList替代;1.0jdk;colleciton—1.2jdk  

(2)Set(集合):

不可存放重复元素,元素存取是无序的;该集合体系没有索引;
  //无序不代表随机:底层的存储是有序的,只是存入和取出的顺序不一致;

Set:注重独一无二的性质,该体系集合可以知道某物是否已近存在于集合中,不会存储重复的元素


set集合的去重原理(HashSet的去重原则)
Set集合去重主要是调用 add 方法时,使用了 hashCode 方法和 equals 方法:


如果在 Set集合 中找不到与该元素 hashCode 值相同的元素,则说明该元素是新元素,会被添加到 Set 集合中;


如果两个元素的 hashCode 值相同,并且使用 equals 方法比较后,返回值为 true,那么就说明两个元素相同,新元素不会被添加到 Set 集合中;


如果 hashCode 值相同,但是 equals 方法比较后,返回值为 false ,则两个元素不相同,新元素会被添加到 Set 集合中。

方法返回值说明
hashCode() == true && equals () == truetrue判定元素重复
hashCode() == true && equals() == falsefalse判定元素不重复
hashCode() == falsefalse判定元素不重复
public class User {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
 
    //重写 equals 方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return id == user.id &&
                Objects.equals(name, user.name);
    }
	
    //重写 hashCode 方法
    @Override
    public int hashCode() {
        return Objects.hash(id, name); //使用属性的值生成一个哈希散列值
    }

    public static void main(String[] args) {
        HashSet<User> hs = new HashSet<>();
        User u1 = new User(1,"zs");
        User u2 = new User(1,"zs");
        hs.add(u1);
        hs.add(u2);
        for (User h : hs) {
            System.out.println(h);
        }
    }
}

控制台打印:
User{id=1, name='zs'}


Set中常用类:
  1.HashSet:哈希表结构;线程不同步;通过hashCode和equals两方法保证数据的唯一性;如果hashCode相同才判断元素的equals;
  2.LinkedHashSet;内部是一个双向链表结构;
  3.TreeSet:二叉树;**自然顺序排序;**通过compareTo或者compare两方法保证元素唯一性;元素以二叉树形式存放;

TreeSet的排序
TreeSet是SortedSet的实现类不同于其他Set集合,它采用红黑树的数据结构来存储集合元素,与其他Set集合一样,它也不允许有相同的元素,同时它具有着排序功能.通常情况下,如果我们直接向TreeSet中添加元素时,它是默认使用自然排序来对我们添加的元素进行排序

TreeSet可以自动的将集合中的数据,默认为从小到大的顺序排列
如果我们要从大到小排列应该怎么办呢?
其实也非常简单,java为我们提供了一个Collection.sort()的方法,我们只需要把需要排序的集合放入这个方法中,并带上一个比较器即可:


这个排序的方法,它的核心所在就是compareTo()这个方法,这个方法的作用我们都知道,当第一个参数比第二个参数大的时候,返回值为1;相等时,返回0;小于时,返回-1.可是它明明返回值是int,那为什么能够根据这个方法进行排序呢?
其实很简单,当前对象与后一个对象进行比较,如果比较结果为1进行交换,其他不进行交换。
当后一个对象比当前对象大,返回结果值为-1时,前后交换,说明是倒序排列。
当后一个对象比当前对象小,返回结果值为1时,前后交换,说明是升序排列.
总结一下,也就是说为-1时,就是倒序,为1时,就是正序.


总结TreeSet的两种排序:
(1)实现Comparable接口:根据重写的compareTo的返回值排序;

import java.util.Comparator; 
import java.util.Set; 
import java.util.TreeSet;
import lombok.Data;
public class TreeSetOrderDemo { 
/** 
* 演示TreeSet集合中的定制排序,按照学生年龄从小到大排序, 
* @param args 
*/ 
public static void main(String[] args) { 
Set set = new TreeSet<>(); 
Student stu1 = new Student(“貂蝉”, 17); 
Student stu2 = new Student(“西施”, 25); 
Student stu3 = new Student(“王昭君”, 22); 
Student stu4 = new Student(“杨贵妃”, 19); 
set.add(stu1); 
set.add(stu2); 
set.add(stu3); 
set.add(stu4); 
System.out.println(set); 
} 
}
@Data 
class Student implements Comparable { 
private String name; 
private int age;
public Student(String name, int age) {
    this.name = name;
    this.age = age;
}
 
@Override
public int compareTo(Student stu) {
 
    return this.age - stu.age;
}
 
@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Student other = (Student) obj;
    if (age != other.age)
        return false;
    if (name == null) {
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))
        return false;
    return true;
}

(2)实现Comparator接口:根据compare的返回值排序;

import java.util.Comparator; 
import java.util.Set; 
import java.util.TreeSet;
import lombok.Data;
public class TreeSetOrderDemo { 
/** 
* 演示TreeSet集合中的定制排序,按照学生年龄从小到大排序, 
* @param args 
*/ 
public static void main(String[] args) { 
Set set = new TreeSet<>(**new Comparator() { 
@Override 
public int compare(Student obj1, Student obj2) { 
if (obj1.getAge() > obj2.getAge()) { 
return 1; 
} else { 
if (obj1.getAge() < obj2.getAge()) { 
return -1; 
} 
} 
return 0; 
} 
}**); 
Student stu1 = new Student(“貂蝉”, 17); 
Student stu2 = new Student(“西施”, 25); 
Student stu3 = new Student(“王昭君”, 22); 
Student stu4 = new Student(“杨贵妃”, 19); 
set.add(stu1); 
set.add(stu2); 
set.add(stu3); 
set.add(stu4); 
System.out.println(set); 
} 
}
@Data 
class Student { 
private String name; 
private int age;
 
public Student(String name, int age) {
    this.name = name;
    this.age = age;
}
} 

//两种比较器都存在时,第2种优先于第1种;
https://www.cnblogs.com/jiliunyongjin/p/9603246.html


TreeSet的去重原理
通过比较方法,得到的结果是0,此时认为两个元素是重复的


Collections工具类
https://blog.csdn.net/qq_40521205/article/details/80812009?depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-2&utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-2

  1. 批量添加元素

Collections.addAll(collection, 元素1,元素2,…);

  1. 对集合中的元素进行随机排列

Collections.shuffle(collection);

  1. 使用指定的元素填充整个集合

Collections.fill(collection,指定的元素);

  1. 对集合中的元素进行排序,实现的Comparable接口中的规则进行大小比较

Collections.sort(collection)
使用指定的排序依据进行排序
Collections.sort(collection,(s1,s2)->s1.length()-s2.length());
 5. 将一个集合中的数据拷贝到另外一个集合中,dest空间要提前开辟好
Collections.copy(dest, src);  src拷贝到dest中

  1. 把线性不安全的集合转成线性安全的集合

List synchronizedList = Collections.synchronizedList(collection);

  1. 集合反转

Collections.reverse(collection);
**


11.4. Map(集合):

存储的为键值对;Map为双列集合,Colleciton单列集合;set集合底层为map集合;不是Collection的子接口;不能用迭代器;

**  1.HashTable:底层是哈希表数据结构,不可以存入null键null值;JDK1.0;线程同步,用的synchronized锁,表锁;效率低;
  2.ConcurrentHashMap:是HashtTable的升级版本,线程同步,用的分段锁,提升了效率;
  3.HashMap:底层是哈希表数据结构,允许存入null键null值;JDK1.2;线程不同步;效率高;
  4.LinkedHashMap;
  5.TreeMap:**底层是二叉树结构;可以排序;JDK1.2;线程不同步;

12. IO流

1、IO基本分类:(input and output stream)
**    1.字节流:byte[];没有缓冲区;
      抽象基类:InputStream,OutputStream;
    2.字符流**:char[];有缓冲区;//读取文档使用,可以防止中文乱码;
      抽象基类:Reader,Writer;
**2、资源释放:**jdk1.8新特性;
     **try(){}:**小括号内写需要释放资源的代码,会自动释放;
      //无需把close()写在finally代码块中;

12.1. File类

位于java.io包;
File:对磁盘上某一个文件(目录)的描述。因为IO流需要对文件进行操作,因此在使用IO流的时候,一定会使用到File类。File类,还包含了若干个对文件的操作方法。
File类:文件、文件夹(目录)、路径的描述
一、构造函数
  File(File parent, String child);     File从父抽象路径名和子路径名字符串创建新实例。
  File(String pathname);        File通过将给定的路径名字符串转换为抽象路径名来创建新实例。
  File(String parent, String child);    File根据父路径名字符串和子路径名字符串创建新实例。
  File(URI uri);            File通过将给定file:URI转换为抽象路径名来创建新实例 。
二、字段
  static String pathSeparator 系统相关的路径分隔符。(Windows系统中path1;path2;)中的;分隔路径
  static char pathSeparatorChar 与系统有关的路径分隔符。
  static String separator 系统相关的默认名称分隔符。(Windows系统中c:\)中的\
  static char separatorChar 取决于系统的默认名称分隔符。
三、常见功能
获取文件名
  String getName();
  String getPath();//获取文件路径
  String getAbsolutePath();绝对路径
获取文件大小
  long length();//获取文件大小
获取目录空间大小
  long getFreeSpace();//空闲空间(常用)
  long getTotalSpace();//总空间
  long getUsableSpace();//可用空间
获取文件修改时间
  long lastModified();
创建
  boolean createNewFile();//创建文件,如果存在就创建,否则,就不创建
  static File createTempFile(String prefix, String suffix) //使用给定的前缀和后缀生成其名称
 
创建目录
  boolean mkdir();//单级目录
  boolean mkdirs();//多级目录
删除
  boolean delete();//慎用可以删除文件或目录(目录中不能有文件或文件夹,)
  void deleteOnExit();//退出时,删除
判断
  boolean exists();//存在判断
  boolean isAbsolute();//绝对路径
  boolean isDirectory();//目录判断
  boolean isFile();//文件判断
  boolean isHidden();//隐藏判断
重命名
  boolean renameTo(File dest);//有剪切功能效果
换取系统根目录集合
  static File[] listRoots()
其他:
  String[] list();//当前目录下的文件夹名称和文件(包含隐藏文件),必须是目录
  String[] list(FilenameFilter filter);//过滤器(后缀名:name.endsWith(".java"))
深度遍历文件夹

public static void main(String[] args) {
    File dir=new File("目录");
    getListAll(dir,0);
    }

    private static void getListAll(File dir,int level) {
    System.out.println(getSpace(level)+dir.getName());
    File[] list=dir.listFiles();
    level++;
    for(File f:list) {
        if(f.isDirectory()) {
        getListAll(f,level);
        }else {
        System.out.println(getSpace(level)+f.getName());
        }        
    }
    }

    private static String getSpace(int level) {
    StringBuilder sb=new StringBuilder();
    sb.append("|--");
    for(int i=0;i<level;i++) {
        sb.insert(0, " ");
    }
    return sb.toString();
    }

12.2. IO流的简介

IO(Input Output)流:IO流用来处理设备之间的数据传输
流按操作数据分为两种:字节流和字符流
流按流向分为:输入流、输出流
字节流的两个顶层父类:
1、InputStream
  方法:
    int available(); 
    void close();–关闭输入流并释放与流关联的所有系统资源
    void mark(int readLimit);–标记此输入流中的当前位置
    boolean markSupported();–测试输入流是否支持mark和reset方法
    static InputStream nullInputStream()


    abstract int read();–从输入流中读取下一个数据字节
    int read(byte[] b);–从输入流中读取一定数量的字节,并将其存储到缓冲区数组b中
    int read(byte[],int off,int len);–len从输入流中读取多达字节的数据到字节数组中。
  
    byte[] readAllBytes(); --从输入流中读取所有剩余的字节。
    int readNBytes(byte[] b,int off,int len); --从输入流中将请求的字节数读取到给定的字节数组中。
    byte[] readNBytes(int len); --从输入流中读取指定数量的字节。
    void reset(); --将此流重新定位到mark在此输入流上最后一次调用该方法的位置 。
    long skip(long n);–跳过并丢弃n此输入流中的数据字节。
    void skipNBytes(long n);–跳过并完全丢弃n此输入流中的数据字节。
    long transferTo(OutputStream out);–从此输入流中读取所有字节,然后按读取顺序将字节写入给定的输出流。
2、OutPutStream
  方法:
    void close(); --关闭此输出流并释放与此流关联的所有系统资源。
    void flush(); --刷新此输出流,并强制写出所有缓冲的输出字节。
    static OutputStream nullOutputStream();
    void write(byte[] b);–将b.length指定字节数组中的字节写入此输出流。
    void write(byte[] b,int off,int len); --将len指定字节数组中从偏移量开始的字节写入off此输出流。
    abstract void write(int b); --将指定的字节写入此输出流。
字符流的两个顶层父类
 1、Reader
  方法:
    abstract void close();–关闭流并释放与其关联的所有系统资源。
    void mark(int readAheadLimit);–标记流中的当前位置。
    boolean markSupported();–此流是否支持mark()操作。
    static Reader nullReader();–返回Reader不读取任何字符的新字符。
  
    int read();–读取单个字符。
    int read(char[] cbuf);–将字符读入数组。
    abstract int read(char[] cbuf, int off, int len);–将字符读入数组的一部分。
    int read(CharBuffer target);–尝试将字符读入指定的字符缓冲区。
  
    boolean ready();–此流是否已准备好被读取。
    void reset();–重置流。
    long skip(long n);–跳过字符。
    long transferTo(Writer out);–从该读取器读取所有字符,并按照读取顺序将这些字符写入给定的写入器。
2、Writer
  方法:
    Writer append(char c);–将指定字符追加到此编写器。
    Writer append(CharSequence csq);–将指定的字符序列附加到此编写器。
    Writer append(CharSequence csq, int start, int end);–将指定字符序列的子序列追加到此编写器。
  
    abstract void close();–关闭流,先冲洗。
    abstract void flush();–冲洗流。(抽象方法需要子类实现)
  
    static Writer nullWriter();–返回一个Writer丢弃所有字符的新字符。
  
    void write(char[] cbuf);–写一个字符数组。
    abstract void write(char[] cbuf, int off, int len);–写入一部分字符数组。
    void write(int c);–写一个字符。
    void write(String str);–写一个字符串。
    void write(String str, int off, int len);–写入字符串的一部分。

12.3. 缓冲流

对父类流进行了一层包装,添加了一个缓冲区(Buffer),可以提高流操作的效率。
BufferedInputStream、BufferedOutputStream、BufferedReader和BufferedWriter

所有的流,在使用完成后,都需要进行关闭
可以将留的实例化放在try后面的小括号里,省略掉流的关闭操作
在所有的缓冲流中,都有一个属性,用来记录所包装的流对象。在缓冲流的close中,会对包装的流对象进行自动的关闭操作,也就是说,在使用缓冲流的时候,只需要对缓冲流进行关闭即可。

IO流(字节流)-使用缓冲

字节流:顾名思义操作(读、写)文件的流对象
字节流的体系:
1、InputStream
  |–FileInputStream:将文件数据读取到缓冲区中
2、OutputStream
  |–FileOutputStream:将缓冲区数据写入文件
一、FileInputStream和FileOutputStream

// 文件过大,有可能内存溢出
    private static void copy_3() throws IOException {
    FileInputStream fis = new FileInputStream("源文件路径");
    FileOutputStream fos = new FileOutputStream("目标文件路径");
    // available()文件大小
    byte[] buf = new byte[fis.available()];
    fis.read(buf);
    fos.write(buf);
    fis.close();
    fos.close();
    }

    // 使用缓冲区对象
    private static void copy_2() throws IOException {
    FileInputStream fis = new FileInputStream("源文件路径");
    BufferedInputStream bfis = new BufferedInputStream(fis);
    FileOutputStream fos = new FileOutputStream("目标文件路径");
    BufferedOutputStream bfos = new BufferedOutputStream(fos);

    int ch = 0;
    while ((ch = bfis.read()) != -1) {
        bfos.write(ch);
    }
    bfis.close();
    bfos.close();
    }

    // 自定义缓冲区
    private static void copy_1() throws IOException {
    FileInputStream fis = new FileInputStream("源文件路径");
    FileOutputStream fos = new FileOutputStream("目标文件路径");
    byte[] buf = new byte[1024];
    int len = 0;
    while ((len = fis.read(buf)) != -1) {
        fos.write(buf, 0, len);
    }
    fis.close();
    fos.close();
    }

IO流(字符流)

字符流:顾名思义操作(读、写)字符的流对象
字符流的体系:
1、Reader
  |–BufferedReader:从字符输入流中读取文本到缓冲区,便于有效读取字符,数组,和行
    |–LineNumberReader:有行号(setLineNumber(int) 、getLineNumber())
  |–InputStreamReader:字节流到字符流
    |–FileReader:将字符文件数据读取到缓冲区中
2、Writer
  |–BufferedWriter:将文本写入字符输出流到缓冲区,便于有效写入单个字符,数组和字符串。
  |–OutputStreamWriter:字符流到字节流
    |–FileWriter:将缓冲区数据写入字符文件
一、FileReader和FileWriter
FileReader原理:将指定的文本文件读到内存中
FileWriter原理:把内存中字符数据写入到实体设备上(落地)。
以文本文件Copy为例:

private static final int BUFFER_SIZE = 1024;

    public static void main(String[] args) {
    fileCopy();
    }

    private static void fileCopy() {
    FileReader fr=null;
    FileWriter fw=null;
    try {
        /*
         * 1、必须要明确被读取的文件,一定要确认文件时存在的。
         */
        fr=new FileReader("demo.txt");
        /*
         * 2、明确copy目的地
         * 如果文件不存在,自动创建 如果文件存在,会覆盖(删除再创建)
         */
        fw=new FileWriter("CopyDemo2.txt");
        
        //方式一:一个字符读取
//        int ch=0;
//        while((ch=fr.read())!=-1) {
//            fw.write(ch);
//        }
        
        //方式二:先把读取数据放入固定缓存中,然后把固定缓存写到硬盘
        //创建一个临时容器,用于缓存读取到的字符
        char[] buff=new char[BUFFER_SIZE];
        //记录读取到的字符数(其实就时向数组中装的字符个数)
        int len=0;
        while((len=fr.read(buff))!=-1) {
        /*
         * 把内容存储器读到内容全部写入到输出流中
         * 此时没有写入实体设备中(这里指硬盘)
         */
            fw.write(buff,0,len);
        }
        
    }  catch (IOException e) {
        throw new RuntimeException();
    }finally {
        if(fr!=null)
        try {
            fr.close();
        } catch (IOException e) {
            
            throw new RuntimeException();
        }
        if(fw!=null)
        try {
            fw.close();//先刷新,将输出流中数据写到实体设备中,关闭系统资源,
        } catch (IOException e) {
            throw new RuntimeException();
        }
    }
 }

427081-20191231164226697-2063098694[1].png
二、BufferedReader和BufferedWriter
BufferedReader和BufferedWriter其实就是Reader和Writer的装饰类,主要目的是为了提高读写效率。
Copy文本文件实例:

private static void fileCopy() {
    BufferedReader br = null;
    BufferedWriter bw = null;
    try {
        /*
         * 1、必须要明确被读取的文件,一定要确认文件时存在的。
         */
        FileReader fr = new FileReader("demo.txt");
        br = new BufferedReader(fr);
        /*
         * 2、明确copy目的地 如果文件不存在,自动创建 如果文件存在,会覆盖(删除再创建)
         */
        FileWriter fw = new FileWriter("CopyDemo2.txt");
        bw = new BufferedWriter(fw);
        /*
                  String len = null; 
                  while ((len = br.readLine()) != null) 
                  { 
                          bw.write(len);
                          bw.newLine();//会在文本文件最后多一个空行,没想到解决方法
                          bw.flush();//防止程序非正常停止
                  }
         */
        /*
                int len = 0;
                while ((len = br.read()) != -1) {
                bw.write(len);
                bw.flush();//防止程序非正常停止
                }
         */
        
        char[] buff=new char[1024];
        int len = 0;
        while ((len = br.read(buff)) != -1) {
        bw.write(buff,0,len);
        bw.flush();//防止程序非正常停止
        }
        

    } catch (IOException e) {
        throw new RuntimeException();
    } finally {
        if (br != null)
        try {
            br.close();
        } catch (IOException e) {

            throw new RuntimeException();
        }
        if (bw != null)
        try {
            bw.close();// 先刷新,将输出流中数据写到实体设备中,关闭系统资源,
        } catch (IOException e) {
            throw new RuntimeException();
        }
    }
 }

427081-20191231184642901-642994627[1].png

12.4 Scanner类

Scanner类,不是做键盘输入。是一个扫描器类,从一个流或者一个文件中扫描数据。


在使用完成后需要关闭

12.5. 标准输入、输出流

System.in:系统标准输入流。本质来说,就是一个InputStream。但是这个对象是系统自己实例化的。
连接了程序与控制台,在内部封装了一些逻辑,可以阻塞线程,等待用户的输入,直到用户输入完成,可以继续进行其他的操作


System.out:系统标准输出流。本质来说,就是一个打印流(printStream),可以将程序中的数据输出到控制台。其实连接控制台的操作是由打印流完成的。

12.6. 转换流

转换流:就是对字节流和字符流之间转换的流对象(防止出现字节丢失导致的乱码问题)
有字符操作的便利性以及字节流的可靠性
InputStreamReader:字节流到字符流的桥梁。解码
OutputStreamWriter:字符流到字节流的桥梁。编码

example1
package etransfer;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

public class Program {

	public static void main(String[] args) {
		
		try(InputStreamReader reader = new InputStreamReader(new FileInputStream("src\\file\\source2"),"utf-8")){
			char [] array=new char[10];
			int length = 0;
			while ((length=reader.read(array))!=-1) {
				System.out.print(new String(array, 0 , length));
			}
			
	} catch (UnsupportedEncodingException e) {
		e.printStackTrace();
	} catch (FileNotFoundException e) {
		e.printStackTrace();
	} catch (IOException e) {
		e.printStackTrace();
	}

  }
}
example2
package etransfer;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;

public class Program1 {

	public static void main(String[] args) {
		try(OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("src\\file\\target2"), "utf-8")){
			
			osw.write("撒上发生了发挥撒了风格");
			osw.flush();
			
			
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

}

example3
private static void readKey3() throws IOException {
    // 字节流
    InputStream in = System.in;

    // 字节流转成字符流。解码
    Reader r = new InputStreamReader(in);
    // 字符流
    BufferedReader br = new BufferedReader(r);

    // 字节流
    OutputStream out = System.out;
    // 字符流转成字节流。编码
    Writer w = new OutputStreamWriter(out);
    // 字符流
    BufferedWriter bw = new BufferedWriter(w);
    String line = null;
    while ((line = br.readLine()) != null) {
        if ("over".equals(line)) {
        break;
        }
        //System.out.println(line.toUpperCase());
        //接收字符
        bw.write(line.toUpperCase());
        bw.newLine();
        bw.flush();
    }
    }

分析解码、编码:
解码:二进制数据转换成字符流数据,输入流操作字符或字节
编码:字符数据转换成字节流数据,输出流操作的数据最终都是字节

12.7. Properties

Properties是用来读写一个.properties文件

properties文件,是一个属性列表文件。在这个文件中,可以存储一些简单的配置数据。properties文件中存储的数据是以键值对存储的


Map
  |–Hashtable
    |–Propreties
特点:
  1、集合中的键和值都是字符串类型
  2、集合数据可以保存到流中,或从流中获取
用于操作以键值对形式存在的配置文件
特有方法
  String getProperty(String key)
  Object setProperty(String key, String value)
  Set stringPropertyNames()
  void list(PrintStream out);//将此属性列表打印到指定的输出流。
 
  void store(OutputStream out, String comments);//持久化,存储到流中
  void store(Writer writer, String comments);//
 
  void load(InputStream inStream);//流中获取数据
  void load(Reader reader);

public static void main(String[] args) throws IOException {
    //propertiesDemo();
    propertiesStream();
    }

    private static void propertiesStream() throws IOException {
    File file=new File("info.txt");
    //判断文件是否存在
    if(!file.exists()) {
        file.createNewFile();
    }
    // 创建集合
    Properties p = new Properties();
    //验证Properties集合
    p.list(System.out);
    Reader reader=new FileReader(file);
    p.load(reader);
    //验证Properties集合
    p.list(System.out);
    //添加元素
    p.setProperty("zhao", "6");
    
    //保存
    p.store(new FileOutputStream(file), "info");
    }

    public static void propertiesDemo() throws FileNotFoundException, IOException {
    // 创建集合
    Properties p = new Properties();
    // 存储元素
    p.setProperty("zhang", "3");
    p.setProperty("li", "4");
    p.setProperty("wang", "5");
    p.setProperty("zhou", "6");
    // 修改元素
    p.setProperty("zhou", "7");
    // 取出元素
    Set<String> names = p.stringPropertyNames();

    for (String name : names) {
        String value = p.getProperty(name);
        System.out.println(name + ":" + value);
    }

    p.store(new FileOutputStream("info.txt"), "info");
    }

IO流(操作对象–ObjectInputStream与ObjectOutputStream)

操作对象
ObjectInputStream与ObjectOutputStream
作用:将程序中的对象,以文件的形式序列化到本地
就是把对象持久化(对象存储到硬盘上),表现形式就是对象文件
持久化对象必须实现Serializable接口

public class Person implements Serializable {
    private String name;
    private int age;

    public Person() {
    super();
    }

    public Person(String name, int age) {
    super();
    this.name = name;
    this.age = age;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    }
}

一、ObjectOutputStream——序列化
public static void writeObj() throws IOException {
    ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(“obj.object”));
    oos.writeObject(new Person(“zhang”,13));
   oos.close();
}
二、ObjectInputStream——反序列化
 private static void readObj() throws IOException, ClassNotFoundException {
    ObjectInputStream ois=new ObjectInputStream(new FileInputStream(“obj.object”));
    Person p=(Person)ois.readObject();
    System.out.println(p.getName()+":"+p.getAge());
    }
三、Serializable接口
通过实现java.io.Serializable接口的类,可以启用类的可序列化性。
可序列化性:实现java.io.Serializable接口的类添加一个serialVersionUID
作用:判断序列化的类和反序列化的对象是否是同一版本
强烈建议所有可序列化的类显式声明serialVersionUID值
 
writeObject方法负责为其特定类写入对象的状态,以便相应的readObject方法可以还原它。(序列化)
readObject方法负责从流中读取并还原类字段。(反序列化)
注意:类的签名以及该类及其所有父类型的非瞬态(transient关键字)和非静态字段的值。

12.9 NIO

New IO
NIO和传统IO有相同的功能,但是操作的方式不一样
NIO是面对缓冲区(Buffer),基于通道(Channel)
在jdk1.7后,添加了若干个新的元素,被称为NIO.2


NIO和IO的区别:

  • NIO是面对缓冲区的,IO是面对流的
  • NIO是非阻塞性的,IO是阻塞性的

12.9.1. 缓冲区Buffer

缓冲区,其实是一个容器,类似于一个数组,。这个容器中只能存储基本数据类型的数据。

缓冲区,按照储存的数据类型不同,可以分为以下几类:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffe

以上缓冲区,都拥有相同的父类–Buffer,所以这些类有相同的方式存储和管理数据。

Buffer 是一个抽象类,不能实例化对象,因此,在实际应用中,用到的还是子类的对象。

常见的操作:

  1. 获取缓冲对象:

由于缓冲区都是抽象类,不能通过new的方式进行实例化。需要静态方法allocate()获取对象

注:
缓冲区是一个容器,因此对一个缓冲区的操作只有读和写
缓冲区有两种模式:读模式和写模式
读模式:在这个模式下,一般情况下是进行缓冲区的数据读取操作
写模式:在这个模式下,一般情况下是进行缓冲区的数据写入操作
一个缓冲区实例化完成之后,会自动的处于写模式

  1. 缓冲区的常见属性:

mark:标记,在缓冲区添加一个标记,配合reset()方法,使position重置为mark标记
       position:目前正在操作的下标
       limit:能够操作的空间容量(写模式和capacity相等)
       capacity:缓冲区的容量,表示这个缓冲区中最多有多少数据

四个属性的关系:mark<=position<=limit<=capacity

  1. 缓冲区的常见操作:

put():往缓冲区里写数据,如果写的数据超出了缓冲区的容量,会出现异常BufferOverflowException
flip():将缓冲区切换成读模式
get():从缓冲区中读取数据,如果读取的数据超出limit,会出现异常
rewind():重置操作,将position重置为0(重置mark)(读模式+写模式)
clear():清空(将position,limit,capacity,mark重置为初始化的状态),并且使缓冲区切换为写模式
mark():在当前的position位置添加一个标记
reset():重置position为mark的位置
直接缓冲区与非直接缓冲区的概念:
1)非直接缓冲区:通过
static ByteBuffer allocate(int capacity)
创建的缓冲区,在JVM中内存中创建,在每次调用基础操作系统的一个本机IO之前或者之后,虚拟机都会将缓冲区的内容复制到中间缓冲区(或者从中间缓冲区复制内容),缓冲区的内容驻留在JVM内,因此销毁容易,但是占用JVM内存开销,处理过程中有复制操作。
非直接缓冲区写入步骤:
1.创建一个临时的直接ByteBuffer对象。
2.将非直接缓冲区的内容复制到临时缓冲中。
3.使用临时缓冲区执行低层次I/O操作。
4.临时缓冲区对象离开作用域,并最终成为被回收的无用数据。
2)直接缓冲区    :通过
static ByteBuffer allocateDirect(int capacity)
创建的缓冲区,在JVM内存外开辟内存,在每次调用基础操作系统的一个本机IO之前或者之后,虚拟机都会避免将缓冲区的内容复制到中间缓冲区(或者从中间缓冲区复制内容),缓冲区的内容驻留在物理内存内,会少一次复制过程,如果需要循环使用缓冲区,用直接缓冲区可以很大地提高性能。虽然直接缓冲区使JVM可以进行高效的I/O操作,但它使用的内存是操作系统分配的,绕过了JVM堆栈,建立和销毁比堆栈上的缓冲区要更大的开销。
直接缓冲区与非直接缓冲区的区别:

  1. 字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则 Java 虚拟机会尽最大努力直接在此缓冲区上执行本机 I/O 操作。也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。
  2. 直接字节缓冲区可以通过调用此类的 allocateDirect() 工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
  3. 直接字节缓冲区还可以通过 FileChannel 的 map() 方法 将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer 。 Java 平台的实现有助于通过 JNI 从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。
  4. 字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其 isDirect() 方法来确定。提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理。

307536-20170731145300974-520326124[1].png
307536-20170731145311224-406164516[1].png

12.9.2. 通道Channel


https://www.cnblogs.com/yy3b2007com/p/7263033.html
是一个文件和程序的连接,通道本身不负责数据的传递,数据的传递是由缓冲区负责的。


由java.nio.channels包定义的,Channel表示IO源与目标打开的连接,Channel类似于传统的“流”,只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互。通道主要用于传输数据,从缓冲区的一侧传到另一侧的实体(如文件、套接字…),反之亦然;通道是访问IO服务的导管,通过通道,我们可以以最小的开销来访问操作系统的I/O服务;顺便说下,缓冲区是通道内部发送数据和接收数据的端点。


在标准的IO当中,都是基于字节流/字符流进行操作的,而在NIO中则是是基于Channel和Buffer进行操作,其中的Channel的虽然模拟了流的概念,实则大不相同。

区别StreamChannel
支持异步不支持支持
是否可双向传输数据不能,只能单向可以,既可以从通道读取数据,也可以向通道写入数据
是否结合Buffer使用必须结合Buffer使用
性能较低较高

通道的获取方法:
java.nio.channels.Channel接口:
  |–FileChannel
  |–SocketChannel
  |–ServerSocketChannel
  |–DatagramChannel
获取通道的方法
1)Java针对支持通道的类提供了getChannel()方法
本地IO:
|–FileInputStream/FileOutputStream
|–RandomAccessFile
网络IO:
|–Socket
|–ServerSocket
|–DatagramSocket
|–Pip.***
2)在JDK1.7中的NIO.2针对各个通过提供了静态方法open()
3)在JDK1.7中的NIO.2的Files工具类的newByteChannel()
4)Channles工具类中提供了静态方法newChannel()。

12.9.3. 路径Path类

一个用来描述路径的接口。在实际应用中,Path对象的获取更多的是通过一个工具类Path获取的

Path.get(String first, String… more)

方法的作用:会将参数列表中的每一个组成部分拼接起来,组成一个完整的路径,并返回描述这个路径的一个Path对象。

12.9.4. 分散聚合

分散:将数据写入多个缓冲区,分散写入
聚合:从多个缓冲区读取数据,聚合读取

12.9.5. Files工具类

https://www.jb51.net/article/129125.htm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值