Java基础学习笔记

先赞后看,养成习惯!!!❤️ ❤️ ❤️
码字不易,如果喜欢可以关注我哦!
如果本篇学习笔记对你有所启发,欢迎访问我的个人博客了解更多内容:链接地址

变量

变量:存数的

  • 声明:----相当于在银行开了个帐户
  • 初始化:----相当于给帐户存钱
  • 使用:-----使用的是帐户里面的钱
    • 对变量的使用就是对它所存的那个数的使用
    • 变量的用之前必须声明并初始化
  • 命名:-----相当于给帐户起名
    • 只能包含字母、数字、_和$符,不能以数字开头
    • 严格区分大小写
    • 不能使用关键字
    • 允许中文命名,但不建议,建议"英文的见名知意"、“小驼峰命名法”

基本数据类型

  1. 八种基本数据类型:byte、short、int、long、float、double、boolean、char
  • byte::1个字节,8位 -128—127
  • short:短整型,2个字节,816位
  • int:整型,4个字节,-21个多亿到21个多亿
    • 整数直接量默认为int类型,但不能超出范围,若超范围则发生编译错误
    • 两个整数相除,结果还是整数,小数位无条件舍弃(不会四舍五入)
    • 运算时若超出范围,则发生溢出,溢出不是错误,但是需要避免
  • long:长整型,8个字节,-900万万亿多到900万万亿多
    • 若想表示长整型直接量,需在数字后加L或l
    • 运算时若有可能溢出,建议在第1个数字后加L
  • double:浮点型,8个字节,很大很大很大
    • 小数直接量默认为double型,若想表示float,需在数字后加F或f
    • 不能表示精确数据,运算时有可能会发生舍入误差,精确场合不能使用
  • boolean:布尔型,1个字节
    只能存储true或false
  • char:字符型,2个字节
    • 采用的是Unicode编码格式,一个字符对应一个码表现的形式是字符char,但本质上是码int(0到65535之间)(ASCII:‘a’----97 ‘A’----65 ‘0’----48)
    • 字符型直接量必须放在单引号中,有且仅有1个
    • 特殊符号需要通过\来转义
  1. 类型间的转换:
    在java中,不能将浮点型数据赋值给整型变量,但是可以将一个整型数据赋值给浮点型变量,因为会发生自动类型转换。所以将整型赋值给double型变量是可以的。

float,double ==> int (错误)

基本数据类型从小到大依次为:
Java基础学习笔记_第1张图片

两种方式:

  • 自动/隐式类型转换:小类型到大类型
  • 强制类型转换:大类型到小类型
    • 语法:(要转换成为的数据类型)变量
    • 注意:强转有可能会溢出或丢失精度
  • 两点规则:
    • 整数直接量可以直接赋值给byte,short,char,但不能超出范围
    • byte,short,char型数据参与运算时,系统会将其自动转换为int类型再运算

操作符

在Java中,点操作符(.)和括号操作符(())的优先级最高。这意味着在表达式中,点操作符和括号操作符的计算会首先进行,然后才是其他操作符。

例如,当一个对象调用其成员方法时,点操作符用于指定对象和方法之间的关系,而括号操作符用于传递参数给方法。

下面是一些常见的操作符按照优先级从高到低的顺序:

括号操作符:()
点操作符:.
前缀操作符:++, --, !, ~
乘法和除法操作符:*, /, %
加法和减法操作符:+, -
移位操作符:>, >>>
关系操作符:, =, instanceof
相等操作符:==, !=
位与操作符:&
位异或操作符:^
位或操作符:|
逻辑与操作符:&&
逻辑或操作符:||
条件操作符:?:
赋值操作符:=, +=, -=, *=, /=, %=, >=, >>>=, &=, ^=, |=

请注意,以上仅为一般情况下的操作符优先级,具体的优先级还可以通过使用括号来明确指定。

常量

  1. 常量在程序运行时是不能被修改的。
  2. 声明时要初始化常量
  3. 在 Java 中使用 final 关键字来修饰常量,声明方式和变量类似
  4. 通常使用大写字母表示常量
  5. 常常通过类名点来访问
  6. 编译器载编译时,会将常量直接替换成定义的数据,效率高

数组

一维数组

一维数组是一种线性数据结构,由相同类型的元素按照一定顺序排列而成的数据结构。它是最简单的数据结构之一,也是很多其他数据结构的基础。

一维数组可以存储多个相同类型的元素,这些元素在内存中是连续存储的。数组的每个元素通过索引来访问,索引从0开始,依次递增。数组的大小(即元素个数)在创建时确定,并且不能动态改变。

例如,一个整数类型的一维数组可以表示为:

int[] numbers = {1, 2, 3, 4, 5};

在上述示例中,numbers 是一个整数类型的一维数组,包含了5个元素。可以通过索引来访问数组的元素,例如 numbers[0] 表示第一个元素的值为1。

一维数组常用于存储一系列的数据,可以进行遍历、查找、排序等操作。通过数组的索引,可以高效地访问和修改元素。需要注意的是,数组的大小在创建时确定,因此在使用数组时需要预先知道需要存储的元素个数,以避免数组越界或浪费内存空间。

除了一维数组,还有多维数组(如二维数组、三维数组等)和动态数组等数据结构,可以根据具体情况选择合适的数据结构来存储和处理数据。

二维数组

二维数组在内存中的存储方式

Java基础学习笔记_第2张图片

静态初始化

定义的同时赋值

int [][]arr={{1,2,3},{4,5,6},{7,8,9}};

动态初始化

先声明,后创建数组对象

int [][] arr;

arr=new int[3][3];

在声明的同时创建数组对象(一步到位)

int [][] arr = new int [3][3]

第二维度的数据可以为空

如果在动态初始化时,没有确定“第二维度”的数组长度,在使用之前,必须对第二维度的数组进行初始化,然后才可以使用,例如:

int[][] array = new int [3][];
array[0]=new int [2] ;
array[0][0]= 7;
array[0][1] = 1;
array[1]= new int[ 4];array[1][0]= 4;
array[1][1] =2;array[1][2] = 8;array[1][3] = l;

如果没有确定第二维度的数组长度,也没有进行初始化就输出,运行会报错,空指针异常

Vector

Vector 是一种 Java 中的可变大小的动态数组类,具有以下特点:

  1. 动态大小:Vector 可以根据需要动态调整其大小,可以根据元素的添加或删除自动调整数组的长度。

  2. 线程安全:Vector 是线程安全的,多个线程可以同时访问 Vector 对象而不会导致数据不一致的问题。它使用同步机制来确保线程安全性,但这也会导致一定的性能损失。

  3. 元素顺序:Vector 保持了元素的插入顺序,即元素的顺序与被添加到 Vector 中的顺序相同。

  4. 可以包含任意类型的元素:Vector 可以保存任意类型的对象,包括基本类型的包装类和自定义的对象。

  5. 支持随机访问:通过索引可以直接访问 Vector 中的元素,类似于数组的访问方式,可以使用 get() 方法通过索引获取元素。

  6. 扩展性:当 Vector 的容量不足时,其容量会自动扩展,可以通过调整初始容量和扩展因子来控制扩展的行为。

需要注意的是,由于 Vector 是线程安全的,因此在性能要求较高的场景下,可以考虑使用 ArrayList 代替 Vector,因为 ArrayList 在非多线程环境下具有更好的性能表现。

分支结构

if
if…else
if…else if
switch…case

Java基础学习笔记_第3张图片

Java基础学习笔记_第4张图片

循环结构

循环三要素:------------------------非常重要

  • 循环变量的初始化
  • 循环的条件(以循环变量为基础)
  • 循环变量的改变
  1. 循环结构:
  • while结构:先判断后执行,有可能一次都不执
  • do…while结构:先执行后判断,至少执行一次
  • 当第1要素的代码与第3要素的代码相同时,首选do…while
  • for结构:应用率最高,适合与次数相关的
    • for循环的小括号中必须由三部分组成,三部分之间用 ; 分隔,各部分可以没有内容,但是这一部分必须存在

2.三种循环结构如何选择:

  • 先看循环是否与次数有关:
    • 若有关--------------------------------------直接上for
    • 若无关,再看第1要素与第3要素的代码是否相同:
    • 若相同--------------------------------直接上do…while
    • 若不同--------------------------------直接上while
  1. break:跳出循环 continue:跳过循环体中剩余语句而进入下一次循环

  2. 嵌套循环:

    • 循环中套循环,常常多行多列时使用,外层控制行,内层控制列
    • 执行规则:外层循环走一次,内层循环走所有次
    • 建议:嵌套层数越少越好,能用一层就不用两层,能用两层就不用三层
    • break默认只能跳出当前一层循环
  3. 增强for循环(新循环,forEach)可以遍历集合也可以遍历数组
    遍历集合时采用的是迭代器进行遍历,

遍历数组时则不是采用普通for循环进行遍历,而是编译器将其转换为普通for循环进行遍历。

类和对象

  1. 类是共同特征的描述
  2. 对象是真实存在的具体实例
  3. 在Java中,一个源文件(.java文件)只能包含一个公共(public)类,并且该文件的名称必须与公共类的名称相匹配。然而,在同一个源文件中,可以定义多个非公共类。这些非公共类对于同一个包中的其他类是可见的,但对于其他包中的类来说是不可见的。

如何得到对象

public class 对象名{

成员变量(代表属性)

成员方法(代表方法)

类名 对象名 = new 类名()

}

给成员变量赋值,在创建对象之后赋值

  • 什么是类?什么是对象?
    • 现实生活是由很多很多对象组成的,基于对象抽出了类
    • 对象:软件中真实存在的单个的个体/东西
  • 类:类型/类别,代表一类个体
    • 类是对象的模子/模板,对象是类的具体的实例,可以将类理解为类别/模子/图纸
    • 类中可以包含:
      • 对象的属性/特征/数据----------------------成员变量
      • 对象的行为/动作/功能----------------------方法
        一个类可以创建多个对象
  • this:指代当前对象,哪个对象调用方法它指的就是哪个对象
    • 只能用在方法中,方法中访问成员变量之前默认有个this.
    • this的用法:
      • this.成员变量名--------------------访问成员变量(必须掌握)
      • this.方法名()-------------------------调用方法(一般不用)
      • this()-----------------------------------调用构造方法(一般不用)

当成员变量与局部变量同名时,若想访问成员变量,则this不能省略

  • 构造方法:构造函数、构造器、构建器----------好处:复用给成员变量赋初始值的代码
    • 作用:给成员变量赋初始值
    • 语法:与类同名,没有返回值类型(连void都没有)
    • 调用:在创建(new)对象时被自动调用
    • 若自己不写构造方法,则编译器默认提供一个无参构造方法,若自己写了构造方法,则不再默认提供
    • 构造方法可以重载

内部类

局部内部类、成员内部类和匿名内部类是Java中三种不同的内部类形式,它们在定义和使用上有一些区别。

  1. 定义方式:
    • 局部内部类(Local Inner Class):在方法或代码块内部定义的类,使用关键字class
    • 成员内部类(Member Inner Class):在外部类内部定义的类,可以访问外部类的成员变量和方法,使用关键字class
    • 匿名内部类(Anonymous Inner Class):没有显式的类定义,在创建对象时直接定义并实现,通常基于一个接口或抽象类。
  2. 作用域和可见性:
    • 局部内部类的作用域仅限于所在的方法或代码块内,其他方法无法访问。
    • 成员内部类的作用域与外部类相同,可以被外部类的其他成员访问,包括其他方法。
    • 匿名内部类的作用域也是在所在的方法或代码块内,但可以通过传递给外部方法或赋值给外部变量等方式在外部访问。
  3. 实例化方式:
    • 局部内部类需要在所在方法或代码块内部创建对象来实例化。
    • 成员内部类需要通过外部类的实例来实例化,先实例化外部类,再通过外部类实例化内部类。
    • 匿名内部类在创建对象时直接定义并实现,可以理解为同时进行了类的定义和实例化。
  4. 使用场景:
    • 局部内部类适用于需要在方法或代码块内部定义一个辅助类来完成特定任务的情况,具有更高的可读性和可维护性。
    • 成员内部类适用于需要在外部类内部创建一个独立且可复用的类,可以访问外部类的成员变量和方法。
    • 匿名内部类适用于只需在一个地方使用的简单类,通常用于实现接口或抽象类的方法,或者响应事件处理等情况。

