Java随堂笔记

代码中出现的高频单词

Type  类型
mismatch 不匹配
switch 转换
local  局部
variable  变量
initialized  初始化  赋值
resolved 引用 使用
reference 引用调用
Duplicate  重复的 
Auto-generated 自动生成
Unreachable code 不可到达 代码
declared 声明 定义
 access权限
already 已经
defined 定义
scope 范围

inherit 继承

分支结构

单分支
if(判断条件){
      代码
}

多分支1
if(判断条件){
     满足条件代码1
}else{
     不满住条件代码2
}

嵌套分支
if(判断条件1){
      代码
}else if(判断条件2){
      代码
}else{
       代码    
}


switch (valuce){
case   valuce1  sout();break;
case   valuce2  sout();break;
case   valuce3  sout();break;
case   valuce4  sout();break;
case   valuce5  sout();break;
default sout();
}

循环结构


for(开始条件 ;循环条件 ;更改条件){
    循环体;
}

典型案例:冒泡排序

public class Bubble {
    public static void main(String[] args) {
       // 调用bubbleSort()方法
        bubbleSort();
    }

    private static void bubbleSort() {
        //定义一个int类型成员变量一会做数据交换
        int box;
        //定义一个无序数组
        int[] num ={15,25,36,77,99,2,66,95,54,3};
        System.out.println("原数组顺序");
        //使用Arrays工具类的toString();方法打印num数组
        System.out.println(Arrays.toString(num));
        //数组要做num.length-1轮比较
        for (int i = 1; i <= num.length-1 ; i++) {
            //相邻比较交换位置
            for (int j = 0; j < num.length-i; j++) {
                //交换数据
                if (num[j]>num[j+1]){
                    box =num[j];
                    num[j]=num[j+1];
                    num[j+1]=box;
                }

            }
        }
        System.out.println("经过冒泡排序后的顺序");
        System.out.println(Arrays.toString(num));
    }

}

方法结构


类型名称     字节空间      默认值       取值名称

方法的格式:

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

}

面向对象的三大特征

封装


封装:把相关的数据封装成一个“类”组件

  1. 封装可以提高程序的安全性
  2. 封装可以让资源按照我们预先规定的方式来操作

继承:子类共享父类的属性和方法
多态:增强软件的灵活性和重用性

TIPS:栈和队列指的是一种数据的结构
栈:先进后出(FILO --First In Last Out)
队列:先进先出(FIFO-- First In First Out)


Phone p =new Phone();这句代码,在内存中会发生什么?

new Phone();在栈里栈内存中开辟一块空间并提供一个地址值   Phone p在栈内存中开辟一块空间并指向对应地址值
1.在栈内存中开辟一块空间,存放引用数据类型Phone类型的变量p
2.在堆内存中开辟一块空间,存放Phone类型的对象
3.要给这个对象进行初始化,比如String brand = null;
4.当对象准备好以后,会生成一个唯一的地址值,然后将此地址值交给引用数据类型的变量p来保存
5.如果以后想要操作此对象的各种资源,可以通过p变量保存的地址值找到该对象,比如p.call(); p.price=5.22;

已Phone p =new Phone();为例
new Phone();为匿名对象,在在堆中
Phone p ;对象名为怕,在栈中


一个java文件中只有一个公共类


属性封装的步骤:


1.使用private封装属性
2.提供被封装属性对应的方法
getXXX()--获取属性值 
setXXX()--修改属性值
方法封装的步骤:
1.使用private封装方法
2.可以在本类的公共方法里调用这个私有方法分功能

构造函数/构造方法

构造方法的格式:没有返回值类型并且与本类类名同名的方法
//每一个类中都会存在一个没有参数的构造方法
//每次实例化对象时,都会自动触发这个类对应的构造方法来帮助创造对象
/**如果提供了其他的构造方法,默认的无参构造会被覆盖,就不存在了
 * 所以,如果想不传参数创建对象的话,需要我们手动提供这个类的无参构造*/


1.没有返回值,与本类类同名的
2.作用:用于创建对象
3.执行时机:每次创建对象都会执行构造方法
4.1无参构造:默认存在。但是一旦提供了其他的构造方法
默认的无参构造会被覆盖,所以需要手动提供

4.2含参构造:带有参数的构造方法
 4.3全参构造:带有本类中所以的参数;

构造代码块

/**
 * 1.位置:类里方法外
 * 2.执行时机:每次创建对象时都会执行构造代码块,并且构造代码块优先于构造方法执行
 * 3.作用:用于提取所以构造方法的共性功能
 */

局部代码块

/**
 * 1.位置:方法里
 * 2.执行时机:调用局部代码块所处的方法时才会执行
 * 3.作用:用于控制变量的作用范围越小越好
 */

This


1..当本类的成员变量与局部同名时,使用this.成员变量名指定成员变量
2.this();--调用本类的无参构造
  this(参数);--调用本类的有参构造
  注意:必须写在第一行
           不能来回调用,是单向的 (不然会造成递归死循环)

当成员变量与局部变量同名时,可以使用this关键字指定本类的成员变量
* 如果不使用this指定,打印的就是就近处的这个局部变量,就近原则
*/

继承

继承的好处
提高了代码的复用性(多个类相同的成员可以放在同一个类中)
提高了代码的维护性(如果方法的代码需要修改,只修改一处即可)
继承的坏处
继承让类与类建立了关系,类的耦合性增强
当父类发生变化时,子类实现也不得不跟着变化,削弱了子类的独立性

/**继承相当于子类将父类的功能复制了
 * 继承还具有传递性,爷爷的功能会传递给爸爸,爸爸的功能会传递给儿子*/
/**通过extends继承父类  格式 子类extends父类*/
/**Java只支持单继承,一个子类只有一个父类,但是一个父类可以有多个子类*/
/**
 * 继承是一种is a的关系,比如苹果是一种水果,红富士是一种苹果
 * 继承要求子类必须是父类的一种下属类型,依赖性非常强,强耦合*/
//子类可以拥有自己独有的方法,实现功能拓展
//子类继承父类以后,可以使用父类的所有非私有的资源
//注意:这个私有资源由于被private修饰,所有没有访问权限
/**当父类的成员变量与子类的成员变量同名时,可以使用super关键字指定父类的成员变量
 * 我们可以把super看作是父类的对象:Father super = new Farther();r*/
* 1,子类在创建对象时,默认会先调用父类的构造方法
* 2.原因是子类构造函数中的第一行默认存在super();--表示调用父类的无参构造
* 3.当父类没有无参构造时可以通过super(参数);调用父类的其他含参构造
* 注意:子类必须调用父类的一个构造函数,不论是无参还是含参,选一个即可
* 4.构造方法不能被继承!因为语法的原因,要求构造方法分名字必须是本类的名字
* 不能在子类中出现一个父类名字的构造方法
* */
继承中普通方法的使用
/**如果子类对父类的方法不满意,可以在子类重写父类的方法
 * 重写的语法规则:两同 两小 一大
 * 两同:子类方法的 方法名与参数列表 父父类方法相同
 * 一大:子类方法父 修饰符权限 >= 父类的修饰符权限
 * 两小:子类方法的返回值类型与父类方法的返回值类型相同,或是父类方法的返回值是子类
 * 注意:如果父类方法的返回值类型为void,子类应该保持一致*/
  @Override //注解标记这是一个重写的方法
/**拓展:方法重写之返回值类型
 * 1.如果父类的方法的返回值类型 与子类方法父返回值类型一致,就没错
 * 2.如果父类的返回值类型为:8大基本类型/String/void 子类重写时应该保持一致
 * 3.如果父类的返回值类型是其他引用类型,子类方法:父类返回值类型的子类/两者相同*/

关键字Final

//final 可以用来修饰类 被final修饰的类是最终类 不可以被继承
//可以把final修饰的类看成树结构的叶子节点
//final可以用来修饰方法,被final修饰的方法是这个方法的最终实现,不可以被重写
//被final修饰的是常量,常量的值不可以被修改
//注意:不管是成员变量还是局部变量,常量定义的时候必须赋值
//注意常量的名称必须全大写,单词之间用下换线_分割

关键字static

/**
 * 被static修饰的资源统称为静态资源
 * 静态资源是随着类加载而加载到内存中的,比对象优先进入内存
 * 所以,静态资源可以不通过对像,直接通过类名调用
 * 类名直接调用静态资源
 */
/**静态资源只有一份 而且会被全局所有对象共享
 * 所以:不管我们使用哪种方式修改了静态变量的值,使用任何方式来查看
 * 都是静态变量那个刚刚被修改的值*/
静态的调用关系

/**
 * 总结
 * 1.普通资源既可以调用普通资源  又可以调用静态资源
 * 2.静态资源只能调用静态资源
 */
/**普通资源能否调用静态资源?--可以!*/
/**静态资源能否调用普通资源?---不可以*/
/**静态资源能否调用静态资源?---可以!*/

静态方法是随着类的加载而加载的,普通方法是通过类的实例化加载的

而类先与对象加载,则必然静态资源不能调用普通资源(类加载的时候对象还没有被创建)

静态代码块

/*静态代码块:static{}
* 1.位置:类里方法外
* 2.执行时机:静态代码块也属于静态资源,随着类的加载而加载,优先于对象加载,并且静态代码块
只执行一次
* 3.作用:用于加载那些需要第一时间就加载,并且只加载一次的资源,常用来初始化
* 4.执行顺序:静态代码块-->构造代码块--> 构造方法【对象创建成功】-->普通方法-->局部代码块
* 5.不能在静态代码和静态方法里使用this和super,因为类在对象前加载;
* */
/**构造代码块
 * 1.位置:类里方法外
 * 2.执行时机:创建对象时执行,并且优于构造方法执行
 * 3.作用:用于提取所有构造方法的共性功能*/
/**局部代码块
 * 1.位置:方法里
 * 2.执行时机:当局部代码块所处的方法被调用时才执行
 * 3.作用:用于限制变量的作用范围
 * */

多态

多态是面向对象程序设计(OOP)的一个重要特征,

同一个实体同时具有多种形式,即同一个对象,在不同时刻,代表的对象不一样,指的是对象的多种形态。

可以把不同的子类对象都当作父类来看,进而屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,统一调用标准。(屏蔽子类对象之间的差异,把不同的子类对象都当作父类来看

/*.多态的前提:继承+重写*/
/**口诀1:父类引用指向子类对象
 * 解释:创建出来的子类对象的地址值,交给父类类型的引用类型变量来保存*/
/**口诀2:编译看左边,运行看右边
 * 解释:必须要在父类中定义这个方法,才能通过编译,把多态对象看作是父类类型
 * 必须在子类中重写这个方法,才能满足多态,实际干活的是子类*/
/**多态中,调用的静态方法是父类的,因为多态对象把自己看作是父类类型,直接使用父类的静态资源*/
/*这不是重写的方法,只是恰巧在两个类中出现了一样的两个静态方法
* 注意:静态方法也属于静态资源,静态资源属于类,独一份
* 在哪里定义,就作为哪个类的资源来使用,不存在重写的情况*/

异常

异常层次结构中的根是Throwable
Error:目前我们编码解决不了的问题
Exception:异常
编译异常:未运行代码就报错了,强制要求处理
运行时异常:运行代码才报错,可以通过编译,不强制要求处理

/**如果一个方法抛出了异常,那么谁来调用这个方法,谁就需要处理这个异常
 * 这里的处理也有两种方法:捕获解决 或者 继续向上抛出
 * 但注意:我们一般在main()调用之前将异常解决
 * 而不是将问题抛给main();,因为没人解决,该报错还报错*/
/**
 * 异常捕获的格式:
 * try{
 *     可能会抛出的代码
 * }catch(预先设想的异常类型 异常名){
 *     万一捕获到异常,进行处理的解决方案
 * }
 */
//try - catch结构可以嵌套,如果有多种异常类型需要处理的话
//使用多态的思想,不论什么子异常,统一看作父类型Exception
//做出更加通用的解决方案,甚至只写1个  其他的不写
/**1.不要害怕Bug,要直面自己的Bug
 * 2.学会报错的信息提示,确定自己的错误的方向
 * 3.学会看报错的行号提示,确定自己报错的位置,哪里不对点哪里
 * 注意:源码不会出错,看你自己的代码*/
*异常抛出的格式;在方法的小括号与大括号之间 写:throw 异常类型
* 如果有多个异常,使用逗号分隔即可*/

抽象

Java中可以定义被abstract关键字修饰的方法,这种方法只有声明,没有方法体,叫做抽象方法.
Java中可以定义被abstract关键字修饰的类,被abstract关键字修饰的类叫做抽象类

  1. 如果一个类含有抽象方法,那么它一定是抽象类
  2. 抽象类中的方法实现交给子类来完成
  3. /**抽象类不可以实例化!!!--创建对象*/

抽象
1.被Astract修饰的方法是抽象方法,抽象方法没有方法体
2.一旦一个类中包含抽象方法,这个类必须被声明为抽象类
3.如果子类继承抽象父类,有两只解决方案
1)抽象子类躺平,实现一部分/不实现父类中的抽象方法
2)普通子类,还债,实现抽象类中的所有抽象方法
4.抽象类不可以被实例化
5抽象类中是有构造函数父只不过不是为了自己使用,而是为了子类创建对象时使用
6.抽象类中可以定义成员变量成员常量
7.抽象类中的方法可以全普/全抽/半普半抽
8.如果一个类不想被实例化,那么这个类可以被声明成抽象类
9.abstract关键字不可以与private          static             final公用


抽象:
从理解:对外提供统一的入口   

/**被abstract修饰 的类是抽象类
 * 如果一个类包含了抽象方法,那么这个类必须声明为抽象类*/
/**1.被abstract修饰的方法是抽象方法,抽象方法没有方法体*/
/**3.当一个子类继承了抽象父类以后,有两种解决方案:
 * 方案一:变成抽象类,“躺平,我也不实现,继续抽象”*/
/**方案二:变成普通子类“父债子偿,子类需要实现抽象父类中使用的抽象方法”*/
/**抽象类是否有构造方法? 有
 * 既然抽象类不能实例化,为什么要有构造方法呢?
 * 父类的构造方法不是为了自己的使用,而是为了子类创建对象时使用super();抽象类不能实例化*/
abstract class Fruit{
    /*1.抽象类中可以定义成员变量吗?--可以**/
    int a;
    /*2.抽象类中可以定义成员常量吗?--可以**/
    final int MAX_NUM=10;
    /*3.抽象类中可以定义普通方法吗?--可以**/
    public void eat(){
        System.out.println("我是普通方法eat");
    }
    /*4.抽象类中可以定义抽象方法吗?--可以**/
     public  abstract void run();
    /*5.抽象类中可以全是普通方法吗?--可以
    * 如果一个类中都是普通方法,那它为什么还要被修饰为抽象类呢?
    * 因为:抽象类不可以被实例化,所以如果不想让外界创建本类的对象
    * 就可以把本类抽象**/
    /*6.抽象类中可以全是抽象方法吗?--可以**/
}


接口

与之前学习过的抽象类一样,接口( Interface )在Java中也是一种抽象类型,接口中的内容是抽象形成的需要实现的功能,接口更像是一种规则和一套标准.

接口的特点

通过interface关键字来定义接口
通过implements让子类来实现接口
接口中的方法全部都是抽象方法(JAVA8)
可以把接口理解成一个特殊的抽象类(但接口不是类!!!)
类描述的是一类事物的属性和方法,接口则是包含实现类要实现的方法
接口突破了java单继承的局限性
接口和类之间可以多实现,接口与接口之间可以多继承
接口是对外暴露的规则,是一套开发规范
接口提高了程序的功能拓展,降低了耦合性

