JavaSE学习笔记(持续更新)

这里写目录标题

JavaSE学习笔记(持续更新)

Java跨平台原理与核心机制

1.跨平台原理:

JAVA编译器会将JAVA源代码编译转换为JAVA字节码,然后在运行时通过JVM(JAVA虚拟机)对JAVA字节码进行翻译。由于不同平台的JVM是不一样的,是与平台对应的所以JVM会将JAVA字节码翻译为对应平台可兼容识别的代码,以此来达到跨平台的目的。

2.两种核心机制:

(1)JAVA虚拟机——JVM
JVM可以理解成一个可运行Java字节码的虚拟计算机系统:
它有一个解释器组件,可以实现Java字节码和计算机操作系统之间的通信
对于不同的运行平台,有不同的JVM。
Java虚拟机工作原理图
(2)垃圾回收器——GC
不再使用的内存空间应当进行回收-垃圾回收。

在C/C++等语言中,由程序员负责回收无用内存。

Java语言消除了程序员回收无用内存空间的责任:

JVM提供了一种系统线程跟踪存储空间的分配情况。并在JVM的空闲时,检查并释放那些可以被释放的存储空间。

垃圾回收器在Java程序运行过程中自动启用,程序员无法精确控制和千预。

JDK11的安装流程

1.JDK11的安装包需要从JAVA官网(oracle.com)下载
进入后点击左上角菜单->点击JAVA->下载安装包->双击安装包安装

2.环境配置
右击此电脑->高级系统设置->环境变量->双击Path变量->填入已安装的JDK的bin文件所在的路径

环境配置更合理的方式是新建一个专门用于存放JAVAbin目录的变量。

Java程序开发的三个步骤(无编辑器版)

Java跨平台原理
首先在命令行中通过javac命令将java源文件转化为java字节码文件
举例:
javac HelloWorld.java

命令执行完后会在当前目录生成一个HelloWorld.class文件,这个文件就是对应的java字节码文件。然后使用java命令来运行(注意不要加文件后缀名)
举例:
java Helloworld

Eclipse安装步骤

安装包下载地址:eclipse.org
点击右上角的Download->点击Download Packages->选择合适的安装包并下载

在Eclipse中创建JAVA工程

File->New->Project->选择java project
最后要创建特殊代码文件和默认视图开启都暂时选否。

在src文件夹中创建class等文件进行代码编写:
右击src->New->class

然后填写该类所属的包(package),包的作用就是将class等文件分门别类。(注意是否勾选自动生成main方法)
勾选自动生成main方法

Eclipse新建项目注意点

1.注意设置当前要运行项目的src的Build Path,否则会报错。方法:
右击src->Build Path->Use as Source Folder

设置编辑器字体大小:

Window->Preferences->General->Appearance->Color sand Fonts->
Basic->Text Font->双击选择大小

运行代码:

在程序入口文件的任意位置右击->Run As->1 Java Application
Java代码语句的分类
1.结构定义语句(一般由大括号组成)
2.功能执行语句(一般在大括号内部完成)

Java变量概述

按所属数据类型分:
1.基本数据类型变量
2.引用数据类型变量

按被声明的位置分:
1.局部变量:方法或语句块内部定义的变量
2.成员变量:方法外部、类的内部定义的变量
注意:类的外面不能有变量声明

Java变量的类型:

Java变量的类型

数据类型转换:

boolean类型不能转换成任何其它数据类型。
自动类型转换:容量小的类型自动转换成容量大的数据类型
byte,short,int -> float ->long ->double
byte,short,int不会互相转换,它们三者在计算时会转换成int类型
强制类型转换:容量大的类型转换成容量小的数据类型时,要加上强制转换符
long l = 100L;
int i = (int)l;
有可能造成精度降低或数据溢出,使用时要小心。

Java变量定义的小问题:

1.float类型变量在定义时需注意:我们直接写出的浮点数字,默认类型是double , 编译器会提示需要强转
解决:我们可以在浮点数字的后面加入f ,来表示写出的数字类型是float
float举例

2.long类型变量定义需注意:我们直接写出的整型数字,默认类型是int,当值过大时会提示出错
解决:我们在整型数字后面加入l,来表示写出的整型数字是long类型的
long举例

3.char类型在定义时使用单引号(String用双引号),且可以转换为int类型并参与数字运算。

Java方法的定义:

概述:
方法用于封装一段特定的逻辑功能。方法的主要要素有:权限修饰符、方法名、参数列表和返回值。

格式:
权限修饰符 返回值类型声明 方法名称(参数列表){
方法中封装的逻辑功能;
return 返回值;
}

权限修饰符详解:

权限修饰符所对应的作用域

注意:不含static修饰符的函数会比包含static修饰符的函数加载慢,所以在包含static修饰符的函数中调用不含static修饰符的函数会出错,解决方法是:
1.将不含static修饰符的函数写在包含static修饰符的函数前面
2.为不含static修饰符的函数添加static修饰符

tip:

1.加号“+”运算符两侧只要有一个字符串那么它就会变成字符串连接符。
2.类中定义的是成员变量,方法内定义的是局部变量
3.非输入法状态下ctrl+shift+f可以在编译器中对代码进行格式化。

接收用户输入(Scanner):

注意:

1.导包,即导入Scanner方法所在的包
导包

2.Scanner方法有参数,这里为System.in
nextInt():接收用户输入的int类型的数据,如果输入的数据不是int类型会抛出异常。同类型方法有nextFloat、nextByte等等。
Scanner方法使用举例

next():接收用户输入的内容,返回一个String。空格和回车都算结束,也就是说这个方法无法接收空格

nextLine():接收用户输入的一行内容,也返回一个String,只有回车算结束。但注意不要与next()一起使用会有问题,因为当你输入完内容时需要按回车,这样nextLine()就会判定你输入完了,但你的输入是之前next()的输入而不是新的输入所以nextLine()的值是之前输入的值。

执行结构:

顺序结构
顺序结构是一种基本的控制结构,它按照语句出现的顺序执行操作

分支结构
分支结构又被称为选择结构,根据条件成立与否来执行操作。

循环结构
循环结构是一种重复结构,如果条件成立,它会重复执行某一循环体,直到出现不满足的条件为止。

switch语句:

switch(表达式){ 
	case 取值 1: 
	语句块 1;
	break; 
	case 取值 n: 
	语句块 n;
	break; 
	default: 
	语句块 n+1;
	break; 
}

switch 语句有关规则

1.表达式的返回值必须是下述几种类型之一:int, byte, char, short,String;
2.case 子句中的取值必须是常量,且所有 case 子句中的取值应是不同的;
3.default 子句是可选的;
4.break语句用来在执行完一个case分支后使程序跳出switch语句块;如果case后面没有写break则直接往下面执行!Case后面的执行体可写{ }也可以不写{ }

退出多重循环的方法:

给循环起个别名即可:
退出多重循环举例

这里起了个别名叫“haha”,然后在内层break haha;即可

判断用户的输入类型:

判断用户输入类型举例

用hasNext…()系列方法:如hasNextInt()、hasNextBool()等等。

数组:

概述
数组是相同数据类型的多个数据的容器。
这些元素按线性顺序排列。所谓线性顺序是指除第一个元素外,每一个元素都有唯一的前驱元素;除最后一个元素外,每一个元素都有唯一的后继元素。(“简单理解就是:一个跟一个顺序排列”)。

创建格式

常用格式1.数据类型[] 数组名称=new数据类型[数组长度];

常用格式2.数据类型[] 数组名称={数组内容1,数组内容2,数组内容3…数组内容n};

获取数组长度

数组名称.length

数组常见问题:

1.数组下标越界

2.空指针问题(数组引用的内容为空)
空指针问题举例

二分查找:

中间下标计算方式:(最小下标+最大下标)/2

将要查找的数据和中间下标所在数据进行比较,较大的话更新最小下标,否则更新最大下标(更新:中间下标-1/+1)。循环执行,每次边界变更都要重新计算中间下标。
二分查找演示

当最小下标>最大下标时说明要查找的数据不存在。

多维数组:

多维数组创建格式

面向对象概述:

面向对象是相对于面向过程来讲的,指的是把相关的数据和方法组织为一个整体来看待

从面向过程到面向对象,是程序员思想上从执行者到指挥者的转变。

类:

类必须通过对象才可以使用,对象的所有操作都在类中定义。

类由属性和方法组成:
属性:就相当于人的一个个的特征
方法:就相当于人的一个个的行为,例如:说话、吃饭、唱歌、睡觉

类必须编写在.java文件中,
一个.java文件中,可以存在N个类,但是只能存在一个public修饰的类。
.java文件的文件名称必须与public修饰的类名完全一致;

类的定义格式:

class 类名称{
	成员属性
	成员方法
}

方法定义格式:

返回值类型 方法名称(形式参数列表){
	方法体
	return返回值;
}

调用格式:
对象名.方法名称(实际参数列表)

创建对象格式:

类名 对象名 = null;
或者
类名 对象名 = new 类名();

创建对象内存分析:

创建对象内存分析1

案例1:

以上图程序为例:
在第一行:
当Book出现时,该类的属性和方法会被放到方法区中。

栈中会存放对象名称和基本数据类型的值,所以当Book b1出现时会将对象名称b1压栈,并让其指向一个null(因为new还没执行)。

堆中会存放类的对象,所以当执行new Book()时会在堆中开辟一个内存空间(叫Book,地址假设为0x1234)。

然后执行“=”赋值操作,即将Book对象的地址赋值给b1。则此时b1不是指向null了而是指向0x1234.
创建对象内存分析2
第五行:
Book b2 = b1;
不能理解为复制,而是定义了一个新的Book对象名b2并且让它指向了b1对象所在的地址(0x1234),此时它们可以看做是不同名的同一个对象。

案例2:

创建对象内存分析3
上图为前1~6行
创建对象内存分析4
当执行第7行b2 = b1;时和之前一样,b2指向了b1则原来b2指向的空间会被GC回收(因为没有对象指向其)。所以此时两个对象名又指向了同一块内存。

构造方法:

1、所有的Java类中都会至少存在一个构造方法

2、如果一个类中没有明确的编写构造方法,则编译器会自动生成一个无参的构造方法,构造方法中没有任何的代码!

3、如果自行编写了任意一个构造方法,则编译器不会再自动生成无参的构造方法。

定义格式:

与普通方法基本相同,区别在于:方法名称必须与类名相同,没有返回值类型的声明!

作用:用于对象初始化

构造方法设计建议:

1、建议自定义无参构造方法,不要对编译器形成依赖,避免错误发生。

2、当类中有非常量成员变量时,建议提供两个版本的构造方法,一个是无参构造方法,一个是全属性做参数的构造方法。

3、当类中所有成员变量都是常量或者没有成员变量时,建议不提供任何版本的构造。

方法重载:

一个类中定义的方法,是允许重载(相同的方法名称)
1、方法名称相同

2、参数列表长度或参数列表类型或参数的类型顺序不同

3、与返回值类型无关

构造方法的重载:

构造方法的重载,可以让我们在不同的创建对象的需求下,调用不同的方法来完成对象的初始化!

匿名对象:

匿名对象只能使用一次,使用完后会被GC回收。
如果一个对象,我们准备使用两次或以上。那么一定要给对象创建对象名。

举例:

匿名对象举例
这里就使用了匿名对象,直接new了一个Math2对象而没有使用对象名称。

封装:

概述:
封装的意义在于保护或者防止代码〈数据〉被我们无意中破坏。保护成员属性,不让类以外的程序直接访问和修改;

封装原则:
隐藏对象的属性和实现细节,仅对外公开访问方法,并且控制访问级别

在开发中,为了避免出现逻辑错误,我们建议对所有属性进行封装(将属性设置为private),并为其提供setter及getter方法进行设置和取得操作。

this关键字:

this指的是当前对象
this不止可以调用属性,还可调用方法。

举例:

this关键字
比如这里的无参构造方法,里面就用this调用了下面定义的有参构造方法并赋值(构造方法可以直接用this(),否则得用this.函数名())。

