JAVASE学习记录

   1.什么是java,java的特点

      java是一种面向对象的高级语言
      特点:面向对象;分布性;可移植性;多线程;动态

   2.配置java环境

      为了让java能够被操作系统所使用,需要对其配置开发环境
      修改系统环境变量中的path,添加javac.exe所在目录。

   3.运行java程序:

       打开DOC窗口(cmd)--输入javac 文件名.java--生成.class文件
       --输入java 文件名--运行java程序

   4.java基础语法

     一、关键字:

         被java语法赋予特定含义的单词 int,public,static 等等;
         关键字全部小写
         用于定义数据类型的关键字
           class    interface    byte    short    int
           long     float    double    char    boolean  void                

         用于定义数据类型值的关键字
              true    false    null        
         用于定义流程控制的关键字
            if    else    switch    case    default
           while    do      for     break    continue  return    
            
         用于定义访问权限修饰符的关键字
             private    protected    public        
         用于定义类,函数,变量修饰符的关键字
            abstract    final    static    synchronized    
         用于定义类与类之间关系的关键字
              extends    implements            
         用于定义建立实例及引用实例,判断实例的关键字
              new    this    super    instanceof    
         用于异常处理的关键字
             try    catch    finally    throw    throws
         用于包的关键字
              package    import            
         其他修饰符关键字
             native    strictfp    transient    assert    
    

     二、标识符(命名规则)

         定义:给 类、方法、变量、接口等起的名字
         规则:字母、数字、_ 、 $(且不能以数字开头、不能是关键字class不行,但是CLASS可以)
               严格区分大小写
         细分:
            包 —— 全部小写 com.cskaoyan.www
            类和接口 —— 每个单词首字母大写 class HelloWorld{} / interface TestPage{}
            方法和变量 ——  首单词小写,后面每个首字母大写: upData() / sendMessage
            常量 —— 全部大写,用下划线分割  MAX_LENGTH

    三、注释

        // 、 /* */ 、 /**  */

    四、常量:在执行过程中其值不会发生改变


        分类:字面值常量/自定义常量

        字面值常量:1.整型常量(1,2,3) 2.字符串常量("ABC") 3.小数常量(1.5)  
                    4.字符常量('a','b')  5.布尔常量(true false)  6.空常量(null)

        整型常量的进制表示即数据组成:
           二进制:0b10  0-1
           八进制:02  (0-7)
           十进制:2  (0-9)
           十六进制:0x2 (0-f)

        进制转换:

           1.十进制转2进制:15 % 2 = 1,7 % 2=1,3 % 2 =1,1 % 2 = 1,反过来取就是1111

           2.八进制转2进制:先转换成10进制,然后再转换成2进制

        原码、反码、补码:
          原码: 1 0001010(最高位为符号位,1代表负数)
          反码: 1 1110101(符号位不参与取反)
          补码: 1 1110110 (正数的补码与原码相同,负数的补码在 反码  + 1)


原码 :二进制定点表示法,即最高位为符号位,“0”表示正,“1”表示负,其余位表示数值的大小。

反码 :正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外。
补码 :正数的补码与其原码相同;负数的补码是在其反码的末位加1。

java中用补码表示二进制数,补码的最高位是符号位,最高位为“0”表示正数,最高位为“1”表示负数。

正数补码为其本身;

负数补码为其绝对值各位取反加1;

为何要使用原码、反码和补码

现在我们知道了,计算机可以有三种编码方式表示一个数,对于正数因为三种编码方式的结果都相同, 所以不需要过多解释。 但是对于负数,其 原码、反码和补码是完全不同的。 既然原码才是被人脑直接识别并用于计算表示方式,为何还会有反码和补码呢?

首先,希望能用符号位代替减法...

首先,因为人脑可以知道第一位是 符号位,在计算的时候我们会根据符号位选择对 真值区域的加减。
但是对于计算机,加减乘数是最 基础的运算,要设计的尽量简单, 计算机辨别"符号位"会让计算机的基础电路设计变得复杂 ,于是,人们想出了 将符号位也参与运算 的方法。
我们知道,根据运算法则,减去一个正数等于加上一个负数,即:1-1 = 1 + (-1), 所以机器可以只有加法而没有减法,这样计算机运算的设计就更简单了。

但是,用原码计算时有一些问题...

于是人们就开始探索将符号位参与运算并且只保留加法的方法。
首先来看原码:
1 - 1 = 1 + (-1) = [0000_0001]原 + [1000_0001]原 = [1000_0010]原 = -2 
如果用原码表示, 让符号位也参与计算,显然对于减法来说结果是不正确的。
这也就是为何计算机内部不使用原码表示一个数。
PS:
对于上一句话,白哥要打一个大大的问号?虽说包括Java、C在内的很多编程语言,在设计整型时,其定义都是:
【8/16/32/64-bit signed two's complement integer】
即:
【8/16/32/64位有符号二进制补码整数】
但也不能说计算机内部不是采用原码表示的吧?

于是,反码出现了,但还有问题...

为了解决原码做减法的问题,出现了反码:
1 - 1 = 1 + (-1) = [0000_0001]原 + [1000_0001]原= [0000_0001]反 + [1111_1110]反 = [1111_1111]反 
= [1000_0000]原 = -0
发现用反码计算减法,结果的 真值 部分是正确的,而唯一的问题其实就出现在"0"这个特殊的数值上。虽然人们理解上+0和-0是一样的,但是0带符号是没有任何意义的,而且会有[0000_0000]和[1000_0000]两个编码表示0。

补码解决了遗留的这个问题..

于是补码出现了,它解决了0的符号以及两个编码的问题:
1-1 = 1 + (-1) = [0000_0001]原 + [1000_0001]原 = [0000_0001]补 + [1111_1111]补 = [0000_0000]补
=[0000_0000]原 = 0
这样0用[0000_0000]表示, 而以前出现问题的-0则不存在了。

并且,还有意外收获..

除此之外,还可以用 [1000_0000] 补 表示-128:
(-1) + (-127) = [1000_0001]原 + [1111_1111]原 = [1111_1111]补 + [1000_0001]补 = [1000_0000]补
-1-127的结果应该是-128,在用补码运算的结果中, [1000_0000] 就代表-128。
注意,-128并没有原码和反码表示。

使用补码不仅仅修复了0的符号以及存在两个编码的问题,而且还能够多表示一个最低数,这就是为什么8位二进制使用原码或反码表示的范围为 [-127, +127],而使用补码表示的范围为 [-128, 127] 的原因。
因为机器使用补码,所以对于编程中常用到的32位int类型可以表示范围是  [-2^31, 2^31-1] ,因为第一位表示的是符号位,而使用补码表示时又可以多保存一个最小值。

  

    五、变量

        在程序执行的过程中,某个范围内值可以发生改变的量
        int i = 5;

    六、数据类型


        分类:1.基本数据类型 2.引用数据类型(类、接口、数组)

        类型       占用空间(字节)  取值范围
        ------------------------------------------
        byte           1            -128~127
        char           2             0~65535(因为是无符号类型,所以取值范围0~2^16-1)
        short          2            -2^15~2^15-1
        int            4            -2^31~2^31-1
        long           8            -2^63~2^63-1
        float          4                   大
        double         8               更大


        数据类型的默认转换:
           byte/short/char ——>int ——>long ——>float ——>double
           (只能小范围转大范围)
        强制转换:
           目标类型 变量名 = (目标类型)被转换的数据;
           int a = (int) 1.5;
           long k = 2147483648L;
           int e = (int)k;

     System.out.println(e);//最后结果损失精度,变成负数【-2147483648】
        //原始数据;4个00000000,1个10000000,3个00000000
        //强制转换之后:砍掉原来左边的4个00000000,只剩1个10000000,3个00000000
    

示例:

char m='a';  ——a。
char m = 97;    ——a。

char m='a'+'b';  ——Ã。  //char类型相加,提升为int类型,输出对应的字符。注,在CMD.exe用输出结果是问题?,不同的编码输出显示不一样。Eclipse中须改成UTF-8。

int m='a'+'b';   ——195。//195没有超出int范围,直接输出195。

char m='a'+b;  ——报错。//因为b是一个赋值的变量。

char m=197;  ——Ã。 //输出字符编码表中对应的字符。

char m='197;  ——报错。//因为有单引号,表示是字符,只允许放单个字符。

char m='a'+1;  ——b。//提升为int,计算结果98对应的字符是b。

char m='中'+'国';  ——42282。

char m='中'+'国'+'国'+'国';  ——报错。int转char有损失。因为结果已经超出char类型的范围。

int m='中'+'国'+'国'+'国';  ——86820

char m='中'+1;  ——丮。//1是int,结果提升为int,输出对应的字符。

char m='中'+"国";  ——报错。String无法转换为char。

System.out.println('中'+"国");  ——中国。//没有变量附值的过程。String与任何字符用“+”相连,转换为String。

总结:

用单引号''标识,只能放单个字符。

char+char,char+int——类型均提升为int,附值char变量后,输出字符编码表中对应的字符。


    七、运算符

       1.算数运算符   +   -   *   /    ++   --
       2.赋值运算符   =   +=   *=
       3.比较(关系)运算符  :  ==     >=     !=    instanceof
         "Hello" instanceof String ----ture
       4.逻辑运算符
         &  与     false & true == false;
         |  或     false | true == true;
         !  非     !false == true;
         && 短路与   a==b && a==a -->只执行了a==b的判断,为false就不再继续执行
                     a==a && a==b -->执行了a==a,为true,则继续执行了a==b
         || 短路或   同理,一个为true,接下来就不再判断

         ^  异或  (判断是否不同)左右相同为false

       5.位运算符

         >> 按位右移 最高位补符号位:1000 >> 2 --> 1110
         << 按位左移  空位补0,高位丢弃: 1010 << 2 --> 1000
         >>> 无符号右移  空位补0
         & 按位与:与0,为0,与1则不变 1001 & 0101 --> 0001
         | 按位或: 或0,不变,或1则为1 1001 & 0101 --> 1101
         ^ 按位异或:相同为 0,不同为1  1001 ^ 0101 --> 1100

           

             交换两个数:a = a^b ; b = a^b;a = a^b

             判断一个数是不是2的整数次方倍:n & ( n-1 ) == 0,为ture,则n是2的整数次方倍


         6.三目运算符

          (表达式,返回值为boolean)?表达式1:表达式2
           eg.  1==2 ?true : false(判断之后结果为true就执行true,结果为false执行false)

  八、键盘录入方法

      1.先导包:import java.util.Scanner;(在类外)
      2.得到 Scanner 对象,Scanner sc = new Scanner();(在类里面)
      3.用一个变量来接收这个键入值,  int x = sc.nextInt();
                              或者是 String a = sc.nextLine();

        int i = new Scanner(System.in).nextInt();
       String s = new Scanner(System.in).nextLine();

  九、流程控制

     

     1.顺序结构

          

     2.选择结构:if / switch 语句

       if (判断条件 得 布尔值)
       {语句1
       }
       else if(判断条件2){语句2
       }
       else{语句3
       }

       switch (表达式为一个确定的值)
       {
       case 值1:语句体1
           break;
       case 值2:语句体2
           break;
       default:语句体n
           break;
       }

     3. 循环结构  for / do while / while

        四部分:
          1.初始化语句int i = 0;
          2.判断条件语句(i<100、true...)
          3.循环体语句
          4.控制条件语句(i++)


          for(int i = 0;i <= 3;i++){循环体}
          //运行步骤:
           1.先判断 i <=3;执行循环体;i++;再判断i<=3;直到 i=4时,判断false,不再执行循环体
              
           int i = 0;//初始化
           while(i < 3){//判断条件
              循环体;
              i++;//条件控制
             }
          
          //比while先执行一次循环体
          do{}
          while ();

        死循环:
          while(true){}
          for(;;)


     4.跳转控制语句(break / continue / return)

         break 的作用:跳出最靠近自己的单层循环
        (使用标签可以跳出多层)
        flag for(){
               for(){
               break falg;
           }
        }

        continue 的作用:退出本次循环,
              即不再执行循环体内continue后面的步骤,转入下一次循环
        

      return 的用法

       return语句总是用在方法中,有2个作用:
        1.返回方法指定类型的值(这个值总是确定的)
        2.结束方法的执行(仅仅一个return语句)

        实例example:
           1.返回一个String:
             public String gets(){
                 String s = "asda";
                    return s;
            }

        
           2.结束程序: private void gets(String s){
              if ( s == null){
                 return;  //结束程序,后面的语句就不会执行了
                }
                System.out.println("A");
             }


      5.方法method

        *定义Definition:完成某类特点功能的代码块(类比于C中的函数)
         *修饰符Modifier:public (static)
        *返回值类型: void、 int 、String 、float
         *方法名:(标识符)自己定义的名字

         *格式format:public static void main(String[] args){}
              public int AAA(int a){
                 return a;
              }

              修饰符 + 返回值类型 + 方法名 +(参数类型1 参数名1,参数类型2 参数名2...)
         
      *参数parameter:
             形式参数 :  参数类型 参数名(int x )
             实际参数:

      *方法使用注意点:
            方法不调用不执行
            方法与方法是平级关系,不能嵌套定义
            方法定义的时候参数之间用逗号隔开
            方法调用的时候不用在传递数据类型
            如果方法有明确的返回值,一定要有return带回一个值
      
      

     *方法调用method call:

        有返回值的方法调用:一般使用赋值调用
        eg.        public int add(int x,int y){
                      int c = x + y;
                       return c;
                      }

        没有返回值的方法调用:只能使用直接调用
         eg.      public void test(int add){
                  System.out.println("i=" + add);

                  }


1.直接调用:方法名(实参列表);       //调用的前提是该方法存在于当前类中
2.类名调用:类名.方法名(实参列表);  //调用的前提是该方法属于静态方法(也就是用static修饰的方法)
3.对象调用:对象.方法名(实参列表);

    

       *方法抽取:

        将某个运算比较复杂的过程抽取成为方法,然后再进行调用即可;

        eg.键入三个数,三者取最大
        public static void main(String[] args){
        int a = sc.nextInt();
        int b = sc.nextInt();
        int c = sc.nextInt();
        int max = maxThree(a,b,c)
            System.out.println(max);
        }
        public int maxThree(int i,int j,int k){
                int temp = i > j ? i:j;
                int max = temp > k? temp : k;
                return max;
        }


    *方法重载 Overloading Method

      同名方法,参数个数不同 / 参数类型不同 / 参数顺序不同
      public void add()
      public double add(int a)
      public int add(int a, float b)

      与其返回值类型无关,jvm只认参数列表,来区分同名方法

                         

   6.数组Array

       数组定义:储存同一种数据的集合(可以是基本数据类型,也可以是引用数据类型)
       public static void main(String[] args) {
     //动态初始化

    int[] a = new int[3];

    Student[] stu = new Student[3];//也可以自定义数组类型

    //但是自定义的数组类型要在 Student 类中定义好,才能 new 出来

    //访问数组首地址
    System.out.print(a);
    //访问数组中的元素
    System.out.print(a[0]);

    //调用方法,输出数组中的元素:
    printArray(a);
    System.out.println(Arrays.toString(a));

    //动态初始化两个数组
    int[] a = new int[3];
    int[] b = new int[5];

    
    //将第二个数组的 首地址 给到第一个数组
    int[] a = b;
    
    //静态初始化
    int[] c = new int[]{1,2,3,4,5};
    int[] a = {1,2,3,4,5};
    }

    //定义一个输出数组所有元素的方法
    public static void printArray(int[] a){
       for (int i = 0;i < a.length ; i++ )
       {
           System.out.println(a[i]);
       }
    }

   //定义一个自定义的数组的调用格式

  public void output (Student[] arr){ }

  

    *定义格式Definition Format
               数据类型[] 数组名(推荐)
               或者 数据类型 数组名[]

    *访问方法:通过下标进行访问 a[i];
              如果直接访问a,是访问数组a的首地址
    
    *初始化方式
        动态初始化 :
                   int[] a = new int[3];

        静态初始化
                   int[] c = new int[]{1,2,3,4,5};
                   int[] a = {1,2,3,4,5};(简化写法,有限制,在声明数组的同时需要定义好)

    *java中的内存分配
       1.栈stack:存储局部变量、栈帧Stack Frame
       2.堆heap:存储 new 出来的东西(比如数组,对象)堆上的对象天生就有初值0,null
       3.方法区method area:存放字节码文件等
       4.本地方法栈Native Method Stack:实现非java语言的部分功能,比如指针
       5.程序计数器Program Counter:指明当前正在执行的是哪条java语句。


   ** 栈和堆的区别:
         栈stack                                       堆heap
         ------------------------------------------------------
       1.存储局部变量                           new出来的东西
        Local variables            
       
       2.数据必须初始化                      数据都有其初值
          之后才能使用                         byte/short/int/long - 0
                                                       float/double - 0.0
                                                       char   -'\u0000'
                                                       boolean   -false
                                                      引用数据类型: null
         

       3.变量,一旦方法运行完           堆中变量,使用完毕会变成垃圾,
          就会被回收                            需要垃圾回收器来回收


    

   



     *遍历数组:for循环或加强for循环。

JDK1.5中增加了增强的for循环。

缺点:
  对于数组,不能方便的访问下标值;
  对于集合,与使用Interator相比,不能方便的删除集合中的内容(在内部也是调用Interator).
除了  简单遍历并读取其中的内容 外,不建议使用增强的for循环。

一、遍历数组

语法为:

for (Type value : array) {

    expression value;

}

//以前我们这样写:

void someFunction (){

    int[] array = {1,2,5,8,9};

    int total = 0;

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

        total += array[i];

     }

    System.out.println(total);

 }

//现在我们只需这样写(和以上写法是等价的):

void someFunction (){

    int[] array = {1,2,5,8,9};

    int total = 0;

    for (int n : array){  

       total += n;

    }

    System.out.println(total);

}

这种写法的缺点

 显而易见,for/in(for each)循环自动控制一次遍历数组中的每一个元素,然后将它赋值给一个临时变量(如上述代码中的int n),然后在循环体中可直接对此临时变量进行操作。这种循环的缺点是:

 1. 只能顺次遍历所有元素,无法实现较为复杂的循环,如在某些条件下需要后退到之前遍历过的某个元素;

 2. 循环变量(i)不可见,如果想知道当前遍历到数组的第几个元素,只能这样写:

 int i = 0;

 for (int n : array) {

     System.out.println("This " + i + "-th element in the array is " + n);

     i++;

}

二、遍历集合

 语法为:

 for (Type value : Iterable) {

    expression value;

}

注意:for/in循环遍历的集合必须是实现Iterable接口的(迭代器,其中只有一个方法  iterator())。

 //以前我们这样写:

void someFunction (){

    List list = new ArrayList();

    list.add("Hello ");

    list.add("Java ");

    list.add("World!");

    String s = "";

    for (Iterator iter = list.iterator(); iter.hasNext();){

       String temp= (String) iter.next();

       s += temp;

    }

    System.out.println(s);

 }

//现在我们这样写:

void someFunction (){

    List list = new ArrayList();

    list.add("Hello ");

    list.add("Java ");

    list.add("World!");

    String s = "";

    for (Object o : list){

       String temp = (String) o;

       s += temp;

   }

   System.out.println(s);

}

// 如果结合“泛型”,那么写法会更简单,如下:

 void someFunction (){

    List list = new ArrayList();

    list.add("Hello ");

    list.add("Java ");

    list.add("World!");

    String s = "";

    for (String temp : list){

       s += temp; //省去了对强制类型转换步骤

   }

   System.out.println(s);

}

//上述代码会被编译器转化为:

 void someFunction (){

    List list = new ArrayList();

    list.add("Hello ");

    list.add("Java ");

    list.add("World!");

    String s = "";

    for (Iterator iter = list.iterator(); iter.hasNext();   ){

       String temp = iter.next();

       s += temp;

    }

    System.out.println(s);

 }

这种写法的缺点

    虽然对集合进行的for/in操作会被编译器转化为Iterator操作,但是使用for/in时,Iterator是不可见的,所以如果需要调用Iterator.remove()方法,或其他一些操作, for/in循环就有些力不从心了。 综上所述,Java 5.0中提供的增强的for循环——for/in(for each)循环能让我们的代码更加简洁,让程序员使用时更加方便,但是也有它的局限性,所以一定要根据实际需要有选择性地使用,不要盲目追求所谓的“新特性”。


     *两个异常:
         1.数组越界异常
         2.空指针异常(数组对象没有指向实体,但却操作实体中的元素值)
            int[] b = new int[2];
            b[0] = null;

            输出b[0];
    
     *算法:实现数组逆序(不开辟新空间)
      public static void reverseArray(int[] a) {
        // 记住
        for(int i = 0; i < a.length / 2; i++ ) {
            //交换元素
            swap(a  , i  ,   a.length - 1 -i  );
          }
      }

          public static void swap(int[] a, int iIdex, int jIndex) {
        int tmp;
        //a[iIndex]  a[jIndex]
        tmp = a[iIdex];
        a[iIdex] = a[jIndex];
        a[jIndex] = tmp;
     }


  7.二维数组:

      初始化:
      int[][] arr = new int[m][];
      int[][] arr ={{},{},{}}

      初始化之后,如果想要赋值的话,还需要再重新开辟空间,再赋值

      比如:
            int[][] tri = new int[n][];
            tri[0] =new int[1];

            tri[0][0] = 1;


算法:实现杨辉三角,
      1
      1 1
      1 2 1
      1 3 3 1
      1 4 6 4 1
      1 5 10 10 5 1
      规律:行数=每行个数; 每行第一个和最后一个都是1; 1,2行是固定的
            每行的中间的数m = m头上的数 + m头上的数左边一个数
             (a[n][m] = a[n-1][m] + a[n-1][m-1])

    public static void main(String[] args){
            Scanner sc = new Scanner();
            int n = sc.nextInt();
            //用一个二维数组去接收方法triangleYan()
            int[][] yang = triangleYan(n);
            //调用方法outPrint ,遍历输出二维数组
            outPrint(yang);

      }
         //创建方法计算每个二维数组的值
          public int[][] triangleYan(int n){
         int[][] tri = new int[n][];//初始化二维数组,设为n行
         if (n == 1) {   //开辟数组空间
           tri[0] = new int[1];
           tri[0][0] = 1;
           return tri;
       }else if(n == 2){
           tri[0] = new int[1];
           tri[0][0] = 1;
           
           tri[1] = new int[2];
           tri[1][0] = 1;
           tri[1][1] = 1;
           return tri;

       }
           //如果不是1或2行,那么还是要先将1,2行进行赋值操作

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


           //开始遍历数组,从第3个开始
           for (int i = 2; i<tri.length ; i++ ){  
               //每行的第一个和最后一个都是1
               //一定要初始化

               tri[i] = new int[i + 1];
               tri[i][0] = 1;
               tri[i][i] = 1;
               *//j不能取到每行的最后一个,因为最后一个的头上没有东西
               for (int j = 1;j < tri[i].length - 1;j++ )
               {
                   tri[i][j] = tri[i-1][j]  +  tri[i-1][j-1];
               }
           }
           return tri;
      }
      //创建方法,遍历并且输出
      public static void outPrint(int[][] a){
      for (int i = 0;i<a.lenght ;i++ ){
          for(int j = 0;j< a[i].length;j++){
          System.out.print(a[i][j] + "\t")
          }
          System.out.println();
      }
      }


   8.递归Recursive

     需要注意递归需要出口,可以用if来设置出口
     

