1 Star 2 Fork 0

王泽熙/java学习笔记_markdown

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MulanPSL-2.0

数据类型

类型转换

八种基本数据类型:byte(1),short(2),int(4),long(8),float(4),double(8),boolean(1),char(2)

基本数据类型默认值

数据类型 默认值
byte 0
short 0
int 0
long 0L
float 0.0F
double 0.0
boolean false
char \u0000
引用数据类型 null

八种数据类型大小排序:byte<short(32767)<int(2147483647)<long<float<double

​ char(虽然char和short占用的字节数相同,但是char可以取到更大的正整数)

==除了boolean类型外其他其中都可以进行类型转换==

小容量--->大容量 ==自动类型转换==

大容量--->小容量 ==强制类型转换== 需要加入强制类型转换符,虽然编译可以通过,但是运行可能会损失精度

注:

  1. 当整数字面值没有超过byte short char的取值范围,可以直接赋值

    long g=10;
    byte h=(byte)(int)g/3;//虽然表达式右边的数值没有超过byte的范围,但是编译阶段过不去,因为编译阶段只看语法不做运					 //算,如果这里直接赋值一个3是可以编译通过的
    
  2. byte short char混合运算的时候,都先转换成int之后再进行运算

  3. 多种数据类型混合运算,先转成大容量数据类型再进行计算

    ==在一个域中变量名不能重名==

    double dd=10/3; //10和3都是int,取整得3,之后自动类型转换成double得到3.0
    dd=10.0/3;  	//3先转换成double,之后计算得到的就是double,结果为3.33333333
    

    ==0的ascii是48,a是97,A是65==

运算符

算数运算符

关系运算符

< = > 关系运算符的结果都是boolean类型

逻辑运算符

& 逻辑与 ! 逻辑非 |逻辑或 ^逻辑异或(==两边算子不一样就是true==)

&&短路与 ||短路或 ==逻辑运算符要求两边都是boolean类型,并且最终结果也是boolean类型==

==&&和& 的区别==:运算结果相同,只不过短路与存在短路现象

int x=10;
int y=8;
System.out.println(x<y & ++x<y);//x<y即是false,所以后边并没有执行的必要
System.out.println(x);   	    //x=11,说明&后边的语句执行了

====================================================================
int x=10;
int y=8;
System.out.println(x<y && ++x<y);//x<y即是false,所以后边并没有执行的必要
System.out.println(x);   	    //x=10,说明&&后边的语句没有执行,直接短路了

==||和| 的区别==:运算结果相同,只不过短路或存在短路现象

赋值类运算符

= += *= /= %=

字符串连接运算符

三元运算符

位运算符==之后讲==

控制语句

switch

switch的语法结构

switch(int或String类型的字面值或者变量){
    case int或String类型的字面值或者变量:
		java语句;
         break;  //如果这里的break没有,就会存在case穿透现象,也就是下一个case内容一定会执行
    case int或String类型的字面值或者变量:
		java语句;
         break;
	...
    default:
		java语句;
		...
}  

**注意:**byte short char可以放在==switch和case==后边,因为存在自动类型转换 ==java6之前只能用int,String都不能用,switch也支持枚举==

//case可以合并
int i=3;
switch(i){
    case 1:case 2:case 3:case 10:   	
        System.out.println("Test Code!")
}
//java输入
java.util.Scanner s=new java.util.Scanner(System.in);
int num =s.nextInt();//next()是输入字符串
=======================
InputStreamRead is=new InputStreamRead(System.in);
BufferedRead br=new BufferedRead(is);

swich的简单应用(简单计算器)

public class SimpleComputer {
    public static void main(String[] args) {
        java.util.Scanner scanner=new java.util.Scanner(System.in);
        System.out.println("欢迎使用简单计算器");
        System.out.println("请输入你的第一个数字");
        int a=scanner.nextInt();
        System.out.println("请输入你的运算符");
        String fuhao=scanner.next();
        System.out.println("请输入你的第二个数字");
        int b=scanner.nextInt();
        switch (fuhao){
            case "+":
                System.out.println(a+"+"+b+"="+(a+b));
                break;
            case "-":
                System.out.println(a+"-"+b+"="+(a-b));
                break;
            case "*":
                System.out.println(a+"*"+b+"="+(a*b));
                break;
            case "/":
                System.out.println(a+"/"+b+"="+(a/b));
                break;
            case "%":
                System.out.println(a+"%"+b+"="+(a%b));
                break;
        }
    }
}

方法

方法执行内存分析

==JVM== :栈 堆 方法区

栈内存主要存储==局部变量==

方法重载(overload)

  1. 什么时候考虑使用方法重载?

    功能相似

  2. 什么条件满足之后构成方法重载?

    • 在一个类中
    • 方法名相同
    • 参数列表不同
      • 数量不同
      • 顺序不同
      • 类型不同
    //方法重载的print应用
    Class Test{
        public static void main(String[] args){
            U.p(1);
            U.p(flase);
            U.p("1");
        }
    }
    Class U{
        public static void p(byte b){
            System.out.println(b);
        }
        public static void p(short b){
            System.out.println(b);
        }
        public static void p(int b){
            System.out.println(b);
        }
        public static void p(long b){
            System.out.println(b);
        }
        public static void p(float b){
            System.out.println(b);
        }    
        public static void p(double b){
            System.out.println(b);
        }
        public static void p(boolean b){
            System.out.println(b);
        }
        public static void p(char b){
            System.out.println(b);
        }
    }
    

方法递归

递归很耗费内存,可以不用的时候尽量不用,

当递归一直未结束(没有结束条件),会出现以下错误

  • ==java.lang.StackOverflowError== 栈内存溢出错误,这是个错误不是异常,错误的结果就是JVM停止工作
//java递归算1~N的和
public class Test{
    public static void main(String[] args){
        sum(N);
    }
    public static int sum(int N){
       if(N==1) return 1
       return N+sum(N-1);
    }
}
//java递归算1~N的阶乘
public class Test{
    public static void main(String[] args){
        sum(N);
    }
    public static int mut(int N){
       if(N==1) return 1
       return N*mut(N-1);
    }
}

面向对象

对象和引用

对象是new出来的,存储在堆内存中

对象的地址,给的那个变量称为引用,引用可以是局部变量,也可以是成员变量,存储在栈内存中

==空引用在读取对象的时候会出现空指针异常==

this

this是一个关键字,是一个引用,this变量保存的内存地址是指向自身的

每个对象都有一个this

//this是一个引用
class Customer{
    String name;
    public Customer(){}
    public void shopping(){
        sout(this.name+"正在购物!!!");
    }
}
public class Test{
    psvm(){
        String name="张三";
        Customer a=new Customer();
        a.name=name;
        a.shopping();
    }
}

==注意:==this不能出现在静态方法当中,因为静态方法不涉及引用,==即:实例变量只能在实例方法中使用(除非new对象)==

==带有static的方法不能直接调用实例方法实例变量,因为实例方法和实例变量都需要对象==

this大部分情况下都能省略,什么时候不能省略呢?

//构造方法或者get set方法
public Student{
    private num;
    private name;
    public Student(String name,int num){
        num=num;   //因为有就近原则,这里的num是局部变量,和实例变量没关系,所以这里要使用this
        name=name;
    }
}

this除了可以用在实例方法中,还可以用在构造方法中 :(==this(实际参数列表)==)

==注意==:this只能出现在构造方法的第一行,并且只能写一行,如果出现在第一行,就会出现错误

//构造方法重复代码
class Date{
    private int year;
    private int month;
    private int day;
    public Date(){
        /* 所以以下代码可以改写成
        this.year=1700;
        this.month=12;
        this.day=1;
        */
        this(1700,12,1);
    }
    public Date(int year,int month,int day){
        this.year=year;
        this.month=month;
        this.day=day;
    }
}

super

复习this

  • this只能出现在实例方法和构造方法中
  • this的语法是 this. this()
  • this不能出现在静态方法中
  • this. 大部分情况下是可以省略的
  • this. 在区分局部变量和实例变量的时候不能省略
  • this() 只能出现在构造方法的第一行,通过当前构造方法去调用"本类"中的其他构造方法,目的是 代码复用

super:==super不是一个引用,super也不保存内存地址,super也不指向任何对象,super只是代表当前对象内部的那一块父类型的特征== 所以单独使用this可以,但是直接使用super会出问题,super后边必须跟.或者()

  • super只能出现在实例方法和构造方法中
  • super的语法是 super. super()
  • super不能出现在静态方法中
  • super. 大部分情况下是可以省略的
  • super. 子类中有和父类同名的属性,并且希望在子类中使用父类的属性,那么就是要super不能省略
  • super() 只能出现在构造方法的第一行,通过当前构造方法去调用"父类"中的其他构造方法,目的是 代码复用
  • super后边不仅可以跟属性也可以跟方法
public class Test{
    psvm(){
        new B();
    }
}
class A{
    public A(){
        sout("调用A类的无参数构造方法");
    }
}
class B{
    public B(){
        super();   //这一行是隐形的  所以子类有的构造方法,父类必须有相对应参数的构造方法与之对应,不然super()方法会报错
        sout("调用B类的无参数构造方法");
    }
}
//结果是    调用A类的无参数构造方法   调用B类的无参数构造方法

super() 对应的现实中就是,要有儿子必须先要有老子

==关于this和super题目==

public class Test{
    public static void main(String[] args){
        new C();
    }
}
class A{
    public A(){
        System.out.println("A的无参数构造方法");1
    }
}
class B extends A{
    public B(){
        System.out.println("B的无参数构造方法");2
    }
    public B(String name){
        System.out.println("B的有参数构造方法(String)");3
    }
}
class C extends B{
    public C(){
	    this(name);
        System.out.println("C的无参数构造方法");4
    }
    public C(String name){
        this(name,age);
        System.out.println("C的有参数构造方法(String)");5
    }  
    public C(String name,int age){
        super(name);
        System.out.println("C的有参数构造方法(String ,age)");6
    }  
}
//输出顺序是  13654

父类中的private属性 只能在本类中使用

比如子类的构造方法需要访问父类的private属性的时候,就需要使用super()调用父类的有参数构造方法

public class Test{
    public static void main(String[] args){
    	Vip a=new Vip("张三");
    	a.shopping();   
		System.out.println(a.name);
    }
}
class Customer{
    public String name;
    public Customer(){}
    public Customer(String name){
        super();
        this.name=name;
    }
}
class Vip extends Customer{
    //private String name;   //如果加上这一行代码   结果this 是null super是张三 没有this 和super 默认是this
    public Vip(){}
    public Vip(String name){
        super(name);
    }
    public void shopping(){
        System.out.println(this.name+"正在购物");
        System.out.println(super.name+"正在购物");
        System.out.println(name+"正在购物");
    }
}

静态代码块

static{
	java语句;
	java语句;
	....
}

类加载的时候执行,并且只执行一次,作用:给程序员一个时机--类加载时机

==其他代码块==

基本代码块:

​ 在main方法中 直接{ java语句 }使用 按照上下顺序进行运行

构造代码块:

​ 在对应的class中, 直接{ java语句 }使用 按顺序运行,之后才会调用构造方法

