Java面向对象程序设计(OOP)

1、面向对象程序设计(OOP)


1.1、面向过程&面向对象

面向过程思想

  • 步骤清晰简单,第一步做什么,第二步做什么…(线性思维
  • 面向过程适合处理一些较为简单的问题

面向对象编程

  • 物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索
  • 面向对象适合处理复杂的问题,适合处理需要多人协作的问题!

对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理。

1.2、什么是面向对象?

  • 面向对象编程(Object-Oriented Programming, OOP)
  • 面向对象编程的本质就是:以类的方式组织代码,以对象的形式组织(封装)数据。

抽象:编程思想!

面向对象三大特征

  • 封装
  • 继承
  • 多态

从认识论角度考虑是先有对象后有类。类,是抽象的,它是对象的抽象。而对象,是具体的事物。

从代码运行角度考虑是先有类后有对象。类是对象的模板

1.3、类与对象的关系

  • 类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但是并不能代表某一个具体的事物(类是一个模板)
    • 例:动物、植物、手机、电脑
    • Person类、Pet类、 Car类等,这些类都是用来描述/定义某一类具体的事物应该具备的特点和行为
  • 对象是抽象概念的一个具体实例
    • 张三就是人类的一个具体实例,张三家里的旺财就是狗狗的一个具体实例
    • 能够体现出特点,展现出功能的是具体的实例,而不是一个抽象的概念

类:

静态的属性 属性

动态的行为 方法

1.4、属性的定义

属性(property):字段(Field) 成员变量

默认初始化:

数字:0 0.0

char:u0000

boolean:false

引用:null

语法:修饰符 属性类型 属性名 = 属性值;

public String name = "张三";
public int name = 18;
public boolean adult = false;	//是否是成年人

1.5、创建与初始化对象

  • 使用new关键字创建对象

  • 使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象,进行默认的初始化,以及对类中构造器的调用

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

    1. 方法名和类的名字相同
    2. 没有返回类型,也不能写void

    构造方法的作用:主要用来初始化对象的值

    注:一旦定义了有参构造,系统不再提供默认无参构造方法,无参就必须显示定义

    //一个类即使什么也不行,它也会存在一个方法(无参构造)
     //显示定义构造器
    public Person(){
          
          
    	
    }
    
    //实例化初始值	(有参构造)
    public Person(String name, int age) {
          
          
        this.name = name;
        this.age = age;
    }
    

    this关键字是对一个对象的默认引用(当前类的)

    6.5、创建对象内存

对象的引用:对象是通过引用来操作的:栈–>堆

2、面向对象三大特征之一封装


2.1、什么是封装?

封装:将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问(隐藏内部细节,保留对外接口)

2.2、为什么需要封装?

我们程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合:仅暴露少量的方法给外部使用。

记住这句话就够了:属性私有,get/set,方法公开

2.3、封装的使用

封装的步骤:

  1. 修改属性的可见性 设为private
  2. 创建公有的getter/setter方法 用于属性的读写
  3. 在getter/setter方法中加入属性控制语句 对属性值的合法性进行判断
//属性私有
private String name;	//姓名
private int age;	//年龄

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

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

public int getAge() {
    
    
    return age;
}

public void setAge(int age) {
    
    
    if (age>100||age<0){
    
    
        this.age = 18;
    }else{
    
    
        this.age = age;
    }
}

封装的好处

  1. 提高程序的安全性,保护数据
  2. 隐藏代码的实现细节,保留对外接口(get/set)
  3. 增强系统的可维护性

3、面向对象三大特征之一继承


3.1、什么是继承?

继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模

  • 继承是类和类之间的一种关系,除此之外,类和类之间的关系还有依赖、组合、聚合等
  • 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用extends来表示
  • 子类的父类之间,从意义上讲应该具有"is a"的关系

3.2、为什么要使用继承?

未使用继承前


将重复的代码抽取到父类中

使用继承优化后

3.3、继承的使用?

继承是代码重用的一种方式

将子类共有的属性和行为放到父类中

extends的意思是"扩展"。子类是父类的扩展

//在Java中,所有的类都直接或间接继承Object
//Person 人 父类(基类)
public class Person {
    
    
    protected String name;
    protected int age;
    protected String gender;

    public String getName() {
    
    
        return name;
    }

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

    public int getAge() {
    
    
        return age;
    }

    public void setAge(int age) {
    
    
        this.age = age;
    }

    public String getGender() {
    
    
        return gender;
    }

    public void setGender(String gender) {
    
    
        this.gender = gender;
    }

    public void sayHi(){
    
    
        System.out.println("你好!");
    }
}


//子类继承了父类,就会拥有父类的全部方法、属性
//学生 is 人    派生类(子类)
public class Student extends Person{
    
    
    private int StudentNo;

    public int getStudentNo() {
    
    
        return StudentNo;
    }

    public void setStudentNo(int studentNo) {
    
    
        StudentNo = studentNo;
    }
}


public class Teacher extends Person {
    
    
    private String major;   //所教专业

    public String getMajor() {
    
    
        return major;
    }
    
    public void setMajor(String major) {
    
    
        this.major = major;
    }
}

注:子类不能继承父类的private成员、构造方法、子类与父类不在同包使用默认访问权限的成员

Java中类只有单继承,没有多继承

使用final修饰的类不能再被继承

3.4、super关键字

super关键字可以在子类访问父类方法,进而完成在子类的复用,super代表父类对象

注:

  • super调用父类的构造方法,必须在构造方法的第一个
  • super必须只能出现在子类的方法或者构造方法中!
  • super和this不能同时调用构造方法

super VS this

代表的对象不同:

  • this 表示当前对象引用,调用本类(包括继承)的属性、方法、本类构造方法
  • super:代表父类对象的引用,调用父类的属性、方法、本类构造方法

前提:

  • this:没有继承也可以使用
  • super:只能在继承条件才可以使用

构造方法:

  • this():本类的构造
  • super():父类的构造

注:this与super都要求在构造方法的首行,所以两者不能同时使用

3.5、方法的重写(Override)

方法重写的规则:

  • 前提条件:需要有继承关系,在不同类中,子类重写父类的方法!
  • 方法名必须相同
  • 参数列表必须相同
  • 返回值类型相同或者是其子类
  • 访问修饰符不能严于父类

重写:子类的方法和父类必须一致;方法体不同

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

//子类
public class A extends B{
    
    
    @Override
    public void test() {
    
    
        System.out.println("A=>test");
    }
}

public class Test {
    
    
    //静态方法和非静态的方法区别很大!
    //静态方法:方法的调用只和左边,定义的数据类型有关!
    //非静态:重写
    public static void main(String[] args) {
    
    
        A a = new A();
        a.test();	//A=>test

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

为什么需要重写?

父类的功能,子类不一定需要,或者不一定满足

访问修饰符


方法重载与方法重写

4、面向对象三大特征之一多态


4.1、什么是多态

生活中的多态:同一种操作,由于条件不同,产生的结果也不同

程序中的多态:同一个引用类型,使用不同的实例而执行不同操作(父类引用指向子类对象),从而产生多种形态

4.2、多态的使用

多态存在的条件:

  1. 有继承关系
  2. 子类重写父类方法
  3. 父类引用指向子类对象 Father f1 = new Son(); (自动类型转换)

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

父类和子类,有联系,类型装换异常!ClassCastException

不能被重写的方法:

  • static方法(属于类,不属于实例)
  • final 常量
  • private方法

4.3、instanceof关键字

多态的应用

场景一:使用父类作为方法形参实现多态,使方法参数的类型更为宽泛

package com.zhang.polymorphic;

public class Animal {
    
    
    private String name;

    public String getName() {
    
    
        return name;
    }

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

    public void eat() {
    
    
        System.out.println(this.name + "正在吃!");
    }
}
package com.zhang.polymorphic;

public class Cat extends Animal{
    
    
    @Override
    public void eat() {
    
    
        System.out.println("猫咪正在吃猫粮!");
    }

    public void run() {
    
    
        System.out.println("猫咪正在跑!");
    }
}
package com.zhang.polymorphic;

public class Bird extends Animal {
    
    
    @Override
    public void eat() {
    
    
        System.out.println("鸟正在捕食!");
    }

    public void run(){
    
    
        System.out.println("鸟正在飞翔!");
    }
}
package com.zhang.polymorphic;

/**
 * 主人类
 */
public class Master {
    
    
    private String name;

    public String getName() {
    
    
        return name;
    }

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


    /**
     * 喂猫咪
     *
     * @param cat
     */
    //public void feed(Cat cat) {
    
    
    //    System.out.println(this.name + "喂食");
    //    cat.eat();
    //}

    /**
     * 喂鸟
     */
    //public void feed(Bird bird) {
    
    
    //    System.out.println(this.name + "喂食");
    //    bird.eat();
    //}

    //使用多态优化
    public void feed(Animal animal) {
    
    
        System.out.println(this.name + "正在喂食!");
        animal.eat();
    }

    /**
     * 购买动物
    */
    public Animal buy(int type) {
    
    
        Animal animal = null;
        if (type == 1) {
    
    
            animal = new Cat();
        } else if (type == 2) {
    
    
            animal = new Bird();
        }
        return animal;
    }
}
package com.zhang.polymorphic;

public class TestMaster {
    
    
    public static void main(String[] args) {
    
    
        Master master = new Master();
        master.setName("夏明");

        Cat cat = new Cat();
        Bird bird = new Bird();
        //喂食
        master.feed(cat);
        master.feed(bird);
    }
}

场景二:使用父类作为方法的返回值实现多态,使方法可以返回不同子类对象

package com.zhang.polymorphic;

/**
 * 主人类
 */
public class Master {
    
    
    private String name;

    public String getName() {
    
    
        return name;
    }

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


    /**
     * 喂猫咪
     *
     * @param cat
     */
    //public void feed(Cat cat) {
    
    
    //    System.out.println(this.name + "喂食");
    //    cat.eat();
    //}

    /**
     * 喂鸟
     */
    //public void feed(Bird bird) {
    
    
    //    System.out.println(this.name + "喂食");
    //    bird.eat();
    //}

    //使用多态优化
    public void feed(Animal animal) {
    
    
        System.out.println(this.name + "正在喂食!");
        animal.eat();
    }

    /**
     * 购买动物
     */
    public Animal buy(int type) {
    
    
        Animal animal = null;
        if (type == 1) {
    
    
            animal = new Cat();
        } else if (type == 2) {
    
    
            animal = new Bird();
        }
        return animal;
    }
}
package com.zhang.polymorphic;

import java.util.Scanner;

public class TestMaster2 {
    
    
    public static void main(String[] args) {
    
    
        Scanner input = new Scanner(System.in);
        System.out.println("欢迎来到xxx宠物市场");
        System.out.println("1.猫咪\t2.鸟");
        System.out.print("请输入:");
        int choice = input.nextInt();
        Master master = new Master();
        Animal animal = master.buy(choice);
        if (animal != null) {
    
    
            System.out.println("购买成功!");
        } else {
    
    
            System.out.println("购买失败!");
        }
    }
}

instanceof(强制类型转换)引用类型,判断一个对象是什么类型

package com.zhang.polymorphic;

import java.util.Scanner;

public class TestMaster2 {
    
    
    public static void main(String[] args) {
    
    
        Scanner input = new Scanner(System.in);
        System.out.println("欢迎来到xxx宠物市场");
        System.out.println("1.猫咪\t2.鸟");
        System.out.print("请输入:");
        int choice = input.nextInt();
        Master master = new Master();
        Animal animal = master.buy(choice);
        if (animal != null) {
    
    
            System.out.println("购买成功!");
            if (animal instanceof Cat) {
    
    
                ((Cat) animal).run();
            } else if (animal instanceof Bird) {
    
    
                ((Bird) animal).run();
            }
        } else {
    
    
            System.out.println("购买失败!");
        }
    }
}

多态

  • 子类转换为父类——向上转型,自动进行类型转换,父类引用仅可调用父类所声明的属性和方法,不可调用子类独有的属性和方法
  • 父类转换为子类——向下转型,通常结合instanceof运算符进行强制类型转换
  • 方便方法的调用,减少重复的代码!简洁

4.4、抽象类

//abstract  抽象类:类 extends:单继承~  (接口可以多继承)
public abstract class Action {
    
    
   //abstract 抽象方法,只有方法名字,没有具体的方法实现
   //约束~换而言之就是有人帮我们实现
   public abstract void doSomething();
}

//抽象类的所有方法,只要其它子类继承了它,都必须要实现它的方法~ 除非子类也是抽象的
public class A extends Action{
    
    
   @Override
   public void doSomething() {
    
    

   }
}

抽象类的特点:

  • 抽象类不能被实例化(只能靠子类去实现它;约束!)
  • 抽象类也可以编写普通方法

抽象方法的特点:

  • 抽象方法必须在抽象类中
  • 抽象方法没有方法体
  • 抽象方法必须在子类中被实现,除非子类是抽象类

抽象类存在的意义:提高开发效率,提高程序的可扩展性

5、接口


5.1、什么是接口?

定义接口使用interface关键字

//所有方法默认都是:public abstract
public interface MyInterface {
    
    
    public void foo()//其他方法
}

接口:只有规范!自己无法写方法~专业的约束!约束和实现分离:面向接口编程

接口的本质是契约,就像我们社会的法律一样,制定好大家都遵守

OOP的精髓,是对对象的抽象,最能体现这一点就是接口。为什么讨论设计模式,都只针对能力的语言(比如java、c++、c#等),就是因为设计模式所研究的,实际上就是如何合理的去抽象(架构师)

微观概念:接口表示一种能力
接口的定义:代表了某种能力
方法的定义:能力的具体要求

接口与抽象类的异同

相同点:

  • 可编译成字节码文件
  • 不能创建对象
  • 可以作为引用类型
  • 具备Object类中所定义的方法

不同点:

  • 所有的属性都是公开静态常量,隐式使用public static final修饰
  • 所有的方法都是公开抽象方法,隐式使用public abstract修饰
  • 没有构造方法、动态代码块、静态代码块

5.2、面向接口编程

程序设计时:

  • 关心实现类有何能力,而不关心实现细节
  • 面向接口的约定而不考虑接口的具体实现

例:

  • 防盗门是一个门 (is a的关系)

  • 防盗门是有一个锁 (has a的关系)那么上锁、开锁就是它的能力

5.3、接口的使用

  1. 定义接口

    //interface 定义的关键字  接口都需要有实现类
    public interface UserService {
          
          
        //接口中的所有定义其实都是抽象的  public abstract
        //定义一些方法,让不同的人实现
        void add(String name);
        void del(String name);
        void update(String name);
        void check(String name);
    }
    
    
    public interface TimeService {
          
          
        void time();
    }
    
  2. 实现接口

    //类 可以实现接口!使用implement关键字实现接口
    //实现类接口的类,就需要重写接口中的方法
    //多继承~利用接口实现伪多继承!  可为类扩充多种能力
    public class UserServiceImpl implements UserService,TimeService{
          
          
    
        @Override
        public void add(String name) {
          
          
    		
        }
    
        @Override
        public void del(String name) {
          
          
    
        }
    
        @Override
        public void update(String name) {
          
          
    
        }
    
        @Override
        public void check(String name) {
          
          
    
        }
    
        @Override
        public void time() {
          
          
    		
        }
    
  3. 使用接口

    public class Test {
          
          
        public static void main(String[] args) {
          
          
            UserService userService = new UserServiceImpl();
            userService.add("小张");
        }
    }
    

常见的关系

类与类:

  • 单继承
  • extends 父类名称

类与接口:

  • 多实现
  • implements 接口名称1,接口名称2,接口名称n

回调原理

5.4、接口的特性

  • 接口不可以被实例化,接口中没有构造方法
  • 接口中的方法都是抽象方法(public abstract)
  • 接口中的变量都是静态常量(public static final)
  • 实现类可以实现多个接口
  • 实现类必须实现接口的所有方法,除非此类为抽象类
  • 实现接口中的抽象方法是,访问修饰符必须是public

使用接口的好处:

  • 程序的耦合度降低
  • 更自然的使用多态
  • 设计与实现完全分离
  • 更容易搭建程序框架
  • 更容易更换具体实现

5.5、常量接口、标记接口

常量接口将多个常用于表示状态或固定值的变量,以静态常量的形式定义在接口中统一管理,提高代码可读性。

package com.zhang;

/**
 * 常量接口
 */
public interface ConstInterface {
    
    
    String Const1 = "value1";
    String Const2 = "value2";
    String Const3 = "value3";
}
package com.zhang;

public class TestConstInterface {
    
    
    public static void main(String[] args) {
    
    
        if (ConstInterface.Const1 == "value1") {
    
    
            System.out.println("这是value1");
        }
    }
}

标记接口中没有包含任意成员,仅仅用作标记

  • Serializable(可序列化的)
  • Cloneable(可克隆的)

6、内部类


6.1、什么是内部类?

内部类就是在一个类的内部又定义了一个类,比如,A类中定义了一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了

成员内部类、静态内部类、局部内部类、匿名内部类

public class Outer {
    
    
    private int id = 100;

    public void out(){
    
    
        System.out.println("这是外部类的方法");
    }

    //成员内部类  添加static就是静态内部类
    public class Inner {
    
    
        public void in(){
    
    
            System.out.println("这是内部类的方法");
        }
    }

    //局部内部类
    public void method(){
    
    
        class  Inner{
    
    

        }
    }
}

//一个java类中可以有多个class类,但只能有一个public class
class A{
    
    

}

public class Test {
    
    
    public static void main(String[] args) {
    
    
        //没有名字初始化类,不用将实例保存到变量中  匿名内部类
        new Apple().eat();
        new UserService(){
    
    
            @Override
            public void hello() {
    
    
				
            }
        };
    }
}

注:不推荐使用

7、异常机制(Exception)


7.1、什么是异常?

生活中的异常:

正常情况下,小张每天开车去上班,耗时大约30分钟


但是,异常情况迟早要发生!

程序中的异常:

异常指程序运行中出现的不期而至的各种状况,如:文件找不到、网络连接失败、接收非法参数等

异常处理的必要性:任何程序都可能存在大量的未知问题、错误;如果不对这些问题进行正确处理,则可能导致程序的中断,造成不必要的损失

7.2、异常的分类

检查时异常(CheckedException):就是编译器要求你必须处理的异常。比如我们在编程某个文件的读/写时,编译器要求你必须要对某段代码try…catch… 或者 throws exception,这就是检查异常,简单的来说,你代码还没有运行,编码器就会检查你的代码,对可能出现的异常必须做出相对的处理。(比如当文件不存在时…)

运行时异常(RuntimeException):运行时异常是不需要捕获的,程序员可以不去处理,当异常出现时,虚拟机会处理。常见的运行时异常有空指针异常等

错误error:错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的

7.3、异常体系结构

  • Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有错误和异常的超类

  • 在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception

    Error:

  • Error对象是由Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关

  • Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError(内存溢出)这些异常发生是,Java虚拟机(JVM)一般会选择线程终止!(不能手动处理)

Exception

在Exception分支中有一个重要的子类RuntimeException(运行时异常)

Exception 异常层次结构的父类
ArithmeticException 算术异常情形,如以零作除数
ArrayIndexOutOfBoundsException 数组下标越界
NullPointerException 尝试访问 null 对象成员
ClassNotFoundException 找不到类等异常,该类为不检查异常,程序中可以选择捕获,也可以不处理
IllegalArgumentException 方法接收到非法参数
ClassCastException 对象强制类型转换出错
NumberFormatException 数字格式转换异常,如把"abc"转换成数字
MissingResourceException 丢失资源
StackOverflowError 栈溢出异常
ConcurrentModificationException 并发修改异常

这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;

ErrorException的区别:

Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;

Exception通常情况下是可以被程序处理的,并且在程序中应尽可能的去处理这些异常

异常的传递

  • 异常的传递:按照方法的调用链反向传递,如始终没有处理异常,最终会由JVM进行默认异常处理(打印堆栈跟踪信息),并中断程序运行。

7.4、异常处理

Java的异常处理是通过5个关键字来实现的:try、catch、 finally、throw、throws

捕获异常:

try------>执行可能产生异常的代码

catch------>捕获异常,并处理

finally------>无论是否发生异常,代码总能执行

public class Test {
    
    
    public static void main(String[] args) {
    
    
        int a = 10, b = 0;
        //假设要捕获多个异常:从小到大
        try {
    
       //try 监控区域  可能出现异常的代码
            System.out.println(a / b);
        } catch (ArithmeticException ex) {
    
        //catch(想要捕获的异常类型!) 捕获异常
            System.out.println("除数不能为0!");
            System.exit(1);
        } catch (Error ex) {
    
    
            System.out.println("Error");
            return;
        } catch (Exception ex) {
    
    
            System.out.println("Exception");
        } catch (Throwable ex) {
    
    
            System.out.println("Throwable");
        } finally {
    
      //处理善后工作 最终都会执行,除非手动退出JVM
            System.out.println("程序结束!");
        }

        //finally,可以不要finally,假设I/O,资源,关闭,就需要使用finally了
    }
}

注:

  • 排列catch语句的顺序:先子类后父类
  • 发生异常时按顺序逐个匹配
  • 只执行第一个与异常类型匹配的catch语句
  • finally根据需求可写或不写

存在return的try-catch-finally块的执行顺序:

try(产生异常对象) -----> catch(异常类型匹配) -----> finally(执行finally块) -----> return (执行return,退出方法)

声明异常:

throws------>声明方法可能要抛出的各种异常

public static void sub(int a, int b) throws ArithmeticException {
    
    
    if (b == 0) {
    
    
        throw new ArithmeticException();    //throw 主动抛出异常,一般在方法中使用
    }
    System.out.println(a / b);
}

//方式一:调用者,处理异常
public static void main(String[] args) {
    
    
    try {
    
    
        sub(6,0);
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
}

//方式二:调用者,继续声明异常
public static void main(String[] args) throws Exception {
    
    
    sub(6,0);
}

如果在一个方法体中抛出了异常,如何通知调用者?

  1. 声明异常,多个异常用逗号隔开
  2. 方式一:调用者,处理异常;
  3. 方式二:调用者,继续声明异常;

使用原则:底层代码向上声明或者抛出异常,最上层一定要处理异常,否则程序中断

抛出异常:

throw------>手动抛出异常 语法:throw 异常对象

public class Test {
    
    
    public static void main(String[] args) throws Exception {
    
    
        Scanner input = new Scanner(System.in);
        System.out.print("请输入大于100的数字:");
        int num = input.nextInt();
        if (num < 100) {
    
    
            throw new Exception("输入的数字必须大于100");	//手动抛出异常
        } else {
    
    
            System.out.println("num值为:" + num);
        }
    }
}

7.5、自定义异常

使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可!

使用自定义异常类,大体分为以下几个步骤:

  1. 创建自定义异常类,继承Exception类
  2. 在方法中通过throw关键字抛出异常对象
  3. 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;或者在方法的声明通过throws关键字指明要抛出给方法的调用者的异常,继续下一步操作
  4. 在出现异常方法的调用者中捕获并处理异常
package com.zhang;

public class Person {
    
    
    private String gender;

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

    public Person() {
    
    
    }

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

    public String getGender() {
    
    
        return gender;
    }

    public void setGender(String gender) throws Exception {
    
    
        if (gender.equals("男") || gender.equals("女")) this.gender = gender;
        else throw new GenderException("性别不符合要求!");
    }
}
package com.zhang;

/**
 * 自定义异常类
 * (1)继承Exception或子类
 * (2)添加构造方法
 */
public class GenderException extends Exception {
    
    
    public GenderException() {
    
    
    }

    public GenderException(String message) {
    
    
        System.out.println("GenderException:" + message);
    }
}
package com.zhang;

public class TestPerson {
    
    
    public static void main(String[] args) {
    
    
        Person xiaozhang = new Person();
        try {
    
    
            xiaozhang.setGender("男1");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        System.out.println(xiaozhang);
    }
}

//GenderException:性别不符合要求!

注:子类中的方法,不能抛出比父类更多、更宽的检查时异常

经验:

  • 处理运行时异常时,采用逻辑去合理的规避同时辅助try-catch处理
  • 在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
  • 对于不确定的代码,也可以加上try-catch,处理潜在的异常
  • 尽量去处理异常,切忌只是简单地调用printStackTrace()去打印输出
  • 尽量添加finally语句块去释放占用的资源

8、集合框架


8.1、为什么使用集合框架?

例:存储一个班学员信息,假定一个班容纳20名学员,我们可以使用数组存储

如何存储每天的新闻信息?每天的新闻总数不确定,太少浪费空间,太多空间不足

如果并不知道程序运行时会需要多少对象,或者需要更复杂方式存储对象——可以使用Java集合框架

集合和数组的区别:

  • 数组声明了它容纳的元素的类型,而集合不声明
  • 数组是静态的,一个数组实例具有固定的大小,一旦创建了就无法改变容量了。而集合是可以动态扩展容量,可以根据需要动态改变大小,集合提供更多的成员方法,能满足更多的需求。
  • 数组的存放的类型只能是一种(基本类型/引用类型),集合存放的类型可以不是一种(不加泛型时添加的类型是Object)。
  • 数组是Java语言中内置的数据类型,是线性排列的,执行效率或者类型检查都是最快的

8.2、Java集合框架

Collections:提供了对集合进行排序、遍历等多种算法实现

Collection 接口存储一组唯一(元素不可以重复),无序的对象

List 接口存储一组不唯一(元素可以重复),有序(无序是指存入元素的先后顺序与输出元素的先后顺序不一致)的对象

Set 接口存储一组唯一(元素不可重复),无序的对象

Map接口用于存储任意键值对,提供key到value的映射

  • 键:无序、无下标、唯一(元素不可以重复)
  • 值:无序、无下标、不唯一(元素可以重复)

8.3、List接口常见的实现类

List接口的实现类:ArrayList(长度可变的数组、存储空间连续) LinkedList链表存储(双向链表存储

ArrayList实现了长度可变的数组,在内存中分配连续的空间。遍历元素和随机访问(查询快)元素的效率比较高,增、删慢运行效率快、线程不安全(JDK1.2)

LinkedList采用双向链表存储方式。插入、删除(增删快)元素时效率比较高,查询慢;运行效率快,线程不安全

Vector 可实现自动增长的对象数组。 数组结构实现,遍历元素和随机访问(查询快)元素的效率比较高,增、删慢;运行效率慢、线程安全(JDK1.0)

List接口常用方法

方法名 说 明
boolean add(Object o) 在列表的末尾顺序添加元素,起始索引位置从0开始
void add(int index,Object o) 在指定的索引位置添加元素。索引位置必须介于0和列表中元素个数之间
int size() 返回列表中的元素个数
Object get(int index) 返回指定索引位置处的元素。取出的元素是Object类型,使用前需要进行强制类型转换
boolean contains(Object o) 判断列表中是否存在指定元素
boolean remove(Object o) 从列表中删除元素
Object remove(int index) 从列表中删除指定位置元素,起始索引位置从0开始
public class TestArrayList {
    
    
    public static void main(String[] args) {
    
    
        List<Student> students = new ArrayList<>();

        //初始化数据
        Student stu1 = new Student(1000, "张三", "男", "S1");
        Student stu2 = new Student(1001, "李四", "男", "S2");
        Student stu3 = new Student(1002, "王五", "女", "Y2");
        Student stu4 = new Student(1003, "赵六", "男", "S1");
        //添加到集合中
        students.add(stu1);
        students.add(stu2);
        students.add(stu3);
        students.add(stu4);
        //遍历输出
        System.out.println("共有" + students.size() + "位同学:");
        System.out.println("学号\t姓名\t性别\t年级");
        for (int i = 0; i < students.size(); i++) {
    
    
            Student sdt = students.get(i);	//逐个获取个元素 
            System.out.println(sdt.getStudentNo() + "\t" + sdt.getName() + "\t" + sdt.getGender() + "\t\t" + sdt.getGrade());
        }
        //删除下标为2的同学,从0开始
        students.remove(2);
        //添加到指定位置
        students.add(3, stu1);
        //判断是否包含指定学生
        if (students.contains(stu3)) {
    
    
            System.out.println("包含该学生");
        } else {
    
    
            System.out.println("不包含该学生!");
        }

        System.out.println("=====使用fori遍历=====");
        for (int i = 0; i < students.size(); i++) {
    
    
            Student sdt = students.get(i);
            System.out.println(sdt.getStudentNo() + "\t" + sdt.getName() + "\t" + sdt.getGender() + "\t\t" + sdt.getGrade());
        }
        
         System.out.println("=====使用迭代器遍历=====");
         Iterator it = students.iterator();
         while (it.hasNext()){
    
    
            Student stu = (Student) it.next();
            System.out.println(stu);
        }
    }
}

ArrayList源码分析

//源码分析
private static final int DEFAULT_CAPACITY = 10;		//默认容量
transient Object[] elementData; 		//存放元素的数组
private int size;			//实际元素个数

public boolean add(E e) {
    
    
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    
    
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
    
    
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    
    
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    
    
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

注:如果没有集合中添加任何元素时,容量为0;添加任意一个元素时,容量为10,每次扩容大小是原来的1.5倍

LinkedList的特殊方法

方法名 说 明
void addFirst(Object o) 在列表的首部添加元素
void addLast(Object o) 在列表的末尾添加元素
Object getFirst() 返回列表中的第一个元素
Object getLast() 返回列表中的最后一个元素
Object removeFirst() 删除并返回列表中的第一个元素
Object removeLast() 删除并返回列表中的最后一个元素
public class TestLinkedList {
    
    
    public static void main(String[] args) {
    
    
        LinkedList<Student> list = new LinkedList();
        //初始化数据
        Student stu1 = new Student(1000, "张三", "男", "S1");
        Student stu2 = new Student(1001, "李四", "男", "S2");
        Student stu3 = new Student(1002, "王五", "女", "Y2");
        Student stu4 = new Student(1003, "赵六", "男", "S1");

        list.addFirst(stu4);        //在第一个位置添加元素
        list.add(stu2);
        list.add(stu3);
        list.addLast(stu1);     //在最后一个位置添加元素

        System.out.println("集合中第一位同学的姓名是:"+list.getFirst().getName());  //获取集合中第一个元素
        System.out.println("集合中最后一位同学的姓名是:"+list.getLast().getName()); //获取集合中最后一个元素

        list.removeFirst();     //删除集合中第一个元素
        list.removeLast();      //删除集合中最后一个元素

        System.out.println("共有" + list.size() + "位同学:");
        //使用增强for循环遍历输出
        for (Student std : list) {
    
    
            System.out.println(std.getName() + "\t" + std.getName() + "\t" + std.getGender() + "\t" + std.getGrade());
        }
    }
}

8.4、Set接口常见的实现类

Set接口的实现类:HashSet(内部的数据结构是哈希表) TreeSet(基于红黑树(二叉查找树)实现的

HashSet是基于HashCode计算元素存放位置, 如果对象的hashCode值不同,则不用判断equals方法,就直接存到HashSet中。,当存入元素的哈希码相同时,会调用equals进行确认,如结果为true,则拒绝后者存入。运行效率快、线程不安全

TreeSet是基于排列顺序实现元素不重复,实现了SortedSet接口,对集合元素进行排序,元素对象的类型必须实现Comparable接口,指定排列规则,通过CompareTo方法确认是否为重复元素,根据比较方法的返回结果是否为0,如果是0,则是相同元素,不存,如果不是0,则是不同元素,存储。运行效率快、线程不安全

HashSet的使用

package com.zhang.set;

import java.util.Objects;

/**
 * 人类
 */
public class Person {
    
    
    private String name;
    private int age;

    public String getName() {
    
    
        return name;
    }

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

    public int getAge() {
    
    
        return age;
    }

    public void setAge(int age) {
    
    
        this.age = age;
    }

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

    public Person() {
    
    
    }

    @Override
    public int hashCode() {
    
    
        int n1 = this.name.hashCode();
        int n2 = this.age + 31; 	//(1) 31是一个质数(素数),只能被1和它自身整除的,减少散列冲突  (2)提供执行效率31*i=(i<<5)-i
        return n1 + n2;
    }

    @Override
    public boolean equals(Object o) {
    
    
        //1.判断是不是同一个对象
        if (this == o) return true;
        //2.判断是否为空
        if (o == null) return false;
        //3.判断是否是Person类
        if (o instanceof Person) {
    
    
            Person person = (Person) o;
            //4.比较属性
            if (this.name.equals(person.getName()) && this.age == person.getAge()) {
    
    
                return true;
            }
        }
        //5.条件都不满足返回false
        return false;
    }

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

package com.zhang.set;

import java.util.HashSet;
import java.util.Iterator;

/**
 * Hashset集合的使用
 * 存储结构:哈希表(数组+链表+红黑树)
 * 存储过程:
 * (1)根据hashcode,计算保存的位置,如果此位置为空,则直接保存,如果不为空,执行第二步
 * (2)在执行equals方法,如果equals为true,则认为是重复元素,否则,形成链表
 */
public class HashSetDemo03 {
    
    
    public static void main(String[] args) {
    
    
        //创建集合
        HashSet<Person> personHashSet = new HashSet<>();
        //1.添加数据
        Person p1 = new Person("小赵", 18);
        Person p2 = new Person("小张", 19);
        Person p3 = new Person("小林", 20);
        Person p4 = new Person("小李", 21);
        personHashSet.add(p1);
        personHashSet.add(p2);
        personHashSet.add(p3);
        personHashSet.add(p4);
        //重复  不能添加了
        //personHashSet.add(p4);
        personHashSet.add(new Person("小林", 20));
        System.out.println("元素个数:" + personHashSet.size() + "\n" + personHashSet);
        //2.删除元素
        personHashSet.remove(p2);
        //如果重写hashcode和equals是可以删除的  否则反之
        //personHashSet.remove(new Person("小李", 21));
        System.out.println("删除之后元素个数:" + personHashSet.size() + "\n" + personHashSet);
        //3.遍历元素
        //3.1使用增强for循环遍历
        System.out.println("=====使用增强for循环遍历=====");
        for (Person person: personHashSet) {
    
    
            System.out.println(person);
        }
        //3.2使用迭代器循环遍历
        System.out.println("=====使用迭代器循环遍历=====");
        Iterator<Person> it = personHashSet.iterator();
        while (it.hasNext()) {
    
    
            System.out.println(it.next());
        }
        //判断
        //重写hashcode和equals是寻找的是同一个对象  结果为true  否则为false
        System.out.println(personHashSet.contains(new Person("小林",20)));
        System.out.println(personHashSet.isEmpty());
    }
}

注:HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束。

TreeSet的使用

package com.zhang.set;

import java.util.Objects;

/**
 * 人类
 */
public class Person implements Comparable<Person> {
    
    
    private String name;
    private int age;

    public String getName() {
    
    
        return name;
    }

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

    public int getAge() {
    
    
        return age;
    }

    public void setAge(int age) {
    
    
        this.age = age;
    }

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

    public Person() {
    
    
    }

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

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

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

    //先按照姓名比较 ,然后再按年龄比较
    @Override
    public int compareTo(Person o) {
    
    
        int n1 = this.getName().compareTo(o.getName());
        int n2 = this.getAge() - (o.getAge());
        return n1 == 0 ? n2 : n1;
    }
}

package com.zhang.set;

import java.util.Iterator;
import java.util.TreeSet;

/**
 * treeSet集合的使用
 * 存储结构:红黑树
 * 无序 无下标 不能重复
 * 要求:元素必须要实现Comparable接口,compareTo()方法的返回值为0,证明是重复元素的
   如果不实现将会报错:Exception in thread "main" java.lang.ClassCastException: com.zhang.set.Person cannot be cast to java.lang.Comparable
 */
public class TreeSetDemo02 {
    
    
    public static void main(String[] args) {
    
    
        //创建treeSet集合
        TreeSet<Person> personTreeSet = new TreeSet<>();
        //1.添加数据
        Person p1 = new Person("xyz", 18);
        Person p2 = new Person("abc", 19);
        Person p3 = new Person("efg", 20);
        Person p4 = new Person("ABCD", 21);
        Person p5 = new Person("ABCD", 22);

        personTreeSet.add(p1);
        personTreeSet.add(p2);
        personTreeSet.add(p3);
        personTreeSet.add(p4);
        personTreeSet.add(p5);
        //重复了 不能添加
        personTreeSet.add(p5);
        System.out.println("元素个数:" + personTreeSet.size() + "\n" + personTreeSet);
        //删除元素
        personTreeSet.remove(new Person("ABCD", 21));
        System.out.println("删除之后元素个数:" + personTreeSet.size() + "\n" + personTreeSet);
        //3.遍历元素
        //3.1使用增强for循环遍历
        System.out.println("=====使用增强for循环遍历=====");
        for (Person letter : personTreeSet) {
    
    
            System.out.println(letter);
        }
        //3.2使用迭代器循环遍历
        System.out.println("=====使用迭代器循环遍历=====");
        Iterator<Person> it = personTreeSet.iterator();
        while (it.hasNext()) {
    
    
            System.out.println(it.next());
        }
        //4.判断
        System.out.println(personTreeSet.contains(p4));
        System.out.println(personTreeSet.isEmpty());
    }
}

使用Comparator实现定制比较(比较器)

package com.zhang.set;

import java.util.Comparator;
import java.util.TreeSet;

/**
 * TreeSet集合的使用
 * Comparator:实现定制比较(比较器)
 * Comparable 可比较的
 */
public class TreeSetDemo03 {
    
    
    public static void main(String[] args) {
    
    
        //创建treeSet集合,并指定比较规则
        TreeSet<Person> personTreeSet = new TreeSet<>(new Comparator<Person>() {
    
    
            @Override
            public int compare(Person o1, Person o2) {
    
    
                int n1 = o1.getAge() - o2.getAge();
                int n2 = o1.getName().compareTo(o2.getName());

                return n1 == 0 ? n2 : n1;
            }
        });
        //1.添加数据
        Person p1 = new Person("xyz", 18);
        Person p2 = new Person("abc", 19);
        Person p3 = new Person("efg", 20);
        Person p4 = new Person("ABCD", 21);
        Person p5 = new Person("ABCD", 22);
        Person p6 = new Person("ailin", 20);
        personTreeSet.add(p1);
        personTreeSet.add(p2);
        personTreeSet.add(p3);
        personTreeSet.add(p4);
        personTreeSet.add(p5);
        personTreeSet.add(p6);
        System.out.println("元素个数:" + personTreeSet.size() + "\n" + personTreeSet);
    }
}

注:TreeSet 是二差树实现的,Treeset中的数据是自动排好序的,不允许放入null值。

8.5、Map接口常见的实现类

Map接口专门处理键值映射数据的存储,可以根据键实现对值的操作

Map接口的实现类:HashMap(基于哈希表实现)和TreeMap(基于红黑树(二叉查找树)实现的

HashMap通过hashcode对其内容进行快速查找,允许用null作为key或是value,运行效率快、线程不安全(JDK1.2)

TreeMap中所有的元素都保持着某种固定的顺序,实现了SortedMap接口(是Map的子接口),可以对key自动排序,运行效率快、线程不安全(JDK1.2)

HashTable<K,V>也是一种key-value结构,它继承自Dictionary<K,V>,实现了Map<K,V>和Cloneable以及Serializable接口。(基本已被淘汰),不允许用null作为key或是value,运行效率慢、线程安全(JDK1.0)

Map接口常用方法

方法名 说 明
Object put(Object key, Object val) 以“键-值对”的方式进行存储
Object get (Object key) 根据键返回相关联的值,如果不存在指定的键,返回null
Object remove (Object key) 删除由指定的键映射的“键-值对”
int size() 返回元素个数
Set keySet () 返回此映射中包含的键的Set集合
Collection values () 返回值的集合
boolean containsKey (Object key) 如果存在由指定的键映射的“键-值对”,返回boolean
entrySet() 返回此映射中包含的映射关系的Set集合

HashMap的使用

package com.zhang.map;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * Map接口的使用
 * 特点:(1)存储键值对(2)键不能重复,值可以重复(3)无序,无下标
 */
public class HashMapDemo01 {
    
    
    public static void main(String[] args) {
    
    
        //创建Map集合
        Map<String, Country> map = new HashMap<>();
        //1.添加元素
        Country country1 = new Country("CN", "中国");
        Country country2 = new Country("RU", "俄罗斯联邦");
        Country country3 = new Country("FR", "法兰西共和国");
        Country country4 = new Country("US", "美利坚联合众国");
        map.put(country1.getLog(), country1);
        map.put(country2.getLog(), country2);
        map.put(country3.getLog(), country3);
        map.put(country4.getLog(), country4);
        //由于键重复,无法添加    但是把前面的value给替换了
        map.put(country1.getLog(), new Country("CHINA", "中华人民共和国"));
        System.out.println("元素个数:" + map.size() + "\n" + map);
        //2.删除
        map.remove("US");
        System.out.println("删除之后元素个数:" + map.size() + "\n" + map);
        //3.遍历
        //3.1、使用keySet()方法遍历
        System.out.println("=====使用keySet()方法遍历=====");
        //Set<String> keySet = map.keySet();
        for (String countryKey : map.keySet()) {
    
    
            System.out.println(countryKey + "\t\t" + map.get(countryKey));
        }
        //3.2、使用entrySet()方法遍历  entrySet效率要高于keySet
        System.out.println("=====使用entrySet()方法遍历=====");
        //Set<Map.Entry<String, String>> entries = map.entrySet();
        for (Map.Entry<String, Country> countryEntry : map.entrySet()) {
    
    
            System.out.println(countryEntry.getKey() + "\t\t" + countryEntry.getValue());
        }
        //判断
        System.out.println(map.containsKey("CN"));      //是否包含该键
        System.out.println(map.containsValue(new Country("CHINA", "中华人民共和国")));      //是否包含该值
        System.out.println(map.isEmpty());
    }
}
package com.zhang.map;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * HashMapMap接口的使用
 * 存储结构:哈希表(数组+链表+红黑树)
 * 使用key的hashCode和equals作为重复
 */
public class HashMapDemo02 {
    
    
    public static void main(String[] args) {
    
    
        //创建HashMap集合
        HashMap<Student, String> hashMap = new HashMap<>();
        //添加元素
        Student s1 = new Student(1001, "张三");
        Student s2 = new Student(1002, "李四");
        Student s3 = new Student(1003, "王五");
        Student s4 = new Student(1004, "赵六");
        hashMap.put(s1, "北京");
        hashMap.put(s2, "上海");
        hashMap.put(s3, "西安");
        hashMap.put(s4, "广州");
        //key不能重复,无法添加
        hashMap.put(s1, "广东");
        hashMap.put(new Student(1004, "赵六"), "杭州");
        System.out.println("元素个数:" + hashMap.size() + "\n" + hashMap);
        //2.删除
        hashMap.remove(s4);
        System.out.println("删除之后元素个数:" + hashMap.size() + "\n" + hashMap);
        //3.遍历
        //3.1、使用keySet()方法遍历
        System.out.println("=====使用keySet()方法遍历=====");
        //Set<Student> students = hashMap.keySet();
        for (Student student : hashMap.keySet()) {
    
    
            System.out.println(student + "\t\t" + hashMap.get(student));
        }
        //3.2、使用entrySet()方法遍历  entrySet效率要高于keySet
        System.out.println("=====使用entrySet()方法遍历=====");
        //Set<Map.Entry<Student, String>> entries = hashMap.entrySet();
        for (Map.Entry<Student, String> entries : hashMap.entrySet()) {
    
    
            System.out.println(entries.getKey() + "\t\t" + entries.getValue());
        }
        //4.判断
        System.out.println(hashMap.containsKey(new Student(1003, "王五")));      //是否包含该键
        System.out.println(hashMap.containsValue("杭州"));      //是否包含该值
        System.out.println(hashMap.isEmpty());
    }
}

HashMap源码分析

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16  hashMap初始容量大小
static final int MAXIMUM_CAPACITY = 1 << 30;		//hashMap的最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;		//默认加载因子
static final int TREEIFY_THRESHOLD = 8;				//jdk1.8  当链表长度大于8时,调整为红黑树
static final int UNTREEIFY_THRESHOLD = 6;			//jdk1.8  当链表长度小于6时,调整为链表
static final int MIN_TREEIFY_CAPACITY = 64;			//jdk1.8  当链表长度大于8时,并且集合元素个数大于等于64时,调整为红黑树
transient Node<K,V>[] table;	//哈希表中的数组
transient int size;		//元素个数

总结:

  • HashMap刚创建时, table是null, 为了节省空间,当添加第一个元素时,table容量调整为16
  • 当元素个数大于阈值(16*0.75=12)时,会进行扩容,扩容后大小为原来的2倍。目的是减少调整元素的个数。
  • jdk1.8 当每个链表长度大于8,并且元素个数大于等于64时, 会调整为红黑树,目的提高执行效率
  • jdk1.8 当链表长度小于6时,调整成链表
  • jdk1.8以前链表时头插入,jdk1.8以后时是尾插入

TreeMap的使用

package com.zhang.map;

import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;

/**
 *  TreeMap集合的使用
 *  特点:(1)存储键值对(2)键不能重复,值可以重复(3)无序,无下标
 *  注:treeMap也是需要定制比较的 否则报错:Exception in thread "main" java.lang.ClassCastException: com.zhang.map.Student cannot be cast to java.lang.Comparable
 */
public class TreeMapDemo01 {
    
    
    public static void main(String[] args) {
    
    
        //创建treeMap集合  定制比较
        TreeMap<Student, String> treeMap = new TreeMap<>(new Comparator<Student>() {
    
    
            @Override
            public int compare(Student o1, Student o2) {
    
    
                int n1 = o1.getStudentNo() - o2.getStudentNo();
                return n1;
            }
        });
        //1.添加元素
        Student s1 = new Student(1001, "张三");
        Student s2 = new Student(1002, "李四");
        Student s3 = new Student(1003, "王五");
        Student s4 = new Student(1004, "赵六");
        treeMap.put(s1, "北京");
        treeMap.put(s2, "上海");
        treeMap.put(s3, "广东");
        treeMap.put(s4, "深圳");
        treeMap.put(new Student(1004, "赵六"), "西安");
        System.out.println("元素个数:" + treeMap.size() + "\n" + treeMap);
        //2.删除元素
        treeMap.remove(new Student(1003, "王五"));
        System.out.println("删除之后元素个数:" + treeMap.size() + "\n" + treeMap);
        //3.遍历
        //3.1、使用keySet()方法遍历
        System.out.println("=====使用keySet()方法遍历=====");
        for (Student student : treeMap.keySet()) {
    
    
            System.out.println(student + "\t\t" + treeMap.get(student));
        }
        //3.2、使用entrySet()方法遍历  entrySet效率要高于keySet
        System.out.println("=====使用entrySet()方法遍历=====");
        for (Map.Entry<Student, String> entries : treeMap.entrySet()) {
    
    
            System.out.println(entries.getKey() + "\t\t" + entries.getValue());
        }
        //4.判断
        System.out.println(treeMap.containsKey(new Student(1003, "王五")));      //是否包含该键
        System.out.println(treeMap.containsValue("北京"));      //是否包含该值
        System.out.println(treeMap.isEmpty());
    }
}

8.6、泛型

  • Java泛型是JDK1.5中引入的一个新特性,其本质是参数化类型,把类型作为参数传递
  • 常见形式有泛型类、泛型接口、泛型方法

语法:

<T,...> 	//T称为类型占位符,表示一种引用类型

好处:

  • 提高代码的重用性
  • 防止类型转换异常,提高代码的安全性

泛型集合

概念:参数化类型、类型安全的集合,强制集合元素的类型必须一致

特点:

  • 编译时即可检查,而非运行时抛出异常
  • 访问时,不必类型转换(拆箱)
  • 不同泛型直接引用不能相互赋值,泛型不存在多态

Collections工具类

package com.zhang.collection;

import java.util.*;

/*
	Collections工具类的使用
 */
public class CollectionDemo03 {
    
    
    public static void main(String[] args) {
    
    
        List<Integer> list = new ArrayList<>();
        list.add(455);
        list.add(498);
        list.add(266);
        list.add(100);
        list.add(36);
        System.out.println("排序之前:" + list);
        //sort排序
        Collections.sort(list);
        System.out.println("排序之后:" + list);
        //binarySearch  二分查找器   找到集合中的元素下标  找不到返回-1
        int search = Collections.binarySearch(list,266);
        System.out.println(search);
        //copy  复制
        List<Integer> integerList = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
    
    
            integerList.add(0);
        }
        Collections.copy(integerList, list);
        System.out.println("copy完成的集合数据:" + integerList);
        //reverse  反转
        Collections.reverse(integerList);
        System.out.println("反转之后:" + integerList);
        //shuffle  打乱  相当于洗牌
        Collections.shuffle(integerList);
        System.out.println("随机打乱之后:" + integerList);

        //集合转为数组
        System.out.println("=====集合转为数组=====");
        Integer[] array = list.toArray(new Integer[0]);
        System.out.println("数组长度:" + array.length + "\n" + Arrays.toString(array));

        //数组转为集合
        System.out.println("=====数组转为集合=====");
        String[] names = {
    
    "小赵", "小张", "小林", "小李", "小飞"};
        //数组转为集合是一个受限集合,不能添加和删除  否则报错:Exception in thread "main" java.lang.UnsupportedOperationException
        List<String> namesList = Arrays.asList(names);
        //namesList.add("小刚");
        //namesList.remove(0);
        System.out.println(namesList);
        //把基本类型数组转为集合时,需要修改为包装类
        Integer[] nums = {
    
    10, 2066, 9966, 8875, 100};
        List<Integer> numsList = Arrays.asList(nums);
        System.out.println(numsList);
    }
}

8.7、迭代器Iterator实现遍历

  • 获取Iterator :Collection 接口的iterator()方法
  • Iterator的方法:
    • boolean hasNext(): 判断是否存在另一个可访问的元素
    • Object next(): 返回要访问的下一个元素
//使用迭代器遍历输出  iterator
Set<String> keys = countryMap.keySet();     //取出所有key的集合
Iterator<String> ctIterator = keys.iterator();     //获取Iterator对象
while (ctIterator.hasNext()) {
    
    
    String key = ctIterator.next();     //取出key
    Country country = countryMap.get(key);	
    System.out.println(country.getLog() + "\t\t" + country.getCtName());
}

使用foreach实现遍历(较为常用)

for (Country country : countryMap.values()) {
    
    
    System.out.println(country.getLog() + "\t" + country.getCtName());
}

8.8、Properties(属性集合)

继承自Hashtable,线程安全

  • 存储属性名和属性值
  • 属性名和属性值都是字符串类型
  • 没有泛型
  • 和流有关
package com.zhang.properties;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * Properties属性集合的使用
 */

public class PropertiesDemo01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //1.创建Properties集合
        Properties properties = new Properties();
        //2.添加属性
        properties.setProperty("userName", "MrZhang");
        properties.setProperty("password", "666666");
        System.out.println(properties);
        //3.删除属性
        //properties.remove("userName");
        //System.out.println("删除之后:" + properties);
        //4.遍历
        //4.1、使用keySet遍历
        System.out.println("=====使用keySet遍历=====");
        for (Object o : properties.keySet()) {
    
    
            System.out.println("key:" + o + "\t\tvalue:" + properties.get(o));
        }
        //4.2、使用entrySet遍历
        System.out.println("=====使用entrySet遍历=====");
        for (Map.Entry<Object,Object> entry : properties.entrySet()) {
    
    
            System.out.println("key:" + entry.getKey() + "\t\tvalue:" + entry.getValue());
        }
        //4.3、使用stringPropertyNames()方法遍历
        System.out.println("=====使用stringPropertyNames()方法遍历=====");
        Set<String> propertyNames = properties.stringPropertyNames();
        for (String property : propertyNames) {
    
    
            System.out.println("key:" + property + "\t\tvalue:" + properties.get(property));
        }
        //5.和流有关的方法
        //System.out.println("=====list()方法列表=====");
        //PrintWriter pw = new PrintWriter("properties.txt");
        //properties.list(pw);
        //pw.close();
        //System.out.println("打印成功!");
        System.out.println("=====store()方法保存=====");
        FileOutputStream fos = new FileOutputStream("store.properties");
        properties.store(fos, "key---value");
        fos.close();
        System.out.println("创建成功!");
        System.out.println("=====load()方法加载=====");

        Properties properties2 = new Properties();
        FileInputStream fis = new FileInputStream("store.properties");
        properties2.load(fis);
        fis.close();
        System.out.println(properties2);
    }
}

9、多线程


9.1、什么是多线程?

在了解多线程之前,需要先了解多任务、线程、进程这几个名词

所谓多任务就是在同一时间做多种事情,例如window系统的任务管理器

在操作系统中运行的程序就是进程,比如QQ、播放器、IDE、游戏等

一个进程可以有多个线程,如视频中同时听到声音、看见图像、弹幕等

如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程,多个线程交替占用CPU资源,而非真正的并行执行

9.2、进程(Process)与线程(Thread)

  • 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
  • 进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
  • 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然就没有存在的意义了,线程是CPU调度和执行的单位

普通方法调用和多线程

  • 线程就是独立的执行路径
  • 在程序运行中,即使自己创建线程,后台也会有多个线程,如主线程(用户线程),gc线程(垃圾回收~守护线程
  • main()称为主线程,为系统的入口,用于执行整个程序
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
  • 线程会带来额外的开销,如cpu调度时间,并发控制开销
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

9.3、线程的创建方式

  1. 继承java.lang.Thread类

    1. 自定义线程类继承Thread类
    2. 重写run()方法,编写线程执行体
    3. 创建线程对象,调用start()方法启动线程
    //创建线程方式一:继承Thread类  重写run方法,调用start开启线程
    public class MyThread extends Thread {
          
          
        @Override
        public void run() {
          
          
            //run方法线程体
            for (int i = 0; i < 20; i++) {
          
          
                System.out.println("我在看代码~" + i);
            }
        }
    
        public static void main(String[] args) {
          
          
            //main线程
            MyThread myThread = new MyThread();         //创建线程对象
            myThread.start();       //调用start()方法
            for (int i = 0; i < 200; i++) {
          
          
                System.out.println("我在学习多线程~" + i);
            }
        }
    }
    

    注:线程开启不一定立即执行,有CPU调度执行

  2. 实现java.lang.Runnable接口

    1. 定义MyRunnable类实现Runnable接口
    2. 实现run()方法,编写线程执行体
    3. 创建线程对象,调用start()方法启动线程
    //龟兔赛跑
    public class Race implements Runnable {
          
          
        private static String winner;   //获胜者
    
        @Override
        public void run() {
          
          
            for (int i = 0; i <= 200; i++) {
          
          
                //模拟兔子休息
                if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) {
          
          
                    try {
          
          
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
          
          
                        e.printStackTrace();
                    }
                }
                boolean flag = gameOver(i);
                if (flag) break;
                System.out.println(Thread.currentThread().getName() + "跑了" + i + "米");
            }
        }
    
        //游戏结束
        public boolean gameOver(int steps) {
          
          
            //判断是否有胜利者
            if (winner != null) {
          
             //已经有胜利者了
                return true;
            }
            {
          
          
                if (steps == 200) {
          
            //跑完了
                    winner = Thread.currentThread().getName();
                    System.out.println("winner is" + winner);
                    return true;
                }
            }
            return false;
        }
    
        public static void main(String[] args) {
          
          
            Race run = new Race();
            new Thread(run, "乌龟").start();
            new Thread(run, "兔子").start();
        }
    }
    

    比较两种创建线程的方式:

    继承Thread类

    • 子类继承Thread类具备多线程能力
    • 启动线程:子类对象.start()
    • 不建议使用,避免OOP单继承局限性

    实现Runnable接口

    • 实现接口Runnable具有多线程能力
    • 启动线程:传入目标对象+Thread对象.start()
    • 推荐使用:灵活方便,方便同一个对象被多个线程使用

    注:多个线程操作统一共享资源时,将引发数据不安全问题

9.4、静态代理模式

//静态代理模式:真实对象和代理对象都要实现同一个接口
//代理对象要代理真实角色
//好处:代理对象可以做很多真实对象做不了的事情
//真实对象专注做自己的事情
public class StaticProxy {
    
    
    public static void main(String[] args) {
    
    
        You you = new You();    //你要结婚

        //        WeddingCompany weddingCompany = new WeddingCompany(you);
        //        weddingCompany.happyMarry();
        new WeddingCompany(you).happyMarry();
    }
}

interface Marry{
    
    
    void happyMarry();
}
//真实角色,你去结婚
class You implements Marry{
    
    
    @Override
    public void happyMarry() {
    
    
        System.out.println("张先生要结婚了!真开心");
    }
}

//代理角色,帮助你结婚
class WeddingCompany implements Marry{
    
    
    //代理谁--> 真实目标角色
    private Marry target;

    public WeddingCompany(Marry target) {
    
    
        this.target = target;   //这是真实对象
    }
    @Override
    public void happyMarry() {
    
    
        before();
        this.target.happyMarry();
        after();
    }

    private void before() {
    
    
        System.out.println("结婚之前,布置现场");
    }

    private void after() {
    
    
        System.out.println("结婚之前,收尾款");
    }
}

9.5、线程状态

线程方法

说 明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() 中断线程(尽量少用
boolean isAlive() 测试线程是否处于活动状态

打印当前时间

public class TestCurrTime {
    
    
    public static void main(String[] args) {
    
    
        //打印当前系统时间
        Date currDate = new Date(System.currentTimeMillis()); //获取当前系统时间
        while (true){
    
    
            try {
    
    
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(currDate));
                currDate = new Date(System.currentTimeMillis()); //更新当前系统时间
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

买票

//多个线程同时操作同一个对象
//多个线程操作同一个资源的情况下,线程不安全,数据紊乱  模拟网络延时,放大问题的发生性
public class TestTicket {
    
    
    public static void main(String[] args) {
    
    
        BuyTickets station = new BuyTickets();

        new Thread(station, "小明").start();
        new Thread(station, "黄牛党").start();
        new Thread(station, "夏林").start();
    }
}

//买票
class BuyTickets implements Runnable {
    
    
    int ticketNums = 10;
    boolean flag = true;

    @Override
    public void run() {
    
    
        buy();
    }

    private void buy() {
    
    
        while (flag) {
    
    
            try {
    
    
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName() + "抢到了" + (ticketNums--) + "张票");
                if (ticketNums <= 0) flag = false;     //如果在没有票的情况下
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

9.6、守护(daemon)线程

  • 线程分为用户线程守护线程
  • 虚拟机必须确保用户线程执行完毕(main)
  • 虚拟机不用等待守护线程执行完毕(gc)
  • 如:后台记录操作日志、监控内存、垃圾回收等

9.7、线程同步

为什么需要线程同步?

多个线程操作同一共享资源时,将引发数据不安全问题

并发:同一个对象多个线程同时操作

现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决方法就是:排队一个一个来

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程在使用

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入形成条件:锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待使用后释放锁即可

形成条件:队列和锁

synchronized:同步方法;锁的是this

synchronized(syncObject){
    
    
    //需要同步的代码
}

锁的对象就是变化的量,需要增、删、改的对象

缺陷:若将一个大的方法声明为synchronized将会影响效率

9.7、锁(Lock)

  • Java提供了更强大的线程同步机制----通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当(JDK5.0特性)
  • java.util.concurrent.locks接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、解锁
public class TestLocks {
    
    
    public static void main(String[] args) {
    
    
        Lock lock = new Lock();
        new Thread(lock).start();
        new Thread(lock).start();
        new Thread(lock).start();
    }
}

class Lock implements Runnable {
    
    
    private int ticketNum = 10;     //票数

    //定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
    
    
        while (true) {
    
    
            try {
    
    
                lock.lock();       //加锁
                if (ticketNum > 0) {
    
    
                    try {
    
    
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                } else {
    
    
                    break;
                }
                System.out.println(ticketNum--);
            } finally {
    
    
                lock.unlock();         //解锁
            }
        }
    }
}

synchronized 与 Lock 的对比

  • Lock是显式锁(需要手动开启和关闭)synchronized是隐式锁,出了作用域自动释放
  • Lock只能锁代码块,synchronized可以锁代码块和方法
  • 使用Lock锁,JVM花费较少事件来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)

9.8、线程安全的类型

方法是否同步 效率比较 适合场景
线程安全 多线程并发共享资源
非线程安全 单线程

Hashtable && HashMap

Hashtable:

  • 继承关系(实现了Map接口,Hashtable继承Dictionary类)
  • 线程安全,效率较低
  • 键和值都不允许为null

HashMap:

  • 继承关系(实现了Map接口,继承AbstractMap类)
  • 非线程安全,效率较高
  • 键和值都允许为null

10、I/O流


10.1、什么是流?

流是内存与存储设备之间传输数据的通道

水借助管道传输;数据借助流传输

10.2、流的分类

按流向区分:

  • 输入流:将<存储设备>硬盘中的内容读入到<内存>程序
  • 输出流:将<内存>程序中的内容写入到<存储设备>硬盘

按处理数据单元划分:

  • 字节流:以字节为单位,可以读写所有数据

字节流的父类(抽象类)InputStream、OutputStream

FileInputStream:字节输入流

package com.zhang.file;

import java.io.FileInputStream;
import java.io.FileNotFoundException;

/**
 * FileInputStream的使用
 * 文件字节输入流
 */
public class FileInputStreamDemo01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //1.创建FileInputStream,并指定文件路径
        FileInputStream fis = new FileInputStream("songs.txt");
        //2.读取文件
        //2.1、单个字节读取
        //int data = 0;
        //while ((data = fis.read()) != -1) {
    
    
        //    System.out.println((char) data);
        //}
        //2.2、一次读取多个字节
        byte[] buffer = new byte[1024];
        int count = 0;
        while ((count = fis.read(buffer)) != -1) {
    
    
            System.out.println(new String(buffer, 0, count));
        }
        //关闭
        fis.close();
        System.out.println("\n********************");
        System.out.println("读取完毕!!!");
    }
}

OutInputStream:字节输出流

package com.zhang.file;

import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;

/**
 * FileOutPutStream的使用
 * 文件字节输出流
 */
public class FileOutPutStreamDemo01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //1.创建文件字节输出流对象     第二个参数指的是是否追加文本内容
        FileOutputStream fos = new FileOutputStream("diary.txt", true);
        //2.写入文件  a、97  b、98  c、99  阿斯克码表
        //fos.write(97);
        //fos.write('b');
        //fos.write('c');
        String character = "hello world!!!";
        fos.write(character.getBytes());
        //3.关闭流
        fos.close();
        System.out.println("文件创建成功!");
    }
}

ASCII码对照表

使用文件字节流实现文件的复制

package com.zhang.file;

import java.io.FileInputStream;
import java.io.FileOutputStream;

/**
 * 使用文件字节流实现文件的复制
 */
public class FileCopyDemo01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //创建流
        //1.1、文件字节输入流
        FileInputStream fis = new FileInputStream("01.jpg");
        //1.2、文件字节输出流
        FileOutputStream fos = new FileOutputStream("02.jpg");
        //2.一边读、一边写
        byte[] buffer = new byte[1024];
        //保存实际读取的字节个数
        int count = 0;
        while ((count = fis.read(buffer)) != -1) {
    
    
            fos.write(buffer, 0, count);
        }
        //3.关闭流
        fis.close();
        fos.close();
        System.out.println("复制完成!!!");
    }
}

使用字节缓冲流写入文件

package com.zhang.file;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;

/**
 * 使用字节缓冲流写入文件
 * BufferedOutPutStream的使用
 */
public class BufferedOutPutStreamDemo01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //1、创建字节输出缓冲流
        FileOutputStream fos = new FileOutputStream("buffer.txt");
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        //2、写入文件
        for (int i = 0; i < 10; i++) {
    
    
            bos.write("hello world!\n".getBytes());       //写入8kb的缓冲区,
            bos.flush();        //刷新到硬盘
        }
        //3.关闭流(内部会调用flush方法)
        bos.close();
    }
}

  • 字符流:以字符为单位,只能读写文本数据

字符编码:

  • IS0-8859-1收录除ASCII外,还包括西欧、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号
  • UTF-8:针对UniCode码表的可变长度字符编码
  • GB2312:简体中文
  • GBK:简体中文、扩充
  • BIG5:台湾,繁体中文

注:当编码的方式和解码的方式不一致时,会出现乱码问题

字符流的父类(抽象类)Reader、Writer

FileReader:字符输入流

package com.zhang.file;

import java.io.FileReader;
import java.io.InputStreamReader;

/**
 * 使用FileReader读取文件
 */

public class FileReaderDemo01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //1.创建文件字符输入流
        FileReader fr = new FileReader("study.txt");
        //2.读取文件
        //2.1、单个字符读取
        //int data = 0;
        //while ((data = fr.read()) != -1) {      //读取一个字符,不再是字节
        //    System.out.print((char) data);
        //}

        char[] buffer = new char[1024];
        int count = 0;
        while ((count = fr.read(buffer)) != -1) {
    
    
            System.out.println(new String(buffer, 0, count));
        }
        //3.关闭流
        fr.close();
    }
}

Writer:字符输出流

package com.zhang.file;

import java.io.FileWriter;

/**
 * 使用FileWriter写入文件
 */
public class FileWriterDemo01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //1.创建文件字符输出流
        FileWriter fw = new FileWriter("note.txt");
        //2.写入文件
        for (int i = 0; i < 10; i++) {
    
    
            fw.write("我热爱Java编程语言!!!\n");
            fw.flush();
        }
        //关闭流
        fw.close();
        System.out.println("创建文件成功!");
    }
}

使用文件字符流实现文件的复制

package com.zhang.file;

import java.io.*;

/**
 * 使用文件字符流实现文件的复制
 * 注:只能复制文本文件,不能复制图片或二进制文件
 * 解决方法:使用字节流可以复制任意文件
 */
public class FileCopyDemo02 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //创建流
        //1.1文件字符输入流
        FileReader fr = new FileReader("note.txt");
        //1.2文件字符输出流
        FileWriter fw = new FileWriter("note03.txt");
        //2.读写
        int data = 0;
        char[] buffer = new char[1024];
        while ((data = fr.read(buffer)) != -1) {
    
    
            fw.write(buffer, 0, data);
            fw.flush();
        }
        //3.关闭流
        fw.close();
        fr.close();
        System.out.println("copy完成!");
    }
}

字符缓冲流:BufferedReader/BufferedWriter

  • 高效读写
  • 支持输入换行符
  • 可一次写一行,读一行

字符缓冲流的父类(抽象类)Reader、Writer

BufferedReader:字符缓冲输入流

package com.zhang.file;

import java.io.BufferedReader;
import java.io.FileReader;

/**
 * 使用字符缓冲输入流读取文件
 * BufferedReader的使用
 */

public class BufferedReaderDemo01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //1.创建字符缓冲输入流
        FileReader fr = new FileReader("note03.txt");
        BufferedReader br = new BufferedReader(fr);
        //2.1读取文件  第一种方式
        //char[] buffer = new char[1024];
        //int data = 0;
        //while ((data = br.read(buffer)) != -1) {
    
    
        //    System.out.print(new String(buffer, 0, data));
        //}
        //2.2、一行一行读
        String line = null;
        while ((line = br.readLine()) != null) {
    
    
            System.out.println(line);
        }
        //3.关闭流
        br.close();
        ;
    }
}