在Java基础中,this关键字是一个最重要的概念。使用this关键字可以完成以下的操作:
﹒调用类中的属性
﹒调用类中的方法或构造方法
-在一个构造方法中,调用另一个构造方法时,调用另一个构造方法的代码必须编写在构造方法的第一行。
·表示当前对象

静态static:

static表示“静态"的意思,可以用来修饰成员变量和成员方法(后续还会学习静态代码块和静态内部类)。

static的主要作用在于创建独立于具体对象的域变量或者方法

简单理解:
被static关键字修饰的方法或者变量(它们存在于方法区中,而不是堆)不需要依赖于对象来进行访问,只要类被加载了,就可以通类名去进行访问。并且不会因为对象的多次创建而在内存中建立多份数据
内存中的static

重点:

1.静态成员在类加载时加载并初始化。

2.无论一个类存在多少个对象,静态的属性,永远在内存中只有一份(可以理解为所有对象公用)

3.在访问时:静态不能访问非静态,非静态可以访问静态!(因为静态方法或属性被调用时,对象可能还未创建。对象未创建,非静态方法或属性就也为创建。)

静态属性或方法可以通过类名.属性名或者类名.方法名来访问:
访问举例

包:

包介绍:

1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。

2、包如同文件夹一样,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。

3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

包中java文件的定义:

在.java文件的首部,必须编写类所属哪个包,格式:package包名;

包的定义:

通常由多个单词组成,所有单词的字母小写,单词与单词之间使用点隔开,一般命名为"com .公司名.项目名.模块名…”。

import关键字(导包,当使用其它包中的类时就需要导包):

import包名.类名;

代码块:

普通代码块
在执行的流程中出现的代码块,我们称其为普通代码块。

构造代码块
在类中的成员代码块,我们称其为构造代码块,在每次对象创建时执行,执行在构造方法之前,且必须会执行,与构造方法不同,构造方法不一定会执行(比如一个无参构造方法,一个有参构造方法这两个可能会只执行一个)。所以一些公共的必须执行的代码可以放到构造代码块中。

举例:

构造代码块举例
静态代码块
在类中使用static修饰的成员代码块,我们称其为静态代码块,在类加载时执行。每次程序启动到关闭,只会执行一次的代码块。
静态代码块举例
同步代码块
在后续多线程技术中学习。(后续更新)

面试题tip:

构造方法与构造代码块以及静态代码块的执行顺序:
静态代码块–>构造代码块–>构造方法

*继承

继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。当类之间存在层次关系时建议使用继承,比如人类和学生类。这样可以提高代码的复用性和简洁性。

继承的限制

Java中只有单继承,多重继承没有多继承
单继承:一个子类只继承一个父类

多重继承:如果一个c类想继承a类和b类,那么可以用b类继承a类,再让c类继承b类。(因为b类继承a类时b类就能使用a类的东西;同理c类继承b类时,c类就能使用b类的所有东西,包括b类继承自a类的东西。)这样阶梯式的继承称为多重继承。

多继承:一个子类继承多个父类,与多重继承的区别在于,没有阶梯式继承父类。
格式:

class	父类{

}
class	子类		extends	父类{

}

注意权限修饰符:父类中只有被public或protect修饰的方法和变量才能被子类继承,但是通过反射方法子类是可以使用父类中被private修饰的方法和变量的。所以正确的说法是,子类可以继承父类中private类型的方法和变量但是不能直接使用,得通过反射方式来使用。

子类实例化内存分析:

举例:
子类实例化内存分析
假设Student类继承自Person类。
当创建Student类的对象时,内存不会直接开辟该Student类对象的空间,而是先查看该Student类对象有哪些父类,此时发现Student类有个父类Person。然后会在堆内存中先创建一个Person对象。等到该Person对象完全创建完毕后,再创建一个Student类对象,然后往该Student类对象里加了个属性super,且super指向之前的Person对象的地址
子类实例化内存分析

super详解:

super:
通过super ,可以访问父类构造方法
通过super ,可以访问父类的属性
通过super ,可以访问父类的方法

如果父类没有无参构造方法那么子类在继承时就必须明确的通过super来调用父类的某一构造方法,否则会报错。
举例:
父类中有一个两参构造方法:
super详解
可以在子类中通过super这样调用:
super详解
用super调用父类构造方法的代码,必须写在子类构造方法的第一行。

用super调用父类的方法和属性(注意父类方法和属性的权限)
super详解

重写(可以理解为子类对父类同名方法的扩展):

重写(override)规则:
1、参数列表必须完全与被重写方法的相同;
2、返回类型必须完全与被重写方法的返回类型相同;
3、访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected;
4、父类的成员方法只能被它的子类重写。
5、声明为static和privete的方法不能被重写,但是能够被再次声明。

举例:
重写
此时输出为:床前明月光,玻璃好上霜。要不及时擦,整不好得脏!
也就是说子类中重写的方法能将父类的方法覆盖。
注意:当子类中的重写方法不符合上述规则时,是无法完成重写的,也就是说父类的方法不会被覆盖。
当父类的方法在子类中不适用时可以使用重写。

面试题tip:

Java中重写(Override)与重载(Overload)的区别
1、发生的位置
重载:一个类中
重写:子父类中
2、参数列表限制
重载:必须不同的
重写:必须相同的
3、返回值类型
重载:与返回值类型无关
重写:返回值类型必须一致
4、访问权限:
重载:与访问权限无关
重写:子类的方法权限必须不能低于父类的方法权限
5、异常处理:
重载:与异常无关
重写:异常范围可以更小,但是不能抛出新的异常。

final关键字:

final用于修饰属性、变量。
当属性、变量被final修饰后,它们就成了常量,不允许改变。
如果被final修饰的局部变量在创建时未被赋值,则可以被赋值一次
如果被final修饰的是成员变量(属性),则必须在声明时对其赋值

全局常量( public static final ):这个常量可以在工程中的任何位置通过类名被直接使用。

常量的命名规范:
由1个或多个单词组成,单词与单词之间必须使用下划线隔开,单词中所有字母大写。

final用于修饰类。
final修饰的类,不可以被继承。
final用于修饰方法。
final修饰的方法,不能被子类重写。

抽象类:

抽象类必须使用abstract class声明
一个抽象类中可以没有抽象方法。抽象方法必须写在抽象类或者接口中。
格式:

abstract class	类名{//抽象类

}

抽象类中可以有抽象的部分,比如抽象方法,也可以有不抽象的部分
抽象类
这里的age就是不抽象的部分。

在抽象类的使用中有几个原则:
1、抽象类本身是不能直接进行实例化操作的(不能直接创建对象),即:不能直接使用关键字new完成。(图纸不完全没法造出实际的东西)
2、一个抽象类必须被子类所继承,继承的子类(如果不是抽象类)则必须覆写(重写)抽象类中的全部抽象方法。(通过子类来实现实例化)

抽象方法:

只声明而未实现的方法称为抽象方法(未实现指的是:没有"{}"方法体),抽象方法必须使用abstract关键字声明。
格式:

abstract class	类名{ //抽象类
	public abstract void方法名() ;//抽象方法,只声明而未实现
}

举例:
抽象方法
抽象类Person中声明了一个抽象方法say();
抽象方法
Student类对父类Person抽象类的具体实现。
调用Student类:
抽象方法
输出:
抽象方法

抽象类的常见问题:

1、抽象类能否使用final声明?
不能,因为final修饰的类是不能有子类的,而抽象类必须有子类才有意义,所以不能。
2、抽象类能否有构造方法?
能有构造方法,而且子类对象实例化的时候的流程与普通类的继承是一样的,都是要先调用父类中的构造方法(默认是无参的),之后再调用子类自己的构造方法。

抽象类和普通类的区别:

1、抽象类必须用public或procted 修饰(如果为private修饰,那么子类则无法继承,也就无法实现其抽象方法)。默认缺省为public
2、抽象类不可以使用new关键字创建对象,但是在子类创建对象时,抽象父类也会被JVM实例化。
3、如果一个子类继承抽象类,那么必须实现其所有的抽象方法。如果有未实现的抽象方法,那么子类也必须定义为abstract类。

*接口:

如果一个类中的全部方法都是抽象方法,全部属性都是全局常量,那么此时就可以将这个类定义成一个接口。
定义格式:

interface	接口名称{
	全局常量;
	抽象方法;
}

注意:如果一个接口要想使用,必须依靠子类(和抽象类一样)。子类(如果不是抽象类的话)要实现接口中的所有抽象方法。

接口的实现:

接口可以多实现:
格式:

class 子类 implements 父接口1,父接口2...{ 
} 

如果一个类即要实现接口,又要继承抽象类的话,则按照以下的格式编写即可:

class 子类 extends 父类 implements 父接口1,父接口2...{ 
} 

面向接口编程思想:
这种思想是:接口是定义(规范,约束)与实现(名实分离的原则)的分离。
优点:
1、 降低程序的耦合性
2、 易于程序的扩展
3、 有利于程序的维护

注意:接口中定义的方法都是抽象方法,所以不能实现(即在方法后加上{})否则会报错,而且创建时可以省略public和abstract关键字,编译时会自动加上。接口中的变量都是全局常量,定义时可省略public、static、final关键字,由于这些变量都是全局常量所以要赋初始值。
举例:
接口
接口Person
接口
实现类Student
接口
创建实现类Student对象并使用

接口的继承:

接口因为都是抽象部分,不存在具体的实现,所以允许多继承,例如:

interface C extends A,B{ 
} 

接口和抽象类的区别:

从设计层面来说,抽象类是对类的抽象,是一种模板设计;接口是行为的抽象,是一种行为的规范。

1、抽象类要被子类继承,接口要被类实现。
2、接口只能声明抽象方法,抽象类中可以声明抽象方法,也可以写非抽象方法。
3、接口里定义的变量只能是公共的静态的常量, 抽象类中的变量可以是普通变量。
4、抽象类通过继承来使用, 无法多继承。 接口通过实现来使用, 可以多实现 (单继承,多实现)
5、抽象类中可以包含static方法 ,但是接口中不允许(静态方法不能被子类重写,因此接口中不能声明静态方法)
6、接口不能有构造方法,但是抽象类可以有
7、抽象类中的抽象方法的访问类型可以是public,protected;但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型
8、抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意;但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。

接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则:

行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
选择抽象类的时候通常是如下情况:需要定义子类的行为,又要为子类提供通用的功能。
面试题tips:

1、说一说你对抽象类的理解吧,他到底是干啥用的?

我们常说面向对象的核心思想是:先抽象,后具体。抽象类是含有抽象方法的类,不能被实例化,抽象类常用作当做模板类使用。
接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。
而抽象类在代码实现方面发挥作用,可以实现代码的重用,例如,模板方法设计模式是抽象类的一个典型应用,
假设某个项目的所有Servlet类都要用相同的方式进行权限判断、记录访问日志和处理异常,那么就可以定义一个抽象的基类,
让所有的Servlet都继承这个抽象基类,在抽象基类的service方法中完成权限判断、记录访问日志和处理异常的代码,
在各个子类中只是完成各自的业务逻辑代码。父类方法中间的某段代码不确定,再留给子类干,就用模板方法设计模式。

2、用抽象类实现一个接口,和普通类实现接口会有什么不同么?

一般来说我们使用普通类来实现接口,这个普通类就必须实现接口中所有的方法,这样的结果就是普通类中就需要实现多余的方法,造成代码冗余。但是如果我们使用的是抽象类来实现接口,那么就可以只实现接口中的部分方法,并且当其他类继承这个抽象类时,仍然可以实现接口中有但抽象类并未实现的方法。

如以下代码,抽象类只是实现了接口A中的方法a,方法b,但是当类C继承抽象类B时,可以直接实现接口A中的c方法,有一点需要注意的是,类C中的方法a,方法b都是调用的父类B的方法a,方法b,不是直接实现接口的方法a和b。

