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
}

空文件

简介

动力节点-老杜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

搜索帮助