------------------------------------------------------------------------------------------------

Day 6:(主要是说    类    )

   

   1.面向对象的三大特征:

    ①封装

      是否可直接访问    private    protect    public    默认
        该类内部                 √             √          √          √
        同一个包子类          ×            √             √         √
        不同包的子类          ×          √             √          ×
         同一个包的非子类    ×         √           √          √
        不同包的非子类       ×          ×            √           ×


      ②继承( class 继承的类  extends 被继承的类 )
      需要对一个点很熟悉,也就是 数据类型 的对比
      语句:int a = 1;
      int是一个被定义了的类,所以我们可以使用这个类,并附上自己给的命名

      继承:

      1、在一个test方法声明中
         有一个Student的类,里面定义了一些变量和方法
         class Student
         {
              int a,b;
              public void eat();
              System.out.println("吃饭");
              
              public void sleep();
              System.out.println("睡觉"):
         }

      2、再定义一个类class sc extend student{}
         此时就是:类 sc  继承于  类student。

      3、在main方法中初始化Student类
         Student sc = new Student();
      
      4、此时就可以使用sc.eat();se.sleep();
      
      简单来说:
         有2个类,一个Student,一个class otherStudent extends Student
      c lass Student                    class  otherStudent  extends Student
      {                                        {   }
          public void sleep();         
      }                               

      在主方法中初始化继承的类 otherStudent  Student sc = new Student();
                             (其实是进行了两步:
                                      1.Student sc; 声明变量 类似int a;)
                                      2.sc = new Student();初始化这个变量


      继承完成,可以在main方法中使用   sc.sleep();(即使用 继承类名.被继承类的声明)
                                   这是赋初始值的操作 sc.sleep()
      
      
      注意点:如果不进行 第3步 初始化的话,
              那么就没有办法使用sc.(student类中设置的成员变量和成员方法)

              继承 会 继承 被继承类的 所有 成员参数 和 成员方法(但能访问哪个,取决于其访问权限)。
              被继承的类就是 父类(Student),也就是被拿走了所有东西。

     ③多态:同一个方法在不同情况下表现出不同行为

     

    
         

  2、在类中如果没有声明方法的话,无法使用操作

     类的定义中有两部分东西:成员变量和成员方法
    
          成员变量(field):int a;
          成员方法(method):public void 方法名{
              System.out.print();
               }


  3、类的定义方式:

     [修饰符] class 类名
     {
            [成员变量声明]
            [成员方法声明]
     }

    类的初始化操作:
    类名 a = new 类名();

    注意:类之间的相互调用就是通过继承

  4、this关键字

      理解:用来调用本类的成员变量或成员方法  ;  作为方法的返回值。
      eg.
      public class Book{
          private String name;     //定义一个String型的成员变量
          public String getName(){ //定义了一个getName方法
          int id = 0;              //局部变量
          setName("Java");         //调用类中其他方法
          return id + this.name;   //设置方法返回值
          }
          private void setName(String name){ //定义一个setName的方法
          this.name = name ;       //将参数值赋予类中的成员变量
          }
          public Book getBook(){
             return this;          //返回Book类引用
          }                    //在Book类中定义了一个Book类型的方法,通过this关键字返回
      }                        //在getBook()方法中,方法的返回值为Book类


  5.类的构造方法(见CSDN)

      
      构造方法的特点:
        1.没有返回值,不能定位为void(普通的没有返回值的方法有void)
        2.名称要与本类的名称相同
        3.也可以方法重载
      
      构造方法的语法:
      public class book
        public Book(){
         ....//构造方法体
        }
      
      用途:
         构造方法可以给成员变量赋值

      举例1:

      (无参数的构造方法)

      同一个包下
        public class Car_1{
            private String color;
            private String brand;

            public Car_1(){//构造方法(无参数)               
                this.brand = "奥迪";
                this.color = "黑色";
            }
            public String getColor(){//对私有成员的get方法
               return this.color;
            }
            public String getBrand(){
               return this.brand;
            }
        }

        另一个类
        public class main_constructor{
            public static void main(String[] args){
            Car_1 c = new Car_1();            //初始化类Car_1,并且赋给c
            System.out.println(c.getColor);
            System.out.println(c.getBrand);
            }
        }

        输出结果:
        黑色
        奥迪


        
        举例2:(有参数的多个构造方法)
               public class Car{
            private String color;
            private String brand;

            public Car(){               //构造方法(无参数)               
                this.brand = "奥迪";
                this.color = "黑色";
            }
            public Car(String a,String b){
                this.brand = b;
                this.color = a;
            }
            public String getColor(){        //对私有成员的get方法
               return this.color;
            }
            public String getBrand(){
               return this.brand;
            }
        }
            另一个类中:
         public class main1{
            public static void main(String[] args){
            Car_1 c = new Car_1();         //初始化类Car_1,并且赋给c
            Car c1 = new Car("红色","福特")
            System.out.print(c.getColor);
            System.out.println(c.getBrand);
            System.out.print(c1.getColor);
            System.out.println(c1.getBrand);
            }
        }

           输出结果:
           黑色奥迪
           红色福特


           注意:有参数的构造方法,在最后要初始化的时候,
           需要赋值,否则还是会变成  黑色奥迪

           这就是构造方法的重载
           public Car();
           public Car(String a,String b);


  6.构造方法的继承调用:

    父类无参数时:(编译器会自动调用)
    如果只调用子类的构造方法,并且实例化(初始化:子类 i = new 子类;)
    会先将父类的构造方法初始化,然后才能实例化子类
    
     父类有参数时:
    继承父类时,需要在子类的   构造方法第一句中   加入super();

    class 子类 extends 父类{
        子类构造方法(){
          super();//调用父类的无参构造方法
          super(2);//调用父类的一参构造方法(要注意,只能在子类的构造方法中调用super()父类构造方法,但是super必须放在构造方法的第一句,为了好看,我才写在一起。)

        }

    }

eg.

public class test1 extends B {
    public static void main(String[] args) {
        new B();
        new B(1);
    }
}
class A{
    public A(int a ){
        System.out.println("一参构造输出");
    }
    public A(){
        System.out.println("无参构造输出");
    }
}
class B extends A {
    public B() {
    }
    public B(int b){
        super(2);
    }
}

输出结果:

无参构造输出
一参构造输出
分析原因:

super() 调用的就是父类的构造方法,

如果父类的构造方法为一参,那么在子类调用的时候,super()里面要有对应的父类一参的参数值
来表示自己调用的是  父类  的哪个构造方法来给  父类赋值


    7.return的用法

      return语句总是用在方法中,有2个作用:
        1.返回方法指定类型的值(这个值总是确定的)
        2.结束方法的执行(仅仅一个return语句)


      实例:
        1.返回一个String:
          public String gets(){
                String s = "asda";
                return s;
          }

        

        2.结束程序:

         private void gets(String s){

          if ( s == null){
              return;  //结束程序,后面的语句就不会执行了
          }
          System.out.println("A");
        }

 

  8.静态常量、变量和方法(由static修饰的...)

    被static修饰的常量、变量和方法就是静态成员
     静态成员属于类所有,区别于个别对象,可以在本类或其他类使用
     使用方式 :类名. 调用
    注意点:
            1.静态方法中不能使用this关键字;
            2.静态方法中不能直接调用非静态方法;


    实例:
    public class Test{
    static double PI = 3.1415;
    public static void method1(){//定义静态方法
    }
     public void method(){      //定义动态方法
     System.out.print(Test.PI); //调用静态常量
     Test.method();             //调用静态方法
     }
    }

    
     //这段代码会先执行do something,且只执行一次
    //先执行类的初始化动作,用static{}定义一个静态区域(静态代码块)

    public class example{
        static{
            //do something
        }
    }

  9.类的主方法:public static void main(String[] args)

   特点:
     1.是静态的,所以如果要在主方法中调用其他方法,必须也是静态的
     2.没有返回值
     3.主方法的形式参数是数组,args[0]~args[n]表示参数,用args.length来调用


--------------------------------------------------------------------------------------    

Day 7 :对象


  1.创建对象:

      通过  new 操作符来创建对象
      eg. Test test = new Test();
          Test test = new Test("a");

      在JAVA语言中,初始化和创建时被捆绑在一起的
      本来应该是两步:
          Test test;(类似于 int i)
          test = new Test(); //对构造方法初始化

      eg.
      public class CreateObject{
          public CreateObject{   //构造方法属于一种类,命名是每个首字母大写
                              //方法的命名是和变量一样的首单词小写,后面每个首字母大写,changeValue
          System.out.println("创建对象");
          }
          public static void main (String[] args){
                new CreateObject();
          }
      }

  2.使用方法:

    对象.类成员(方法、变量)
    
    对象调用类成员的方法:

    创建两个对象,初始化时是先将两个对象t1,t2放入 中不同的位置,
    然后分别访问类成员(类成员没有被static修饰),
    如果某一个对象对类成员进行赋值操作,类成员的值只会在这个对象内部发生改变,
    即不影响另一个对象对类成员的访问结果,
    如果用static定义类成员的话,那么类成员的值只要被修改,其值就会发生改变。
    int i = 48;
    t2.i = 60;
    输出对象t2.i和t1.i
    t2.i = 60 , t1.i = 48

    static int i =48;
    t2.i = 60;
    输出对象t2.i和ti.i//指向了同一块内存区域
    t2.i = 60,t1.i = 60;


从JVM的角度来理解:
     static修饰的变量将变成常量,进入内存中的常量池,成为独一份的东西,如果被修改,那么其值就会发生改变。


  3.对象的引用

    类名 对象引用名
    eg.Book book;//Book类的引用
    说法:
    1.book 是Book类的一个对象
    2.book 包含了Book(类的)对象的一个引用

  4.对象的比较(== / equals)

    public class Compare{
        public static void main(String[] args){
        String c1 = new String("abc");  //创建两个String类型的对象引用
        String c2 = new String("abc");
        String c3 = c1;    //将c1对象引用赋值给c3
        //使用“==”运算符比较c2和c3
        System.out.println("c2 == c3的值为" + (c2 == c3));
        System.out.println("c2.equals(c3)的值为" + (c2.equals(c3)));
        System.out.println("c1 == c3的值为" + (c1 == c3));
        }
    }

    输出结果:

               c2 == c3  :   false
              
c2.equals(c3)  :  true
               c1 == c3   :  true

    
    分析原因:== 运算符是比较对象引用  地址  是否相同
              equals 是比较两个对象引用所指的  内容  是否相同
              (c3 = c1 让c1与c3指向堆中同一片地址)



初学者最容易忽略的一个点,导致对创建对象的理解不到位的:

   在创建对象( new 类() )的时候,要看类中是否有构造方法,

        如果有,就要在()中填入要  初始化  构造方法中的参数列表对应的成员变量值;

        如果没有构造方法,那么() 中就不需要填任何东西。
(因为  构造方法  本身就是   new的时候进行赋值的一种 操作)

举个例子:

 
 
public class stringChar {
    public static void main(String[] args) {
        body body = new body();
    }
}
class body{
    String name;
    int i;
}
public class stringChar {
    public static void main(String[] args) {
        body body = new body("AAA");
    }
}
class body{
    String name;
    int i;
    public body(String name){
        this.name = name;
    }
}
在创建对象的时候,就要根据其构造方法中提供的 参数列表,来   相应初始化   类中的成员变量




----------------------------------------------------------------------------------------------------------------            



Day8

--------------------------------------------------------------------------------------------------

  1.对象

    概念:对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位
    组成:一个对象由一组属性和操作(功能)组成
    动态观点:对象的操作就是对象的行为
 

  2.类

            [修饰符] class 类名 [extends 父类名]
                                           [implements 接口名]
        {
            [成员变量声明;]int a
            [成员方法声明;]public void AA(){System.out.println()}
        }
        如何通过类来创建对象呢?
        第一步:声明对象的引用变量
        第二步:初始化这个对象的引用变量


        如何访问对象中的成员变量和成员方法呢?
        如果我们把对象的引用变量看成是对象的名字的话

        对象名(对象的引用变量).成员变量
        对象名(对象的引用变量).成员方法

        第三步:赋值
        通过访问对象的成员变量,来修改成员变量的

   

    实例:(如何通过类来创建对象  以及 访问语法)

    public class CreateObject {
    public static void main(String[] args) {
        //第一步 申明引用变量
        //类比于 int i;

        Student1 s;
        //初始化引用变量
        s = new Student1();
        //访问对象中的成员变量:
        System.out.println("s.age = " + s.age + " s.name = " + s.name + " s.id = " + s.id);
        //访问成员方法:并且赋值
        **都是通过 引用变量s 来访问方法 并且赋值的。
        s.eat("肠粉");
        s.sleep();
        s.name = "zs";
        s.age = 18;
        s.id = 001;
        System.out.println("s.age = " + s.age + " s.name = " + s.name + " s.id = " + s.id);
    }
}
class Student1 {
    //成员变量
    int id;
    String name;
    int age;
    //成员方法(用来表示一种行为或者为空)
    public void eat(String food) {
        System.out.println("eat " + food);
    }
    public void sleep() {
        System.out.println("睡觉");
    }
}


   3.构造方法

        数组的静态初始化:经过静态初始化,数组中的的元素的初值,就变成了我们想要的值,而不是系统的默认值
        数据类型[] 数组名 = {数值1,初值2,...};

        在对象中也可以实现类似的效果:对象的成员变量可以被初始化成我们想要的值,而不是系统的默认值
        
         作用:  给对象的数据进行  *初始化*   (可以初始化成我们想要的值)

        构造方法格式
            1.方法名与类名相同
            2.没有返回值类型,连void都没有
            3.没有具体的返回值

            eg. A b = new A(2)//相当于赋值操作  A b;  //声明引用变量b
                                         //   new A(2);     给A分配一个内存空间,并赋值2
            class A{
                int x;

                 public A(int a){//将形参a赋给成员变量x,形参会在 类A对象初始化的时候得到一个值
                    x = a;       //这个值就会成为变量x的值
                 }
                 public A(double d,int a){ }//构造方法的 重载,会在new的时候自动判断引用的到底是哪个构造方法
               }
         
         注意事项:
            1.构造方法,不是由我们来调用,是由我们jvm,在构造对象的时候调用
               且构造方法不属于成员方法
           2. 当构造方法执行完毕之后,我们才说对象构造完毕
           3.如果你不提供构造方法,系统会给出默认构造方法,如果你提供了构造方法,系统将不再提供默认构造方法
                  默认构造方法长啥样?
                    public com.cskaoyan.objects.Student2() {  }
                构造方法有栈帧和它对应,但是,在真正执行的时候,执行的不是我们定义的构造方法<init>

           4. 构造方法可以重载

           5.构造方法的参数列表中,不同的方法中参数类型不同,个数不同,顺序不同

            
           6.访问对象中的成员方法和成员变量:通过   对象名.
               对象名创建在哪个域,就在哪个域去访问对象(类名 对象名 = new 类名())
               如果在类中,就可以直接通过变量名和方法名访问类中其他的成员变量和方法

               class A
               {int a;
               a = 5;           //通过变量名直接访问
               public void aaa(){
                  }
                  public void bb(){
                      aaa();     //通过方法名在另一个方法中直接访问
                  }
               }

               
      为什么方法不能直接在类的层次直接使用,而要放到其他方法中才能使用?
                        因为方法对应的是操作,类的层次只能放成员变量,
                        只有在定义了方法后 才能使用方法中的操作。
            
       6.构造方法的重载:类似方法重载(同类名,没有返回值,可带参或不带参)
               构造方法的重载的意义:可以实现对所有成员变量的初始化操作。

       7.对象的 成员变量  存储是在堆上,堆上的数据天然有初值
                     局部变量  必须先进行赋值,再使用,jvm不会默认赋初值

            
    

    4.this 关键字(用于解决成员变量的隐藏问题)

       1.成员变量的隐藏:
          class tom
          {int x = 10;//成员变量
            void f(){
            int x = 3;//局部变量
            }
          }

          如果成员变量的名字和局部变量的名字相同的时候,成员变量的名字会暂时被隐藏
          则如果想要在f()方法中访问 成员变量X就无法做到,但是确实想要访问成员变量x的话
          class tom{
            int x = 10;//成员变量
            void f(){
            int x = 3;//局部变量
            this.x    //访问的是 成员变量的值 10
            }
          }

          所以this关键字如果后面跟着成员变量名,那么一定是访问的成员变量的值。

         2.this 表示当前对象(例子见上)

         3.this 调用构造方法:
           class A{
               public A(){
               this("age");       //调用了带有一个参数的构造方法
               }              //调用哪个构造函数是根据this()括号中的参数的类型来确定的
               public A(String a){
                   System.out.print(a);
               }
           }
 
       
this()——调用本类的构造方法
        this.其他方法() ——调用本类的其他方法


        4.this 返回类的引用

           this.类  相当于  return 类


       5.实例,帮助理解 this 关键字:

public class Person {  
       private String name;  
       private int age;  
    // 无参  
    public Person() {  
        this.name = "张三";//或者就是 name = "张三";
        this.age = 20;  //或  age = 20;
    }  
 
    // 有参  
    public Person(String name) {  
        this.name = name; //这里必须用this,因为形参和成员变量名相同,成员变量被隐藏
    }  
 
    // 多参  
    public Person(String name, int age) {  
        this(name);// 调用一个参数的构造方法  
        this.age = age;  
    }  
 
    // 这是一种写法比较常用的方法。重写第一种。用上面的例子直接写  
    public Person() {  
        this("张三", 20);// 调用有参构造方法。设置默认值。和第一种方法功能一样  
    }                           //会自动帮你识别,调用的是哪一种构造方法
 
    public Person(int age , String name) {  //转变参数顺序
        this.name = name;  
        this.age = age;  
    }  


    

    5.static  关键字

        1.独一份,存在  方法区的静态区域
        2.static 修饰的东西会优先于对象所有,即优先进入内存(不依赖于对象而存在)
       3.被 所属类 的  所有对象  所共享(外部想要共享,那么访问权限修饰符就要改变)
       4.被static修饰的成员变量或者成员方法,又有一个名字:类变量或者类方法

       
        5.访问static修饰成员变量或方法:我们可以通过 类名.成员变量
                                                                                 类名.成员方法

           
       6.静态变量和成员变量的比较:
           1.所属不同
                 静态变量属于类
,所以也称为类变量
                 成员变量属于对象,所以也称为实例变量(对象变量)
           2.内存中位置不同
                静态变量存储于方法区的静态区
                成员变量存储于堆内存
           3.内存出现时间不同
               静态变量随着类的加载而加载,随着类的消失而消失 (类卸载)
               成员变量随着对象的创建而存在,随着对象的消失而消失
           4.调用不同
                静态变量可以通过类名调用,也可以通过对象调用  推荐 类名.  对象.
                成员变量只能通过对象名调用  对象名.

       7.能够通过  类名.  的方式来访问,所以可以在不同类中访问。

       8.禁止在 静态 成员方法 中访问非静态的成员变量或者方法。





  6.代码块Block

           1.在Java中,使用{  }括起来的代码被称为代码块,
           2.根据其位置和声明的不同,可以分为
                 局部代码块,构造代码块,静态代码块,同步代码块(多线程讲解)。

        局部代码块:
              在方法中出现;限定变量生命周期,及早释放,提高内存利用率
            
public void  tesetLocalBlock()
{
    //局部代码块
    {
        int i = 100;
        System.out.println(i*i);
    }
}


        构造代码块(每次创建对象的时候都会执行)
              1.在类中方法外出现;
              2.在构造对象时执行,并且在构造方法前执行;
class BlockTest {
//构造代码块的代码,在构造对象的时候都必须执行
int i;
{
 // 构造代码块,也可以用来做一些初始化动作
 System.out.println(" initialize  block");
//如果说每个构造方法中重复代码(保证不管使用哪个构造方法初始化对象,都会执行这段代码),但是如果 //这些代码放在构造代码块中,那么只需要一份就可以,因为构造代码块中的代码在构造对象时一定执行 }
}

        静态代码块(只随着类的加载,只执行一次)

              1.静态代码块 在类中方法外出现
              2.加了static修饰

              随着类加载而执行(同一类来讲,jvm只会加载一次,所以静态代码块也只会执行一次)
//静态代码块
static {
    System.out.println("static block");
}


        同步代码块(多线程中才有的):被 synchronized 修饰的

                          synchronized(obj){
                                //同步代码块
                          }
      详见多线程;


   7.类的访问控制权限

      含义:为了限制成员变量在 类 或 对象 外部可见性。
      类型:
       1.public 任何情况下均可以访问;
       2.protected (①类中可访问 ② 同包其他类中可访问 ③子类可访问,不需要同包
                              但不能在子类的静态方法中访问)
       3.不写,默认访问权限(①在类中可访问 ② 同包其他类中可访问)
       4.private (①仅在类中可访问 ②同一类中创建的对象可以访问 ③其他类的对象不能访问)
       对于普通的类,只能使用public或者不写。
       修饰符取最严访问控制权限,便于数据安全
 
 

 8.this 关键字(见之前一周复习)

   含义:
     1.表示当前对象 this.成员变量(一定不会是成员变量)
     2.用来调用构造方法 this();()中的值就是构造方法中的参数的实际参数值
        eg.  
        class AA
        public AA(String a){}
        public AA(){
            this("Hello")}//此处就引用了单参的构造方法
     3.返回类的引用
        this.类  可以等价于  return.类

  9.方法的参数传递