接口的特点
1.我们使用interface关键字定义接口
2.我们使用implements关键字建立接口实现类与接口的实现
接口是父级。接口实现类是子类
3.接口实现类需要实现接口的所有抽象方法,才能变成一个普通子类
4.接口不能实例化
5.接口没有构造函数,实现类使用的super()是父类的无参构造
如果没有明确指定父类,super()代表的是Object的无参构造
6.接口中都是抽象方法,会默认拼接public abstract
接口中都是常量,会默认拼接public static final
8.接口不是类
9.接口是用来指定规则的:有哪些功能?方法有参数吗?有返回只值吗?
具体实现交给接口的实现类来完成

总结:接口里是没有构造方法的

















如果一个类没有明确指定它的父类,那么它默认继承顶级父类Object,调用的super()是Object的无参构造

接口里没有成员变量,都是常量。所以,你定义一个变量没有写修饰符时,默认会加上:public static final

接口里的方法,默认都是抽象的,方法上会默认拼接public abstract。例如:public abstract void save();

实现类通过关键字implements来建立与接口的关系。重写接口中的方法达到实现接口中抽象方法功能的实现

 * 2.1如果实现类与接口建立实现关系之后
 * 方案一:可以不实现接口中的抽象达到,把自己变成一个抽象类
 * 方案二:可以选择实现接口中所以的抽象方法,把自己变成一个普通子类
/**针对于Java的类而言,一个类只能有一个父类   单继承
 * 但是接口可以实现多个接口   多实现*/

面试解答题
类与接口的关系
1.类与类的关系
继承关系,只支持单继承
比如A是父类,B是子类,B具备A的所有功能
如果B对A的功能不满意,可以重写(两同 两下)

2.类与接口的关系
实现关系,可以单实现 也可以多实现
class A implements B,C{}
A是实现类,B和C是接口,A需要实现BC接口的所有抽象方法,否则A就是一个抽象类、

3.接口与接口的关系
继承关系  可以单继承 也可以多继承
interface A extends B,C{}
A B C 都是接口,A是子接口,具有B C父接口的所有功能(抽象方法)
class X implements A{}
X实现类需要实现A接口,以及A接口继承自B C接口的所有抽象方法,否则就是抽象类

class O extends A implemets B,C{}
其中O是实现类,也是A的子类 同时拥有A的所有功能,并且需要实现BC的所有功能


接口与抽象类的区别
1.接口是用 interface定义的类型
  抽象类是用class定义的类型
2.接口中的方法都是抽象方法
  抽象类中的方法不做限制
3.接口当中都是静态变量
   抽象类中可以写普通的成员变量
4.接口中没有构造方法,不可以实例化
   抽象类中有构造方法,但是也不可以实例化
5.接口是先天设计的结果,抽象是后天重构的结果
6.接口可以多继承,抽象类只能单继承

内部类

如果一个类存在的意义就是为指定的另一个类,可以把这个类放入另一个类的内部。
就是把类定义在类的内部的情况就可以形成内部类的形式。
A类中又定义了B类,B类就是内部类,B类可以当做A类的一个成员看待:

特点:

1) 内部类可以直接访问外部类中的成员,包括私有成员

















2) 外部类要访问内部类的成员,必须要建立内部类的对象

















3) 在成员位置的内部类是成员内部类

















4) 在局部位置的内部类是局部内部类

总结1:

















1.成员内部类被Private修饰以后,无法被外界直接创建创建对象使用

















所以可以创建外部类对象,通过外部类对象间接访问内部类的资源

















2.静态资源访问时不需要创建对象,可以通过类名直接访问

















访问静态类中的静态资源可以通过”. . . ”链式加载的方式访问

3.匿名内部类属于局部内部类,而且是没有名字的局部内部类,通常和匿名对象一起使用

外部类是否可以使用内部类的资源 ---外部类要通过内部类的对象访问资源
内部类是否可以使用外部类的资源 --内部类直接调用外部类的资源包括私有资源
/**根据内部类位置的不同,分为成员内部类(类里方法外),局部内部类(方法里)*/
//如果内部类被private修饰,无法直接在外界创建内部类对象
//我们可以创建外部类的对象,直接访问内部类的资源
成员内部类,被private修饰,私有化资源无法被外界访问
//调用静态内部类的静态方法--链式加载
Outer3.Inner3.show2();
//如何使用局部内部类的资源?
/**
 * 注意:直接调用外部类的show()是无法触发内部类功能的
 * 需要在外部类中创建内部类对象并调用,才能触发内部类的功能*/
//在show()里内部类结束后创建内部类对象
//测试匿名内部类
//匿名内部类没有名字,通常与匿名对象结合一起使用
//匿名对象只能使用一次,一次只能调用一个方法
//创建抽象类对应的匿名对象new inter2()+匿名内部类{实现的抽象方法}
//当匿名内部类实现方法后,就可以调用方法了
//匿名对象+匿名内部类+调用方法
public class TestInner5 {
    public static void main(String[] args) {
        //3.创建了接口1对应的匿名对象,实现了接口中的方法,调用了实现的方法
        new Inter1() {
            @Override
            public void save() {
                System.out.println("保存");
            }

            @Override
            public void get() {

            }
        }.save();
        new Inter2() {
            @Override
            public void drink() {
                System.out.println("一人饮酒醉");
            }
        }.play();
        new Inter3().study();
    }
}

//创建接口
interface Inter1{
    void save();
    void get();
}

//创建抽象类
abstract class Inter2{
    public void play(){
        System.out.println("玩代码");
    }
    public abstract void drink();

}

//创建一个普通类
class Inter3{
    public void study(){
        System.out.println("什么都无法阻挡我学习赚钱的决心");
    }
    public void powerUp(){
        System.out.println("我们会越来越强.");
    }
}

总结2:

1.内部类创建对象的格式
外部类名.内部类名 对象名 =外部类对象.内部类对象

2.根据内部类的位置不同,分为:
成员内部类(类里方法外)
局部内部类(方法里)
3.内部类可以直接使用外部类的资源;但是外部类使用内部类资源时
需要先创建内部类的对象,通过内部类的对象来调用
4.如果内部类被private修饰,无法直接在外界创建内部类对象
我们可以创建外部类的对象,间接访问内部类的资源
5.如果内部类被static修饰,可以把这个类称为静态内部类,
静态内部类不需要先创建外部类对象,而是通过外部类的类名找到内部类,在创建内部类对象
6.如果静态内部类中有静态资源,可以不创建一个对象, 就通过
外部类名.内部类名.静态资源名的链式加载的方式,使用这个资源
7.直接创建外部类对象,调用局部内部类所处的方法时,并不会触发局部内部类的功能
需要在局部内部类所处的方法中,先创建局部内部类对象,并调用其功能功能才会被触发
8.匿名内部类没有名字,通常与匿名对象结合在一起使用
9.匿名对象只能使用一 次,一 次只能调用一个功能

匿名内部类其实就是充当了实现类的角色,去实现未实现的方法,只是没有名字而已
如果想要多次使用实现后的功能,还要创建之前的普通对象

API

API(Application Programming Interface应用程序接口)是一些预先定义的函数。目的是提供应用程序与开发人员基于某软件可以访问的一些功能集,但又无需访问源码或理解内部工作机制的细节.
API是一种通用功能集,有时公司会将API作为其公共开放系统,也就是公司制定自己的系统接口标准,当需要进行系统整合,自定义和程序应用等操作时,公司所有成员都可以通过该接口标准调用源代码.

API是一些别人制定或者写好的应用程序接口/功能
学习的重点:学习这些功能如何更好的使用,怎么使用,使用后有什么效果
比如:怎么创建某个类的对象–看构造方法
怎么使用某个功能–需不需要传参数,传什么样的参数
比如:这个方法有什么样的结果/执行效果:结果看返回值类型,效果要在IDEA去尝试
所以我们可以把API手册当成一个“字典”,哪里不会查哪里
 

Java.util包是java中的工具包,包含各种实用工具类/集合类/日期时间工具等各种常用工具包
import java.util.Scanner;
import java.util.Arrays;

java.lang包是java的核心,包含了java基础类
包括基本Object类/Class类/String类/基本数学类等最基本的类,这个包无需导入,默认会自动导入
import java.lang.Object;
import java.lang.String;
import java.lang.StringBuilder/StringBuffer;
正则表达式
包装类等等 –

Object

Object类是所有Java类的祖先,也就是说我们所说的”顶级父类”
它存在于java.lang.Object,这个包不需要我们手动导包
需要注意的是:每个类都使用Object作为超类.所有对象(包括数组)都实现这个类的方法.
在不明确给出超类的情况下,Java会自动把Object类作为要定义类的超类.

//顶级父类Object
//1.查API
//2.连点两下Shift打开IDEA的搜索,注意勾选"include non-project item"再搜Object
//3.在拓展库External libraries 找到
//4.按住Ctrl+方法名
//5测试hashCode()
/**本方法的作用是返回对象的int类型的哈希吗值
 * 本方法力求不同的对象返回的哈希吗值不同
 * 这样就可以根据哈希吗值区分不同的对象*/
//6.测试toString()
/**Object中toString()的默认实现:对象的地址值[对象类型@十六进制的哈希吗值
 * 重写toString()后,打印的是:对象类型+属性+属性值]*/

/**Object中equals()的默认实现使用的是==比较
* ==比较的是左右两边的值,如果是字面值,比如1==1
* 如果是引用类型,比较的是引用类型变量保存的地址值
* 子类重写equals 和hashCode后,比较的就是对象的类型+属性+属性值*/

//9.添加重写的equals 和hashCode
    /**equals 和hashCode逻辑要保持一致,要重写都重写,不重写都不重写
     * Object默认实现:hashCode()的哈希码值根据地址值生成
     *               equals ()底层使用==比较两个对象的地址值
     *
     *Student类重写后        hashCode()的哈希码值根据传入的对象的属性生成
     *      *               equals ()比较两个对象的类型+所有属性+属性值
     * */

String

//测试String类的使用
public class TestString {
    public static void main(String[] args) {
        //创建String的方式一
        /**字符串类型底层维护的是char[]*/
        char[] value ={'a','b','c'};
        String s1 = new String(value);//触发String(char[])的含参构造来创建对象
        String s11 = new String(value);//触发String(char[])的含参构造来创建对象

        //2.创建String的方式2
        String s2="abc";
        String s22="abc";
        String s3="diccc";

        System.out.println(s1==s2);//false 一个在堆里 一个在常量池里
        System.out.println(s1==s11);//false 两个不同的对象
        System.out.println(s2==s22);//都在堆中常量池,内容一致,地址一致
        System.out.println(s2==s3);//都在堆中常量池,但内容不一致,地址不一致
        /**Object类中equals()默认实现是通过==比较
         * 但是String类已经重写继承自Object中的equals
         * 重写后,不再按照==比较,而是比较字符串的内容
         * 也就是说不论创建方式,只要内容一致equals就返回ture */
        System.out.println(s1.equals(s2));//ture
        System.out.println(s1.equals(s11));//ture
        System.out.println(s2.equals(s3));//false
    }
}

/**String 重写了hashCode().根据字符串的具体内容生成哈希码值,而不是根据地址值
 * 所以s1与s2地址值不同,但内容一致,所以哈希码值一致*/

String类的18个常见方法

public class TestStringMethod {
    public static void main(String[] args) {
        String s1 ="abcd";

        char[] values ={'a','b','c','d'};
        String s2 = new String(values);

        System.out.println(s1.hashCode());//2987074
        System.out.println(s2.hashCode());//2987074
        //String类的hashCode()返回的是指定字符串转换的的哈希码值

        System.out.println(s1.toString());//abcd
        System.out.println(s2.toString());//abcd
        //String类的toString()返回的是指定的字符串

        String s3 = new String(values);
        String s4 ="abcd";
        String s5 ="efgh";
        System.out.println(s1.equals(s2));//true
        System.out.println(s2.equals(s3));//true
        System.out.println(s1.equals(s4));//true
        System.out.println(s4.equals(s5));//false
        //String类的equals()返回的是指定字符串比较的结果
        //由于方法重写的缘故,与那种创建方式及地址值没有关系,只看指定字符串的内容

        System.out.println(s4.length());//4
        //String类的length()返回的是指定字符串的长度

        String s6 ="EFGH";
        System.out.println(s5.toUpperCase());//EFGH
        System.out.println(s6.toLowerCase());//efgh
        //String类的toUpperCase()返回的指定字符串的全大写
        //String类的toLowerCase()返回的指定字符串的全小写

        String s7 ="jklmnb";
        System.out.println(s7.startsWith("j"));//true
        System.out.println(s7.startsWith("m"));//false
        //String类的startsWith("j")返回的是指定的字符串是否以给定的元素开头

        System.out.println(s7.endsWith("j"));//false
        System.out.println(s7.endsWith("b"));//true
        //String类的endsWith("b")返回的是指定的字符串是否以给定的元素结尾

        System.out.println(s7.charAt(5));//b
        //String类的charAt(5)返回的是指定下标处的字符/char值

        String s8 ="soklohgfouo";
        System.out.println(s8.indexOf("o"));//1
        System.out.println(s8.lastIndexOf("o"));//10
        //String类的indexOf("o")返回的是指定元素在指定字符串中第一次出现的下标值
        //String类的lastIndexOf("o")返回的是指定元素在指定字符串中最后一次出现的下标值

        String s9="asd";
        System.out.println(s9.concat("fghjkl"));//asdfghjkl
        //String类的concat("fghjkl")返回的是指定字符串连接/拼接到此字符串的结尾,注意:不会改变原串

        String s10="jdsjovbovorrnonrno";
        String[] st =s10.split("o");
        System.out.println(Arrays.toString(st));//[jdsj, vb, v, rrn, nrn]
        String类的s10.split("o")是根据给定元素来分隔此字符串


        String s11="    jj  kkkkk          ";
        System.out.println(s11.trim());//jj  kkkkk
        //String类的trim()返回的是去除首尾空格的字符串


        String s12="abcdef";
        byte[] b1 =s12.getBytes();
        System.out.println(Arrays.toString(b1));//[97, 98, 99, 100, 101, 102]
        //String类的getBytes()是把字符串存储到一个新的 byte 数组中

        System.out.println(s12.substring(1));//bcdef
        System.out.println(s12.substring(1,3));//bc
        //String类的substring(1)是从指定下标开始截取子串[1,结束]
        //String类的substring(1,3)是从指定下标开始截取子串[1,3)

        System.out.println(String.valueOf(20)+21);//2021
        //String类的String.valueOf(20)是将将int类型的21 转为String类型"21"


        String s13 ="asdfg";
        StringBuffer str = new StringBuffer();
        str.append(s13);
        System.out.println(str);
        String s14 ="qwerr";
        str.append(s14);
        System.out.println(str);

    }
}

        //1.String类的hashCode()返回的是指定字符串转换的的哈希码值

        //2.String类的toString()返回的是指定的字符串

        //3.String类的equals()返回的是指定字符串比较的结果
        //由于方法重写的缘故,与那种创建方式及地址值没有关系,只看指定字符串的内容

        //4.String类的length()返回的是指定字符串的长度

        //5.String类的toUpperCase()返回的指定字符串的全大写
        //6.String类的toLowerCase()返回的指定字符串的全小写

        //7.String类的startsWith("j")返回的是指定的字符串是否以给定的元素开头

        //8.String类的endsWith("b")返回的是指定的字符串是否以给定的元素结尾

        //9.String类的charAt(5)返回的是指定下标处的字符/char值

        //10.String类的indexOf("o")返回的是指定元素在指定字符串中第一次出现的下标值
        //11.String类的lastIndexOf("o")返回的是指定元素在指定字符串中最后一次出现的下标值

        //12.String类的concat("fghjkl")返回的是指定字符串连接/拼接到此字符串的结尾,注意:不会改变原串

        //13.String类的s10.split("o")是根据给定元素来分隔此字符串

        //14.String类的trim()返回的是去除首尾空格的字符串

        //15.String类的getBytes()是把字符串存储到一个新的 byte 数组中

        //16.String类的substring(1)是从指定下标开始截取子串[1,结束]
        //17.String类的substring(1,3)是从指定下标开始截取子串[1,3)

        //18.String类的String.valueOf(20)是将将int类型的21 转为String类型"21"

 


