欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【干货】长达4万字的Java知识点!

程序员文章站 2024-03-14 15:08:22
...

最近在学习Redis的时候,突然发现以前学的集合等内容都忘了,所以干脆花了两天时间把大一学习JAVA的知识差不多复习了一遍,并且写下了这篇博客,本文写的东西基本都是学习中或者个人理解的重点,因为写的时间比较短,所以可能有一些地方略微粗糙或者是某些知识点一笔带过,希望大家见谅!也希望能够帮助到大家!

本文长度在4W字左右,以下为目录:


1-Java的基础信息了解

三大平台:

  • Java SE-Java Platform Standard Edition
    学习及开发Java其他平台的应用,必须先了解Java SE以奠定基础
    Java SE包括:JVM(虚拟机)、JRE(Java运行时环境)、JDK(Java开发工具包)和Java语言
  • Java EE-Java Platform Enterprise Edition
    以Java SE为基础,定义了一系列的服务、API、协议等
    适用于开发分布式、以WEB为基础的应用程序
  • Java ME-Java Platform Micor Edition
    主要用于开发消费性电子产品或嵌入式系统中的应用程序

Java SE分为四个组成部分

  1. JVM:Java Virtual Machine
    将Java编译好的文件翻译成对应的机器语言
  2. JRE:Java SE Runtime Environment
    包含用于Java的运行环境
  3. JDK:Java Development kits
    包含用于Java开发的工具
  4. Java语言:定义Java开发的语法规则
  • 掌握Java开发工具JDK配置

  • 掌握利用DOS代码进行编辑、编译、运行Java程序

  • 常用的开发工具的选择
    ·记事本/Notepad++/editplus/ultraedit
    ·Eclipse/MyEclipse(MY比前者功能丰富)(选择)
    ·JCreator
    ·Netbeans
    ·IntelliJ IDEA

  • 了解安装目录:
    -bin目录:存放可执行文件
    -lib目录:存放Java的类库文件
    -include目录:存放用于本地方法的文件
    -demo目录:存放演示程序
    -jre目录:存放Java运行环境文件

有关main中的String[] args的理解:
String[]表示定义一个字符串数组变量,变量名为args.
println中为Hello World,即为传入main中的字符串变量.
如果将println中的字符串改为args[0],则表示输出args字符串数组中的第一个字符串.
这时在cmd命令行中不可以直接用java HelloWorld在执行java程序.
因为这时没有传入任何字符串数组,故要执行还要在其后加上字符串,则输出结果就为该字符串.

Java特性

  1. Java平台无关性:Java是一种既面向对象又可跨平台的语言
    Java源程序代码->编译->Java字节码文件(可跨平台)->运行->Java虚拟机
    Java虚拟机面向:Windows系统、Linux系统、Solaris系统
  2. 开发Java程序小结
    1、创建Java源程序:扩展名为.java,可以用任何文本编辑器创建与编辑;
    2、编译源程序:使用"javac"命令,读取书写好的Java源程序并翻译成Java虚拟机能够明白的指令集合,
    且以字节码的形式保存在文件中.通常,字节码文件以.class作为扩展名;
    3、运行class文件:Java解释器读取字节码,取出指令并且翻译成计算机能执行的代码,完成运行过程;

2-Java的变量知识

数据类型 类型说明符 位数
整形 int 32
短整型 short 16
长整形 long 64
字节型 byte 8
单精浮点 float 32
双精浮点 double 64
布尔类型 boolean 8
字符类型 char 16
字符串型 String -

注意:对精确的超大型数值计算比如天文数据(一般指64位以上的数据运算),一般不会采用基本数据类型,而采用BigInteger或BigDecimal类型的数据进行精确计算

掌握变量命令规则
掌握变量的声明和初始化
注意:当声明一个float类型并初始化的时候,会默认该变量为double型,故要在数据后加f
注意:常量的定义:final + 类型名称 + 变量名 = 数据; //只有赋初值,后面无法更改

常见错误:
1、未声明、未初始化的变量和未使用的变量
2、赋值时数值溢出
要参考类型的最大值和最小值,具体操作如:syso(Integer.MAX_VALUE);-syso(Integer.MIN_VALUE);
3、取整错误
比如:1.0-0.9=0.99999999…
4、冗余(重复、复杂、啰嗦)的输入对象

格式化输出内容:

Row1 Row2 Row3
%d-10进制输出整数 %f-10进制输出小数 %e、%E科学计数法输出小数
%o-8进制输出整数 %x、%X-16进制输出整数 %s、%S-字符串方式输出
%c、%C-字符符号输出 %n-按照特定操作系统输出换行符 -------------

字符串的定义以及比较

定义字符串变量时最好给初值:String a=null; //字符串指向空 或 String b=""; //字符串为空字符
字符串比较时,不可以直接用"=="来进行比较,最好用"常量".equals(变量);
Java中的字符串不允许if(password == "123456")这样的判断
标准的写法:if(password.equals("123456"))
更加专业的写法:if("123456".equals(password))	//这两种否定情况都是在前面加'!'
延申另一种写法:if("123456".equalsIgnoreCase(password))	//忽略大小写的比较

接收用户输入Scanner用法:

import java.util.Scanner;	//输入Scnner后自动出现的导入工具包
Scanner input = new Scanner(System.in);		//扫描对象,用来扫描系统的输入'System.in'
总的来说输入就是:
Scanner 类型名 = new Scanner(System.in);
注意:良好的习惯就是在最后加一个input.close();
----------------------------------------------------------------------------------
类型 变量名 = 类型名.对应的"next"
对应的"next":如果类型为int,则为nextInt;为double,则为nextDouble;为String,则为next;
难点:录入char类型
char 变量名 = input.next().charAt(0);		//"."可以理解为"的"
----------------------------------------------------------------------------------
关于Scanner input = new Scanner(System.in);的理解↓
其实这相当于两句话:
第一句话是Scanner input;作用是先声明一个变量,变量的名字为input
第二句话是input = new Scanner(System.in);作用是将新创建的空间的地址赋值给左边的变量
匿名的方式调用Scanner: 变量 = new Scanner(System.in).nextINt();

强制类型转换
类型转换:1、自动类型转换 2、强制类型转换

1、示例:long number = 2147483647l;		//整型数字后加l则自动转换为long型
示例:int num = 90; double num1 = num;	 //将num自动转换为double赋值给num1,*小转大*
2、当double转为int时则不可以用自动转换,要用强制类型转换
示例:double num = 90.0;	 int num1 = (int)num;	 //强制转换
注意:double num = 5/2;时,结果还是2.0,因为右边为整形,如果要为2.5则右边要为5/2d或5/2.0
满足自动类型转换的条件:
1、两种类型要兼容(数值类型)
2、目标类型大于源类型	short -> int -> long -> float -> double

字符串常用方法:

 方法								说明
length()				获取字符串中的字符个数(不包括'\0')
chatAt(index)			返回字符串中指定下标的字符
concat(str)				拼接字符串,返回一个新字符串对象
toUpperCase()			返回一个新字符串,所有字母大写
toLowerCase()			返回一个新字符串,所有字母小写
trim()					返回一个新字符串,去掉了两边空格
char[] toCharArray()	将此字符串转换为一个新的字符数组
conpareTo(str)			作比较:大于返回>0;等于返回0;小于返回<0

一定要掌握的方法:
indexOf(ch)				返回字符串中出现的第一个字符ch的下标,没有匹配返回-1
indexOf(ch,2)			//这里2相当于从第几个下标开始计算
indexOf(s)				返回字符串中出现的第一个字符串s的下标,无匹配返回-1
substring(begin)		返回该字符串的子字符串,从begin下标到字符串的结尾
substring(begin,end)	返回该字符串的子字符串,从begin下标到end-1下标之间

3-成员变量与局部变量

public class Hero{
	整型类型1 变量1;					
	整型类型2 变量2;					成员变量
	整型类型3 变量3;
	
	public 返回值类型 方法1(){
		数据类型4 变量4;				局部变量
	}
	public 返回值类型 方法2(){}
}

注意:

  1. 成员变量的作用域在整个类都是可见的,方法1和方法2都可以使用变量1、2、3
  2. 局部变量的作用域仅限于定义他的方法,变量4只能分别在方法1中使用
  3. 成员变量有默认的初始值,数字为0、对象为null
  4. 局部变量默认没有初始值,需要赋初值再使用
  5. 成员变量和局部变量重名时,局部变量优先级更高

4-Java的运算符以及判断语句

算术运算符:(几元就是有几个操作数)
一元运算符:++、–
二元运算符:+、-、*、/、%
三元运算符:?、:
一元运算符:难点:区别前置还是后置
int result1 = num1++; 等价于 int result1 = num1;num1++;
int result2 = ++num2; 等价于 ++num2;int result2 = num2;
二元运算符:%的难点,取模的时候,结果的正负由第一个操作数决定
三元运算符:int num = 5>2?39:93; 意思是,如果5>2那么把39赋值给num;否则把93赋值给num

if-else 条件选择结构 —— 掌握

if(条件1){
	//代码块1
	}else if(条件2){
	//代码块2
	}else if(条件3){
	//代码块3
	}
}

switch-case 结构 —— 掌握

