Java基础

标识符

Java 语言目前定义了 51 个关键字,这些关键字不能作为变量名、类名和方法名来使用。以下对这些关键字进行了分类

数据类型:boolean、int、long、short、byte、float、double、char、class、interface
流程控制:if、else、do、while、for、switch、case、default、break、continue、return、try、catch、finally
修饰符:public、protected、private、final、void、static、strict、abstract、transient、synchronized、volatile、native
动作:package、import、throw、throws、extends、implements、this、supper、instanceof、new
保留字:true、false、null、goto、const

标识符命名规则

 - 所有的标识符都应该以字母(A-Z或者a-z),美元符$、或者下划线   _  开始
 - 首字符之后可以是字母(A-Z或者a-z),美元符$、下划线  _  或数字的任何字符组合
 - 不能使用关键字作为变量名或方法名
 - 标识符是大小写敏感的
 - 不建议使用中文命名

Java类型转换

由于Java是强类型语言,所以要进行有些运算的时候,需要类型转换

低----------------------------------------->高
byte,short,char-->int-->long-->float-->double

在运算过程中.不同类型的数据先转化为同一类型,然后再进行运算
//其中类型转换分为两种.强制转换和自动转换
public static void main(String[] args) {
        //类型转换
        int i = 128;
        byte b = (byte) i;//内存溢出  强制转换  (类型)变量名  高-->低
        double c = i;//自动转换  低--高

        System.out.println(i);//128
        System.out.println(b);//-128
        System.out.println(c);//128.0

        /*
        注意点:
        1.不能对布尔值进行转换
        2.不能把对象类型转换为不相干的类型
        3.在把高容量转换到低容量的时候,强制转换
        4.转换的时候可能存在内存溢出,或者精度问题
         */

        //小数向整数类型转换时,最终结果往0的方向靠近
        System.out.println((int) 23.7);//23
        System.out.println((int) -45.89);//-45
        
        char d = 'a';
        int e = d + 1;
        System.out.println(e);//98  ASCLL码
        System.out.println((char)e);//b

    }

数据类型

Java是一种强类型语言,要求变量的使用要严格符合规定,所有变量都必须先定义后才能使用

Java数据类型分类
在这里插入图片描述

字节

 - 位(bit):是计算机内部数据储存的最小单位,11001100是一个八位二进制数
 - 字节(byte) :是计算机中数据处理的基本单位,习惯上用大写B来表示
 - 1B (byte,字节)= 8bit(位)
 - 字符:是指计算机中使用的字母、数字、字和符号
 - 1bit表示1位
 - 1Byte表示一个字节1B=8b         
 - 1024B=1KB
 - 1024KB=1M
 - 1024M=1G
public static void main(String[] args) {
        //整数拓展
        //进制  二进制0b  十进制  八进制0  十六进制0x
        int i = 10;
        int i2 = 010;//八进制0
        int i3 = 0x10;//十六进制0x   0~9:A~F

        System.out.println(i);//10
        System.out.println(i2);//8
        System.out.println(i3);//16
        System.out.println("========================================");
        //========================================
        
        /*
        浮点数扩展?  银行业务怎么表示?钱
        BigDecimal   使用数学工具类,后续会学习
        ========================================
        float  有限  离散  舍入误差  大约  接近但不等于
        double
        
        最好完全避免使用浮点数进行比较
        最好完全避免使用浮点数进行比较
        最好完全避免使用浮点数进行比较
         */
        float f = 0.1f;//0.1
        double d = 1.0 / 10;//0.1
        System.out.println(f == d);//false

        float d1 = 231223212131312121212f;
        float d2 = d1 + 1;
        System.out.println(d1 == d2);//true
        System.out.println("========================================");

        //========================================
        //字符拓展?
        //========================================
        char c1 = 'a';
        char c2 = '中';
        System.out.println(c1);
        System.out.println((int) c1);//强制转换
        System.out.println(c2);
        System.out.println((int) c2);//强制转换
        //所有字符的本质还是数字
        //编码  Unicode  表:(97=a   65=A)   2字节    0-65536   Excel   2的16次方=65536
        //U0000~UFFFF
        char c3 = '\u0061';
        System.out.println(c3);//a

        //转义字符
        //\t  制表符
        //\n  换行
        System.out.println("Hello\tWorld");
        System.out.println("Hello\nWorld");
        System.out.println("========================================");

        //以下内容后续会学习
        String sa = new String("Hello World");
        String sb = new String("Hello World");
        System.out.println(sa == sb);

        String sc = "Hello World";
        String sd = "Hello World";
        System.out.println(sc == sd);
        //对象 从内存分析
        //布尔值扩展
        boolean flag = true;
        if (flag == true) {
        }
        if (flag) {
        }
        //以上两行代码表达的意思相同
    }

溢出

在操作比较大的数的时候,要注意溢出问题

public static void main(String[] args) {
        //操作比较大的数的时候,注意溢出问题
        //JDK7新特性,数字之间可以用下划线分割
        int money =10_0000_0000;
        int years=20;
        int total=money*years;
        System.out.println(total);//-1474836480  计算的时候溢出了
        long total2=money*years;//默认是int,转换之前就已经存在问题了
        System.out.println(total2);//-1474836480

        //以下为正确的操作方式
        //以下三张方式都可,前两种为先把一个数转换为long,或者两个都转换
        long total3=money*((long)years);
        System.out.println(total3);//20000000000
        long total4=((long)money)*years;
        System.out.println(total3);//20000000000
        long total5=((long)money)*((long)years);
        System.out.println(total3);//20000000000
    }

变量和常量

变量

  • 变量是什么:就是可以变化的量
  • Java是一种强类型语言,每个变量都必须声明其类型
  • Java变量是程序中最基本的存储单元,其要素包括变量名,变量类型和作用域
type varName [=value] [{,varName[=value]}] ;
//数据类型  变量名=值;  可以使用逗号隔开来声明多个同类型变量。

注意事项:

  • 每个变量都有类型,类型可以是基本类型,也可以是引用类型
  • 变量名必须是合法的标识符
  • 变量声明是一条完整的语句,因此每一个声明都必须以分号结束

变量命名规范

  • 所有变量、方法、类名:能够明确表达要表达的意思
  • 类成员变量:首字母小写和驼峰原则: monthSalary
  • 局部变量:首字母小写和驼峰原则
  • 常量:大写字母和下划线:MAX_VALUE
  • 类名:首字母大写和驼峰原则: Man, GoodMan
  • 方法名:首字母小写和驼峰原则: run(), runRun()

变量作用域

  1. 类变量
  2. 实例变量
  3. 局部变量
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

//变量作用域
public class Demo02 {
    //类变量  static
    static double salary=2500;

    //属性:变量
    //实例变量:从属于对象,如果不自行初始化,类型会有默认值
    //布尔值:默认是false
    //除了基本类型,其余默认值都是null
    String name;
    int age;

    //main方法
    public static void main(String[] args) {
        //局部变量  必须声明和初始化值
        int i = 10;
        System.out.println(i);//10

        //变量类型  变量名字=new Demo02();
        Demo02 demo02 = new Demo02();
        System.out.println(demo02.age);//0
        System.out.println(demo02.name);//null

        //类变量  static
        System.out.println(salary);//2500.0

    }

    //其他方法
    public void add() {
        int i=1;//局部变量
    }
}

常量

  • 常量:初始化后不能再改变值!不会变动的值
  • 所谓常量可以理解成一种特殊的变量,它的值被设定后,在程序运行过程中不允许被改变
final 常量名=;
final double PI=3.14;
  • 常量名一般使用大写字符
	//常量
    //修饰符,不存在先后顺序  final static double PI=3.14;也可以
    static final double PI=3.14;
    public static void main(String[] args) {
        System.out.println(PI);//3.14
    }

Java运算符

  • 算术运算符:+, -,*,l, %,++,–
public static void main(String[] args) {
        //二元运算符
        int a=10;
        int b=20;
        int c=25;
        int d=25;
        System.out.println(a+b);//30
        System.out.println(a-b);//-10
        System.out.println(a*b);//200
        System.out.println(a/(double)b);//0.5

		//   ++,--    自增,自减运算符   一元运算符
        int a = 3;
        int b = a++;//先赋值。再自增
        int c = ++a;//先自增,再赋值
        System.out.println(a);//5
        System.out.println(b);//3
        System.out.println(c);//5


        //扩展
        //幂运算 2^3=8  很多运算,会使用一些工具类来操作
        double pow = Math.pow(2, 3);
        System.out.println(pow);
    }
  • 赋值运算符:=
  • 关系运算符:>,<,>=,<=,==,!= instanceof
public static void main(String[] args) {
        //关系运算符返回的结果  布尔值  true,false
        int a=10;
        int b=20;
        int c=21;
        System.out.println(c%a);//取余,模运算
        System.out.println(a>b);//false
        System.out.println(a<b);//true
        System.out.println(a==b);//false
        System.out.println(a!=b);//true
    }
  • 逻辑运算符:&&,||,!
public static void main(String[] args) {
        //逻辑运算符
        //与或非
        boolean a = true;
        boolean b = false;
        /*
        &&  与  两个变量都为true,结果才为true     遇到假则为假
        ||  或  两个变量都为false,结果才为false   遇到真则为真
        !   非  如果为true,则变为false,如果为false,则变为true
         */
        System.out.println("a && b:" + (a && b));//true
        System.out.println("a || b:" + (a || b));//true
        System.out.println("!(a && b):" + (!(a && b)));//false

        //短路运算
        int c = 5;
        boolean d = (c < 4) && (c++ < 4);
        System.out.println(d);//5<4,这时候就已经确定为false了,就不再判断c++<4了
        System.out.println(c);
    }
  • ·位运算符:&,|,^,~,>>,<<,>>>(了解即可)
public static void main(String[] args) {
        //位运算
        /*
        &:对应位都为1,结果才为1
        |:对应位都为0,结果才为0
        ^:对应位相同为0,不同为1
        ~:每位直接取反
        ----------------------------
       举例:
        A=0011 1100
        B=0000 1101
        ----------------------------
        A&B=0000 1100
        A|B=0011 1101
        A^B=0011 0001
        ~B=1111 0010
        ----------------------------
        2*8任何计算最快?  2*2*2*2  位运算:效率极高
        <<  左移*2
        >>  右移/2
        ----------------------------
        0000 0000  0
        0000 0001  1
        0000 0010  2
        0000 0100  4
        0000 1000  8
        0001 0000  16
         */
    }
  • 条件运算符: ?:
public static void main(String[] args) {
        //三元运算符
        //x?y:z
        //如果x==true,则结果为y,否则为z
        int score = 80;
        String type = score < 60 ? "不及格" : "及格";
        System.out.println(type);//及格
    }
  • 扩展赋值运算符:+=,-=,*=,/=
public static void main(String[] args) {
        //扩展赋值运算符
        int a = 10;
        int b = 20;
        a += b;//a=a+b
        System.out.println(a);//30
        a -= b;//a=a-b
        System.out.println(a);//10


        //字符串连接符  +
        System.out.println(""+a+b);//1020    在+运算符左侧,出现了String类型,会把其他操作数转换为String再进行连接
        System.out.println(a+b+"");//30      ""字符串在后面,前面会先进行运算,自左向右运算
    }

实参和形参

public static void main(String[] args) {
        //实际参数,实际调用传递给他的参数
        int add = add(1, 2);
        System.out.println(add);
        test();
    }

    //加法
    //a,b  形式参数,用在方法中的
    public static int add(int a, int b) {
        return a + b;
    }

练习

//输出1-1000之间能够被5整除的数,并且每行输出三个
public static void test() {
        for (int i = 1; i <= 1000; i++) {
            if (i % 5 == 0) {
                System.out.print(i + "\t");
                if (i % 15 == 0) {//遍历的数能够被15整除时,换行
                    System.out.print("\n");
                }
            }
        }
    }
//输出结果
5	10	15	
20	25	30	
35	40	45	
50	55	60	
65	70	75	
80	85	90	
95	100	105	
110	115	120	
125	130	135	
140	145	150	
155	160	165	
170	175	180	
185	190	195	
200	205	210	
215	220	225	
230	235	240	
245	250	255	
260	265	270	
275	280	285	
290	295	300	
305	310	315	
320	325	330	
335	340	345	
350	355	360	
365	370	375	
380	385	390	
395	400	405	
410	415	420	
425	430	435	
440	445	450	
455	460	465	
470	475	480	
485	490	495	
500	505	510	
515	520	525	
530	535	540	
545	550	555	
560	565	570	
575	580	585	
590	595	600	
605	610	615	
620	625	630	
635	640	645	
650	655	660	
665	670	675	
680	685	690	
695	700	705	
710	715	720	
725	730	735	
740	745	750	
755	760	765	
770	775	780	
785	790	795	
800	805	810	
815	820	825	
830	835	840	
845	850	855	
860	865	870	
875	880	885	
890	895	900	
905	910	915	
920	925	930	
935	940	945	
950	955	960	
965	970	975	
980	985	990	
995	1000
//Java比较两个数的大小(输出较大的数)
public static void main(String[] args) {
        int max = max(10, 20 );
        System.out.println(max);
    }
    //比大小
    public static int max(int number1,int number2){
        int result=0;
        if(number1==number2){
            System.out.println("number1==number2");
            return 0;//终止方法
        }
        if(number1>number2){
            result=number1;
        }else {
            result=number2;
        }
        return result;
    }
//输出结果
20

重载

重载就是在一个类中,有相同的函数名称,但形参不同的函数

方法的重载的规则:
1.方法名称必须相同
2.参数列表必须不同(个数不同、或类型不同、或参数多类型的排列顺序不同)三者至少一个不同
3.方法的返回类型可以相同也可以不相同
4.仅仅返回类型不同不足以成为方法的重载

重载的好处:
只需要记住唯一一个方法的名称,就可以实现类似的多个功能

方法重载与下列因素无关:
1.与参数的名称无关
2.与方法的返回值无关

实现理论:

方法名称相同时,编译器会根据调用方法的参数个数、参数类型等去逐个匹配,以选择对应的方法,如果匹配失败,则编译器报错

public static void main(String[] args) {
        //重载
        //方法名称相同时,编译器会根据调用方法的参数个数、参数类型等去逐个匹配,以选择对应的方法,如果匹配失败,则编译器报错
        double max = max(10.0, 20);
        System.out.println(max);
    }
    //比大小
    public static double max(double number1, double number2) {
        double result = 0;
        if (number1 == number2) {
            System.out.println("number1==number2");
            return 0;//终止方法
        }
        if (number1 > number2) {
            result = number1;
        } else {
            result = number2;
        }
        return result;
    }
    
    //比大小
    public static int max(int number1, int number2) {
        int result = 0;
        if (number1 == number2) {
            System.out.println("number1==number2");
            return 0;//终止方法
        }
        if (number1 > number2) {
            result = number1;
        } else {
            result = number2;
        }
        return result;
    }
//此时调用的为第一个方法
//输出结果:20.0

当调用语句改为:double max = max(10, 20);

//此时调用的为第二个方法
//输出结果为:20

以下为几个例子:

public static void main(String[] args) {
        //重载
        int add01 = add(1, 2);
        System.out.println(add01);
        System.out.println("===================");
        int add02=add(1,2,3);
        System.out.println(add02);
        System.out.println("===================");
        double add03=add(1.0,2.0);
        System.out.println(add03);

    }

    public static int add(int a, int b) {
        return a + b;
    }
    public static int add(int a, int b, int c) {
        return a + b + c;
    }
    public static double add(double a, double b) {
        return a + b;
    }
//输出结果
3
===================
6
===================
3.0

可变参数(不定向参数)

 可变参数(不定向参数) 
 
 - JDK1.5开始,Java支持传递同类型的可变参数给一个方法
 - 在方法声明中,在指定参数类型后加一个省略号(….)
 - 一个方法中只能指定一个可变参数,它必须是方法的最后一个参数
 - 任何普通的参数必须在它之前声明
public static void main(String[] args) {
        //可变参数  不定向参数
        Demo06 demo06 = new Demo06();
        demo06.test(1,2,3,4);

    }
    public void test(int... i){
        System.out.println(i[0]);
        System.out.println(i[1]);
        System.out.println(i[2]);
        System.out.println(i[3]);
    }

//运行结果
1
2
3
4

递归

 - A方法调用B方法,很容易理解
 - 递归就是:A方法调用A方法!就是自己调用自己
 - 利用递归可以用简单的程序来解决一些复杂的问题。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合

递归结构包括两个部分:

  • 递归头:什么时候不调用自身方法。如果没有头,将陷入死循环
  • 递归体:什么时候需要调用自身方法
public static void main(String[] args) {
        //递归  求n的阶乘
        System.out.println(f(3));
    }
    public static int f(int n){
        if (n==1){
            return 1;
        }else {
            return n*f(n-1);
        }
    }

Scanner

 //类名称 对象名 = new  类名称();
Scanner   str =  new Scanner(System.in);

例一

//从键盘输入数据,获得两个数字的和
    public static void main(String[] args) {
    	 //类名称 对象名 = new  类名称();
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入第一个数字:");
        int num1=scanner.nextInt();
        System.out.println("请输入第二个数字:");
        int nun2=scanner.nextInt();
        System.out.println("sum:"+(num1+nun2));
    }

输出结果:

请输入第一个数字:
1
请输入第二个数字:
2
sum:3
通过Scanner类的next()与nextLine()方法获取输入的字符串,在读取前我们
一般需要使用hasNext()与hasNextLine()判断是否还有输入的数据


next():
1、一定要读取到有效字符后才可以结束输入
2、对输入有效字符之前遇到的空白,next()方法会自动将其去掉
3、只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符
4、next()不能得到带有空格的字符串

例二

 public static void main(String[] args) {
        //创建一个扫描器对象,用于接收键盘数据
        Scanner scanner = new Scanner(System.in);//System.in:从键盘输入
        System.out.println("使用next方式接受:");
        //判断用户有没有输入字符串
        if(scanner.hasNext()){
            //使用next方式接收
            String str=scanner.next();
            System.out.println("输出的内容为:"+str);
        }
        //凡是属于IO流的类如果不关闭会一直占用资源,用完就关掉
        scanner.close();
    }

输出结果:

使用next方式接受:
How are you?
输出的内容为:How   //next()方法遇到空格会结束,不会输出空格后的内容
nextLine():

1、以Enter为结束符,也就是说nextLine()方法返回的是输入回车之前的所有字符 
2、可以获得空白

例三

public static void main(String[] args) {
        //创建一个扫描器对象,用于接收键盘数据
        Scanner scanner = new Scanner(System.in);
        System.out.println("使用next方式接受:");
        //判断用户有没有输入字符串
        if(scanner.hasNext()){
            //使用next方式接收
            String str=scanner.nextLine();
            System.out.println("输出的内容为:"+str);
        }
        //凡是属于IO流的类如果不关闭会一直占用资源,用完就关掉
        scanner.close();
    }

输出结果:

使用next方式接受:
How are you?
输出的内容为:How are you?  //nextLine()方法返回的是输入回车之前的所有字符

例四

public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        //从键盘接收数据
        int i = 0;
        float f = 0.0f;
        System.out.println("请输入整数:");
        if (scanner.hasNextInt()) {
            i = scanner.nextInt();
            System.out.println("整数数据:" + i);
        } else {
            System.out.println("输入的不是整数数据!");
        }
        System.out.println("请输入小数:");
        if (scanner.hasNextFloat()) {
            f = scanner.nextFloat();
            System.out.println("小数数据:" + f);
        } else {
            System.out.println("输入的不是小数数据!");
        }
        scanner.close();
    }

运行结果:

请输入整数:
1
整数数据:1
请输入小数:
1.2
小数数据:1.2

例五

```java
public static void main(String[] args) {
        //我们可以输入多个数字,并求其总和与平均数,每输入一个数字用回车确认,通过输入非数字来结束输入,并输出执行结果
        Scanner scanner = new Scanner(System.in);
        //和
        double sum=0;
        //计算输入了多少数字
        int m=0;
        //通过循环判断是否还有输入,并在里面对每一次进行求和统计
        System.out.println("请输入数据:");
        while(scanner.hasNextDouble()){
            double x = scanner.nextDouble();
            m=m+1;//m++
            sum=sum+x;
            System.out.println("你输入了第"+m+"个数据,当前结果sum="+sum);
        }
        System.out.println(m+"个数的和为:"+sum);
        System.out.println(m+"个数的平均值是:"+(sum/m));
        scanner.close();
    }
```
运行结果:

```java
请输入数据:
1
你输入了第1个数据,当前结果sum=1.0
2
你输入了第2个数据,当前结果sum=3.0
3
你输入了第3个数据,当前结果sum=6.0
q
3个数的和为:6.0
3个数的平均值是:2.0
```

数组

  • 数组是相同类型数据的有序集合
  • 数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成
  • 其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们

动态初始化
Java语言使用new操作符来创建数组,语法如下:

//数据类型[]  数据名称   = new  数据类型[数组长度];
dataType[ ] arrayRefVar = new dataType[arraySize];

数组的元素是通过索引访问的,数组索引从0开始
获取数组长度:

arrays.length

静态初始化

//数据类型[]={元素1,元素2,......};
int[] a = {1,2,3};

动态初始化

int[] a = new int[2];
a[0]=1;
a[1]=2;

数组的默认初始化

数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化
其长度是确定的。数组一旦被创建,它的大小就是不可以改变的。其元素必须是相同类型,不允许出现混合类型

数组的四个基本特点

其长度是确定的。数组一旦被创建,它的大小就是不可以改变的
其元素必须是相同类型,不允许出现混合类型
数组中的元素可以是任何数据类型,包括基本类型和引用类型
数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量
数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对数组对象本身是在堆中的

数组的使用

For-Each循环

//arrays.for   快速调出
//这个方法用来打印结果
int[] arrays={1,2,3,4,5};
    for (int array : arrays) {
        System.out.println(array);
    }

数组作返回值

public static void main(String[] args) {
        //数组作为返回值
        int[] arrays = {1, 2, 3, 4, 5};
        int[] reverse=reverse(arrays);
        printArray(reverse);
        
    }
	//对数组中的数据进行反转
    public static int[] reverse(int[] arrays) {
        //创建一个新的数组,不要在原来的数组上进行反转操作
        int[] result = new int[arrays.length];
        for (int i = 0, j = arrays.length; i < arrays.length; i++, j--) {
            result[j - 1] = arrays[i];
        }
        return result;
    }
    public static void printArray(int arrays[]){
        for (int i = 0; i < arrays.length; i++) {
            System.out.print(arrays[i]+" ");
        }
    }

运行结果:

5 4 3 2 1 

多维数组

多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组

二维数组

int a[][] = new int[3][2];//可以看成一个两行五列的数组

内存分析:
在这里插入图片描述
小结:

数组是相同数据类型(数据类型可以为任意类型)的有序集合数组也是对象
数组元素相当于对象的成员变量
数组长度的确定的,不可变的。如果越界,则报:ArraylndexOutofBounds

Array数组的使用

Person类

public class Person {
    private String name;
    private int age;

    public Person() {
    }

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

    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;
    }
}

main方法

public static void main(String[] args) {
        //创建一个长度为3的数组,用来存放Person类型的对象
        Person[] array = new Person[3];
        Person person1 = new Person("Sherlock", 23);
        Person person2 = new Person("Jone", 25);
        Person person3 = new Person("Jack", 26);
        //将person1当中的地址值赋值到数组的0号元素位置
        array[0] = person1;
        array[1] = person2;
        array[2] = person3;

        System.out.println(array[0].getName());
        System.out.println(array[0].getAge());
    }

运行结果:

Sherlock
23

ArrayList的使用

数组的长度不可以改变,但ArrayList长度可以随意变化

对于ArrayList来说,有一个尖括号代表泛型,也就是装在集合中 的所有元素,全都是统一的类型

注意:
泛型只能是引用类型,不能是基本类型
对于ArrayList集合来说,直接打印得到的不是地址值,而是内容
若内容为空,得到的是空中括号[]

public static void main(String[] args) {
        //创建了一个ArrayList集合,集合的名称是list,里面装的字符全都是String字符串类型的数据
        ArrayList<String> list = new ArrayList<>();
        System.out.println(list);

        //向集合里添加一些数据,需要用到add方法
        list.add("Sherlock");
        list.add("Jone");
        list.add("jack");
        System.out.println(list);
    }

运行结果:

[]
[Sherlock, Jone, jack]

ArrayList方法的使用

泛型

ArrayList中的常用方法

1.add(E e):向集合当中添加元素,参数的类型和泛型一致。返回值代表添加是否成功
备注:对于ArrayList集合来说,add添加动作一定是成功的,所以返回值可用可不用
但是对于其他集合(今后学习)来说,add添加动作不一定成功
2.get(int index):从集合当中获取元素,参数是索引编号,返回值就是对应位置的元素
3.remove(int index):从集合当中删除元素,参数是索引编号,返回值就是被删除掉的元素
4.size():获取集合的尺寸长度,返回值是集合中包含的元素个数

public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        System.out.println(list);//[]

        //向集合中添加元素
        boolean success = list.add("Sherlock");
        System.out.println("添加的动作是否成功:" + success);//true
        System.out.println(list);//[Sherlock]

        list.add("Jone");
        list.add("Jack");
        System.out.println(list);//[Sherlock, Jone, Jack]

        //从集合中获取元素:get  索引从0开始
        String str = list.get(2);
        System.out.println("第二号索引位置值为:"+str);//Jack

        //从集合中删除元素:remove  索引值从0开始
        String remove = list.remove(2);
        System.out.println("被删除的人是:"+remove);//Jack
        System.out.println(list);//[Sherlock, Jone]

        //获取集合的长度尺寸,也就是其中的元素个数
        int size=list.size();
        System.out.println("集合的长度是:"+size);//2
    }

基本类型

如果希望向集合ArrayList当中存储基本类型数据,必须使用基本类型对应的’包装类’

基本类型包装类(引用类型)
byteByte
shortShort
intInteger
longLong
floatFloat
doubkeDouble
charCharacter
booleanBoolean
public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(100);
        list.add(200);
        System.out.println(list);//[100, 200]

        int num = list.get(1);
        System.out.println("第一号元素是:"+num);//200
    }

稀疏数组

当一个数组中大部分元素为0,或者为同一值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方式是:
记录数组一共有几行几列,有多少个不同值
把具有不同值的元素和行列及值记录在一个小规模的数组中,从而缩小程序的规模如下图:左边是原始数组,右边是稀疏数组
在这里插入图片描述

public static void main(String[] args) {
        //稀疏数组
        //1.创建一个二维数组  11*11  0:没有棋子  1:黑棋  2.白棋
        int[][] array1 = new int[11][11];
        array1[1][2] = 1;
        array1[2][3] = 2;
        //输出原始数组
        System.out.println("原始数组:");
        for (int i = 0; i < 11; i++) {
            for (int j = 0; j < 11; j++) {
                System.out.print(array1[i][j] + "\t");
            }
            System.out.print("\n");
        }
        System.out.println("=========================");
        //转换为稀疏数组
        //获取有效值的个数
        int sum = 0;
        for (int i = 0; i < 11; i++) {
            for (int j = 0; j < 11; j++) {
                if (array1[i][j] != 0) {
                    sum++;
                }
            }
        }
        System.out.println("有效值个数:" + sum);
        System.out.println("=========================");
        //2.创建一个稀疏数组的数组
        int[][] array2 = new int[sum + 1][3];
        array2[0][0] = 11;//第一行第一个数
        array2[0][1] = 11;//第一行第二个数
        array2[0][2] = sum;//第一行第三个数
        //遍历二维数组
        int count=0;//count起到记录行数的作用
        for (int i = 0; i < array1.length; i++) {
            for (int j = 0; j < array1[i].length; j++) {
                if(array1[i][j]!=0){
                   count++;
                   array2[count][0]=i;
                   array2[count][1]=j;
                   array2[count][2]=array1[i][j];
                }
            }
        }
        System.out.println("输出稀疏数组:");
        for (int i = 0; i < array2.length; i++) {
            for (int j = 0; j < array2[i].length; j++) {
                System.out.print(array2[i][j]+"\t");
            }
            System.out.println();
        }
        System.out.println("=========================");
        System.out.println("还原数组:");
        int[][] array3=new int[array2[0][0]][array2[0][1]];
        for (int i = 1; i < array2.length; i++) {
            array3[array2[i][0]][array2[i][1]]=array2[i][2];
        }
        //打印还原数组
        for (int i = 0; i < array3.length; i++) {
            for (int j = 0; j < array3[i].length; j++) {
                System.out.print(array3[i][j]+"\t");
            }
            System.out.print("\n");
        }
    }

运行结果:

原始数组:
0	0	0	0	0	0	0	0	0	0	0	
0	0	1	0	0	0	0	0	0	0	0	
0	0	0	2	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
=========================
有效值个数:2
=========================
输出稀疏数组:
11	11	2	
1	2	1	
2	3	2	
=========================
还原数组:
0	0	0	0	0	0	0	0	0	0	0	
0	0	1	0	0	0	0	0	0	0	0	
0	0	0	2	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	

冒泡排序(???)