StringBuilder/StringBuffer

特点

1.封装了char[]数组
2.是可变的字符序列
3.提供了一组可以对字符内容修改的方法
4.常用append()来代替字符串做字符串连接”+”
5.内部字符数组默认初始容量是16:super(str.length() + 16);
6.如果大于16会尝试将扩容,新数组大小原来的变成2倍+2,容量如果还不够,直接扩充到需要的容量大小。int newCapacity = value.length * 2 + 2;
7.StringBuffer 1.0出道线程安全,StringBuilder1.5出道线程不安全
 

//测试字符串的拼接
public class TestString3 {
    public static void main(String[] args) {
        //需求:将26个字母拼接10000次
        String s ="abcdefghijklmnopqrstuvwxyz";
//        method1(s);
        method2(s);
    }

    private static void method2(String s) {
        //创建变量保存拼接后的效果
        String result =  "";
        //创建工具类对象
        StringBuffer sb = new StringBuffer();
        StringBuilder sb2 = new StringBuilder();
        //获取当前系统时间作为开始时间
        long t1 =System.currentTimeMillis();
        //拼接10000次
        for (int i = 0; i < 10000; i++) {
            //使用append()进行拼接
//            sb.append(s);
            sb2.append(s);
        }
        //获取当前循环结束的时间作为结束时间
        long t2 =System.currentTimeMillis();
//        System.out.println(sb);
        System.out.println(sb2);
        //时间差 单位:毫秒
        System.out.println((t2-t1));


    }

    private static void method1(String s) {
        //创建一个变量来保存拼接后的结果
        String result ="";
        //获取当前系统时间作为开始时间
        long t1 =System.currentTimeMillis();

        //创建循环执行10000次
        for (int i = 0; i < 10000; i++) {
            //字符串的拼接
            result =result+s;
        }
        //获取当前循环结束的时间作为结束时间
        long t2 =System.currentTimeMillis();
        System.out.println(result);
        //时间差 单位:毫秒
        System.out.println((t2-t1)/1000);
    }
}

==和equals的区别

1.当使用= =比较时,如果相比较的两个变量是引用类型,那么比较的是两者的物理地值(内存地址),如果相比较的两个变量都是数值类型,那么比较的是具体数值是否相等。
2.当使用equals()方法进行比较时,比较的结果实际上取决于equals()方法的具体实现
众所周知,任何类都继承自Object类,因此所有的类均具有Object类的特性,比如String、integer等,他们在自己的类中重写了equals()方法,此时他们进行的是数值的比较,而在Object类的默认实现中,equals()方法的底层是通过==来实现的。
 

正则表达式

正确的字符串格式规则。
常用来判断用户输入的内容是否符合格式的要求,注意是严格区分大小写的

在这里插入图片描述

String提供了支持正则表达式的方法

Matches(正则) : 当前字符串能否匹配正则表达式
replaceAll(正则,子串) : 替换子串
split(正则) : 拆分字符串

//本类用于正则表达式的入门案例
//需求:接收身份证,结果输出
public class TestRegex {
    public static void main(String[] args) {
        //编辑正则表达式
        //身份证号的规律:18位数字  ,前17位数值,18位可能是数值或X
//        String regex ="[0-9]{17}[0-9X]";
        /**单个\在java中有特殊的含义,表示转义符号,不认为是一个单纯的斜杠
         * 所以想要表示斜杠,需要在它的前面加一个用来转义的\
         * 也就是\\才表示一个单纯的斜杆
         * \t是制表符 \r回车符 \n换行符*/
        String regex ="\\d{17}[0-9X]";//

        System.out.println("请输入身份证号:");
        String input = new Scanner(System.in).nextLine();

        if (input.matches(regex)){
            System.out.println("恭喜输入正确");
        }else {
            System.out.println("您输入的身份证号不合法");
        }
    }
}
/**单个\在java中有特殊的含义,表示转义符号,不认为是一个单纯的斜杠
 * 所以想要表示斜杠,需要在它的前面加一个用来转义的\
 * 也就是\\才表示一个单纯的斜杆
 * \t是制表符 \r回车符 \n换行符*/

 包装类

把基本类型进行包装,提供更加完善的功能。
基本类型是没有任何功能的,只是一个变量,记录值,而包装类可以有更加丰富的功能

在这里插入图片描述

number类

数字包装类的抽象父类。
提供了各种获取值的方式。

在这里插入图片描述

在这里插入图片描述

Integer

//创建int类型对应的包装类Integer的对象--方式1
Integer i1 = new Integer(5);
//创建int类型对应的包装类Integer的对象--方式2
Integer i2 =Integer.valueOf(127);
//Integer有一个高效的效果,如果使用valueOf()的创建方式
/*并且数据在-128~127范围内,相同的数据只会存一次
* 后续再存都是使用之前存过的数据*/

/**包装类型也是引用类型,默认值null*/
/*如果使用valueOf()的创建方式,并且数据在-128~127范围内,相同的数据只会存一次*/
//.parseInt("800"):将String类型的数据转为int类型
System.out.println(Integer.parseInt("800")+8);
//parseDouble("2.2"):将String类型的数据转为double类  型
System.out.println(Double.parseDouble("2.2")+1.1);

自动装箱和自动拆箱

//      以前的方式
        Integer i1 = new Integer(127);
        Integer i2=Integer.valueOf(127);

//        现在的方式
        /**自动装箱:编译器会自动把基本类型int 5,包装成包装类型Integer
         * 然后交给引用类型的变量i3来保存,自动装箱时底层发生的代码:integer.ValueOf(5)
         * ValueOf()的方向:int--> Integer*/
        Integer i3 =5;
        /**自动拆箱:编译器会自动把包装类型的i1拆掉“箱子”,变回基本类型的数据127
         *然后把这个值交给int类型的变量i4来保存,自动拆箱时底层发生的代码:i1.intValue();
         * intValue() 的方向:Integer--> int*/
        int i4 =i1;

BigDecimal

BigDecimal:常用来解决精确的浮点数运算不精确的问题

//2.创建工具类BigDecimal
/**最好不要使用double作为构造函数的参数类型,不然还有不精确的现象,有坑
 * 最好使用重载的,参数类型为String的构造函数*/
BigDecimal a1 = new BigDecimal(a+"");//加上""变成String类型
BigDecimal b1 = new BigDecimal(b+"");
//通过BigDecimal对象来调用方法,实现精确运算结果
//定义BigDecimal的变量保存结果

常用方法

Add(BigDecimal bd) : 做加法运算
Subtract(BigDecimal bd) : 做减法运算
Multiply(BigDecimal bd) : 做乘法运算
Divide(BigDecimal bd) : 做除法运算,除不尽时会抛异常
Divide(BigDecimal bd,保留位数,舍入方式) : 除不尽时使用
setScale(保留位数,舍入方式) : 同上
pow(int n) : 求数据的几次幂

        //加法
        BigDecimal bd3;
        bd3=a1.add(b1);
        System.out.println("加法结果"+bd3);

        //减法
        bd3=a1.subtract(b1);
        System.out.println("减法结果"+bd3);


        //乘法
        bd3 = a1.multiply(b1);
        System.out.println("乘法结果"+bd3);

        //除法
        /**除法运算,除不尽时会抛出异常:ArithmeticException算数异常
         * 所以我们应该使用重载的divide(m,n,o)
         * m是要除以那个对象
         * n是除不尽时保留的个数
         * o是舍入方式,常用四舍五入*/

        bd3 =a1.divide(b1,3,BigDecimal.ROUND_HALF_UP);
        System.out.println("除法结果"+bd3);

拓展

舍入方式解析
ROUND_HALF_UP 四舍五入,五入 如:4.4结果是4; 4.5结果是5
ROUND_HALF_DOWN 五舍六入,五不入 如:4.5结果是4; 4.6结果是5
ROUND_HALF_EVEN 公平舍入(银行常用)
比如:在5和6之间,靠近5就舍弃成5,靠近6就进位成6,如果是5.5,就找偶数,变成6
ROUND_UP 直接进位,不算0.1还是0.9,都进位
ROUND_DOWN 直接舍弃,不算0.1还是0.9,都舍弃
ROUND_CEILING(天花板) 向上取整,取实际值的大值
朝正无穷方向round 如果为正数,行为和round_up一样,如果为负数,行为和round_down一样
ROUND_FLOOR(地板) 向下取整,取实际值的小值
朝负无穷方向round 如果为正数,行为和round_down一样,如果为负数,行为和round_up一样
 

IO流

Java 内存                                                                                                    磁盘
 
  程序的对象<<<-----------------in(输入流):读取/输入--------------------------文件
   程序的对象----------------------out(输出流):写入/输出---------------------->>>文件

TIPS:我们应该从程序的角度出发,程序读入就是输入,程序数据存到外面(比如文件里)就是输出
TIPS:流是单方向的,数据只能从头到尾顺序流动一次
输入流只能用来读取数据,输出流只能用来输出数据

在这里插入图片描述

在java中,根据处理的数据单位不同,可以把流分为字节流和字符流
字节流 : 针对二进制文件
字符流 : 针对文本文件
再结合对应类型的输入和输出方向,常用的流有:

File
字节流:针对二进制文件
InputStream
FileInputStream
BufferedInputStream
ObjectInputStream
OutputStream
FileOutputStream
BufferedOutputStream
ObjectOutputStream
字符流:针对文本文件
Reader
FileReader
BufferedReader
InputStreamReader
Writer
FileWriter
BufferedWriter
OutputStreamWriter
PrintWriter一行行写出
 

 File文件类

封装一个磁盘路径字符串,对这个路径可以执行一次操作
可以封装文件路径、文件夹路径、不存在的路径
 

//创建File类对象
/**1.Filex需要导包:import java.io.File;
 * 2.路径是String类型,必须正确,不然找不到文件
 * 3.完整的文件名包含两部分:文件名+后缀名
 * 4.传输路径*/

File file = new File("D:\\新建文件夹\\ready\\1.txt");

//测试常用方法
System.out.println(file.length());//3 获取当前文件的字节量
System.out.println(file.exists());//true 判断文件是否存在
System.out.println(file.isFile());//ture 判断是否是文件
System.out.println(file.isDirectory());//false 判断是否是文件夹
System.out.println(file.getName());//1.txt 获取完整文件名
System.out.println(file.getParent());//D:\新建文件夹\ready 获取文件的父级路径
System.out.println(file.getAbsolutePath());//D:\新建文件夹\ready\1.txt  获取带盘符的绝
对路径

//测试创建与删除
        /**new只会在内存中创建一个File类型的对象
         * 并不会帮你在磁盘中创建一个真实存在的2.txt*/
        File file2 = new File("D:\\新建文件夹\\ready\\2.txt");
        //创建一个之前不存在的文件2.txt,返回值类型boolean
         System.out.println(file2.createNewFile());//true 
        /**如果指定创建文件的路径不对,会抛出异常
         * 所以我们需要提前处理这个问题,暂时选择在main()上抛出
         * 这个IO异常目前我们遇到的强制必须预先处理的异常
         * 如果不处理,方法的调用会报错,通不过编译*/
        
        File file3 = new File("D:\\新建文件夹\\ready\\m");
        System.out.println(file3.mkdir());//true 创建不存在的单层文件夹

        File file4 = new File("D:\\新建文件夹\\ready\\a\\b\\c");
        System.out.println(file4.mkdirs());//true 创建不存在的多层文件夹
        /**delete();只能删除文件和空文件夹*/
        file2.delete();//删除文件
        file4.delete();//删除空文件夹c
        file3.delete();//直接删除空文件夹m

        //测试展现文件列表
        File file5 = new File("D:\\新建文件夹\\ready");

        String[] list = file5.list();/**不常用*/
        System.out.println(Arrays.toString(list));[1.txt, a]
        /**下面这句话报错,因为这是一个String[],所以数组中的每个元素都是String类型
         * 那么我们只能使用String类中的方法,getName()是File类的方法,在这里不常用*/
//        System.out.println(list[0].getName);

        File[] fs = file5.listFiles();/**常用 这是一个File[] fs 元素都是File类可以使用File的方法 */
        System.out.println(Arrays.toString(fs));[D:\新建文件夹\ready\1.txt, D:\新建文件夹\ready\a]

在这里插入图片描述

字节流读取

字节流是由字节组成的,字符流是由字符组成的.
Java里字符由两个字节组成.字节流是基本流,主要用在处理二进制数据。
所以字节流是比较常用的,可以可以处理多种不同种类的文件,比如文本文档/音频/视频等等

InputStream抽象类

此抽象类是表示字节输入流的所有类的超类/抽象类,不可创建对象哦

FileInputStream子类

直接插在文件上,直接读取文件数据

创建对象
FileInputStream(File file)—直接传文件对象,通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定

FileInputStream(String pathname)传路径
通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定