继承

继承的缺点:耦合度太高

子类继承父类,相当于把父类的代码复制了一份,只是不在子类中显示罢了

实际开发中什么时候用extends呢?

  • 猫是一个动物
  • 信用卡账户是一个银行账户
  • 白菜是一种蔬菜

以上这种 是 或者 is 都是可以进行继承的突破口

object类

==面向对象编程:OOA(面向对象分析),OOD(面向对象设计),OOP(面向对象编程)==

==封装的作用:==

  1. ==保证内部结构的安全==
  2. ==屏蔽复杂,暴漏简单==

java源码位置:==jdk/lib/src/java.base/java/==

object类中的方法:

  • toString()

    • 直接对未重写toString方法的对象执行.toString得到的结果 xxxx@yyyy 这里的yyyy对应的是该对象所在内存地址经过“哈希算法”得到的十六进制结果,xxxx是对象的类型

    • 如果直接sout对应的引用,那么java会默认调用这个引用的toString方法

    • public String toString() {
              return getClass().getName() + "@" + Integer.toHexString(hashCode());
       }//源代码
      
  • equals()==之后再讲==

     public boolean equals(Object obj) {
            return (this == obj);
        }//equals方法的源代码
    
    //重写equals方法
    public boolean equals(Object obj){
        if(obj==null) return false;
        if(obj==this) return true;
        if(!(obj instanceof MyTime)) return false;
        MyTime m=(MyTime)obj;
        if(this.year==m.year&&this.month==m.month&&this.day==m.day) return true;
    }
    

    java中所有的==基本数据类型==比较是否相等使用==

    java中所有的==引用数据类型==比较是否相等使用equals方法

  • clone()

  • hashcode

  • finalize()

    //Object中的源码
    protected void finalize() throws Throwable{}
    //这个方法不需要程序员自己来调用,JVM的垃圾回收器负责调用
    

    ==finalize方法的执行时机==:当一个对象即将被垃圾回收器(GC)回收的时候,垃圾回收器负责调用finalized方法

    ==finalize方法的作用==:对象销毁的时机叫做垃圾销毁时机,如果希望在对象销毁时机执行一段代码,就需要讲代码写入finalize方法中

    ==静态代码块是类加载时机==

    ==finalize是垃圾回收时机==

    当给对应的对象赋值null的时候就相当于给它回收了

    建议启动垃圾回收器 System.gc();

==注意==:在源码中,如果一个方法是以“;”结尾的,并且修饰符列表有“native”关键字,那么其底层是调用了c++写得dll(动态链接库)文件

String

String重写toString和equals方法

两个字符串进行比较直接使用String重写的==equals==方法即可进行比较

String类的==toString==方法直接输出对应的字符串

方法覆盖

什么时候使用方法覆盖呢?

​ 子类继承父类之后,当继承过来的方法无法满足子类的业务需求的时候,需要考虑重写

方法重写=方法覆盖

override、overwrite != overload(方法重载)

当子类对父类的方法进行重写的时候,父类的方法仍然在子类当中,不过继承过来的方法已经被标记了,所以JVM只认识我们程序员自己写得重写之后的方法,不会调用已经标记的继承过来的父类的方法

==方法覆盖必须:方法名相同,参数列表相同,相同的返回值类型==

方法覆盖注意事项:

  • ==继承中,对于方法的覆盖,访问权限只能越来越高,不能越来越低 并且 重写之后的方法不能比之前的方法抛出更多的异常==
  • ==方法覆盖只针对方法,和属无关==
  • ==私有方法无法覆盖==
  • ==构造方法不能继承,所以也不能覆盖==
  • ==静态方法覆盖没有意义==

多态

向上转型(子转父)和向下转型(父转子)的前提都是 ==要有继承关系== ==编译过程不会new对象==

//多态
Animal a=new Cat();
a.move();
//编译阶段:只知道a是一个Animal,并且去Animal.class中找到了move方法,所以编译通过(绑定父类方法)
//运行阶段:a指向自己的子类对象,运行子类对象的move方法(动态绑定子类方法)

什么时候要向下转型 当你要访问的是子类中特有的方法的时候需要向下转型

为什么要使用instanceof关键词(运行阶段动态判断) 因为向下转型有风险 比如如下代码

Animal a=new Bird();
Cat b=(Cat) a;
b.catchMouse();
//运行错误

instanceof的结果是boolean类型 用法 引用 instanceof 对象

多态在开发中的作用是:

==降低程序的耦合度,提高扩展力==

final

final是java语言中的一个关键字

final标志最终的不可变的

final可以修饰变量、方法还有类

  • final修饰的方法无法被覆盖

  • final修饰的类无法被继承

  • final修饰的变量一旦赋值之后不能重新赋值(final修饰一个引用也是这个道理,因为引用中存储的是一个地址,一旦final修饰的引用指向了对应的对象,也就是地址已经写入到了该引用内存,那么地址不可再变)

  • final修饰的实例变量,系统不管赋值,要求程序员自己赋值 ==实例变量在new对象的时候赋值==

    class User{
        final double height=1.8;
        final double weight;//这一行不可行,系统不管赋值
        public User(){
            weight=0.8;//这样可以,虽然无参构造,或者无构造的时候,系统会自动赋值,但是这里是在系统赋值之前进行的手动赋值,所以编译可以通过
        }
    }
    

==如果不想让某个类被继承,那么可以用final修饰符来修饰该类== String就是final修饰的不可继承

==子类中特有的方法,在使用多态的时候会报错,因为编译器只会看父类引用是否有对应名称的方法,没有就会直接报错,除非强制向下转型,或者不适用多态==

final不管能不能调用,final管的是方法变量和类是最终的,不变的

常量

对于chinese这样的类,国家是一个固定值,不管加不加final,只要是实例变量,就会占用内存空间(在堆中)。

所以final一般修饰的实例变量都要加static ----->static final+变量名,==静态变量存储在方法区==,也就是常量。

==注意==:

  • 常量的命名,建议全部大写, 每个单词之间使用下划线连接
  • 常量和静态变量,都是存储在方法区,并且都是在类加载的时候初始化
  • ==常量一般都是公开的,因为公开你也改不了,不用封装==

抽象类

graph TD; 银行账户类-->储蓄卡类;
银行账户类-->信用卡类; 信用卡类-->小明的信用卡; 信用卡类-->小花的信用卡; 储蓄卡类-->小明的储蓄卡; 储蓄卡类-->小花的储蓄卡;

银行账户类是==抽象类==

信用卡类是==类==

下边对应的卡是==对象==

当然==抽象类仍然可以进一步抽象==

抽象类无法进行实例化,因为抽象类的具体---->类本身在生活中就是不存在的实体

抽象类属于==引用数据类型==

抽象类的语法

[修饰符列表] abstract class 类名{
	类体;
}

final是为了防止继承

abstract是为了继承 所以final和abstract是==敌人== 所以final和abstact不能联合使用

抽象类不能实例化对象,但是有构造方法,这个构造方法是给子类创造对象使用的

抽象方法

没有实现的方法, 没有方法体的方法就是抽象方法

public abstract void doSome();
//特点1:没有方法体,以分号结尾
//特点2:前边的修饰符列表有abstract关键字

抽象类不一定有抽象方法,并且抽象类中也可以有非抽象方法,抽象方法必须出现在抽象类中

==非抽象类继承抽象类,必须将抽象类的抽象方法实现==

==面向抽象编程==

public class Test{
    public static void mian(String[] args){
        Animal a=new Bird();//这就是面向抽象编程
    }
}
abstract class Animal{
    
}
class Bird extends Animal{
    
}

==OCP原则==:开闭原则,是说一个实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化

接口(行为动作 like a)

接口是==完全抽象的==

接口支持多继承,一个接口可以继承多个接口,==接口中只包含两部分内容:1、常量 2、抽象方法==

  • 接口中方法的修饰符列表可以省略:public abstract
  • 接口中常量的修饰符列表也可以省略:public static final

接口中的所有元素都是公开的(public修饰的)

语法是:

[修饰符列表] interface 接口名{}

类和类之间叫做继承,类和接口之间叫做实现

==缺省的权限小于public==

一个类可以实现多个接口

==注意==:

class Test{
    public static void main(String[] args){
        M a=new E();
        K b=(K)a;//接口和接口之间进行强制类型转换的时候,没有继承关系也可以强转  
        //类转成一个没有实现的接口,可以强转成改接口
        //运行的时候可能会出现ClassCastException 
    }
}
interface K{}
interface M{}
class E implements M {}

==向上转型或向下转型的前提是两个类之间有继承关系,否则在编译期间就会报错,但是这个结论不适用在接口方面==

一个类同时继承类和接口的时候,先extends之后再implements

接口的作用

面向接口编程,有了接口就是可插拔,可插拔也就是 低耦合 高扩展

==has a就要以属性的形式存在==

面向接口编程,可以降低程序的耦合度,提高程序的扩展力,符合OCP原则,接口的使用离不开多态(接口+多态才能解耦合)

//去餐厅吃饭的接口例子
public class Test{
    public static void main(String[] args){
        FoodMenu f=new AmericaCook();
        Customer c=new Customer(f);
        c.order();
    }
}
interface FoodMenu{
    void xihongshichaojidan();
    void yuxiangrousi();
}
class ChineseCook implements FoodMenu{
    public void xihongshichaojidan(){
        System.out.println("中国厨师做西红柿超级蛋");
    }
    public void yuxiangrousi(){
        System.out.println("中国厨师做鱼香肉丝");
    }
}
class AmericaCook implements FoodMenu{
    public void xihongshichaojidan(){
        System.out.println("美国厨师做西红柿超级蛋");
    }
    public void yuxiangrousi(){
        System.out.println("美国厨师做鱼香肉丝");
    }
}
class Customer{
    private FoodMenu foodmenu;
    public Customer(FoodMenu foodmenu){
        this.foodmenu=foodmenu;
    }
    public void order(){
        foodmenu.xihongshichaojidan();
        foodmenu.yuxiangrousi();
    }
}

接口解耦合,解的是 ==调用者和实现者的耦合==

==接口和接口的区别==:

  • 接口是完全抽象的 抽象类是半抽象的
  • 接口没有构造方法 抽象类有构造方法
  • 接口和接口之间支持多继承 类和类之间只能单继承
  • 一个类可以同时实现多个接口 但是一个抽象类只能继承一个类

import和包

包名的命名规范: ==公司域名倒叙+项目名+模块名+功能名==

package需要出现在代码的第一行 import再package下边

如果代码第一行有包名 如何编译 可以直接讲对应的包全部建好 ==javac -d . 项目名.java==这里的.是当前目录的意思

java.lang包小的类都不需要导包

访问控制权限

4个访问控制权限

  • private 表示私有的,只能再本类中使用
  • public 公开的,在任何位置都可以访问
  • protected 只能在本类或者同包或者子类中访问
  • 默认啥也不写 只能在本类,以及同包下访问
访问控制符 本类 同包 子类 任意位置
public 可以 可以 可以 可以
protected 可以 可以 可以 不行
默认 可以 可以 不行 不行
private 可以 不行 不行 不行