总的来说,局部内部类用于方法内部的辅助类,成员内部类用于外部类内的独立类,而匿名内部类则是一种简洁的定义和实例化同时进行的方式。开发者应根据具体需求选择合适的内部类形式。

成员内部类

  1. 成员内部类:应用率低,了解
    • 类中套类,外面的称为外部类,里面的称为内部类
    • 内部类通常只服务于外部类,对外不具备可见性
    • 内部类对象通常在外部类中创建
    • 内部类可以直接访问外部类的成员,在内部类中有个隐式的引用指向创建它的外部类对象
    • 何时用:若A类(Baby)只让B类(Mama)用,并且A类(Baby)还想访问B类(Mama)的成员时,可以设计成员内部类

隐式的引用:外部类名.this

  1. 当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量 
外部类.this.成员方法
  1. 虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问
  2. 成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下
public class Test { 
	public static void main(String[] args) { 
	//第一种方式: Outter outter = new Outter();
	Outter.Inner inner = outter.new Inner(); 
	//必须通过Outter对象来创建 //第二种方式:
	Outter.Inner inner1 = outter.getInnerInstance(); 
	} 
}

匿名内部类

何时用:若想创建一个派生类的对象,并且对象只创建一次,可以设计为匿名内部类,可以大大简化代码
注意:匿名内部类中不能修改外面局部变量的值

小面试题:
问:内部类有独立的.class吗?
答:有

匿名内部类必须继承一个父类或者实现一个父接口

匿名内部类格式

new 父类名或者接口名(){ 
	// 方法重写 
	@Override 
	public void method() { 
		// 执行语句 
	} 
};

工具类

Java基础学习笔记_第5张图片

测试类

用来检查其他类是否书写正确。带有main方法的类,是程序的入口

javabean类

Javabean类―用来描述一类事物的类。比如,Student,Teacher,Dog,Cat等

权限修饰符

Java基础学习笔记_第6张图片

实际开发一般用private和public(成员变量私有,方法公开)

this关键字

this:指代当前对象,哪个对象调用方法它指的就是哪个对象

  • 只能用在方法中,方法中访问成员变量之前默认有个this.
  • this的用法:
    • this.成员变量名--------------------访问成员变量(必须掌握)
    • 当成员变量与局部变量同名时,若想访问成员变量,则this不能省略
    • this.方法名()-------------------------调用方法(一般不用)
    • this()-----------------------------------调用构造方法(一般不用)

static

关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被static修饰的成员和方法是属于类的是放在静态区中,没有static修饰的成员变量和方法则是属于对象的。我们上面案例中的成员变量都是没有static修饰的,所以属于每个对象。

有static修饰成员变量,说明这个成员变量是属于类的,这个成员变量称为类变量或者静态成员变量。 直接用 类名访问即可。因为类只有一个,所以静态成员变量在内存区域中也只存在一份。所有的对象都可以共享这个变量。

如何使用呢

例如现在我们需要定义传智全部的学生类,那么这些学生类的对象的学校属性应该都是“传智”,这个时候我们可以把这个属性定义成static修饰的静态成员变量。

定义格式

修饰符 static 数据类型 变量名 = 初始值;

举例

public class Student { 
	public static String schoolName = "传智播客"// 属于类,只有一份。 
	// ..... }

静态成员变量的访问:

格式:类名.静态变量

静态变量

共享的数据设置静态变量

  • 加载到test.class到方法区中
  • 静态变量num一并存储到方法区中
  • 到方法区中获取num的值并输出

静态代码块

静态代码块: 格式:static{} 特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次 优先加载 使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。

  1. 由static修饰的语句块
  2. 属于类,在类被加载期间自动执行,因为只被加载一次,所以静态块也只执行一次
  3. 什么时候用?初始化/加载静态资源

静态方法

成员变量分为两种:

1.实例变量 :无static修饰,属于对象,由对象访问,存储在堆栈中,有几个对象就有几份

2.静态变量:有static修饰,属于类,由类名来访问

适用范围:如果有业务和对象无关,不需要访问对象的属性,可以设置成静态方法

static final常量

  • 必须声明同时初始化
  • 常常通过类名点来访问,不能被改变
  • 建议:常量名所有字母都大写,多个单词之间用_分隔
  • 编译器在编译时,会将常量直接替换为具体的数,效率高
  • 何时用:在程序运行过程中数据永远不变,并且经常使用

构造方法

在创建对象时给成员变量进行赋值

在创建对象的时候,给成员变量进行初始化。

初始化即赋值的意思。

  1. 构造方法:构造函数、构造器、构建器----------好处:复用给成员变量赋初始值的代码
  • 作用:给成员变量赋初始值
  • 语法:与类同名,没有返回值类型(连void都没有)
  • 调用:在创建(new)对象时被自动调用
  • 若自己不写构造方法,则编译器默认提供一个无参构造方法,若自己写了构造方法,则不再默认提供
    构造方法可以重载

构造器

构造器的特征

  • 它具有与类相同的名称
  • 它不声明返回值类型。(与声明为void不同)
  • 不能被static、final、synchronized、abstract、native修饰,不能有 return语句返回值

构造器的作用:创建对象;给对象进行初始化

根据参数不同,构造器可以分为如下两类:

  • 隐式无参构造器(系统默认提供)
  • 显式定义一个或多个构造器(无参、有参)

注意:

  • Java语言中,每个类都至少有一个构造器
  • 默认构造器的修饰符与所属类的修饰符一致
  • 一旦显式定义了构造器,则系统不再提供默认构造器
  • 一个类可以创建多个重载的构造器
  • 父类的构造器不可被子类继承

封装

对象代表什么就得封装对应数据,并提供数据对应的行为

把零散的数据分装成一个整体,这就是对象

Java基础学习笔记_第7张图片

当对象越来越多时,把同一类的共性内容抽取出来放到父类person里,子类student和tecaher可以访问父类里非私有的成员

这就是继承(解决代码重复的问题)
Java基础学习笔记_第8张图片

继承

继承:

  • 作用:代码复用

  • 通过extends实现继承

  • 超类/基类/父类:共有的属性和行为

  • 派生类/子类:特有的属性和行为

  • 派生类可以访问:超类的+派生类的,超类不能访问派生类的

  • 一个超类可以有多个派生类,一个派生类只能有一个超类---------单一继承

  • 具有传递性

  • java规定:构造派生类之前必须先构造超类

    • 在派生类的构造方法中若没有调用超类的构造方法,则默认super()调用超类的无参构造方法
    • 在派生类的构造方法中若自己调用了超类的构造方法,则不再默认提供

注意:super()调用超类构造方法,必须位于派生类构造方法中的第1行

继承

就是子类继承父类的属性和行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。

  1. 要满足子类是父类中的一种
    把多个子类中的代码抽取到父类中,子类可以直接使用

格式:

public class 子类 extends 父类 { }
  1. 并不是父类的所有内容都可以给子类继承的:

子类不能继承父类的构造方法。

值得注意的是子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量。

  1. 子父类中出现了同名的成员变量时,子类会优先访问自己对象中的成员变量,在子类中需要访问父类中非私有成员变量时,需要使用super 关键字访问父类成员变量,修饰父类成员变量,类似于之前学过的 this 。需要注意的是:super代表的是父类对象的引用,this代表的是当前对象的引用。使用格式:
super.父类成员变量名
  1. Fu 类中的成员变量是非私有的,子类中可以直接访问。若Fu 类中的成员变量私有了,子类是不能直接访问的。通常编码时,我们遵循封装的原则,使用private修饰成员变量,那么如何访问父类的私有成员变量呢?对!可以在父类中提供公共的getXxx方法和setXxx方法。
  2. 如果子类父类中出现重名的成员方法,则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法

super关键字

  1. super:指代当前对象的超类对象
    • super.成员变量名-------------------------访问超类的成员变量
    • super.方法名()------------------------------调用超类的方法
    • super()----------------------------------------调用超类的构造方法

当超类成员变量和派生类成员变量同名时,super指超类的,this指派生类的若没有同名现象,则写super和this是一样的

重写

方法重写:发生在子父类之间的关系。子类继承了父类的方法,但是子类觉得父类的这方法不足以满足自己的需求,子类重新写了一个与父类同名的方法,以便覆盖父类的该方法。也称为重写或者复写。声明不变,重新实现。

方法的重写(override/overriding):重新写、覆盖

  • 发生在父子类中,方法名相同,参数列表相同
  • 重写方法被调用时,看对象的类型---------------new谁就调谁的,这是规定
  • 重写后的方法权限要大于等于原方法
  • @Override:注解,重写注解校验!
  • 这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。
  • 建议重写都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错!

重载

在Java中,方法的重载发生在以下情况之一:

方法名相同,但参数列表不同(包括参数数量、类型或顺序的差异)。

对于重载的方法,它们的返回值可以相同也可以不同。重载方法的返回值类型不是识别方法重载的因素,而是方法执行后的返回结果。因此,重载的方法可以有相同的返回值类型,也可以有不同的返回值类型。

需要注意的是,只有参数列表不同才能构成重载,返回值不同不能构成重载。同时,重载函数的函数体可以相同或不同

final

final:最终的、不能改变的------------单独应用几率低

  • 修饰变量:变量不能被改变,修饰的变量是常量,只能被赋值一次
  • 修饰方法:方法不能被重写
  • 修饰类:类不能被继承

在这里插入图片描述

多态

同类型的对象表现出不同的形态

是指同一行为,具有多个不同表现形式。

从上面案例可以看出,Cat和Dog都是动物,都是吃这一行为,但是出现的效果(表现形式)是不一样的。

  1. 多态的实际应用:
    • 将不同对象(狗、鱼、鸡)统一封装到超类数组(动物数组)中来访问,实现代码复用
    • 将超类型(Animal)作为参数或返回值类型,传递或返回派生类(Dog/Fish/Chick)对象,以扩大方法的应用范围(所有Animal),实现代码复用

前提条件

  1. 有继承父类

  2. 父类引用指向子类对象(向上造型)

  3. 有方法重写

格式:

父类类型 对象名称 = 子类对象()

多态中调用成员变量和成员方法时

成员变量:编译看左边,运行看左边

成员方法:编译看左边,运行看右边

Java基础学习笔记_第9张图片

多态的优势

  1. 在多态形势下,右边对象可以实现解耦合,便于拓展和维护

  2. 定义方法的时候,使用父类作为参数,可以接收所有子类对象,体现多态的拓展性与便利

弊端是不能调用子类特有的功能,但是变回子类类型就ok(强制类型转换:可以转换成真正的子类类型,从而调用子类独有的功能)

Anime a = new Dog();
a.eat();
Dog d = (Dog) a;

多态的表现形式

  1. 重载:在同一个类中,可以定义多个同名的方法,它们的参数类型、个数或顺序不同,这被称为方法的重载。
  2. 重写:在子类中,可以根据需要重写父类中的方法,使用@Override 注解表示方法的重写。在实例化子类对象时,如果调用该方法,会执行子类中的方法。
  3. 接口实现:一个类可以实现多个接口,对于同样的接口,不同的类实现方式是不同的,这样在调用时就可以根据实际情况进行选择。
  4. 类与类的继承

多态常见的应用场景

1 代码的可扩展性
使用多态可以有效地使代码扩展性更强。例如,增加一个实现一个新的接口,只需要实现这个接口即可,对原有代码的修改很少,这使得程序的可扩展性更加强大。