private static void method1() {
        InputStream in2 =null;//定义一个本方法中都生效的局部变量 ,并初始化赋值
        try {
//            InputStream in =new FileInputStream(new File("D:\\新建文件夹\\ready\\1.txt"));
            in2 =new FileInputStream("D:\\新建文件夹\\ready\\1.txt");//常用
            //开始读取
            /**read()方法每次调用都会读取一个字节,如果读到数据的末尾,返回-1,就没数据了*/
//            System.out.println(in2.read());
//            System.out.println(in2.read());
//            System.out.println(in2.read());
//            System.out.println(in2.read());
//            System.out.println(in2.read());
            /**需求:需要循环读取文件所有内容,直至读完
             * 定义变量,记录读到的数据*/
                int b;
                while ((b=in2.read())!=-1){
                    System.out.println(b);
                }

        } catch (Exception e) {
            e.printStackTrace();//打印错误栈痕迹,出错了给程序员看,方便调错

            /**finally{}是try-catch结构的第3部分
             * 这个部分不论是否捕获到异常,是一定会被执行的代码块,常用于关流*/
        }finally {
            //3.关流,流资源使用完毕必须释放
            try {
                in2.close();//关流
            } catch (IOException e) {
                e.printStackTrace();
            }

 BufferedInputStream子类

BufferedInputStream 为另一个输入流添加一些功能,在创建BufferedInputStream 时,会创建一个内部缓冲区数组(默认8k大小)。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。

创建对象
BufferedInputStream(InputStream in)
创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。

字节流写出

OutputStream抽象类

此抽象类是表示输出字节流的所有类的超类.输出流接受输出字节并将这些字节发送到某个接收器.

常用方法:
Void close() 关闭此输出流并释放与此流相关的所有系统资源
Void flush() 刷新此输出流并强制写出所有缓冲的输出字节
Void write(byte[ ] b) 将b.length个字节从指定的byte数组写入此输出流
Void write(byte[ ] b,int off ,int len) 将指定byte数组中从偏移量off开始的len个字节写入输出流
Abstract void write(int b) 将指定的字节写入此输出流

FileOutputStream 子类

构造方法(创建对象):
FileOutputStream(String name)
创建一个向具有指定名称的文件中写入数据的文件输出流
FileOutStream(File file)
创建一个向指定File对象表示的文件中写入数据的文件输出流
FileOutStream(File file,boolean append)—如果第二个参数为true,表示追加,不覆盖
创建一个向指定File对象表示的文件中写入数据的文件输出流,后面的参数是指是否覆盖原文件内容
 

BufferedOutputstream 子类

构造方法(创建对象):
BufferedOutputStream(OutputStream out)
创建一个新的缓冲输出流,用以将数据写入指定的底层输出流

字符流写出

Writer 抽象类

写入字符流的抽象类

常用方法:
Abstract void close() 关闭此流,但要先刷新它
Void write(char[ ] cbuf) 写入字符数组
Void write(int c) 写入单个字符
Void write(String str) 写入字符串
Void write(String str,int off,int len) 写入字符串的某一部分
Abstract void write(char[] cbuf,int off,int len)写入字符数组的某一部分
 

FileWriter 子类

     用来写入字符文件的便捷类,此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的.如果需要自己自定义这些值,可以先在FileOutputStream上构造一个OutputStreamWriter.

构造方法(创建对象):
FileWriter(String filename)
根据给定的文件名构造一个FileWriter对象
FileWriter(String filename,boolean append)
根据给定的文件名以及指示是否附加写入数据的boolean值来构造FileWriter

 BufferedWriter子类

构造方法(创建对象):
BufferedWriter(Writer out)
创建一个使用默认大小输出缓冲区的缓冲字符输出流

总结:IO的继承结构


1.主流分类

按照方向进行分类:输入流 输出流(相对于程序而言,从程序写数据到文件中是输出)
按照传输类型进行分类:字节流 字符流
组合: 字节输入流 字节输出流 字符输入流 字符输出流
2.学习方法:在抽象父类中学习通用的方法,在子类中学习如何创建对象


字节输入流:

InputStream 抽象类,不能new,可以作为超类,学习其所提供的共性方法
--FileInputStream 子类,操作文件的字节输入流,普通类
--BufferedInputStream 子类,缓冲字节输入流,普通类

字符输入流

Reader 抽象类,不能new,可以作为超类,学习其所提供的共性方法
--FileReader,子类,操作文件的字符输入流,普通类
--BufferedReader,子类,缓冲字符输入流,普通类


字节输出流:

OutputStream 抽象类,不能new,可以作为超类,学习其所提供的共性方法
--FileOutputStream 子类,操作文件的字节输出流,普通类
--BufferedOutputStream 子类,缓冲字节输出流,普通类

字符输出流

Writer 抽象类,不能new,可以作为超类,学习其所提供的共性方法
--FileWriter,子类,操作文件的字符输出流,普通类
--BufferedWriter,子类,缓冲字符输出流,普通类

流的分类


1.按照流的方向:输入流 输出流
2.按照传输的单位:字节流 字符流
组合情况:
字节输入流
InputStream--抽象父级,不可实例化
FileInputStream--操作文件的字节输入流--构造函数:File/String
BufferedInputStream--高效字节输入流--构造函数:InputStream

字节输出流
OutputStream--抽象父级,不可实例化
FileOutputStream--操作文件的字符输出流----构造函数:File,append/String,append 追加 File/String覆盖
BufferedOutputStream--高效字节输出流--构造函数:OutputStream
字符输入流
Reader ---抽象父级,不可实例化
FileReader --操作文件的字符输入流--构造函数:File/String
BufferedReader --高效字符输入流--构造函数:Reader  
         
字符输出流
Writer--抽象父级,不可实例化
FileWriter--操作文件的字符输出流--构造函数:File/String
BufferedWriter--高效字符输出流--构造函数:Writer

综合案例

/实现IO综合练习之文件复制案例
public class TestCopeFile {
    public static void main(String[] args) {
        //提示并接收用户输入的两个路径
        System.out.println("请输入源文件路径");
        String f =new Scanner(System.in).nextLine();
        System.out.println("请输入新文件路径");
        String t =new Scanner(System.in).nextLine();

        //2.调用自己创建好的方法完成文件的复制
//        ZFCopy(f,t);
        ZJCopy(f,t);
    }
//本方法使用字节流完成文件的复制
    private static void ZJCopy(String f,String t) {
        //定义在整个方法中都生效的局部变量,初始化
        InputStream in=null;
        OutputStream out =null;
        //由于IO操作可以会抛出异常,所以需要完成try-catch-finally结构
        //创建流对象
        try{
            in=new BufferedInputStream(new FileInputStream(f));
            out=new BufferedOutputStream(new FileOutputStream(t));
           //定义变量保存读到的数据
            int b;
            //循环读取源文件,返回-1,没有数据了
            while ((b= in.read())!=-1){
                //将本轮循环读到的数据写入新文件中
                out.write(b);
            }
            System.out.println("恭喜,复制成功");
        }catch (Exception e){
            System.out.println("抱歉,复制失败");
            e.printStackTrace();
        }finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
//本方法使用字符流完成文件的复制
    private static void ZFCopy(String f,String t) {
        //定义局部变量 初始化
        Reader in =null;
        Writer out =null;

        try{
            //3.1创建高效字符输入流对象
            //3.1创建高效字符输出流对象
            in =new BufferedReader(new FileReader(f));
            out=new BufferedWriter(new FileWriter(t));

            //创建好流对象,就可以完成业务
            //定义变量,保存读到的数据
            int b;
            //循环读取源文件,返回-1,没有数据了
            while ((b=in.read())!=-1){
                //将本轮循环读到的数据写入新文件中
                out.write(b);
            }
            System.out.println("恭喜,复制成功");
        }catch (Exception e){
            System.out.println("抱歉,复制失败");
            e.printStackTrace();
        }finally {
            /**1.关流是有顺序的,如果有多个流,最后创建的流最先关闭
             * 2.多个关流语句需要各自try-catch*/
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }


        }
    }
}

序列化与反序列化

序列化是指将对象的状态信息转换为可以存储传输形式的过程.在序列化期间,对象将其当前状态写入到临时或持久性存储区.以后可以通过从存储区中读取或者反序列化对象的状态,重新创建该对象.

序列化:利用ObjectOutputStream,对象的信息,按照固定的格式转成一串字节值输出并持久保存到磁盘
反序列化:利用ObjectInputStream,读取磁盘中之前序列化好的数据,重新恢复成对象
 

序列化: 对象转字节值反序列化:字节值(之前序列化生成的) 转 对象

/**序列化是指把程序中的java对象,永久保存在磁盘中,相当于是 写出的过程
 * 方向:out,用到的流是:ObjectOutputStream*/

/**反序列化:是把已经序列化在文件中保存的数据,读取/恢复到java程序中的过程
 * 方向:in 用到的流是ObjectInputStream*/

特点:

1.需要序列化的文件必须实现Serializable接口,用来启用序列化功能
2.不需要序列化的数据可以修饰成static,原因:static资源属于类资源,不随着对象被序列化输出
3.每一个被序列化的文件都有一个唯一的id,如果没有添加此id,编译器会自动根据类的定义信息计算产生一个
4.在反序列化时,如果和序列化的版本号不一致,无法完成反序列化
5.常用与服务器之间的数据传输,序列化成文件,反序列化读取数据
6.常用使用套接字流在主机之间传递对象
7.不需要序列化的数据也可以被修饰成transient(临时的),只在程序运行期间在内存中存在,不会被序列化持久保存

为什么反序列化版本号需要与序列化版本号一致?

我们在反序列化时,JVM会拿着反序列化流中的serialVersionUID与序列化时相应的实体类中的serialVersionUID来比较,如果不一致,就无法正常反序列化,出现序列化版本不一致的异常InvalidClassException。

而且我们在定义需要序列化的实体类时,如果没有手动添加UID,
Java序列化机制会根据编译的class自动生成一个,那么只有同一次编译生成的class才是一样的UID。

如果我们手动添加了UID,只要这个值不修改,就可以不论编译次数,进行序列化和反序列化操作。

1.同时执行序列化与反序列化方法--不会报错 UID的值一致
2.先执行序列化,再给Student添加属性,然后反序列化--会报错  UID的值不一致
3.先执行序列化,不修改Studnet,然后反序列化--不会报错 UID的值一致
4.如果UID的值写死,不会改变怎么序列化与反序列化都是成功的 UID的值写死
private static final long serialVersionID=1L;

5.IDea是默认持有UID版本号,是自动生成的

序列化和反序列化代码

需要序列化的对象

//如果本类的对象想要被序列化输出,那么本类必须实现序列化接口,否则会报错
// //Serializable接口是一个空接口,里面一个方法也没有,作用是当做标志,标志这个类可以被序列化
public class Student implements Serializable {
//本类用于封装学生类,用于测试序列化的物料类

//如果本类的对象想要被序列化输出,那么本类必须实现序列化接口,否则会报错
// //Serializable接口是一个空接口,里面一个方法也没有,作用是当做标志,标志这个类可以被序列化
public class Student implements Serializable {
    //定义学生相关的属性+private封装

    private String name;//姓名
    private int age;//年龄
    private String addr;//地址
    private char gender;//性别

    public Student() {
        System.out.println("我是student类的无参构造");
    }

    public Student(String name, int age, String addr, char gender) {
        super();//Object的无参构造
        this.name = name;
        this.age = age;
        this.addr = addr;
        this.gender = gender;
        System.out.println("我是Student类的全参构造");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    public char getGender() {
        return gender;
    }
//为了查看Student类的类型+属性+属性值  重写toString
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", addr='" + addr + '\'' +
                ", gender=" + gender +
                '}';
    }

    public void setGender(char gender) {
        this.gender = gender;

    }
}
测试序列化和反序列化

使用的是 out.writeObject(obj);方法来输出对象  in.readObject()方法来读取对象

import java.io.*;

/**x序列化:是指把程序中的java对象,永久保存在磁盘中,相当于是 写出的过程
 * 方向:out,用到的流是:ObjectOutputStream*/

/**反序列化:是吧已经序列化在文件中保存的数据,读取/恢复到java程序中的过程
 * 方向:in 用到的流是ObjectInputStream*/
public class TestSerializable {
    public static void main(String[] args) {
        method1();

        method2();
    }
//反序列化方法
    private static void method2() {
        //声明局部变量 初始化
        ObjectInputStream in =null;
        //2.try-catch
        try {
            //创建反序列化流对象
            in =new ObjectInputStream(new FileInputStream("D:\\新建文件夹\\ready\\1.txt"));
            //通过OIS反序列化读取对象
            System.out.println(in.readObject().toString());
            System.out.println("反序列化成功");
        } catch (Exception e) {
            System.out.println("反序列化失败");
            e.printStackTrace();

        }finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
//序列化方法
    private static void method1() {
        //声明一个局部变量 初始化;
        ObjectOutputStream out =null;
        //2.IO操作会发生异常 try-catch
        try {
            out =new ObjectOutputStream(new FileOutputStream("D:\\新建文件夹\\ready\\1.txt"));
            //4.指定要序列化输出的对象 要被implements Serializable标记
            Student obj = new Student("海绵宝宝", 3, "大海里", '男');
            //通过OOS流对象序列化输出Student对象
            out.writeObject(obj);
            System.out.println("恭喜,序列化成功");

        }catch (Exception e) {
            System.out.println("序列化失败");
            e.printStackTrace();

        }finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

泛型

1.泛型,不是指一种具体的类型,而是说,这里有个类型需要设置,那么后续具体需要设置成什么类型,得看具体的业务
2.泛型通常与集合一起使用,用来限制集合中存入的元素类型
3.泛型具体设置成什么类型,那么这个集合只能存这个类型的元素
4.泛型是一颗"语法糖"
1)泛型可以把报错的时机提前,用于在编译期检查集合的元素类型,只要不是泛型设置的类型,就报错,通不过编译
2)泛型只在编译时生效,编译通过以后,说明符合语法规范,泛型就会被抛弃,编译生成的字节码文件中没有泛型
3.泛型的类型必须使用引用类型,比如:Student String Integer
泛型方法:如果想要在方法上使用泛型,必须两处同时出现
1)一个是方法的参数列表中的参数类型
2)方法返回值类型前的泛型类型,表示这是一个泛型方法

//1.泛型怎么来的?--集合想要模拟数组的数据类型检查
//数组的好处:在编译时检查数据的类型:如果不是要求的数据就会报错
/**2.泛型通常结合集合一起使用
 * 3.引入泛型--主要目的是想通过泛型来约束集合中的元素类型
 * 引入泛型的好处:可以把报错时机提前,在编译期报错,而不是运行后才抛出异常
 * 向集合中添加元素时,会检查元素的数据类型,不是要求的类型就编译失败 */
//没有泛型,数据类型没有约束
//约束类型后只能传泛型规定的类型数据
/**泛型可以使用通用代码的编写,使用E表示元素的类型是Element类型
 * 2.泛型的语法要求:
 * 如果在方法上使用泛型,必须两处同时出现,一个是传入参数的参数
 * 一个是返回值类型前的泛型类型,表示这是一个泛型方法*/

在这里插入图片描述

public class TestGeneric2 {

    public static void main(String[] args) {
        Integer[] a = {1,2,3,4,5,6,7,8,9,10};
        print1(a);
        String[]  b = {"1哥","2哥","3哥","4哥","5哥","6哥","7哥","18","小弟"};
        print1(b);
        Double[]  c = {6.0,6.6,6.6,6.666,6.6666};
        print1(c);
    }

    /**泛型可以使用通用代码的编写,使用E表示元素的类型是Element类型
     * 2.泛型的语法要求:
     * 如果在方法上使用泛型,必须两处同时出现,一个是传入参数的参数
     * 一个是返回值类型前的泛型类型,表示这是一个泛型方法*/
    private static<E> void print1(E[] e) {
        for (E d:e) {
            System.out.println(d);

        }

泛型声明



泛型可以在接口 类 方法上使用
在这里插入图片描述


//高效for循环/foreach循环
//        /**好处:比普通循环写法简便,效率高
//         * 坏处:没有办法按照下标操作值,只能从头到尾遍历,不灵活
//         * for (2 3:1) { 循环体}
//         * 1是要遍历的内容,2 3是每轮遍历到的具体元素的类型与名字*/

集合

集合的英文名称是Collection,是用来存放对象的数据结构,而且长度可变,可以存放不同类型的对象,并且还提供了一组操作成批对象的方法.Collection接口层次结构 中的根接口,接口不能直接使用,但是该接口提供了添加元素/删除元素/管理元素的父接口公共方法.
由于List接口与Set接口继承了Collection接口,因此这些方法对于List集合和Set集合是通用的.
 

  1. Collection是集合层次中的根接口
  2. 集合的继承关系
  3. 是集合层次的根接口,学习抽象父级的公共方法

在这里插入图片描述

Collection接口












List 接口【数据有下标,有序,可重复】












ArrayList子类












LinkedList子类












Set 接口【数据无下标,无序,不可重复】












HashSet子类












Map 接口【键值对的方式存数据】












HashMap子类

Collection

单个集合的操作:

boolean add(E e) 将指定元素添加到集合中
void clear() 清空集合
boolean contains(Object o) 判断本集合是否包含指定的元素
boolean equals(Object o) 比较集合对象与参数对象o是否相等
int hashCode() 返回本集合的哈希码值。
boolean isEmpty() 判断本集合是否为空
boolean remove(Object o) 从本集合中移除指定元素o
int size() 返回本集合中元素的个数
Object[] toArray() 将本集合转为数组
 

集合间的操作:

boolean addAll(Collection<> c) 将c集合中的所有元素添加到本集合中
boolean containsAll(Collection<> c) 判断本集合是否包含c集合的所有元素
boolean removeAll(Collection<> c) 移除本集合中属于参数集合c的所有元素
boolean retainAll(Collection<> c) 保留本集合与参数集合c的公共元素
 

在这里插入图片描述

import java.util.Arrays;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Iterator;

//本类用于测试Connection接口
public class TestCollection {
    public static void main(String[] args) {
        //创建Connection相关的集合对象
        //Connection是接口
        Collection<Integer> c = new ArrayList<>();
        c.add(100);
        c.add(200);
        c.add(300);
        c.add(400);
        c.add(500);
        System.out.println(c);//可以直接打印集合的名字,查看集合中存入的元素
//        c.clear();
//        System.out.println(c);//[]  清空集合
        System.out.println(c.hashCode());//获取集合对象对应的哈希码值
        System.out.println(c.equals(200));//false  集合对象c与元素200不等
        System.out.println(c.contains(200));//true  判断集合是否包含指定元素200
        System.out.println(c.isEmpty());//false  判断集合是否为空
        System.out.println(c.remove(100)); //删除集合中的指定元素100
        System.out.println(c);//[200, 300, 400, 500]
        System.out.println(c.size());//获取集合中元素个数
        Object[] array = c.toArray();//将指定集合转为数组Object[]
        System.out.println(Arrays.toString(array));//[200, 300, 400, 500]
        //3.测试多个集合间的操作
        ArrayList<Integer> c2 = new ArrayList<>();
        c2.add(2);
        c2.add(4);
        c2.add(5);
        System.out.println(c2);

        c.addAll(c2);//将c2集合的所有元素添加到c集合中
        System.out.println(c);//[200, 300, 400, 500, 2, 4, 5]
        System.out.println(c2);//[2, 4, 5]

        System.out.println(c.containsAll(c2));//判断c集合是否包含c2集合的所有元素
        c.retainAll(c2);//保留c集合中那些也属于c2集合的那些公共元素 交集
        System.out.println(c);//[2, 4, 5]
        c.removeAll(c2);//删除c集合中属于c2中的所有元素
        System.out.println(c);//[]

        //4.集合的迭代/遍历
        /**1.获取集合对应的迭代器
         * 2.通过迭代器判断集合中是否有下一个元素可以迭代
         * 3.通过迭代器获取当前迭代的元素*/
        Iterator<Integer> it = c2.iterator();
        while (it.hasNext()){//如果有下一个元素
            System.out.println(it.next());//打印本轮循环的元素
        }

    }
}

//创建Connection相关的集合对象 //Connection是接口
     Collection<Integer> c = new ArrayList<>();
        
     System.out.println(c);//可以直接打印集合的名字,查看集合中存入的元素
//   c.clear();
//   System.out.println(c);//[]  清空集合
     c.hashCode());//获取集合对象对应的哈希码值
     c.equals(200));//false  集合对象c与元素200不等
     c.contains(200));//true  判断集合是否包含指定元素200
     c.isEmpty());//false  判断集合是否为空
     c.remove(100)); //删除集合中的指定元素100
     c.size());//获取集合中元素个数
     Object[] array = c.toArray();//将指定集合转为数组Object[]
     System.out.println(Arrays.toString(array));//[200, 300, 400, 500]
        