     方法调用时参数值的传递可以分为“值传递”和“引用传递”两种
        
******1.值传递(重要)
            a.当方法的形式参数为基本数据类型时
            b.实参的值被复制给形参,改变形参不会影响实参的值

       
public class TestPassValue {
    public static void main(String[] args){
        double one=1.0;
        System.out.println("before: one="+one);
        halveIt(one);//将实参的值复制 给到 halveIt的形参
        System.out.println("after: one="+one);
    }
    public static void halveIt(double arg){
        arg=arg/2.0;
        System.out.println("havled: arg="+arg);
    }

}

输出结果:

before: one=1.0
havled: arg=0.5
after: one=1.0

*****2.引用传递 —
           a.当方法的参数为对象引用时,实参和形参都同时引用了同一对象,

           b.通过形参修改对象的状态后会影响对象的其它引用

public class TestPassRef {
    public static void main(String[] args){
        Body sirius=new Body("Sirius"); //天狼星
        System.out.println("before: "+sirius.name);
        commonName(sirius);
        System.out.println("after: "+sirius.name);
    }
    public static void commonName(Body bodyRef){
        bodyRef.name="Dog Star";
    }
}
class Body{                  //记住这种类的定义方法
    String name;
    public Body(String name) {    //用构造方法来完成对形参的赋值操作
           this.name = name;
    }
}

输出结果
before: Sirius
after: Dog Star


实例:
class Demo{
    int a;
    public Demo(int a){
        this.a=a;
    }
}

public class TestQuote{
    public static void main(String args[]){
        Demo d1=new Demo(1);
        Demo d2=new Demo(2);
        System.out.println(d1.a);
        System.out.println(d2.a);
        function(d1,d2);
        System.out.println(d1.a);
        System.out.println(d2.a);
    }
    private static void function(Demo d1,Demo d2){//开辟了一个新栈帧
        int a;//局部变量
        a=d1.a;//将d1所指的值和d2所指的值进行了交换。
        d1.a=d2.a;
        d2.a=a;
    }
}

输出结果

1221


实例二:
class Demo1{
    int a;
    public Demo1(int a){//用构造方法对形参进行赋值操作
        this.a=a;
    }
}
public class TestQuote1{
    public static void main(String args[]){
        Demo1 d1=new Demo1(1);//初始化Demo1并赋值1给d1
        Demo1 d2=new Demo1(2);//给d2
        System.out.println(d1.a);
        System.out.println(d2.a);
        function(d1,d2);
        System.out.println(d1.a);
        System.out.println(d2.a);
    }
    private static void function(Demo1 d1,Demo1 d2){//因为是一个方法,所以创建了一个新的栈帧空间
        Demo1 temp;//类似于int i,定义了一个Demo1类型的变量temp
        temp=d1;   //将原来main方法中的d1,d2的地址通过temp进行了交换,但是所指的值仍然没有发生改变
        d1=d2;
        d2=temp;
    }
}


输出结果为:

1212








------------------------------------------------------------------------------------------

Day9


   1.类的继承

        1.对真实世界中对象继承关系的支持,满足面向对象建模技术的需要 (描述一种关系 继承 类之间的继承 父子)
        2.是面向对象程序设计语言中,对象多态性(Polymorphism)实现的前提 运行时多态  的实现前提
                                                                运行时多态可以在任意类中发生?不行
                                                                编译时多态,可以在人以类中发生
        3.代码复用的一种形式 继承-》 父类有的子类也会有  即不劳而获
        4.数据类型
            Animal   cat
            一个动物对象的类型是Ainmal
            Animal a = new Animal()  动物是动物

            一只猫也是一个动物
            Animal b = new Cat()

            java: 强类型语言
            A a = new A();
            A a = new 子类();

            继承帮我们摆脱了一点点,java中严格的类型限制,——》 给我们代码带来了很打的灵活性

         如果要声明类B继承类A,则可按照以下的语法声明类B:
         [修饰符] class B extends A { …… }

         类B称为类A的子类(subclass),类A称为类B的父类或基类(superclass 超类),
         称类A的父类或父类的父类为类B的祖先(ancestor)

         子类将拥有在其父类和祖先类中声明的所有的成员(但是仅可以访问到public或protected,默认权限(包访问权限))
 
     实例:
public class ExtendDemo1 {
    public static void main(String[] args) {
        // 继承 代码复用
        Cat mycat=new Cat();
        mycat.weight=5.5f;
        mycat.heartRate=80;
        mycat.longHair=true;
        mycat.eat() ;
        mycat.breath() ;
        mycat.purr() ;
        
        // 一个哺乳动物是(=)动物
        Animal a = new Mammal();

        //一只猫是动物
        Animal b = new Cat();
       }
}
class Animal{
    public float weight;
    public void eat(){}
}

class Mammal extends Animal{
    public int heartRate;
    public void breath(){}
}

class Cat extends Mammal{
    boolean longHair;
    public void purr(){
        // 子类代码依赖父类的实现
        breath();
    }
}

 

  2.类的继承中需要注意的点

          子类对象的成员初始化之前,必须先完成对父类或祖先类对象的成员的初始化

        1.为啥在初始化子类对象之前必须先初始化父类对象呢?

          子类代码可能依赖于父类的实现,为了保证子类代码一定能够正常运行,java中规定,
          在初始化子类对象必须先初始化父类对象

        2.子类对象有两种初始化方式:
          a.隐式初始化

            隐式初始化:(子类中,不通过调用父类的构造方法的方式,来初始化父类对象)
               1. 当父类  提供了  默认的构造函数,
               2. 且子类的构造函数中  没有  显式调用父类的其它构造函数,
               3. 则在执行子类的构造函数之前jvm会自动执行父类的构造函数(初始化父类对象),


              java语言中所有的类都直接或者间接的继承自Object类
             换句话说,只要不是Object类的类,都有父类(要么直接)


          b.显式初始化:(在子类中通过 显式的调用父类的构造方法,来初始化父类对象)
            显式初始化:可以在子类构造函数的 第一条语句通过 super()super(…)   调用父类的  默认构造函数  或   特定签名的构造函数
         且在父类中必须设置一条无参的构造方法

 

  3.父类域的隐藏

         1. 父类 和 子类 有同名的成员变量
         2. 在子类中访问和父类同名的成员变量,访问到的是子类的同名成员变量的值
         3. 在父类中访问和子类同名的成员变量,访问到的是父类的同名成员变量的值
         4. 好像在子类中,通过变量名,访问不到父类中同名的成员变量  这种现象我们称之为父类的域的隐藏


             在子类中不仅可以有和父类同名的成员变量,还可以有和父类方法签名完全一致的方法

        1.在子类中如果调用同方法签名的方法,执行到的是子类的同方法签名的方法代码

        2.在父类中调用,和子类同方法签名的方法时,执行的仍然是子类的同方法签名的方法

        3.好像看起来,子类的方法将父类中的同方法签名的方法给"覆盖掉了"



         此时如果,需要访问父类中同名的成员变量,可以   super.父类成员变量,来访问父类的成员变量


4.super 关键字

     目的:    为了访问被屏蔽的直接父类的成员变量和成员方法
  ********使用方式:***************************************************************************
         在子类的方法中使用。(因为类有成员变量和方法,super关键字描述的是一种动作,所以要放在方法中才能执行)
        
         用 super.    的方式来访问父类的  成员变量  和  成员方法
         用 super()   的方式来调用父类的  构造方法  ,但是必须放在方法体的首句

         super() 使用的地方:子类的  构造方法  的句首,就可以调用到父类的构造方法
        
  
         如何调用父类的构造方法:
         
         Father father = new Father(参数列表)
         会根据参数列表里面的东西,自动判断你到底是创建的哪个对象。
         此时的 father. 就会得到Father里面的  非构造方法 和 成员变量

         super() 表示的是调用的是构造器,专门调用构造方法

         首先构造方法里面的参数是一个类型值,所以在super()进行调用的时候
         ()中应该输入的是一个值,而不是一个方法名或者变量名
         即
         class A
         {public int weight
         public A(int weight){
             }
         }
         class B extends A{
             public B(){
             super(10);//此时就是将10给到类A的构造方法中的weight
             }
         }


 
         举例2:
public class Son extends Father {
       public  Son(){
               super(5);
           System.out.println(super.i);
           System.out.println("chirld say");
             }
        
       public static void main(String[] args) {
            Son son = new Son();
            Father father = new Father();
          }
      }

    class Father{
    int i = 5;
     public Father(int a){
        System.out.println("Father say");
         }
     }


此程序的执行步骤如下:
          1.先加载类 Father ,里面有成员变量i 和 一个带一个参数的构造方法
         2.加载类 Son 继承自 Father ,里面有一个构造方法 Son,一个main方法
         3.在构造方法Son 中,我们想调用父类的 构造方法 ,我们就在句首使用super
         4.因为构造方法带了一个参数,所以我们要有一个对应的参数来让JVM知道我们调用的
           父类构造方法是带了一个 int 参数的构造方法,所以用 super(5)
         5.最后在主方法 main 中写我们需要运行的东西。我们想要运行 Son这个类,那我们就
           通过Son这个类来创建 对象 ,命名为 son ,即Son son = new Son();
         6.此时 JVM就会开始自动执行 构造方法,因为 new Son(),new Father(),所以会执行
           类 Son  和类Father 中的对应的构造方法。
           我们也可以通过创建好的对象 son ,来访问所有和 son 有关的 变量和方法
            比如 son.i 访问到了 父类Father中的成员变量 i



   5.final 关键字

        1.Final修饰的变量变成常量。不能修改。
        2.被final修饰的类不能被继承,被final修饰的方法不能被覆盖

        作用或者说好处:

        1.使用final修饰类和方法可以提高安全性(减少出错的可能)   因为你的不能被别人通过继承的方式来修改
        2.使用final修饰类和方法可以加快某些类型检查过程(类型)

        通过final修饰符,我们可以定义自定义常量:
           自定义常量, 何时对其进行初始化?
         
   a.在声明自定义常量的同时,初始化该自定义常量
            final int i = 100;

          

   b.通过构造方法初始化,自定义常量的初值, 但是必须保证,在每一个构造方法中,都  必须  对该自定义常量初始化。

     否则就会报错

            final int j;
            public 构造方法1(){
              j = 100;
            }
            public 构造方法2(){
              j = 100;
            }


  6.package 关键字

      包有以下用途:
     (1) 将相关的类和接口分组,包名通常表示包中各个类和接口的用途(开发中 用来组织我们项目中的类)
     (2) 包创建了不同的命名空间(Namespace),从而有效地解决了类型命名冲突问题 (解决命名冲突)
         
         命名冲突:
         1. 同一个包中不能出现类名相同的类
         2. 在不同的包中可以存在同名的类(通过这样的命名方式解决命名冲突)
     
     (3) 包提供了对应用程序内部实现机制的保护域 和 访问权限控制
          默认访问权限: 同包  不同  访问权限控制
          比如如果某个类的成员被默认权限修饰符所修饰, 不同包的类访问不到这个成员变量


     命名方式:域名反转
     com.cskaoyan.packages


 

  7.import 关键字

      完全限定名 Full Qualified Name(包名 + 类名)
      com.cskaoyan.imports.ImportDemo  //就相当于java中的绝对路径
      
      当在类体中使用了与当前类不同包的类名时,编译器编译时因为无法找到该类的定义而失败
     (1) 使用不同包类的完全限定名
     (2) 使用import声明,为编译器提供该类的定义

     import声明一般紧跟在package声明之后,必须在类声明之前,其基本语法如下:
           import <类的完全限定名>;

     Java语言核心包 java.lang包中的类将被隐式导入,可以直接使用其中的类,String,int。。。
      
      import声明提供了一种包的智能导入方式:
      import <包名>.*  ————这种方式只能导到当前包下的类,子包下的类依旧不能被导入

      智能导入方式不能递归导入。




Day 9

---------------------------------------------------------------------------------------

  1.多态:


      概述
         某一个事物(相同函数名) ,在不同时空表现出来的不同形态(不同函数体)
         比如; 水  < 0  冰
               水  常温  水
               水  >= 100 水蒸气


      *****多态的前提:

            1.要有继承关系, 继承关系
            2.要有方法覆盖,
            3.要有父类引用指向子类对象


      

      *****方法覆盖:

          1.定义子类中替换 父类或祖先类 对某个方法的实现
                       class Fu{ public void method1(){输出Fu};  }
                       class Zi { public void method1(){输出Zi};  }

          
          2.实现方式:通过 子类 重新定义与 父类或祖先类 中具有 同样签名 返回类型 的非静态方法(注意不是变量)
                                (因为 成员变量静态方法的访问,编译和运行都是访问 父类的 ,子类无法覆盖)

          3.覆盖的方法的权限范围:只能比原先的父类的访问权限  等于 或者 大于。
             private <  default < protected < public

          4.注意事项

              1.父类中被 final 修饰的不能被子类覆盖;其他修饰符修饰的方法可以被子类覆盖
              2.静态方法不能被覆盖,只能被隐藏(域的隐藏)
              3.构造方法不参与方法覆盖(构造方法不是属于成员)
              4.只有在子类类体中可以访问的父类或祖先类的方法才能被覆盖 (父类中private修饰的方法不能被覆盖)
              5.父类被覆盖的方法的 参数列表中被声明为final的参数 在子类的覆盖方法的中可以不必指定为final
              
    

     *****父类引用指向子类实例对象

          
          语法格式:父类类型 a = new 子类类型();

          从数据类型的角度:
               子类类型可以被看成是父类类型
          
          从代码复用的角度:
               子类继承父类,子类拥有父类的所有成员,并且能访问可访问的所有成员。
              
          子类引用不能指向父类实例:(子类 b = new 父类();)
              这是因为子类可能会有自己独有的方法类型,这是父类所访问不到的,
              编译器为了避免出错,所以会直接报错。

            

        实例:

public class PoliDemo1 {
public static void main(String[] args) {
        Cat cat = new Cat();//初始化
        cat.shout(1,2);//通过引用对象cat来访问其实际类型中shout()方法,因为覆盖,所以调用子类的shout方法
        Cat.testStatic();//直接访问Cat
        Animal.testStatic();
        
        Animal a = new Cat(); //父类引用指向子类实例
        a.shout(1, 2);               //此时调用的就是Cat类中的shout方法
        System.out.print(a.i); //会输出左边Animal中的成员变量i = 100,而不会输出Cat中的成员变量i =200

       /* 在成员访问的时候,遵循:
        成员变量的访问:编译看左边,运行看左边
        成员方法的访问:编译看左边,运行看右边

         Animal a = new Cat();
        a.成员变量:
         在编译的时候,会根据左边的Animal来判断,a能够访问到Animal中的成员变量和成员方法
        System.out.println(a.成员变量) :在运行的时候,实际访问到的成员变量的值就是左边Animal的成员变量的值。

        

        a.成员方法:
        在编译的时候,同上,还是看左边的Animal这个父类的方法中有哪些可以访问到的方法。
        System.out.println(a.成员方法)  :在运行的时候,实际能够访问到的方法就是Cat中和父类共有的或者父类有的方法。

        一定要清楚:构造方法不存在访问的情况,在new的时候已经对构造方法进行了访问。
                            且构造方法不属于成员,所以多态针对的是普通的  成员变量  和  成员方法。

         */

     }
}
class Animal {
    int i = 100;
    public void eat() {
        System.out.println("Animal eat");
    }
    public void sleep() {
        System.out.println("Animal sleep");
    }
      void shout(final int a, final int b) {
        System.out.println("Animal shout");
    }
    public static void  testStatic() {
        System.out.println("Animal static");}
    }
}

class Cat extends Animal {
    int i = 200;
    //在子类中重新定义父类中的方法的实现(方法覆盖)
    protected void shout(int a, int b) {//它的范围是protected ,父类是默认权限,
                                        //  protected权限比默认大,所以可以覆盖

        System.out.println("Cat shout");}
    public static void  testStatic() {
        System.out.println("Cat static");}
}


实例2.

public class test1{
    public static void main(String[] args){
        Car benz=new Benz("奔驰车");//父类引用指向子类实例
        Car dz=new DZ("大众车");//父类引用指向子类实例
        Car bmw=new BMW("宝马车");//父类引用指向子类实例
        people p =new people();
        p.drive(benz);
        p.drive(dz);
        p.drive(bmw);
    }
}

class people{
    public String name;
    public Car car;
    public void drive(Car car){
        this.car=car;//为了让成员变量 car 获得 drive之后 new得的实际参数值
        car.run();
    }
}

class Car{
    public String name;
    public Car(String name){
        this.name=name;   //用构造方法来获取这个name值
    }
    public void run(){   //此处不是线程,因为Car没有实现Runnable接口,也没有继承Thread
        System.out.println(name+"已在路上飞快的奔跑");
    }
}

class DZ extends Car{
    public DZ(String name){
        super(name);//调用父类的构造方法,初始化父类对象
    }               //此处过程是:new出对象dz,并且初始化name为"大众车" DZ dz = new DZ("大众车")
                  //然后此时,DZ类中的形参被赋予"大众车",super(),调用了父类Car的构造方法
                    //Car的构造方法为将 得到的name 赋值给Car的成员变量name,从而Car的成员变量name就变成了大众车

class Benz extends Car{
    public Benz(String name){
        super(name);
    }
}
class BMW extends Car{
    public BMW(String name){
        super(name);
    }
}


输出结果:
奔驰车已在路上飞快的奔跑
大众车已在路上飞快的奔跑
宝马车已在路上飞快的奔跑


*****************************************************************************************

成员变量在访问中的特点,要清楚


    String s = new Panel();  // 虽然编译不通过,但这里只是作为一个极端的例子来演示

             a. 使用引用s只能访问String的数据成员和方法;

             b. 即使s的运行时类型是Panel,但其运行时类型对于一个引用来说是不可见的;

             c. s不能访问其运行时类型Panel中的任何数据和方法,只能通过s调用String的数据和方法;

   
      如果  运行时类型 Panel 是 编译时类型 String 的子类的话,那么就不存在 强制类型转换,

那多态有什么用呢?

         i. 设想一个父类Father,有多个子类Son1、Son2、Son3...,每个子类都覆盖了f1方法,并且f1的内容还都不一样;
                                                               (即要在  每个子类  重写  覆盖方法)

         ii. 现在用一个Father的引用来指向各个不同子类的对象,然后分别调用f1,就会呈现出每个子类f1的行为;
              Father a = new Son1;    a.f1 ; //是Son1的f1方法

              Father a = new Son2;    a.f1;  //是Son2的f1方法

!!每修改一次引用的指向,就会更新一次该引用所对应的虚函数表,例如f = new Xxx(),这里修改f的指向,
             因此就会根据新的运行时类型更新一下f的虚函数表;

         iii. 这就发生了,同一个引用,调用同一个方法却能表现出不同行为的现象,这就是多态;


         iv. 而其本质就是编译时类型的引用表现出运行时类型的行为;


Fu f = new Zi();

在这个引用变量f指向的对象中,他的成员变量和静态方法与父类是一致的,他的非静态方法,在编译时是与父类一致的,运行时却与子类一致(发生了复写)。

个人分析:

Fu f = new Zi();----------首先了解变量F到底是什么,把这句子分2段:Fu f;这是声明一个变量f为Fu这个类,那么知道了f肯定是Fu类。然后我们f=newZi();中建立一个子类对象赋值给了f,结果是什么??

结果是,拥有了被Zi类函数覆盖后的Fu类对象----f    

也就是说:
只有  子类的函数覆盖了父类的函数   这一个变化,但是f肯定是Fu这个类,也就是说 f 不可能变成其他比如Zi这个类等等(突然f拥有了Zi类特有函数,成员变量等都是 不可能 的)。所以 f 所代表的是函数被复写后(多态的意义)的一个Fu类,而Fu类原来有的成员变量(不是成员方法不可能被复写)没有任何变化----------------获得结论:成员变量:编译和运行都看Fu。

但是f的Fu类函数被复写了。--------------获得结论:非静态方法:编译看Fu,运行看Zi

对于静态方法:编译和运行都看Fu!!

其实很简单,首先我们要理解静态情况下发生了什么?

