java基础笔记很实用

时间:2021-11-05 17:23:49
day1


变量名命名要做到见名知意(变量的名称保持在15个字符以内)
java中小数类型默认为double
8个比特=1个字节
java中基本数据类型中整型为默认为int
long类型全都是用大写的L因为小写l与1太相似不宜发现
常量,变量
byte b2 = 120+3;
它在编译前它们能确定其值。当你输入javac命令进行编译时java编译器默认为你进行强制转换为byte
byte b2 =(byte)(120+3);

byte b1 =12;
byte b2 =15;
byte b3 = b1+b2;不能编译通过,因为b1,b2为变量编译前无法知道它们确定的值
常量:它是在编译时期确定其值,后面不能改变其值。
final 放在数据变量前面表示常量。

运算符:
1.算数运算符
%:取模运算符(取余数,结果一定小于被模的数)
456%100=56(利用这个特性我们可以获取指定位置后面的数)

 结论:算数运算符参与的数只能是整数或小数

2.逻辑运算
    短路与  &&(短路出现条件前面一个为假)
    短路或  || (短路出现的条件前面一个为真)
    非       !
    

3.比较符运算符

4.位运算符(操作整数所对应的比特位,参与位运算的都是整数)
   &(上下位都为1时结果位为1其他情况为0)
   | (上下位都为0时结果位为0其他情况为1)
   ^  (上下位相同时它的结果位为0否则为1)

5.位移运算符
 <<  它在计算机中的原理是:先再右边加上相应个数的0,再在左边出掉相应个数的0
 >>  它先再右边出掉相应个数的0,再在左边补上相应个数的0

6.自增(自减)运算符

8 请计算:
  a=3,b=4;

 int result =(a++)/3+(--b)*2-(a--)%6+(b++)*3-(b--);

//解法:(a++)它相当于temp= (a++);
   (a++)它返回3 a的值为4
   (--b)它的返回值为3  b的值为3
    (a--)它的返回值为4 a的值为3
    (b++)它的返回值为3 b的值为4
     (b--)它的返回值为4 b的值为3
   3/3+3*2-4%6+3*3-4
 0+6-4+9-4 = 15;

~取反操作:
   对于负数它的结果是 对其取反再减1(如 -123 ~(-123) = 122)
   对于正数它的结果是 对其取反再减1  (如 11 ~(11)= -12)

转义字符:
 \t tab空格
  \r\n 回车加换行
 
注意:window下\r\n要一起使用,代表回车加换行。
linux,solaris \n代表换行


表达试:它一定有个确定的结果.

优先级:
  () > 算数运算符 > 比较运算符 > 逻辑运算符



day2

顺序结构,
  从上到下依次执行