//3.测试多个集合间的操作
        ArrayList<Integer> c2 = new ArrayList<>();
        c.addAll(c2);//将c2集合的所有元素添加到c集合中
        c.containsAll(c2));//判断c集合是否包含c2集合的所有元素
        c.retainAll(c2);//保留c集合中那些也属于c2集合的那些公共元素 交集
        c.removeAll(c2);//删除c集合中属于c2中的所有元素
  

//4.集合的迭代/遍历
        /**1.获取集合对应的迭代器Iterator
         * 2.通过迭代器判断集合中是否有下一个元素可以迭代
         * 3.通过迭代器获取当前迭代的元素*/
        1.   Iterator<Integer> it = c2.iterator();
        2.   while (it.hasNext()){//如果有下一个元素
        3.   System.out.println(it.next());//打印本轮循环的元素}
        

List接口

有序的colletion(也称为序列).此接口的用户可以对列表中的每个元素的插入位置进行精确的控制,用户可以根据元素的整数索引(在列表中的位置)来访问元素,并搜索列表中的元素.

特点

  1. 元素都有下标
  2. 数据是有序的
  3. 允许存放重复的元素

在这里插入图片描述

单个集合间的操作

void add(int index, E element) 在集合的指定下标index处插入指定元素element
E get(int index) 返回本集合中指定下标index处的元素
E remove(int index) 移除本集合中指定下标index处的元素
E set(int index, E element) 用参数元素element替换集合中指定下标index处的元素
int indexOf(Object o) 判断指定元素o在本集合中第一次出现的下标,如果不存在,返回-1
int lastIndexOf(Object o) 判断指定元素o在本集合中最后一次出现的下标,如果不存在,返回-1
List subList(int fromIndex, int toIndex) 截取子集合,包含formidex处的元素,不包含toIndex处的元素
 

//本类用于测试List接口
public class TestList {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();

        //测试继承自Collection中的方法
        list.add("1娃");
        list.add("2娃");
        list.add("3娃");
        list.add("4娃");
        list.add("5娃");
        list.add("6娃");
        list.add("7娃");
        System.out.println(list);
//        list.clear();
//        System.out.println(list);//[]
        System.out.println(list.contains("1娃"));
        System.out.println(list.equals("1娃"));
        System.out.println(list.isEmpty());
        System.out.println(list.remove("1娃"));
        System.out.println(list.size());
        System.out.println(Arrays.toString(list.toArray()));//将集合转为数组


        //测试List集合的特点:有顺序 :跟下标有关 可以存放重复的元素
        //可以存放重复的元素
        list.add("小蝴蝶");
        list.add(1,"蛇精");//在指定的索引处添加元素

        list.add(3,"小蝴蝶");//在指定的索引处添加元素
        list.add("小蝴蝶");//可以存放重复的元素
        System.out.println(list);
        System.out.println(list.indexOf("小蝴蝶"));//获取元素第一次出现的下标
        System.out.println(list.lastIndexOf("小蝴蝶"));//获取元素最后一次出现的下标
        System.out.println(list);
        list.remove(5);//根据索引删除元素
        System.out.println(list);
        System.out.println(list.get(3));//根据下标获取元素;
        System.out.println(list.set(7,"蝎子精"));//根据下标修改元素;
        System.out.println(list);

        //测试集合间的操作
        List<String> list2 = new ArrayList<>();
        list2.add("1");
        list2.add("2");
        list2.add("3");
        list2.add("4");
        System.out.println(list2);//查看集合中的元素

        list.addAll(list2);
        System.out.println(list);//默认将集合2的元素追加到集合1末尾
        list.addAll(1,list2);//指定下标,将2集合的所有元素添加到1集合中
        System.out.println(list);

        //作业
        //使用3种方式遍历集合2
        /**
         * for循环
         * 高效for循环
         * iterator*/
        System.out.println(list2);
        System.out.println("--------");
        for (int i = 0; i < list2.size(); i++) {
            System.out.println(list2.get(i));
        }
        System.out.println("--------");
        for (String s:list2) {
            System.out.println(s);

        }
        System.out.println("--------");
        Iterator<String> it = list2.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }
    }

}

        List<String> list=new ArrayList<>();//

        //测试继承自Collection中的方法
       
        System.out.println(list);
//      list.clear();
//      System.out.println(list);//[]
        System.out.println(list.contains("1娃"));
        System.out.println(list.equals("1娃"));
        System.out.println(list.isEmpty());
        System.out.println(list.remove("1娃"));
        System.out.println(list.size());
        System.out.println(Arrays.toString(list.toArray()));//将集合转为数组


        //测试List集合的特点:有顺序 :跟下标有关 可以存放重复的元素
        //可以存放重复的元素
        list.add("小蝴蝶");
        list.add(1,"蛇精");//在指定的索引处添加元素

        list.add(3,"小蝴蝶");//在指定的索引处添加元素
        list.add("小蝴蝶");//可以存放重复的元素
        ist.indexOf("小蝴蝶"));//获取元素第一次出现的下标
        list.lastIndexOf("小蝴蝶"));//获取元素最后一次出现的下标
        list.remove(5);//根据索引删除元素
        list.get(3));//根据下标获取元素;
        list.set(7,"蝎子精"));//根据下标修改元素;
        

        //测试集合间的操作
        List<String> list2 = new ArrayList<>();
        
        list.addAll(list2);//默认将集合2的元素追加到集合1末尾
        list.addAll(1,list2);//指定下标,将2集合的所有元素添加到1集合中
        

        
        遍历集合
//使用3种方式遍历集合2
        /**
         * for循环
         * 高效for循环
         * iterator*/
        System.out.println(list2);
        System.out.println("--------");
        for (int i = 0; i < list2.size(); i++) {
            System.out.println(list2.get(i));
        }
        System.out.println("--------");
        for (String s:list2) {
            System.out.println(s);

        }
        System.out.println("--------");
        Iterator<String> it = list2.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }
    }

}

ArrayList

  1. 存在java.util包中
  2. 内部是用数组结构存放数据,封装数组的操作,每个对象都有下标
  3. 内部数组默认的初始容量是10,如果不够会以1.5倍的容量增长
  4. 查询快,增删数据效率会低

特点

1.List接口的实现类
2.底层的数据结构是数组,内存空间是连续的
3.元素有下标,有序,允许存放重复的元素
4.通常可以根据下标进行操作
5.增删操作比较慢,查询操作比较快[数据量比较大时]

ArrayList代码

//本类用于ArrayList的相关测试
public class TestArrayList {
    public static void main(String[] args) {
        //1.创建对应的集合对象
        //底层自动创建数组存放对象,并且数组的初始容量是10
        ArrayList<Integer> list = new ArrayList<>();

        list.add(100);
        list.add(200);
        list.add(300);
        list.add(400);
        list.add(400);
        System.out.println(list);
        //测试常用方法

//        list.clear();//清空集合
//        System.out.println(list);//[]

        System.out.println(list.contains("100"));//false 。是否指定包含指定元素“100”,不包含,"100"是字符串
        System.out.println(list.get(0));//根据下标获取指定的元素
        System.out.println(list.indexOf(400));//第一次出现元素的下标
        System.out.println(list.lastIndexOf(400));//最后一次出现元素的标标
        //System.out.println(list.remove(300));//下标越界异常,若选择的参数列表是 (int index)删除指定的下标元素
        /**index 300 size 6  List中有两个remove() 传入300 会默认看作是 int类型的index索引
         * 所以如果想指定元素删除数据,需要报int类型的300手动装箱成Integer类型的300元素*/
        System.out.println(list.remove(Integer.valueOf(300)));//自动装箱 把int转为Integer

        System.out.println(list.set(2,777));//修改指定位置的元素为777
        System.out.println(list.size());//获取集合中的元素个数

        //测试集合间的操作
        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(1);
        list2.add(2);
        list2.add(3);
        list2.add(4);
        System.out.println(list2);
        list.addAll(list2);//添加到,末尾
        System.out.println(list);
        list.addAll(0,list2);//添加到集合的指定下标处
        list.removeAll(list2);
        System.out.println(list.containsAll(list2));//是否包含指定集合的所有元素

        list.retainAll(list2);//取交集
        //测试集合的迭代
        /**
         * for循环
         * 高效for循环
         * iterator
         * ListIterator
         */
        list.add(5);
        list.add(6);
        list.add(77);
        list.add(8);
        list.add(9);

        //方式1.List集合有序,有下标,可根据下标进行遍历
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));


        }
        System.out.println("------------------方式二---------------");
        //普通for遍历效率低,语法复杂。使用高效for循环遍历
        for (Integer a:list) {
            System.out.println(a);
        }
        System.out.println("------------------方式三---------------");
        /**获取迭代集合对应的迭代器
         * 通过迭代器判断是否由下一个元素可以迭代
         * 3.通过迭代器获取迭代的元素
         */
        Iterator<Integer> l1 = list.iterator();
        while (l1.hasNext()){
            System.out.println(l1.next());
        }
        /**
         * ListIterator属于List接口独有的迭代器
         * Iterator<E>--父接口--hasNext() next()
         * ListIterator<E> --子接口 --可以使用父接口的功能,除了父接口的功能外,比如逆序遍历,添加元素等, 不常用
         * public interface ListIterator<E> extends Iterator<E>
         */
        System.out.println("------------------方式四---------------");
        ListIterator<Integer> it2 = list.listIterator();
        while (it2.hasNext()){
            System.out.println(it2.next());
        }


    }
}

LinkedList

链表,两端效率高,底层就是链表实现的

在这里插入图片描述

请添加图片描述

在这里插入图片描述

常用方法

void addFirst(E e) 添加首元素
void addLast(E e) 添加尾元素
E getFirst() 获取首元素
E getLast() 获取尾元素
E element() 获取首元素
E removeFirst() 删除首元素
E removeLast() 删除尾元素

boolean offer(E e) 添加尾元素
boolean offerFirst(E e) 添加首元素
boolean offerLast(E e) 添加尾元素
E peek() 获取首元素
E peekFirst() 获取首元素
E peekLast() 获取尾元素
E poll() 返回并移除头元素
E pollFirst() 返回并移除头元素
E pollLast() 返回并移除尾元素
 

特点

 1.List接口的实现类
2.底层的数据结构是链表,内存空间是不连续的
3.元素有下标,有序,允许存放重复的元素
4.通常进行首尾节点的操作比较多
5.增删操作比较快,查询操作比较慢[数据量比较大时]
注意:LinkedList的查询操作也不是都慢,首尾操作还是很快的

简单方法:

void addFirst(E e) 添加首元素
void addLast(E e) 添加尾元素
E removeFirst() 删除首元素
E removeLast() 删除尾元素
E getFirst() 获取首元素
E getLast() 获取尾元素
E element() 获取首元素

功能一致但是名字不太好记的方法:

boolean offer(E e) 添加尾元素
boolean offerFirst(E e) 添加首元素
boolean offerLast(E e) 添加尾元素
E peek() 获取首元素
E peekFirst() 获取首元素
E peekLast() 获取尾元素
E poll() 返回并移除头元素
E pollFirst() 返回并移除头元素
E pollLast() 返回并移除尾元素

LInkedList测试代码

//本类用于LinkedList的相关测试
public class TestLinkedList {
    public static void main(String[] args) {
        //1.创建集合对象
        LinkedList<String> list = new LinkedList<>();
        list.add("孙悟空");
        list.add("猪八戒");
        list.add("唐三藏");
        list.add("沙悟净");
        list.add("白龙马");
        System.out.println(list);

        //课后继承collection的方法
        //测试LinkedList特有的方法
        list.addFirst("蜘蛛精");//添加首元素
        list.addLast("兔子精");//添加尾元素
        System.out.println(list);
        System.out.println(list.getFirst());//获取首元素
        System.out.println(list.getLast());//获取尾元素

        System.out.println(list.remove());//删除首元素
        System.out.println(list.removeLast());//删除尾元素
        System.out.println(list);
        System.out.println(list.element());//获取首元素
        /**别名,查询系列*/
        System.out.println(list.peek());//获取首元素
        System.out.println(list.peekFirst());//获取首元素
        System.out.println(list.peekLast());//获取尾元素
        /**别名,新增系列*/
        System.out.println(list.offer("海绵宝宝"));//添加尾元素
        System.out.println(list);
        System.out.println(list.offerFirst("派大星"));//添加首元素
        System.out.println(list.offerLast("蟹老板"));//添加尾元素
        System.out.println(list);

        /**别名,删除系列*/
        System.out.println(list.poll());//删除首元素
        System.out.println(list.pollFirst());//删除首元素
        System.out.println(list.pollLast());//删除尾元素


    }
}

