先赞后看,养成习惯!!!❤️ ❤️ ❤️
码字不易,如果喜欢可以关注我哦!
如果本篇学习笔记对你有所启发,欢迎访问我的个人博客了解更多内容:链接地址
变量:存数的
float,double ==> int (错误)
两种方式:
在Java中,点操作符(.)和括号操作符(())的优先级最高。这意味着在表达式中,点操作符和括号操作符的计算会首先进行,然后才是其他操作符。
例如,当一个对象调用其成员方法时,点操作符用于指定对象和方法之间的关系,而括号操作符用于传递参数给方法。
下面是一些常见的操作符按照优先级从高到低的顺序:
括号操作符:()
点操作符:.
前缀操作符:++, --, !, ~
乘法和除法操作符:*, /, %
加法和减法操作符:+, -
移位操作符:>, >>>
关系操作符:, =, instanceof
相等操作符:==, !=
位与操作符:&
位异或操作符:^
位或操作符:|
逻辑与操作符:&&
逻辑或操作符:||
条件操作符:?:
赋值操作符:=, +=, -=, *=, /=, %=, >=, >>>=, &=, ^=, |=
请注意,以上仅为一般情况下的操作符优先级,具体的优先级还可以通过使用括号来明确指定。
一维数组是一种线性数据结构,由相同类型的元素按照一定顺序排列而成的数据结构。它是最简单的数据结构之一,也是很多其他数据结构的基础。
一维数组可以存储多个相同类型的元素,这些元素在内存中是连续存储的。数组的每个元素通过索引来访问,索引从0开始,依次递增。数组的大小(即元素个数)在创建时确定,并且不能动态改变。
例如,一个整数类型的一维数组可以表示为:
int[] numbers = {1, 2, 3, 4, 5};
在上述示例中,numbers 是一个整数类型的一维数组,包含了5个元素。可以通过索引来访问数组的元素,例如 numbers[0]
表示第一个元素的值为1。
一维数组常用于存储一系列的数据,可以进行遍历、查找、排序等操作。通过数组的索引,可以高效地访问和修改元素。需要注意的是,数组的大小在创建时确定,因此在使用数组时需要预先知道需要存储的元素个数,以避免数组越界或浪费内存空间。
除了一维数组,还有多维数组(如二维数组、三维数组等)和动态数组等数据结构,可以根据具体情况选择合适的数据结构来存储和处理数据。
二维数组在内存中的存储方式
定义的同时赋值
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 是一种 Java 中的可变大小的动态数组类,具有以下特点:
动态大小:Vector 可以根据需要动态调整其大小,可以根据元素的添加或删除自动调整数组的长度。
线程安全:Vector 是线程安全的,多个线程可以同时访问 Vector 对象而不会导致数据不一致的问题。它使用同步机制来确保线程安全性,但这也会导致一定的性能损失。
元素顺序:Vector 保持了元素的插入顺序,即元素的顺序与被添加到 Vector 中的顺序相同。
可以包含任意类型的元素:Vector 可以保存任意类型的对象,包括基本类型的包装类和自定义的对象。
支持随机访问:通过索引可以直接访问 Vector 中的元素,类似于数组的访问方式,可以使用 get() 方法通过索引获取元素。
扩展性:当 Vector 的容量不足时,其容量会自动扩展,可以通过调整初始容量和扩展因子来控制扩展的行为。
需要注意的是,由于 Vector 是线程安全的,因此在性能要求较高的场景下,可以考虑使用 ArrayList 代替 Vector,因为 ArrayList 在非多线程环境下具有更好的性能表现。
if
if…else
if…else if
switch…case
循环三要素:------------------------非常重要
2.三种循环结构如何选择:
break:跳出循环 continue:跳过循环体中剩余语句而进入下一次循环
嵌套循环:
增强for循环(新循环,forEach)可以遍历集合也可以遍历数组
遍历集合时采用的是迭代器进行遍历,
遍历数组时则不是采用普通for循环进行遍历,而是编译器将其转换为普通for循环进行遍历。
如何得到对象
public class 对象名{
成员变量(代表属性)
成员方法(代表方法)
类名 对象名 = new 类名()
}
给成员变量赋值,在创建对象之后赋值
当成员变量与局部变量同名时,若想访问成员变量,则this不能省略
局部内部类、成员内部类和匿名内部类是Java中三种不同的内部类形式,它们在定义和使用上有一些区别。
总的来说,局部内部类用于方法内部的辅助类,成员内部类用于外部类内的独立类,而匿名内部类则是一种简洁的定义和实例化同时进行的方式。开发者应根据具体需求选择合适的内部类形式。
隐式的引用:外部类名.this
外部类.this.成员变量
外部类.this.成员方法
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() {
// 执行语句
}
};
用来检查其他类是否书写正确。带有main方法的类,是程序的入口
Javabean类―用来描述一类事物的类。比如,Student,Teacher,Dog,Cat等
实际开发一般用private和public(成员变量私有,方法公开)
this:指代当前对象,哪个对象调用方法它指的就是哪个对象
关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被static修饰的成员和方法是属于类的是放在静态区中,没有static修饰的成员变量和方法则是属于对象的。我们上面案例中的成员变量都是没有static修饰的,所以属于每个对象。
有static修饰成员变量,说明这个成员变量是属于类的,这个成员变量称为类变量或者静态成员变量。 直接用 类名访问即可。因为类只有一个,所以静态成员变量在内存区域中也只存在一份。所有的对象都可以共享这个变量。
如何使用呢
例如现在我们需要定义传智全部的学生类,那么这些学生类的对象的学校属性应该都是“传智”,这个时候我们可以把这个属性定义成static修饰的静态成员变量。
定义格式
修饰符 static 数据类型 变量名 = 初始值;
举例
public class Student {
public static String schoolName = "传智播客"; // 属于类,只有一份。
// ..... }
静态成员变量的访问:
格式:类名.静态变量
共享的数据设置静态变量
静态代码块: 格式:static{} 特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次 优先加载 使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。
成员变量分为两种:
1.实例变量 :无static修饰,属于对象,由对象访问,存储在堆栈中,有几个对象就有几份
2.静态变量:有static修饰,属于类,由类名来访问
适用范围:如果有业务和对象无关,不需要访问对象的属性,可以设置成静态方法
在创建对象时给成员变量进行赋值
在创建对象的时候,给成员变量进行初始化。
初始化即赋值的意思。
构造器的特征
构造器的作用:创建对象;给对象进行初始化
根据参数不同,构造器可以分为如下两类:
注意:
对象代表什么就得封装对应数据,并提供数据对应的行为
把零散的数据分装成一个整体,这就是对象
当对象越来越多时,把同一类的共性内容抽取出来放到父类person里,子类student和tecaher可以访问父类里非私有的成员
继承:
作用:代码复用
通过extends实现继承
超类/基类/父类:共有的属性和行为
派生类/子类:特有的属性和行为
派生类可以访问:超类的+派生类的,超类不能访问派生类的
一个超类可以有多个派生类,一个派生类只能有一个超类---------单一继承
具有传递性
java规定:构造派生类之前必须先构造超类
注意:super()调用超类构造方法,必须位于派生类构造方法中的第1行
就是子类继承父类的属性和行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
格式:
public class 子类 extends 父类 { }
子类不能继承父类的构造方法。
值得注意的是子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量。
super.父类成员变量名
当超类成员变量和派生类成员变量同名时,super指超类的,this指派生类的若没有同名现象,则写super和this是一样的
方法重写:发生在子父类之间的关系。子类继承了父类的方法,但是子类觉得父类的这方法不足以满足自己的需求,子类重新写了一个与父类同名的方法,以便覆盖父类的该方法。也称为重写或者复写。声明不变,重新实现。
方法的重写(override/overriding):重新写、覆盖
在Java中,方法的重载发生在以下情况之一:
方法名相同,但参数列表不同(包括参数数量、类型或顺序的差异)。
对于重载的方法,它们的返回值可以相同也可以不同。重载方法的返回值类型不是识别方法重载的因素,而是方法执行后的返回结果。因此,重载的方法可以有相同的返回值类型,也可以有不同的返回值类型。
需要注意的是,只有参数列表不同才能构成重载,返回值不同不能构成重载。同时,重载函数的函数体可以相同或不同
final:最终的、不能改变的------------单独应用几率低
同类型的对象表现出不同的形态
是指同一行为,具有多个不同表现形式。
从上面案例可以看出,Cat和Dog都是动物,都是吃这一行为,但是出现的效果(表现形式)是不一样的。
有继承父类
有父类引用指向子类对象(向上造型)
有方法重写
格式:
父类类型 对象名称 = 子类对象()
多态中调用成员变量和成员方法时
成员变量:编译看左边,运行看左边
成员方法:编译看左边,运行看右边
在多态形势下,右边对象可以实现解耦合,便于拓展和维护
定义方法的时候,使用父类作为参数,可以接收所有子类对象,体现多态的拓展性与便利
弊端是不能调用子类特有的功能,但是变回子类类型就ok(强制类型转换:可以转换成真正的子类类型,从而调用子类独有的功能)
Anime a = new Dog();
a.eat();
Dog d = (Dog) a;
1 代码的可扩展性
使用多态可以有效地使代码扩展性更强。例如,增加一个实现一个新的接口,只需要实现这个接口即可,对原有代码的修改很少,这使得程序的可扩展性更加强大。
提高代码的复用性
面向对象编程的一个核心思想就是复用已有代码,而多态是实现代码复用的有效手段。减少相似代码的重复,提高代码的复用性和可靠性。
代码的可维护性
使用多态的代码结构更加清晰,模块化,更易于日后代码的维护和修改。
实现程序的动态性
如果不使用多态,在程序中需要大量使用类型判断或者具体类实例化来实现对象之间的不同操作,而使用多态则能够使得程序的动态性更高。
总之,多态是面向对象编程的一个重要理念,可以提高代码质量、复用性和可维护性,实现程序的灵活性和动态性。
我们把没有方法体的,由abstract修饰的方法称为抽象方法。
Java语法规定,包含抽象方法的类就是抽象类。
abstract是抽象的意思,用于修饰方法方法和类,修饰的方法是抽象方法,修饰的类是抽象类。
使用abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。
定义格式:
修饰符 abstract 返回值类型 方法名 (参数列表);
代码举例:
public abstract void run();
如果一个类包含抽象方法,那么该类必须是抽象类。
定义格式:
abstract class 类名字 { }
代码举例:
public abstract class Animal {
public abstract void run();
}
格式:
抽象方法 public abstract 返回值类型 方法名(参数列表)
没有方法体的方法是抽象方法
抽象方法所在的类必须是抽象类
抽象类 public abstract class 类名 { }
注意事项
抽象类的特征总结起来可以说是有得有失
有得:抽象类得到了拥有抽象方法的能力。
有失:抽象类失去了创建对象的能力。
其他成员(构造方法,实例方法,静态方法等)抽象类都是具备的。
关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
抽象类
不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
理解:抽象类中已经实现的是模板中确定的成员,抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现
接口是更加彻底的抽象,JDK7之前,包括JDK7,接口中全部是抽象方法。接口同样是不能创建对象的。
只能包含抽象方法(常量、默认方法、静态方法、私有方法------暂时搁置)
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字。
原因:子类如果能继承多个父类,如果多个父类中存在同名属性或者方法,子类继承时将不能判断继承自哪个父类的属性和方法,所以类之间不能多继承。
Java中,接口与接口之间是可以多继承的:也就是一个接口可以同时继承多个接口。大家一定要注意
接口的抽象方法没有具体的方法体,多接口即使拥有同名的抽象方法,类在实现时只需重写一个方法即可。
接口与接口是继承关系
接口继承接口就是把其他接口的抽象方法与本接口进行了合并。
继承关系,可以单继承,也可以多继承
原因:接口中的方法均为抽象方法,没有具体实现的方法体,所以在多继承的情况下,即使方法同名,也不会出现类多继承那样的矛盾。
关于接口的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的。
继承的父类,就好比是亲爸爸一样 实现的接口,就好比是干爹一样 可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。
实现类能不能继承一个抽象类的时候,同时实现其他接口呢?
实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写。
实现类Zi,实现了一个接口,还继承了一个Fu类。假设在接口中有一个方法,父类中也有一个相同的方法。子类如何操作呢?
处理办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。
处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。
可以在接口跟实现类中间,新建一个中间类(适配器类) 让这个适配器类去实现接口,对接口里面的所有的方法做空重写。
让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。 因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象
实现多态
多态是面向对象编程的一个重要特性,它允许不同类的对象对相同的消息作出不同的响应。通过接口,可以实现多态的效果,具体实现如下:
javaCopy Codepublic interface Animal {
void makeSound();
}
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!");
}
}
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 访问修饰符,所以外部无法调用。
枚举既可以包含具体方法,也可以包含抽象方法。 如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它。
JDK5之后推出的另一个特性:泛型
泛型也称为参数化类型,允许我们在使用一个类时指定它当中属性,方法参数或返回值的类型.
边界定义
//为方法单独定义一个泛型,且指定类型的
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;
}
存放引用类型元素,因此集合中的元素保存的是地址
常用方法:
c.add(123);//这里的123是自动装箱后再添加
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);
常用方法:
list:
get(int index)方法 获取集合里面对应下标的元素,获取到的数据不能强转
set方法 E set(int index, E e) 将给定元素设置到指定位置并替换该位置元素,并将被替换的元素返回
一对重载的add,remove方法
add 将给定元素插入到指定位置add(index,内容)
remove 删除并返回指定位置上的元素
subList 获取指定下标范围内的子集,两个下标参数
List.toArray 把集合转换成一个数组
Arrays.asList 把数组转换成一个集合
Collections.sort 对List进行自然排序(要求集合里的元素是可比较的,比较标准:元素是否实现了compare接口)
List是接口,ArrayList是它的实现类
ArrayList底层是数组,查询快(get,contain)增删(add,remove)慢。
遍历方式
迭代器遍历
forEach遍历(增强for循环遍历集合时采用的是迭代器进行遍历)
for循环遍历,size方法
问题
1. Array和Arrays的区别?
2. Collection 和 Collections 区别?
Set 接口实例存储的是无序的,不重复的数据。
Set 检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 。
键值对**[key-value]**的形式存数据
方法:
Set<String> keySet = map.keySet();
for(String key:keySet){
System.out.println(key);
}
Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
for (Map.Entry<String,Integer> entry:entrySet){
System.out.println(entry.getKey()+" "+entry.getValue());
}
迭代器接口规定使用迭代器遍历集合的通用操作,不同的集合都提供了一个用于遍历自身元素的迭代器实现类
迭代器遍历集合的过程: 遵循:问->取->删
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
用来在类,方法,常量,构造器上定义,说明整体功能
c.add(123);//这里的123会自动装箱后,再添加
字符串常量池
在jvm堆内存里创建一个字符串常量池,第一次直接给出字面量会把这个字面量放到字符常量池里面,后续如果有相同的字面量赋值,会先读取常量池里的数据,如果有相同的字符串,直接拿出来用。
String s1 = "abc";
String s2 = "abc";
String s5 = new String();
s5="1123" String s4 = s1+"!";
比较引用数据类型 比的是地址值
两个对象的equals()相等,hashCode()一定相等。
两个对象的hashCode()相等,equals()不一定相等
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");
任何类型的数据和字符串类型的数据做相加操作时,其他类型的数据会自动转化成字符串类型
String s6 = 1+2+"abc"+1+2; //输出结果为3abc12
String s6 = "abc"+"123"; //替换为
String s6 = "abc123";
eplace和replaceAll是JAVA中常用的替换字符的方法,它们的区别是:
相同点是都是全部替换,即把源字符串中的某一字符或字符串全部换成指定的字符或字符串,如果只想替换第一次出现的,可以使用 replaceFirst(),这个方法也是基于规则表达式的替换,但与replaceAll()不同的是,只替换第一次出现的字符串;
另外,如果replaceAll()和replaceFirst()所用的参数据不是基于规则表达式的,则与replace()替换字符串的效果是一样的,即这两者也支持字符串的操作;
由于String是不变对象,每次修改内容都要创建新对象,因此String不适合做频繁修改操作.为了解决这个问题,java提供了StringBuilder类.
StringBuilder可以看成是一个容器,创建之后里面的内容是可变的
作用:提高字符串的操作效率
StringBuilder是非线程安全,不可并发处理,性能稍快。
StringBuffer是线程安全,可以并发处理,性能稍慢。
StringBuilder是专门用来修改String的一个API,内部维护一个可变的char数组,修改都是在这个数组上进行的, 内部会自动扩容.修改速度和性能开销优异.并且提供了修改字符串的常见操作对应的方法:增删改插
//添加
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);
构造方法
常用方法
delimiter(中间符号) prefix(开始符号)suffix(结束符号)
一种规则
只管格式,不管是否有效
预定义字符
"."表示任意一个字符,没有范围限制
\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次以上
常用方法
tostring 方法
返回对象的字符串表示形式
equals方法
判断两个对象内容是否相等
finalize方法
java提供了8个包装类,对应8个基本类型
作用:将基本类型以对象的形式表示
要把参数中给的值,转化为对应类型,Integer.valueOf()就是把参数给的值,转化为Integer类型。
java推荐我们使用包装类的静态方法valueOf将基本类型转换为对应的包装类对象
Integer的valueOf可以重用-128-127之间的整数对象
将字符串转换为对应的基本类型,前提是:该字符串正确描述了基本类型可以保存的值
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);//编译器里执行的代码
省略规则:
Thread t1 = new Thread(() -> f.methodA());
方法
FileFilter filter = new FileFilter(){
public boolean accept(File f) {
return f.getName().endsWith(".txt");
}
}
//用文件过滤器获取一个目录中符合条件的所有子项
IO流 (字节流、字符流)-CSDN博客
JAVA IO将流划分为两类:节点流和处理流
*流的连接:实际开发中经常会串联一组高级流最终到某个低级流上,对数据进行流水线式的加工读写。
Java中的 InputStream 和 OutputStream 都是 io 包中面向字节操作的顶级抽象类,关于java同步 io字节流的操作都是基于这两个的。
子类:
网络数据传输:SocketInputStream 和 SocketOutputStream
文件操作:FileInputStream 和 FileOutputStream
字节数据操作:DataInputStream 和 DataOutputStream
进制
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(String path)
创建文件输出流对指定的path路径表示的文件进行写操作,如果该文件不存在则将其创建出来
FileOutputStream(File file)
创建文件输出流对指定的file对象表示的文件进行写操作,如果该文件不存在则将其创建出来
文件输出流继承自java.io.OutputStream
是连接程序与文件的"管道",负责将字节数据写入文件的流
构造器
FileInputStream(String path)
基于给定的路径对应的文件创建文件输入流
FileInputStream(File file)
基于给定的File对象所表示的文件创建文件输入流
文件输入流用于从文件中读取字节数据
文件输入流只能读文件,不能读目录,如果指定的文件或者目录不存在会抛出异常FileNotFoundException
对应的构造器:
FileOutputStream(String path)
FileOutputStream(File file)
创建文件流时,如果指定的文件已经存在,则会将该文件原有内容清空.
对应的构造器:
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操作,提高效率
字节缓冲流仅提供缓冲区,真正读写数据还要靠基本字节流对象操作。
字节缓冲流实现复制文件比基本字节流使用块操作快很多。并不是一定快
//文件复制
FileInputStream fis = new FileInputStream("./image.jpg");
BufferedInputStream bif = new BufferedInputStream(fis);//缓冲字节输出流
FileOutputStream fos = new FileOutputStream("./image_copy.jpg");
BufferedOutputStream bof = new BufferedOutputStream(fos);//缓冲字节输入流
解决方法
高级流的flush会掉低级流的flush
提供的read方法读取字节数据
该方法返回缓冲区中的下一个字节的整数表示(0-255),当返回值为-1时,表示流读取到了末尾
把对象信息转换成一个字节序列的过程叫对象序列化,相反叫反序列化
如果要忽略不必要的属性达到瘦身的目的,可以加上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();
反序列化
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);
字符流的实现类,处于中间环节,衔接字符流与字节流,起到转换器的作用
使用该流可以设置字符集,并按照指定的字符集将字符转换为对应字节后通过该流写出。
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();
使用该流可以设置字符集,并按照指定的字符集将字节转换为对应字符后通过该流写入。
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
PrintWriter 提供了对文件直接进行连接的构造器,可以直接对文件进行写操作
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方法
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()方法
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自动关闭特性
不必调用close方法,因为所有流都实现了AutoCloseable接口
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);
}
可以
不可以
Socket封装了TCP协议的通讯细节,使用它可以与服务端建立连接,并基于两条流的IO操作与服务端进行数据的可靠传输。
实例化Socket的过程就是与服务端建立连接的过程。
三次握手和四次挥手是在 TCP(传输控制协议)中用于建立和终止连接的过程。
三次握手(Three-Way Handshake)是建立 TCP 连接的过程,具体如下:
客户端向服务器发送一个 SYN(同步)报文段,其中包含客户端的初始序列号(Client Sequence Number)。
服务器收到 SYN 报文段后,会回复一个 SYN-ACK(同步-确认)报文段,其中包含服务器的初始序列号(Server Sequence Number),同时也确认了客户端的初始序列号。
客户端收到 SYN-ACK 报文段后,会发送一个 ACK(确认)报文段给服务器,确认了服务器的初始序列号。此时,TCP 连接已经建立起来,可以进行数据的传输。
四次挥手(Four-Way Handshake)是终止 TCP 连接的过程,具体如下:
客户端发送一个 FIN(结束)报文段给服务器,表示不再发送数据。
服务器收到 FIN 报文段后,会发送一个 ACK 报文段作为确认。此时,服务器进入了 CLOSE_WAIT 状态,表示准备关闭连接,但仍然可以接收客户端发送的数据。
当服务器不再需要连接时,会发送一个 FIN 报文段给客户端。
客户端接收到服务器的 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();
}
}
是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。
并发和并行【理解】
并行:在同一时刻,有多个指令在多个CPU上同时执行。
并发:在同一时刻,有多个指令在单个CPU上交替执行。
进程和线程【理解】
方法介绍
方法名
void run()
在线程开启后,此方法将被调用执行
void start()
使此线程开始执行,Java虚拟机会调用run方法()
实现步骤
定义一个类MyThread继承Thread类
在MyThread类中重写run()方法
创建MyThread类的对象
启动线程
代码演示
两个小问题
Thread构造方法
方法名
实现步骤
方法介绍
方法名
说明
V call()
计算结果,如果无法计算结果,则抛出一个异常
FutureTask(Callable callable)
创建一个 FutureTask,一旦运行就执行给定的 Callable
V get()
如有必要,等待计算完成,然后获取其结果
实现步骤
实现Runnable、Callable接口
好处: 扩展性强,实现该接口的同时还可以继承其他的类
缺点: 编程相对复杂,不能直接使用Thread类中的方法
继承Thread类
好处: 编程比较简单,可以直接使用Thread类中的方法
缺点: 可以扩展性较差,不能再继承其他的类
继承Thread和实现Runnable是实现多线程的两种常见方式。
继承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方法会立即抛出中断异常
方法名
代码演示
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的使用权是不一定的
优先级相关方法
方法名
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();
}
}
同步方法:就是把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();
}
}
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock是接口不能直接实例化,这里采用它的实现类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.常见的线程池拒绝策略:当线程池中的工作队列已满时,线程池可能无法接受新的任务,此时通常会采取一些拒绝策略,如:
8.案例应用:线程池在并发编程中被广泛应用。例如,在 Web 服务器中,线程池可以处理客户端请求,避免每次请求都创建新线程导致的性能问题。另外,一些计算密集型任务,如图像处理、加密解密等,也可以通过线程池来并行执行,提高计算速度。
总之,线程池是一种重要的并发编程技术,可以有效管理和执行多线程任务,提高系统性能和稳定性。但需要充分考虑线程池的问题和合理配置线程池参数,以确保线程池的正确性和高效性。
反射是java的动态机制,允许程序在运行期间确定对象实例化,方法调用,属性操作。提高了代码的灵活度,但是运行效率慢,开销大,不能过度依赖。
利用反射创建的对象可以无视修饰符调用类里面的内容
可以跟配置文件结合起来使用,把要创建的对象信息和方法写在配置文件中。
字节码文件:就是通过java文件编译之后的class文件(是在硬盘上真实存在的,用眼睛能看到的)
字节码文件对象:当class文件加载到内存之后,虚拟机自动创建出来的对象。
这个对象里面至少包含了:构造方法,成员变量,成员方法。
而我们的反射获取的是什么?字节码文件对象,这个对象在内存中是唯一的。
获取类的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);
以下是对每句代码的详细分析:
Class c1 = Class.forName(“zpb.reflect.Person”);
这一行代码通过Class.forName()方法加载类对象zpb.reflect.Person。Class.forName()是一个静态方法,它返回一个表示指定类名的Class对象。在这里,我们加载了名为zpb.reflect.Person的类。
Constructor constructor = c1.getConstructor();
这一行代码通过getConstructor()方法获取无参构造器。getConstructor()方法用于获取指定类中的公共(public)无参构造器,并返回一个Constructor对象。在这里,我们获取了Person类的无参构造器,并将其赋值给constructor变量。
Constructor constructor = c1.getConstructor(String.class, int.class);
这一行代码通过getConstructor()方法获取含有两个参数(一个字符串类型和一个整数类型)的构造器。与上一行代码类似,我们使用getConstructor()方法获取了Person类的特定构造器,并将其赋值给constructor变量。
Object o = constructor.newInstance(“李四”, 20);
这一行代码通过调用newInstance()方法创建一个新的Person对象。newInstance()方法是通过构造器对象来创建一个新的实例,可以传递相应的参数值来调用特定的构造器。在这里,我们调用了含有两个参数的构造器,并传递了字符串"李四"和整数20作为参数值,然后将返回的新实例赋值给o变量。
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//,如果有实参,要传);//执行方法
注解必须先定义后使用
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoRunMethod {
int value() default 1;
}
用于指定当前注解可以被应用的位置
ElementType指定了相应的位置,常见的有:
@Target(ElementType.TYPE):当前注解仅能在类上使用
@Target({ElementType.TYPE,ElementType.CONSTRUCTOR}):当前注解可以在类上或构造器上使用
@Retention:指定注解的保留策略
所有的反射对象都支持该方法:
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来管理的-------------了解即可
由于不严谨的代码和错误的操作导致滴
内存泄漏----->内存溢出
内存泄漏与内存溢出