/**
 *接口
 */
 interface A{
 public void aaa();
 public void bbb();
 public void ccc();
 }
 /**
 *抽象类
 */
 abstract class B implements A{
 public void aaa(){}
 public void bbb(){}
 }
 /**
 * 实现类
 */
 public class C extends B{
 public void aaa(){}
 public void bbb(){}
 public void ccc(){}
 }

引用说明:
版权声明:上述部分内容为CSDN博主「_陈哈哈」的原创文章。
原文链接:https://blog.csdn.net/qq_39390545/article/details/117638169

*多态:

多态:就是对象的多种表现形式(多种体现形态)

多态的体现:

对象的多态性,从概念上非常好理解,在类中有子类和父类之分,子类就是父类的一种形态 ,对象多态性就从此而来。

ps: 方法的重载 和 重写也是多态的一种, 不过是方法的多态(相同方法名的多种形态)。
重载: 一个类中方法的多态性体现
重写: 子父类中方法的多态性体现。

多态的使用:对象的类型转换:

类似于基本数据类型的转换:
· 向上转型:将子类实例变为父类实例
|- 格式:父类 父类对象 = 子类实例 ;
· 向下转型:将父类实例变为子类实例
|- 格式:子类 子类对象 = (子类)父类实例 ;

注意:子类对象可以与父类对象进行相互转换,但同级子类对象之间不能进行相互转换。

举例:
多态
此处,Student类个Nurse类都继承自Person类。Person类(父类)对象可以转换为子类对象(Person p1 = a;),子类对象也可以转为父类对象(Student a2 = (Student)p1;)。但是同级子类对象之间不能相互转换,执行Student a3 = (Student)p2;时会报错,因为a3是一个Student类对象而p2是一个Nurse类对象,这两个类属于同级子类。

多态的应用举例:

多态应用
Student类是Person类的子类。这里的say()方法需要传递一个Person类对象,由于Student类是Person类的子类那么可以直接传递(会进行转换)。如果有一天我们觉得Student类需要修改了,那么只需要修改Student类即可而这个say()方法依旧不用修改(程序耦合性低)。

instanceof的使用:

作用:
判断某个对象是否是指定类的实例,则可以使用instanceof关键字,可以防止对象转换错误(比如同级子类对象相互转换)
格式:
实例化对象 instanceof 类 //此操作返回boolean类型的数据

Object类:

Object类是所有类的父类(基类),如果一个类没有明确的继承某一个具体的类,则将默认继承Object类。

Object的多态:

使用Object可以接收任意的引用数据类型
举例:
Object多态

toString()方法:

建议每个类都重写Object的toString方法用来更明确的描述这个类。

equals()方法:

内部实现是两个对象之间采用=='的方式比较,返回一个布尔值。而对象地址不同用=='来比较的结果会是false,所以使用equal()方法比较对象时很多时候需要重写equal()方法。
举例:
equals
此时输出:false

equals比较时相等推荐将常量放在前面,以防止空指针异常。
equals
重写举例(根据自己的要求定义两个对象的相等规则):
equals
也可用快捷键生成。

包装类:

在Java中有一个设计的原则“一切皆对象”,那么这样一来Java中的一些基本的数据类型,就完全不符合于这种设计思
想,因为Java中的八种基本数据类型并不是引用数据类型,所以Java中为了解决这样的问题,引入了八种基本数据类型的包装类
包装类
以上的八种包装类,可以将基本数据类型按照类的形式进行操作。
但是,以上的八种包装类也是分为两种大的类型的:
· Number:Integer、Short、Long、Double、Float、Byte都是Number的子类表示是一个数字。
· Object:Character、Boolean都是Object的直接子类。

包装类是一种类,所以普通数据类型的值存储在栈中,而包装类的值存储在堆中。
包装类

装箱和拆箱操作:

以下以Integer和Float为例进行操作
将一个基本数据类型变为包装类,那么这样的操作称为装箱操作。
将一个包装类变为一个基本数据类型,这样的操作称为拆箱操作,
因为所有的数值型的包装类都是Number的子类,Number的类中定义了如下的操作方法,以下的全部方法都是进行拆箱的操作:
包装类
在JDK1.5,Java新增了自动装箱和自动拆箱,而且可以直接通过包装类进行四则运算和自增自建操作。例如:
Float f = 10.3f ; // 自动装箱
float x = f ; // 自动拆箱
System.out.println(f * f) ; // 直接利用包装类完成
System.out.println(x * x) ; // 直接利用包装类完成

字符串转换:

使用包装类还有一个很优秀的地方在于:可以将一个字符串变为指定的基本数据类型,此点一般在接收输入数据上使用 较多。
在Integer类中提供了以下的操作方法:
public static int parseInt(String s) :将String变为int型数据
在Float类中提供了以下的操作方法:
public static float parseFloat(String s) :将String变为Float
在Boolean 类中提供了以下操作方法:
public static boolean parseBoolean(String s) :将String变为boolean

可变参数:

语法:

返回值类型 方法名称(数据类型…参数名称){ 
	//参数在方法内部 , 以数组的形式来接收 
} 

将可变参数当做数组来使用即可。
注意:可变参数只能出现在参数列表的最后。

举例:
可变参数

递归:

明确一个概念:当调用的方法未执行完毕时,程序是不会往下走的。
递归

异常处理:

异常是在程序中导致程序中断运行的一种指令流。

try-catch捕获异常(不让异常反馈给JVM而是自己处理):

如果要想对异常进行处理,则必须采用标准的处理格式,处理格式语法如下:

try{
	// 有可能发生异常的代码段 
}catch(异常类型1 对象名1){ 
	// 异常的处理操作 
}catch(异常类型2 对象名2){
	// 异常的处理操作 
} ... 
finally{ 
	// 异常的统一出口,无论是否发生异常都会走 
}

异常处理的作用:

进行过异常处理的程序,能继续往下执行,而未进行的程序会直接中断。

异常的体系结构:

异常指的是Exception , Exception类, 在Java中存在一个父类Throwable(可能的抛出)
Throwable存在两个子类:
1.Error:表示的是错误,是JVM发出的错误操作,只能尽量避免,无法用代码处理。

2.Exception:一般表示所有程序中的错误,所以一般在程序中将进行try…catch的处理。
异常处理
受检异常:又称为非运行时异常,代码编写时就会提示错误
非受检异常:又称为运行时异常,代码运行时才会提示错误

try+catch的处理流程:

1、 一旦产生异常,则系统会自动产生一个异常类的实例化对象。

2、 那么,此时如果异常发生在try语句,则会自动找到匹配的catch语句执行,如果没有在try语句中,则会将异常抛出。

3、 所有的catch根据方法的参数匹配异常类的实例化对象,如果匹配成功,则表示由此catch进行处理。
异常处理
异常处理举例:
异常处理
在catch中对异常进行处理。

多个异常处理格式:

1、多个catch:
异常处理
2、并列异常(使用较少):
异常处理
3、利用多态机制(使用较多):
举例:因为InputMismatchException和ArithmeticException都是RuntimeException的子类,所以直接写RuntimeException就可以捕获这两种异常。只输入Exception就是捕获所有异常。
异常处理

*注意:finally块的使用:

finally块除了程序在内存中被移除的情况外(电脑停电等),都会执行。如果使用代码,比如在finally之前使用System.exit(0)等强制退出程序,则finally也不会执行。
finally块会在return执行前执行:
异常处理
注意:这里引用数据类型和非引用数据类型有差别。try中的return p;的p实际上是Person对象p的一个备份。也就是说此时创建了一个备份的p指向了原来的p。那么此时执行finally的话,由于p是引用数据类型,则p.age的值会被修改。但如果p不是引用数据类型,则值不会被修改。
举例:
非引用类型:
异常处理
虽然a的值被改为了20,但备份的返回值是10。

引用类型:异常处理
如果是引用数据类型,那么备份的就不是值了,而是地址。我们没有改变地址,而是改变了地址中的值。所以最后地址指向的值改变了。

throws关键字:

在程序中异常的基本处理已经掌握了,但是随异常一起的还有一个称为throws关键字,此关键字主要在方法的声明上使用,表示方法中不处理异常,而交给调用处处理。
格式:

返回值 方法名称() throws Exception{ 
}

*如何选择处理异常的方式?

异常是否抛出去,应该站在哪个角度思考?

1、如果是因为传参导致异常,不是该方法会导致的异常而是调用者会导致的异常,应该通过throws将异常抛出去(抛给调用者处理)。

2、否则使用try-catch处理。

throw关键字(用的比较少):

throw关键字表示在程序中人为的抛出一个异常,因为从异常处理机制来看,所有的异常一旦产生之后,实际上抛出的就是一个异常类的实例化对象,那么此对象也可以由throw直接抛出。

代码: throw new Exception(“抛着玩的。”) ;
相当于程序员自己抛出一个异常,来显示程序中不合理的地方
举例:
异常处理

自定义异常类了解:

编写一个类, 继承Exception,并重写一参构造方法 即可完成自定义受检异常类型。

编写一个类, 继承RuntimeExcepion,并重写一参构造方法 即可完成自定义运行时异常类型。
例如:

class MyException extends Exception{ // 继承Exception,表示一个自定义异常类 
	public MyException(String msg){ 
		super(msg) ; // 调用Exception中有一个参数的构造 
	} 
}; 

自定义异常可以做很多事情, 例如:

class MyException extends Exception{ 
	public MyException(String msg){ 
		super(msg) ; 
		//在这里给维护人员发短信或邮件, 告知程序出现了BUG。 
	} 
}; 

项目结构:

MVC结构(适用于客户端-服务器项目):
(V)1.视图展示(欢迎,菜单,子菜单…)
(M)2.数据存取
(C)3.调度逻辑(根据视图接收到的用户输入内容,调度数据存取)

一般包介绍:
项目结构
view包中可以放视图展示模块

bean包中可以放没有任何业务逻辑,所有属性全部私有,提供get、set方法、无参构造方法的对象。

dao包中可以放数据存取模块

泛型:

泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定 义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

泛型的使用:

1.泛型类(使用最多):
定义一个泛型类:

public class ClassName<T>{ 
	private	T data; 
	public T getData() { 
		return data; 
	}
	public void setData(T data) { 
		this.data = data; 
	} 
}

T就是在使用该类时传入的类型

2.泛型接口:

public interface IntercaceName<T>{ 
	T getData(); 
}

实现接口时,可以选择指定泛型类型,也可以选择不指定, 如下:
指定类型:

public class Interface1 implements IntercaceName<String> { 
	private String text; 
	@Override 
	public String getData() { 
		return text; 
	} 
}

指定了之后,内部实现的类型就是指定的类型

不指定类型:

public class Interface1<T> implements IntercaceName<T> { 
	private T data; 
	@Override 
	public T getData() { 
		return data; 
	} 
}

这个T可以在使用该类new对象时传入。

指定多个泛型用“,”隔开:
泛型

3.泛型方法:

泛型
方法的泛型声明在权限修饰符之后,在返回值类型之前。

使用举例:
泛型
泛型跟着泛型声明走,和返回值类型无关。

泛型限制类型:

1.在使用泛型时, 可以指定泛型的限定区域 ,

  • 例如: 必须是某某类的子类或 某某接口的实现类,格式:
<T extends 类或接口1 & 接口2>

泛型中的通配符:

类型通配符是使用?代替方法具体的类型实参。
1、 <? extends Parent> 指定了泛型类型的上届
2、<? super Child> 指定了泛型类型的下届
3、<?> 指定了没有限制的泛型类型

举例:
泛型
这里的Apple是Fruit接口的实现类。而此时=号两边的Plate对象是没有子父关系的,但是Plate里的内容:Fruit和Apple是有子父关系的,这里报错是因为多态不能用在容器(Plate)的内容(Fruit、Apple)上。如果坚持要这么使用可以采用如下方法:
泛型
这里?代表通配符,这个通配符extends Fruit,代表?是Fruit的子类,而Apple是Fruit的子类,则程序不会报错。这种称为上届限定。即new的泛型只能是前面的子类泛型。

下届限定与之相反,用法差不多。