BufferedReader:字符缓冲输出流

package com.zhang.file;

import java.io.BufferedWriter;
import java.io.FileWriter;

public class BufferedWriterDemo01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //1.创建字符缓冲输入流
        FileWriter fw = new FileWriter("ana.txt");
        BufferedWriter bw = new BufferedWriter(fw);
        //2.写入文件
        for (int i = 0; i < 10; i++) {
    
    
            bw.write("Strong relationships need honesty.\t\t只有以诚相待,关系才会更加牢固。");
            bw.newLine();       //写入一个换行符 window  \r  \n  linux  \n
            bw.flush();
        }
        //关闭流
        bw.close();
        System.out.println("创建成功!");
    }
}

打印流:PrintWriter

  • 封装了print()/println()方法,支持写入后换行
  • 支持数据原样打印
package com.zhang.file;

import java.io.PrintWriter;

/**
 * PrintWriter的使用
 */
public class PrintWriterDemo01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //1.创建打印流
        PrintWriter pw = new PrintWriter("print.txt");
        //2.打印方法
        pw.println(97);
        pw.println(true);
        pw.println(3.14);
        pw.println("b");	//数据是什么就是什么
        //3.关闭流
        pw.close();
        System.out.println("执行完成!");
    }
}

桥转换流:InputStreamReader/OutputStreamWriter

  • 可将字节流转换为字符流
  • 可设置字符的编码方式