选择结构,
   执行前先判断,条件满足下执行程序指令(根据条件进行选择)
   语法:
      if(boolean条件){ //为真的情况下执行的语句}

  使用位&运算判断奇偶性,效率最高(因为奇数的最低位为1偶数的最低位为0)
  if((num&1)==0) 由于1的二进制数为 0...001的最低位为1所以但num最低位为1时&运算为1否则为0

 switch只判断一次条件。
    语法:
    switch(条件){//switch中的条件只能是int,char,byte,short,String(jdk7才支持String)
      case  常量:
      .....
      break;
      case 常量2:
      .....
      break;
      default://当所有的条件不满足时执行它
       .....
      break;
    }

如何在控制输入中取char
Scanner input = new Scanner(System.in);
char c = input.next().charAt(0);
原因:input.next()是取字符串,在字符串中有个方法取单个字符(charAt(int i));
补充点:
从控制台上获取输入内容还有
    BufferedReadr read = new BufferedReadr(new InputStreamReader(System.in));
    char[] cbuf = new char[1024];
    int len =-1;
   while((len=read.read(cbuf))!=-1)
     System.out.println(new String(cbuf,0,len));

三元运算符:对于简单的boolean条件使用它
    
循环结构,

java中有4中循环:
 while,for,do while,foreach
分为两类:
  1,当型循环(做循环之前先判断boolean条件。即先判断后执行)while,for,foreach
  2,直到循环(先执行一次循环,再判断条件,如果条件为真进行下一次循环,否则结束循环。即先执行后判断)do  while




day3

定义变量写注释,定义class写注释,定义循环写注释
for(变量初始值;条件判断;更新循环变量){
     循环体
}
first:变量初始值在整个循环过程中只执行一次

如何在for循环定义两个变量?
 for(int i=0,j=1;i<4&&j<5;i++,j++)多个初始化以逗号分隔
如:
0+6=6
1+5=6
2+4=6
3+3=6
4+2=6
5+1=6
6+0=6
for(int i=0,j=6;i<=6;i++,j--)
  sum = i+j;
break用在循环中,终止整个循环,执行循环后面的语句。它可用于do...while,while,for,foreach(你在哪个循环break就跳出哪个循环)
continue用在循环中,表示结束本次循环执行下次循环,它跳过continue后面的语句,对于for循环它会跳到更新变量哪里,对于while它会跳到while中的判断那里。(总之它是向前跳)

int count=0;
for(int i=0;i<10;i++){
  count = count++;
}
这里有个临时变量来存储temp = count++,然后把temp赋值给count
结果为0

do{
  循环体;

}while(boolean条件表达式);

方法的重载:
 方法名和参数列表决定了方法的唯一性,方法的重载和返回值无关。
在重载中先是精确匹配,如果不行采用模糊匹配时采用最近原则
方法的重载是在编译时期确定(同名不同参,是在同一类中)
方法的重写是在运行时期确定(同名又同参,涉及多态,即继承)






day5

二维数组其实就是数组中的数组
为什数组中的下标是从0开始计数
它是指的偏移量

类和对象?
类是对象的抽象表示,对象是类的一个实例
new的开销内存有点大
基础类型创建的对象只使用栈内存,直接将值存储到栈上
引用类型创建对象。它会在堆内存开辟一个空间,然后再在栈内存中存放执行堆中创建那个对象的地址。
是用赋值运算符,等号右边把创建好的地址赋值给等号左边

局部变量:在方法体内定义的变量
成员变量:对象的属性,定义在类中且没有被static修饰
它们区别:
局部变量必须初始化才能使用,成员变量不用初始化就可以使用。如果使用成员变量没有初始化java虚拟机会为其分配一个默认值。

day6
构造方法:
根类同名的方法它用来完成对象(属性)的初始化。
语法:类名称(<参数列表>){
}
构造方法没有返回值,连void也不能有。必须和类同名

程序进入构造方法时(如果不存在显示继承)它会先看类中是否有成员属性要显示初始化,是否有构造代码块有就先将它们顺序执行在回到构造方法中执行里面的语句

this java这里指的是当前对象
this是在new之前创建出来并且它在类中所有成员变量赋值时,成员方法调用就存在(包括构造方法)
局部变量优先级高于成员变量

构造方法不能直接显示调用需要jvm调用(只能new ),类中用this(...)。在一个构造方法中调用另一个构造方法最多只能掉用一次

哪些地方可以初始化属性
1.属性定义处
2.构造方法
3.成员方法 (缓式初始化,即需要使用时才初始化)
4.构造代码块

关联:一个对象依赖与另一个对象
持有has a
继承 is a
实现 like a

在类中属性的显示初始化会先于构造方法执行
(执行构方法之前要确保成员属性显示初始化完毕)
成员属性是属于对象的每创建一个对象都会拥有自己的成员属性。

static修饰的属性所有对象共享一个即类变量。

-------------------------------------类中代码执行的顺序-----------------------------------------------
类的对象创建时各部分执行顺序?
先静态属性默认初始化-->静态属性显示初始化,执行静态代码块(这步骤与它们出现顺序相同)-->成员属性默认初始化-->成员属性显示初始化,构造代码块(它们是顺序执行)-->构造方法
//静态块和静态属性显示初始化同级它们的执行顺序与出现顺序相关
//构造块和成员属性显示初始化同级它们的执行顺序与出现顺序有关




day7
eclipse中的快捷键。Alt+Shift+R//重组代码
Alt + Shift + j自动生成注释

构造块使用情景:对象之间的共性的初始化

在eclipse下使用断点(breakpoint)的方式调试程序?

 f11调试程序   
 f5进入方法执行
 f6单步调试
 f7跳出方法
 f8是跳到下一个断点

//在执行创建对象之前所有的静态属性要先进行默认初始化完毕





面向对象3大特征:
封装:隐藏实现的细节,将相同对象的属性和方法封装成一个类
不能使用private修饰类,private可以修饰属性和方法(你写的类是给客户使用如果定义为私有客户无法使用所以没有意义)
friendly友善的。就是没有加修饰符默认为友善。
friendly在哪些地方可以让外界访问?
  它是一个package级别的访问权限,除了所在包之外任何地方不能访问(package可以理解为文件夹)

1.包名+类名=全名
2.包存在的目的是管理类的


程序启动jvm会默认为你导入java.lang

子类初始化时先初始化父类
执行顺序:(首次加载)
父类静态属性默认初始化-->父类静态属性显示初始化,静态代码块-->子类静态属性默认初始化-->子类静态属性显示初始化,静态代码块-->父类成员属性默认初始化-->父类成员属性显示初始化,构造代码块-->父类构造方法-->子类成员属性默认初始化-->子类成员属性显示初始化,构造代码块-->子类构造方法

super:
使用它可调用父类的构造方法,父类成员方法,父类的属性

当父类中存在有参构造方法,没有无参构造方法时,子类的构造方法中都必须显示的调用父类的有参构造方法
(必须在子类构造方法的第一行调用父类构造方法。。。。super("小米"))

在执行new 时会首先进入构造方法中。。如果有父类则进入父类构造方法中。。
构造方法执行时会先看有没有成员属性需要显示初始化,有没有构造代码块。执行完这些,它就会执行构造方法中的语句

向上转型:用父类引用或接口引用来接受子类对象
在使用某个类对象又不想让用户去操作它可也把它定义一个内部类并用private来修饰它

子类构造方法调用父类构造方法时只能调用一个而且必须放在构造方法中的第一句,this和super在构造方法中不能同时出现来调用构造方法,因为它们都必须为构造方法的第一句。。



day8

多态:
一个事物有多种不同表现。(表现形式:用父类引用或接口引用来接受子类对象)
支持的条件:1,向上转型  2,方法都重写(覆盖)

成员方法,编译看左边的引用是否有调用的那个方法(做什么)运行看右边真正的实现对象并调用它的方法(怎么做)
动态绑定,将做什么和怎么做分离开。
 在运行时期来确定真正的对象类型并调用上的方法。
向下转型(强制类型转换):不安全
instanceof 判断某个引用是不是某个类的实例引用(在做向下转型先用instanceof判断再做转型)
注意instanceof的用法有限制,它只能是用于判断向确定子类转型。。。即一般用于多态中左边引用变量向右边实际对象类型的转换
重写父类静态方法没有意义,它是与类相关和对象无关。
多态时:当程序运行到父类中的类体时它的this是子类的对象的应用(用eclipse断点调试可以很方便的知道)




抽象类:(它是一个模板)
一个抽象类中可以有0个或多个抽象方法,抽象方法只需定义。用abstract来修饰。
抽象类中可以具体方法,抽象类不能直接创建出来
抽象类中有构造方法
抽象类存在的目的:是让子类继承它重写它的抽象方法(具体实现下沉到子类)
子类继承抽象类时如果子类是具体类那么它必须实现抽象父类中未实现的方法




接口:
接口中只能为这样的常量static final public(只能定义常量)//接口中的常量一定要在编译期知道其值(如果不这样会产生无法想象的结果)
接口中的方法 abstract public(在jdk1.8以下只能是方法的声明)
什么时候使用接口?
  需要定义抽象描述的时候,优先使用接口(协议)。除了模板使用抽象类,其它都使用接口作为父类
如果你在接口中定义如下:
double PI =3.14;经过编译时JVM会帮你转换为public static final double PI = 3.14;
public void draw();经过编译时JVM会帮你转变为public abstract void draw();
接口与接口之间为继承(在接口的继承中可以继承多个接口)extends
类与接口之间为实现 implements(在实现接口时不要太多最好小与5个)
JDK1.8接口新特性
默认实现:
public default String getDevice(){
    return "游泳衣";
}
注意:
在接口中写实现要加default关键字不加就会报错
当一个类实现多个接口时这些接口中不能出现两个方法名相同,参数相同的default方法实现,如果存在这要的两个接口则不能同时实现
JDK1.8在接口中可以写模板
不要在接口中出现方法体




内部类:
嵌套在外部类里面的类
内部类分为4种
1.成员内部类
成员内部类也叫实例内部类,即没有用static修饰的内部类(它的存在依赖在外部类的对象上,可以理解为它是个寄生虫没有实体对象外部类它就不可能活)

外部类.this 表示外部类当前对象(在内部类中调用外部类.this来获取外部类当前对象)
为什么成员内部类可以访问外部内成员属性和成员方法(静态属性,静态方法也可以)?
因为它在调用外部成员属性时jvm会在编译其加上 外部类.this.成员属性  外部类.this.成员
方法。

外部类名义上是内部类实际上是外部类。
因为它会产生单独的.class文件(格式  外部类名$内部类名.class)
总结:成员内部类绑定在外部类引用上,要想获取内部类引用必须要先有外部类实例对象
class House{
  public class Window{
  }
}
House.Window window = new House().new Window();



2.局部内部类
嵌套在方法的内部的类,方法调用完毕,局部类部类产生的对象会被JVM回收掉
局部内部类只能访问final的局部变量
局部内部类中也持有外部类的引用



3.匿名内部类
没有名字的内部类,存在外部类中
使用条件:某对象只使用一次
new ParentName(){
...// 内部类的定义
}

new Runnalbe
//匿名内部类开始
{
  public void run(){
   System.out.println("hello");
   }

}//匿名内部类结束
;
匿名内部类也叫接口回调

4.静态内部类
如果一个内部类使用static声明,则此内部类就称为静态内部类。(静态是类级别)
静态内部类只有一份存储空间


好处:
1.提高代码的封装性
2.内部类持有外部类的引用
3.完成多重继承


在文件管理方面,成员内部类在编译完成后也会产生.class文件,而文件名称则是”外部类名称$内部类名称.class”
非静态Inner class 可以使用宿主类的静态和实例成员变量
在宿主类之外在权限允许时可以通过宿主类名.内部类名来访问。
Outer.Inner inner = new Outer.Inner();
在宿主类内部无论是否在静态方法中,都可以直接创建该内部类的对象
静态内部类不会持有外部类的引用,创建时可以不用创建外部类对象.
静态内部类可以直接访问宿主类的静态变量,但如果要访问宿主类的成员变量必须通过宿主类的实例访问

day9
23种设计模式
 针对某个特定的问题做出的解决方案。依靠前人总结的经验来实现特定的解除方案
  1.创建模式(针对特定问题创建对象的实例)
      好处:做什么和怎么做分离
            对扩展开放,对修改关闭
            依赖抽象(接口),不依赖实现
            让代码更健壮,更清晰,可读性更强

    


 单列模式
     从项目的开始到项目的结束,某个对象只有唯一的一个实例
     使用情况:在整个项目中,相让某个对象具有唯一实例的时候使用单例模式
     特征:
      1.私有化构造方法(让外界无法new 对象)
      2.提供一个公有static方法让外界获取这个唯一实例

class Outer{

//通过嵌套类来实现单例模式
  private static class Inner{
    private static Outer instance = new Outer();
  }

//私有化外部类的构造方法使其无法主动new
  private Outer(){}
//提供静态方法来返回唯一的实例
  public static Outer getInstance(){
    return Inner.instance;
   }
 }

//一个约定:getInstance()就是使用单列
为什么使用静态了?因为静态属性只在类加载时实例化一次。。




 简单工厂模式
    好处:将产品的创建和产品的行为分开
    使用条件:对客户端隐藏创建产品的细节
    缺点:没有做到对扩展开放,对修改关闭

 工厂方法模式(多态工厂模式)
     针对每个产品的工厂


  2.结构模式
  3.行为模式
     状态模式:
     每个对象在运行的时期状态都会发生变化,根据状态的不同,调用对象的相应行为
     


Runtime:在程序执行期获取内存中的数据(程序执行使用了多少内存,启动一个程序分配了多少内存,剩下多少内存)

Date获取系统时间
new  Date(long params)
注意:它内面填入的数是1970+填入的毫秒数


SimpleDateFormat格式化时间 Y M d(一个月中所占天数) HH(一天中24小时制) mm ss
SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
sdf.format(new Date());
注意:DD(它是一年中所占的天数) hh(它是一天中12小时制)



java中常用的类:
  日历Calendar(可以通过它来设置系统实际,获取系统实间)
  Calendar为abstract 不能直接new 要Calendar.getInstance();
  常用方法:
       getTime();获取系统时间
       setTime(Date date);设置系统时间

  常用常量:
       Calendar.YEAR;//年
       Calendar.MONTH;//月
       Calendar.DAY_OF_MONTH//一个月的第几天
       Calendar.HOUR_OF_DAY//一天的第几个小时
       Calendar.MINUTE//分钟
       Calendar.SECOND//秒

  综合应用:
       get(Calendar.YEAR);//获取年
       get(Calendar.MONTH);//它获取的月份是从0-11
       set(Calendar.YEAR,int Value)//设置年数
       set(int yearValue,int monthValue,int dayofMothValue,int hourofDayValue,int minuteValue,int secondValue)

 总结:日历类有获取年月日时分秒,可以设置年月日时分秒。
   注意获取的月要加1,设置月时要减1

   F3 打开源码
     -->观察类是如何定义的
     -->看构造方法,如何创建对象(Ctrl + o)
     -->观察有哪些属性


day10
String 类
    由字符数组所组成的一系列文字字符(字母,数字,标点符号)
    本质:是字符数组。
    特征:字符串一旦赋值结果不能改变,它不是基本类型是引用类型
     public final class String
     它中有一个属性:
     private final char value[];

    它是最终类不能被改变,不能被任何类继承

java字符串的创建过程(不使用new的那中方式)?
   当创建一个字符串时它首先在方法区中的常量池中查找是否有这个字符串。存在就把它的地址赋值给那个引用变量,没有先创建并把它放入到方法区中的字符串池中并把它的地址给那个引用变量
(方法区中字符串不存在则创建并放入,存在则改变栈内存的指向)


    方法区中字符串池查找---(存在)--->把它的地址赋值给那个引用变量(即把栈中那个变量指向它)
             |
              ------(不存在)--->创建后放入字符串池---->把它的地址赋值给那个引用变量

这样做是为了节约内存(String 它的创建开销比较大)

难点:
String str =“sfsd”;
它不会在堆中创建,它的创建与否在方法区(具体看上面)
String str2 = new String(str);
它是在堆中创建的。
str!=str2因为它们存在地方不同一个在堆中,一个在方法区中地址当然不同

常用方法:
   equals(Object object):实现原理 1.先判断是否为同一个对象 2.接着比交长度是否相等 3.再放入字符数组依次比较
    return (this == anotherString) ? true
                : (anotherString != null)
                && (anotherString.value.length == value.length)
                && regionMatches(true, 0, anotherString, 0, value.length);
 //这句写的非常好。其顺序不能被改变。

   substring(int beginIndex,int endIndex): 注意它不包括endIndex
      实现原理:1.如果它操作截取的长度就是len它就返回this 2.否则会重新创建一个
   replace(String oldone,String newone) jdk1.8中不需对转意字符转换
   split 需要对特殊的字符进行转意\\. \\'
   startsWith
   endsWith
   toLowerCase 它们的实现很简单先依次判断每个字符是否为小写(小写字符在一个范围之间)如果是就对其进行加上(可能减去,忘了)一个数 将其变为大写
   toUpperCase
   equalsIgnoreCase
***intern():检查要创建字符串在方法区中的字符串池中是否存在,存在就指向字符串池中的那个字符串,不存在将其拷贝到常量池中并将其指向字符串池中那个。

    indexOf()效率最高,返回第一次出现位置
    indexOf(String string,int beginStart);它是从第beginStart个位置开始
  总结:该方法返回结果一定是一个整数
        -1代表没有找到
    
注意:使用isEmpty判断是否为null会导致空指针异常(因为这个方法是成员方法,而为null时何来的对象啊)
     if(str==null ||str.length()==0){
     System.out.println("为空");
     }else{
     System.out.println("不为空");
     }

好的判断(apache 中的equals的实现)
   return str1 == null?str2 == null:str1.equals(str2);(写的好)


StringBuffer(但要添加的数大与3就用它不用String)
   它是操作它里面的字符数组(默认大小16),它只会对字符数组扩容(字符是基本类型,操作效率高)而String加是不停的在堆类型中创建。
   String str = "hello";
   String str2 = "nice";
   String str3 = str + str2;
------------它实际是如下代码------------
    String str3 = new StringBuffer(str).append(str2).toString();
使用new就会在堆内存中创建对象
 
链式编程:用返回this实现

StringBuilder是非线程安全?(通过源码分析)它的append不是同步方法。。而StringBuffer的append
是同步方法(synchronized)

StringBuffer中的toString方法
 public synchronized String toString() {//从这个方法可知它是在堆上产生了一个String对象
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
    }

如何监控你的程序,你写的程序如何在JVM运行的?
 在JDK的bin目录下执行JConsole.exe可以监控你的程序。

如何设置你的堆内存?
 右键-->runas -->configuration -->Arguments 中的VM arguuments 输入-Xmx1024m -Xms1024m
-Xmx 设置最大的堆内存
-Xms 设置初始堆内存

堆内存:分为年轻代,中年代,老年代



day11
Object是所有对象的超类
Object 中的equals方法是比较两个对象是否相等(地址相同)
toString();
 clone();//完成对象的浅复制
 notifyAll();
 wait();
 finalize();

定义实体类:
  1.定义属性
  2.生成getter/setter
  3.生成构造方法
  4.覆盖Object类中的toString hashCode equals

同一个类中产生不同的对象(同一个类)它们的hashCode相同


泛型:
    不是泛型错误只能在运行时期发现,而泛型可以把错发降到编译时期。
    泛型只能帮你做数据类型的检查,不能完成逻辑,在运行时期泛型发生擦除
    只在编译时期有效,在.class文件中泛型已经消失掉


 什么是泛型?
  泛型定义者:在编译其不确定数据类型,它是泛型持有者
  泛型使用者: 在使用泛型式必须明确类型

好处:
  无需做强制类型转换,使用它更安全。
 
 
 如果定义泛型类?
   public class 类名 <T>{} //在这里类上的泛型的名称要和属性名称前泛型保持一致
   
  在一个类中定义多个类型
   public class 类名 <T1,T2>{}
   尖括号里面的类型只能是引用类型不能是基本类型。

 public<T> T show(){}; 泛型方法

注意: T[] t = new T[1024];编译报错,编译不确定大小无法分配空间(数组的空间分配是根据个数及类型,不同类型分配大小不同),凡是有new的就会到堆上分配空间




集合
 持有你的对象
 使用集合的好处?
   数组一旦定义了容量就不能改变,集合可以为你自动扩容。
   数组:对象大小确定,对象寿命已知
   集合:对象大小确定,对象寿命未知

list:有序可重复
set : 无序不重复

ArrayList的本质是一个Object[] (Object 数组)

  private static final int DEFAULT_CAPACITY = 10;
  private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
new ArrayList()它不会为里面的Object数组分配空间。但add方法时才分配空间。
属性Object[]使用的是懒加载(延迟加载)
add所做的事。1.扩容(源码中在grow方法中扩容使用位移扩容(count+count>>1))
              2.把你的数据加入
   int newCapacity = oldCapacity + (oldCapacity >> 1);

size() 返回集合大小(集合中放入的元素)
contains() 判断某个对象是否在集合中
remove() 将对象从容器中移除(它有两种一种是对象一种是索引,使用索引删除其返回值为被删的对象,使用对象删除其返回值是boolean它表示是否删除成功)
泛型为Integer类时如果你传人的为基本类型int就是以索引来删除,它不会帮其装箱。要想使用类型来删必须显示转变为Integer (new Integer(1))


LinkedList
 它的内部是一个双向链表

   transient int size = 0;
   transient Node<E> first;
   transient Node<E> last;
    public LinkedList() {
    }
 Node是他的一个静态内部类(
private static Node<T>{
 Node pre;
 Node next;
 T data;
})静态可以使其只加载一次节约内存,private使其不能被外面访问。//把内部类看成外部类的一个属性
它的查找操作
  Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) { //mumber>>1 ==mumber/2;(这样写效率最高)
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
特有方法:
      getFirst(),addFirst(),removeFirst(),getLast(),addLast(),removeLast()
      addFirst()与offerFirst()区别,如果列表爆满addFirst会抛出异常,offerFirst只插入不了不会抛出异常
      getFirst()与peekFirst()区别,当没有数据时getFirst()会抛出异常,peekFirst()会返回null
      removeFirst()与pollFirst()区别,在没有数据下removeFirst会抛出异常,pollFirst返回null



Set(不重复)
 使用hashCode和equals来做判断(先比hashCode再比equals)
 它就先是一个桶你面装这数据,每当新元素放入时会逐个比较
 
定义实体类一定要覆盖hashCode,equals,toString。
 hashCode如果不同不会判断equals.hashCode如果相同会继续判断equals如果equals为真不会放入,为假放入



Map(键值对)它的核心在key上面
缓存:数据的缓冲区(经常使用,不经常被修改)
什么时候放入:项目启动时时候,将数据放入缓存(这个动作只执行一次)通常放在静态代码块中
注意:缓存是以空间换时间,不要乱使用缓存

 HashMap:特点(以键值对的形式来存储数据)键必须唯一值可以重复
 它的键可以存入null不过只能存放一次

通常的用法:
 class Test{
   private final static Map<String,String> cache = new HashMap<>();
   static{
     加载数据到cache中(使用put方法)
    }
 }

常用方法:
 size()获取容器的大小
 containsKey()判断某个key在map容器是否存在
 containsValue()判断某个value在map容器中是否存在
 clear()清空数据但是new的对象还在
    public void clear() {
        Node<K,V>[] tab;
        modCount++;
        if ((tab = table) != null && size > 0) {
            size = 0;
            for (int i = 0; i < tab.length; ++i)
                tab[i] = null;
        }
    }

  //从这可知它的内部是靠一个Node[] 即表来维护的
    
  entrySet()的迭代来实现 for(Map.Entry<K,V>:map.entrySet())  //重点
 Set<Map.Entry<K,V>> set = map.entrySet();//多用它,它的安全性高

   interface Map<K,V>{
      interface Entry<K,V> {
          }
   }


HashSet 实现使用HashMap的键它将其值为null

   private transient HashMap<E,Object> map;
   private static final Object PRESENT = new Object();

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

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

//取操作系统的环境变量
  System.getenv()//它返回一个Map<String,String>


 LinkedHashMap(可以保证有序)

总结:
  List:内存表(List的key必须为数字)
  Set:只需要在被添加的对象类中添加hashCode,equals,toString
  Map:键值对的形式存储数据
  ---Properties




集合的排序。
1.使用TreeSet可以
2.使用集合的工具类Collections(它是集合工具类)
  使用Collections.sort()。要比较的元素必须实现Compable接口
   它是当前对象会从已排好的最后一个元素先前开始比较,把要和它比的对象作为参数传人。
在调用Collections.sort()方法时如果传人只是list集合它会执行list元素上的compareTo






异常
  程序在执行期间出现的非正常的事件。
  1,Exception
    NullPointException
    ArrayIndexOfBoundException
    可以处理。

  2,Error
     *Error
     OutOfMemeryError
     JVM级别的非正常的事件,不可以恢复。


   Exception分为两类:非受检异常和受检异常
    Throwable
           ----Exception
              ----非受检异常(NullPointerException)特征:可以处理也可以不处理
              ----受检异常(FileNotFoundException)特征:必须处理
           ----Error(*Error)

java中处理异常的5个关键字:
    try,catch,finally,throw,throws

try,catch使用时必须子类在前,父类在后。原因执行如果父类在前子类永远执行不到。
同理finally必须要放在catch之后不然catch执行不到。finally的后面必须加大括号

错误是渐渐向上抛出,最后还是自己处理不然它会抛到jvm哪里去。

throws (只能跟在方法定义的后面)
  声明一个异常,但是不处理,将声明的异常交给调用者处理,特征:向上抛出,交给调用者来处理

throw 语法throw new 异常() 抛出异常 注意:抛出的异常一定要在try....catch块中抓取

千万不要压制异常,try。。。catch中一定要抓住异常
最大的忌讳:catch里面啥都没有

子类的异常级别只能和父类异常级别相同(或低于父类异常级别),不能比它高
注意:不要在main方法中直接throws掉它是抛给java虚拟机,即必须在main方法中try。。catch来处理

自定义异常: 异常类名 extends RuntimeException{
               public 异常类名(String msg){super(msg);}  
               public 异常类名(){super();}                 
                }
             异常类名 extends Exception{}
 自定义异常要和throw new 相匹配使用。




File(文件)磁盘中一个路径表示,这个路径可以是文件还可以是目录
以new的方式来创建  File path = new File("");//参数必须是路径
  File(String pathname);
  File(File parentFile,String pathname);
  File(String parentname,String childname);
  File(URI uri);
  getAbsolutePath()获取File对象的绝对路径

工作中切记不要用绝对路径,使用相对路径。
使用系统默认的相对路径来代替绝对路径,好处跨平台,避免硬编码
 File file = new File("AI78");
JVM如何可以找到AI78的路径(java虚拟机默认为你加上System.getProperty("user.dir")+File.separator)
String path = System.getProperty("user.dir")+File.separator+"AI78";

File对象最常用的方法:
isDirectory():某个路径是否是目录
isFile():某个路径是否为文件
exists():判断路径是否存在
createNewFile():创建文件
mkdir():创建子目录
mkdirs():连级创建目录
getAbsolutePath():获取绝对路径
getName():获取文件或目录的名称
getPath():获取相对路径
length():获取文件的大小单位byte
getTotalSpace():获取文件所在磁盘的总的空间单位byte
getUsableSpace():获取文件所在磁盘的使用空间大小byte
lastModified():获取文件最后修改时间(Date date = new Date(modifyTime); SimpleDateFormat sdf = new SimpleDateFomate("YYYY-MM-dd HH:mm:ss");  sdf.format(date);)
isHidden():是否为隐藏文件或目录
canRead():判断某个路径是否能读
canWrite():判断某个路径是否能写
canExecute():判断某个个路径是否能执行
list(): 返回String[] 返回此目录所有文件和目录名称(不带路径)
listFiles():返回File[] 返回路径下的所有目录和文件的File对象
renameTo(File ):重命名
delete():删除文件

有两个过滤器:
 interface FilenameFilter{  boolean accept(File dir, String name);}
 interface FileFilter{   boolean accept(File pathname);}
//推荐使用它们时使用匿名内部类


File中调用list(filter)的源码
 public String[] list(FilenameFilter filter) {
        String names[] = list();
        if ((names == null) || (filter == null)) {
            return names;
        }
        List<String> v = new ArrayList<>();
        for (int i = 0 ; i < names.length ; i++) {
            if (filter.accept(this, names[i])) {
                v.add(names[i]);
            }
        }
        return v.toArray(new String[v.size()]);
    }


File中调用listFile(filter)的源码
    public File[] listFiles(FilenameFilter filter) {
        String ss[] = list();
        if (ss == null) return null;
        ArrayList<File> files = new ArrayList<>();
        for (String s : ss)
            if ((filter == null) || filter.accept(this, s))
                files.add(new File(s, this));
        return files.toArray(new File[files.size()]);
    }
//注意new File(s,this)它使用的是一个私有的构造方法
    private File(String child, File parent) {
        assert parent.path != null;
        assert (!parent.path.equals(""));
        this.path = fs.resolve(parent.path, child);
        this.prefixLength = parent.prefixLength;
    }





java中IO
try(){}//小括号自动关闭资源

FileInputStream:基础流
源码解析: (分析可知它真正从磁盘上读取数据到程序中的方法只有2个)

   private native int read0() throws IOException;//读取一个字节数据到程序中
   private native int readBytes(byte b[], int off, int len) throws IOException;//读取一个字节数组数据到你传入的数组中


FileOutputStream:基础流
源码解析:  (分析可知它真正写到磁盘中的方法也只有2个)

   private native void writeBytes(byte b[], int off, int len, boolean append)//把你程序中一个指定大小的字节数组写入到磁盘上 最后一个参数表示是否采用追加方式
        throws IOException;
   private native void write(int b, boolean append) throws IOException;//把一个字节数据写到磁盘上 最后一个参数表示是否采用追加方式


处理流:(装饰者模式)
 BufferedInputStream
从硬盘上获取数据,并放入到内存中其源码有点混乱,它内部也是一个缓存机制吧


 BufferedOutputStream
源码分析:
   protected byte buf[];//缓存数据用
   protected int count;//记录缓存中数据个数

   public BufferedOutputStream(OutputStream out) {
        this(out, 8192);
    }

    public BufferedOutputStream(OutputStream out, int size) {
        super(out);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }

    private void flushBuffer() throws IOException {//这个方法才真正的把数据写到硬盘上
        if (count > 0) {
            out.write(buf, 0, count);//处理流调用基础流来写入到硬盘上
            count = 0;//将数据大小给为0
        }
    }

    public synchronized void write(int b) throws IOException {//写数据时如果没有超出缓存大小它会放入缓存中
        if (count >= buf.length) {//超出缓存容量会先刷新
            flushBuffer();
        }
        buf[count++] = (byte)b;//将数据添加到缓存中
    }

 public synchronized void write(byte b[], int off, int len) throws IOException {
        if (len >= buf.length) {//如果要写的数据长度大于缓存中的数据则直接将要写的数据写到磁盘上(或是网络服务器上)
            /* If the request length exceeds the size of the output buffer,
               flush the output buffer and then write the data directly.
               In this way buffered streams will cascade harmlessly. */
            flushBuffer();//在写之前将上次缓存中的数据写入到磁盘上(或是网络服务器)
            out.write(b, off, len);
            return;
        }
        if (len > buf.length - count) {//如缓存的长度大与要写数组长度当剩余大小比它小
            flushBuffer();//先将缓存上数据写到目的地上
        }
        System.arraycopy(b, off, buf, count, len);//将要写的数据复制到缓存数组中
        count += len;
    }

 public synchronized void flush() throws IOException {
        flushBuffer();
        out.flush();
    }


特征:在基础流上增加一个缓冲区,真正处理数据的是基础流
      处理流有一个基础流的属性,处理流会调用基础流的read和write方法进行

注意:
BufferedOutputStream 默认大小8192(1024*8),它会先将读取的数据放入内置的缓冲,满了才写入到文件中
它并不是读一次写一次,你可以手动修改其缓存的大小。

PrintWriter:源码分析

protected Writer out;//这是个基础流
private final boolean autoFlush;//这是个标记来判断是否自动刷新
public PrintWriter(Writer out,
                       boolean autoFlush) {
        super(out);
        this.out = out;
        this.autoFlush = autoFlush;
        lineSeparator = java.security.AccessController.doPrivileged(
            new sun.security.action.GetPropertyAction("line.separator"));
  }

  public void println(boolean x) {//它先输出数据在接着输入一个换行符
        synchronized (lock) {
            print(x);
            println();
        }
    }

  public void println() {
        newLine();
    }

 private void newLine() {//它如果自动标记刷新为true会自动刷新
        try {
            synchronized (lock) {
                ensureOpen();
                out.write(lineSeparator);
                if (autoFlush)
                    out.flush();
            }
        }
        catch (InterruptedIOException x) {
            Thread.currentThread().interrupt();
        }
        catch (IOException x) {
            trouble = true;
        }
    }


内存流:字节数组
用途:
1.从网络上下载资源
2.完成对象的克隆

下面的流和其它的流有完全不同点,它不需要目标路径

管道:用来传输字节数组
ByteArrayInputStream//将数据读入到程序中进行使用(它一般和ByteArrayOutputStream)

源码分析
 
//这是用指向要访问的传人的字节数组
protected byte buf[];
//读取下一个数据的索引
protected int pos;
protected int mark = 0;
//缓存中数据的个数
protected int count;

public ByteArrayInputStream(byte buf[]) {
        this.buf = buf;
        this.pos = 0;
        this.count = buf.length;
    }

public ByteArrayInputStream(byte buf[], int offset, int length) {
        this.buf = buf;
        this.pos = offset;
        this.count = Math.min(offset + length, buf.length);
        this.mark = offset;
    }

public synchronized int read() {
        return (pos < count) ? (buf[pos++] & 0xff) : -1;
    }

public synchronized int read(byte b[], int off, int len) {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        }

        if (pos >= count) {
            return -1;
        }

        int avail = count - pos;
        if (len > avail) {
            len = avail;
        }
        if (len <= 0) {
            return 0;
        }
        System.arraycopy(buf, pos, b, off, len);
        pos += len;
        return len;
    }

 public synchronized int available() {
        return count - pos;
    }

  public void close() throws IOException {
    }