switch(表达式){
	case 常量1:
		语句1;
		break;
	case 常量2:
		语句2;
		break;
	...
	default:
		语句;
}

注意:
1、switch后的表达式可以是整形、字符型、String型
2、case后常量表达式的值不能相同
3、case后允许多条语句,不需要大括号
4、如果不添加break语句,需要特别注意执行顺序
5、case和default子句的先后顺序可以自动变动
6、default可以省略


5-循环语句

1、理解循环结构的基本原理
2、使用while循环结构
3、使用do-while循环
4、会使用工具调试程序

循环三要素:
1、循环变量的初值 2、循环变量的判断 3、循环变量的更新 特点:先判断,后执行

while循环:

while(循环条件){
	//循环操作语句
	....
}

do-while循环:

do{
	循环操作
}while(循环条件);

do-while特点:先执行一边循环操作,若符合条件,循环继续执行;否则循环退出
while循环和do-while循环的区别:
1.执行顺序不同
2.初识情况不满足循环条件时:1、while循环一次都不会执行 2、do-while循环无条件执行一次

for与for-each循环:

for(循环变量的初值;循环变量的判断;循环变量的更新){
	语句;
}

综合案例(用到了穷举法):
/*甲乙丙丁一共加工零件370个,如果把甲的个数加10,乙的个数-20,丙的个数乘2
丁的个数除以2,那么四个人做的零件数量相等。问:四个人分别做了几个?*/
for(int i=1;i<367;i++){//甲的数量
	for(int j=1;j<367;j++){//乙的数量
		for(int k=1;k<367;k++){//丙的数量
			//丁的数量
			int d = 370 - i - j - k;
			if(i+j+k+d==370&&i+10==j-20&&j-20==k*2&&k*2==d/2)
			System.out.printf("四人的数量分别为:%-4d%-4d%-4d%-4d\n",i,j,k,d);
		}
	}
}

小议break语句
break语句的作用:跳出循环,执行循环之后的语句(跳出for,while,do-while,switch语句)


6-数组的使用

语法:datatype[] arrayName = new datatype[size];
例子:String[] names = new String[15];
错误例子:int[] nums = null; nums[0] = 9;
错误原因:这里因为把nums数组设为了空,所以找不到空间赋值
错误例子:int years[6] = {1,2,3,5,6,8};
错误原因:声明并初始化数组时不需要指定数组的大小

关于数组大小和默认值:
1、创建数组后就不能再修改数组的大小
2、基本数据类型数组的默认值为0
3、char型数组元素的默认值为\u0000
4、布尔类型数组的默认值为false

除了数组的静态赋值(声明并赋值),我们还要掌握数组的动态赋值:

int[] nums = new int[10];
for (int i = 0; i < nums.length; i++) {
	nums[i] = (((int)(Math.random()*10)%10+1))*100;
}
System.out.println("数组元素:");
for (int i = 0; i < nums.length; i++) {
	System.out.print(nums[i]+" ");
}

//实际上与 for(int i=0;i<nums.length;i++)一样,都是遍历作用,这里的i相当于nums[i]
for(int i:nums){
	System.out.print(i+" ");
}

这里有个利用随机数来赋值的操作,可能有伙伴看不懂,这里顺便说一下Math.random的使用
Java中生成随机数字的三种方式(原理都一样):

int start = 5; int end = 15;
//方案1:使用Math类提供的随机方法生成start到end之间的数
int attack1 = ((int)(Math.random()*1000))%(end - start +1)+start;
//方案2:使用随机类,生成start到end之间的数
Random rand = new Random();
int attack2 = rand.nextInt(end-start+1)+start;
//方案3:使用系统时间(1970-1-1到当前的毫秒数)生成start到end之间的数
int attack3 = (int)(System.currentTimeMillis()%(end-start+1)+start);
long 变量名 = System.currentTimeMillis();//1970-1-1到现在的毫秒数

数组元素的删除和插入:
1、找到要删除元素的下标(插入元素则相反)
2、从要删除下标开始,后面元素赋值给前面元素(覆盖)
3、数组总长度-1 (障眼法:实际上数组总大小没变,只是输出的时候长度减一)
主要代码:

for(int i=index;i<array.length-1;i++){
	array[i] = array[i+1];
}
for (int i = 0; i < array.length -1; i++) {
	System.out.print(array[i]+" ");
}

7-重点:冒泡排序、选择排序、快速排序

冒泡排序:

//1.一共会比较数组元素个数-1轮
//2.每一轮,比较的次数比上一轮少1
//3.如果前面一个数组大于/小于后面一个数字,那么交换
final int N = 100;	//常量
int[] nums = new int[N];
for (int i = 0; i < nums.length; i++) {
	nums[i] = (int)(Math.random()*10000);
}
int temp;	//临时变量
for (int i = 0; i < nums.length -1; i++) {
	for (int j = 0; j < nums.length - i -1; j++) {
		if(nums[j]<nums[j+1]){
			temp = nums[j];
			nums[j] = nums[j+1];
			nums[j+1] = temp;
		}
	}
}
System.out.println("排序后:");
for (int i = 0; i < nums.length; i++) {
	System.out.print(nums[i]+"\t");
	if((i+1)%10==0)System.out.println();
}System.out.println();

选择排序:
选择排序效率高于冒泡,因为并没有像冒泡一样每一次都交换,而是一次性的比较后再交换

final int N = 100;	//常量
int[] nums = new int[N];
for (int i = 0; i < nums.length; i++) {
	nums[i] = (int)(Math.random()*10000);
}
int temp,maxindex,max;	//临时变量
for (int i = 0; i < nums.length; i++) {
	max = nums[i];
	maxindex = i;
	for (int j = i+1; j < nums.length; j++) {
		if(max<nums[j]){
			max = nums[j];
			maxindex = j;
		}
	}
	temp = nums[i];
	nums[i] = nums[maxindex];
	nums[maxindex] = temp;
}
System.out.println("排序后:");
for (int i = 0; i < nums.length; i++) {
	System.out.print(nums[i]+"\t");
	if((i+1)%10==0)System.out.println();
}System.out.println();

快速排序:

利用Arrays.sort(数组名);直接进行快速排序,默认为升序排序
如果要将快速排序后的数组逆序,则利用for循环来排序
for(i=0;i<nums.length/2;i++){		//nums.length/2是为了避免后面再逆序一次
	int temp = nums[i];
	nums[i] = nums[nums.length - i - 1];
	nums[nums.length - i - 1] = temp;
}

二分查找法:
使用前提:数组已经按升序排序
基本原理:首先将要查找的元素(key)与数组的中间元素比较
1、如果key小于中间元素,只需要在数组的前一半中继续查找(high的下标移到中间值的前一个)
2、如果key和中间元素相等,匹配成功,查找结束
3、如果key大于中间元素,只需要在数组的后一半元素中继续查找key(low的下标移到中间值的后一个)
mid = (low+high)/2
代码示例:

Scanner input = new Scanner(System.in);
int[] array={2,5,8,10,15,35,45,55,65,75,89};
System.out.print("请输入要查找的数字:");
int searchNum = input.nextInt();
boolean isFind = false;					//是否找到
int low = 0,high = array.length - 1;	//下标
int index = 0;
//不知道要查找几次
while(high>=low){
	if(searchNum>array[(low+high)/2]){
		low = (low+high)/2+1;
	}
	else if(searchNum<array[(low+high)/2]){
		high = (low+high)/2-1;
	}else{
		isFind = true;
		index = (low+high)/2;
		break;
	}
}
System.out.println(index);

8-方法、类、对象

方法有内置方法与自定义方法
内置方法主要由JDK类库提供,需要导入相应的包
自定义方法可以通过自己的需要来写
上面我们所说的Math.random函数就是内置方法的一种,这里列出常用的数学函数

Math类提供了很多使用的方法,用来计算常用的数学函数
-----------------------------------------------------------------------------------------------
类别:三角函数方法
Math.PI/Math.E									圆周率的近似值/算术常量e的值(近似于2.71828)
Math.sin(radians"弧度")							返回角度的三角正弦函数值(以弧度为单位)
Math.cos(radians)								返回角度的三角余弦函数值
Math.tan(radians)								返回角度的三角正切函数值
Math.toRadians(degree)							将以度为单位的值转换为弧度
Math.toDegrees(radians)							将以弧度为单位的值转换为度
Math.asin(a)									返回角度的反三角正弦函数值
Math.acos(a)									返回角度的反三角余弦函数值
Math.atan(a)									返回角度的反三角正切函数值
-----------------------------------------------------------------------------------------------
类别:指数函数方法
Math.exp(x)										返回算术常量E的x次方
Math.log(x)										返回x的自然底数--Math.log(Math.E)的值为1.0
Math.log10(x)									返回x的以10为底的对数
Math.pow(a,b)									返回a的b次方
Math.sqrt(x)									对于0以上的数字,返回x的平方根
-----------------------------------------------------------------------------------------------
类别:取整方法
Math.ceil(x)/Math.floor(x)						向上/下取整x最接近的整数
Math.rint(x)									取整为x最接近的整数,距离相等则返回偶数整数
Math.round(x)									四舍五入取整--返回(int/long)Math.floor(x+0.5)
Math.min/max/Math.abs							返回两个数的最小/大值|返回绝对值