泛型的作用:

1、 提高代码复用率

2、 泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)

注意:

1.在编译之后程序会采取去泛型化的措施。 也就是说Java中的泛型,只在编译阶段有效。

2.在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

java.util.Objects(详情见API):

Objects类中包含许多方法,调用格式:
Objects.方法名(参数列表);

java.lang.Math(详情见API):

java.util.Arrays(详情见API):

Arrays.copyOf()方法可以用来进行数组扩容。

java.math.BigDecimal(详情见API):

作用:解决小数运算的误差问题
常用构造方法:

public BigDecimal(String val){
……
}

常用方法:
下述的所有方法,不会影响参与运算的数据本身,运算的结果会被封装为一个新的BigDecimal对象,这个对象会通过return返回出去:
public BigDecimal add(BigDecimal augend) 加法运算
public BigDecimal subtract(BigDecimal augend) 减法运算
public BigDecimal mutiply(BigDecimal augend) 乘法运算
public BigDecimal divide(BigDecimal augend) 除法运算

举例:
BigDecimal

java.util.Date(详情见API):

举例:
Date

java.text.DateFormat(详情见API):

作用:格式化Date对象
DateFormat是一个抽象类,无法直接实例化。通过调用其子类SimpleDateFormat来使用。
举例:
DateFormat
SimpleDateFormat()中的参数是日期的格式。

SimpleDateFormat对象.format()方法:
作用:将Date对象转化为SimpleDateFormat对象。这样该Date对象就有了刚才定义的日期格式。
举例:
DateFoemat
输出:

返回值类型为String

SimpleDateFormat对象.Parse()方法:
作用:将一串字符串转成Date对象(format方法相反)。
其参数必须符合SimpleDateFormat对象创建时的日期格式。
返回值类型为Date。

java.util.Calendar(详情见API):

Calendar是一个抽象类不能直接实例化,它提供了一个getInstance()方法来获取其准备好的非抽象子类。所以我们要通过调用getInstance()方法来创建这个类的对象。

这个类的原理就是将时间信息存储在一个数组中:
Calendaer
所以我们是通过传入数组的下标来进行get和set操作的。
举例:

这样就可以取出当前的年份。
这里的Calendar.YEAR其实是一个int常量,是用来访问数组的下标值。

Calendar对象.get()方法返回一个int。用来获取时间信息。
举例:

这样可以获取当前的天数是一年当中的第几天。

Calendar对象.set()方法。用来设置时间信息
举例:

Calendar类自带了对日历的加减操作:

add()方法:

参数1是对日期的哪一部分操作,参数2是增加的值。
要减去值的话,传入负数即可。

注意:日期中月份比实际少一天,因为是从0~11。注意+1

getTime()方法:
作用:获取日历时间表示的Date对象
举例:

getActualMaxmum()方法:
作用:获取最大值
举例:

这就获取到了月份的最大值
返回值为int

java.lang.System(详情见API):

**String类(重点):

String类表示字符串。Java程序中的所有字符串文字(例如"abc")都是此类的实例。
字符串是不变的;它们的值在创建后无法更改。因为在内部字符串就是一个char数组,而数组的长度已经创建就无法改变。
字符串缓冲区支持可变字符串。因为String对象是不可变的,所以可以共享它们。
举例:
string str = “abc” ;
相当于:
char data[]={ ‘a, b’, ‘c’);
String str =new string(data) ;
可以共享是指两个字符串指向的值相同则它们指向的地址也相同。

举例:
String
但如果是通过new来创建的String对象那么一定是新开辟的空间,哪怕值一样,地址也不一样。

字符串保存在字符串常量池中,这个常量池存在于方法区中。

方法区定义(方法区存在于堆中的永久代中):
方法区是被所有线程共享(不安全,各个线程间的操作会引起冲突)。
所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。

简单说,所有定义的方法的信息都保存在该区域,此区属于共享区间。
这些区域存储的是:静态变量+常量+类信息(构造方法/接口定义)+运行时常量池。

简单来说方法区中保存的是静态的只会加载一次的东西。

但是,实例变量存在堆内存中,和方法区无关。

堆定义:
一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行。

堆在逻辑上分为三部分(Perm) :
新生代( Young Generation,常称为YoungGen):
刚创建的对象存储在新生代中
新生代GC(垃圾回收)频率极高,如果一个对象经过15次GC都没被回收则会被移入老年代。

老年代(old Generation,常称为oldGen、TenuringGen)
相较于新生代,老年代的GC频率较慢

永久代(Permanent Generation,常称为PermGen)
会执行Full GC但是条件苛刻,一般不会执行,一旦加入永久存在(程序不关闭)。一般存放类、方法、常量。特别是静态修饰的所有东西都会放进去。

JDK1.8之后将永久代换成了元空间,这两个东西在概念上一样但在对数据的处理方式上有些差别。

每一个字符串对象在创建后都会放到永久代里。

字符串拼接内存分析:

String
前三行创建了三个String对象,由于值不同他们的内存地址也不同。

String
当执行第四行时,先开辟一个内存空间并将text1+text2的结果放到这个空间中。然后在开辟一个内存空间并将text1+text2的结果和text3拼接到一起。(可以看到开辟了很多额外空间)。最后将这个空间地址(0x127)赋给text1对象。

String
可以发现0x123和0x126变成了垃圾空间,但是由于字符串保存在永久代中它们并不会被回收。这就造成了空间的极大浪费。

所以字符串拼接能避免尽量避免!

如果必须要进行字符串拼接,那么我们不应该使用String而应该使用StringBuffer, StringBuilder(它们都是可变字符序列)

StringBuffer(线程安全的实现):不支持多线程同时执行,得排队执行,因为它原码中的方法都是同步方法(被synchronized修饰)。详情见多线程部分
StringBuilder(线程不安全的实现):支持多线程同时执行
它们的使用方法完全一致。

拼接方法:
先new一个StringBuffer或StringBuilder对象,然后调用它的
append()方法进行拼接。要使用拼接后的字符串可以用它的
toString()方法

举例:
String

集合(重点)

类集设置的目的(重点)

对象数组有那些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最早的时候可以通过链表实现一个动态对象数组。
但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构, 所以引入了类集的概念,有时候就可以把类集称为 java 对数据结构的实现。
在整个类集中的,这个概念是从 JDK 1.2(Java 2)之后才正式引入的,最早也提供了很多的操作类,但是并没有完 整的提出类集的完整概念。
类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。
所有的类集操作的接口或类都在 java.util 包中。
集合

链表:

链表 [Linked List]:链表是由一组不必相连(不必相连:可以连续也可以不连续)的内
存结构(节点),按特定的顺序链接在一起的抽象数据类型。
补充:
抽象数据类型(Abstract Data Type [ADT]):表示数学中抽象出来的一些操作的集合。
内存结构:内存中的结构,如:struct、特殊内存块…等等之类;
数组和链表的区别和优缺点:
数组是一种连续存储线性结构,元素类型相同,大小相等

数组的优点:
存取速度快

数组的缺点:
事先必须知道数组的长度
插入删除元素很慢
空间通常是有限制的
需要大块连续的内存块
插入删除元素的效率很低

链表是离散存储线性结构
n 个节点离散分配,彼此通过指针相连,每个节点只有一个前驱节点,每个节点只有一 个后续节点,首节点没有前驱节点,尾节点没有后续节点。

链表优点:
空间没有限制
插入删除元素很快

链表缺点:
存取速度很慢

链表共分几类?
链表常用的有 3 类: 单链表、双向链表、循环链表。
链表

链表的核心操作集有 3 种:插入、删除、查找(遍历)

单链表
单链表 [Linked List]:由各个内存结构通过一个 Next 指针链接在一起组成,每一个内 存结构都存在后继内存结构(链尾除外),内存结构由数据域和 Next 指针域组成。

解析:
Data 数据 + Next 指针,组成一个单链表的内存结构 ;
第一个内存结构称为 链头,最后一个内存结构称为 链尾;
链尾的 Next 指针设置为 NULL [指向空];
单链表的遍历方向单一(只能从链头一直遍历到链尾)

单链表操作集:

双向链表
双向链表 [Double Linked List]:由各个内存结构通过指针 Next 和指针 Prev 链接在一 起组成,每一个内存结构都存在前驱内存结构和后继内存结构(链头没有前驱,链尾没有后 继),内存结构由数据域、Prev 指针域和 Next 指针域组成。

解析:
Data 数据 + Next 指针 + Prev 指针,组成一个双向链表的内存结构;
第一个内存结构称为 链头,最后一个内存结构称为 链尾;
链头的 Prev 指针设置为 NULL, 链尾的 Next 指针设置为 NULL;
Prev 指向的内存结构称为 前驱, Next 指向的内存结构称为 后继;
双向链表的遍历是双向的,即如果把从链头的 Next 一直到链尾的[NULL] 遍历方向定 义为正向,那么从链尾的 Prev 一直到链头 [NULL ]遍历方向就是反向;

双向链表操作集:

循环链表
单向循环链表 [Circular Linked List] : 由各个内存结构通过一个指针 Next 链接在一起 组成,每一个内存结构都存在后继内存结构,内存结构由数据域和 Next 指针域组成。

双向循环链表 [Double Circular Linked List] : 由各个内存结构通过指针 Next 和指针 Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构,内存结构由 数据域、Prev 指针域和 Next 指针域组成。


解析:
循环链表分为单向、双向两种;
单向的实现就是在单链表的基础上,把链尾的 Next 指针直接指向链头,形成一个闭环;

双向的实现就是在双向链表的基础上,把链尾的 Next 指针指向链头,再把链头的 Prev 指针指向链尾,形成一个闭环;

循环链表没有链头和链尾的说法,因为是闭环的,所以每一个内存结构都可以充当链头和链尾;

循环链表操作集:

Collection接口(重点)

Collection 接口是在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。
此接口定义在 java.util 包中。
此接口定义如下:

public interface Collection<E> extends Iterable<E> 

此接口使用了泛型技术,在 JDK 1.5 之后为了使类集操作的更加安全,所以引入了泛型。
此接口的常用方法如下所示。

本接口中一共定义了 15 个方法,那么此接口的全部子类或子接口就将全部继承以上接口中的方法。
但是,在开发中不会直接使用 Collection 接口。而使用其操作的子接口:List、Set。
之所以有这样的明文规定,也是在 JDK 1.2 之后才有的。一开始在 EJB 中的最早模型中全部都是使用 Collection 操作
的,所以很早之前开发代码都是以 Collection 为准,但是后来为了更加清楚的区分,集合中是否允许有重复元素所以 SUN
在其开源项目 —— PetShop(宠物商店)中就开始推广 List 和 Set 的使用。

List接口(重点)

在整个集合中 List 是 Collection 的子接口,里面的所有内容都是允许重复的。
List 子接口的定义:

public interface List<E> extends Collection<E> 

此接口上依然使用了泛型技术。此接口对于 Collection 接口来讲有如下的扩充方法:

在 List 接口中有以上 10 个方法是对已有的 Collection 接口进行的扩充。
所以,证明,List 接口拥有比 Collection 接口更多的操作方法。
了解了 List 接口之后,那么该如何使用该接口呢?需要找到此接口的实现类,常用的实现类有如下几个:
· ArrayList(95%)、Vector(4%)、LinkedList(1%)
ArrayList和Vector是基于动态数组实现的(即进行数组的动态扩容,新数组覆盖旧数组)。而LinkedList是基于链表实现的。

ArrayList(线程不安全 重点)

ArrayList 是 List 接口的子类,此类的定义如下:

public class ArrayList<E> extends AbstractList<E> 
implements List<E>, RandomAccess, Cloneable, Serializable 

此类继承了 AbstractList 类。AbstractList 是 List 接口的子类。AbstractList 是个抽象类,适配器设计模式。
范例:增加及取得元素