public static void main(String[] args) {
        /*
        冒泡排序:
        1.比较数组中两个相邻的元素,如果第一个数比第二个数大,我们就交换他们的位置
        2.每一次比较,都会产生出一个最大或者最小的数字
        3.下一轮则可以少一次排序
        4.依次循环,直到结束
         */
        int[] array = {3, 2, 1};
        sort(array);
    }

    public static void sort(int[] array) {
        int swap = 0;
        for (int i = 0; i < array.length; i++) {
            boolean sample = false;//通过flag标识位减少没有意义的比较
            for (int j = 0; j < array.length - 1 - i; j++) {
                if (array[j + 1] > array[j]) {
                    swap = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = swap;
                    sample = true;
                }
            }
            //如果从头到尾数组的每个位置都没有发生变化,则就不需要再往下进行比较,直接跳出整个循环
            if (sample = false) {
                break;
            }
        }
        System.out.println(Arrays.toString(array));
    }

运行结果:

[1, 3, 4, 5, 6, 7, 9]

面向对象–方法的调用

关于调用方法:

1.调用非静态方法

调用非静态方法需要实例化这个类

语法:
对象类型  对象名 =  对象值

Student类

public class Student {
    //非静态方法
    public void say(){
        System.out.println("学生说话了");
    }
}

main方法

public static void main(String[] args) {
        //调用非静态方法需要实例化这个类
        //对象类型  对象名 =  对象值
        Student student = new Student();
        student.say();//学生说话了
    }

2.调用静态方法
Student类

public class Student {
    //静态方法
    public  static void say(){
        System.out.println("学生说话了");
    }
}

main方法

public static void main(String[] args) {
		//类名.方法名  可以直接调用
        Student.say();//学生说话了  
    }

注:

//static和类一起加载的  静态无法调用非静态
    public static void a(){
        b();
    }

    //在类实例化之后才存在
    public void b(){
        
    }

值传递和引用传递

值传递

public static void main(String[] args) {
        int a=1;
        System.out.println(a);//1
        change(a);
        System.out.println(a);//1
    }

    //没有返回值,形参的变化不影响实参
    public static void change(int a){
        a=10;
    }

引用传递

//引用传递:传递对象   本质还是值传递
public class Demo05 {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person.name);//null
        Demo05.change(person);
        System.out.println(person.name);//Sherlock
    }
    public static void change(Person person){
        //person是一个对象,指向的是  Person person = new Person();这是一个具体的人,可以改变属性
        person.name="Sherlock";
    }
    
    
//定义了一个Person类,有一个属性:name
static class Person{
    String name;//属性:name  默认值为null
}

面向对象–类与对象的关系

类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但是并不能代表某一个具体的事物
对象是抽象概念的具体实例

对象的创建分析

使用new关键字创建对象
使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化

创建类

//学生类  类里只有属性和方法
public class Student {
    //属性:字段
    String name;//默认为null
    int age;//默认为0

    //方法
    public void study() {
        System.out.println(this.name + "在学习");
    }
}

main方法 创建对象

public class Application {
    public static void main(String[] args) {

        //类:抽象的   需要实例化
        //类实例化后会返回一个自己的对象
        //student对象就是一个Student类的具体实例
       //类名称  对象名  =new 类名称();
        Student xiaoming = new Student();
        Student xiaohong = new Student();

        xiaoming.name = "小明";
        xiaoming.age = 3;
        System.out.println(xiaoming.name);
        System.out.println(xiaoming.age);
        xiaoming.study();

        xiaohong.name = "小红";
        xiaohong.age = 6;
        System.out.println(xiaohong.name);
        System.out.println(xiaohong.age);
        xiaohong.study();
    }
}

运行结果:

小明
3
小明在学习
小红
6
小红在学习

面向对象–构造器

类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的
并且构造器有以下几个特点:

必须和类的名字相同
必须没有返回类型,也不能写void
不能return一个具体的返回值
如果没有编写任何构造方法,那么编译器将会赠送一个构造方法,没有参数,方法体什么都不做

下面来验证一下,在创建对象的时候,是否会执行构造方法
Student类

public class Student {
    public Student() {
        System.out.println("构造方法执行啦");
    }
}

main方法

public static void main(String[] args) {
        Student student = new Student();//通过new创建对象的时候,其实就是在使用构造方法
    }

运行结果:

构造方法执行啦

说明在通过new创建对象的时候,其实就是在使用构造方法

Person类

public class Person {
    //一个类即使什么都不写 也会存在一个方法(构造方法)
    //显示定义构造器
    String name;
    //无参构造
    //1.使用new关键字必须要有构造器,本质是在调用构造器
    //2.用来初始化值
    public Person(){
    }
    //有参构造:一旦定义了有参构造,无参就必须写出来,实际也为重载
    public Person(String name){
        this.name=name;
    }
}

main方法:

public static void main(String[] args) {
        //实例化了一个对象
        Person person1 = new Person();//没有参数,调用无参构造
        System.out.println(person1.name);

        //实例化了一个对象
        Person person2 = new Person("Sherlock");//有参数,调用有参构造
        System.out.println(person2.name);//
    }

运行结果:

null
Sherlock

总结:

构造器

1.和类名相同
2.没有返回值

作用

1.new的本质是在调用构造器(构造方法)
2.初始化对象的值

注意点

定义了有参构造后,如果想使用无参构造,必须把无参构造写出来

面向对象–小结

1.类与对象
    类是一个模板,抽象的
    对象是一个具体的实例
2.方法
    定义,调用
3.对象的引用
    引用类型:基本类型(8)
    对象通过引用来操作  栈-->(地址)
4.对象的属性:字段Filed  成员变量
    默认初始化:
        数字:0   0.0
        char:u0000
        boolean:false
        引用:null
    定义:
        修饰符  属性类型  属性名=属性值
5.对象的创建和使用
    必须使用new关键字创建对象,而且必须有构造器  Person Sherlock = new Person();
    对象的属性  Sherlock.name
    对象的方法  Sherlock.study
6.类
    静态的属性  属性
    动态的行为  方法

面向对象–封装(***)

封装
1.方法就是一种封装
2.关键字private也是一种封装
也就是将一些细节信息隐藏起来,对于外界不可见
一旦使用了private修饰.那么本类中仍然可以访问,但是,超出了本类范围之外就不能直接访问了
即需要间接访问,定义get/set方法

程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合:仅暴露少量的方法给外部使用。
封装(数据的隐藏) 通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏。
封装中要做到的就是:属性私有,get/set

Student类

//封装
//学生类   private:私有
public class Student {
    /*
    封装的意义:
        1.提高代码的安全性.保护数据
        2.隐藏代码的实现细节
        3.统一接口
        4.系统的可维护性增加
     */
    //属性私有
    private String name;
    private int id;
    private char sex;
    private int age;

    /*
    get/set方法
    提供一些可以操作这些属性的方法
    提供一些public的get set方法
     */
    //get获得这个数据
    public String getName() {
        return this.name;
    }

    //set给这个设置值
    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    //可以在set方法中加一些约束条件
    public void setAge(int age) {
        if (age < 0 || age > 120) {
            this.age = 3;
        } else {
            this.age = age;
        }
    }
}

main方法

public static void main(String[] args) {
        Student s1 = new Student();
        s1.setName("Sherlock");
        System.out.println(s1.getName());
        s1.setId(0106);
        System.out.println(s1.getId());
        s1.setSex('男');
        System.out.println(s1.getSex());
        s1.setAge(23);
        System.out.println(s1.getAge());
    }

运行结果:

Sherlock
100623

面向对象–继承(***)

1.继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模
2.extands的意思是“扩展”,子类是父类的扩展
3.Java中类只有单继承,没有多继承 (一个类的直接父类只能有一个)
4.继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等
5.继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示
6.子类和父类之间,从意义上讲应该具有"is a"的关系
7.Java语言可以多级继承
8.一个子类的直接父类是唯一的,但是一个父类可以拥有很多个子类
9.在Java中,所有的类都默认直接或者间接继承Object类

代码①

Person类(父类)

public class Person {
	public int money=10_0000_0000;
    public void say(){
        System.out.println("说了一句话");
    }
    

Student类(子类)

public class Student extends Person {
}

main方法

public static void main(String[] args) {
        Student student = new Student();
        student.say();
        System.out.println(student.money);
}

运行结果:

说了一句话
1000000000
代码分析:

1.Student类中并没有语句,但是Student类继承了Person类,也就继承了Person中的全部方法,所以可以调用成功
2.即子类继承了父类,就会拥有父类的全部方法

代码②

当把money改为私有的时候,会出现报错的情况
在这里插入图片描述

在这个时候,需要在Person中加入money的get/set方法,student才可以引用
Person类(父类)

public class Person {
	private int money=10_0000_0000;
    public void say(){
        System.out.println("说了一句话");
    }
    public int getMoney() {
        return money;
    }
    public void setMoney(int money) {
        this.money = money;
    }
}

Student类(子类)

public class Student extends Person {
}

main方法

public static void main(String[] args) {
        Student student = new Student();
        student.say();
        student.setMoney(10_0000_0000);
        System.out.println(student.getMoney());
}

运行结果:

说了一句话
1000000000

代码③

直接通过子类对象访问成员变量:
等号左边是谁.就优先用谁,没有则向上找

间接通过成员方法访问成员变量:

方法属于谁,就优先用谁,没有则向上找

Fu类

public class Fu {
    int numFu=10;
    int num=200;

    public void methodFu(){
        //num使用的是本类当中的,不会找子类的
        System.out.println(num);
    }
}

Zi类

public class Zi extends Fu{
    int numZi=20;
    int num=100;

    public void methodZi(){
        //本类中有num,所以使用的是本类中的num
        System.out.println(num);
    }
}

main方法

public static void main(String[] args) {
        Fu fu = new Fu();
        System.out.println(fu.numFu);//10
        System.out.println("=========================");
        Zi zi = new Zi();
        System.out.println(zi.numFu);//10
        System.out.println(zi.numZi);//20
        System.out.println("=========================");
        System.out.println(zi.num);//100
        System.out.println("=========================");
        zi.methodZi();//100
        zi.methodFu();//200
        fu.methodFu();//200
    }

代码④

在父子类的继承关系中,创建子类对象,访问成员方法的规则
创建的对象是谁,就优先用谁,如果没有则向上找

注意:
无论是成员方法还是成员变量,如果没有就是向上找,绝对不会向下找子类的

Fu类

public class Fu {
    public void methodFu(){
        System.out.println("父类方法执行");
    }

    public void method(){
        System.out.println("父类重名方法执行");
    }
}

Zi类

public class Zi extends Fu{
    public void methodZi(){
        System.out.println("子类方法执行");
    }

    public void method(){
        System.out.println("子类重名方法执行");
    }
}

main方法

public static void main(String[] args) {
        Zi zi = new Zi();
        zi.methodZi();//子类方法执行
        zi.methodFu();//父类方法执行
        zi.method();//子类重名方法执行
    }

代码⑤ 设计原则

对于已经投入使用的类,尽量不要修改,推荐定义一个新的类,来重复利用其中共性内容,并且添加改动新内容

Phone类(父类)

//老款手机
public class Phone {
    public void call(){
        System.out.println("打电话");
    }
    public void send(){
        System.out.println("发短信");
    }
    public void show(){
        System.out.println("显示号码");
    }
}

NewPhone类(子类)

//新手机,老手机作为父类
public class NewPhone extends Phone{
    @Override
    public void show() {
        super.show();//把父类的show方法拿过来重复利用
        //自己子类再来添加更多内容
        System.out.println("显示姓名");
        System.out.println("显示头像");
    }
}

main方法

public static void main(String[] args) {
        Phone phone = new Phone();
        phone.call();
        phone.show();
        phone.send();
        System.out.println("=============================");
        NewPhone newPhone = new NewPhone();
        newPhone.call();
        newPhone.send();
        newPhone.show();
    }

运行结果:

打电话
显示号码
发短信
=============================
打电话
发短信
显示号码
显示姓名
显示头像

代码⑥ 关于super()

通过super关键字来实现对父类成员的访问,用来引用当前对象的父类

Person类(父类)

public class Person {
    protected String name="Sherlock";//受保护的属性
    //输出的方法
    public void print(){
        System.out.println("person");
    }
}

Student类(子类)

public class Student extends Person {
	private String name="Jone";
	//输出的方法
	public void print(){
        System.out.println("student");
    }
    //测试输出的方法
    public void test1(){
        print();//student
        this.print();//student
        super.print();//person
    }
	public void test(String name){
        System.out.println(name);//main方法中输入的名字
        System.out.println(this.name);//Jone,this.name:当前类的东西
        System.out.println(super.name);//Sherlock,调用父类的name
    }
}

main方法

public static void main(String[] args) {
        Student student = new Student();
        student.test("夏洛克");
        System.out.println("==============");
        student.test1();
    }

运行结果:

夏洛克
Jone
Sherlock
==============
student
student
person

补充① this

super用来访问父类内容,this关键字用来访问子类内容
1.在本类成员方法中,访问本类成员变量
2.在本类成员方法中,访问本类另一个成员方法
3.在本类的构造方法中,访问本类的另一个构造方法
注:
this(…)调用也要注意,必须是构造方法的第一个,也是唯一一个

Fu类

public class Fu {
    int num=30;
}

Zi类

public class Zi extends Fu{
    int num=20;

    public Zi() {
        this(6);//本类的无参构造,调用本类的有参构造
    }

    public Zi(int n){

    }

    public void showNum(){
        int num=10;
        System.out.println(num);//局部变量
        System.out.println(this.num);//本类中的成员变量
        System.out.println(super.num);//父类中的成员变量

    }

    public void methodA(){
        System.out.println("AAA");
    }
    public void methodB(){
        this.methodA();
        System.out.println("BBB");
    }
}

补充② super()

当把Person类中的print方法改成私有private的时候,在student中用super也无法访问
这是因为私有的东西无法继承

在这里插入图片描述

补充③ super

Person类(父类)

public class Person {
    protected String name="Sherlock";//受保护的属性
    //父类构造器
    public Person() {
        System.out.println("无参构造执行");
    }
}

Student类(子类)

public class Student extends Person {
    //子类构造器
    public Student(){
        System.out.println("student的无参构造执行了");
    }
}

main方法

public static void main(String[] args) {
        Student student = new Student();
    }

运行结果:

无参构造执行
student的无参构造执行了

代码分析:
子类中的构造器中,有super();的默认代码,会调用父类的构造器
如下所示

public class Student extends Person {
    //子类构造器
    //隐藏代码  调用了父类的无参构造
    public Student(){
        //super();隐藏代码  调用了父类的无参构造
        super();//调用父类的构造器,必须要在子类构造器的第一行,这一行代码默认就会有,自己不用写出来
        System.out.println("student的无参构造执行了");
    }

补充④ super

子类构造可以通过super关键字来调用父类重载构造

Fu类

public class Fu {
    public Fu() {
    }

    public Fu(int num) {
        System.out.println("父类构造方法!");
    }
}

Zi类

public class Zi extends Fu{
    public Zi() {
        super(10);//子类构造可以通过super关键字来调用父类重载构造
        System.out.println("子类构造方法!");
    }
}

super总结

super注意点:
1.super调用父类的构造方法,只能有一个super,且必须在构造方法的第一个,即一定是先调用父类构造.后执行子类构造
2.super必须只能出现在子类的方法或者构造方法中
3.super和this不能同时调用构造方法,即super和this在构造方法中不能同时使用
super vs this:
1.代表的对象不同:
this:本身调用者这个对象
super:代表父类对象的应用
2.前提:
this:没有继承也可以使用
super:只能在继承情况下使用
3.构造方法:
this:默认调用本类的构造
super:默认调用父类的构造

面向对象–重写(***)

重写和重载的区别
重写:方法的名称一样,参数列表一样
重载:方法的名称一样,参数列表不一样

①static 静态方法

A类

//继承
public class A extends B {

    //重写
    public static void test(){
        System.out.println("A=>test()");
    }
}

B类

public class B {
    public static void test(){
        System.out.println("B=>test()");
    }
}

main方法

public static void main(String[] args) {

        //方法的调用只和左边定义的数据类型有关
        A a = new A();
        a.test();//A

        //父类地引用指向了子类
        B b = new A();
        b.test();//B
    }

运行结果:

A=>test()
B=>test()

代码分析:

静态是类的方法
在静态方法中,b调用的是类的方法,方法的调用只和左边定义的数据类型有关,即调用了B 的方法

②非静态方法

A类

//继承
public class A extends B {
    //重写
    @Override//注解:有功能的注释
    public void test() {
        System.out.println("A=>test()");
    }
}

B类

//重写都是方法的重写 与属性无关
public class B {
    public void test(){
        System.out.println("B=>test()");
    }
}

main方法

public static void main(String[] args) {
        A a = new A();
        a.test();//A=>test()

        //父类地引用指向了子类
        B b = new A();//子类重写了父类的方法
        b.test();//A=>test()
    }

运行结果:

A=>test()
A=>test()

代码分析:

非静态是对象的方法
在非静态方法中,b调用的是对象的方法,而b是A类new出来的对象,即调用的为A,即子类重写了父类的方法

总结

关于静态和非静态:

静态是类的方法,非静态是对象的方法
静态方法和非静态方法区别很大
静态方法:方法的调用只和左边,定义的数据类型有关
重写只和非静态方法有关系
重写的方法只能用在public方法,private不可用

重写:

需要有继承关系:子类重写父类的方法
1.方法名必须相同
2.参数列表必须相同
3.子类方法的返回值必须小于等于父类方法的返回值
4.子类方法权限修饰符必须大于等于父类方法的权限修饰符 public>protected>(default)>private
5.抛出的异常:范围可以被缩小,但不能扩大.ClassNotFoundException(小)—>Exception(大)
注:子类的方法名和父类的方法名必须要一致,方法体(里面需要实现的东西)不同

为什么需要重写?

父类的功能子类不一定需要,不一定满足
override:重写

面向对象–多态(***)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gcIbK8Ff-1638929155455)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210805173633672.png)]

动态编译:类型:可扩展性
即同一方法可以根据发送对象的不同而采用多种不同的行为方式。
一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多

多态存在的条件
1.有继承关系
2.子类重写父类方法
3.父类引用指向子类对象
格式:
①父类名称 对象名=new 子类名称();
②接口名称 对象名=new 实现类名称();

注意: 多态是方法的多态,属性没有多态性 instanceof

Person类(父类)

public class Person {
    public void run(){
        System.out.println("run");
    }
}

Student类(子类)

public class Student extends Person{
    @Override//重写父类的run方法
    public void run() {
        System.out.println("song");
    }
    public void eat(){
        System.out.println("eat");
    }
}

main方法

public static void main(String[] args) {
        //一个对象的实际类型是确定的
		//new Person();
		//new Student();

        //可以指向的引用类型就不一样了
        //Student能调用的方法都是自己的或者继承父类的
        Student s1 = new Student();
        Person s2 = new Student();//父类的引用指向子类的类型
        Object s3 = new Student();

        s2.run();//song  子类重写了父类的方法.执行子类,如果子类中没有,则在父类中找
        s1.run();//song
        s1.eat();//eat
        //对象能执行哪些方法,主要看对象左边的类型,和右边关系不大
        //s2.eat();//不可直接调用,父类虽然可以指向子类,但不可调用子类独有的方法
        ((Student) s2).eat();//用类型转换可以调用(下文会讲),高-->低
    }

运行结果:

song
song
eat
eat

多态总结

多态注意事项:
1.多态是方法的多态,属性没有多态
2.父类和子类 有关系 ClassCastException:类型转换异常
3.多态存在的条件::①有继承②方法需要重写③父类引用指向子类对象 Father f1=new Son();

不能被重写的方法:
1.static 静态方法 属于类,不属于实例
2.final 常量
3.private 私有的

instanceofof关键字

instanceof (类型转换) 引用类型,判断一个对象是什么类型(如何才能知道一个父类引用的对象,本来是什么子类)
格式:
对象 instanceof 类名称

Person类(父类)

public class Person {
    }
}

Student类(子类)

public class Student extends Person{
    }
}

Teacher类(子类)

public class Teacher extends Person {
}

main方法

public static void main(String[] args) {
        //Object>String
        //Object>Person>Teacher
        //Object>Person>Student
        Object object = new Student();

        //System.out.println(x instanceof Y);//看这行代码能不能编译通过,取决于X和Y之间是否存在父子关系
        //并且要注意x是谁new出来的对象,看看X和Y书是否是一条线上的


        System.out.println(object instanceof Student);//true
        System.out.println(object instanceof Person);//true
        System.out.println(object instanceof Object);//true
        System.out.println(object instanceof Teacher);//false
        System.out.println(object instanceof String);//false
        System.out.println("==========================");
        Person person = new Student();
        System.out.println(person instanceof Student);//true
        System.out.println(person instanceof Person);//true
        System.out.println(person instanceof Object);//true
        System.out.println(person instanceof Teacher);//false  person和teacher是两条路,无关
        //System.out.println(person instanceof String);//编译报错,String和person毫无关系
        System.out.println("==========================");
        Student student = new Student();
        System.out.println(student instanceof Student);//true
        System.out.println(student instanceof Person);//true
        System.out.println(student instanceof Object);//true
        //System.out.println(student instanceof Teacher);//false 编译报错
        //System.out.println(student instanceof String);//false 编译报错
    }

类型转换

Person类(父类)

public class Person {
    }
}

Student类(子类)

public class Student extends Person{
	public void go(){
        System.out.println("go");
    }
}

Teacher类(子类)

public class Teacher extends Person {
}

main方法

public static void main(String[] args) {
        //类型之间的转化:基本类型转换(高-->低需要强制转换)
        //同理                      父-->子也需要强制转换
        //高                  低
        Person student = new Student();//student是person类的
        //student.go();不能直接调用子类当中的方法,需要强制转换
        //student将这个对象转换为Student类型,我们就可以使用Student类型的方法了
        ((Student)student).go();

        //子类转父类,可能会丢失方法
        Student student1=new Student();
        student1.go();
        Person person=student1;
    }

总结:

1.父类引用指向的对象
2.把子类转换为父类,向上转型:不用强转,可以直接转,丢失子类中原本可以直接调用的方法
3.把父类转换为子类,向下转型,需要强制转化,丢失父类被子类所重写掉的方法
4.方便方法的调用,减少重复代码

static关键字

①静态方法和非静态方法

静态方法是和类一起加载的
非静态方法可以调用静态方法里的所有东西
静态方法可以调用静态方法的所有东西
静态方法不可以直接调用非静态方法里的东西

student类

public class Student {
    private static int age;//静态变量
    private double score;//非静态变量
    /*
    静态方法和类一起加载

    非静态方法可以调用静态方法里的所有东西
    静态方法可以调用静态方法的所有东西
    静态方法不可以直接调用非静态方法里的东西
     */

    public void run(){
        go();
    }

    public static void go(){
        new Student().run();//静态方法不可以直接调用非静态方法里的东西 需要new一下
    }
}

Person类

public class Person {
    //第2执行  一般用来赋初始值
    {
        System.out.println("匿名代码块");
    }
    //第1执行   static只执行一次
    static {
        System.out.println("静态代码块");
    }
    //第3执行
    public Person() {
        System.out.println("构造方法");
    }

    public static void main(String[] args) {
        Person person1 = new Person();
        System.out.println("=================");
        Person person2 = new Person();
    }
}

//运行结果:
静态代码块
匿名代码块
构造方法
=================
匿名代码块
构造方法

②static关键字修饰成员

Student类

public class Student {
    private int id;
    private String name;
    private int age;
    static String room;
    private static int idCounter=0;//计数器

    public Student() {
        this.id=++idCounter;//每当new一个新对象,对自动+1
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public static int getIdCounter() {
        return idCounter;
    }

    public static void setIdCounter(int idCounter) {
        Student.idCounter = idCounter;
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        this.id=++idCounter;//每当new一个新对象,对自动+1
    }

    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 static String getRoom() {
        return room;
    }

    public static void setRoom(String room) {
        Student.room = room;
    }
}

main方法

Student one=new Student("Sherlock",23);
        one.room="101";
        System.out.println("姓名:"+one.getName()+"年龄:"+one.getAge()+"教室:"+one.room+"学号:"+one.getId());
        Student two=new Student("Jone",22);
        System.out.println("姓名:"+two.getName()+"年龄:"+two.getAge()+"教室:"+two.room+"学号:"+two.getId());

运行结果;

姓名:Sherlock年龄:23教室:101学号:1
姓名:Jone年龄:22教室:101学号:2

代码分析:

虽然在main方法中,只写了one.room=“101”,但由于romm为static修饰的变量,所以可以多个对象共享同一份数据,即two对象也可以使用"101"

③static关键字修饰成员

MyClass类

public class MyClass {
    int num;//成员变量
    static int numStatic;//静态变量

    //成员方法
    public void method() {
        System.out.println("这是一个普通的成员方法:");
    }
    //静态方法
    public static void methodStatic(){
        System.out.println("这是一个静态方法");
    }
}

main方法

public static void main(String[] args) {
        MyClass obj=new MyClass();
        obj.method();

        //对于静态方法来说,可以通过对象名进行调用,也可以直接通过类名称调用
        obj.methodStatic();//正确  不推荐
        MyClass.methodStatic();//正确  推荐

        //对于本类中的静态方法,可以省略类名称
        myMethod();

    }
    public static void myMethod(){
        System.out.println("自己的方法!");
    }

注意事项:

一旦使用static修饰成员方法,那么这就成为了静态方法 静态方法不属于对象,而是属于类
因为静态方法是和类一起加载的

如果没有static关键字,必须首先创建对象,然后通过对象才能使用
如果有了static关键字.那么不需要创建对象,直接就能通过类名称来使用他

无论是成员变量还是成员方法,如果有了static,都推荐使用类名称调用
静态变量:类名称.静态变量
静态方法:类名称.静态方法()

根据类名称访问静态成员变量的时候,全程和对象没关系,只和类有关系

注意:
1.静态只能直接访问静态,不能直接访问非静态
2.静态方法中不能用this
原因:this代表当前对象,但静态和对象无关

④静态代码块

特点:当第一次用到本类时,静态代码块执行唯一的一次
静态内容总是优先于非静态,所以静态代码块比构造方法先执行

静态代码块的用途:
用来一次性对静态成员变量进行赋值

Person类

public class Person {
    static {
        System.out.println("静态代码块执行");
    }
    public Person(){
        System.out.println("构造方法执行!");
    }
}

main方法

public static void main(String[] args) {
        Person one = new Person();
        Person two = new Person();
    }

运行结果:

静态代码块执行
构造方法执行!
构造方法执行!

抽象类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pUxF6HCY-1638929155459)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210805110201881.png)]

abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类。
抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类,不能使用new关键字来创建对象,它是用来让子类继承的
抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的
子类继承抽象类,那么就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类

例一

Animal抽象类

public abstract class Animal {
    //这是一个抽象方法,代表吃东西,但是具体吃什么,不确定
    public abstract void eat();
}

Cat类

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

main方法

public class Main {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();
    }
}
//运行结果
猫吃鱼

例二

Action抽象类

//abstract 抽象类
//extends:单继承   但接口可以实现多继承
public abstract class Action {

    //约束~有人帮我们实现
    //abstract 抽象方法:只有方法的名字,没有方法的实现
    public abstract void doSomething();
}

A类

//抽象类的所有方法.继承他的子类必须要实现他的方法,除非这个类也是抽象类
public class A extends Action {
    @Override
    public void doSomething() {
    }
}

总结:

1.不能new这个抽象类,只能靠子类去实现:约束
2.抽象类里可以写普通方法
3.抽象方法必须在抽象类中

抽象方法:加上abstract关键字,然后去掉大括号,直接分号结束
抽象类:抽象方法所在的类,必须是抽象类,在class之前写上abstract即可

如何使用抽象类和抽象方法:

1.不能直接创建new抽象类对象

2.必须用一个子类来继承抽象父类

3.子类必须重写父类当中所有的抽象方法(去掉抽象方法中的abstract关键字,然后补上大括号)

4.创建子类对象进行使用

注意事项:
1.抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义
2.抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
3.抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
4.抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

接口

接口都能定义抽象方法
格式:

public abstract 返回值类型  方法名称(参数列表);:
 1.接口中的抽象方法,修饰符必须是两个固定的关键字:public abstract
 2.这两个关键字修饰符可以选择性省略(目前不推荐)
接口使用的步骤:
1.接口不可直接使用,必须有一个"实现类"来实现该接口
格式:
 public class 实现类名称 implements 接口名称{...}
2.实现类必须重写接口中所有抽象方法
3.创建实现类对象进行使用
注:
如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类

MyInterfaceAbstract接口

public interface MyInterfaceAbstract {
    //这是一个抽象方法
    public abstract void methodAbs1();

    //这是一个抽象方法
    abstract void methodAbs2();

    //这是一个抽象方法
    public void methodAbs3();

    //这是一个抽象方法
    void methodAbs4();
}

MyInterfaceAbstractImpl实现类

public class MyInterfaceAbstractImpl implements MyInterfaceAbstract{

    @Override
    public void methodAbs1() {
        System.out.println("这是第一个方法");
    }

    @Override
    public void methodAbs2() {
        System.out.println("这是第二个方法");
    }

    @Override
    public void methodAbs3() {
        System.out.println("这是第三个方法");
    }

    @Override
    public void methodAbs4() {
        System.out.println("这是第四个方法");
    }
}

main方法

public static void main(String[] args) {
        //创建实现类对象进行使用
        MyInterfaceAbstractImpl inter = new MyInterfaceAbstractImpl();
        inter.methodAbs1();
        inter.methodAbs2();
        inter.methodAbs3();
        inter.methodAbs4();
    }