       当静态时,Fu类的所有函数跟随Fu类加载而加载了。也就是Fu类的函数(是先于对象建立(new的时候)之前就存在了,无法被后出现的Zi类对象所复写的,所以没发生复写,那么获得:C:静态方法:编译和运行都看Fu。


例:

class Fu {
    int num =5;
    static void method4() {
        System.out.println("fu method_4");
    }

    void method3() {
        System.out.println("fu method_3");
    }
}

class Zi extends Fu {
    int num =8;

    static void method4() {
        System.out.println("zi method_4");
    }

    void method3() {
        System.out.println("zi method_3");
    }
}

class DuoTaiDemo4 {
    public static void main(String[] args) {
        Fu f = new Zi();  //父类引用指向子类实例
        System.out.println(f.num);   //访问成员变量,编译运行均看左边
        f.method4();     //访问 静态成员方法,规则同成员变量
        f.method3();     //访问成员方法,编译时与父类一致,运行时与子类一致
        Zi z = new Zi();
        System.out.println(z.num);
        z.method4();
        z.method3();
    }
}

    输出结果:
    5
    fu method_4
    zi method_3
    8
    zi method_4
    zi method_3



   2.多态的优势和弊端

       优势:
              提高代码的可维护性。
             提高代码的拓展性。
             避免经常书写重复代码。


       劣势:
         **** 不能使用子类的特有功能:通过父类引用只能访问(编译)父类所定义的方法;
             无法访问子类中某些特定的方法
     如何解决
            1.创建子类对象调用子类方法
                子类  a  =new 子类();(不使用多态)


             2.把父类的引用强转为子类的引用
              Animal4  a = new Dog4();
               if(a instanceof Dog4){
                 Dog4 d = (Dog4) a;
                 d.子类特有方法;
                 }

            此时就可以通过这种方式访问到子类的特有方法。

             

   3.instanceof 运算符

       解决父类引用强转为子类引用时,无法判断数据类型的问题。
       运算符 instanceof 用于判断对象(引用变量所指向的实际的对象) 是否是某个类的对象
       目的:判断该引用的   运行时类型    是否为   指定类或接口的 子类  或者  相等,简单地说就是
       检查该引用指向的对象是否是 指定类或接口的实例(或者其子类的实例),返回值当然是boolean类型的;

       格式:  对象引用a  instanceof   类名或者接口名  
         
         结果 true : 表示对象a是X这个类的对象
              false : 表示对象a 不是X这个类的对象

       通过 instanceof运算符,规避把父类的引用强转为子类引用所将要面对的风险

       注意点:
       1. instanceof 左边的引用的编译时类型 必须 和右边的类型有继承关系


  4.引用类型兼容


    定义:如果某个引用变量的值,可以赋值给另外一个引用变量,我们就说这两个引用类型兼容
           String  a ;  
           Object  b;
             b  =  a;(a 必须  与b相同 或者  是b的子类)
             a  =  (String)b;(如果右边范围更大,那么就需要 强制转换)

    引用变量的赋值
       赋值兼容性规则:
         (1) 对引用类型的变量进行赋值操作时,赋值号 右边的表达式的类型必须与赋值号 左边
               a.对象引用类型相同 或者
               b. 是其子类型
         (2) 方法的return语句返回的结果的类型必须与方法声明中的返回类型相同或是其子类型

         (3) 所有的引用变量都可以被赋予null


    向上转型 : 把 子 变成父(  自动类型转换,只要有继承关系 )
     父类类型 a  = new 子类类型();
     (我们把一个原本是 Son 对象的 地址 赋值给了一个 父类类型的引用
   
    
    向下转型 : 把 父转成子( 要求父类的引用本来就指向子类对象(强制类型转换)
      子类类型 b = new 父类类型();
     (我们把一个原本是Father类型的对象地址,赋值给了一个子类类型的引用)
    

******(引用类型)向下转型例子:

      Object s;
      String o = (String)s;
      因为  右边s 范围比  左边o 更大,所以向下转型的时候可以强制转型
      这是前提,不满足就会报错。


       1. 向下转型,存在风险

           当子类没有定义只属于自己的成员的时候,此时使用强制转化,实现向下转型,此时,
           通过强制转化之后的引用(子类类型的引用),来访问子类中的成员,
            Father f = new Faher();
            Son son  = (Son) f;
            son.访问子类成员  可以访问的

        实际开发中会不会向下转型呢?
       
           会,比如,我们昨天利用运行时多态所写的,方法,在这个方法中,我们确实可以收集到所有动物的声音,但是,
       有可能我们可能对个别种类的动物,做一些特殊操作(访问这些动物独有的成员),但是通过父类引用是无法访问到
       这些需要特殊操作的动物,所独有的成员,因此我们做向下转型

       但是我们可以通过 instanceof运算符,来规避向下转型的风险


        Circle Triangle  Retangle



-------------------------------------------------------------------------------------------------

Day10

           

   1.抽象类 (abstract 修饰符)


        在Java中,一个没有方法体的方法应该定义为抽象方法,
        而如果一个类中含有抽象方法,则该类必须定义为一个抽象类

        抽象方法   和   抽象类  语法:
          1.抽象类和抽象方法必须使用 abstract(修饰符) 声明
          2.抽象方法不能有方法体(连定义方法体的{}都没有)  public void abs();
          3.抽象类不一定有抽象方法,但是含有抽象方法的类必须是抽象类
          4.抽象类本身不能实例化(但是多态机制可以用子类实例化)
             a. 通过抽象类的子类来实例化抽象类
       
        
        抽象类的子类(二选一):

          ***** 1.如果不想重写抽象类里面的抽象方法,则子类必须也是抽象类
                (如果重写了抽象类里面的 所有方法,就可以不定义为抽象类,如果没有重写所有方法,则必须定义为抽象子类)
          ***** 2.如果不是抽象类,则必须实现抽象父类的所有抽象方法
                (实现 部分方法也必须定义为抽象子类)

   *********抽象类不能被实例化,即不能使用抽象类的构造函数创建对象(java语法),抽象类中可以声明抽象方法,
        除此以外抽象类与一般的类没有区别

       1.抽象类只定义了类的部分行为,这些行为是子类共有的(具体行为可以和抽象行为共存)
       2.其它行为(抽象方法)由子类实现的抽象方法提供,因此抽象类通常作为一个框架(该类中有声明了这样一个行为)
       3.把子类将实现的抽象方法组织起来,简化或限制子类的设计

  ******抽象类的成员特点:

        1. 抽象类的成员变量(和普通类没有任何区别):既可以变量,又可以是常量
        2. 抽象类的构造方法(可以):用于子类访问父类数据的初始化
        3. 抽象类的方法可以是抽象的,也可以是非抽象的(具体行为可以和抽象行为共存),作用是:
            抽象的:(从语法层面)强制子类实现  枚举类型
            非抽象的:子类可以复用

        第二点:
        初始化问题:
        abstract class Creature {
           int i;
           int j;
            public Creature() {
            }
            public Creature(int i, int j) {
               this.i = i;
                this.j = j;
            }
        }

        abstract class Animal extends Creature{//没有重写父类Creature的方法,
          int height;                                               //所以要定义为抽象类
          int weight;
          int age;

          public abstract void shout(Animal a); //抽象方法
        
          public Animal(int height, int weight, int age,int i ,int j) {
           super(i,j);             //仍然可以用super() 来调用 抽象父类 的构造方法                                 
           this.height = height;
           this.weight = weight;
           this.age = age;
        }
        
        class Cat extends Animal {
              int k;

        public Cat(int height, int weight, int age, int i, int j, int k) {//重写了父类Animal的方法,
            super(height, weight, age, i, j);                                         //所以不用定义为抽象类
            this.k = k;
            }

        public  void shout(Animal a){   //实现了父类的抽象方法
        System.out.println("cat shout");
        }

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

        // 抽象类本身不能被实例化
        //Animal animal = new Animal();
        //animal.shout(animal);

        // 抽象类不能被直接实例化
        // ctrl + 鼠标左键,点击代码产看源代码
        //TestAbstractClass testAbstractClass = new TestAbstractClass();
        Animal cat = new Cat(1,2,3,4,5,6);//实例化子类,用多态来使用抽象类中的方法
        cat.shout(cat);//******很重要的思路,运行时实际上运行的是父类被子类覆盖的方法。

        //首先分析cat.shout的方法-----  public  void shout(Animal a)
        //那么shout中,必须对应的Animal a 即是  类 + 引用对象
        //*****Animal cat = new Cat,中的cat,需要将其看成是  new Cat(参数列表)  的形式,
        //*****因为引用对象cat实际上是为了对父类进行访问(包括赋值,访问方法等)
        //*****建立了一个对象cat之后,我其实初始化的是Cat中的所有成员和方法
        //*****那么用cat. 的方式去访问的时候,访问的是Cat中的成员对象和方法
        //*****cat.成员方法(引用对象),代表的含义即是在方法中调用类Animal的引用对象
        //所以记住 父类 a = new 子类,在引用子类的方法的时候,需要看清楚方法中的参数列表,
        //如果参数列表为空,那么引用也为空  cat.shout();
        //如果参数列表不为空,就要看清楚参数列表的东西,如果为类名,那么就用 引用对象名cat(cat.shout(cat))
        //如果为数据类型,那么就用 cat.shout(2)或者cat.shout("aaa")这种符合原方法中定义的参数列表格式


    }
}



2.接口  interface (看成一种特殊的类)

         

   接口的含义:一个完全抽象的类,所有的方法都是抽象方法(或者有默认方法和静态方法)

        
            想象另一个情况:马戏团里的猫,狗

        马戏团里的猫和狗,比较特殊,猫和狗,他们都有一些特殊的技能比如:算术,钻火圈
        但是,对猫和狗而言,他们具体的算术和钻火圈的具体表演方式,也不一样
        比如: 狗 算术  听声计算
                   猫 算术  看着表达式来计算

        当分析完应用场景之后我们知道,将马戏团中动物的计算和钻火圈的行为,放入抽象类Animal是不合适的
        因为,一旦将行为放入,Animal抽象类当中,意味者所有的动物都有了算术和钻火圈的行为。

        同时,我们分析出,即使对于马戏团中的动物来讲,不同类型的动物,对于同一种行为,它们的表现方式也各有不同

        -》 所有马戏团中的动物
                共性: 有算术和钻火圈的行为
                个性: 针对不同的动物种类,他们的具体的算术和钻火圈的行为各有不同

        他们具备的技能应该放在动物类里面吗?  放在一个接口当中

        接口的使用场景: 通常使用接口来声明,存在这样一些行为,某些不是被所有子类所共有的行为,
                      而是一些特殊群体所共有的行为, 使用接口来组织这些抽象行为


        实现接口,其实相当于表示的是一个实质上的继承关系

        继承方法: class A implements 接口B      接口B和A它们之间是继承关系

        定义方式: interface + 接口名{}

        一种比较特殊的接口:空接口,常用来作为一种标记,比如 Cloneable ,
                            只有子类继承了这个接口,才能使用 clone() 类。
        

         接口的实例化:无法直接实例化(不能 new ),但是可以通过 new 接口的子类 来间接实例化接口

        接口的子类(普通类)
        1.要么是抽象类(接口的实现类没有实现接口中定义的所有的抽象方法)
        2.要么重写接口中所有的抽象方法 (子类就变成一个普通类,从而该普通类可以被实例化)

     接口的关系:

        接口和接口的关系:(继承)

        1.接口之间可以相互继承即可以定义一个接口用extends关键字去继承一个已有的接口

         普通类和接口的关系:(实现)
        2.可以定义一个类用implements关键字去实现一个接口中的 所有方法

         抽象类和接口的关系:(实现)
        3.可以去定义一个抽象类用implements关键字去实现一个接口中定义的 部分方法

        类与类,类与接口的关系:
        4.一个类可以继承一个父类的同时,实现一个或多个接口,extends关键字必须位于implements关键字之前 。
           class Son extends  Father  implements   inteface1 , interface2 , interface3,....{    }
        

    接口中的静态方法和默认方法:


    但是以上对普通类的普通类的多重继承的说法,只针对jdk1.8之前,在jdk1.8之后  接口   中新增了两种方法:
    1. 默认方法  default  
    2. 静态方法  static
当你要定义工具方法的时候,通常如果是某接口的实现类都需要的,那么就可以在接口中定义静态方法
    这两种方法在接口中,都是可以有方法实现的

    静态方法和默认方法的访问方式:(先要用一个类先继承了这个接口,才能访问操作)
       1.在接口中,如果要访问静态方法,如果只通过   方法名  来访问的话,
         只能在定义静态方法的接口中访问(且需要在 接口 的 某个方法中 ,输入 静态方法名 ,访问)
       2.如果使用  接口名.   的形式来  访问静态方法,静态方法在任何地方都可以访问到
       3.对于默认方法,在子类类体,或者  子类对象名.的  形式时都可以访问到
   
 

    接口中的注意事项:

        1.接口中的成员都不能指定访问修饰权限,默认 public
        
        2.接口中的  成员变量 默认为 static final (所以定义时,需要显式初始化)
                              成员方法 默认为 public abstract
        
        3.接口中不能定义构造方法、构造代码块、静态代码块等(所有方法都是抽象方法)

         4.接口方法可以 覆盖 和 重载,可以把  默认方法  覆盖成  抽象方法(不继承就不能覆盖)
            抽象方法 覆盖 默认方法:
                      父接口:default void addDefault() {testStatic();}//默认方法
                                     
                      子接口:  {@Override  //在子类中重写父类方法所使用的检测工具
                                              void testDefault();}//抽象方法

        5.如果不考虑接口静态方法和默认方法,接口除了作为一种标记,就是用来继承的


    接口的优势:

         可以实现 不相关 类的 相同行为,而不用考虑 类的层次。
        (接口 反正都是用来继承的,最后实例化 继承了接口的 子类)

        

 

  3.单亲继承和多亲继承


    在普通类中,java也声称实现了多重继承,如果把实现接口当做是一种继承,那么因为一个普通类,可以在继承一个类的
    同时,实现一个或多个接口,从这个角度来理解,普通类实现了多重继承(并非真正意义上的多重继承)
    
    通过  class A extends B implements C,D  {}
    使 类A 形式上继承了 类B,同时继承了 接口C和D

    

  4.内部类(即在主类中 还有 定义类)

      访问特点:
                1.内部类 可以访问 外部类(即内部类之外,主类之内)
                2.外部类如果要访问内部类,需要先在 外部类 创建对象 (new 内部类) ,用引用对象访问内部类

      优势:可以限制外部类的外部用户访问内部类的东西。
            内部类中可以创建对象,访问外部类的成员。

      class A{//主类
        class B{//成员内部类(主类成员)
            public void C(){
            class D{ }//局部内部类(方法体中)
            }
         }
      }


      1. 成员内部类:

          位置:主类的其他成员之外(和主类成员方法 一个级别)
          访问方式:
               1. 在外部类访问:在外部类的 方法中 创建对象(new 内部类)
               2. 在外部类 外部 访问:(1)先在外部类创建内部类对象
                                                    ( 2 )然后在外部类外部,创建外部类对象
                即  new 外部类();
                     外部类对象.new 内部类;

               
                如果内部类被 static 修饰——— new 外部类.内部类

    class A{//主类
        int i;
        class B{//成员内部类(主类成员)

            public void C(){
           (final) int b; //含有局部内部类的方法体的成员变量默认被final修饰
            class D{ }   //局部内部类(方法体中)

            }
         }
      }
      此时用: new A() --->>外部类对象.new B
    
   class A{//主类
       static class B{//成员内部类(主类成员)
            public void C(){
            (final)int a; //含有局部内部类的方法体的成员变量默认被final修饰
            class D{ }    //局部内部类(方法体中)
            }
         }
      }
    此时用: new A.B()

    类名.this —— 指的是this所在这层类。
    

    
    

     2. 局部内部类:

          位置:内部类中方法之内(可以外部有嵌套成员内部类)
          访问方法:(反正只能在方法体中)
                 1.可以在方法体中访问外部类。
                 2.可以在内部类中创建对象,调用内部类中其他成员。

         注意事项:
         1.只能在方法体中创建内部类的对象,且只能在最后再创建,
                                因为 顺序为:定义类 ——>> 使用类。
         2.局部内部类  所在方法体中的  局部变量  均默认被 final 修饰
         3.需要 final 的原因如下:
                        1.局部变量会随着方法结束而被收回。
                        2.为了继续使用局部变量。
                        3.被 final 修饰的变量会进入方法区中常量池。

            

      3. 匿名内部类

           本质:一个(继承了类 或 实现了接口 的子类)匿名)对象。
                要将其看成一个对象。
          
           格式:   new  类名或接口名(只能使用一次) 
             eg.  System.out.print(new String(bytes, 0 ,length))

           
           匿名内部类的  类型  为?
                其父类类型( new 的类型)



***************演示匿名内部类用法:***********************


public class DevelopAnoInnerClass {
        public static void main(String[] args) {
        int i = new Scanner(System.in).nextInt();//键入
        //为 UserInputHandler 类创建对象,转 看对象
        UserInputHandler userInputHandler = new UserInputHandler();
        
        //对象.方法(参数列表)的形式来访问,不过在参数列表中
        //handle方法的引用类型参数 通过 new 一个接口,并在接口中
        //重写了接口中的方法。(必须重写所有抽象方法)

        userInputHandler.handle(i, new TypeHandler() {
            @Override
            public void handleFirst() {
                System.out.println("亲,您输入的是1哦");
            }
            @Override
            public void handleSecond() {
                System.out.println("亲,您输入的是2哦");
            }
        });
        userInputHandler.handle(i, new TypeHandler() { //覆盖前面的同名方法
            @Override
            public void handleFirst() {
                System.out.println("不好意思1");
            }
            @Override
            public void handleSecond() {
                System.out.println("不好意思2");
            }
        });
    }
}

class UserInputHandler {
    //定义了一个handle方法,参数有int 类型的type,TypeHandler类型的handler
    //TypeHandler 为接口,转而看接口|接口看完之后就是熟悉 : 参数列表传递值的方式,下面讲。
    //此处参数列表,给handle传递了一个int 值,一个 TypeHandler 的引用

    public void handle(int type, TypeHandler handler) {
        if(type == 1) {
            handler.handleFirst();        //引用类型传递接口 的形式,可以访问接口中的方法
        } else if(type == 2) {
           handler.handleSecond() ;   //此方法需要重视,可以通过这种方式来间接访问某些方法
        } else {
            System.out.println("亲,input illeagal哦");
        }
    }
}

interface  TypeHandler {
    //定义了两个方法,可以通过重写所有抽象方法来拓展我们代码的实现功能多样化
    void handleFirst();
    void handleSecond();
}




  4.JAVA中参数列表的参数传递方式

      1.值传递 int a;
      2.引用传递  Person person;

      区分方式:通过  传递参数的类型  来区分到底是值传递,还是引用传递。
//一个Person类
class   Person{
        //name字段
        String name;
        //构造器
        public Perspn(String name){
            this.name=name;
        }
}
//A类
class A{
    //B方法 需要两个参数 一个int类型 一个 Person类型
   public void  B(int a , Person p){
       //输出下
       Sysrem.out.prinltln("数字是:"+a+"Person 的名字是:"+p.name);
   }
   //然后在main方法里面调用方法B,需要传递两个参数,一个int类型,一个Person类型
   public static void main(String args[]){  
       //new 一个名叫person的Person对象   
       Person person =  new Person();
       int i=1;
       //传递参数, 调用B方法
       B(i,person);
   }
}



一个常见的匿名类:

    键入  int i = new Scanner(System.in).nextInt();
             new Scanner(System.in)
   就可以看出一个对象,
              .nextInt()   可以看作是对象中的一个方法(得到键入值)





----------------------------------------------------------------------------------------------------------------------------

Day11.


    1.Object 类

    
    ******是所有类的直接或间接父类*******
        API(除了5个线程相关的API):
                 返回值类型   类名
        1.public find Class getClass()
        2.public int hashCode()
        3.public boolean equals(Object obj)
        4.public String toString()
        5.public void finalize()
        6.public Object clone();


       具体功能:

           1.getClass():返回运行时类(获取实际运行时类的  字节码文件对象

                   getClass.getName()——————获取运行时类的类名:包名 + 类名(全限定名)
                           .getSimpleName()——————获取运行时类的类名:类名
           

           2.hashCode():返回哈希码值(内存地址的映射值)


           3.equals(Object obj):默认比较对象的地址(相等对象必须具有相等的哈希码)

               obj——要与之比较的引用对象。返回值ture/false
               name equals(str.name)
               对象通过赋值就会指向同一个地址

           4.public String toString():返回该对象的字符表示,以文本的方式。

               用法:重写toString()方法。
               public static void main(String[] args) {
                      Dog d = new Dog();
                      System.out.println("dog="+d);}
               class Dog{}

                
               输出结果:
dog=cn.galc.test.Dog@150bd4d(类名+哈希码值)
               
               重写方式:
                class Dog{
                       @Override
                       public String toString() {
                       return "Dog"; }// toString返回值类型为字符串

               输出结果:Dog
                   

            5.protected void finalize():当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

                不推荐使用,因为垃圾回收器回收执行的时机不确定。
                System.gc()——强制启用垃圾回收器

            6.protected Object clone():创建并返回此对象的一个副本。

                因为每个类直接或间接的父类都是Object,因此它们都含有 clone()方法,
                但是因为该方法是protected,所以都不能在类外进行访问。
                要想对一个对象进行复制,就需要对clone方法覆盖。
                
              目的:克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,
                    所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。

  浅克隆的步骤:

                   1. 被复制的类需要  实现  Clonenable  接口
                     (不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)

                   2. 覆盖clone()方法,访问修饰符设为public
                      方法中调用super.clone()方法得到需要的复制对象。( native为本地方法)
public class ObjectDemo1 implements Cloneable{
                 int i = 10;
                 double j = 100;

                 InnerObject obj = new InnerObject(100);

      public static void main(String[] args) throws CloneNotSupportedException {

              //clone 创建并返回此对象的一个副本。
             ObjectDemo1 objectDemo14 = new ObjectDemo1(); //创建对象 objectDemo14

             System.out.println(objectDemo14.hashCode()); //1956725890

             Object clone1 = objectDemo14.clone(); //覆盖Object的clone(),赋给clone1

             System.out.println(clone.hashCode());//356573597,

             //通过重写方法,发现 objectDemo14 的哈希值和 clone 的不同,
             //说明已经克隆出了一份对象

             System.out.println(clone); //com.cskaoyan.objects.ObjectDemo1@1540e19d
                                       //默认输出方式是输出Object类对象clone的字符表示
                                       //可以通过重写toString()方法来输出其数值(Alt+insert来自动创建输出方式)


        ObjectDemo1 objectDemo15 = new ObjectDemo1();//同类创建 objectDemo15
        Object clone2 = objectDemo15.clone();        //覆盖Object的clone()方法,赋给对象clone2
        //其实质是:将 ObjectDemo1 的所有成员变量和方法给到对象 objectDemo15,然后 objectDemo15
        //克隆一份,覆盖Object的克隆方法,
        //此时产生了两个对象,并且其具有值相同但地址不同的成员变量和方法(objectDemo15和objectDemo14)

        
        //修改复制之后的成员变量的值
        ObjectDemo1 clonea = (ObjectDemo1) clone2;//强转clone2 类型为 ObjectDemo1,
        clonea.obj.i = 300;                          //才能访问到ObjectDemo1的obj对象
        System.out.println("AAA"+clonea.obj.i);   //此时访问到的clonea.obj.i是被修改过的,为300
        System.out.println("BBB"+objectDemo14.obj.i);//访问到的 objectDemo14 的对象指向的boj.i
    }

    class InnerObject{
        int i = 100;
    }

    输出结果:
              AAA300

              BBB100

               

    克隆的主要步骤:

        1.创建要克隆的类的对象a
        2.克隆对象 Object clone1 = a.clone();  //因为每个对象都有一个隐式的clone()方法
        3.强制转换 Object 类的对象clone1,让其可以被访问 clone1.i = 100;





  2.String 类

     
     字符串是常量,创建之后不能再改变。所以String对象可以共享。
             
                  String str="abc";
     等效于:char data[] = {'a','b','c'};
                  String str = new String(data);


     String 的构造方法:

         1.public String():初始化一个 空字符序列的 对象;

         2.String(byte[] bytes)

           通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String
          byte[] 字符数组:用来放 要ASCii解码的字符(解码:将 码值 —> 相应的内容)
           eg. byte[] bytes = {97,98,99,100};(或者byte[] bytes = {'a','b','c','d'})
               System.out.println( new String(bytes) );
               输出结果:abcd

        

         3.String(byte[] bytes, int offset, int length)

             通过使用平台的默认字符集解码指定的 byte 子数组,构造一个新的 String。
             offset:  要解码的第一个字符的索引(0 —— n)
             length: 要解码的字符个数
             使用方法:byte[] byte = {97,98,99,100};
                    String s1 = new String(byte , 2 , 2 );

         4.String(char[] value)

           分配一个新的 String,使其表示字符数组参数中当前包含的 字符序列。
             使用方法:
               char[] chars = {'a', 'b', 'c', 'd'};
              String s3 = new String(chars);

              输出结果:abcd
           

         5.String(char[] value, int offset, int count)

             分配一个新的 String,它包含取自字符数组参数一个子数组的字符。
             offset 参数是子数组第一个字符的索引,count 参数指定子数组的长度。
             该子数组的内容已被复制;后续对字符数组的修改不会影响新创建的字符串。
            
             offset:初始偏移量(0 —— n)
             count:长度
        eg.
             char[] chars = {'a', 'b', 'c', 'd'};
             String s4 = new String(chars, 2, 2);
             System.out.println("S4="+s4);
             //输出结果S4=cd


        + 号 需要注意的事项:

            1.“ + ” 在字符串的运算中代表 “拼接”;
                拼接一定是创建一个新空间,不会修改原来常量池上的子字符串
            2.每次 new String 都会进入常量池,每次查找String 都会先在常量池查找,

              如果没有就会新建。


Day13

1.compareTo() 函数  ————用于字符串比较大小

    用法:
       "abcd".compareTo("abc")     

    输出结果:1
    分析原因:字符串如果能比较的都相等,那么就是前一个字符串的长度 - 后一个字符串的长度(4-3)

       "abcd".compareTo("ad")
    输出结果:-2
    分析原因:字符串每一位依次比较,第一位'a'相等,继续比较下一位'b'和'd'不相等,
              然后用各自字符的ASCII来相减,'b' - 'd' = -2

    所以只有在输出结果为0时,表示两个字符串相等。


2.可变参数列表(...)

    
    作用:形参个数不确定时,可在方法声明的参数列表中使用( public void A(int... ags) )
    
    形参个数: 0 ~ N
    
    优先级:在方法重载的时候,可变参数列表  优先级低于 确定参数列表。
    
    设置位置:可变参数只能位于方法参数列表的最后一个(int a,double b,int... args)
    
    在JVM中,会将 int... a 和 int[] a 等价,在方法体中 int... a,可以赋值 a[1] = 1;
             不同的是,在不传参数的时候,数组为0,无法输出数组长度,而可变参数的长度为0.


    

3.包装类

    每种基本数据类型,都对应着一个相应的包装类。
    int - Integer / short - Short /特别地: char - Character
    
    基本数据类型的   装箱 / 包装类型的  拆箱
    

    装箱:(基本数据类型 装入 包装类,可以使用包装类中的一些特定方法)

         Integer i = 10;
       实质上: Integer integer = new Integer(10);
                    i = integer;

        
        Charater c = 'a';
    

    拆箱:( 包装类对象直接 赋值 给基本数据类型)

         int i = new Integer(10);
        

数据类型转换的方式:

intValue()

如Integer类型,就会有intValue()方法:把Integer类型转化为Int类型。其他类似,都是一个意思

valueOf()

如String就有valueOf()方法,意识是说,要把参数中给的值,转化为String类型,Integer的valueOf()就是把参数给的值,转化为Integer类型。其他类似,都是一个意思。


    装箱和拆箱的面试题:

        1.下面这段代码的输出结果是什么?
public class Main {
    public static void main(String[] args) {
 
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
 
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}


也许有些朋友会说都会输出false,或者也有朋友会说都会输出true。
但是事实上输出结果是:
true
false

原因解释:
    在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,
    便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
     上面的代码中i1和i2的数值为100,因此会直接从cache中取已经存在的对象,
     所以i1和i2指向的是同一个对象,而i3和i4则是分别指向不同的对象。


2.下面这段代码的输出结果是什么?    
public class Main {
    public static void main(String[] args) {
         Double i1 = 100.0;
        Double i2 = 100.0;
        Double i3 = 200.0;
        Double i4 = 200.0;
 
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}


也许有的朋友会认为跟上面一道题目的输出结果相同,但是事实上却不是。
实际输出结果为:
false
false

说明只有包装类才会存在搜索的

         
    

4.异常(Exception)

            

     1)分类:Throwable: 按照不正常的情况的"严重程度"来划分

             Exception:虽然是不正常的情况,但是这种不正常的情况,还不算致命,我们自己(应用程序)
                        仍然有可能解决(1,2)
             Error: 描述的不正常情况,其程度非常严重,我自己(我们自己的应用程序)已经无法解决
                     stackoverFlow  outOfMemmoryeError(我们的程序解决不)

             Exception:
                 RuntimeException
(1):  不太好,在程序运行之前预见到的
                                        (在程序运行时,满足某些特殊条件,才会发生的异常)
                 CheckableException (2): 可以在异常发生之前,可轻易预见到一些很容易出现的异常情况
                                          通常,在程序运行之前做一些有预见性的处理
    

     2)当发生异常的时候,虚拟机的默认处理:

            1. 从异常发生处,我们的代码被一分为2,在异常发生之前的代码,全部都已经执行,
               在异常发生位置之后的代码,不会执行
            2. 不执行我们异常发生之后的代码,捕获当前发生的异常,并且将当前发生的异常情况的相关信息,
                封装成对象
            3. 将异常的相关信息全部输出到,屏幕上
            4. 输出完毕,jvm停止运行

        开发者可以通过 try-catch语句,当异常发生的时候,自己处理异常

        当我们使用了try-catch语句,虚拟机就不会再帮我们做默认异常处理
    

     3)我们需要知道的是,从异常发生,到异常处理的过程

        1. 当在try-catch语句中发生异常的时候,首先,jvm捕获到异常,仍然将该异常的相关信息封装成异常对象
        2. 当构造 异常对象 之后,利用异常对象,通过catch语句,来将异常对象传递给我们的程序,怎么通过catch语句
           传递给我们的呢?通过catch代码块中参数的形式( 异常名 + 引用对象名 ),将异常对象传递给我们
        3. catch之后的语句,继续正常执行(jvm认为我们已经在catch,作了相应的异常处理)


    

     4)当发生多个异常的时候:


        第一种处理多个异常的方式:

             一个try块,对应多个catch分支;
             try-catch成对出现: catch 代码块只处理相应try代码块中出现的异常

     4.1当try-catch多个异常的时候的执行流程:

        1. 当try块中的某条语句发生异常的时候,jvm捕获正在发生的异常,将异常的相关信息封装成一个异常对象
        
        2. 得到异常对象之后,jvm  终止  同一try块中发生异常位置 之后 的代码的正常执行
        
        3. 转而开始,跳转到catch代码块的执行,其执行流程是,按照catch代码块的书写顺序,依次匹配,匹配实际发生的
           异常类型和catch代码块中声明的捕获的异常类型
        
        4. 当发现,实际发生的异常类型与某catch代码块中声明的捕获的异常类型兼容(catch中为父类)的时候,
           就开始进入,catch代码块执行该catch代码块中的代码
        
        5. catch分支,jvm同时只会最多匹配一个

        6. 当try块中发生的异常,在我们的多个catch代码块中,没有任何一个catch代码块声明的异常类型与之匹配
           (我们开发者并未处理该种类型的异常),此时jvm又会执行默认的异常处理流程。

     第二种,处理多个异常的方式:

        每一个异常用专门的一个try-catch代码块来处理
     

     5)捕获多个异常的情况:

        如果有多个catch语句,则没有继承关系的异常前后顺序无所谓,
        但是如果出现了继承关系的异常,则子必须在父之前(Exception e 必须放在最后,因为是超类)


      6)jdk  1.7新特性:

        try{
        }
        catch(exception1 | exception2 | ..){
         //表名该异常分支可以处理多个异常类型
        }
        //假设,对于数组越界异常和空指针异常,处理这两种异常的操作完全相同
        如果使用jdk7的新特性就可以少写重复的代码


      7)finally 关键字

        
         作用:比如申请一定的系统资源,然后在使用过程中产生了异常,导致后面的程序段都没有执行,
               无法释放资源,此时需要一个不管异常是否发生一定执行的语句来执行处理方案。
    