面试题:
底层使用数组
ArrayList的特点
1.List 接口的实现类
2.可以通过下标进行操作
3.有下标,有序可重复
4.底层结构是数组,内存空间是连续的
5.增删操作比较慢,查询操作比较快【数据量比较大时】
6.初始容量为10,扩容以1.5倍扩容


【底层使用链表】
LinkedList的特点
1.List 接口的实现类
2.有下标,有序可重复
3.底层的数据结构是链表,内存空间是不连续的
4.通过进行首尾节点的操作比较多
5.增删操作比较快,查询操作比较慢【数据量比较大时】
注意:Linked的查询操作也不是都慢。首尾操作比较快

 Map接口

特点

  1. map集合的结构是:键值对KEY与VALUE、Map.Entry<K,V>的映射关系
  2. map中key值不允许重复,如果重复,对应的value会被覆盖
  3. map中的映射关系是无序
  4. map没有自己的迭代器,所以迭代时通常需要转成set集合来迭代

简单方法:

void clear() 清空集合
boolean equals(Object o) 判断集合对象与参数o是否相等
int hashCode() 返回本集合的哈希码值
boolean isEmpty() 判断集合是否为空
int size() 返回本集合中键值对的个数

map单个集合间的操作

boolean containsKey(Object key) 判断map中是否包含指定的key
boolean containsValue(Object value) 判断map中是否包含指定的value
V get(Object key) 根据指定的key返回对应的value,如果不存在,返回null
V remove(Object key) 删除本集合中参数key对应的键值对
V put(K key, V value) 向集合中添加映射关系(键值对)
void putAll(Map<> m) 向本集合中添加m集合的所有映射关系(键值对)

map的迭代

Collection values() 把本map中的Value值取出放入一个Collection中并返回这个Collection
Set keySet() 把本map中的Key值取出放入一个Set集合中并返回这个Set集合
Set<Map.Entry<K,V>> entrySet()
把本map中的每一对KV都看成是一个Entry,把所有的Entry取出放入一个Set集合中并返回这个Set集合
 

在这里插入图片描述

在这里插入图片描述

 Map集合代码

/**本类用于测试map接口*/
public class MapDemo {
    public static void main(String[] args) {
        //1.创建Map集合对象
        /**Map中的数据要符合映射规则,一定要注意要同时指定K和V的数据类型
         * 至于这个K和V具体要设置成什么类型,取决于具体的业务需求*/
        HashMap<Integer, String> map = new HashMap<>();
        //向map集合存入数据,注意是put并且需要存入一对<K,V>的值
        map.put(9527,"白骨精");
        map.put(9528,"黑熊精");
        map.put(9529,"鲤鱼精");
        map.put(9530,"黄毛怪");
        map.put(9527,"女儿国");
        /**map中的K不允许重复,如果重复,后面的value会把前面的value覆盖掉
         * 例:key值是9527的女儿国覆盖白骨精,
         * map中的value可以重复,比如我们有两个黑熊精*/

        map.put(9531,"黑熊精");
        System.out.println(map);

        //方法测试
//        map.clear();//清空接口
//        System.out.println(map);//{}
        System.out.println(map.isEmpty());//false,判断集合是否为空
        System.out.println(map.size());//获取集合中键值对的对数
        System.out.println(map.containsKey(9527));//判断集合是否包含指点的键
        System.out.println(map.containsValue("白骨精"));//判断集合是否包含指点的值
        System.out.println(map);
        System.out.println(map.remove(9529));//根据key删除对应的这一对键值对,K和V都删了
        System.out.println(map);

        Collection<String> c = map.values();//将map中所有的value值取出,存入collection中
        System.out.println(c);//[女儿国, 黑熊精, 黄毛怪, 黑熊精]

        //map集合的迭代方式1
        /**方式1:
         * 遍历map中的数据,但是map本身没有迭代器,所以需要先转成set集合
         * 用到的方法:keySet():把map中所有的key存入set集合中Set<Key>*/
        //将所有的key值取出来。存入Set集合。Set集合的泛型就是key的类型
        Set<Integer> set = map.keySet();
        System.out.println(set);
        Iterator<Integer> it = set.iterator();
        while (it.hasNext()){
            Integer key = it.next();
            String value = map.get(key);
            System.out.println("{"+key+","+value+"}");



            //方式二
            /**遍历map集合,需要把map集合转为set集合
             * 是把map中的一对键值对key&value 作为一个Entry<K,V>整体放入Set
             * 一对K,V就是一个Entry*/

        }


        System.out.println("--------------");
        Set<Map.Entry<Integer, String>> set2 = map.entrySet();
        Iterator<Map.Entry<Integer, String>> it2 = set2.iterator();
        while (it2.hasNext()){
            Map.Entry<Integer, String> entry = it2.next();
            System.out.println(entry);
            Integer key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key+"!!!"+value);
        }

    }
}

面试题

HashMap的存储过程:

HashMap的结构是数组+链表 或者 数组+红黑树 的形式
HashMap底层的Entry[ ]数组初始容量为16加载因子是0.75f,扩容按约为2倍扩容
当存放数据时,会根据hash(key)%n算法来计算数据的存放位置n就是数组的长度,其实也就是集合的容量
当计算到的位置之前没有存过数据的时候,会直接存放数据
当计算的位置,有数据时,会发生hash冲突/hash碰撞
解决的办法就是采用链表的结构,在数组中指定位置处以后元素之后插入新的元素
也就是说数组中的元素都是最早加入的节点
如果链表的长度>8并且数组长度>64时,链表会转为红黑树,当链表的长度<6时,会重新恢复成链表
在这里插入图片描述

统计字符串的字符个数

public class MapExec1 {
    public static void main(String[] args) {
        System.out.println("请输入字符串:");
        String str =new Scanner(System.in).nextLine();
        HashMap<Character, Integer> map = new HashMap<>();

        int key=0;
        for (int i = 0; i < str.length(); i++) {

            char key1 = str.charAt(i);
            System.out.println(key1);
            
            Integer value = map.get(key1);

            if (value==null){
                map.put(key1,1);
            }else {
                map.put(key1,value+1);
            }


        }
        System.out.println(map);

    }
}

set接口

在这里插入图片描述

  1. Set是一个不包含重复数据的Collection
  2. Set集合中的数据是无序的(因为Set集合没有下标)
  3. Set集合中的元素不可以重复 – 常用来给数据去重

 特点

  1. 数据无序且数据不允许重复
  2. HashSet : 底层是哈希表,包装了HashMap相当于向HashSet中存入数据时,会把数据作为K,存入内部的HashMap中。当然K仍然不许重复
  3. TreeSet : 底层是TreeMap,也是红黑树的形式,便于查找数据

set集合没有重复的元素
set集合的元素是无序
set集合可以存null值,并且null最多有一个
我们自定义对象如果想去重,需要在自定义类中添加重写的equals()与hashCode()

方法

学习Collection接口中的方法即可

HashSet

底层是哈希表,包装了HashMap,相当于向HashSet中存入数据时,会把数据作为K存入内部的HashMap中,其中K不允许重复,允许使用null.

测试Set

//测试Set
public class TestSet {
    public static void main(String[] args) {
        //创建对应的集合对象
        HashSet<String> set = new HashSet<>();

        set.add("紫霞");
        set.add("紫霞");
        set.add("至尊宝");
        set.add("蜘蛛精");
        set.add(null);
        set.add(null);
        /**1.Set集合中的元素都是没有顺序的‘
         * 2.set的元素不能重复
         * 3.Set集合可以存NULL但最多只有一个*/

        System.out.println(set);

        //常用方法
        System.out.println(set.contains("唐三藏"));//判断是否包含指定元素
        System.out.println(set.isEmpty());//判断集合是否为空
        System.out.println(set.remove(null));//移除集合中的元素null
        System.out.println(set);
        System.out.println(set.size());
        System.out.println(Arrays.toString(set.toArray()));

        //集合间的操作
        Set<String> set2 = new HashSet<>();
        set2.add("兔子");
        set2.add("老虎");
        set2.add("海豚");
        set2.add("狮子");
        System.out.println(set2);
        set2.addAll(set);//添加
        System.out.println(set2);

        System.out.println(set.containsAll(set2));//包含
        System.out.println(set2.removeAll(set));//移除

        System.out.println(set2.retainAll(set));//交集
        //Set集合的迭代
        //获取迭代器
        Iterator<String> it = set.iterator();

        while (it.hasNext()){
            System.out.println(it.next());
        }

    }
}

测试Set集合存放自定义的引用类型对象Student

//本类用于测试Set集合存放自定义的引用类型对象
public class Student {
    String name;
    int id;
    //全参构造
    public Student(String name, int id) {
        this.name = name;
        this.id = id;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", id=" + id +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id == student.id && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, id);
    }
}

测试Set存放自定义对象去重问题

//测试Set存放自定义引用类型对象的去重问题
public class TestSet2 {
    public static void main(String[] args) {
        Set<Student> set = new HashSet<>();
        //创建Student的对象
        Student s1 = new Student("张三",3);
        Student s2 = new Student("李四",4);
        Student s3 = new Student("王五",5);
        Student s4 = new Student("王五",5);
        //3.将创建好的学生对象存入Set集合中
        set.add(s1);
        set.add(s2);
        set.add(s3);
        set.add(s4);
        /**Set集合中存放的是自定义的类型
         * 那么需要给自定义类中添加重写的equals() 与hashcode(),才会去重
         * 不然,set集合会认为s3和s4的地址值不同,是两个不同的对象,不会去重*/
        System.out.println(set);

        HashSet<Dog> dogs = new HashSet<>();
        Dog d1 = new Dog("小黑", 3);
        Dog d2 = new Dog("小白", 6);
        Dog d3 = new Dog("小黄", 9);
        Dog d4 = new Dog("小黄", 9);
        dogs.add(d1);
        dogs.add(d2);
        dogs.add(d3);
        dogs.add(d4);
        System.out.println(dogs);

    }
}

进程与线程

进程

进程就是正在运行的程序,它会占用对应的内存区域,由CPU进行执行与计算。

特点

独立性
进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间
动态性
进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的.
并发性
多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响.
 

线程

线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.
一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程
我们看到的进程的切换,切换的也是不同进程的主线程
多线程可以让同一个进程同时并发处理多个任务,相当于扩展了进程的功能。

什么是线程?什么是程序?
进程:给程序加入了时间的概念,是动态的,代表的是OS中正在运行的程序
独立性      动态性        并发性
程序:数据与指令的集合。程序是静态的

线程:OS能够进行运算调度的最小单位
一个进程能够有多个线程也可以只有一个线程
只有一个线程的进程叫做单线程程序

CPU:电脑的核心处理器,类似大脑 


线程的三种状态
                
就绪,执行,阻塞

线程的五种状态
创建  就绪  执行  阻塞  终止

线程与进程的关系

1.一个操作系统中可以有多个进程,一个进程中可以包含一个线程(单线程程序),也可以包含多个线程(多线程程序)

2.每个线程在共享同一个进程中的内存的同时,又有自己独立的内存空间.

线程的5种状态

线程生命周期,主要有五种状态:

1.新建状态(New) : 当线程对象创建后就进入了新建状态.如:Thread t = new MyThread();
2.就绪状态(Runnable):当调用线程对象的start()方法,线程即为进入就绪状态.
处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()此线程立即就会执行
3.运行状态(Running):当CPU调度了处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态
就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态
4.阻塞状态(Blocked):处于运状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行.
根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种:
等待阻塞:运行状态中的线程执行wait()方法,本线程进入到等待阻塞状态
同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态
其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态.当sleep()状态超时.join()等待线程终止或者超时或者I/O处理完毕时线程重新转入就绪状态
5.死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
 