  1. 提高代码的复用性
    面向对象编程的一个核心思想就是复用已有代码,而多态是实现代码复用的有效手段。减少相似代码的重复,提高代码的复用性和可靠性。

  2. 代码的可维护性
    使用多态的代码结构更加清晰,模块化,更易于日后代码的维护和修改。

  3. 实现程序的动态性

如果不使用多态,在程序中需要大量使用类型判断或者具体类实例化来实现对象之间的不同操作,而使用多态则能够使得程序的动态性更高。

总之,多态是面向对象编程的一个重要理念,可以提高代码质量、复用性和可维护性,实现程序的灵活性和动态性。

抽象类和抽象方法

我们把没有方法体的,由abstract修饰的方法称为抽象方法。

Java语法规定,包含抽象方法的类就是抽象类。

abstract是抽象的意思,用于修饰方法方法和类,修饰的方法是抽象方法,修饰的类是抽象类。

使用abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。

定义格式:

修饰符 abstract 返回值类型 方法名 (参数列表)

代码举例:

public abstract void run()

如果一个类包含抽象方法,那么该类必须是抽象类。

定义格式:

abstract class 类名字 { }

代码举例:

public abstract class Animal { 
	public abstract void run()}

格式:

抽象方法 public abstract 返回值类型 方法名(参数列表)

没有方法体的方法是抽象方法

抽象方法所在的类必须是抽象类

抽象类 public abstract class 类名 { }

注意事项

  • 抽象类不能实例化(创建对象)
  • 抽象类不一定有抽象方法,有抽象方法的一定是抽象类
  • 可以有构造方法
  • 继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。
  • 抽象类的意义:
    • 封装共有的属性和行为------------------代码复用
    • 可以包含抽象方法,为所有派生类统一入口(名字统一),强制必须重写

抽象类的特征总结起来可以说是有得有失

有得:抽象类得到了拥有抽象方法的能力。

有失:抽象类失去了创建对象的能力。

其他成员(构造方法,实例方法,静态方法等)抽象类都是具备的。

关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

  1. 抽象类
    不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
    理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

  2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

  1. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

  1. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。

理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

  1. 抽象类存在的意义是为了被子类继承。

理解:抽象类中已经实现的是模板中确定的成员,抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现

接口

接口是更加彻底的抽象,JDK7之前,包括JDK7,接口中全部是抽象方法。接口同样是不能创建对象的。

只能包含抽象方法(常量、默认方法、静态方法、私有方法------暂时搁置)

  • 是一种引用数据类型
  • 由interface定义 一种规则,对行为的抽象
  • 不能被实例化(创建对象)
  • 接口没有构造方法
  • 接口的成员变量只能是常量,默认修饰符public static final
  • 接口的成员方法只能是抽象方法 默认修饰符 public abstract
  1. static方法
  2. default方法
    在接口中定义的成员变量默认会加上: public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量。这里是使用public static final修饰后,变量值就不可被修改,并且是静态化的变量可以直接用接口名访问,所以也叫常量。常量必须要给初始值。常量命名规范建议字母全部大写,多个单词用下划线连接。
  • 实现接口的概述

类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字。

  • 类实现接口的要求和意义
    1. 必须重写实现的全部接口中所有抽象方法。重写后的方法权限要大于等于原方法 加上public
    2. 如果一个类实现了接口,但是没有重写完全部接口的全部抽象方法,这个类也必须定义成抽象类。
    3. 意义:接口体现的是一种规范,接口对实现类是一种强制性的约束,要么全部完成接口申明的功能,要么自己也定义成抽象类。这正是一种强制性的规范。
  1. 类与类的关系
    继承关系,只能单继承,但是可以多层继承

原因:子类如果能继承多个父类,如果多个父类中存在同名属性或者方法,子类继承时将不能判断继承自哪个父类的属性和方法,所以类之间不能多继承。

  1. 类与接口的关系
    实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口。

Java中,接口与接口之间是可以多继承的:也就是一个接口可以同时继承多个接口。大家一定要注意

接口的抽象方法没有具体的方法体,多接口即使拥有同名的抽象方法,类在实现时只需重写一个方法即可。

  1. 接口与接口的关系

接口与接口是继承关系
接口继承接口就是把其他接口的抽象方法与本接口进行了合并。
继承关系,可以单继承,也可以多继承

原因:接口中的方法均为抽象方法,没有具体实现的方法体,所以在多继承的情况下,即使方法同名,也不会出现类多继承那样的矛盾。

关于接口的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

  1. 当两个接口中存在相同抽象方法的时候,该怎么办?

只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的。

  1. 实现类能不能继承A类的时候,同时实现其他接口呢?

继承的父类,就好比是亲爸爸一样 实现的接口,就好比是干爹一样 可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。

  1. 实现类能不能继承一个抽象类的时候,同时实现其他接口呢?
    实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写。

  2. 实现类Zi,实现了一个接口,还继承了一个Fu类。假设在接口中有一个方法,父类中也有一个相同的方法。子类如何操作呢?

处理办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。
处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。

  1. 如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?

可以在接口跟实现类中间,新建一个中间类(适配器类) 让这个适配器类去实现接口,对接口里面的所有的方法做空重写。
让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。 因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象

例子

实现多态
多态是面向对象编程的一个重要特性,它允许不同类的对象对相同的消息作出不同的响应。通过接口,可以实现多态的效果,具体实现如下:

  1. 定义接口
    :首先需要定义一个接口,其中包含要实现的方法或属性。例如:
javaCopy Codepublic interface Animal { 
	void makeSound(); 
}
  1. 实现接口
    不同的类可以实现相同的接口,并提供不同的具体实现。例如:
javaCopy Codepublic class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

public class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow!");
    }
}
  1. 使用多态
    通过接口引用,可以实现多态的效果,即以统一的方式处理不同类的对象。例如:
javaCopy CodeAnimal dog = new Dog();
Animal cat = new Cat();

dog.makeSound(); // 输出 Woof!
cat.makeSound(); // 输出 Meow!

通过上述步骤,就可以实现多态,即不同的类对象通过相同的接口引用可以产生不同的行为。

在面向对象编程中,多态可以通过继承和接口实现:

  • 通过继承:子类继承父类,可以重写父类的方法,然后通过父类引用指向子类对象,从而实现多态。
  • 通过接口:不同的类实现相同的接口,可以以统一的方式处理这些类的对象,也可以实现多态。

枚举

Java 枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一年的 12 个月份,一个星期的 7 天,方向有东南西北等。

Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割。

例如定义一个颜色的枚举类。

enum Color { RED, GREEN, BLUE; }

以上枚举类 Color 颜色常量有 RED, GREEN, BLUE,分别表示红色,绿色,蓝色。

  • 引用数据类型
  • 对象数目固定,常用于定义一组常量
  • 构造方法私有private

枚举类成员

枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用。

枚举既可以包含具体方法,也可以包含抽象方法。 如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它。

  • values() 返回枚举类中所有的值。
  • ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样。
  • valueOf()方法返回指定字符串值的枚举常量。

泛型

JDK5之后推出的另一个特性:泛型

泛型也称为参数化类型,允许我们在使用一个类时指定它当中属性,方法参数或返回值的类型.

  • 泛型在集合中被广泛使用,用来指定集合中的元素类型.
  • 有泛型支持的类在使用时若不指定泛型的具体类型则默认为原型Object

边界定义

//为方法单独定义一个泛型,且指定类型的
public <S extends List> void test1(S s){ }

//不定义边界
public <S> void test1(S s){ }

//静态方法无法引用类上指定的泛型
public static void test1(T t){}
静态方法上使用泛型必须要声明
public static<T> T test1(T t){
    return null;
}

集合

存放引用类型元素,因此集合中的元素保存的是地址

Java基础学习笔记_第10张图片

1.单列集合Collection

常用方法:

  • add方法 只能添加引用类型,不能添加基本类型
c.add(123);//这里的123是自动装箱后再添加
  • size方法 返回当前集合的元素个数
  • isEmpty (boolean)判断当前集合是否为空集
  • clear 清空当前集合
  • remove方法 如果有多个重复数据,只删除一次,删除靠前的那一个
Collection c = new ArrayList();
c.add(new Testclass("zs",12));
c.add(new Testclass("ls",13));
c.add(new Testclass("ww",14));
c.add(new Testclass("zl",16));

Testclass t = new Testclass("zs",12);

System.out.println(c.contains(t));//比较集合中是否有和t一样属性的数据
//记得要重写equals方法
c.remove(t);先比较再删除,先比较集合中是否有和t一样属性的数据,再删除这个数据

System.out.println(c);
  • contains(boolean)方法比较集合中是否有和t一样属性的数据
  • containsAll (boolean)判断当前集合是否包含给定集合中的元素
  • addAll方法 把一个集合作为数据传入另一个集合,相当于两个集合取并集
  • retainAll 保留当前集合中与给定集合共有的元素,相当于两个集合取交集
  • removeAll 删除当前集合中与给定集合的共有元素

1.1List

  • List 接口实例存储的是有序的,可以重复的元素。
  • List 和数组类似,可以动态增长,根据实际存储的数据的长度自动增长 List 的长度。
  • 查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 。

常用方法:

list:

  • get(int index)方法 获取集合里面对应下标的元素,获取到的数据不能强转

  • set方法 E set(int index, E e) 将给定元素设置到指定位置并替换该位置元素,并将被替换的元素返回
    一对重载的add,remove方法

  • add 将给定元素插入到指定位置add(index,内容)

  • remove 删除并返回指定位置上的元素

  • subList 获取指定下标范围内的子集,两个下标参数

  • List.toArray 把集合转换成一个数组

  • Arrays.asList 把数组转换成一个集合

    • 转换后,对集合的修改操作也会改变数组的内容,比如集合用add方法,但是是数组定长,会抛出异常UnsupportedOperationException
    • 如果要修改集合内容,只能新创一个集合,修改后再导入原集合
      Collections:
  • Collections.sort 对List进行自然排序(要求集合里的元素是可比较的,比较标准:元素是否实现了compare接口)

1.1.1ArrayList
  • List是接口,ArrayList是它的实现类

  • ArrayList底层是数组,查询快(get,contain)增删(add,remove)慢。
    遍历方式

  • 迭代器遍历

  • forEach遍历(增强for循环遍历集合时采用的是迭代器进行遍历)

    • 如果集合指定了泛型,那么获取元素时可以直接用元素的实际类型接受
    • 我们在使用forEach循环时看起来是直接遍历集合的元素,但实际上编译器会将其背后转换为迭代器的遍历方式来完成。这样编译器可以更灵活地处理不同类型的集合,并保证遍历的安全性和效率。
  • for循环遍历,size方法

1.1.2LinkList
  • LinkedList底层是链表,查询慢,增删快。

问题

1. Array和Arrays的区别?

  • 数组类Array
    是Java中最基本的一个存储结构。提供了动态创建和访问 Java 数组的方法。其中的元素的类型必须相同。效率高,但容量固定且无法动态改变。它无法判断其中实际存有多少元素,length只是告诉我们array的容量。
  • 静态类Arrays
    此静态类专门用来操作array ,提供搜索、排序、复制等静态方法。
    • equals():比较两个array是否相等。array拥有相同元素个数,且所有对应元素两两相等。
    • sort():用来对array进行排序。
    • binarySearch():在排好序的array中寻找元素。
    • Arrays.asList(array):将数组array转化为List

2. Collection 和 Collections 区别?

  • Collection 是一个集合接口
    提供了对集合对象进行基本操作的通用接口方法,所有集合都是它的子类,比如 List、Set 等。
  • Collections 是一个工具类
    它包含了很多静态方法,不能被实例化,比如排序方法: Collections. sort(list)等。

1.2Set

Set 接口实例存储的是无序的,不重复的数据。
Set 检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 。

2.双列集合Map

键值对**[key-value]**的形式存数据