访问修饰符可以修饰什么?

  • 属性 4个都可以
  • 方法 4个都可以
  • 类 只能public 和默认
  • 接口 只能public和默认

API

Application Program Interface(应用程序编程接口)

整个JDK的类库就是javase的API

内部类

内部类的分类:

  • 静态内部类:相当于静态变量

  • 实例内部类:相当于实例变量

  • 局部内部类:相当于局部变量

    class Test{
        static class Inner1{}   //静态内部类        
        class Inner2{}      //实例内部类
        public void doSome(){  
            class Inner3{}   //局部内部类
        }
    }
    

匿名内部类

匿名内部类是局部内部类的一种

//举例说明
class Test{
    public static void main(String[] args){
        MyMath m=new MyMath();
        m.mySum(new Computer(){
            public int sum(int a,int b){
                return (a+b);
            }
        },10,20);
    }
}
interface Computer{
    int sum(int a,int b);
}


class MyMath{
	public void mySum(Computer c,int a,int b){
        System.out.println( c.sum(a,b));
    }    
}

为什么不建议使用匿名内部类?

​ ==因为只能用一次,没有直接用类实现接口方便==

数组

一维数组

数组是存放在==堆内存==的

数组一旦创建,Java中规定,长度不可再变

length属性是数组自带的

数组的优缺点:

==优点==:

  • 每一个元素的内存地址空间存储上都是连续的
  • 每一个元素在空间中所占的内存都是一样大的
  • 知道第一个元素的位置,知道每个元素的内存大小,可以直接通过数学计算得到对应的元素位置,所以一个100大小的数组和100w大小的数组检索的效率都是一样的

==缺点==:

  • 由于地址是连续的,所以对数据进行增删的时候效率低,因为随机增删会涉及到后边元素统一前移或者后移。
  • 数组不能存储大数据量,因为在内存中很难找到一块很大的地址连续内存空间
  • ==数组的最后一个元素的增删,没有效率影响==

数组初始化

  • 静态初始化 int[] a={1,2,3};
  • 动态初始化 int[] a=new int[5];
  • sout(new int[] {1,2,3})

==数组扩容== :数组扩容效率较低,涉及到数组拷贝的问题

二维数组

int[][] a=new int[2] [2]

a[0] [0]的意思是第一个以为数组的第一个元素

sout(new int[ ] [ ] {{1,2,3},{2,3,4},{2,3,4}});

异常

异常在java中是以类的形式存在,每一个异常类都可以创建异常对象

graph TD; A(Object)--"继承"---B(Throwbale) B(Object)--"继承"---C(Error) B(Object)--"继承"---D(Exception) D(Excption)--"继承"---E(RuntimeException) D(Excption)--"Exception的直接子类"---F(ExceptionSubClass) E(RuntimeException)--"继承"---G(NullPointException) E(RuntimeException)--"继承"---H(ArithmeticException) E(RuntimeException)--"继承"---I(ClassCastException)

不管是错误还是异常都是可抛出的

==所有的Error,java程序只有一个结果就是终止程序的执行,推出JVM==

==所有的RuntimeException及其子类都属于运行时异常==

所有Exception的直接子类,都叫做==编译时异常==

==编译时异常的意思是在编译阶段发生的异常嘛?==

  • 不是,编译时异常的意思是,在编译阶段程序员就要处理的异常,如果不处理编译器会报错
  • 运行时异常在编写程序阶段,==可以选择处理,也可以选择不处理==

编译时异常和运行异常,都发生在运行阶段,编译阶段异常是不会发生的,因为异常是new出来的,只有程序运行阶段才可以new对象

==编译时异常和运行时异常的区别==

  1. 编译时异常(受检异常,受控异常)发生的概率比较高

    ==举例说明==:

    ​ 看见外边下雨,防止出门淋雨感冒(异常),所以出门带了一把伞(感冒异常发生之前的处理方式)

    ​ 对于这种概率较高的异常,需要运行之前进行预处理

  2. 运行时异常(未受检异常,非受控异常)发生的概率比较低

    ==举个例子==:

    ​ 出门可能被飞机砸到(异常)

    ​ 没必要对这种低概率异常进行预处理,不然会活得很累

java中异常处理的两种方式:

  • 在方法声明的地方,使用throws关键字
  • 使用try...catch语句对异常捕捉

如果一直throws给上级,直到main抛给了JVM之后,只有一个结果,就是JVM终止程序运行

class Test{
    public static void main(String[] args){
        dosome();//因为doSome方法有throws声明,所以在调用doSome方法的时候就要对异常进行处理,要么继续throws,也么try catch,catch的写法  catch(Exception e){e.printStackTrace();}
    }
    public static void doSome() throws ClassNotFoundException{
        System.out.println("doSome");
    }
}
//手动抛异常
if(要判断的异常){
    throw 要抛出的异常对象
}

==代码出现异常,一个域内,出现代码之后的代码不会执行== ==catch的后续代码会执行== catch可以写多个,这样精确到一个个的时候,有利于程序的调试

catch中的异常可以通过 逻辑 | 进行统一

==如何选择throws和try catch呢?== 如果确定了调用者需要处理,就throws

异常的重要方法

String msg=exception.getMessage();//获取异常简单的表述信息
exception.printStackTrace();//打印异常追踪的堆栈信息   print异常信息有一个单独的线程负责

finally

finally通常使用在什么情况?

try{
	FileInputStream f=new FileInputStream("路径");
    
    String s=null;
    s.toString();//这里会出现空指针异常
    
    f.close();//关闭流
}catch(Excepption e){
    e.printStackTrace();
}
//以上代码如果不加finally的话,close就关闭不了,一直占用资源
//finally中的代码一定会执行

return不会对finally的运行产生影响 但是System.exit() 退出JVM语句,会影响finally的运行

==一道简单的finally的面试题==

public static int m(){
    int i=100;
    try{
        return i;
    }finlly{
        i++;
    }
}//上述代码的最终结果是100,因为只要return i出现在i的下边,就会遵循java更古不变的自上而下的规则
//以上代码 反编译之后的结果如下
public static int m(){
    int i=100;
    int j=i;
    i++;
    return j;
}

final finally 和finalize 的区别

final

  • final修饰的类无法继承
  • final修饰的方法无法覆盖
  • final修饰的变量无法重新赋值

finally

  • 和try一起使用
  • finally语句块中的代码必须执行

finalize

  • 是一个Object类中的方法名
  • 这个方法是由垃圾回收器GC负责调用的

如何自定义一个异常

/*
两步:
1、编写一个类继承Exception或者RuntimeException
2、提供两个构造方法,一个无参的,一个String的
*/
public class MyException extends Exception{ //Exception对应编译时异常  RuntimeException对应运行时异常
    public MyException(){
        
    }
    public MyException(String s){   //这里的s就是  getMessage的结果
        super(s);
    }
}

集合

集合存储的都是引用数据类型

集合在java.util.*中 ArrayList 底层是数组 LinkedList 底层是链表 TreeSet 底层是二叉树

Collection

接口Interable(==Interator方法==)<-----接口Collection<----接口Interator(==hasNext()、next()、remove()方法==) 两个都是接口

Collection有两个泛化(继承)接口,分别是List和Set:

  • ==List==:存储的元素有序可重复,有下标,有序的意思是==存进和取出的顺序不变==,有序是因为有下标

    • ArrayList(初始化容量是10,扩容是1.5倍)

      • ArrayList有三种构造方法

        1. 无参的 2.传入int类型的集合初始容量 3.传入一个结合
      • 底层是Object类型的数组,非线程安全的

        • 如何变成线程安全的呢

          List l=new ArrayList();
          Collections.synchronizedList(l);//集合工具类
          
  • 初始化容量是10(底层是:==先创建一个容量为0的数组,当添加第一个元素的时候,初始化结合容量为10==),如果add的元素如果超过了初始容量,那么集合就会扩容为之前的1.5倍,ArrayList优化,最好还是少进行扩容,因为数组扩容效率比较低

  • 初始化容量可以使用int构造方法传初始化容量进去

    • 数组优点:检索效率比较高 缺点:增删改查效率低 但是向数组末尾添加元素效率很高

    • LinkedList(没有初始化容量)

      • 底层采用了==双向链表数据结构==

      • //单链表的简单实现代码
        public class Test
        {
        	public static void main(String[] args){
        		Link l=new Link();
                 l.add("aaa");
        		System.out.println(l.header.data+"?"+l.end.data+"size"+l.size);
        	}
        }
        class Node
        {
            public Object data;
            public Node next;
            public Node(){}
            public Node(Object data,Node next){
                this.data=data;
                this.next=next;
            }
        }
        class Link
        {
        	int size=0;
            Node header=null;
            Node end=null;
            public void add(Object obj){
                if(header==null){
                    header=new Node(obj,null);
                    end=header;
                }else{
                    Node newNode=new Node(obj,null);
                    end.next=newNode;
                    end=newNode;
                }
        		size++;
            }
            public void remove(Object obj){}
            public void modify(Object newObj){}
            public int find(Object obj){return 1;}
        }
        //注意************
        //LinkedList也有下标,List都有下标,但是ArrayList的检索效率高并不是仅仅因为有下标
        //是因为ArrayList的底层是数组,而LinkedList的底层是有下标的链表,导致它的检索或者查找元素的效率比较低,只能从头节点开始一个一个遍历
        链表的优缺点:
            优点:链表不需要连续存储空间
            	因此随机增删效率很高,所以在遇到随机增删集合中元素的业务比较多时,建议使用			LinkedList
            缺点:不能像数组那样通过数学表达式的方式得到对应元素的内存地址,每次都需要从头节点		  进行一个一个检索,效率较低
                        
            ArrayList把检索发挥到了极致
            LinkedList把随机增删发挥到了极致
                        
            不过平时开发都是往末尾添加元素,所以ArrayList用的是比LinkedList多的
                    
        
    • Vector(初始化容量是10,扩容是2倍)

      • 底层采用数组数据结构,相对于ArrayList,Vector是线程安全的(sychronized,虽然线程安全,但是效率较低,因为现在线程安全有别的方案,所以Vector用的少了)
  • ==Set==:无序不可重复,没有下标,所以无序(不可重复是==如果重复只会存在一个结果==)

    • HashSet

      • new HashSet实际上是new HashMap,所以HashSet存储元素也是存到了HashMap当中了,所以其底层是哈希表数据结构
    • TreeSet

      • TreeSet的父接口是SortedSet,再往上才是Set接口,new TreeSet底层实际上是new TreeMap,所以其底层是二叉树数据结构,放在TreeSet里的==数据可以自动按照大小排序==

      • 对于自定义的类型来说,TreeSet可以排序吗 ? 不能,因为没有指定对应的比较规则

        • 解决方式

          1. ==需要实现Comparable接口才可以==(Comparable是lang下的)

            class WuGui implements Comparable{
                int age;
                public WuGui(){}
                public WuGui(int age){this.age=age}
                public int compareTo(WuGui o){
                    return this.age-o.age;
                }
            }
            
          2. 构造方法传入比较器(Comparator是util下的)

            class MyComparator implements Comparator<WuGui>{
                public int compare(Wugui o1,WuGui o2){
                    return o1.age-o2.age;
                }
            }
            class Test{
                public static void main(String[] args){
                    MyComparator m=new MyComparator();
                    Set<WuGui> set=new TreeSet(m);
                }
            }
            
          3. 使用匿名内部类

            Set<Customer> m=new TreeSet<>(new Comparator<Customer>() {
                            @Override
                            public int compare(Customer o1, Customer o2) {
                                return o1.getAge()-o2.getAge();
                            }
                        });
            

          ==对于以上三种方式==:要选哪种方式,如果比较规则很少改变,建议使用Comparable接口

          如果比较规则有多个,并且多个比较规则之间频繁切换,建议使用Comparator接口

    • TreeSet 使用的遍历方式是:中序遍历