从上面可以知道ByteArrayInputStream 它内置的缓存byte[   ] 没有自己主动new 而是别人传入将自己指向它。它的作用是把byte[]中的数据包装来方便程序操作(ObjectInputStream)
它的close方法没做任何事情


        
ByteArrayOutputStream//将数据写入内存中ByteArrayOutStream中并存储起来
ByteArrayOutputStream是用来缓存数据的(数据写入的目标(output stream原义),向它的内部缓冲区写入数据,缓冲区自动增长,当写入完成时可以从中提取数据。由于这个原因,ByteArrayOutputStream常用于存储数据以用于一次写入。
 ---write("hello".getBytes());-----
 ----out.toByteArray()------------把ByteArrayOutStream放入到byte[] 数组中
注意: 它主要对内存进行读写,和路径没关系。主要起转化

分析其原码:
   protected byte buf[];//内置的缓存字节数组
   protected int count;//它用来记录每次存放时位置

     public ByteArrayOutputStream() {
        this(32);
    }

    public ByteArrayOutputStream(int size) {
        if (size < 0) {
            throw new IllegalArgumentException("Negative initial size: "
                                               + size);
        }
        buf = new byte[size];//这里真正的获取大小
    }

   public synchronized void write(int b) {
        ensureCapacity(count + 1);//这里主要是用来扩容
        buf[count] = (byte) b;
        count += 1;
    }
 public void close() throws IOException {
    }
ByteArrayOutputStream它的close方法没做任何事件




利用ByteArrayInputStream 和ByteArrayOutputStream实现指定字符替换
public class ByteArrayStreamTest {

    public static void main(String[] args) {
          byte[] data =   fillByteArray("nice");
        data =  changeData(data);
        writeFile("good.txt",data);
             
    }
    
//将文件中的数据放入内存中ByteArrayOutputStream中的缓存中
    public static byte[] fillByteArray(String path){
        try(
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path));
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                ){
                int len;
                byte[] bytes = new byte[1024];
                while((len=bis.read(bytes))!=-1){
                    bos.write(bytes, 0, len);
                }
                
                       //将ByteArrayOuputStream中的数据变成字节数组
                return bos.toByteArray();
            
        }catch(Exception e){
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    
    
    public static byte[] changeData(byte[] data){
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要替换的字符");
        char oldChar = scanner.next().charAt(0);
               //将字符强制转化为byte
        byte oldByte = (byte) oldChar;
        System.out.println("请输入要替换后的字符");
        char newChar = scanner.next().charAt(0);
              //将字符强制转化为byte
        byte newByte = (byte) newChar;
        for(int i=0;i<data.length;i++){
            if(data[i]==oldByte){
                data[i]=newByte;
            }
        }
        return data;
    }
    

    public static void writeFile(String path,byte[] data){
        try(
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));
                ByteArrayInputStream bis = new ByteArrayInputStream(data);
                ){
            int len;
            byte[] bytes = new byte[1024];
            while((len=bis.read(bytes))!=-1)
                bos.write(bytes,0,len);
        }catch(Exception e){
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}


ByteArrayOutputStream:补充点
只能在内存中操作,它的核心东西是byte[] buf。
如何从网络上下载资源:
从网络上下载一个图片,下载的数据保存到ByteArrayOutputStream再将内存中的自己数放入到硬盘上
new File(URI urI)//URL:统一资源定位符 作用:能够从网络上获取资源到本地计算机(可以把URL看成图书馆,百度logo可以看成图书馆里面的一本书,都是唯一的标识)
URL: 协议  ip或域名地址 端口号 路径 资源


URL url = new URL(String path);//创建URL对象
InputStream in = url.openStream();//获取链接的输入流(ULRconnction conn = url.openConnection(); conn.connect(); InputStream in = conn.getInputStream(); OuputStream out = con.getOutputStream();)
要尽可能把常量放入接口中
如果某个类只有静态方法没有成员方法将这个类声明为final


DataInputStream 和 DataOutputStream(处理流)
提供对基本类型读写
writeUTF
writeInt
writeLong
WriteCharater




ObjectInputStream 和 ObjectOutputStream(处理流)
提供对各种对象的读写
writeObject
这个方法它先将对象进行一定转换为字节然后调用基础流来写入转换后的字节数据

readObject
这个方法先用基础流来获取数据然后把这些字节用一定规则来创建一个对象并返回



序列化与反序列化
  序列化:使用new创建的对象---->字节数组---->写入到本地磁盘
          游离的状态------>持久化的状态

  反序列化:本地磁盘数据----->字节数组----->内存中的对象
          持久状态----->游离状态
用ObjectOuptStream 和ObjectInputStream 来实现序列化与反序列化
注意:只要参与序列化与反序列化必须实现Serializable(它是个哑巴接口,只是个标识)
并且最后添加private static final long serialVersionUID = 2270204972394858602L;这样属性是保证它的唯一性(全局的)
在属性上加上transient来保证该属性不会参加序列化和反序列化(它的值为默认初始值)
可以使用泛型来改造装饰writeObject和readObject
如果要序列化多个对象可以将它们放入一个数组或者是一个List中然后再进行反序列化


对象的克隆:
   对象之间做了一次深度拷贝,克隆出来的对象和源对象内存地址不同,但是克隆出来的新对象和源对象的数据完全相同。
   特征:在内存中完成对象的深拷贝
   ByteArrayOutputStream,ObjectOuputStream将对象转化为字节数组
   ByteArrayInputStream(out.toArray()),ObjectInputStream(in) 将字节数组读取,再产生新克隆的对象。

固定操作
   ByteArrayOutputStream bos = new ByteArrayOutputStream();
   ObjectOutputStream output = new ObjectOutputStream(bos);//先将程序中的数据存在内存中
    output.writeObject(....);
   ByteArrayInputStream bis = new ByteArrayInputStream(bos.toArray());
   ObjectInputStream input = new ObjectInputStream(bis);//从内存中获取并生成对象放入程序中
     input.readObject();
   

总结:
   FileInputStream/FileOutputStream 磁盘的路径进行读写
   ByteArrayInputStream/ByteArrayOutputStream 缓冲区将字节数组写入内存流,从内存流中读取数据
   BufferedInputStream/BufferedOutputStream 开启缓冲的功能,本身不能做任何事情,必须要有一个基础流,在基础流上加了一个字节数组作为缓冲区
   DataInputStream/DataOuputStream 读写基本类型
   ObjectInputStream/ObjectOutputStream 序列化,反序列化,对象克隆

输入流: 源:磁盘,网络服务器
         目的:程序(内存)


输出流:源: 程序(内存)
        目的:磁盘,网络服务器



字符流(操作的数据是字符时使用它效率高些)
BufferedReader
InputStreamReader
      FileReader

BufferedWriter
PrintWriter
OutputStreamWriter
   FileWriter

转换流:(读取字节流的数据转换为字符流,它是字节流和字符流的桥梁)
InputStreamReader
OuputStreamWriter

BufferedReader 每次读取8192个字符 缓冲的读者
当你每次掉用readLine()它不是每次读取磁盘而是从缓存总获取数据当你读到8192个字符时它才开始下一次读取磁盘(每次读取8192个字符数据)
readLine()的原理是读到换行(\r或\n)时它会结束本次读取并返回这次读取的数据
window下换行最好是\r\n

如何判断操作系统:
 System.getProperty("os.name").indexOf("Windows")>-1 为Window操作系统(System.getenv("os")这也是获取操作系统)
 
BufferedWriter 缓冲写者
newLine()写入换行符


源码分析:
BufferedReader://它使用的是装饰者模式(它效率高是因为它不是程序需要一个就从磁盘上读一个,它是先读一定数目的数据到内存的缓存数组中需要时是直接从这里获取)
   private Reader in;//这是基础流真正的读磁盘是它在进行
   private char cb[];//用来存放基础流读取出来的数据
   private int nChars, nextChar;//nChars用来记录缓冲字符中从磁盘上读取的个数,nextChar是用来存放读到某个换行符时在缓冲中的索引(对于readLine它的nextChar是这样,对于read它的nextChar是要读下一个字符)
   private static int defaultCharBufferSize = 8192;//它是缓冲char数组的默认大小
    public BufferedReader(Reader in, int sz) {
        super(in);
        if (sz <= 0)
            throw new IllegalArgumentException("Buffer size <= 0");
        this.in = in;
        cb = new char[sz];
        nextChar = nChars = 0;
    }

分析其read方法中有这句:
    for (;;) {
                if (nextChar >= nChars) {//如果索引值大于或等于缓存中数据的个数它会调用基础流来实现从磁盘上获取数据
                    fill();//这个方法是真正的从磁盘上获取数据,如果磁盘上数据足够大它会获取8192个字符数据到缓存数组中,如果磁盘上数据较小它会全都获取完
                    if (nextChar >= nChars)//这表示磁盘上已经没有数据了
                        return -1;
                }
下面还有这句
    if (skipLF) {
                    skipLF = false;
                    if (cb[nextChar] == '\n') {//可以知道如果从缓存中获取的数据是\n它会继续取下一个字符
                        nextChar++;
                        continue;
                    }
                }
                return cb[nextChar++];//否则就放回读取的数据

个人对readLine的理解:
  从上面我们可以知道它会读取8192数据到内存中字符缓冲数组中,它应有个标记来记录读取前的位置(beignIndex),再一个是读取到\n时的nextChar的数字  new String(beginIndex,nextChar-1); beginInex+=1;
每个操作系统的换行不同但它们的思路应该是这

BufferedWriter://它也是装饰者模式(它效率高是因为它不是程序需要写一个数据时就写一个到硬盘上,它是先将要写的数据放在内存中的缓存数组中,等这个数组装满了,或已经没数据要写它就会调用基础流把数组上的数据一次写到硬盘上)

    private Writer out;//这是个基础流
    private char cb[];//这是个缓冲字符数组它的大小在构造方法中创建大小为8192
    private int nChars, nextChar;//nChars用来记录存放了多少个数据,nextChar存放上次放入缓冲中的索引
    private static int defaultCharBufferSize = 8192;//它用来创建字符数组时设置容量用的

    public void write(int c) throws IOException {

        synchronized (lock) {
            ensureOpen();
            if (nextChar >= nChars)//如果缓存数组中已满则将数据执行真正的写到磁盘操作
                flushBuffer();
            cb[nextChar++] = (char) c;
        }
    }

 public void newLine() throws IOException {
        write(lineSeparator);
    }



不用Scanner实现读取控制台输入的信息:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try{
   String line = br.readLine();
   }catch(Exception e){}
可以使用apach-lang包中的NumberUtils.isDigist(...)来判断它是否是数字



枚举:
存储常量的集合。它是一个新的数据类型,可以在枚举中定义属性,方法,构造方法,但是枚举中的构造方法的访问权限只能是private 和friendly(枚举中不能有公有的和保护的构造方法)
使用class定义类,使用interface定义接口,使用enum定义枚举

访问修饰符 enum <枚举名称>{   }
1.使用枚举定义常量的集合
2.所有的枚举最好以E开头方便别人理解
3,每个元素使用逗号分隔,最后一个元素使用分号结束(只有一个元素也要加分号)枚举中元素和常量一样所有字母必须大写,如果有多个单词用下划线分隔

好处:
1.可以去掉常量中多余的代码


枚举常用方法:
public enum ESeason{
     SPRING,//它和SPRING()等价,把它们理解为对象
     SUMMER,
     AUTUMN,
     WINDER;

public static ESeason from(int index){
    for(ESeason season:ESeason.values())
         if(index==season.ordinal())
            return season;
     return null;
 }


}

ESeason spring = ESeason.SPRING;
int index = spring.ordinal();//返回枚举元素对应的索引下标
String name = spring.name();//返回枚举元素定义的名称
ESeason.values();//返回枚举中的所有元素的值(能够让外界访问枚举中的元素)

switch语句中可以放枚举,它的case中只能放入枚举中定义的常量,它提供编译时期的检查
在switch枚举之前要先判断是否为空,如果为空会发生空指针异常
例如:
  ESeason season = ESeason.from(number);
  switch(sease){
    case SPING:
    break;
    case SUMMER:
    break;
    case AUTUMN:
    break;
    case WINTER:
    break;
   }
枚举中整形最好用byte来定义节约空间
枚举的元素不要超过64个,一旦超过64过程序的性能会下降很多

枚举中可以定义属性,成员方法,私有构造,静态方法,枚举还能定义抽象方法
public enum ESeason{
  SPRING((byte)0,"chutian"),
  SUMMER((byte)1,"xiatian"),
  AUTUMN((byte)2,"qiutian"),
  WINTER((byte)3,"dongtian");
  private byte value;
  private String seasonName;
  private ESeason(byte value,String seasonName){
        this.value = value;
        this.seasonName = seasonName;
   }
  public byte getValue(){
       return value;
   }
  public String getSeasonName(){
       return seasonName;
  }
}
注意在枚举中它的ordinal()获取的数字默认是从0往下排

枚举中定义抽象方法:(一定义抽象方法,必须在枚举中的元素实现该抽象方法)
public enum ETree{
  ONE{
    @Override
   public void method(){.....}
   },
  TWO{//枚举中的元素
   @Override
   public void method{....}
   };
   public abstract method();//定义的抽象方法
}
 
利用枚举来实现多态工厂
interface Shapes{
  public void draw();
}

enum EShapesFactory{
 CIRCLE{
    public Shapes create(){
      return new Circle();
   }
  },
 TRIANGULAR{
    public Shapes create(){
     return new Triangular();
  }
 };
   public abstract Shapes create();
 
   public static EShapesFactory from(int index){
      for(EShapes factory:EShapesFactory.values())
            if(factory.ordinal()==index)
             return factory;
         return null;
     }
 }

使用枚举:来实现工厂
好处:封装性更高,能够隐藏创建细节,避免硬编码
不足:对扩展开放,对修改关闭做的不够好


多线程:

Thread源码分析:
   private volatile char  name[];//这是用来存储你命名的名字
   private int            priority;//它用来存放优先级
   private Runnable target;//它用来存放你传人的Runnable实现对象
   public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
   public void run() {//当Thread类没有被你继承时你调用start()方法线程启动会回调这个方法,它其实就是会调用你实现Runnable中的run方法
        if (target != null) {
            target.run();
        }
    }
    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         *
         * A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called Object.wait()
         * on an object is waiting for another thread to call
         * Object.notify() or Object.notifyAll() on
         * that object. A thread that has called <tt>Thread.join()
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

分析源码可知道:
线程的优先级只有3中1,5,10,默认是5使用优先级时最后使用Thread.MAX_PRIORITY
误区:
优先级只能说明它获得的cpu执行器的概率高并不能说明它一定会比优先级低的先获得cpu

线程在cpu中执行的过程:
   一个任务它不能单独运行,它需要线程中执行。cpu会从就绪队列中随机的取出一个线程放入里面执行。当这个线程的时间片用完后,如果它的run方法还未执行完,cpu会记录这次执行的位置并保存到这个线程程序计数器中。然后把它放入到就绪队列中,cpu再从就绪队列中随机的取出一个线程对象。在回调它的run方法运行之前它会先判断是否记录了上次执行位置如果有就接着上次执行,没有就从run的第一就开始执行...依照这个步骤直到cpu执行完所有就绪队列中的线程

sleep方法的实现:
  它的内部应该有个类似时间器它会获取当前时间,它中应该还有个记录第一次进入的时间。如果当前时间-第一次时间==你传人的时间,就跳出sleep方法,否则继续执行sleep,它应也是抢占cpu的方式
   当sleep方法加入到某个线程对象中的run方法中时,当这个线程获得cpu给的时间片时如果sleep没执行完就执行sleep方法,执行完就接着执行下面语句。


程序计数器:
程序计数器(program counter register)只占用了一块比较小的内存空间,至于小到什么程度呢,这样说吧,有时可以忽略不计的。

作用
可以看作是当前线程所执行的字节码文件(class)的行号指示器。在虚拟机的世界中,字节码解释器就是通过改变计数器的值来选取下一条执行的字节码指令,分支、循环、跳转、异常处理、线程恢复都需要这玩意来实现的。

特性
因为处理器在一个确定是时刻只会执行一个线程中的指令,线程切换后,是通过计数器来记录执行痕迹的,因而可以看出,程序计数器是每个线程私有的。
如果执行的是java方法,那么记录的是正在执行的虚拟机字节码指令的地址的地址,如果是native方法,计数器的值为空(undefined)。
这个内存区域是唯一一个在java虚拟界规范中没有规定任何OutOfMemoryError的情况的区域。至于为什么没有这个异常呢,要是一个计数的功能在出这个异常。

在使用多线程时最好使用实现Runnable接口,如果必须继承Thread记住只能复写runnable方法,不然会出现很多想不到的方法



size++
1 内存----->程序计数器
2 程序计算器对变量进行+1
3 程序计算器将结果---->内存

size++
first: tmp = size;
second: tmp+=1;
third: size = tmp;

临界区:一个资源只能被一个线程同时访问
线程在访问临界区的代码之前,临界区会做一个检查,共享资源是否已经被其它线程访问了。如果已经有线程进入(访问)临界区的共享资源,该线程只能在临界区外面挂起,直到其它线程访问完毕,该线程才能进入临界区访问共享资源。
如果有多个线程在临界区挂起,jvm随机选则一个被挂起的线程进入


synchronized
同步 临界区
被synchronized修饰的代码块,可以看作临界区的代码。

同步:为了保证共享资源的一致性与有效性,某个资源在被多个线程访问之前,它的数据都是一致。

线程之间的协作
1.生成者与消费者


lock写时的规范://注意一定要养成这个好习惯
lock.lock();
try{
   }finally{
  lock.unLock();


finally执行完在执行return
wait,await方法。我猜想是它先释放掉自己持有的锁,然后一直等待被别人唤醒。唤醒后它又会取和其它线程争取锁

使用情景:
用户量较小时使用synchronized关键字
用户量较大时使用lock自己来实现加锁和解锁


读写锁:ReentranReadWriteLock
读操作并行,写操作串行
公平读写锁:那个线程等待时间最长,就让它优先进入共享资源;(它的效率没有非公平锁高,因为它要先去判断那个线程等待时间是最长的)


线程池:只在项目启动时创建,项目结束时销毁
new ThreadPoolExecutor(50,100,0L,TimeUnit.MILISECONDS,new LinkedBlockingQueue<>());
corePoolSize: 线程池维护线程的最少数量
 
maximumPoolSize:线程池维护线程的最大数量
 
keepAliveTime: 线程池维护线程所允许的空闲时间
 
unit: 线程池维护线程所允许的空闲时间的单位
 
workQueue: 线程池所使用的缓冲队列
1 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
 
l  如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
 
l  如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
 
l  如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
 
l  当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

使用线程池:
1 任务
2 队列:职责将任务注入到队列
3 线程:取队列中的任务来进行执行


使用线程池:Executors
  //获取固定大小的线程池(优点稳定缺点销毁的资源大,核心数不应超过500)
  public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  //获取大小不固定大小线程池(优点:开销小缺点:不稳定,容易发生内存溢出,适用于小型参数)
  public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());//它使用无缓冲队列(不能大于1)
    }
  //获取一个可重用的单个线程池
  public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

规范:
 使用Callable接口类名以Job结尾,使用Runnable接口类名以Task结尾

可以把线程池中的创造放入枚举中



网络通信:
节点(主机)NetWorkElement简称NE
定义:网络是遵循一定的规则进行通信
分类:传输网络(PTN) 和 接入网络(Epon)

网络7层:
应用层:
表示层:
会话层(Session):
传输层(TCP):
网际层(IP):
数据链路层:
物理层:

协议:主机之间按照一定的规则进行数据共享和数据交换
HTTP协议:
FTP协议 (文件传输协议):
SMTP简单邮件通信协议:
TCP/IP传输控制协议/网际协议 (以流为传输介质进行通信):

端口:主机下面某个进程的编号
IP地址:网络上识别主机的一个ID
127.0.0.1:回环IP地址


客户端向服务器发送数据是以包的形式(数据帧)
分为:
    包头:rmt+1024
    包体(有效载荷)

客户端和服务端的通信分为两种:
1,全双工  双向通信(客户端发送请求,服务器收到请求进行逻辑处理最后将相应的结果返回给客户端)
2,半双工  单向通信(客户端发送请求,服务器收到请求)注意:服务器只结受请求不会将结果发送给客户端

TCP套接字
  ServerSocket
  1.创建对象
  2.调用accept方法监听客户端链接,此方法为阻塞式
  3.链接上accept会返回一个客户端对象
  4. 打开客户端的输入输出流
  5. 服务器处理请求
  6. 关闭ServerSockt对象

  Socket
  1.创建对象//Socket(String host, int port)
  2.调用它上面的connect方法这是个阻塞方法//connect(SocketAddress endpoint, int timeout) InetSocketAddress(String hostname, int port)
  3. 从对象上获取输入输出流进行处理
  4. 关闭对象

Socket通信:同一主机不同进程通信
            不同主机不同进程通信



java中TCP/IP通信 封装为Socket套接字


数据库
Sqlite
如何数据库的本质:数据的仓库-----存放数据的(它会存放到磁盘上)
常用数据库:Access SQLService DB2 Oracle Mysql
E-R图:实体关系图
什么叫实体:
 java中用类来描述实体,Sqlite中使用表来描述实体

数据库是一个仓库,以表为单位来存放数据
表由行列存成
 行:数据记录
 列: 字段

Sqlite的数据库是弱类型的语言
number ,int,integer表示整型
real(浮点型总称),float,double , money 表示浮点型
text /char 表示字符型
data 日期
boolean 真假

语法:
create table <表名称>( <字段名称> 数据类型, <字段名称> 数据类型);
注意:最后一个字段名称不加逗号。
Sqlite中 -- 表示行注释
.schema <表名称> 查看表的结构
.tables 显示数据库中所有表名

数据库不区分大小写,某个字段如果由多个单词组成每个单词之间使用下划线分隔
select 是对表(或临时表)进行列过滤
where 是进行记录过滤
它们进行完操作如果是中间步骤都会产生临时表
判断不等于使用 <> 的效率比 != 的效率高很多

数据库中的逻辑运算符
在数据库中and表示java中&&
在数据库中or 表示java中||
在数据库中not表示java中!
in在什么里面(集合查询)in(number1,number2,number3)它很销毁性能里面的数据最好不要超过10个

delete只是删除表中的数据不会删去表结构,drop删去了表的结构


注意:在操作表结构时语句中会出现table操作字段时不会出现这个单词


视图:它是个虚表只能查看不能修改表结构
特征:跟表结构一样
create view view_emp as select ...,... from ...
语法:create view <视图名称> as <select 查询语句>;
好处:保证你表结构不会被破坏

索引:如果把某张表看成一本书,索引可以理解为书上的目录
好处:速度快
语法:create index <索引名称> on <表名称>(要建的字段名称,列名称);
解释:在某个表上以某个字段来创建一个索引 on在那个表上 (列名)在那一列上
当你在查询表示你会自动隐式调用

哪些列需要建立索引:
  where后面跟的条件最好建立索引

备份:
create table <表名> as select 字段1,字段2 ..... from <原来表的名称>

常用的sql语句:
between and
distinct
like(后面一定是单引号包裹内容)
  |前面精确后面模糊:'aaa%'
  |前面模糊后面精确:'%aaa'
  |前面模糊后面模糊:'%aa%'
%,_:它们的区别是 %不确定个数也不确定内容(可能0个或多个)  _确定个数但不确定内容
模糊查询效率低,表中数据超过5000跳记录建议不要使用模糊查询

表别名:表的小名称   列别名:列的小名称
聚合函数:
count
avg
max
min
sum

*号的使用不能随便用在统计时尽量使用count(1) 它表示获取某一列的总数

如何设计表:
先建立实体表再建立关系表,先删去关系表再删去实体表
实体表为主表,关系表为从表

约束:
 主键约束
   主键:主要的关键字,能保证该条记录在表中的唯一性
   primary key(<字段1>,<字段2>,....);

 外键约束
    两张表之间的约束,确保数据的完整性,也叫外部关键字
   foreign key(<字段名称>) references <表名>(列名)

 检查约束
    检查某个字段的有效性
 唯一约束
    和主键约束类似,不同地方是主键约束不能为空,唯一约束可以为空
  unique

 非空约束
  not null

 自定义约束
目的:保证表中数据的有效性,完整性和一致性

多表联合查询
必须明确一个主表,将其放在左边(左链接将最左边表设为主表)
左外链接:以最左边的表为主表来链接其它表


分组:
group by(它必须放在where的下面,必须和聚合函数一起使用,后面跟的字段除了聚合函数任意字段)
order by它是进行排序desc 降序 asc升序

drop table if exists 表名称
注意:sqlite中的自动增长只能是integer 类型 而且为autoincrement


HTML
<html>
 <head></head>
  <body></body>
</html>

<h1>最大标题标签
<h6>最小标题标签
<b>字体加粗标签
<p>段落标签
<l>斜体
<u>下划线
<img>图片标签(使用相对路径)
<img src="img/may.log" alt="我的图片"/>
<a href="">...</a>由一个页面跳转到另外一个页面(尽量写相对路径)
<table>表格
   <form name="..." action="..." method="get/post">//action:表示你提交到那个页面处理
          <tr> <td></td> <td></td>... </tr>//tr是表格中的行td是表格中的列
           ........
   </form>
</table>
get与post的区别?
get提交它的所有参数都会在地址栏上(状态栏),post把参数放入到内容体中。
get主要用于查询读,post主要是查询写


Servlet
使用java开发的服务器端应用程序,它运行在web服务器上
作用:处理客户端的请求

MVC设计模式:
M model java类(进行数据处理)
V view  视图(html,jsp)
C control 控制器 Servlet(分发数据进行跳转)

接口   Servlet
          |
抽象类 GenericServlet
          |
抽象类 HttpServlet

request里面封装了所有客户端请求信息
response里面封装了所有发送给客户的的信息

上下文:上tomcat服务器下你写的程序。如request.getContextPath()它获取的是链接在服务器和你写的servlet的path之间的项目(如/day22)

Servlet执行它先是构造函数---->再是init方法


JDBC(javaEE)它是一门技术用来链接应用程序和数据库
1. 创建数据库并添加数据
2. 建立驱动链接
3. 声明SQL(结构化查询语句)
    包括 select,insert,create,update,delete, drop,
4. 执行查询语句
5. 返回结果


sqlite轻量级数据库使用:
要导入:sqlite-jdbc-3.7.2.jar
再在本项目中创建一个文件夹 如sqlite ---my.db
配置如下:
Class.forName("org.sqlite.JDBC");注册驱动
Connection conn = DriverManager.getConnection("jdbc:sqlite:sqlite\\my.db");
里面文字含义jdbc:sqlite:+数据库所在文件夹+"\\"+数据库(注意我的数据库文件夹就在项目下)
解决操作系统差异:DriverManager.getConnection("jdbc:sqlite:sqlite(文件夹名称)"+File.separator+"my.db");



在javaee项目中使用Sqlite数据库
如果你的数据库文件放入在webContent下它的路径获取是
getServletContext().realPath(File.separator);它获取的路径是项目在tomact上部署的路径的工程下(.....\\day12\\)
  它是上下文路径(项目路径)不清楚可以自己打断点查看路径


注解:元数据(它是给程序看的)
特点:解释你的程序,说明你的程序
 源注解(描述注解的注解)Target,Rentention
 自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {//它说明你的定义的注解可以出现的地方
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     */
    ElementType[] value();//它可以定义多个
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {//注解保留的阶段
    /**
     * Returns the retention policy.
     */
    RetentionPolicy value();//它只能定义一个
}

public enum ElementType {//它是一个枚举可以描述注解的使用范围
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,//只在源代码有效,执行javac编译之后该注解就会消失

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,//在源代码中有效经过编译以后产生class文件后仍然有效,但在程序运行时该注解就失去作用

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME//它在程序运行期有效,该保留策略和反射一起使用,功能比较强大
}
注解是jdk5.0引入的概念

语法:
@Target(ElementType.)
@Rentention(RententionPolicy.)
public @interface MYAnnotion{
}


使用情况:
但你确定和反射配合使用时:保用策略为RUNTIME
但你只是对程序进行检查:保用策略用SOURCE

在注解中定义方法:
注解的方法中的返回值只能8中基本类型和String还有枚举
注解中定义的方法不能有大括号必须以分号结束

@Target({ElementType.Type,ElementType.FIELD})
@Rentention(RententionPolicy.RUNTIME)
public @interface MYAnnotion{
    public boolean readOnly() default false;
    public String name() default "";
}

MYAnnotion(readOnly=true,name="student")
public class MyClass {
}


反射:

当一个类需要使用的时候才加载,jvm
同一类的所有对象公用同一个Class对象

获取Class对象的方式:
1. Class.forName(".....")
2. 类.class(String.class)
2. 对象.getClass()(“hello”.getClass())
需要加载的类加载失败会抛出ClassNotFoundException
常用方法:
isInterface()
isPrimitive()
getSuperClass()
getGenericSuperclass() 获取泛型父类

利用反射获取构造方法来生成对象
 Constructor<T> getConstructor(Class<?>... parameterTypes)  
而Constructor上有个方法
 T newInstance(Object... initargs)

获取包信息
getPackage() Package
获取类信息
getSimpleName() String(获取类名)
getName() String (包名+类名)
getModifiers() int (获取修饰符)
Modifiers.toString(int )
获取字段信息:
getDeclaredFields()
getType()

获取构造方法
getDeclaredConstruction()
getParameters() Parameter

Method
geteReturnType() Class
getName() String 获取方法名称


代理:
  |
   __静态代理
  |
   —动态代理
接口主题
真实主题
代理主题

你使用的对象不要由你来创建,由客户创建
客户创建的对象有两种
方式:
1 提供构造方法让客户注入(构造注入)
2 提供set方法让客户注入对象(set注入也叫属性注入)

动态代理:
为什使用动态代理?
静态代理每当产生新的代理类必须就自己定义新的Class,灵活性和弹性很低。
定义:在程序运行期间通过反射动态产生代理类(在编译期看不到代理类),然后动态调用方法。
好处:无需手动创建,JVM为你自动生成代理类

1.需要自己定义类去实现 InvocationHandler
2. 使用Proxy.newProxyInstance创建代理类
3. 动态调用代理类的方法

动态代理产生出来的对象和Proxy之间的关系

public  代理出来类 extends Proxy implements 要代理的接口{
}

invoke如果与掉联系在一起

源码:

 public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{
        //将要代理类上的接口克隆
        final Class<?>[] intfs = interfaces.clone();
        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);//获取动态代理Class(加载到内存)
        final Constructor<?> cons = cl.getConstructor(constructorParams);//获取动态代理上的构造方法
        final InvocationHandler ih = h;
        return cons.newInstance(new Object[]{h});//调用上构造方法(注入要代理的方法)  
    }

动态代理的结构
  public $Proxy0 extends Proxy implements 要代理的接口们
它中的属性:
Method m0,Method m1,Method m2,Method m3
m0 = $Proxy.getClass().getDeclaredMethod("kiss");
m4 = $Proxy.getClass().getDeclaredMethod("add",int.class,int.class);
public void kiss(){
    super.h.invoke(this,m0,null);
}


观察者模式(一呼百应)也叫反应堆模式,发布订阅模式
目的:一呼(被观察者)百应(观察者)
前提:观察者需要向被观察者注册
注册才是核心