方法:

  • put(没有新的put改变操作,返回值为Null,否则返回改变的值)将一对键值对存入map中
  • get 根据key获取对应的value,如果指定的key在map中不存在则返回null
    size 返回当前Map的元素个数
  • Collection values 遍历集合map中所有的value,以一个集合的形式返回
  • keyset 将当前map中所有的key以一个Set集合返回
Set<String> keySet = map.keySet();
for(String key:keySet){
    System.out.println(key);
}
  • entrySet 将当前Map中的每一组键值对以一个Entry实例表示,并将所有的Entry实例以一个Set集合返回
    • 每个Entry实例可以调用getKey和getValue获取key和value的值
Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
for (Map.Entry<String,Integer> entry:entrySet){
    System.out.println(entry.getKey()+" "+entry.getValue());
}

3.迭代器

迭代器接口规定使用迭代器遍历集合的通用操作,不同的集合都提供了一个用于遍历自身元素的迭代器实现类

迭代器遍历集合的过程: 遵循:问->取->删

  • ArrayList的迭代器
Iterator i = c.iterator(); 
while (i.hasNext()){}

迭代器遍历示例代码:

        /*
            boolean hasNext()
            通过迭代器判断集合是否还有下一个元素可供遍历
            E next()
            迭代器会向后移动到下一个元素位置并将其返回
         */
        Collection<String> c = new ArrayList<>();
        .......
        Iterator<String> it = c.iterator();
        while(it.hasNext()){
            String e = it.next();
            if("#".equals(e)){
                //迭代器要求在使用迭代器遍历集合的过程中不可以通过集合的方法增删元素,否则会抛出异常
                //c.remove(e);
                /*
                    可以使用迭代器的remove方法在遍历过程中删除元素
                    该方法不需要传递参数,删除的是本次next方法获取的元素
                    remove方法不可连续调用,每调用一次next方法才可以调用一次remove操作
                 */
                it.remove();
            }
            System.out.println(e);
        }

forEach遍历

        Collection c = new ArrayList();
        for(Object o:c){
            String e = (String)o;
            System.out.println(e);
        }
        
        //如果集合指定了泛型,那么获取元素时可以直接用元素的实际类型接受
        Collection<String> c = new ArrayList<>();
        for(String o:c){
            System.out.println(o);
        }

1.2Set

  • Set 接口实例存储的是无序的,不重复的数据。
  • Set 检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 。

常用API

1.文档注释

用来在类,方法,常量,构造器上定义,说明整体功能

c.add(123);//这里的123会自动装箱后,再添加

2.String

  1. java.lang包下的,使用final修饰,不能被继承。
  2. 字符串一但被创建,对象内容不可改变,没有提供修改内容的方法,不便于内容修改,如果拼接字符串会创建新的对象。所以不适合频繁拼接,性能低,开销大
  3. java字符串在内存在中采用Unicode编码
  4. 相同字面量会重用对象

字符串常量池
在jvm堆内存里创建一个字符串常量池,第一次直接给出字面量会把这个字面量放到字符常量池里面,后续如果有相同的字面量赋值,会先读取常量池里的数据,如果有相同的字符串,直接拿出来用。

String s1 = "abc"; 
String s2 = "abc";
  • new String()和字符串拼接创建的新的字符串对象不会被缓存到常量池里面。
String s5 = new String(); 
s5="1123" String s4 = s1+"!";

2.1字符串的比较

  • ==比较
    比较基本数据类型 比的是数据值

比较引用数据类型 比的是地址值

  • equals
    String重写了equals方法,比较数值
    没有重写,比较地址值

两个对象的equals()相等,hashCode()一定相等。
两个对象的hashCode()相等,equals()不一定相等

NullPointerException异常

while (true){
    String data = sc.nextLine();
    if ("exit".equals(data)){
        break;
    }
}

在Java中,比较字符串时应该使用equals()方法而不是==运算符。equals()方法用于比较两个字符串的内容是否相等,而==运算符用于比较两个对象引用是否指向同一个对象。

如果将"exit"和data交换位置后出现空指针异常,可能是因为data为null。当你将"exit"放在equals()方法的前面时,如果data为null,则会抛出NullPointerException异常。这是因为在调用equals()方法之前,会尝试访问data对象的成员或方法,但由于data为null,所以会抛出异常。

为了避免空指针异常,你可以将判断条件改为**if (data != null && data.equals(“exit”))**这样在判断data是否为null之后再进行字符串内容的比较。这样即使data为null,也不会引发空指针异常。

字符拼接

拼的是字符的ASCII码

System.out.println('2'+'2'); //结果输出100

boolean类型的拼接

只能:

System.out.println("111"+222+b); 
System.out.println("111"+b+"222");

2.2字符串的拼接

任何类型的数据和字符串类型的数据做相加操作时,其他类型的数据会自动转化成字符串类型

  • 字符串和算术的拼接
    在abc字符串前面的数字拼接会进行算术运算,在后面的则进行数字拼接
String s6 = 1+2+"abc"+1+2; //输出结果为3abc12
  • 编译器载编译代码时,发现一个计算表达式两边都是字面量时会进行计算,这是编译器的计算特性,因此下面代码会被直接计算,并替换该表达式。并且虚拟机会采用常量池里的对象进行重用。
String s6 = "abc"+"123"; //替换为 
String s6 = "abc123";

2.3常用方法

  • indexOf(正向查) lastIndexOf(反向查)用于查找字符串中某个字符或字符串的位置的方法(查询包含自己,从自己对应的索引开始查,返回自己的索引值)
  • substring 截取当前字符串指定范围的内的字符串(含头不含尾)
  • trim 去除当前字符串两侧的空白字符,并将去除后的字符串返回
  • charAt 根据索引取出对应的值
  • startWith endsWith当前字符串是不是以给定字符串结尾 返回boolean类型
  • toUpper ,toLower 大小写转换方法
  • valueOf 将其他类型转换为字符串
  • parseXXX() 类似于 valueOf() 方法,用于将一个字符串解析为指定的基本类型或对象类型的值
  • length 获取字符串长度
  • replace
  • getBytes 将字符串转换为字节数组。

eplace和replaceAll是JAVA中常用的替换字符的方法,它们的区别是:

  1. replace的参数是char和CharSequence,即可以支持字符的替换,也支持字符串的替换(CharSequence即字符串序列的意思,说白了也就是字符串);
  2. replaceAll的参数是regex,即基于规则表达式的替换,比如,可以通过replaceAll(“\d”, “*”)把一个字符串所有的数字字符都换成星号;

相同点是都是全部替换,即把源字符串中的某一字符或字符串全部换成指定的字符或字符串,如果只想替换第一次出现的,可以使用 replaceFirst(),这个方法也是基于规则表达式的替换,但与replaceAll()不同的是,只替换第一次出现的字符串;

另外,如果replaceAll()和replaceFirst()所用的参数据不是基于规则表达式的,则与replace()替换字符串的效果是一样的,即这两者也支持字符串的操作;

3.StringBuilder

由于String是不变对象,每次修改内容都要创建新对象,因此String不适合做频繁修改操作.为了解决这个问题,java提供了StringBuilder类.

StringBuilder可以看成是一个容器,创建之后里面的内容是可变的

作用:提高字符串的操作效率

StringBuilder是非线程安全,不可并发处理,性能稍快。

StringBuffer是线程安全,可以并发处理,性能稍慢。

StringBuilder是专门用来修改String的一个API,内部维护一个可变的char数组,修改都是在这个数组上进行的, 内部会自动扩容.修改速度和性能开销优异.并且提供了修改字符串的常见操作对应的方法:增删改插

3.1常用方法

  1. toString 把StringBuilder转换成String,将其表示内容以,字符串的形式返回
//添加
b.append("鸡哥");
System.out.println(b);

//替换
b.replace(0,2,"鸡");
System.out.println(b);

//删除
b.delete(5,7);
System.out.println(b);

//插入
b.insert(1," ");
System.out.println(b);

//反转
b.reverse();
System.out.println(b);

构造方法

Java基础学习笔记_第11张图片

常用方法

Java基础学习笔记_第12张图片

3.2StringJoiner

Java基础学习笔记_第13张图片

delimiter(中间符号) prefix(开始符号)suffix(结束符号)

Java基础学习笔记_第14张图片

3.3创建string对象的两种方式

Java基础学习笔记_第15张图片

4.正则表达式

一种规则

只管格式,不管是否有效

预定义字符

"."表示任意一个字符,没有范围限制

\d:表示任意一个数字,等同于[0-9]

\w:表示任意一个单词字符,等同于a-zA-Z0-9_

\s:表示任意一个空白字符.

\D:表示不是数字

\W:不是单词字符

\S:不是空白字符

[abc] a/b/c出现一次
[abc]+ a/b/c出现一次以上
[abc]* a/b/c出现任意次
[abc]{3} a/b/c出现三次
[abc]{3,4} a/b/c出现最少3次,最多4次
[abc]{3,} a/b/c出现3次以上

常用方法

  • matches方法 用于检测字符串是否匹配给定的正则表达式。
  • split方法 将当前字符串按照满足正则表达式的部分进行拆分,将拆分出的每一段最终以数组的方式返回
  • replaceAll方法 将当前字符串中满足正则表达式的部分替换为给定内容

5.Object类

  • tostring 方法
    返回对象的字符串表示形式

  • equals方法
    判断两个对象内容是否相等

  • finalize方法

6.包装类

java提供了8个包装类,对应8个基本类型

作用:将基本类型以对象的形式表示

  • valueOf方法

要把参数中给的值,转化为对应类型,Integer.valueOf()就是把参数给的值,转化为Integer类型。

  • java推荐我们使用包装类的静态方法valueOf将基本类型转换为对应的包装类对象

  • Integer的valueOf可以重用-128-127之间的整数对象

  • 静态方法:parseInt,parseDouble。。。

将字符串转换为对应的基本类型,前提是:该字符串正确描述了基本类型可以保存的值

        String line = "123";
//        String line = "123  ";注意不能有空格,会有数据格式异常
//        String line = "123.123";//注意,小数不能解析为整数
        int n = Integer.parseInt(line);
        System.out.println(n);

        double d = Double.parseDouble(line);
        System.out.println(dd);

MAX_VALUE,MIN_VALUE获得对应类型的最大最小值

/**
 * 获取int最大最小值
 */
System.out.println("-------------------------");
int max = Integer.MAX_VALUE;
int min = Integer.MIN_VALUE;
System.out.println(min+" "+max);

自动拆装箱

  • 触发了编译器的自动拆箱特性
    编译器会补充代码将包装类转换为基本类型

下面代码会被编译器改为:

int a = new Integer(123);

int a = new Integer(123).intValue();//编译器里执行的代码

  • 触发了编译器的自动装箱特性
    编译器会补充代码将基本类型转换为包装类

下面代码会被编译器改为:

int i = 123;

Integer i = Integer.valueOf(123);//编译器里执行的代码

7.lamdba表达式

  • 可以简化匿名内部类的书写
  • 只能简化函数式接口的匿名内部类得书写方法
  • 函数式接口:有且仅有一个抽象方法的接口

省略规则:

  • 参数类型可以不写
  • 如果只有一个参数,参数类型可以省略不写,()也可以不写
  • 如果方法体只有一行,{},分号,return可以不写,需要同时省略
  • 当lambda表达式的参数和调用方法的参数相同,可以用::代替
Thread t1 = new Thread(() -> f.methodA());

8.File

  1. 访问其表示的文件或者目录的属性
  2. 创建/删除 文件或目录
  3. 访问一个目录中的子项

方法

  • length
  • getName
  • canRead(boolean)
  • canWrite(boolean)
  • isHidden 是否隐藏
  • exists(boolean)是否存在
  • createNewFile
  • mkdir 在当前目录创建目录
  • mkdirs 在当前目录创建多级目录
  • delete 删除目录
  • -isDirectory 判断对象是不是文件夹(目录)
  • isFile 判断对象是不是文件
  • listFiles 获取当前目录下的所有子项
    文件过滤器