InputStreamReader

package com.zhang.file;

import java.io.FileInputStream;
import java.io.InputStreamReader;

/**
 * InputStreamReader读取文件,指定编码
 */

public class InputStreamReaderDemo01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //1.创建InputStreamReader对象
        FileInputStream fis = new FileInputStream("songs.txt");
        InputStreamReader isr = new InputStreamReader(fis, "utf-8");	//可指定编码格式
        //2.读取文件
        char[] buffer = new char[1024];
        int data = 0;
        while ((data = isr.read(buffer)) != -1) {
    
    
            System.out.print(new String(buffer, 0, data));
        }
        //3.关闭流
        isr.close();
        System.out.println("读取完成!");
    }
}

OutputStreamWriter

package com.zhang.file;

import java.io.FileOutputStream;
import java.io.OutputStreamWriter;

/**
 * 使用OutputStreamWriter写入文件,使用指定编码
 */
public class OutputStreamWriterDemo01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //1.创建OutputStreamWriter对象
        FileOutputStream fos = new FileOutputStream("ana02.txt");
        OutputStreamWriter osw = new OutputStreamWriter(fos, "utf-8");     //可指定编码格式
        //2.写入文件
        for (int i = 0; i < 10; i++) {
    
    
            osw.write("Bend down and climb up\t\t向下弯腰向上攀升!!!\n");
            osw.flush();
        }
        //3.关闭流
        osw.close();
        System.out.println("创建成功!!!");
    }
}