         特点: 一定会执行除非之前运行 System.exit(0)
         
         与 return 的关系:
             1.return 放在 try-catch 语句中也无法阻止 finally 运行,只是要小心  运行时return的作用原理
             例:public static int testFinally() {
                       int i = 10;
                    try{
                        int j = i / 0;
                    } catch (ArithmeticException e) {
                         return i;
                    } finally {
                            i = 100;
                     }
                     }

                输出 i 值:结果为:i = 10;

 原因分析:因为 return 返回值在栈帧中只会有一个空间,在执行到catch的时候返回了i = 10;
                  此时再执行 finally ,由于finally中并没有将 i = 100 返回回去,所以不会改变返回值。


5. throw / throws 关键字

    
         throw 关键字,可以让我们自己抛出异常
            使用位置:在 方法体 中可以通过throw语句抛出异常
         throw <异常类的对象> 如果抛出的异常对象属于可检查的异常,
               则必须与方法的异常列表中的异常兼容(方法声明中声明的异常可以是该被抛出的异常对象的父类)

         throws                                                                          throw
-----------------------------------------------------------------------------------------
      1.  用在方法后,跟异常类名                                      用在方法体内,后跟异常对象名
     
      2.  可跟多个异常类名,由方法调用者处理                  只能抛出一个异常对象名
     
      3.  表示可能抛出这些异常,但不是一定会抛              表示抛出异常,由方法体内的语句产生
                                                                                     执行throw 语句必然抛出某种异常

        
        
        

6.Throwable 中的常用Api

    .getMessage() :  对异常的简单字符串描述
    .toString() : 全类名 + ":" + getMessage()的内容
    .printStackTrace():   打印当前调用栈信息,和相应异常的具体类型的全类名,简单的对当前发生的异常的描述
    
    

7.自定义异常:

    好处:

          JVM无法描述很多特定的符合我们要求的异常,我们需要通过异常来判断程序的出现异常的点,
          那么就需要通过自定义异常来实现这种功能。

    分类:

    1. 非运行时异常(Checked Exception)
       Java中凡是继承自Exception但  不是  继承自RuntimeException的类都是  非运行时异常。
    
    2. 运行时异常(Runtime Exception / Unchecked Exception)
       RuntimeException
  类直接继承自Exception类,称为  运行时异常。
       Java中所有的运行时异常都直接或间接的继承自RuntimeException。
       Java中所有的异常类都直接或间接的继承自Exception。


  7.1自定义异常的步骤:

    1.首先编写异常类,即所有可能产生的异常,每个异常 一个类
       类中只需要包括: 一个固定的构造方法和固定的super语句
                                  和一个无参构造方法

    class nullException extends  Exception{
       public nullException(String s){
           super(s);
       }
          public nullException(){
          }
       }

    class lowAgeException extends  Exception{
       public lowAgeException (String s){
           super(s);
       }
       public lowAgeException(){
       }
    }

    
    2.再编译其他类,用来实现某些特定功能,并制定相关的方法,比如 employee
      然后对所有可能抛出异常的方法,在方法声明中 声明 throws 自定义异常
        public employee(int ID, int age) throws nullException,lowAgeException{
              if(age < 18){
                throw new lowAgeException("年龄低异常");
              }
        }

       
       然后通过判断条件,对不同条件下的出现的异常进行 抛出异常 : throw new 自定义异常("要说的话")

    3.在 main 方法中,将参数一一赋值之后,通过
   try{
        employee employee1 = new employee(ID:1 , age:8);
    }
    catch (nullException | lowAgeException e){
        System.out.println("输出的异常是" + e);

    }
   最后的输出结果:com.company.Day14_Java_exception.lowAgeException: 年龄低异常

   如果在main 方法后 throws  自定义的Exception ,那么就不需要try—catch语句了

   但是如果没有throws 自定义异常的话,就要需要try—catch语句来捕获这个异常,并且可以修改捕获来的异常

public static void main(String[] args)  {

    try {
        TestCollection a = new TestCollection(15);
    } catch (lowAgeException e) {
        e.printStackTrace();//可以改成 System.out.println("我捕获到的异常是" + e);
       }
}

   



Day14-Day20数据结构,详见数据结构章节


Day 21

1. 集合框架 之 Collection 接口
可参考此博客内容,很详细
http://www.cnblogs.com/Qian123/p/5685155.html

  

   1.为何要引入集合的概念:

       在面对多个对象进行操作的时候,希望有一个可动态改变的数据空间。
       虽然数组也可以对多个对象进行操作,但是数组的容量在new的时候就已固定。
    

   2.集合和数组的比较

       1)数组虽然也可以存储对象,但长度是固定的;集合长度是可变的。(主要有原因)
       2)数组中可以存储基本数据类型,集合只能存储对象(因为jdk1.5以后引入了自动装箱和自动拆箱,
          所以对于集合来讲已经没有这个限制了)。
    

   3.集合类的特点:

       1.集合只能装对象,其长度可变;
       2.集合可以存储不同类型的对象。(数组也可以存储不同类型的对象,不过前提是 Object[] )
      

4.集合框架的层级结构:

Collection<E> 接口:
            List<E>接口 :
非常类似于我们的线性表
                   具体实现类:
                   ArrayList<E>     顺序映象,多线程不安全
                   Vector<E>         顺序映象,并且线程安全
                   LinkedList<E>   非顺序映像,链表(双向链表实现)
            Set <E>接口 :  类似于我们数学中的集合
                   具体实现类:
                   HashSet<E>    哈希表,通过hashMap实现
                   TreeSet<E>    排序二叉树 + 平衡二叉树(其中每个元素都是可以比较的)
   

   5.List / Set 的比较:

        1)List 可存重复元素/ Set 不能
        2)List 内元素有序,与其实际存储顺序相同 / Set 元素无序
        3)List 访问特征:每个元素都有一个唯一的位置index / Set 不存在元素位序的概念。


   6. Collection 的实现类中的方法:

            查看JDK文档  (  主要有add / addAll/ remove/ removeAll/ contains/ isEmpty/
                                           iterator/ retainAll/ size/ toArray
等)

2.实现 Collection

    ****需要注意一个点:如果添加的类型不唯一,那么在删除操作的时候,就需要重写equals()、hashCode()方法
        可以Alt + insert 快捷生成。
        //Collection是个接口,无法直接实例化
        //Collection C = new Collection();


        //得到collection对象(实际是一个子类对象)
        Collection<String> c = new ArrayList();
        //其中 < > 表示参数化数据类型:对象中只能存此种类型的数据——可以保证在编译时确保不会出错,
        //从而阻止运行时错误的产生,然后 new 的对象就是 Collection 的子类(实现类)

        c.add("hello");
        c.add("java");
        c.remove("hello");




3.集合元素的遍历方式:

      Collection<String> arrayList = new LinkedList<>();
    方式一.  Object[] objects = arrayList.toArray();   //toArray--返回一个数组
        然后遍历数组,来遍历集合


    方式二 . Collection<String> iterator = arrayList.iterator();    //获得迭代器
        while (iterator.hasNext()){                   //判断对象是否有下一个元素
            System.out.println(iterator.next());   //输出对象的下一个元素
        }

        

迭代器工作原理:

    其遍历过程是:
             1.有两个指针 cursor / lastRet
                   cursor初始指向集合第一个元素,lastRet 初始指向cursor的前一个位置(-1)
             2.调用hasNext()方法,其判断条件是: cursor != size
             3.当hasnext 结果为true 时,lastRet = cursor; cursor = cursor + 1;
             4.此时访问lastRet所指向的元素值。
     三个方法:

boolean hasNext()
          如果仍有元素可以迭代,则返回 true
 E next()
          返回迭代的下一个元素。
 void remove()
          从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)

   

                      

4.List<E>  接口

    1.有序的Collection(序列)
    2.可精确操作列表中的每个元素(index)
    3.可有重复元素

    主要方法:见 JDK文档 。(add/remove/ListIterator/contains/equals/get/hashCode/set/size....)


    ListIterator :    可以任意方向遍历集合(逆序和正序)
     列表迭代器,允许程序员按任一方向遍历列表、迭代期间修改列表,并获得迭代器在列表中的当前位置
       逆序:
需要先正序遍历,将cursor指针移到最后一个位置,然后再调用previous()方法
           ArrayList<String> arrayList = new ArrayList<>();
           ListIterator<String> stringListIterator = arrayList.listIterator();
           while (stringListIterator.hasPrevious()){
              System.out.println(stringListIterator.previous());
           }


5.ConcurrentModificationException 异常

   
    出现场合:当获取到了迭代器之后,还对集合数据集进行添删操作,再进行遍历就会报错

    原因:
          1.  迭代器一定依赖于当前集合中的元素。
          2.  获取迭代器一定要在添加元素之后。


6.遍历过程中,修改数据集的方式:

    1.List迭代器遍历,List迭代器添加
      List 的实现类均有 Iterator 迭代器,遍历中添加

    2.集合遍历,集合修改

      for (int i = 0;i<list.size() ;i++ ){
       String s = list.get(i);
        list.add("asdasd");
      }

                  

7.增强for循环(遍历时方便)

    for( 元素数据类型 x  :  数组或Collection 集合 ){
          输出 x ;
    }

       表示遍历整个集合或数组中的此类型的变量x。



8.Set 接口

    1.特征:无重复元素/无序/没有index位序的概念

       1)无序是指   插入顺序  与  遍历顺序   不一定相同
       2)无重复元素:添加了重复元素,不会添加成功(源码是新值覆盖了旧值,返回旧值)

    2.存储内容:

           1:字符串类型
           2 :  自定义类型对象
    

    3.HashSet 通过 HashMap 实现,那么  HashSet 如何判断元素重复的呢?

        首先 查看 JDK 文档中的 Map 接口,其中有一个实现类——HashMap,HashSet 的实现需要通过HashMap,
        其中的数据存储在hash表中,hash表是一个一维数组,其中每个存储单元存储的是一个链表。
        链表的每个节点存储的是一对键值对(key|value)。
        
        步骤:
            1.如果key为null,则存储在hash表的第 0 个位置的链表中,但是第0个位置不只有null

            2.通过key计算其hash值,针对得到的hash值,计算其所对应的在hash表中的存储位置index,

            3.然后判断,if(e.hash == hash && (k = e.key) == key || key.equals(k)){
                                  oldValue = e.value;
                                   return oldValue;
                                   }
                                   return null;
              成立就说明原链表中已经有相同的元素,就让新值覆盖旧值,然后返回旧值,
              如果没有找到相同元素,那么就返回null,表示可以进行插入操作。


        唯一性:key ——> hashcode ——>index (反方向都不能唯一确定其值)
               即key相同,hash值一定相同,hash值相同index一定相同(反向均不成立)

        
    依据:
          a.HashMap 对key 的hashcode 所计算出的hash值
          b.新旧key指向同一个对象 或者 两个key的内容相

JDK源码:

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    public HashSet() {
    map = new HashMap<E,Object>();
    }

    public boolean contains(Object o) {
    return map.containsKey(o);
    }

    public boolean add(E e) {
    return map.put(e, PRESENT)==null;
    }
}

由此可见,HashSet中的元素实际上是作为HashMap中的Key存放在HashMap中的。下面是HashMap类中的put方法:
public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
        V oldValue = e.value;
        e.value = value;
        e.recordAccess(this);
        return oldValue;
        }
    }
}


    4.在JDK1.7中hash表 是纯链表,但是在JDK1.8中,当链表中元素大于8个的时候,就会将链表自动转换成
            红黑树(平衡二叉树 + 排序二叉树)



      5.Set用法:

        Set<String> set = new HashSet<>();

        System.out.println(set.add("java"));

        //遍历字符串类型
        Iterator<String> iterator = set.iterator();

        while(iterator.hasNext()) {
            System.out.print(iterator.next() + " ");
        }
       //向set中存储自定义对象
        Set<Student> students = new HashSet<>();

        students.add(new Student("zs", 18, 004));
       
       //Hashset 如何判断元素重复呢? -> hashMap 如何保证其key值不重复

        students.add(new Student("zs", 18, 004));


        

9.Map<K,V>  接口

    public interface Map<K,V>

     1.概述:

              将键映射到值的对象。
              一个映射不能包含重复的键;
              每个键最多只能映射到一个值。

 

     2.Map 和 Collection 接口的比较:

         1.Map 是双列的(key|value),Collection 是单列的
         2.Map 键唯一,Collection 中子体系Set内元素唯一
         3.Map 集合的数据结构(HashMap)只对键有效,跟 value 无关
           Collection 中的HashSet 的数据结构是针对元素有效的(List)

     3.用法:

       Map<Key ,Value> map = new HashMap<>();
       map.put(1,"sz");
       map.remove(key);
       boolean containsKey(Object key);
       boolean containsValue(Object Value);
       V get(Object key):通过key得到Value值

       .....
       Set<K> keySet()此方法返回当前Map中的key值的集合(不重复)
       Collection <V> values():  返回当前Map中所有Value 中值的集合;

       Set<Map.Entry<K,V>> entrySet():  返回当前Map中的键值对

     4.遍历Map中键值对:

          1.利用 Set<K> keySet 、V get(Object key)
           for(int index : map.keySet()){
           输出(index + " " + map.get(index));
           }

           其中的index 会根据后面的范围来自动匹配,这里是遍历key值的集合
           
          2.利用entrySet()  
              for(Map.entry<Integer , String>  en : map.entrySet()){
               输出( en.getKey() + " " + en.getValue());
              }


             其中要注意:Map.Entry<K,V>  是一个嵌套类,他有一个方法是 entrySet(),
                 方法的返回值是 Map 中的所有键值对。

                

     5.二维Map

         Map<Integer , Map<String,String>> complexMap = new HasMap();
         Map<String,String> firstMap = new HashMap;

         添加:comlexMap.put(1,firstMap);



如果访问List集合中的元素,可以直接根据元素的索引来访问;如果需要访问Map集合中的元素,可以根据每项元素的key来访问其value;如果希望访问Set集合中的元素,则只能根据元素本身来访问(这也是Set集合里元素不允许重复的原因)

---------------------------------------------------------------------------------------------------

    Day 22


1.线程   Thread

    操作系统四大功能:进程管理、内存管理、文件管理、I/O管理
    
    线程依赖于进程而存在(线程需要通过进程得到资源);

    多线程的引入 目的
:为了提高CPU的使用率。

    并发:同一时刻只运行一个进程,在同一时间段,可以运行多个进程
    并行:同一时刻可以运行多个进程。
    
    在单核计算机中,多进程的运行——并发(同一时刻,只为一个进程服务)
    Windows系统中每个进程都是分时进行的(进程不断交替运行)
    
    进程在切换时,其运行的上下文(状态信息)也要存储,这样需要开辟专门空间来
    存储运行的过程信息,需要付出较大代价。而如果通过线程,则会付出较小代价。
    
    进程是资源的分配的基本单位,线程是进程调度的基本单位。
    
    JVM 是多线程运行的:比如垃圾回收器的运行会在另外的线程中执行。
    每个JAVA程序会分配一个jvm实例,所以jvm的功能可看成:字节码解释器 + 内存管理



