7.11
一.学习基本类型
1.学习搭建java环境以及java中的基本类型: 基本类型一共有八种:
- 整型有byte 1字节 int 4字节 short 2字节 long 8字节
- 浮点型有 folat 4字节 double 8字节
- 还有布尔型boolean 1或4字节(单独使用四个字节,数组中使用一个字节)
- char字符型。
- 布尔型用于流程的控制
- 布尔表达式(Boolean expression)是一段代码声明,它最终只有true和false两种取值 关于Java的从键盘上获取值:
Scanner scanner = new Scanner(System.in); //指从键盘上获取值,假设获取的是整数
int num1 = scanner.nextInt(); //定义一个整型变量num1,使它获取刚刚键盘输入的值
二.学习字符集
Unicode可以包含所有字符集(2个字节) Char类型采用的是Unicode字符集
- 转义字符
三.Switch语句和while语句
- Switch是一个控制语句中的关键字特别适合做多值判断
while循环的语法流程:while(布尔表达式){ 循环体; }
while循环流程图
四.基础运算符
7.12
五.学习循环嵌套
九九乘法表:
for(int n=1;n<=9;n++){
for(int m=1;m<=n;m++){
System.out.print("m+""+n+"="+(mn)+"\t");*
}
System.out.println();
}
运行结果:
六.break和continue
break代表强行结束整个循环结构
continue代表结束本次循环,继续下次循环
七.重载
重载的方法,实际是完全不同的方法,只是名称相同。
7.13
八.类的定义
九.构造方法
构造方法的核心作用是:用于对象的初始化,Java通过new关键字来调用构造器,它是一种特殊的方法。
1.构造器通过new关键字调用
2.构造器不能定义返回值类型(返回值的类型肯定是本类),不能在构造器里使用return返回某个值。
3.如果我们没有定义构造器,则编译器会自动定义一个无参的构造方法。如果已定义则编译器不会自动添加!
4.构造器的方法名必须和类名一致!
十.创建对象
想要创建一个对象要分为四个步骤:
1.分配对象空间,并将对象成员变量初始化为0或空。
2.执行属性值的显示初始化(如int x=100;)。
3.执行构造方法,初始化对象。
4.返回对象的地址给相关的变量。
十一.虚拟机栈、堆、方法区
栈:
堆:
方法区:
十二.this
this的本质是什么?
this的本质是"当前对象的地址"
普通方法和构造方法中this分别指向什么?
普通方法中,this指向调用该方法的对象 构造方法中,this指向正要初始化的对象
十三.static
static本质是一个关键字,使用static声明的变量为静态成员变量,也被称为类变量。
static成员变量和方法的字节码存储在JVM的方法区中。
static的特点有什么?
1.从属于类,只有一份,在类被载入时被显式初始化
2.一般用"类名.类属性/方法"来调用
3.在static方法中不可直接访问非static成员
十四.静态初始化块
- 构造方法用于对象的初始化.
- 静态初始化块,用于类的初始化操作
- 可以初始化static属性
- 在静态初始化块中不能直接访问非static成员
十五.package
在Java中,package 是一个关键字,它用于将类、接口、枚举和注解等组织成组,这些组被称为包(Packages)。包的主要目的是提供命名空间的管理,帮助避免命名冲突,控制访问级别,以及实现模块化。(文心一言 (baidu.com))
package在java中的作用类似于"操作系统中文件夹的作用".那package主要解决了什么问题?
多个类的管理以及类的重名问题
如何处理"同名类"?
com.baoming.test.tongming tm =new com.baoming.test.tm();
如上,只需在类名前增加包名区分即可.
十六.JDK中的主要包
哪一个包中的代码,不需要导入就可以直接使用?
Java.lang核心包
十六(1).继承
继承是一种面向对象编程(OOP)的核心概念,它允许我们创建一个新的类(称为子类或派生类),这个新类可以继承另一个类(称为父类或基类)的属性和方法。
a.类之间的继承关系是单一继承,即一个类只能直接继承一个父类;而一个类可以实现多个接口。
b.继承关系中,子类可以继承父类的属性和方法,还可以重写或增加新的方法;接口中的方法只是声明,并没有具体实现,实现接口的类需要实现接口中的方法。
c.一个类如何不被继承? 用finally修饰 或用私有化构造器
十七.import和instanceof
import的用法
如果我们要使用其他包的类,需要用import导入,从而可以在本类中直接通过类名来调用,否则就需要书写完整包名和类名。
JAVA会默认导入JAVA.LANG包下的所有类,因此这些类我们可以直接使用。
如果导入两个同名的类,只能用包名+类名来显示调用相关类。
判断对象是否是这个类的实例对象 用instanceof
System.out.println(stu1 instanceof Student);
System.out.println(stu1 instanceof Person);
十八.final类
final修饰方法则这个方法不能被子类重写
final修饰类则这个类不能被继承
总之 final不管修饰什么 修饰后都不能再被改变
final修饰变量:常量
final修饰方法:不可重写
final修饰类:不能被继承
7.14
十九.组合
组合的核心实现原理是什么?
组合的核心就是"将父类对象作为子类的属性",然后,子类通过调用这个属性来获得父类的属性和方法
二十.多态和转型
多态的要点 多态指的是同一个方法调用,由于对象不同可能会有不同的行为
- 1.多态是方法的多态,不是属性的多态(多态与属性无关)。
- 2.多态的存在有3个必要条件:继承,方法重写,父类引用指向子类对象。
- 3.父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了
二十一.抽象方法和抽象类
抽象方法:
- 使用abstract修饰的方法,没有方法体,只有声明。
- 定义的是一种"规范",就是告诉子类必须要给抽象方法提供具体的实现。
抽象类:
- 包含抽象方法的类就是抽象类。
- 通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。
- 有抽象方法的类只能定义成抽象类。
- 抽象类不能实例化,即不能用new来实例化抽象类。
- 抽象类可以包含属性、方法、构造方法。但是构造方法不能用来new实力,只能用来被子类调用。
- 抽象类只能用来被继承。
- 抽象方法必须被子类实现。
package com.itbaizhan.polymorphism;
//抽象类
public abstract class Animal {
//抽象方法
public abstract void shout();
public void sleep(){
System.out.println("Animal.sleep");
}
public static void main(String[] args) {
Dog d = new Dog();
d.shout();
// Animal a = new Animal(); //抽象类不能实例化
}
}
class Dog extends Animal{
public void shout(){ //方法的重写
System.out.println("Dog.shout");
}
class Cat extends Animal{
@Override
public void shout() { //方法的重写
System.out.println("Cat.shout");
}
}
}
抽象类的应用:
package com.itbaizhan.abstractClass;
//模板方法模式(抽象类的应用)
public abstract class DBOperator {
//1.建立连接 2.打开数据库 3.使用数据库 4.关闭连接
public abstract void connection();
public void open() {
System.out.println("打开数据库");
}
public void use() {
System.out.println("使用数据库");
}
public void close() {
System.out.println("关闭连接");
}
public void process() {
connection();
open();
use();
close();
}
public static void main(String[] args) {
// new MySqlOperator().process();
new OracleOperator().process();
}
}
class MySqlOperator extends DBOperator {
@Override
public void connection() {
System.out.println("建立和Mysql数据库的连接");
}
}
class OracleOperator extends DBOperator{
@Override
public void connection() {
System.out.println("建立和Oracle数据库的连接");
}
}
二十二.接口 (不太懂)
抽象类和接口的区别:
- 抽象类只能单继承,但接口可以多继承
- 抽象类有构造器,但接口没有
不在乎具体的实现,但会有具体的方法
抽象类
在Java中,接口(Interface)是一种引用类型,是一种抽象的类型,它是方法声明的集合。 接口不能定义变量,只能定义常量。
- 接口全面的实现了:规范和具体的分离。
- 普通类:具体实现
- 抽象类:具体实现,规范(抽象方法)
- 接口:规范
如何定义和使用接口 声明格式:
[访问修饰符]interface 接口名[extends 父接口1,父接口2..]{
常量定义;
方法定义;
}
使用接口说明:
- 子类通过implements来实现接口中的规范。
- 接口不能创建实例,但可用于声明引用变量类型。
- 一共类实现了接口,必须实现接口中的所有方法,并且这些方法只能是public的
- JDK1.8(不含8)之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。
- JDK1.8(含8)后,接口中包含普通的静态方法、默认方法。
接口的应用代码示例:
public class Testinterface {
public static void main(String[] args) {
Volant a = new Angel();
a.fly();
Honest h =new Angel();
h.helpOthers();
}
}
/**
* 飞行接口
*/
interface Volant{
int FLY_HEIGHT = 100; // public static final
void fly(); // public abstrac void fly();
}
/**
* 善良接口
*/
interface Honest{
void helpOthers();
}
class Angel implements Volant,Honest{
@Override
public void helpOthers() {
System.out.println("我是老奶奶");
}
@Override
public void fly() {
System.out.println("我是天使");
}
}
class BirdMan implements Volant{
public void fly(){
System.out.println("我是鸟人");
}
}
接口的多继承代码示例:
/**
* 测试接口多继承
*/
public class TestInterface2 implements c {
@Override
public void TestC() {
System.out.println("12321");
}
@Override
public void TestA() {
System.out.println("213213");
}
@Override
public void TestB() {
System.out.println("asdwqe");
}
public static void main(String[] args) {
c C = new TestInterface2();
C.TestC();
C.TestA();
C.TestB();
}
}
interface A{
void TestA();
}
interface B{
void TestB();
}
interface c extends A,B{
void TestC();
}
接口的默认方法:
- 默认方法用default关键字,一个接口中可以有多个默认方法。默认方法也叫扩展方法。
- 子类实现接口时,可以直接调用接口中的默认方法,也可以重写。
接口的静态方法
- 接口中同时还可以定义静态方法,静态方法通过接口名调用。
- 如果实现类中定义了相同名字的静态方法,那就是完全不同的方法了,直接从属于实现类。通过类名直接调用。
/**
* 测试接口的:默认方法、静态方法
*/
public class TestInterface3 {
public static void main(String[] args) {
TestA ta= new TestA();
ta.moren1();
A1.staticMethod();//通过接口名称A1直接调用
TestA.staticMethod();//调用了实现类的静态方法
}
}
interface A1 {
default void moren1() {
System.out.println("A1.moren1");
}
default void moren2() {
System.out.println("A1.moren2");
}
static void staticMethod(){
System.out.println("A1.staticMethod");
}
}
class TestA implements A1 {
public void moren1(){
System.out.println("TestA.moren1");
}
static void staticMethod(){
System.out.println("A1.staticMethod");
}
}
二十三.内部类(非静态内部类 静态内部类 匿名内部类 局部内部类)
内部类是一类特殊的类,指的是定义在一个类的内部的类,内部类可以直接访问外部类的私有属性,内部类被当成其外部类的成员。但外部类不能访问内部类的内部属性。
非静态内部类:
package com.itbaizhan.innerClass;
public class Outer1 {
private int age =18;
public void show(){
System.out.println("外部类.age:"+age);
}
//内部类,定义在外部类Outer1里面
//非静态内部类不能有静态的方法、静态的属性、静态初始化块
class Inner1{
private int id =10001;
private int age =28;
public void test1(){
System.out.println("Inner1.test1");
System.out.println("内部类的属性,age:"+this.age);
System.out.println("外部类的属性,age:"+Outer1.this.age);
Outer1.this.show();
}
}
}
package com.itbaizhan.innerClass;
public class TestInnerClass1 {
public static void main(String[] args) {
//非静态内部类的对象必须寄存在一个外部类对象里
//先创建外部类的对象,然后使用外部类对象创建内部类对象
Outer1.Inner1 inner1 = new Outer1().new Inner1();
inner1.test1();
}
}
静态内部类:
定义方法:
static class ClassName{
//类体
}
要点:静态内部类可以访问外部类的静态成员,不能访问外部类的普通成员
静态内部类可以看作外部类的一个静态成员
package com.itbaizhan.innerClass;
/**
* 测试静态内部类
*/
public class Outer2 {
private int a =10;
private static int b =20;//static 静态属性定义变量
//静态内部类
static class Inner2{
public void test(){
// System.out.println(a); //静态内部类不能访问外部类的普通属性/方法
System.out.println(b); //静态内部类可以访问外部类的静态属性/方法
}
}
}
package com.itbaizhan.innerClass;
/**
* 测试调用静态内部类
*/
public class TestStaticInnerClass {
public static void main(String[] args) {
//通过new 外部类名.内部类名()来创建静态内部雷达对象
Outer2.Inner2 inner2 = new Outer2.Inner2();
inner2.test();
}
}
匿名内部类
适合只需要使用一次的类。比如:键盘监听操作等等。在安卓开发awt swing GUI(用户界面编程)里开发中常见
package com.itbaizhan.innerClass;
/**
* 测试匿名内部类
*/
public class TestAnonymousInnerClass {
public void test(A3 a){
System.out.println("TestAnonymousInnerClass.test");
a.run();
}
public static void main(String[] args) {
TestAnonymousInnerClass tc =new TestAnonymousInnerClass();
//匿名内部类只是用一次
tc.test(new A3() {
@Override
public void run() {
System.out.println("匿名内部类的run()");
}
});
tc.test(new A3() {
@Override
public void run() {
System.out.println("TestAnonymousInnerClass.run");
}
});
}
}
interface A3{
void run();
}
二十四.数组
数组的定义: int[] a = new int[5]; Car[] cars = new Car[5]; 数组是想用类型数据的有序集合。数组的四个特点:
- 长度是确定的。数组一旦被创建,它的大小就是不可以改变的。
- 其元素的类型必须是相同的类型,不允许出现混合类型。
- 数组类型可以是任何数据类型,包括基本类型引用类型。
- 数组也是对象。数组中的元素相当于该对象的成员变量。
一维数组
数组的定义和基本使用方式
package com.itbaizhan;
/**
* 测试数组的定义和基本使用方式
*/
public class Test01 {
public static void main(String[] args) {
//数组的定义
int []a =new int[5];
int b[] = new int[3];
//静态初始化
int []c ={100,200,300,400,500};
//默认初始化规则,数字0,布尔flase,引用null
System.out.println(a[0]);
//数组初始化
a[0] = 10;
a[1] = 20;
a[2] = 30;
//动态初始化
for(int i=0;i<a.length;i++){
a[i] = 10+10*i;
}
//遍历数组
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}
for(int y=0;y<c.length;y++){
System.out.println(c[y]);
//通过for-each循环,遍历数组
for(int temp:c){
System.out.println(temp);
}
}
}
}
测试数组的使用
package com.itbaizhan;
/**
* 测试数组的使用
*/
public class Test02 {
public static void main(String[] args) {
User[] users= new User[3];
User user2[] ={new User(101,"lvhao"),
new User(102,"lvpei")};
users[0] = new User(1001,"张三");
users[1] = new User(1002,"李四");
users[2] = new User(1003,"王五");
for (int i =0;i<users.length;i++){
System.out.println(users[i]);
}
for(User u:user2){
System.out.println(u);
}
}
}
class User{
private int id;
private String name;
public User(int id,String name){
this.id=id;
this.name = name;
}
public String toString(){
return "User{"+
"id="+id+
",name='"+name+'''+
'}';
}
}
数组的拷贝
package com.itbaizhan;
/**
* 数组的拷贝
*/
public class Test03 {
public static void main(String[] args) {
String[] a = {"阿里巴巴", "京东", "搜狐", "网易"};
String[] b = new String[6];
System.arraycopy(a,1,b,2,3);
for(String temp:b){
System.out.println(temp);
}
}
}
数组的工具
package com.itbaizhan;
import java.lang.reflect.Array;
import java.util.Arrays;
/**
* 测试工具类:java.util.Arrays
*/
public class Test4 {
public static void main(String[] args) {
int[] c ={10,32,8,4,100,40};
System.out.println(c);
//打印数组元素
System.out.println(Arrays.toString(c));
//排序sort
Arrays.sort(c);
System.out.println(Arrays.toString(c));
//查找binarySearch
System.out.println(Arrays.binarySearch(c,100));
//填充 Arrays.fill
int[] d = new int[10];
Arrays.fill(d,100);
System.out.println(Arrays.toString(d));
}
}
二十五.多维数组
多维数组的本质
- 多维数组可以看成以数组为元素的数组。比如:
int [] [] a ={ [1,2,3], [3,4], [3,5,6,7] };
二维数组语法代码展示:
package com.itbaizhan;
import java.util.Arrays;
/**
* 测试二维数组的基本语法
*/
public class Test05 {
public static void main(String[] args) {
int[][] a =new int [3][];
a[0] = new int[3];
a[0] = new int[4];
a[0] = new int[3];
a[0][0] = 10;
a[0][1] = 10;
a[1] = new int[]{10,23,30,40};
a[2] = new int[]{10,20,30};
int [][] b ={
{10,20,30},
{10,20},
{100,200,300,400}
};
//遍历,嵌套循环
for(int i=0;i<b.length;i++){
for(int m=0;m<b[i].length;m++){
System.out.println(b[i][m]);
}
System.out.println();
}
//遍历,Arrays.toString()
for( int i=0;i<b.length;i++){
System.out.println(Arrays.toString(b[i]));
}
}
}
利用object数组存储表格信息(测试二维数组)
package com.itbaizhan;
import java.util.Arrays;
/**
* 测试二维数组:利用Object数组存储表格信息
*/
public class Test06 {
public static void main(String[] args) {
Object[] a1 ={1001,"吕浩",18,"学生","2021-4-15"}; //包装类
Object[] a2 ={1002,"吕二浩",18,"老板","2021-4-10"};
Object[] a3 ={1003,"吕三浩",18,"保安","2023-3-10"};
Object[][] emps =new Object[3][];
emps[0] =a1;
emps[1] =a2;
emps[2] =a3;
for(int i=0;i<emps.length;i++){
System.out.println(Arrays.toString(emps[i]));
}
Emp e1 =new Emp(1001,"吕浩",18,"老板","2021-4-15");
Emp e2 =new Emp(1002,"吕1浩",19,"老板","2021-4-5");
Emp e3 =new Emp(1003,"吕2浩",120,"老板","2021-4-25");
Emp[] employees = {e1,e2,e3};
//for-each方法遍历
for(Emp temp:employees){
System.out.println(temp);
}
}
}
class Emp{
private int id;
private String name;
private int age;
private String job;
private String hiredate;
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", name='" + name + ''' +
", age=" + age +
", job='" + job + ''' +
", hiredate='" + hiredate + ''' +
'}';
}
public Emp(int id, String name, int age, String job, String hiredate) {
this.id = id;
this.name = name;
this.age = age;
this.job = job;
this.hiredate = hiredate;
}
}
二十六.冒泡排序法以及冒泡排序的优化(能手写)
package com.itbaizhan;
import java.util.Arrays;
/**
* 测试冒泡排序
*/
public class Test07 {
public static void main(String[] args) {
int[] values = {2, 6, 8, 9, 1, 0, 3, 4, 5, 7};
System.out.println("排序前,数组:" + Arrays.toString(values));
int temp;//用于交换的中间变量
//定义一个布尔类型变量,标记数组是否已达到有序状态
boolean flag = true;
for (int i = 0; i < values.length; i++) {
//length-i是因为第0趟会筛出9第1趟会筛出8依次类推就不需要重新循环所以-i,
// length-i-1是因为有j+1如果不-1那么下标会超出数组范围即越界
for (int j = 0; j < values.length - i - 1; j++) {
if (values[j] > values[j + 1]) {
//两两比较。如果大于,则互换元素
temp = values[j];
values[j] = values[j + 1];
values[j + 1] = temp;
//本趟发生了交换,表明该数组在本趟处于无序状态,需要继续排序。
flag = false;
}
System.out.println("第" + i + "趟:第" + j + "次:" + Arrays.toString(values));
}
System.out.println("第" + i + "趟,完成时:" + Arrays.toString(values));
//冒泡排序的优化
if (flag) {
System.out.println("结束排序!");
break;
} else {
flag = true;//重置回原值
}
}
}
}
二十七.二分法查找(手写)
二分法查找导图
代码示例:
package com.itbaizhan;
import java.util.Arrays;
/**
* 二分法查找
*/
public class Test08 {
public static void main(String[] args) {
int[] array={30,20,50,10,80,9,7,12,100,40,8};
int searchWord = 20;//所要查找的值
System.out.println(searchWord+"索引位置"+BinarySearch(array,searchWord));
}
/**
* 二分法查找
* @param array 目标数组
* @param value 所要查找的值
* @return 如果查找到,则返回这个值在数组中索引;如果未找到,则返回-1
*/
public static int BinarySearch(int[] array,int value) {
//二分法查找 必须先做排序
Arrays.sort(array);
System.out.println(Arrays.toString(array));
int low = 0;
int high = array.length - 1;//下标从零开始,所以最高的数就是length-1
while (low <= high) {
int middle = (low + high) / 2;
if (value == array[middle]) {
return middle; //查询到这个值,返回对应的索引位置
}
if (value > array[middle]) {
low = middle + 1;
}
if (value < array[middle]) {
high = middle - 1;
}
}
return -1; //上面循环完毕,说明未找到,返回-1
}
}
二十八.String类解读
- String类又称作不可变字符序列。
- String位于java.lang包中,Java程序默认导入java.lang包下的所有类。
- Java字符串就是Unicode字符序列,例如字符串"Java"就是4个Unicode字符'J'、'a'、'v'、'a'组成
- Java没有内置的字符串类型,而是在标准Java类库中提供了一个预定义的类String,每个用双引号括起来的字符串都是String类的一个类别
字符串的基本使用方式:
代码示例:
package com.itbaizhan.commonClass;
import java.util.Arrays;
/**
* 测试String类的基本用法
*/
public class TestString {
public static void main(String[] args) {
test3();
}
public static void test1(){
//String类的定义
String s1 ="abc"; //凡是字符串常量,都会放到字符串的常量池中
String s2 =new String("abc");//建了一个新的字符串对象
String s3 ="abc";
String s4 ="aBc";
//判断字符串是否是同一个
System.out.println(s1==s2);//false
System.out.println(s1==s3);//true
//判断字符串的值是否相等
System.out.println(s1.equals(s2));
//忽略大小写
System.out.println(s1.equalsIgnoreCase(s4));
}
public static void test2(){
String s1 = "0123456789,How are you,";
System.out.println(s1.charAt(4));
System.out.println(s1.length());
//字符串转换成数组
char[] chars = s1.toCharArray();
System.out.println(chars);
String[] strs =s1.split(",");//可以传入正则表达式
System.out.println(Arrays.toString(strs));
//判断是否包含子字符串
System.out.println(s1.indexOf("are"));//从头找
System.out.println(s1.lastIndexOf("are"));//从尾部找
System.out.println(s1.contains("How"));
System.out.println(s1.startsWith("0123"));//是否以这个开头
System.out.println(s1.endsWith("yoi"));//是否以这个结尾
}
public static void test3(){
String s1 ="0123456789,How are you";
String s2=s1.replace(' ','&');//用后面的替换前面的,替换成功后是一个全新的数组,原数组无变化
System.out.println(s1);
System.out.println(s2);
s2 = s1.substring(4);
System.out.println(s2);
s2 = s1.substring(4,18); //[4,10)不包含18这个位置
System.out.println(s2);
s2=s1.toLowerCase(); //全转为小写
System.out.println(s2);
s2 = s1.toUpperCase(); //全转为大写
System.out.println(s2);
//去除首尾空格
String s3 =" How are you ";
s2 =s3.trim(); //trim 去除首尾空格
System.out.println(s3);
System.out.println(s2);
}
}
StringBuilder和StringBuffer(可变字符序列)
StringBuilder和StringBuffer的主要区别体现在线程安全和性能上:
StringBuffer是线程安全的,其所有公开方法都被synchronized修饰,适用于多线程环境下操作;而StringBuilder是线程不安全的,没有同步操作,适用于单线程环境。
由于StringBuffer的线程安全机制,其性能相对较低;而StringBuilder没有线程安全的负担,因此在单线程环境下性能更优。
关于拼接字符串(频繁修改字符串数据)效率测试代码示例: test1为StringBuilder和StringBuffer的一些使用 test2为StringBuilder和String频繁修改数据的效率测试
package com.itbaizhan.commonClass;
/**
* 测试可变字符序列的用法
*/
public class TestStringBuilder {
public static void main(String[] args) {
test2();
}
public static void test1() {
String s = "";
StringBuilder sb = new StringBuilder();
StringBuffer sb2 = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c").append("d").append("e"); //添加字符串
System.out.println(sb);
sb2.append("吕浩123");
sb2.insert(0, "爱").insert(0, "我"); //插入字符串
System.out.println(sb2);
sb2.delete(0, 2);//[0,2) 包头不包尾 删除"我"和"爱"
System.out.println(sb2);
sb2.deleteCharAt(0).deleteCharAt(0); //删除字符串
System.out.println(sb2);
System.out.println(sb2.reverse());//字符串逆序
}
public static void test2(){
//使用String进行字符串的拼接
String str = "";
long num1 = Runtime.getRuntime().freeMemory();//获取JVM剩余的内存空间 单位是字节
long time1 =System.currentTimeMillis();//获取当前的时间,单位是毫秒
for(int i =0;i<5000;i++){
str +=i;
}
long num2 = Runtime.getRuntime().freeMemory();
long time2 = System.currentTimeMillis();
System.out.println("String占用的内存"+(num2-num1));
System.out.println("String占用的时间"+(time2-time1));
System.out.println("使用可变字符序列,完成拼接====================");
StringBuilder sb = new StringBuilder();
long num3 = Runtime.getRuntime().freeMemory();//获取JVM剩余的内存空间 单位是字节
long time4 =System.currentTimeMillis();//获取当前的时间,单位是毫秒
for(int i =0;i<5000;i++){
sb.append(i);
}
long num5 = Runtime.getRuntime().freeMemory();
long time6 = System.currentTimeMillis();
System.out.println("String占用的内存"+(num5-num3));
System.out.println("String占用的时间"+(time6-time4));
}
}
二十九.包装类
JAVA是面向对象的语言,但并不是"纯面向对象"。他不像Python,一切都是对象。Java中,基本数据类型不是对象。这时候,我们可以用包装类把基本数据类型变成对象。
八种基本类型分别对应八种包装类:
1.包装类的基本用法:
public static void testInteger() {
//基本数据类型转化成Integer对象
Integer int1 = Integer.valueOf(100);
//包装类对象转成基本数据类型
int int2 = int1.intValue();
long long1 = int1.longValue();
//字符串转换成Integer对象
Integer int3 = Integer.parseInt("324");
System.out.println(int3);
System.out.println(int3.toString()); //包装类对象转成字符串
//
System.out.println("int能表示的最大整数:" + Integer.MAX_VALUE);
}
2.自动装箱和自动拆箱
public static void testAutoBox() {
Integer a = 100; //自动装箱,编译器添加:Integer a =Integer.value0f(100);
int b = a; //a是包装类的对象 b是一个数字可以相互转换 因为自动拆箱 int b =a.intValue();
//空指针异常
Integer c = null;
// int d = c; //自动拆箱。编译器添加:int d = c.intValue();
}
3.包装类缓存问题:
public static void testCache() {
//整型、char类型所对应的包装类。在自动装箱时,对于-128到127之间的值会进行缓存的处理,为了提高效率。
Integer a = Integer.valueOf(100);
Integer b = 100;
System.out.println(a == b); //是同一个对象
Integer c =300;
Integer d =300;
System.out.println(c==d); //不是同一个对象
}
4.自定义一个简单的包装类:(不懂)
package com.itbaizhan.commonClass;
/**
* 自定义一个简单的包装类
*/
public class MyInteger {
private int value;
private static MyInteger[] cache = new MyInteger[256];
public static final int LOW=-128;
public static final int HIGH=127;
//静态初始化块,是在类被加载的时候,初始化类的静态属性
static {
for (int i=MyInteger.LOW;i<=HIGH;i++){
//128,0;-127.1;-126.2;
cache[i+(-LOW)]= new MyInteger(i);
}
}
public static MyInteger value0f(int i){
//如果在-128到127之间,则返回数组中缓存的对象,否则创建新的对象
if(i>=LOW&&i<=HIGH){
return cache[i+(-LOW)];
}else {
return new MyInteger(i);
}
}
public int intValue(){
return value;
}
@Override
public String toString() {
return value+"";
}
public MyInteger(int i){
this.value = i;
}
public static void main(String[] args) {
MyInteger a=MyInteger.value0f(100);
MyInteger b=MyInteger.value0f(100);
System.out.println(a==b);
MyInteger c =MyInteger.value0f(400);
MyInteger d =MyInteger.value0f(400);
System.out.println(c==d);
}
}
三十.时间相关的类:时间转化字符串
1.Date类的使用
public static void test1() {
long nowNum = System.currentTimeMillis();//返回当前时刻的毫秒数
System.out.println(nowNum);
Date d1 = new Date();
System.out.println(d1.getTime()); //getTime获得毫秒数
Date d2 = new Date(1000L * 3600 * 24 * 365 * 150); //距离1970年150年后的日期,忽略闰年
System.out.println(d2);
Date d3 = new Date();
d3.getDate(); //方法被废弃,可以用但不建议用(主要是因为它只返回月份中的日期(即日),而不提供完整的日期信息(年、月、日)。)
}
2.DateFormat类
DateFormat的作用是什么?
完成字符串和Date对象的转换。 DateFormat和SimpleDateFormat的关系是什么?
DateFormat 是抽象类。SimpleDateFormat是DateFormat的子类。
public static void test2() throws ParseException {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date d2 = new Date(1000L * 3600 * 24 * 365 * 150);
String str2 = df.format(d2); // 时间类转换为字符串
System.out.println(str2);
String str3 = "2049-10-1 10:10:20";
Date d3 = df.parse(str3);// 字符串转换为时间类
System.out.println(d3.getTime()); //getTime获取毫秒数
DateFormat df2 = new SimpleDateFormat("yyyy年MM月dd日");
//利用格式化字符可以方便的做一些其他的事,获取当前时间是今年的第几天
DateFormat df3 = new SimpleDateFormat("D");
Date d4 = new Date();
System.out.println(df3.format(d4));
}
3.Calendar日历类的使用
public static void test3(){
//月份默认0-11。 0:1月,1:2月
//星期:1-7 1:周日,2:周一....7:周六
Calendar calendar =new GregorianCalendar(2999,9,9,11,25,40);
int year= calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
System.out.println(year);
System.out.println(month);
calendar.set(Calendar.YEAR,2049);
System.out.println(calendar.get(calendar.YEAR));
System.out.println(calendar.getTime());//返回对应的Date对象
System.out.println(calendar.getTimeInMillis());//返回对应的毫秒数
//日期的计算
calendar.add(Calendar.DATE,1000); //往后加1000天
System.out.println(calendar.getTime());
calendar.add(Calendar.YEAR,-30); //往前减30年
System.out.println(calendar.getTime());
}
三十一.异常
Java中的异常被分为两大类:检查型异常(Checked Exceptions) 和 非检查型异常(Unchecked Exceptions)(非运行时异常) 。
CheckException和RuntimeException的区别是什么?
CheckedException:编译期处理的异常
RuntimeException:运行时异常
Java面向对象处理异常过程 抛出异常:在执行一个方法时,如果发生异常,则这个方法生成代表该异常的一个对象,停止当前执行路径,并把异常对象提交给JRE
捕获异常:JRE得到该异常后,寻找相应的代码来处理该异常JRE在方法的调用栈中查找,从生成异常的方法开始回溯,直到找到相应的异常处理代码为止.
以下这些都属于运行时异常:
ArithmeticException:数学异常 NullPointerException:空指针异常 ClassCastException:类型转换异常 ArrayIndexOutOfBoundsException:数组下标越界异常 NumberFormatException:数字格式化异常
非运行时异常一般是指自定义异常。
以下是示例代码:
package com.itbaizhan;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Test01 {
public static void main(String[] args) {
test3();
}
public static void test1() {
System.out.println("111");
int a = 1 / 0;
System.out.println("222");
}
public static void test2() {
System.out.println("111");
try {
int a = 1 / 0; //ctrl+alt+t快捷键,增加try.catch
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println("222");
}
/**
* RunTimeException需要程序员做逻辑处理
*/
public static void test3() {
//ArithmeticException:by Zero
int a = 1;
int b = 0;
// System.out.println(a/b);
if (b != 0) {
System.out.println(a / b);
}
//空指针异常,NUllPointerException
String str = null;
if (str != null) {
System.out.println(str.charAt(2));
}
//数组格式化异常
String str2 = "234abc";
// int c = Integer.parseInt(str);
// System.out.println(c);
Pattern p = Pattern.compile("^\d+$");
Matcher m = p.matcher(str2);
if (m.matches()) {
System.out.println(Integer.parseInt(str2));
} else {
System.out.println("数字格式不对");
}
//数组下标越界异常
int[] arrs = new int[5];
int f = 5;
if (f >= 0 && f < arrs.length) {
System.out.println(arrs[f]); //[0],length-1数组范围
}
//类型转换异常
Anima am = new Dog();
// Cat cat = (Cat) am;
if(am instanceof Cat){
Cat cat =(Cat) am;
}
}
}
class Anima{}
class Dog extends Anima{}
class Cat extends Anima{}
package com.itbaizhan;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test02 {
public static void main(String[] args) throws ParseException {
// tes1();
// test2();
// test3();
test4();
}
//测试try-catch-finally
public static void tes1() {
FileReader reader = null;
try {
reader = new FileReader("D:/a.txt");
char c1 = (char) reader.read();
char c2 = (char) reader.read();
System.out.println("文件内容:" + c1 + c2);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
//测试throws,声明式抛出异常
public static void test2() throws ParseException {
DateFormat df = new SimpleDateFormat("yyyy-MM-DD");
String str = "2049-10-1";
Date d =df.parse(str);
System.out.println(d);
}
//try-with-resource:可以自动关闭实现了AutoClosable接口的类
//将try-catch-finally:try-catch
//其实一种语法糖。编译器帮我们做了处理,转化成了:try-catch-finally
public static void test3(){
try(FileReader reader = new FileReader("d:/a.txt")){
char c1 = (char) reader.read();
char c2 = (char) reader.read();
System.out.println("文件内容:" + c1 + c2);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 任何执行try中的return语句之前,都会先执行finally语句(如过finally语句存在)
* 如果finally语句中也有return,则直接调用return
* @return
*/
public static int test4(){
int a = 10;
System.out.println("step1");
try {
System.out.println("step2");
a=20;
return a;
}catch (Exception e){ //Exception a用于捕获并引用在try块中抛出的异常对象
e.printStackTrace();
}finally {
a=40;
System.out.println("step3");
return a;
}
}
}
三十二.可视化bug追踪
自定义异常:
代码示例:
package com.itbaizhan;
/**
* 测试自定义异常
*/
public class Test03 {
public static void main(String[] args) throws IllegalAgeException {
Person p = new Person();
p.setAge(-80);
}
}
/**
* 不合法年龄异常
*/
class IllegalAgeException extends Exception{
//默认无参构造器
public IllegalAgeException(){
}
public IllegalAgeException(String message){
super(message);
}
}
class Person{
private String name;
private int age;
public void setAge(int age)throws IllegalAgeException{
if(age<0){
throw new IllegalAgeException("人的年龄不能是负数");
}
this.age =age;
}
public int getAge(){
return age;
}
}
可视化
代码示例:
package com.itbaizhan;
/**
* 测试调试功能
*/
public class Test05 {
private int num =10;
public void run(int a){
System.out.println("Test05,run");
for(int i=0;i<a;i++){
num += a;
go();
}
}
public void go(){
System.out.println("Test05.go");
System.out.println("num*10="+(num*10));
}
public static void main(String[] args) {
Test05 test05 =new Test05();
test05.run(3);
}
}
三十二.容器_List用法(有序可重复)
List:在Java中,List 是一个接口(interface),它属于Java集合框架(Java Collections Framework)的一部分。List 接口定义了一个有序的集合,允许存储重复的元素。与 Set 接口不同,List 接口的实现(如 ArrayList、LinkedList 等)保留了元素的插入顺序,并且允许通过索引访问元素。
常见方法:isEmpty,add,remove,size,contains,toArrays
isEmpty:检查List是否为空。
add:在List里添加元素。
remove:把元素清出List但不删除。
size:查询List元素数量。
contains:查询List中指定的元素。
toArrays:将List转成数组。
代码示例:
package com.itbaizhan;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
/**
* 测试List接口的常用方法
*/
public class ListTest {
public static void main(String[] args) {
// test01();
// test02();
test03();
}
//测试isEmpty,add,remove,size,contains,toArrays等常见方法
//这些方法多位于Collection接口中
public static void test01() {
//有序、可重复
List list = new ArrayList(); //泛型
System.out.println(list.isEmpty()); //检查容器是否为空
list.add("高淇");
System.out.println(list);
System.out.println(list.isEmpty());
list.add("吕浩");
list.add("吕尔豪");
System.out.println(list);
System.out.println("List的大小:" + list.size()); //查询元素
System.out.println("是否包含指定的元素:" + list.contains("吕浩")); //查询容器中指定的元素
list.remove("吕三号"); //把内容从容器中清出去,但不是删除
System.out.println(list);
Object[] objs = list.toArray(); //转换成数组
System.out.println("转换成Object数组:" + Arrays.toString(objs));
list.clear();
System.out.println(list);
}
//测试和索引操作相关的方法
public static void test02() {
//List存储的是、有序、可重复
List list = new ArrayList();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
System.out.println(list);
list.add(2, "高"); //在2这个位置插入一个"高",原先位置的元素往后移。索引
System.out.println(list);
list.remove(2);
System.out.println(list);
list.set(2, "张"); //在2这个位置直接做修改,即原本的元素被替代。
System.out.println(list);
System.out.println(list.get(1));
list.add("B");
System.out.println(list); //可以重复往里放入内容,有序可重复
System.out.println(list.indexOf("B"));//从左到右找
System.out.println(list.lastIndexOf("B"));//从尾到头找
}
//测试两个容器之间的元素处理
public static void test03() {
List list = new ArrayList();
list.add("吕浩");
list.add("123");
list.add("lh");
List list2 = new ArrayList();
list2.add("吕浩");
list2.add("王五");
list2.add("徐六");
System.out.println(list.containsAll(list2)); //list中有没有包含list2中所有的元素
list.addAll(list2);//把list2所有的元素加入到list里
System.out.println("list:" + list);
System.out.println("list2:" + list2);
// list.removeAll(list2); //删除掉list2和list中存在的元素
// System.out.println(list);
//
list.retainAll(list2);//取交集
System.out.println(list);
}
}
ArrayList类、LinkedList类、Vector类的区别:
ArrayList类、LinkedList类、Vector类的使用建议:
三十三.容器_Set用法(无序不可重复)
Set 是一个不包含重复元素的集合接口,它继承自 Collection 接口。Set 接口的实现类主要有 HashSet、LinkedHashSet、TreeSet 等。这些实现类提供了不同的数据结构和算法来存储集合中的元素,但共同点是它们都实现了 Set 接口,即不允许集合中存在重复的元素。
Set不可重复实现的核心是equals()方法
代码示例:
package com.itbaizhan;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
* 测试set的用法
* Set:无序、不可重复
*/
public class SetTest {
public static void main(String[] args) {
// test01();
test02();
}
public static void test01(){
Set s =new HashSet();
s.add("hello");
s.add("world");
s.add("hello");//相等的元素不会再被加入!
System.out.println(s);
//其他的方法和List一致。因为Set和List都是Collection接口的子接口!
System.out.println(s.size());
System.out.println(s.isEmpty());
}
//Set中不可重复的核心,equals()方法
public static void test02(){
Emp e1 =new Emp("1001",123);
Emp e2 =new Emp("1002",1234);
Emp e3 =new Emp("1003",1235);
Set s =new HashSet();
s.add(e1);
s.add(e2);
s.add(e3); //相当(equals()返回true)的元素不会再被加入
System.out.println(s);
}
}
class Emp{
private int id;
private String ename;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Emp emp = (Emp) o;
return id == emp.id;
}
@Override
public int hashCode() {
return Objects.hashCode(id);
}
public Emp(String ename, int id) {
this.ename = ename;
this.id = id;
}
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", ename='" + ename + ''' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
}
三十四.容器_Map
Map就是用来存储"键(key)-值(value)对"的 Map类中存储的"键值对"通过键来标识 Map是成对存储,按照"键值对"的形式存储
代码示例:
package com.itbaizhan;
import java.util.*;
/**
* 测试Map的使用方法
*/
public class MapTest {
public static void main(String[] args) {
// test01();
// test02();
// iterateListSet();
test03();
}
// Map的常见用法
public static void test01() {
Map m1 = new HashMap(); //new HashTable();
m1.put(1, "one"); //第一个键值对
m1.put(2, "two"); //第二个键值对
m1.put(3, "three");//第三个键值对
m1.put(2, "二"); //如果键重复,则替换旧的键值对
Object obj = m1.get(2);
System.out.println(obj);
System.out.println(m1);
System.out.println(m1.size());
System.out.println(m1.isEmpty());
System.out.println(m1.containsKey(4));
System.out.println(m1.containsValue("three"));
m1.remove(3); //从Map容器中移除这个键值对
System.out.println(m1);
Map m2 = new HashMap();
m2.put(4, "D");
m2.put(5, "E");
m2.put(6, "F");
m1.putAll(m2);
System.out.println(m1);
}
// 泛型的简单用法
public static void test02() {
List<String> list = new ArrayList<>();
list.add("aaa");
list.get(0);
String str = list.get(0);
System.out.println(str);
Set<Integer> set =new HashSet<>();
// set.add("dddd"); //只能放integer
Map<String,String> map =new HashMap<>();
map.put("一","one");
map.put("二","two");
System.out.println(map);
}
//遍历List、Set
public static void iterateListSet(){
// List<String> s = new ArrayList<>();
Set<String> s = new HashSet<>();
s.add("aaa");
s.add("bbb");
s.add("ccc");
//通过索引遍历List (只适用于List)
// for (int i=0;i<s.size();i++){
// String temp = s.get(i);
// System.out.println(temp);
// }
//增强for循环(foreach)完成遍历 (适用于List、Set)
for(String temp:s){
System.out.println(temp);
}
//使用Iterator对象完成遍历 (适用于List、Set)
for(Iterator<String> iter=s.iterator();iter.hasNext();){
String temp =iter.next(); //当前正在遍历的对象返回,同时指向下一个
System.out.println(temp);
}
}
//遍历Map(遍历Key、遍历Value)
public static void test03(){
Map<String,String> map =new HashMap<>();
map.put("1","1");
map.put("2","2");
map.put("3","3");
//遍历键
Set<String> keys =map.keySet();
for(String temp:keys){
// System.out.println(temp);
System.out.println(map.get(temp));
System.out.println(temp+"---------"+map.get(temp));
}
Collection<String> value = map.values();
//遍历值
for(String temp:value){
System.out.println(temp);
}
//使用EntrySet遍历key、value
Set<Map.Entry<String,String>> entrySet=map.entrySet();
for(Map.Entry e:entrySet){
System.out.println(e.getKey()+"--------------------"+e.getValue());
}
}
}
三十五.容器_存取二维表格信息
Test01类为使用List和Map存储表格信息的代码
Test02类为使用List和Javabean存储表格信息的代码 代码示例:
package com.itbaizhan;
import java.util.*;
/**
* 使用容器的不同方式,存储表格信息
*/
public class StoreTable {
public static void main(String[] args) {
// test01();
test02();
}
//Map对应一行数据
public static void test01() {
Map<String, Object> m1 = new HashMap<>();
m1.put("id", 1001);
m1.put("title", "我爱Java");
m1.put("createTime", "2021-8-10");
m1.put("length", "300");
Map<String, Object> m2 = new HashMap<>();
m2.put("id", 1002);
m2.put("title", "我爱lh");
m2.put("createTime", "2021-9-10");
m2.put("length", "400");
Map<String, Object> m3 = new HashMap<>();
m3.put("id", 1003);
m3.put("title", "我爱编程");
m3.put("createTime", "2021-10-10");
m3.put("length", "500");
//存储了整个表格的信息
List<Map<String, Object>> list = new ArrayList<>();
list.add(m1);
list.add(m2);
list.add(m3);
System.out.println("id\t\ttitle\tcreateTime\tlength");
for (int i = 0; i < list.size(); i++) {
Map<String, Object> temp = list.get(i);
System.out.println(temp.get("id") + "\t"
+ temp.get("title") + "\t"
+ temp.get("createTime") + "\t"
+ temp.get("length"));
}
}
//List+Javabean的方式
public static void test02() {
VideoInfo v1 = new VideoInfo(1001, "我爱Java", "2021-8-10", 300);
VideoInfo v2 = new VideoInfo(1002, "我爱lh", "2021-9-10", 400);
VideoInfo v3 = new VideoInfo(1003, "我爱编程", "2021-10-10", 500);
List<VideoInfo> list =new ArrayList<>();
list.add(v1);
list.add(v2);
list.add(v3);
for(VideoInfo temp:list){
System.out.println(temp);
}
}
}
class VideoInfo {
private int id;
private String title;
private String createTime;
private int length;
public VideoInfo(int id, String title, String createTime, int length) {
this.id = id;
this.title = title;
this.createTime = createTime;
this.length = length;
}
@Override
public String toString() {
return
id +"\t"+
title + '\t' +
createTime + '\t'
+ length;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
}
三十六.泛型详解
泛型的本质就是"数据类型的参数化" 处理的数据类型不是固定的,而是可以作为参数导入.
把类型当作参数一样传递
<数据类型>只能是引用类型
泛型的好处:
代码可读性更好[不用强制转换]
程序更加安全[只要编译时期没有警告,运行时期就不会出现ClassCastException异常]
泛型只要用于编译阶段,编译后生成的字节码Class文件不包含泛型中的类型信息,涉及类型转换仍然是普通的强制类型转换。
泛型主要是为了方便程序员的代码编写,以及更好的安全性检测。
定义泛型时通常使用的字母:
代码示例:
package com.itbaizhan;
import java.util.ArrayList;
import java.util.List;
/**
* 测试泛型的基本用法
*/
public class GenericTest1 {
public static void main(String[] args) {
}
public static void test1(){
List<String> list =new ArrayList<>();
list.add("add");
// list.add(222);
Generic1<Integer> g1 =new Generic1<>();
g1.a(22);
Generic1<String> g2 =new Generic1<>();
g2.a("2");
}
}
class Generic1<T>{
public T a(T obj){
System.out.println(obj);
return obj;
}
public <N> void bb(N obj){
System.out.println(obj);
}
}
interface MyList<E>{
int size();
boolean isEmpty();
void add(E a);
E get(int index);
}
class MyArrayList<E> implements MyList<E>{
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public void add(E a) {
}
@Override
public E get(int index) {
return null;
}
}
通配符?和上下限定代码详解:
package com.itbaizhan;
import java.util.ArrayList;
import java.util.List;
/**
* 通配符?和上下限限定
*/
public class GenericTest2 {
public static void test01(List<? extends Dog> list){
System.out.println(list);
}
public static void test02(List<? super Dog> list){ //限定符限定Dog和它的子类,限定符限定类型,传入的是Animal为Dog的父类 所以不能调用
System.out.println(list);
}
public static void main(String[] args) {
List<Animal> list1 = new ArrayList<>();
List<Dog> list2 = new ArrayList<>();
List<Taidi> list3 = new ArrayList<>();
// test01(list1);
test01(list2);
test01(list3);
}
}
class Animal{}
class Dog extends Animal{}
class Taidi extends Dog{}
边遍历边删除(使用迭代器)
package com.itbaizhan;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
*测试边遍历变删除(使用迭代器)
*/
public class iteratorTest {
public static void main(String[] args) {
List<String> list =new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("bad");
list.add("bag");
System.out.println(list);
//边遍历边删除,容器的size会发生变化
/* for (int i=0;i<list.size();i++){
String temp = list.get(i);
if(temp.startsWith("b")) {
list.remove(i);
}
System.out.println("size:"+list.size());
}
System.out.println(list);*/
//使用迭代器对象。它始终指向下一个元素,不关心容器整体的变化
for (Iterator<String> iter=list.iterator();iter.hasNext();){
String temp =iter.next();
if(temp.startsWith("b")){
iter.remove();
}
}
System.out.println(list);
}
}
三十七.IO流
IO流(Input/Output Stream)在计算机科学中是一种用于在程序的输入和输出之间传输数据的抽象表示。它是连接数据源头(如文件、网络、内存等)和数据处理程序(如程序本身)之间的桥梁。通过IO流,程序可以读取(输入)数据,也可以写入(输出)数据到各种数据源。
IO流的主要分类
根据数据流动的方向,IO流可以分为两大类:
- 输入流(Input Stream) :用于从数据源读取数据到程序中。例如,从文件、键盘或网络连接中读取数据。
- 输出流(Output Stream) :用于将数据从程序中写入到目标源。例如,将数据写入文件、屏幕或网络连接中。
根据处理的数据类型,IO流又可以分为字节流和字符流:
- 字节流(Byte Stream) :以字节为单位处理数据,不考虑具体的字符编码,如
InputStream和OutputStream。适用于处理二进制数据(如图片、音频、视频等)。 - 字符流(Character Stream) :以字符为单位处理数据,如
Reader和Writer。它们提供了对字符编码的支持,适用于处理文本数据。
常见的IO流
-
字节流:
- 输入流:
FileInputStream,ByteArrayInputStream等。 - 输出流:
FileOutputStream,ByteArrayOutputStream等。
- 输入流:
-
字符流:
- 输入流:
FileReader,BufferedReader(带缓冲的字符输入流,效率更高),InputStreamReader(字节流到字符流的桥梁)等。 - 输出流:
FileWriter,BufferedWriter(带缓冲的字符输出流),OutputStreamWriter(字符流到字节流的桥梁)等。
- 输入流:
try-catch-finally语法结构代码示例:
package com.itbaizhan;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 使用try-catch-finally经典结构
*/
public class Test02 {
public static void main(String[] args) {
readFile();
}
public static void readFile(){
FileInputStream fis =null;
try {
fis = new FileInputStream("D:/b.txt");
int s1 =fis.read();
int s2 =fis.read();
int s3 =fis.read();
int s4 =fis.read();
int s5 =fis.read();
System.out.println((char) s1);
System.out.println((char) s2);
System.out.println((char) s3);
System.out.println((char) s4);
System.out.println(s5); //由于文件内容已经读取完毕,返回-1
//流对象用完后,必须关闭!不然,总占用系统资源,最终会造成系统崩溃
fis.close();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
//流对象用完后,必须关闭!不然,总占用系统资源,最终会造成系统崩溃
try {
if(fis!=null){
fis.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public static void writeFile(){
//只测试英文,不测试中文
FileOutputStream fos =null;
try {
fos = new FileOutputStream("D:c.txt");
fos.write('l');
fos.write('v');
fos.write('h');
fos.write('a');
fos.write('o');
fos.close();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
try-with-resource语法结构代码示例:
package com.itbaizhan;
/**
* try-with-resource新语法结构,使用流对象
*/
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Test03 {
public static void main(String[] args) {
readFile();
}
public static void readFile() {
try (FileInputStream fis = new FileInputStream("D:/b.txt");) {
int s1 = fis.read();
int s2 = fis.read();
int s3 = fis.read();
int s4 = fis.read();
int s5 = fis.read();
System.out.println((char) s1);
System.out.println((char) s2);
System.out.println((char) s3);
System.out.println((char) s4);
System.out.println(s5); //由于文件内容已经读取完毕,返回-1
//流对象用完后,必须关闭!不然,总占用系统资源,最终会造成系统崩溃
fis.close();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
IO流文件拷贝代码示例 :
package com.itbaizhan;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 流的使用,经典代码
*/
public class Test04 {
public static void main(String[] args) {
// readFile2();
// readFile();
copyFileBuffered();
}
public static void readFile() {
FileInputStream fis = null;
try {
fis = new FileInputStream("D:/b.txt");
StringBuilder sb = new StringBuilder();
int temp = 0;
while ((temp = fis.read()) != -1) {
sb.append((char) temp);
}
System.out.println(sb);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace(); // 打印异常的堆栈跟踪信息
}
}
}
public static void readFile2() {
try (FileInputStream fis = new FileInputStream("D:/b.txt");) {
StringBuilder sb = new StringBuilder();
int temp = 0;
while ((temp = fis.read()) != -1) {
sb.append((char) temp);
}
System.out.println(sb);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void writeFile() {
//只测试英文,不测试中文
FileOutputStream fos = null;
try {
fos = new FileOutputStream("D:c.txt");
fos.write("abcdefg".getBytes());
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
//使用缓存增加拷贝效率
public static void copyFileBuffered(){
try (FileInputStream fis =new FileInputStream("D:/1.png");
FileOutputStream fos =new FileOutputStream("D:/1_copy.png");){
byte[] buffer =new byte[1024];
int temp = 0;
while ((fis.read(buffer))!=-1){
fos.write(buffer);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
三十七(二).IO流体系图:
文件字符流:
代码示例:
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* 关于文件字符流的测试操作(FileReaderWriter)
*/
public class TestFileReaderWriter {
public static void main(String[] args) {
try (FileReader fr = new FileReader("D:/d.txt");
FileWriter fw = new FileWriter("D:/d_copy.txt")) {
int temp = 0;
while ((temp=fr.read())!=-1){
fw.write(temp);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
缓冲字符流:
代码示例:
package com.itbaizhan;
import java.io.*;
/**
* 缓冲字符流(BufferedReaderWriter)
*/
public class TestBufferedReaderWriter {
public static void main(String[] args) {
try(BufferedReader br = new BufferedReader(new FileReader("D:/d.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("D:/d2.txt"))) {
String tempString = "";
while ((tempString=br.readLine())!=null){
bw.write(tempString);
bw.newLine(); //换行符
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
字节数组流:
代码示例:
package com.itbaizhan;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
* 字节数组流(字节数组作为数据源)
*/
public class ByteArrayInputOutputStream {
public static void main(String[] args) {
test01("abcefg".getBytes());
}
public static void test01(byte[] bytes){
int temp = 0;
int num = 0;
try(ByteArrayInputStream bis =new ByteArrayInputStream(bytes)){
while((temp=bis.read())!=-1){
System.out.println((char) temp);
num++;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println("读取的字节数:"+num);
}
}
数据流:
代码示例:
package com.itbaizhan;
import java.io.*;
/**
* 数据流
*/
public class TestDataInputOutputStream {
public static void main(String[] args) {
// writeData();
readData();
}
public static void writeData(){
try(DataOutputStream dos =new DataOutputStream(new FileOutputStream("D:/data.txt"))){
dos.writeChar('a');
dos.writeInt(10);
dos.writeDouble(Math.random());
dos.writeBoolean(true);
dos.writeUTF("百战程序员");
dos.flush(); //手动刷新缓存区,将流中的数据写入到文件中
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//读取
public static void readData(){
try(DataInputStream dis = new DataInputStream(new FileInputStream("D:/d.txt"))){
//读取数据时,需要和写入的数据顺序保持一致!否则,顺序不能正确读取
System.out.println('a');
System.out.println(10);
System.out.println(Math.random());
System.out.println(true);
System.out.println("百战程序员");
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
对象流和序列化/反序列化:
代码示例:
package com.itbaizhan;
/**
* 对象流
*/
public class User implements java.io.Serializable{ //序列化接口
private int id;
private String uname;
transient private String pwd; //未参与序列化
public User(int id, String uname, String pwd) {
this.id = id;
this.uname = uname;
this.pwd = pwd;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
package com.itbaizhan;
import java.io.*;
import java.util.ArrayList;
/**
* 对象流
*/
public class TestObjectInputOutputStream {
public static void main(String[] args) {
// writeObject();
readObject();
}
public static void writeObject(){
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/object_data.txt"))){
ArrayList<User> list =new ArrayList<>();
list.add(new User(1001,"lh","123456"));
list.add(new User(1001,"lh2","1234568"));
list.add(new User(1001,"lh3","1234567"));
oos.writeObject(list);
oos.flush();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void readObject(){
try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/object_data.txt"))){
ArrayList<User> list =(ArrayList)ois.readObject();
for (User u:list){
System.out.println(u.getId()+","+u.getUname()+","+u.getPwd());
}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
随意访问文件流
三十八.装饰器模式和Apache IO包:
装饰器模式:
装饰器模式是一种结构型设计模式,它允许在不改变原有对象结构的前提下,动态地给对象添加一些额外的职责(即功能)。装饰器模式通过创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象类结构不变的前提下,为其提供额外的功能。这种模式比继承更加灵活,可以有效地避免类爆炸,并保持类的封装性。
代码示例:
package com.itbaizhan;
/**
* 测试装饰器模式
*/
public class TestDecoration {
public static void main(String[] args) {
Iphone phone = new Iphone("Iphone80");
phone.show();
System.out.println("----------装饰后");
TouyingIphone typhone = new TouyingIphone(phone);
typhone.show();
}
}
class Iphone{
private String name;
public Iphone(String name){
this.name=name;
}
public void show(){
System.out.println("我是"+name+",可以在自己屏幕上显示内容");
}
}
class TouyingIphone{
Iphone phone;
public TouyingIphone(Iphone phone) {
this.phone = phone;
}
public void show(){
System.out.println("开启投影套件...");
phone.show();
System.out.println("还可以投影,在墙壁上显示");
System.out.println("投影结束");
}
}
IO包代码示例:
package com.itbaizhan;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
/**
* 测试apache commons组件下的io工具库
*/
public class TestApacheFileUtils {
public static void main(String[] args) {
// writeFile();
// readFile();
readURL();
}
//将1000个随机数输入进文本
public static void writeFile() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(Math.random() + "\n");
}
try {
FileUtils.write(new File("D:/apache-test.txt"), sb.toString(), "gbk");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//读取文本内的内容
public static void readFile() {
try {
List<String> content = FileUtils.readLines(new File("D://apache-test.txt"), "gbk");
for (String temp : content) {
System.out.println(temp);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//读取输入网址的网页源码
public static void readURL() {
try {
URL url = new URL("http://www.baidu.com");
InputStream is =url.openStream();
String content = IOUtils.toString(is,"UTF-8");
System.out.println(content);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
三十九.多线程概念
程序是什么:
进程是什么:
线程是什么:
Java中的线程:
Java中如何多线程编程:(通过继承Thread类实现多线程):
三十九(一).Java线程的两种实现方式:
通过Runnable接口创建线程:
package com.itbaizhan;
/**
* 通过Runnable接口创建线程
*/
public class TestThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) {
TestThread2 t = new TestThread2();
//把实现了Runnable接口的对象作为参数传入;
Thread t1 =new Thread(t);
t1.start();
Thread t2 =new Thread(t);
t2.start();
}
}
通过lambda表达式创建线程:
package com.itbaizhan;
/**
* 使用lambda表达式创建线程
*/
public class TestThread3 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}).start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}).start();
}
}
线程状态和生命周期:
停止执行线程的方式:
package com.itbaizhan;
/**
* 测试终止线程的典型方法
*/
public class TestThreadCiycle_Terminated implements Runnable {
boolean live =true; //标记变量,表示线程是否可以终止
@Override
public void run(){
int i=0;
while (live){
System.out.println(Thread.currentThread().getName()+":"+(i++));
}
}
public void terminate(){
live =false;
}
public static void main(String[] args) {
TestThreadCiycle_Terminated tt = new TestThreadCiycle_Terminated();
Thread t1 =new Thread(tt);//新生状态
t1.start(); //就绪状态
for (int i = 0; i < 100; i++) {
System.out.println("主线程:"+i);
}
tt.terminate();
System.out.println("tt stop");
}
}
三十九(二).线程的多种状态和线程的优先级:
测试线程的暂停:
sleep(使线程进入阻塞状态)
yield(使线程进入就绪状态)
代码示例:
package com.itbaizhan;
/**
* 测试线程的暂停:sleep(使线程进入阻塞状态).yield(使线程进入就绪状态)
*/
public class TestThreadCiycle_Sleep_yield implements Runnable {
@Override
public void run(){
int i=0;
while (i<10){
System.out.println(Thread.currentThread().getName()+":"+(i++));
/* try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}*/
Thread.yield(); //进入就绪状态
}
}
public static void main(String[] args) {
TestThreadCiycle_Sleep_yield tt = new TestThreadCiycle_Sleep_yield();
Thread t1 =new Thread(tt);//新生状态
t1.start(); //就绪状态
for (int i = 0; i < 100; i++) {
System.out.println("主线程:"+i);
}
// tt.terminate();
System.out.println("tt stop");
}
}
线程的优先级:
- 处于就绪状态的线程,会进入"就绪队列"等待JVM来挑选。
- 线程的优先级用数字表示,范围从1到10,一个线程的缺省(默认)优先级是5.
- 注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程
测试优先级示例代码:
package com.itbaizhan;
/**
* 测试线程的基本信息、优先级
*/
public class TestThreadInfo {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread t =new Thread(mt,"我的线程");
t.setPriority(1); //设置线程优先级
t.start();
System.out.println("name is:"+t.getName());
System.out.println(t.isAlive()); //判断线程是否还在运行
System.out.println("主线程,Over!");
Thread t2 =new Thread(mt,"另一个线程");
t2.setPriority(10);//设置线程优先级
t2.start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <=10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
/* try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}*/
Thread.yield();
}
}
}
Jion方法:
三十九(三).线程安全和同步性:
线程同步的概念:
多个线程访问同一个对象,并且某些线程还想修改这个对象。这时,我们就需要用到"线程同步"。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
用代码实现:不同步时出现的不安全问题:
package com.itbaizhan;
/**
* 测试同步
*/
public class TestSynchronized {
public static void main(String[] args) {
Account a1 =new Account(100,"高淇");
Drawing draw1 =new Drawing(80,a1);//模拟取钱的线程
Drawing draw2 =new Drawing(60,a1);//模拟取钱的线程
draw1.start(); //我在取钱
draw2.start();//我女朋友在取钱
}
}
class Account{
int money;
String aname;
public Account(int money,String aname){
this.money=money;
this.aname=aname;
}
}
//模拟取款动作
class Drawing extends Thread{
int drawingNum;//取多少钱
Account account; //从哪个账户取
int expenseTotal;//总共去了多少钱
public Drawing(int drawingNum,Account account){
this.drawingNum=drawingNum;
this.account=account;
}
@Override
public void run(){
if(account.money-drawingNum<0){
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
account.money-=drawingNum;
expenseTotal+=drawingNum;
System.out.println(this.getName()+",账户余额:"+account.money);
System.out.println(this.getName()+",总共取了:"+expenseTotal);
}
}
以上代码因为线程不同步所以导致多取了四十块钱
用代码实现:使用synchronized解决安全性问题:
package com.itbaizhan;
/**
* 测试同步
*/
public class TestSynchronized {
public static void main(String[] args) {
Account a1 =new Account(100,"高淇");
Drawing draw1 =new Drawing(80,a1);//模拟取钱的线程
Drawing draw2 =new Drawing(60,a1);//模拟取钱的线程
draw1.start(); //我在取钱
draw2.start();//我女朋友在取钱
}
}
class Account{
int money;
String aname;
public Account(int money,String aname){
this.money= money;
this.aname= aname;
}
}
//模拟取款动作
class Drawing extends Thread{
int drawingNum;//取多少钱
Account account; //从哪个账户取
int expenseTotal;//总共去了多少钱
public Drawing(int drawingNum,Account account){
this.drawingNum=drawingNum;
this.account=account;
}
@Override
public void run(){
synchronized (account){
if(account.money-drawingNum<0){
System.out.println("钱不够");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money-=drawingNum;
expenseTotal+=drawingNum;
}
System.out.println(this.getName()+",账户余额:"+account.money);
System.out.println(this.getName()+",总共取了:"+expenseTotal);
}
}
三十九(四).生产者消费模式
生产者消费者的概念:
wait和notify方法的使用:
用代码实现生产者消费者模式:
三十九(五).线程池:
池化技术:
线程池基本概念和优势:
线程池(Thread Pool)是一种基于池化技术设计用于管理线程的生命周期、执行任务的框架。它避免了在每次执行任务时都创建和销毁线程的开销,通过重用已存在的线程来提高程序执行效率和响应速度。线程池的概念广泛应用于并发编程中,尤其是在需要处理大量并发任务的场景中。
线程池工作原理:
线程池工作原理简述:预先创建一组工作线程并管理它们的生命周期,当有任务提交时,不直接创建新线程,而是将任务分配给空闲线程执行,避免频繁创建销毁线程的开销。若所有线程忙碌,则任务排队等待。任务完成后,线程回池等待新任务,实现资源复用和高效并发处理。
JDK内置的四种线程池:
代码示例:
package com.itbaizhan;
import java.util.concurrent.*;
/**
* 测试线程池的使用
*/
public class TestThreadPool {
public static void main(String[] args) {
//核心线程池大小:5 线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收
//maximumPoolSize:10 线程池中可以容纳的最大线程的数量(核心线程数+非核心线程数)
//keepAliveTime:200和TimeUnit.Milliseconds 非核心线程可以保留的最长的空闲时间是200毫秒(TimeUnit就是计算这时间的一个单位)
//workQueue:任务队列 任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)
//threadFactory:创建线程对象的工厂
//handler:线程池已满,拒绝执行策略
/**
* 四种JDK内置的线程池的使用方式
*/
// ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10,200,TimeUnit.MICROSECONDS,
// new ArrayBlockingQueue<Runnable>(5));
// ExecutorService executor = Executors.newCachedThreadPool();
// ExecutorService executor = Executors.newFixedThreadPool(5);//需要指定核心池大小
// ExecutorService executor = Executors.newSingleThreadExecutor();
ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);//需要指定核心池大小
executor.schedule(() -> {
System.out.println("30秒后,延时执行!");
}, 30, TimeUnit.SECONDS);
for (int i = 0; i < 20; i++) {
MyTask myTask = new MyTask(i);
executor.execute(myTask);
/* System.out.println("线程池中线程数目:"+executor.getPoolSize()+
",队列中等待的任务数:"+executor.getQueue().size()+"已执行完的任务数:"+executor.getCompletedTaskCount());*/
}
executor.shutdown();
}
}
class MyTask implements Runnable {
private int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
System.out.println("正在执行任务:" + taskNum);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务:" + taskNum + ",执行完毕!");
}
}
四十.ThreadLocal
ThreadLocal是什么
ThreadLocal 是 Java 中提供的一个线程局部变量(Thread Local Variables)的类。这个类允许你创建线程的局部变量,这些变量对于访问它们的每个线程都是独立的。这意味着每个线程都可以独立地改变自己所持有的变量副本,而不会影响到其他线程中的变量。
ThreadLocal 变量通常用于解决多线程环境中,每个线程需要独立地保存数据副本的情况。这可以避免线程间的数据共享冲突,并简化了同步处理。
主要特点和用法
- 线程隔离:每个线程访问自己的
ThreadLocal变量时,都是访问自己线程内部的一个独立副本,从而避免了线程间的数据共享问题。 - 数据传递:在复杂的多线程环境中,
ThreadLocal可以用来传递数据,特别是当数据传递路径复杂或者难以通过其他方式(如方法参数或返回值)传递时。 - 内存泄漏:使用
ThreadLocal时需要注意内存泄漏问题。如果ThreadLocal变量被设置为null后,该线程对应的ThreadLocalMap中的数据项并不会被自动删除,这可能会导致内存泄漏。因此,在使用完ThreadLocal后,应该显式地调用remove()方法来清除数据。 - 继承性:默认情况下,子线程会继承父线程中的
ThreadLocal变量,但可以通过设置ThreadLocal的inheritableThreadLocals属性来改变这一行为。
ThreadLocal怎么使用:
- 创建
ThreadLocal对象 - 设置线程局部变量
- 获取线程局部变量
- 移除线程局部变量
代码示例:
package com.itbaizhan;
/**
* 测试ThreadLocal
*/
public class TestThreadlocal extends Thread {
public static ThreadLocal<Integer> seqNum1 = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
System.out.println("初始化,第一次会被调用!");
return new Integer(100); //100是初始值。每个线程第一个调用set/get方法,都会调用initialValue()
}
};
public static int gerNextNum1() {
seqNum1.set(seqNum1.get() + 1);
return seqNum1.get();
}
public static ThreadLocal<Integer> seqNum2 = new ThreadLocal<Integer>() {
protected Integer initialValue(){
return new Integer(10);
}
};
public static int gerAdd10Num2(){
seqNum2.set(seqNum2.get()+10);
return seqNum2.get();
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+",num:"+gerNextNum1()+",num2:"+gerAdd10Num2());
}
}
public static void main(String[] args) {
Thread t0= new TestThreadlocal();
Thread t1 = new TestThreadlocal();
t0.start();
t1.start();
}
}