FileFilter filter = new FileFilter(){
     public boolean accept(File f) {
     return f.getName().endsWith(".txt");
    }
}
//用文件过滤器获取一个目录中符合条件的所有子项

9.IO流

IO流 (字节流、字符流)-CSDN博客

JAVA IO将流划分为两类:节点流和处理流

  1. 节点流:俗称"低级流",特点:真实连接我们程序和另一端的"管道",负责实际读写数据的流文件流就是典型的节点流,真实连接我们程序与文件的"管道",可以读写文件数据了。
  2. 处理流:俗称"高级流"
    特点:
    • 不能独立存在(单独实例化进行读写操作不可以)
    • 必须连接在其他流上,目的是当数据"流经"当前流时,可以对其做某种加工操作,简化我们的工作、

*流的连接:实际开发中经常会串联一组高级流最终到某个低级流上,对数据进行流水线式的加工读写。

Java中的 InputStream 和 OutputStream 都是 io 包中面向字节操作的顶级抽象类,关于java同步 io字节流的操作都是基于这两个的。

子类:

网络数据传输:SocketInputStream 和 SocketOutputStream

文件操作:FileInputStream 和 FileOutputStream

字节数据操作:DataInputStream 和 DataOutputStream
Java基础学习笔记_第16张图片

进制

int m = 0x4f5e;//0x开头十六进制
int i = 025;//0开头八进制
int n = 0b1101_0010_1111;0b开头十六进制


System.out.println(Integer.toBinaryString(m));//按2进制输出 
System.out.println(Integer.toHexString(n));//按16进制输出
System.out.println(Integer.toString(n));/

文件流(低级流)是字节流的一种

文件流是用来链接我们的程序与文件之间的"管道",用来读写文件数据的流。

  • int read()
    读取一个字节,以int形式返回,该int值的”低八位”有效,若返回值为-1则表示EOF

  • int read(byte[] data)
    尝试最多读取给定数组的length个字节并存入该数组,返回值为实际读取到的字节量。

文件输出流FileOutputStream

构造器

FileOutputStream(String path)
创建文件输出流对指定的path路径表示的文件进行写操作,如果该文件不存在则将其创建出来

FileOutputStream(File file)
创建文件输出流对指定的file对象表示的文件进行写操作,如果该文件不存在则将其创建出来
  • 文件输出流继承自java.io.OutputStream

  • 是连接程序与文件的"管道",负责将字节数据写入文件的流

  • write
  • close 流使用完毕后要关闭释放底层资源
文件输入流FileInputStream

构造器

FileInputStream(String path)
基于给定的路径对应的文件创建文件输入流
    
FileInputStream(File file)
基于给定的File对象所表示的文件创建文件输入流 

文件输入流用于从文件中读取字节数据

文件输入流只能读文件,不能读目录,如果指定的文件或者目录不存在会抛出异常FileNotFoundException

  • read 读到文件末尾返回 int 整数型-1
    文件输出流的两种模式:
  1. 覆盖模式

对应的构造器:

FileOutputStream(String path)

FileOutputStream(File file)

创建文件流时,如果指定的文件已经存在,则会将该文件原有内容清空.

  1. 追加模式

对应的构造器:

FileOutputStream(String path,boolean append)

FileOutputStream(File file,boolean append)

如果第二个参数为true,则创建文件输出流时开启追加模式.

如果指定的文件已经存在,那么原数据都会保留,新写入的内容都会陆续的追加到文件中

单字节复制
FileInputStream fis = new FileInputStream("./image.png");
FileOutputStream fos = new FileOutputStream("./image_copy.png");

int d;
while ((d = fis.read()) != -1){
    fos.write(d);
}

System.out.println("复制完成");
fis.close();
fos.close();

块读写

一次一组字节的读写称为:块读写操作

一次性读取给定字节数组总长度的字节量并存入到该数组中。

返回值为实际读取到的字节数。如果返回值为-1表示本次没有读取到任何字节已经是流的末尾了

void write(byte[] data)
一次性将给定数组中所有字节写出    
void write(byte[] data,int offset,int len)
一次性将data数组中从下标offset处开始的连续len个字节一次性写出  

FileInputStream fis = new FileInputStream("./image.jpg");
FileOutputStream fos = new FileOutputStream("./image_copy.jpg");

int len;
byte[] data = new byte[1024 * 100];
while ((len = fis.read(data)) != -1) {
    fos.write(data,0,len);
}

fis.close();
fos.close();

写出文本数据
FileOutputStream fos = new FileOutputStream("./demo/test2.txt");

String word = "玄武说那斩月刀,刀斩刀哥蹦九霄,你狗屁才艺";
String word2 = "我徒弟呢?害tm带着眼镜呢?我徒弟!啊!团长,我跟你没完,你等我!!!";

byte[] data = (word+word2).getBytes(StandardCharsets.UTF_8);

fos.write(data);
System.out.println("写入完成");

这段代码实现了将字符串数据写入指定文件中的功能。

首先,使用 FileOutputStream 类创建一个能够将数据写入文件的输出流对象 fos,文件路径为 "./demo/test2.txt",如果该文件不存在则会创建一个新文件。

接着,定义了两个字符串变量 word 和 word2,分别存储了两段文本内容。

然后,通过 `(word+word2).getBytes(StandardCharsets.UTF_8)` 将两个字符串拼接起来,并将其转换为字节数组 data,编码字符集使用 UTF-8。
接下来,使用 fos.write(data) 将字节数组 data 中的数据写入文件中。

最后,通过 `System.out.println("写入完成");` 打印输出信息,表示写入操作完成。

这样,内容为两段文本的字符串已经被写入到指定的文件中。
读取文本数据
public static void main(String[] args) throws IOException {
        File file = new File("./demo/ReadString.java");
        FileInputStream fis = new FileInputStream(file);

        byte[] data = new byte[(int) file.length()];
        fis.read(data);

        String s = new String(data, StandardCharsets.UTF_8);

        fis.close();
        System.out.println(s);
    }
}
这段代码实现了从指定文件读取数据并将其存储到字符串中。具体过程为:

首先通过 File 类构造一个指向特定文件的文件对象(这里文件路径为 "./demo/ReadString.java"),然后使用 FileInputStream 类创建一个能够从文件中读取数据的输入流。

接着,为了便于存储读取到的数据,定义了一个字节数组 data,该数组的长度是文件长度(即 `file.length()`),然后使用 fis.read(data) 将文件中的数据读取到 data 数组中。

最后,利用 String 的构造方法将 data 数组中的字节数据转换为字符串 s,编码字符集使用 UTF-8。完成后,变量 s 中就存储了文件中的内容。

缓冲字节流(高级流)

在读写数据时提供内部缓冲区,减少实际的IO操作,提高效率

  • 在流链接中的作用:加快读写效率
  • 通常缓冲是最终链接在低级流上的流
  • 缓冲字节流是一对处理流(高级流),他们的作用是保证读写效率
  • 内部维护一个默认8192长的字节数组(长度可调),通过缓冲流读写时,他们总是以块读写形式进行来保证读写效率

字节缓冲流仅提供缓冲区,真正读写数据还要靠基本字节流对象操作。

字节缓冲流实现复制文件比基本字节流使用块操作快很多。并不是一定快

Java基础学习笔记_第17张图片

//文件复制
FileInputStream fis = new FileInputStream("./image.jpg");
BufferedInputStream bif = new BufferedInputStream(fis);//缓冲字节输出流

FileOutputStream fos = new FileOutputStream("./image_copy.jpg");
BufferedOutputStream bof = new BufferedOutputStream(fos);//缓冲字节输入流

写缓冲问题
  • 缓冲输出流可以提高写出效率,写出数据缺乏即时性。
  • 当需要在执行完某写出操作后,希望将数据确实写出,而非在缓冲区中保存直到缓冲区满后才写出。
  • void flush()
  • 清空缓冲区,将缓冲区中的数据强制写出。
  • 写出数据不足以装满缓存区

解决方法

高级流的flush会掉低级流的flush

  • bos.flush方法 将已缓存的数据一次性写出
  • bos.close方法 缓冲输出流的close方法会执行flush方法,确保将已缓存的数据全部写出
缓冲字节输出流BufferedOutputStream
缓冲字节输入流BufferedInputStream

提供的read方法读取字节数据

该方法返回缓冲区中的下一个字节的整数表示(0-255),当返回值为-1时,表示流读取到了末尾

对象流

把对象信息转换成一个字节序列的过程叫对象序列化,相反叫反序列化

  • 当使用对象流写入或读取对象时,需要保证对象是可序列化的
  • 一个类如果实现了Serializable接口,那么这个类创建的对象就是可序列化的对transient关键字:被该关键字修饰的属性在序列化时其值将被忽略
  • 静态字段不会被序列化
  • 如果一个类可以序列化,它的子类也将可以自动序列化
对象输出流ObjectOutputStream

序列化
Java基础学习笔记_第18张图片

如果要忽略不必要的属性达到瘦身的目的,可以加上transient

在相应的实体类里面加