//运行结果
这是第一个方法
这是第二个方法
这是第三个方法
这是第四个方法

接口的默认方法

接口的默认方法,可以通过接口实现类的对象直接调用
接口的默认方法,也可以被接口实现类进行重写

格式:
public default 返回值类型  方法名称(参数列表){
	方法体
}

备注:
	接口中的默认方法,可以解决接口升级的问题(以下代码中有体现)

MyInterfaceDefault 接口

public interface MyInterfaceDefault {
    //抽象方法
    public abstract void methodAbs();
    
    //在保持实现类不变的情况下,新添加的方法,改成默认方法
    public default void methodDefault(){
        System.out.println("这是新添加的默认方法");
    }
}

MyInterfaceDefaultA 实现类

public class MyInterfaceDefaultA implements MyInterfaceDefault{
    @Override
    public void methodAbs() {
        System.out.println("实现了抽象方法AAA");
    }
}

MyInterfaceDefaultB 实现类

public class MyInterfaceDefaultB implements MyInterfaceDefault{

    @Override
    public void methodAbs() {
        System.out.println("实现了BBB");
    }

    //接口的默认方法,也可以被接口实现类进行重写
    @Override
    public void methodDefault() {
        System.out.println("实现类B覆盖重写了接口的默认方法");
    }
}

main方法

public static void main(String[] args) {
        MyInterfaceDefaultA a = new MyInterfaceDefaultA();
        //调用抽象方法,实际运行的是右侧的实现类
        a.methodAbs();//实现了抽象方法AAA
        //调用默认方法,如果实现类没有,会向上找接口
        a.methodDefault();//这是新添加的默认方法
    
    
        MyInterfaceDefaultB b = new MyInterfaceDefaultB();
        b.methodAbs();//实现了BBB
        b.methodDefault();//实现类B覆盖重写了接口的默认方法
    }

1.普通类:只有具体实现
2.抽象类:具体实现和规范(抽象方法)都有!
3.接口就是规范,定义的是一组规则,体现了现实世界中“如果你是…则必须能.….”的思想 如果你是天使,则必须能飞。如果你是汽车,则必须能跑。如果你好人,则必须干掉坏人;如果你是坏人,则必须欺负好人。
4.接口的本质是契约,就像我们人间的法律一样。制定好后大家都遵守。
5.OO的精髓,是对对象的抽象,最能体现这一点的就是接口。为什么我们讨论设计模式都只针对具备了抽象能力的语言(比如c++、java、c#等),就是因为设计模式所研究的,实际上就是如何合理的去抽象

接口静态方法使用

接口定义静态方法: 
格式:

public static 返回值类型  方法名称(参数列表){方法体}


注意:
	不能通过接口实现类的对象来调用接口中的静态方法
	通过接口名称直接调用其中的静态方法

MyInterfaceStatic 接口

public interface MyInterfaceStatic {
    public static void methodStatic(){
        System.out.println("这是接口的静态方法");
    }
}

main方法

public static void main(String[] args) {
        //通过接口名称直接调用其中的静态方法
        MyInterfaceStatic.methodStatic();//这是接口的静态方法
    }

接口常量定义和使用

接口中也可以定义成员变量,但必须用public static final三个关键字修饰
从效果上看,其实就是接口的常量

格式:
public static final 数据类型 变量名称=数据值;

注:
1.一旦使用final关键字,说明不可改变
2.接口中的常量,可以省略public static final
3.接口中的常量必须赋值,不可以不赋值
4.接口中常量的名称,使用完全大写的字母,用下划线分隔
5.访问常量时:接口名称.常量名

MyInterfaceConst接口

public interface MyInterfaceConst {
    //这其实就是一个常量,一旦赋值,不可以修改
    public static final int NUM=10;
    
}

main方法

public static void main(String[] args) {
        System.out.println(MyInterfaceConst.NUM);//10
    }

实现多个接口

一个类的直接父类是唯一的,但一个类可以同时实现多个接口

接口实例

UserService接口

//interface 定义的关键字,接口都需要有实现类
public interface UserService {
    //定义的属性  默认为:public static final(不写也行)
    int age=99;
    
    //接口中的所有定义的方法都是抽象的   默认为public abstract(不写也行)
    void add(String name);
    void delete(String name);
    void update(String name);
    void query(String name);
}

TimeService接口

public interface TimeService {
    void timer();
}

UserServiceImpl执行接口类

//类可以实现接口  implements+接口名
//实现了接口的类必须要重写接口中的方法
//多继承  利用接口实现多继承
public class UserServiceImpl implements UserService,TimeService{

    @Override
    public void add(String name) {

    }

    @Override
    public void delete(String name) {

    }

    @Override
    public void update(String name) {

    }

    @Override
    public void query(String name) {

    }

    @Override
    public void timer() {

    }
}

接口总结

接口作用:
1.约束
2.定义一些方法,让不同的人实现
3.方法都为public abstract
4.常量都为public static final
5.接口不能被实例化,结构中没有构造方法
6.接口可以实现多个接口,通过implements
7.实现了接口的类必须要重写接口中的方法
8.接口没有静态代码块或者构造方法
9.一个类的直接父类是唯一的,但一个类可以同时实现多个接口
10.如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可
11.如果实现类锁实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写
12.如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类
13.一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法

关于多继承
1.类于类之间是单继承的.直接父类只有一个
2.类于接口之间是多实现的,一个类可以实现多个接口
3.接口于接口之间是多继承的
注:
1.多个父类接口当中的抽象方法重复,没关系
2.多个父类接口当中的默认方法如果重复,那么子类接口必须进行默认方法的覆盖重写,而且带着default关键字

一个类内部包含另一个类
例如:
身体和心脏的关系
汽车和发动机的关系

分类
1.成员内部类
2.局部内部类(包含匿名内部类)

定义格式:

修饰符 class  外部类名称{
    修饰符  class  内部类名称{
        ...
    }
    ...
}

内部类(???)

访问内部类

如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类
例如:身体和心脏  汽车和发动机

分类:
    1.成员内部类
    2.局部内部类(包含匿名内部类)

成员内部类定义格式:
    修饰符 class  外部类名称{
        修饰符  class  内部类名称{
            ...
        }
        ...
    }
    
注意:
内用外:随意访问
外用内:需要内部类对象

如何使用成员内部类?
     1.间接方式:在外部类的方法中,使用内部类,然后main只是调用外部类的方法
     2.直接方式:
     类名称  对象名 = new 类名称();
     外部类名称.内部类名称 对象名 = new  外部类名称().new 内部类名称();

Body类

public class Body {//外部类

    public class Heart {// 成员内部类

        //内部类的方法
        public void beat() {
            System.out.println("心脏跳动");
            System.out.println("我叫:"+name);
        }
    }

    //外部类成员变量
    private String name;


    //外部类的方法
    public void methodBody() {
        System.out.println("外部类的方法");
        Heart heart = new Heart();
        heart.beat();
    }

    public String getName() {
        return name;
    }

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

}
如何使用成员内部类?有两种方式
1.间接方式:在外部类的方法当中,使用内部类,然后main只是调用外部类的方法
2.直接方式:公式:
		外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称; 

main方法

public static void main(String[] args) {
        Body body = new Body();//外部类的对象
        //间接方式:通过外部类的对象,调用外部类的方法,里面间接使用内部类Heart
        body.methodBody();
        
    
    
        //直接方式
        Body.Heart heart=new Body().new Heart();
        heart.beat();
    }

运行结果:

外部类的方法
心脏跳动
我叫:null
========================
心脏跳动
我叫:null

内部类的同名变量访问

如果出现了重名现象
访问格式:
外部类名称.this.外部类成员变量名

Outer类

public class Outer {
    int num = 10;//外部类成员变量

    public class Inner {
        int num=20;//内部类成员变量

        public void methodInner(){
            int num=30;//内部类方法的局部变量
            System.out.println(num);//局部变量
            System.out.println(this.num);//内部类的成员变量
            System.out.println(Outer.this.num);//外部类的成员变量
        }
    }
}

main方法

public static void main(String[] args) {
        Outer.Inner inner=new Outer().new Inner();
        inner.methodInner();
    }

运行结果:

30
20
10

局部内部类定义

如果一个类是定义在一个方法内部的,那么这就是一个局部内部类
"局部":只有当前所属的方法才能使用它,出了这个方法 外面就不能用了

定义格式:
修饰符 class 外部类名称{
	修饰符 返回值类型  外部类方法名称(参数列表)
		class 局部内部类名称{
		//...
		}
}

Outer类

public class Outer {
    public void methOuter(){
        class Inner{//局部内部类
            int num=10;
            public void methodInner(){
                System.out.println(num);//10
            }
        }
        Inner inner = new Inner();
        inner.methodInner();
    }
}

main方法

public static void main(String[] args) {
        Outer outer = new Outer();
        outer.methOuter();//10
    }

局部内部类的final问题:
如果希望访问所在方法的局部变量,那么这个局部变量必须是有效的final的

public class MyOuter {
    public void methodOuter() {
        int num = 10;//必须为final的,只要局部变量不变,那么final关键字可以省略
        class MyInner{
            public void methodInner(){
                System.out.println(num);//10
            }
        }
    }
}

匿名内部类

如果接口的实现类(或者是父类的子类)只需要使用唯一的一次
那么这种情况就可以省略该类的定义,而改为使用 匿名内部类

匿名内部类定义格式:
	接口名称 对象名 = new 接口名称(){
		//覆盖重写所有抽象方法	
	};

MyInterface类

public interface MyInterface {
    //两个抽象方法
    void method1();
    void method2();
}

main方法

public static void main(String[] args) {
        //使用匿名内部类  而且省略了对象名称(没名字)  既是匿名内部类,也是匿名对象
        new MyInterface() {
            @Override
            public void method1() {
                System.out.println("匿名内部类实现了方法A");
            }
        }.method1();
    }

public static void main(String[] args) {
    //使用匿名内部类  希望同一个对象,调用多次方法,那么必须给对象起个名字
    MyInterface obj = new MyInterface() {
        @Override
        public void method1() {
            System.out.println("匿名内部类实现了方法A");
        }

        @Override
        public void method2() {
            System.out.println("匿名内部类实现了方法B");
        }
    };
    obj.method1();
    obj.method2();
}
对new  接口名称{...}进行解析:
1.new代表创建对象的动作
2.接口名称就是匿名内部类需要实现哪个接口
3.{...}这才是匿名内部类的内容
另外还要注意几点问题:
1.匿名内部类,在【创建对象】的时候,只能使用唯一一次。
如果希望多次创建对象,而且类的内容一样的话,那么就必须使用单独定义的实现类了
2.匿名对象,在【调用方法】的时候,只能调用唯——次。
如果希望同一个对象,调用多次方法,那么必须给对象起个名字。
3.匿名内部类是省略了【实现类/子类名称】
  但是匿名对象是省略了【对象名称】(既是匿名内部类,也是匿名对象)
  强调:匿名内部类和匿名对象不是一回事!!!l
定义一个类时,权限修饰符规则
1.外部类:public/(default)
2.成员内部类:public/protected/(default)/private
3.成员内部类:什么都不能写

类作为成员变量类型

Weapon类

public class Weapon {
    private String code;

    public Weapon() {
    }

    public Weapon(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}

Hero类

//英雄角色类
public class Hero {
    private String name;
    private int age;
    private Weapon weapon;//类作为成员变量类型

    public Hero() {
    }

    public Hero(String name, int age, Weapon weapon) {
        this.name = name;
        this.age = age;
        this.weapon = weapon;
    }

    public void attack(){
        System.out.println("年龄为"+age+"的"+name+"用"+weapon.getCode()+"攻击敌人");//这里的武器需要用 对象.getCode()
    }

    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 Weapon getWeapon() {
        return weapon;
    }

    public void setWeapon(Weapon weapon) {
        this.weapon = weapon;
    }
}

main方法

public static void main(String[] args) {
    Hero hero = new Hero();
    hero.setName("Sherlock");
    hero.setAge(23);
    Weapon weapon = new Weapon("巴雷特");
    hero.setWeapon(weapon);
    hero.attack();//年龄为23的Sherlock用巴雷特攻击敌人
}

接口作为成员变量的类型

Skill接口

public interface Skill {
    void use();
}

SkillImpl实现类

public class SkillImpl implements Skill{
    @Override
    public void use() {
        System.out.println("biu~biu~biu");
    }
}

Hero类

public class Hero {
    private String name;
    private Skill skill;//英雄的法术技能

    public Hero() {
    }

    public Hero(String name, Skill skill) {
        this.name = name;
        this.skill = skill;
    }

    public void attack(){
        System.out.println("我叫"+name+"开始释放技能");
        skill.use();
        System.out.println("技能释放完毕");

    }

    public String getName() {
        return name;
    }

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

    public Skill getSkill() {
        return skill;
    }

    public void setSkill(Skill skill) {
        this.skill = skill;
    }
}

main方法

public static void main(String[] args) {
        Hero hero = new Hero();
        hero.setName("Sherlock");

        //设置英雄技能
        SkillImpl skill = new SkillImpl();
        hero.setSkill(skill);
        hero.attack();
    }

接口作为方法的参数或返回值

//Java.util.List正是ArrayList所实现的接口
public static void main(String[] args) {
        //左边是接口名称,右边是实现类名称,多态的写法
        List<String> list=new ArrayList<>();
        List<String> result=addNames(list);
        for (int i = 0; i < result.size(); i++) {
            System.out.println(result.get(i));
        }
    }
public static List<String> addNames(List<String> list){
    list.add("Sherlock");
    list.add("Jone");
    list.add("Jenny");
    return list;
}

//运行结果
Sherlock
Jone
Jenny

Object中的equals方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jh27umyS-1638929155459)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210805212829326.png)]

StringBuilder类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5dUnLw2d-1638929155459)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210805215807821.png)]

构造方法

StringBuilder类:字符串缓冲区,可以提高字符串效率
构造方法:
    StringBuilder():构造一个不带任何字符的字符串生成器,其初始容量为16个字符
    StringBuilder(String str)构造一个字符串生成器,并初始化为指定的字符串内容
public static void main(String[] args) {
        //空参构造方法
        StringBuilder bu1=new StringBuilder();
        System.out.println("bu1:"+bu1);//bu1:

        //带字符串的构造方法
        StringBuilder bu2 = new StringBuilder("Sherlock");
        System.out.println("bu2:"+bu2);//bu2:Sherlock
    }

成员方法

StringBuilder常用方法
	public StringBuilder append(...):添加任意类型数据的字符串形式,并返回当前对象自身
public static void main(String[] args) {
        StringBuilder bu1 = new StringBuilder();
        //使用append方法添加数据
        //append方法返回的是this,也就是调用方法的对象bu1,this=bu1
        StringBuilder bu2 = bu1.append("Sherlock");//相当于把bu1的地址复制给了bu2
        System.out.println(bu1);//Sherlock
        System.out.println(bu2);//Sherlock
        System.out.println(bu1==bu2);//true

        //即使用append方法无需接收返回值
        bu1.append("abc");
        bu1.append(6);
        bu1.append(true);
        bu1.append("哈喽");
        System.out.println(bu1);//Sherlockabc6true哈喽

        /*
        链式编程:方法返回值是一个对象,可以继续调用方法
         */
        bu1.append(1).append(2).append(3);
        System.out.println(bu1);//Sherlockabc6true哈喽123
    }

StringBuilder的toString方法

StringBuilder和String可以相互转换
String-->StringBuilder:可以使用StringBuilder的构造方法
	StringBuilder(String str)构造一个字符串生成器,并初始化为指定字符串内容
StringBuilder-->String:可以使用StringBuilder中的toString方法
	public String toString():将当前StringBuilder对象转换为String对象
public static void main(String[] args) {
        //String->StringBuilder
        String str="Hello";
        StringBuilder bu = new StringBuilder(str);
        //往StringBuilder中添加数据
        bu.append(" World!");
        System.out.println(bu);//Hello World!

        //StringBuilder-->String
        String s = bu.toString();
        System.out.println("s:"+s);//s:Hello World!
    }

包装类

Java提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类,如下:
基本类型对应的包装类(位于java.lang包中)
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

装箱和拆箱

/*
    装箱:
        把基本类型的数据,包装到包装类中(基本数据类型-->包装类)
        构造方法:
            Integer(int value)构造一个新分配的Integer对象,它表示指定的int值
            Integer(String s)构造一个新分配的Integer对象,它表示String参数所指示的int值
                传递的字符串,必须是基本类型的字符串,否则会抛出异常"100"正确."a"抛出异常
        静态方法:
            static Integer valueOf(imt i)返回一个表示指定int值的Integer实例
            static Integer valueOf(String s)返回保存指定的String的值的Integer对象
    拆箱:在包装类中取出基本类型的数据(包装类-->基本类型的数据)
        成员方法:
            int intValue()以int类型返回该Integer的值
 */
public class Demo01Integer {
    public static void main(String[] args) {
        //装箱
        Integer in1 = new Integer(1);
        System.out.println(in1);

        Integer in2 = new Integer("1");
        System.out.println(in2);

        //静态方法
        Integer in3 = Integer.valueOf(1);
        System.out.println(in3);

        Integer in4 = Integer.valueOf("1");
        System.out.println(in4);

        //拆箱
        int i=in1.intValue();
        System.out.println(i);
    }
}

自动装箱与自动拆箱

/*
    自动装箱与自动拆箱:基本类型的数据和包装类之间可以自动的相互转换
 */
public class Demo02Integer {
    public static void main(String[] args) {
        /*
            自动装箱:直接把int类型的整数复制给包装类
            Integer in=1;相当于Integer in=new Integer(1)
         */
        Integer in=1;

        /*
            自动拆箱:in是包装类,无法直接参与运算,可以自动转换为基本数据类型,再进行计算
            in+2;相当于in.inValue()+2=3

         */
        in=in+2;
    }
}

基本类型与字符串之间的相互转换

/*
    基本类型与字符串之间的相互转换
    基本类型-->字符串(String)
        1.基本类型的值+""
        2.包装类的静态方法toString(参数)
            static String toString(int i)返回一个表示指定整数的String对象
        3.String类的静态方法valueOf(参数)
            static String valueOf(int i)返回int参数的字符串表示形式
    字符串-->基本类型
        使用包装类的静态方法parse("数值")
            Integer类:static int parseInt(String s)
            Double类:static double parseDouble(String s)
 */
public class Demo03Integer {
    public static void main(String[] args) {
        //基本类型-->字符串(String) 三种方式
        int i1=100;
        String s1=i1+"";
        System.out.println(s1+200);//100200

        String s2 = Integer.toString(100);
        System.out.println(s2+200);//100200

        String s3 = String.valueOf(100);
        System.out.println(s3+200);//100200

        //字符串-->基本类型
        int i2 = Integer.parseInt(s1);
        System.out.println(i2-10);//50
    }
}

Collection集合

集合概述

集合:集合是Java中提供的一种容器,可以用来存储多个数据

集合按照其存储结构可以分为两大类,分别是单列集合`java.util.Collection`和双列集合`java.util.Map`,
本节主要学习Collection集合
//集合和数组的区别
数组的长度是固定的。集合的长度是可变的
数组中存储的是同一类型的元素,可以存储基本数据类型值
集合存储的都是对象。而且对象的类型可以不一致
在开发中一般当对象多的时候,使用集合进行存储 f'f'f
Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素

它有两个重要的子接口,分别是"java.util.List"和"java.util.Set"

其中,List的特点是元素有序、元素可重复    Set的特点是元素无序,而且不可重复

List接口的主要实现类有"java.util.ArrayList"和"java.util.LinkedList",