以上的操作向集合中增加了三个元素,其中在指定位置增加的操作是 List 接口单独定义的。随后进行输出的时候,
实际上调用的是 toString()方法完成输出的。
可以发现,此时的对象数组并没有长度的限制,长度可以任意长,只要是内存够大就行。
范例:进一步操作
· 使用 remove()方法删除若干个元素,并且使用循环的方式输出。
· 根据指定位置取的内容的方法,只有 List 接口才有定义,其他的任何接口都没有任何的定义。

特点:增删慢、查找快

它有三种构造方法:

当初始需求的容量较大时请使用第二种构造方法来构造。如果使用第一种构造方法则会浪费大量时间和空间用来进行数组扩容!
第三种构造方法可以将Collection的对象(如List和Set底下所有的集合)转换成ArrayList。

Vector(线程安全 重点)

与 ArrayList 一样,Vector 本身也属于 List 接口的子类,此类的定义如下:

public class Vector<E> extends AbstractList<E> 
implements List<E>, RandomAccess, Cloneable, Serializable 

此类与 ArrayList 类一样,都是 AbstractList 的子类。所以,此时的操作只要是 List 接口的子类就都按照 List 进行操作。

以上的操作结果与使用 ArrayList 本身并没有任何的区别。因为操作的时候是以接口为操作的标准。
但是 Vector 属于 Java 元老级的操作类,是最早的提供了动态对象数组的操作类,在 JDK 1.0 的时候就已经推出了此
类的使用,只是后来在 JDK 1.2 之后引入了 Java 类集合框架。但是为了照顾很多已经习惯于使用 Vector 的用户,所以在
JDK 1.2 之后将 Vector 类进行了升级了,让其多实现了一个 List 接口,这样才将这个类继续保留了下来。

Vector 类和 ArrayList 类的区别(重点)

这两个类虽然都是 List 接口的子类,但是使用起来有如下的区别:

LinkedList(重点)

此类的使用几率是非常低的,但是此类的定义如下:

public class LinkedList<E> extends AbstractSequentialList<E> 
implements List<E>, Deque<E>, Cloneable, Serializable

此类继承了 AbstractList,所以是 List 的子类。但是此类也是 Queue 接口的子类,Queue 接口定义了如下的方法:

范例:验证 LinkedList 子类

特点:增删快、查找慢

集合输出(重点)

对于集合的输出本身也是有多种形式的,可以使用如下的几种方式:
· Iterator 迭代输出(90%)、ListIterator(5%)、Enumeration(1%)、foreach(4%)
但是在讲解输出的时候一定要记住以下的原则:“只要是碰到了集合,则输出的时候想都不想就使用 Iterator 进行输出。”

Iterator(绝对重点)

Iterator 属于迭代输出,基本的操作原理:是不断的判断是否有下一个元素,有的话,则直接输出。
此接口定义如下:

public interface Iterator<E> 

要想使用此接口,则必须使用 Collection 接口,在 Collection 接口中规定了一个 iterator()方法,可以用于为 Iterator 接口进行实例化操作。
此接口规定了以下的三个方法:

通过 Collection 接口为其进行实例化之后,一定要记住,Iterator 中的操作指针是在第一条元素之上,当调用 next()方法的时候,获取当前指针指向的值并向下移动,使用 hasNext()可以检查序列中是否还有元素。调用remove时当前迭代器必须已经指向了某个位置,然后才能remove

范例:观察 Iterator 输出

以上的操作是 Iterator 接口使用最多的形式,也是一个标准的输出形式。
但是在使用 Iterator 输出的时候有一点必须注意,在进行迭代输出的时候如果要想删除当前元素,则只能使用 Iterator接口中的 remove()方法,而不能使用集合中的 remove()方法。否则将出现未知的错误。

举例:


此时出现了错误,因为原本的要输出的集合的内容被破坏掉了。

但是,从实际的开发角度看,元素的删除操作出现的几率是很小的,基本上可以忽略,即:集合中很少有删除元素的操作。
Iterator 接口本身可以完成输出的功能,但是此接口只能进行由前向后的单向输出。如果要想进行双向输出,
则必须使用其子接口 —— ListIterator。

ListIterator(理解)

ListIterator 是可以进行双向输出的迭代接口,此接口定义如下:

public interface ListIterator<E> 
extends Iterator<E> 

此接口是 Iterator 的子接口,此接口中定义了以下的操作方法:

但是如果要想使用 ListIterator 接口,则必须依靠 List 接口进行实例化。
List 接口中定义了以下的方法:ListIterator<E> listIterator()

范例:使用 ListIterator 输出

但是,此处有一点需要注意的是,如果要想进行由后向前的输出,则首先必须先进行由前向后的输出。
但是,此接口一般使用较少。

foreach(重点)

foreach 可以用来输出数组的内容,那么也可以输出集合(只能是Collection下的集合)中的内容。

在使用 foreach 输出的时候一定要注意的是,里面的操作泛型要指定具体的类型,这样在输出的时候才会更加有针对性。

Set 接口(重点)

Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。
Set 接口并没有对 Collection 接口进行扩充,基本上还是与 Collection 接口保持一致。因为此接口没有 List 接口中定义的 get(int index)方法,所以无法使用循环进行输出。
那么在此接口中有两个常用的子类:HashSet、TreeSet
set没有提供get方法来获取数据,但是我们可以用迭代器或者用toArray将其转换为一个数组来取数据。

HashSet(重点)

既然 Set 接口并没有扩充任何的 Collection 接口中的内容,所以使用的方法全部都是 Collection 接口定义而来的。
HashSet 属于散列的存放类集,里面的内容是无序存放的。

范例:观察 HashSet

使用 HashSet 实例化的 Set 接口实例,本身属于无序的存放。
那么,现在思考一下?能不能通过循环的方式将 Set 接口中的内容输出呢?
是可以实现的,因为在 Collection 接口中定义了将集合变为对象数组进行输出。

但是,以上的操作不好,因为在操作的时候已经指定了操作的泛型类型,那么现在最好的做法是由泛型所指定的类型变为指定的数组。
所以只能使用以下的方法:<T> T[] toArray(T[] a)


下面再进一步验证 Set 接口中是不能有重复的内容的。

以上字符串“A”设置了很多次,因为 Set 接口中是不能有任何的重复元素的,所以其最终结果只能有一个“A”。

TreeSet(重点)

采用有序二叉树存储
与 HashSet 不同的是,TreeSet 本身属于排序的子类,此类的定义如下:

public class TreeSet<E> extends AbstractSet<E> 
implements NavigableSet<E>, Cloneable, Serializable 

下面通过代码来观察其是如何进行排序的。


虽然在增加元素的时候属于无序的操作,但是增加之后却可以为用户进行排序功能的实现

排序的说明(Comparable接口 重点)

既然 Set 接口的 TreeSet 类本身是允许排序,那么现在自定义一个类是否可以进行对象的排序呢?

范例:定义 Person 类

下面定义一个 TreeSet 集合,向里面增加若干个 Person 对象。


执行以上的操作代码之后,发现出现了如下的错误提示:
此时的提示是:Person 类不能向 Comparable 接口转型的问题?

所以,证明,如果现在要是想进行排序的话,则必须在 Person 类中实现 Comparable 接口。


那么此时再次使用之前的代码运行程序。程序的执行结果如下:
[姓名:张三,年龄:10, 姓名:王五,年龄:11, 姓名:赵六,年龄:12, 姓名:孙七,年龄:13]
从以上的结果中可以发现,李四没有了。因为李四的年龄和张三的年龄是一样的,所以会被认为是同一个对象。则此时必须修改 Person 类,如果假设年龄相等的话,按字符串进行排序

此时,可以发现李四出现了,如果加入了同一个人的信息的话,则会认为是重复元素,所以无法继续加入。

Comparable接口主要的作用:为你定义的类指定比较的方式。


这里的泛型是用来指定比较对象的类型的(即当前类型的对象与什么类型对象比较,这个什么类型就是在泛型中指定的类型),如下图:

一个与Comparable相似的接口:Comparator。建议了解:

关于重复元素的说明(重点)

之前使用 Comparable 完成的对于重复元素的判断,那么 Set 接口定义的时候本身就是不允许重复元素的,
那么证明如果现在真的是有重复元素的话,使用 HashSet 也同样可以进行区分。

此时发现,并没有去掉所谓的重复元素,也就是说之前的操作并不是真正的重复元素的判断,而是通过 Comparable接口间接完成的。
如果要想判断两个对象是否相等,则必须使用 Object 类中的 equals()方法。
从最正规的来讲,如果要想判断两个对象是否相等,则有两种方法可以完成:
· 第一种判断两个对象的编码是否一致,这个方法需要通过 hashCode()完成,即:每个对象有唯一的编码
· 还需要进一步验证对象中的每个属性是否相等,需要通过 equals()完成。

所以此时需要覆写 Object 类中的 hashCode()方法,此方法表示一个唯一的编码,一般是通过公式计算出来的。

发现,此时已经不存在重复元素了,所以如果要想去掉重复元素需要依靠 hashCode()和 equals()方法共同完成。

小结:

关于 TreeSet 的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现Comparable 接口,则不能实现 TreeSet 的排序,会报类型转换(转向 Comparable 接口)错误。换句话说要添加到 TreeSet 集合中的对象的类型必须实现了 Comparable 接口。
不过 TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值,而 HashSet 虽然是Set 接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet集合中也是不能去掉重复值的。

Map 接口(重点)

以上的 Collection 中,每次操作的都是一个对象,如果现在假设要操作一对对象,则就必须使用 Map 了,
类似于以下一种情况:
· 张三 123456
· 李四 234567
那么保存以上信息的时候使用 Collection 就不那么方便,所以要使用 Map 接口。里面的所有内容都按照 key,value的形式保存,
也称为二元偶对象。
此接口定义如下:

public interface Map<K,V> 

此接口与 Collection 接口没有任何的关系,是第二大的集合操作接口。此接口常用方法如下:

Map 本身是一个接口,所以一般会使用以下的几个子类:HashMap、TreeMap、Hashtable
注意:键不可重复

HashMap(重点)

HashMap 是 Map 的子类,此类的定义如下:

public class HashMap<K,V> extends AbstractMap<K,V> 
implements Map<K,V>, Cloneable, Serializable 

此类继承了 AbstractMap 类,同时可以被克隆,可以被序列化下来。

范例:向集合中增加内容

以上的操作是 Map 接口在开发中最基本的操作过程,根据指定的 key 找到内容,如果没有找到,则返回 null,找到了则返回具体的内容。

范例:得到全部的 key 或 value
在这里插入图片描述
既然可以取得全部的 key,那么下面就可以对以上的操作进行扩充,循环输出 Map 中的全部内容。

}
HashMap 本身是属于无序存放的。

哈希表概述:

所谓哈希表在Java的内部实现是一种对象数组+链表的结构,当存入某个数据时,先通过一种算法计算出一个值(算法:将存入数据对象的hashCode值与对象数组的长度进行取余(此处为16,初始的哈希桶数量就是16),将余数作为下标)
然后将这个值作为该数据的下标存入,如下图:


哈希桶就是对象数组的每个对象节点。


初始桶数量:对象数组的初始大小
散列因子:当一定数量(这里为75%)的哈希桶都存有数据后,对哈希桶进行扩容,扩容比例一般为2倍。

Map集合使用案例:

1、取value:

HashMap<String, String> data = new HashMap<>();
data.put( "key1""锄禾日当午");
data.put( "key2""汗滴禾下土");
String value = data.get( "key1");
System.out.println(value);
value = data.get("key2");
System.out.println(value);

输出结果:

2、分别取key和value:

Set<String> set = data.keySet();
for (String key: set){
	System.out.println(key+ "-> "+data.get(key));
}

输出结果:

3、转换为Collection:

Collection<String> values = data.values();
for (String value: values){
	System.out.println(value);
}

输出结果:

Map集合各个子类之间的区别分析:

HashMap:线程不安全,效率高(多个线程同时执行)
Hashtable:线程安全,效率低(多个线程排队执行)
ConcurrentHashMap:采用分段锁机制,保证线程安全,效率又比较高。

Hashtable(重点)