是抽象的概念,仅仅是模板,比如说:“演员”、“总统”。
对象是一个你能够看得到、摸得着得具体实体。
通过自定义类来创建一个对象,如我们现在要创建一个演员对象,那么首先我们要自定义一个演员类:

public class Role {					//以后使用Role的时候就相当于使用一个类型名,类型为Role
	// 名称:劳拉
	// 等级:25
	// 职业:考古学家
	// 技能:双枪
	/** 游戏角色的名称属性 */
	public String name;
	/** 等级 */
	public int level;
	/** 职业 */
	public String job;
	public void show(){
		System.out.println(name+"\t"+level+"\t"+job);
	}
	//构造方法
	public Role(){}
	public Role(String name1,int level1,String job1){
		name = name1;
		level = level1;
		job = job1;
	}
	/* 释放技能 */
	public void doSkill(){
		if(name.equals("劳拉")){
			System.out.println("看我双枪!");
		}else if(name.equals("孙悟空")){
			System.out.println("吃俺老孙一棒!");
		}else{
			System.out.println(name+":发出了一记必杀技!");
		}
	}
}

//再创建一个main函数,代码如下:
public class Roletest {
	public static void main(String[] args) {
		//实例化
		Role role1 = new Role();
		role1.name = "劳拉";
		role1.level = 25;
		role1.job = "摸金校尉";
		role1.doSkill();
		role1.show();
		
		Role role2 = new Role();
		role2.name = "至尊宝";
		role2.doSkill();
	}
}

关于构造方法
1、没有返回值类型
2、名称与类名一致
3、可以指定参数及实现重载
4、第二个构造方法Role()实际上就是重载,也就是说我们可以利用带参的构造方法在创建对象的时候直接把属性赋值了,如:Actor actor = new Actor("三上悠亚",99,检察官);

自定义类的时候,我们可以使用封装来保护对象的属性,即在类中将要保护的属性前把public改为private,这样当你在main函数中创建了对象之后,就不能够直接调用对象.属性来访问这个对象,而只能通过Get或Set函数来访问,这两个函数可自行定义,这只是一种思想。

最熟悉的陌生人 —— String
String对象是不可变的,字符串一旦创建,内容不能再变
Java为了效率:以""包括的字符串,只要内容、顺序、大小写相同,无论再程序代码中出现几次JVM都只会建立一个实例,并放在字符串池(String pool)中维护

案例1public static void main(String[] args){
	String song = "Yesterday Onece More";
	song = "Batter Man";
}
/*song是一个String变量,它其实存放的是String内容(对象)空间的地址,当第二次赋值的时候,
	它就指向了另一个内容(对象)空间,所以反过来想就是原来的字符串还在原来的空间位置,
		即它是固定存在的,所以它的内容不再改变。*/
案例2:
String name1 = "123";
String name2 = "123";
System.out.println(name1 == name2);

//这里的结果是true,因为"123"这个内容的空间地址是不变的,即两个变量指向同一个对象。
案例3:
String value = "Hello";
value += "World";
String value2 = "HelloWorld";
syso(value == value2);

//这里结果为false,因为value+="World";相当于调用了value.concat(),会返回一个新字符串对象空间

加强版字符串

  1. 使用字符串相加的时候,除了直接相加,使用StringBuffer和StringBuilder都可以大幅度提升效率
  2. StringBuffer效率为普通相加的1000倍左右,而Builder又是Buffer的几倍,但是Buffer更安全
  3. Buffer代码如下(使用Builder的时候直接将Buffer替换即可):
    StringBuffer str = new StringBuffer("*");
    for(int i = 0;i<100;i++){
    	str.append("*");			//append相当于+=
    }
    
  4. StringBuffer/StringBuilder(可以看成类型)
    与String类不同的是,StringBuffer和StringBuilder类的对象能够被多次的修改,并且不会产生新的未使用对象
    StringBuilder类在Java5中被提出,它和StringBuffer之间的最大不同在于StringBuilder的方法不是线程安全的(不能同步访问,相当于一个字符串可以同时被多次修改,那么可能修改会重叠)
  5. 构造方法																		说明
    StringBuffer()												构建一个默认缓存为16的StringBuffer对象
    StringBuffer(int capacity)									构建一个指定缓存容量的StringBuffer对象
    StringBuffer(String str)									构建一个指定字符串值的StringBuffer对象
    修改StringBuffer中的字符串
    StringBuffer append(String str)									将指定的字符串追加到此字符序列
    StringBuffer reverse()											反转字符序列
    StringBuffer void delete(int start,int end)						移除此字符序列中的字符
    StringBuffer void insert(int offset,String str)					将字符串str插入到字符序列中
    StringBuffer void replace(int start,int end,String)				使用给定的字符串str替换序列中的字符
    void setCharAt(int index,int char)								在指定索引位置设定字符
    int capacity()													返回StringBuffer当前的缓存容量
    void setLength(int newLength)									设置StringBuffer的新长度
    void trimToSize()												如果缓冲区大于其当前字符序列,那么它可能会
    																调整大小,以便更加节省空间
    

9-继承和多态

与现实世界类似,编程中也有继承这个说法,利用extends关键字实现继承,如:public class Warrior extends Hero{}

继承的优点:

  1. 父类定义公共内容,方便统一修改
  2. 整体减少了代码量
  3. 子类作为扩展类很方便

注意:

  • 使用super关键字调用父类构造方法
  • 默认会调用父类构造,再调用子类构造,即先调用了父类构造的super();
  • 构造方法不可继承
  • Java中只能继承一个父类
  • 父类私有属性在子类中不能直接访问(父亲的内裤不能穿)
  • 继承关系 - 是一种"is-a"的关系:父类和子类之间必须存在is-a关系,空调is a电器
  • 子类比父类更加强大,是父类的加强版
提问:java中的子类和父类是不是一定要在同一个包中?
回答:肯定不用在一个包里的,引用下就行了,一般放一个包里是为了容易理解.
	 以前老师说过,JAVA里所有的类都是继承自Object这个类的,也就是说它是所有类的父类,也就是说你
	 不继承某个类的话就是默认继承了Object类,所以不用在一个包里!

重写 - 继承后很重要的技术
1、在子类中提供一个对方法的新的实现 - 重写父亲的赚钱方法,但是赚得更多
2、方法重写具有同样的方法签名和返回值类型,只不过方法中的代码可以不同
3、如果怕重写的时候名称出错,可以alt+/,再打出父类中的方法即可
4、@override称为重写标注,用来保证重写的方法与原方法的签名和返回值一致

多态 - 同一个实体,多种形式
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。

public class Test {
	public static void main(String[] args) {   
		Animal a = new Cat();  // 向上转型  
		a.eat();               // 调用的是 Cat 的 eat
		Cat c = (Cat)a;        // 向下转型  
		c.work();        // 调用的是 Cat 的 work
	}
}  
abstract class Animal {  
    abstract void eat();  
}  
  
class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
    public void work() {  
        System.out.println("抓老鼠");  
    }  
}  
输出结果为:
吃鱼
抓老鼠
解释:实际上是父类对象调用了父类方法,只不过父类方法被子类重写了,
	所以不同子类对象在调用父类方法的时候实际上就是调用了对应子类中的方法

10-抽象类

final关键字:
 修饰成员变量:常量,不可更改
 修饰方法:不可被重写
 修饰类:不可被继承
作用:
 防止扩展和重写

抽象类(abstract) - 概念类
主要用来描述该类或对象应该具备的基本特征与功能,让子类来实现
注意点:

  • 包含一个抽象方法的类必须是抽象类
  • 抽象类和抽象方法都是用abstract关键字声明
  • 抽象方法只需要声明而不需要实现
  • 抽象类必须被子类继承
    子类不是抽象类时必须重写父类中的所有抽象方法
    子类是抽象类时依然可以继续声明成抽象方法
  • 抽象类不可使用new实例化
  • 抽象方法必须是非静态(Static)的
  • 抽象类除了不能用new进行实例化,其他都和普通的一样
实例:
public abstract class ClassA(){
	void test();
}

这里介绍一下日历对象的使用:
Calendar和GregorianCalendar(java.util包)
GregorianCalendar是抽象类Calendar的一个具体实例
Calendar是一个抽象的基类,可以提取出详细的日历信息(年、月、日、时秒分等)
Calendar类的子类可以实现特定的日历系统,如公历(Gregorian历)、农历和犹太历

常量			  说明				 		常量					说明
YEAR			日历的年份				DAY_OF_WEEK			一周的天数(周几),1是星期日
MONTH			日历的月份,0表示1月		DAY_OF_MONTH		同DATE(几号)
DATE			日历的天(几号)			DAY_OF_YEAR			当前年的天数,1是一年第一天
HOUR			小时(12小时制)			WEEK_OF_MONTH		当前月内的星期数,从1开始
HOUR_OF_DAY		小时(24小时制)			WEEK_OF_YEAR		当前年内的星期数,从1开始
MINUTTE			分钟					AM_PM				0表示上午,1表示下午
SECOND			秒

日历类用法示例:

Calendar cal1 = Calendar.getInstance();
//使用日历打印当前的时间
int year = cal1.get(Calendar.YEAR);
int month = cal1.get(Calendar.MONTH)+1;
int day = cal1.get(Calendar.DATE);
System.out.println("当前时间:");
String strTime = String.format("%d-%d-%d", year,month,day);
System.out.println(strTime);

//实现两个日期相减
GregorianCalendar cal2 = new GregorianCalendar(2017,8-1,19);
long diff = Math.abs(cal1.getTimeInMillis() - cal2.getTimeInMillis());
int diffdays = (int)(diff / 1000 / 60 / 60 / 24);
System.out.println("相差"+diffdays+"天");

//实现日期的增加
cal1.add(year, 3);

日历类的用法还能够更高级更方便,我总结出了所有的日历类用法:

注意:
	获得当前日期的方法有:
		1、先实例化一个Calendar对象,再声明对应的时间单位赋值,如:
			int year = cal1.get(Calendar.YEAR);
		   再将每个变量组合成String型并打印
			String time = String.format("%d-%02d-%d", year,month+1,day);
		说明:因为cal1.get();只能获取每个单位的时间,如YEAR,所以不用Date变量保存,用int来保存
		2、声明一个Date变量直接储存日期,如:
			Calendar cal2 = Calendar.getInstance();
			Date date1 = cal2.getTime();
			System.out.println(date_Of_date1);
		说明:对于Calendar类型的getTime()可以直接获取日期,所以直接用Date
		
	设置日期的方法有:
		1、先实例化一个Calendar对象,再用set函数设置时间,如:
			Calendar cal4 = GregorianCalendar.getInstance();
			cal4.set(2012, 5, 3);
			再用Date赋值并打印
			Date date = cal.getTime();
			System.out.println(date.toLocaleString());
		说明:该方法中的set函数可以有多个选项,可以单个设置比如单纯设置YEAR也可以YEAR+MONTH等
		2、先声明一个Date变量,最后将字符串转换为Date类型,如:
			Date newDate = format1.parse("2017-12-30 23:50:11:555");
			System.out.println(newDate.toLocaleString());
		说明:该方法就直接将右边的字符串类型以对应格式赋值
		
	定义一个时间表示的格式的方法:
		1、先声明一个表格变量(SimpleDateFormat),在将对印格式赋值,如:
			SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
			String strDate = format1.format(date);
			System.out.println(strDate);
		说明:这里的格式化中的格式内容中,所有的字母必须为对应时间单词的字母开头,第二三两行则
			 进行了格式的操作
			 
	关于声明Calendar变量和GregorianCalendar变量的难点:
		1、声明Calendar变量时直接用Calendar cal = Calendar.getInstance();
		2、声明GregorianCalendar变量时:
			·如果用GregorianCalendar cal = GregorianCalendar.getInstance();	 则会报错
			·正确的声明:
				*Calendar cal = GregorianCalendar.getInstance();
				*Calendar cal = new GregorianCalendar();
				*GregorianCalendar cal = new GregorianCalendar();
				
	关于打印date是的格式问题:
		1、打印date时如果不用date1.toString(),则会是用外国默认的英文格式
		2、打印date时如果用date1.toLocaleString(),则会以当地对应的格式打印
	
	补充:获得一个月中的天数
	Calendar.getActualMaximum(Calendar.DAY_OF_MONTH);

11-接口

使用接口(和继承类似,加implements)
 接口是一种与类相似的结构,只包含常量和抽象方法 - 是一种更抽象的抽象类
 目的是指明相关或者不相关类的多个对象的共同行为(功能)
使用接口的案例:
 可以指明对象是可比较的、可食用的、可被攻击的、可飞翔的甚至是可克隆的。。。

定义接口:									注意:
//定义接口(可攻击的)							1、在Java中,接口可被看作是一种特殊的类,与抽象
public interface Assaliable(){					类相似,不能使用new操作符直接创建接口的实例	
	//常量及抽象方法声明						2、接口必须由其它类实现,实现类不能实现的方法
}												可以继续标识为abstract
											3、接口中所有的数据域都是public static final,
												所有方法都是public abstract,默认情况可以省略
												这些关键字

接口与抽象类的比较:

  • 类中可以定义成员变量,接口中不允许存在成员变量(因为默认为public static final)
  • 接口中所有方法都需要实现类去实现(Java8前)
  • 接口可以被多继承
  • 使设计和实现完全分离
  • 能够更自然的使用多态
  • 可以更容易搭建程序框架
  • 可以更容易更换实现

注意:
在jdk1.7及以前都是抽象类中可以有抽象方法,接口是特殊的抽象类,因为接口中的所有方法都必须是抽象的,不能有方法实现。而到了Java8,我们发现在接口中也可以有方法的实现,在接口中写方法要加default

JavaOO到此结束


12-异常以及异常处理机制

异常就是一种对象(Exception),表示阻止程序正常执行的错误或情况

  • 在程序运行的过程中,如果JVM检测出一个不可能执行的操作,就会出现运行时异常
  • 在Java中,运行时异常(RuntimeException/Error)会作为异常(对象)抛出
  • 如果异常没有被处理,程序将会非正常终止

异常处理 - 使程序可以继续运行或者优雅地终止方法抛出异常,一般我们用try-catch-finally块来处理

  • 调用者可以捕获以及处理该异常
  • 恢复正常的程序执行
  • 进行日志处理
  • 以某些方式提醒用户(错误提示、对话框等)

注意:如果没有异常处理的话,那么如果一个地方出现异常,下面的所有代码都不会运行成功

Java中的异常处理机制
可以使程序处理非预期的情景,并继续正常的执行
异常处理机制的主要组成

try:监控有可能产生异常的语句块
catch:以合理的方式捕获并处理异常
finally:不管有无异常,一定会执行的语句块(一般用来释放资源等)
throw:手动引发异常
throws:指定由方法引发的异常
用法:
	try{
		//所监控的有可能产生异常的语句块
	}catch(Exception e){//捕获异常,e就是所捕获的异常对象
		//异常处理:打印异常信息、日记记录等
	}finally{
		//不管有无异常,一定会执行的语句块(一般用来释放资源等)
	}

异常可分为Exception(异常,如一个人好吃懒做还能接受)和Error(系统级错误,一个人犯罪了无法接受)
RunTimeException为运行时异常,为特殊异常,也就是免检异常;其他异常为必检异常

Exception Error
AWTException(图形界面) AwtError(界面错误)
SQLException(链接数据库) LinkageError(依赖错误)
IOException(文件读写) VirtualMachineError(虚拟机错误)

多重catch块解决问题
try块中的代码可能会引发多种类型的异常
当引发异常时,会按catch的顺序进行匹配异常类型,并执行第一个匹配的catch语句

public static void division(String strNum1,String strNum2){
	int result = Integer.MIN_VALUE;
	try{
		int num1 = Integer.parseInt(strNum1);
		.................
	}catch(InputMismatchException e){
		e.printStackTrace();
	}catch(ArithmeticException e){
		e.printStackTrace();
	}
}

JDK7后的简化写法(multi-catch feature)catch(Exception1 | Exception2 | Exception 3|...ex){}
当然也可以直接用父类Exception,如catch(Exception);来处理异常,只不过效率没那么高。

try-catch-finally的注意点
无论是否发生异常,finally中的代码总能被执行
比如catch中的异常捕捉错误,但是仍能够执行finally中的代码

  1. 遇到System.exit(0)语句是不执行finally块的唯一情况(即使前面还有return)
  2. 使用finally块的场合:如果程序开启了相关资源(文件、数据库等),为了避免因出现错误没有关闭,就可以使用finally块在执行完毕后强制要求关闭所有打开的资源
  3. 使用finally时,可以省略catch块(慎重)

注意:使用try-catch-finally的时候,常常会忘记close();或flush();,为了避免这种情况,可以直接在try后面加一个括号,如:try(BufferedWriter bWriter = new BufferedWriter(new FileWriter(filepath))),这是使用了jdk7之后新增的自动关闭资源的写法,使用的前提是资源类必须实现Closeable接口

直接抛出异常
throws直接抛出异常,如public static void division throws Exception(){}
throws后面的异常可以是父类也可以是子类,子类如果名字记不住可看源代码,,一般来讲是谁调用,谁处理