按功能:

  • 节点流:具有实际传输数据的读写功能
  • 过滤流:在节点流的基础之上增强功能

10.3、对象流

对象流:ObjectOutputStream/ObjectIntputStream

  • 增强了缓冲区功能
  • 增强了读写8种基本数据类型和字符串功能
  • 增强了读写对象的功能
    • readObject()从流中读取一个对象
    • writeObject()向流中写入一个对象

使用流传输对象的过程称为序列化、反序列化

使用ObjectOutputStream实现对象的序列化

package com.zhang.file;

import com.zhang.pojo.Student;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

/**
 * 使用ObjectOutPutStream实现对象的序列化
 * 要求:序列化的类必须要实现Serializable接口
 */
public class ObjectOutPutStreamDemo01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //1.创建对象流
        FileOutputStream fos = new FileOutputStream("stu.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        //2.序列化(写入操作)
        Student student = new Student("张三", 18);
        oos.writeObject(student);
        //3.关闭
        oos.close();
        System.out.println("序列化完毕!!!");
    }
}

使用ObjectInputStream实现反序列化

package com.zhang.file;

import com.zhang.pojo.Student;

import java.io.FileInputStream;
import java.io.ObjectInputStream;

/**
 * 使用ObjectInputStream实现反序列化
 */
public class ObjectInputStreamDemo01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //1.创建对象流
        FileInputStream fis = new FileInputStream("stu.bin");
        ObjectInputStream ois = new ObjectInputStream(fis);
        //2.反序列化(读取操作)
        Student o = (Student) ois.readObject();
        //3.关闭
        ois.close();
        System.out.println("反序列化完成!!!");
        System.out.println(o.toString());
    }
}