Hashtable 是一个最早的 keyvalue 的操作类,本身是在 JDK 1.0 的时候推出的。其基本操作与 HashMap 是类似的。

操作的时候,可以发现与 HashMap 基本上没有什么区别,而且本身都是以 Map 为操作标准的,所以操作的结果形式都一样。但是 Hashtable 中是不能向集合中插入 null 值的。

HashMap 与 Hashtable 的区别(重点)

TreeMap(重点)

TreeMap 子类是允许 key 进行排序的操作子类,其本身在操作的时候将按照 key 进行排序,另外,key 中的内容可以为任意的对象,但是要求对象所在的类必须实现 Comparable 接口。


此时的结果已经排序成功了,但是从一般的开发角度来看,在使用 Map 接口的时候并不关心其是否排序,所以此类只需要知道其特点即可。

在Map集合中存储自定义对象:

1、自定义对象一定要支持equals和hashCode。

建议用编译器直接生成。

2、当将自定义类的对象作为key时,不可随意改变自定义对象的值。因为当自定义对象改变后,改变后的对象的hashCode值也会改变,这样在现在索引时使用的hashCode值与原来的不一样,所以就找不到原来的hashCode值所对应的value了。

3、当索引一个Map对象时不仅需要key计算出的hashCode一样而且equals也要满足。
举例:

上图中尽管book3作为key时计算出的hashCode值与book1作为key时计算出的hashCode值相等,但是book1的name变为了“铜苹果”,所以book3与book1的equals不满足,此时通过book3依然无法索引到book1的value。

JDK9有关集合的新特性(注意是固定长度的集合,一经创建长度不可变)

主要为:List、Set、Map中的of方法(见API文档)。
举例:

Java.io.File

表示文件的对象,文件和目录路径名的抽象表示。
文件操作一定要慎重,防止误操作将自己电脑上的重要文件删除。
构造方法:


注意第一个和第三个两参构造方法,下面介绍它们的区别:

主要是传入的参数不同,一个是传入一个父目录作为一参,一个是转入路径作为一参。

常用方法:
1、createNewFile()
作用:当且仅当具有此名称的文件尚不存在时,以原子方式创建由此抽象路径名命名的新空文件。简单来说就是按照文件对象创建时指定的路径创建一个新文件。
返回值:
true如果指定的文件不存在且已成功创建
false如果指定的文件已存在
2、mkdir()
作用:创建此抽象路径名指定的目录。 这个方法创建的是个目录(文件夹)
返回值:
true当且仅当目录已创建时;
否则为false
近似:mkdirs()
作用:创建一层目录
举例:

这样就能创建三个嵌套的ha文件夹了,如果调用mkdir则会报错。
3、delete()
作用:删除此抽象路径名表示的文件或目录。
返回值:
true当且仅当文件或目录被成功删除时;
否则为false
4、getAbsolutePath()
作用:返回此抽象路径名的绝对路径名字符串。
返回值:绝对路径名字符串,表示与此抽象路径名相同的文件或目录
5、getAbsoluteFile()
作用:返回此抽象路径名的文件对象。
返回值:与此抽象路径名相同的文件或目录,File类型
6、getName()
作用:返回此抽象路径名表示的文件或目录的名称。(String类型)
7、getParent()
作用:返回此抽象路径名父项的路径名字符串,如果此路径名未指定父目录,则返回 null 。
8、getParentFile()
作用:返回此抽象路径名父项的文件对象,如果此路径名未指定父目录,则返回 null 。(File类型)
9、getPath()
作用:将此抽象路径名转换为路径名字符串。获取路径(String类型)
10、length()
作用:返回此抽象路径名表示的文件的大小,单位为字节(long类型)。
11、exists()
作用:测试此抽象路径名表示的文件或目录是否存在。
返回值:true当且仅当此抽象路径名表示的文件或目录存在时; 否则为false
所以在程序中创建文件时通常的操作是先判断文件是否存在,然后再创建。
12、isDirectory()
作用:测试此抽象路径名表示的文件是否为目录。
13、isFile()
作用:测试此抽象路径名表示的文件是否为普通文件。
14、isHidden()
作用:测试此抽象路径名指定的文件是否为隐藏文件。
15、listFiles()
作用:获取一个文件夹中的所有子文件(返回值类型为File数组)
16、list()
作用:获取一个文件夹中的所有子文件的路径(返回值类型为String数组)
17、renameTo​(File dest)
作用:重命名此抽象路径名表示的文件。 本质上是将旧的名字的文件复制到新的名字的文件。
举例:

注意:如果新旧文件的路径不同是会发生移动的,也就是说旧路径下的文件会移除。

File类中的字段:


由于JAVA跨平台的特性,不同平台间的路径分隔符和名称分隔符有所不同,所以File类提供了以上这些字段用来适配。
可以用它们来替换路径中的相应分隔符。
举例:

在Windows系统中的相应输出。

文件遍历案例:

要求:查找E盘下的所有.avi文件。

分析:首先传入E盘下的第一层的文件数组,然后对该文件数组进行遍历并判断:如果是一个文件则判断文件是不是以.avi结尾的,是的话就输出路径;如果是文件夹则获取文件夹下的子文件并且递归调用该方法。

文件过滤器:

相对路径和绝对路径

绝对路径:从盘符开始,是一个完整的路径.例如:c://a.txt

相对路径:在Java代码中是相对于项目目录路径,这是一个不完整的便捷路径.在Java开发中很常用.

IO流:

可以将这种数据传输操作,看做一种数据的流动,按照流动的方向分为输入Input和输出Output
Java中的I0操作主要指的是java.io包下的一些常用类的使用.
通过这些常用类对数据进行读取(输入Input)和写出(输出0utput)

IO流的分类:
按照流的方向来分,可以分为:输入流和输出流.
按照流动的数据类型来分,可以分为:字节流和字符流

顶级父类:
字节流:

-输入流:InputStream
-输出流:outputStream

字符流:

-输入流:Reader
-输出流:Writer

java.io.OutputStream

常用方法:
1、close()
作用:关闭此输出流并释放与此流关联的所有系统资源。
非常重要的方法,当流使用完毕一定要关闭!
2、flush()
作用:刷新此输出流并强制写出任何缓冲的输出字节。
3、写出方法:

注意三参方法的参数,是从第off个字节开始写len个字节,包括off处的字节。

java.io.FileOutputStream( java.io.OutputStream中的常用子类之一)

构造方法:


注意:如果append为true则表示追加,否则为清空重写。

举例:

java.io.InputStream

常用方法:
1、close()
作用:关闭此文件输入流并释放与该流关联的所有系统资源。
2、read()
作用:从输入流中读取下一个数据字节。 值字节返回int ,范围为0至255 。 如果由于到达流末尾而没有可用字节,则返回值-1 。 此方法将阻塞,直到输入数据可用,检测到流的末尾或抛出异常。
3、read​(byte[] b)
作用:从输入流中读取一些字节数并将它们存储到缓冲区数组 b 。
从输入流中读取一些字节数并将它们存储到缓冲区数组b 。 实际读取的字节数以整数形式返回。 此方法将阻塞,直到输入数据可用,检测到文件结尾或引发异常。

如果b的长度为零,则不读取任何字节,并返回0 ; 否则,尝试读取至少一个字节。 如果由于流位于文件末尾而没有可用字节,则返回值-1 ; 否则,至少读取一个字节并存储到b 。

读取的第一个字节存储在元素b[0] ,下一个字节存入b[1] ,依此类推。 读取的字节数最多等于b的长度。 设k为实际读取的字节数; 这些字节将存储在元素b[0]到b[ k -1] ,使元素b[ k ]到b[b.length-1]不受影响。

java.io.FileInputStream( java.io.InputStream中的常用子类之一)

构造方法:

举例(循环读取一个字节)不常用

举例(一次读取一组字节)常用

注意这边有个极为容易忽视的bug,如下图:

假设我们每次读取10个字节,当读到最后,剩余数据不满10个字节时,比如剩余6个,则会用这6个字节覆盖之前读到的10个字节中的前6个字节,但是最后4个字节仍然是旧的10个字节中的最后4个。

解决方法:

首先获取每次读取到的字节的长度,然后指定长度来操作。

字符输出java.io.FileWriter:

构造方法:

可以自己设定输出的字符集,但不常用
常用方法(都是它的父类的方法java.io.Writer):
都比较方便简单:

举例(追加写入字符串):

举例(使用append连续写入字符串):

注意:此处的append并非追加写入(不清空文件内容),而是可以连续调用多个append方法来进行输出操作,如果对象创建时未指定为追加模式则依旧会清空文件中的内容后再写入。

字符读取java.io.FileReader:

构造方法:

常用方法(都是它的父类的方法java.io.Reader):

read方法会在读取到文件末尾时返回-1:

举例(一次读取一组字符):

这里我们用于存储字符的数组长度为100,所以无论你读了多少内容他都占100个字符的空间,非常浪费。所以我们应当在处理时指定要处理的字符长度。read()方法会返回读取的字符的长度。

flush刷新管道

调用flush()方法可以将缓冲区里的字符立即读入或写出。
注意:字符流执行后一定要close或者flush否则内容会存在缓存中

字节转换字符流

字节流’装饰’为字符流,使用了装饰者设计模式.

举例(输入流的转换):

举例(输出流的转换):

Print与BufferedReader

Print打印流,可以直接进行字节流和字符流间的转换(推荐使用):


BufferedReader缓存读取流,将字符输入流转换为带有缓存可以一次读取一行的缓存字符读取流
注意:只有字符输入流才可以使用,字节流不行。如果是字节流,需要先将字节流转成字符流再用BufferedReader。

举例:

这里就使用了BufferedReader特有的readLine()方法,能一次读取一行字符串,之前的方法都是只能一次读一个字符或者一个字符数组。当读到文件末尾时,其会返回null。

利用流收集异常日志(了解)

以前的异常信息通常输出到控制台里,意义不大。现在我们可以利用流将异常信息输出到文件里。

核心就是在e.printStackTrace()中传入一个打印流。
这是手动写法,实际上有很多库都已经提供了错误日志的收集方法,不需要自己去写。

properties

这个类本质上属于集合,其父类为Hashtable。但它有与io相关的扩展方法。
可以使用properties格式的文件来存储数据,其采用键值对的方式进行存储。
常用方法:

作用:读取一个properties格式文件中的数据。

作用:将数据以properties格式存储到文件中。

举例(输出数据):

首先使用put方法向Properties对象传入数据。然后存到book.properties文件中(注意:这里文件必须以.properties结尾)。最后调用store方法完成存储,这个方法的第二个参数是文件中的首行注释,可省略。

文件内容:

第一行是因为JAVA认为注释中不能有中文,所以转换为了相应的Unicode编码(虽然新版本支持中文了但是仍然不建议写中文)。

举例(获取数据):

序列化技术:

Java序列化是指把Java对象转换为字节序列的过程,Java反序列化是指把字节序列恢复为Java对象的过程。通过徐序列化和反序列化实现网络传输、本地存储的目的。

作用:方便数据的存取,便于跨平台操作。

要想让一个类的对象能被序列化,那么这个类需要继承SeriaLizabLe接口(这个接口是一个单纯的标记接口,不需要任何的实现)。
注意:这个类包含的所有其它的类也要继承SeriaLizabLe接口。
比如下图中的Person类


举例(将一个类的对象序列化并输出到文件):

举例(将文件中的内容反序列化并获取):

反序列化对象支持强转,便于操作。

try-with-resources

主要作用是便于对流对象进行try-catch:

我们可以将定义的流对象写在try后面的括号中,多个对象间用分号隔开。
注意:这些对象都必须继承了Clsoeable和AutoCloseable类。

举例:

Externalizable接口实现序列化和反序列化(部分属性和全部属性都可以,使用较少)

Externalizable继承自Serializable,使用Externalizable接口需要实现readExternal方法和writeExternal方法来实现序列化和反序列化。

首先类要实现相应接口:

然后是要实现两个方法,在方法中指定要进行序列化和反序列化的属性:

默认方法writeObject和readObject用于实现序列化和反序列化(部分属性和全部属性都可以,使用较多)

首先实现相应接口:

然后使用这两个方法指定要进行序列化和反序列化的属性:

注意:序列化和反序列化的属性顺序要对应。

Serializable VS Externalizable(两者区别)

面试题tip:实现部分字段序列化的方式?

1、使用transient修饰符
2、使用static修饰符
3、默认方法writeObject和readObject。
4、使用Externalizable接口。

多线程

进程:
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间
线程:
是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程

同步与异步

同步:排队执行 , 效率低但是安全.
异步:同时执行 , 效率高但是数据不安全.

并发与并行

并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。

线程调度

分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

Thread(Java实现多线程一种方法)

首先要实现多线程的类要继承Thread类,然后重写run()方法
最后在需要执行的地方开启(调用strat方法)。

举例:

这里的MyThread类继承了Thread类,在重写的run方法中定义了该线程要执行的代码。
注意:这里的run方法只是定义了该线程要执行的内容,真正启动这个线程需要调用start()方法。但有个概念不要混淆,start()方法执行时线程只是进入了就绪状态,此时并没有获得CPU时间片,当run()方法执行时才获得了CPU时间片并开始执行。

举例(开启线程):

上述例子的线程执行时序图:

Runnable(Java实现多线程一种方法,用的较多)

首先实现Runnable接口,然后重写run方法,然后借助Thread对象实现。

举例(定义线程任务):

举例(开启线程):

实现Runnable与继承Thread相比有如下优势:

1.通过创建任务,然后给线程分配的方式来实现的多线程。更适合多个线程同时执行相同任务的情况。
2.可以避免单继承所带来的局限性。
3.任务与线程本身是分离的,提高了程序的健壮性。
4.后续学习的线程池技术,接受Runnable类型的任务,不接收Thread类型的线程。

但是Thread也有它的用武之地,我们可以通过匿名内部类的方式创建一个一次性的线程:

ThreadAPI补充:

构造方法:

常用方法:
1、start()
作用:导致此线程开始执行; Java虚拟机调用此线程的run方法。
2、sleep​(long millis)
作用:导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。
3、sleep​(long millis, int nanos)
作用:导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。
4、setDaemon​(boolean on)
作用:将此线程标记为 daemon(守护)线程或用户线程。
所谓守护线程就是守护用户线程的线程,它的出生与死亡取决于用户线程的出生与死亡。

设置和获取线程的名称

1、currentThread()
作用:返回对当前正在执行的线程对象的引用。
返回值:static Thread类型
2、setName​(String name)
作用:将此线程的名称更改为等于参数 name 。
3、getName()
作用:返回此线程的名称。 (String)

我们可以利用这三个方法来设置和获取当前线程的名称:

线程的休眠sleep

通过调用Thread的sleep方法可以让调用的线程按设定的时间进行休眠:

线程阻塞

即线程中的耗时操作,例如:读取文件、等待用户输入等

线程的中断

一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定。
我们可以给线程打标记,来标记线程在什么时候应当中断。当程序遇到该标记时会抛出异常,我们便可以进行try-catch然后在catch块中进行处理。

添加中断标记的方法:
interrupt()

注意:这个标记只是告诉程序在这里应当抛出异常,进入catch块了。真正让该线程死亡需要进行return来停止方法,在return前注意释放资源。如果不return或不用其它方式停止方法则线程不会停止。

举例:
先创建一个子类并重新run方法,作为子线程:

然后在main类主线程中设置标记来杀死子线程,这里设置的是当主线程的循环执行完毕后杀死子线程:

当执行t1.interrupt()时,程序检测到标记,抛出InterruptedException异常,被MyRunnable类中的run方法中的catch捕获,执行return,停止了子线程。

守护线程

线程:分为守护线程和用户线程

用户线程:当一个进程不包含任何的存活的用户线程时,进程结束.

守护线程:守护用户线程的线程,当最后一个用户线程结束时,所有守护线程自动死亡。

举例:

首先主线程中执行着一个循环,子线程t1中也执行着一个循环。此时将t1设置为守护线程(t1.setDaemon(true);),然后无论t1是否执行完成,只要主线程执行完了t1也就跟着结束了因为这里的用户线程只有主线程一个,如果有其它用户线程那么当它们都执行完时t1守护线程才会结束)。

线程安全问题

举例(卖票程序):

由于JAVA中线程的时间片分配是抢占式的,这就容易导致线程不安全问题。
如上图,假设有两个线程A、B进行卖票,A线程先进入循环,设此时count为1。当执行到try时发生了sleep,A线程抛出时间片,此时时间片被B线程抢到,此时的count仍然为1(A线程抛出了时间片没有执行仍然在sleep,所以count没有–),这样B线程也进入了循环,然后B线程也进入了sleep抛出了时间片,此时A线程sleep结束抢到时间片,进行了count–操作,此时count为0。当B线程sleep结束时,又执行了count–操作,count变为了-1。这不是我们想要的,因为票数不能为负数,此时就产生了线程不安全问题。

线程不安全问题的处理

解决方案1:同步代码块

格式:

synchronized(锁对象){
	……
	具体需要排队执行内容
	……
}

JAVA中任何对象都可以作为锁对象(可以理解为任何对象都可打上线程终止标记)
注意:所有要同步的线程必须使用同一把锁才能实现排队,避免线程不安全问题。

举例(三个线程使用同一把锁):

此时的锁o在run方法外部,则每个线程执行run方法时使用的都是同一把锁。这样就避免了线程不安全问题。

举例(三个线程使用各自的锁):

此时的锁o处在run方法内部,则每个线程执行run方法时使用的是各自的锁,无法将别的线程锁住,也就无法解决线程不安全问题。

解决方案2:同步方法

原理与同步代码块相同但写法不同
同步方法是以方法为单位加锁。

举例:
接着上面的例子,我们将需要排队执行的if语句剥离出来,形成一个单独的方法,然后给方法添加synchronized修饰即可:

那么这里的锁是什么呢?
1、如果是静态的方法那锁就是,该方法所属的类名.class
2、如果不是静态方法那锁就是this

注意:采用这种方法要想实现排队,那么需要线程中的任务对象是同一个。例如:

多个线程,但共用一个任务对象。可以解决

多个线程,各自有各自的任务对象。不能解决

混合使用同步代码块和同步方法的注意点


如上图:如果你在同步代码块中使用的锁和同步方法使用的锁(同步方法的锁是自带的,不是人为设定的)是同一个(这里都为this),那么谁写在前面谁就会先执行,当先执行的没有执行完毕时,后执行的无法执行。
同理一个类中的多个同步方法也是排队执行(因为都使用同一把锁)。

解决方案3:显式锁Lock

同步代码块和同步方法都属于隐式锁(自动锁和解锁,我们只需要创建)
而显式锁需要自己手动进行锁和解锁

步骤
首先创建锁对象

然后在要开启锁的地方调用lock方法,在要解锁的地方调用unlock方法。

显式锁和隐式锁的区别

1.synchronized对多个锁只能按照获得锁的顺序的反序释放(先获得后释放),显式锁可以按照需要释放锁,无此约束.

2.显式锁提供可中断的获取锁的方法,lockInterruptibly

3.显式锁提供尝试获得锁方法

4.显式锁提供精度更细的等待与唤醒(利用Condition)

5.显式锁不像内置锁那样会自动释放,使用显式锁一定要在finally块中手动释放,释放一定要放到finally块中,否则可能会因为异常导致锁永远无法释放!

6.底层不同:
synchronized 是java中的关键字,是JVM层面的锁。
ReentrantLock 是JDK5以后出现的具体的类。使用lock是调用对应的API。

7.使用方式不同,上面已经列举

8.是否可以中断:
synchronized 是不可中断的。除非抛出异常或者正常运行完成
lock可以中断的。
中断方式:
调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断

9.是否是公平锁:
synchronized 是非公平锁

lock创建时可以传入一个Boolean值来设置是公平锁还是非公平锁:
true:公平锁
false:非公平锁

10.性能
在java1.6之前synchronized性能较低,因为其需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还要多,相比之下lock的性能更高一些。

然而到了java1.6之后对锁进行了很多的优化,进而出现轻量级锁,偏向锁,锁消除,适应性自旋锁导致synchronized的性能也不差,并且由于其在语义上很清晰所以也被官方更多的支持。

11.使用锁的方式
lock 获取锁与释放锁的过程,都需要程序员手动的控制 Lock用的是乐观锁方式。

乐观锁:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

synchronized托管给JVM执行,原始采用的是CPU悲观锁机制
悲观锁:线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。

公平锁与非公平锁:

公平锁:线程间排队执行,排队解锁
非公平锁:线程间抢占执行,抢占解锁。谁抢到谁就执行。

同步代码块、同步方法、显式锁Lock默认都是非公平锁

如何设定?
在显式锁的构造方法中(ReentrantLock())传入true或者false,true为公平锁,false为非公平锁。

线程死锁

线程间的互相等待构成死锁
避免方法:
在任何有可能导致锁产生的方法里不要再去调用另一个有可能产生锁的方法。

多线程通信问题

确保生产者线程与消费者线程交替执行。

举例(厨师与服务员):