throw手动抛出异常(在调用的时候还是谁调用谁处理,throw和throws一般连用(:

public void setexp(long exp)throws Exception{
	if(exp>=0)this.exp = exp;
	else{
		throw new Exception("经验值不能为负数!");
	}
}
总结:
try-catch-finally组合
	try-catch、try-catch-finally、try-finally
异常继承架构
	Throwable、Exception、RuntimeException
throw、throws的使用
	throws:声明方法会出现异常
	throw:在方法中手动抛出异常

13-Java I/O系统

1、File(文件和文件夹),可以用来处理文件目录的问题
基本代码示例:

public class FileDemo {
	/** 当前操作的文件的路径 */
	private static final String FilePath = "src/(default)/FileDemo.java";
	public static void main(String[] args) {
		//当前工程所在根目录
		File file = new File(FilePath);
		System.out.println("是否存在:"+file.exists());
		System.out.println("是不是一个文件:"+file.isFile());
		System.out.println("是否是文件夹:"+file.isDirectory());
		System.out.println("文件名/目录名为:"+file.getName());
		System.out.println("文件/目录路径:"+file.getPath());		//相对路径
		System.out.println("文件/目录绝对路径:"+file.getAbsolutePath());
		System.out.println("最后修改时间:"+new Date(file.lastModified()).toLocaleString());
		System.out.println("是否隐藏:"+file.isHidden());
		System.out.println("是否可读:"+file.canRead());
		System.out.println("是否可写"+file.canWrite());
		System.out.println("文件空间:"+file.length()/1024+"KB");
	}
}

使用File类创建文件/文件夹:

if(!file.exists()){
	if(file.isFile()){
		file.createNewFile();
		if(file.isFile())System.out.println("文件创建成功!");
	}else{
		file.mkdir();
		if(file.mkdir())System.out.println("文件夹创建成功!");
	}
}

使用File类删除文件/文件夹:

if(file.exists()&&file.isDirectory()){
	file.delete();			//立即删除
	file.deleteOnExit();	//运行完删除
}

文件过滤器案例:

static class DirFilter implements FilenameFilter{
@Override
	public boolean accept(File dir, String name) {
		//假设要求只显示git为后缀的文件
		if(name.endsWith("git") || name.endsWith("project")){
			return true;
		}
		return false;
	}
}
//列出当前目录下的所有文件/文件夹
String[] fileNames = file.list(new DirFilter());
for (int i = 0; i < fileNames.length; i++) {
	System.out.println(fileNames[i]);
}

注意:

  • 在Java.io包中操作文件内容主要由两大类:字节流和字符流,两类都有输入和输出操作
  • 流中保存的实际上全部是字节文件,字符流中多了对编码的处理
  • 在字节流中输入数据使用的是InputStream,输出数据主要使用OutputStream
    InputStream和OutputStream是为字节流设计的,主要用来处理字节或二进制对象
  • 在字符流中输入数据主要使用Reader完成,输出数据主要使用Writer完成
    Reader和Writer是为字符流(一个字符占两个字节)设计的,主要用来处理字符或字符串
    对多国语言支持比较好,如果是音频、视频、图片等建议使用字节流
  • 磁盘上保存的文件是二进制字节数据,字符流中多了对编码的处理

2、JavaIO中的装饰流与装饰器

FileInputStream							用于从文件中读取信息
ByteArrayInputStream					允许将内存的缓冲区作为输入流使用
FilterInputStream						抽象类,作为"装饰器"接口
ObjectInputStream						从输入流中读取对象
BufferedInputStream						使用缓冲区的输入流
DataInputStream							可以从流读取基本数据类型(int,char,long)

组件:Component,相当于JavaIO中的InputStream/OutputStream/Read/Write
组件的实现类:TrueComponent,真正干活的组件类,继承了Component的方法
装饰器类:Decorator,也是继承了Component方法,但是是由TrueComponent来实现的,装饰器只是把方法升级

使用装饰器的场合:
1、当我们需要在不影响其他对象的情况下,以动态、透明的方法为对象添加功能(扩展方法的实现)
2、当不适合使用继承但是又想进行方法的扩展时(如:班长不能被副班长继承但可以让副班长修饰)

使用步骤:Component是父类,TrueComponent和Decorator为子类
1、先创建一个抽象类(Component),用于让子类继承,该类中有核心方法read
2、创建一个TrueComponent,用于继承父类并且重写方法,作为实现类
3、因为如果要升级read方法则每次都要修改TrueComponent,而且会影响TrueComponent中的方法内容,
	所以使用修饰器进行修饰,即添加升级后的read方法但不影响原read方法的使用
4、创建Decorator对象,由于装饰器是要让TrueComponent对象来实现扩展方法,所以应该定义一个私人的TrueComponent对象,又由于调用扩展方法时
	肯定要先创建Component(或TrueComponent对象),然后再把这个对象传给装饰器的实现类才能使用扩展方法,所以再装饰器类中要构造方法,
		参数为对应的对象,再将传入的对象赋值给装饰器中的私人对象
5、重写read方法,方法体中只有Component.read();将请求转发给Component对象,之后才可以执行扩展操作
6、书写Decorator的子类对象,其中也需要有参数为组件的构造方法(原理同4,将传入的对象super给装饰器),然后再重写扩展操作,具体就是重写read的方法

读取网络文件案例:

package URLTest;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URL;
/**
 * 从给定的URL上读取文件内容-暂时只支持HTTP协议
 */
public class ReadURLTest {
	private static final String filepath = "src/获取网页.html";
	public static void main(String[] args) throws MalformedURLException {
		URL url = new URL("https://www.163.com/");
		StringBuffer content = new StringBuffer();
		try(BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
			BufferedWriter bWriter = new BufferedWriter(new FileWriter(filepath));
			){
			String line = null;
			while((line = reader.readLine())!=null){
				content.append(line);
				content.append(System.getProperty("line.separator"));
			}
			bWriter.write(content.toString());
			System.out.println("获取网页成功!");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

3、序列化和反序列化

对象的输入和输出

  • ObjectInputStream&ObjectOutputStream可以读写可序列化的对象
  • 序列化的作用就是把对象以二进制写入到文件(Save)
  • 反序列化的作用是把二进制文件转化为对象(Load)

什么情况下需要序列化?

  • 当把内存中的对象保存到文件中或者从文件中读取出来(游戏存档/读档功能)
  • 当想用套接字在网络上传送对象的时候
  • 当想通过RMI(远程方法调用)传输对象时

4、随机访问与综合实战
RandomAccessFile - 使用顺序流打开的文件称为顺序访问文件,顺序访问文件的内容不能更新
RandomAccessFile允许在文件的任意位置上进行读写(随机访问文件)

注意:在RandomAccessFile中

  • seek函数可以移动文件的指针,即把当前指向的位置移动多少字节
  • setLength函数可以设置文件的长度大小,当设置为0时,即把内容删除

综合实战篇:利用IO下载网络资源

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class DownLoadDemo {
	//下载原理
	//1、当前案例主要实现从http协议下载(文件流),将文件流以缓冲的方式读取到内存中
	// 		InputStream -> BufferedInputStream
	//2、将缓存中的数据分块写入到硬盘上(使用RandomAccessFile类实现)
	//		这里我下载一个CSGO的游戏平台
	static final String StrUrl = "https://oss.5ewin.com/app/setup/5EClient-2.1.54.zip";
	/** 设置下载时的默认缓存大小 - 1M大小,下载1M存一次 */
	private static final int MAX_BUFFER_SIZE = 10240;
	public static void main(String[] args) {
		//1、打开http链接,获得下载内容给长度(补充内容,格式固定)
		//2、创建RandomAccessFile对象
		//3、将下载的内容缓存到字节数组中
		//4、将缓存字节数组通过RandomAccessFile对象写入到文件中(涉及到一个文件指针的操作)
		HttpURLConnection connection = null;
		BufferedInputStream bInput = null;
		try {
			URL url = new URL(StrUrl);
			//读取二进制流或者文件大的时候不用openStream
			connection = (HttpURLConnection)url.openConnection();
			//设置链接属性-Range指从服务器请求下载文件的字节数范围,0-,表示0-最大值
			connection.setRequestProperty("Range", "bytes=0-");
			connection.connect();//连接到服务器
			//判断连接是否成功 - 一般连接成功后返回的代码应该在200的范围内
			/*
			 * 1xx:指示信息,表示请求已被接受,继续操作
			 * 2xx:成功,表示请求已被成功接受,理解,操作
			 * 3xx:重定向,要求完成请求必须进行进一步的操作
			 * 4xx:客户端错误,请求有语法错误或请求无法实现
			 * 5xx:服务器错误,服务器未能实现合法请求
			 */
			if(connection.getResponseCode()/100!=2){
				System.err.println("连接的响应状态不在200范围内,请重试!");
				return;
			}
			int fileSize = connection.getContentLength();	//获得要下载文件的大小(字节数)
			bInput = new BufferedInputStream(connection.getInputStream(),MAX_BUFFER_SIZE);
			int downloaded = 0;//已下载的字节数 - 用来计算当前下载的百分比
			String fileName = url.getFile();//获得下载的文件名
			//截取字符串,从最后一个斜线+1的位置开始截取
			fileName = fileName.substring(fileName.lastIndexOf("/")+1);
			RandomAccessFile file = new RandomAccessFile(fileName,"rw");
			file.seek(0);	//将文件指针置零
			file.setLength(0);	//文件清空
			while(downloaded<fileSize){//当已下载的字节数小于文件总大小时,继续下载
				byte[] buffer = null;	//下载缓存字节数组
				//判断为下载的大小是否超过最大缓存
				if(fileSize - downloaded>MAX_BUFFER_SIZE){
					buffer = new byte[MAX_BUFFER_SIZE];
				}else{
					buffer = new byte[Math.abs(fileSize - downloaded)];
				}
				//将缓存中的内容读取
				int read = bInput.read(buffer);
				if(read == -1)break;	//下载结束
				//将当前下载的缓存写入到文件中
				file.seek(downloaded);	//设置文件指针
				file.write(buffer,0,read);
				downloaded += read;
				System.out.printf("已下载:%.2f%%",downloaded*1.0/fileSize*100);
				System.out.println();
			}
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally{
			connection.disconnect();
			try {
				bInput.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

集合篇

14-List集合

问:为什么要使用集合框架?
答:在不确定数组存储大小的情况下,就可以用集合框架

JavaSE提供了:

  • Collection接口:存储另一个(某一个)元素的集合,相当于一个一个对象存储
  • Map接口:存储(键/值)对,即key-value集合
  • Collections:操作集合的工具类(包括了对集合的操作,如:排序、取值等)

Collection中的三种主要类型的集合:

  1. Set(规则表):存储一组不重复的元素(可能无序)
  2. List(线性表):存储一个有序集合
  3. Queue(队列):存储用先进先出的方式处理的对象

注意:

  • 集合框架中的所有接口和类都在Java.util包中
  • 集合框架中所有的具体类都实现了Cloneable和Serializable接口,即他们的实例都是可复制且可序
    列化的对象
  • 在定义不同类型的对象时,可以声明Object数组,在数组中赋予不同的类型,但是这样提取困难,特别
    是对于一些不知道数组中到底是什么类型的开发者开说,所以应该使用泛型
  • 使用泛型的实例代码如下:
    public class ElementDemo<E> {
    	public static void main(String[] args){
    		//<>里填什么类型,在add中就传入什么类型
    		ElementDemo<Integer> demo = new ElementDemo<Integer>;
    		demo.add(123);
    	}
    	public void add(E e){
    		//代码省略...
    	}
    }
    

Collection接口方法和描述

---------------------------------------------------------------------------------------------
				方法											描述
boolean add(E e); / int size();					向集合中添加元素e / 返回集合中的元素个数
boolean addAll(Collection<? extends E> c);		将集合c中的所有元素添加到当前这个集合
boolean contains(Object o);						如果该集合中包含对象o,返回true
boolean containsAll(Collection<?> c);			如果该集合中包含集合c中的所有元素,返回true
boolean isEmpty();	/ void clear();				如果集合不包含任何元素,返回ture / 删除集合中
												的所有元素
Ierator<E> iterator();							返回该集合中元素所用的迭代器(遍历所有元素)
boolean remove(Object o);						从集合中删除元素o
boolean removeAll(Collection<?> c);				从集合中删除集合c中的所有元素
boolean retainAll(Collection<?> c);				保留c和该集合都有的元素(交集)
Object[] toArray();								返回该集合构成的Object数组
---------------------------------------------------------------------------------------------
注意:
	Collection虽然是集合的最高父接口,但是直接使用Collection接口会造成操作意义不明确,所以在实
	际开发中不提倡直接使用Collection接口

List接口方法和描述

---------------------------------------------------------------------------------------------
				方法											描述
public void add(int index,E element)						在指定位置增加元素
public boolean addAll(int index,Collection<? extends E> c)	在指定位置增加一组元素
E get(int index) / E set(int index,E element);				返回/替换指定位置的元素
public int indexOf(Objetct o)								查找指定元素的位置
public int lastIndexOf(Object o)							从后往前查找指定元素的位置
public ListIterator<E> listIterator()	//升级版			获得List迭代器对象
public E remove(int index)									删除指定位置的元素
public List<E> subList(int fromIndex,int toIndex)			取出集合中的子集合(左闭右开)
public E set(int index,E element)							替代指定位置的元素
---------------------------------------------------------------------------------------------
注意:
	List接口扩展了Collection接口的方法,这些方法使用起来比父接口更加方便,要使用List接口,需要对
	List接口的实现类实例化

List的实例化以及使用示例

//多态:使用List接口的一个子类ArrayList进行实现
List<String> list1 = new ArrayList<>();
//遍历的方法 1、for循环中用list 2、foreach 3、迭代器 4、ListIterator迭代器
Iterator<String> it = list1.iterator();
//使用迭代器的方法相对比较固定
while(it.hasNext()){
	System.out.print(it.next() + ", ");
}
System.out.println();
ListIterator<String> listIt = list1.listIterator();
while(listIt.hasNext()){
	System.out.print(listIt.next()+", ");
}
System.out.println();
//使用ListIterator与传统迭代器的区别
//1、ListIterator不止可以向后访问,也可以向前访问
//2、ListIterator可以修改集合中的元素
System.out.println("使用ListIterator从后向前遍历");
ListIterator<String> listIt2 = list1.listIterator(list1.size()); //将指针调整到list1大小
while(listIt2.hasPrevious()){//当迭代器中的元素存在上一个时
	System.out.print(listIt2.previous()+", ");
}
System.out.println();
ListIterator<String> listIt3 = list1.listIterator(2);	//相当于将指针调整到2
if(listIt3.hasPrevious()){			//如果2前有东西,则把指针移到2前,再修改
	listIt3.previous();
	listIt3.set("蛤蛤蛤");
}
System.out.println(list1);

ArrayList与LinkedList的特性

ArrayList(数组线性表):
	1、是一个大小可变的数组,在内存中分配连续的空间
	2、遍历元素和随机访问元素的效率比较高
LinkedList(链表):
	1、采用链表存储方式
	2、提供从线性表两端提取、插入和删除元素的方法
	3、插入、删除元素效率比较高
	4、链表相当于座位,读取效率不高(要从第一个往后读),但是插入和删除很方便,比如删除元素时,数组要将后面的所有元素向前移动一格,但是链表只用将后面一个元素向前移动一格即可

ArrayList与LinkedList添加元素的效率对比

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class ArrayListAndLinkedListDemo {
	public static void main(String[] args) {
		System.out.println("ArrayList耗时:"+CostTime(new ArrayList<>()));
		System.out.println("LinkedList耗时:"+CostTime(new LinkedList<>()));
	}
	public static long CostTime(List<Object> list){
		Object obj = new Object();
		long startTime = System.currentTimeMillis();
		final int N = 500000;
		for (int i = 0; i < N; i++) {
			//如果是List.add(obj); 往最后一个位置添加,则Array不一定慢
			list.add(0, obj);
		}
		long endTime = System.currentTimeMillis();
		return endTime - startTime;
	}
}
结果:
ArrayList耗时:25148 ms
LinkedList耗时:335 ms
总结:
在两种情况交叉使用的前提下,直接使用父类List即可,比如使用完LinkedList,在转换为ArrayList也可以

15-Set、Queue集合

Set集合类似于一个罐子,“丢进"Set集合里的多个对象之间没有明显的顺序。Set继承自Collection接口,不能包含有重复元素(记住,这是整个Set类层次的共有属性)。
Set判断两个对象相同不是使用”=="运算符,而是根据equals方法。

  • HashSet:用来存储互不相同的任何元素(无序)
  • LinkedHashSet:使用链表扩展实现HashSet类,支持对元素的操作
    注意:如果不需要维护元素被插入的顺序,就应该使用HashSet,更加高效
  • TreeSet:可以确保所有元素是有序的,可排序

Queue用于模拟"队列"这种数据结构(先进先出 FIFO)。队列的头部保存着队列中存放时间最长的元素,队列的尾部保存着队列中存放时间最短的元素。新元素插入(offer)到队列的尾部,
访问元素(poll)操作会返回队列头部的元素,队列不允许随机访问队列中的元素。结合生活中常见的排队就会很好理解这个概念

  • PriorityQueue:PriorityQueue保存队列元素的顺序并不是按照加入队列的顺序,而是按照队列元素的大小进行重新排序
  • Deque:Deque代表一个"双端队列",双端队列可以同时从两端来添加、删除元素,因此Deque的实现类既可以当成队列使用、也可以当成栈使用
    1 - ArrayDeque:是一个基于数组的双端队列,和ArrayList类似,它们的底层都采用一个动态的、可重分配的Object[]数组来存储集合元素,当集合元素超出该数组的容量时,系统会在底层重新分配一个Object[]数组来存储集合元素
    2 - 此外,LinkedList也可以是西安Deque接口,将LinkedList当作双端队列使用。

16-Map集合

  • HashMap:HashMap不能保证key-value对的顺序。判断相等的方法是两个key通过equals()方法比较返回true,同时两个key的hashCode值也必须相等
    1 - LinkedHashMap:
    LinkedHashMap也使用双向链表来维护key-value对的次序,该链表负责维护Map的迭代顺序,与key-value对的插入顺序一致(注意和TreeMap对所有的key-value进行排序进行区
    分)
  • Hashtable:一个古老的Map实现类
    1 - Properties
    Properties对象可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入到属性文件中,也可以把属性文件中的"属性名-属性值"加载到Map对象中
  • SortedMap
    正如Set接口派生出SortedSet子接口,SortedSet接口有一个TreeSet实现类一样,Map接口也派生出一个SortedMap子接口,SortedMap接口也有一个TreeMap实现类
    1 - TreeMap
    TreeMap就是一个红黑树数据结构,每个key-value对即作为红黑树的一个节点。TreeMap存储key-value对(节点)时,需要根据key对节点进行排序。TreeMap可以保证所有的key-value对处于有序状态。同样,TreeMap也有两种排序方式: 自然排序、定制排序

17-线程

相关概念

  1. 程序(Program):指令集 静态概念
  2. 进程(Process):操作系统调度程序 动态概念
    进程是程序的一次动态执行过程,占用特定的地址空间
    每个进程都是独立的,由3部分组成(cpu、data、code)
    缺点:内存的浪费,cpu的负担
  3. 线程(Thread):在进程内多条执行路径(main方法也是线程)
    是进程中的"单一的连续控制流程",又被称为轻量级进程
    Threads run at the same time,independently of one another
    一个进程可拥有多个并行的(concurrent)线程
    一个进程中的线程共享相同的内存地址空间->可以访问相同的变量和对象,而且它们从同一堆中分配对象->通信、数据交换、同步操作
    由于线程间的通信时在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快

如何使用线程?

步骤:
一、继承Thread + 重写run();
启动:创建子类对象+对象.start();
注意:内部由cpu管控,线程的启动,如果直接调用run方法,那就不是线程运行了,还是一个一个运行

二、实现Runnable + 重写run();
启动:使用静态代理
	1、创建真实对象
	2、创建代理对象 Thread+引用 Thread 名 = new Thread(Runnable对象);
	3、代理角色.start();

三、实现Callable接口
优点:有返回值,可以处理异常
Callable 和 Future接口的区别:
  (1)Callable规定的方法是call(),而Runnable规定的方法是run(). 
  (2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。  
  (3)call()方法可抛出异常,而run()方法是不能抛出异常的。 
  (4)运行Callable任务可拿到一个Future对象,Future表示异步计算的结果。 
  (5)它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。 
  (6)通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。 
  (7)Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程
	   执行的任务。 

推荐使用接口来实现多继承:
优点:
	1、可以同时实现继承。实现Runnable接口方式要通用一些
	2、避免单继承
	3、方便共享资源 同一份资源 多个代理访问
-----------------------------------------------------------------------------------------
线程的终止情况:
1、自然终止:线程体正常执行完毕
2、外部干涉
		1)、线程类中定义线程体使用的表示
		2)、线程体使用该标识
		3)、提供对外的方法改变该标识
		4)、外部根据条件调用该方法即可
-----------------------------------------------------------------------------------------
线程阻塞:
join和yield:
	1、join:合并线程
	2、yield:暂停线程
	3、sleep:休眠,不释放锁(每个线程对应一把锁)
		sleep使用场景:1、与时间相关的(如倒计时的打印)	2、模拟网络延时(可能有并发的问题)
	
	join是先把自己的线程运行完毕,再运行其他的线程,相当于把其他线程阻塞.
	yield相反,yield则是把自己先暂停,等待其他运行结束再运行自己的线程
使用方法:
	join是直接把Thread对象使用,如t.join();
	yield是在哪个线程体中使用,哪个线程体就暂停(main也是一个线程体)
-----------------------------------------------------------------------------------------
线程的其他方法以及优先级:
 * Thread.currentThread();		当前线程
 * setName(); 					设置名称
 * getName();					获取名称
 * isAlive();					判断状态
 * 优先级:概率,不是绝对的优先级,没有先后顺序
 * 设置优先级:Thread对象.setPriority();
 * 获得优先级:Thread对象.gerPriority();
 * MAX_PRIORITY 10
 * NORM_PRIORITY 5(默认)
 * MIN_PRIORITY 1

线程的同步与锁定:

synchronized锁大家又通俗的称为:方法锁、对象锁和类锁 三种
同步:并发-多个线程访问同一份资源,要确保资源安全-线程安全
synchronnized(lock),即把lock对象当成被锁对象,当a访问时就把lock给锁住,其他对象不能访问
使用同步的方法:
一、直接在类名行中加入synchronized修饰符,即整个类
二、同步块(在某一位置中使用该修饰符,则有特定范围)
	synchronized(引用类型|this|类.class){//其中类.class相当于第一种
		注意:
		1、synchronized(this)
			因为this是当前对象本身,所以锁定只对你自己new了并调用的那个对象有用,所以另外一个人如果
			要new并调用,则和这个不是同一个锁,因为this变了。
		2、synchronized(类的名.class)
			每个类在jvm里面只有一个唯一的字节码,所以.class是唯一的,无论多少对象,共用此同一把锁。
	}

死锁:过多的同步容易造成死锁
死锁产生原理:
	其中线程A运行时会先获取lock1锁对象,然后在不释放lock1的情况下去争夺lock2
	而线程B运行时会先获取lock2锁对象,然后在不释放lock2的情况下去争夺lock1
	这时就有可能会出现这么一种情况:线程A获取到了lock1,准备去抢lock2,线程B获取到了lock2,准备去
	抢lock1。但是此时他们所需要的资源已经被对方所占有而且拥有不会主动释放,程序就会一直等待下去,
	死锁就产生了
-----------------------------------------------------------------------------------------
单例设计模式:确保一个类只有一个对象
一、懒汉式(对象先为null)
	1、构造器私有化,避免外部直接创建对象
	2、声明一个私有的静态变量
	3、创建一个对外的公共的静态方法,访问改变量,如果变量没有对象,则创建新对象
二、饿汉式
	1、构造器私有化,避免外部直接创建对象
	2、声明一个私有的静态变量,同时创建该对象
	3、创建一个对外的公共的静态方法
懒汉式注意点:
	如果单纯的使用懒汉式的话效率会低下,因为只有一个判断对象是否存在的方法,那么不管有没有对象,
	存在其他的线程都要等待,则效率低下
	如果使用下面的DoubleChecking模式,则会提高效率,如果多个线程指向该方法,当已经有存在的对象时
	,则直接退出,所以效率提高
	public static Demo03 getInstance (){
		if(instance == null){ //提供效率
			synchronized(Demo03.class){
				if(null==instance){ //安全
					instance =new Demo03();
				}		
			}		
		}		
		return instance;	
	}
饿汉式注意点:
	单纯的饿汉式是在创建MyJvm2对象的时候就实例化对象的属性,等到要调用的时候又要调用,效率低下
	使用内部类的饿汉式,则会提高效率,在该用的时候调用内部类的实例化,否则不调用,提高效率
	class MyJvm2 {
		private static MyJvm2 instance = new MyJvm2();	
		private MyJvm2(){}
		public static MyJvm2 getInstance(){
			return instance;
		}
	}
	/** 
	 * 类在使用的时候加载 ,延缓加载时间 
	 */
	class MyJvm3 {
		private static class JVMholder{//内部类,这里就会提高效率	
			private static MyJvm3 instance = new MyJvm3();	
		}	
		private MyJvm3(){}	
		public static MyJvm3 getInstance (){
			return JVMholder.instance;	
		} 
	}
-----------------------------------------------------------------------------------------
生产者消费者模式_信号灯法(ThreadDemo中)
notify();	唤醒在此对象监视器上等待的单个线程
notifyAll();	唤醒在此对象监视器上等待的所有线程
wait();		在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待
wait和sleep的区别,wait释放锁,sleep不释放锁
//信号灯(使用前提是必须要有synchronized)
//flag = T 生产者生成,消费者等待 生产完成后通知消费
//flag = F 消费者消费,生产者等待 消费完成后通知生产

Timer定时器类
TimerTask人物类
timer.schedule(task, time);					//执行一次
timer.schedule(task, firstTime, period);	//执行多次,period为过多少毫秒运行一次
问题:JAVA相关抽象类和接口不是不能直接实例化吗,在匿名内部类中为什么就可以直接用new来实例化?
回答:匿名类其实就是相当于实现接口或者实现抽象类的一个具体类,以及不是抽象类或者接口,所以可以实例化,在匿名内部类中必须实现抽象方法,或者接口的方法,否则就会报错的,就是让匿名类变成可以实例化的类,所以匿名内部类不是例外

18-网络编程

  1. 网络:将不同区域的计算机连接在一起 局域网 城域网 互联网
  2. 地址:IP地址 确定网络上的一个绝对的地址 | 位置 -->相当于房子的地址
  3. 端口号:区分计算机软件的 -->房子的房门 2个字节 0-65535 共65536个
    1、在同一个协议下 端口号不能重复 不同的协议可以重复
    2、1024以下的端口不要使用,给一些之名的厂商使用如: 80–>http 21–>ftp
  4. 资源定位:URL:统一资源定位符 URI:统一资源
  5. 数据的传输
    1、协议:TCP协议和UDP协议
      1)、TCP:电话 类似于三次握手 面向连接 安全可靠 效率低下
      2)、UDP:短信 非面向连接 安全较低 效率高
    2、传输:
      1)、先封装
      2)、后拆封

一些基础方法

InetAddress:封装Ip及DNS
getLocalHost();返回本机的Ip地址
getHostAddress();返回IP地址
getHostName();获得域名|本机的计算机名
getByName("ip地址|域名");
如果创建InetAddress对象的时候用的是Ip,如果解析成功,则getHostName()返回的是域名,否则返回的还是Ip地址

URL_爬虫原理
URL由四部分组成:协议 存放资源的主机域名 端口 资源文件名(/之后的,相对路径)

一、创建
	URL(String spec):绝对路径构建
	URL(URL context,String spec):相对路径构建
二、方法
	//绝对路径的构建
	URL url = new URL("http://www.baidu.com:80/index.html#aa?uname=bjsxt");
	System.out.println("协议:"+url.getProtocol());
	System.out.println("域名:"+url.getHost());
	System.out.println("端口:"+url.getPort());
	System.out.println("资源:"+url.getFile());
	System.out.println("相对路径的资源:"+url.getPath());
	System.out.println("锚点:"+url.getRef());//锚点
	//?后的参数,如果存在锚点,就当成锚点的一部分了,为null
	System.out.println("参数:"+url.getQuery());
	//相对路径的构建
	url = new URL("http://www.baidu.com:80/a/");
	url = new URL(url,"b.txt");
	System.out.println(url.toString());
三、流
	openStream();

UDP编程原理
UDP:以数据为中心 非面向连接 不安全 数据可能丢失 效率高

一、类 DategramSocket DategramPacket
1、客服端:
	1)、创建客户端 DategramSocket 类 + 指定端口
	2)、准备数据 字节数组
	3)、打包 打成DategramPacket + 发送的服务器的地址及端口
	4)、发送
	5)、释放资源