Set`接口的主要实现类有"java.util.HashSet"和"java.util.TreeSet"

集合中的各种方法

/*
    所有单列集合的最顶层的接口,里面定义了所有单列集合共性的方法
    任意的单列集合都可以使用Collection接口中的方法

    共性的方法:
        public boolean add(E e):把给定的对象添加到当前集合中
        public void clear():清空集合中所有元素
        public boolean remove(E e):把给定的对象在当前集合中删除
        public boolean contains(E e):判断当前集合中是否包含给定的对象
        public boolean isEmpty():判断当前集合是否为空
        public int size():返回集合中元素的个数
        public object[] toArray():把集合中的元素存储到数组中
 */

import java.util.ArrayList;
import java.util.Collection;

public class Demo01Collection {
    public static void main(String[] args) {
        //创建集合对象,可用多态  接口指向实现类
        Collection<String> coll = new ArrayList<>();//接口
        System.out.println(coll);//[]   重写了toString方法

        /*
        public boolean add(E e):把给定的对象添加到当前集合中
        返回值:布尔值
         */
        coll.add("Sherlock");
        coll.add("Jone");
        coll.add("Jenny");
        coll.add("Jack");
        coll.add("Tom");
        System.out.println(coll);//[Sherlock, Jone, Jenny, Jack, Tom]

        /*
        public boolean remove(E e):把给定的对象在当前集合中删除
        返回值:布尔值
                集合中存在要删除的元素,返回true
                集合中不存在要删除的元素,返回false
         */
        coll.remove("Jone");
        System.out.println(coll);//[Sherlock, Jenny, Jack, Tom]

        /*
        public boolean contains(E e):判断当前集合中是否包含给定的对象
        返回值:布尔值
            包含返回true
            不包含返回false
         */
        boolean b1 = coll.contains("Sherlock");
        System.out.println(b1);//true

        /*
        public boolean isEmpty():判断当前集合是否为空
        返回值:布尔值
            为空:true
            不为空:false
         */
        boolean b2 = coll.isEmpty();
        System.out.println(b2);//false

        /*
        public int size():返回集合中元素的个数
         */
        int size = coll.size();
        System.out.println(size);//4

        /*
        public object[] toArray():把集合中的元素存储到数组中
         */
        Object[] array = coll.toArray();
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i]);
        }

        /*
        public void clear():清空集合中所有元素
         */
        coll.clear();
        System.out.println(coll);//[]
    }
}

把其中的ArrayList集合替换成以下任何一个集合都可使用
在这里插入图片描述

常用功能

java.utils.Collections 是集合工具类,用来对集合进行操作

①批量添加/打乱

 public static <T> boolean addAll(Collection<T> c, T... elements) :往集合中添加一些元素
 public static void shuffle(List<?> list) 打乱顺序 :打乱集合顺序
 public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        //public static <T> boolean addAll(Collection<T> c, T... elements) :往集合中添加一些元素
        Collections.addAll(list,"a","b","c","d","e");
        System.out.println(list);//[a, b, c, d, e]

        //public static void shuffle(List<?> list) 打乱顺序:打乱集合顺序
        Collections.shuffle(list);
        System.out.println(list);//[b, a, e, c, d] 每次运行都不同
    }

②排序

public static <T> void sort(List<T> list) :将集合中元素按照默认规则排序
public static <T> void sort(List<T> list,Comparator<? super T> ) :将集合中元素按照指定规则排序(自定义类型元素)

注意事项:
 	  对自定义的对象进行排序的使用前提:
      在自定义的类当中需要实现Comparable,重写接口中的comparable方法中定义排序的规则
Comparable排序规则:
	  自己(this)-参数:升序
	  参数-自己(this):降序

Person类

public class Person implements Comparable<Person>{
    private String name;
    private int age;

    public Person() {
    }

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

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

    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;
    }
    //重写排序的规则
    @Override
    public int compareTo(Person o) {
        //自定义比较规则,比较两个人的年龄
        // return o.getAge()-this.getAge();//按年龄降序排序
        return this.getAge()-o.getAge();//按年龄升序排序
    }
}

main方法

 public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,1,5,3);
        //public static <T> void sort(List<T> list) :将集合中元素按照默认规则排序
        Collections.sort(list);
        System.out.println(list);//[1,3,5]

        ArrayList<String> list1 = new ArrayList<>();
        Collections.addAll(list1,"a","c","b");
        Collections.sort(list1);
        System.out.println(list1);//[a, b, c]

        ArrayList<Person> list2 = new ArrayList<>();
        list2.add(new Person("张三",18));
        list2.add(new Person("李四",20));
        list2.add(new Person("王五",15));
        System.out.println(list2);
        Collections.sort(list2);
        System.out.println(list2);
    }

增强for

增强for循环格式
/*
     格式:
        for(集合/数组的数据类型  变量名 : 集合名/数组名){
            sout(数组名)
        }
 */
//使用增强for循环遍历集合
private static void demo02() {
    ArrayList<String> array = new ArrayList<>();
    array.add("Sherlock");
    array.add("Jone");
    array.add("Jack");
    for (String str : array) {
        System.out.println(str);
    }
}

//用增强for循环遍历数组
private static void demo01() {
    int[] array = {1, 2, 3, 4, 5, 6};
    for (int i : array) {
        System.out.println(i);
    }
}

运行结果:

1
2
3
4
5
6
=================
Sherlock
Jone
Jack

迭代器

即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,再继续判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代
/*
    迭代器
    有两个常用方法:
        boolean hasNext()如果有元素可以迭代,返回true
            判断集合有没有下一个元素,有就返回true,没有就返回false
        next()返回迭代的下一个元素
              取出集合中的下一个元素
    Iterator迭代器,是一个接口,我们无法直接使用,需要使用Iterator接口的实现类对象,获取实现类的方式比较特殊
    Collection接口中有一个方法,叫iterator(),这个方法返回的就是迭代器的实现类对象

    迭代器使用步骤:
        1.使用集合方法iterator()获取迭代器实现类对象,使用Iterator接口接收(多态)
        2.使用Iterator接口中的方法hasNext判断还有没有下一个元素
        3.使用Iterator接口中的方法next取出集合中的下一个元素
 */
public class Demo01Iterator {
    public static void main(String[] args) {
        //创建集合
        Collection<String> coll=new ArrayList<>();
        //往集合中添加元素
        coll.add("Sherlock");
        coll.add("Jone");
        coll.add("Jack");
        coll.add("Jenny");
        coll.add("Tom");
        /*
        使用集合方法iterator()获取迭代器实现类对象,使用Iterator接口接收(多态)
        注意:
            Iterator<E>接口也是有泛型的,迭代器的泛型跟着集合走,集合是什么泛型,迭代器就是什么泛型
         */
        //多态           接口   实现类对象
        Iterator<String> it=coll.iterator();
        //在while判断条件中使用Iterator接口中的方法hasNext判断还有没有下一个元素
        //使用Iterator接口中的方法next取出集合中的下一个元素
        while (it.hasNext()){//判断还有没有下一个元素
            String str = it.next();
            System.out.println(str);
        }
    }
}

运行结果:

Sherlock
Jone
Jack
Jenny
Tom

泛型

泛型:可以在类或方法中预支地使用未知的类型

不使用泛型

/*
    创建集合对象,不使用泛型
    好处:
        集合不使用泛型,默认类型为Object.可以随意存储任意类型数据
    弊端:
        不安全,会引发异常
    */
private static void demo01() {
        ArrayList list = new ArrayList();
        list.add("a");
        list.add(1);
        //使用迭代器遍历list集合
        //获取迭代器
        Iterator iter = list.iterator();
        while (iter.hasNext()){
            Object i = iter.next();
            System.out.println(i);

            //这时想要使用String类特有的方法,Length获取字符串的长度,不可使用.因为这是多态Object obj="a"
            //需要向下转型
            //会抛出类型转换异常,不能把Integer类型转换为String类型
            String str=(String) i;
            System.out.println(str.length());
        }
        }

使用泛型

/*
        创建集合对象,使用泛型
        好处:
            1.避免类型转换的麻烦,存储的是什么类型,取出的就是什么类型
            2.把运行期异常(代码运行之后抛出的异常),提升到了编译器(写代码时报的错)
        弊端:
            泛型是什么类型,只能存储什么类型的数据
     */
private static void demo02() {
        ArrayList<String> list = new ArrayList<>();
        list.add("Sherlock");
        list.add("Jone");
        list.add("Jack");
        //list.add(1);编译异常
        //使用迭代器遍历集合
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            String str = iterator.next();
            System.out.println(str+":"+str.length());
        }
    }

定义含有泛型的类

定义含有泛型的类
泛型为未知数据类型,当我们不确定使用什么数据类型,可以使用泛型
泛型可以接收任意数据类型
在创建对象的时候确定泛型的数据类型

GenericClass类

public class GenericClass<E> {

    private E name;

    public E getName() {
        return name;
    }

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

main方法

//创建对象是什么类型,存入和输出的就是什么类型
    public static void main(String[] args) {
        //不写默认为Object类型
        GenericClass gen = new GenericClass();
        gen.setName("Sherlock");
        Object name = gen.getName();
        System.out.println(name);//Sherlock

        //创建GenericClass对象,泛型使用Integer类型
        GenericClass<Integer> gen2 = new GenericClass<>();
        gen2.setName(1);
        Integer name1 = gen2.getName();//返回值为Integer类型
        System.out.println(name1);//1


        //创建GenericClass对象,泛型使用Stringr类型
        GenericClass<String> gen3 = new GenericClass<>();
        gen3.setName("Sherlock");
        String name2 = gen3.getName();//返回值为Integer类型
        System.out.println(name2);//Sherlock
    }

定义含有泛型的方法

/*
    定义含有泛型的方法:泛型定义在方法的修饰符和返回值之间

   格式:
        修饰符  <E>  返回值类型  方法名(参数列表(E e)){
            方法体
        }
    含有泛型的方法,在调用方法的时候确定泛型的数据类型
    传递什么类型的参数,泛型就是什么类型
 */

GenericMethod方法

public class GenericMethod {
    public <E> void method01(E e){
        System.out.println(e);
    }

    //定义含有泛型的静态方法
    public static <E> void method02(E e){
        System.out.println(e);
    }
}

main方法

public static void main(String[] args) {
        GenericMethod gen = new GenericMethod();
        /*
            调用含有泛型的方法method01
            传递什么类型,泛型就是什么类型
         */
        gen.method01(1);
        gen.method01("a");
        gen.method01(6.6);
        gen.method01(true);
        System.out.println("===============");
        //通过类名.方法名调用静态方法,不建议
        GenericMethod.method02("B");
        GenericMethod.method02(2);
        GenericMethod.method02(6.9);
    }

运行结果:

1
a
6.6
true
===============
B
2
6.9

定义含有泛型的接口

//定义含有泛型的接口
public interface GenericInterface<E> {
    public abstract void method(E e);
}

使用泛型的接口有两种方式

定义实现类,实现接口,直接指定接口的泛型

//定义实现类,实现接口,                                  直接指定接口的泛型
public class GenericInterfaceImpl1 implements GenericInterface<String>{
    @Override
    public void method(String s) {
        System.out.println(s);
    }
}

main方法

public static void main(String[] args) {
        //第一种使用方法
        GenericInterfaceImpl1 gen = new GenericInterfaceImpl1();
        //由于在实现类中已经指定了为String类型,所以只能输入String类型的数据
        gen.method("sherlock");//SHERLOCK
    }

接口使用什么泛型,实现类就是用什么泛型,类跟着接口走
相当于定义了一个含有泛型的类,创建对象的时候确定泛型的类型

//接口使用什么泛型,实现类就是用什么泛型,类跟着接口走
public class GenericInterfaceImpl2<E> implements GenericInterface<E> {
    @Override
    public void method(E e) {
        System.out.println(e);
    }
}

main方法

public static void main(String[] args) {
       //第二种使用方法:在创建对象时再指定什么类型的泛型,此时为整数类型
        GenericInterfaceImpl2<Integer> gen2 = new GenericInterfaceImpl2<>();
        gen2.method(6);//6
    }

泛型通配符

    泛型的通配符:
        ?:代表任意数据类型
    使用方式:
        不能创建对象使用
        只能作为方法参数使用
public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        list1.add(1);
        list1.add(2);
        ArrayList<String> list2 = new ArrayList<>();
        list2.add("a");
        list2.add("b");

        printArray(list1);
        printArray(list2);
    }

    /*
        定义一个方法,能遍历所有ArrayList集合
        这时候不知道ArrayList使用的什么数据类型,这时可以使用泛型通配符?来接受数据类型
     */
    public static void printArray(ArrayList<?> list) {
        Iterator<?> it = list.iterator();
        while (it.hasNext()) {
            Object result = it.next();
            System.out.println(result);
        }
    }

泛型的上下限定

泛型的上限限定:? extends E 代表使用的泛型只能是E类型的子类/本身
泛型的下线限定:? super   E 代表使用的泛型只能是E类型的父类/本身

List

ArrayList

List接口特点:
1. 它是一个元素存取有序的集合
2. 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
3. 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。
List接口中带索引的方法:
public void add(int index, E element) : 将指定的元素,添加到该集合中的指定位置上
public E get(int index) :返回集合中指定位置的元素
public E remove(int index) : 移除列表中指定位置的元素, 返回的是被移除的元素
public E set(int index, E element) :用指定元素替换集合中指定位置的元素,返回更新前的元素

注意:
	操作索引的时候,一定要注意索引越界异常
public static void main(String[] args) {
        //创建一个List集合对象,多态
        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("a");
        System.out.println(list);//[a, b, c, d, a]  重写了toString方法

        //public void add(int index, E element):将指定的元素,添加到该集合中的指定位置上
        list.add(3, "Sherlock");
        System.out.println(list);

        //public E remove(int index) : 移除列表中指定位置的元素, 返回的是被移除的元素
        String remove = list.remove(2);
        System.out.println("被移除的元素:" + remove);
        System.out.println(list);

        //public E set(int index, E element) :用指定元素替换集合中指定位置的元素,返回更新前的元素
        String a = list.set(4, "A");
        System.out.println("被替换的元素:"+a);//a
        System.out.println(list);//[a, b, Sherlock, d, A]

        //public E get(int index) :返回集合中指定位置的元素
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

LinkedList

特点:
	1.底层是一个链表结构:查询慢,增删快
	2.里面包含了大量操作首位元素的方法
注意:
	使用linkedList集合特有的方法,不能使用多态
LinkedList

public void addFirst(E e) :将指定元素插入此列表的开头
public void addLast(E e) :将指定元素添加到此列表的结尾   等效于add()
public void push(E e) :将元素推入此列表所表示的堆栈      等效于addFirst()

public E getFirst() :返回此列表的第一个元素
public E getLast() :返回此列表的最后一个元素

public E removeFirst() :移除并返回此列表的第一个元素
public E removeLast() :移除并返回此列表的最后一个元素
public E pop() :从此列表所表示的堆栈处弹出一个元素        等效于removeFirst

public boolean isEmpty() :如果列表不包含元素,则返回true
/*
        public E removeFirst() :移除并返回此列表的第一个元素
        public E removeLast() :移除并返回此列表的最后一个元素
        public E pop() :从此列表所表示的堆栈处弹出一个元素   等效于removeFirst
     */
    private static void show03() {
        //创建LinkedList集合对象
        LinkedList<String> list = new LinkedList<>();
        //使用add方法向集合中添加元素
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        list.add("f");

        String s = list.removeFirst();
        System.out.println("移除的第一个元素:"+s);
        String s1 = list.removeLast();
        System.out.println("移除的最后一个元素:"+s1);
        System.out.println(list);
    }

    /*
        public E getFirst() :返回此列表的第一个元素
        public E getLast() :返回此列表的最后一个元素
     */
    private static void show02() {
        //创建LinkedList集合对象
        LinkedList<String> list = new LinkedList<>();
        //使用add方法向集合中添加元素
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        list.add("f");

        //public E getFirst() :返回此列表的第一个元素
        String first = list.getFirst();
        System.out.println(first);

        //public E getLast() :返回此列表的最后一个元素
        String last = list.getLast();
        System.out.println(last);
    }

    /*
        public void addFirst(E e) :将指定元素插入此列表的开头
        public void addLast(E e) :将指定元素添加到此列表的结尾   等效于add()
        public void push(E e) :将元素推入此列表所表示的堆栈      等效于addFirst()
     */
    private static void show01() {
        //创建LinkedList集合对象
        LinkedList<String> list = new LinkedList<>();
        //使用add方法向集合中添加元素
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        list.add("f");
        System.out.println(list);//[a, b, c, d, e, f]

        //public void addFirst(E e) :将指定元素插入此列表的开头
        list.addFirst("Sherlock");
        System.out.println(list);//[Sherlock, a, b, c, d, e, f]

        //public void addLast(E e) :将指定元素添加到此列表的结尾

        list.addLast("Jone");
        System.out.println(list);//[Sherlock, a, b, c, d, e, f, Jone]
    }

Set接口

java.util.Set 接口和 java.util.List 接口一样,同样继承自 Collection 接口,它与 Collection 接口中的方法基本一致,并没有对 Collection 接口进行功能上的扩充,只是比 Collection 接口更加严格了。

与 List 接口不同的是,Set 接口中元素无序,并且都会以某种规则保证存入的元素不出现重复

Set 集合有多个子类,这里我们介绍其中的 java.util.HashSet 、 java.util.LinkedHashSet 这两个集合

tips:Set集合取出元素的方式可以采用:迭代器、增强for

HashSet

Set接口的特点:
  1.不允许存储重复元素
  2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历(使用迭代器或者增强for遍历)

HashSet集合 implements Set接口
特点:
  1.不允许存储重复元素
  2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历
  3.是一个无序集合,存储元素和取出元素有可能不一致
  4.底层是一个哈希表结构,查询速度非常快
public static void main(String[] args) {
        HashSet<Integer> set = new HashSet<>();
        set.add(1);
        set.add(3);
        set.add(2);
        //使用迭代器遍历
        Iterator<Integer> iterator = set.iterator();
        while (iterator.hasNext()){
            Integer i = iterator.next();
            System.out.println(i);
        }
        //使用增强for遍历
        for(Integer i:set){
            System.out.println(i);
        }
    }

HashCode

哈希值:是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来得到的地址,不是数据实际存储的物理地址)Object类有一个方法,可以获取对象的哈希值
    int hasCode() 返回对象的哈希值                                    

Person类

public class Person {
    //可以重写hasCode方法,可以任意返回哈希值
//    @Override
//    public int hashCode() {
//        return 1;
//    }
}

main方法

public static void main(String[] args) {
        //Person类继承了Object类,所以可以使用Object类的hasCode方法
        Person p1 = new Person();
        int i = p1.hashCode();
        System.out.println(i);//356573597

        Person p2 = new Person();
        int i1 = p2.hashCode();
        System.out.println(i1);//1735600054

        //toString方法的源码
        System.out.println(p1);//darkhouse.demo09.Person@677327b6  677327b6为356573597的十六进制形式
        System.out.println(p2);//darkhouse.demo09.Person@1540e19d  1540e19d为1735600054的十六进制形式

        /*
            String类的哈希值
                String类重写了Object类的hasCode方法
         */
        String s1=new String("abc");
        String s2=new String("abc");
        System.out.println(s1.hashCode());//96354
        System.out.println(s2.hashCode());//96354
    }

HashSet集合不允许存放重复元素原理

public static void main(String[] args) {
        //创建一个HashSet集合对象
        HashSet<String> set = new HashSet<>();
        String s1=new String("abc");
        String s2=new String("abc");
        set.add(s1);
        set.add(s2);
        set.add("重地");
        set.add("通话");
        set.add("abc");
        System.out.println(set);
    }

在这里插入图片描述

HashSet存储自定义类型元素

HashSet存储自定义类型元素
用HashSet存储自定义元素,必须在类中重写hashCode方法和equals方法,才能保证存储的元素不会重复
要求:
   同名和同年龄的人只能存储一次

Person类

public class Person {
    private String name;
    private int age;

    public Person() {
    }

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

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

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

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

    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;
    }
}

main方法

public static void main(String[] args) {
        //创建HashSet集合存储Person
        HashSet<Person> set = new HashSet<>();
        Person p1 = new Person("Sherlock",18);
        Person p2 = new Person("Sherlock",18);
        Person p3 = new Person("Sherlock",19);
        set.add(p1);
        set.add(p2);
        set.add(p3);
        System.out.println(set);//[Person{name='Sherlock', age=19}, Person{name='Sherlock', age=18}]
    }

LinkedHashSet集合

LinkedHashSet集合 extend HashSet集合
LinkedHashSet集合特点:
   底层是一个哈希表(数组+链表/红黑树)+链表:多了一条链表(记录元素的存储顺序),保证元素有序
public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        set.add("www");
        set.add("abc");
        set.add("abc");
        set.add("itcast");
        System.out.println(set);//[abc, www, itcast]  无序,不允许重复

        LinkedHashSet<String> linked = new LinkedHashSet<>();
        linked.add("WWW");
        linked.add("abc");
        linked.add("abc");
        linked.add("itcast");
        System.out.println(linked);//  [WWW, abc, itcast]  有序的 不允许重复
    }

可变参数

可变参数:
   使用前提:
       当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数
   使用格式:定义方法时使用
       修饰符 返回值类型  方法名(数据类型...b变量名)
   可变参数的原理:
       可变参数的底层是一个数组,根据传递参数的个数不同,会创建不同长度的数组.才存储这些参数
       传递参数的个数,可以是0(不传递),1,2...多个

   注意事项:
       1.一个方法的参数列表,只能有一个可变参数
       2.如果方法的参数有多个,可变参数写在参数列表的末尾
public static void main(String[] args) {
        int i=add(10,20,30);
        System.out.println(i);//60

    }
    /*
        定义计算(0-n)整数和的方法
        已知:计算整数的和,数据类型已经确定int
        但是参数的个数不确定,不知道要计算几个整数的和,就可以使用可变参数
        add():创建一个长度为0的数组,new int[0]
        add(10):创建一个长度为1的数组,存储传递过来的参数,new int[]{10}
        add(10,20):创建一个长度为2的数组,存储传递过来的参数,new int[]{10,20}
        ...
     */
    public static int add(int...arr){
        //定义一个初始化变量,记录累加求和
        int sum=0;
        //遍历数组,获取数组每个元素
        for (int i : arr) {
            //累加求和
            sum+=i;
        }
        //把求和结果返回
        return sum;
    }

Map

Collection中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储
Map中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值
Collection中的集合称为单列集合,Map中的集合称为双列集合
需要注意的是,Map中的集合不能包含重复的键,值可以重复;每个键只能对应一个值
Map<k,v>集合
    特点:
        1.Map集合是一个双列元素,一个元素包含两个值(一个key,一个value)
        2.Map集合中的元素,key和value的数据类型可以相同,也可以不同
        3.Map集合中的元素,key不允许重复,value允许重复
        4.Map集合中的元素,key和value一 一对应

Map的常用子类

① HashMap集合
②LinkedHashMap集合(后半段介绍)

HashMap<k,v>集合 implements Map<k,v>接口
    HasMap集合特点:
        1.HashMap底层是哈希表,查询速度快
        2.HashMap集合是一个无序集合,存储元素和取出元素顺序有可能不一致

Map接口中的常用方法

HashMap的方法:
    public V put(K key, V value) : 把指定的键与指定的值添加到Map集合中
    public V remove(Object key) : 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值
    public V get(Object key) 根据指定的键,在Map集合中获取对应的值
    boolean containsKey(object key):判断集合中是否包含指定的键
/*
    public V put(K key, V value) : 把指定的键与指定的值添加到Map集合中
        返回值:v
            存储键值对的时候,若key不重复,返回值为null
                            若key重复,会使用新的value替换map中重复的value,返回被替换的value值
     */
    private static void show01() {
        //创建Map集合
        Map<String, String> map = new HashMap<>();
        String v1 = map.put("A", "1");
        System.out.println("v1:"+v1);//v1:null
        String v2 = map.put("A", "11");
        System.out.println("v2:"+v2);//v2:1

        map.put("B","2");
        map.put("C","3");
        map.put("D","4");
        System.out.println(map);//{A=11, B=2, C=3, D=4}

    }

    /*
    public V remove(Object key) : 把指定的键所对应的键值对元素 在Map集合中删除,返回被删除元素的值
        返回值:v
            key存在,返回被删除的值
            key不存在,返回null
     */
    private static void show02(){
        Map<String, Integer> map = new HashMap<>();
        map.put("W",180);
        map.put("L",178);
        map.put("Y",185);
        System.out.println(map);//{W=180, Y=185, L=178}
        //删除key存在的
        Integer v1 = map.remove("Y");
        System.out.println(v1);//185
        System.out.println(map);//{W=180, L=178}
        //删除key不存在的
        Integer v2 = map.remove("Y");
        System.out.println(v2);//null
    }

    /*
    public V get(Object key) 根据指定的键,在Map集合中获取对应的值
    返回值:value
        key存在:返回对应值
        key不存在,返回null
     */
    private static void show03(){
        Map<String, Integer> map = new HashMap<>();
        map.put("W",180);
        map.put("L",178);
        map.put("Y",185);
        //key存在
        Integer v1 = map.get("W");
        System.out.println(v1);//180
        //key不存在
        Integer v2 = map.get("S");
        System.out.println(v2);//null
    }

    /*
    boolean containsKey(object key):判断集合中是否包含指定的键
    返回值:
        包含返回true
        不包含返回false
     */
    private static void show04(){
        Map<String, Integer> map = new HashMap<>();
        map.put("W",180);
        map.put("L",178);
        map.put("Y",185);
        boolean result1 = map.containsKey("W");
        System.out.println(result1);//true
        boolean result2 = map.containsKey("S");
        System.out.println(result2);//false
    }

Map集合的遍历方式

Map集合的第一种遍历方式:通过键找值的方式
    public Set<K> keySet() : 获取Map集合中所有的键,存储到Set集合中
    实现步骤:
        1.使用Map集合中的方法keySet(),Map集合所有的key取出来,存储到一个set集合中
        2.遍历set集合,获取Map集合中的每一个键
        3.利用get(key)方法找到键对应的值
public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("W",180);
        map.put("L",178);
        map.put("Y",185);

        // 1.使用Map集合中的方法keySet(),把Map集合所有的key取出来,存储到一个set集合中
        Set<String> set = map.keySet();
        //2.遍历set集合,获取Map集合中的每一个键
        for(String str:set){
            //3.利用get(key)方法找到键对应的值
            System.out.println(str+":"+map.get(str));
            /*
            W:180
            Y:185
            L:178
             */
        }
    }

Map集合遍历的第二种方式:使用Entry对象遍历

    Map集合中的方法:
        public Set<Map.Entry<K,V>> entrySet() : 获取到Map集合中所有的键值对对象的集合(Set集合)
    实现步骤:
        1.使用Map集合中的方法entrySet(),Map集合中多个Entry对象取出来,存储到一个Set集合中
        2.遍历Set集合,获取每一个Entry对象
        3.使用Entry对象中的方法getKey()和getValue获取键与值
public static void main(String[] args) {
        //1.使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中
        Map<String, Integer> map = new HashMap<>();
        map.put("W",180);
        map.put("L",178);
        map.put("Y",185);
        Set<Map.Entry<String, Integer>> set = map.entrySet();
        //2.遍历Set集合,获取每一个Entry对象
        //使用迭代器
        Iterator<Map.Entry<String, Integer>> iterator = set.iterator();
        while (iterator.hasNext()){
            Map.Entry<String, Integer> entry = iterator.next();
            System.out.println(entry.getKey()+":"+entry.getValue());
        }
        //使用增强for
        for (Map.Entry<String, Integer> entry : set) {
            System.out.println(entry.getKey()+":"+entry.getValue());
    }

HashMap存储自定义类型键值

HashMap存储自定义类型键值
    Map集合保证key是唯一的:
        作为key的元素,必须重写hasCode方法和equals方法,以保证key唯一

Person类

public class Person {
    private String name;
    private int age;

    public Person() {
    }

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

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

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

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

    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;
    }
}

main方法

	/*
    HashMap存储自定义类型键值
    key:String类型
        String类重写hasCode方法和equals方法,以保证key唯一
    value:Person类型(整个Person相当于值)
     */
    private static void show01() {
        //创建HashMap集合
        HashMap<String, Person> map = new HashMap<>();
        //往集合中添加元素
        map.put("北京",new Person("张三",18));
        map.put("上海",new Person("李四",19));
        map.put("广州",new Person("王五",18));
        map.put("厦门",new Person("宋六",20));
        map.put("深圳",new Person("杨七",21));

        //遍历map集合
        Set<String> set = map.keySet();
        for(String key:set){
            System.out.println(key+"--->"+map.get(key));
        }
    }

    /*
     HashMap存储自定义类型键值
     key:Person类型
        Person类必须重写hashCode方法和equals方法,以保证key唯一
    value:String类型
        可以重复
     */
    private static void show02() {
        HashMap<Person, String> map = new HashMap<>();
        map.put(new Person("Sherlock",18),"英国");
        map.put(new Person("Jone",19),"英国");
        map.put(new Person("Jack",20),"加拿大");
        map.put(new Person("Jenny",21),"加拿大");
        //遍历
        Set<Map.Entry<Person, String>> set = map.entrySet();
        for (Map.Entry<Person, String> entry : set) {
            System.out.println(entry.getKey()+"--->"+entry.getValue());
        }
    }

LinkedHashMap

LinkedHashMap<k,v>集合 extend HashMap<k,v>集合
    LinkedHashMap特点:
        1.LinkedHashMap集合底层是哈希表+链表(保证迭代顺序)
        2.LinkedHashMap集合是一个有序集合,存储元素和取出元素顺序一致
public static void main(String[] args) {
        //这里说的有序和无序指的是存入数据顺序的有序和无序,并不是按照字母顺序的有序和无序
        HashMap<String, String> map = new HashMap<>();
        map.put("a","a");
        map.put("c","c");
        map.put("b","b");
        map.put("d","d");
        map.put("e","e");
        System.out.println(map);//{a=a, b=b, c=c, d=d, e=e}   key不允许重复,无序

        LinkedHashMap<String, String> linked = new LinkedHashMap<>();
        linked.put("a","a");
        linked.put("c","c");
        linked.put("b","b");
        linked.put("d","d");
        linked.put("e","e");
        System.out.println(linked);//{a=a, c=c, b=b, d=d, e=e}  key不允许重复,有序

Hashtable

Hashtable<k,v>集合 implements Map<k,v>接口

    特点:
        底层是一个哈希表,是一个线程安全的集合,单线程集合,速度慢
        HashMap:底层是一个哈希表,是一个线程不安全的集合,速度快

        HashMap集合(还有之前学的所有集合),可以存储null,nullHashtable集合,不可以存储null,null
 public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        map.put(null,"a");
        map.put("b",null);
        map.put(nul l,null);
        System.out.println(map);

        Hashtable<String, String> table = new Hashtable<>();
        //table.put(null,"a");空指针异常
        //table.put("b",null);空指针异常
        //table.put(null,null);空指针异常
        System.out.println(table);
    }

练习

/*
    需求:
        计算一个字符串中每个字符出现次数。
    分析:
        1. 获取一个字符串对象
        2. 创建一个Map集合,键代表字符,值代表次数。
        3. 遍历字符串得到每个字符。
        4. 判断Map中是否有该键。
        5. 如果没有,第一次出现,存储次数为1;如果有,则说明已经出现过,获取到对应的值进行++,再次存储。
        6. 打印最终结果
 */
public class Demo07Practise {
    public static void main(String[] args) {
        char[] array = strMessage();
        calculate(array);
    }
    //输入字符串,并存放在数组中
    public static char[] strMessage(){
        System.out.println("请输入一串字符串:");
        Scanner scanner = new Scanner(System.in);
        String str = scanner.next();
        char[] chars = str.toCharArray();
        return chars;
    }
    //统计字符串中每个字符的个数
    public static void calculate(char[] array){
        //创建HashMap集合
        HashMap<Character, Integer> map = new HashMap<>();
        for (int i = 0; i < array.length; i++) {
            if(map.containsKey(array[i])){
                Integer value = map.get(array[i]); 
                value++;
                //将更新过的值添加到map集合中
                map.put(array[i],value);
            }else {
                map.put(array[i],1);
            }
        }
        //遍历集合,输出统计数据
        Set<Character> set = map.keySet();
        for (Character character : set) {
            System.out.println(character+":"+map.get(character));
        }
    }
}

线程简介

线程与进程

在这里插入图片描述

主线程

主线程:执行(main)方法的线程
    单线程程序:Java程序中只有一个线程
    执行从main方法开始,从上到下依次执行

Person类

public class Person {
    private String name;

    public void run(){
        //定义循环执行5次
        for (int i = 0; i < 5; i++) {
            System.out.println(name+i);
        }
    }

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

main方法

public static void main(String[] args) {
        Person p1 = new Person("小强");
        p1.run();
        Person p2 = new Person("旺财");
        p2.run();
    }

运行结果:

小强0
小强1
小强2
小强3
小强4
旺财0
旺财1
旺财2
旺财3
旺财4

在这里插入图片描述

创建线程类

 创建多线程的第一种方式:(第二种方式见Java--线程)
 java.Lang.Thread:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类

实现步骤:
    1.创建Thread类的子类
    2.Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
    3.创建Thread类的子类对象
    4.调用Thread类中的方法-->start方法,开启新的线程,执行run方法
        void start():使该线程开始执行,Java虚拟机调用该线程的run方法
        结果使两个线程并发地运行,当前线程(main线程)和另一个线程(创建的新线程,执行其run方法)
        多次启动一个线程是非法的,特别是当前线程结束执行后,不能再重新启动
Java程序属于抢占式调度,哪个线程的优先级高,哪个线程优先执行,同一个优先级,随机选择一个执行

Thread类的子类

//1.创建Thread类的子类
public class MyThread extends Thread {
    //2.在Thread类的子类中重写Thread类中的方法,设置线程任务(开启线程要做什么?)
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("run:"+i);
        }
    }
}

main方法

public static void main(String[] args) {
     //3.创建Thread类的子类对象
     MyThread mt = new MyThread();
     //4.调用Thread类中的方法-->start方法,开启新的线程,执行run方法
     mt.start();
     for (int i = 0; i < 5; i++) {
         System.out.println("main:"+i);
    }
}

运行结果:

main:0
main:1
main:2
run:0
run:1
run:2
run:3
run:4
main:3
main:4
每次运行的结果也会不同,Java程序属于抢占式调度,哪个线程的优先级高,哪个线程优先执行,同一个优先级,随机选择一个执行

原理分析:
在这里插入图片描述
内存图分析:
在这里插入图片描述

线程(???)

Thread类

获取当前线程名称

//获取当前线程名称
public String getName()

方法①

 获取线程的名称
   1.使用Thread类中的方法getName()
        String getName() 返回该线程的名称
   2.可以先获取当前正在执行的线程,使用线程中的方法getName()获取线程的名称
        static Thread currentThread() 返回对当前正在执行的线程对象的引用

Thread类的子类

public class MyThread extends Thread{
    //重写Thread类中的run方法
    @Override
    public void run() {
        //获取线程名称
        String name = getName();
        System.out.println(name);
    }
}

main方法

public class Demo04GetThreadName {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        //调用start方法,开启新线程,执行run方法
        myThread.start();

        new MyThread().start();
        new MyThread().start();
    }
}

运行结果:

Thread-0
Thread-1
Thread-2

分析:

主线程:main
线程名称:Thread-0,Thread-1,Thread-2

方法②

Thread类的子类

//定义一个Thread类的子类
public class MyThread extends Thread{
    //重写Thread类中的run方法
    @Override
    public void run() {
        //获取线程名称
        Thread thread = Thread.currentThread();

        String name = thread.getName();
        System.out.println(name);

        //链式编程
        //System.out.println(Thread.currentThread().getName());此代码和上述代码产生的效果相同
    }
}

main方法

public static void main(String[] args) {
        MyThread myThread = new MyThread();
        //调用start方法,开启新线程,执行run方法
        myThread.start();

        new MyThread().start();
        new MyThread().start();

        //获取主线程名称
        System.out.println(Thread.currentThread().getName());//main
    }

设置线程的名称

设置线程的名称(了解)
  1.使用Thread类中的方法setName(名字)(在main方法中操作)
       void setName(String name)改变线程名称,使之与参数name相同
  2.创建一个带参数的构造方法,参数传递线程的名称,调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
       Thread(String name)分配新的Thread对象

SetMyThread类

public class SetMyThread extends Thread{

	public SetMyThread(){

    }

    public SetMyThread(String name){
        super(name);//把线程名称传递给父类,让父类(Thread)给子线程起一个名字
    }
    
    @Override
    public void run() {
        //获取线程的名称
        Thread thread = Thread.currentThread();
        String name = thread.getName();
        System.out.println(name);
    }
}

main方法

public static void main(String[] args) {
        //开启多线程
        MyThread myThread = new MyThread();
        myThread.setName("Sherlock");
        myThread.start();//Sherlock
			
		 //开启多线程
        new SetMyThread("Jone").start();//Jone
    }

暂时停止执行

 使当前正在执行的线程以指定的毫秒数暂停
public static void sleep(Long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止运行)
    毫秒数结束后,线程继续执行
使用方法:
        Thread.sleep(***);然后解决sleep的异常
public static void main(String[] args) {
        //模拟秒表
        for (int i = 1; i <= 60; i++) {
            System.out.println(i);

            //使用Thread类的sleep方法让程序睡眠一秒
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

创建线程的第二种方式

创建多线程程序的第二种方式:实现Runnable接口
java.lang. Runnable
        Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run的无参数方法
java.Lang.Thread类的构造方法
        Thread ( Runnable target)分配新的 Thread 对象
        Thread ( Runnable target, string name)分配新的 Thread 对象
    实现步骤:
        1.创建一个Runnable接口的实现类
        2.在实现类中重写Runnable接口的run方法,设置线程任务
        3.创建一个Runnable接口的实现类对象
        4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        5.调用Thread类中的start方法,开启新的线程执行run方法

Runnable接口的实现类

// 1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable{
    // 2.在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

main方法

public static void main(String[] args) {
        //3.创建一个Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        // 4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread thread = new Thread(run);
        thread.start();

        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }

运行结果:(每次运行的结果都会不同)

main-->0
main-->1
main-->2
main-->3
Thread-0-->0
Thread-0-->1
Thread-0-->2
Thread-0-->3
Thread-0-->4
main-->4

实现Runnabte接口创建多线程程序的好处

实现Runnabte接口创建多线程程序的好处:
1.避免了单继承的局限性
	一个类只能继承一个类一个人只能有一个亲参),类继承了Thread类就不能继承其他的类
	实现了Runnable接口,还可以继承其他的类,实现其他的接口
2.增强了程序的扩展性,降低了程序的耦合性(解耦)
	实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
	实现类中,重写了run方法:用来设置线程任务
	创建Thread类对象,调用start方法:用来开启新线程

匿名内部类方式实现线程的创建

匿名内部类方式实现线程的创建
    匿名:没有名字
    内部类:写在其他类内部的类
    匿名内部类作用:简化代码
        把子类继承父类,重写父类的方法,创建子类对象合一步完成
        把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
    匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

    格式:
        new 父类/接口(){
            重写父类或者接口种的方法
        };
public static void main(String[] args) {
        //一.线程的父类Thread
        //new MyThread().start();
        new Thread() {
            //重写run方法,设置线程任务
            @Override
            public void run() {
                for (int i = 1; i <= 5; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + "Sherlock");
                }
            }
        }.start();
        
        //二.线程的接口Runnable
        Runnable r = new Runnable() {
            //重写run方法,设置线程任务
            @Override
            public void run() {
                for (int i = 1; i <= 5; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + "Jone");
                }
            }
        };
        Thread thread = new Thread(r);
        thread.start();
		
		//三.线程的接口Runnable(简化形式)
        new Thread(new Runnable() {
            //重写run方法,设置线程任务
            @Override
            public void run() {
                for (int i = 1; i <= 5; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + "Jack");
                }
            }
        }).start();
    }

运行结果:

Thread-0-->Sherlock
Thread-0-->Sherlock
Thread-0-->Sherlock
Thread-0-->Sherlock
Thread-0-->Sherlock
Thread-1-->Jone
Thread-1-->Jone
Thread-1-->Jone
Thread-1-->Jone
Thread-1-->Jone
Thread-2-->Jack
Thread-2-->Jack
Thread-2-->Jack
Thread-2-->Jack

线程安全

通过模拟电影院的卖票过程来介绍线程安全问题

RunnableImpl实现类

public class RunnableImpl implements Runnable{
    //定义一个多个线程共享的票源
    private int ticket=100;

    //设置线程任务  买票
    @Override
    public void run() {
        //使用死循环  让买票操作重复执行
        while (true){
            //先判断票是否存在
            if (ticket>0){
                //提高安全问题出现概率 让程序睡眠一下
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
        }
    }
}

main方法

public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
Thread-2-->正在卖第5张票
Thread-0-->正在卖第5张票
Thread-1-->正在卖第5张票
Thread-2-->正在卖第2张票
Thread-0-->正在卖第2张票
Thread-1-->正在卖第2张票
Thread-2-->正在卖第-1张票

发现程序出现了两个问题:
1.相同的票数,比如5这张票被卖了两回
2.不存在的票,比如0票与-1票,是不存在的
这种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全

线程同步(解决线程安全问题)

方法一

卖票案例出现了线程安全问题,卖出了不存在的票和重复的票

解决线程安全问题(第一种方法)(使用同步代码块):
同步机制synchronized
格式:                                               
    synchronized(锁对象){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }
注意:
    1.通过代码块中的锁对象,可以使用任意的对象
    2.但是必须保证多个线程使用的锁对象是同一个
    3.锁对象作用:
        把同步代码块锁住,只让一个线程在同步代码块中执行

RunnableImpl实现类

public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private int ticket = 10;

    //创建一个锁对象(必须创建在run方法的外边)
    Object obj = new Object();

    //设置线程任务  买票
    @Override
    public void run() {
        //使用死循环  让买票操作重复执行
        while (true) {
            //同步代码块
            synchronized (obj){
                //先判断票是否存在
                if (ticket > 0) {
                    //提高安全问题出现概率 让程序睡眠一下
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                    ticket--;
                }
            }

        }
    }
}

main方法

public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //开启多线程
        t0.start();
        t1.start();
        t2.start();
    }

运行结果:

Thread-1-->正在卖第10张票
Thread-1-->正在卖第9张票
Thread-2-->正在卖第8张票
Thread-2-->正在卖第7张票
Thread-2-->正在卖第6张票
Thread-2-->正在卖第5张票
Thread-2-->正在卖第4张票
Thread-2-->正在卖第3张票
Thread-2-->正在卖第2张票
Thread-2-->正在卖第1张票

同步原理:
在这里插入图片描述

方法二

卖票案例出现了线程安全问题,卖出了不存在的票和重复的票

    解决线程安全问题(第二种方法):使用同步方法
    使用步骤:
        1.把访问了共享数据的代码抽取出来,放到了一个方法中
        2.在方法上添加Synchronized的修饰符
    格式:
    修饰符  synchronized  返回值类型  方法名(参数列表){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }

RunnableImpl实现类

public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private int ticket = 100;

    //创建一个锁对象(必须创建在run方法的外边)
    Object obj = new Object();

    //设置线程任务  买票
    @Override
    public void run() {
        //使用死循环  让买票操作重复执行
        while (true) {
            payTicket();
        }
    }
    /*
    定义一个同步方法
    同步方法也会把方法内的代码锁住
    只让一个线程执行
    同步方法的锁对象是谁?
    就是实现类对象 new RunnableImpl()
    也就是this
     */
    public synchronized void payTicket(){
        if (ticket > 0) {
            //提高安全问题出现概率 让程序睡眠一下
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
            ticket--;
        }
    }
}

main方法

public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //开启多线程
        t0.start();
        t1.start();
        t2.start();
    }

运行结果:

Thread-0-->正在卖第10张票
Thread-0-->正在卖第9张票
Thread-0-->正在卖第8张票
Thread-2-->正在卖第7张票
Thread-2-->正在卖第6张票
Thread-2-->正在卖第5张票
Thread-2-->正在卖第4张票
Thread-2-->正在卖第3张票
Thread-2-->正在卖第2张票
Thread-2-->正在卖第1张票

Lock锁

卖票案例出现了线程安全问题,卖出了不存在的票和重复的票

解决线程安全问题(第三种方法):
Lockjava.util.concurrent.Locks.Lock接口
Lock实现提供了比使用synchronized方法和语句可获得更广泛的锁定操作
Lock接口中的方法:
    void Lock():获取锁
    void unLock():释放锁
ReentrantLock implements Lock接口
使用步骤:
    1.在成员位置创建一个ReentrantLock对象
    2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
    3.在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁

RunnableImpl实现类

public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private int ticket = 10;

    //在成员位置创建一个ReentrantLock对象
    Lock l = new ReentrantLock();

    //设置线程任务  买票
    @Override
    public void run() {
        //使用死循环  让买票操作重复执行
        while (true) {
            //2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
            l.lock();
                //先判断票是否存在
                if (ticket > 0) {
                    //提高安全问题出现概率 让程序睡眠一下
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                    ticket--;
                }
            //3.在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
            l.unlock();
            }

    }
}

也可以这样写(无论程序是否出现异常,都会把锁释放,可提高效率)

public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private int ticket = 10;

    //在成员位置创建一个ReentrantLock对象
    Lock l = new ReentrantLock();

    //设置线程任务  买票
    @Override
    public void run() {
        //使用死循环  让买票操作重复执行
        while (true) {
            //2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
            l.lock();
                //先判断票是否存在
                if (ticket > 0) {
                    //提高安全问题出现概率 让程序睡眠一下
                    try {
                        Thread.sleep(10);
                        System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                        ticket--;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        //3.在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
                        l.unlock();//无论程序是否出现异常,都会把锁释放
                    }
                }
            }
    }
}

main方法

public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //开启多线程
        t0.start();
        t1.start();
        t2.start();
    }

运行结果:

Thread-0-->正在卖第10张票
Thread-0-->正在卖第9张票
Thread-0-->正在卖第8张票
Thread-0-->正在卖第7张票
Thread-0-->正在卖第6张票
Thread-0-->正在卖第5张票
Thread-0-->正在卖第4张票
Thread-0-->正在卖第3张票
Thread-0-->正在卖第2张票
Thread-0-->正在卖第1张票

线程状态概述

在这里插入图片描述

等待唤醒案例分析

在这里插入图片描述

等待唤醒代码实现

等待唤醒案例:线程之间的通信
    创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu执行,进入到waiting状态(无线等待状态)
    创建一个老板线程(生产者):5秒做包子.调用notify方法,唤醒顾客吃包子

    注意事项:
        顾客和老板必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
        同步使用的锁对象必须保证唯一
        只有锁对象才能调用wait和notify方法
    Object类中的方法:
    void wait()
        在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待
    void notify()
        唤醒在此对象监视器上等待的单个线程
        会继续执行wait方法之后的代码

匿名内部类的方法:

//匿名内部类的方法
    public static void main(String[] args) {
        //创建锁对象,保证唯一
        Object obj=new Object();
        //创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
                //用死循环让买包子的过程持续
                while (true) {
                    //保证等待和唤醒只能有一个在执行,须使用同步机制
                    synchronized (obj) {
                        System.out.println("告知老板要的包子的种类和数量");
                        //调用wait方法,放弃cpu执行,进入到waiting状态(无线等待状态)
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒后执行的代码
                        System.out.println("包子已经做好了,开吃");
                        System.out.println("---------------------");
                    }
                }
            }
        }.start();

        //创建一个老板线程(生产者)
        new Thread(){
            @Override
            public void run() {
                while (true) {
                    //花5秒做包子
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj) {
                        System.out.println("老板五秒钟做好包子,告知顾客可以吃了");
                        //做好包子后,调用notify方法,唤醒顾客吃包子
                        obj.notify();

                    }
                }
            }
        }.start();
    }

利用线程的接口Runnable

 //利用线程的接口Runnable
    public static void main(String[] args) {
        Object obj=new Object();
        Runnable r=new Runnable() {
            @Override
            public void run() {
                //用死循环让买包子的过程持续
                while (true) {
                    //保证等待和唤醒只能有一个在执行,须使用同步机制
                    synchronized (obj) {
                        System.out.println("告知老板要的包子的种类和数量");
                        //调用wait方法,放弃cpu执行,进入到waiting状态(无线等待状态)
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒后执行的代码
                        System.out.println("包子已经做好了,开吃");
                        System.out.println("---------------------");
                    }
                }
            }
        };
        Thread thread = new Thread(r);
        thread.start();

        //创建一个老板线程(生产者)
        Runnable r1=new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj){
                        System.out.println("老板五秒钟做好包子,告知顾客可以吃了");
                        //做好包子后,调用notify方法,唤醒顾客吃包子
                        obj.notify();
                    }
                }
            }
        };
        Thread thread1 = new Thread(r1);
        thread1.start();
    }

运行结果:

告知老板要的包子的种类和数量
老板五秒钟做好包子,告知顾客可以吃了
包子已做好,开吃
-----------------
告知老板要的包子的种类和数量
老板五秒钟做好包子,告知顾客可以吃了
包子已做好,开吃
-----------------
....

wait和notify

进入到TimeWaiting(计时等待)有两种方式
     1.使用sleep(long m)方法,在毫秒值结束后,线程睡醒进入到Runnable/Blocked状态
     2.使用wait(long m)方法,wait方法如果在毫秒值结束之后还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态

 唤醒的方法:
     void notify():唤醒在此对象监视器上等待的单个线程
     void notifyAll():唤醒在此对象监视器上等待的所有线程
public static void main(String[] args) {
        //创建锁对象,保证唯一
        Object obj = new Object();
        
        //创建顾客1线程(消费者)
        new Thread() {
            @Override
            public void run() {
                //用死循环让买包子的过程持续
                while (true) {
                    //保证等待和唤醒只能有一个在执行,须使用同步机制
                    synchronized (obj) {
                        System.out.println("顾客1告知老板要的包子的种类和数量");
                        //调用wait方法,放弃cpu执行,进入到waiting状态(无线等待状态)
                        try {
                            obj.wait(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒后执行的代码
                        System.out.println("包子已经做好了,顾客1开吃");
                        System.out.println("---------------------");
                    }
                }
            }
        }.start();


        //创建顾客2线程(消费者)
        new Thread() {
            @Override
            public void run() {
                //用死循环让买包子的过程持续
                while (true) {
                    //保证等待和唤醒只能有一个在执行,须使用同步机制
                    synchronized (obj) {
                        System.out.println("顾客2告知老板要的包子的种类和数量");
                        //调用wait方法,放弃cpu执行,进入到waiting状态(无线等待状态)
                        try {
                            obj.wait(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒后执行的代码
                        System.out.println("包子已经做好了,顾客2开吃");
                        System.out.println("---------------------");
                    }
                }
            }
        }.start();


        //创建一个老板线程(生产者)
        new Thread(){
            @Override
            public void run() {
                while (true) {
                    //花5秒做包子
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj) {
                        System.out.println("老板五秒钟做好包子,告知顾客可以吃了");
                        //做好包子后,调用notify方法,唤醒顾客吃包子
                        obj.notifyAll();
                    }
                }
            }
        }.start();
    }

运行结果:

顾客2告知老板要的包子的种类和数量
包子已经做好了,顾客1开吃
---------------------
顾客1告知老板要的包子的种类和数量
老板五秒钟做好包子,告知顾客可以吃了
包子已经做好了,顾客2开吃
---------------------

线程池

简介

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,
无需反复创建线程而消耗过多资源

在这里插入图片描述

线程池的使用

线程池:
    Executors:线程池的工厂类,用来生成线程池
    Executors中的静态方法:
        static ExecutorService newFixedThreadPool(int nThreads)创建一个可重用固定线程数的线程池
        参数:
            int nThreads:创建线程池中包含的线程数量
        返回值:
            ExecutorService接口,返回的是ExecutorService接口的实现类对象,可使用ExecutorService接口接收(面向接口编程)
    ExecutorService接口
        用来从线程池中获取线程,调用start方法,执行线程任务
            submit(Runnable task)提交一个Runnable任务用于执行
        关闭/销毁线程池的方法
            void shutdown()


线程池的使用步骤:
    1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
    2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
    3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
    4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行)

RunnableImpl实现类

/*
    //2.创建-一个类,实现Runnable接口,重写run方法,设置线程任务
 */
public class RunnableImpl implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"创建了一个新的线程执行");
    }
}

main方法

public static void main(String[] args) {
        //1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产-一个指定线程数量的线程池
        ExecutorService es = Executors.newFixedThreadPool(2);
        // 3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());
        //线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
        es.submit(new RunnableImpl());
    }

运行结果:

pool-1-thread-1创建了一个新的线程执行
pool-1-thread-2创建了一个新的线程执行
pool-1-thread-1创建了一个新的线程执行

Lambda表达式

函数式编程思想概述

	在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做
面向对象的思想:
	做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.
函数式编程思想:
	只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程

冗余的Runnable代码

Runnable实现类

public class RunnableImpl implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"新线程创建");
    }
}

main方法

public static void main(String[] args) {
    //方式一
    RunnableImpl runnable = new RunnableImpl();
    Thread thread = new Thread(runnable);
    thread.start();

    //方式二
    Runnable runnable1= new Runnable(){
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"新线程创建");
        }
    };
    Thread thread1 = new Thread(runnable1);
    thread1.start();

    //方式三
    new Thread(new Runnable(){
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"新线程创建");
                }
            }
    ).start();
}

运行结果:

Thread-0新线程创建
Thread-1新线程创建
Thread-2新线程创建

Lambda标准格式

借助Java 8的全新语法,上述 Runnable 接口的匿名内部类写法可以通过更简单的Lambda表达式达到等效
Lambda表达式的标准格式:
    由三部分组成:
        a.一些参数
        b.一个箭头
        c.一段代码
    格式:
        (参数列表)->{一些重写方法的代码};
    解释说明格式:
        ():接口中抽象方法的参数列表,没有参数,就空着,有参数就写出参数,多个参数使用逗号分隔
        ->:传递的意思,把参数传递给方法体()
        {}:重写接口的抽象方法的方法体
public static void main(String[] args) {
    //使用匿名内部类
    new Thread(new Runnable(){
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"新线程创建");
        }
    }
    ).start();

    //使用Lambda表达式
    new Thread(() ->{
            System.out.println(Thread.currentThread().getName()+"新线程创建");
        }
    ).start();
}
这段代码和刚才的执行效果是完全一样的,可以在1.8或更高的编译级别下通过.从代码的语义中可以看出:线程任务的内容以一种更加简洁的形式被指定

练习①

Lambda表达式无参数无返回值

给定一个厨子Cook接口,内含唯一的抽象方法makeFood,且无参数,无返回值
使用Lambda的标准格式调用invokeCook方法,打印输出"吃饭啦!"字样

Cook接口

public interface Cook {
    public abstract void makeFood();
}

main方法

public static void main(String[] args) {
    //用Lambda表达式
    invokeCook(()->{
        System.out.println("吃饭啦!");
    });
}

//定义一个方法,参数传递Cook接口,方法内部调用Cook接口中的方法makeFood
public static void invokeCook(Cook cook){
    cook.makeFood();
}

运行结果:

吃饭啦!

练习②

Lambda表达式有参数有返回值

Lambda表达式有参数有返回值的练习
需求:
    使用数组存储多个Person对象
    对数组中的Person对象使用Arrays的sort方法通过年龄进行升序排序

Person类

public class Person {
    private String name;
    private int age;

    public Person() {
    }

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

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

    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;
    }
}

main方法

public static void main(String[] args) {
        Person[] array={
                new Person("Sherlock",20),
                new Person("Jone",19),
                new Person("Jack",21)
        };

        //使用Lambda表达式
        Arrays.sort(array,(Person o1, Person o2)->{
            //对年龄进行升序排序
            return o1.getAge()-o2.getAge();
        });

        //遍历数组
        for (Person person : array) {
            System.out.println(person.getName()+":"+person.getAge());
        }
    }

运行结果:

Jone:19
Sherlock:20
Jack:21

练习③

Lambda表达式有参数有返回值

给定一个计算器 Calculator 接口,内含抽象方法 calc 可以将两个int数字相加得到和值

Calculator接口

public interface Calculator {
    //定义一个计算两个int整数和方法并返回结果
    public abstract int calc(int a,int b);
}

main方法

public static void main(String[] args) {
    //调用invokeCalc方法
    invokeCalc(1, 2, new Calculator() {
        @Override
        public int calc(int a, int b) {
            return a+b;
        }
    });

    invokeCalc(11,12,(int a,int b)->{
        return a+b;
    });
}
/*
    定义一个方法
    参数传递两个int型整数,Calculate接口
    方法内部调用calc方法,计算两个整数和
 */
public static void invokeCalc(int a,int b,Calculator calculator){
    int sum=calculator.calc(a, b);
    System.out.println(sum);//3
}

Lambda省略格式

凡是根据上下文推导出来的内容,可以省略
可省略的内容:
	1.(参数列表):括号中的参数列表的数据类型,可以省略不写	
	2.括号中的参数如果只有一个,可以省略不写
    3.(一些代码):如果{}中的代码只有一行,无论是否有返回值,都可省略({},return,一行代码的那个分号)
        注意:如果要省略,{},return,一行代码的那个分号,这三样必须都省略

以下是几个例子:

//省略前
new Thread(()->{
    System.out.println(Thread.currentThread().getName()+"新线程创建");
}
).start();
//省略后
new Thread(()->System.out.println(Thread.currentThread().getName()+"新线程创建")).start();
//省略前
invokeCook(()->{
    System.out.println("吃饭啦!");
});
//省略后
invokeCook(()-> System.out.println("吃饭啦!"));
//省略前
Arrays.sort(array,(Person o1, Person o2)->{
    //对年龄进行升序排序
    return o1.getAge()-o2.getAge();
});
//省略后
Arrays.sort(array,( o1,  o2)-> o1.getAge()-o2.getAge());
invokeCalc(11,12,(int a,int b)->{
    return a+b;
});

invokeCalc(11,12,( a, b)->a+b);

Lambda的使用前提

1.使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。 无论是JDK内置的 RunnableComparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一 时,才可以使用Lambda
2.使用Lambda必须具有上下文推断。 也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例
    
   备注:有且仅有一个抽象方法的接口,称为“函数式接口”

File类

概述

java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作

java.io.FiLe类
文件和目录路径名的抽象表示形式。
java把电脑中的文件和文件夹(目录)封装为了一个FiLe类,我们可以使用FiLe类对文件和文件夹进行操作我们可以使用File类的方法
    创建一个文件/文件夹
    删除文件/文件夹
    获取文件/文件夹
    判断文件/文件夹是否存在
    对文件夹进行遍历
    获取文件的大小
File类是一个与系统无关的类,任何的操作系统都可以使用这个类中的方法
重点:记住这三个单词
    fiLe:文件
    directory:文件夹/目录
    path:路径
/*
        static String pathSeparator 与系统有关的路径分隔符,为了方便,它被表示一个字符串
        static char pathSeparatorChar 与系统有关的路径分隔符

        static String separator 与系统有关的默认名称分隔符,为了方便, 它被表示一个字符串
        static char separatorChar 与系统有关的路径分隔符

        所以操作路径不可以写死
        C:\develop\a\a.txt  windows
        C:/develop/a/a.txt  linux
        "C:"+File.separator+"develop"+File.separator+"a"+File.separator+"a.txt"
     */
    public static void main(String[] args) {
        String pathSeparator = File.pathSeparator;
        System.out.println(pathSeparator);//路径分隔符 windows:分号  linux:冒号

        String separator = File.separator;
        System.out.println(separator);//文件名称分隔符 windows:反斜杠\  linux:正斜杠/
    }
路径:
    绝对路径:一个完整路径
        以盘符(c:,D:)开始的路径
            c:\\a.txt
            c:\\Users\Sherlock\b.txt
    相对路径:一个简化的路径
        相对指的是相对于当前项目的根目录(c:\\Users\Sherlock\b.txt)
        如果使用当前项目的根目录,路径可简化书写:b.txt
    注意:
        路径不区分大小写
        路径中文件名称分隔符windows使用反斜杠,反斜杠是转义字符,两个反斜杠代表一个普通的反斜杠

构造方法

public File(String pathname) :通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例
public File(String parent, String child) :从父路径名字符串和子路径名字符串创建新的 File实例
public File(File parent, String child) :从父抽象路径名和子路径名字符串创建新的 File实例
/*
    File(String pathname)通过将给定路径名字符串转换为抽象路径来创建一个新File实例
    参数:
        String pathname:字符串的路径名称
        路径可以以文件结尾,也可以以文件夹结尾
        路径可以是相对路径,也可以是绝对路径
        路径可以是存在的,也可以是不存在的
        创建File对象只是把字符串路径封装为File对象,不考虑路径真假情况
 */
private static void show01() {
    File file = new File("E:\\Java\\src\\darkhouse\\demo15_File\\a.txt");
    System.out.println(file);//重写了toString方法  E:\Java\src\darkhouse\demo15_File\a.txt


    File file1 = new File("E:\\Java\\src\\darkhouse\\demo15_File");
    System.out.println(file1);

    File file2 = new File("b.txt");
    System.out.println(file2);//b.txt
}

/*
File(String parent,String child)根据parent路径名字串和child路径名字符串创建一个新File实例
参数:把路径分成了两部分
    String parent:父路径
    String child:子路径
好处:
    父路径和子路径可以单独书写,使用起来非常灵活,父路径和子路径都可以变化

 */
private static void show02(String parent, String child) {
    File file = new File(parent, child);

    //在main方法中调用show02("c:\\","a.txt");
    System.out.println(file);//c:\a.txt
}

/*
    File(File parent,String child)根据parent抽象路径和child路径名字符串创建一个新File实例
    参数:
        把路径分为两部分
        File parent:父路径
        String child:子路径
    好处:
        父路径和子路径,可以单独书写,使用起来非常灵活,父路径和子路径都可以变化
        父路径是File类型,可以使用File的方法对路径进行一些操作,再使用路径创建对象
 */
private static void show03() {
    File parent = new File("c:\\");
    File file = new File(parent,"hello.java");
    System.out.println(file);
}

获取功能的方法

File类获取功能的方法
    public String getAbsolutePath():返回File的绝对路径名字符串
    public String getPath():将此File转换为路径名字符串
    public String getName():返回由此File表示的文件或目录的名称
    public long Length():返回由此File表示的文件长度
/*
 public String getAbsolutePath():返回File的绝对路径名字符串
 获取的构造方法中传递的路径
 无论路径是绝对的还是相对的,getAbsolutePath方法返回的都是绝对路径
 */
private static void show01() {
    File file = new File("E:\\Java笔记\\Day03\\a.txt");
    String absolutePath1 = file.getAbsolutePath();
    System.out.println(absolutePath1);//E:\Java笔记\Day03

    File file1 = new File("a.txt");
    String absolutePath2 = file1.getAbsolutePath();
    System.out.println(absolutePath2);//E:\Java\Day03
}

/*
public String getPath():将此File转换为路径名字符串
获取的构造方法中传递的路径
 */
public static void show02(){
    File file = new File("E:\\Java笔记\\Day03");
    String path = file.getPath();
    System.out.println(path);//E:\Java笔记\Day03
    File file1 = new File("Day03");
    String path1 = file1.getPath();
    System.out.println(path1);//Day03
}

/*
public String getName():返回由此File表示的文件或目录的名称
获取构造方法传递路径的结尾部分
 */
private static void show03() {
    File file = new File("E:\\Java笔记\\Day03");
    String name = file.getName();
    System.out.println(name);//Day03

    File file1 = new File("E:\\Java笔记\\Day03\\a.txt");
    String name1 = file1.getName();
    System.out.println(name1);//a.txt
}

/*
    public long Length():返回由此File表示的文件长度
    获取的是构造方法指定的文件大小,以字节为单位
    注意:
        文件夹没有大小概念,不能获取文件夹大小(如要获取,则返回0)
        如果构造方法中给出的路径不存在,那么length方法返回0
 */
private static void show04(){
    File file = new File("E:\\Java笔记\\Day03\\a.txt");
    long length = file.length();
    System.out.println(length);//336
}

判断功能的方法

判断功能的方法
public boolean exists() :此File表示的文件或目录是否实际存在
public boolean isDirectory() :此File表示的是否为目录(是否以文件夹结尾)(前提是路径存在,否则为false)
public boolean isFile() :此File表示的是否为文件(是否以文件结尾)(前提是路径存在,否则为false)
public static void main(String[] args) {
    //public boolean exists() :此File表示的文件或目录是否实际存在
    File file = new File("E:\\Java笔记\\Day03\\a.txt");
    System.out.println(file.exists());//true


    File file2 = new File("E:\\Java笔记\\Day03\\b.txt");
    System.out.println(file2.exists());//false

    //public boolean isDirectory() :此File表示的是否为目录(是否以文件夹结尾)
    File file4 = new File("E:\\Java笔记\\Day03");
    System.out.println(file4.isDirectory());//true

    File file5 = new File("E:\\Java笔记\\Day03\\a.txt");
    System.out.println(file5.isFile());//true
}

创建删除功能的方法

创建删除功能的方法
public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。
public boolean delete() :删除由此File表示的文件或目录
public boolean mkdir() :创建由此File表示的目录
public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录
/*
    public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件
    创建的路径和名称在构造方法中给出(构造方法参数)
    返回值:布尔值
        文件不存在,创建文件,返回true
        文件存在,不会创建,返回false
    注意:
        1.此方法只能创建文件,不能创建文件夹
        2.创建文件路径必须存在,否则抛出异常
 */
private static void show01() throws IOException {
    File file = new File("E:\\Java笔记\\Day03\\1.txt");
    boolean newFile = file.createNewFile();
    System.out.println(newFile);//true
}


/*
public boolean mkdir():创建单级文件夹
public boolean mkdirs():单极文件夹和多级文件夹都可创建
创建的路径和名称在构造方法中给出(构造方法参数)
    返回值:布尔值
        文件不存在,创建文件夹,返回true
        文件夹存在,不会创建,返回false,构造方法中给出的路径不存在返回false
    注意:
        1.此方法只能创建文件夹,不能创建文件
 */
private static void show02(){
    File file = new File("E:\\Java笔记\\Day03\\aaa");
    System.out.println(file.mkdir());//true

    File file1 = new File("E:\\Java笔记\\Day03\\111\\222\\333");
    System.out.println(file1.mkdirs());//true
}

/*
    public boolean delete() :删除由此File表示的文件或目录
    删除构造方法中给出的文件/文件夹
    返回值:布尔值
        文件/文件夹删除成功,返回true
        文件夹中有内容,不会删除返回false,路径不存在返回false
    注意:
        直接在硬盘删除,不会进入回收站

 */
private static void show03(){
    File file = new File("E:\\Java笔记\\Day03\\111\\222\\333");
    System.out.println(file.delete());//true
}

目录的遍历

public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录
public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录

注意:
    List方法和listFiles方法遍历的是构造方法中给出的目录
    如果构造方法中给出的路径不存在,会抛出空指针异常
    如果构造方法中给出的路径不是一个目录,也会抛出空指针异常
/*
    public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录
    遍历构造方法中给出的目录,获取目录中所有文件/文件夹名称,把获取的多个名称存储到一个String类型的数组中
 */
private static void show01() {
    File file = new File("E:\\Java笔记\\Day03");
    String[] list = file.list();
    for (String s : list) {
        System.out.println(s);
    }
}

/*
public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录
遍历构造方法中给出的目录,获取目录中所有的的文件/文件夹,把文件/文件夹封装为File对象,多个File对象存储到File数组中
 */
public static void show02(){
    File file = new File("E:\\Java笔记\\Day03");
    File[] files = file.listFiles();
    for (File file1 : files) {
        System.out.println(file1);
    }
}

递归

递归:指在当前方法内调用自己的这种现象。
递归的分类:
	递归分为两种,直接递归和间接递归。
	直接递归称为方法自身调用自己。	
	间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。
注意事项:
	递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
	在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。
	构造方法,禁止递归
递归的使用前提:
	在调用方法时,方法的主体不变,每次方法的参数不同,可使用递归

IO概述

IO的流向说明图解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MhMsWmRD-1638929155461)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210730090717839.png)]

字节流

一切皆为字节

一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据

字节输出流写入到数据文件

    java.io.OutputStream 抽象类是表示字节输出流的所有类的超类

    定义了一些子类共性的方法:
        public void close() :关闭此输出流并释放与此流相关联的任何系统资源
        public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出
        public abstract void write(int b) :将指定的字节输出流

    java.io.FileOutputStream:文件字节输出流,用于将数据写出到文件
    作用:
        把内存中的数据写入到硬盘的文件中
    构造方法:
        FileOutputStream(String name):创建一个向具有指定名称的文件中写入数据的输出文件流
        FileOutputStream(File file):创建一个向指定File对象表示的文件中写入数据的文件输出流
        参数:写入数据的目的地
            String name:目的地是文件的路径
            File file:目的地是一个文件
        构造方法的作用:
            1.创建一个FileOutputStream对象
            2.会根据构造方法中传递文件/文件路径,创建一个空的文件
            3.会把FileOutputStream对象指向创建好的文件
    写入数据的原理(内存->硬盘)
        Java程序-->JVM(java虚拟机)-->OS(操作系统)-->OS调用写数据的方法-->把数据写入到文件中
    字节输出流的使用步骤:
        1.创建FileOutputStream对象,构造方法中传递写入数据的目的地
        2.调用FileOutputStream对象中的方法write,把输入写入到文件中
        3.释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提高程序的效率)
public static void main(String[] args) throws IOException {
        //1.创建FileOutputStream对象,构造方法中传递写入数据的目的地
        FileOutputStream fos = new FileOutputStream("E:\\Java\\src\\darkhouse\\demo17_IO\\a.txt");
        //2.调用FileOutputStream对象中的方法write,把输入写入到文件中
        //public abstract void write(int b) :将指定的字节输出流
        fos.write(97);
        //3.释放资源
        fos.close();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IwpQ4rEp-1638929155461)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210730103614731.png)]

 一次写多个字节的方法:
		public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流
         public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流
public static void main(String[] args) throws IOException {
        //创建FileOutputStream对象,构造方法中绑定要写入数据的目的地
        FileOutputStream fos = new FileOutputStream("E:\\Java\\src\\darkhouse\\demo17_IO\\b.txt");
        //调用FileOutputStream对象中的方法write,把数据写入到文件中
        //在文件中显示100,写个字节
        fos.write(49);
        fos.write(48);
        fos.write(48);

        /*
             public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流
             一次写多个字节:
                如果写的第一个字节是正数(0-127),那么显示的时候会查询ASCII码表
                如果写的第一个字节是负数,那么第一个字节会和第二个字节,两个字节组成一个中文显示,查询系统默认码表(GBK)
         */
        byte[] btes={65,66,67,68,69};//ABCDE
        //byte[] btes={-65,-66,-67,68,69};//烤紻E
        fos.write(btes);
        
        /*
        public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流
        把字节数组的一部分写入到文件中
            int off:数组的开始索引
            int len:写几个字节
         */
        fos.write(btes,1,2);//BC

        /*
            写入字符的方法:可以使用String类中的方法把字符串,转换为字节数组
                byte[] getBytes():把字符串转换为字节数组
         */
        byte[] bytes2="你好".getBytes();
        System.out.println(Arrays.toString(bytes2));//[-28, -67, -96, -27, -91, -67]
        fos.write(bytes2);

        
        //释放资源
        fos.close();
    }

数据追加续写

追加写/续写:使用两个参数的构造方法
        public FileOutputStream(File file, boolean append) : 创建文件输出流以写入由指定的 File对象表示的文件
        public FileOutputStream(String name, boolean append) : 创建文件输出流以指定的名称写入文件
    参数:
        String name,File file:写入数据的目的地
        boolean append:追加写开关
            true:创建对象不会覆盖源文件,继续在文件末尾追加写数据
            false:创建新文件,覆盖源文件

    写换行:写换行符号
        windows:\r\n
public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("E:\\Java\\src\\darkhouse\\demo17_IO\\c.txt",true);
        for (int i = 0; i < 10; i++) {
            fos.write("你好".getBytes());
            fos.write("\r\n".getBytes());
        }
        fos.close();
    }

字节输入流

java.io.InputStream:字节输入流
    此抽象类是表示字节输入流的所有类的超类

    定义了所有子类共性的方法:
        int read():从输入流中读取数据的下一个字节
        int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓存区数组b中
        void close():关闭此输入流并释放与该流相关的所有系统资源
    java.io.FileInputStream extends InputStream
    FileInputStream:文件字节输入流
    作用:把硬盘文件中的数据,读取到内存中使用
    构造方法:
        FileInputStream(String name)
        FileInputStream(File file)
        参数:读取文件的数据源
            String name:文件的路径
            File file:文件
        构造方法的作用:
            1.会创建一个FileInputStream对象
            2.会把FileInputStream对象指定构造方法中要读取的文件
    读取数据的原理(硬盘-->内存)
        java程序-->JVM-->OS--OS读取数据的方法-->读取文件

    字节输入流的使用步骤(重点):
        1.创建FileInputStream对象,构造方法中绑定要读取的数据源
        2.使用FileInputStream对象中的方法read,读取文件
        3.释放资源
public static void main(String[] args) throws IOException {
        // 1.创建FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fos = new FileInputStream("E:\\Java\\src\\darkhouse\\demo17_IO\\a.txt");
        //2.使用FileInputStream对象中的方法read,读取文件
        //int read():从输入流中读取数据的下一个字节
        System.out.println(fos.read());//97
        //再继续读取,指针 自动向后移动
        System.out.println(fos.read());//98
        System.out.println(fos.read());//99
        System.out.println(fos.read());//-1

        //释放资源
        fos.close();
    }

优化一下,用while循环

public static void main(String[] args) throws IOException {
        // 1.创建FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fos = new FileInputStream("E:\\Java\\src\\darkhouse\\demo17_IO\\a.txt");
        //2.使用FileInputStream对象中的方法read,读取文件
        //int read():从输入流中读取数据的下一个字节
        int len=0;
        while ((len=fos.read())!=-1){
            System.out.println(len);
        }

        //释放资源
        fos.close();
    }

或者直接用while循环读取源数据

while ((len=fos.read())!=-1){
            System.out.println((char)len);//结果为abc
}

字节输入流一次读取多个字节

 字节输入流一次读取多个字节的方法:
        int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓存区数组b中
    明确两件事情:
        1.方法的参数byte[]的作用?
            起到缓冲作用,存储每次读取到的多个字节
            数组的长度一般定义为1024(1kb)
        2.方法的返回值int是什么?
            每次读取的有效字节个数


    String类的构造方法:
        String(byte[] bytes):把字节数组转换为字符串
        String(byte[] bytes,int offset,int length):把字节数组的一部分转换为字符串
public static void main(String[] args) throws IOException {
        // 1.创建FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fos = new FileInputStream("E:\\Java\\src\\darkhouse\\demo17_IO\\a.txt");
        //2.使用FileInputStream对象中的方法read,读取文件
        // int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓存区数组b中
        byte[] bytes = new byte[1024];//存储每次读取到的多个字节
        int len = 0;
        while ((len=fos.read(bytes))!=-1){
            //String(byte[] bytes,int offset,int length):把字节数组的一部分转换为字符串
            System.out.println(new String(bytes,0,len));
        }

        //释放资源
        fos.close();
    }

文件复制练习:一读一写

明确:
        数据源:c:\\1.jpg
        数据的目的地:d:\\1.jpg
    文件复制步骤:
        1.创建一个字节输入流对象,构造方法中绑定要读取的数据源
        2.创建一个字节输出流对象,构造方法中绑定写入的目的地
        3.使用字节输入流对象中的方法read读取文件
        4.使用字节输出流中的方法write,把读取到的字节写入到目的地文件中
public static void main(String[] args) throws IOException {
        long time1 = System.currentTimeMillis();
        // 1.创建FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("E:\\1.jpg");
        // 2.创建一个字节输出流对象,构造方法中绑定写入的目的地
        FileOutputStream fos = new FileOutputStream("D:\\1.jpg");
        //一次读取一个字节写入一个字节的方式
        byte[] bytes = new byte[1024*100];
        int len=0;
        while ((len=fis.read(bytes))!=-1){
            fos.write(bytes,0,len);
        }

        //释放资源(先关闭写 再关闭读)
        fos.close();
        fis.close();
        long time2 = System.currentTimeMillis();
        System.out.println("赋值文件耗时:"+(time2-time1)+"毫秒");
}
//用byte数组的方式处理复制操作,效率高

字符流

当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件

字符输入流Reader

java.io.Reader 字符输入流,抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法

    共性的成员方法:
        public int read():从输入流读取一个字符并返回
        public int read(char[] cbuf):从输入流中读取多个字符,并将它们存储到字符数组cbuf中
        public void close():关闭此流并释放与此流相关联的任何系统资源
    java.io.FileReader extends InputStreamReader extends Reader
    FileReader:文件字符输入流
    作用:把硬盘文件中的数据以字符的方式读取到内存中

    构造方法:
        FileReader(File file)
        FileReader(String fileName)
            String fileName:文件路径
            File file:一个文件
         FileReader构造方法的作用:
            1.创建一个 FileReader对象
            2.会把 FileReader对象指向要读取的文件
    字符输入流的使用步骤:
        1.创建FileReader对象,构造方法中绑定要读取的数据源
        2.使用FileReader对象中的方法read读取文件
        3.释放资源

利用int read():从输入流读取一个字符并返回

public static void main(String[] args) throws IOException {
        //1.创建FileReader对象,构造方法中绑定要读取的数据源
        FileReader fr = new FileReader("E:\\Java\\src\\darkhouse\\demo17_IO\\c.txt");
        //2.使用FileReader对象中的方法read读取文件
        //int read():从输入流读取一个字符并返回
        int len=0;
        while ((len=fr.read())!=-1){
            System.out.print((char) len);
        }
        //3.释放资源
        fr.close();
    }

利用int read(char[] cbuf):从输入流中读取多个字符,并将它们存储到字符数组cbuf中

public static void main(String[] args) throws IOException {
        //1.创建FileReader对象,构造方法中绑定要读取的数据源
        FileReader fr = new FileReader("E:\\Java\\src\\darkhouse\\demo17_IO\\c.txt");
        //2.使用FileReader对象中的方法read读取文件
        //int read(char[] cbuf):从输入流中读取多个字符,并将它们存储到字符数组cbuf
        char[] cs = new char[1024];//存储读取到的多个字符
        int len=0;//记录每次读取的有效字符个数
        while ((len=fr.read(cs))!=-1){
            /*
                String类的构造方法
                String(char[] value):把字符数组转换为字符串
                String(char[] value,int offset,int count):把字符数组的一部分转换为字符串
             */
            System.out.println(new String(cs,0,len));
        }

        //3.释放资源
        fr.close();
    }

字符输出流Writer

java.io.writer:字符输出流,是所有字符输出流的最顶层的父类,是一个抽象类

    共性的成员方法:
        void write(int c) 写入单个字符
        void write(char[] cbuf) 写入字符数组
        abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
        void write(String str) 写入字符串
        void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
        void flush() 刷新该流的缓冲
        void close() 关闭此流,但要先刷新它

    java.io.FileWriter extends OutputStreamWriter extends writer
    FileWriter:文件字符输出流
    作用:把内存中的字符数据写入到文件中
    构造方法:
        FileWriter(File file) : 创建一个新的 FileWriter,给定要读取的File对象
        FileWriter(String fileName) : 创建一个新的 FileWriter,给定要读取的文件的名称
        参数:写入数据的目的地
            File file:是一个文件
            String fileName:文件的路径
        作用:
            1.创建FileWriter对象
            2.根据构造方法中传递的文件/文件的路径,创建文件
            3.FileWriter指向创建好的文件
    使用步骤:
        1.创建FileWriter对象,构造方法中绑定要写入数据的目的地
        2.使用FileWriter中的方法write,把数据写入到内存缓存区中(字符转换为字节的过程)
        3.使用FileWriter中的方法flush,把内存缓存区中的数据,刷新到文件中
        4.释放资源(会把内存缓存区中的数据刷新到文件中)

写入单个字符

public static void main(String[] args) throws IOException {
        //1.创建FileWriter对象,构造方法中绑定要写入数据的目的地
        FileWriter fw = new FileWriter("E:\\Java\\src\\darkhouse\\demo17_IO\\d.txt");
        //2.使用FileWriter中的方法write,把数据写入到内存缓存区中(字符转换为字节的过程)
        //void write(int c) 写入单个字符
        fw.write(97);
        //3.使用FileWriter中的方法flush,把内存缓存区中的数据,刷新到文件中
        fw.flush();
        //4.释放资源
        fw.close();
    }
flush和close的区别:
	flush:刷新缓冲区,流对象可以继续使用
	close:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了

写数据的其他方法

void write(char[] cbuf) 写入字符数组
abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
void write(String str) 写入字符串
void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("E:\\Java\\src\\darkhouse\\demo17_IO\\d.txt");
        char[] cs={'a','b','c','d','e'};

        //void write(char[] cbuf) 写入字符数组
        fw.write(cs);//abcde

        //abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
        fw.write(cs,1,3);//bcd

        //void write(String str) 写入字符串
        fw.write("Sherlock");//Sherlock

        //void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
        fw.write("黑马程序员",2,3);//程序员
        fw.close();
    }

续写和换行

续写和换行
    续写:追加写,使用两个参数的构造方法
        FileWriter(String filename,boolean append)
        FileWriter(File file,boolean append)
        参数:
            String filename,File file:写入数据的目的地
            boolean append:续写开关
                 true:创建对象不会覆盖源文件,继续在文件末尾追加写数据
                 false:创建新文件,覆盖源文件
     换行:换行符号
        windows:\r\n
public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("E:\\Java\\src\\darkhouse\\demo17_IO\\e.txt",true);
        for (int i = 0; i < 10; i++) {
            fw.write("HelloWorld"+i+"\r\n");
        }
        fw.close();
    }

IO异常的处理(???)

JDK1.7前

在JDK1.7,使用try catch finally处理流中的异常
    格式:
        try{
            可能会产生异常的代码
        }catch(异常类变量 变量名){
            异常的处理逻辑
        }finally{
            一定会执行的代码
            资源释放
        }
public static void main(String[] args) {
        //提高变量fw的作用域,让finally可使用
        //变量在定义的时候,可以没有值,但是使用的时候必须有值
        FileWriter fw=null;
        try{
            //可能会产生异常的代码
            fw = new FileWriter("E:\\Java\\src\\darkhouse\\demo17_IO\\e.txt",true);
            for (int i = 0; i < 10; i++) {
                fw.write("HelloWorld"+i+"\r\n");
            }
        }catch (IOException e){
            //异常的处理逻辑
            System.out.println(e);
        }finally {
            //一定会执行的代码
            //创建对象失败了,fw默认值就是null,null是不能调用方法的,会抛出NullPointException,需要增加一个判断,不是null再把资源释放
            if (fw!=null){
                try {
                    //fw.close方法声明抛出了IOException异常对象,需要处理
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }

JDK7新特性

JDK7新特性
    再try的后边可以增加一个(),在括号中可以定义流对象
    那么这个流对象的作用域就在try中有效
    try中的代码执行完毕,会自动把流对象释放,不用写finally
    格式:
        try(定义流对象;定义流对象...){
            可能会产出异常的代码
        }catch(异常类变量  变量名){
            异常的处理逻辑
        }
public static void main(String[] args) {
        try(// 1.创建FileInputStream对象,构造方法中绑定要读取的数据源
            FileInputStream fis = new FileInputStream("E:\\1.jpg");
            // 2.创建一个字节输出流对象,构造方法中绑定写入的目的地
            FileOutputStream fos = new FileOutputStream("D:\\1.jpg");) {

            //可能会产出异常的代码
            //一次读取一个字节写入一个字节的方式
            byte[] bytes = new byte[1024*100];
            int len=0;
            while ((len=fis.read(bytes))!=-1){
                fos.write(bytes,0,len);
            }
        }catch (IOException e){
            //异常的处理逻辑
            System.out.println(e);
        }
    }

Properties类

java.util.Properties 继承于 Hashtable<k,v>  implements Map<k,v>
    Properties表示一个持久的属性集,Properties可保存在流中或从流中加载,属性列表中每个键及其对应值都是一个字符串
    Properties集合是一个唯一和IO流相结合的集合
        可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
        可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
    属性列表中每个键及其对应值都都是一个字符串
        Properties集合是一个双列集合,key和value默认都是字符串

遍历Properties数据

使用Properties集合存储数据,遍历取出Properties集合中的数据
    Properties集合是一个双列集合,key和value默认都是字符串
    Properties集合有一些操作字符串的特有方法
    Object setProperty(String key,String value)添加数据,调用Hashtable的方法put
    String getProperty(String key)用指定的键在此属性列表中搜索属性(通过key找到value,相当于Map集合中的get(key)方法)
    Set<String> stringPropertyNames() 返回此属性中的键集,其中该键及其对应值是字符串,此方法相当于Map集合中的keySet方法
private static void show01() {
        //创建Properties集合对象
        Properties prop = new Properties();
        //使用setProperty方法往集合中添加数据
        prop.setProperty("Sherlock","20");
        prop.setProperty("Jone","21");
        prop.setProperty("Jack","20");

        //使用stringPropertyNames方法把Properties把集合中的键取出,存储到Set集合中
        Set<String> set = prop.stringPropertyNames();
        //遍历set集合
        for (String key : set) {
            //使用getProperty方法通过key获取value
            String value = prop.getProperty(key);
            System.out.println(key+"="+value);
        }
    }
运行结果:
Jone=21
Sherlock=20
Jack=20

store方法(写入数据)

可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
        void store(OutputStream out,String comments)
        void store(Writer writer,String comments)
        参数:
            OutputStream out:字节输出流,不能写入中文
            Writer writer:字符输出流,可以写中文
            String comments:注释,用来解释说明保存的文件是做什么用的
                不能使用中文,会产生乱码,默认是Unicode编码
                一般使用"空字符串"
        使用步骤:
            1.创建Properties集合对象,添加数据
            2.创建字节输出流/字符输出流,构造方法中绑定要输出的目的地
            3.使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
            4.释放资源
private static void show02() throws IOException {
        //1.创建Properties集合对象,添加数据
        //创建Properties集合对象
        Properties prop = new Properties();
        //使用setProperty方法往集合中添加数据
        prop.setProperty("Sherlock","20");
        prop.setProperty("Jone","21");
        prop.setProperty("Jack","20");
        //2.创建字节输出流/字符输出流,构造方法中绑定要输出的目的地
        FileWriter fw = new FileWriter("E:\\Java\\src\\darkhouse\\demo17_IO\\d.txt");
        //3.使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
        prop.store(fw,"sava date");
        //4.释放资源
        fw.close();
    }
运行结果:
#sava date
#Mon Aug 02 08:34:51 CST 2021
Jone=21
Sherlock=20
Jack=20

load方法(读取数据)

可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
        void load(InputStream inStream)
        void load(Reader reader)
        参数:
			//括号中需要传入流  所以需要写成 new FileReader("......")
            InputStream inStream:字节输入流,不能读取含有中文的键值对
            Reader reader:字符输入流,能读取含有中文的键值对
        使用步骤:
            1.创建Properties对象
            2.使用Properties集合对象中的方法load读取保存键值对的文件
            3.遍历Properties集合
        注意:
            1.存储键值对的文件中,键与值默认的连接符号可以使用=,空格,其他符号
            2.存储键值对的文件中,可以使用#进行注释,被注释的键值对不会再被读取
            3.存储键值对的文件中,键与值默认都是字符串,不用加引号
private static void show03() throws IOException {
        //1.创建Properties对象
        Properties prop = new Properties();
        //2.使用Properties集合对象中的方法load读取保存键值对的文件
        prop.load(new FileReader("E:\\Java\\src\\darkhouse\\demo17_IO\\d.txt"));
        //3.遍历Properties集合
        Set<String> set = prop.stringPropertyNames();
        //遍历set集合
        for (String key : set) {
            //使用getProperty方法通过key获取value
            String value = prop.getProperty(key);
            System.out.println(key+"="+value);
        }
    }
运行结果:
Jone=21
Sherlock=20
Jack=20

缓冲流

缓冲流,也叫高效流,是对4个基本的 FileXxx 流的增强,所以也是4个流,按照数据类型分类:
字节缓冲流: BufferedInputStreamBufferedOutputStream
字符缓冲流: BufferedReaderBufferedWriter
    
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率


都是在基本的流对象基础之上创建而来的,就像穿上铠甲的武士一样,相当于是对基本流对象的一种增强

字节缓冲流

输出流

java.io.BufferedOutputStream extends  OutputStream
    BufferedOutputStream:字节缓存输出流

    继承自父类的共性成员方法:
        public void close() :关闭此输出流并释放与此流相关联的任何系统资源
        public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出
        public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流
        public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量off开始输出到此输出流
        public abstract void write(int b) :将指定的字节输出流
    构造方法:
        public BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流
        public BufferedOutputStream(OutputStream out,int size):创建一个新的缓冲输出流
        参数:
            OutputStream out:字节输出流
                我们可以传递FileOutputStream,缓冲流会给FileOutputStream增加一个缓冲区,提高FileOutputStream的写入效率
            int size:指定缓冲流内部缓冲区的大小,不指定就是默认大小
    使用步骤:(重点)
        1.创建FileOutputStream对象,构造方法中绑定要输出的目的地
        2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream效率
        3.使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
        4.使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
        5.释放资源(先调用flush刷新数据,即第4步可省略)
public static void main(String[] args) throws IOException {
        //1.创建FileOutputStream对象,构造方法中绑定要输出的目的地
        FileOutputStream fos = new FileOutputStream("E:\\Java\\src\\darkhouse\\demo17_IO\\f.txt");
        //2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream效率
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        //3.使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
        bos.write("我把数据写入到内部缓冲区中".getBytes());
        bos.close();
    }

输入流

java.io.BufferedInputStream extends InputStream
    BufferedInputStream:字节缓冲输出流

    继承自父类的成员方法:
        int read():从输入流中读取数据的下一个字节
        int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓存区数组b中
        void close():关闭此输入流并释放与该流相关的所有系统资源
    构造方法:
        BufferedInputStream(InputStream in):创建一个BufferedInputStream并保存其参数,即输入流in,以便将来使用
        BufferedInputStream(InputStream in,int size):创建具有指定缓冲区大小的BufferedInputStream并保存其参数,即输入流
        参数:
            InputStream in:字节输入流
                我们可以传递FileInputStream,缓冲流会给FileInputStream增加一个缓冲区,提高FileInputStream的读取效率
            int size:指定缓冲流内部缓冲区的大小,不指定就是默认大小
    使用步骤:
        1.创建FileInputStream对象,构造方法中绑定要读取的数据源
        2.创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
        3.使用BufferedInputStream对象中的方法read,读取文件
        4.释放资源

①int read():从输入流中读取数据的下一个字节

public static void main(String[] args) throws IOException {
        //1.创建FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("E:\\Java\\src\\darkhouse\\demo17_IO\\f.txt");
        //2.创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
        BufferedInputStream bis = new BufferedInputStream(fis);
        //3.使用BufferedInputStream对象中的方法read,读取文件
        //int read():从输入流中读取数据的下一个字节
        int len=0;//记录每次读取到的字节
        while ((len=bis.read())!=-1){
            System.out.print((char) len);
        }
        //4.释放资源(只用关闭缓冲流即可,会自用关闭字节流)
        bis.close();
    }

②int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓存区数组b中

public static void main(String[] args) throws IOException {
        //1.创建FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("E:\\Java\\src\\darkhouse\\demo17_IO\\f.txt");
        //2.创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
        BufferedInputStream bis = new BufferedInputStream(fis);
        //3.使用BufferedInputStream对象中的方法read,读取文件
        //int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓存区数组b中
        int len=0;//记录每次读取到的字节
        byte[] bytes = new byte[1024];//存储每次读取的数据
        while ((len=bis.read(bytes))!=-1){
            System.out.println(new String(bytes,0,len));
        }
        //4.释放资源(只用关闭缓冲流即可,会自用关闭字节流)
        bis.close();
    }

效率测试(复制文件)

文件复制练习:一读一写
    明确:
        数据源:c:\\1.jpg
        数据的目的地:d:\\1.jpg
    文件复制步骤:
        1.创建字节缓冲输入流对象,构造方法中传递字节输入流
        2.创建字节缓冲输出流对象,构造方法中传递字节输出流
        3.使用字节缓冲输入流对象中的方法read,读取文件
        4.使用字节缓冲输出流中的方法write,把读取的数据写入到内部缓冲区
        5.释放资源

①一次读取一个字节写入一个字节的方式

public static void main(String[] args) throws IOException {
        long time1 = System.currentTimeMillis();
        //1.创建字节缓冲输入流对象,构造方法中传递字节输入流
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\1.jpg"));
        //2.创建字节缓冲输出流对象,构造方法中传递字节输出流
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\1.jpg"));
        //3.使用字节缓冲输入流对象中的方法read,读取文件
        //一次读取一个字节写入一个字节的方式
        int len=0;
        while ((len=bis.read())!=-1){
            bos.write(len);
        }
        bos.close();
        bis.close();

        long time2 = System.currentTimeMillis();
        System.out.println("复制文件共耗时:"+(time2-time1)+"毫秒");
    }

复制文件共耗时:234毫秒

②使用数组缓冲读取多个字节,写入多个字节

public static void main(String[] args) throws IOException {
        long time1 = System.currentTimeMillis();
        //1.创建字节缓冲输入流对象,构造方法中传递字节输入流
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\1.jpg"));
        //2.创建字节缓冲输出流对象,构造方法中传递字节输出流
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\1.jpg"));
        //3.使用字节缓冲输入流对象中的方法read,读取文件
        //使用数组缓冲读取多个字节,写入多个字节
        int len=0;
        byte[] bytes = new byte[1024];
        while ((len=bis.read(bytes))!=-1){
            bos.write(bytes,0,len);
        }
        bos.close();
        bis.close();

        long time2 = System.currentTimeMillis();
        System.out.println("复制文件共耗时:"+(time2-time1)+"毫秒");
    }

复制文件共耗时:31毫秒

字符缓冲流

输出流

java.io.BufferedWriter extend Writer
    BufferedWriter:字符缓冲输出流

    继承自父类的共性成员方法:
        void write(int c) 写入单个字符
        void write(char[] cbuf) 写入字符数组
        abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
        void write(String str) 写入字符串
        void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
        void flush() 刷新该流的缓冲
        void close() 关闭此流,但要先刷新它
    构造方法:
        BufferedWriter(Writer out)创建一个使用默认大小输出缓冲区的缓冲字符输出流
        BufferedWriter(Writer out,int sz)创建一个使用给定大小输出缓冲区的新缓冲字符输出流
        参数:
            Writer out:字符输出流
                我们可以传递FileWriter,缓冲流会给FileWriter增加一个缓冲区,提高FileWriter的写入效率
                int sz:指定缓冲区大小,不写默认大小
    特有成员方法:
        void newLine():写入一个行分隔符.会根据不同操作系统,获取不同行分隔符
    使用步骤:
        1.创建字符缓冲输出流对象,构造方法中传递字符输出流
        2.调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
        3.调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件中
        4.释放资源
public static void main(String[] args) throws IOException {
        //1.创建字符缓冲输出流对象,构造方法中传递字符输出流
        BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\Java\\src\\darkhouse\\demo17_IO\\d.txt"));
        //2.调用字符缓冲输出流中的方法write,把数据写入到y中
        for (int i = 0; i < 10; i++) {
            bw.write("Sherlock");
            bw.newLine();//换行
        }
        //释放资源
        bw.close();
    }

输入流

java.io.BufferedReader extend Reader

    继承自父类的共性成员方法:
        int read():读取单个字符返回
        int read(char[] cbuf):一次读取多个字符,将字符读入数组
        void close():关闭该流并释放与之关联的所有资源
    构造方法:
        BufferedReader(Reader in):创建一个使用默认大小输入缓冲区的缓冲字符输入流
        BufferedReader(Reader in,int sz):创建一个使用指定大小输入缓冲区的缓冲字符输入流
        参数:
            Reader in:字符输入流
                我们可以传递FileReader,缓冲流会给FileReader增加一个缓冲区,提高FileReader的读取效率
    特有的成员方法:
        String readLine():读取文本行,读取一行数据
            行的终止符号:通过下列字符之一即可认为某行已经终止:换行('\n'),回车('\r'),或回车后直接跟着换行('\r\n')
        返回值:
            包含该行内容的字符串,不包含任何终止符,如果已到达流末尾,则返回null
    使用步骤:
        1.创建字符缓冲输入流对象,构造方法中传递字符输入流
        2.使用字符缓冲输入流对象中的方法read/readLine读取文本
        3.释放资源
public static void main(String[] args) throws IOException {
        //1.创建字符缓冲输入流对象,构造方法中传递字符输入流
        BufferedReader br = new BufferedReader(new FileReader("E:\\Java\\src\\darkhouse\\demo17_IO\\d.txt"));
        //2.使用字符缓冲输入流对象中的方法read/readLine读取文本
        String line;
        while ((line=br.readLine())!=null){
            System.out.println(line);
        }
        //3.释放资源
        br.close();
    }

练习

练习:
        对文本的内容进行排序
        按照(1,2,3....)进行排序
    分析:
        1.创建一个HashMap集合对象
            key:存储每行文本的序号(1,2,3...)
            value:存储每行的文本
        2.创建字节缓冲输入流对象,构造方法中绑定字符输入流
        3.创建字符缓冲输出流对象,构造方法中绑定字符输出流
        4.使用字符缓冲输入流的方法readLine,逐行读取文本
        5.对读取到的文本进行切割,获取行中的序号和文本的内容
        6.把切割好的序号和文本内容存储到HashMap,key序号有序,会自动排序
        7.遍历HashMap集合,获取每一个键值对
        8.每一个键值对拼接为文本行
        9.把拼接好的文本行使用字符缓冲输出流中的方法write,写入到文本中
        10.释放资源
 public static void main(String[] args) throws IOException {
        //1.创建一个HashMap集合对象
        HashMap<String, String> map = new HashMap<>();
        //2.创建字节缓冲输入流对象,构造方法中绑定字符输入流
        BufferedReader br = new BufferedReader(new FileReader("E:\\Java\\src\\darkhouse\\demo21_BufferedStream\\1.txt"));
        //3.创建字符缓冲输出流对象,构造方法中绑定字符输出流
        BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\Java\\src\\darkhouse\\demo21_BufferedStream\\2.txt"));
        //4.使用字符缓冲输入流的方法readLine,逐行读取文本
        String line;
        while ((line=br.readLine())!=null){
            //5.对读取到的文本进行切割,获取行中的序号和文本的内容
            String[] arr = line.split("\\.");
            //6.把切割好的序号和文本内容存储到HashMap中,key序号有序,会自动排序
            map.put(arr[0],arr[1]);
        }
        //7.遍历HashMap集合,获取每一个键值对
        for(String key:map.keySet()){
            String value=map.get(key);
            //8.每一个键值对拼接为文本行
            line=key+"."+value;
            //9.把拼接好的文本行使用字符缓冲输出流中的方法write,写入到文本中
            bw.write(line);
            bw.newLine();//换行
        }
        //10.释放资源
        bw.close();
        br.close();
    }

转化流

字符编码和字符集

字符编码

	计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。
    反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本f符号。
    反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。
    编码:字符(看得懂的)-->字节(看不懂的))
         字节(看不懂的))-->字符(看得懂的)
	字符编码:Character Encoding:就是一套自然语言的字符与二进制数之间的对应规则

字符集

字符集:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
常见字符集有ASCII字符集、GBK字符集、Unicode字符集等

编码引出的问题

FileReader可以读取默认编码格式(UTF-8)文件
FileReader读取系统默认编码(中文GBK)会产生乱码
public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("E:\\Java\\src\\darkhouse\\demo22_ReverseStream\\GBK格式.txt");
        int len=0;
        while ((len=fr.read())!=-1){
            System.out.print((char) len);
        }
        fr.close();
    }

那么如何读取GBK编码的文件呢?

OutputStreamWriter类

java.io.OutputStreamWriter extends Writer
    OutputStreamWriter:是字符流通向字节流的桥梁,可使用指定的charset将要写入流中的字符编码成字节(编码)

    继承自父类的共性成员方法:
        void write(int c) 写入单个字符
        void write(char[] cbuf) 写入字符数组
        abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
        void write(String str) 写入字符串
        void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
        void flush() 刷新该流的缓冲
        void close() 关闭此流,但要先刷新它
    构造方法:
        OutputStreamWriter(OutputStream in):创建一个使用默认字符集的字符流。
        OutputStreamWriter(OutputStream in, String charsetName):创建一个指定字符集的字符流
        参数:
            OutputStream out:字节输出流,可以用来写转换之后的字节的文件中
            String charsetName:指定的编码表名称,不区分大小写,不指定默认为UTF-8
    使用步骤:
        1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
        2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储在缓冲区中(编码)
        3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
        4.释放资源
	/*
        使用转换流OutputStreamWriter写GBK格式的文件
     */
    private static void write_gbk() throws IOException {
        //1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
        OutputStreamWriter osw = new OutputStreamWriter(new 					         FileOutputStream("E:\\Java\\src\\darkhouse\\demo22_ReverseStream\\gbk.txt"),"GBK");
        //2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储在缓冲区中(编码)
        osw.write("你好");
        //3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
        osw.flush();
        //4.释放资源
        osw.close();
    }

    /*
        使用转换流OutputStreamWriter写UTF-8格式的文件
     */
    private static void write_utf_8() throws IOException {
        //1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\Java\\src\\darkhouse\\demo22_ReverseStream\\utf_8.txt"),"utf-8");
        //2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储在缓冲区中(编码)
        osw.write("你好");
        //3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
        osw.flush();
        //4.释放资源
        osw.close();
    }

InputStreamReader类

java.io.InputStreamReader extends Reader
    是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符

    继承自父类的共性成员方法:
        int read():读取单个字符返回
        int read(char[] cbuf):一次读取多个字符,将字符读入数组
        void close():关闭该流并释放与之关联的所有资源
    构造方法:
        InputStreamReader(InputStream in)创建一个使用默认字符集的InputStreamReader
        InputStreamReader(InputStream in,String charsetName)创建一个使用字符集的InputStreamReader
        参数:
            InputStream in:字节输入流,用来读取文件中保存的字节
            String charsetName:指定的编码表名称,不区分大小写,不指定默认为UTF-8
    使用步骤:
        1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
        2.使用InputStreamReader对象中的方法read读取文件
        3.释放资源
    注意事项:
        构造方法中指定的编码表名称要和文件的编码相同,符合会发生乱码
     /*
        使用InputStreamReader读取gbk格式的文件
     */
    private static void read_gbk() throws IOException {
        //1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
        InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\Java\\src\\darkhouse\\demo22_ReverseStream\\gbk.txt"), "gbk");
        //2.使用InputStreamReader对象中的方法read读取文件
        int len = 0;
        while ((len = isr.read()) != -1) {
            System.out.print((char) len);
        }
        isr.close();
    }


    /*
        使用InputStreamReader读取utf-8格式的文件
     */
    private static void read_utf_8() throws IOException {
        //1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
        InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\Java\\src\\darkhouse\\demo22_ReverseStream\\utf_8.txt"), "utf-8");
        //2.使用InputStreamReader对象中的方法read读取文件
        int len = 0;
        while ((len = isr.read()) != -1) {
            System.out.print((char) len);
        }
        isr.close();
    }

练习

将指定GBK编码的文件,转换为UTF-8编码的文本文件
    步骤:
        1.创建InputStreamReader对象
        2.创建OutputStreamWriter对象
        3.读取GBK编码的文件
        4.以UTF-8的格式写入文件
        5.释放资源
public static void main(String[] args) throws IOException {
        //1.创建InputStreamReader对象,读取GBK编码的文件
        InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\Java\\src\\darkhouse\\demo22_ReverseStream\\GBK_test.txt"),"gbk");
        //2.创建OutputStreamWriter对象
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\Java\\src\\darkhouse\\demo22_ReverseStream\\UTF_8_test.txt"),"utf-8");
        int len=0;
        //3.读取GBK编码的文件
        while ((len=isr.read())!=-1){
            //4.以UTF-8的格式写入文件
            osw.write(len);
        }
        //5.释放资源
        osw.close();
        isr.close();
    }

序列化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8GJRx8wM-1638929155462)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210803093843503.png)]

序列化和反序列化的时候,会抛出NotSerializableException没有序列化异常
    类通过实现java. io. Serializable接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。
    Serializable接口也叫标记型接口
        要进行序列化和反序列化的类必须实现Serializable接口,就会给类添加一个标记:(在对象的类中实现接口)
        当我们进行序列化和反序列化的时候,就会检测类上是否有这个标记
            有:就可以序列化和反序列化
            没有:就会抛出NotSerializableExcept ion异常
    去市场买肉-->肉上有一-个蓝色章(检测合格)-->放心购买-- >买回来怎么吃随意
                
若不想成员被序列化,则需要使用transient关键字

Person类

public class Person implements Serializable {
    private String name;
    private  int age;

    public Person() {
    }

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

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

    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;
    }
}

序列化

java.io.ObjectOutputStream extends OutputStream
    ObjectOutputStream:对象的序列化流
    作用:把对象以流的方式写入到文件中保存
    构造方法:
        ObjectOutputStream(OutputStream out):创建写入指定OutputStreamObjectOutputStream
        参数:
            OutputStream out:字节输出流
    特有成员方法:
        void writeObject(Object obj):将指定的对象写入ObjectOutputStream
    使用步骤:
        1.创建ObjectOutputStream对象,构造方法中传递字节输出流
        2.使用ObjectOutputStream中的方法writeObject,把对象写入到文件中
        3.释放资源
public static void main(String[] args) throws IOException {
        //1.创建ObjectOutputStream对象,构造方法中传递字节输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\Java\\src\\darkhouse\\demo23_ObjectStream\\person.txt"));
        //2.使用ObjectOutputStream中的方法writeObject,把对象写入到文件中
        oos.writeObject(new Person("Sherlock",20));
        //3.释放资源
        oos.close();
    }

反序列化①

java.io.ObjectInputStream extends InputStream
    ObjectInputStream:对象的反序列化流
    作用:把文件中保存的对象,以流的方式读取出啦使用

    构造方法:
        ObjectInputStream(InputStream in):创建从指定InputStream读取的ObjectInputStream
        参数:
            InputStream in:字节输入流
    特有成员方法:
        Object readObject():ObjectInputStream读取对象
    使用步骤:
        1.创建ObjectInputStream对象,构造方法中传递字节输入流
        2.使用ObjectInputStream中的方法readObject读取保存对象的文件
        3.释放资源
        4.使用读取出来的对象(打印)
    readObject方法声明抛出了ClassNotFoundException(class文件找不到异常)
    当不存在对象的class文件时抛出异常
    反序列化前提:
        1.类必须实现Serializable接口
        2.必须存在类对应的class文件(必须抛出异常)
public static void main(String[] args) throws IOException, ClassNotFoundException {
        //1.创建ObjectInputStream对象,构造方法中传递字节输入流
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\Java\\src\\darkhouse\\demo23_ObjectStream\\person.txt"));
        //2.使用ObjectInputStream中的方法readObject读取保存对象的文件
        Object o = ois.readObject();
        //3.释放资源
        ois.close();
        //4.使用读取出来的对象(打印)
        Person person = (Person) o;
        System.out.println(person.getName()+":"+person.getAge());
    }

反序列化②

	当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常
	原因:该类的序列版本号与从流中读取的类描述符的版本号不匹配
    解决方法:在对象类中定义序列号
        private static final long serialVersionUID = 1L;

练习

练习:序列化集合
        当我们想在文件中保存多个文件的时候
        可以把多个对象存储到一个集合中
        对集合序列化和反序列化
    分析:
        1.定义一个存储Person对象的ArrayList集合
        2.ArrayList集合中存储Person对象
        3.创建一个序列化流ObjectOutputStream对象
        4.使用ObjectOutputStream对象中的方法writeObject对象对集合进行序列化
        5.创建一个反序列化ObjectInputStream对象
        6.使用ObjectInputStream对象中的方法readObject读取文件中保存的集合
        7.Object类型的集合转换为ArrayList类型
        8.遍历ArrayList集合
        9.释放资源
public static void main(String[] args) throws IOException, ClassNotFoundException {
        //1.定义一个存储Person对象的ArrayList集合
        ArrayList<Person> list = new ArrayList<>();
        //2.往ArrayList集合中存储Person对象
        list.add(new Person("Sherlock",20));
        list.add(new Person("Jone",22));
        list.add(new Person("Jack",20));
        //3.创建一个序列化流ObjectOutputStream对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\Java\\src\\darkhouse\\demo23_ObjectStream\\person_test.txt"));
        //4.使用ObjectOutputStream对象中的方法writeObject对象对集合进行序列化
        oos.writeObject(list);
        //5.创建一个反序列化ObjectInputStream对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\Java\\src\\darkhouse\\demo23_ObjectStream\\person_test.txt"));
        //6.使用ObjectInputStream对象中的方法readObject读取文件中保存的集合
        Object o = ois.readObject();
        //7.把Object类型的集合转换为ArrayList类型
        ArrayList<Person> list2=(ArrayList<Person>) o;
        //8.遍历ArrayList集合
        for (Person person : list2) {
            System.out.println(person.getName()+":"+person.getAge());
        }
        //9.释放资源
        ois.close();
        oos.close();
    }

打印流(未看)

java.io.PrintStream 打印流
        PrintStream:为其他输出流添加了功能,是他们能够方便地打印各种数据值表示形式
    PrintStream:
        1.只负责数据地输出,不负责数据读取
        2.与其他输出流不同,PrintStream永远不会抛出IOException
        3.有特有的方法,print,println
            void print(任意类型的值)
            void println(任意类型的值并换行)
    构造方法:
        PrintStream(File file):输出的目的地是文件
        PrintStream(OutputStream out):输出目的地是字节输出流
        PrintStream(String fileName):输出目的地是文件路径

    PrintStream extends OutputStream
    继承自父类的成员方法:
        public void close() :关闭此输出流并释放与此流相关联的任何系统资源
        public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出
        public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流
        public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量off开始输出到此输出流
        public abstract void write(int b) :将指定的字节输出流
    注意事项:
        如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表97->a
        如果使用自己特有方法print/println写数据,写的数据原样输出
public static void main(String[] args) throws FileNotFoundException {
        //创建打印流PrintStream对象,构造方法中绑定要输出的目的地
        PrintStream ps = new PrintStream("E:\\Java\\src\\darkhouse\\demo23_ObjectStream\\print.txt");
        //如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表97->a
        ps.write(97);

        //如果使用自己特有方法print/println写数据,写的数据原样输出
        ps.println(97);
        ps.println(8.8);
        ps.println("a");
        ps.println("你好");
        ps.println(true);

        //释放资源
        ps.close();
    }

改变打印流的流向

可以改变输出语句的目的地(打印流的流向)
    输出语句,默认在控制台输出
    使用System.setOut方法改变输出语句的目的地改为参数传递的打印流的目的地
        statics void setOut(PrintStream out)
        重新分配"标准"输出流
public static void main(String[] args) throws FileNotFoundException {
        System.out.println("控制台输出");

        PrintStream ps = new PrintStream("E:\\Java\\src\\darkhouse\\demo23_ObjectStream\\目的地是打印流.txt");
        System.setOut(ps);//把输出语句的目的地改为打印流的目的地
        System.out.println("我在打印流的目的地输出");
        ps.close();
    }

网络编程

网络编程入门

1.1软件结构

C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件
B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等
    
网络编程,就是在一定的协议下,实现两台计算机的通信的程序

1.2 网络通信协议

网络通信协议:
    通信协议是对计算机必须遵守的规则,只有遵守这些规则,计算机之间才能进行通信。这就好比在道路中行驶的汽车一定要遵守交通规则一样,协议中对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守,最终完成数据交换
    
TCP/IP协议:
    传输控制协议/因特网互联协议,是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Mjhtv56-1638929155463)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210803183735844.png)]

1.3 协议分类

java.net包中提供了两种常见的网络协议的支持:

TCP(打电话):
	传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输
	三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
        第一次握手,客户端向服务器端发出连接请求,等待服务器确认
        第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
        第三次握手,客户端再次向服务器端发送确认信息,确认连接
  	完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等

UDP(发短信):
	用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中,例如视频会议、QQ聊天等

1.4 网络编程三要素

协议
计算机网络通信必须遵守的规则
IP地址
指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”
IP地址分类
IPv4:
	是一个32位的二进制数,通常被分为4个字节,表示成 a.b.c.d 的形式,例如 192.168.65.100 。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个

IPv6:
	由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。有资料显示,全球IPv4地址在2011年2月分配完毕为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789 ,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题
常用命令
ipconfig //查看本机IP地址
ping 空格 IP地址 //检查网络是否连通
netstat -ano  //查看所有端口
netstat -ano|findstr "5900"//查看指定的端口
tasklist|findstr "12240"//查看指定端口的进程   
端口号
如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了

端口分类
	公有端口:0-1023
		HTTP:80(网络端口)
		HTTPS:443
		FTP:21
		Telent:23
    程序注册端口:1024-49151,分配用户或者程序
         Tomcat:8080
         MySQL:3306
         Oracle:1521

TCP通信程序

概述

TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EPaMReGN-1638929155463)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210804141240748.png)]

TCP通信的客户端代码实现

TCP通信的客户端:向服务器发送连接请求,给服务器发送数据,读取服务器回写的数据
    表示客户端的类:
        java.net.socket:此类实现客户端套接字,套接字是两台机器间通信的端点
        套接字:包含了IP地址和端口号的网络单位

    构造方法:
        Socket(String host, int port):创建套接字对象并将其连接到指定主机上的指定端口号
        参数:
            String host:服务器主机名称/服务器IP地址
            int port:服务器端口号

    成员方法:
        OutputStream getOutputStream():返回此套接字的输出流
        InputStream getInputStream():返回此套接字的输入流
        void close():关闭此套接字
    实现步骤:
        1.创建一个客户端对象Socket,构造方法中绑定服务器的IP地址和端口号
        2.使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
        3.使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
        4.使用Socket对象中的方法getInputStream()获取网络字节输入流对象InputStream
        5.使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
        6.释放资源(Socket)
    注意:
        1.客户端和服务器端进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象
        2.当我们创建客户端对象Socket对象的时候,就会去请求服务器和服务器经过三次握手建立通路
            如果服务器没有启动,就会抛出异常
            如果服务器已经启用,那么就可以进行交互
public static void main(String[] args) throws IOException {
        //1.创建一个客户端对象Socket,构造方法中绑定服务器的IP地址和端口号
        Socket socket = new Socket("127.0.0.1",8888);
        //2.使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
        OutputStream outputStream = socket.getOutputStream();
        //3.使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
        outputStream.write("你好,服务器".getBytes());
        //4.使用Socket对象中的方法getInputStream()获取网络字节输入流对象InputStream
        InputStream is = socket.getInputStream();
        //5.使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
        byte[] bytes = new byte[1024];
        int len=is.read(bytes);
        System.out.println(new String(bytes,0,len));
        //6.释放资源(Socket)
        socket.close();
    }

TCP通信的服务器端代码实现

TCP通信的服务器端:接收客户端的请求,读取客户端发送的数据,给客户端回写数据
    表示服务器的类:
        java.net.ServerSocket:此类表示服务器套接字
    构造方法:
         ServerSocket(int port):创建绑定到特定端口的服务器套接字

     服务器端必须明确一件事:必须指导是哪个客户端请求的服务器,可使用accept方法获取到请求的客户端对象Socket
     成员方法:
        Socket accept():侦听并接收到此套接字的连接

    服务器实现步骤:
        1.创建服务器ServerSocket对象和系统要指定的端口号
        2.使用ServerSocket对象中的方法accept,获取到请求的客户端对象Socket
        3.使用Socket对象中的方法getInputStream()获取网络字节输入流对象InputStream
        4.使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
        5.使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
        6.使用网络字节输出流OutputStream对象中的方法write给客户端回写数据
        7.释放资源
public static void main(String[] args) throws IOException {
        //1.创建服务器ServerSocket对象和系统要指定的端口号
        ServerSocket server = new ServerSocket(8888);
        //2.使用ServerSocket对象中的方法accept,获取到请求的客户端对象Socket
        Socket socket = server.accept();
        //3.使用Socket对象中的方法getInputStream()获取网络字节输入流对象InputStream
        InputStream is = socket.getInputStream();
        //4.使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
        byte[] bytes = new byte[1024];
        int len=is.read(bytes);
        System.out.println(new String(bytes,0,len));
        //5.使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //6.使用网络字节输出流OutputStream对象中的方法write给客户端回写数据
        os.write("收到谢谢".getBytes());
        //7.释放资源
        os.close();
        server.close();
    }

综合案例

【客户端】输入流,从硬盘读取文件数据到程序中
【客户端】输出流,写出文件数据到服务端
【服务端】输入流,读取文件数据到服务端程序
【服务端】输出流,写出文件数据到服务器硬盘中

客户端

文件上传案例的客户端:读取本地文件,上传到服务器,读取服务器回写的数据

明确:
    数据源:c:ll1.jpg目的地:服务器
实现步骤:
1.创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
2.创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
3.使用Socket中的方法getOutputStream,获取网络字节输出流OutputStream对象
4.使用本地字节输入流FileInputStream对象中的方法read,读取本地文件
5.使用网络字节输出流outputStream对象中的方法write,把读取到的文件上传到服务器
6.使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象
7.使用网络字节输入流InputStream对象中的方法read读取服务回写的数据
8.释放资源(FiLeInputStream, socket)
public static void main(String[] args) throws IOException {
        //1.创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("D:\\1.txt");
        //2.创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
        Socket socket = new Socket("127.0.0.1",8888);
        //3.使用Socket中的方法getOutputStream,获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //4.使用本地字节输入流FileInputStream对象中的方法read,读取本地文件
        int len=0;
        byte[] bytes = new byte[1024];
        while ((len=fis.read())!=-1){
            //5.使用网络字节输出流outputStream对象中的方法write,把读取到的文件上传到服务器
            os.write(bytes,0,len);
        }
        //6.使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //7.使用网络字节输入流InputStream对象中的方法read读取服务回写的数据
        while ((len=is.read())!=-1){
            //5.使用网络字节输出流outputStream对象中的方法write,把读取到的文件上传到服务器
            System.out.println(new String(bytes,0,len));
        }
        //8.释放资源(FiLeInputStream, socket)
        fis.close();
        socket.close();
    }

服务器

    文件上传案例服务器端:读取客户端上传的文件,保存到服务器的硬盘,给客户端回写"上传成功”

    明确:
        数据源:客户端上传的文件
        目的地:服务器的硬盘d: i lupLoad\l1.jpg

    实现步骤:
    1.创建一个服务器ServerSocket对象,和系统要指定的端口号
    2.使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
    3.使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
    4 .判断d:\\upLoad文件夹是否存在,不存在则创建
    5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
    6.使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
    7.使用本地字节输出流FiLeOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
    8.使用Socket对象中的方法getOutputStream,获取到网络字节输出流outputStream对象
    9.使用网络字节输出流outputStream对象中的方法Write,给客户端回写"上传成功”
    10.释放资源(FileOutputStream, Socket , ServerSocked
public static void main(String[] args) throws IOException {
        //1.创建一个服务器ServerSocket对象,和系统要指定的端口号
        ServerSocket serverSocket = new ServerSocket(8888);
        //2.使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
        Socket socket = serverSocket.accept();
        //3.使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //4 .判断d:\\upLoad文件夹是否存在,不存在则创建
        File file = new File("E:\\upload");
        if(!file.exists()){
            file.mkdir();
        }
        //5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
        FileOutputStream fos= new FileOutputStream(file+"\\1.txt");
        //6.使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
        int len=0;
        byte[] bytes = new byte[1024];
        while ((len=is.read(bytes))!=-1){
            //7.使用本地字节输出流FiLeOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
            fos.write(bytes,0,len);
        }
        //8.使用Socket对象中的方法getOutputStream,获取到网络字节输出流outputStream对象
        //9.使用网络字节输出流outputStream对象中的方法Write,给客户端回写"上传成功”
        socket.getOutputStream().write("上传成功".getBytes());
        //10.释放资源(FileOutputStream, Socket , ServerSocked
        fos.close();
        socket.close();
        serverSocket.close();
    }

函数式接口

函数式接口

1.1 概念

函数式接口在Java中是指:有且仅有一个抽象方法的接口

函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导

1.2 格式

只要确保接口中有且仅有一个抽象方法即可:
    
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}

1.4 函数式接口的使用

MyFunctionalInterface接口

/*
    函数式接口:有且只有一个抽象方法的接口,称之为函数式接口
    当然接口中可以包含其他的方法(默认,静态,私有)

    @FunctionalInterface注解
    作用:可以检测接口是否是一个函数式接口
        是:编译成功
        否:编译失败(接口中没有抽象方法,抽象方法的个数不止一个)
 */
@FunctionalInterface
public interface MyFunctionalInterface {
    //定义一个抽象方法
    public abstract void method();
}

MyFunctionalInterfaceImpl实现类

public class MyFunctionalInterfaceImpl implements MyFunctionalInterface{
    @Override
    public void method() {

    }
}

main方法

/*
    函数式接口的使用:一般可以作为方法的参数和返回值类型
 */
public class Demo {
    //定义一个方法,参数使用函数式接口MyFunctionalInterface
    public static void show(MyFunctionalInterface myInter){
        myInter.method();
    }

    public static void main(String[] args) {
        //调用show方法,方法的参数是接口,所以可以传递接口的实现类对象
        show(new MyFunctionalInterfaceImpl());

        //调用show方法,方法的参数是接口,所以我们可以传递接口的匿名内部类
        show(new MyFunctionalInterface() {
            @Override
            public void method() {
                System.out.println("使用匿名内部类重写接口中的抽象方法");
            }
        });

        //调用show方法,方法的参数是函数式接口,所以可以传递Lambda表达式
        show(()->{
            System.out.println("使用Lambda表达式重写接口中的抽象方法");
        });
    }
}

函数式编程

2.1 Lambda的延迟执行

日志案例
    发现以下代码存在性能浪费问题
    调用showLog方法,传递的第二个参数是拼接后的字符串
    先把字符串拼接好,再调用showLog方法
    showLog方法中如果传递的日志等级不是1级
    那么就不会输出拼接后的字符串
    所以感觉字符串白拼接,所以存在一些浪费
//定义一个根据日志的级别,显示日志信息的方法
    public static void showLog(int level,String message){
        //对日志等级进行判断,如果是1级别,那么输出日志信息
        if (level==1){
            System.out.println(message);
        }
    }
    public static void main(String[] args) {
        //定义三个日志信息
        String msg1="Hello";
        String msg2="World";
        String msg3="Java";

        //调用showLog方法,传递日志级别和日志信息
        showLog(1,msg1+msg2+msg3);
    }

进行优化

MessageBuilder接口

@FunctionalInterface
public interface MessageBuilder {
    //定义一个拼接消息的抽象方法,返回被拼接的消息
    public abstract String builderMessage();
}
使用Lambda优化日志案例
    Lambda特点:延迟加载
    Lambda的使用前提,必须存在函数式接口
//定义一个显示日志的方法,方法的参数传递日志等级和MessageBuilder接口
    public static void showLog(int level,MessageBuilder mb){
        //对日志的等级进行判断,如果是一级,则调用MessageBuilder接口中的builderMessage方法
        if (level==1){
            System.out.println(mb.builderMessage());
        }
    }

    public static void main(String[] args) {
        //定义三个日志信息
        String msg1="Hello";
        String msg2="World";
        String msg3="Java";

        //调用showLog方法,参数MessageBuilder是一个函数式接口,所以可以传递Lambda表达式
        showLog(1,()->{
            //返回一个拼接好的字符串
            return msg1+msg2+msg3;
        });

        /*
        使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中
        只有满足条件,日志的等级是1级
            才会调用接口MessageBuilder中的方法builderMessage
            才会进行字符串的拼接
        如果条件不满足,白志的等级不是1级
            那么MessageBuilder接口中的方法builderMessage也不会执行
            所以拼接字符串的代码也不会执行
        所以不会存在性能的浪费
         */
    }

2.2 使用Lambda作为参数和返回值

/*
     java.lang.Runnable 接口就是一个函数式接口
     假设有一个 startThread 方法使用该接口作为参数,那么就可以使用Lambda进行传参
     这种情况其实和 Thread 类的构造方法参数为 Runnable没有本质区别
 */
public class Demo03Runnable {
    //定义一个startThread,方法的参数使用函数式接口Runnable
    public static void startThread(Runnable run){
        //开启多线程
        new Thread(run).start();
    }

    public static void main(String[] args) {
        //调用startThread方法,方法的参数是一个接口,那么我们可以传递接口的匿名内部类
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了");
            }
        });

        startThread(()->{
            System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了");
        });

        //优化Lambda
        startThread(()-> System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了"));
    }
}

2.3 使用Lambda作为参数和返回值

如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式
当需要通过一个方法来获取一个 java.util.Comparator 接口类型的对象作为排序器时,就可以调该方法获取
//定义一个方法  方法的返回值类型使用函数式接口Comparator
    public static Comparator<String> getComparator() {
        //方法的返回值类型是一个接口,那么我们可以返回这个接口的匿名内部类
//        return new Comparator<String>() {
//            @Override
//            public int compare(String o1, String o2) {
//                //按照字符串的降序排序
//                return o2.length() - o1.length();
//            }
//        };

        //方法的返回值是一个函数式接口,所以我们可以返回一个Lambda表达式
//        return (String o1, String o2) -> {
//            return o2.length() - o1.length();
//        };

        return (o1,o2)-> o2.length() - o1.length();

    }

    public static void main(String[] args) {
        //创建一个字符串数组
        String[] arr={"aaa","b","cccc","dddddddddddd"};
        //输出排序前的数组
        System.out.println(Arrays.toString(arr));
        //调用Array中的sort方法,对字符串数组进行排序
        Arrays.sort(arr,getComparator());
        //输出排序后的数组
        System.out.println(Arrays.toString(arr));
    }

常用函数式接口

JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function 包中被提供
下面是最简单的几个接口及使用示例:

3.1 Supplier接口

常用的函数式接口
    java.util.function.Supplier<T> 接口仅包含一个无参的方法: T get().用来获取一个泛型参数指定类型的对象数据

    Supplier<T>接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口的get方法就会生产什么类型的数据
//定义一个方法,方法的参数传递Supplier<T>接口,泛型指定String类型,get就会返回一个String
    public static String getString(Supplier<String> supplier){
        return supplier.get();
    }

    public static void main(String[] args) {
        //调用getString方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
        System.out.println(getString(() -> "Sherlock"));
    }

3.2 练习

求数组的最大值
使用 Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。
提示:接口的泛型请使用java.lang.Integer类
//定义一个方法,求数组的最大值
public static int getMax(Supplier<Integer> supplier){
    return supplier.get();
}

public static void main(String[] args) {
    //定义一个数组
    int[] arr={6,100,-20,90,999};
    //调用getMax方法,因为此方法参数是函数式接口,所以可以使用Lambda表达式
    int result = getMax(() -> {
        int max = arr[0];
        //遍历数组
        for (int i : arr) {
            if (i > max) {
                max = i;
            }
        }
        return max;
    });
    System.out.println(result);
}

3.3 Consumer接口

抽象方法:accept
    java.util.function.Consumer<T> 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定

    Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据
    至于怎么消费(使用)这个数据,需要自定义(输出,计算......)
    /*
        定义一个方法
        方法的参数传递字符串的姓名
        方法的参数传递Consumer接口,泛型使用String
        可以使用Consumer接口,消费字符串的姓名
     */
    public static void method(String name, Consumer<String> consumer){
        consumer.accept(name);
    }

    public static void main(String[] args) {
        //调用method方法,传递字符串的姓名,方法的另一个参数是Consumer接口,是一个函数式接口,所以可以传递Lambda表达式
        method("Sherlock",(String name)->{
            //消费方式:直接输出
            System.out.println(name);

            //消费方式:把字符串反转
            System.out.println(new StringBuilder(name).reverse());
        });
    }
默认方法:andThen
Consumer接口的默认方法:andThen
    作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,在对数据进行消费

例如:
    Consumer<String> con1
    Consumer<String> con2
    String s ="hello";
    con1.accept(s);
    con2.accept(s);

    以上的五行代码和以下代码等效:
    连接两个Consumer接口,再进行消费
    con1.andThen(con2).accept(s);谁写前边谁先消费
//定义一个方法  方法的参数传递一个字符串和两个Consumer接口,Consumer接口的泛型使用字符串
    public static void method(String name, Consumer<String> consumer1,Consumer<String> consumer2){
        //consumer1.accept(name);
        //consumer2.accept(name);
        //以上两行代码和下一行代码的实现结果相同
        
        consumer1.andThen(consumer2).accept(name);
    }

    public static void main(String[] args) {
        method("Sherlock",
                (String name)->{
                    System.out.println(name.toUpperCase());
                },
                (String name)->{
                    System.out.println(name.toLowerCase());
                }
                );
    }

3.4 练习

    下面的字符串数组当中存有多条信息,请按照格式“ 姓名:XX。性别:XX。 ”的格式将信息打印出来
    要求将打印姓名的动作作为第一个Consumer接口的Lambda实例
    将打印性别的动作作为第二个Consumer接口的Lambda实例,将两个Consumer接口按照顺序“拼接”到一起
public static void main(String[] args) {
        String[] array = {"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男"};
        method(array,
                (t)->{
                    String name=t.split(",")[0];
                    System.out.print("姓名:"+name+"  ");
                },
                (t)->{
                    String sex=t.split(",")[1];
                    System.out.println("性别:"+sex);
                }
                );
    }


    //定义一个方法,参数传递String类型的数组和两个Consumer接口,泛型使用String
    public static void method(String[] arr, Consumer<String> consumer1,Consumer<String> consumer2){
        for (String s : arr) {
            consumer1.andThen(consumer2).accept(s);
        }
    }

3.5 Predicate接口

抽象方法:test
    java.util.function.Predicate<T>接口
    作用:对某种数据类型的数据进行判断,结果返回一个boolean值

    Predicate接口中包含一个抽象方法:
        boolean test(T t):用来对指定数据类型的数据进行判断的方法
            结果:
                符合条件:true
                不符合条件:false
    /*
        定义一个方法
        参数传递一个String类型的字符串
        传递一个Predicate接口,泛型使用String
        使用Predicate中的方法test对字符串进行判断,并把判断的方法返回
     */
    public static boolean method(String str, Predicate<String> predicate){
        return predicate.test(str);
    }

    public static void main(String[] args) {
        //定义一个字符串
        String str="Sherlock";

        //调用method方法,对字符串进行校验,参数传递字符串和Lambda表达式
//        boolean result = method(str, (String s) -> {
//            return str.length()>6;
//        });

        //优化lambda
        boolean result = method(str,s->str.length()>6);
        System.out.println(result);
    }
默认方法:and
    逻辑表达式:可以连接多个判断的条件
    &&:与运算符,有false则false
    ∶或运算符,有true则true
    !:非(取反)运算符,非真则假,非假则真

    需求:判断一个字符串,有两个判断的条件
        1.判断字符串的长度是否大于5
        2.判断字符串中是否包含a
        两个条件必须同时满足,我们就可以使用&&运算符连接两个条件

    Predicate接口中有一个方法and,表示并且关系,也可以用于连接两个判断条件
    default Predicate<T> and(Predicate<? super T> other) {
        objects.requireNonNull(other);
        return (t) -> this.test(t) && other.test(t);
    }
    方法内部两个判断条件,也是使用&&运算符连接起来的
/*
        定义一个方法,方法的参数,传递一个字符串
        传递两个Predicate接口
            一个用于判断字符串的长度是否大于5
            一个用于判断字符串中是否包含a
            两个条件必须同时满足
     */
    public static boolean checkString(String string, Predicate<String> predicate1,Predicate<String> predicate2){
        return predicate1.test(string) && predicate2.test(string);
    }

    public static void main(String[] args) {
        //定义一个字符串
        String string="aSherlock";
        //调用checkString方法,参数传递字符串和两个Lambda表达式
        boolean result = checkString(string,
                (String s) -> {
                    return s.length()>5;
        },
                (String s) -> {
                    return s.contains("a");
        });
        System.out.println(result);
    }
默认方法:or
    逻辑表达式:可以连接多个判断的条件
    &&:与运算符,有false则false
    ∶或运算符,有true则true
    !:非(取反)运算符,非真则假,非假则真

    需求:判断一个字符串,有两个判断的条件
        1.判断字符串的长度是否大于5
        2.判断字符串中是否包含a
        两个条件满足其中一个即可,我们就可以使用||运算符连接两个条件

    Predicate接口中有一个方法or,表示或者关系,也可以用于连接两个判断条件
    default Predicate<T> or(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) ‐> test(t) || other.test(t);
    }
    方法内部两个判断条件,也是使用||运算符连接起来的
/*
        定义一个方法,方法的参数,传递一个字符串
        传递两个Predicate接口
            一个用于判断字符串的长度是否大于5
            一个用于判断字符串中是否包含a
            两个条件满足其中一个即可
     */
    public static boolean method(String string, Predicate<String> predicate1, Predicate<String> predicate2) {
        return predicate1.or(predicate2).test(string);//等价于return predicate1.test(string) || predicate2.test(string);
    }


    public static void main(String[] args) {
        //定义一个字符串
        String string = "Sherlock";
        //调用method方法
        boolean result = method(string,
                (String str) -> {
                    return str.length() > 5;
                },
                (String str) -> {
                    return str.contains("a");
                });
        System.out.println(result);
    }
默认方法:negate
    需求:判断一个字符串的长度是否大于5
        如果字符串的长度大于5:返回false
        如果字符串长度不大于5:返回true
    所以我们可以使用取反符号,对判断的结果进行取反

    Predicate接口中有一个方法negate,表示取反的意思
    default Predicate<T> negate() {
    return (t)> !test(t);
    }
/*
        定义一个方法,方法的参数传递一个字符串
        使用Predicate接口判断字符串的长度是否大于5
     */
    public static boolean method(String string, Predicate<String> predicate) {
        return predicate.negate().test(string);
    }

    public static void main(String[] args) {
        //定义一个字符串
        String string = "Sherlock";
        //调用method方法
        boolean result = method(string, (String str) -> {
            return str.length() > 5;
        });
        System.out.println(result);
    }

3.6 练习:集合信息筛选

    数组当中有多条“姓名+性别”的信息如下,请通过 Predicate 接口的拼装将符合要求的字符串筛选到集合
    ArrayList 中,需要同时满足两个条件:
    1. 必须为女生
    2. 姓名为4个字
 /*
        定义一个方法
        方法的参数传递一个包含人员信息的数组
        传递两个Predicate接口,用于对数组信息进行过滤
        把满足条件的信息存到ArrayList集合中并返回
     */
    public static ArrayList<String> method(String[] arr, Predicate<String> predicate1, Predicate<String> predicate2) {
        //定义一个集合,存储过滤之后的信息
        ArrayList<String> list = new ArrayList<>();
        //遍历数组
        for (String s : arr) {
            boolean result = predicate1.and(predicate2).test(s);
            if (result) {
                list.add(s);

            }
        }
        return list;
    }

    public static void main(String[] args) {
        //定义一个数组
        String[] array = {"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男"};
        //调用method方法
        ArrayList<String> result = method(array, (str) -> {
            String s1 = str.split(",")[0];
            return s1.length() == 4;
        }, (str) -> {
            String s2 = str.split(",")[1];
            return s2.contains("女");
        });
        System.out.println(result.toString());
    }

3.7 Function接口

java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件
抽象方法:apply
java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据
    前者称为前置条件,后者称为后置条件
Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。
    使用的场景例如:将string类型转换为Integer类型
 /*
        定义一个方法
        方法的参数传递一个字符串类型的数据
        方法的参数传递一个Function接口,泛型使用<String,Integer>
        使用Function接口中的apply方法把字符串类型的整数转换为Integer类型的整数
     */
    public static void method(String string, Function<String,Integer> function){
        Integer result = function.apply(string);
        System.out.println(result);
    }

    public static void main(String[] args) {
        //定义一个字符串
        String string="123";
        //调用method方法,传递字符串和Lambda表达式
//        method(string,(String str)->{
//            return Integer.parseInt(str);
//        });

        //优化Lambda
        method(string,(String str)-> Integer.parseInt(str));
    }
默认方法:andThen
    Function接口中的黑认方法andThen:用来进行组合操作

    需求:
        把string类型的"123",转换为Integer类型,把转换后的结果加10
        把增加之后的Integer类型的数据,转换为string类型
    分析:
        转换了两次
        第一次是把string类型转换为了Integer类型
            所以我们可以使用Function<string, Integer> fun1
                Integer i = fun1.apply ( "123")+10;
        第二次是把Integer类型转换为string类型
            所以我们可以使用Function<Integer,String> fun2
                string s = fun2.appLy (i);
        我们可以使用andThen方法,把两次转换组合在一起使用
            String s = fun1.andThen(fun2).apply( "123");
            fun1先调用apply方法,把字符串转换为Integer
            fun2再调用apply方法,把integer转换为字符串
/*
    定义一个方法
    参数传递一个字符串类型的整数
    参数再传递两个Function接口
    一个泛型使用Function<String , Integer>
    一个泛型使用Function<Integer, string>
 */
public static void method(String string, Function<String,Integer> function1,Function<Integer,String> function2){
    String result = function1.andThen(function2).apply(string);
    System.out.println(result);
}

public static void main(String[] args) {
    //定义一个字符出啊
    String string ="123";
    //调用method方法
    method(string,
            (String str)->{
                int i = Integer.parseInt(str)+10;
                return i;
            },
            (Integer integer)->{
                return integer+"";
            }
            );
}

3.8 练习

请使用 Function 进行函数模型的拼接,按照顺序需要执行的多个函数操作为:
    String str = "赵丽颖,20";
    1. 将字符串截取数字年龄部分,得到字符串;
    2. 将上一步的字符串转换成为int类型的数字;
    3. 将上一步的int数字累加100,得到结果int数字。
public static void method(String string, Function<String, Integer> function) {
    Integer integer = function.apply(string);
    System.out.println(integer);
}

public static void main(String[] args) {
    //定义字符出啊
    String string = "赵丽颖,20";
    //调用method方法
    method(string, (String str) -> {
        String str_number = str.split(",")[1];
        int number = Integer.parseInt(str_number)+100;
        return number;
    });
}

Stream流

1.1 引言

循环遍历的弊端

public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
List<String> zhangList = new ArrayList<>();
for (String name : list) {
if (name.startsWith("张")) {
zhangList.add(name);
}
}
List<String> shortList = new ArrayList<>();
for (String name : zhangList) {
if (name.length() == 3) {
shortList.add(name);
}
}
for (String name : shortList) {
System.out.println(name);
}
}
这段代码中含有三个循环,每一个作用不同:
    1. 首先筛选所有姓张的人
    2. 然后筛选名字有三个字的人
    3. 最后进行对结果进行打印输出
    每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始
    
那,Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢?

Stream的更优写法

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("张无忌");
    list.add("周芷若");
    list.add("赵敏");
    list.add("张强");
    list.add("张三丰");
    list.stream()
    .filter(s ‐> s.startsWith("张"))
    .filter(s ‐> s.length() == 3)
    .forEach(System.out::println);
}


直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。代码中并没有体现使用线性循环或是其他任何算法进行遍历,我们真正要做的事情内容被更好地体现在代码中

1.2 流式思想概述

Stream流中的一些方法,例如filter 、 map 、 skip 都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法 count执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性

Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)

Stream(流)是一个来自数据源的元素队列
	元素是特定类型的对象,形成一个队列.ava中的Stream并不会存储元素,而是按需计算
	数据源 流的来源。 可以是集合,数组等

和以前的Collection操作不同, Stream操作还有两个基础的特征:
	Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style.这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)
	内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法
	
	当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道

1.3 获取流

    java.util.stream.Stream<T>是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)
    获取一个流非常简单,有以下几种常用的方式:
        -所有的collection集合都可以通过stream默认方法获取流;
            default stream<E> stream (
        - Stream接口的静态方法of可以获取数组对应的流。
            static <T> Stream<T> of ( T... values)
            参数是一个可变参数,那么我们就可以传递一个数组
public static void main(String[] args) {
        //把集合转换为流
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream();

        HashSet<String> set = new HashSet<>();
        Stream<String> stream1 = set.stream();

        HashMap<String, String> map = new HashMap<>();
        //获取键
        Set<String> keySet = map.keySet();
        Stream<String> stream2 = keySet.stream();

        //获取值
        Collection<String> values = map.values();
        Stream<String> stream3 = values.stream();

        //获取键值对的映射关系
        Set<Map.Entry<String, String>> entries = map.entrySet();
        Stream<Map.Entry<String, String>> stream4 = entries.stream();

        //把数组转化为Stream流
        Stream<Integer> Stream2 = Stream.of(1, 2, 3, 4, 5, 6);
        //可变参数可以传递数组
        Integer[] arr={1,2,3,4,5};
        Stream<Integer> arr1 = Stream.of(arr);

        String[] arr2={"a","b","c"};
        Stream<String> arr21 = Stream.of(arr2);
    }

1.4 常用方法

forEach

    Stream流中的常用方法_forEach
    void forEach( consumer< ? super T>action );
    该方法接收一个consumer接口函数,会将每一个流元素交给该函数进行处理。
    consumer接口是一个消费型的函数式接口,可以传递Lambda表达式,消费数据

    简单记:
        forEach方法,用来遍历流中的数据
        是一个终结方法,遍历之后就不能纠继卖调用Stream流中的其他方法
public static void main(String[] args) {
        //获取一个Stream流
        Stream<String> stream = Stream.of("张三", "李四", "王五");
        //使用Stream流中的方法forEach对Stream流中的数据进行遍历
//        stream.forEach((String name)->{
//            System.out.println(name);
//        });

        //优化Lambda
        stream.forEach(name-> System.out.println(name));
    }

//运行结果:
张三
李四
王五

filter

    Stream流中的常用方法_filter:用于对Stream流中的数据进行过滤
    Stream<T> filter(Predicate<? super T> predicate);
    filter方法的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤
    过滤完成会返回一个新的流
    Predicate中的抽象方法:
    boolean test(T t);
    public static void main(String[] args) {
        //创建一个Stream流
        Stream<String> stream = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");
        //对Stream流中的元素进行过滤,只要姓张的人
        Stream<String> stream2 = stream.filter((String name) -> {
            return name.startsWith("张");
        });
        //遍历Stream2
        stream2.forEach((String name)->{
            System.out.println(name);
        });

        /*
        Stream流属于管道流,只能被消费(使用)一次
        第一个stream流调用完毕方法,数据就会流转到下一个stream上而这时第一个stream流已经使用完毕,就会关闭了
        所以第一个stream流就不能再调用方法了
        IllegalStateException:stream has already been operated upon or closed
         */
    }

//运行结果:
张三丰
张翠山
张无忌

map

    如果需要将流中的元素映射到另一个流中,可以使用map方法.
    <R>Stream<R> map(Function<? super T, ? extends R> mapper);
    该接口需要一个Function函数式接口参数,可以将当前流中的7类型数据转换为另一种R类型的流。Function中的抽象方法:
    R appLy(T t);
public static void main(String[] args) {
        //获取一个String类型的Stream流
        Stream<String> stringStream = Stream.of("1", "2", "3", "4", "5", "6");
        //使用map方法,把字符串类型的整数,转换(映射)为Integer类型的整数
        Stream<Integer> IntegerStream=stringStream.map((String name)->{
            return Integer.parseInt(name);
        });

        //遍历
        IntegerStream.forEach((Integer number)->{
            System.out.println(number);
        });
    }
//运行结果:
1
2
3
4
5
6

count

    Stream流中的常用方法_count:用于统计stream流中元素的个数
    long count( );
    count方法是一个终结方法,返回值是一个long类型的整数
    所以不能再继续调用Stream流中的其他方法了
public static void main(String[] args) {
        //获取一个Stream流
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);

        Stream<Integer> stream = list.stream();
        long count = stream.count();
        System.out.println(count);
    }

//运行结果:
6

limit

    Stream流中的常用方法_limit:用于截取流中的元素
    Limit方法可以对流进行截取,只取用前n个
    Stream<T> limit(Long maxsize);
        参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作
    limit方法是一个延迟方法,只是对流中的元素进行截取,返回的是一个新的流,所以可以继续调用stream流中的其他方法
    public static void main(String[] args) {
        //获取一个Stream流
        String[] arr={"Sherlock","Jone","Jack"};
        Stream<String> stream = Stream.of(arr);
        Stream<String> stream1 = stream.limit(2);

        //遍历stream1
        stream1.forEach((String name)->{
            System.out.println(name);
        });
    }

//运行结果:
Sherlock
Jone

skip

    Stream流中的常用方法_skip:用于跳过元素
    如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:Stream<T> skip( Long n);
    如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为3的空流
    public static void main(String[] args) {
        //获取一个Stream流
        String[] arr={"Sherlock","Jone","Jack"};
        Stream<String> stream1 = Stream.of(arr);
        //使用skip方法跳过第一个元素
        Stream<String> stream2 = stream1.skip(1);
        //遍历stream2
        stream2.forEach((String name)->{
            System.out.println(name);
        });
    }

//运行结果:
Jone
Jack

concat

    Stream流中的常用方法_concat:用于把流组合到一起
    如果有两个流,希望合并成为一个流,那么可以使用stream接口的静态方法concat
    static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
    public static void main(String[] args) {
        //获取一个String类型的Stream流
        Stream<String> stream1 = Stream.of("1", "2", "3", "4", "5", "6");
        //获取一个Stream流
        String[] arr={"Sherlock","Jone","Jack"};
        Stream<String> stream2 = Stream.of(arr);
        //把以上两个流组合成一个流
        Stream<String> concat = Stream.concat(stream1, stream2);
        //遍历concat
        concat.forEach((String name)->{
            System.out.println(name);
        });
    }

//运行结果:
1
2
3
4
5
6
Sherlock
Jone
Jack

1.5 练习

集合元素处理

    1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中
    2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中
    3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中
    4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中
    5. 将两个队伍合并为一个队伍;存储到一个新集合中
    6. 根据姓名创建 Person 对象;存储到一个新集合中
    7. 打印整个队伍的Person对象信息
public static void main(String[] args) {
        //第一支队伍
        ArrayList<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("庄子");
        one.add("洪七公");
        //1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中
        //2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中
        Stream<String> stream = one.stream();
        Stream<String> streamOne = stream.filter((String name) -> {
            return name.length() == 3;
        }).limit((3));


        //第二支队伍
        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("赵丽颖");
        two.add("张三丰");
        two.add("尼古拉斯赵四");
        two.add("张天爱");
        two.add("张二狗");

        //3.第二个队伍只要姓张的成员姓名;存储到一个新集合中
        //4.第二个队伍筛选之后不要前2个人;存储到一个新集合中
        Stream<String> stream1 = two.stream();
        Stream<String> streamTwo = stream1.filter((String name) -> {
            return name.startsWith("张");
        }).skip(2);

        //5.将两个队伍合并为一个队伍;存储到一个新集合中
        //6.根据姓名创建 Person 对象:存储到一个新集合中
        //7.打印整个队伍的Person对象信息
        Stream.concat(streamOne, streamTwo).map(name -> new Person(name)).forEach(person -> System.out.println(person));
    }

注解和反射

什么是注解

Annotation是从JDK5.0开始引入的新技术.

Annotation的作用:
	不是程序本身,可以对程序作出解释.(这一点和注释(comment)没什么区别)
	可以被其他程序(比如:编译器等)读取.
	
Annotation的格式∶
	注解是以"@注释名"在代码中存在的,还可以添加一些参数值
	例如:@SuppressWarnings(value="unchecked").
	
Annotation在哪里使用?
可以附加在package , class , method , field等上面﹐相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问

//什么是注解
public class Test01 {

    //@Override:重写的注解
    @Override
    public String toString() {
        return super.toString();
    }
}

内置注解

@Override:定义在java.lang.Override 中,此注释只适用于修辞方法﹐表示一个方法声明打算重写超类中的另一个方法声明.

@Deprecated:定义在java.lang.Deprecated中,此注释可以用于修辞方法﹐属性﹐类,表示不鼓励程序员使用这样的元素﹐通常是因为它很危险或者存在更好的选择.

@suppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息.
    与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们选择性的使用就好了.
    @SuppressWarnings("all")
    @SuppressWarnings("unchecked")
    @SuppressWarnings(value={"unchecked","deprecation"})
    等等.......
//@Override:重写的注解
    @Override
    public String toString() {
        return super.toString();
    }
//@Deprecated:不推荐程序员使用,但是可以使用,或者 存在更好的方式
    @Deprecated
    public static void test(){
        System.out.println("Deprecated");
    }
//@SuppressWarnings:用来抑制编译时的警告信息
@SuppressWarnings("all")
    public void test01(){
        ArrayList<Object> list = new ArrayList<>();
    }

元注解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7fH6cPol-1638929155463)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810140611025.png)]

//测试元注解
public class Test02 {
    
    @MyAnnotation
    public void test(){}
}

//定义一个注解
//Target:表示我们的注解可以用在哪些地方
@Target(value = ElementType.METHOD)

//

@interface MyAnnotation{
}

//ElementType.METHOD表示只能在方法中使用,若要放在类上,会报错

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-igMPsxk2-1638929155463)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810141403462.png)]

经过修改,加入ElementType.TYPE就不会报错
//测试元注解
@MyAnnotation
public class Test02 {

    @MyAnnotation
    public void test(){}
}

//定义一个注解
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@interface MyAnnotation{
}

举例:

//定义一个注解
Target:表示我们的注解可以用在哪些地方
@Target(value = {ElementType.METHOD,ElementType.TYPE})

//Retention:表示我们的注解在什么地方还有效
//RUNTIME>class>source
@Retention(value = RetentionPolicy.RUNTIME)

//@Documented:表示是否将我们的注解生成在Javadoc中
@Documented

//@Inherited:子类可以继承父类的注解
@Inherited
@interface MyAnnotation{
}

自定义注解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VxceADId-1638929155463)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810142244670.png)]

//自定义注解
public class Test03 {
    //注解可以显示赋值,如果没有默认值我们就必须就必须给注解赋值
    @MyAnnotation2(name = "Sherlock")
    public void test(){}
    
    //注解中只有一个值的情况
    @MyAnnotation3("Sherlock")
    public void test2(){
   
    }
}

@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface  MyAnnotation2{
    //注解的参数:参数类型+参数名();
    String name() default "";
    int age() default 0;
    int id() default -1;//如果默认为-1,则代表不存在
    
    String[] schools() default {"清华大学"};
}

@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3{
    //注解只有一个值,可以使用value命名
    String value();
}

反射

静态VS动态语言

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PF8J07VM-1638929155464)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810150712893.png)]

Java反射机制概述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TQfLWgWo-1638929155464)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810151801569.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1YIfe9ow-1638929155464)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810152333937.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tBNZ9jrL-1638929155464)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810152755714.png)]

//什么叫反射
public class Test01 {
    public static void main(String[] args) throws ClassNotFoundException {
        //通过反射获取类的Class对象
        Class c1 = Class.forName("sherlock.demo01_annotation.reflection.User");
        System.out.println(c1);

        Class c2 = Class.forName("sherlock.demo01_annotation.reflection.User");
        Class c3 = Class.forName("sherlock.demo01_annotation.reflection.User");
        Class c4 = Class.forName("sherlock.demo01_annotation.reflection.User");

        //一个类在内存中只能有一个Class对象
        //一个类被加载后,类的整个结构都会被封装在Class对象中
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
        System.out.println(c4.hashCode());
        //三个hashCOde相同
    }
}

//实体类
class User{
    private String name;
    private int id;
    private int age;

    public User() {
    }

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

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

    public String getName() {
        return name;
    }

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QoaSX4PR-1638929155465)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810152846878.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mReDE0Vd-1638929155465)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810161938579.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ervbMDbz-1638929155465)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810162243745.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4IzU38R5-1638929155466)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810162431786.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P0VwVhJ1-1638929155466)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810162551170.png)]

//测试Class类的创建方式有哪些
public class Test03 {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Student();
        System.out.println("这个人是"+person.name);

        //方式一:通过对象获得
        Class c1 = person.getClass();
        System.out.println(c1.hashCode());

        //方式二:forName获得
        Class c2 = Class.forName("sherlock.demo01_annotation.reflection.Student");
        System.out.println(c2.hashCode());

        //方式三:通过类名.class获得
        Class c3 = Student.class;
        System.out.println(c3.hashCode());
        
        //方式一二三的hashCode相同
        

        //方式四:基本内置类的包装类都有一个Type属性
        Class c4 = Integer.TYPE;
        System.out.println(c4);//int
        System.out.println(c4.hashCode());

        //获得父类类型
        Class c5 = c1.getSuperclass();
        System.out.println(c5);
    }

}

class Person {
    String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

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

    public String getName() {
        return name;
    }

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

class Student extends Person {
    public Student() {
        this.name = "学生";
    }
}

class Teacher extends Person {
    public Teacher() {
        this.name = "老师";
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8DiviYk0-1638929155466)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810164515665.png)]

//所有类型的Class
public class Test04 {
    public static void main(String[] args) {
        Class c1 = Object.class;//类
        Class c2 = Comparable.class;//接口
        Class c3 = String[].class;//一维数组
        Class c4=int[][].class;//二维数组
        Class c5=Override.class;//注解
        Class c6= ElementType.class;//枚举
        Class c7 = Integer.class;//基本数据类型
        Class c8 = void.class;//void
        Class c9 = Class.class;//Class

        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
        System.out.println(c4);
        System.out.println(c5);
        System.out.println(c6);
        System.out.println(c7);
        System.out.println(c8);
        System.out.println(c9);

        //只要元素类型与维度一样,就是同一个Class
        int[] a = new int[10];
        int[] b = new int[100];
        System.out.println(a.getClass().hashCode());//1232367853
        System.out.println(b.getClass().hashCode());//1232367853
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cJIW3xzs-1638929155466)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810172547656.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-axjAstbp-1638929155467)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810172629831.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lroaxA3V-1638929155467)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810175538660.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XHu143FN-1638929155467)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810175603834.png)]

public class Test05 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(A.m);/
    }
}

class A{
    static {
        System.out.println("静态代码块");
        m=300;
    }
    static int m=100;

    public A() {
        System.out.println("A类的无参构造初始化");
    }
}
//测试类什么时候会初始化
public class Test06 {

    static {
        System.out.println("Main类被加载");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        //主动引用
        //Son son = new Son();

        //反射也会产生主动引用
        Class.forName("sherlock.demo01_annotation.reflection.Son");

    }

}
class Father{

    static int b=2;

    static {
        System.out.println("父类被加载");

    }
}

class Son extends Father{
    static {
        System.out.println("子类被加载");
        m=300;
    }

    static int m=100;
    static final int M=1;
}
//测试类什么时候不会初始化
public class Test06 {

    static {
        System.out.println("Main类被加载");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        //不会产生引用的方法
        //System.out.println(Son.b);

        //Son[] arr=new Son[5];

        System.out.println(Son.M);
    }

}
class Father{

    static int b=2;

    static {
        System.out.println("父类被加载");

    }
}

class Son extends Father{
    static {
        System.out.println("子类被加载");
        m=300;
    }

    static int m=100;
    static final int M=1;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ganWDGnK-1638929155467)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810180626498.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d4Bin7N8-1638929155467)(C:\Users\12097\AppData\Roaming\Typora\typora-user-images\image-20210810181037809.png)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值