public static void main(String[] args) throws IOException {
    String name = "张三";
    Integer age = 20;
    
    Person p = new Person(name,age);
    
    FileOutputStream fos = new FileOutputStream("./demo/person.obj");
    ObjectOutputStream oos = new ObjectOutputStream(fos);

    oos.writeObject(p);
    System.out.println("写出完毕");
    oos.close();
对象输入流ObjectInputStream

反序列化

Java基础学习笔记_第19张图片

FileInputStream fos = new FileInputStream("./demo/person.obj");
ObjectInputStream ois = new ObjectInputStream(fos);

Person p2 = (Person) ois.readObject();
System.out.println("读入完毕");
ois.close();

System.out.println(p2);

转换流

字符流的实现类,处于中间环节,衔接字符流与字节流,起到转换器的作用

Java基础学习笔记_第20张图片

转换输出流OutputStringWriter

使用该流可以设置字符集,并按照指定的字符集将字符转换为对应字节后通过该流写出。

public static void main(String[] args) throws IOException {
    String word = "玄武说那斩月刀,刀斩刀哥蹦九霄,你狗屁才艺.....";
    String word2 = "我徒弟呢?害tm带着眼镜呢?我徒弟!啊!团长,我跟你没完,你等我!!!";

    FileOutputStream fos =new FileOutputStream("./demo/test.txt",true);
    OutputStreamWriter osw = new OutputStreamWriter(fos,StandardCharsets.UTF_8//设置字符集);
    osw.write(word);
    osw.write(word2);
        
    System.out.println("写出完毕");

    osw.close();    
转换输入流InputStringWriter

使用该流可以设置字符集,并按照指定的字符集将字节转换为对应字符后通过该流写入。

public static void main(String[] args) throws IOException {
    FileInputStream fis = new FileInputStream("./demo/test.txt");
    InputStreamReader isr = new InputStreamReader(fis);

    int d;
    while ((d=isr.read())!=-1){
        System.out.print((char)d);//读后16位
    }
    isr.close();
}

字符流(都是高级流)

以char为单位读写数据,一次处理一个Unicode

  • 只适合读写文本数据
  • 字符流只能连接在字符流上面使用
缓冲字符输出流BufferWriter

PrintWriter 提供了对文件直接进行连接的构造器,可以直接对文件进行写操作

  • java.io.PrintWriter是常用的缓冲字符输出流

  • 它内部总是链接着BufferedWriter作为缓冲写效率的工作,其还提供了按行写字符串和自动行刷新的功能
    Java基础学习笔记_第21张图片

PrintWriter提供了丰富的重载print与println方法

常用方法:

  • void print(int i):打印整数√void print(char c):打印字符

  • void print(boolean b):打印boolean值

  • void print(char[]c):打印字符数组

  • void print(double d):打印double值

  • void print(float f):打印float值

  • void print(longl):打印long值

  • void print(String str):打印字符串。

上述方法都有println方法

缓冲字符输入流BufferReader

BufferedReader提供了一个可以便于读取一行字符串的方法:String readLine()

该方法返回缓冲区中一行字符串,返回的字符串中不包含该换行符。当返回值为null时,表示流读取到了末尾

组建流链接

public static void main(String[] args) throws FileNotFoundException {
    //文件流(低级流,字节流),作用:负责将写出的字节写入文件中
    FileOutputStream fos = new FileOutputStream("pw2.txt");
    
    //转换流(高级流,字符流),作用:起到"转换器"的作用,负责衔接其他字符流与字节流
    OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
    
    //缓冲字符流(高级流,字符流),作用:内部维护一个默认8192长的char数组,以块写操作保证写出效率
    BufferedWriter bw = new BufferedWriter(osw);
    
    //PrintWriter(高级流,字符流),作用:按行写出字符串,具有自动行刷新功能
    PrintWriter pw = new PrintWriter(bw);

    System.out.println("请输入数据:");
    Scanner sc = new Scanner(System.in);

    while (true){
        String data = sc.nextLine();
        if ("exit".equals(data)){
            break;
        }
        pw.println(data);
        //pw.flush();//自动刷新
    }
    System.out.println("写入完成");
    pw.close();
}
    
}

  PrintWriter pw = new PrintWriter(bw,true);//开启自动刷新后,可以不用调用pw.flush()方法

异常

Java基础学习笔记_第22张图片

try…catch…finally

  • 如果try中出现异常,则剩余代码不会在执行
  • catch时父类异常要写在子类异常下面
  • finally的用法
    • 跟在catch后面
    • 跟在try后面
    • finally不应写return,否则上面return均失效
String s = null;

    try {
        System.out.println(s.length());
        System.out.println(s.charAt(0));
        
        System.out.println("西西物者为俊杰");
    }catch (NullPointerException | IndexOutOfBoundsException e){
        System.out.println("字符串为空,西西物者为俊杰");
        System.out.println("数组下标越界");
    }catch (Exception e){
        System.out.println("111");
    }
    finally {
        System.out.println("处理异常");
    }
    System.out.println("执行完毕");
}

java自动关闭特性

Java基础学习笔记_第23张图片

不必调用close方法,因为所有流都实现了AutoCloseable接口

throw

  • 不要在main方法上throws
  • 用于主动抛出异常
  • 除了RuntimeException(或者其子类异常)之外,用throw抛出异常都必须在方法上使用throws声明该异常的抛出以达到通知调用者的目的。
  • throws声明抛出的异常可以是这里实际throw的异常类型或者它的超类型异常.
public void setAge(int age) throws Exception {
    if (age<0 || age >100){
        throw new Exception("年龄不合法");
    }
    this.age = age;
}

- 调用一个含有 throws声明异常抛出 的方法时,编译器要求必须处理该异常,否则编译器不通过,而处理方式有两种:
- 在当前方法上继续使用throws声明该异常的抛出,给上层调用者处理,上层调用者为main方法时不能throws
- 主动使用try…catch捕获并处理异常

public static void main(String[] args) {
    Person p = new Person();
    p.setName("张三");
    try {
        p.setAge(101);
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println(p);
}
  • 子类继承超类后,子类重写超类含有throws声明异常抛出的方法时,对throws重写规则
    • 可以

      • 可以不抛异常
      • 可以抛父类声明中的部分异常
      • 可以抛父类异常的子类异常
    • 不可以

      • 不可以抛出额外异常(超类没有声明的,且不存在继承关系的异常)
      • 不可以抛出超类方法中抛出异常的父类异常。

自定义异常

  • 类名见名知意
  • 继承自Exception
  • 提供超类所有构造器

Socket

Socket封装了TCP协议的通讯细节,使用它可以与服务端建立连接,并基于两条流的IO操作与服务端进行数据的可靠传输。

实例化Socket的过程就是与服务端建立连接的过程。

三次握手和四次挥手是在 TCP(传输控制协议)中用于建立和终止连接的过程。

三次握手(Three-Way Handshake)是建立 TCP 连接的过程,具体如下:

  1. 客户端向服务器发送一个 SYN(同步)报文段,其中包含客户端的初始序列号(Client Sequence Number)。

  2. 服务器收到 SYN 报文段后,会回复一个 SYN-ACK(同步-确认)报文段,其中包含服务器的初始序列号(Server Sequence Number),同时也确认了客户端的初始序列号。

  3. 客户端收到 SYN-ACK 报文段后,会发送一个 ACK(确认)报文段给服务器,确认了服务器的初始序列号。此时,TCP 连接已经建立起来,可以进行数据的传输。

四次挥手(Four-Way Handshake)是终止 TCP 连接的过程,具体如下:

  1. 客户端发送一个 FIN(结束)报文段给服务器,表示不再发送数据。

  2. 服务器收到 FIN 报文段后,会发送一个 ACK 报文段作为确认。此时,服务器进入了 CLOSE_WAIT 状态,表示准备关闭连接,但仍然可以接收客户端发送的数据。

  3. 当服务器不再需要连接时,会发送一个 FIN 报文段给客户端。

  4. 客户端接收到服务器的 FIN 报文段后,会发送一个 ACK 报文段作为确认。此时,客户端进入了 TIME_WAIT 状态,等待一段时间后才关闭连接,以确保服务器收到最终的确认。

需要注意的是,在四次挥手过程中,服务器可以立即关闭连接,而客户端需要经过 TIME_WAIT 状态。这是为了防止已经关闭的连接上出现延迟的数据包,确保完整地传输所有数据。

三次握手和四次挥手过程的设计是为了保证数据的可靠传输和双方连接的正常终止。

Client客户端

public class Client {
    /*
        java.net.Socket 套接字
        Socket封装了TCP协议的通讯细节,使用它可以与服务端建立连接,并基于两条流的IO操作与服务端
        进行数据的可靠传输.
     */
    private Socket socket;
    //构造方法用于初始化客户端
    public Client(){
        try {
            System.out.println("正在连接服务端...");
            /*
                实例化Socket的过程就是与服务端建立连接的过程
                参数1:服务端计算机的IP地址,如果连接本机可以使用"localhost"
                参数2:服务端程序打开的端口
                如果连接失败会抛出异常
             */
            socket = new Socket("localhost",8088);
            System.out.println("服务端已连接");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    //start方法用于让客户端开始工作
    public void start(){
    }

    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }
}

Server服务端

public class Server {
    /**
     * 运行在服务端的java.net.ServerSocket
     * 它的主要工作:
     * 1:向操作系统申请服务端口
     * 2:监听服务端口,等待客户端的连接
     *
     * 如果我们把Socket比喻为电话插座,那么ServerSocket相当于是"总机"
     */
    private ServerSocket serverSocket;
    //构造器用来初始化服务端
    public Server(){
        try {
            System.out.println("正在启动服务端...");
            /*
                ServerSocket实例化时要指定对外的服务端口,客户端创建Socket时就是通过这个端口
                与服务端建立连接的.
                注意,该端口不能与服务器计算机其他运行的程序申请的端口一致,否则会抛出端口被占用
                的异常:java.net.BindException:address already in use
                如果出现需要更换端口,或者将占用该端口的程序结束.
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //start方法用来让服务端开始工作
    public void start(){
        try {
            System.out.println("等待客户端连接...");
            Socket socket = serverSocket.accept();
            System.out.println("一个客户端连接了");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

}

Java基础学习笔记_第24张图片
Java基础学习笔记_第25张图片
Java基础学习笔记_第26张图片
Java基础学习笔记_第27张图片

多线程

是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。

并发和并行【理解】
并行:在同一时刻,有多个指令在多个CPU上同时执行。
并发:在同一时刻,有多个指令在单个CPU上交替执行。

进程和线程【理解】

  • 进程:是正在运行的程序
    • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
    • 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
    • 并发性:任何进程都可以同其他进程一起并发执行
  • 线程:是进程中的单个顺序控制流,是一条执行路径
    • 单线程:一个进程如果只有一条执行路径,则称为单线程程序
    • 多线程:一个进程如果有多条执行路径,则称为多线程程序

实现多线程方式一:继承Thread类【应用】

方法介绍
方法名

  • void run()
    在线程开启后,此方法将被调用执行

  • void start()
    使此线程开始执行,Java虚拟机会调用run方法()

实现步骤
定义一个类MyThread继承Thread类
在MyThread类中重写run()方法
创建MyThread类的对象
启动线程
代码演示
Java基础学习笔记_第28张图片

两个小问题

  • 为什么要重写run()方法?
    因为run()是用来封装被线程执行的代码
  • run()方法和start()方法的区别?
    run():封装线程执行的代码,直接调用,相当于普通方法的调用
    start():启动线程;然后由JVM调用此线程的run()方法

实现多线程方式二:实现Runnable接口【应用】

Thread构造方法
方法名

  • Thread(Runnable target)
    分配一个新的Thread对象
  • Thread(Runnable target, String name)
    分配一个新的Thread对象

实现步骤

  • 定义一个类MyRunnable实现Runnable接口
  • 在MyRunnable类中重写run()方法
    -创建MyRunnable类的对象
  • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
  • 启动线程
    Java基础学习笔记_第29张图片

实现多线程方式三: 实现Callable接口【应用】

方法介绍
方法名

说明

  • V call()
    计算结果,如果无法计算结果,则抛出一个异常

  • FutureTask(Callable callable)
    创建一个 FutureTask,一旦运行就执行给定的 Callable

  • V get()
    如有必要,等待计算完成,然后获取其结果

实现步骤

  • 定义一个类MyCallable实现Callable接口
  • 在MyCallable类中重写call()方法
  • 创建MyCallable类的对象
  • 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
  • 创建Thread类的对象,把FutureTask对象作为构造方法的参数
  • 启动线程
  • 再调用get方法,就可以获取线程结束之后的结果。
    Java基础学习笔记_第30张图片

三种实现方式的对比

实现Runnable、Callable接口
好处: 扩展性强,实现该接口的同时还可以继承其他的类
缺点: 编程相对复杂,不能直接使用Thread类中的方法
继承Thread类
好处: 编程比较简单,可以直接使用Thread类中的方法
缺点: 可以扩展性较差,不能再继承其他的类
继承Thread和实现Runnable是实现多线程的两种常见方式。

  1. 继承Thread:
    • 优点:继承Thread类可以直接重写Thread类的run()方法,使得代码结构相对简单,适合简单的多线程场景。
    • 缺点:由于Java只支持单继承,如果已经继承了其他类,则无法再继承Thread类。此外,由于线程与线程之间是独立的对象,因此无法共享数据。
  2. 实现Runnable接口:
    • 优点:实现Runnable接口可以避免单继承的限制,因为Java允许一个类实现多个接口。同时,实现Runnable接口将线程的任务与线程的执行分离开来,使得代码结构更加清晰,便于代码的维护和扩展。
    • 缺点:相对于继承Thread类,实现Runnable接口需要额外创建一个Thread对象,并将实现了Runnable接口的对象作为参数传递给Thread对象。此外,由于实现Runnable接口的对象并不是线程对象,因此无法直接调用Thread类的一些方法。
      总结:

继承Thread类适合简单的多线程场景,代码结构相对简单,但受到单继承的限制。
实现Runnable接口适合复杂的多线程场景,代码结构清晰,便于维护和扩展,并能避免单继承的限制。
方法

设置和获取线程名称【应用】

方法介绍
方法名

  • void setName(String name)
    将此线程的名称更改为等于参数name

  • String getName()
    返回此线程的名称

  • Thread currentThread()
    返回对当前正在执行的线程对象的引用

代码演示

public class MyThread extends Thread {
    public MyThread() {}
    public MyThread(String name) {
        super(name);
    }@Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();//void setName(String name):将此线程的名称更改为等于参数 name
        my1.setName("高铁");
        my2.setName("飞机");//Thread(String name)
        MyThread my1 = new MyThread("高铁");
        MyThread my2 = new MyThread("飞机");
​
        my1.start();
        my2.start();//static Thread currentThread() 返回对当前正在执行的线程对象的引用
        System.out.println(Thread.currentThread().getName());
    }
}

线程休眠【应用】

sleep方法要求必须处理中断异常InterruptedException

  • 当一个线程调用sleep方法处于睡眠阻塞的过程中,此时该线程的interrupt方法被调用,那么就会立即

  • 中断其睡眠阻塞,此时sleep方法会立即抛出中断异常

方法名

  • static void sleep(long millis)
    使当前正在执行的线程停留(暂停执行)指定的毫秒数

代码演示

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        /*System.out.println("睡觉前");
        Thread.sleep(3000);
        System.out.println("睡醒了");*/MyRunnable mr = new MyRunnable();Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
​
        t1.start();
        t2.start();
    }
}

线程中断

线程优先级【应用】

  • 线程调度
    两种调度方式
    • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
    • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
  • Java使用的是抢占式调度模型
  • 随机性

假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的

优先级相关方法
方法名

  • final int getPriority()
    返回此线程的优先级

  • final void setPriority(int newPriority)
    更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10

min.setPriority(Thread.MAX_PRIORITY);
max.setPriority(Thread.MIN_PRIORITY);
norm.setPriority(Thread.NORM_PRIORITY);

代码演示

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
        return "线程执行完毕了";
    }
}
public class Demo {
    public static void main(String[] args) {
        //优先级: 1 - 10 默认值:5
        MyCallable mc = new MyCallable();FutureTask<String> ft = new FutureTask<>(mc);Thread t1 = new Thread(ft);
        t1.setName("飞机");
        t1.setPriority(10);
        //System.out.println(t1.getPriority());//5
        t1.start();MyCallable mc2 = new MyCallable();FutureTask<String> ft2 = new FutureTask<>(mc2);Thread t2 = new Thread(ft2);
        t2.setName("坦克");
        t2.setPriority(1);
        //System.out.println(t2.getPriority());//5
        t2.start();
    }
}

守护线程【应用】

  • 我们正常创建出来的线程称为"用户线程"或"前台线程".守护线程是通过用户线程调用setDaemon(true)在

  • 启动前设置转变而来的.守护线程一般也可以称为后台线程.

  • 用户线程和守护线程的区别体现在进程结束时: 当进程中所有用户线程都结束时,进程就会结束,结束前会无差别杀死所有还在运行的守护线程

  • GC就是运行在守护线程上的
    方法名

  • void setDaemon(boolean on)
    将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

代码演示

public class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();
​
        t1.setName("女神");
        t2.setName("备胎");//把第二个线程设置为守护线程
        //当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
        t2.setDaemon(true);
​
        t1.start();
        t2.start();
    }
}