序列化与反序列化注意事项:

  1. 序列化的类必须要实现Serializable接口
  2. 序列化类中对象属性要求实现Serializable接口
  3. 序列化版本号ID,保证序列化的类和反序列化的类是同一个类
  4. 使用transient(瞬间的)修饰属性,这个属性就不能序列化了
  5. 静态属性不能序列化
  6. 序列化多个对象,可以借助集合来实现

10.4、File类

什么是文件?

文件可认为是相关记录或放在一起的数据的集合,代表物理盘符中的一个文件或者文件夹

File类常用方法

方法 概述
boolean createNewFile() 创建名称的空文件,不创建文件夹
boolean mkdir() 创建一个空目录
boolean delete() 删除文件或空目录
boolean delete() 判断文件或目录是否存在
String getAbsolutePath() 获取文件的绝对路径名
String getName() 获取文件的名称
String getParent() 获取文件/目录所在的目录
boolean isDirectory() 判断是否是目录
boolean isFile() 判断是否是文件
long length() 获取文件的长度/大小,单位为字节,如果文件不存在,返回0L
File[] listFiles() 列出目录中的所有内容
boolean renameTo() 修改文件名为xxx

文件操作

package com.zhang.file;

import java.io.File;
import java.util.Date;

/**
 * File类的使用
 * (1)分隔符
 * (2)文件操作
 * (3)文件夹操作
 */