==Collection中可以存储什么东西?==集合中不能直接存储基本数据类型,也不能存java对象,只能存储java对象的内存地址

==Collection中的方法:==

  • boolean add(E e)

  • int size() ==获取当前集合中的元素个数, 不是获取集合的容量==

  • void clear()

  • boolean contains(Object o)

    • public class Test{
          public static void main(String[] args){
              Collection c=new ArrayList();
              String s1=new String("abc");
              c.add(s1);
              String s1=new String("def");
              c.add(s2);
              String x=new String("abc");
              sout(c.contains(x));//contains底层调用的是equals方法,对比的是String,所以结果是true
          }
      }
      //重写equals方法  
      public boolean equals(Object o){
          if(o==null || !(o.instanceof(User))) return false;
          if(o==this)  return true;
          User u=(User) o;
          return (o.name.equals(this.name));
      }
      
      
      //结论:存放在集合中的类型,一定要重写equals方法
      
  • boolean remove(Object o)

    • Collection c=new ArrayList();
      String s1="abc";
      c.add(s1);
      String s2="abc";
      c.remove(s2);
      sout(c.size());// 说明remove底层调用了equals方法
      
      
      
      Collection c=new HashSet();
      c.add("aa");
      c.add("abb");
      c.add("ab");//存进去什么类型,取出来还是什么类型,只不过输出的时候会调用toString方法
      Iterator i=c.iterator();
      while(i.hasNext()){
          sout(i.next());
          c.remove(i.next());//会出现异常,因为集合的内容发生了变化 所以这里可以使用迭代器的remove方法      i.remove();  即删除迭代器当前所指的对象
      }
      
  • boolean isEmpty()

  • Object[] toArray() 将集合转换成数组

  • Ierator iterator()

    • Collection c=new HashSet();
      c.add("aa");
      c.add("abb");
      c.add("ab");//存进去什么类型,取出来还是什么类型,只不过输出的时候会调用toString方法
      Iterator i=c.iterator();
      while(i.hasNext()){
          sout(i.next());
      }
      
    • 迭代器对象的两个方法

      • boolean hasNext()

      • Object next()

List中的方法,在Collection的基础上:

  • void add(int index,E element) 这样添加效率太低
  • E get(int index) 因为有下标,所以List集合有他自己的遍历方式
  • int indexOf()
  • int lastIndexOf()
  • E remove(int index)
  • E set(int index,E element)

Map

==Key和value里都存着java对象的内存地址==,所有Map的key都是无需不可重复的,Map集合的ket和Set集合存储元素特点相同

==Map常用方法==

  • void clear()

  • boolean containsKey(Object key) contains方法底层都是equals方法

  • boolean containsValue(Object value)

  • V get(Object key) 通过key获取value

  • Set keySet() 获取所有的Key

  • V put(K key,V value) 添加键值对

  • V remove(Object key)

  • int size()

  • Collection values() 获取Map集合中所有的value,返回一个Collection

  • Set<map.Entry<K,V>>entrySet() 将Map集合转换成Set集合

    • key      value
      ===============
      1		zhangsan
      2		lisi
          上述Map通过entrySet之后,得到的结果如下:
          1=zhangsan
          2=lisi
      
      import java.util.*;
      public class Test
      {
      	//遍历Map集合
      	public static void main(String[] args){
      		Map<Integer ,String > map=new HashMap<>();
      		map.put(1,"zhangsan");
      		map.put(2,"wangwu");
      		//迭代器
      		Set<Integer> set=map.keySet();
      		Iterator<Integer> i=set.iterator();
      		while(i.hasNext()){
      			//Integer key=i.next();
      			//String value=map.get(key);
      			//System.out.println(key+"="+value);
      			System.out.println(i.next()+"="+ map.get(i.next()));//注意 调用一次next就会迭代一次
      		}
      		//foreach
      		for(Integer ii:set){
      			System.out.println(ii+"="+map.get(ii));
      		}
      		//Set<Map.Entry<K,V>> entrySet()
      		Set<Map.Entry<Integer,String>> set2=map.entrySet();
      		for(Map.Entry<Integer,String> s:set2){
      			System.out.println(s.getKey()+"="+s.getValue());
      		}//这种foreach适合大数据量使用,因为是直接通过Node的属性值进行获取的
      	}
      }
      

HashMap(初始容量是10,默认加载因子是0.75)

Map<----HashMap(非线程安全的,类) Map<----HashTable(线程安全的,过时了,类)<--Properties(类,存储在Properties的key和value都必须是String类型,不能是其他类型,被称为==属性类 ==) 两个底层都是哈希表

HashMap允许key和value值都可以为null(HashTable的key和value都不能为空)

HashMap扩容是2的倍数

Map map=new HashMap();
Integer i=map.put(null,100);
sout(map.size());//1
  • HashTable的初始化容量是11,默认加载因子是0.75 它的扩容是原容量乘2再加1

哈希表/散列表

==数组==是查询遍历效率高 ==链表==是随机增删改效率高 ==哈希表==是结合两个的优点

哈希表:底层是一维数组,这个数组的每个元素是一个单向链表

==map.put(k,v)的实现原理:==

  1. 先将kv封装到Node对象中
  2. 底层会调用hashMap()得到hash
  3. 通过哈希函数/哈希算法,将hash值转换成数组下标,下标位置如果没有任何元素,就把Node添加到或者位置,如果下标对应的位置有链表,拿着k和链表上每个节点的k进行equals,如果所有的equals方法返回都是false,那么就将新节点添加到链表的末尾,如果其中一个equals返回true,会把新的节点的value替换上

==map.get(key)的实现原理:==

先调用k的hashCode方法得到哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置,如果这个位置什么都没有,返回null。如果有一个单向链表,那么就一一equals k值,有的话返回对应的value,没有就返回null

==为了保证hashMap的key或hashSet的==无序不可重复==,对应的元素对象必须重写hashCode和equals方法==

假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成一个纯单向链表,这种情况成为==散列分布不均匀==

==散列分布==需要重写hashCode的时候有一定的技巧

==重点==:HashMap集合初始化容量必须是2的倍数,因为可以达到散列均匀,提高存取效率

如果一个类的equals方法重写了,那么hashCode方法必须重写

如果哈希表的单个链表的元素超过8个,那么单向链表会编程红黑树结构,如果数量小于6,会将红黑树改成单向链表,为了提高检索效率

Properties(线程安全的)

是一个Map集合,继承Hashtable,它的key和value都是String类型,属于==属性类对象==

Properties两个方法:

  • setProperty
  • getProperty

TreeMap

Map<--SortedMap(接口)<--TreeMap(类,数据结构是二叉树) SortedMap集合的key会自动按照大小顺序排序

泛型

没用泛型的话,迭代器拿出来的就是Object类型,代码不好写

List<Animal> myList=new ArrayList<Animal>();

==自动类型推断机制(又成为钻石表达式)==

//jdk8之后   new对象的时候<>中的类型可以不写,系统会自动检测出来
List<Animal> myList=new ArrayList<Animal>();

自定义泛型

public class Test<E(名字任意)>{}
public E doSome(){}   //java自带的E是Element T是type

foreach

缺点:没有下标

List<Integer> l=new ArrayList<>();
l.add(1);
for(Integer i:l){
    sout(i);
}

IO流

Read Input InputStream --->读 读入 输入流 ==硬盘--->内存==

==IO流分类==:

  • 按照流的方式分:输入 和 输出流
  • 按照读取数据的方式不同: 字节流(什么类型的文件都可以读取) 和 字符流(主要为了读取普通文本文件)

IO流四大家族

  • java.io.InputStream 字节输入流
  • java.io.OutputStream 字节输出流
  • java.io.Reader 字符输入流
  • java.io.Writer 字符输出流

四大家族的首领都是抽象类,所有的流都==实现了closeable接口==,所有流都是可关闭的,所有的==输出流都实现了Flushable==,所有输出流都是可刷新的,所以***==要养成好习惯,所有的流都要关闭,所有的输出流输出之后都要刷新==***

java.io包中需要掌握的流有16个

==文件专属==

  • java.io.FileInputStream 构造方法有==append参数==,为true,说明文件内容可以追加
    • ==对应方法==
      • int read()
      • int read(byte[] b)
      • int available() 返回流当中剩余没有读到的字节数量
      • long skip(long n) 跳过几个字节不读
  • java.io.FileOutputStream
    • ==对应方法==
      • int write()
      • int write(byte[] b)
      • int write(byte[] b,int off,int len)
      • 构造 FileOutputStream(String 文件名,boolean append) append如果为True,那么直接在文件后边追加,不会从头开始替换
  • java.io.FileReader
  • java.io.FileWriter

==转换流(字节流转换成字符流)==

  • java.io.InputStreamReader
  • java.io.OutputStreamWriter

==缓冲流==

  • java.io.BufferedReader
  • java.io.BufferedWriter
  • java.io.BufferedInputStream
  • java.io.BufferedOutputStream

==数据流==

  • java.io.DataInputStream
  • java.io.DataOutputStream

==标准流==

  • java.io.PrintWriter
  • java.io.PrintReader

==对象流==

  • java.io.ObjectInputStream
  • java.io.ObjectOutputStream

FileInputStream读取文件的代码架子

