Java学习日志
begin:
以下为我对于Java的一些见解,供日常复习而用
前言
[API文档](Java 8 中文版 - 在线API中文手册 - 码工具 (matools.com))
1.关于计算机的基础知识
- 硬件及冯·诺依曼结构
计算机软件
计算机软件可以使计算机按照事先约定好的顺序完成特定的功能
计算机软件是操作计算机硬件
按功能可分为系统软件和应用软件
2. 计算机快捷键
CTRL + C :复制
CTRL + V :粘贴
CTRL + A :全选
CTRL + X :剪切
CTRL + X :撤销
CTRL + S :保存
Alt + F4 :关闭当前窗口
CTRL + shift + Esc :打开任务管理器
shift + delete :删除文件
Alt + tab :切换界面
3. 打开cmd
常用的dos命令
通过/d实现跨盘符的切换
#盘符切换 cd D: || cd /d d: (change directory)
#查看当前目录下的所有文件 dir
#切换目录 cd+当前目录下的文件路径
#返回上一级目录 cd ..
#清理屏幕 cls (clear screen)
#退出终端 exit
#显示IP地址 ipconfig
#打开应用
calc (打开计算器)
mspaint(打开画图软件)
notepad(打开记事本)
#ping 命令
ping www.baidu.com 得到百度的IP地址
#文件操作
md(make directory) text 创建目录
rd(remove diretory) text 移除目录
cd>文件名 创建文件
del 文件名 删除文件
Java基础
学习过程中推荐Java API文档
1. 面向对象开发方法的一些核心思想与概念
面向对象分析与设计
建立模拟问题领域的对象模型
自底向上的抽象
- 把问题领域中的事物抽象为具有特定属性和行为的对象
- 把具有相同属性和行为的对象抽象为类
- 将具有共性的类的共性抽象到父类
自顶向下的分解
对象(Object)
类(Class)和类型(Type)
消息(Message)和服务(Service)
接口(Interface)
抽象(Abstract)
封装(Encapsulation)
继承(Inheritance)
多态(Polymorphism)
2. java 入门
2.1 java注释
//注释内容 单行注释 ctrl+/ 可进行多行的单行注释
/*注释内容*/ 多行注释
/**
*@auther 作者名
*@version 版本号
*@since 指明需要最早使用的jdk版本
*@param 参数名
*@return 返回值情况
*@throws 异常抛出情况
*
*/ JavaDoc:文档注释(/**+回车)
加在类上面就是类的注释,加在方法上面就是方法的注释
用命令行创建JavaDoc文件
javadoc -encoding(编码) UTF-8 -charset(字符集编码) UTF-8 java文件 //多显示一点中文
tips: 使用IDEA生成javadoc文档
2.2 标识符
Java所有的组成部分都需要名字。类名,变量名以及方法名
所有的标识符都应该以字母(AZ或者az),美元符($),或者下划线(_)开始
不能使用关键字作为变量名或者方法
标识符是大小写敏感的
2.3 数据类型
定义字符串:String s = "abc"
基本数据类型
1位(byte)=8字节(bit)
1 bit表示1位 1 Byte表示一个字节 1B=8b 1024B=1KB 1024KB=1MB 1024MB=1GB
数据类型 默认值 字节数 byte 0 1byte short 0 2byte int 0 4byte long 0L 8byte float 0.0f 4byte double 0.0d 8byte char ‘\u0000’ 2byte boolean false 1byte long数据类型和float类型定义
long i = 1234L; float j = 123.3f;
数据类型扩展
整数: 进制 二进制0b 十进制 八进制0 十六进制0x int i=10; int i=010; //八进制0 int i=0x10; //十六进制0x 0~9 A~F 16 浮点数:有限 离散 舍入误差 大约 接近但不等于 (最好完全避免使用浮点数进行比较) 银行业务使用BigDecimal 数学工具类 字符类型 (所有的字符本质还是数字)(编码使用 Unicode 2字节 0~65536) char c1='a'; char c2='中'; System.out.println((int)c1); //强制类型转换 转移字符 // \t 制表符 // \n 换行符
引用数据类型
- 类
- 接口
- 数组
2.4 变量作用域
public class Variable{
static int a=0; //类变量 static;从属于类
String str = "hello world"; //实例变量;从属于对象;如果不进行初始化,则变成这个类型的默认值;除了基本数据类型,其余的默认都是NULL
public void method(){
int i=0; //局部变量;必须声明和初始化值
}
}
常量初始化后不能再改变的值
常量名一般使用大写字母
final 常量名=值;
final double PI=3.14;
final在类加载时只进行一次初始化,在类内部的值也不再改变
static意思是静态,在类加载时完成,在一个类里面只有一个值,在类的内部可以修改多次
2.5 自增自减
int a = 3;
int b = a++; 执行完这行代码后,先给b赋值,再++
int c = ++a; 执行完这行代码前,先++,再给b赋值
2.6 位运算(了解)
A = 0011 1100
B = 0000 1101
A&B = 0000 1100 (上下比较,两个都为1,才为1,否则为0)
A|B = 0011 1101 (上下比较,都为0结果才为0,否则为1)
A^B = 0011 0001 (相同为0,否则为1)
~B = 1111 0010 (与B完全相反)
<< 左移 *2 2<<3 16
>> 右移 /2
0000 0010 2
0001 0000 16
2.7 字符串连接符 (+)
“+”号运算符两侧,只要出现String类型,如果另一侧不是字符串,则会将其转换为字符串再连接起来
System.out.println(""+a+b); 字符串在前面,直接进行拼接
System.out.println(a+b+""); 字符串在后面,a+b先进行计算,再拼接
2.8 三元运算符
x ? y : z //如果x==true,则结果为y,否则为z
2.9 包机制
package pkg1.pkg2.pkg3
一般利用公司域名的倒置作为包名
such: package com.baidu.www
import 包的路径 //导入包
2.10 switch多选择结构
switch(变量类型) //变量类型可以是byte,short,int,char,String类型
{
//case穿透,不写case会一直执行下面的语句直到遇到case
case value: //case标签必须是字符串常量或字面量
//语句
break;//可选
case value:
//语句
break;//可选
default://可选
//语句
}
2.11 break和continue
break :强行退出循环,不执行循环中剩余的语句
continue : 用于终止某次循环过程,跳过循环体中尚未执行的语句,接着进行下一次是否执行循环的判定
2.12 打印三角形
public class Main {
public static void main(String[] args) {
for(int i=1;i<=5;i++){
for(int j=5;j>i;j--){
System.out.print(" ");
}
for(int j=1;j<=i;j++){
System.out.print("*");
}
for(int j=1;j<i;j++){
System.out.print("*");
}
System.out.println();
}
}
}
2.13 方法
System.out.println() //调用系统类中标准输出对象out的println方法
方法是语句的集合,它们在一起执行一个功能
1. 方法包含于类或对象中
2. 方法在程序中被创建,在其他地方被引用
方法的定义:
修饰符 返回类型 方法名 (参数类型 参数名){
···
方法体
···
return 返回值; //返回值 , 终止方法
}
方法重载
方法名称必须相同
参数列表必须不同
方法的返回类型可以相同也可以不同
tips: 仅仅返回类型不同不足以成为方法的重载
方法名称相同时,编译器会根据调用方法的参数个数,参数类型等去逐个匹配对应的方法
Java传参为值传递
2.14 命令行传递参数
public static void main(String[] args){
for(int i=0;i<args.length;i++)
System.out.println("args["+i+"]: "+args[i]);
}
javac 路径名 //编译为.class文件
java 路径名 参数 //注意要加上包的路径
such:
java h1.Main this is hh
result:
args[0]:this
args[1]:is
args[2]:hh
2.15 可变参数
在方法的声明中,可在指定参数类型后面加一个省略号(…)
一个方法只能指定一个可变参数,它必须是方法的最后一个参数,任何普通参数必须在它之前声明
//本质上类似于命数组传参
public class Demo04{
public static void main(String[] args){
Demo04 demo04 = new Demo04();
demo04.test(1,2,3,4)
}
public void test(int... i){ //类似于int类型的数组
}
}
2.16 递归
递归结构包括两个部分:
递归头:什么时候不带哦一年自身的方法,如果没有头,将陷入死循环
递归体:什么时候需要调用自身的方法
理解:就是有个“递”和“归”的过程
important :明确这个函数的功能是什么
2.17 数组
数组定义:数组是相同类型数据的有序集合
//变量的类型 变量的名字 = 变量的值
数组也是对象,数组元素相当于对象的成员变量
数组的定义
声明数组
int[] nums; //首选的方法
int nums2[]; //效果相同,但不是首选方法
//静态初始化
int[] a = {1,2,3,4,5,6,7,8};
使用new来创建数组(开辟了一段空间)
//动态初始化:包含默认初始化
int[] nums = new int[arrysize]; //这里面可以存放arrysize个int类型的数据
nums[0]=10;
数组的元素是通过索引访问的,数组索引从0开始
二维数组
int[] a = {1,2,3};
int[][] nums= {{1,2},{2,3},{3,4}};
实际上相当于将原来的一维数组的每一个数字嵌套
2.18 Java内存分析
Java内存:
堆:
存放new的对象和数组
可以被所有的线程共享,不会存放到别的对象引用
栈:
存放基本变量类型(会包含这个基本数据类型的具体数值)
引用对象的变量(会存放这个引用在堆里面的具体地址)
方法区:
可以被所有的线程共享
包含了所有的class和static变量
2.19 for each
//遍历数组中的所有元素
int[] arrays = {1,2,3,4,5}
//没有下标
for(int array : arrays){
System.out.println(array);
}
3. 类和对象
以类的方式组织代码,以对象的方式封装数据
3.1 Scanner 对象
需要导入包:
import java.util.Scanner;
基本语法:
Scanner s = new Scanner(System.in);
//创建一个扫描器对象,用于接收键盘数据
Scanner s = new Scanner(System.in);
//凡是属于IO流的类如果不关会一直占用资源
S.close();
Scanner类的next()与nextLine()方法
next()方法:
对输入有效字符之前遇到的空白,next()方法会将其自动去掉
只有输入有效字符后才将后面输入的空白作为分隔符或者结束符
不能得到带有空格的字符串
若想输入一个字符,即可
Scanner scanner = new Scanner(System.in);
char c = scanner.next().charAt(0);
nextLine()方法:
该方法返回输入回车之前的所有字符,包括空格
//循环输入数据
while(scanner.hasNextDouble())
{
double x = scanner.nextDouble();
}
3.2 一般类的组成
类是一个模板,对象是一个具体的实例
类:
静态的属性 属性
动态的行为 方法
public class Student{
成员变量
方法:
成员方法
构造方法(构造器)
}
static 可以修饰成员变量和成员方法,是和类一起加载的,static修饰的成员变量为类变量
没用static修饰的只有在类实例化之后才存在,static修饰的方法为静态方法
非静态方法只跟对象有关
this 当前对象的引用
匿名对象:在调用多次方法的时候,只能调用唯一一次 //省略了对象名称
如果希望同一个对象调用多次方法,那么必须给对象起个名字
3.3 构造器
构造器:
1.和类名相同
2.没有返回值
作用:
1.new 本质在调用构造方法
2.初始化对象的值
注意点:
定义有参构造后,如果想使用无参构造,需要显示的定义一个无参的构造器
每个类都会有默认构造器
alt + insert : 可生成一个构造器
子类new一个对象的同时也new了一个父类对象
super注意点:
super调用父类构造器必须要在子类构造器的第一行
super必须只能出现在子类的方法或者构造方法中
super和this不能同时调用构造方法
super和this的不同:
代表的对象不同:
this:本身调用这个对象
super:代表父类对象的引用
前提:
this:没有继承也能使用
super:只能在继承条件下才可以使用
构造方法:
this() :本类的构造
super():父类的构造
public class Student extends Person{
public Student(){
super(); //调用父类构造器
System.out.println("构造器执行了");
}
}
除了用对象引用来访问对象成员外,还可以直接使用创建的对象本身来引用对象成员,格式如下:
new 类名().对象成员
创建后只能访问其中某一个成员,不能像对象引用那样可以访问多个对象成员,在完成某一个对象成员的访问后,该对象就会变成垃圾对象
3.5 Java访问控制四个修饰符
同一类中 | 同一包中 | 不同包子类 | 全局范围 | |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default(一般都是默认) | √ | √ | × | × |
private | √ | × | × | × |
tips :default并不是关键字”default”,而是根本不写
3.6 static
static只执行一次,与类一起加载
3.7 abstract
抽象类的所有方法,继承了它的子类,都必须要实现它的抽象方法(除非那个子类也是抽象类)
不能new抽象类,只能靠子类去实现它
抽象类中可以写普通的类
抽象方法必须要在抽象类中
3.8 接口
接口:只有方法的定义,没有方法体
接口中的方法用public abstract修饰
接口中作为常量时用public static final
接口是更高级别的抽象,若不定义为常量,若有多个类实现该接口,其中一个类改变接口中的变量,其他类的也要跟着改变,容易
使用 interface 来定义接口
implements 来实现接口
实现了接口的类,就需要重写接口中的方法
接口可实现多继承
//定义接口
public interface UserService{
[public] [static] [final] 常量类型 常量名 = 常量值;
[public] [abstract] 方法返回值类型 方法名 (参数列表);
//jdk 8之后
//在方法名前面加个 default 关键字即可实现默认方法
[public] default 方法返回值类型 方法名(){
//默认方法的方法体
}
[public] static 方法返回值类型 方法名(){
//静态方法(类方法)的方法名
}
}
//实现接口
public class Student implements UserService,TimeService{
}
静态方法可通过"接口名.方法名"
抽象方法和默认方法只能通过接口实现类的实例对象来调用
接口中不能含有静态代码块但含有静态方法,而抽象类可以有静态代码块和静态方法
因为静态代码块中只含有表达式,而且接口是方法的集合体,这违背了它的基本原理
与抽象类相比,接口不能搞定的:
抽象类能够定义非 static final 的属性(field),而接口不能。接口的属性都是static final的。
抽象类能够定义非 public方法,而接口不能。接口的方法都是public的。
与接口相比,抽象类不能搞定的:
接口可以多继承(实现),而抽象类不能。抽象类只能单继承。
tips: 当一个类实现接口时,如果这个类是抽象类,只需实现接口中的部分抽象方法即可,否则需要实现接口中所有抽象方法
个人理解:
非抽象类继承抽象类或者实现接口时,必须实现(重写)抽象类的所有抽象方法
抽象类实现接口时,可以只实现(重写)接口中的部分抽象方法
基于上述,我们可以这样做,当你只想实现接口中的个别方法(不是所有方法)时,你可以先写一个抽象类来实现该接口,并在抽象类中实现除了你想要的方法之外的所有方法(方法体为空)。接着再用你的类继承这个抽象类,这个类中就只用实现你需要的方法了,这样就可以达到你的需要了,我们也可以不用管其他方法具体怎么实现(因为我们用不到那些方法)
3.9 内部类
内部类可获得外部类的私有属性
public class Outer{
private int id;
public void out(){
System.out.println("这是外部类的方法");
}
public class Inner{
public void in(){
System.out.println("这是内部类的方法");
}
}
}
public static void main(String[] args){
Outer outer = new Outer();
//通过这个外部类来实例化内部类
Outer.Inner inner = outer.new Inner();
}
成员内部类
成员内部类可以访问外部类中所有的成员,同时外部类也可以访问成员内部类的所有成员
创建内部类的具体语法格式如下:
外部类名.内部类名 变量名 = new 外部类名().new 内部类名(); //外.内
如果出现了重名现象,那么格式是,外部类名称.this.外部类成员变量名
局部内部类
也叫方法内部类,定义在某个局部范围的类
局部内部类可以访问外部类中所有成员,而只有当前所属的方法才能使用它,出了这个方法外面就不能用了
如果希望访问所在方法的局部变量,那么这个局部变量必须是 final 修饰的
备注:从Java 8开始,只要局部变量事实不变,那么 final 关键字可以省略
原因:
1.new出来的对象放在堆内存当中
2.局部变量是跟着方法走的,在栈内存当中
3.方法运行结束之后,立刻出栈,局部变量就会跟着消失
4.但是new出来的对象会在堆中持续存在,直到垃圾回收消失
静态内部类
只能访问外部类的静态成员,同时通过外部类访问内部类成员时,可以跳过外部类直接通过内部类访问静态内部类成员
创建静态内部类对象的基本语法格式如下:
外部类名.静态内部类名 变量名 = new 外部类名.静态内部类名
匿名内部类 //省略了实现类/子类名称
适合创建后只使用一次的类,创建匿名内部类时须继承一个已有的父类或者实现一个接口
匿名内本身无名,创建时不存在构造方法,不能重复使用
//父类/接口构造方法 + 类体 = 匿名内部类
匿名内部类的定义格式
接口名称 对象名 = new 接口名称(){ //这里的new可理解为创建了一个匿名内部类对象
//覆盖重写所有抽象方法
};
//使用了匿名内部类,而且省略了对象名称,也是匿名对象
new 接口名称(){
//覆盖重写所有抽象方法
};
//定义一个接口
public interface MyInterface {
void method();
}
//接口实现类
public class MyInterfaceImpl implements MyInterface{
@Override
public void method() {
System.out.println("匿名内部类实现了方法");
}
}
public class DemoMain {
public static void main(String[] args) {
//使用匿名内部类
MyInterface obj = new MyInterface() { //MyInterface是接口名称,表明匿名内部类实现该接口
@Override //{ }之间的内容就是接口实现类中的内容
public void method() {
System.out.println("匿名内部类实现了方法");
}
};
}
}
3.10 包装类
基本类型也字符串类型之间的相互转换
基本类型->字符串(String)
1.基本类型的值+"" 最简单的方法(工作中常用)
int i1 = 100;
String s1 = i1 + "";
System.out.println(s1 + 200); //100200
2.包装类的静态方法toString(参数),是Object类toString方法的重载
static String toString(int i) 返回一个表示指定整数的String对象
String s2 = Integer.toString(100);
System.out.println(s2 + 200); //100200
3.String 类的静态方法valueOf(参数)
static String valueOf(int i) 返回int参数的字符串表示形式
String s3 = String.valueOf(100);
System.out.println(s3 + 200); //100200
字符串(String)->基本类型
使用包装类的静态方法parseXXX("字符串");
Integer 类:static int parseInt(String s)
Double 类:static double parseDouble(String s)
自动装箱和自动拆箱可使运算更加方便
4. 常用类
4.1 Arrays类(数组的工具类)
方法均为static修饰,使用时可直接使用类名进行调用
toString 方法 打印出数组的所有元素
sort方法 升序排列
4.2 Random类
1. 导包
import java.util.Random
2. 创建
Random r = new Random();//小括号中留空即可
3. 使用
获取一个随机的int数字(范围是int所有范围,有正负两种),int num = r.nextInt();
获取一个随机的int数字(参数代表了范围,左闭右开区间),int num = r.nextInt(3);
实际上代表的含义是[0,3),也就是0~2
若想获取[1.n]
可用int num = r.nextInt(n)+1;
5. Java三大特性
5.1 封装
程序设计应当追求"高内聚,低耦合"
高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;
低耦合:仅暴露少量的方法给外部使用
封装(数据的隐藏):
通常下禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这这称为信息隐藏
属性私有,通过getter和setter方法来进行属性值的更改
意义:
1.提高程序的安全性,保护数据
2.隐藏代码的实现细节
3.统一接口
4.提高系统可维护性
5.2 继承
继承本质是对某一批类的抽象,从而实现对现实世界更好的建模
是类和类之间的一种关系
extends意思是扩展,子类是父类的扩展
//父类
public class Animal{
}
//派生类或子类
public class dog extends Animal{
}
子类继承了父类,就会拥有父类的全部方法
ctrl + H 显示继承类的关系图
tips: Java类中只有单继承,没有多继承
方法重写
静态方法由static修饰,只与类有关
非静态方法与对象有关,与类无关
//父类
public class Animal{
public void test(){
System.out.println("这是父类");
}
}
//派生类或子类
public class dog extends Animal{
//Override 重写
System.out.println("这是子类");
}
重写需要有继承关系,子类重写父类的方法!
1. 方法名必须相同
2. 参数列表必须相同
3. 修饰符:范围可以扩大,但不能缩小
4. 抛出的异常:范围可以缩小,不能扩大
为什么需要重写:
父类的功能,子类不一定需要,或者不一定满足
tips:
对于继承关系,新建一个子类对象时,都是先创建一个父类对象,在创建子类对象
若父类只有有参的构造方法,则子类必须要显示定义一个有参的构造方法
5.3 多态
多态:同一方法可以根据发送对象的不同而采用多种不同的行为方式
1.多态时方法的多态,属性没有多态
2.父类和子类有联系
3.存在继承关系,方法需要重写,父类引用指向子类对象
instanceof关键字,如果匹配,则可以进行类型之间的转换
//父类
public class Animal{
public void test(){
System.out.println("这是父类");
}
}
//派生类或子类
public class dog extends Animal{
//Override 重写
System.out.println("这是子类");
}
Animal animal = new dog();
tips:以下修饰符修饰的子类不能重写:
1.static 方法:属于类,它不属于实例
2.final 常量
3.private 方法
对象类型的转换:
//上转型对象(将子类对象当作父类类型来使用)
/**
* 此时不能通过父类对象去调用子类特有的方法,不能操作子类新增加的成员变量(较子类失去一些属性和功能)
* 上转型对象可以操纵父类原有的属性和功能,无论这些方法是否被重写
* 上转型对象调用方法时,就是调用子类继承和重写过的方法
*/
abstract class Animal{
}
class Cat extends Animal{
}
class Dog extends Animal{
}
Animal an1 = new Dog(); //将Dog类当作Animal类来使用
Animal an2 = new Cat(); //将Cat类当作Animal类来使用
//下转型对象
//将本质为Dog类型的an1对象由Animal类型向下转型为Dog,就可以使用Dog类型的所有属性和方法
关键字instanceof
语法格式:
对象(或者对象引用变量) instanceof 类(或接口)
Animal an1 = new Dog();
if(an1 instanceof Cat){ //判断an1本质类型
Cat cat = (Cat)an1;
}
else{
System.out.println("该类型的对象不是Cat类型!");
}
6.异常
Throwable是所有异常的超类
Exception 受检查异常,非运行时异常
RuntimeException 非受检查异常,运行时异常
异常处理机制
try{ //try监控区域
}catch{ //捕获异常
}finally{ //处理善后工作,程序必定会执行
}
如果要捕获多个异常,从小范围到大范围
快捷生成 Ctrl + Alt + T
//假设在这方法中,处理不了这个异常,方法上抛出异常
public void test(int a,int b) throws ArithmeticException{
if(b==0){
throw new ArithmeticException();//主动抛出的异常,一般在方法中使用
}
}
7. 泛型
泛型提供了编译时类型安全监测机制,允许我们在编译时检测到非法的类型数据结构
Java进阶
1.集合和对象数组
1.1 集合
Collection接口(元素是孤立存在的,单列集合)
List接口和Set接口的父接口
定义的是所有单列集合中共性的方法
所有单列集合都可以使用共性的方法
没有带索引的方法
List 接口
1.有序的集合(存储和取出元素顺序相同)
2.允许存储重复的元素
3.有索引,可以使用普通的for循环遍历
实现类:
1.Vector集合
2.ArrayList集合
3.LinkedList集合
Set接口
1. 不允许存储重复元素
2.没有索引(不能使用普通的for循环遍历)
实现类:
1.TreeSet集合
2.HashSet集合
Map接口(元素是成对存在的,每个元素由键与值两部分组成,通过键可以找到对应的值)
Map中的集合不能包含重复的键,值可以重复,每个键只能对应一个值
1.2 对象数组
定义一个数组来存储3个Person对象
缺点:一旦创建,程序运行时期长度不可以发生改变
public class Person{
private String name;
public String getName(){
return name;
}
}
public static void main(String[] args){
//首先创建一个长度为3的数组,里面用来存放Person类型的对象
Person[] array = new Person[3];
Person one = new Person();
Person two = new Person();
Person three = new Person();
array[0] = one;
array[1] = two;
array[2] = three;
System.out.println(array[1].getName());
}
1.3 ArrayList集合
List接口的实现类
ArrayList集合的长度是可以随意变化的
底层是数组,查询快,增删慢
对于ArrayList来说,有一个<E>代表泛型
泛型,也就是装在集合当中的所有元素,全都是统一的什么类型
对于ArrayList集合来说,直接打印得到的不是地址值,而是内容,如果内容是空,得到的是空的中括号,[]
ArrayList<String> list = new ArryList<>();
/*
ArrayList当中的常用方法:
public boolean add(E e),向集合中添加元素,参数的类型和泛型一致 //返回值代表添加是否成功
public E get(int index),从集合中获取元素,参数是索引编号(即类似数组下标),返回值为对应为对应位置的元素
public E remove(int index),从集合中删除元素,参数是索引编号(即类似数组下标),返回值为被删除掉的元素
public int size(),获取集合的尺寸长度,返回值是集合中包含的元素个数
*/
如果希望想集合ArrayList当中存储基本类型,必须使用基本类型对应的包装类
自动装箱:基本类型 -> 包装类型
自动拆箱:包装类型 -> 基本类型
1.4 LinkedList
List接口的实现类
底层由链表进行实现的
里面包括了大量操作首尾元素的方法
使用LinkedList集合特有的方法,不能使用多态
public boolean addFrist(){
}
public boolean addLast(){
}
public E getFirst(){
}
public E getLast(){
}
public E removeFirst(){
}
public E removeLast(){
}
1.5 Set接口
1.不允许存储重复的元素
2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历
HashSet特点
1.不允许存储重复的元素
2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历,可使用迭代器和增强for循环
3.是一个无序的集合,存储元素和取出元素的顺序有可能不一致
4.底层是一个哈希表结构(查询速度非常快)
ArrayList<String> list = new ArryList<>();
Iterator<String> it = list.iterator();
1.6 HashSet集合
哈希值:是一个十进制的整数,由系统随机给出,就是对象的地址,是一个逻辑地址,是模拟出来得到的地址,不是数据实际存储的物理地址
HashSet集合存储数据的结构(哈希表) //无序,不允许重复
jdk1.8之前哈希表=数组+链表
jdk1.8之后:
哈希表=数组+链表
哈希表=数组+红黑树(提高查询效率)
LinkedHashSet //有序,不允许重复
1.7 Collections类
集合工具类,用来对集合进行操作
public static <T> boolean addAll(Collection<T> c, T...elements);往集合中添加一些元素
public static void shuffle(List<?> list); 打乱集合顺序
public static <T> vodi sort(List<T> list); 将集合中元素按照默认规则进行排序(被排序的集合里边存储的元素,必须实现实现Comparable接口,重写了compareTo方法作为排序的规则)
1.8 红黑树(平衡排序树)
排序树:左子树小,右子树大
平衡树:左孩子和右孩子相等
约束:
1.节点可以是红色或者黑色的
2.根节点是黑色的
3.叶子结点是黑色的
4.每个红色的节点的子节点都是黑色的
5.任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
2. Java多线程
程序:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
进程:执行程序的一次执行过程,它是一个动态的概念,是系统资源分配的单位
通常一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义,线程是CPU调度和执行的单位
main()称之为主线程,为系统的入口,用于执行整个程序
线程开启不一定立即执行,
tips:
很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器,如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所有就有同时执行的错觉
2.1 创建线程的方式
三种创建方式
Thread class 继承Thread类(重点)
Runnable 接口 实现Runnable接口(重点)
Callable 接口 实现Callable(了解)
继承Thread类
子类继承Thread类具备的多线程能力
启动线程:子类对象.start()
不建议使用:避免oop单继承的局限性
实行Runnable接口
实现接口Runnable具有多线程能力
启动线程:new Thread(实现类对象).start()
推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
多个线程共享一个资源,只需创建一个该资源对象
2.2 继承Thread类创建线程
创建线程方式一:继承Tread类,重写run()方法,调用start开启线程
public class TestThread1 extends Thread{
@Override
public void run() {
//run 方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码---"+i);
}
}
public static void main(String[] args) {
//main线程,主线程
//创建一个线程对象
TestThread1 testThread1 = new TestThread1();
//调用start方法
teatThread1.start();
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程---"+i);
}
}
}
两条线程同时执行,交替执行的
2.3 网图的下载
//首先导入jar包
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class TextThread extends Thread{
private String name;
private String url;
public TextThread(String url,String name){
this.url = url;
this.name = name;
}
@Override
public void run() {
WecDownloader wecDownloader = new WecDownloader();
wecDownloader.downloader(url, name);
}
public static void main(String[] args) {
TextThread textThread = new TextThread("https://tse3-mm.cn.bing.net/th/id/OIP-C.s4LzOcaohmeW3Qkjwy153AHaFP?w=266&h=188&c=7&r=0&o=5&dpr=1.5&pid=1.7", "1.jpg");
textThread.start();
}
}
class WecDownloader{
public void downloader(String url,String name){
try{
FileUtils.copyURLToFile(new URL(url), new File(name));
}catch (IOException e){
System.out.println("IO异常");
}
}
}
2.4 实现Runnable接口(常用)
适合于无返回值的方法
避免了Java单继承带来的局限性,通过Runnable接口实现多线程
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class TextThread implements Runnable{
private String name;
private String url;
public TextThread(String url,String name){
this.url = url;
this.name = name;
}
@Override
public void run() {
WecDownloader wecDownloader = new WecDownloader();
wecDownloader.downloader(url, name);
}
public static void main(String[] args) {
TextThread textThread = new TextThread("https://tse3-mm.cn.bing.net/th/id/OIP-C.s4LzOcaohmeW3Qkjwy153AHaFP?w=266&h=188&c=7&r=0&o=5&dpr=1.5&pid=1.7", "1.jpg");
new Thread(textThread).start();
}
}
//下载器
class WecDownloader{
public void downloader(String url,String name){
try{
FileUtils.copyURLToFile(new URL(url), new File(name));
}catch (IOException e){
System.out.println("IO异常");
}
}
}
2.5 实现Callable接口
适合于有返回值的方法,并抛出异常
//借助FutureTask执行
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
//定义一个实现Callable接口的实现类
public class TextCallable implements Callable<Boolean> {
private String name;
private String url;
public TextCallable(String url, String name){
this.url = url;
this.name = name;
}
@Override
public Boolean call() throws Exception{
WecDownloader wecDownloader = new WecDownloader();
wecDownloader.downloader(url, name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建Callable接口的实现类对象
TextCallable textCallable = new TextCallable("https://tse3-mm.cn.bing.net/th/id/OIP-C.s4LzOcaohmeW3Qkjwy153AHaFP?w=266&h=188&c=7&r=0&o=5&dpr=1.5&pid=1.7", "1.jpg");
//使用FutureTask封装Callable接口
FutureTask<Boolean> ft1 = new FutureTask<>(textCallable);
//使用Thread(Runnable target,String name)构造方法创建线程对象
Thread thread1 = new Thread(ft1,"thread1");
//调用线程对象的start方法启动线程
thread1.start();
//可以通过FutureTask对象的方法管理返回值
System.out.println("thread1返回结果:"+ft1.get());
}
}
class WecDownloader{
public void downloader(String url,String name){
try{
FileUtils.copyURLToFile(new URL(url), new File(name));
}catch (IOException e){
System.out.println("IO异常");
}
}
}
//借助线程池来执行
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
//定义一个实现Callable接口的实现类
public class TextCallable implements Callable<Boolean> {
private String name;
private String url;
public TextCallable(String url, String name){
this.url = url;
this.name = name;
}
@Override
public Boolean call() throws Excepton{
WecDownloader wecDownloader = new WecDownloader();
wecDownloader.downloader(url, name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TextCallable textCallable = new TextCallable("https://tse3-mm.cn.bing.net/th/id/OIP-C.s4LzOcaohmeW3Qkjwy153AHaFP?w=266&h=188&c=7&r=0&o=5&dpr=1.5&pid=1.7", "1.jpg");
//创建执行服务
ExecutorService service = Executors.newFixedThreadPool(1);
//提交执行
Future<Boolean> r1 = service.submit(textCallable);
//获取结果
boolean rs1 = r1.get();
//关闭服务
service.shutdown();
}
}
class WecDownloader{
public void downloader(String url,String name){
try{
FileUtils.copyURLToFile(new URL(url), new File(name));
}catch (IOException e){
System.out.println("IO异常");
}
}
}
2.6 Lambda表达式
避免匿名内部类定义过多
可以让代码看起来更加简洁
去掉了一堆没有意义的代码,只留下了核心的逻辑
函数接口的定义:
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
对于函数式接口,我们可以通过lambda表达式来创建接口的对象
//定义一个接口
public interface MyInterface {
void method();
}
//接口实现类
public class MyInterfaceImpl implements MyInterface{
@Override
public void method() {
System.out.println("匿名内部类实现了方法");
}
}
//匿名内部类
MyInterface myinterface = new MyInterface(){
@Override
public void method() {
System.out.println("匿名内部类实现了方法");
}
};
//lambda表达式简化
MyInterface myinterface = ()->{
System.out.println("匿名内部类实现了方法");
};
//有参数的lambda表达式
MyInterface myinterface = (int a)->{
System.out.println("匿名内部类实现了方法"+a);
};
//简化1:参数类型
MyInterface myinterface = (a)->{
System.out.println("匿名内部类实现了方法"+a);
};
//简化2:简化括号
MyInterface myinterface = a->{
System.out.println("匿名内部类实现了方法"+a);
};
//简化2:去掉花括号(前提是花括号里面只有一行代码,一般不常用)
MyInterface myinterface = a->System.out.println("匿名内部类实现了方法"+a);
总结:
lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,用代码块进行包裹
前提是接口为函数式接口
多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号
2.7 线程状态
2.7.1 线程停止(stop)
public class TestStop implements Runnable{
//线程中定义县城提使用的标识
private boolean flag = true;
@Override
public void run() {
//线程体使用该标识
while (flag){
System.out.println("run... Thread");
}
}
//对外提供方法改变标识
public void stop(){
this.flag = flag;
}
}
2.7.2 线程休眠(sleep)
sleep(时间)指定当前线程阻塞的毫秒数
sleep存在异常InterruptedException
sleep时间达到后线程进入就绪状态
sleep可以模拟网络延时,倒计时等
每一个对象都有一个锁,sleep不会释放锁
2.7.3 线程礼让(yield)
礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态
让cpu重新调度,礼让不一定成功,看cpu心情
2.7.4 线程强制执行(join)
join合并线程,待此线程执行完成后,再执行其他线性程,其他线程堵塞
可以想象成c
2.7.5 观测线程状态
Thread.State state = thread.getState();
2.7.6 线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
优先级的设定在start()调度之前
线程优先级用数字表示,范围从1~10
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度
//改变优先级
对象名.setPriority(int xxx)
//获取优先级
对象名.getPriority()
2.7.7 守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
such:后台记录操作日志,监控内存,垃圾回收等待
//上帝
class God implements Runnable{
@Override
public void run(){
}
}
//你
class You implements Runnable{
@Override
public void run(){
}
}
public static void main(String argc){
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true); //默认是false表示是用户线程,正常的线程都是用户线程
thread.start(); //上帝守护线程启动
new Thread(you).start(); //你 用户线程启动
}
2.8 线程同步
并发:同一个对象被多个线程同时操作
处理多线程问题是,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
为了保证数据在方法中被访问的正确性,在访问时加入锁机制 synchronized(同步的),当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可。但存在以下问题
1.一个线程持有锁会导致其他所有需要此锁的线程挂起
2.在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
3.如果一个优先级高的线程等待一个优先级低的线程会释放锁,会导致优先级倒置,引起性能问题
2.9 同步方法和同步代码块
每个对象对应一把锁
方法里面需要修改的内容才需要锁,锁的太多,浪费资源
锁的对象就是变化的量,需要增删改的对象
//同步方法
public synchronized void run(){
}
//同步代码快
synchronized(Obj){
}
Obj称为同步监视器,推荐使用共享资源作为同步监视器
同步监视器的执行过程
1.第一个线程访问,锁定同步监视器,执行其中的代码
2.第二个线程访问,发现同步监视器被锁定,无法访问
3.第一个线程访问完毕,解锁同步监视器
4.第二个线程访问,发现同步监视器没有锁,然后锁定并访问
2.10 死锁
产生死锁的四个必要条件:
1.互斥条件:一个资源每次只能被一个进程使用
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
4.循环等待条件:若干进程之间形成的一种头尾相接的循环等待资源关系
2.11 Lock(锁)
synchronized和lock的对比
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费更少的时间来调度线程,性能更好,并有更好的扩展性(提供更多的子类)
优先使用顺序:
Lock > 同步代码块(已经进入了方法体,分配了相应的资源) > 同步方法(在方法体之外)
ReentrantLock(可重入锁)
class A{
private final ReentrantLock lock = new ReentrantLock();
public void m(){
lock.lock(); //加锁
try{
//保证线程安全的代码
}finally{
lock.unlock(); //解锁
//如果同步代码有异常,要将unlock()写入finally语句块
}
}
}
2.12 线程通信
Java提供了几个方法来解决线程之间的通信问题(Object类中的方法,只能在同步方法或者同步代码块中使用)
wait() 表示线程会一直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度
并发协作模型”生产者/消费者模式”
管程法(l)
生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
缓冲区:消费者不能直接使用生产者的数据,它们之间有个"缓冲区"
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
public class Text {
public static void main(String[] args) {
SyContainer container = new SyContainer();
new Productor(container).start();
new Customer(container).start();
}
}
//生产者
class Productor extends Thread{
SyContainer container;
public Productor(SyContainer container){
this.container = container;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了"+i+"只鸡");
}
}
}
//消费者
class Customer extends Thread{
SyContainer container;
public Customer(SyContainer container){
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了-->"+container.pop().id+"只鸡");
}
}
}
class Chicken{
int id;
public Chicken(int id){
this.id = id;
}
}
//缓冲区
class SyContainer{
Chicken[] chickens = new Chicken[10];
int count = 0;
//生产者放入产品
public synchronized void push(Chicken chicken){
if(count == chickens.length) {
//等待消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
chickens[count] = chicken;
count++;
this.notifyAll();
}
//消费者使用产品
public synchronized Chicken pop(){
if(count == 0){
//通知生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count --;
Chicken chicken = chickens[count];
this.notifyAll();
return chicken;
}
}
信号灯法
3. Java I/O流
3.1 输入流和输出流
四大输入输出流(抽象类,使用时使用其子类):
InputStream(字节输入流):
所有字节输入流的超类,提供从一个字节输入流读取数据的基本方法
OuputStream(字节输出流):
所有字节输出流的超类,提供向一个字节输出流写入数据的基本方法
Reader(字符输入流):
所有字符输入流的超类,提供从一个字符输入流读取数据的基本方法
Writer(字符输出流):
所有字符输出流的超类,提供向一个字符输出流写入数据的基本方法
- Post link: https://avereed.github.io/2021/09/30/Java%E5%AD%A6%E4%B9%A0/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.