多线程并发安全问题

同步代码块解决数据安全问题
  • 安全问题出现的条件
    是多线程环境
    有共享数据
    有多条语句操作共享数据
  • 如何解决多线程安全问题呢?
    基本思想:让程序没有安全问题的环境
  • 怎么实现呢?
    • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
    • Java提供了同步代码块的方式来解决
同步方法的格式

同步方法:就是把synchronized关键字加到方法上

修饰符 synchronized 返回值类型 方法名(方法参数) { 方法体; }

同步方法的锁对象是什么呢?

synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
如果是synchronized(this),锁的是当前对象

同步的好处和弊端

  • 好处:解决了多线程的数据安全问题
  • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
静态同步方法

同步静态方法:就是把synchronized关键字加到静态方法上
静态方法上如果指定synchronized,那么该方法一定具有同步效果

修饰符 static synchronized 返回值类型 方法名(方法参数) { 方法体; }

同步静态方法的锁对象是当前类的类对象(Class的实例)

类名.class

代码演示

public class MyRunnable implements Runnable {
    private static int ticketCount = 100;@Override
    public void run() {
        while(true){
            if("窗口一".equals(Thread.currentThread().getName())){
                //同步方法
                boolean result = synchronizedMthod();
                if(result){
                    break;
                }
            }if("窗口二".equals(Thread.currentThread().getName())){
                //同步代码块
                synchronized (MyRunnable.class){
                    if(ticketCount == 0){
                       break;
                    }else{
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        ticketCount--;
                        System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
                    }
                }
            }}
    }private static synchronized boolean synchronizedMthod() {
        if(ticketCount == 0){
            return true;
        }else{
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticketCount--;
            System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
            return false;
        }
    }
}

public class Demo {      
   public static void main(String[] args) {          
      MyRunnable mr = new MyRunnable();

      Thread t1 = new Thread(mr);
      Thread t2 = new Thread(mr);
​
      t1.setName("窗口一");
      t2.setName("窗口二");
​
      t1.start();
      t2.start();
  }
同步代码块

有效的缩小同步范围可以在保证并发安全的前提下提高并发效率

同步块可以更准确的控制需要多个线程同步执行的代码片段

synchronized(任意对象) { 
    多条语句操作共享数据的代码 
}

synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

  • 同步的好处和弊端
    • 好处:解决了多线程的数据安全问题
    • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

代码演示

public class SellTicket implements Runnable {
    private int tickets = 100;
    private Object obj = new Object();@Override
    public void run() {
        while (true) {
            synchronized (obj) { // 对可能有安全问题的代码加锁,多个线程必须使用同一把锁
                //t1进来后,就会把这段代码给锁起来
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                        //t1休息100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //窗口1正在出售第100张票
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--; //tickets = 99;
                }
            }
            //t1出来了,这段代码的锁就被释放了
        }
    }
}public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");
​
        t1.start();
        t2.start();
        t3.start();
    }
}

Lock锁【应用】

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

ReentrantLock构造方法
方法名

  • ReentrantLock()
    创建一个ReentrantLock的实例

加锁解锁方法
方法名

  • void lock()
    获得锁

  • void unlock()
    释放锁

死锁【理解】

概述
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

  • 什么情况下会产生死锁
    • 资源有限
    • 同步嵌套

什么是死锁,产⽣死锁的四个条件

(1) 互斥条件:⼀个资源每次只能被⼀个进程使⽤。

(2) 请求与保持条件:⼀个进程因请求资源⽽阻塞时,对已获得的资源保持不放。

(3) 不剥夺条件:进程已获得的资源,在末使⽤完之前,不能强⾏剥夺。

(4) 循环等待条件:若⼲进程之间形成⼀种头尾相接的循环等待资源关系。

这四个条件是死锁的必要条件,只要系统发⽣死锁,这些条件必然成⽴,⽽只要上述条件之⼀不 满⾜,就不会发⽣死锁。

生产者和消费者模式概述【应用】

  • 概述
    生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。

所谓生产者消费者问题,实际上主要是包含了两类线程:

一类是生产者线程用于生产数据

一类是消费者线程用于消费数据

为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为

消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为

  • Object类的等待和唤醒方法
    方法名

  • void wait()
    导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法

  • void notify()
    唤醒正在等待对象监视器的单个线程

  • void notifyAll()
    唤醒正在等待对象监视器的所有线程

线程池

线程池是一种线程管理机制,它包含了固定数量的线程,用于执行任务或处理请求,以避免频繁地创建和销毁线程,从而提高系统的性能和效率。
以下是线程池的一些关键知识点:
1.线程池的组成:线程池通常由线程池管理器、工作队列和一组工作线程组成。线程池管理器用于创建、销毁和管理线程池;工作队列用于存储待执行的任务;工作线程则负责从工作队列中取出任务并执行。
2.线程池的优点:线程池具有以下几个优点:

  • 重用线程:线程池中的线程可重复利用,避免了频繁创建和销毁线程的开销。
  • 控制线程数量:线程池可以限制同时执行的线程数量,避免线程过多导致系统资源不足。
  • 提高响应速度:线程池可以快速分配可用线程来处理任务,提高响应速度和系统吞吐量。

3.线程池的工作流程:线程池的工作流程包括以下几个步骤:

  • 当有任务到达时,线程池管理器会根据策略判断是否接受任务。
  • 如果接受任务,线程池管理器会从工作队列中取出一个任务,并将其分配给一个空闲线程。
  • 线程执行任务,执行完成后线程返回线程池并等待下一个任务。
  • 如果工作队列为空且没有空闲线程,则新建一个线程执行任务。
  • 当线程池关闭时,线程池管理器会等待所有线程完成当前任务后关闭。

4.线程池的参数配置:线程池通常可以配置以下参数:

  • 核心线程数:线程池中保持的线程数量,即使线程是空闲的。
  • 最大线程数:线程池能容纳的最大线程数量。
  • 工作队列:用于存储待执行任务的队列。
  • 线程存活时间:在线程池中空闲线程的存活时间。
  • 拒绝策略:当工作队列已满且无法接受新任务时,决定如何处理新任务的策略。

5.线程池的实现方式:线程池的实现方式有多种,包括 Java 中的 Executor 框架、C++11 中的 ThreadPool 和 Boost.Thread 等。

6.线程池的主要问题:线程池虽然可以提高系统性能,但需要充分考虑线程池的一些主要问题,如:线程任务的正确性和稳定性、线程池的任务调度和拒绝策略、线程池的线程安全性等。

7.常见的线程池拒绝策略:当线程池中的工作队列已满时,线程池可能无法接受新的任务,此时通常会采取一些拒绝策略,如:

  • DiscardPolicy:丢弃任务并不抛出任何异常。
  • AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常。
  • CallerRunsPolicy:直接在调用线程中执行被拒绝的任务。
  • DiscardOldestPolicy:丢弃工作队列中最旧的任务,然后尝试重新提交新的任务。

8.案例应用:线程池在并发编程中被广泛应用。例如,在 Web 服务器中,线程池可以处理客户端请求,避免每次请求都创建新线程导致的性能问题。另外,一些计算密集型任务,如图像处理、加密解密等,也可以通过线程池来并行执行,提高计算速度。
总之,线程池是一种重要的并发编程技术,可以有效管理和执行多线程任务,提高系统性能和稳定性。但需要充分考虑线程池的问题和合理配置线程池参数,以确保线程池的正确性和高效性。

反射

反射是java的动态机制,允许程序在运行期间确定对象实例化,方法调用,属性操作。提高了代码的灵活度,但是运行效率慢,开销大,不能过度依赖。

利用反射创建的对象可以无视修饰符调用类里面的内容

可以跟配置文件结合起来使用,把要创建的对象信息和方法写在配置文件中。

  • 读取到什么类,就创建什么类的对象
  • 读取到什么方法,就调用什么方法
  • 此时当需求变更的时候不需要修改代码,只要修改配置文件即可。
    java文件:就是我们自己编写的java代码。

字节码文件:就是通过java文件编译之后的class文件(是在硬盘上真实存在的,用眼睛能看到的)

字节码文件对象:当class文件加载到内存之后,虚拟机自动创建出来的对象。

​这个对象里面至少包含了:构造方法,成员变量,成员方法。

而我们的反射获取的是什么?字节码文件对象,这个对象在内存中是唯一的。

获取类对象的方法

获取类的java.lang.Class实例对象,常见的三种方式分别为:

  • 通过MyClass.class获取,这里的MyClass指具体类
    • 通过MyClass.class获取,JVM会使用ClassLoader类加载器将类加载到内存中,但并不会做任何类的初始化工作,返回java.lang.Class对象
  • 通过Class.forName(“类的全局定名”)获取,全局定名为包名+类名
    • 通过Class.forName(“类的全局定名”)获取,同样,类会被JVM加载到内存中,并且会进行类的静态初始化工作,返回java.lang.Class对象
  • 通过new MyClass().getClass()获取,这里的MyClass指具体类
    • 通过new MyClass().getClass()获取,这种方式使用了new进行实例化操作,因此静态初始化和非静态初始化工作都会进行,getClass方法属于顶级Object类中的方法,任何子类对象都可以调用,哪个子类调用,就返回那个子类的java.lang.Class对象

反射机制实例化