class Test {
    public static void main(String[] args) {
        FileInputStream f = null;
        try {
            f = new FileInputStream("Comparable/src/com/wzx/io/tempFile");
            int readData = 0;
            while ((readData = f.read()) != -1) {
                System.out.println(readData);
            }
        } catch (FileNotFoundException fileNotFoundException) {
            fileNotFoundException.printStackTrace();
        } catch (IOException ioException) {
            ioException.printStackTrace();
        } finally {
            if (f != null) {
                try {
                    f.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

IDEA默认的当前路径是哪里? 工程Porject的根就是默认当前路径

  • read() 返回值是读取的数据
  • read(byte[] b) 返回值是读取的字节数
class Test {
    public static void main(String[] args) {
        FileInputStream f = null;
        try {
            f = new FileInputStream("Comparable/src/com/wzx/io/tempFile");
            int readCount = 0;
            byte[] b = new byte[4];
            while ((readCount = f.read(b)) != -1) {
                System.out.println(new String(b, 0, readCount));
            }
        } catch (FileNotFoundException fileNotFoundException) {
            fileNotFoundException.printStackTrace();
        } catch (IOException ioException) {
            ioException.printStackTrace();
        } finally {
            if (f != null) {
                try {
                    f.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

available() 剩余字节个数

	try {
            f = new FileInputStream("Comparable/src/com/wzx/io/tempFile");
            byte[] b = new byte[f.available];
            int readCount =f.read(b);
            System.out.println(new String(b));

skip() 跳过几个字节

文件拷贝

public class Copy {
    public static void main(String[] args) {
        FileInputStream fi=null;
        FileOutputStream fo=null;
        try {
            fi=new FileInputStream("myfile");
            fo=new FileOutputStream("Comparable/src/com/wzx/io/myfile");
            byte[] b=new byte[1024*1024];
            int readCount=0;
            while((readCount=fi.read(b))!=-1){
                fo.write(b,0,readCount);
            }
            fo.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fi!=null){
                try {
                    fi.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fo!=null){
                try {
                    fo.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

FileReader 文件流

FileReader reader=null;
try{
    reader=new FileReader("tempfile");
    char[] c=new char[4];
    int readCount=0;
    while((readCount=reader.read(c))!=-1){
        sout(String(c,0,readCount));
    }
}catch{
}
//或者
try{
    reader=new FileReader("tempfile");
    char[] c=new char[4];
    reader.read(c);
    for(char cc:c){
        sout(cc);
    }
}

FileWriter 的write方法,可以直接传入字符串String类型

BufferedReader

带有缓冲区的字符输入流,使用该流的时候不需要自定义char数组,或者说不需要自定义byte数组,自带缓冲。

==注意:==当一个流的构造方法中需要一个流的时候,这个被传进来的节点流称为节点流,外部负责包装的流称为包装流(处理流),对于包装类来说,关闭包装流,节点流就会自动关闭

FileReader reader=new FileReader("文件名");
BufferedReader br=new BufferedReader(reader);

//String firstLine=br.readLine();
String s=null;
while((s=br.readLine())!=null){
    sout(s);
}
br.close();
FileInputStream in=new FileIputStream();
BufferedReader br=new BufferedReader(new FileInputStreamReader(in));
//合并
BufferedReader br=new BufferedReader(new FileInputStreamReader(new FileInputStream));

数据流

DataOutputStream dos=new DataOutputStream(new FileOutputStream("fileName"));
byte b=100;  //写到data文件中的不仅仅是具体的数据,还包括数据的类型都一并写入到了文件中
short s=200;  //所以直接打开文件会出现乱码,所以这时候就需要DataInputStream来读取
int i=300;
long l=400L;
float f=4.4F;
double d=3.14;
boolean bo=true;
char c='a';

dos.writerByte(b);  //DataInputStream dis 的方法  dis.readByte()....
dos.writerInt(i);
......
dos.writerChar(c);

dos.flush();//刷新
dos.close();//关闭最外层

标准输出流

java.io.PrintStream 标准字节流输出 ==不需要关闭,默认输出到控制台==

PrintStream ps=System.out;
ps.println("aaaa");
//标准输出流不需要  flush()  和  手动close()

标准输出流怎么指向别处,==不指向控制台==

PrintStream pr=new PrintStream(new FileOutputStream("fileName"));
System.setOut(pr);
System.out.println("aaa");//这里的输出方向就是file文件	
//记录日志的文件
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;
public class LoggerTest {
    public static void main(String[] args) {
        Logger.log("项目开始了");
    }
}
class Logger{
    public static void log(String msg){
        try {
            PrintStream out=new PrintStream(new FileOutputStream("log.txt",true));
            System.setOut(out);
            Date date=new Date();
            SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
            String time=sdf.format(date);
            System.out.println(msg+time);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

java.io.File

File是一个文件路径的抽象表示形式

==File和四大流没有关系==

==File的常用方法==:

  • 构造 File(String 路径)
  • boolean exists() 判断是否存在
  • creatNewFile() 以文件的形式创建出来
  • mkdir() 以路径的形式创建出来
  • mkdirs() 多重目录
  • String getParent() 获取父路径
  • File getParentFile() 获取父File
  • getAbsolutePath() 获取绝对路径
  • getName() 获取文件名
  • isFile() 判断是否为一个文件
  • isDirectory() 判断是否为一个路径
  • long lastModified() 获取文件最后一次修改时间 返回值是毫秒,从1970年开始
  • long length() 获取文件的大小
  • File [ ] ==listFile()== 获取当前目录下的所有子文件

对象的序列化和反序列化

==将内存的Java对象,存储到硬盘文件中---->序列化==

序列化:ObjectOutputStream 进行拆分对象

//写一个Student类,对Student对象进行序列化
Student stu=new Student("zhangsan");
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("studentFile"));
//序列化
oos.writeObject(stu);
oos.flush();
oos.close();

==没有内容的接口是标志性接口(起到标识的作用,Java虚拟机看该类实现了该接口,就会特殊对待,JVM看到标志性接口,就会给该类自动生成一个序列化版本号)==:Serializable接口就是标志性接口

==怎么一次序列化多个对象? 将对象放在集合中,序列化集合即可==注意:放进集合中的对象都要实现Serializable接口,如果不用集合,按顺序序列化,那么在第二个对象序列化的时候会报错

要实现对象序列化,必须将对应的类实现Serializable接口

//如果不想序列化某个实例变量的时候
private int a;=====>  private transient int a;   //虽然设置成游离的,但反序列化会得到对应变量的默认参数

反序列化:ObjectInputStream 进行组装对象

ObjectInputStream ois=new ObjectInputStream(new FileInputStream("studentFile"));
ois.readObject();
ois.close();

==序列化版本号==

10年前,写了一个Student类,并对其对象进行序列化。10年后,Student类序列化版本号改变了,如果直接对硬盘中的对象进行反序列化,会报错:java.io.InvalidClassException

==java中如何区分不同的类==

  1. 首先通过类名区分,类名不同,肯定不是一个类
  2. 如果类名一样,就要对比序列化版本号,版本号不同,不是一个类

==自动生成序列化版本号的缺陷:==

  • 一旦代码确定之后,不能修改,修改之后会重新编译,这样序列化版本号就变了,JVM会认为这是一个全新的类
  • 最终结论:凡是一个类实现了Serializable接口,建议给该类手动添加一个固定不变的序列化版本号
  • 手动:private static final long serialVersionUID=1L

==IDEA自动生成序列化版本号:setting-Editor-Inspections-搜索serializable class without 'serialVersionUID '==

IO和properties

file文件内容:

username=admin

password=123

//读取file文件中的内容并存储到properties对象中
FileInputStream in=new FileInputStream("file");
Properties pro=new Properties();
//调用properties的load(Reader reader/InputStream in)将Map中的数据加载到Map集合当中
pro.load(in);  //文件中=的左边是在properties的key,=右边是properties的value
String username=pro.getProperty("username");
in.close();

IO和properties的设计理念的应用:经常需要修改的数据,可以单独写在一个文件里,需要使用的时候用程序动态调用即可。

我们把有这种机制的文件称为==配置文件==,并且如果文件中都是以key=value的形式出现的时候,我们称为属性配置文件,==属性配置文件中的注释用:#==

java规范:属性配置文件命名以properties结尾,但是不是必须的

多线程

什么是进程?什么是线程?

==一个软件就是进程==,线程是进程中的一个执行场景/执行单元

在运行java程序的时候,目前至少有两个线程在并发,JVM是进程,进程先调用main方法线程,再调用垃圾回收线程。

==注意== 线程A和线程B,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈

多线程的目的:为了提高程序的处理效率

多线程的实现方法

==方法1==、编写一个类,直接继承java.lang.Thread,重写run方法

public class Test{
    public static void main(String[] args){
        MyThread thread=new MyThread();
        thread.start();
    }
}
class MyThread{
    public void run(){
        sout("子线程");
    }
}

上边代码中的start的作用是,在JVM中开辟一个新的栈空间 ,start执行完成,这个代码语句使命就结束了,启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底

<img alt="image-20220124204819694">

***==方法2、==***编写一个类,实现java.lang.Runnable接口,实现run方法

public class Test{
    public static void main(String[] args){
        MyThread thread=new MyThread(new MyRunnable);
        t.start();
    }
}
class MyRunnable implements Runnable{//该类不是线程,只是一个可运行的类
    public void run(){
        sout("子线程");
    }
}

我们一般使用第二种,面向接口,因为还可以进一步继承类==以上代码还可以通过匿名内部类进行实现==

线程的生命周期

为什么线程的运行会一会前一会后? 因为声明周期中的==就绪态== ==运行态==

线程生命周期图

<img alt="image-20220124224440314">

线程的方法

  • 设置线程的名称 thread.setName("aaa")

  • 获取线程的名称 thread.getName(); 如果不设置名称系统获取名称会得到 "Thread-线程的次序"

  • 获取当前线程对象 static Thread currentThread()

  • 让当前线程进入休眠,进入“阻塞状态”,放弃占有的CPU时间片 static void sleep(long millis),该方法可以实现一定的效果==即,间隔特定的时间,去执行一段特定的代码,每隔多久执行一次==

//关于sleep的一个面试题
public static void main(String[] args){
    Thread t=new Thread3();
    t.setName("aa");
    t.start();
    t.sleep(1000*5);//这行代码是主线程休眠,因为sleep方法是静态的和对象无关,最终都是Thread.sleep()
}
class Thread3 extends Thread{
    public void run(){
        
    }
}

如果sleep时间久了,怎么叫醒一个正在睡眠的线程,终端线程的睡眠,而不是终端线程的执行

  • 实例方法==t.interrupt() 终止线程的睡眠==

  • t.stop() 已过时不建议使用 强行终止线程的执行 ==缺点==:数据丢失

    • 那么怎么合理终止一个线程呢? 答:打==布尔标记== 如下代码

    • class Test{
          MyRunnable m=new MyRunnable();
          Thread r=new Thread(m);
          r.setName("a");
          r.start();
          r.run=false;//终止线程执行
      }
      class MyRunnable implements Runnanle{
          boolean run=true;
          public void run(){
              if(run){
                      
              }else{
                  //在return之前可以进行数据保存,从而防止stop引起的数据丢失
                  return;
              }
          }
      }
      

线程调度

  • 抢占式调度模型

    • java就是使用这种模型,谁的优先级高,谁抢到cpu时间片的概率越高
  • 均分式调度模型

  • ==线程调度方法==

    • void setPriority(int newPriority) 设置线程的优先级

    • int getPriority() 获取线程的优先级

    • 最低的优先级是1,默认优先级是5,最高优先级是10

    • static void yield() 让位方法,暂停当前正在执行的线程对象,并执行其他线程,yield不是阻塞方法,yield的执行,会让当前线程从运行态就绪态

    • void join() 合并线程

      • //举例
        class Test{
            MyThread t=new MyThread();
            t.join();//使得主线程进入阻塞,t线程执行,直到t线程结束,主线程才可以运行
        }
        class MyThread extends Thread{
                  
        }
        

多线程的安全问题

==什么时候存在多线程问题呢?==

  1. 多线程并发

  2. 有共享数据

  3. 共享数据有修改的行为

    public class BankTest {
        public static void main(String[] args) throws InterruptedException {
            Account act=new Account(10000);
            Thread t1=new Thread(new BankRunnable(act));
            Thread t2=new Thread(new BankRunnable(act));
            t1.setName("t1");
            t2.setName("t2");
            t1.start();
            t2.start();
        }
    }
    class BankRunnable implements Runnable{
        Account act;
        public BankRunnable(Account act) {
            this.act=act;
        }
        @Override
        public void run() {
            int balanceBefore=act.getBalance();
            act.withDraw(5000);
            System.out.println(Thread.currentThread().getName()+"run从"+balanceBefore+"取出"+5000+"元,还有余额:"+(act.getBalance())+"元");
        }
    }
    class Account{
        private int balance;
        public Account() {
        }
        public Account(int balance) {
            this.balance = balance;
        }
        public int getBalance() {
            return balance;
        }
        public void setBalance(int balance) {
            this.balance = balance;
        }
        public void withDraw(int num){
            int balanceBefore=this.getBalance();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(getBalance()-num);
        }
    }
    /*
    t2从10000取出5000元,还有余额:5000元
    t1从10000取出5000元,还有余额:5000元
    */
    

==怎么解决线程安全问题呢?==

  1. 线程排队执行(不能并发),这种机制称为==线程同步机制==,同步机制会降低效率

    通过同步机制来解决以上代码的安全问题:

    //只需要修改会出现错误的操作代码,因为取钱的动作不能异步,所以要对取钱这个动作采用同步机制
    
     public void withDraw(int num){
         synchronized{//synchronized括号中的数据必须是多线程共享的数据,这里的代码共享对象是账户对象也就是this
         	int balanceBefore=this.getBalance();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(getBalance()-num);   
         }
        }
    /*
    t1run从10000取出5000元,还有余额:5000元
    t2run从5000取出5000元,还有余额:0元
    */
    synchronized(){}称为同步代码块
    

    怎么解释synchronized呢?

    ​ java语言中,每个对象都有一把==锁==,是一个标志。假设t1先执行,遇到了synchronized,就会锁定共享对象的锁,之后执行同步代码块中的代码,直到同步代码块中的代码执行完毕。如果在t1锁定对象锁之后,t2也遇到了synchronized,不要怕,锁已经被占有了,要等t1执行完同步代码块的代码再释放锁之后,t2才会执行。 ==()中放的共享对象一定要是多个需要排队的线程的共享对象==

    image-20220125160137394

    对象是共享的,那么对象的实例对象也是共享的,只要是==对象==,就可以占有

    如果是synchronized("abc") 那么所有线程都会同步

java中的三大变量:局部(栈中) 静态(方法区) 实例(堆 ) ==局部变量永远都不存在线程安全问题==,因为局部变量在栈内,一个线程一个栈,而方法区和堆只有一个,都是多线程共享的,所以可能存在线程安全问题

==编程模型==

  • 异步编程模型
    • 多线程并发,谁也不等谁
  • 同步编程模型
    • 排队执行

扩大同步范围的缺点

不仅可以直接在取钱方法体内使用同步机制,也可以直接在调用取钱方法的时候使用同步机制,如:sychronized(act){act.withDraw(5000)} ==这样扩大了同步范围,但是代码执行效率降低了==

实例方法上使用Synchronized(缺点 和优点)

synchronized修饰实例变量,public synchronized void withDraw 是将==this锁住了==

缺点:

  • 这种方式不灵活 一直锁住this
  • 这种方式会无缘无故扩大同步范围,从而降低代码执行效率

优点:

  • 代码少了,所以共享对象是this,并且需要同步的代码块是整个方法体,那么建议实例方法使用synchronized

StringBuilder 和 StringBuffer的线程安全问题

如果不存在线程安全问题,局部变量使用StringBuilder,因为StringBuilder不需要做同步处理,效率较高

synchronized的三种用法

  1. 同步代码块
  2. 实例方法使用synchronized
  3. 在静态方法上使用synchronized,表示找==类锁==,类锁只有一把,和对象的个数没有关系(第三种是为了保证==静态变量的安全==)

synchronized面试题

public class Test
{
	public static void main (String[] args) throws Exception{
		Myclass m=new Myclass();
		MyThread t1=new MyThread(m);
		MyThread t2=new MyThread(m);
		t1.setName("t1");
		t2.setName("t2");
		t1.start();
		try{Thread.sleep(1000);	}
		catch (Exception e){}
		t2.start();
	}
}
class MyThread extends Thread{
	Myclass m=new Myclass();
	public MyThread(Myclass m){
		this.m=m;
	}
	public void run() {
		if(Thread.currentThread().getName().equals("t1")){
			m.doSome();
		}
		if(Thread.currentThread().getName().equals("t2")){
			m.doOther();
		}
	}
}
class Myclass
{
	public synchronized void doSome() {
		System.out.println("doSome开始");
		try{Thread.sleep(1000*10);}
		catch (Exception e){}
		System.out.println("doSome结束");
	}
	public void doOther(){
		System.out.println("doOther开始");
		System.out.println("doOther结束");
	}
}
//t1执行doSome并不影响t2执行doOther,因为doOther方法并不涉及到线程安全问题
//那么如果在doOther方法上加synchronized,那么就需要等待t1线程调用完doSome
//如果创建两个Myclass对象分别给t1和t2,那么两者之间也需要等待

==类锁面试题,根据上边代码修改得到==

public class Test
{
	public static void main (String[] args) throws Exception{
		Myclass m=new Myclass();
		MyThread t1=new MyThread(m);
		MyThread t2=new MyThread(m);
		t1.setName("t1");
		t2.setName("t2");
		t1.start();
		try{Thread.sleep(1000);	}
		catch (Exception e){}
		t2.start();
	}
}
class MyThread extends Thread{
	Myclass m=new Myclass();
	public MyThread(Myclass m){
		this.m=m;
	}
	public void run() {
		if(Thread.currentThread().getName().equals("t1")){
			m.doSome();
		}
		if(Thread.currentThread().getName().equals("t2")){
			m.doOther();
		}
	}
}
class Myclass
{
	public synchronized static void doSome() {
		System.out.println("doSome开始");
		try{Thread.sleep(1000*10);}
		catch (Exception e){}
		System.out.println("doSome结束");
	}
	public synchronized static void doOther(){
		System.out.println("doOther开始");
		System.out.println("doOther结束");
	}
}
//synchronized放在静态方法上,是锁类锁的,所以线程t1直接锁住了Myclass类,t2需要等t1的doSome方法结束之后才可以占用Myclass锁

==目前所学的“对象锁”和“类锁”都是排他锁,之后还会有互斥锁==

死锁

死锁代码要求会写,面试有时会让你写一个死锁

//死锁
public class Test{
	public static void main(String[] args){
        Object o1=new Object();
        Object o2=new Object();
        Thread t1=new MyThread1(o1,o2);
        Thread t2=new MyThread2(o1,o2);       
        t1.start();
        t2.start(); 
    }    
}
class MyThread1 extends Thread{
    Object o1;
    Object o2;
    public MyThread1(Object o1,Object o2){
        this.o1=o1;
        this.o2=o2;
    }
    public void run(){
        synchronized(o1){
			try{Thread.sleep(1000);}catch (Exception e){}
            synchronized(o2){ }
        }
    }
}
class MyThread2 extends Thread{
    Object o1;
    Object o2;
    public MyThread2(Object o1,Object o2){
        this.o1=o1;
        this.o2=o2;
    }
    public void run(){
        synchronized(o2){
			try{Thread.sleep(1000);}
			catch (Exception e){}
            synchronized(o1){
            }
        }
    }
}

所以在开发过程中,synchronized最好不要嵌套使用

开发中怎么解决线程安全问题(考虑synchronized的低效率)

  1. 尽量使用局部变量代替“实例变量”和“静态变量”
  2. 如果必须使用实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了
  3. 如果不能使用局部变量,对象也不能创建多个,那么只能选择synchronized线程同步机制

守护线程

除了守护线程(如gc垃圾回收线程),其他都是==用户线程==,当所有的用户线程结束的时候,守护线程也就结束了

//守护线程的实现
class Test{
    Thread t=new BakcThread();
    t.setDaemon();//设置成守护线程,主线程(用户线程)结束,守护线程也就结束
    t.start();
}
class BackThread extends Thread{
    public void run(){
        int i=0;
        while(true){
            System.out.println(Thread.currentThread().getName()+"--->"+(++i));
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

定时器

定时器的作用:间隔特定的时间,执行特定的程序

首先可以想到sleep(),不过这种方法是最原始的,现在都是java.util.Timer,不过在实际的开发过程中,目前使用最多的是==Spring框架中提供的SpringTask框架==(其底层原理就是Timer),简单配置即可完成定时器的任务

import java.util.*;
import java.text.*;
public class Test{
    public static void main(String[] args) throws Exception{
        Timer timer=new Timer();
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        //Date firstTime=sdf.parse("2022-01-25 21:42:22 222");
		Date firstTime=new Date();
        timer.schedule(new TimerTask(){int i=0;
        						public void run(){++i;
                                    System.out.println("定时器第"+i+"次执行,执行时间										是"+sdf.format(new Date()));}
        },firstTime,1000*10);
    }
}

TimerTask是一个接口,任务接口,该接口实现了Runnable接口,也是一个线程

定时器的方法:==schedule(TimerTask task,Date date,long time)==

线程实现的第三种方法

实现Callable接口(jdk8新特性),这种方式==可以获取线程的返回值==,之前的实现Runnable和继承Thread的run方法的返回值都是void,如果希望完成任务的线程结束之后可以返回东西,就需要这种实现了Callable接口的线程实现方式。

实现过程见以下代码:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadThird {
    public static void main(String[] args) {
        FutureTask task=new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {//call方法相当于是run方法,只不过有返回值
                System.out.println("call method begin");
                Thread.sleep(1000*3);
                System.out.println("call method end");
                return 100+200;
            }
        });
        Thread t=new Thread(task);
        t.start();
        //那么如何获得线程的返回值呢?
        try {
            Object o=task.get();
            System.out.println(o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("aaa");//这里的代码需要等t线程结束才可以运行,所以get()方法会导致当前线程阻塞
    }
}

线程实现的第三种方法的优缺点:

  • 优点:可以获取线程的执行结果
  • 缺点:效率比较低,get()方法会让当前线程阻塞,效率较低

wait和notify(生产者和消费者模式)

wait方法和notify方法,不是线程的方法,是java中任何对象的方法,因为是Object的方法

wait()的作用:

Object o=new Object();
o.wait();//让正在o对象上活动的线程进入等待状态,无期限等待,直到唤醒为止

notify()的作用:o.notify() 唤醒o对象上等待的线程

notifyAll()的作用:唤醒o对象上所有等待的线程

==生产者和消费者模式==

注意:o.wait() 让o对象上活动的线程t进入等待状态,并释放掉t线程之前占有的o对象的锁

​ o.notify() 让o对象上等待的锁唤醒,但是不释放之前占有o对象的锁

//KuCun  对象是共享对象,对于生产者产品为偶数,或满100个就停止生产,对于消费者为奇数或为0时停止消费
public class ShengChanZheXiaoFeiZhe {
    public static void main(String[] args) {
        KuCun c=new KuCun(50);
        Thread  ts=new ThreadShengChan(c);
        Thread tx=new ThreadXiaoFei(c);
        ts.setName("生产者线程");
        tx.setName("消费者线程");
        ts.start();
        tx.start();
    }
}
class ThreadShengChan extends Thread{
    KuCun c;
    public ThreadShengChan(KuCun c ) {
        this.c = c;
    }
    @Override
    public void run() {
        while(true){
            synchronized (c){
                if(c.getC()%2==0||(c.getC()==100)){
                    try {
                        c.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                c.setC(c.getC()+1);;
                try {
                    Thread.sleep(1000*2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"当前库存---->"+c.getC());
                c.notifyAll();
            }
        }
    }
}
class ThreadXiaoFei extends Thread{
    KuCun c;

    public ThreadXiaoFei(KuCun c) {
        this.c = c;
    }
    @Override
    public void run() {
        while (true){
            synchronized (c){
                if (c.getC()==0||(c.getC()%3==0)){
                    try {
                        c.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                c.setC(c.getC()-2);
                try {
                    Thread.sleep(1000*2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"当前库存---->"+c.getC());
                c.notifyAll();
            }

        }
    }
}
class KuCun{
    private Integer c;
    public KuCun(Integer c){
        this.c=c;
    }
    public Integer getC() {
        return c;
    }
    public void setC(Integer c) {
        this.c = c;
    }
}

反射

反射机制相关的类

java.lang.reflect.*;

java.lang.Class 代表字节码文件 代表一个类型

java.lang.reflect.Method 代表字节码中的方法字节码

java.lang.reflect.Constructor 代表字节码中的构造方法字节码

java.lang.reflect.Field 代表字节码中的属性字节码

获取Class的三种方式

/*方式1
Class.forName()
1、静态方法
2、方法的参数是一个字符串,并且字符串是一个完整的类名
3、完整类名必须带有包名,不可省略
*/
class Test{
    public static void main(String[] args){
        Class c1=Class.forName("java.lang.String");//c1代表的就是java/lang/String.class
        Class c2=Class.forName("java.util.Date");
        Class c3=Class.forName("java.lang.System");
    }
}
/*方式2
java中任何一个对象都有一个方法:getClass()
*/
String s="abc";
Class c=s.getClass();//Class 内存储的内存地址是加载到方法区的String字节码文件
/*方式3
 java语言中任何一种类型,包括基本数据类型,他都有.class属性 如String.class
*/

通过反射实例化对象

Class c=Class.forName("java.lang.String");
String s=c.newInstance("aaa");//newInstance会调用User这个类的无参数构造方法,不会调用有参构造
//newInstance已经过时了 从jdk9开始

反射机制更加灵活,灵活在哪里呢?

配合属性配置文件和流进行实现

Spring SpringMVC MyBatis

Spring Structs Hibernate

Class.forName()

这个方法会导致类加载,从而会执行静态代码块的内容,所以以后只要希望一个类的静态代码块执行,其他代码一律不执行,可以使用Class.forName()

路径问题

以Module为根得到的相对路径==移植性==太差

这里说一种即使代码换了位置,路径也是通用的方式,前提是:这个文件必须在类路径下【==凡是在src下的路径都是类路径==】

String path=Thread.currentThread().getContextClassLoader().getResource("").getPath();
InputStream in=new FileInputStream(path);
Properties pro=new Properties();
pro.load(in);
in.close();
String className=pro.getProperty("className");

InputStream reader=Thread.currenThread().getContextClassLoader().getResourceAsStream("");
/*
getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象
getResource()是类加载器对象的方法,当前线程的类加载器默认从类的根路径加载资源
*/

资源绑定器

java.util包下提供了一个资源绑定器,便于获取属性配置文件的内容

同样的,这里的属性配置文件也要放在类路径下,资源绑定器只能绑定==.properties文件==

ResourceBundle bundle=ResourceBundle.getBundle("filename")  //文件名不加后缀properties
String className=bundle.getString("文件中的Key")

类加载器

类加载器就是专门负责加载类的命令/工具(ClassLoader)

JDK中自带的3中类加载器

  • 启动类加载器

    ​ 代码再开始执行之前,会将所有需要的类加载到JVM中,首先通过”启动类加载器“加载,加载的是==jre\lib\rt.jar==内的类。

  • 扩展类加载器

    ​ 如果启动类加载器加载不到的时候,就是用扩展类加载器,加载的是==jre\lib\ext==目录下的包

  • 应用类加载器

    	如果启动类加载器和扩展类加载器都加载不出来,就会启动应用类加载器,也就是在classpath下找对应的包资源
    

==双亲委派机制==

启动类加载器 String

扩展类加载器

应用类加载器 String(你自己写了一个String类,但是JVM加载不到)

==java中为了保证类加载的安全,使用了双亲委派机制,“父”是启动类加载器,“母”是扩展类加载器==,如果都加载不到,就去应用类加载器中加载,直到加载到为止

获取Field

//********************获取属性
//先获取整个类
Class class=Class.forName("类名");
class.getName();//获取完整类名
class.getSimpleName();//获取简类名
//获取类中所有的Field
Field[] fields=class.getFields();//获取的是所有public的属性
Field f=fields[0];
String fieldName=f.getName();

Field[] fields=class.getDeclaredFields();//获取所有属性
//*********************获取属性的类型   Field对象是包括修饰符列表的  如:pirvate String name;
 Class fieldType=f.getType();
 String fieldName=fieldType.getName();
//*********************获取属性的修饰符列表
String modifiersName=Modifier.toString(f.getModifiers());//getModifiers的返回值是int,实际是每个修饰符的代号,怎么将代号转换成字符串呢?使用到Modifier类中的静态方法------toString(int mod)即可

获取单个属性

//通过反射机制调用参数
Class c=Class.forName("className");
Object obj=c.newInstance();
Field noFiedl=c.getDeclaredField("no");
noField.set(obj,222);
noField.get(obj);//获取对应属性的值 如果属性的值是private修饰的 那么只能打破封装
//打破封装代码
noField.setAccessible(true);

Method(超级重点)

==可变长参数==:以一个静态方法为例

public static void main(String[] args){m(1);m(1,2);m(12,3,2);}
public static void m(int... args){System.out.println("m方法执行了!")}
//可变长参数只能出现在参数列表的最后一个,并且可变长参数一个参数列表里只能有一个
//可以把可变长参数看成  一个数组 它也有Length属性
public static void m1(String... args){
    for(int i=0;i<args.length;i++){
        System.out.println(args[i]);
    }
}

==如何反射Method==

Method[] method=Class.forName("").getDeclaredMethods();
Method m=method[0];
m.getReturnType().getSimpleName();//返回值类型
m.getName();//返回方法名
m.getParameterTypes();//获取方法的修饰符列表
//通过反射调用方法
Class c=Class.forName("");
Object obj=c.newInstance();
Method login=c.getDeclaredMethod("方法名",Class<?>... agrs)//这里传入的是参数的类型	
Object retValue=login.invoke(obj,"admin","123");
//反射方法四要素
//1、login方法
//2、obj对象
//3、admin 123 实参
//4、retValue 返回值

反射constructor

//通过反射机制创建对象  使用构造方法   newInstance()的调用有参构造
Class c=Class.forName("");
Constuctor con=c.getDelcaredConstructor(对应的形参类型);
Object obj=con.newInstance(对应的实参);

//newInstance()调用无参构造的正确方式
Object newObj=con.newInstance();

反射继承的父类和实现的父接口

Class stringClass=Class.forName("java.lang.String");
Class supoerClass=stringClass.getSuperclass();  //getSuperclass
//获取所有的接口
Class[] interfaces=stringClass.getInterfaces();

反射的缺点

noFiedl.setAccessible(true);容易打破封装,给不法分子留下机会

注解(annotation)

注解基本概念

/*
1、注解是一种引用数据类型,编译之后也生成xxx.class文件
2、定义注释的语法格式:
		[修饰符列表] @interface 注解类型名{}
3、注解怎么使用?  @注解类姓名
	注解可以出现在类上、属性上、方法上、变量上,还可以出现在注解上
*/
public @interface MyAnnocation{
    
}
@MyAnnocation
class Dosome{
    @MyAnnocation
    int a;
    @MyAnnocation
    public static void dosome(){}
    @MyAnnocation
    public void doOther(){@MyAnnocation int a;}
}
@MyAnnocation
interface Myinterface{}
@MyAnnocation
enum Season{}
@MyAnnocation
@interface OtherAnnocation{}

JDK自带的几个注解

java.lang报下的注解

  1. Deprceated 已过时

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
    public @interface Deprecated {
        String since() default "";
        boolean forRemoval() default false;
    }
    
    
  2. Override 方法覆盖

    ​ override这个注解是给编译器看的,和运行阶段无关,凡是带有这个注解的方法,编译阶段都要检查是否重写了父类的方法,如果没有编译器就会报错。

    //Override注解的源代码
    @Target(ElementType.METHOD)//这两个标注“注解类型”的“注解”称为元注解
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override{}
    //Target注解 用来标注“被标注注解”可以出现在哪些位置上
    
    //Retention注解 用来标注“被标注注解”最终保存到哪
    @Retention(RetentionPolicy.SOURCE)//表示该注解被保留在java源文件中
    @Retention(RetentionPolicy.CLASS)//表示该注解被保留在class文件中
    @Retention(RetentionPolicy.RUNTIME)//表示该注解被保留在class文件中,并且可以通过反射机制进行读取
    

注解中怎么定义属性

@interface MyAnnotation{
    String name() default "ss";//如果这里的属性名是value,并且只有一个属性,那么使用该注解的时候,赋值可以省略属性名
    String[] ss();
    Sesson[] season();
}
public class Test{
    @MyAnnotation(name="sss",ss="ssss")//数组只有一个元素{}可以省略
    public void doSome(){}
}

注解的属性类型可以是:byte short int lang float double boolean char ==String Class 枚举类型==,以及这些类型的数组形式

反射注解

import java.lang.annotation.*;
public class ReflectAnnotation{
    public static void main(String[] args) throws Exception{
        Class c= Class.forName("com.wzx.reflect.MyAnnotation");
        System.out.println(c.isAnnotationPresent(MyAnnotation.class));//看看这个类上是否有注解
    }
}
@MyAnnotation
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{}


//反射得到注解对象
Class c= Class.forName("com.wzx.reflect.MyAnnotation");
System.out.println(c.isAnnotationPresent(MyAnnotation.class));
if (c.isAnnotationPresent(MyAnnotation.class)){
    MyAnnotation myAnnotation= (MyAnnotation) c.getAnnotation(MyAnnotation.class);
    System.out.println(myAnnotation);
}
//注解的属性可以直接    引用点  



//通过反射获取注解对象属性的值
public class Test{
    @MyAnnotation(username="admin",password="123")
    public void doSome(){}
    public static void main(String args){
       	//获取Test的doSome方法上的注解信息
        Class c=Class.forName("Test");
        Method method=c.getDeclaredMethod("doSome");
        if(method.isAnnotationPresent(MyAnnotation.class)){
            System.out.println(method.Annotation.username+method().Annotation.password());
        }
    }
}

注解在开发中的作用(举例说明)

需求:假设有一个叫id的注解,要求这个注解只能出现在类上,并且类有这个注解的时候必须有一个int类习惯的id,如果没有就报错,如果有则正常执行!

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
public class AnnotationTest {
    public static void main(String[] args) throws Exception{
        try {
            panDuan("com.wzx.reflect.User1");
        }catch (MyException me){
            System.out.println(me.getMessage());
        }
    }
    public static void panDuan(String add) throws ClassNotFoundException, MyException {
        Class userClass=Class.forName(add);
        if (userClass.isAnnotationPresent(ID.class)){
            //当一个类有@ID注解的时候  要判断这个类里是否有int id
            Field[] fields=userClass.getDeclaredFields();
            for (Field field:fields){
                if ("id".equals(field.getName())&&("int".equals(field.getType().getSimpleName()))){
                    System.out.println("语法正确");
                    return;
                }
            }
            throw new MyException("语法错误");
        }
    }
}
class MyException extends Exception{
    public MyException() {
    }
    public MyException(String message) {
        super(message);
    }
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface ID
}
@ID
class User1{
    int id;
    String name;
    String password;
}

注解在开发中到底有什么用?

常见运行时异常

类型转换异常 java.lang.ClassCastException

空指针异常 java.lang.NullPointException

数组越界异常 java.lang.ArrayIndexOutOfBoundsException

数字格式化异常 java.lang.NumberFormatException

分母为零异常 java.lang.ArithmeticException

集合内容发生变化,没有重新获取iterator时 java.util.ConcurrentModificationException ==因为迭代器类似一个照相机,看到的都是集合获取迭代器时候的集合内容==

==和equals的区别

==是对内存地址进行对比

equals是对对象的内容进行对比

常用的方法

数组拷贝:==System.arrayCopy==(==源目标==,==源开始下标==,==目标==,==目标开始下表==,==拷贝长度==)

获取当前毫秒数(自1970年0000 至今):==System.currentTimeMillis()==;

建议启动垃圾回收:==System.gc()==

推出JVM:==System.exit(0)==

==Arrays:==

  • 排序:Arrays.sort(int[] a);

  • 二分查找:Arrays.binarySearch(int[] a,对应元素); 如果返回结果是-1说明不存在改元素

==String:==

  • char charAt(int index ) "中国人".charAt(1) ----> "国"
  • int compareTo(String anotherString) 两个字符串相等即为0 其他看大小 差值
  • boolean contains(CharSequence s)
  • boolean endWith(String suffix)
  • startsWith
  • boolean equalsIgnoreCase(String anotherString) 判断两个字符串是否相等,并且不区分大小写
  • String toLowerCase() 转换到小写
  • toUpperCase()
  • String trim() 去除字符串前后空白
  • byte[ ] getByte(String s) 将字符串转成byte数组
  • char[ ] toCharArray() 将字符串转成char数组
  • int indexOf(String s) 判断s子字符串在某个串中第一次出现处的索引
  • lastIndexOf
  • boolean isEmpty() 判断字符串是否为空
  • int leagth() 判断数组长度是length属性,判断字符串长度是length()方法
  • String replace(charSquence target,charSquence replacement)
  • String[ ] split(String regex) "1998-02-16".split("-");
  • String subString(int beginIndex) 截取字符串
  • String subString(int beginIndex,int endIndex) 截取字符串 包括beginIndex 不包括endIndex
  • ==静态方法== String.valueOf(非字符串) 将非字符串转换成字符串

==Integer==

  • static int parseInt(String s) 字符串转换成int 所以也有parseDouble

==引用.valueOf()== 将括号内的类型转换成 对应的包装类

常用类

String

字符串从出生到死亡都是不能变的

==在JDK中双引号括起来的字符串都是放在方法区的字符串常量池的==为了提高执行效率

String s=new String("xy");
//双引号的字符串都是在方法区的字符串常量池中,这里是在堆中创建对象,并且指向字符串常量池中的"xy"

==垃圾回收器是不会释放常量的==

String类重写了equals方法,所以对于字符串对象的相同比较直接使用equals方法即可

//以下程序创建了几个对象
public class Test{
    public static void main(String[] args){
        String a=new String("aaa");
        String b=new String("aaa");
    }
}
//以上程序一共创建了3个对象  分别是一个字符串常量池对象和两个堆String对象

String的构造方法可以使用==byte[],char[]==

StringBuffer

思考:在实际开发中,如果需要频繁对字符串进行拼接,有什么问题?

​ ==在对字符串进行 拼接得时候,每一次拼接都会产生新字符串,从而方法区占用内存,造成空间浪费==

如果需要频繁进行字符串拼接,要使用java自带得类:java.lang.StringBuffer和java.lang.StringBuilder

  • ==StringBuffer(线程安全的)==

    • 初始化容量是16(字符串缓冲对象)

    • StringBuffer的底层是byte数组 ==String的底层也是byte数组,不过是被final修饰了的byte数组==

    • StringBuffer中有一个方法,可以追加字符串 即==append== 这个append方法已经被==重载==可以传入很多参数类型

    • ==如何优化Stringbuffer的性能==

      ​ 在创建StringBuffer的时候尽可能给定一个初始化容量,最好减少底层数组的扩容次数

  • ==Stringbuilder==

    • 初始容量也是16

基础类型对应的8个包装类

基本数据类型 包装类型
byte java.lang.Byte
short java.lang.Short
int java.lang.Integer
long java.lang.Long
float java.lang.Float
double java.lang.Double
boolean java.lang.Boolean
char java.lang.Character

八种包装类型中,Boolean和Character的父类是java.lang.Object,其他的都是java.lang.Number

Number类有多个方法,Number类是一个抽象类,不可以实例化对象

  • intValue floatValue doubleValue byteValue longValue shortValue ==这几个方法都是负责拆箱的==

==基本数据类型 ---> 引用数据类型(装箱)==

==引用数据类型 ---> 基本数据类型(拆箱)==

==Integer==

Integer的构造方法可以装箱String public Integer(String s)

Integer的最大值和最小值 Integer.MAX_VALUE 最小值Integer.MIN_VALUE

==自动装箱== Integer i=10; ==自动拆箱== int a=i;

有了自动拆箱之后 Number类中的方法就用不着了

==Integer类型的i +1 会自动将Integer转换成int类型, 自动拆箱机制== 运算才会拆箱 ==不会拆箱

==Java为了提高执行效率,将-128----127之间的包装对象提前创建好,挡在了"整数型常量池"中==这个整数型常量池在Integer类加载的时候就会实现

==缓存== 优点:效率高 缺点:耗费内存 大型项目的重要优化手段就是cache缓存机制

//String int 和Integer之间的转换
class Test
{
	public static void main(String[] agrs){
		//String  ---->  int
		String s1="100";
		int i1=Integer.parseInt(s1);
		//int ----->String
		String s2=String.valueOf(i1);
		String s3=i1+" ";
		//int -->Integer
		Integer ie=i1;
		//Integer-->int
		int i2=ie;
		//String--->Integer
		Integer ie1=Integer.valueOf(s1);
		//Integer--->String
		String s4=String.valueOf(ie1);
	}
}

日期相关类

java.util.Date

SimpleDateFormat 专门用于日期格式化的类 java.text包下的

import java.util.Date;
import java.text.SimpleDateFormat;
class Test
{
	public static void main(String[] args){
		Date nowTime=new Date();
		SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
		System.out.println(sdf.format(nowTime));
	}
}  //Date--->String

以上是将日期对象转换成了字符串

如何将字符串转化成对应的Date对象 使用==SimpleDateFormat.parse==方法

String time="2009-12-30 08:08:08 888";
SimpleDateFormate sdf=new SimpleDateFormate("yyyy-MM-dd hh:MM:ss SSS");
Date d=sdf.parse(time);
//String ---> Date

获取自1970年1月1日 00:00:00 000到当前系统时间的总毫秒数

System.currentTimeMillis();
Date d=new Date(System.currentTimeMillis());
//  毫秒  --->  Date

数字相关类

数字格式化

java.text.DecimalFormat专门负责数字格式化

  • #代表任何数字 ,代表千分位 . 代表小数点 0代表不够的时候补0

数字格式化和日期格式化

DecimalFormat df=new DecimalFormat("###,###.0000")
    df.format();

==BigDecimal==属于大数据,精度极高,用在财务软件当中

BigDecimal v1=new BigDecimal(100);
BigDecimal v2=new BigDecimal(200);
System.out.println(v1.add(v2));
//BigDecimal的toString重写了    subtract乘法   divide除法

Random

Random random=new Random();//创建随机数对象
int num=random.nextInt();//随机产生一个int类型取值范围内的数字  还有重载的nextInt()形参输入对应的int边界,范围不包含形参中
System.out.println(num);

Enum

为什么要用枚举,因为boolean类型无法再满足需求,可能性在两个之上

==如果要判断的结果有多种可能性,那么建议使用枚举==

  • ==枚举,一枚一枚可以列举出来的,才建议使用枚举类型==
  • ==枚举编译之后生成的也是class文件==
  • ==枚举是一种引用数据类型==
  • ==枚举中的每个值都可以看作是常量==
class Test{
    public static Result divide(int a,int b){
        try{
            int c=a/b;
            return Result.SUCCESS;
        }catch(Exception e){
            return Result.FAIL;
        }
    } 
}
enum Result{
    SUCCESS,FAIL
}
木兰宽松许可证, 第2版 木兰宽松许可证, 第2版 2020年1月 http://license.coscl.org.cn/MulanPSL2 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: 0. 定义 “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 “法人实体”是指提交贡献的机构及其“关联实体”。 “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 1. 授予版权许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 2. 授予专利许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 3. 无商标许可 “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 4. 分发限制 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 5. 免责声明与责任限制 “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 6. 语言 “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 条款结束 如何将木兰宽松许可证,第2版,应用到您的软件 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; 3, 请将如下声明文本放入每个源文件的头部注释中。 Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details. Mulan Permissive Software License,Version 2 Mulan Permissive Software License,Version 2 (Mulan PSL v2) January 2020 http://license.coscl.org.cn/MulanPSL2 Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: 0. Definition Software means the program and related documents which are licensed under this License and comprise all Contribution(s). Contribution means the copyrightable work licensed by a particular Contributor under this License. Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. Legal Entity means the entity making a Contribution and all its Affiliates. Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. 1. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. 2. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. 3. No Trademark License No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. 4. Distribution Restriction You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. 5. Disclaimer of Warranty and Limitation of Liability THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 6. Language THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. END OF THE TERMS AND CONDITIONS How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; iii Attach the statement to the appropriate annotated syntax at the beginning of each source file. Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details.

简介

动力节点-老杜yyds 展开 收起
README
MulanPSL-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/wzx1914/java-learning-notes-markdown.git
git@gitee.com:wzx1914/java-learning-notes-markdown.git
wzx1914
java-learning-notes-markdown
java学习笔记_markdown
master

搜索帮助