2.线程创建

    方法一:

          1.继承 Thread 类;
          2.重写子类的run()方法;
          3.创建该子类的对象(在主类中)
          4.启动线程( 对象.Start() )
          5.JVM会自动调用 子类run()方法,但不会调用子类其他方法。

            

    方法二:

          1.实现 Runnable 接口的类,实现run()方法。
          2.分配该类的实例
          3.在创建 Thread 时作为一个参数来传递该Thread。

         (  Thread 的一参构造方法:Thread(Runnable target)   )

    方法三:(依赖于线程池)

        1.<T>Future<T>
          submit(Callable<T> task)

        2.创建线程池
          ExecutorService executorService = Executors.newCachedThreadPool();
        3.向线程池提交有返回值的任务  接口 Callable<V>,
            需要实现接口中的call()方法,其 返回值为泛型,在下面为Integer
            返回的是一个Future类型的对象。
           JDK文档: Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
            计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。
            取消 则由  cancel()  方法来执行。

            Future<Integer> future = executorService.submit(new Callable<Integer>(){
            
            @Override
            public Integer call() throws Exception {
                TimeUnit.SECONDS.sleep(3);
                int sum = 0;
                for (int i = 0; i < 10; i++) {
                    sum += i;
                }
                return sum;
            }
        });
        //获取其他线程中 的异步计算结果
        Integer integer = future.get();(处理一个异常)


        
    eg.
public class test  {
    public static void main(String[] args) {
        //方法一
        test1 test1 = new test1();
        test1.start();
        //方法二
        test2 t2 = new test2();
        new Thread(t2).start();  //一个线程只能被启动一次
    }
}
class test2 implements Runnable{   //实现Runnable接口
    @Override
    public void run() {
        System.out.println("我也被执行了");
    }
}
class test1 extends Thread{   //继承Thread类
    @Override
    public void run() {
        System.out.println("我被执行了");
    }
}


      

     方法一/二 的优缺点:

                          方法一:缺点在于继承了Thread类,就不能再继承其他类了
                          方法二:解决了单继承的局限性;便于多线程数据共享。
             比如一个需求,三个窗口一起卖100张电影票,
             如果用方法一:需要创建3个对象
             方法二:只需要新建一个对象,然后放入三个不同的线程即可(new Thread(new 线程).start() );实现了数据和业务模型的分离。
      

3.Thread 类中的Api

      1.Sleep(long mills):   让当前线程休眠指定毫秒。
      2.join():    等待该线程终止(   该线程对象.join()  )
               (当前线程(join所在的线程环境)  等待  该线程终止 )
      3.yield():暂停当前正在执行的线程。
                让其他线程先执行,但仍然具有争夺CPU的权利。
      4.setDeamon(boolean on):  设置守护线程。
            当前运行的所有线程均为守护线程,则JVM退出。
            设置守护线程,要在Start()之前。
******5.CurrentThread():    得到当前线程的引用。*******
           CurrentThread().getName() ——可得到当前线程的名称。
      
      6.interrupt():   中断线程(的运行状态)
   一个不处于等待状态的线程,此时调用该方法,无任何效果。
   Thread.interrupt 的作用其实也不是中断线程,而是「通知线程应该中断了」,
   具体到底中断还是继续运行,应该由被通知的线程自己处理。

  具体来说,当对一个线程,调用 interrupt() 时,
    ① 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,
       并抛出一个  InterruptedException  异常。仅此而已。
    ② 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。
      被设置中断标志的线程将继续正常运行,不受影响。interrupt() 并不能真正的中断线程,
      需要被调用的线程自己进行配合才行。

   也就是说,一个线程如果有被中断的需求,那么就可以这样做。
   ① 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
   ② 在调用阻塞方法时正确处理InterruptedException异常。(例如,catch异常后就结束线程。)

    Day 23


1.多线程问题


生产者和消费者问题:

    /*
需求:
    生产者生产包子,消费者吃包子,然后大家都共享一个蒸笼,并且蒸笼的容量只能容纳一个包子
    如果蒸笼有包子,那么生产者不能往里面放包子,如果蒸笼里面没有包子,则消费者不能吃包子。
    需要显式生产的什么包子,吃的什么包子。
    */

    

第一种代码形式:(粗糙的实现方式)

//设置一个Food 类,给包子赋值
public class Food {
    //成员变量
    private String foodName;
    private int price;

    //构造方法给成员变量赋值
    public Food(String foodName, int price) {
        this.foodName = foodName;
        this.price = price;
    }
    //重写toString()
    @Override
    public String toString() {
        return "Food{" +
                "foodName='" + foodName + '\'' +
                ", price=" + price +
                '}';
    }
}


//设置一个蒸笼用来表示 *临界区*

public class Container {
    //表示当前蒸笼是否有包子
    private boolean isExist;

    //表示当前蒸笼所放的包子
    private Food food;

    public Container() {
    }

    //获取当前蒸笼中是否有包子

    public boolean isExist() {
        return isExist;
    }

    //设置当前蒸笼的状态
    public void setIsExist(boolean Exist){
        isExist = Exist;
    }

    //还有生产者放包子
    public void setFood(Food food){
        this.food = food;
    }
    //消费者吃包子
    public Food getFood(){
        return food;
    }
}


//生产者线程

public class Producer implements Runnable {

    //先创建对蒸笼对象的一个引用
    private Container con;
    //把蒸笼对象给到生产者(创建了引用就要构造方法来接收,两步一起)
    public Producer(Container con) {
        this.con = con;
    }

    //创建一个菜单
    Food[] foods = {new Food("肉包",2),new Food("糖包",3),
            new Food("馒头",1)};

    @Override
    public void run() {
        //循环,否则只会执行一次
        while(true) {
            synchronized (con){   //加锁
                //判断蒸笼是否有包子
                if( con.isExist()){
                    //有包子,克制自己,不要生产包子
                    try {
                        con.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //没有包子就生产一个
                else {
                    //生产一个包子,生成方法
                    Food food = producerFood();
                    //放入蒸笼
                    con.setFood(food);

                    //转换蒸笼状态
                    con.setIsExist(true);
                    System.out.println("包子做好了" + food);

                    //通知消费者来吃包子
                    con.notify();
                    }
            }
        }
    }

    private Food producerFood() {
        Random random = new Random();
        int index = random.nextInt(3);
        return foods[index];
    }
}


//消费者线程
public class Consumer implements Runnable {
    //同样要一个蒸笼对象,并且给到消费者
    private Container con;
    public Consumer(Container con) {
        this.con = con;
    }

    @Override
    public void run() {
        while(true){
        //加锁
        synchronized (con) {
            //判断蒸笼中是否有包子
            if ( !con.isExist() ) {
                    //没有包子的时候,克制自己
                    try {
                        con.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
            }
            else {
                    //有包子就直接吃包子
                    Food food = con.getFood();

                    //吃完,将蒸笼状态改为false
                    con.setIsExist(false);
                    //将蒸笼内清空
                    con.setFood(null);
                    System.out.println( food + "包子吃完了");

                    //通知生产者生产包子
                    con.notify();
                }
            }
        }
    }
}


测试类:

public class TeastFood {
    public static void main(String[] args) {
        //先创建蒸笼对象
        Container container = new Container();

        //用第二种方式实现
        //new的时候 用生产者和消费者的构造方法将蒸笼给到他们(同一个)

        Producer producer = new Producer(container);
        Consumer consumer = new Consumer(container);

        new Thread(producer).start();
        new Thread(consumer).start();
    }
}


输出结果:
包子做好了Food{foodName='馒头', price=1}
Food{foodName='馒头', price=1}包子吃完了
包子做好了Food{foodName='肉包', price=2}
Food{foodName='肉包', price=2}包子吃完了
包子做好了Food{foodName='馒头', price=1}
Food{foodName='馒头', price=1}包子吃完了
包子做好了Food{foodName='糖包', price=3}
Food{foodName='糖包', price=3}包子吃完了
包子做好了Food{foodName='馒头', price=1}


第二种优化实现:

    /*
    在临界区内实现进程的同步和互斥。
    智能蒸笼
    */

Food类不变

生产者线程:

public class Producer implements Runnable {

    //先创建对蒸笼对象的一个引用
    private Container con;

    //把蒸笼对象给到生产者
    public Producer(Container con) {
        this.con = con;
    }

    //创建一个菜单
    Food[] foods = {new Food("肉包", 2), new Food("糖包", 3),
            new Food("馒头", 1)};

    @Override
    public void run() {
        while (true) {
            //只需要不断生产包子就可以了,互斥滴
            con.setFood(foods[new Random().nextInt(foods.length)]);
        }
    }
}


消费者线程:
    
public class Consumer implements Runnable {
    //同样要一个蒸笼对象
    private Container con;
    public Consumer(Container con) {
        this.con = con;
    }

    @Override
    public void run() {
        //消费者只要吃包子就行了,不断吃
        while (true) {
            Food food = con.getFood();
            //万一没有拿到包子也需要处理
            if(food = null){
                //处理
                System.out.println("没有包子你吃什么鬼");
            }
        }
    }
}


蒸笼临界区:将同步操作变成两个同步方法,互斥滴进行访问

public class Container {
    //表示当前蒸笼是否有包子
    private boolean isExist;

    //表示当前蒸笼所放的包子
    private Food food;

    public Container() {
    }

    //获取当前蒸笼中是否有包子
    public boolean isExist() {
        return isExist;
    }

    //设置当前蒸笼的状态
    public void setIsExist(boolean Exist) {
        isExist = Exist;
    }

    //还有生产者放包子
    public synchronized void setFood(Food food) {
        //判断蒸笼中是否有包子
        if (isExist()) {
            //有包子就克制自己不要生产包子
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            //没有包子,就生产包子
            System.out.println(Thread.currentThread().getName() + "做" + food);
            this.food = food;
            //将蒸笼的状态改成true
            isExist = true;

            //通知消费者来吃包子
            notify();
        }
    }

    //消费者吃包子

    public synchronized Food getFood() {
        //判断蒸笼中是否有包子
        if (!isExist()) {
            //没有包子,就克制自己
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            //有包子就吃
            Food f = food;
            System.out.println(Thread.currentThread().getName() + "吃" + food);
            isExist = false;
            food = null;
            //吃完通知生产者生产包子
            notify();
            //因为想要得到消费者吃到了什么包子,所以还要返回一个值
            return f;
        }
        //没拿到包子
        return null;
    }
}



测试类不变:

测试结果:同方法一


第三种优化实现:

    /*
    因为发现菜单更改非常麻烦,如果后期想要添加菜单的话,需要重新修改代码。
    引入:
    配置文件方法:properties , 配置文件中的东西必须以键值对的形式存在:
    类似:1=狗不理,10
              2=小笼包,3
              3=糖包,2
    
     */

Food 类不变

Container(蒸笼) 类、Consumer(消费者) 类 均同方法二


生产者producer类需要进行改变:

public class Producer implements Runnable {

    //先创建对蒸笼对象的一个引用
    private Container con;

   //创建一个菜单
    static List<Food> foods = new ArrayList<>();

    //用静态代码块,可使此  配置文件  只执行一次
    static {
        //查看一下当前的文件相对路径
        System.out.println(System.getProperty("user.dir"));

        //读取配置文件的内容
        //可以当做HashMap来使用,里面存储的键值对都是String类型的。
        Properties properties = new Properties();

        //从文件系统中的某个文件中获得输入字节
        FileInputStream fis = null;

        try {
            //配置当前文件路径,为了将配置文件内容读取到内存,采用文件输入流
            fis = new FileInputStream("food.properties");

            //载入路径
            properties.load(fis);

            for (Object o : properties.keySet()) {
                //循环HashSet
                String s = (String) o;

                //根据配置文件中的包子内容创建包子对象
                String foodSet = properties.getProperty(s);
                //分别拿到value和price
                //split方法可以用标记来分割字符串

                String[] split = foodSet.split(",");

                //Integer中的paraInt()方法或者ValueOf()方法可将数字字符串转成相应的整数
                int i = Integer.parseInt(split[1]);

                Food food = new Food(split[0] , i );

                foods.add(food);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //释放流
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //把蒸笼对象给到生产者
    public Producer(Container con) {
        this.con = con;
    }

    @Override
    public void run() {
        while (true) {
            Food food = select();

            //将包子放入蒸笼
            con.setFood(food);
        }
    }

    public Food select(){
        Random random = new Random();
        int index = random.nextInt(foods.size());
        return foods.get(index);
    }
}

2.线程间通信机制

   1.API:
       .wait(): 1.在其他线程调用此对象的  notify()/notifyAll() 方法之前,当前线程一直等待。
                2.当前线程必须获得锁对象(锁对象.wait() )
                3.调用wait 后该线程放弃 锁的所有权
                4.直到其他线程通过调用notifyAll()方法通知此对象的监视器上等待的线程醒来,然后
                  该线程等到重新获得对监视器的所有权后才能继续执行。

       .notify():
                  1.直到当前线程放弃 此锁对象的锁定,才能在其他线程调用notify()方法,来唤醒当前线程
                  2.被唤醒的线程,处于同步阻塞(拿到锁才能进入就绪状态)

       关键字: synchronized(锁对象) :

                  1.让拥有同一个锁对象的不同线程之间处于同步阻塞。

                  2.任意一个对象都可以作为锁对象,因为任意一个对象都会有一个标志位,如果有程序占用,标志位会改变,用这种方式,来让不同线程之间实现互斥访问。

                  3.同时只能有一个线程拿到锁对象,那么其他具有相同代码块synchronized(锁对象){ }的代码就要进入同步阻塞状态,直到拿到锁对象的线程释放锁,其他线程才能争抢这个锁。








3.线程状态转换图:(将阻塞状态分成了3种阻塞)

    





4.线程池(ThreadPool)

     引入背景: 普通线程仅能执行一次,下次再用就需要 new 新线程出来,但是创建一个线程的代价相对来说还是比较高的。、
    (因为要和操作系统的底层进行交互)
    如果通过一块缓存来存储已经创建了的线程,则可以多次复用,减少了和操作系统底层交互的次数。

    原理:线程池里每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一次被使用。

JDK5提供了    Executors    来产生线程池,有如下方法:
    1.public static ExecutorService newCachedThreadPool() //创建一个默认线程池
      //JDK文档: 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。

    2.public static ExecutorService newFixedThreadPool(int nThreads)
    //创建一个指定数量 初始线程的线程池

    3.public static ExecutorService newSingleThreadExecutor()
    以上方法返回了一个  ExecutorService ,该对象表示一个线程池,它可以执行Runable对象代表的线程。
    它提供了如下方法:
         submit(Runnable task)


  eg.   示范无参的线程池
      public static void main(String[] args) {
        //创建线程池
        //executorService,指向了一个线程池对象

        ExecutorService  executorService = Executors.newCachedThreadPool();

        //向线程池提交没有返回值的任务   Runnable
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("你好,线程池");
            }
        });

        //向线程池提交有返回值的任务  Callable
        Future<Integer> future = executorService.submit( new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
        
        TimeUnit.SECONDS.sleep(3);
                int sum = 0;
                for (int i = 0; i < 10; i++) {
                    sum += i;
                }
                return sum;
            }
        });

        try {
            //获取其他 线程中执行的异步计算结果
            Integer sum = future.get();
            System.out.println("sum = " + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

}


向线程池提交任务的方式:

        1.<T>Future<T>
          submit(Callable<T> task)
        2.创建线程池
          ExecutorService executorService = Executors.newCachedThreadPool();
        3.向线程池提交有返回值的任务Callable,
            需要实现接口中的call()方法,其返回值为泛型,在下面为Integer
            返回的是一个Future类型的对象。
            Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
            计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。
            取消则由 cancel 方法来执行。


            Future<Integer> future = executorService.submit(new Callable<Integer>(){
            
            @Override
            public Integer call() throws Exception {
                TimeUnit.SECONDS.sleep(3);
                int sum = 0;
                for (int i = 0; i < 10; i++) {
                    sum += i;
                }
                return sum;
            }
        });


        //获取其他线程中 的异步计算结果
        Integer integer = future.get();(处理一个异常)



关闭线程池:ExecutorService 接口

                 void shutdown()
                      启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
                      等现在还未执行完的任务执行完毕,再关闭线程池


                List<Runnable> shutdownNow()
                     试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。


5.Future 接口:            

            Future 表示异步计算的结果:

            1.它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
            2.计算完成后只能使用 get 方法来获取结果
            3.返回一个表示任务的未决结果的 Future ; get是一个阻塞方法(计算结果还没有出来之前,get方法会被阻塞)





6.定时器 Timer

    应用十分广泛的线程工具,可用于调度多个定时任务以普通线程的方式来执行(加上参数true,可变为后台线程)。
    
    Timer    简单来说就是调度定时任务何时执行的。
    TimerTask (抽象类)   决定定时任务执行什么(由Timer安排为一次执行或重复执行的任务。)
    两者总是一起使用。

 
在Java中,可以通过Timer和TimerTask来实现定义调度功能。
Timer:
   1.public Timer()

  主要方法有 (默认均是毫秒数)
    void cancel():终止此计时器,丢弃所有当前已安排的任务。
    void schedule(TimerTask task, Date time) :安排在指定的时间执行指定的任务。
    void schedule(TimerTask task, Date firstTime, long period)
              安排指定的任务在指定的时间开始进行重复的固定延迟执行。
    void schedule(TimerTask task, long delay)
            安排在指定延迟后执行指定的任务。
    void schedule(TimerTask task, long delay, long period)
            安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。
    
    使用方式:Timer对象.schedule(TimerTask类的对象,.....,.....);
    (由Timer 来 安排TimerTask 的执行,所以执行代码在TimerTask的实现类中)
  2.TimerTask:
       public boolean cancel():  取消此计时器任务。
       public abstract void run() :此计时器任务要执行的操作。



取消定时任务:

    方式一
            使用TimerTask的cancel()方法,
            一旦运行,无法在TimerTask中使用cancel()取消本次运行,只能取消掉下次的。

    方式二
            使用Timer的cancel()方法,

演示:


class MyTimerTask extends TimerTask {

    @Override
    public void run() {
        //具体这个定时任务的执行代码
        cancel();
        System.out.println("我喜欢java");
    }
}

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

        //定时器对象
        Timer timer = new Timer();

        MyTimerTask myTimerTask = new MyTimerTask();

      Timer() 下的方法:
        // 1.schedule(TimerTask task, long delay)
        // 所有的定时任务都运行在,Timer定义的一个线程

       timer.schedule(myTimerTask, 3000);


        // 2.schedule(TimerTask task, long delay, long period) 单位是毫秒
        //          安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。

        timer.schedule(myTimerTask,1000, 3000);

        try {
            TimeUnit.SECONDS.sleep(5);
            // 第一种方式,使用TimerTask的cancel方法取消该定时任务
            myTimerTask.cancel();

            //第二种方式 使用timer的cancel方法
            timer.cancel();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Day23

1.File 类

   1.概述

      是文件和文件路径目录名的抽象表达形式。
       何为抽象表达:即File类的对象不代表物理文件,只是相当于一个抽象的路径或者文件,可能物理存在,也可能不存在。

   2.文件路径

       若想知道  物理文件/路径:.exist()

   3.构造方法

       1.File(String pathname);
             在当前目录下创建一个文件或路径
             eg.new File("e:\\a.txt")  或 new File("e:\\a\\text")

       2.File(String parent,String child);

             创建一个父路径 + 子文件/子目录
              eg. new File("e:\\text\\a","b.txt")

       3.File(File parent , String child);

             创建一个父目录类 + 子文件/子目录
    

   4.API:

       1.创建
           public boolean createNewFile():
(原子操作)当且仅当不存在具有此抽象路径名指定名称的文件时,
                                          不可分地创建一个新的空文件。
          eg.创建多重目录下的文件的方式:
              File file = new File("E:\\a\\txt\\text\\","b.txt");
              file.getParentFile().mkdirs();//找到父目录
              file.createNewFile();


          boolean mkdir()
               创建此抽象路径名指定的目录。
               若创建的目录的父目录不存在,则无法创建,可使用mkdirs创建。

          boolean mkdirs()
               创建此抽象路径名指定的目录,包括所有必需但不存在的父目录
 

       2.删除
           boolean delete() :删除此抽象路径名表示的文件或目录。
                         如果目录下还有文件,则无法删除目录。(递归删除)


       3.重命名
            boolean renameTo(File dest)  : 重新命名此抽象路径名表示的文件
                (需给成绝对路径才能重命名当前文件,否则会将对象剪切到当前路径,并重命名)
        eg.
        File file = new File("E:\\a.txt");
        File file1 = new File("b.txt");
        file.createNewFile();
        System.out.println(System.getProperty("user.dir"));//查看当前路径
        file.renameTo(file1);
        //此时会将a.txt 文件,剪切到当前路径中,并且重命名成b.txt



       4.判断
           .isFile();          // 文件
          . isDirectory();    //目录



   5.File类的成员方法:

       1.基本获取方法

          获取绝对路径: .getAbsolutePath();
         
          获取此抽象路径的表示的文件长度,以字节为单位:.length();
               
           

       2.高级获取功能:

            1.public String[] List():只返回当前目录中的文件和目录的名称(不会递归)
               返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。
               如果此抽象路径名不表示一个目录,那么此方法将返回 null。
               否则返回一个字符串数组,每个数组元素对应目录中的每个文件或目录。
               表示目录本身及其父目录的名称不包括在结果中。每个字符串是一个文件名,而不是一条完整路径.
           eg.         
             File cskaoyan = new File("d:\\", "cskaoyan");
             System.out.println(Arrays.toString(cskaoyan.list()));

             输出结果:返回了当前目录 cskaoyan 下的 文件 和 目录:[abcd.txt, text]。


           2.public File[] listFile():返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。
             eg.         
             File cskaoyan = new File("d:\\", "cskaoyan");
             System.out.println(Arrays.toString(cskaoyan.list()));

             输出结果:返回了当前目录 cskaoyan 下的包括绝对路径文件目录:[d:\cakaoyan\abcd.txt, d:\cskaoyan\text]。

    *******3.File[] listFile(FileFilter filter):
                  返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。
                  注:FilFilter是一个接口,它表示一个文件过滤器(查看源码,发现此方法称为 CallBack——回调)
                   
                 FileFilter 接口 的唯一方法:  boolean accept(File pathname)
                                                              测试指定抽象路径名是否应该包含在某个路径名列表中。
                                                               
——此方法需要自己去实现(覆盖)

                                           
 
            eg.
            File[] files = cskaoyan.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                //通过返回true or false来表示,是否满足当前过滤条件
                String name = pathname.getName();

                return name.endsWith(".java");
            }
        });
        System.out.println(Arrays.toString(files));