2、服务器端:
	1)、创建 服务端 DatagramSocket 类 + 指定端口
	2)、准备接收容器 字节数组 封装 DategramPacket
	3)、包 接受数据
	4)、分析
	5)、释放资源

TCP_Socket通信原理

一、面向连接:请求-相应 Request -- Response
二、Socket编程
	1、服务器:ServerSocket
	2、客户端:Socket
	3、连接客户端和服务端的用来传输的管道叫Socket
注意:
	1、如果服务端已经启动过再启动则会出错
	2、如果服务端没有启动而启动客户端则会出错(而UDP不出错)

TCP、UDP实现聊天室步骤

TCP实现步骤(服务端和客户端基本差不多):
1、
	声明2个输入流 1个输出流 1个ServerSocket 1个Socket
	其中,ServerSocket用于接受Client传来的消息并存放在Socket内
	Socket就相当于一个存放消息的临时容器
	2个输入流中1个输入流用于读取Socket中的信息,1个用来写入要发送的信息
	1个输出流就是将写入的信息传给Socket,再利用Socket传送到Client端口
2、
	在Try-catch-finally结构中写入主要代码
	Try中写入比如具体输入、输出、Socket、ServerSocket的实例化,并实现功能
	catch捕捉异常,并输出捕捉的异常
	finally用于关闭资源,避免出现不必要的浪费