多线程的创建方式:(面试重点

方式1:extends Thread   继承Thread类  
1.定义自己的多线程类,并且继承Thread
2.重写父类的run(),添加自己的业务
3.创建多个自定义线程对象
4.通过线程对象.start()将线程加入到就绪队列中
5.查看多线程抢占资源的效果

方式2:implements Runnable  实现 Runnable接口  (常用)
1.定义自己的业务类并实现接口Runnable
2.在业务类中添加接口里的抽象方法run(),并实现业务
3.创建唯一的业务类对象
4.创建多个Thread类对象,作为多个线程对象,并将刚刚的业务对象传入
5.使用多个线程类对象,调用start()方法,将线程加入到就绪队列之中
6.查看多线程抢占的效果
方案2的优点:(常用原因)

1.没有继承,耦合性不强,后续仍然可以继承或实现其他的接口,比较自由
2.可以给所有的线程对象统一业务,业务只需要发布一次,保持统一
3.面向接口进行编程,代码更加高级

方式3:Executors 创建线程池的方式

1.创建线程池的工具类:
Executors.newFixedThreadPool(int n);可以创建包含最多n个线程的线程池对象
2.创建好的线程池对象:ExecutorService
使用pool.excute()来讲线程池中的线程以多线程的方式启动,每次调用都会将一个线程对象加入到就绪队列之中
这个线程池对象负责: 新建/启动/关闭线程,而我们主要负责的是自定义的业务
注意:线程池是不关闭的,实现的效果就是线程池中线程对象的随取随用,这样就避免了频繁的创建与销毁线程,不会造成资源浪费
3.合理利用线程池可以拥有的优势:
1. 降低系统的资源消耗:减少系统创建与销毁线程对象的次数,每个线程都可以重复利用,执行多次任务
2. 提高响应速度:当任务到达时,任务可以不用等待线程创建就能立即执行
3. 提高线程的可管理性:可以根据系统的承受能力,调整线程池中线程的数目
防止因为创建多个线程消耗过多的内存导致服务器的崩溃
【每个线程大约需要1MB的内存,线程开的越多,消耗的内存也就越大,最后死机】
 

线程常用方法

构造方法

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

普通方法

static Thread currentThread( )
返回对当前正在执行的线程对象的引用
long getId()
返回该线程的标识
String getName()
返回该线程的名称
void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法
static void sleep(long millions)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
void start()
使该线程开始执行:Java虚拟机调用该线程的run()
 

继承Thread类  代码

//需求:设计多线程模型,4个窗口共计售票100张
//本方案使用多线程编程实现方案一:继承Thread类的方式来完成
public class TestThread {
    public static void main(String[] args) {
        TicketThread t1 = new TicketThread("窗口1");
        TicketThread t2 = new TicketThread("窗口2");
        TicketThread t3 = new TicketThread("窗口3");
        TicketThread t4 = new TicketThread("窗口4");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
class TicketThread extends Thread{
    //定义成员变量,保存票数//
    /**问题:4个线程对象共计买票400张,原因是创建了4个本类的对象,各自操作各自的成员变量
     * 解决:让所有的对象共享同一个变量,这个变量设置为静态的*/
    static  int tickets=100;
//添加本类的含参构造,用于给线程起名字,默认的无参被覆盖
    public TicketThread(String name) {
        /**表示调用父类的含参构造,因为实际起名的功能是父类完成的
         * 所以我们需要将本构造函数接到的名字参数,传   给父类的构造函数,让父类起名字*/
        super(name);
    }

    @Override
    public void run() {
        /**让每个线程历经休眠。增加线程状态切换的频率与出错的效率,产生问题
         * 出现重卖,超卖的现象*/
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打印当前正在买票的线程名称。并且票数-1
        while (true){
            System.out.println(getName()+"="+tickets--);
             if (tickets<=0) break;//如果if的{}里只有一句话,大括号可以省略不写

    }
}}

实现 Runnable接口 代码

//需求:设计多线程模型,4个窗口共计售票100张
//实现Runnable接口的方式来完成
public class TestRunnable {
    public static void main(String[] args) {
        //创建唯一的自定义类对象作为唯一的业务对象
        TicketRunnable terget = new TicketRunnable();

        Thread t1 =new Thread(terget,"窗口一");
        Thread t2 =new Thread(terget,"窗口二");
        Thread t3 =new Thread(terget,"窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

class TicketRunnable implements Runnable{
     int tickets=100;
/**由于自定义类对象,作为唯一的业务对象,只new了一次
 * 所以发布的任务,也就是票数天然的会被所有的线程对象共享,无需设置为静态也只有一分*/
    @Override
    public void run() {
        //打印当前正在买票的线程名称。并且票数-1
        while (true){
            try {
                Thread.sleep(100 );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"="+tickets--);
            if (tickets<=0) break;//如果if的{}里只有一句话,大括号可以省略不写

        }
    }
}

Executors 创建线程池

public static void main(String[] args) {
        //创建自定义类对象,作为唯一的业务对象
        TicketsRunnable terget = new TicketsRunnable();
        /**Executors是用来辅助创建线程池的工具对象
         * 常用方法newFixedThreadPool(int)这个方法可以创建指定数量的线程对象
         * 创建出来的线程池对象是ExecutorService:这个池子用来 新建 启动 储存 销毁线程*/
        ExecutorService pool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
           /** execute()是让线程池中的线程来执行任务,每次调用这个方法
            * 都会讲一个线程加入到就绪队列中,但注意具体执行还是要看OS是否选中*/
            pool.execute(terget);//本方法的参数就是我们的业务,也就是业务对象terget
            //线程池是不关的,得手动结束程序
        }
    }
}
/**多线程的出现数据安全的原因:多线程程序+共享数据+多条语句操作共享数据
 * 2.解决方案 :同步锁:相当于给容易出现问题的代码加了一把锁,包裹了所有可能会出现数据安全问题的代码
 * 加锁之后,就有了同步(排队)效果,加锁需要考虑:
 * 锁的范围:不能太大:太大,干啥都排队,效率低
 *         不能太小,太小,锁不住,还是会出现安全隐患
 * 注意   多个线程间 必须使用统一把锁,否则锁不住*/
//自定义多线程售票类
class TicketsRunnable implements Runnable{
    int tickets =100;
    //添加未实现的方法run,里面是我们的业务
    @Override
    public void run() {
        Object o = new Object();
//        Dog d = new Dog();

        while (true) {
            /**同步代码块   synchronized (唯一的锁对象){会出现安全隐患的代码}
             * 同步代码块在同一时刻,同一资源只会被一个线程对象独享*/

            /**这个写法不对,相当于每个线程进来时都会new一个新的锁对象
             * 多线程间使用的不是同一个唯一的锁对象,锁不住*/
//            synchronized (new Object()) {
            synchronized (TicketsThread.class) {//同步代码块解决的是重卖的问题
                    if (tickets>0){
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "=" + tickets--);
                    }

                if (tickets <= 0) break;
            }







            }

    }
}
class Dog{}
/**Executors是用来辅助创建线程池的工具对象
 * 常用方法newFixedThreadPool(int)这个方法可以创建指定数量的线程对象
 * 创建出来的线程池对象是ExecutorService:这个池子用来 新建 启动 储存 销毁线程*/
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
   /** execute()是让线程池中的线程来执行任务,每次调用这个方法
    * 都会讲一个线程加入到就绪队列中,但注意具体执行还是要看OS是否选中*/
    pool.execute(terget);//本方法的参数就是我们的业务,也就是业务对象terget
    //线程池是不关的,得手动结束程序
}

多线程代码会出现数据安全的问题  比如:多卖,重卖

超重点:
如何判断有没有可能出现线程安全问题,主要有以下三个条件
在多个线程程序中 有共享数据    +多条语句操作共享数据


同步:体现排队的效果 同一时刻只能有一个线程独占资源,其他没有权利的线程排队
坏处是效率较低,不过保证安全性
异步:体现多线程抢占资源的效果,线程互相不等待,互相抢占资源
坏处就是有安全隐患,效率比较高

出现数据安全问题的原因:

  1. 多线程程序
  2. 多个线程拥有共享数据
  3. 多条语句操作共享数据

解决方案:加锁synchronized

/**多线程的出现数据安全的原因:多线程程序+共享数据+多条语句操作共享数据
 * 2.解决方案 :同步锁:相当于给容易出现问题的代码加了一把锁,包裹了所有可能会出现数据安全问题的代码
 * 加锁之后,就有了同步(排队)效果,加锁需要考虑:
 * 锁的范围:不能太大:太大,干啥都排队,效率低
 *         不能太小,太小,锁不住,还是会出现安全隐患
 * 注意   多个线程间 必须使用统一把锁,否则锁不住*/

同步代码块【常用】,格式:
synchronized(唯一的锁对象){
可能出现数据安全问题的所有代码
}

使用同步时的注意事项:
1.锁对象必须唯一!!!
      比如:如果是实现接口的方式,只创建了一个目标业务类对象(接口实现类对象),那么也只有一个锁对象
      比如2:如果是继承Thread类的方式,你可能要创建多个子类的对象,那这个时候需要给锁对象 加static,保证锁对象唯一被所有对象共享
     2.注意:类名.class字节码对象作为锁对象,不管哪种实现多线程的方式都可以用哦,不过一般在继承Thread实现方式使用较多
    3.锁对象的类型不做限制,只要能保证唯一即可
    4.加锁的范围需要认真考虑
不能太大,也不能太小,太大浪费效率,太小锁不住

方式一:继承线程类的加锁

public class TestThread {
    public static void main(String[] args) {
        TicketsThread t1 = new TicketsThread("窗口1");
        TicketsThread t2 = new TicketsThread("窗口2");
        TicketsThread t3 = new TicketsThread("窗口3");
        TicketsThread t4 = new TicketsThread("窗口4");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

class TicketsThread extends Thread{
    static  int tickets=100;




    static Object o = new Object();//继承线程用static关键字修饰锁对象,表示唯一

    public TicketsThread(String name) {
        super(name);
    }

    @Override
    public void run() {


        while (true){

            synchronized (TicketsThread.class){//同步代码块解决重卖的问题
                if(tickets>0){//如果有票就买票,解决多卖
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName()+"="+tickets--);
                }


            if (tickets<=0)break;
            }
        }
    }
}
class A{

}


实现Runnable接口 及线程池的加锁

public class TestRunnable {
    public static void main(String[] args) {
        //创建自定义类对象,作为唯一的业务对象
        TicketsRunnable terget = new TicketsRunnable();
//        //创建多个线程对象,并把唯一的业务对象交给业务对象交给线程对象干活
//        Thread t1 = new Thread(terget, "窗口1");
//        Thread t2 = new Thread(terget, "窗口2");
//        Thread t3 = new Thread(terget, "窗口3");
//        Thread t4 = new Thread(terget, "窗口4");
//
//        t1.start();
//        t2.start();
//        t3.start();
//        t4.start();
        /**Executors是用来辅助创建线程池的工具对象
         * 常用方法newFixedThreadPool(int)这个方法可以创建指定数量的线程对象
         * 创建出来的线程池对象是ExecutorService:这个池子用来 新建 启动 储存 销毁线程*/
        ExecutorService pool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
           /** execute()是让线程池中的线程来执行任务,每次调用这个方法
            * 都会讲一个线程加入到就绪队列中,但注意具体执行还是要看OS是否选中*/
            pool.execute(terget);//本方法的参数就是我们的业务,也就是业务对象terget
            //线程池是不关的,得手动结束程序
        }
    }
}
/**多线程的出现数据安全的原因:多线程程序+共享数据+多条语句操作共享数据
 * 2.解决方案 :同步锁:相当于给容易出现问题的代码加了一把锁,包裹了所有可能会出现数据安全问题的代码
 * 加锁之后,就有了同步(排队)效果,加锁需要考虑:
 * 锁的范围:不能太大:太大,干啥都排队,效率低
 *         不能太小,太小,锁不住,还是会出现安全隐患
 * 注意   多个线程间 必须使用统一把锁,否则锁不住*/
//自定义多线程售票类
class TicketsRunnable implements Runnable{
    int tickets =100;
    //添加未实现的方法run,里面是我们的业务
    @Override
    public void run() {
        Object o = new Object();
//        Dog d = new Dog();

        while (true) {
            /**同步代码块   synchronized (唯一的锁对象){会出现安全隐患的代码}
             * 同步代码块在同一时刻,同一资源只会被一个线程对象独享*/

            /**这个写法不对,相当于每个线程进来时都会new一个新的锁对象
             * 多线程间使用的不是同一个唯一的锁对象,锁不住*/
//            synchronized (new Object()) {
            synchronized (TicketsThread.class) {//同步代码块解决的是重卖的问题
                    if (tickets>0){
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "=" + tickets--);
                    }

                if (tickets <= 0) break;
            }







            }

    }
}
class Dog{}

注解

它可以增强我们的java代码,同时利用反射技术可以扩充实现很多功能。它们被广泛应用于三大框架底层。

传统我们通过xml文本文件声明方式(,但是XML比较繁琐且不易检查),而现在最主流的开发都是基于注解方式,代码量少,框架可以根据注解去自动生成很多代码,从而减少代码量,程序更易读。例如最火爆的SpringBoot就完全基于注解技术实现。

注解的分类

  • JDK自带注解
  • 元注解
  • 自定义注解

JDK注解的注解,就5个:

@Override :用来标识重写方法
@Deprecated标记就表明这个方法已经过时了,但我就要用,别提示我过期
@SuppressWarnings(“deprecation”) 忽略警告
@SafeVarargs jdk1.7出现,堆污染,不常用
@FunctionallInterface jdk1.8出现,配合函数式编程拉姆达表达式,不常用
 

元注解  用来描述注解的注解,就5个:

@Target 注解用在哪里:类上、方法上、属性上等等
@Retention 注解的生命周期:源文件中、字节码文件中、运行中

@Inherited 允许子注解继承
@Documented 生成javadoc时会包含注解,不常用
@Repeatable注解为可重复类型注解,可以在同一个地方多次使用,不常用
 

 @Target ElementType…

描述注解存在的位置:

ElementType.TYPE 应用于的元素
ElementType.METHOD 应用于方法
ElementType.FIELD 应用于字段或属性(成员变量)
ElementType.ANNOTATION_TYPE 应用于注解类型
ElementType.CONSTRUCTOR 应用于构造函数
ElementType.LOCAL_VARIABLE 应用于局部变量
ElementType.PACKAGE 应用于声明
ElementType.PARAMETER 应用于方法的参数

@Retention RetentionPolicy…

该注解定义了自定义注解被保留的时间长短,比如某些注解仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中; 编译在class文件中的注解可能会被虚拟机忽略,而另一些在class被装载时将被读取。

SOURCE 在源文件中有效(即源文件保留)








CLASS 在class文件中有效(即class保留)








RUNTIME 在运行时有效(即运行时保留)

自定义注解注意:注解的语法写法和常规java的语法写法不同

第一端代码详细解说自定义注解的步骤与解释,        第二段为实例一目了然

/本类用于完成自定义注解
public class annotation {
    public static void main(String[] args) {

    }
}
//2.通过@Target注解标记自定义注解可以使用的位置
/**通过元注解@Target规定自定义注解可以使用的位置
 * 我们可以使用“ElementType。静态常量”的方式来指定自定义注解具体可以加在什么位置
 * 而且,值可以写多个,格式:@Target({ElementType.XXX1,ElementType.XXX2})*/
@Target({ElementType.METHOD,ElementType.TYPE})//表明注解只能加在方法上面
    //通过@Retention注解标记自定义注解的生命周期
/**4通过元注解@Retention规定自定义注解的生命周期
 * 我们使用“RetentionPolicy.静态常量”的方式来指定自定义注解的生命周期
 * 注意:值只能写一个:SOURCE CLASS RUNTIME 3选1*/
    @Retention(RetentionPolicy.RUNTIME)

//1.定义自定义注解
/**1.首先注意:注解定义的语法与Java不同
 * 2.定义自定义注解的格式: @interface 注解名
 * */
@interface Rice{
    //我们可以给注解进行功能增强---添加注解的属性
    /**6.注意int age():不是方法的定义,而是给自定义注解添加了age属性*/
    //int age();//给自定义注解添加一个普通属性age,类型int
    int age() default 0;//给自定义注解的普通属性赋予默认值0
    /**7.注解中还可以添加特殊属性value
     * 特殊属性的定义方式与普通属性一样,主要是使用方式不同
     * 注意:特殊属性的名字必须叫value,但是类型不做限制
     * 特殊属性也可以赋予默认值,格式与普通属性的赋值一样,不能简写*/
//    String value();//定义一个特殊属性value 类型是String
    String value() default "lemon";
}
//4.定义一个类测试自定义注解
@Rice
class TestAnno{
    /**分别给TestAnno类 name属性 eat方法上都添加Rice注解
     * 结论:属性上的注解报错,说明自定义注解可以加在什么了位置由@Target规定*/
/**测试2:当我们给Rice注解添加一个age属性以后,@Rice注解使用时直接报错
 * 结论:当注解没有定义属性可以直接使用
 *       当注解定义了属性以后,必须给属性赋值,格式:@Rice(age = 10)*/

    String name;
    //@Rice(age=10)
    /**测试3:给age属性赋予默认值以后。可以直接使用@Rice注解
     * 不需要个age属性赋值,因为age属性已经有默认值0*/
    /**测试四;给Rice注解添加特殊属性value以后,也必须给属性赋值
     * 只不过特殊属性赋值是可以简写成@Rice("XXX ")*/

//    @Rice("")
    /**测试5:如果特殊属性也赋予了默认值,那么可以直接使用这个注解
     * 如果想要给注解的所有属性赋值,每条赋值都不能简写*/
//    @Rice(age=10,value = "XX")
    @Rice
    public void eat(){
        System.out.println("eat方法");

    }
}
public class Demo {

}
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface Cat{
    String name()default "";
    int value() default 0;
}
@Cat(name="",value = 10)
class TestDemo{
    @Cat(666)
    String host;
    @Cat(name = "")
    public void play(){

    }
}

          1. 我们也可以根据自己的需求来定义个性化的注解
               使用的是@interface 注解名来定义的,主要使用的就是上面的两个元注解
           2. 除此之外,我们还可以给注解加功能,比如注解的属性:
               格式:属性类型 属性名(); 比如:int age();
               注意:定义了注解的普通属性以后,使用注解时必须给属性赋值,格式:@Rice(age=10)
        如果给属性设置了默认值,那么使用注解时就不需要给属性赋值了,格式:int age() default 0;
3.我们还可以给注解添加特殊的属性value,注意这个属性名字必须是value,类型不作限制注意:特殊属性如果不设置默认值,使用注解时也需要赋值,不过赋值可以简写,比如@Rice("apple") 特殊属性赋予默认值后,就可以直接使用注解了,赋予默认值的格式:String value() default "apple";
             注意:如果有多个属性,并且都没有赋予默认值,那么使用注解时的格式:@Rice(value="apple",age=10)
 

设计模式(Design pattern)

代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
JAVA一共有23种设计模式,我们今天首先来学其中一种:单例设计模式

23种设计模式

单例设计模式

单例模式可以说是大多数开发人员在实际中使用最多的,常见的Spring默认创建的bean就是单例模式的。
单例模式有很多好处,比如可节约系统内存空间,控制资源的使用。
其中单例模式最重要的是确保对象只有一个
简单来说,保证一个类在内存中的对象就一个。

概念:是一些前人总结出来的值得学习的编程“套路”,设计模式一共有23种
单例设计模式:确保代码中本类的实例只有一个
实现思路:
方案一:饿汉式
1)把本类的构造方法私有化–为了不让外界调用构造函数来创建对象
2)通过本类的构造方法创建对象,并把这个对象也私有化,为了防止外界调用
3)提供公共的全局访问点向外界返回本类的唯一的一个对象
注意:公共方法需要设置成静态–需要跳过对象,通过类名直接调用这个返回本类对象的公共方法
对象也需要设置成静态的–这个对象需要在静态方法中被返回,而静态只能调用静态
 

