政府网站群建设/浏览器下载安装2022最新版
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关键字创建的时候,除了分配内存空间之外,还会给创建好的对象,进行默认的初始化,以及对类中构造器的调用
-
类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下两个特点:
- 方法名和类的名字相同
- 没有返回类型,也不能写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、封装的使用
封装的步骤:
- 修改属性的可见性 设为private
- 创建公有的getter/setter方法 用于属性的读写
- 在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;}
}
封装的好处
- 提高程序的安全性,保护数据
- 隐藏代码的实现细节,保留对外接口(get/set)
- 增强系统的可维护性
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{@Overridepublic 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、多态的使用
多态存在的条件:
- 有继承关系
- 子类重写父类方法
- 父类引用指向子类对象 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{@Overridepublic void eat() {System.out.println("猫咪正在吃猫粮!");}public void run() {System.out.println("猫咪正在跑!");}
}
package com.zhang.polymorphic;public class Bird extends Animal {@Overridepublic 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{@Overridepublic 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、接口的使用
-
定义接口
//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(); }
-
实现接口
//类 可以实现接口!使用implement关键字实现接口 //实现类接口的类,就需要重写接口中的方法 //多继承~利用接口实现伪多继承! 可为类扩充多种能力 public class UserServiceImpl implements UserService,TimeService{@Overridepublic void add(String name) {}@Overridepublic void del(String name) {}@Overridepublic void update(String name) {}@Overridepublic void check(String name) {}@Overridepublic void time() {}
-
使用接口
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(){@Overridepublic 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 | 并发修改异常 |
这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;
Error和Exception的区别:
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 { //处理善后工作 最终都会执行,除非手动退出JVMSystem.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);
}
如果在一个方法体中抛出了异常,如何通知调用者?
- 声明异常,多个异常用逗号隔开
- 方式一:调用者,处理异常;
- 方式二:调用者,继续声明异常;
使用原则:底层代码向上声明或者抛出异常,最上层一定要处理异常,否则程序中断
抛出异常:
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
类即可!
使用自定义异常类,大体分为以下几个步骤:
- 创建自定义异常类,继承Exception类
- 在方法中通过throw关键字抛出异常对象
- 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;或者在方法的声明通过throws关键字指明要抛出给方法的调用者的异常,继续下一步操作
- 在出现异常方法的调用者中捕获并处理异常
package com.zhang;public class Person {private String gender;public Person(String gender) {this.gender = gender;}public Person() {}@Overridepublic 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 codeif (minCapacity - elementData.length > 0)grow(minCapacity);
}private void ensureExplicitCapacity(int minCapacity) {modCount++;// overflow-conscious codeif (minCapacity - elementData.length > 0)grow(minCapacity);
}private void grow(int minCapacity) {// overflow-conscious codeint 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() {}@Overridepublic int hashCode() {int n1 = this.name.hashCode();int n2 = this.age + 31; //(1) 31是一个质数(素数),只能被1和它自身整除的,减少散列冲突 (2)提供执行效率31*i=(i<<5)-ireturn n1 + n2;}@Overridepublic 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.条件都不满足返回falsereturn false;}@Overridepublic 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 否则为falseSystem.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() {}@Overridepublic 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);}@Overridepublic int hashCode() {return Objects.hash(name, age);}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}//先按照姓名比较 ,然后再按年龄比较@Overridepublic 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>() {@Overridepublic 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效率要高于keySetSystem.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效率要高于keySetSystem.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>() {@Overridepublic 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效率要高于keySetSystem.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 二分查找器 找到集合中的元素下标 找不到返回-1int 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.UnsupportedOperationExceptionList<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(); //取出keyCountry 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、线程的创建方式
-
继承java.lang.Thread类
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
//创建线程方式一:继承Thread类 重写run方法,调用start开启线程 public class MyThread extends Thread {@Overridepublic 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调度执行
-
实现java.lang.Runnable接口
- 定义MyRunnable类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
//龟兔赛跑 public class Race implements Runnable {private static String winner; //获胜者@Overridepublic 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{@Overridepublic void happyMarry() {System.out.println("张先生要结婚了!真开心");}
}//代理角色,帮助你结婚
class WeddingCompany implements Marry{//代理谁--> 真实目标角色private Marry target;public WeddingCompany(Marry target) {this.target = target; //这是真实对象}@Overridepublic 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;@Overridepublic 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();@Overridepublic 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 \nbw.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());}
}
序列化与反序列化注意事项:
- 序列化的类必须要实现Serializable接口
- 序列化类中对象属性要求实现Serializable接口
- 序列化版本号ID,保证序列化的类和反序列化的类是同一个类
- 使用transient(瞬间的)修饰属性,这个属性就不能序列化了
- 静态属性不能序列化
- 序列化多个对象,可以借助集合来实现
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(满足该条件)就返回truereturn 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、获取类对象
-
通过类的对象,获取类对象
//语法:类名.getClass(); Person zhangSan = new Person("张三", 18); Class<? extends Person> zhangSanClass = zhangSan.getClass(); System.out.println(zhangSanClass.hashCode());
-
通过类名获取类对象
//语法:Class c = 类名.class; Class<Person> personClass = Person.class; System.out.println(personClass.hashCode());
-
通过静态方法获取类对象
//语法: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("这是一个静态方法!");}@Overridepublic 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 toStringSystem.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
工厂设计模式
- 工厂设计模式主要负责对象的创建
- 开发中有一个非常重要的原则“开闭原则”,对拓展开放、对修改关闭
- 可通过反射进行工厂模式的设计,完成动态的对象创建