3(编写线程服务器)、
	将服务器的发送消息和传出消息的功能封装到线程中去
	只用在main函数中调用该线程即可,这样main中只有一个用于读取的输入流
	并且这样可以实现更灵活的双向通信,一个专门接受,一个专门发送

UDP协议实现聊天室的主要步骤:
1、将要发送的信息存放在DategramPacket中,再利用DatagramSocket发送
2、UDP传送数据基本都是用Byte传输,故不特意使用其他输入流
3、如果要传入其他基本数据类型,将Byte流和Date流一起使用,可以将类型转换并传输
4、如果要传入对象类型,将Obj流和Byte流一起使用
5、基本步骤和TCP类似,但简单一些

19-注解及动态代理

另一篇博客写的很清楚了:5分钟帮你搞懂自定义注解及动态代理

20-正则表达式和文本操作

操作文本的非常常用的技术,它是一个简单的小语言,有了正则,处理代码的效率会大大提高
学习正则表达式很大程度上是学习正则表达式的语法规则

【标准字符集合_自定义字符集合】
·普通字符和简单转义字符
·标准字符集合:
	能够与'多种字符'匹配的表达式
	*注意区分大小写,大写是相反的意思,比如大写的D就是非数字
		\d	任意一个数字,0~9种的任意一个,注意是一个一个匹配
		\w	任意一个字母或数字或下划线,也就是A~Z,a~z,0~9,_中任意一个
		\s	包括空格、制表符、换行符等空白字符的其中任意一个
		'.' 小数点能匹配任意一个字符(除了\n),如果要匹配包括'\n'在内的所有字符,一般用[\s\S]
		[]  自定义字符集合,表示范围可用'-',如[2-8].表示取反可用'^',如[^abc]