通过分析,底层的实现思路一共分为了3个步骤:

  • 对本类构造方法私有化,防止外部调用构造方法创建对象
  • 创建全局唯一的对象,也做私有化处理
  • 通过自定义的公共方法将创建好的对象返回(类似封装属性后的getXxx() )

//本类用于实现单例设计模式方案1
public class Singleton1 {
    public static void main(String[] args) {
        MySingle s1 =MySingle.getSingle();
        MySingle s2 = MySingle.getSingle();
        System.out.println(s1==s2);
        System.out.println(s2);
        System.out.println(s2);
    }



}

//0.创建自己的单例程序
class MySingle{
    //1.提供本类的构造方法,并将构造方法私有化
    //构造方法私有化的目的:为了防止外界随意调用,创建本类对象

    private MySingle() { }//私有化构造方法

    //创建本类的对象
    //4.2静态资源调用静态资源
   private static MySingle single=new MySingle();
    public static MySingle getSingle(){
        //3.提供公共的返回方式,返回刚刚创建好的对象
        //4.1为了不通过对象,直接调用本方法。需要将本方法设置为静态
        /**后续可以添加权限*/
        return single;
    }
}
/0.创建自己的单例程序
class MySingle{
    //1.提供本类的构造方法,并将构造方法私有化
    //构造方法私有化的目的:为了防止外界随意调用,创建本类对象

    private MySingle() { }//私有化构造方法

    //创建本类的对象
    //4.2静态资源调用静态资源
   private static MySingle single=new MySingle();
    public static MySingle getSingle(){
        //3.提供公共的返回方式,返回刚刚创建好的对象
        //4.1为了不通过对象,直接调用本方法。需要将本方法设置为静态
        /**后续可以添加权限*/
        return single;
    }
}
单例设计模式1:饿汉式
//本类用于复写单例设计模式1:饿汉式
public class Singleton1 {
    public static void main(String[] args) {
        MySingle s1 = MySingle.getSingle();
        MySingle s2 = MySingle.getSingle();

        System.out.println(s1);
        System.out.println(s2  );
    }
}

class MySingle{
    //提供私有的构造方法
    private MySingle() {
    }
//    创建本类的对象 将对象的地址值交给引用类型的变量single
    private static MySingle single =new MySingle();
    //提供一个公共的访问方式,向外界提供创建好的唯一本类对象
    public static MySingle getSingle(){
        return single;
    }
}
单例设计模式方案2:懒汉式

/**关于单例设计模式的两种实现方式
 * :1.饿汉式:不管你用不用这个类的对象,都会创建一个
 *   2.饿汉式。先不创建对象,等你需要才创建--延迟加载的思想
 *   延迟加载的思想---是指不会第一时间就把对象创建好占用内存资源
 *   而是什么时候用,什么时候创建对象*/

/**3.线程安全问题:由于我们存在唯一的对象single2,并且多条语句存在这个变量
 * 所以 如多将程序放在多线程的环境下,就容易出现数据安全问题 解决方案
 * 1.将3条语句都使用同步代码块包裹,保证同步排队的效果,
 * 2.由于getSingle2方法里也只有这3条语句,所以也可以将本方法修饰为同步方法
 * 注意:被synchronized修饰的方法叫同步方法 但是不推荐使用*/
public class Singleton2 {

    public static void main(String[] args) {
        MySingle2 m1 = MySingle2.getSingle2();
        MySingle2 m2 = MySingle2.getSingle2();
        System.out.println(m1);
        System.out.println(m2);
    }
}

class MySingle2{
     static Object o =new Object();
    private MySingle2() {
    }
//    创建本类对象对应类型的引用类型变量 用来保存对象的地址值
    //引用类型的成员变量默认值为null
    static MySingle2 single2;
//提供公共的静态方法能被外界调用


  //synchronized public static MySingle2 getSingle2() {  第二种加synchronized关键字
   public static MySingle2 getSingle2() {
       // synchronized (o) {  //第一种
            //使得对象唯一,只创建一次
            if (single2 == null) {
                single2 = new MySingle2();
            }
            return single2;
        }
   // }
}

反射

Reflection(反射) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,也有称作“自省”。
反射非常强大,它甚至能直接操作程序的私有属性。我们前面学习都有一个概念,被private封装的资源只能类内部访问,外部是不行的,但这个规定被反射赤裸裸的打破了。
反射就像一面镜子,它可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分
 

 为什么需要反射?

如果想创建对象,我们直接new User(); 不是很方便嘛,为什么要去通过反射创建对象呢?

我们在后面的学习中,会学习框架,有一个框架Spring就是一个非常专业且功能强大的产品,它可以帮我们创建对象,管理对象。以后我无需手动new对象,直接从Spring提供的容器中的Beans获取即可。Beans底层其实就是一个Map<String,Object>,最终通过getBean(“user”)来获取。而这其中最核心的实现就是利用反射技术。

总结一句,类不是你创建的,是你同事或者直接是第三方公司,此时你要或得这个类的底层功能调用,就需要反射技术实现。(通过反射获得别人底层代码的功能)

反射需要用到的API

获取字节码文件

Class.forName(“类的全路径”);







类名.class







对象.getClass();

Class.forName(“类的全路径”); 注意:传入的是类的全路径名,包含包名.类名,而且会抛出异常







类名.class 注意:这个写法需要自己手动接一下获取到的字节码对象,不能用快捷方式的







对象.getClass(); 注意:经常与匿名对象一起使用

常用方法

获取包名 类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名

获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)

获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)

获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)

反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance(666,”海绵宝宝”);//执行含参构造创建对象
clazz.getConstructor(int.class,String.class)//获取构造方法

反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null

反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法
 

反射技术使用的物料类 Student

public class Student {
    private String name;
    public int age;
    public double price;

    public Student(String name, int age, double price) {
        this.name = name;
        this.age = age;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void play(){
        System.out.println("今天JavaSE结束课程");
    }

    public void eat(int n){
        System.out.println("今天吃"+n+"道菜");
    }
//查看对象的类型。属性 。属性值

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", price=" + price +
                '}';
    }
}

测试暴力反射的物料类 Person

//测试暴力反射的物料类
public class Person {
    private String name;
    private int age;
    private void save(int n,String s){
        System.out.println("save方法"+n+s);
    }
    private void update(){
        System.out.println("update方法");
    }
    private void add(String str){
        System.out.println("add方法"+str);
    }

}

普通反射

//测试反射
public class TestReflect {
    //1.如果单元测试方法,获取目标类Student对象字节码文件
    @Test
    public  void getClazz() throws ClassNotFoundException {
        //练习获取字节码对象的3种方式
        //第一种
        Class<?> clazz1 = Class.forName("cn.tedu.cn.tedu.reflection.Student");
        //第二种
        Class<?> clazz2 = Student.class;
        //第三种
        Class<?> clazz3 = new Student().getClass();

        //打印获取的字节码对象
        System.out.println(clazz1);
       //通过刚刚获得的字节码对象,获取Student类的资源


        System.out.println(clazz1.getName());//cn.tedu.cn.tedu.reflection.Student
        //以上获取字节码对象对应的全路径名
        System.out.println(clazz1.getSimpleName());//Student
        //以上获取字节码对象对应的类名
        System.out.println(clazz1.getPackage());//package cn.tedu.cn.tedu.reflection
        //以上获取Student类对应的包对象
        System.out.println(clazz1.getPackage().getName());//cn.tedu.cn.tedu.reflection
        //以上先获取Student类对应的包对象,在通过这个包对象,获取包名

    }
    //2.通过单元测试方法,获取Student类中的成员变量
    @Test
    public void getFie() throws ClassNotFoundException {
        //获取目标类对应的字节码对象//包含对象的所有的资源
        Class<?> clazz = Class.forName("cn.tedu.cn.tedu.reflection.Student");
        //通过字节码对象获取成员变量们
        Field[] fs = clazz.getFields();
        //打印这个数组
        System.out.println(Arrays.toString(fs));
        //遍历数组 获取每个成员
        for (Field a:fs) {
            /**注意!目前成员变量的修饰符需要public才能获得 其他的修饰符 要通过暴力反射*/
            System.out.println(a);
            System.out.println(a.getName());
            System.out.println(a.getType());
        }

    }
    //3.通过单元测试方法,获取Student类中的成员方法
    @Test
    public void getFuction(){
        //获取目标类对应的字节码对象
        Class<?> clazz = Student.class;
//       通过字节码对象获取目标类中的成员方法们
        Method[] ms = clazz.getMethods();
        for (Method m:ms) {
            System.out.println(m.getName());//查看方法名
            System.out.println(Arrays.toString(m.getParameterTypes()));//查看方法的参数类型


        }

    }
    //3.通过单元测试方法,获取Student类中的所以构造方法
    @Test
    public void getCons(){
        //遍历获取到的构造函数们,分别获取每个构造函数方法名和方法参数
        Class<?> clazz = new Student().getClass();
        Constructor<?>[] con = clazz.getConstructors();
        for (Constructor c:con) {
            System.out.println(c.getName());
            System.out.println(Arrays.toString(c.getParameterTypes()));


        }
    }

    //通过反射,创建目标类对象
    @Test
    public void getObject() throws Exception {
        //获取字节码对象
        Class<?> clazz = Student.class;
//       通过反射技术创建目标类的对象,注意抛出异常
        /**反射创建对象方案一:通过触发目标类中的无参构造创建对象
         * */
        //多态的思想,父类Object接收可以接收任何类包括(Student类)
//        newInstance()创建实例
        Object o = clazz.newInstance();//通过无参构造
        System.out.println(o);

        /**反射创建对象方案二:通过触发目标类中的全参构造创建对象
        1.获取指定的构造函数对象,注意需要指定构造函数的参数,并且传入的是.class字节码对象*/
        //获取目标类中指定的那个全参构造对象
        Constructor<?> c = clazz.getConstructor(String.class,int.class);
        System.out.println(c);
        //通过获取到的构造函数:创建对象+给对象的所有属性赋值
        Object o2 = c.newInstance("张三", 5);
        System.out.println(o2);

        Constructor<?> cla = clazz.getConstructor(String.class,int.class,double.class);
        System.out.println(cla);
        Object o3 = cla.newInstance("张三", 4, 1.0);
        System.out.println(o3);
    }


}

暴力反射

//测试暴力反射
public class TestReflect2 {
    /**通过暴力反射获取与操作属性*/
    @Test
    public void getFie2() throws Exception {
        //获取字节码对象
        Class<Person> clazz = Person.class;
        Field field = clazz.getDeclaredField("name");
        //打印查看刚刚获取到的属性对象
        System.out.println(field);
        //根据刚刚获取到的属性对象,进一步查看属性的各种信息
        System.out.println(field.getType());//class java.lang.String
        System.out.println(field.getType().getName());//

        //设置属性的值
        //1.需要指定到底给那个对象的name属性设置值,没有对象创建对象
        Object p1 = clazz.newInstance();//利用反射,触发无参构造创建对象
        /**暴力反射。需要设置私有可见的权限*/
        field.setAccessible(true);
        //通过字段对象Field给刚刚创建好的对象p1设置属性值
        //field.set(o,n);o是指具体给那个对象的属性设置值,n是指具体的值是什么
        field.set(p1,"海绵宝宝");
        //field.get(0);具体查看那个对象的name属性值
        System.out.println(field.get(p1));


    }
    @Test
    public void getFie3() throws Exception {
        Class<Person> clazz = Person.class;
//        获取制定的私有属性
        Field f = clazz.getDeclaredField("age");
//        System.out.println(f);
//        System.out.println(f.getType());
//        System.out.println(f.getType().getName());

        Person p2 = clazz.newInstance();
        f.setAccessible(true);
        f.set(p2,66);
        System.out.println(f.get(p2));

    }
    /**通过暴力反射获取与操作ff*/
    @Test
    public void getFunction2() throws Exception {
     //获取字节码对象
        Class<?> clazz = Person.class;
        //通过暴力反射获取私有方法
        /**getDeclaredMethod(n,x,y,z...)
         * n:要获取的指定方法的方法名
         * x,y,z..可变参数,是指定方法的参数类型,注意传入的是字节码对象*/
        Method method = clazz.getDeclaredMethod("save", int.class, String.class);
//        System.out.println(method);
        /**暴力反射。需要设置私有可见的权限*/
        method.setAccessible(true);
        /**没有对象。通过反射创建Person类的对象*/
        Object p3 = clazz.newInstance();
        /**invoke(o,x,y,z...)表示通过反射执行方法 ******
         * o:要给那个对象执行上面获取到的save()
         * x,y,z...执行save()时要传入的参数
         * */
        method.invoke(p3,999,"感冒灵");
    }
    @Test
    public void getFunction3() throws Exception {
        Class<Person> clazz = Person.class;
        Method method = clazz.getDeclaredMethod("update");
        method.setAccessible(true);
        Person p3 = clazz.newInstance();
        method.invoke(p3);
    }
    @Test
    public void getFunction4() throws Exception {
        Class<Person> clazz = Person.class;
        Method method = clazz.getDeclaredMethod("add", String.class);
        method.setAccessible(true);
        Person p6 = clazz.newInstance();
        method.invoke(p6,"xxxxxxxxxx");
    }
}

此篇博客内容大多粘贴泡泡老师的博客一起学java的内容,特此说明,特此感谢!!!!!!!!!!!!!!!!!

目录

代码中出现的高频单词

分支结构

循环结构

方法结构

面向对象的三大特征

封装

属性封装的步骤:

构造函数/构造方法

构造代码块

局部代码块

This

继承

关键字Final

关键字static

多态

异常

抽象

接口

内部类

API

Object

String

StringBuilder/StringBuffer

正则表达式

 包装类

number类

Integer

自动装箱和自动拆箱

BigDecimal

IO流

 File文件类

字节流读取

InputStream抽象类

FileInputStream子类

 BufferedInputStream子类

字节流写出

OutputStream抽象类

FileOutputStream 子类

BufferedOutputstream 子类

字符流写出

Writer 抽象类

FileWriter 子类

 BufferedWriter子类

总结:IO的继承结构

1.主流分类

流的分类

序列化与反序列化

为什么反序列化版本号需要与序列化版本号一致?

序列化和反序列化代码

泛型

集合

Collection

List接口

ArrayList

LinkedList

 Map接口

HashMap的存储过程:

set接口

进程与线程

进程

线程

多线程的创建方式:(面试重点)

注解

注解的分类

 @Target ElementType…

设计模式(Design pattern)

单例设计模式

反射

 为什么需要反射?


参与评论 您还未登录,请先 登录 后发表或查看评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:1024 设计师:我叫白小胖 返回首页

打赏作者

白木松

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值