JavaSE学习
数组
数组的初始化
int[] ids = new int[]{1,2,3}; //1. 数组静态初始化 数组的初始化和数组元素的赋值操作同时进行
String[] names = new String[3]; //2. 动态初始化 数组的初始化和数组元素的赋值操作分开进行
属性
属性和局部变量
相同点:
- 定义的格式 数据类型 变量名 = 变量值
- 先声明,后使用
- 变量都有其对应的作用域
不同点:
-
在类中声明的位置不同
属性:直接定义在类中的{}内
局部变量:声明在方法内、方法形参、代码块、构造器形参、构造器内部的变量
-
关于权限修饰符的不同
属性:可以在声明属性是,指明其权限、使用权限修饰符
局部变量:不可以使用权限修饰符
-
默认初始化值
属性:有默认初始化值
局部变量:没有默认初始化值
-
位置
属性:存放在堆空间中
局部变量:存放在栈空间中
方法
描述类应该具有的功能
-
方法的声明: 权限修饰符 返回值类型 方法名(形参列表){
? 方法体
-
说明
- 关于权限修饰符:关于JAVA规定的4种权限修饰符:private、public、缺省、protected
- 返回值类型:
- 有返回值:必须在方法声明时,指定返回值的类型。同时,方法中,需要使用return关键字来返回指 定类型的变量或常量
- 无返回值:方法声明时,使用void来表示,如要使用,只能“return;”结束方法
- 方法名:属于标识符,遵循标识符规则和规范,”见名知意“
- 形参列表:方法可以声明0个,1个,或多个形参。格式:数据类型1 形参1,数据类型2 形参2,...
重载和重写
重载
-
定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
-
"两个一不同":同一个类、相同的方法名
? 参数列表不同,参数个数不同,参数类型不同
-
判断是否是重载:
? 跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系!
-
在通过对象调用方法时,如果确定某一个指定的方法:
? 方法名 ---> 参数列表
重写
什么是重写?
子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
重写的规则
方法的声明: 权限修饰符 返回值类型 方法名(形参列表) throws异常的类型{
? //方法体
约定俗称:子类中的叫重写的方法,父类中的叫被重写方法
-
子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
-
子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
特殊情况:子类不能重写父类中声明为private权限的方法
-
返回值类型:
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
- 父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须是double)
-
子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)
-
子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写,要么都声明为static的(不是重写))
重载和重写的区别
-
概念的不同
-
重写和重载的具体规则不同
- 重载发生在本类,重写发生在父类与子类之间
- 重载的方法名必须相同,重写的方法名相同且返回值类型必须相同
- 重载的参数列表不同,重写的参数列表必须相同
-
重载:不表现为多态性
重写:表现为多态性。
this和super
this关键字的使用
- this可以用来修饰:属性、方法、构造器
- this修饰属性和方法:
- this理解为:当前对象 或 当前正在创建的对象
- 在类的方法中,我们可以使用“this.属性”或"this.方法",调用当前对象属性或方法,但是通常情况下,我们都选择省略"this."。特殊情况下,如果方法的形参和类的属性同名是,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
- 在类的构造器中,我们可以使用“this.属性”或"this.方法",调用当前正在创建的对象属性或方法,但是通常情况下,我们都选择省略"this."。特殊情况下,如果构造器的形参和类的属性同名是,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
super关键字的使用
- super关键字可以理解为:父类的
- 可以用来调用的结构:属性、方法、构造器
- super调用属性、方法:
- 我们可以在子类的方法或构造器中,通过使用"super.属性"或者"spuer.方法"的方式 ,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
- 特殊情况,当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性。
- 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法。
- super调用构造器
- 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式。调用父类中声明的指定的构造器
- "super(形参列表)"的使用,必须声明在子类构造器的首行!
- 我们在类中的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能选择一个使用,不能同时出现
- 在构造器的首行,没显式的声明"this(形参列表)"或"super(形参列表)",则默认调用的是父类中空参的构造器:super()
- 在类的多个构造器中,至少一个类的构造器使用了"super(形参列表)",调用父类中的构造器
封装性
封装就是隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别,将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体。
追求"高内聚,低耦合"
- 高内聚:类的内部数据操作细节自己掌握,不允许外部干涉
- 低耦合:仅对外暴露少部分的方法便于使用
隐藏对象内部的复杂性,只对外公开简单的接口,便于外界使用,从而提高系统的可扩展性、可维护性。通俗的说,把改隐藏的隐藏起来,该暴露的暴露出来,这就是封装性的设计思想,
封装性的体现
- 将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值。
- 不对外暴露私有的方法
- 单例模式(将构造器私有化)
- 如果不希望类在包外被调用,可以将设置为缺省的。
继承性
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
继承性的好处
- 减少了代码的冗余性,提高了代码的复用性
- 便于功能的扩展
- 为之后多态性的使用,提供了前提
继承性的格式
class A extends B{
? A:子类、派生类、subclass
? B:父类、超类、基类、superclass
子类继承父类的不同之处
- 体现:一旦子类A继承父类B之后,子类A就获取了父类B中的声明的所有属性和方法。特别的,父类中声明为private的属性和方法,子类继承父类之后,仍然认为获取了父类中私有的结构。只因为封装性的影响,使得子类不能直接调用父类的结构而已。
- 子类继承父类以后,还可以声明自己特有的属性和方法。类实现功能的扩展。
Java继承性的说明
- 一个类可以被多个子类继承
- Java中类的单继承性,一个类只能继承一个父类
- 子父类是相对的概念
- 子类直接继承的父类,称之为直接父类,间接继承的父类成为:间接父类
- 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
多态性
多态是指一个类实例(对象)的相同方法在不同情形有不同表现形式
理解多态性
一个事物的多种形态
何为多态性
对象的多态性:父类的引用指向子类的对象(或者子类的对象赋给父类的引用)
举例:
Person p = new Man();
Objoct obj = new Date();
多态的使用,虚拟方法的调用
有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法,总结:编译,看左边;运行,看右边。
多态性的使用前提
- 类的继承关系
- 方法的重写
多态性使用的注意点
对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
"=="和equals()的区别
==
-
可以使用在基本数据类型变量和引用数据类型变量中
-
如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(类型不一定相同)
如果比较的是引用数据类型变量:比较两个变量保存的数据是否相同。-
equals()
-
是一个方法,而非运算符
-
只能适用于引用数据类型
-
Object类中equals()的i当以:
publi boolean equals(Object obj){
? return (this == obj);
说明:Object类中定义的equals()和==的作用是相同的。
-
像String、Date、File,包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。
-
通常情况下,我们自定义的类如果使用equals()的话,他通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写。
重写的原则:比较两个对象的实体内容是否相同
因为在Object类中,equals()方法它之间的比较是用"=="来比较的,所以要重写equals()来比较两个对象的"实体内容"是否相同。
toString
1. toString的说明
-
当我们输出一个对象的引用时,实际上就是调用对象的toString()
-
Object类中toString的定义:
public String toString(){
? return getClass().getName() + "@" + Interger.toHexString(hashCode());
-
像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回"实体信息"
-
自定义类也可以重写toStrng()方法,当调用此方法时,返回对象的“实体内容”。
包装类
基本数据类型具有类的特征
包装类的使用
-
java提供了8中基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
Byte,Short,Integer,Long,Float,Double,Boolean,Character 前六个的父类时Number
-
掌握的:基本数据类型、包装类、String三者之间的相互转换
基本数据类型<--->包装类:自动装箱和自动拆箱
基本数据类型、包装类---->String:调用String重载的valueOf(Xxx xxx)
String--->基本数据类型、包装类:调用包装类的parseXxx(String s)
转换时,可能会包NumberFormatException
static
1. 可以用来修饰的结构:
主要用来修饰类的内部结构
属性、方法、代码块、内部类
-
static修饰属性:静态变量(或类变量)
-
属性,是否使用static修饰,又分为:静态属性 vs 非静态属性(实际变量)
实际变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性,当修改其中以恶对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静变量时,会导致其他对象调用此静态变量时,是修改过了的。
-
static修饰属性的其他说明:
-
静态变量随着类的加载而加载。可以通过”类.静态变量“的方式进行调用
-
静态变量的加载要早于对象的创建
-
由于类只会加载一次,则静态变量再内存中也只会存在一份:存在方法区的静态域中。
-
? 类变量 实例变量
类 yes no
对象 yes yes
-
-
静态属性距离:System.out,Math.PI;
-
2. static的内存解析
3. static修饰方法:
静态方法、类方法
-
随着类的加载而加载,可以通过”类.静态方法“的方式进行调用
-
? 静态方法 非静态方法
? 类 yes no
? 对象 yes yes
-
静态方法中,只能调用静态的方法或属性
非静态发放中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
4. static的注意点:
- 在静态的方法内,不饿能使用this关键字、super关键字
- 关于静态属性和静态方法的使用,要从生民周期的角度去理解。
5. 如果判断属性和方法应该使用static关键字:
- 关于属性
- 属性是可以被多个对象所共享的,不会随着对象的不同而不同
- 类中的常量也常常声明为static
- 关于方法
- 操作静态属性的方法,通常设置为static的
- 工具类中的方法,习惯上声明为static的。比如:Math、Arrays、Collections
6. 使用举例:
举例一:Math、Arrays、Collections等工具类
举例二:单例模式
举例三:
设计模式
1. 设计模式的说明
-
理解
设计模式是在大量的实践中总结和理论化之后优的代码结构、编程风格、以及解决问题的思考方法。
-
常用的设计模式 ---23中经典的设计模式
创建型模式:共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构性模式:共7种:设配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
2. 单例模式
2.1 要解决的问题:
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统种,对某个类只能存在一个对象实例。
2.2 具体代码的实现:
饿汉式1:
class Bank{
//1. 私化的构造器
private Bank(){
}
//2. 内部创建类的对象
//4. 要求此对象也必须声明为静态的
private static Bank instance = new Bnak();
//3. 提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
饿汉式2:
class Bank{
//1. 私化的构造器
private Bank(){
}
//2. 内部创建类的对象
//4. 要求此对象也必须声明为静态的
private static Bank instance = null;
static{
instance = new Bnak();
}
//3. 提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
懒汉式(目前线程不安全,要用到锁来让线程变得安全):
class Bank{
//1. 私化的构造器
private Bank(){
}
//2. 内部创建类的对象
//4. 要求此对象也必须声明为静态的
private static Bank instance = null;
//3. 提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
if(instance == null){
instance = new Bank();
}
return instance;
}
}
懒汉式(线程安全的):
class Bank{
//1. 私化的构造器
private Bank(){
}
//2. 内部创建类的对象
//4. 要求此对象也必须声明为静态的
private static Bank instance = null;
//3. 提供公共的静态的方法,返回类的对象
public synchronized static Bank getInstance(){
if(instance == null){
instance = new Bank();
}
return instance;
}
}
2..3 两种方式的对比:
饿汉式:
? 坏处:对象加载时间过长
? 好处:饿汉式是线程安全的
懒汉式:
? 好处:延迟对象的创建
3 模板方法设计模式
3.1 要解决的问题
- 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的暴露出去,让子类实现。
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了,但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
4 代理模式(Proxy)
4.1 概述
代理模式是Java开发使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
4.2 应用场景
- 安全代理:屏蔽对真实角色的直接访问。
- 远程代理:通过代理类处理远程方法调用(RMI)。
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。
4.3 分类
- 静态代理(静态定义代理类)
- 动态代理(动态生成代理类)
- JDK自带的动态代理,需要反射等知识
5 工厂模式
5.1 概念
工厂模式:实现了创建者和调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
其实设计模式和面向对象设计原则都是为了使得开发项目更加容易扩展和维护,解决方式就是要给”分工“。
核心本质:
实例化对象,用工厂方法代替new操作。将选择实现类、创建对象统一管理和控制,从而将调用者跟我们的实现类解耦。
5.2 举例
-
无工厂模式
interface Car{ void run(); } class Audi implenments Car{ public void run(){ System.out.println("奥迪在跑"); } } class BYD implenments Car{ public void run(){ System.out.println("比亚迪在跑"); } } public class Client01{ public static void main(String[] arg){ Car a = new Audi(); Car b = new BYD(); a.run(); b.run(); } }
-
简单工厂模式
简单工厂模式,从命名上就可以看出这个模式一定很简单,他存在的目的很简单,定义一个用于创建对象的工具类。
interface Car{ void run(); } class Audi implenments Car{ public void run(){ System.out.println("奥迪在跑"); } } class BYD implenments Car{ public void run(){ System.out.println("比亚迪在跑"); } } class CarFactory{ //方式一 public static Car getCar(String type){ if("奥迪".equals(type)){ return new Audi(); }else if("比亚迪".equals(type){ return new BYD(); }else{ return null; } } //方式二 // public static CargetAudi(){ // return new Audi(); // } // public static CargetBYD(){ // return new BYD(); // } } public class Client02{ public static void main(String[] arg){ Car a = CarFactory.getCar("奥迪"); a.run(); Car b = CarFactory.getCar("比亚迪"); b.run(); } }
把调用者和创建者分离,简单共产也叫静态工厂模式,就是共产类一般是使用静态方法,通过接受的参数不同来返回不同的实例对象。
缺点:对于增加新产品,不修改代码的话,是无法扩展的。违反了开闭原则(对扩展开放,对修改封闭)。
-
工厂方法模式
为了避免简单工厂模式的缺点,不完全满足OCP(对扩展开放,对修改关闭)。工厂方法模式和简单工厂模式最大的不同在于,简单工厂只有一个(对于一个项目或者一个独立的模块而)共产类,而工厂方法模块有一组实现了相同接口的工厂类。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。
interface Car{ void run(); } class Audi implenments Car{ public void run(){ System.out.println("奥迪在跑"); } } class BYD implenments Car{ public void run(){ System.out.println("比亚迪在跑"); } } //工厂接口 interface Factory{ Car getCar(); } //两个工厂类 class AudiFactory implements Factory{ public Audi getCar(){ return new Audi(); } } class BYDFactory implements Factory{ public BYD getCar(){ return new BYD(); } } public class Client{ public static void main(String[] arg){ Car a = AudiFactory.getCar(); a.run(); Car b = BYDFactory.getCar(); b.run(); } }
-
抽象工厂模式
抽象工厂模式和工厂方法模式的区别在于需要创建对象的复杂程度上,而且抽象工厂模式是三个里面最为抽象的、最具一般性的。抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象。而且使用抽象工厂模式还要满足以下条件:
- 系统中由多个产品族,而系统一次只可能消费其中一族产品。
- 同属于用一个产品族的产品以其使用。
代码块
1. 代码块的作用:
用来初始化类、对象的信息
2.分类:
代码块要是使用修饰符,只能使用static
分类:静态代码块 vs 非静态代码块
3. 静态代码块和非静态代码块
静态代码块:
- 内部可以输出语句
- 随着类的加而执行,而且只执行一次
- 作用:初始化类的信息
- 如果一个类中定义了多个静态的代码块,则按照声明分先后顺序执行
- 静态代码块的执行要优先于非静态代码块的执行
- 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
非静态代码块:
- 内部可以输出语句
- 随着类的加而执行,而且只执行一次
- 作用:可以在创建对象时,对对象的属性进行初始化
- 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
- 非静态代码块内可以调用静态的属性、静态的方法,或用非静态的属性、非静态的方法
4. 加载顺序
实例化子类对象时,涉及到父类、子类中静态代码块、非静态代码库奥、构造器的加载的加载顺序:
由父及子,静态先行。
- 默认初始化
- 显式初始化
- 构造器中初始化
- 有了对象以后,可以通过“对象.属性”或“对象.方法”的方式,进行赋值
- 在代码块中赋值
执行的先后顺序:1 - 2 / 5 - 3 - 4
final
最终的
1. 可以用来修饰:
类、方法、变量
2. 具体的:
-
final用来修饰一个类:此类不能被其他类所继承。
比如:String类、System类、StringBuffer类
-
final用来修饰方法:表明此方法不可以被重写
比如:Object类中getClass();
-
final用来修饰变量:此时的”变量“就称为是一个常量
- final修饰属性:可以考虑赋值的位置:显式初始化、代码块中初始化、构造器中初始化
- final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
static final 用来修饰属性:全局变量
abstruct
- abstra:抽象的
- abstract可以用来修饰的结构:类、方法
1. 具体的
-
abstract修饰类:抽象类
- 此类不能实例化
- 抽象类中一定有构造器,便于类实例化时调用(涉及:子类对象实例化的全过程)
- 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作。--->抽象的前提:继承性
-
abstract修饰方法:抽象方法
-
抽象方法只有方法的声明,没有方法体
-
包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
-
若子类重写了父类中的所有的抽象方法后,此子类可实例化
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
-
2. 注意点
- abstract不能用来修饰:属性、构造器等结构
- abstract不能用来修饰私有方法、静态方法、final的方法、final的类
3. 应用举例
举例一:
public abstract class Vehicle{
public abstract double calcFuelEfficiency(); //计算燃料效率的抽象方法
public abstract double calcTripDistance(); //计算行驶距离的抽象方法
}
public class Truck extends Vehicle{
public abstract double calcFuelEfficiency(); //写出计算卡车的燃料效率的具体方法
public abstract double calcTripDistance(); //写出计算卡车行驶举例的具体方法
}
public class RiverBarge extends Vehicle{
public abstract double calcFuelEfficiency(); //写出计算驳船的燃料效率的具体方法
public abstract double calcTripDistance(); //写出计算驳船行驶举例的具体方法
}
举例二:
abstract class GemetricObject{
public abstract double findArea();
}
class Circle extends GemetricObject{
private double radius;
public double findArea(){
return 3.14 * radius * radius;
}
}
举例三:IO流中涉及到的抽象类:InputStream / OutputStream / reader / Writer
interface
1. 使用说明
-
接口使用interface来定义
-
在java中,接口和类时并列的两个结构
-
如果定义接口:定义接口中的成员
- JDK7及以前:只能定义全局常量和抽象方法
- 全局常量:public static final的但是书写时可以省略不写
- 抽象方法:public abstract的
- JDK8: 除了定义全局常量和抽象方法之外,还可以定义静态方法,默认方法
- JDK7及以前:只能定义全局常量和抽象方法
-
接口中不能定义构造器的!意味着接口不可以实例化
-
java开发中,接口通过让类去实现(implements)的方式来使用。
如果实现类覆盖了接口中的所抽象方法,则此实现类就可以实例化
如果实现类没覆盖接口中所抽象方法,则此实现类仍为一个抽象类
-
java类可以实现多个接口 --->弥补了java单继承i性的局限性
格式:class AA extends BB implements CC,DD,EE
-
接口与接口之间可以继承,而且可以多继承
-
接口的具体使用,体现多态性
-
接口,实际上可以看作是一个规范
2. 举例
体会:
- 接口使用上也满足多态性
- 接口,实际上就是定义了一宗规范
- 开发中,体会面向接口编程!
3. 体会面向接口编程的思想
面向接口编程:我们在应用程序中,调用的结构都是JDBC中定义的接口,不会出现具体某一个数据库厂商的API。
4. java8中关于接口的新规范
-
接口中定义的静态方法,只能通过接口来调用
-
通过实现类的对象,可以调用接口中的默认方法,如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
-
如果子类(或实现类)继承的父类和实现类的接口中声明了同名同参数的默认方法,那么子类在设置此方法的情况下,默认调用的是父类中的同名同参数的方法。--->类优先原则
-
如果实现类实现了多个接口,而这多个接口中定义了同参数的默认方法,那么在实现类没重写此方法的情况下,报错。--->接口冲突。这就需要我们必须在实现类中重写此方法。
-
如果在子类(或实现类)的方法中,调用父类、接口中被重写的方法
public void myMethod(){ method3();//调用自己定义的重写的方法 super.method3();//调用的是父类中声明的 //调用接口的默认方法 CompareA.super.method3(); CompareB.super.method3(); }
5. 面试题
抽象类和接口的异同?
相同点:不能实例化;都可以包含抽象方法的。
不同点:
-
把抽象类和接口(java7,java8,java9)的定义、内部接口解释说明
-
类:但继承性 接口:多继承
类与接口:多实现
内部类
1. 定义
Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B为外部类
2. 内部类的对象
成员内部类(静态、非静态) vs 局部内部类(方法内、代码块内、构造器内)
3. 成员内部类的理解
一方面,作为外部类的成员:
- 调用外部类的结构
- 可以被static修饰
- 可以被4中不同的权限修饰
另一方面,作为一个类:
- 类内可以定义属性、方法、构造器等
- 可以被final修饰,表示此类不能被继承。言外一直,不使用final就可以被继承
- 可以被abstract修饰
4. 成员内部类
-
如果创建成员内部类的对象?(静态的,非静态的)
//创建静态的Dog实例(静态的成员内部类): Person. Dog dog = new Person.Dog(); //创建非静态的Bird实例(非静态的成员内部类): //Person.Bird bird = new Person.Bird(); 错误的 Person p = new Person(); Person.Bird bird = p.new Bird();
-
如果在成员内部类中调用外部类的结构
class Person{ String name = "小明"; //非静态成员内部类 class Bird{ String name = "杜鹃"; Bird(){ } public void sing(){ System.out.println("我是一只小小鸟"); Person.this.eat(); //调用外部类的非静态属性 eat(); System.out.println(age); } public void display(String name){ System.out.println(name); //方法的形参 System.out.println(this.name); //内部类的属性 System.out.println(Person.this.name); //外部类的属性 } } }
5. 局部内部类的使用
//返回一个实现了Comparable接口的类的对象
public Comparable getComparable(){
//创建一个实现了Comparable接口的类:局部内部类
//方式一:
//class MyComparable implements Comparable{
// public int compareTo(Object o){
// return 0;
// }
//}
//return new MyComparable();
//方式二:
return new Comparable(){
public int compareTo(Object o){
return 0;
}
};
}
总结:成员内部类和局部内部类,在编译以后,都会生成字节码文件
异常
1. 异常的体系结构
1.1 java.lang.Throwable
-
ava.lang.Error:一般不编写针对性的代码进行处理。
-
java.lang.Exception:可以进行异常的处理
- 编译时异常(checked)
- IOException
- FileNotFoundException
- ClassNotFoundException
- IOException
- 运行时异常(unchecked,RuntimeException)
- NullPointerException
- ArrayIndexOutOfBoundsException
- ClassCastExeption
- NumberFormatException
- InputMismatchException
- ArithmeticException
- 编译时异常(checked)
1.2 编译时异常和运行时异常
编译时异常:执行javac.exe命名时,可能出现的异常
运行时异常:执行java.exe命名时,出现的异常
1.3 常见的异常类型
- NullPointerException
- ArrayIndexOutOfBoundsException
- ClassCastExeption
- NumberFormatException
- InputMismatchException
- ArithmeticException
2. 异常的处理:抓抛模型
过程一:“抛":程序在正常执行的过程中,一旦出现问题,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出。一旦抛出对象以后,其后的代码就不再执行。
关于异常对象的产生:
- 系统自动生成的异常对象
- 手动生成一个异常对象,并抛出(throw)
过程二:“抓”:可以理解为异常的处理方式:
- try-catch-finally
- throws
- ?
2.1 异常的处理方式一:try-catch-fianlly
2.1.1 使用说明
try - catch - finally的使用
try{
? //可能出现异常的代码
}catch(异常类型1 变量名1){
? //处理异常的方式1
}catch(异常类型2 变量名2){
? //处理异常的方式2
}catch(异常类型3 变量名3){
? //处理异常的方式3
……
finally{
? //一定会执行的代码
说明:
-
finally是可以没有的。
-
使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch进行匹配。
-
一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前try-catch-finally。继续执行后面的代码
-
catch中的异常类型如果没子父类关系,则谁声明在上,谁声明在下无所谓
catch中的异常类型如果有子父类关系,则要求子类一定声明在父类的撒谎给你面,否则,报错
-
常用的异常对象处理的方式:1. String getMessage() 2. printStackTrace()
-
在try结构中声明的变量,再出了try结构以后,就不能再被调用
-
try-catch-finally结构可以嵌套
总结:如何看待代码中的编译时异常和运行时异常
体会1:使用try-catch-finally处理编译时异常,是得程序再编译时就不再报错,但是运行时仍可能报错。相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了,针对编译时异常,我们说一定要考虑异常的处理。
2.1.2 finally的在说明:
- finally时可以不写的。
- finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try中return语句,catch中return语句等情况。
- 像数据库链接、输入输出流、网络编程Socket等资源,JVM时不能自动回收的,我们需要自己搜东进行资源的释放。此时的资源释放,就需要声明再finally中。
- ?
2.1.3 面试题
fianl、finally、finalize三者的区别?
throw和throws的区别?
2.2 异常处理方式二:thorws + 异常类型
”throws + 异常类型“写在方法的声明处。知名此方法执行时,可能会抛出的异常类型。一旦当方法体执行时,出现异常,仍会在异常代码出生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。异常代码后续的代码不再执行
2.3 对比两种处理方式
try-catch-finally:真正的见异常给处理掉了
throws的方式只是将异常抛给了方法的调用者,并没有真正将异常处理掉。
2.4 体会开发中应该如果选择两种处理方式
- 如果父类中被重写的方法没throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中异常,必须使用try-catch-finally方式出处理。
- 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理,而执行的方法a可以考虑使用try-catch-finally方式进行处理。
3. 手动抛出异常
3.1 使用说明
在程序执行中,除了自动抛出异常对象的情况之外,我们还可以手动的throw一个异常类的对象。
3.2 面试题
throw 和 throws的区别:
throw表示抛出一个异常类的对象,生成异常对象的过程。声明在方法体内
throws属于异常处理的一种方法,声明在方法的声明处。
3.3 例题
4. 自定义异常类
如果自定义异常类?
- 继承于现的异常结构:RuntimeException、Exception
- 提供全局常量:serialVersionUID
- 提供重载的构造器
public class EcDef extends Exception {
static final long serialVersionUID = -3387516934524229948L;
public EcDef(){
}
public EcDef(String message) {
super(message);
}
}
多线程
1. 程序、进程、线程的理解
1.1 程序(programm)
概念:是为了完成特定任务、用某种语言编写的一组指令的结合。既指一段静态的代码,静态对象,
1.2 进程(process)
概念:是程序的一次执行过程,或是一个正在运行的一个程序。
说明:进程作为资源分配的单位,系统运行时会为每个进程分配不同的内存区域。
1.3 线程(thread)
概念:是一个程序内部的一条执行路径。
说明:线程作为调度和执行的单位,每个线程都拥有独立的运行栈和程序计数器(pc)。多个线程,共享同一个进程的结构:方法去、堆。
2. 并行与并发
2.1 单核CPU与多核CPU的理解
- 单核CPU其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。但是因为CPU时间单元特别短,所以感觉不出来。
- 如果是多核的化,才能更好的发挥多线程的效率。
- 一个java的应用程序java.exe,其实至少有3个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
2.2 并行与并发的理解
- 并行:多个CPU同时执行多个任务。如果:多个人同时做不同的事。
- 并发:一个CPU(采用时间片)同时执行多个任务。
3. 创建多线程的两种方式
3.1 方法一:继承Thread的方式
- 创建一个继承于Thread类的子类
- 重写Thread类的run()-->将此线程执行的操作声明在run()中
- 创建Thread的子类的对象
- 通过此对象调用start():1. 启动当前线程 2. 调用当前线程的run()
说明两个问题
问题一:我们启动一个线程,必须调用start(),不能调用run()的方式启动线程。
问题二:如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start()。
3.2 方法二:实现Runnable接口的方式
- 创建一个实现了Runnable接口的类
- 实现类去实现Runnable中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start()
3.3 两种方式的对比
开发中:优先选择:实现Runnable接口的方式
原因:
- 实现的方式没类的单继承性的局限性
- 实现的方式更适合来处理多个线程共享数据的情况。
联系:public class Tread implements Runnable
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明再run()中。目前这两种方法,要想启动线程,都是调用的Thread类中的start()。
4. Thread类中的常用方法
- start():启动当前线程;调用当前线程的run()
- run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明再此方法中
- currentThread():静态方法,返回执行当前代码的线程
- getName:获取当前线程的名字
- setName:设置当前线程的名字
- yield():释放当前CPU的执行权
- join():在线程a中调用线程b的join(),此时线程a就进入了阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
- stop:已过时,当执行方法时,强制结束当前线程。
- sleep(long millitime):让当前线程"睡眠"指定的millitime毫秒,在指定的millitime毫秒时间内,当前线程是阻塞状态。
- isAlive():判断当前线程是否存活。
线程的优先级:
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5 -->默认优先级
说明:高优先级的线程要抢占低优先级线程cpu的是执行权。但是知识从概率上讲,高优先级的线程高于低优先级的线程先被执行。并不意味着只有当高优先级线程执行完成之后,低优先级的线程才执行。
线程通信:wait() / notify() / notifyAll() ;此三个方法定义在Object类中的。
补充:线程的分类
Java中的线程分为两类:一种是守护线程,一种是用户线程
- 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
- 守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
- 若JVM中都是守护线程,当前JVM将退出。
5. Thread的生命周期
图示:
说明:
-
生命周期关注两个概念:状态、相应的方法
-
关注:状态a--->状态b:哪些方法执行了(回调方法)
? 某个方法主动调用:状态a--->状态b
-
阻塞:临时状态,不可以作为最终状态
-
死亡:最终状态。
6. 线程的同步机制
6.1 背景
6.2 java解决方案
在java中,我们通过同步机制,来解决线程的安全问题。
方式一:同步代码块
synchronized(同步监视器){
? //需要被同步的代码
说明:
- 操作共享数据的代码,即为需要被同步的代码。--->不能包含代码多了,也不能包含代码少了
- 共享数据:多个线程共同操作的变量
- 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。要求:多个线程必须要共用同一把锁。
- 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
方式二:同步方法。
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
关于同步方法的总结:
-
同步方法仍然涉及到同步监视器,知识不需要我们显式的声明。
-
非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类的本身
方式三:lock锁
面试题:synchronized与lock的异同?
相同:两者都可以解决线程安全的问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器。lock需要手动的启动同步(lock(),同时结束同步也需要手动的实现(unlock()))
使用的优先顺序:
lock--->同步代码块(已经进入了方法体,分配了相应资源)--->同步方法(在方法体之外)
6.3 利弊
同步的方式,解决了线程的安全问题。---好处
操作同步代码时,只能一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低。
6.4 死锁
6.5 面试题
面试题:java时如果解决线程安全问题的,有几种方式?并对比几种方式的不同
面试题:synchronized和lock方式解决线程安全问题的对比
相同:两者都可以解决线程安全的问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器。lock需要手动的启动同步(lock(),同时结束同步也需要手动的实现(unlock()))
7. 线程通信
7,1 线程通信涉及到的三个方法:
- wait():一旦执行此方法,当前线程就会进入阻塞状态,并设防同步监视器。
- notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就会唤醒优先级高的那个。
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
7.2 说明
- wait(),notify(),natifyAll()三个方法必须使用在同步代码块或同步方法中。
- wait(),notify(),natifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则,会出现IllegalMonitorStateException异常
- wait(),notify(),notifyAll()三个方法时定义在java.lang.Object类中的
7.3 面试题
sleep()和wait()的异同?
- 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
- 不同点:
- 两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
- 调用的要求不同:sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块或同步方法中
- 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
7.4 小结
释放锁的操作:
- 当前线程的同步方法、同步代码块执行结束。
- 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
不释放锁的操作:
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行。
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。尽量避免使用suspend()和resume()来控制线程。
8. JDK5.0新增线程的创建方式
创建线程的方式三:实现Callable接口。 ----JDK 5.0新增
如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
- call()可以有返回值的。
- call()可以抛出异常,被外面的操作捕获,获取异常的信息。
- Callable是支持泛型的。
创建线程的方式四:使用线程池。 ----JDK 5.0新增
好处:
-
提高响应速度(减少了创建新线程的时间)
-
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
-
便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没任务时最多保持多长时间后会终止
8.1 面试题
线程创建的方式有几种?
4种
JAVA常用类
1 String类
1.1 概述
String:字符串,使用一对“"引起来表示。
-
String声明为final的,不可被继承
-
String实现了Serializable接口:表示字符串是支持序列比的
String实现了Comparable接口:表示String可以比较大小
-
String内部定义了final cahr[] value用于存储字符串数据
-
通过字面量的方式(区别于new给一个字符串赋值,此时的字符串值声明在常量池中)。
-
字符串常量池中时不会存储相同内容(使用String类中的equals()比较,返回true)的字符串的。
1.2 String的不可变性
1.2.1 说明
String:代表不可变的字符序列。简称不可变性。
体现:
- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
- 当对现有的字符串进行连接操作时,也许需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值。不能使用原有的value进行赋值。
1.2.2代码举例
String s1 = "helloworld";
String s2 = "HelloWorld";
System.out.println(s1.equals(s2));
System.out.println(s1.equalsIgnoreCase(s2));
String s3 = "abc";
String s4 = s3.concat("def");
System.out.println(s4);
String s5 = "abc";
String s6 = new String("abd");
System.out.println(s5.compareTo(s6)); //涉及到字符串排序
String s7 = "123456";
String s8 = s7.substring(2);
System.out.println(s7);
System.out.println(s8);
String s9 = s7.substring(2, 5); //(左闭右开)
System.out.println(s9);
1.2.3 图示
1.3 String实例化的不同方式
1.3.1 方式说明
方式一:通过字面量定义的方式
方式二:同通过new + 构造器的方式
1.3.2 代码举例
//通过字面量的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。
String s1 = "javaEE";
String s2 = "javaEE";
//通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
String s3 = new String("javaEE");
String s4 = new String("javaEE");
System.out.println(s1 == s2); //true
System.out.println(s1 == s3); //false
System.out.println(s1 == s4); //false
System.out.println(s3 == s4); //false
1.3.3 面试题
String s = new String(”abc“);方式创建对象,在内存中创建了介个对象?
答:两个,一个是堆空间中new就结构,另一个是char[]对应的常量池中的数据:“abc”
1.3.3 图示
1.4 字符串凭借方式赋值的对比
1.4.1 说明
- 常量与常量的拼接结果在常量池。且常量池中不会存在想用内容的常量。
- 只要其中一个是变量,结果就在堆中。
- 如果拼接的结果调用inrern(),返回值就在常量池中
1.4.2 代码举例
1.5 常用方法
int length(): 返回字符串的长度: return value. Length
char charAt(int index): 返回某索引处的字符:return value[ index]
boolean isEmpty(): 判断是否是空字符串: return value. Length ==日
String toLowerCase(): 使用默认语言环境,将String中的所有字符转换为小写
String toUpperCase(): 使用默认语言环境,将String中的所有字符转换为大写
String trim(): 返回字符串的副本,忽略前导空白和尾部空白 (去除前后空格)
boolean equals(0bject obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString): 与equals方法类似, 忽略大小写
String concat(String str): 将指定字符串连接到此字符串的结尾。等价于用“+” (连接两个字符串)
int compareTo(String anotherString): 比较两个字符串的大小
String substring(int beginIndex): 返回- -个新的字符串,它是此字符串的从beginIndex开始截取
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串 (左闭右开)
boolean endsWith(String suffix):测试此子付串是含以指定的后缀结宋
boolban startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
boolean contains(CharSequence s): 当且仅当此字符串包含指定的char值序列时,返回true (是否包含s这个字符串)
int indexOf(String str): 返回指定子字符串在此字符串中第次出现处的索引
int indexOf(String str, int fromIndex): 返回指定子字符串在此字符串中第一 次出现处的索引,从指定的索引开始
int lastlndexOf(String str): 返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex): 返回指定子字符串在此字符串中最后.次出现处的索引,从指定的索引开始反向搜索
注意:indexOf和lastIndexOf方法如果未找到都是返回-1
替换:
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用newChar替换此字符串中出现的所有oldChar得到的。
String replace(CharSequence target, CharSequence replacement): 使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement): 使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串。 regex:正则表达式
String replaceFirst(String regex, String replacement): 使用给定的replacement替换此字符串匹配给定的正则表达式的第一个子字符串。
匹配:
boolean matches(String regex): 告知此字符串是否匹配给定的正则表达式。
切片:
StringD] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
1.6 String与其他结构之间的转换
1.6.1 与基本数据类型、包装类之间的转换
String 与基本数据类型、包装类之间的转换。
String --> 基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)
基本数据类型、包装类 -->String:调用String重载的valueOf(xxx)
@Test
public void test1(){
String s1 = "123"; //在常量池里
int num = Integer.parseInt(s1);
String str2 = String.valueOf(num);
String str3 = num + ""; //在堆里
}
1.6.2 与字符数组之间的转换
String 与 char[]数组之间的转换
String --> char[]:调用String的toCharArray()
char[] -->String:调用String的构造器
@Test
public void test2(){
String str1 = "abc123";
char[] charArray = str1.toCharArray();
for (int i = 0; i < charArray.length; i++) {
System.out.println(charArray[i]);
}
char[] arr = {'h', 'e', 'l', 'l', 'o'};
String s = new String(arr);
System.out.println(s);
}
1.6.3 与字节数组之间的转换
/*
* String 与 byte[]数组之间的转换
* 编码:String --> byte[]:调用String的getBytes()
* 解码:byte[] -->String:调用String的构造器
* */
@Test
public void test3() throws UnsupportedEncodingException {
String s1 = "abc123中国";
byte[] b1 = s1.getBytes(); //使用默认的字符集进行转换
System.out.println(Arrays.toString(b1));
byte[] gbks = s1.getBytes("gbk"); //使用gbk字符集进行编码
System.out.println(Arrays.toString(gbks));
String s2 = new String(b1); //使用默认的字符集进行解码
System.out.println(s2);
}
1.6.4 与StringBuffer、StringBuilder之间的转换
- String --> StringBuffer、StringBuilder:调用StringBuffer、StrngBuilder构造器
- StringBuffer、StringBuilder --> String:
- 调用String构造器
- StringBuffer、StringBuilder的toString()
1.7 JVM中字符常量池存放位置 说明
jdk1.6 :字符串常量池存储在方法区(永久区)
jdk1.7 :字符串常量池存储在堆空间
jdk1.8 :字符串常量池存储在方法区(元空间)
1.8 常见算法题目的考查
-
模拟一个trim方法,去除字符串两端的空格。
-
将一个字符串进行反转。将字符串中指定部分进行反转。比如“abcdefg"反转 为"abfedcg”
-
获取一个字符串在另-个字符串中出现的次数。比如:获取“ab"在“abkkcadkabkebfkabkskab"中出现的次数
-
获取两个字符串中最大相同子串。比如:str1 = "abcwerthelloyuiodef";str2 = "cvhellobnm"提示:将短的那个串进行长度依次递减的子串与较长的串比较。
-
对字符串中字符进行自然顺序排序。提示:
1)字符串变成字符数组。
2)对数组排序,选择,冒泡,Arrays. sort();3)将排序后的数组变成字符串。
2 StringBuffer、StringBuilder
2.1 String、StringBuffer、StringBuilder三者之间的对比
- String:不可变的字符序列,底层使用char[]进行存储
- StringBuffer:可变的字符序列 : 线程安全的,效率低,底层使用char[]进行存储
- StringBuilder:可变的字符序列: JDK5.0新增的,线程不安全的,效率高,底层使用char[]进行存储
2.2 StringBuffer与StringBuilder的内存解析
源码分析:
String str = new String(); // new char[0];
String str1 = new String("abc"); //new char[]{'a','b','c'};
StringBuffer sb1 = new StringBuffer(); //new char[16];底层创建了一个长度为16的数组
sb1.append('a'); //value[0] = 'a’;
sb1.append('b'); //value[1] = 'b';
StringBuffer sb2 = new StringBuffer("abc"); //char[] value = new char["abc".length() + 16];
问题1:System.out.println(sb2.length) //3
问题2:扩容问题:如果你要添加的数据底层数组盛不下了,那就需要扩容底层数组。
默认情况下,扩容为原来容量的2倍+2,同时将原有的数组复制到新的数组中。
开发中建议使用:StringBuffer(int capacity) 或 StringBuilder(int capacity)
2.3 对比String、StringBuffer、StringBuilder三者的执行效率
从高到低排列:StringBuilder > StringBuffer > String
2.4 StringBuffer、StringBuilder中的常用方法
StringBuffer append(xxx): 提供了很多的append()方法,用于进行字符串拼接
StringBuffer delete(int start, int end): 删除指定位置的内容
StringBuffer replace(int start, int end, String str): 把[start,end)位 置替换为strStringBuffer insert(int offset, xxx): 在指定位置插入xxx
StringBuffer reverse() :把当前字符序列逆转
public int indexOf(String str)
public String substring(int start, int end):返回一个从start开始到end结束的左闭右开的子字符串
public int Length()
public char charAt(int n )
public void setCharAt(int n , char ch)
总结:
增:append(xxx)
删:delete(int start, int end)
改:setCharAt(int n , char ch) 修改一个字符 / replace(int start, int end, String str) 修改一个字符串
查:charAt(int n)
插:insert(xxx)
长度:length()
遍历:for() + charAt()
反转:reverse()
3 JDK 8之前日期时间API
3.1 获取系统当前时间
System类中的currentTimeMillis()
long time = System.currentTimeMillis(); //返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差 称为时间戳
3.2 java.util.Date类与java.sql.Date类
2.java.util.Date类
|---java.sql.Date类
2.1 两个构造器的使用
>构造器一:Date():创建一个对应当前时间的Date对象
>构造器二:创建指定毫秒数的Date对象
2.2 两个方法的使用
>toString():显示当前的年、月、日、时、分、秒
>getTime():获取当前Date对象对应的毫秒数。(时间戳)
3. java.sql.Date对应着数据库中的日期类型的变量
>如果实例化
>如果将java.util.Date对象转换为java.sql.Date对象
@Test
public void test2(){
//构造器一:Date():创建一个对应当前时间的Date对象
Date date1 = new Date();
System.out.println(date1.toString()); //Wed Jan 12 16:57:52 CST 2022
System.out.println(date1.getTime()); //1641977872247
//构造器二:创建指定毫秒数的Date对象
Date date2 = new Date(1641977872247L);
System.out.println(date2.toString());
//创建java.sql.Date对象
java.sql.Date date3 = new java.sql.Date(1641977872247L);
System.out.println(date3);
//如果将java.util.Date对象转换为java.sql.Date对象
//情况一:
//Date date4 = new java.sql.Date(1641977872247L);
//java.sql.Date date5 = (java.sql.Date)date4;
//情况二
Date date6 = new Date();
java.sql.Date date7 = new java.sql.Date(date6.getTime());
}
}
3.3 java.text.SimpleDataFormat类
SimpleDateFormat对日期Date类的格式化和解析
1 两个操作
-
格式化:日期--->字符串
-
解析:格式化的逆过程,字符串--->日期
2 SimpleDateFormat的实例化test
//**************按照指定的方式进行格式化和解析:调用带参的构造器***************************
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//格式化
String format1 = sdf1.format(date);
System.out.println(format1);
//解析:要求字符串必须符合SimpleDateFormat识别的格式(通过构造器参数体现),否则抛异常
String str1 = "2022-01-13 03:14:22";
Date parse1 = sdf1.parse(str1);
System.out.println(parse1);
}
小练习
//练习一:字符串"2020-09-08"转换为java.sql.Date
@Test
public void test3() throws ParseException {
String birth = "2020-09-08";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse(birth);
java.sql.Date date1 = new java.sql.Date(date.getTime());
System.out.println(date1);
}
3.4 Calendar类
Calendar:(抽象类)日历类的使用
//1.实例化
//方式一: 创建其子类(GregorianCalender)的对象
//方式二: 调用其静态方法getInstance()
Calendar calendar = Calendar.getInstance();
//2. 常用方法
//get()
int days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//set()
calendar.set(Calendar.DAY_OF_MONTH,14);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//add()
calendar.add(Calendar.DAY_OF_MONTH,2);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//getTIme()
Date date = calendar.getTime();
System.out.println(date);
//setTime()
Date date1 = new Date();
calendar.setTime(date1);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
4 JDK8中新日期时间API
4.1 日期时间API的迭代
第一代:jdk 1.0 Date类
第二代:jdk 1.1 Calendar类,一定程度上替换Date类
第三代:jdk1.8 提出了新的一套API
4.2 前两代存在的问题举例
可变性
偏移性
格式化
线程不安全;不能处理闰秒
4.3 java8中新的日期时间API涉及到的包
4.4 本地日期、时间、日期时间的使用
4.4.1 说明
4.4.2 常用方法
//方式一:now():获取当前的日期、时间、日期+时间
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate);
System.out.println(localTime);
System.out.println(localDateTime);
//方式二:of():设置指定的年、月、日、时、分、秒,没有偏移量
LocalDateTime localDateTime1 = LocalDateTime.of(2022, 1, 13, 20, 18, 30);
System.out.println(localDateTime1);
//getXxx()
System.out.println(localDateTime.getDayOfMonth());
//体现不可变性 withXxx():设置相关属性
LocalDateTime localDateTime2 = localDateTime.withDayOfMonth(22);
System.out.println(localDateTime2);
//plusXxx():增加相关属性
LocalDateTime localDateTime3 = localDateTime.plusDays(2);
System.out.println(localDateTime3);
//minusXxx():减去相关属性
LocalDateTime localDateTime4 = localDateTime.minusDays(2);
System.out.println(localDateTime4);
}
4.5 时间点:Instant
4.5.1 说明:
- 时间上的一个瞬时点。从1970年1月1日0时0分0秒开始计时
- 类似于java.util.Date类
4.5.2 常用方法
* Instant的使用
* 类似于java.util.Date类
@Test
public void test2(){
//now():获取本初子午线对应的标准时间
Instant instant = Instant.now();
System.out.println(instant);
//添加时间的偏移量
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);
//获取对应毫秒是(1970.1.1(UTC))--->Date(getTime())
long milli = instant.toEpochMilli();
System.out.println(milli);
//ofEpochMilli():通过给定的毫秒数,获取Instant实例--->Date(long millis)
Instant ofEpochMilli = Instant.ofEpochMilli(1642077535539L);
System.out.println(ofEpochMilli);
}
4.6 日期时间格式化类:DateTimeFormatter
4.6.1 说明
- 格式化或解析日期、时间
- 类似于SimpleDateFormat
4.6.2 常用方法
/*
* DteTimeFormatter:格式化或解析日期、时间
* 类似于SimpleDateFormat
* */
@Test
public void test3(){
//方式一:预定义的标准格式。
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
//格式化:日期-->字符串
LocalDateTime localDateTime = LocalDateTime.now();
String str1 = formatter.format(localDateTime);
System.out.println(localDateTime);
System.out.println(str1);
//解析:字符串-->日期
TemporalAccessor parse = formatter.parse("2022-01-13T20:55:08.507");
System.out.println(parse);
//方式二:本地化相关的格式。
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
//格式化
String str2 = formatter.format(localDateTime);
System.out.println(str2);
//重点:
//方式三:自定义的格式。如:ofPattern("yyyy-MM-dd hh:mm:ss")
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
String str3 = formatter2.format(LocalDateTime.now());
System.out.println(str3);
}
4.7 其他API的使用
5 Java比较器
5.1 Java比较器的使用背景
Java中的比较对象,正常情况下,只能进行比较:== 或者!=。不能使用> 或 < 的,但是在开发场景中。我们需要对多个对象进行排序,言外之意,就需要比较对象的大小,如果实现?使用两个接口中的任何一个:Comparable 或 Comparator。
5.2 自然排序:使用Comparable接口
5.2.1 说明
-
像String、包装类等实现了Comparable接口,重写了compareTo(),给出了比较两个对象大小的方法
-
重写compareTo()的规则:
- 如果当前对象this大于形参对象obj,则返回正整数,
- 如果当前对象this小于形参对象obj,则返回负整数,
- 如果当前对象this等于形参对象obj,则返回零,
-
对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo()方法,
在compareTo(obj)方法中指明如何排序
5.2.2 自定义类代码举例
5.3 定制排序:使用Comparator接口
5.3.1 说明
-
背景:当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码或者实现了java.lang.Comparable接口的排序规则不适合当前的操作。那么可以考虑使用Comparator的对象来排序
-
重写compare(Object o1, Object o2)方法,比较o1和o2的大小:
- 如果方法返回正整数,则表示o1大于o2;
- 如果返回0,表示相等;
- 如果返回负整数,表示o1小于o2。
5.3.2 代码举例
public void test2(){
String[] arr = {"AA", "CC", "KK", "MM", "GG", "JJ", "DD"};
Arrays.sort(arr, new Comparator() {
//按照字符串从大到小的顺序排序
@Override
public int compare(String o1, String o2) {
if (o1 instanceof String && o2 instanceof String){
String s1 = (String) o1;
String s2 = (String) o2;
return -s1.compareTo(s2);
}
throw new RuntimeException("输入的数据类型不一致");
}
});
}
5.3 两种排序方式的对比
- Comparable接口方式一旦指定,保证Comparable接口实现类的对象在任何位置都可以比较大小
- Comparator接口属于临时性的比较。
6 其他类
6.1 System类
6.2 Math类
枚举类与注解
枚举类的使用
1. 枚举类的说明
- 枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
- 当需要定义一组常量时,强烈建议使用枚举类
- 如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
2. 如何自定义枚举类
//自定义枚举类
class Season{
//1. 私有化Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//2. 私有化类的构造器,并给对象赋值
private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//3. 提供当前枚举类的读个对象; public static final
public static final Season SPRING = new Season("春天","春暖花开");
public static final Season SUMMER = new Season("夏天","夏日炎炎");
public static final Season AUTUMN = new Season("秋天","秋高气爽");
public static final Season WINTER = new Season("冬天","冰天雪地");
//4. 其他诉求1:获取枚举类对象的其他属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
3. jdk5.0 新增使用enum定义枚举类
//使用enum关键字定义枚举类
//说明:定义的枚举类默认继承于java.lang.Enum类
enum Season1{
//1. 提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","冰天雪地");
//2. 私有化Season对象的属性:private final修饰
private final String seasonName1;
private final String seasonDesc1;
//3. 私有化类的构造器,并给对象赋值
private Season1(String seasonName,String seasonDesc){
this.seasonName1 = seasonName;
this.seasonDesc1 = seasonDesc;
}
//4. 其他诉求1:获取枚举类对象的其他属性
public String getSeasonName1() {
return seasonName1;
}
public String getSeasonDesc1() {
return seasonDesc1;
}
4. 枚举类的常用方法
- values():返回枚举类型的对象数组,该方法可以很方便的遍历所有的枚举值。
- valueOf(String str):可以把一个字符串转为对应的枚举类对象,要求字符串必须时枚举类对象
- toString():返回当前枚举类对象常量的名称
Season1 spring = Season1.SPRING;
//toString
System.out.println(spring.toString());
//values():
Season1[] values = Season1.values();
for (int i = 0; i < values.length; i++) {
System.out.println(values[i]);
}
//valueOf(String objName):返回枚举类中对象名时objName的对象+
//如果没有objName的枚举类的对象,则抛异常
Season1 winter = Season1.valueOf("WINTER");
System.out.println(winter);
5. 枚举类对象实现接口
- 实现接口,在enum类中是实现抽象方法
- 让枚举类的对象分别去实现接口中的方法
注解的使用
1. 注解的理解
- jdk5.0 新增的功能
- Annotation其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。
- 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,逻辑警告等。
在javaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替了javaEE旧版中所遗留的繁冗代码和XML配置等。
框架 = 注解 + 反射机制 + 设计模式
2. 注解的使用示例
Annocation的使用示例
- 示例一:生成文档相关的注解
- 示例二:在编译时进行格式检查(JDK内置的三个基本注解)
- @Oberride:限定重写父类方法,该注解只能用于方法。
- @Deprecated:用于表示所修饰的元素(类,方法等)已过时。
- @SuppressWarnings:抑制编译器警告
3. 如何自定义注解
说明:如何自定义注解:参照@SuppressWarnings定义
-
注解声明为:@interface
-
内部定义成员,通常使用value表示
-
可以指定成员的默认值,使用default定义
-
如果自定义注解没有成员,表示是一种标识。
如果注解有成员,子啊使用注解时,需要知名成员的值。自定义注解配上注释的信息处理流程(使用反射)才有意义。自定义注解通常都会知名两个元注解:@Retention和@Target
代码举例:
4. 元注解
对现有的注解进行解释说明的注解。
@Retention:指定所修饰的Annotation的生命周期:SOURCE\CLASS(默认行为)\RUNTIME,只有声明为RUNTIME生命周期的注解,才能通过反射获取。
-
@Target:用于指定被修饰的Annotation能用于修饰哪些程序元素
-
@Documented:表示所修饰的注解在被javadoc解析时,会被保留下来
-
@Inherited:被它修饰的注解具有继承性
5. 如何获取注解信息
通过反射获取注解信息
6. jdk 8 中注解的新特性
可重复注解、类型注解
6.1 可重复注解:
- 在MyAnnotation上证明@Repeatble,成员值为MyAnnotations.class
-
MyAnnotation的Target和Retetion等元注解和MyAnnotations相同
6.2 类型注解:
-
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)
-
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
Java集合
1. 数组与集合
1.1 集合与数组存储数据概述
集合、数组都是对多个数据进行存储操作的结构,简称java容器。
说明:此时的存储,主要指内存能层面的存储,不涉及持久化层面的
1.2 数组存储的特点
数据在存储多个数据方面的特点:
-
一旦初始化以后,其长度就确定了。
-
数组一定定义好,其元素的类型也就确定了,我们只能操作指定类型的数据了,
-
比如:String[] arr ; int[] arr1 ; Object[] arr2;
1.3 数组存储的弊端
数组在存储多个数据方面的缺点:
-
一旦初始化以后,其长度就不可修改。
-
数组中提供的方法非常有限,对于添加、删除、插入等操作,非常不便,同时效率不高。
-
获取数组中实际元素的个数的需求,数组没有现成的属性或犯法可用
-
数组存储数据的特点:有序、可重复。对于无序、不可重复的,不能满足。
1.4 集合存储的优点
解决数组存储数据方面的弊端
2. Collection接口
2.1 单列集合框架结构
- Collection接口:单列集合,用来存储一个一个的对象
- Lis接口:存储有序的、可重复的数据 ---> "动态"数组
- ArrayList、LinkedList、Vector
- Set接口:存储无序的、不可重复的数据
- HashSet、LinkedHashSet、TreeSet
对应图示:
2.2 Collection接口常用方法
void add(Object ele):添加一个元素
addAll(Collection coll)
void size()
void isEmpty()
void clear()
contains(Object obj):判断当前集合中是否包含obj
containsAll(Collection coll1):判断形参coll1中的所有元素是否都存在于当前集合中
remove(Object obj):从当前结合中移除obj元素
removeAll(Collection coll1):从当前集合中移除coll1中所有的元素
ratainAll(Collection coll1):获取两个集合中相同的部分
equals(Object obj):比较集合是否相等
hashCode():返回当前对象的哈希值
toArray()
iterator():返回Iterator接口的实例,用于遍历集合元素
2.3 Collection集合与数组间的转换
//集合--->数组:toArray()
Object[] arr = coll.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
//拓展:数组--->集合:调用Arrays的静态方法asList()
List
2.4 使用Collection集合存储对昂,要求对象所属的类满足:
向Collection接口的实现类对象中添加数据obj时,要求obj所在类要重写equals()。
2.5 本章节对大家的要求:
-
选择合适的集合类去实现数据的保存,调用其内部的相关方法
-
不同的集合底层的数据结构是什么?如果实现数据的操作的:增删改查等。
3. Iterator接口和foreach循环
3.1 遍历Collection的两种方式:
- 使用迭代器Iterator
- foreach循环
3.2 java.utils包下定义的迭代器接口:Iterator
3.2.1 说明
Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection 集合中的元素。
GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。I
3.2.2 作用
遍历集合Collection元素
3.2.3 如何获取实例
coll.iterator()返回一个实例
3.2.4 遍历的代码实现
Iterator iterator = coll.iterator();
//hasNext():判断是否还有下一个元素
while (iterator.hasNext()){
//next():1. 指针下移 2. 将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}
3.2.5 图示说明
3.2.6 remove()的使用
//此时Iterator中的remove()
//日过还未调用next()或在上一次调用next方法之后已经调用了remove方法,
//再调用remove都会报IllegalStateException。
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("Tom"));
coll.add(false);
coll.add(new Person("Jerry",20));
Iterator iterator = coll.iterator();
//删除集合中"Tom"元素
while (iterator.hasNext()){
Object obj = iterator.next();
if ("Tom".equals(obj)){
iterator.remove();
}
}
//遍历集合
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
3.3 jdk5.0新特性---foreach循环
3.3.1 遍历集合举例
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("Tom"));
coll.add(false);
coll.add(new Person("Jerry",20));
//for(集合元素的类型 局部变量 : 集合对象)
//内部仍然调用了迭代器
for (Object obj : coll) {
System.out.println(obj);
}
说明:内部仍然调用了迭代器
3.3.2 遍历数组举例
//for(数组元素的类型 局部变量 : 数组对象)
int[] arr = new int[]{1,2,3,4,5};
for (int i : arr) {
System.out.println(i);
}
4. Collection子接口:List接口
4.1 存储数据的特点
|---Collection接口:单列集合,用来存储一个一个的对象
* |---Lis接口:存储有序的、可重复的数据 ---> "动态"数组
* |---ArrayList、LinkedList、Vector
4.2 常用方法
- void add(int index, Object ele):在index位置插入ele元素
- boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元索添加进来
- Object get(int index):获取指定index位置的元素
- int indexOf(Object obj):返回obj在集合中首次出现的位置
- int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
- Object remove(int index):移除指定index位置的元素,并返回此元素
- Object set(int index, Object ele):设置指定index位置的元素为ele
- List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
总借:常用方法
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:1. Iterator迭代器方式
2. foreach循环
3. for循环
4.3 常用实现类
- ArrayList:作为List接口的主要实现类,线程不安全的,效率高,底层使用Object[] elementData存储
- LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高,底层使用双向链表存储
- Vector:作为List接口的古老实现类,线程安全,效率低,底层使用Object[] elementDate存储
4.4 源码分析(难点)
ArrayList源码分析:jdk 7情况如下
ArrayList list = new ArrayList(); //底层创建了长度时10的Object[]数组elementData
list。add(123); // elementData[0] = new Integer(123);
...
list.add(11); //如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍。同时需要将原有数组中的数据copy到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
jdk 8中ArrayList的变化:
ArrayList list = new ArrayList(); //底层Object[] elementData初始化为{},并没有创建长度为10的数组
list.add(123); //第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData数组中
...
后续的添加和扩容操作与jdk 7无异。
小结:jdk7中的ArrayList的对象创建类似于单例模式的饿汉式,而jdk8中的ArrayList的对象
的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
LinkedList的底层源码分析
LinkdeList list = new LinkedList(); //内部声明了Node类型的first和last属性,默认值为null
list.add(123); //将123封装到Node中,创建了Node对象
其中,Node定义为:体现了LinkedList的双向链表的说法
private static class Node {
E item;
Node next;
Node prev;
Node(Node prev, E element , Node next){
this.item = element;
this.next = next;
this.prev = prev;
}
}
Vector的源码分析
jdk 7和jdk 8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组,在扩容方面,默认扩容为原来的数组长度的2倍。
4.5 存储的元素的要求
添加的对象,所在的类要重写equals()方法
4.6 面试题
面试题:ArrayList、LinkedList、Vector三者的异同?
同:三个类都是实现了List接口,存储数据的特点相同:存储有序的、可重复的数据
不同:
- ArrayList:作为List接口的主要实现类,线程不安全的,效率高,底层使用Object[] elementData存储
-
LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高,底层使用双向链表存储
-
Vector:作为List接口的古老实现类,线程安全,效率低,底层使用Object[] elementDate存储
5. Collection子接口:Set接口
5.1 存储的数据特点:存储无序的、不可重复的数据
具体的:
以HashSet为例说明:
-
无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加。
而是根据数据的哈希值决定的。 -
不可重复性:保证添加的元素按照equals()判断时,不能返回true,即相同的元素只能添加一个。
5.2 元素添加过程
添加元素的过程:以HashSet为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
数组此位置上是否已经有元素:
如果此位置山没有其他元素,则元素a添加成功 --->情况1
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的哈希值:
如果hash值不相同,则元素a添加成功。 --->情况2
如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功。 --->情况3
对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。
jdk7:元素a放到数组中,指向原来的元素
jdk8:原来的元素在数组中,指向元素a
总结:其七上八下
HashSet底层:数组加链表(jdk 7)
5.3 常用方法
Set中没有额外的新的方法,使用的都是Collection中声明过的方法。
5.4 常用实现类
- HashSet:作为Set接口的主要实现类;线程不安全;可以存储null值
- LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历, 在添加数据的同时,每个数据害维护了两个引用,记录此数据前一个数据和后一个数据。对于频繁的遍历操作,LinkedHashSet效率高于HashSet
- TreeSet:可以按照添加对象的指定属性,进行排序
5.5 存储对象所在类的要求
HashSet / LinkedHashSet
- 向Set中添加的数据,其所在的类一定要重写hashCode()和equals()
- 重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
TreeSet
- 自然排序中,比较两个对像是否相同的标准为:compareTo()返回0,不再是equals()。
- 定制排序中,比较两个对象是否仙童的标准为:compare返回0,不再是equals()。
6. TreeSet的使用
6.1 使用说明
- 向TreeSet中添加的数据,要求时相同类的对象
- 两种排序方式:自然排序(实现Comparable接口)和定制排序(Comparator)
6.2 常用的排序方式
自然排序:
TreeSet set = new TreeSet();
// set.add(34);
// set.add(-34);
// set.add(43);
// set.add(11);
// set.add(8);
set.add(new Person("Jack",18));
set.add(new Person("Mack",21));
set.add(new Person("Mike",18));
set.add(new Person("Jerry",26));
set.add(new Person("Juli",19));
set.add(new Person("Tom",45));
set.add(new Person("Black",33));
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
定制排序:
Comparator com = new Comparator() {
//按照年龄从小到大排列
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Person && o2 instanceof Person){
Person p1 = (Person)o1;
Person p2 = (Person)o2;
return Integer.compare(p1.getAge(),p2.getAge());
}else {
throw new RuntimeException("输入的数据类型不匹配");
}
}
};
TreeSet set = new TreeSet(com);
set.add(new Person("Jack",18));
set.add(new Person("Mack",21));
set.add(new Person("Mike",18));
set.add(new Person("Jerry",26));
set.add(new Person("Juli",19));
set.add(new Person("Tom",45));
set.add(new Person("Black",33));
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
6. Map接口
6.1 常用实现类结构
Map:双列数据,存储key-value对的数据
HashMap:作为Map的主要实现类;线程不安全的,效率高,能存储null的key和value
LinkedHashMap:保证在遍历Map元素时,可以按照添加的顺序实现遍历,
原因:在原有的HashMap底层结构的基础上,添加了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类的效率高于HashMao。
TreeMap:可以保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或者定制排序。
底层使用红黑树
Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
Properties:常用来处理配置文件,key和value都是String类型。
HashMap的底层:数组 + 链表(jdk 7及之前)
数组 + 链表 + 红黑树(jdk 8)
面试题
- HashMap的底层实现原理?
- HsahMap 和 Hashtable的异同?
6.2 存储结构的理解
Map中的key:无序的、不可重复的,使用Set存储所有的key ---> key所在的类要重写equals()和hashCode() (以hashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所有的value
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所有的entry
6.3 常用方法
添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear(): 清空当前map中的所有数据
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key): 是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet(): 返回所有key-value对构成的Set集合
6.4 内存结构的说明
6.4.1 HashMap在jdk 7中实现原理
HashMap map = new HashMap();
在实例化以后,底层创建了一个长度是16的以为数组Entry[] table。
...可能已经执行过多次put...
map.put(key1,value1);
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置
- 如果此位置上的数据为空,此时的key1-value1添加成功。---情况1
- 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(已链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
- 如果key1的哈希值与已经存在的额数据的哈希值都不相同,此时key1-value1添加成功。---情况2
- 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals()方法,比较:
- 如果equals()返回false:此时key1-value1添加成功。---情况3
- 如果equals()返回true:使用value1替换value2值。
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,默认的扩容方式:扩容为原来容量的2倍,并将原来的数据复制过来。
6.4.2 HashMap在jdk 8中实现原理的不同
- new HashMap():底层没有创建一个长度为16的数组
- jdk 8底层的数组是Node[],而非Entry[]
- 首次调用put()方法时,底层创建长度为16的数组
- jdk 7底层结构只有数组+链表,jdk 8底层结构:数组 + 链表 + 红黑树
当数组的莫普一个索引位置上的元素以链表结构形式存在的数据个数 > 8且当前数组的长度 > 64时;
此时此索引位置上的所有数据改为使用红黑树存储。
6.4.3 HashMap底层典型属性的属性的说明
- DEFAULT_INITAL_CAPACITY:HashMap的默认容量,16
- DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
- threshold:扩容的临界值 = 容量 * 加载因子:16 * 0.75 =>12
- TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
- MIN_TREEIFY_CAPACITY:桶中的Node呗树化时最小的hash表容量:64
6.4.4 LinkedHashMap的底层实现原理
LinkedHashMap底层使用的机构与HashMap相同,因为LinkedHashMap继承于HashMap,区别就在于:LinkedHashMap内部提供了Entry,替换了HashMap中的Node。
LinkedHashMap的底层实现原理(了解)
源码中:
static class Entry extends HashMap.Node{
Entry before, after; //能狗记录添加的元素的先后顺序
Entry(int hash, K key, V value, Node next){
super(hash, key, value, next);
}
}
6.5 TreeMap的使用
向TreeMap中天机key-value,要求key必须是由同一个类创建的对象
因为要按照key进行排序:自然排序、定制排序
6.6 使用Properties读取配置文件
7. Collections工具类的使用
7.1 作用
操作Collection和Map的工具类
7.2 常用方法
reverse(List):反装List中元素的順序
shuffle(List):対List集合元素迸行随机排序
sort(List):根据元素的自然順序対指定List 集合元素按升序排序
sort(List, Comparator):根据指定的Comparator 广生的順序対List集合元素迸行排序
swap(List, int, int): 将指定list集合中的i処元素和j処元素迸行交換
0bject max(Collection): 根据元素的自然傾序,返回給定集合中的最大元素
0bject max(Collection, Comparator): 根据Comparator指定的順序,返回給定集合中的最0bject min(Collection)
Object min(Collection, Comparator)
int frequency(Collection, object): 返回指定集合中指定元素的出現次数
void copy(List dest,List src):将src 中的内容夏制到dest中
boolean replaceAll(List list, Object oldVal, Object newVal):使用新値替換List
说明:ArrayList和HashMap都时线程不安全的,如果程序要求线程安全,我们可以将ArrayList、HashMap转换为线程安全的。 使用synchronizedList(List list) 和 synchronizedMap(Map map)
7.3 面试题
Collection 和 Collections的区别
泛型
1. 泛型的理解
1.1 泛型的概念
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时确定(即传入实际的类型参数,也称为类型实参。
1.2 泛型的引入背景
2.泛型在集合中的作用
2.1 在集合中使用泛型之前的例子
ArrayList list = new ArrayList();
//需求:存放学生的成绩
list.add(78);
list.add(76);
list.add(89);
list.add(88);
//问题一:类型不安全
// list.add("Tom");
// for (Object obj :
// list) {
// //问题二:强转时,可能会出现ClassCastException
// int stuobj = (int) obj;
// System.out.println(stuobj);
// }
2.2 在集合中使用泛型例子1
ArrayList list = new ArrayList<>();
list.add(78);
list.add(76);
list.add(89);
list.add(88);
//方式一
// for (Integer score : list) {
// int stuScore = score;
// System.out.println(stuScore);
// }
//方式二
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
2.3 在集合中使用泛型例子2
// HashMap map = new HashMap();
//jdk 7.0新特性:类型推断
HashMap map = new HashMap<>();
map.put("AA",87);
map.put("BB",65);
map.put("CC",44);
//泛型的嵌套
Set> entries = map.entrySet();
Iterator> iterator = entries.iterator();
while (iterator.hasNext()){
Map.Entry e = iterator.next();
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key + "--->"+ value);
}
2.4 集合中使用泛型总结
- 集合接口或集合类中在jdk 5.0时都修改为带泛型的结构。
- 在实例化集合类时,可以指明具体的泛型类型
- 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构使用到类的泛型的位置,
- 都指明为实例化的泛型类型,比如:add(E e) --->实例化以后:add(Integer e),注意点:泛型的类型必须是类,不能是基本类型。需要用到基本数据类型的位置,需要拿包装类替换
- 如果实例化时,没有指明泛型。默认类型为java.lang.Object类型。
3.自定义泛型类、泛型接口、泛型方法
3.1 举例
package genericexercise;
/*
* 自定义泛型类
* */
import java.util.ArrayList;
import java.util.List;
public class Order {
String orderName;
int orderId;
//类的内部结构就可以使用类的泛型
T orderT;
public Order(){
}
public Order(String orderName,int orderId, T orderT){
this.orderName = orderName;
this.orderId = orderId;
this.orderT = orderT;
}
public T getOrderT() {
return orderT;
}
public void setOrderT(T orderT) {
this.orderT = orderT;
}
@Override
public String toString() {
return "Order{" +
"orderName='" + orderName + '\'' +
", orderId=" + orderId +
", odderT=" + orderT +
'}';
}
//泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
//换句话说,泛型方法所属的类是不是泛型类都没有关系。
//泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的,并非在实例化类时确定
//泛型方法
public List copyFromArrayToList(E[] arr){
ArrayList list = new ArrayList<>();
for (E e : arr){
list.add(e);
}
return list;
}
}
3.2 注意点
/*
* 如何自定义泛型结构:泛型类,泛型接口,泛型方法
* */
public class GenericTest1 {
@Test
public void test(){
//如果定义了泛型类,实例化没有指明类的泛型,则认为泛型类型为Object类型
//要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。
Order order = new Order();
order.setOrderT(123);
order.setOrderT("ABC");
//建议:实例化时要指明类的泛型
Order order1 = new Order<>("orderAA",1001,"order:AA");
order1.setOrderT("AA:hello");
}
//泛型不同的引用不能互相赋值
//静态方法中不能使用泛型(泛型是在创建对象之后,静态时在类开始加载的时候)
// 异常类不能声明为类的泛型
//测试泛型方法
@Test
public void test5(){
Order order = new Order<>();
Integer[] arr = {1, 2, 3, 4};
//泛型方法在调用时,指明泛型参数的类型
List list = order.copyFromArrayToList(arr);
System.out.println(list);
}
3.3 应用场景举例
4.泛型在继承上的体现
/*
1. 泛型在继承方面的体现
虽然类A是类B的父类,但是G 和G二者不具备子父类关系,二者时并列关系
类A是类B的父类,A 是 B的父类
* */
@Test
public void test(){
Object obj = null;
String str = null;
obj = str;
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;
List
5.通配符
/*
2. 通配符的使用
通配符:?
类A是类B的父类,G 和 G是没有关系的,二者共同的父类是:G<?>
* */
@Test
public void test1(){
List
IO流
1. File类的使用
1.1 File的理解
- File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
- File类声明在java.io包下
- File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法
并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成 - 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点"
1.2 File的实例化
1.2.1 常用的构造器
File(String filePath)
File(String parentPath,String childPath)
File(File parentFile,String childPath)
//构造器1
File file = new File("hello.txt"); //相对于当前module
File file1 = new File("D:\\he.txt");
System.out.println(file);
System.out.println(file1);
//构造器2
File file2 = new File("D\\study", "javase");
System.out.println(file2);
//构造器3
File file3 = new File(file2, "h1.txt");
System.out.println(file3);
1.2.2 路径的分类
相对路径:相较于某个路径下,指明的路径
绝对路径:包含盘符在内的文件或文件目录的路径
说明:IDEA中,如果大家开发使用JUnit的单元测试方法测试,相对路径即为当前Module下。如果使用main()测试,相对路径即为当前的Project下。
Eclipse中:不管是单元测试方法还是main()测试,相对路径都是当前的Project下。
1.2.3 路径分隔符
windows:\
unix:/
1.3 File的常用方法
File类的获取功能
public String getAbsolutePath():获取绝对路径
public String getPath() :获取路径
public String getName():获取名称
public String getParent():获取上层文件目录路径。若无,返回null
public long length():获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified():获取最后一次的修改时间,毫秒值
如下的两个方法适用于文件目录
public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组。
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
重命名
public boolean renameTo(File dest):把文件重命名为指定的文件路径
要想保证返回为true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在
测试
public boolean isDirectory():判断是否是文件目录
public boolean isFile():判断是否是文件
public boolean exists():判断是否存在
public boolean canRead():判断是否可读
public boolean canWrite():判断是否可写
public boolean isHidden():判断是否隐藏
创建硬盘中对应的文件或文件目录
public boolean createNewFile():创建文件。若文件存在,则不创建,返回false
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。(如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs():创建文件目录。如果上层文件目录不存在,一并创建
删除磁盘中的文件或文件目录
public boolean delete():删除文件或文件夹 注意事项:java中的删除不走回收站。
2.IO流的概述
2.1 流的分类
- 操作数据单位:字节流、字符流
- 数据的流向:输入流、输出流
- 流的角色:节点流、处理流
2.2流的体系结构
2.3重点说明的几个流结构
抽象基类 节点流(文件流) 缓冲流(处理流的一种)
InputStream FileInputStream (read(byte[] buffer)) BufferedInputStream (read(byte[] buffer))
OutputStream FileOutputStream (write(byte[] buffer,0,len)) BufferedOutputStream (write(byte[] buffer,0,len) / flush())
Reader FileReader (read(byte[] cbuf)) BufferedReader (read(byte[] cbuf) /readLine())
writer FileWriter (write(byte[] cbuf,0,len)) BufferedWriter (write(byte[] cbuf,0,len) / flush())
2.4输入、输出的标准化过程
2.4.1 输入过程
-
创建File类的对象,指明读取的数据的来源。(文件一定要存在)
-
创建相应的输入流,将File类的对象作为参数,传入流的构造器中
-
具体的读入过程:
创建相应的byte[] 或 char[]。
-
关闭流资源
说明:程序中出现的异常需要使用try-catch-finally处理。
2.4.2 输出过程
-
创建File类的对象,指明写出的数据的来源。(文件不一定要存在)
-
创建相应的输出流,将File类的对象作为参数,传入流的构造器中
-
具体的写出过程:
write(char[]/byte[] buffer, 0 ,len)
-
关闭流资源
说明:程序中出现的异常需要使用try-catch-finally处理。
3.节点流(或文件流)
3.1 FileReader / FileWriter的使用
3.1.1 FileReader的使用
将JavaSE下的hello.txt文件内容读入程序中,并输入到控制台
说明点:
- read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
- 异常的处理:为例保证流资源一定可以执行关闭操作,需要使用try-catch-finally处理
- 读入的文件一定要存在,否则就会报FileNotFoundException。
@Test
public void testFileReader1() throws IOException {
FileReader fr = null;
try {
//1. File类的实例化
File file = new File("hello.txt");
//2. FileReader流的实例化
fr = new FileReader(file);
//3. 读入操作
//read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件的末尾,返回-1
char[] cbuf = new char[5];
int len;
while((len = fr.read(cbuf)) != -1){
//错误写法
// for (int i = 0; i< cbuf.length; i++) {
// System.out.print(cbuf[i]);
// }
//正确写法
// for (int i = 0; i< len; i++) {
// System.out.print(cbuf[i]);
// }
//方式二:
//错误写法,对应着方式一的错误方法
// String str = new String(cbuf);
// System.out.print(str);
//正确写法
String str = new String(cbuf, 0, len);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr != null){
fr.close();
}
}
//资源的关闭
}
3.1.2 FileWriter的使用
从内存中写出数据到硬盘的文件里。
说明:
-
输出操作,对应的File可以不存在的。并不会报异常
-
File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件
File对应的硬盘中的文件如果存在:
- 如果流使用的构造器是:FileWriter(file,false) / FileWriter(false)
- 如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容
@Test
public void testFileWriter(){
FileWriter fw = null;
try {
//1. 提供File类的对象,指明写出到的文件
File file = new File("hello.txt");
//2. 提供FileWriter的对象,用于对象的写出
fw = new FileWriter(file);
//3. 写出的操作
fw.write("I hava a dream");
fw.write("you need to have a dream!");
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 流资源的关闭
try {
if (fw != null){
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.1.3 文本文件的复制
@Test
public void testFileReaderFileWriter(){
FileReader fr = null;
FileWriter fw = null;
try {
//1. 创建File类的对象,指明读入和写出的文件
File srcFile = new File("hello.txt");
File destFile = new File("hello2.txt");
//不能使用字符流的设置来处理图片
//2. 创建输入流和输出流的对象
fr = new FileReader(srcFile);
fw = new FileWriter(destFile);
//3. 数据的读入和写出操作
char[] cbuf = new char[5];
int len; //记录每次读入到cbuf数组中的字符的个数
while ((len = fr.read(cbuf)) != -1){
//每次写出len个字符
fw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
//4. 关闭流资源
} finally {
try {
if (fr != null){
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (fw != null){
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.2 FileInputStream / FileOutputStream的使用
/*
测试FileInputStream和FileOutputStream的使用
结论:
1. 对于文本文件(.txt, ,java, .c , .cpp),使用字符流处理
2. 对于非文本文件(.jpg , .mp3 , .doc , .ppt ,...),使用字节流处理
* */
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputOutputStreamTest {
@Test
public void testFileInputStream(){
FileInputStream fi = null;
try {
//1. 造文件
File file = new File("hello.txt");
//2. 造流
fi = new FileInputStream(file);
//3. 读数据
byte[] buffer = new byte[5];
int len;
while ((len = fi.read(buffer))!= -1){
String str = new String(buffer, 0, len);
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fi != null){
fi.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//4. 关闭流
}
}
4.缓冲流的使用
4.1 缓冲流涉及到的类
BufferedInputStream
BufferedOutputStream
BufferedRead
BufferedWriter
4.2作用
提供流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区,默认情况下时8kb
4.3典型代码
4.3.1 使用BufferedInputStream和BufferedOutputStream
处理非文本文件
//实现文件复制的方法
public void copyFileWithBuffered(String srcPath, String destPath){
FileInputStream fis = null;
FileOutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//1. 早文件
File srcFile = new File(srcPath);
File destFile = new File(destPath);
//2. 造流
//2.1 造节点流
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//2.2 造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3. 复制的细节:读取、写入
byte[] buffer = new byte[10];
int len;
while ((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
bos.flush(); //刷新缓冲区
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bis != null){
bis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bos != null){
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//4. 资源关闭
//要求:先关闭外层的流,再关闭内层的流
//说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略
// fis.close();
// fos.close();
}
4.3.2 使用BufferedReader和BufferedWriter
处理文本文件
@Test
public void testBufferedReaderBufferedWriter(){
BufferedReader br = null;
BufferedWriter bw = null;
try {
//创建文件和相应的流
br = new BufferedReader(new FileReader(new File("hello.txt")));
bw = new BufferedWriter(new FileWriter(new File("hello1.txt")));
// //读写操作
// //方式一:使用char[]数组方式
// char[] cbuf = new char[1024];
// int len;
// while ((len = br.read(cbuf)) != -1){
// bw.write(cbuf,0,len);
// // bw.flush();
// }
//方式二:使用String
String data;
while ((data = br.readLine()) != null){
//方法一:
// bw.write(data + "\n"); //data中不包含换行符
//方法二:
bw.write(data);//data中不包含换行符
bw.newLine();//提供一个换行操作
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bw != null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//关闭资源
}
5.转换流的使用
5.1 转换流涉及到的类:属于字符流
InputStreamReader:将一个字节的输入流转换为字符的输入流
解码:字节、字节数组 ----> 字符数组、字符串
OutputStreamWriter:将一个字符的输出流转换为字符的输出流
编码:字符数组、字符串 ----> 字节、字节数组
说明:编码决定了解码的方式
5.2 作用
提供字节流和字符流之间的转换
5.3 典型实现
//InputStreamReader的使用
@Test
public void test(){
InputStreamReader isr = null; //使用系统默认的字符集
try {
FileInputStream fis = new FileInputStream("hello.txt");
//参数2指明了字符集,具体使用哪个字符集,取决于文件使用的字符集
isr = new InputStreamReader(fis,"UTF-8");
char[] cbuf = new char[20];
int len;
while ((len = isr.read(cbuf)) != -1){
String str = new String(cbuf, 0, len);
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (isr != null){
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void test1(){
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
//1. 早文件、造流
File file1 = new File("hello.txt");
File file2 = new File("hello_gbk.txt");
FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);
isr = new InputStreamReader(fis,"utf-8");
osw = new OutputStreamWriter(fos,"gbk");
//2. 读写过程
char[] cbuf = new char[20];
int len;
while ((len = isr.read(cbuf)) != -1){
osw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
//3. 关闭流
} finally {
if (isr != null){
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (osw != null){
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
5.4 说明
文件编码的方式,决定了解析时使用的字符集
字符集
ASCII:美国标准信息交换码。用一个字节的7位可以表示。
IS08859-1: 拉丁码表。欧洲码
用一个字节的8位表示。
GB2312: 中国的中文编码表。最多两个字节编码所有字符
GBK: 中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
Unicode: 国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节表示
UTF-8: 变长的编码方式,可用1-4个字节来表示一个字符。
6.其它流的使用
/*
其他流的使用
1. 标准的输入、输出流
2. 打印流
3. 数据流
* */
import org.junit.Test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class OtherStreamTest {
/*
1. 标准的输入、输出流
1.1
System.in:标准的输入流,默认从键盘输入
System.out:标注的输出流,默认从控制台输出
1.2
System类的setIn(inputStream is) / setOut(PrintStream ps)方式重新指定输入和输出的流
1.3 练习:
从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作。
直至当输入“e”或者“exit”时,退出程序。
方法一:使用Scanner实现,调用next()方法,返回一个字符串
方法二:使用System.in实现。System.in ----> 转换流 ----> BufferedReader的readLine()
* */
@Test
public void test(){
BufferedReader br = null;
try {
InputStreamReader isr = new InputStreamReader(System.in);
br = new BufferedReader(isr);
while (true){
System.out.println("请输入字符串");
String data = br.readLine();
if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)){
System.out.println("程序结束");
break;
}
String upperCase = data.toUpperCase();
System.out.println(upperCase);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/*
2. 打印流:PrintStream 和PrintWriter
2.1 提供了一系列重载的print() 和 println()
* */
/*
3. 数据流
3.1 DataInputStream 和 DataOutputStream
3.2 作用:用于读取或写出基本数据类型的变量或字符串
* */
}
7.对象流的使用
7.1 对象流
ObjectInputStream 和ObjectOutputStream
7.2 作用
ObjectOutputStream:内存中的对象---->存储中的文件、通过网络传输出去 :序列化过程
ObjectInputStream :存储中的文件、通过网络接收过来---->内存中的对象 :反序列化过程
用于存储和读取基本数据类型数据或对象的处理流。它的强大之处在于可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
7.3 对象的序列化机制
对象序列化机制允许把内存中的java对象转换成平台无关的二进制流,从而允许把这种二进制流之久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原来地java对象
7.4 序列化过程
将内存中的java对象保存到磁盘中或通过网络传输出去
使用ObjectOutputStream实现
@Test
public void testObjectOutputStream(){
ObjectOutputStream oos = null;
try {
//1.
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
//2.
oos.writeObject(new String("我爱北京天安门"));
oos.flush();
oos.writeObject(new Person("小帆",22));
oos.flush();
} catch (IOException e) {
} finally {
//3. 关闭流资源
if (oos != null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
7.5 反序列化过程
将磁盘文件中的对象还原为内存中的java对象
使用ObjectInputStream来实现
@Test
public void testObjectInputStream(){
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("object.dat"));
Object obj = ois.readObject();
String str = (String) obj;
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
7.6 实现序列化的对象所属的类需要满足
- 需要实现接口:Serializable
- 当前类提供一个全局常量:serialVersionUID
- 除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有的属性
也必须时可序列化的。(默认情况下,基本数据类型可序列化) - 序列化机制:
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种
二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。
当其它程序获取了这种二进制流,就可以恢复成原来的Java对象
补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
8.RandmoAccessFile的使用
8.1 任意存取文件流
RandomAccessFile
8.2 使用说明
- RandomAccessFile直接继承与java.lang.Object类,实现了DataInput和DataOutput接口
- RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
- 如果RandomAccessFile作为一个输出流时,写出到的文件如果不存在,则执行过程中自动创建
如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖) - 可以通过相关的操作实现RandomAccessFile“插入”的效果
8.3 典型代码
@Test
public void test() {
RandomAccessFile raf1 = null;
RandomAccessFile raf2 = null;
try {
raf1 = new RandomAccessFile(new File("图片.jpg"), "r");
raf2 = new RandomAccessFile(new File("图片.jpg"), "rw");
byte[] buffer = new byte[1024];
int len;
while ((len = raf1.read(buffer)) != -1) {
raf2.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (raf1 != null) {
try {
raf1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (raf2 != null) {
try {
raf2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
private void test1(){
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(new File("hello.txt"), "rw");
raf.seek(3);//将指针调到角标为3的位置
raf.write("xyz".getBytes());//覆盖数据的操作
} catch (IOException e) {
e.printStackTrace();
} finally {
if (raf != null){
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/*
使用RandomAccessFile实现数据的插入效果
* */
@Test
public void test2(){
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(new File("hello.txt"), "rw");
raf.seek(3);
//保存指针3后面的所有数据到StringBuilder中
StringBuilder builder = new StringBuilder((int) new File("hello.txt").length());
byte[] buffer = new byte[20];
int len;
while ((len = raf.read(buffer)) != -1){
builder.append(new String(buffer,0,len));
}
//调回指针,写入"xyz"
raf.seek(3);
raf.write("xyz".getBytes());
//将StringBuilder中的数据写入到文件中
raf.write(builder.toString().getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (raf != null){
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
9.Path、Paths、Files的使用
网络编程
一、网络编程中有两个主要的问题:
- 如何准确地定位网络上一台或多态主机;定位主机上的特定的应用
- 找到主机后如何可靠高效地进行数据传输
二、网络编程中的两个要素:
- 对应问题一:IP和端口号
- 对应问题二:提供网络通信协议:TCP/IP参考模型(应用曾、传输层、网络层、物理+数据链路层)
三、通信要素一:IP和端口号
-
IP:唯一的额表示Internet上的计算机(通信实体)
-
在java中使用InetAddress类代表IP
-
IP分类:IPv4 和IPv6;万维网和局域网
-
域名:
-
本地回路地址:127.0.0.1 对应着:localhost
-
如何实例化InetAddress:两个方法:getByName(String host) \ getLocalHost()
两个常用方法:getHostName() / getHostAddress() -
端口号:正在计算机进行的进程。
要求:不同的进程有不同的端口号
范围:被规定为一个16位的证书0~65535 -
端口号和IP地址的组合得出一个网络套接字:Socket
public static void main(String[] args) {
try {
InetAddress inet1 = InetAddress.getByName("192.168.10.14");
System.out.println(inet1);
InetAddress inet2 = InetAddress.getByName("www.baidu.com");
System.out.println(inet2);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
TCP
/*
实现TCP的网络编程
例题2:客户端发送文件给服务器,服务器将文件保存在本地,并回复"发送完成"
* */
import org.junit.Test;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPTest3 {
@Test
public void client(){
OutputStream os = null;
FileInputStream fis = null;
try {
//1.
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
//2.
os = socket.getOutputStream();
//3.
fis = new FileInputStream(new File("图片.jpg"));
//4.
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1){
os.write(buffer,0,len);
}
//关闭数据的输出
socket.shutdownOutput();
//接受来自于服务器段的数据,并显示在控制台上
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer1 = new byte[20];
int len1;
while ((len1 = is.read(buffer1)) != -1){
baos.write(buffer1,0,len1);
}
System.out.println(baos.toString());
baos.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
//5.
if (fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void server(){
ServerSocket ss = null;
Socket socket = null;
InputStream is = null;
FileOutputStream fos = null;
try {
ss = new ServerSocket(9090);
socket = ss.accept();
is = socket.getInputStream();
fos = new FileOutputStream(new File("图片5.jpg"));
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
// 服务器端给予客户端反馈
OutputStream os = socket.getOutputStream();
os.write("发送成功".getBytes());
os.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ss != null){
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
UDP
/*
UDP协议的网络编程
* */
import org.junit.Test;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPTest {
//发送端
public void sender(){
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
String str = "UDP";
byte[] data = str.getBytes();
InetAddress inet = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);
socket.send(packet);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null){
socket.close();
}
}
}
//接收端
@Test
public void receiver(){
DatagramSocket socket = null;
try {
socket = new DatagramSocket(9090);
byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),packet.getLength()));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null){
socket.close();
}
}
}
}
Java反射机制
1. 反射的概述
1.1 本章的主要内容
本章标题
1.2 关于反射的理解
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并直接操作任意对象的内部属性和方法。
框架 = 反射 + 注解 + 设计模式
1.3 体会反射机制的“动态性”
//体会反射的动态性
@Test
public void test2(){
int num = new Random().nextInt(3);//0,1,2
String classPath = "";
switch (num){
case 0:
classPath = "java.util.Data";
break;
case 1:
classPath = "java.sql.Date";
break;
case 2:
classPath = "exercise.reflectionexercise.Person";
break;
}
try {
Object obj = getInstance(classPath);
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
/*
创建一个指定类的对象
classPath:指定类的全类名
* */
public Object getInstance(String classPath) throws Exception{
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
1.4 反射机制能提供的功能
- 在运行是判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时注解
- 生成动态代理
1.5 相关的API
java.lang.Class:反射的源头
java.lang.reflect.Method
java.lang.reflect.Field
java.lang.reflect.Constructor
...
2.Class类的理解与获取Class的实例
2.1 Class类的理解
关于java.lang.Class类的理解
- 类的加载过程:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class),接着我们使用
java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中,此
过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的
一个实例。 - 换句话说,Class的实例就对应着一个运行时类。
- 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类,
2.2 获取Class实例的几种方式
//方式一:调用运行时类的属性:.class
Class clazz1 = Person.class;
System.out.println(clazz1);
//方式二:通过运行时类的对象
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
//方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("reflectionexercise.Person");
System.out.println(clazz3);
//方式四:类的加载器:Classloader(了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("reflectionexercise.Person");
System.out.println(clazz4);
2.3 总结:创建类的对象的方式
方式一:new +构造器
方式二:要创建Xxx类的对象,可以考虑:Xxx、Xxxs、XxxFactory、XxxBuilder类中查看是否有静态方法的存在。可以调用其静态方法,创建Xxx对象。
方式三:通过反射
2.4 Class实例可以是哪些结构的说明
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;
3.了解ClassLoader
public class ClassLoaderTest {
@Test
public void test1(){
//对于自定义类,使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//调用系统类加载器的getParent():获取扩展类加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
//调用扩展类加载器的getParent():无法获取引导类加载器
//引导类加载器主要负责加载java的核心类库,无法加载自定义的
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2);
}
/*
Properties:用来读取配置文件
*/
@Test
public void test2() throws Exception {
Properties pros = new Properties();
//此时的文件默认在当前的module下。
//读取配置文件的方式一:
// FileInputStream fis = new FileInputStream("jdbc.properties");
// pros.load(fis);
//读取配置文件的方式二:使用ClassLoader
//配置z文件默认识别为:当前module的src下
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc.properties");
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user = " + user + " password =" + password);
}
}
4.创建运行时类的对象
4.1 代码举例
Class clazz = Person.class;
Person obj = clazz.newInstance();
System.out.println(obj);
4.2 说明
newInstance():调用此方法,创建对应的运行时类的对象
内部调用了运行时类的空参构造器
要想此方法正常的创建类的对象,要求:
- 运行时类必须提供空参的构造器
- 空参的构造器的访问权限得够。通常,设置为public。
在javabean中要求提供一个public的空参构造器。原因:
- 便于通过反射,创建运行时类的对象
- 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
5.获取运行时类的完整结构
我们可以通过反射,获取对应的运行时类中所有的属性、方法、构造器、父类接口、父类的泛型、包、注解、异常等。。。
典型代码:
属性
@Test
public void test1(){
Class clazz = Person.class;
//获取属性结构
//getFields():获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = clazz.getFields();
for (Field f : fields) {
System.out.println(f);
}
//getDeclaredFields():获取当前运行类中声明的所有属性。(不包含父类中声明的属性)
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f : declaredFields) {
System.out.println(f);
}
}
//权限修饰符 数据类型 变量名
@Test
public void test2(){
Class clazz = Person.class;
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f : declaredFields) {
//1. 权限修饰符
int modifiers = f.getModifiers();
System.out.print(Modifier.toString(modifiers) + "\t");
//2. 数据类型
Class<?> type = f.getType();
System.out.print(type.getName() + "\t");
//3. 变量名
String fName = f.getName();
System.out.print(fName + "\t");
}
}
方法
@Test
public void test1() {
Class clazz = Person.class;
//getMethods():获取当前运行时类及其父类中声明为public访问权限的方法
Method[] methods = clazz.getMethods();
for (Method m : methods) {
System.out.println(m);
}
//getDeclaredMethods():获取当前运行类中声明的所有方法。(不包含父类中声明的方法)
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method m : declaredMethods) {
System.out.println(m);
}
}
/*
权限修饰符 返回值类型 方法名(参数类型1 形参名1,...)throws xxxException()
*/
@Test
public void test2() {
//1. 获取方法声明的注解
Class clazz = Person.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method m : declaredMethods) {
//1. 获取方法声明的注解
Annotation[] annos = m.getAnnotations();
for (Annotation a : annos) {
System.out.println(a);
}
//2. 权限修饰符
System.out.print(Modifier.toString(m.getModifiers()) + "\t");
//3. 返回值类型
System.out.print(m.getReturnType().getName() + "\t");
//4. 方法名
System.out.println(m.getName());
System.out.print("(");
//5. 形参列表
Class<?>[] parameterTypes = m.getParameterTypes();
if (!(parameterTypes == null && parameterTypes.length == 0)) {
for (int i = 0; i < parameterTypes.length; i++) {
if (i == parameterTypes.length - 1) {
System.out.print(parameterTypes[i].getName() + "args_" + i);
break;
}
System.out.print(parameterTypes[i].getName() + "args_" + i + ",");
}
}
System.out.print(")");
//6. 抛出的异常
Class<?>[] exceptionTypes = m.getExceptionTypes();
if (exceptionTypes.length > 0) {
System.out.print("throws ");
for (int i = 0; i < exceptionTypes.length; i++) {
if (i == exceptionTypes.length - 1) {
System.out.print(exceptionTypes[i].getName() + "args_" + i);
break;
}
System.out.print(exceptionTypes[i].getName() + "args_" + i + ",");
}
System.out.println();
}
}
}
/*
获取构造器结构
* */
@Test
public void test1(){
Class clazz = Person.class;
//getConstructors():获取当前运行类中声明为public的构造器
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor c : constructors){
System.out.println(c);
}
System.out.println();
//getDeclaredConstructors():获取当前运行时类中声明的所有的构造器
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor c : declaredConstructors) {
System.out.println(c);
}
}
/*
获取运行时类的父类
* */
@Test
public void test2(){
Class clazz = Person.class;
Class<? super Person> superclass = clazz.getSuperclass();
System.out.println(superclass);
}
/*
获取运行时类的带泛型的父类
* */
@Test
public void test3(){
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
}
/*
获取运行时类的带泛型的父类的泛型
* */
@Test
public void test4(){
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
//获取泛型类型
Type[] actualTypeArguments = paramType.getActualTypeArguments();
System.out.println(actualTypeArguments[0]);
}
/*
获取运行时类实现的接口
* */
@Test
public void test5(){
Class clazz = Person.class;
Class<?>[] interfaces = clazz.getInterfaces();
for (Class c : interfaces) {
System.out.println(c);
}
System.out.println();
//获取运行时类的父类实现的接口
Class<?>[] interfaces1 = clazz.getSuperclass().getInterfaces();
for (Class c : interfaces1) {
System.out.println(c);
}
}
/*
获取运行时类所在的包
* */
@Test
public void test6(){
Class clazz = Person.class;
Package pack = clazz.getPackage();
System.out.println(pack);
}
/*
获取运行时类声明的注解
* */
@Test
public void test7(){
Class clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annos : annotations) {
System.out.println(annos);
}
}
6.调用运行时类的指定结构
6.1 调用指定的属性
@Test
public void testField() throws Exception {
Class clazz = Person.class;
//创建运行时类的对象
Person p = clazz.newInstance();
//获取指定属性:要求运行时类中属性声明为public
Field age = clazz.getField("age");
/*设置当前属性的指
set():参数1:指明设置哪个对象的属性 参数2:将此属性设置多少
*/
age.set(p,28);
/*
get():参数1:获取哪个对象的属性值
* */
int pAge = (int) age.get(p);
System.out.println(pAge);
}
/*
如何操作运行时类中的指定的属性
* */
@Test
public void testField1() throws Exception {
Class clazz = Person.class;
//创建运行时类的对象
Person p = clazz.newInstance();
//1. getDeclaredField(String fieldName):获取运行时中指定变量名的属性
Field name = clazz.getDeclaredField("name");
//2. 保证当前属性是可访问的
name.setAccessible(true);
//3. 获取、设置指定对象的此属性值
name.set(p,"Tom");
System.out.println(name.get(p));
}
6.2 调用指定的方法
/*
如何调用运行时类中的指定的方法
* */
@Test
public void testMethod() throws Exception {
Class clazz = Person.class;
//创建运行时类的对象
Person p = clazz.newInstance();
/*
1.获取指定的方法
getDeclaredMethod():参数1:指明获取的方法的名称,参数2:指明获取的方法的形参列表
* */
Method show = clazz.getDeclaredMethod("showNation", String.class);
//2. 保证方法是可访问的
show.setAccessible(true);
/*
3. invoke():参数1:方法的调用这 参数2:给方法形参赋值的实参
invoke()的返回值即为对应类中调用的方法的返回值。
* */
Object retrunValue = show.invoke(p, "CHN");
System.out.println(retrunValue);
System.out.println("************如何调用静态方法*************");
Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
//如果调用的运行时类中的方法没有返回值,则此invoke()返回null
Object returnVal = showDesc.invoke(Person.class);
System.out.println(returnVal); //null
}
6.3 调用指定的构造器
/*
如何调用运行时类中的指定的构造器
* */
@Test
public void testConstructor() throws Exception{
Class clazz = Person.class;
/*
1. 获取指定的构造器
getDeclaredConstructor():参数:指明构造器的参数列表
*/
Constructor constructor = clazz.getDeclaredConstructor(String.class);
//2. 保证此构造器是可访问的
constructor.setAccessible(true);
//3. 调用此构造器创建运行时类的对象
Person per = constructor.newInstance("Tom");
System.out.println(per);
}
7.动态代理
7.1 代理模式的原理
使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
7.2 静态代理
7.2.1 举例
实现Runnable接口的方法创建多线程。
Class MyThread implements Runnable{} //相当于被代理类
Class Thread implements Runnable{}//相当于代理类
main(){
? Mythread t = new MyThread();
? Thread thread = new Thread(t);
? thread.start();//启动线程;调用线程的run()
package exercise.proxyexercise;
/*
静态代理举例
特点:代理类和被代理类在编译期间,就确定下来了。
* */
interface ClothFactory {
void produceCloth();
}
//代理类
class ProxyClothFactory implements ClothFactory {
private ClothFactory factory; //用被代理类对象进行实例化
public ProxyClothFactory(ClothFactory factory) {
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println("代理工厂做一些准备工作");
factory.produceCloth();
System.out.println("代理工厂做一些后续的收尾工作");
}
}
//被代理类
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("Nike工厂生产一批运动服");
}
}
public class StaticProxyTest {
public static void main(String[] args) {
//创建被代理类的对象
NikeClothFactory nike = new NikeClothFactory();
//创建代理类的对象
ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nike);
proxyClothFactory.produceCloth();
}
}
7.2.2 静态代理的缺点
- 代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。
- 每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。
7.3 动态代理的特点
动态代理是指客服通过代理类来调用其他对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
7.4 动态代理的实现
7.4.1 需要解决的两个问题
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。(通过Proxy.newProxyInstance()实现)
问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法。(通过InvaocationHandler接口的实现类及其方法invoke())
7.4.2代码实现
package exercise.proxyexercise;
/*
动态代理的举例
* */
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
public void method1(){
System.out.println("==========通用方法一==========");
}
public void method2(){
System.out.println("==========通用方法二==========");
}
}
/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法。
* */
class ProxyFactory{
public static Object getProxyInstance(Object obj){ //obj:被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj; //需要使用被代理类的对象进行赋值
public void bind(Object obj){
this.obj = obj;
}
//当我们同通过代理类的对象,调用方法a时,就会自动调用如下的方法:invoke()
//将被代理类要执行的方法a的功能就声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SuperMan superMan = new SuperMan();
superMan.method1();
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//obj:被代理类的对象
Object returnValue = method.invoke(obj, args);
superMan.method2();
//上述方法的返回值就作为当前类中的invoke()的返回值
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("四川麻辣烫");
System.out.println("*********************");
NikeClothFactory nikeClothFactory = new NikeClothFactory();
ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
proxyClothFactory.produceCloth();
}
}