【自定义字符集合特殊用法_量词】
{n}		表示重复几次,如:\d{6}相当于\d\d\d\d\d\d
{m,n}	表示至少重复m次,最多重复n次
{m,}	表示至少重复m次
?		匹配表达式0或1次,相当于{0,1}
+		表达式至少出现1次,相当于{1,}
*		表达式不出现或出现任意次,相当于{0,}
【字符串匹配_捕获】
\b,匹配一个位置,如果一个字符串为gaoqi,则gaoqi\b则可以匹配,因为i左边为q,右边为' '
字符边界:^ $ \b匹配的不是字符是位置,匹配到的是零宽度的,而字符占宽度

反向引用:([a-z]{2})\1可以用来捕获gogo toto dodo这类字符串,即要捕获两组才满足要求
捕获到的内容会存在内存中,为了节省内存,对不需要的内容前加?:即可

Eclipse和Notepad++使用正则表达式的方式
在Notepad++使用正则表达式,ctrl+f,选择正则表达式
在Elipse使用正则表达式,search,选择正则表达式

Java中使用正则表达式

1、创建Pattern对象,填写正则表达式,注意一杠变两杠
2、创建Matcher对象,填写待匹配对象,与表达式关联
	调用matches()返回真值,尝试将整个字符串序列与该模式匹配
	调用find()返回真值,如果在序列中找到了,则指针指向满足条件的子序列末尾,下一次接着找
	调用group()返回字符串,返回满足条件的字符串,group(0)|()都是查找整个字符串
	调用group(1)...(n)是查找捕获的对象
	调用replaceAll("?")返回字符串,将查找到的字符串替换为?
	调用字符串的split("?")返回字符串数字,用'?'来切割字符串,并存放到字符数组中

21-JDBC在Java中的使用

老版:加载Mysql驱动的代码:Class.forName("com.mysql.jdbc.Driver");
*新版:加载Mysql驱动的代码:Class.forName("com.mysql.cj.jdbc.Driver");

老版:获得连接对象的代码:Connection con = DriverManager.getConnection("jdbc:mysql://host:port/数据库","user","password");
*新版:获得连接对象的代码:con = DriverManager.getConnection("jdbc:mysql://localhost:3306/数据库?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC","user","password");

·建立连接对象实际上就是利用socket与远程的数据库进行连接,看似在一个软件上运行,实际上不是
·连接对象内部其实包含了Socket对象,属于远程的连接,比较耗时!这是Connection对象管理的一个要点
·真正的开发中,为了提高效率,都会使用连接池来管理连接对象

JDBC使用应注意的点:

一般开发中比较少直接使用Statement语句,因为有注入危险,比如:
	String id = "5 or 1=1";
	String sql = "delete from t_user where id="+id;
	stmt.execute(sql);
如果要删除的id是客户端传入的id,则像上面修改的一样,如果1=1那么就将所有的列都删除了,所以我们使用PreparedStatment来执行SQL语句
批处理要避免使用PreparedStatment,因为PreparedStament本身会占用内存

使用PreparedStatment接口主要的注意点:
1、占位符的问题:
	//?是占位符,避免拼sql注入,传入的时候有个预处理的过程,只能填单类型,多余的进不来
	String sql = "insert into t_user (username,pwd,regTime) values (?,?,?)";
	//这样就避免了sql注入危险
2、插入元素方式:
	//第一种插入方式,要设置对应的类型插入
	ps.setString(1, "HPF");	//参数索引是从1开始计算,而不是0!
	ps.setString(2, "HX");
	ps.setDate(3, new java.sql.Date(System.currentTimeMillis()));
	//第二种插入方式,直接使用OBJ对象插入,就不用管是什么类型了
	ps.setObject(1, "L0UE");
	ps.setObject(2, "123456");
	ps.setObject(3, new java.sql.Date(System.currentTimeMillis()));
	ps.execute();

选取数据库中元素的方式有:
	1、String sql = "select id,username,pwd from t_user where id>?";
	   select后面加上要选取的元素名称
	2、String sql = "select * from t_user where id>?";
	   select后面加上*即选取整列所有元素
常用的Statment方法:
	execute():运行语句,返回是否有结果集,之后用的少
		也可以用来执行sql语句,con.execute(sql);
	executeQuery():运行select语句,返回ResultSet结果集
		ResultSet rs = ps.executeQuery();
		while(rs.next()){//next从0开始,第一次使用指向1,并返回1后有无数据
			//sql语句:String sql = "select id,username,pwd from t_user where id>?";
			System.out.println(rs.getInt(1)+"---"+rs.getString(2)+"---"+rs.getString(3));
		}
	executeUpdate():运行insert/update/delete操作,返回更新的行数,即返回修改了几次
		int count = ps.executeUpdate();
		System.out.println(count);
使用完sql驱动后一定要关闭(finally):
	关闭的顺序遵循:后开的先关,resultset --> statment --> connection
	关闭的时候一定要分开关,将三个try-catch分开写,否则出现异常将不会执行下面语句

22-JAVASE完结

这篇文章整合了我的所有JavaSE笔记以及一些经验理解,分享给大家看看,也方便我自己回顾知识!

相关标签: JavaWeb个人心得