public class FileDemo01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //separator();
        //fileOpe();
        directoryOpe();
    }

    //分隔符
    public static void separator() {
    
    
        System.out.println("路径分隔符" + File.pathSeparator);
        System.out.println("名称分隔符" + File.separator);
    }

    //文件操作
    public static void fileOpe() throws Exception {
    
    
        //1.创建文件    createNewFile()
        File file = new File("file.txt");
        //System.out.println(file);
        if (!file.exists()) {
    
         //如果这个文件不存在就创建该文件
            boolean newFile = file.createNewFile();
            System.out.println("创建结果:" + newFile);
        }
        //2.删除文件
        //2.1、直接删除
        //if (file.exists()) {    //如果这个文件存在就删除该文件
        //    System.out.println("删除结果:" + file.delete());
        //}
        //2.2、使用jvm退出时删除文件
        //Thread.sleep(5000);
        //file.deleteOnExit();
        //3.获取文件信息
        System.out.println("=====文件信息=====");
        System.out.println("获取文件绝对路径:" + file.getAbsolutePath());
        System.out.println("获取文件路径:" + file.getPath());
        System.out.println("获取文件名称:" + file.getName());
        System.out.println("获取文件父目录:" + file.getParent());
        System.out.println("获取文件长度:" + file.length());
        System.out.println("文件创建时间:" + new Date(file.lastModified()).toLocaleString());
        //4.判断
        System.out.println("是否是目录?" + file.isDirectory());
        System.out.println("是否是文件?" + file.isFile());
        System.out.println("是否可读?" + file.canRead());
        System.out.println("是否可写?" + file.canWrite());
        System.out.println("是否隐藏?" + file.isHidden());
        System.out.println("是否是绝对路径?" + file.isAbsolute());
    }

    public static void directoryOpe() throws Exception {
    
    
        //1.创建文件夹
        File dir = new File("E:\\study\\note\\chapter");
        System.out.println(dir);
        if (!dir.exists()) {
    
         //如果不存在就创建
            //dir.mkdir();    //只能创建单级目录
            System.out.println("创建结果:" + dir.mkdirs());       //创建多级目录
        }
        //2.删除文件夹
        //2.1、直接删除
        //if (dir.exists()) {    //如果这个目录存在就删除该目录   注:(只能删除空目录)
        //    System.out.println("删除结果:" + dir.delete());
        //}
        //2.2、使用jvm退出时删除文件
        //Thread.sleep(5000);
        //dir.deleteOnExit();
        //3.获取文件夹信息
        System.out.println("=====文件夹信息=====");
        System.out.println("获取绝对路径:" + dir.getAbsolutePath());
        System.out.println("获取路径:" + dir.getPath());
        System.out.println("获取文件夹名称:" + dir.getName());
        System.out.println("获取父目录:" + dir.getParent());
        System.out.println("目录创建时间:" + new Date(dir.lastModified()).toLocaleString());
        //4.判断
        System.out.println("是否是目录?" + dir.isDirectory());
        System.out.println("是否是文件夹?" + dir.isFile());
        System.out.println("是否隐藏?" + dir.isHidden());
        System.out.println("是否是绝对路径?" + dir.isAbsolute());
        //5.遍历文件夹
        File dir2 = new File("E:\\study\\note\\chapter");
        String[] files = dir2.list();       //获取该文件夹下的文件信息
        for (String file : files) {
    
    
            System.out.println(file);
        }
    }
}