package com.java.demo;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo4  {

    /**
     * 多线程通信问题, 生产者与消费者问题
     * @param args
     */
    public static void main(String[] args) {
        Food f = new Food();
        new Cook(f).start();
        new Waiter(f).start();
    }

    //厨师
    static class Cook extends Thread{
        private Food f;
        public Cook(Food f) {
            this.f = f;
        }

        @Override
        public void run() {
            for(int i=0;i<100;i++){
                if(i%2==0){
                    f.setNameAndSaste("老干妈小米粥","香辣味");
                }else{
                    f.setNameAndSaste("煎饼果子","甜辣味");
                }
            }
        }
    }
    //服务生
    static class Waiter extends Thread{
        private Food f;
        public Waiter(Food f) {
            this.f = f;
        }
        @Override
        public void run() {
            for(int i=0;i<100;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                f.get();
            }
        }
    }
    //食物
    static class Food{
        private String name;
        private String taste;

        //true 表示可以生产
        private boolean flag = true;

        public synchronized void setNameAndSaste(String name,String taste){
            if(flag) {
                this.name = name;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.taste = taste;
                flag = false;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public synchronized void get(){
            if(!flag) {
                System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
                flag = true;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

如何实现线程交替执行?主要靠wait()和notifyAll()两类方法:


上述代码就利用这两类方法完成了线程间的交替执行。

线程的六种状态

带返回值的线程Callable

Callable使用步骤:

  1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> { 
	@Override 
	public <T> call() throws Exception { 
		return T; 
	} 
} 
  1. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
    FutureTask future = new FutureTask<>(callable);
  2. 通过Thread,启动线程
    new Thread(future).start();

然后通过FutureTask的对象来调用相应方法来获取返回值:

get()无参方法会使得主线程等待Callable线程执行完毕才会执行。

线程池 Executors

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

1. 缓存线程池

缓存线程池.(长度无限制)
执行流程:

  1. 判断线程池是否存在空闲线程
  2. 存在则使用
  3. 不存在,则创建线程 并放入线程池, 然后使用

举例:

ExecutorService service = Executors.newCachedThreadPool(); 
//向线程池中 加入 新的任务 
service.execute(new Runnable() { 
	@Override 
	public void run() { 
		System.out.println("线程的名称:"+Thread.currentThread().getName()); 
	} 
}); 
service.execute(new Runnable() { 
	@Override 
	public void run() { 
		System.out.println("线程的名称:"+Thread.currentThread().getName()); 
	} 
}); 
service.execute(new Runnable() { 
	@Override 
	public void run() { 
		System.out.println("线程的名称:"+Thread.currentThread().getName()); 
	} 
}); 

2. 定长线程池

定长线程池 (长度是指定的数值)
执行流程:

  1. 判断线程池是否存在空闲线程
  2. 存在则使用
  3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
  4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
ExecutorService service = Executors.newFixedThreadPool(2); 
service.execute(new Runnable() { 
	@Override 
	public void run() { 
		System.out.println("线程的名称:"+Thread.currentThread().getName()); 
	} 
}); 
service.execute(new Runnable() { 
	@Override 
	public void run() { 
		System.out.println("线程的名称:"+Thread.currentThread().getName()); 
	} 
}); 

3. 单线程线程池

单线程线程池(效果与定长线程池创建时传入数值1 效果一致)
执行流程:

  1. 判断线程池 的那个线程 是否空闲
  2. 空闲则使用
  3. 不空闲,则等待 池中的单个线程空闲后 使用
ExecutorService service = Executors.newSingleThreadExecutor(); 
service.execute(new Runnable() { 
	@Override 
	public void run() { 
		System.out.println("线程的名称:"+Thread.currentThread().getName()); 
	} 
}); 
service.execute(new Runnable() { 
	@Override 
	public void run() { 
		System.out.println("线程的名称:"+Thread.currentThread().getName()); 
	} 
}); 

4. 周期性任务定长线程池

周期任务定长线程池
执行流程:

  1. 判断线程池是否存在空闲线程
  2. 存在则使用
  3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
  4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程

周期性任务执行时:
定时执行, 当某个时机触发时, 自动执行某任务

ScheduledExecutorService service = Executors.newScheduledThreadPool(2); 
/**
* 定时执行 
* 参数1. runnable类型的任务 
* 参数2. 时长数字 
* 参数3. 时长数字的单位 
*/ 
service.schedule(new Runnable() { 
	@Override 
	public void run() { 
		System.out.println("俩人相视一笑~ 嘿嘿嘿"); 
	} 
},5,TimeUnit.SECONDS);
/**
* 周期执行 
* 参数1. runnable类型的任务 
* 参数2. 时长数字(延迟执行的时长) 
* 参数3. 周期时长(每次执行的间隔时间) 
* 参数4. 时长数字的单位 
*/ 
service.scheduleAtFixedRate(new Runnable() { 
	@Override 
	public void run() { 
		System.out.println("俩人相视一笑~ 嘿嘿嘿"); 
	} 
},5,2,TimeUnit.SECONDS);

Lambda表达式

函数式编程思想(只关注解决问题的方法而不关注过程)。

举例(代码演变):
使用Lambda表达式前:

使用Lambda表达式后:

括号就相当于上面run方法的括号,要传参数就直接写。->后面的大括号里就是方法要执行的内容。
注意:要实现Lambda表达式那么被Lambda表达式化的接口或者类中只能有一个(抽象)方法。

网络编程概述

什么是计算机的IP地址?

IP地址 是计算机在互联网中的唯一标识 . 就像人在社会中的身份证号码.
本机IP: 127.0.0.1
localhost

IP地址分类
IPv4:是一个32位的二进制数,通常被分为4个字节,表示成a.b.c.d的形式,例如192.168.65.100。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。

IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。全球IPv4地址在2011年2月分配完毕。为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。

什么是网络中网站的域名?

域名可以简单的理解为, IP地址的别名. 更方便记忆, 当输入域名后(例如www.baidu.com) , 计算机会访问域名解析商 , 然后得到ip地址, 再进行访问。

什么是计算机的端口号?

端口号的范围 0-65535 之间 .*****

与ip地址很相似, IP地址是计算机在网络中的唯一标识 .

端口号是计算机中程序的标识 .用于在一台计算机中区分不同的应用程序
端口号在使用时, 应尽量避免0-1024之间的端口号, 因为已经被一些知名的软件和windows操作系统所占用了

什么是计算机之间的通信协议?

是计算机与计算机之间交流的标准。
是对数据的传输速率, 传入接口, 步骤控制出错控制等等制定的一套标准!通信双方必须同时遵守,最终完成数据交换。

常用的通信协议:

  1. http协议 : 超文本传输协议 . 80端口号

  2. https协议: 安全的超文本传输协议 443端口号

  3. ftp协议: 文件传输协议 21端口号

  4. TCP协议: 传输控制协议(Tr ansmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的崴数据传输。
    三次握手:
    TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
    第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
    第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
    第三次握手,客户端再次向服务器端发送确认信息,确认连接。
    完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等

  5. UDP协议:用户数据报协议(User Datagram Protoco7)。UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中,例如视频会议、QQ聊天等。

网络编程程序的分类:

  1. B/S 程序 : 浏览器与服务器程序
  2. C/S 程序 : 客户端与服务器程序

TCP协议 - OSI网络模型

指的是从一台计算机的软件中, 将数据发送到另一台计算机的软件中的过程。

七层网络模型: 应用层 / 表现层 / 会话层 / 传输层 / 网络层 / 数据链路层 / 物理层

三次握手和四次挥手(熟悉)

tcp协议客户端与服务器连接时, 存在三次握手操作, 确保消息能准确无误的发送。
断开连接时, 存在四次挥手操作

TCP程序

TCP协议的C/S程序(重点)

需要使用到两个类, 来编写TCP协议的C/S程序

  1. ServerSocket 搭建服务器
  2. Socket 搭建客户端

两方使用socket(套接字, 通信端点) 进行交流
注意顺序一定是先搭建服务器再搭建客户端

ServerSocket(重点,更多见API)

用于创建服务器,创建完毕后,会绑定一个端口号
然后此服务器可以等待客户端连接
每连接一个客户端,服务器就会得到一个新的Socket对象, 用于跟客户端进行通信
常用构造方法:

ServerSocket(int port);

创建一个基于TCP/IP协议的服务器,并绑定指定的端口号
注意: 参数port的范围是: 0-65535 (建议1025-65535)

常用方法:

Socket accept();

等待客户端连接
此方法会导致线程的阻塞!
直到一个新的客户端连接成功, return Socket对象后, 线程在继续执行

void close(); 

释放占用的端口号 , 关闭服务器

Socket(重点,更多见API)

是两台计算机之间通信的端点,是网络驱动提供给应用程序编程的一种接口一套标准,一种机制
构造方法:

Socket(String ip,int port)

创建一个套接字,并连接指定ip和端口号的服务器.
参数1:服务器的ip地址
参数2:服务器软件的端口号

常用方法:

OutputStream getOutputStream(); 

返回的是,指向通信的另一端点的输出流

InputStream getInputStream(); 

返回的是,指向通信的另一端点的输入流

void close(); 

关闭套接字

注意:
在网络编程时,获取输入输出流的操作,对于客户端与服务器来说是相对的
客户端的输入流,输入的是服务器的输出流输出的内容.
客户端的输出流,输出到了服务器的输入流中
所以在使用时,需要注意以下一点规则:
客户端与服务器获取流的顺序必须是相反的
例如:
客户端先得到了输入流,那服务器必须先获取输出流

举例(客户端与服务器端相互通信):
客户端:

//1. 连接服务器 
Socket socket = new Socket("192.168.102.228",8888); 
//2. 得到输出流 
//2.1 得到输出流 
OutputStream os = socket.getOutputStream(); 
//2.2 将输出流, 转换为打印流 
PrintStream ps = new PrintStream(os); 
//3. 得到输入流 
//3.1 得到输入流 
InputStream is = socket.getInputStream(); 
//3.2 将字节输入流, 转换为字符输入流 , 并转换为逐行读取流InputStreamReader isr = new InputStreamReader(is); 
BufferedReader br = new BufferedReader(is); 
//4. 循环接收用户输入 
Scanner input = new Scanner(System.in); 
while(true) { 
	System.out.println("请输入要发送给服务器的内容:"); 
	String text1 = input.nextLine(); 
	//5. 将用户输入的内容, 发送给服务器 
	ps.println(text1); 
	//6. 接收服务器回复的消息 
	String text2 = br.readLine(); 
	System.out.println(text2); 
	if("886".equals(text1)) { 
		break; 
	} 
} 

服务器端:

public static void main(String[] args) throws Exception { 
	//1. 启动服务器, 并侦听8888端口号 
	ServerSocket server = new ServerSocket(8888); 
	//2. 打印提示 
	System.out.println("服务器已启动 , 等待客户端连接中..."); 
	//3. 等待客户端连接 
	Socket socket = server.accept(); 
	System.out.println("一个客户端连接成功:"+socket.getInetAddress().toString()); 
	//4. 获取输入流 
	//4.1 获取输入流 
	InputStream is = socket.getInputStream(); 
	//4.2 将输入的字节流 ,转换为字符流 
	InputStreamReader isr = new InputStreamReader(is); 
	//4.3 将字符流, 转换为逐行读取流 
	BufferedReader br = new BufferedReader(isr); 
	//5. 获取输出流 
	//5.1 获取输出流 
	OutputStream os = socket.getOutputStream(); 
	//5.2 将字节输出流, 转换为打印流 
	PrintStream ps = new PrintStream(os); 
	while(true) { 
		//6. 循环读取一行行的数据 ,读取操作会导致线程的阻塞, 直到客户端真的发送了数据, 
		//服务器才能接到, 顺序继续执行下面的代码 
		String text = br.readLine(); 
		//7. 将这个文字, 再打印给客户端 
		ps.println("服务器:"+text); 
		if("886".equals(text)) { 
			break; 
		}
	} 
}

运用多线程实现一个服务器端与多个客户端交互


核心是利用循环创建Socket和子线程,并在子线程中进行交互。(主要针对服务器端)

InetAddress 描述IP地址的类

InetAddress 这个类的对象, 用于描述IP

得到InetAddress对象的方式:

InetAddress ip = InetAddress.getByName("192.168.102.228");

在UDP协议中. 通过数据包DatagramPacket的getAddress方法, 可以得到数据包来自哪个ip

在TCP协议中, 通过套接字Socket的getInetAddress方法, 可以得到套接字连接的ip地址

常用方法:

String getHostAddress() 

ip地址字符串

String getHostName() 

计算机名称, 当名称无法获取时, 获取的为ip地址

IDEA Debug调试技巧

1、添加断点

单击断点可以添加条件(在什么条件下触发该断点):

2、功能区域释义
(1)左侧工具栏:

示意图功能介绍
第一个:重新启动程序
第二个:正常执行代码直到下一个断点
第四个:停止运行
第五个:显示当前所有的断点
第六个:屏蔽所有断点

(2)中间靠左工具栏:

第一个加号:添加语句,例如:

这里每执行到该断点处都会多执行一遍count++,因为在观察窗里添加了一个。

最后一个眼镜:将选定变量分栏显示

(3)上方工具栏

示意图功能介绍
第一个:将光标移到当前运行的代码的位置
第二个:单步跳过,指的是向下一行代码运行。也就是说不会进方法
第三个:单步跳入,如果当前行有方法则会进入方法内部,这个是只有你自己写的方法才会进入
第四个:单步跳入,如果当前行有方法则会进入方法内部,无论是系统给的方法还是自己写的方法都会进入。
第五个:跳出方法
第六个:也是跳出方法
第七个:直接执行到光标所在位置

3、变量调试菜单
右击观察窗里的变量可以调出变量调试菜单:

里面可以对选定变量进行赋值,复制等操作。

Junit单元测试

步骤:
使用idea IDE 进行单元测试,首先需要下载jar文件。
1.新建lib文件夹

2. 将jar文件粘贴到lib文件夹中(建议放在外部库中)

3. 引入Jar文件(第一步在Libraries中第二步在Modules中)

4. 新建test文件夹

5. 将test文件夹设置为测试文件夹

6. 在任意要测试的类里按下shift+ctrl+t 生成测试类

7. 选择要测试的方法和版本

8. 测试类创建完毕, 编写测试代码

9. 执行测试观察效果

Junit中断言(Assert)的使用:

假设测试这样一个方法:

我们本意是想要求和,但是我们不小心写错了写成了求积。
然后我们在测试类中就可以使用断言(Assert)方法来比较实际值与预期值的差距:

这里使用的是Assert的assertEquals方法,用于比较两个值的差距。
第一个参数是预期值,第二个参数是实际值。

输出(实际值与预期值有差异):

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值