        输出结果:[d:\cskaoyan\abcd.java] (目录中还有一个路径和一个txt后缀的文件,都没有输出)

        其中的File pathname 接收到的是 File[]的files,即当前目录下的所有文件和目录
            然后返回条件是:后缀为.java的文件,是,则返回true;
            在源码中的步骤是:如果遍历发现满足条件,就会添加到数组当中。


    6.递归遍历文件

 
        File[] files = file.listFiles();
        //遍历当前目录的下的所有子文件或子目录
        for (File f : files) {
            if(f.isFile()) {
                //对比当前文件的后缀,看下当前文件是否是以.txt结尾,如果是则输出
                if(f.getName().endsWith(".txt")) {
                    System.out.println(f.getName());
                }
            } else if(f.isDirectory()) {
                //现在访问到的file对象,是file目录下的子目录,对子文件目录递归访问,
                // 搜索其中是否有以.txt结尾的文件
                recursiveTraverseDir(f);
            }
        }
 

     
那么递归删除文件呢?
        File[] files = file.listFiles();
        //遍历当前目录的下的所有子文件或子目录
        for (File f : files) {
            if(f.isFile()) {
                //对比当前文件的后缀,看下当前文件是否是以.txt结尾,如果是则输出
                if(f.getName().endsWith(".txt")) {
                    //删除文件
                    f.delete();
                }
            } else if(f.isDirectory()) {
                //现在访问到的file对象,是file目录下的子目录,对子文件目录递归访问,
                // 搜索其中是否有以.txt结尾的文件

                recursiveTraverseDir(f);
            }
            //循环一次就可以删除一个空目录
            f.delete();
        }
        //循环完成可以将整个目录删除
        files.delete();



2.*************************IO流**************************

   超类:      In/OutputStream(字节流)         、                   Reader/Writer(字符流)
   子类:  FileIn/OutputStream               In/OutputStreamReader / In/OutputStreamWriter

     (子子类)                                            (子类)
 缓冲流:BufferedIn/OutputStream            BufferedReader / BufferedWriter
            (字节缓冲流)                                                    (字符缓冲流)

 特殊流:    
    打印流:  PrintStream                                            PrintWriter
   标准输入输出流: System.in/System.out
  序列化流:    Serializable
  反序列化流:   Object Out/In putStream



概述:是一种设备间数据传输的方式。

分类:

       1.按照流向分为:输入/输出流 (以内存为基准,输入内存,从内存输出到文件)
       2.按照数据类型分类:字节流/字符流(字节流中的数据是二进制数01/字符流以字符为单位进行传输)
          字节流可以兼容字符流,可以传输所有类型的东西,字符流无法正确传输多媒体文件(视频/图片/音频等)
          但是字符流在传输字符时优势明显,少了字节流编码—转码—编码的过程。
   注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
        如:InputStream的子类FileInputStream。 首先是一个输入流,然后是从文件中读入内容的输入流
        如:Reader的子类FileReader。 文件字符输入流。

    1.字节流基类:


        1.输入字节流InPutStream:(抽象类,表示所有字节输入流的基类)

        
            **由于无法直接实例化抽象基类,所以研究其中的一个实现子类: FileInputStream
          
            创建(通道):
                 得到 底层流对象 FileInputStream fis = new FileInputStream("a.txt");
                 表示要输入内存的目标文件(要从哪个文件里面提取内容到内存?)
            在创建的时候的JVM内部执行步骤:
                 1.创建目标文件的file对象(要读取的文件)。
                 2.判断file对象所指向的文件是否物理存在?不存在丢异常
                 3.创建FileInputSteam 对象,和目标文件发生联系。

            FileInputStream 的方法:
              1.abstract  int read()
                 从输入流中读取数据的下一个字节.
              返回值:下一个字节的字节值。
              
              2.int read(byte[] buffer)
                 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 byte[] 中。
              返回值:读取的字节数组的字节数,不是其中的值。
              
              3.int read(byte[] buffer, int off, int len)
                 将输入流中最多 len 个数据字节读入 byte 数组。
              返回值:同2
            
              4. void close()
                 关闭此输出流并释放与此流有关的所有系统资源。
                 流也是一种资源,必须主动释放。

            
             将读入内存的字节内容变成字符串:
                 byte[] bytes  = new byte[1024];

                 int readCount = fis.read(buffer)
                 String s = new String(buffer , 0 ,readCount)
                 System.out.print(s);

             (new String(byte[] b,int offset,int length) 表示通过使用平台的默认字符集解码指定的
                            byte 子数组,构造一个新的 String。)

          
             

        2.输出字节流OutPutStream:

           
         **由于无法直接实例化抽象基类,所以研究其中的一个实现子类: FileOutputStream
         
           创建:
得到流对象 FileOutputStream fos = new FileOutputStream("b.txt");
                  表示要输出的目标文件(要将内存的内容读取到哪个文件当中呢?)
           创建时的执行步骤:
              1.创建一个File 对象 表示目标文件(要流入数据的文件)
              2.判断File对象所表示的文件是否物理存在,如果不存在,则新建(与输入区别)
              3.创建FileOutStream 对象,与目标文件建立联系。
            

           OutputStream 的方法:
              1.void write(byte[] b)
                   将 b.length 个字节从指定的 byte 数组写入此输出流。
                   
                向其中写入内容: .write("hello".getBytes());
                        不管什么内容都会直接原模原样写入。
                String类 byte[] getBytes() :
                     使用平台的默认字符集将此 String 编码为 byte 序列,
                     并将结果存储到一个新的 byte 数组中。

                此byte需要存储在文件中,如果在内存中生成,无法写入文件


              2.void write(byte[] b, int off, int len)
                   将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
                   

              3.abstract  void write(int b)
                   将指定的字节写入此输出流。
                   int b 只能是正数 (>=0)
                  eg. write(97) --->文件中是 a
                      说明在写入的时候,进行了解码过程。
              
        *****均无返回值对是否写入进行判断,需要自己去验证。

              4. void close()
                 关闭此输出流并释放与此流有关的所有系统资源。
                 流也是一种资源,必须主动释放。
                 *****关闭的时候只要关闭最上层的流,即正在使用的流。

              

       3.复制:

    
    public static void copyByte(File file) throws IOException {
        FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(file,true);

        //在创建输出对象的时候,如果需要追加内容的话,需要在()中加入true,
        //表示要在写入时,在原文件内容后面追加,否则会直接覆盖原来的文件内容。

    单字节的复制方式:
        int len = -1;
        while((len = fis.read()) != -1 ){
            fos.write(len);
        }
    字节数组的复制方式:
        int len = -1;
        byte[] buffer = new byte[1024];//一般取1024的整数倍
        while((len = fis.read(buffer)) != -1 ){
            fos.write(len);
        }
    缓冲字符流的复制方式;
        String line = null;
        while((line = bufferedReader.readline()) != null){
            bufferedWriter.write(line);//读取一行并将其写入到一行
            bufferedWriter.newline();//换行继续读取下一行
        }

    关闭两个流(必须)
        fos.close();
        fis.close();
    }


    
 *******读入的是一个字节数组的话,在输出到控制台的时候,需要进行一下强转
           System.out.println((char)buffer);

*******关闭流的方法:

       1.将关闭流的代码提成方法(Alt+shift+insert),然后修改接收参数的类型
         (Closeable file) Closeable 接口实现了 close()方法,是所有流关闭方法的超类

       2.使用 try-with-catch 语法:
         try(只能写和申请资源有关的代码){} catch(){} finally{}
          try()中申请的资源只要执行完整个try-catch语句就会自动释放资源
              try( new FileInputStream ){操作流}



    2.字符流基类

      ****转换流:   InputStreamReader    (是字节流转换成字符流的桥梁)
                             OutputStreamWriter

        //看着就像是 将一个字节流(FileOutputStream) 通过 转换流(OutputStreamWriter),
        //被 字符流(Writer)接收。

        eg.   Writer  osw = new OutputStreamWriter(new FileOutputStream("b.txt"));
                Reader  isr   = new InputStreamReader(new FileInputStream("a.txt"));

      概述: 字符流自带缓冲区,也就是说,如果缓冲区的内容没有满,
            那么字符流的数据不会写入到文件当中(可以通过flush()
             和close()方法强行将未满的缓冲区数据写入)  
      
      劣势:无法复制多媒体文件,因为字符在拷贝的过程中,由于图片等格式的编码方式
            无法完全解码,所以会出现无法识别的错误,但是字节流不需要转码,会原模原样地
            拷贝文件。

        1.输入字符流 Reader:

         用 多态 和 转换流 来构建字符流:
         Reader isw = new InputStreamReader(字节流的对象)
        
         使用Reader 中的3个read()方法:
          1.int read() : 读取单个字符。
             返回值:0~65535,读到末尾返回 -1。

          2.int read(char[] cbuf) :将字符读入数组。
             返回值:读取的字符数,到末尾为-1
             
          3.abstract  int read(char[] cbuf, int off, int len) :将字符读入数组的某一部分。
             返回值:同2
         
          

        2.输出字符流 Writer:

         Writer osw = new OutputStreamWriter(字节流对象)
         
         使用Writer 中的5个write()方法:
         1.void write(char[] cbuf) :写入字符数组。
         
         2.abstract  void write(char[] cbuf, int off, int len) :写入字符数组的某一部分。
     
         3.void write(int c) : 写入单个字符。    
        
         4.void write(String str) :写入字符串。
         
         5.void write(String str, int off, int len) :写入字符串的某一部分。
           

        3.字符流简化方法:

        FileReader(File file) / FileWriter(File file)
        (File类的构造方法的参数列表全是字符串类型,  String pathname,String parent ,String child)
         eg.
         FileOutputStream fos1 = new FileOutputStream("mn.jpg");
         OutputStreamWriter osw1 = new OutputStreamWriter(fos1);

         等价于: FileWriter fw = new FileWriter("mn.jpg")


       

    3.缓冲流(Buffered):

        
    *****缓冲流均是每个字节流和字符流的一个实现类

        优点:可以有效提高传输效率,因为在于底层操作系统进行交互的时候,
              默认是达到8K才进行一次交互。
    
        1.字节缓冲流
           BufferedInputStream / BufferedOutputStream
          
        2.字符缓冲流

           BufferedReader / BufferedWriter
         
    *****可以使用System.currentTimeMillis()来记录当前时间的方式比较这几种方式在复制
         的时候的一个效率问题。

实例:(字节流的缓冲/非缓冲,单个字节/字节数组)
public class HomeWork3 {
    public static void main(String[] args) throws IOException {
        //单字节复制:
        //copyByte();
        //复制mp3所用时间为:13680

        //字节数组赋值
        //copyBytes();
        //复制MP3所用时间为:38

        //字节缓冲流,单字符复制
        //bufferCopyByte();
        //复制MP3所用时间为:149

        //字节缓冲流,字符数组
        //bufferCopyBytes();
        //复制mp3所用时间为:10

//文件字符流,字符数组
//

    }

    //单字节复制
    public static void copyByte() throws IOException {
        //MP3文件
        FileInputStream fis2 = new FileInputStream("E:\\text\\copy.mp3");
        FileOutputStream fos2 = new FileOutputStream("E:\\text\\copy1.mp3");

        System.out.print("复制mp3");
        Copy(fis2, fos2);
    }

    //字节数组复制
    public static void copyBytes() throws IOException {
        //MP3文件
        FileInputStream fis2 = new FileInputStream("E:\\text\\copy.mp3");
        FileOutputStream fos2 = new FileOutputStream("E:\\text\\copy1.mp3");

        System.out.print("复制MP3");
        CopyBytes(fis2,fos2);
    }

    //字节缓冲流复制
    public static void bufferCopyByte() throws IOException {
        //MP3文件
        FileInputStream fis2 = new FileInputStream("E:\\text\\copy.mp3");
        FileOutputStream fos2 = new FileOutputStream("E:\\text\\copy1.mp3");
        BufferedInputStream bis2 = new BufferedInputStream(fis2);
        BufferedOutputStream bos2 = new BufferedOutputStream(fos2);

        System.out.print("复制MP3");
        Copy(bis2,bos2);
    }

    //字节数组缓冲流复制
    public static void bufferCopyBytes() throws IOException{
        //MP3文件
        FileInputStream fis2 = new FileInputStream("E:\\text\\copy.mp3");
        FileOutputStream fos2 = new FileOutputStream("E:\\text\\copy1.mp3");
        BufferedInputStream bis2 = new BufferedInputStream(fis2);
        BufferedOutputStream bos2 = new BufferedOutputStream(fos2);

        System.out.print("复制mp3");
        CopyBytes(bis2,bos2);
    }

//字符数组普通流复制
public static void  Copychar() throws IOException{
        //MP3文件
        FileReader   FR  = new FileReader("E:\\text\\copy.mp3");
        FileWriter    FW = new FileWriter("E:\\text\\copy1.mp3");

        System.out.print("复制mp3");
        CopyChar(FR  ,FW );
    }

//字符数组缓冲流复制
public static void  Copychars() throws IOException{
        //MP3文件
        BufferReader  br  = new BufferReader(new FileReader("E:\\text\\copy.mp3"));
        BufferWriter  bw = new  BufferWriter ( new FileWriter("E:\\text\\copy1.mp3"));

        System.out.print("复制mp3");
        CopyChar(br  ,bw );
    }


    //数组复制方法
    private static void CopyBytes(InputStream fis, OutputStream fos) throws IOException {
        int len = -1;
        byte[] buffer = new byte[1024];
        //计时开始
        long s = System.currentTimeMillis();
        while((len = fis.read(buffer)) != -1){
            fos.write(buffer,0,len);
        }
        fis.close();
        fos.close();
        //计时完成
        System.out.println("所用时间为:" + (System.currentTimeMillis() - s));
    }

    //单个字节复制
    private static void Copy(InputStream fis, OutputStream fos) throws IOException {
        int value = -1;
        //计时开始
        long s = System.currentTimeMillis();
        while((value =fis.read()) != -1 ){
            fos.write(value);
        }
        fis.close();
        fos.close();
        //计时完成
        System.out.println("所用时间为:" + (System.currentTimeMillis() - s) );
    }

//普通字符流的复制
private static void CopyChar(FileReader FR, FileWriter FW) throws IOException {
            int len = -1;
            char[] buffer = new char[1024];
            //计时开始
            long s = System.currentTimeMillis();
            while((len = FR.read(buffer)) != -1){
                FW.write(buffer,0,len);
            }
            FR.close();
            FW.close();
            //计时完成
            System.out.println("所用时间为:" + (System.currentTimeMillis() - s));
        }


   //缓冲字符流的复制
private static void CopyChars(BufferReader br, BufferWriter bw) throws IOException {

            String line = null;
            //计时开始
            long s = System.currentTimeMillis();
            while((line = osw.readline()) != -1){
                isr.write(line);
                isr.newline();
            }
            br.close();
            bw.close();
            //计时完成
            System.out.println("所用时间为:" + (System.currentTimeMillis() - s));

            }
}



   4.几个特殊流

       1.打印流:

            打印流概述(只能输出)
            字节流打印流   PrintStream
            字符打印流       PrintWriter


            PrintWriter(OutputStream out, boolean autoFlush)
              通过现有的 OutputStream 创建新的 PrintWriter。
              通过构造方法中的第二个参数开启,开启打印流的自动刷新功能

            打印流特点
            1.只能操作目的地,不能操作数据。
              //之前我们所学习的所有的流都是,成对出现的, BufferedInputStream,BufferedOutputStream
              //但是对于打印流而言,只有输出流,没有输入流
            2.可以操作任意类型的数据。
              //int char long float double
            3.如果启动了自动刷新,能够自动刷新。
            //PrintWriter: 如果启用了自动刷新,则只有在调用 println、printf 或 format 的其中一个方法时才可能完成此操作
            4.可以操作文件的流
            PrintWriter(File file)
                  使用指定文件创建不具有自动行刷新的新 PrintWriter。
             PrintWriter(String fileName)
                  创建具有指定文件名称且不带自动行刷新的新 PrintWriter。

            //测试自动刷新功能
            FileWriter fileWriter = new FileWriter("b.txt");
            //开启自动刷新
            PrintWriter printWriter = new PrintWriter(fileWriter, true);
            //普通字符打印流
            printWriter.write("你好");





       2.标准输入输出流

            System类中的字段:in,out。
            它们各代表了系统标准的输入和输出设备。
            默认输入设备是键盘,输出设备是显示器。

            System.in

            // 标准输入流:System.in是一个阻塞流,当没有数据的时候,调用其read方法,不会结束读取,
            // 而是进入阻塞状态,等待用户的输入
               如果要结束System.in的read方法,只能自己定义结束标志

            //自己实现,从标准输入流中读取一行数据的功能

                 BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
                 //自定义结束方式
                 while((line = br.readLine()) != null) {
                    System.out.println("out: " + line);
                    if("886".equals(line)) {
                        break;
                    }
                }
                //释放流资源
                br.close();



       3.序列化流:

            序列化流
            ObjectOutputStream
            反序列化流
            ObjectInputStream



        如何序列化?
        1. ObjectOutputStream 将 Java 对象的基本数据类型和图形(对象中的成员变量可以是对象,对象之间可以相互引用,
           可能会形成一张相互引用的网或者图)写入 OutputStream
        2. 通过在流中使用文件可以实现对象的持久存储


        jdk example:
        FileOutputStream fos = new FileOutputStream("t.tmp");
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        ObjectOutputStream(OutputStream out)
          创建写入指定 OutputStream 的 ObjectOutputStream。


          NotSerializableException: com.csaoyan.serializable.A


          //反序列化ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
          ObjectInputStream(InputStream in)
          创建从指定 InputStream 读取的 ObjectInputStream。


          序列化数据后,再次修改类文件,读取数据会出问题,如何解决呢?

           每个类的都可以有 serialVersionUID(long类型的整数) 根据类签名,以及类的成员信息等等计算出来的,
           可以通过,在类定义中将
                serialVersionUID = -962895958221913318L 写死在类定义中



            是否所有的成员变量都会被序列化呢?
                  瞬态(transient)和静态(static)字段的值都将不会

                  使用transient关键字声明不需要序列化的成员变量 :瞬态成员变量


           使用场景:
           1.对象状态的永久存储,比如,想要让用户在程序结束后,下一次打开程序的时候,程序直接进入上一次退出时的状态
              a. 在退出程序之前,将用户的状态,存储在硬盘中
              b. 在程序启动的时候,读取,之前保存硬盘上的,用户上次退出我们的程序之前的信息
              c. 恢复到上次的状态

           2. 有的时候,我们需要调用的方法并不在我们这台主机中,可能在另一台服务器上(远程调用RMI),为了传输
              远程调用的结果,通过将序列化文件,通过流传输方式,使调用者能够拿到远程调用的结果


eg.
            //得到序列化流对象
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            A a = new A();
            //利用序列化流,将对象存储到文件中(将内存里的数据存储到磁盘上)
           objectOutputStream.writeObject(a);
           
           //反序列化过程,将刚刚保存的对象,读取到内存中并访问
            FileInputStream fileInputStream = new FileInputStream("t.tmp");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            // 反序列化流,读取文件内容,并解析,得到一个对象
            Object o = objectInputStream.readObject();
            objectInputStream.close();
            System.out.println(o);







------------------------------------------------------------------------------------------------------------

Day26


1.通过Scanner 来读取文件内容

public class Demo {
    public static void main(String[] args) {
        //创建此文件的对象
        File file = new File("C:/Users/hp/Desktop/data.txt");
        Scanner scanner = null;
        try {
            //获取扫描流对象(扫描的内容为一个文件对象)
            scanner = new Scanner(file);
            String str = null;
            while (scanner.hasNextLine()) {
                str += scanner.nextLine() + "\r\n";
            }
            System.out.println(str);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (scanner != null) {
                scanner.close();
            }
        }
    }
}



2. Charactor 类

public char charValue()
    返回此 Character 对象的值。

    返回:
       此对象表示的基本 char 值。


public int compareTo(Character anotherCharacter)
        根据数字比较两个 Character 对象。

        指定者:
            接口 Comparable<Character> 中的 compareTo
        参数:
            anotherCharacter - 要比较的 Character。
        返回:
            如果该 Character 等于此 Character,则返回 0;
            如果该 Character 的数值小于参数 Character,则返回小于 0 的值;
            如果该 Character 的数值大于参数 Character,则返回大于 0 的值(有符号比较)。
            注意,这是一次严格的数字比较;它并不依赖于区域。

public boolean equals(Object obj)
     将此对象与指定对象比较。当且仅当参数不是 null,而是一个与此对象包含相同 char 值的 Character 对象时,结果才是 true。

覆盖:
       类 Object 中的 equals
参数:
       obj - 比较的对象。
返回:
       如果对象相同,则返回 true;否则返回 false。


public static boolean isDigit(char ch)
     确定指定字符是否为数字。
    如果通过 Character.getType(ch) 提供的字符的常规类别类型为 DECIMAL_DIGIT_NUMBER,则字符为数字。