FileFilter接口

public interface FileFilter

  • boolean accept(File pathname)

当调用File类中的listFiles()方法时,支持传入FileFilter接口实现类,对获取文件进行过滤,只有满足条件的文件才可出现在listFiles()的返回值中。

package com.zhang.file;

import java.io.File;
import java.io.FileFilter;

/**
 * FileFilter的使用,实现对文件的过滤
 */
public class FileFilterDemo01 {
    
    
    public static void main(String[] args) {
    
    
        directoryOpe(new File("E:\\study\\note\\chapter"));
    }

    public static void directoryOpe(File fileName) {
    
    
        File[] files = fileName.listFiles(pathname -> {
    
    
            if (pathname.getName().endsWith(".jpg")) return true;   //如果文件名后缀为.jpg(满足该条件)就返回true
            return false;
        });
        for (File file1 : files) {
    
    
            System.out.println(file1.getName());
        }
    }
}

递归遍历文件夹和递归删除文件夹

package com.zhang.file;

import java.io.File;

/**
 * 实现递归遍历文件夹和递归删除文件夹
 * 不能删除空目录
 */
public class ListDirDemo01 {
    
    
    public static void main(String[] args) {
    
    
        //listDir(new File("E:\\study\\note"));
        delDir(new File("E:\\study\\note"));
    }