public static void main(String[] args) throws ClassNotFoundException {
    Class c1 = ReflectDemo1.class;

    System.out.println("请输入类名:");
    Scanner sc = new Scanner(System.in);
    String className = sc.nextLine();
    c1 = Class.forName(className);

    System.out.println("完全限定名:"+c1.getName());
    System.out.println("类名:"+c1.getSimpleName());
    System.out.println("包信息:"+c1.getPackage());
    System.out.println("包名:"+c1.getPackage().getName());
}

获取构造方法

Constructor类(构造器对象)也是反射对象之一,他的每一个实例用于表示一个构造器

//加载类对象
Class c1 = Class.forName("zpb.reflect.Person");
//通过对象获取特定的构造器
//无参构造器,等效于Class中的newInstance(),但是如果构造器抛出特定异常此方式也对应抛出
Constructor constructor = c1.getConstructor();//Person()
//含参
Constructor constructor = c1.getConstructor(String.class,int.class);//Person()
Object o = constructor.newInstance("李四",20);//new Person()
System.out.println(o);

以下是对每句代码的详细分析:

  1. Class c1 = Class.forName(“zpb.reflect.Person”);
    这一行代码通过Class.forName()方法加载类对象zpb.reflect.Person。Class.forName()是一个静态方法,它返回一个表示指定类名的Class对象。在这里,我们加载了名为zpb.reflect.Person的类。

  2. Constructor constructor = c1.getConstructor();
    这一行代码通过getConstructor()方法获取无参构造器。getConstructor()方法用于获取指定类中的公共(public)无参构造器,并返回一个Constructor对象。在这里,我们获取了Person类的无参构造器,并将其赋值给constructor变量。

  3. Constructor constructor = c1.getConstructor(String.class, int.class);
    这一行代码通过getConstructor()方法获取含有两个参数(一个字符串类型和一个整数类型)的构造器。与上一行代码类似,我们使用getConstructor()方法获取了Person类的特定构造器,并将其赋值给constructor变量。

  4. Object o = constructor.newInstance(“李四”, 20);
    这一行代码通过调用newInstance()方法创建一个新的Person对象。newInstance()方法是通过构造器对象来创建一个新的实例,可以传递相应的参数值来调用特定的构造器。在这里,我们调用了含有两个参数的构造器,并传递了字符串"李四"和整数20作为参数值,然后将返回的新实例赋值给o变量。

  5. System.out.println(o);
    **这一行代码打印输出变量o的值。System.out.println()方法用于将指定的内容输出到控制台。**在这里,我们输出了变量o的值,即通过构造器创建的Person对象。

以上就是对每句代码的详细分析。这段代码的作用是加载类对象、获取特定的构造器,并使用构造器创建一个新的实例。最后,将创建的实例输出到控制台。

规则:
get表示获取
Declared表示私有
最后的s表示所有,复数形式

如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用

方法名

  • Constructor[] getConstructors()
    获得所有的构造(只能public修饰)

  • Constructor[] getDeclaredConstructors()
    获得所有的构造(包含private修饰)

  • Constructor getConstructor(Class… parameterTypes)
    获取指定构造(只能public修饰)

  • Constructor getDeclaredConstructor(Class… parameterTypes)
    获取指定构造(包含private修饰)

获取成员变量

规则:
get表示获取
Declared表示私有
最后的s表示所有,复数形式

如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用

方法名:

  • Field[] getFields()
    返回所有成员变量对象的数组(只能拿public的)

  • Field[] getDeclaredFields()
    返回所有成员变量对象的数组,存在就能拿到

  • Field getField(String name)
    返回单个成员变量对象(只能拿public的)

  • Field getDeclaredField(String name)
    返回单个成员变量对象,存在就能拿到

获取成员方法

规则:
get表示获取
Declared表示私有
最后的s表示所有,复数形式

如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用

方法名

  • Method[] getMethods()
    返回所有成员方法对象的数组(只能拿public的)

  • Method[] getDeclaredMethods()
    返回所有成员方法对象的数组,存在就能拿到,只能获取本类自己定义的方法,不含超类继承的方法。

  • Method getMethod(String name, Class… parameterTypes)
    返回单个成员方法对象(只能拿public的)

  • Method getDeclaredMethod(String name, Class… parameterTypes)
    返回单个成员方法对象,存在就能拿到

获取所有的方法
Class c1 = Class.forName(zpb.reflect.Person);
Method[] arr = c1.getMethods();
Method[] arr2 = c1.getDeclaredMethods();

for (Method m:arr){
    System.out.println("所有public修饰的方法"+m.getName());
}

for (Method m:arr2){
    System.out.println("所有方法"+m.getName());
}
获取单个方法信息
Class c1 = Class.forName(zpb.reflect.Person);
Method[] arr = c1.getMethods();
Method[] arr2 = c1.getDeclaredMethods();
for (Method m:arr){
    System.out.println("所有public修饰的方法"+m.getName());
}
for (Method m:arr2){
    System.out.println("所有方法"+m.getName());
}
调用并执行单个方法
Class c1 = Class.forName(className);
Object o = c1.newInstance();

Method method = c1.getDeclaredMethod(methodName);//获取对应方法
//Method method = c1.getDeclaredMethod(methodName,String.class,int.class);//有参方法要加上对应的参数类型的类

method.setAccessible(true);//设置权限,强行打开访问权限
method.invoke(o//,如果有实参,要传);//执行方法

  • method.setAccessible(true);
    反射机制访问类的私有成员,设置权限,强行打开访问权限
类加载路径和当前类路径

Java基础学习笔记_第31张图片

注解(Annotation)

注解必须先定义后使用

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoRunMethod {
    int value() default 1;
}

注解中常用的元数据:

@Target

用于指定当前注解可以被应用的位置

ElementType指定了相应的位置,常见的有:

  • ElementType.TYPE:类上
  • ElementType.FIELD:属性上
  • ElementType.METHOD:方法上
  • ElementType.CONSTRUCTOR:构造器上

@Target(ElementType.TYPE):当前注解仅能在类上使用

@Target({ElementType.TYPE,ElementType.CONSTRUCTOR}):当前注解可以在类上或构造器上使用

@Retention

@Retention:指定注解的保留策略

  • 注解是否会被编译器保留到编译后的类文件中,以及在运行时是否可以通过反射获取注 解信息
  • 使用枚举类RetentionPolicy来指定保留级别,它有三个取值:
    • SOURCE(源代码保留,编译时被丢弃)
    • CLASS(编译期保留)
    • RUNTIME(运行期保留,可通过反射获取)
  • 如果没有指定@Retention,则默认为CLASS
  • 在反射机制使用时必须是RUNTIME级别(在反射里调用isAnnotationPresent(注解名.class)方法来判断类是否被注解时)
    • 类对象.isAnnotationPresent(注解名.class) 判断该类是否有注解
    • 方法对象.isAnnotationPresent(注解名.class) 判断该方法是否有注解

所有的反射对象都支持该方法:
boolean isAnnotationPresent(Class cls)
用于判断当前反射对象表示的内容是否被指定注解标注

注解传参

package reflect.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoRunMethod {
    /*
        注解可以定义参数,格式为:
        类型 参数名() [default 默认值]      注:默认值是可选项,不指定时使用注解必须传参

        例如:
        @AutoRunMethod(3)   那么此时该注解参数value的值为3
        @AutoRunMethod      那么此时该注解value使用默认值1


        -------------------------------------------------------------
        如果注解只有一个参数时,推荐的参数名为"value"



        -------------------------------------------------------------
        注解的传参机制
        例如:
        public @interface AutoRunMethod {
            int age()
        }

        当注解仅有一个参数,且参数名不为value时,正常使用注解传参语法:参数名=参数值
        举例:
        在Person类的方法sayHello上使用该注解,并指定参数:

        @AutoRunMethod(age=3)           此时必须写"作参数名=参数值"
        public void sayHello(){
            System.out.println(name+":hello!");
        }

        @AutoRunMethod(3)               编译不通过,因为参数没有指定参数名
        public void sayHello(){
            System.out.println(name+":hello!");
        }


        如果注解仅有一个参数时,参数名使用value,则使用注解可以忽略参数名:
        public @interface AutoRunMethod {
            int value()
        }

        使用时:
        @AutoRunMethod(3)               可以
        public void sayHello(){
            System.out.println(name+":hello!");
        }

        -------------------------------------------------------------
        注解可以声明多个参数
        例如:
        public @interface AutoRunMethod {
            int age() default 1;
            String name();
        }

        当注解有多个参数时,使用该注解时每个参数都需要使用:参数名=参数值
        例如:
        @AutoRunMethod(age=2,name="张三")
        public void sayHello(){
            System.out.println(name+":hello!");
        }

        实际使用中多个参数传参顺序可以与注解定义时参数顺序不一致
        @AutoRunMethod(age=2,name="张三")
        public void sayHello(){
            System.out.println(name+":hello!");
        }
        或
        @AutoRunMethod(name="张三",age=2)
        public void sayHello(){
            System.out.println(name+":hello!");
        }

  ------------------------------------------------------------------------
        当注解有多个参数时,就算其中一个注解取名为value,实际使用时参数名也不可以忽略!
        例如:
        public @interface AutoRunMethod {
            int value();
            String name();
        }

        使用时:
        @AutoRunMethod(name="张三",value=2)       可以
        @AutoRunMethod(value=2,name="张三")       可以
        @AutoRunMethod(name="张三",2)             不可以
        @AutoRunMethod(2,name="张三")             不可以


        参数指定默认值,仍然在使用时可以忽略
        public @interface AutoRunMethod {
            int value() default 1;
            String name();
        }

        @AutoRunMethod(name="张三")       可以
     */
    int value() default 1;
}

获取注解里的参数

AutoRunMethod arm = method.getAnnotation(AutoRunMethod.class);
int value = arm.value();
System.out.println(value);

JVM

内存管理

由JVM来管理的-------------了解即可

  • 堆:new出来的对象(包括实例变量、数组的元素、方法的地址)
  • 栈:局部变量(包括方法的参数)
    方法区:.class字节码文件(包括所有方法、静态变量)

垃圾回收机制(可靠)

  • JVM垃圾回收机制. JVM垃圾回收机制是自动的
    • 这是Java的特性之一,在其它编程语言中可能需要你手动处理
    • 当某个对象不再被引用,则会被视为垃圾,后续,垃圾回收机制会清理该对象占用的内存空间(具体细节暂不讨论)
    • 它执行规则人为不可控
      • 一定时执行;
      • 一可用内存不足时执行;
      • 接到通知后执行;
    • 你可以通过system. gc ()语句通知JVM执行回收
      • 该语句仅通知,JVM将知道你希望执行垃圾回收,但何时执行仍由JVM决定

内存泄漏,内存溢出

由于不严谨的代码和错误的操作导致滴
内存泄漏----->内存溢出

内存泄漏与内存溢出

  • 首先,JVM的垃圾回收机制是可靠的;
  • 内存泄漏和内存溢出均是由于不严谨的代码或错误的操作导致的!
  • 理想的情况下,不再使用的对象应该被视为垃圾,将会被回收,如果这样的对象没有被视为垃圾,它将继续占用内存空间,则视为内存泄漏;
  • 如果内存泄漏持续发生,导致内存里的对象越来越多(尽量有很多是不再使用的),最终内存中实际存储的数据量会超出可用内存的上限,则是内存溢出。
    少量的内存泄漏并不会有明显的危害

避免内存泄漏

  • 为避免出现内存泄漏,应该注意:
  • 当某对象不再需要使用时,及时将其变量重新赋值为null-仅全局变量
  • 需要注意多层引用的数据,例如数组
  • 所有连接外部资源的对象都有释放资源的方法,不再需要使用时及时调用-即使是局部变量
  • 如果你需要使用try…catch来捕获某些异常,在try代码块中使用到了连接外部资源的对象,请在finally代码块中进行释放

你可能感兴趣的:(学习笔记,java,学习,笔记)