    包含数字的 Unicode 字符范围:
'\u0030' 到 '\u0039',ISO-LATIN-1 数字('0' 到 '9')
'\u0660' 到 '\u0669',Arabic-Indic 数字
'\u06F0' 到 '\u06F9',扩展了的 Arabic-Indic 数字
'\u0966' 到 '\u096F',梵文数字
'\uFF10' 到 '\uFF19',全形数字
其他许多字符范围也包含数字。
        参数:
        ch - 要测试的字符。
        返回:
        如果字符为数字,则返回 true;否则返回 false。

public static boolean isLetter(char ch)
    确定指定字符是否为字母
    如果通过 Character.getType(ch) 为字符提供的常规类别的类型为以下类型中的任意一种,
    则认为该字符为字母:
UPPERCASE_LETTER
LOWERCASE_LETTER
TITLECASE_LETTER
MODIFIER_LETTER
OTHER_LETTER
    并非所有的字母都有大小写。许多字符都是字母,但它们既不是大写的,也不是小写的,并且也不是首字母大写的。
参数:
ch - 要测试的字符。
返回:
如果字符为字母,则返回 true;否则返回 false。

public static boolean isLetterOrDigit(char ch)
    确定指定字符是否为字母或数字。


public static boolean isLowerCase(char ch)
确定指定字符是否为小写字母。
如果通过 Character.getType(ch) 提供的字符的常规类别类型为 LOWERCASE_LETTER,则字符为小写字母。

以下是小写字母的示例:
 a b c d e f g h i j k l m n o p q r s t u v w x y z
 '\u00DF' '\u00E0' '\u00E1' '\u00E2' '\u00E3' '\u00E4' '\u00E5' '\u00E6'
 '\u00E7' '\u00E8' '\u00E9' '\u00EA' '\u00EB' '\u00EC' '\u00ED' '\u00EE'
 '\u00EF' '\u00F0' '\u00F1' '\u00F2' '\u00F3' '\u00F4' '\u00F5' '\u00F6'
 '\u00F8' '\u00F9' '\u00FA' '\u00FB' '\u00FC' '\u00FD' '\u00FE' '\u00FF'
 其他许多 Unicode 字符也是小写的。

public static boolean isWhitespace(char ch)
    确定指定字符依据 Java 标准是否为空白字符。
    当且仅当字符满足以下标准时,该字符才是一个 Java 空白字符:

它是 Unicode 空格字符(SPACE_SEPARATOR、LINE_SEPARATOR 或 PARAGRAPH_SEPARATOR),但不是非中断空格('\u00A0'、'\u2007'、'\u202F')
它是 '\u0009',HORIZONTAL TABULATION
它是 '\u000A',LINE FEED
它是 '\u000B',VERTICAL TABULATION
它是 '\u000C',FORM FEED
它是 '\u000D',CARRIAGE RETURN
它是 '\u001C',FILE SEPARATOR
它是 '\u001D',GROUP SEPARATOR
它是 '\u001E',RECORD SEPARATOR
它是 '\u001F',UNIT SEPARATOR


3.Object 类

    
public boolean equals(Object obj)指示其他某个对象是否与此对象“相等”。

equals 方法在非空对象引用上实现相等关系:
自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
对于任何非空引用值 x,x.equals(null) 都应返回 false。

Object 类的 equals 方法实现对象上差别可能性最大的相等关系;
    即,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true(x == y 具有值 true)。

注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

参数:
obj - 要与之比较的引用对象。
返回:
如果此对象与 obj 参数相同,则返回 true;否则返回 false。

protected  void finalize()
          当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。


public final Class<?> getClass()
    返回此 Object 的运行时类。
    返回的 Class 对象是由所表示类的 static synchronized 方法锁定的对象。
实际结果类型是 Class<? extends |X|>,其中 |X| 表示清除表达式中的静态类型,该表达式调用 getClass。
    
例如,以下代码片段中不需要强制转换:
Number n = 0;
Class<? extends Number> c = n.getClass();

返回:
表示此对象运行时类的 Class 对象。

public int hashCode()

    返回该对象的哈希码值。
    支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。

 void notify()
          唤醒在此对象监视器上等待的单个线程。
    
 void notifyAll()

          唤醒在此对象监视器上等待的所有线程。
    
public String toString()

     返回该对象的字符串表示。
    通常,toString 方法会返回一个“以文本方式表示”此对象的字符串。
    结果应是一个简明但易于读懂的信息表达式。建议所有子类都重写此方法。
Object 类的 toString 方法返回一个字符串,该字符串由:
    类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。
    
换句话说,该方法返回一个字符串,它的值等于:
getClass().getName() + '@' + Integer.toHexString(hashCode())
 
返回:
该对象的字符串表示形式。

 void wait()
          在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。


4. InetAddress 类

  byte[] getAddress()
          返回此 InetAddress 对象的原始 IP 地址。

  static InetAddress[] getAllByName(String host)
          在给定主机名的情况下,根据系统上配置的名称服务返回其 IP 地址所组成的数组。

  等等,主要获取的是主机名、IP的各种返回形式。

5. Socket 类

 
构造方法:
  Socket()
       通过系统默认类型的 SocketImpl 创建未连接套接字
  Socket(InetAddress address, int port)
          创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
  Socket(String host, int port)
          创建一个流套接字并将其连接到指定主机上的指定端口号。
  Socket(String host, int port, InetAddress localAddr, int localPort)
          创建一个套接字并将其连接到指定远程主机上的指定远程端口。

方法:
    InetAddress getInetAddress()
          返回套接字连接的地址。
    InputStream getInputStream()
          返回此套接字的输入流。
    InetAddress getLocalAddress()
          获取套接字绑定的本地地址。
    int getLocalPort()
          返回此套接字绑定到的本地端口。
     void shutdownOutput()
          禁用此套接字的输出流。

6.DatagramSocket 类











----------------------------------------------------------------------------------------

    Day26

1.网络

    概述:多台计算机连接在一起实现资源共享和信息传递。

    实现主机之间的数据交换:分层为7层



作者:繁星亮与鲍包包
来源:知乎

撇开复杂与晦涩难懂的术语不说,个人理解七个层次通俗来说主要实现以下内容:

应用层:就是应用软件使用的协议,如邮箱使用的POP3,SMTP、远程登录使用的Telnet、获取IP地址的DHCP、域名解析的DNS、网页浏览的http协议等;这部分协议主要是规定应用软件如何去进行通信的。

(应用层此部分有修改,感谢@小张指正。)


表示层:决定数据的展现(编码)形式,如同一部电影可以采样、量化、编码为RMVB、AVI,一张图片能够是JPEG、BMP、PNG等。


会话层:为两端通信实体建立连接(会话),中间有认证鉴权以及检查点记录(供会话意外中断的时候可以继续,类似断点续传)。


传输层:将一个数据/文件斩件分成很多小段,标记顺序以被对端接收后可以按顺序重组数据,另外标记该应用程序使用的端口号及提供QOS。(不同的应用程序使用不同计算机的端口号,同样的应用程序需要使用一样的端口号才能正常通信)


网络层:路由选路,选择本次通信使用的协议(http、ftp等),指定路由策略及访问控制策略。(IP地址在这一层)


数据链路层:根据端口与MAC地址,做分组(VLAN)隔离、端口安全、访问控制。(MAC地址在这一层)处理VLAN内的数据帧转发,跨VLAN间的访问,需要上升到网络层。


物理层:将数据最终编码为用0、1标识的比特流,然后传输。(例如将题主头像的图片,变为一串01100111100这样的数字来表示)。




    进程之间的数据交互:IP + 端口号

    网络通信三要素:

        1.IP地址   2.逻辑端口号   3.传输协议

    1.ip地址:在TCP/IP 中使用4个字节(32bit)来表示IP地址,为了方便人们使用,
              将每个字节换算成十进制(点分十进制):192.168.1.164
      
      A类地址:第一个字节(网络编号)+后三个字节(网络中的主机编号)
      B类地址:前两个字节(网络编号)+后两个字节(网络中的主机编号)
      C类地址:前三个字节(网络编号)+后一个字节(网络中的主机编号)


      其中表示 主机编号的二进制位 全是1 ———— 广播地址
                                  全是0 ———— 网络地址
      均不可用。
      还有一些私有地址、保留地址也不可用。

      在各自的局域网中,只要内部ip不冲突就可以,因为这个ip不会出现在Internet中。

      类 InetAddress :此类的对象代表一个IP(使用静态方法获取对象)
          .getByName(String host)的传入host:
             1.null : 返回 127.0.0.1,本地回环地址,用于测试本机网络是否有问题。
             2.计算机名: 返回 实际IP地址(ipconfig)
             3.IP地址的文本表示:返回IP地址
             4.localhost : 返回 127.0.0.1,同null
          
          .getHostName():获取主机名
        
          .getHostAddress() : 获取原始IP地址

    2.端口号:

        用一个0~65535之间的整数表示。

        一个端口只能对应一个进程,但是一个进程可以有多个端口。


    3.协议 UDP/TCP

        协议可以看出是通信的规则。
    UDP:
        无连接通信协议,不可靠,即可以单向发送,不管是否有接收者。
        可以向若干个目标发送信息,接收发自若干个源的数据。

     udp协议对应的socket:
          1.DatagramSocket(int port):
               此类表示用来 发送 和 接收 数据报包的套接字,并指定进行交互的端口。
          2.DatagramPacket(byte[] buf, int length)
               构造 DatagramPacket,用来接收长度为 length 的数据包。
         
     发送端:

        //1. 创建一个soket
        DatagramSocket  udpSenderSocket = new DatagramSocket(8192);

        //2. 准备好要发送的数据,创建一个用于盛装发送数据的数据包,并将数据放入数据包
        byte[] bytes = "hello socket".getBytes();
        //创建数据报包对象,并且将数据放入其中,并且指明数据报包  发送的  目的端(目的主机的 ip+端口号)
        DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length, ip, 10086);

        //3. 利用socket发送数据
        udpSenderSocket.send(packet);

        //4. socket资源的释放
        udpSenderSocket.close();

     接收端:
        //1.创建一个Socket(端口号为 匹配发送端指定的端口号)
        DatagramSocket datagramSocket = new DatagramSocket(10086);

        //2.创建一个空数组来接收发送过来的数据,并放入缓冲包
        byte[] bytes = new byte[1024];
        DatagramPacket receiverPacket = new DatagramPacket(bytes, bytes.length);

        //3.开始接收数据( .receive(DatagramPacket p):从此套接字接收数据报包,数据存入 p 中)
        datagramSocket.receive(receiverPacket);//阻塞方法

        //4.必须要还原发送端过来的消息
        byte[] data = receiverPacket.getData();//byte[] getData(): 返回数据缓冲区。
          
        int length = receiverPacket.getLength();//int getLength() :返回将要发送或接收到的数据的长度。
        //因为返回来的是一个字符数组,所以用new String来转换成字符串
        String s = new String(data , 0 ,length);

        //5.释放资源,只需要释放Socket就可以,DatagramPocket()是一个数据报包,无资源释放方法。
        datagramSocket.close();


        运行的时候,没有先后顺序,因为是无连接的方式。推荐先运行接收端,再运行发送端。




优化实例:

利用线程、线程池来进行同步操作,不需要运行两个端的程序。

发送端:

     public class udpThreadSender implements Runnable {

    //成员变量
    //固定端口、目标端口和IP
    private final int SATANDER_PORT;
    int dest_port;
    String dest_ip;

    /**
     *
     * @param local_port  本地端口,发送端端口
     * @param dest_port 目标端口,接收端端口
     */
    public udpThreadSender(int local_port,int dest_port) {
        this.SATANDER_PORT = local_port;
        this.dest_port = dest_port;
        //设置一下自己的IP
        dest_ip = "192.168.6.23";
    }

    /**
     *
     * @param SATANDER_PORT 本地固定发送端口
     * @param dest_port 目标端口
     * @param dest_ip 目标Ip
     */
    //如果想要更改自己的发送端口的话
    public udpThreadSender(int SATANDER_PORT, int dest_port, String dest_ip) {
        this.SATANDER_PORT = SATANDER_PORT;
        this.dest_port = dest_port;
        this.dest_ip = dest_ip;
    }

    @Override
    public void run() {
        BufferedReader br = null;
        DatagramSocket datagramSocket = null;

        try {
            //1.创建Socket对象
            datagramSocket = new DatagramSocket();
            br = new BufferedReader(new InputStreamReader(System.in));

            //自动键入古诗
            String s = "FBI说";
            String ss = null;
            while((s = printpoem()) != null &&(ss = "FBI说"+ br.readLine()) != null){

                //2.建立接收数据的容器,并创建一个数据报包,指定Ip
                byte[] bytes = s.getBytes();
                byte[] bytes1 = ss.getBytes();

                //数据包(包括要传输的数据,还有 目的地 的地址和端口号)
                InetAddress Ip = InetAddress.getByName("192.168.6.255");
                DatagramPacket datagramPacket = new DatagramPacket(bytes ,0 ,bytes.length , Ip,10086);
                DatagramPacket datagramPacket1 = new DatagramPacket(bytes1, 0, bytes1.length, Ip, 10086);

                //3.传输数据,设置休眠
                datagramSocket.send(datagramPacket1);
                if(datagramPacket != null){
                    Thread.sleep(1000);
                    datagramSocket.send(datagramPacket);
                }

                //4.设置终止条件
                if("000".equals(s)){
                    break;
                }
            }

        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if(datagramSocket != null){
                datagramSocket.close();
            }
        }
    }
    private static String printpoem() {
        int i = new Random().nextInt(12);
        String[] arr = {"对酒当歌,人生几何","譬如朝露,去日苦多","譬如朝露,去日苦多","何以解忧,唯有杜康",
                "青青子衿,悠悠我心","但为君故,沉吟至今","呦呦鹿鸣,食野之苹","我有嘉宾,鼓瑟吹笙","忧从中来,不可断绝",
                "山不厌高,海不厌深","月明星稀,乌鹊南飞","周公吐哺,天下归心"};
        return arr[i];
    }
}


接收端:
    public class udpThreadRecive implements Runnable{
    static int count;
    //设置接收端的端口
    private final int port;

    public udpThreadRecive(int port) {
        this.port = port;
    }

    @Override
    public void run() {

        DatagramSocket reciveSocket = null;

        try {

            //1.创建socket对象,创建对象的时候,就要将输入端要发送过来端口输入
            reciveSocket = new DatagramSocket(port);

            //2.创建一个空的数据包接收数据
            byte[] bytes = new byte[1024];

            while (true) {
                DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);

                //3.接收数据
                reciveSocket.receive(datagramPacket);

                //4.还原数据
                byte[] data = datagramPacket.getData();
                int length = datagramPacket.getLength();

                String s = new String(data, 0, length);

                String[] aaa = new String[1024];

                //获取发射端的IP地址
                if(reciveSocket != null) {
                    InetAddress address = datagramPacket.getAddress();
                    //查看IP的名字
                    byte[] b = address.getHostName().getBytes();
                    int bb = address.getHostName().length();
                    String s2 = new String(b ,0 ,bb++);
                    System.out.println(s2);

                    System.out.println("from__" + address + " :" + s);

                }
                if("000".equals(s)){
                    break;
                }

            }
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(reciveSocket != null){
                reciveSocket.close();
            }
        }
    }
}


测试:
    public class HomeWork2 {
    public static void main(String[] args) {
        //两种方式
        udpThreadSender udpThreadSender = new udpThreadSender(2999,10086);
        udpThreadRecive udpThreadRecive = new udpThreadRecive(10086);
        //1.流
        //new Thread(udpThreadSender).start();
        //new Thread(udpThreadRecive).start();

        //2.线程池
        ExecutorService pool = Executors.newCachedThreadPool();
        pool.submit(udpThreadRecive);
        pool.submit(udpThreadSender);
    }
}



 数据传输的实现过程:

      TCP方式:
         有链接的通信方式,相当于一旦建立连接,就会在两个socket中间形成一个通道。
     TCP协议对应的socket:
         ServerSocket :此类实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。
         ServerSocket(int port)
              创建绑定到特定端口的服务器套接字。

     接收端:
         //1.创建socket对象(服务器套接字)
         ServerSocket serverSocket = new ServerSocket(8848);

         //2.建立连接(  accept()——阻塞方法 )
         Socket conntionSocket = serverSocket.accept();

         //3. 从socket中获取输入流对象(输入内存)
         InputStream in = conntionSocket.getInputStream();

         OutputStream br = conntionSocke.getOutputStream();

 
         //4. 读入内存并输出、写入文件
        int len = -1;
        byte[] buffer = new byte[1024];
        
        while((len = in.read(buffer)) != -1) {
            System.out.println(new String(buffer, 0, len));
        }

        //5.释放资源
        conntionSocket.close();
        serverSocket.close();

    发送端:

          //1.创建socket对象
         Socket socket = new Socket(destinationIp, 8848);

         //2.利用socket对象中的输出流,向目的端输出数据
         //先从socket对象中,获取本次连接产生的通道中的 输出流对象
         OutputStream out = socket.getOutputStream();

         //3.利用输出流对象,发送数据
         //此抽象类是表示输出字节流的所有类的超类。输出流接受输出字节并将这些字节发送到某个接收器。
         out.write("hello tcp".getBytes());

        //4.释放资源(在释放socket资源之前,释放连接中的流资源)
        socket.close();




eg. TCP多线程下的聊天室


发送线程:

    public class SenderThread implements  Runnable {
    String Ip;
    int port;
    //构造方法传入ip和端口
    public SenderThread(String Ip ,int port) {
        this.Ip = Ip;
        this.port = port;
    }


    @Override
    public void run() {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
                //1.创建Socket对象,要和我们连接的对端的IP和端口号
            Socket socket = new Socket(Ip, port);
            System.out.println("连接成功了吗?");
                //标准输入流录入信息(即作为复制的基文件)
                bis = new BufferedInputStream(new FileInputStream("aaaa.txt"));

                //2.通过输出流对象传输数据
                OutputStream out = socket.getOutputStream();
                bos = new BufferedOutputStream(out);
            System.out.println("这里得到了吗");
                //3.不断读写
                int len = -1;
                byte[] buffer = new byte[2048];
                while ((len = bis.read(buffer)) != -1) {
                    System.out.println("循环进来了吗");
                    bos.write(buffer, 0, len);
                    //每次读取都刷到文件中
                    bos.flush();
                }
                //关闭客户端的输出流
                socket.shutdownOutput();

                //4.作为客户端,接收服务器端反馈的信息
                int length = -1;
                byte[] bytes = new byte[2048];

            InputStream inputStream = socket.getInputStream();//如果得不到反馈信息会一直阻塞
            length = inputStream.read(bytes);
            System.out.println("这里阻塞了吗");
                System.out.println(new String(bytes, 0, length));


        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //5.释放资源
            if(bis != null){
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}



接收端父线程(专门用于监听端口的):

    public class ReceiveThread implements Runnable {
    private static boolean flag = true;

    @Override
    public void run() {

        ServerSocket serverSocket = null;
        try {
            //创建监听端口,由主线程传值过来
            serverSocket = new ServerSocket(TestThread01.port);

            while (flag == true) {
                new Thread(new ReceiveSonThread(serverSocket.accept())).start();
            }

        } catch (IOException e1) {
            e1.printStackTrace();
        }finally {
            try {
                if(serverSocket != null){
                serverSocket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void cancle(){
        ReceiveThread.flag = false;
    }
}




接收端子线程:

    public class ReceiveSonThread implements Runnable {

    Socket socket ;
    int port;
    public ReceiveSonThread(Socket socket) {
        this.socket = socket;
    }

    public ReceiveSonThread(int port) {
        this.port = port;
    }
    @Override
    public void run() {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            //3.获取Socket对象的输入流
            InputStream in = socket.getInputStream();
            bis = new BufferedInputStream(in);

            //选择要复制到的文件,输出到此文件,自定义文件名
            long lon = System.currentTimeMillis();
            bos = new BufferedOutputStream(new FileOutputStream(lon + ".txt"));

            //4.读取数据
            int len = -1;
            byte[] buffer = new byte[2048];

            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
                bos.flush();
            }

            //发送反馈信息
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("文件传输完毕".getBytes());

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (bis != null) {
                    bis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}



主线程:

    public class TestThread01 {
    static int port ;
    static String IP ;

    public TestThread01(int port, String IP) {
        this.port = port;
        this.IP = IP;
    }

    public static void main(String[] args) {

        //System.out.println("请输入发送端的端口");
        //int port = new Scanner(System.in).nextInt();
        //
        //System.out.println("请输入发送到的目的IP");
        //String IP = new Scanner(System.in).nextLine();
            
        new TestThread01(10086,"192.168.6.23");
        
        //线程
        new Thread(new SenderThread(IP,port)).start();
        //或者线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.submit(new ReceiveThread());
        executorService.submit(new SenderThread(TestThread01.IP,TestThread01.port));

    }
}









--------------------------------------------------------------------------------------------------------------------

    Day28  反射机制


1.类的加载

    类的加载
    当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
加载
   就是指将class文件读入内存,并为之创建一个Class对象(类加载器)。
   任何类被使用时系统都会建立一个Class对象。(字节码文件对象)

连接

   验证 确保被加载类的正确性
   准备 负责为类的静态成员分配内存,并设置默认初始化值。给给对象分配空间
   解析 将类中的符号引用替换为直接引用

初始化 就是我们以前讲过的初始化步骤

类的加载时机:
   创建类的实例
   访问类的静态变量
   调用类的静态方法
   使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
   初始化某个类的子类,子类父类都加载
   直接使用java.exe命令来运行某个主类

类加载器:
      负责将.class文件加载到内在中,并为之生成对应的Class对象。
      虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。
 
   类加载器的组成
      Bootstrap ClassLoader 根类加载器
      Extension ClassLoader 扩展类加载器
      System  ClassLoader  系统类加载器


类加载器的作用:
    Bootstrap ClassLoader 根类加载器
         也被称为引导类加载器,负责Java核心类的加载
         比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
    
    Extension ClassLoader 扩展类加载器
         负责JRE的扩展目录中jar包的加载。
         在JDK中的 JRE\lib\ext 目录。
    
    Sysetm ClassLoader 系统类加载器(应用类加载器)
         负责在JVM启动时加载来自java命令的class文件,


2.反射


    JAVA反射机制是在运行状态中,
        对于任意一个类,都能够知道这个类的所有属性和方法;
        对于任意一个对象,都能够调用它的任意一个方法和属性;

        这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
        要想解剖一个类,必须先要获取到该类的字节码文件对象。
        而解剖使用的就是Class类对象。从而得到其中的方法或成员.所以先要获取到每一个字节码文件对应的Class类型的对象.

    获取字节码文件对象:

    方式一
    Person p = new Person();
    Class c = p.getClass();

    
    方式二(如果此类只使用一次)
    Class c3 = Person.class;
   
    方式三(企业开发中使用)
    Class c4 = Class.forName("com.cskaoyan.Person");


获取类中的三个模块:
    用  字节码文件对象.   的方式来获取
    用  获取得到的对象.   的方式来使用
    
    1.获取构造方法:
       获取构造方法:
          .getConstructors
:获取所有子类和父类的公共构造方法(public 修饰的)
          .getDeclaredConstructors :获取本类的所有构造方法(所有修饰符,非父类)
       
       创建对象:
          .newInstance():使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数
      初始化该实例
          con.newInstance(“zhangsan", 20);


    2.获取成员变量:
        获取所有成员
             .getFields,getDeclaredFields
        获取单个成员
             .getField,getDeclaredField
        修改成员的值
             .set(Object obj,Object newvalue) :
将指定对象变量上此 Field 对象表示的字段设置为指定的新值。


    3.获取成员方法并使用

          获取所有方法
             .getMethods
             .getDeclaredMethods

          获取单个方法
             .getMethod
             .getDeclaredMethod

          暴力访问
             method.setAccessible(true);



-------------------------------------------------------------------------------------------------------

    Day28


1.注解










猜你喜欢

转载自blog.csdn.net/qq_38962004/article/details/79729665
今日推荐