    //1.1、递归遍历文件夹
    public static void listDir(File dir) {
    
    
        File[] files = dir.listFiles();
        System.out.println(dir.getAbsolutePath());
        if (files != null && files.length > 0) {
    
    
            for (File file : files) {
    
    
                if (file.isDirectory()) listDir(file);      //递归
                else System.out.println(file.getAbsolutePath());
            }
        }
    }

    //1.2、递归删除文件夹
    public static void delDir(File dir) {
    
    
        File[] files = dir.listFiles();
        if (files != null && files.length > 0) {
    
    
            for (File file : files) {
    
    
                if (file.isDirectory()) delDir(file);    //递归
                else System.out.println(file.getAbsolutePath() + "删除结果:" + file.delete());       //先删除文件,再删除文件夹
            }
            System.out.println(dir.getAbsolutePath() + "删除结果:" + dir.delete());       //先删除文件,再删除文件夹
        }
    }
}

11、常用类


12、反射


12.1、什么是类对象?

类的对象:基于某个类new出来的对象,也称为实例对象

类对象:类加载的产物,封装了一个类的所有信息(类名、父类、接口、属性、方法、构造方法…),每个类加载到内存中后都对应一个Class对象,每个类有且只有一个Class对象

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射

12.2、获取类对象

  1. 通过类的对象,获取类对象

    //语法:类名.getClass();
    Person zhangSan = new Person("张三", 18);
    Class<? extends Person> zhangSanClass = zhangSan.getClass();
    System.out.println(zhangSanClass.hashCode());
    
  2. 通过类名获取类对象

    //语法:Class c = 类名.class;
    Class<Person> personClass = Person.class;
    System.out.println(personClass.hashCode());
    
  3. 通过静态方法获取类对象

    //语法:Class<?> c = Class.forName("包名.类名");
    Class<?> c = Class.forName("com.zhang.reflect.Person");
    System.out.println(c.hashCode());
    

反射常用操作

package com.zhang.reflect;

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

    public Person(String name, int age) {
    
    
        this.name = name;
        this.age = age;
        System.out.println("带参构造执行!");
    }

    public Person() {
    
    
        System.out.println("无参构造执行!");
    }

    public String getName() {
    
    
        return name;
    }

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

    public int getAge() {
    
    
        return age;
    }

    public void setAge(int age) {
    
    
        this.age = age;
    }

    public void eat() {
    
    
        System.out.println(name + "吃东西!");
    }

    //带参方法
    public void eat(String food) {
    
    
        System.out.println(name + "开始吃:" + food);
    }

    //私有方法
    private void privateMethod() {
    
    
        System.out.println("这是一个私有方法!");
    }

    //静态方法
    public static void staticMethod() {
    
    
        System.out.println("这是一个静态方法!");
    }

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

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;

public class TestPerson {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //Person zhangSan = new Person("张三", 18);
        //zhangSan.eat();
        //reflectOpe1();
        //reflectOpe2();
        //reflectOpe3();
        reflectOpe4();

        Properties properties = new Properties();
        //properties.setProperty("name", "zhangSan");
        //System.out.println(properties);

        invokeAny(properties, "setProperty", new Class[]{
    
    String.class, String.class}, "userName", "张三");
        System.out.println(properties);
    }

    public static void getPersonClass() throws Exception {
    
    
        Person zhangSan = new Person("张三", 18);
        //1.通过类的对象,获取类对象
        Class<? extends Person> zhangSanClass = zhangSan.getClass();
        System.out.println(zhangSanClass);
        //2.通过类名获取类对象
        Class<Person> personClass = Person.class;
        System.out.println(personClass.hashCode());
        //3.通过静态方法获取类对象
        Class<?> c = Class.forName("com.zhang.reflect.Person");
        System.out.println(c.hashCode());
    }

    //1.使用反射获取类的名字、包名、父类、接口
    public static void reflectOpe1() throws Exception {
    
    
        Class<?> c = Class.forName("com.zhang.reflect.Person");
        System.out.println("类的名称:" + c.getName());
        System.out.println("包名:" + c.getPackage());
        System.out.println("父类:" + c.getSuperclass());
        System.out.println("接口:" + c.getInterfaces().hashCode());
    }

    //2.使用反射获取构造方法,创建对象
    public static void reflectOpe2() throws Exception {
    
    
        //1.获取类对象
        Class<?> c = Class.forName("com.zhang.reflect.Person");
        //2.获取类的构造方法  Constructors
        //Constructor<?>[] cons = c.getConstructors();
        //for (Constructor<?> con : cons) {
    
    
        //    System.out.println(con);
        //}

        //3.获取类中无参构造,创建对象
        Constructor<?> con = c.getConstructor();
        Person liSi = (Person) con.newInstance();
        liSi.setName("李四");
        System.out.println(liSi);

        //简易方法  类对象.newInstance();
        Person xiaoMing = (Person) c.newInstance();
        xiaoMing.setName("晓明");
        System.out.println(xiaoMing);

        //4.获取类中带参构造方法
        Constructor<?> con2 = c.getConstructor(String.class, int.class);
        Person xiaoLin = (Person) con2.newInstance("晓琳", 19);
        System.out.println(xiaoLin);
    }

    //3.使用反射获取类中方法,并调用方法
    public static void reflectOpe3() throws Exception {
    
    
        //1.获取类对象
        Class<?> c = Class.forName("com.zhang.reflect.Person");
        //2.获取方法,Method对象
        //2.1、getMethods()获取公开的方法,包括从父类继承的方法
        //Method[] methods = c.getMethods();
        //for (Method method : methods) {
    
    
        //    System.out.println(method);
        //}

        //2.2、getDeclaredMethods()获取类中的所有方法,包括私有、默认、保护(不包括继承的)
        //Method[] methods1 = c.getDeclaredMethods();
        //for (Method method : methods1) {
    
    
        //    System.out.println(method);
        //}

        //3.获取单个方法
        Method eatMethod = c.getMethod("eat");
        //调用方法
        //正常调用: Person zhangSan = new Person();  zhangSan.eat();
        System.out.println("*****调用无参方法*****");
        Person xiaoTian = (Person) c.newInstance();
        xiaoTian.setName("小天");
        eatMethod.invoke(xiaoTian);     //zhangSan.eat();
        //3.2 toString
        System.out.println("*****调用有返回值的方法*****");
        Method toStringMethod = c.getMethod("toString");
        Object result = toStringMethod.invoke(xiaoTian);
        System.out.println(result);
        //3.3 eatMethod 带参
        System.out.println("*****调用带参方法*****");
        Method eatMethod1 = c.getMethod("eat", String.class);       //调用的方法名称  参数类型
        eatMethod1.invoke(xiaoTian, "烧鸡");      //调用方法的对象  传递的参数
        //3.4 获取私有方法
        Method privateMethod = c.getDeclaredMethod("privateMethod");
        privateMethod.setAccessible(true);
        //调用私有方法必须设置访问权限无效  否则报错:IllegalAccessException(访问权限的异常)  Class com.zhang.reflect.TestPerson can not access a member of class com.zhang.reflect.Person with modifiers "private"

        privateMethod.invoke(xiaoTian);
        //3.4、获取静态方法
        Method staticMethod = c.getMethod("staticMethod");
        //正常调用  Person.staticMethod();
        staticMethod.invoke(null);
    }

    //4.使用反射实现一个可以调用任何对象方法的通用方法
    public static Object invokeAny(Object obj, String methodName, Class<?>[] types, Object... args) throws Exception {
    
    
        //1.获取类对象
        Class<?> c1 = obj.getClass();
        //2.获取方法
        Method method = c1.getMethod(methodName, types);
        //3.调用
        return method.invoke(obj, args);
    }

    //5.使用反射获取类中的属性
    public static void reflectOpe4() throws Exception {
    
    
        //1.获取类对象
        Class<?> c = Class.forName("com.zhang.reflect.Person");
        //2.获取属性(字段)公开的字段、父类继承的字段
        //Field[] fields = c.getFields();
        //for (Field field : fields) {
    
    
        //    System.out.println(field);
        //}

        //2.1、getDeclaredFields()获取类中所有的属性,包括私有、保护、默认,(不包含继承的)
        Field[] allFiled = c.getDeclaredFields();
        for (Field field : allFiled) {
    
    
            System.out.println(field);
        }

        //3.获取单个属性
        Field nameFiled = c.getDeclaredField("name");
        //3.1、赋值  3.2、获取值
        // 正常调用  Person xiaoKun = new Person();   xiaoKun.setName();
        Person xiaoKun = (Person) c.newInstance();
        //必须给私有属性设置访问权限无效 否则报错:IllegalAccessException(访问权限的异常)   Class com.zhang.reflect.TestPerson can not access a member of class com.zhang.reflect.Person with modifiers "private"
        nameFiled.setAccessible(true);
        nameFiled.set(xiaoKun, "小坤");     //xiaoKun.setName("小坤");
        System.out.println(nameFiled.get(xiaoKun));  //xiaoKun.getName();
    }
}

12.3、设计模式

什么是设计模式?

一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,简而言之来说就是特定问题的固定解决方法

好处:使用设计模式为了可重用代码、让代码更容易被他人理解、增强代码的可靠性、重用性

GOF

工厂设计模式

  • 工厂设计模式主要负责对象的创建
  • 开发中有一个非常重要的原则“开闭原则”,对拓展开放、对修改关闭
  • 可通过反射进行工厂模式的设计,完成动态的对象创建

猜你喜欢

转载自blog.csdn.net/zhang_0202/article/details/119836859
今日推荐