JavaSE笔记
构造函数
###基本概念
构造函数是一个(或多个)特别的方法,在对象创建时自动调用。
构造函数的名字必须和类名一样,且没有返回值(普通方法也可以和类名一样)
###缺省构造函数
不带参数的构造函数称作“缺省构造函数”。
没有其他构造函数时,缺省构造函数不需要写出来,由编译器自动生成。
有多个构造函数时编译器不会自动生成缺省构造函数,如果需要缺省构造函数,就必须要写出来,否则该类就是没有缺省 无参的构造函数。
###构造函数的重载
构造函数有多个时,它们的形参列表(即方法的参数类型、数量、位置)必须不同。
构造函数有多个时,只会执行其中的一个。
构造函数有多个时,一个构造函数中可以通过this(…); 语句来调用另一个构造函数,该语句必须是函数的第一行。
构造函数与继承之间的关系
构造函数不会被继承。
父类的构造函数会比子类的构造函数先调用。
当父类有多个构造函数时,子类的构造函数中可以通过 super(…); 语句来选择使用哪个父类的构造函数。同样的,该语句必须是函数的第一行。(即有super就不能有this)
如果子类没有选择使用父类的哪个构造函数,那么系统会调用父类的缺省构造函数。
如果父类没有缺省构造函数,那么子类的构造函数中必须通过 super(…); 来选择使用哪个父类的构造函数。
构造函数可见性及其应用
构造函数可以被 public/protected/private 修饰,其含义与一般方法一样。
private 构造函数不能被子类和其他类调用。例如某构造函数为 private,则该构造函数只能在该类内部使用。
只有在构造函数的可见范围内才能使用 new 关键字来调用它
在单例模式中,设计单例类就是将该类的构造函数设为private,并将该类的一个引用变量instance设为其成员变量。
###静态块和构造块
如果要在类中初始化一些静态变量,则可以使用静态块,一个类中可以有多个静态块
静态代码块在JVM加载类的时候就运行了,而且只运行一次,并且优先于各种代码块以及构造函数。
静态代码块不能访问普通变量
构造代码块在每次new对象时都会运行一次,构造块可以访问静态变量
执行顺序优先级:静态块,main(),构造块,构造方法。
基本数据类型
Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。
byte:
- byte 数据类型是8位、有符号的,以二进制补码表示的整数;占1个字节
- 最小值是 -128(-2^7);
- 最大值是 127(2^7-1);
- 默认值是 0;
- byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int 类型的四分之一;
- 例子:byte a = 100,byte b = -50。
short:
- short 数据类型是 16 位、有符号的以二进制补码表示的整数;占2个字节
- 最小值是 -32768(-2^15);
- 最大值是 32767(2^15 - 1);
- Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;
- 默认值是 0;
- 例子:short s = 1000,short r = -20000。
int:
- int 数据类型是32位、有符号的以二进制补码表示的整数;占4个字节
- 最小值是 -2,147,483,648(-2^31);
- 最大值是 2,147,483,647(2^31 - 1);
- 一般地整型变量默认为 int 类型;
- 默认值是 0 ;
- 例子:int a = 100000, int b = -200000。
long:
- long 数据类型是 64 位、有符号的以二进制补码表示的整数;占8个字节;
- 最小值是 -9,223,372,036,854,775,808(-2^63);
- 最大值是 9,223,372,036,854,775,807(2^63 -1);
- 这种类型主要使用在需要比较大整数的系统上;
- 默认值是 0L;
- 例子: long a = 100000L,Long b = -200000L。
"L"理论上不分大小写,但是若写成"l"容易与数字"1"混淆,不容易分辩。所以最好大写。
float:
- float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;占4个字节;
- float 在储存大型浮点数组的时候可节省内存空间;
- 默认值是 0.0f;
- 浮点数不能用来表示精确的值,如货币;
- 例子:float f1 = 234.5f。
double:
- double 数据类型是双精度、64 位、符合IEEE 754标准的浮点数;占8个字节;
- 浮点数的默认类型为double类型;
- double类型同样不能表示精确的值,如货币;
- 默认值是 0.0d;
- 例子:double d1 = 123.4。
boolean:
- boolean数据类型表示一位的信息;
- 只有两个取值:true 和 false;
- 这种类型只作为一种标志来记录 true/false 情况;
- 默认值是 false;
- 例子:boolean one = true。
char:
- char类型是一个单一的 16 位 Unicode 字符;占2个字节;
- 最小值是 \u0000(即为 0);
- 最大值是 \uffff(即为65、535);
- 默认值是**\u0000**
- char 数据类型可以储存任何字符;
- 例子:char letter = ‘A’;。
自动类型转换
赋值或运算时 ,两边数据类型不一致时就会发生类型转换
整型、实型(常量)、字符型数据可以混合运算。
运算中,不同类型的数据先转化为同一类型,然后进行运算。
规则:从小到大 ,低字节向高字节自动提升
顺序:
- byte(1字节) – > short(2字节)-- > int(4字节) – > long(8字节) --> float(4字节) – > double(8字节)
- char (2字节)-- > int(4字节) – > long(8字节) --> float(4字节) – > double(8字节)
注意:
- 不能对boolean类型进行类型转换,byte也不能自动转换为char
- 不能把对象类型转换成不相关类的对象。
- 在把容量大的类型转换为容量小的类型时必须使用强制类型转换。除非使用赋值运算符(+=、-=等),左值类型小右值类型大是允许的,会自动完成强制类型转换
- 转换过程中可能导致溢出或损失精度
- 如果有多个重载的方法,传入的参数也可以发生自动类型转换,基本数据类型的实参先按顺序发生自动类型转换然后优先匹配对应的重载方法,接着自动装箱为对应的包装类,顺着包装类的继承树继续发生自动类型转换,直至Object。
包装类
Java将8种基本数据类型分别对应了8种包装类。8种包装类属于引用类型,父类是Object
因此可以使用面向对象的思想来处理基本数据类型
byte java.lang.Byte
short java.lang.Short
int java.lang.Integer
long java.lang.Long
float java.lang.Float
double java.lang.Double
boolean java.lang,Boolean
char java.lang.Character
以下进行Integer的学习,其他类进行类比:
基本数据类型----(转换为)------>引用数据类型 (装箱)
引用数据类型----(转换为)------>基本数据类型 (拆箱)
Java新特性可以实现基本数据类型和包装类的自动转换
自动装箱:Integer x = 900;
自动拆箱:int y = x;
哪些地方会自动拆装箱?
场景一、可以直接将基本数据类型放入集合类
场景二、包装类型和基本类型的大小比较
场景三、包装类型的加减乘除运算
场景四、三目运算符的使用
场景五、函数参数与返回值
**注意:**Java中为了提高效率,将[-128,127]之间所有的整数型包装类(Byte、Short、Integer、Long)的对象提前创建好放到了方法区的整数型常量池中,目的是当使用这个区间的数据时不需要new对象,直接从常量池中取出来。
Integer x = 128;
Integer y = 128;
System.out.println(x==y); //结果为:false,因为常量池中没有128,需要new包装对象
Integer a = 127;
Integer b = 127;
System.out.println(a==b); //结果为:true。常量池中有127.直接
常用方法(其他包装类类比学习):
static int parseInt(String str):将str类型的数字转换为int类型数字,如果str不是数字则发生NumberFormatException异常
static String toBinaryString(int i):将十进制整数转换为二进制字符串
static String toHexString(int i):将十进制整数转换为十六进制字符串
static String toOctalString(int i):将十进制整数转换为八进制字符串
static Integer valueOf(String s):将数字字符串转换成Integer包装类
String,Integer,int类型之间的相互转换
-
基本数据类型的转换
-
int -> Integer (两种方法)
Integer it = 6; (自动装箱)
Integer it1 = new Integer(int a); //装箱的基本原理
-
int -> String(两种方法)
String s1=10+"";
String s2= String.valueOf(6)
-
-
String类型的转换
-
String->int
int i = Integer.pareInt(“123”);
-
String->Integer
Integer it = Integer.valueOf(“123”);
-
-
Integer类型的转换
-
Integer->int
自动拆箱
-
Integer->String
String .valueOf(it);
-
总结:
任何类型转换为String类型都可以使用String.valueOf()方法,特别地,基本数据类型可以通过toBinaryString(),基本数据类型和包装类一般直接通过自动装箱拆箱。
String类转换为int类通过Integer.parseInt(str),转换为Integer类通过Integer.valueOf(str)
访问控制权限修饰符
权限 | 本类中 | 同包下 | 不同包的子类 | 不同包非子类 |
---|---|---|---|---|
private | 可以 | × | × | × |
空(包修饰符) | 可以 | 可以 | × | × |
protected | 可以 | 可以 | 可以 | × |
public | 可以 | 可以 | 可以 | 可以 |
default |
访问控制权限修饰符可以修饰什么?
属性:四个修饰符都可以
方法:四个修饰符都可以
类:只能使用public或空
接口:只能使用public或空
枚举:只能使用public或者空
关于default修饰符
- default 修饰符只能用在接口的方法和属性上,并且不能是静态的。
- 没有访问修饰符(空)和使用default 修饰符 不能说成一样。没有修饰符能用在各种情况下,而default只能用在接口里面,且必须要实现接口方法。
- 按照jdk1.8的新特性,接口可以有具体方法实现,但是必须是default或者static修饰的(不能同时default和static)
继承
this关键字
- this代表本类的当前对象引用
- this可以出现在实例方法和构造方法中
- this的语法:“this.”、“this()”
- this不能使用在静态方法中
- this.大部分情况下是可以省略的,this在区分局部变量和实例变量的时候不能省略
- this()只能出现在构造方法的第一行,通过当前的构造方法去调用本类中的其他构造方法,目的是代码复用
super关键字
- super代表当前对象内部的那一块父类型特征,并不是引用,也不保存内存地址
- super可以出现在实例方法和构造方法中
- super的语法:“super.” (访问父类属性和方法)、“super()”(调用父类的构造方法)
- super不能使用在静态方法中
- super.大部分情况下是可以省略的,但当子类有与父类属性同名的属性或者子类重写覆盖了父类的方法时,需要显式使用this.和super.区分两个同名属性和方法,什么都不加默认为子类的属性;
- super(xxx)只能出现在构造方法的第一行,如果构造方法中既没有显式声明this(xxx),又没有显式声明super(xxx),会默认提供super(),通过当前的构造方法去调用父类中的构造方法,目的是在创建子类对象的时候,先初始化父类特征(并不是创建父类对象)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4nQJQ88d-1634267070859)(C:\Users\DaY1zz\AppData\Roaming\Typora\typora-user-images\image-20201210172217197.png)]
多态
方法重写Override
-
什么时候考虑使用方法重写?
父类中方法无法满足子类的需求或与子类的需求不符,子类有必要对继承的方法进行重写
-
方法重写的条件
-
两个类之间具有继承关系,子类可以重写父类的方法
-
子类重写的方法和父类被覆盖的方法具有相同的方法名、返回值类型(基本数据类型必须一致,但子类的返回值引用类型可以变小,但一般开发都是相同返回值类型)、形参列表
-
子类重写的方法访问权限不能更低,因此父类的private方法不能被重写覆盖
-
子类重写的方法抛出的异常不能更多
-
父类的构造方法无法被继承,所以构造方法不能被重写覆盖
-
方法重写只是针对于实例方法(成员方法),静态方法的重写覆盖没有意义,因为静态方法的执行与对象无关
-
-
方法重载和方法重写的区别
- 方法重载是发生在同一个类中,方法名相同,参数列表不同的多个方法
- 方法重写是发生在具有继承关系的父子类之间,子类重写的方法必须和父类中的方法一致:方法名一致、形参列表一致、返回值一致
向上转型和向下转型
Java引用在不同阶段的类型
Java引用类型分为:编译时类型、运行时类型
编译时类型是由声明该引用的类型决定
运行时类型是由该引用指向的堆内存的对象的类型决定
Animal a = new Cat();
a的编译时类型为Animal,运行时类型为Cat
因此真正执行的时候会调用运行时类型的相关方法
####向上转型和向下转型的概念
向上转型:子类---->父类(upcasting)
又被称为自动类型转换:Animal a = new Cat();
但父类引用无法调用子类对象特有的方法,会出现编译错误,因为编译器只认识编译时类型,且父类引用无法使用子类的特有属性(成员变量+静态变量),若有子类和父类的属性重名,使用的是父类的属性
向下转型:父类----->子类(downcasting)
又被称为强制类型转换:Cat c = (Cat) a;
当需要调用子类中特有的方法时,必须进行向下转型才能够调用,而且只有当父类引用指向的堆内存中的对象是属于该子类时,父类引用才可以强转为子类,否则会出现ClassCastException(类型转换异常),该异常与NullPointerException都非常常见
instanceof运算符
语法: obj instanceof class ,返回值为boolean
作用:instanceof可以在运行阶段动态判断指向的obj与class之间的关系
注意:
1. obj只能是引用,不能是基本数据类型
2. obj为null时,返回false
3. obj所指向的对象为class类的实例对象时,返回true
4. ob所指向的对象属于class类的直接或间接子类时,返回true
5. obj所指向的对象属于clss接口的实现类时,返回true
总结:当需要向下转型(强制类型转换)的情况时,必须先进行instanceof判断以避免ClassCastException,这是Java编程的规范
抽象类
final关键字
- final修饰的类无法被其他类继承,可以继承其他类
- final修饰的方法无法被重写覆盖,可以被子类继承
- final修饰的变量只能赋一次值
- final修饰的引用一旦指向某个实例对象,就不能在重新指向其它对象,但该对象的数据可以修改
- final修饰的实例变量(成员变量)必须手动初始化,不能采用系统默认值
- final修饰的实例变量常常和static一起使用来创建类常量,命名用全大写字母和下划线分隔
抽象类基本语法
-
抽象类的声明:在class 前添加abstract关键字即可
-
抽象方法声明:
public abstract void doSome();
没有方法体的方法称为抽象方法 -
final和abstract不能联合使用,这两个关键字是相互对立的
-
包含抽象方法的类为抽象类,且必须声明为抽象类
-
抽象类无法通过new实例化创建对象
-
抽象类可以没有抽象方法,但是如果一个类已经声明成了抽象类,即使这个类中没有抽象方法,它也不能再实例化
-
抽象类的子类如果没有实现(重写、覆盖)抽象类中所有的抽象方法,子类仍为抽象类,应该用abstract修饰,直到实现了抽象类中所有的抽象方法后,子类才能实例化。
-
抽象类本身设计就是用来被子类继承的,因此抽象类尽量不要继承具体类
-
抽象类虽然无法实例化,但抽象类也有一个或多个构造方法,,可以供子类通过super调用
-
Java中没有方法体的方法不一定就只能是抽象方法(了解)
接口
接口的基本语法
- 接口也是一种“引用数据类型”。编译之后也会生成一个class字节码文件
- 接口时完全抽象的。(Java抽象类可以认为时半抽象),也可以认为接口是特殊的抽象类
- 接口的定义语法:[修饰符列表] interface 接口名 {…}
- 接口支持多继承,一个接口可以extends多个接口,一个类可以implements多个接口只能extends一个类,
- 接口中只包含且只能包含两种内容:一种是常量(修饰符默认为public static final,且注意要初始化!),一种是抽象方法(修饰符默认为public abstract,且注意不能有方法体{ }!)
- 接口中所有内容都是默认public修饰的
- 一个非抽象的类implements接口时,必须将接口中所有的抽象方法全部实现(重写覆盖),否则需要声明为abstract class
- 当非抽象类实现了接口之后,可以使用接口的引用变量指向实现了该接口的类的实例对象,从而实现接口回调,即多态的实现。
- 一个类继承和实现都存在的话,extends关键字在前,implements关键字在后
- 接口的使用离不开多态机制,接口+多态才可以降低程序耦合度,接口把调用者和实现者分隔开
抽象类和接口的区别
- 抽象类是半抽象的 ,接口是完全抽象的
- 抽象类中有构造方法,接口中没有构造方法
- 类和类之间只能单继承,接口和接口直接可以多继承
- 一个类只能继承一个类,但能实现多个接口
- 接口中只允许出现常量和抽象方法
- 接口在实际开发中比抽象类多,接口一般是对“行为”的抽象。
枚举
-
枚举是一种引用数据类型
-
枚举类型定义的语法:
enum 枚举类型名{ 枚举值1,枚举值2... } 典型例子: public enum Season{ SPRING,SUMMER,AUTUMN,WINTER }
3.枚举中的每一个值看作为常量,命名按照常量规范命名
匿名内部类
匿名内部类可以使你的代码更加简洁但是易读性较差,你可以在定义一个类的同时对其进行实例化。它与局部类很相似,不同的是它没有类名不能重复使用,如果某个局部类你只需要用一次,那么你就可以使用匿名内部类。
官方文档中的例子:
public class HelloWorldAnonymousClasses {
/**
* 包含两个方法的HelloWorld接口
*/
interface HelloWorld {
public void greet();
public void greetSomeone(String someone);
}
public void sayHello() {
// 1、局部类EnglishGreeting实现了HelloWorld接口
class EnglishGreeting implements HelloWorld {
String name = "world";
public void greet() {
greetSomeone("world");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Hello " + name);
}
}
HelloWorld englishGreeting = new EnglishGreeting();
// 2、匿名类实现HelloWorld接口
HelloWorld frenchGreeting = new HelloWorld() {
String name = "tout le monde";
public void greet() {
greetSomeone("tout le monde");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Salut " + name);
}
};
englishGreeting.greet();
frenchGreeting.greetSomeone("Fred");
}
public static void main(String... args) {
HelloWorldAnonymousClasses myApp = new HelloWorldAnonymousClasses();
myApp.sayHello();
}
}
运行结果为:
1 Hello world
2 Salut Fred
匿名内部类的语法:
匿名类表达式包含以下部分:
-
操作符:new;
-
一个要实现的接口或要继承的类(不要误以为new接口对象)
-
一对括号,如果是匿名子类,与实例化普通类的语法类似,如果有构造参数,要带上构造参数;如果是实现一个接口,只需要一对空括号即可;
-
一段被"{}"括起来类声明主体;
-
末尾的";"号(因为匿名类的声明是一个表达式,是语句的一部分,因此要以分号结尾)。
HelloWorld frenchGreeting = new HelloWorld() { String name = "tout le monde"; public void greet() { greetSomeone("tout le monde"); } public void greetSomeone(String someone) { name = someone; System.out.println("Salut " + name); } };
**注意:**由于匿名内部类没有类名,所以有许多限制!
-
匿名内部类没有构造方法(至少是明面上没有),构造方法是要有类名的。 不过使用构造代码块可以起到构造器的作用,但有局限性。
-
匿名内部类中不能定义静态属性、方法;
-
当创建匿名内部类时,必须实现接口或抽象父类里的所有抽象方法。
-
被匿名内部类访问的局部变量必须使用final修饰,即匿名内部类不能对外部的局部变量重新赋值
Object类
-
Object类是一个特殊的类,是所有类的父类,如果一个类没有用extends明确指出继承于某个类,那么它默认继承Object类,因此Java中每个类都含有Object类的方法
-
常用方法:
- protected Object clone():负责对象克隆
- int hasCode():获取对象哈希值
- boolean equals(Object obj):判断两个对象是否相等
- String toString():将对象转换成字符串形式
- protected void finalize():垃圾回收器负责调用的方法
-
关于toString()方法
Object中源代码是:
public String toString() { return getClass().getName() + '@' + Integer.toHexString(hashCode()); }
默认实现是:类名@对象的内存地址的十六进制形式
设计目的:返回一个以简洁的、详实的、易阅读的文本形式表示该对象的字符串
建议所有子类都去重写toString()方法
System.out.println( )输出引用的时候,会自动调用该引用指向的对象的toString()方法(println底层实现了这一功能)
-
关于equals()方法
Object中源代码
public boolean equals(Object obj) { return(this == obj); }
默认实现:判断两个对象内存地址是否相同
设计目的:判断两个对象是否相等
Object的默认实现对于大部分类不够用,建议子类都去重写equals()方法
Java中比较基本数据类型可以使用“==”判断,比较引用数据类型应该使用equals()方法判断(Java中没有运算符重载!)
重写equals()方法的编写模式一般都是固定的:
public class Student{ int no; String name; public boolean equals(Object obj) { if(obj == null || !(obj instanceof Student)) return false; if(this == obj) //若内存地址相同,则对象肯定相同 return true; //以上内容是固定模板 Student s = (Student)obj; return this.no == s.no && this.name.equals(s.name); } }
-
关于finalize()方法
作用类似于c++中的析构函数,不需要手动调用,Java垃圾回收器(GC)自动调用
-
关于hasCode()方法
返回哈希码,实际上可以看作是对象的内存地址
Arrays类
Java提供的Arrays类里包含的一些static修饰的方法可以直接操作数组
- int binarySearch(type[] a, type key):使用二分法查询 key元素值在a数组中出现的索引;如果a数组不包含key元素值,则返回负数。调用该方法时要求数组中元素已经按升序排列,这样才能得到正确结果。
- int binarySearch(type[] a, int fromIndex, int toIndex, type key):这个方法与前一个方法类似,但它只搜索a数组中 fromIndex到toIndex索引的元素。调用该方法时要求数组中元 素已经按升序排列,这样才能得到正确结果。
- type[] copyOf(type[] original, int length):这个方法将 会把original数组复制成一个新数组,其中length是新数组的 长度。如果length小于original数组的长度,则新数组就是原数组的前面length个元素;如果length大于original数组的长 度,则新数组的前面元素就是原数组的所有元素,后面补充 0(数值类型)、false(布尔类型)或者null(引用类型)。
- type[] copyOfRange(type[] original, int from, int to):这个方法与前面方法相似,但这个方法只复制original数组的from索引到to索引的元素。
- boolean equals(type[] a, type[] a2):如果a数组和a2数组 的长度相等,而且a数组和a2数组的数组元素也一一相同,该方法将返回true。
- void fill(type[] a, type val):该方法将会把a数组的所有元素都赋值为val。
- void fill(type[] a, int fromIndex, int toIndex, type val):该方法与前一个方法的作用相同,区别只是该方法仅仅 将a数组的fromIndex到toIndex索引的数组元素赋值为val。
- void sort(type[] a):该方法对a数组的数组元素进行排序。
- void sort(type[] a, int fromIndex, int toIndex):该方 法 与 前 一 个 方 法 相 似 , 区 别 是 该 方 法 仅 仅 对 fromIndex 到 toIndex索引的元素进行排序。
- String toString(type[] a):该方法将一个数组转换成一个字符串。该方法按顺序把多个数组元素连缀在一起,多个数组元素使用英文逗号(,)和空格隔开([1, 2, 3])。
Java 8增强了Arrays类的功能,为Arrays类增加了一些工具方 法,这些工具方法可以充分利用多CPU并行的能力来提高设值、排序的性能。
- void parallelPrefix(xxx[] array, XxxBinaryOperator op):该方法使用op参数指定的计算公式计算得到的结果作为新的数组元素。op计算公式包括left、right两个形参,其中left 代表新数组中前一个索引处的元素,right代表array数组中当 前索引处的元素。新数组的第一个元素无须计算,直接等于 array数组的第一个元素。 (op参数一般用lambda表达式输入)
- void parallelPrefix(xxx[] array, int fromIndex, int toIndex, XxxBinaryOperator op):该方法与上一个方法相似,区别是该方法仅重新计算fromIndex到toIndex索引的元 素。
- void setAll(xxx[] array, IntToXxxFunction generator): 该方法使用指定的生成器(generator)为所有数组元素设置值,该生成器控制数组元素的值的生成算法。
- void parallelSetAll(xxx[] array, IntToXxxFunction generator):该方法的功能与上一个方法相同,只是该方法增 加了并行能力,可以利用多CPU并行来提高性能。
- void parallelSort(xxx[] a):该方法的功能与Arrays类以前 就有的sort()方法相似,只是该方法增加了并行能力,可以利用多CPU并行来提高性能。
- void parallelSort(xxx[] a, int fromIndex, int toIndex):该方法与上一个方法相似,区别是该方法仅对 fromIndex到toIndex索引的元素进行排序。
- Spliterator.OfXxx spliterator(xxx[] array):将该数组的 所有元素转换成对应的Spliterator对象。
- Spliterator.OfXxx spliterator(xxx[] array, int startInclusive, int endExclusive):该方法与上一个方法相 似,区别是该方法仅转换startInclusive到endExclusive索引 的元素。
- XxxStream stream(xxx[] array) : 该 方 法 将 数 组 转 换 为 Stream,Stream是Java 8新增的流式编程的API。
- XxxStream stream(xxx[] array, int startInclusive, int endExclusive):该方法与上一个方法相似,区别是该方法仅将 fromIndex到toIndex索引的元素转换为Stream。
注意:所有以parallel开头的方法都表示该方法可利 用CPU并行的能力来提高性能。上面方法中的xxx代表不同的数据类 型,比如处理int[]型数组时应将xxx换成int,处理long[]型数组时应 将xxx换成long。
String类
基本知识
-
String类代表字符串,属于引用数据类型,Java中所有字符串字面值如“abc”都作为String类的实例
-
String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。(在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。)
-
String类中所有的属性都被final修饰,因此String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了,任何看似改变的操作都是生成了新对象,且内存中不会有重复相同的字符串
-
String类其实是通过byte数组来保存字符串的,且byte数组的引用是常引用,被final修饰,且是private修饰,故其byte数组不可改变!!!。
-
Java中凡是直接双括号括起来的字符串(如"abc")会存储在方法区中的字符串常量池中
-
通过String构造函数new出来的字符串对象(如String str=new string(“abc”))会存储在堆上,堆中的String对象指向字符串常量池中的字符串
-
由于String类的底层存储原理较复杂和String实例的不可改变性,因此比较字符串对象不应该用"==",要用equals()方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tU1gSiB3-1634267070862)(C:\Users\DaY1zz\AppData\Roaming\Typora\typora-user-images\image-20201221214508540.png)]
构造方法
- String(byte[] bytes):将byte数组按默认字符集解码全部转换为字符串
- String(byte[] bytes, int off, int len):将byte数组中从off序号开始,长度为len的元素转换为字符串
- String(char[] chars):将char数组全部转换为字符串
- String(char[] chars, int off, int len):将char数组中从off序号开始,长度为len的元素转换为字符串
byte[] bytes ={97,98,99};
String s1 = new String(bytes); //s1为“abc”
String s2 = new String(bytes,1,2); //s2为“bc”
char[] chars = {'我','是','大','帅','比'};
String s3 = new String(chars); //s3为“我是大帅比”
String s4 = new String(chars,2,3) //s4为“大帅比”
常用方法
- char charAt(int index):返回字符串中指定索引处的char值
- int compareTo(String anotherString):按字典顺序比较两个字符串,相等返回0,前小后大返回-1,前大后小返回1(因此字符串之间比较大小不能直接使用 <、>)
- boolean contains(CharSequence s):判断字符串中是否包含子串s
- boolean endsWith(String s):判断字符串是否以串s结尾
- boolean startWith(String s):判断字符串是否以串s开始
- boolean equals(Object anObject):判断两字符串内容是否相等
- boolean equalsIgnoreCase(String s):判断两字符串内容是否相等,忽略大小写
- byte[] getBytes():将字符串转换为byte数组并返回
- char[] toCharArray():将字符串转换为char数组并返回
- int indexOf(String str):返回子串str在当前字符串中第一次出现处的索引(子串第一个字符的索引),没有该子串则返回-1
- int lastIndexOf(String str):返回子串str在当前字符串中最后一次出现的索引(子串第一个字符的索引),没有该子串则返回-1
- boolean isEmpty():判断字符串是否为空串
- int length():返回字符串长度(注意与数组的属性length相区别)
- String replace(CharSequence target, CharSequence replacement):将target串替换为replacement串
- String[] split(String regex):以regex为分割符进行拆分字符串,并返回各子串
- String subString(int beginIndex):从下标为beginIndex的元素(包括)开始到末尾截取字符串
- String subString(int beginIndex, int endIndex):截取从beginIndex到endIndex-1的子串,注意不包括下标为endIndex的元素(左闭右开)
- String toLowerCase():将该字符串全部转换为小写
- String toUpperCase():将该字符串全部转换为大写
- String trim():去除字符串前后的空白,注意不会去除中间的空白
- 唯一一个静态方法:String valueOf():将“非字符串”转换为“字符串”,参数是基本数据类型则直接转换,参数是引用数据类型则调用其toString()方法
StringBuffer类和StringBuilder类
-
**设计目的:**由于Java中的字符串是不可变的,每次拼接都会产生新的字符串,占用大量方法区中的内存,造成浪费。故使用StringBuffer和StringBuilder,它们的类对象中存储的字符内容是可以改变的。
-
**底层原理:**StringBuffer类和StringBuilder类实际以byte数组存储字符串,但与String不同的是:byte数组的引用没有被final修饰,且是属性被默认访问修饰符修饰,因此其byte数组可以修改内容也可以扩容(创建新数组并改变引用的指向)
-
构造函数(以StringBuffer为例,StringBuilder同理):
StringBuffer():构造一个不带字符且初始容量为16个byte的字符串缓冲区
StringBuffer(int capacity):构造一个没有字符且具有指定容量capacity个byte的字符串缓冲区
StringBuffer(String str):构造一个初始化指定字符串内容的字符串缓冲区,容量为str.len+16或127(如果str.len+16 > 127)
-
**优化性能:**虽然StringBuffer和StringBuilder可以扩容,但是底层还是通过创建新数组、复制、改变引用指向的地址来实现,如果初始容量不恰当仍会导致性能偏低。故在构造StringBuffer和StringBuilder前最好指定一个合适的初始容量,以减少底层数组的扩容次数
-
StringBuffer类和StringBuilder类的区别
StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
即StringBuilder速度快,StringBuffer线程安全
-
常用方法:
-
StringBuffer append(String s) :将指定的字符串追加到此字符序列末尾(以后拼接统一使用append)
-
StringBuffer reverse() :将此字符序列用其反转形式取代。
-
StringBuffer delete(int start, int end) :移除此序列的从start到end-1的字符,(左闭右开)。
-
StringBuffer insert(int offset, String str) :将str插入到下标为offset的位置中,原位置及其之后的字符后移(有很多重载,可以直接插入基本数据类型的字符串形式)
-
StringBuffer replace(int start, int end, String str) :使用给定 Str中的字符替换此序列的子字符串中的字符。(start包含,end不包含,左闭右开)
-
-
一般使用StringBuffer类和StringBuilder类对象进行字符串拼接、反转、插入、替代、删除等频繁改变字符串的操作,然后使用toString()方法转换成String类对象
##Scanner类
java.util.Scanner 是 Java5 的新特征,我们可以通过 Scanner 类来获取用户的输入。
下面是创建 Scanner 对象的基本语法:
Scanner s = new Scanner(System.in);
常用方法:
next() 与 nextLine() 区别:*
next():
- 1、一定要读取到有效字符后才可以结束输入。
- 2、对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
- 3、只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
- next() 不能得到带有空格的字符串。
nextLine():
- 1、以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
- 2、可以获得空白。
如果要输入 int 或 float 类型的数据,在 Scanner 类中也有支持,但是在输入之前最好先使用 hasNextXxx() 方法进行验证,再使用 nextXxx() 来读取:
public static void main(String[] args) {
Scanner scan = new Scanner(System.in); // 从键盘接收数据
int i = 0;
float f = 0.0f;
System.out.print("输入整数:");
if (scan.hasNextInt()) { // 判断输入的是否是整数
i = scan.nextInt(); // 接收整数
System.out.println("整数数据:" + i);
} else { // 输入错误的信息
System.out.println("输入的不是整数!");
}
Scanner 不仅能从输入流中读取,也能从文件中读取,除了构建 Scanner 对象的方法,其他和上文给出的完全相同
关于日期的操作
使用java.util.Date类
构造函数:Date():按照当前系统时间构造一个Date对象
Date(Long date):按照给定的时间毫秒数构造以一个Date对象(从1970年为起点)
long System.currentTimeMills():获取自1970年1月1日到系统当前时间的总毫秒数
日期格式化:利用java.text.SimpleDateFormat类对象进行日期格式化
EE 周几 EEEE 星期几
yyyy 年 MM 月
dd 日 HH 小时数(0-23)
ss 秒 SS 毫秒
Date----->String:使用SimpleDateFormat中的format方法可以将Date对象对应的时间按照对应的格式转换为字符串
String---->Date:使用SimpleDateFormat中的parse方法可以将字符串表示的时间转换为Date对象(字符串中日期的格式一定 要和SimpleDateFormat指定的格式对应!)
public static void main(String[] args) throws ParseException { //main函数
Date date = new Date();
System.out.println(date); //Wed Dec 23 15:18:00 CST 2020
SimpleDateFormat sdf = new SimpleDateFormat("EE yyyy-MM-dd HH:mm:ss SSS");
String str = sdf.format(date);
System.out.println(str); //周三 2020-12-23 15:23:17 614
String time = "2008-08-08 08:08:08 888";
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
Date dateTime = sdf2.parse(time);
System.out.println(dateTime); //Fri Aug 08 08:08:08 CST 2008
dateTime = sdf.parse(time);
System.out.println(dateTime); //java.text.ParseException
}
##异常
1.异常的基本概念
Java程序执行过程中发生了不正常的情况,这种不正常的情况叫做:异常
Java语言提供了异常的处理方式:若程序执行过程中出现了不正常的情况,Java会把该异常信息打印输出到控制台,供程序员参考,程序员可以根据异常信息,对程序进行修改,使程序更加健壮。
2.Java中异常的特点
-
异常在Java中以类的形式存在,每一个异常类都可以创建异常对象
Java程序运行发生不正常情况时,JVM会在异常发生处,new异常对象(如除0异常: new ArithmeticException("/ by zero");)并且JVM会将new出来的异常对象抛出,打印输出信息到控制台。
-
异常的继承结构
Error—>…---->… (所有的错误只要发生,立刻抛出错误并终止程序执行,错误是不能处理的)
Object—>Throwable—> …(编译时异常:在编写程序阶段必须预先处理这种异常,否则编译器会报错)
Exception—>
RuntimeException(运行时异常:在编写程序阶段可以不处理这种异常,编译器不会报错)—>…—>…
-
编译时异常和运行时异常
-
相同点:都是发生在运行阶段,编译阶段异常不会发生,因为只有在程序运行阶段才可以new异常对象。
(编译时异常是因为这种异常必须在编写阶段预处理,否则编译器会报错,因此得名)
-
不同点:编译时异常(CheckedException)发生概率比较高(出门淋雨),必须预处理,否则编译报错
运行时异常(UnCheckedException)发生概率比较低(出门被鸟屎砸到),可以不预处理,编译器不管
-
###Java中异常的处理机制
- 处理异常的方法(类似递归调用)
-
在方法声明位置上,使用throws关键字,抛给上一级方法(上一级方法处理异常也使用这两个方法)
public void fun() throws ClassNotFoundException
throws关键字后加 可能发生的异常的类名或者其父类名,也可以有多个异常,中间用逗号隔开
-
使用try…catch语句进行异常的捕捉处理(类似递归出口)
public void fun() { try{ ... //可能抛出异常的代码语句块 }catch(ClassNotFoundException e){ //catch(异常类名 异常变量名) e.printStackTrace(); //捕捉异常后的处理代码语句块 } }
注意:什么代码不会执行
-
异常发生之后,如果一直上抛,最终抛给main方法,若main方法继续向上抛给JVM,最终程序终止。
-
若方法块当中有可能发生的异常没有写入try语句块,异常发生位置之后的代码(在方法中)不再执行,而执行上抛
-
若try语句块当中有异常捕捉了,异常后面的代码(在try语句块中)不再执行,直接执行catch语句块中的代码。
-
try…catch的深入使用
public void fun() { try { //创建输入流可能导致FileNotFoundException FileInputStream fis = new FileInputStream("test.txt"); //读文件可能导致IOException byte b = fis.read(); }catch(FileNotFoundException e) { ... }catch(IOException e) { ... } }
- catch后面的小括号中的类型可以是捕获的具体异常,也可以是该异常的父类
- catch可以写多个,建议精确的处理每一个异常,而不是写父类
- catch写多个时,从上到下,必须遵守异常范围从小到大
- JDK8支持catch(IOException | FileNotFoundException e) 用 | 符号连接多种异常
###异常的常用方法
- getMessage( )—>返回异常的简单信息字符串
- printStackTrace( )—>打印异常的追踪信息(较为常用)
finally关键字
-
首先finally子句必须和try…catch一起使用,不能单独编写
-
finally子句中的代码是最后执行的,且一定会执行的,即使try语块中出现了异常,即使try语块中有return关键字(这时return最后执行) ,除非try语块中有System.exit(-1)(退出JVM)
-
finally使用场景?
通常在finally语块中完成资源的释放/关闭。因为finally语块中的代码一定会执行。
例如文件操作中,文件的close()操作应该放在finally语句块中,而不是try语块中,因为文件的许多操作都可能抛出异常,close操作可能因为异常抛出不会执行,因此应把close()操作放在finally语块中,注意finally语块中也可以使用try…catch来捕获close()抛出的异常。
自定义异常类(实际开发中很重要)
第一步:编写一个类继承Exception或者RunTimeException(区分是运行时异常还是编译时异常)
第二步:编写两个构造方法,一个无参构造方法,一个含一个String参数的构造方法
在实际开发中,一般会自定义异常类,然后在方法中遇到不正常情况时(如空栈时退栈)使用throw关键字手动抛出异常对象(Java类库源码就是这么做的)
class FuShuException extends Exception //自定义FuShuException为编译异常
{
private int value;
FuShuException()
{
super();
}
FuShuException(String msg) //无参构造方法
{
super(msg);
}
FuShuException(String msg,int value) //含String参数的构造方法
{
super(msg);
this.value = value;
}
public int getValue()
{
return value;
}
}
class Demo
{
//方法中有异常一定要处理,要么throws要么try...catch,但自抛一般throws给上一级方法
int div(int a,int b) throws FuShuException
{
if(b<0) {
// 手动通过throw关键字抛出一个自定义异常对象。
throw new FuShuException("出现了除数是负数的情况------ / by fushu",b);
}
return a/b;
}
}
学习完自定义异常要有写异常的思想,即当编写程序中,考虑错误情况时不要再return -1,要学会抛出异常,并输出错误信息
IO流
基本知识
-
什么是IO
I:input O:output
通过IO可以完成硬盘文件的读和写。
-
IO流的分类?
-
按照流的方向进行分类:
input/output以内存为参照物、read/write以硬盘为参照物
输入流:数据从硬盘往内存中去,叫做输入(input),或者叫做读(read)
输出流:数据从内存中出来到硬盘中,叫做输出(output),或者叫做写(write)
-
按照读取数据方式的不同进行分类
字节流:一次读取一个字节byte,这是万能流,什么类型的文件都可以读取,包括:文本、图片、音频视频
字符流:一次读取一个字符,这种是为了方便读取普通文本文件而存在的,只能读取纯文本文件,word文件也不能读取
-
-
Java中所有的流都在 java.io.*中
-
Java中IO流有四大家族(抽象类):
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
注意:在Java中以“Stream”结尾的都是字节流,以“Reader/Writer”结尾的都是字符流
- 所有的流都实现了java.io.Closeable接口,都有close()方法。流是内存和硬盘之间的管道,用完之后一定要关闭,否则会耗费很多资源
- 所有的输出流都实现了java.io.Flushable接口,都有flush()方法。养成好习惯,在输出流最终输出之后,一定要记得flush()刷新一下。这个刷新表示将管道中剩余未输出的数据强行输出完(清空管道),如果没有flush可能会丢失数据
-
掌握16种IO流
文件专属:
java.io.FileInputStream
java.io.FileOutputStream
java.io.FileReader
java.io.FileWriter
转换流:(将字节流转换为字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter
缓冲流:
java.io.BufferInputStream
java.io.BufferOutputStream
java.io.BufferReader
java.io.BufferWriter
数据专属流:
java.io.DataInputStream
java.io.OutputStream
标准输出流:
java.io.PrintStream
java.io.PrintWirter
对象专属流
java.io.ObjectInputStream
java.io.ObjectOutputStream
从 FileInputStream初步学习输入流
-
构造方法(调用流的构造方法会抛出FileNotFoundException或IOException)
FileInputStream(String name):通过打开与实际文件的连接来创建一个流对象,该文件由文件系统中的路径名name命名。
-
close()方法
所有的流使用完之后都需要close(),最好在finally语句块中close(),且close()会抛出IOException异常,需要try…catch
-
常用方法(基本都会抛出IOException)
- int read() :从该输入流中向后读取一个字节的数据(一定会转换为int类型)并返回,若没有读取到数据返回-1
- int read(byte[] b) :从该输入流中向后读取b.length个字节的数据到bytes[]数组中,返回读取到的字节数量(并非b.length),若没有读取到数据则返回-1。如果重复使用b数组来读取,新数据覆盖旧数据,若读取到的新数据字节数量不足b.length则旧数据仍存在于b数组中
- int available() : 返回流当中剩余的没有读到的字节数量
- long skip(long n) : 跳过几个字节不读取
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStreamTest {
public static void main(String[] args)
{
//通过路径创建字节输入流对象
//文件路径:C:\Users\DaY1zz\学习\简答题.txt(编译器会自动把 \ 转换为 \\,在Java中 \ 表示转义符)
//将文件路径中 \ 改成 / 也可以
//文件路径改成相对路径也可以
FileInputStream fis = null;
try {
fis = new FileInputStream("C:\\Users\\DaY1zz\\学习\\简答题.txt");
//FileInputStream fis = new FileInputStream("C:/Users/DaY1zz/学习/简答题.txt");
int data = fis.read();
System.out.print(data);
byte[] bytes = new byte[4];
int readCount = fis.read(bytes);
//System.out.println(new String(bytes)); //利用String构造函数将byte数组转换为String并输出,但这不是一个好习惯,应该读了多少才输出多少
System.out.print(new String(bytes,0,readCount)); //利用String构造函数,读了多少个字节就输出多少个字节
//常用的读取完全文件的方式:
//1.利用循环
readCount = 0;
while((readCount = fis.read(bytes)) != -1) //注意这里已经把数据读到了bytes数组里
{
System.out.print(new String(bytes, 0, readCount));
}
//2.利用available()方法创建byte数组(注意是在文件还没有被读取过之前),但不适合大文件,byte数组不能太大
bytes = new byte[fis.available()];
System.out.print(new String(bytes));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//在finally语句中执行close语句,确保流一定关闭
if( fis != null) //关闭流的前提是:流不为空,流为null时关闭会发生NullPointerException
try {
fis.close(); //close()方法会抛出IOException,所以在finally语句块中使用try..catch语句
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
###从 FileOutputStream初步学习输入流
-
构造函数(如果目标文件不存在则会自动新建到IDE的默认路径下)
FileOutputStream(String name) :这种构造方法谨慎使用,这种方法会先将原文件清空,然后重新写入
FileOutputStream(String name, boolean append):若append为true则以追加的方式在文件末尾写入,不清空原文件内容
-
flush()函数
输出流(写文件)写完之后一定要flush(),注意会抛出IOException异常
-
常用函数(基本都会抛出IOException)
void write(byte[] b) :将byte数组中数据全部写入文件中
void write(byte[] b, int off, int len) :将byte数组中从off位置开始的len个字节的数据写入文件中
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamTest {
public static void main(String[] args)
{
FileOutputStream fos = null;
try {
//如果目标文件不存在则会自动新建到IDE的默认路径下
//这种构造方法谨慎使用,这种方法会先将原文件清空,然后重新写入
fos = new FileOutputStream("myfile");
//以追加的方式在文件末尾写入,不清空原文件内容的构造方式
fos = new FileOutputStream("myfile",true);
byte[] bytes = {97,98,99,100};
//将byte数组全部写入文件中
fos.write(bytes);
//将byte数组中部分写入文件中
fos.write(bytes, 0, 2);
//字符串形式
String s = "我是大帅比";
byte[] b = s.getBytes();
fos.write(b);
//写完之后,一定要刷新
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fos != null)
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
文件复制(读写结合应用)
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class Copy01 {
public static void main(String[] args)
{
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("C:\\Users\\DaY1zz\\Pictures\\Camera Roll\\1.mp4"); //创建流对象连接目标文件
fos = new FileOutputStream("D:\\copy1.mp4"); //指定要复制到的路径和文件名(文件不存在会自动新建)
int readCount = 0;
byte[] bytes = new byte[1024*1024]; //1Mb
while((readCount = fis.read(bytes)) != -1) //循环判断前数据已经读取到内存中的bytes数组中,readCount保存了读取的字节数
{
fos.write(bytes, 0, readCount); //关键点,利用write(byte[] b, int off, int len)方法实现读多少写多少
}
fos.flush(); //写完之后一定要flush
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos != null) { //注意这里要和fis.close()抛出的异常分开try...catch,防止fis.close真的抛出异常而不进行fos.close
try {
fis.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
}
类比学习FileReader和FileWriter
-
基本使用与FileInputStream和FileOutputStream一样,但FileReader和FileWriter只适用于普通文本文件(普通文本文件与后缀名无关,只要能用记事本打开的文件都是普通文本文件)
-
与字节流FileInputStream和FileOutputStream使用byte或byte数组不同,字符流FileReader和FileWriter使用char或char数组
注意:FileWriter中的write方法有write(String str)和write(String str, int off, int len)可以直接地写入字符串或字符串的一部分,不需要像FileOutputStream那样byte[] b = str.getBtyes()
再write(b)
###缓冲流
-
什么是缓冲流?
在内存中开辟了一个缓冲区,缓冲流先将数据读/写到这个默认大小的缓冲区中,只有在这个缓冲区装满之后,系统才将这个缓冲区中的内容进行处理,以此提高效率,因此使用缓冲流的目的是为了提高效率。
-
有哪些缓冲流?
BufferReader、BufferedWriter、BufferedInputStream、BufferedOutputStream
其中BufferedWriter、BufferedInputStream、BufferedOutputStream在使用上与FileWriter、FileInputStream和FileOutputStream没有多大区别,可以类比学习,
-
缓冲输出流BufferWriter和BufferedOutputStream的flush()函数
缓存输出流会将要输出的内容缓存在内存的缓冲区中,如果忘记调用flush()方法,则不会将内容输出到磁盘中,另外调用缓冲输出流的close()方法会自动调用flush()方法
-
构造方法
构造缓冲流需要在调用构造函数时传入相应的流对象
BufferedReader(Reader in)
BufferedWriter(Writer out)
BufferedInputStream(InputStream in)
BufferedOutputStream(OutputStream out)
//第一种方式 FileWriter fw = new FileWriter("myfile",true); BufferedWriter bw = new BufferedWriter(fw); //第二种方式 BufferedReader br = new BufferedReader(new FileReader("myfile")); //使用转换流让字节流转换为字符流再使用对应的缓冲流 BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("myfile"))); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("myfile",true)));
-
BufferedReader的新技能
String readline():读取一行文本,但不读取换行符,当读取不到数据时返回null
BufferedReader br = new BufferedReader(new FileReader("myfile")); String s = null; while((s = bfr.readLine()) != null) System.out.println(s);
File类
-
什么是File类?
文件和目录路径名的抽象表示。与IO流无关
-
构造方法
File(File parent, String child) File从父抽象路径名和子路径名创建新实例。
File(String parent, String child) File根据父路径名和子路径名创建新实例。
File(String pathname) File通过将给定的路径名来创建新实例。
流对象的构造也可以直接传File对象,从而与文件形成连接
-
常用方法
-
boolean exists():判断File对象对应的该文件或目录是否存在
-
String getAbsolutePath():返回File对象对应的文件或目录的绝对路径
-
String getPath():返回File对象对应的文件或目录的相对路径(File的toString方法调用getPath)
-
String getName():返回File对象对应的文件或目录的名称
-
String getParent():返回File对象对应的文件或目录的父路径名,没有上一级则返回null
-
File getParentFile():返回File对象对应的文件或目录的上一级的File对象,没有上一级则返回null
-
boolean isFile():判断File对象对应的是否为普通文件
-
boolean isHidden():判断File对象对应的是否为隐藏文件
-
boolean isDirectory():判断File对象对应的是否为目录
-
long lastModified():返回File对象对应的文件的最后修改时间(返回毫秒,自1970年开始)
-
String[] list():返回File对象对应的目录中所有子文件或目录的名称,如果不表示目录则返回null
-
File[] listFiles():返回File对象对应的目录中所有的子文件或目录,如果不表示目录则返回null
-
boolean createNewFile():当且仅当File对象对应的文件尚不存在时,创建一个新的空文件。
-
boolean mkdir():当且仅当File对象对应的目录尚不存在时,创建一个新的目录。
-
boolean mkdirs():如果父目录也不存在时,以多重目录的形式创建新目录
-
目录复制(综合应用)
经验教训:
- 对File的getName()和getAbsolutePath()方法要区分好!
- 区分盘根路径和普通根路径:盘根路径(D:\),普通根路径(D:\Java)一个结尾带\ 一个结尾没有带
3. 递归算法不要一直写,要学会输出测试一下!!!
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyAll {
public static void main(String[] args) {
File srcFile = new File("E:\\桌面备份");
File dstFile = new File("D:\\abc");
copyAll(srcFile,dstFile);
}
public static void copyAll(File srcFile, File dstFile) { //递归调用
if(srcFile.isFile()) //如果目标是文件则拷贝,同时递归出口
{
BufferedInputStream fis = null;
BufferedOutputStream fos = null;
try {
fis = new BufferedInputStream(new FileInputStream(srcFile));
String dstPath = (dstFile.getAbsolutePath().endsWith("\\") ? dstFile.getAbsolutePath() :dstFile.getAbsolutePath()+"\\" )+srcFile.getAbsolutePath().substring(3);
fos = new BufferedOutputStream(new FileOutputStream(dstPath));
int readCount = 0;
byte[] bytes = new byte[1024*1024];
while((readCount = fis.read(bytes)) != -1)
fos.write(bytes, 0, readCount);
fos.flush();
}catch(FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fis!=null)
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
if(fos!=null)
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return;
}
//如果目标是目录则遍历目录下所有的内容并拷贝
File[] files = srcFile.listFiles();
for(File file:files)
{
if(file.isDirectory())
{
String dstPath = (dstFile.getAbsolutePath().endsWith("\\") ? dstFile.getAbsolutePath() :dstFile.getAbsolutePath()+"\\" )+srcFile.getAbsolutePath().substring(3);
File newdir = new File(dstPath);
if(!newdir.exists())
newdir.mkdir();
}
copyAll(file,dstFile);
}
}
}
多线程
多线程概述
-
什么是进程?什么是线程?
进程是一个应用程序(一个软件就是一个进程)
线程是进程中的执行场景/执行单元
一个进程可以启动多个线程
-
对于Java程序来说,运行一个Java程序会先启动JVM,而JVM就是一个进程,JVM再启动主线程调用main方法,再启动一个垃圾回收线程负责看护、回收垃圾。所以Java程序最起码有两个线程并发
-
进程A和进程B的内存资源独立不共享。
Java程序中同一个进程下的线程A和线程B,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈,各自线程执行各自任务,称为多线程并发,可以提高程序的处理效率
-
使用多线程机制,main方法结束了仅仅意味着主线程结束,主栈空了,其他线程可能还在压栈弹栈
一个线程的生命周期
线程是一个动态执行的过程,它有一个从产生到死亡的过程。
-
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
-
就绪状态:
当线程对象调用了start()方法之后,在JVM中开辟了分支栈,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
-
运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
当占有的CPU时间片(资源)用完后,如果run()方法中仍有代码没有执行完成,则回到就绪状态,继续抢夺CPU时间片,每当再次抢到CPU资源之后会重新进入run()方法接着上一次的代码继续往下执行。
当run()方法结束则进入死亡状态
-
阻塞状态:
如果一个线程执行了sleep(睡眠)、**suspend(挂起)**等方法,放弃所占用CPU资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
-
死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
创建一个线程
1.类继承Thread类,重写run()方法
创建一个线程的第一种方法是创建一个新的类,该类继承 Thread 类,该类则为线程类,然后创建一个该类的实例,然后调用实例的start()方法启动线程
继承类必须重写 run() 方法,该方法是新线程的入口点。它必须调用 start() 方法才能执行。
注意:
-
start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,完成任务后瞬间结束。启动成功的线程JVM会自动调用该线程的run()方法,将run()方法压入分支栈的底部。
-
==run()方法是新线程的入口点,和主线程的main()方法功能类似。==在多线程中,run()和main()是平级的,run()方法在分支栈底部,main()方法在主栈底部。
-
如果在主线程中没有调用线程的start()方法而直接调用run()方法,则只是普通的单线程程序,没有开辟分支栈,run()方法压入主栈
class MyThread extends Thread{ //定义线程类
public void run(){
for(int i = 0; i < 100; i++)
System.out.println("分支线程---->"+i);
}
}
public class ThreadTest{
pubic static void main(String[] args){
MyThread t = new MyThread();
//t.run(); 单线程
t.start(); //启动一个新的线程,调用MyTread的run()方法
for(int i = 0; i < 100; i++)
System.out.println("主线程---->"+i);
}
}
2.类实现Runnable接口,实现run()方法
创建一个线程的第二种方法是创建一个新的类,实现java.lang.Runnable接口,实现run()方法。
注意实现Runnable接口的类不是一个线程类,只是一个可运行的类。
因此和第一种继承extends Thread类的方法不一样的是:需要构造一个Thread对象,构造时要将实现Runnable接口的类对象传给Thread类对象。启动线程是调用Thread对象的start()方法,开辟分支栈
class MyRunnable implements Runnable{
public void run(){
for(int i = 0; i < 100; i++)
System.out.println("分支线程---->"+i);
}
}
public class ThreadTest02{
public static void main(String[] args){
//创建一个可运行的对象
MyRunnable r = new MyRunnable();
//把可运行对象封装成线程对象
Thread t = new Thread(r);
//Thread t = new Thread(new MyRunnable);
//启动线程
t.start();
for(int i = 0; i < 100; i++)
System.out.println("主线程---->"+i);
}
}
3.两种方法的选择
一般选择第二种方法:编写实现Runnable接口的类;但第一种方法更加简单
原因:实现Runnable接口的类更加的灵活,可以继承其他的类,而且可以将Thread类的对象封装起来,编写start()方法实现启动线程开辟分支栈(注意不是重写Thread类的start()方法),表面上与继承Thread的线程类没有差别
下面的例子体现了将Thread对象封装到实现Runnable接口的类中的思想
class RunnableDemo implements Runnable {
private Thread t; //将线程对象封装
private String threadName; //线程名
RunnableDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
//实现run()方法,运行入口
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// 让线程睡眠一会
Thread.sleep(50);
}
}catch (InterruptedExceptionInter e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
//编写start()方法
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName); //创建线程对象
t.start (); //实际是调用Thread的start()方法来启动线程
}
}
}
4.多线程并发和IO流的结合应用
实现多线程复制文件
class CopyThread extends Thread{
File srcFile;
File dstFile;
public CopyThread(String srcPath, String dstPath) {
srcFile = new File(srcPath);
dstFile = new File(dstPath);
}
public void run() {
try {
System.out.println("复制"+srcFile.getName()+"开始!");
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dstFile));
int readCount = 0;
byte[] bytes = new byte[1024*1024];
while((readCount = bis.read(bytes)) != -1 ) {
bos.write(bytes);
}
bos.flush();
bis.close();
bos.close();
System.out.println("复制"+srcFile.getName()+"结束!");
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}
}
}
public class ThreadTest {
public static void main(String[] args) {
for(int i = 1; i<=3;i++) {
String src = "E:/"+i+".docx";
String dst = "E:/"+(i+3)+".docx"; //在同一文件夹下,名字要不同,否则会覆盖
CopyThread thread = new CopyThread(src,dst);
thread.start();
}
}
}
Thread类
每个线程都有一个名称供识别。
一个以上的线程可能具有相同的名称。如果在创建线程时未指定名称,则会为其生成一个新名称。
默认的名称是“Thread-x”,x为0、1、2…
Thread类的常用方法
-
String getName():获取线程的名称
-
void setName():设置线程的名称
-
static Thread currentThread():返回当前线程对象
-
static void sleep(long millis):让当前线程进入休眠,进入“阻塞状态”,放弃占有的CPU资源。可能抛出InterruptedException(编译异常)。利用sleep()方法可以间隔特定的时间,去执行一段特定的代码
-
static void yield():当前线程让位给主线程,当前线程进入“就绪状态”。
-
void interrupt():终止线程的休眠(注意不是终止线程的执行),依靠了Java的异常机制,如果线程休眠过程被中断, sleep()方法抛出InterruptedException异常
-
终止线程的执行一般会导致数据丢失,但可以设置终止标记,当终止标记为真时先进行保存操作然后再结束run()方法
-
void join():将调用join方法的线程t合并到当前线程中,让当前线程受阻塞,线程t执行直到结束后,当前线程进入就绪状态。和sleep()方法一样也可能抛出InterruptedException异常,也会被interrupt()方法中断
线程安全(重点)
在多线程并发环境下,若存在共享数据(在堆或方法区中),且有修改共享数据的行为时会存在线程安全问题(线程同时修改共享数据,而更新不及时出现问题)
此时要让线程不再并发执行,而是排队执行,这种机制称为线程同步机制
异步编程模型:多线程并发
**同步编程模型:**线程之间存在等待关系,效率较低,线程排队执行
synchronized关键字
1.synchronized代码块(最灵活最常用)
synchronized(obj){
//线程同步代码块
}
在Java语言中,任何一个对象都有一把“锁”标记
假设t1和t2线程并发执行,假设线程t1先执行遇到了synchronzied代码块,线程t1寻找对象锁,然后直接占有了( )中共享对象obj的锁,直到同步代码块执行结束,线程t1才释放这把锁;
假设线程t1在占有共享对象obj的锁时,线程t2也遇到了同样的synchronized代码块,则线程t2寻找对象锁,但却必须等待线程t1执行同步代码块结束后才能占有共享对象obj的锁,执行同步代码块中的内容。
因为对象锁的存在而实现了线程排队执行,线程在寻找对象锁时会释放之前占有的CPU资源,占有对象锁后进入就绪状态
因此synchronized后面的共享对象obj一定要选择正确,这个共享对象一定是你需要排队执行的这些线程对象所共享的(在堆或方法区中),注意这个共享对象只是一个工具,并不是意味着同步代码块中的代码只能对共享对象起作用,但一般同步代码块中都是对线程共享的数据进行操作
2.synchronized修饰实例方法
用synchronized关键字修饰函数
public class Account{
private double balance;
public synchronized void withdraw(double money){
//线程同步函数体
}
}
synchronized修饰实例方法,共享对象一定是this,保证了实例变量的安全,因此这种方式不灵活
另外一个缺点:synchronized函数表示整个方法体都需要同步执行,可能无故同步范围,导致程序的执行效率降低,所以这种方式不常用。
3.synchronized修饰静态方法
此时线程占有的是类锁,所有该类的对象共同使用这一把类锁,保证了静态变量的安全
synchronized最好不要嵌套使用,一不小心可能出现死锁问题!!!
多个售票窗口(多线程)同步出售100张票的案例:
class TicketStore{ //共享数据
private int tickets = 100;
public int getTickets() {
return tickets;
}
public boolean sell() {
synchronized(this){
if(tickets > 0) {
try {
Thread.sleep(10); //模拟延迟
}catch(InterruptedException e) {
e.printStackTrace();
}
tickets--;
return true;
}
else {
System.out.println("票已售光!");
return false;
}
}
}
}
class TicketWindow extends Thread{ //操作共享数据的线程
private TicketStore store;
public TicketWindow(TicketStore store) {
this.store = store;
}
public void run() {
while(store.sell())
{
System.out.println(Thread.currentThread().getName()+"号窗口售出了第"+store.getTickets()+"张票");
}
}
}
public class ThreadTest {
public static void main(String[] args) {
TicketStore store = new TicketStore();
TicketWindow thread1 = new TicketWindow(store);
thread1.setName("1");
TicketWindow thread2 = new TicketWindow(store);
thread2.setName("2");
TicketWindow thread3 = new TicketWindow(store);
thread3.setName("3");
//三个不同的TicketWindow线程共享一个TicketStore对象
thread1.start();
thread2.start();
thread3.start();
}
}
###守护线程
Java中线程分为两类:
- 用户线程
- 守护线程(后台线程):如垃圾回收线程
守护线程的特点:
一般守护线程是一个死循环,在程序运行过程中一直运行,不会主动终止,直到所有的用户线程(非守护线程)结束,守护线程自动结束(JVM直接结束整个进程)
实现守护线程:
实现一个线程类,其run()方法是一个死循环,该线程类的实例在调用start()方法前,先调用setDaemon(true)方法标注该线程对象是守护线程即可
class WatchingThread extends Thread{
public void run(){
while(true){
System.out.println("Watching...");
try{
Thread.sleep(5000); //每隔5秒输出一次Watch...
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
public class Demo{
public static void main(String[] args){
WatchingThread wt = new WatchingThread();
wt.setDaemon(true);
wt.start();
try{
Thread.sleep(10000); //10秒后主线程结束,同时守护线程结束
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
生存者和消费者模式
关于Object类的wait()和notify()
-
wait()和notify()是Object类的方法,意味着Java中每个对象都可以调用
-
wait()方法和notify的作用?
obj.wait();
表示让正在使用obj对象的线程进入等待状态并且释放线程之前占有的obj对象的锁,无期限等待,直到obj.notifyAll();
让obj对象上处于等待的所有线程唤醒为止(obj.notify()
是唤醒一个等待中的线程)
####生产者和消费者模式的示例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0IiznVfX-1634267070870)(C:\Users\DaY1zz\AppData\Roaming\Typora\typora-user-images\image-20201225204348759.png)]
集合框架
集合概述
-
什么是集合?为什么在开发中频繁使用集合?
集合是一个容器,可以容纳其他类型的数据,不同的集合底层对应不同的数据结构
集合可以依次容纳多个对象,在实际开发中需要存储多条信息或记录,这时需要根据实际需要选择合适的集合容器(数据结构)来进行开发。
-
集合不能直接存储基本数据类型,另外集合不是直接存储Java对象,集合中存储的是Java的对象引用(地址)。list.add(100); //自动装箱Integer
-
集合使用了泛型,没有声明指定类型之前,集合中可以存储Object的所有子类型引用,声明指定了类型之后,集合中只能存储指定类型的引用。
-
所有的集合类和集合接口都在java.util包下
-
在Java的util包中有两个所有集合的父接口Collection和Map
实现Collection的类,每个位置只有一个元素。
实现Map的类,持有 key-value pair(键值对的方式存储元素),像个小型数据库。
Collection的实现结构框架
Collection接口:Collection 是最基本的集合接口,继承了Iterable接口(可迭代的),一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。
**List接口:**继承了Collection接口,特点是元素有序(插入顺序)可重复,储存的元素有索引(从0开始递增),可实现随机存取
**Set接口:**继承了Collection接口,特点是元素无序不可重复,储存元素没有索引
Queue接口:继承了Collection接口,代表队列
**SortedSet接口:**继承了Set接口,特点是元素放入集合后会自动按大小排序
####List接口的常用实现类
- ArrayList类:实现了可变大小的数组,随机访问和遍历元素时,提供更好的性能。底层采用了数组数据结构。非线程安全
- Vector类:底层采用了数组数据结构,线程安全但效率较低,使用较少了
- LinkedList类:底层采用了带头尾指针不含头结点的双向链表数据结构,允许有null(空)元素。非线程安全
Set接口的常用实现类
-
HashSet类:底层使用了HashMap集合的key部分存储元素,实际是一个哈希表数据结构,允许包含值为null的元素,但最多只能一个。非线程安全
-
TreeSet类:采用了二叉树数据结构,实现了SortedSet接口,底层使用了TreeMap集合的key部分存储元素。
Map的实现结构框架
**Map接口:**Map是最基本的集合接口,以key和value键值对的方式存储元素。key和value都是存储Java对象的内存地址,所有Map集合的key的特点是无序不可重复的
**SortedMap接口:**继承了Map接口,SortedMap的key部分的元素会自动按大小排序,称为可排序的Map集合
Map接口的常用实现类
- HashMap:实现了Map接口,底层是哈希表数据结构,非线程安全
- Hash Table:实现了Map接口,底层也是哈希表数据结构,线程安全,所有方法都带有synchronized关键字,效率较低使用较少
- TreeMap:实现了SortedMap接口,底层是二叉树数据结构
Collection接口的常用方法
- boolean add(Object obj):加入新元素,返回是否添加成功
- boolean addAll(Collection c):将集合c中的所有元素添加到此集合末尾(两集合合并)
- int size():返回集合大小(所含元素的个数)
- boolean isEmpty():判断集合是否为空
- void clear():清空集合
- boolean contains(Object obj):判断当前集合中是否包含元素obj,注意是比较obj内容而不是obj的所指地址,因为底层调用了equals()方法
- boolean remove(Object obj):如果存在,则从当前集合中删除元素obj(底层同样调用equals()方法),返回true,否则返回false
- Object[] toArray():将集合转换为一个数组
Collection集合的迭代遍历
Collection接口继承了Iterable接口,因此可以通过迭代器iterator遍历集合,注意在Map集合中不能用迭代器遍历
迭代器常用方法:
-
boolean hasNext():判断是否有下一个元素可以迭代
-
next():返回迭代器所指的下一个元素,可能抛出NoSuchElementException异常(没有下一个元素可以迭代)
-
void remove():从迭代器对应的集合中移除迭代器所指元素(即最后一次调用next()方法后所指的元素)
注意:如果在迭代进行过程中以其他方式(而不是通过调用迭代器的remove()方法)修改对应集合,必须重新获取迭代器,从头开始
public class CollectionTest {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("abc");
c.add("def");
c.add(100);
c.add(new Object());
Iterator it = c.iterator();
while(it.hasNext()) {
Object obj = it.next();
System.out.println(obj);
}
/*abc
def
100
java.lang.Object@6e5e91e4
*/
it.remove(); //移除元素
Iterator it2 = c.iterator(); //集合结构发生改变,必须重新获取迭代器,从头开始
while(it1.hasNext()) {
Object obj = it1.next();
System.out.println(obj);
}
/*
abc
def
100
*/
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-10mA3nsu-1634267070871)(C:\Users\DaY1zz\AppData\Roaming\Typora\typora-user-images\image-20201226145702087.png)]
###List接口的常用方法
回顾List集合的特点:元素有序且可重复,有索引下标
- void add(int index, Object elem):在指定位置index插入元素,并后移之后的元素
- Object get(int index):根据下标获取集合中的元素
- int indexOf(Object obj):获取obj在集合中第一次出现的索引
- Object remove(int index):删除并返回集合中指定位置index的元素,并前移之后的元素
- Object set(int index, Object elem):将集合中index处的元素替换成elem对象
**总结:**List特有的方法主要是根据索引对集合进行操作
ArrayList类
-
底层是一个Object[]数组,因此随机检索效率高,但随机增删元素效率较低,但向末尾添加删除元素效率高,且不能存储巨大量数据
-
默认初始化容量为10(先创建一个长度为0的空数组,当添加第一个元素的时候,初始化容量10)
-
ArrayList是非线程安全的
-
构造方法:
new ArrayList()
new ArrayList(int capacity):指定初始化容量
new ArrayList(Collection c):将某一Collection集合转换为ArrayList集合
-
Array List的扩容:扩大至原容量的1.5倍
-
优化性能:在使用ArrayList集合时预估计元素个数,给定一个初始化容量,尽可能少的扩容
###LinkedList类
-
底层是一个带头尾指针不含头结点的双向链表数据结构,但LinkedList集合的元素也有索引下标,但随机检索元素效率较低,但随机增删元素效率高
-
没有默认初始化容量,初始时成员变量头尾指针first和last都为null;
-
LinkedList 实现了 Queue 接口,可作为队列使用。
LinkedList 实现了 List 接口,可进行线性表的相关操作。
LinkedList 实现了 Deque 接口,可作为双端队列使用
-
由于LinkedList类实现了很多集合接口,所以很多方法名字不同功能相似
HashMap类
Map集合中所有的key是不能重复的,而value可以重复。调用keySet()方法可以返回由所有的key组成的Set集合,调用values()方法可以返回所有的value组成的Collection集合。
判断key中两个元素是否相等的标准是通过equals()方法比较相等且hashCode()方法返回值相等,因此以自定义类型作为key的类型时,最好重写equals()和hashCode()两个方法,Object类的不够用。
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
HashMap 是无序的,即不会记录插入的顺序。
HashMap 继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。
HashMap 的 key 与 value 类型可以相同也可以不同。
Collections工具类
Java提供了操作集合的工具类java.util.Collections,类中提供的方法都是静态方法,可以直接对集合进行操作
注解(Annotation)
注解就是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。可以在不改变原有逻辑的基础上,在源文件中嵌入一些补充信息。
使用注解时要在其前面增加@符号,并把该注解当成修饰符使用,用于修饰支持的程序元素
框架 = 注解 + 反射 + 设计模式
生成文档相关的注解
@author 标明开发该模块的作者
@version 标明该模块的版本
@see 相关主题
@since 该模块从哪个版本开始增加的
用于方法:
@param 形参名 形参类型 形参说明 对方法中某参数的说明,如果没有参数就不能写
@return 返回值类型 返回值说明 对方法返回值的说明,如果方法的返回值类型时void就不能写
@exception 异常类型 异常说明 对方法可能抛出的异常进行说明,如果方法没有throw显式的抛出异常就不能写
JDK内置的三个基本注解
@Override 限定重写父类方法,只能用于方法
@Deprecated 用于表示所修饰的元素(类、方法等)已过时
@SuppressWarnings 抑制编译器警告
代替配置文件功能的注解
不同框架有不同的自定义注解
自定义注解
可参考@SuppressWarnings
@Target ({TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE
@Rentation(RentationPolicy.Runtime)
public @interface MyAnnotation{
String[] value();
}
元注解
对现有注解进行解释说明的注解
-
Retention:指定所指定的 Annotation 的生命周期,有三种状态(枚举类型)SOURCE、CLASS、RUNTIME
SOURCE:在编译后该注解便不存在
CLASS:默认行为,编译后该注解仍存在,但不会在运行时在内存中存在
RUNTIME:运行时该注解也存在,只有声明为RUNTIME的注解才能通过反射获取
-
Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素(TYPE、FIELD、METHOD、PARAMETER等)
------以下两个元注解使用较少-------------
-
Documented:用于指定被修饰的 Annotation类将被javadoc提取成文档,默认情况下,javadoc不包括注解
定义为Documented的注解必须设置Retention值为RUNTIME
##单元测试Junit
-
@Test:
测试方法,每个方法都应该有一个对应的测试方法进行白盒测试
-
@Before:
某一个方法中,加入了@Before注解以后,那么这个方法中的代码会在测试方法执行之前执行
一般加入:初始化代码,申请资源(数据库、IO、网络)的代码
-
@After:
某一个方法中,加入了@After注解以后,那么这个方法中的代码会在测试方法执行之后执行
一般加入:释放资源的代码
-
断言
一般使用Assert.assertEquals方法对测试结果进行判断,还需要使用Assert对可能抛出的异常进行预测
Java8 新特性
###Lambda表达式
Lambda表达式是一种匿名函数,可以表达一些简单的函数,主要作用就是代替匿名内部类的繁琐语法,注意和匿名内部类相互比较学习
形式:(parameters)-> {expressions}
- **可选类型声明:**不需要声明参数类型,编译器可以统一识别参数值。
- **可选的参数圆括号:**一个参数无需定义圆括号,但多个参数需要定义圆括号。
- **可选的大括号:**如果主体只包含了一个语句,就不需要使用大括号。
- **可选的返回关键字:**如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回两者中的比较情况
(x, y) -> Integer.compare(x,y)
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
访问限制规则:
- 只能引用标记了 final 的外层局部变量,这就是说 lambda 内部不能修改定义在域外的局部变量,否则会编译错误。
- 局部变量可以不用声明为 final,但是必须该变量不可被lambda表达式后面的代码修改(即隐性的具有 final 的语义)
- lambda表达式不允许声明一个与局部变量同名的参数或者局部变量
函数式接口
只包含一个抽象方法的接口,称为函数式接口
在一个接口上使用 @FunctionalInterface 注解,来检查它是否是一个函数式接口,同时生成javadoc时也会声明这是一个函数式接口。
Lambada表达式本质就是一个函数式接口的实例
➢ Lambda表达式的目标类型必须是明确的函数式接口。
➢ Lambda表达式只能为函数式接口创建对象。Lambda表达式只能实现一个方法,因此它只能为只有一个抽象方法的接口(函数式接口)创建对象。
Java内置的4大核心函数式接口:
- XxxFunction<T,R> 方法:R apply(T t) 该函数式接口通常用于对指定数据进行转换处理。
- XxxComsumer 方法: void accept(T t) 也负责对参数进行处理,只是不会返回处理结果。
- XxxSupplier 方法:T get() 按 某 种 逻 辑 算 法 (getAsXxx ()方法的逻辑算法由Lambda表达式来实现)返回一个数据。
- XxxPredicate 方法: boolean test(T t) 通常用于判断参数是否满足特定条件,经常用于进行筛滤数据。
- Runnable 方法:void run()
- 还有其他扩展的函数式接口
Lambada表达式可以是以上函数式接口的实例,而不需要使用传统的new匿名内部类实现接口类的实例
方法引用
方法引用和构造器引用可以让Lambda表达式的代码块更加简洁。 方法引用和构造器引用都需要使用两个英文冒号。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mj5KY4Pe-1634267070873)(C:\Users\DaY1zz\AppData\Roaming\Typora\typora-user-images\image-20211007122809218.png)]
注意引用某类对象的实例方法的情况:第一个参数作为调用者,后面的参数才是方法参数
###Lambda表达式和匿名内部类的区别
➢ 匿名内部类可以为任意接口创建实例——不管接口包含多少个抽象方法,只要匿名内部类实现所有的抽象方法即可;但Lambda 表达式只能为函数式接口(只有一个抽象方法)创建实例。
➢ 匿名内部类可以为抽象类甚至普通类创建实例;但Lambda表达 式只能为函数式接口创建实例。
➢ 匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认方法;但Lambda表达式的代码块不允许调用接口中定义的默 认方法。
反射机制
Java的反射机制可以操作字节码文件,是在程序运行时动态加载类并获取类的内部详细信息,从而操作类或对象的属性和方法,甚至是private修饰的。
**疑问:**反射机制与封装性是不是矛盾的,如何看待两个技术
面向对象的封装性是代码设计者对类行为和属性的使用限制,其实是一种使用建议,调用者即使不使用私有属性和方法也可以实现目的功能,反射机制只是提供了一种可以动态操作对象的途径
###与反射机制相关的类
在java.lang.reflect.*包下是关于反射机制的相关类
java.lang.Class; 代表字节码文件,代表Java的一个类型,代表整个类
java.lang.reflect.Method; 代表字节码文件中的方法字节码部分,代表类中的方法
java.lang.reflect.Constructor; 代表字节码文件中构造方法字节码部分,代表类中的构造方法
java.lang.reflect.Field; 代表字节码文件中属性字节码部分,代表类中的成员变量、静态变量
**Class类的理解:**使用java.exe命令对某个字节码文件进行解释运行,将字节码文件加载到内存中,此过程称为类的加载,加载到内存中的类,称为运行时类,Class的一个实例就对应着一个运行时类。
哪些类型有Class对象:
- class:外部类、成员类、局部内部类、匿名内部类
- interface 接口
- 数组
- enum 枚举
- annotation 注解
- 基本数据类型
- void
获取Class对象的三种方法
- Class.forName(String classPath) 最常用最体现动态性
- Class类的静态方法
- 会抛出ClassNotFoundException编译时异常
- 方法参数是字符串,为一个完整的带有包名的类名,即使是java.lang包也不能省略
try{
Class c1 = Clsss.forName("java.lang.String");
Class c2 = Class.forName("java.util.Date");
}catch(ClassNotFoundException e){
e.printStackTrace();
}
-
调用运行时类的属性 .class
Class c1 = Person.class;
-
调用运行时类的实例的方法getClass()
Person p1 = new Person();
Class c2 = p1.getClass();
加载配置文件
class ClassLoaderTest{
public static void main(String[] args){
Properties pros = new Properties();
//方法1:使用io流,默认的相对路径为当前module下
FileInputStream fis = new FileInputStream("xxx.properties");
pros.load(fis);
//方法2:使用ClassLoader,默认相对路径为当前module的src下
ClassLoader classloader = ClassLoaderTest.class.getClassLoader();
InputStram is = classloader.getResourceAsStream("xxx.properties");
pros.load(is);
}
}
创建运行时类的对象
class Person{
private int age;
private String name;
public Person(){
}
}
class NewInstanceTest{
public void test(){
Class clazz = Person.class;
Person obj = clazz.newInstance();
System.out.println(obj);
}
}
调用**Class的非静态方法newInstance()😗*创建对应的运行时类的对象,内部调用的是运行时类的空参构造器。会抛出 InstantiationException, IllegalAccessException
要求:该运行时类必须提供空参的构造器,且访问权限足够,否则抛出上述异常。因此写类时一般要写一个public的空参构造器,既保证能够方便地通过反射创建对象,也能保证子类能够默认调用super()
获取运行时类的属性结构和内部结构
public class Test {
@Test
public void filedTest() throws ClassNotFoundException {
Class clazz1 = ArrayList.class;
//获取当前运行时类及其父类中声明为public的属性
Filed[] fields = clazz1.getField();
//获取当前运行时类中声明的所有属性(不包括父类、接口的属性)
Field[] declaredFields = clazz1.getDeclaredFields();
for(Field f: declaredFields){
System.out.println(f);
//1.权限修饰符
int modifiers = f.getModifiers();
//Modifer是java.lang.reflection包下的类,代表字节码文件中的修饰符部分,调用toString方法即可显示修饰符类型
System.out.println(Modifier.toString(modifiers));
//2.数据类型
Class type = f.getType();
System.out.println(type);
//3.变量名
String name = f.getName();
System.out.println(name);
System.out.println();
}
}
@Test
public void methodTest(){
Class clazz = List.class;
//获取当前运行时类及其所有父类和接口声明为public的方法
Method[] methods = clazz.getMethods();
for (Method m : methods) {
System.out.println(m);
}
//获取当前运行时类中声明的所有方法,不包含父类、接口中声明的方法
Method[] declaredMethods = clazz.getDeclaredMethods();
/*
@Xxxx
权限修饰符 返回值类型 方法名 (参数类型1 形参名1, ...) throws XxxxException
*/
for (Method m : declaredMethods) {
//输出方法,但不含注解、异常
System.out.println(m);
//1.获取方法的注解
Annotation[] annotations = m.getAnnotations(); //一个方法的注解可能有多个
for (Annotation a : annotations) {
System.out.println(a);
}
//2.获取权限修饰符
System.out.print(Modifier.toString(m.getModifiers()) + "\t");
//3.获取返回值类型
System.out.print(m.getReturnType().getName() + "\t");
//4.获取方法名
System.out.print(m.getName() + "\t");
System.out.print("(");
//5.获取形参列表
Class[] parameterTypes = m.getParameterTypes();
if (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(")\t");
//6.获取抛出的异常
Class[] exceptionTypes = m.getExceptionTypes();
if (exceptionTypes.length > 0) {
System.out.println("throws");
for (int i = 0; i < exceptionTypes.length; i++) {
if (i == exceptionTypes.length - 1) {
System.out.println(exceptionTypes[i].getName());
break;
}
System.out.println(exceptionTypes[i].getName() + ", ");
}
}
System.out.println();
}
}
}
总结:
Class实例指向当前运行时类,可以获取该运行时类的全部结构,通常方法为getXxxx(),获取运行时类的接口和父类的实际泛型和获取注解在实际应用中使用较多。
调用运行时类的指定结构
####1.调用运行时类的指定属性
public void test() throws Exception {
Class clazz = ArrayList.class;
ArrayList arrayList = (ArrayList) clazz.newInstance();
//ArrayList中的属性都是私有的
//1.获取当前运行时类指定名名称的属性
Field size = clazz.getDeclaredField("size");
//2.设置保证该属性是可访问的,十分重要!
size.setAccessible(true);
//3.set()设置指定对象的此属性的值,第一个参数是要操作的对象,第二个参数是要设置的值
size.set(arrayList,10);
//4.获取指定对象的此属性的值
System.out.println(size.get(arrayList));
}
####2. 调用运行时类的指定方法
public void test() throws Exception {
Class clazz = ArrayList.class;
//1.创建运行时类的对象
ArrayList arrayList = (ArrayList) clazz.newInstance();
//2.获取指定的某个方法
//getDeclaredMethod():参数1:指明获取的方法名称 参数2:指明获取的方法的形参列表
Method newCapacity = clazz.getDeclaredMethod("newCapacity", int.class);
//3.保证当前方法时可访问的
newCapacity.setAccessible(true);
//4.调用方法
//invoke():参数1:方法的调用者 方法2:给方法形参赋值的实参
//invoke()的返回值即为对应的调用方法的返回值
System.out.println(newCapacity.invoke(arrayList,30));
}
如果调用静态方法,invoke()的参数1即为运行时类,在本例中为ArrayList.class,调用静态属性同理。
获取构造方法要使用getDeclaredConstructor():参数:指明构造器的形参列表
调用构造方法要使用Constructor类的newInstance():参数即为给构造器形参赋值的实参
动态代理
首先了解代理模式:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
interface ClothFactory{
void produceCloth();
}
class NikeClothFactory implements ClothFactory{
public void produceCloth(){
//实现代码
}
}
//代理类
class ProxyClothFactory implements ClothFactory{
private ClothFactory clothFactory;
public ProxyClothFactory(ClothFactory clothFactory){
this.clothFactory = clothFactory;
}
public void produceCloth(){
System.out.println("预处理。");
clothFactory.producCloth();
System.out.println("后续处理。");
}
}
public class Test{
public static void main(String[] args){
NikeClothFactory nike = new NikeClothFactory();
ProxyClothFactory proxy = new ProxyClothFactory(nike);
proxy.produceCloth();
}
}
什么是动态代理?
上例中我们需要手动写好特定代理类ProxyClothFactory,并且提前编译生成代理对象,完成一系列代理工作
在动态代理中可以使用通用的代理类来调用其他对象的方法,并且是在程序运行时动态的创建目标类的代理对象。
实现动态代理,需要解决的问题
-
如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象?
-
当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法?
泛型
Java 泛型是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
泛型方法
泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
public static < E > void printArray( E[] inputArray )
{
// 输出数组元素
for ( E element : inputArray ){
System.out.print( element+"," );
}
System.out.println();
}
-
所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在上面例子中的)。
-
每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
-
类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
-
泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)
泛型类
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。
和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
}
}
类型通配符
类型通配符一般是使用?代替具体的类型参数。例如 List<?> 在逻辑上是List,List 等所有List<具体类型实参>的父类。可以实现类似多态的应用(父类变量接收子类引用)
有界的类型参数
有时候,会想限制那些被允许传递到一个类型参数的类型种类范围。例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例。这就是有界类型参数的目的。
要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界。
<T extends Number>
:即只能接受Number或Number的子类
<T super Number>
:只能接受Number及其三层父类类型,如 Object 类型的实例。
JDBC
###基本知识
JDBC ( Java DataBase Connection )是 实现Java 数据库连接的 API
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t9T49Zqf-1634267070875)(C:\Users\DaY1zz\AppData\Roaming\Typora\typora-user-images\image-20201130205047211.png)]
简单地说,JDBC 能完成 3 件事:
-
与一个数据库建立连接。
-
向数据库发送SQL 语句。
-
处理数据库返回的结果。
程序员、数据库、JDBC之间的关系:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Chh2eRv-1634267070876)(C:\Users\DaY1zz\AppData\Roaming\Typora\typora-user-images\image-20201130210837361.png)]
JDBC编程基本六步
- 加载驱动(告诉Java程序,即将连接哪种数据库)
- 与数据库获取连接(JVM进程与数据库进程之间的通道打开)
- 获取具体的数据库操作对象
- 执行SQL语句
- 处理查询结果集
- 释放资源
UML图
###六种关系
-
空心三角形+实线:继承关系(泛化)
-
空心三角形+虚线:实现接口
-
空心菱形+实线箭头:聚合关系(成员对象是整体对象的一部分,但是成员对象可以脱离整体对象独立存在。)
-
实心菱形+实线箭头:组合关系(一旦整体对象不存在,部分对象也将不存在,部分对象与整体对象之 间具有同生共死的关系。)
-
实线箭头:关联关系(一个事物的对象与另一个事物的对象相联系,可以单向关联、双向关联、单关联)
-
虚线箭头:依赖关系(一个事物使用另一个事物,体现为某个类的方法使用另一个类的对象作为参数)
关联,聚合,组合在语法实现上并没有显著区别,相区别他们只有通过判断关系双方之间的实际关系
访问修饰符:
一共有三种符号:
“+”:public
“-”:private
“#”:protected
类:
第一行写类名称。
第二行声明属性及变量。
第三行声明方法。
类名称以斜体形式表示时,说明此类为抽象类。
接口:
第一行除了写接口名称之外,还要在最上层标注<>。
第二行为空。
第三行为方法声明。
属性:
[访问修饰符] 属性名 : [类型名]
方法:
[访问修饰符] 方法名 : [返回值类型]
象
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
## 装饰器模式
装饰模式职责是动态的为一个对象增加新的功能。
装饰模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。
**实现细节**:
* Component抽象构件角色:真实对象和装饰对象有相同的接口。这样,客户端对象就能够以与真实对象相同的方式同装饰对象交互。
* Concrete Component具体构件角色(真实对象):例如io流中的FileInputStream、FlieOutputStream
* Decorator装饰角色:持有一个抽象构件的引用。装饰对象接受所有客户端的请求,并把这些请求转发给真实的对象。这样,就能在真实对象调用前后增加新的功能。
* Concrete Decorator具体装饰角色:负责给结构对象增加新的责任。
```java
public interface Shape { //抽象构件角色Component
void draw();
}
public class Rectangle implements Shape { //具体构件角色
public void draw() {
System.out.println("Shape: Rectangle");
}
}
public class Circle implements Shape { //具体构件角色
public void draw() {
System.out.println("Shape: Circle");
}
}
public abstract class ShapeDecorator implements Shape{ //装饰角色(通过关联关系)
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}
public void draw(){
decoratedShape.draw();
}
}
public class RedShapeDecorator extends ShapeDecorator { //具体装饰角色
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}
public class DecoratorPatternDemo { //调用
public static void main(String[] args) {
Shape circle = new Circle();
ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());
//也可以通过接口回调
//Shape redCircle = new RedShapeDecorator(new Circle());
//Shape redRectangle = new RedShapeDecorator(new Rectangle());
System.out.println("Circle with normal border");
circle.draw();
System.out.println("\nCircle of red border");
redCircle.draw();
System.out.println("\nRectangle of red border");
redRectangle.draw();
}
}
设计模式
单例模式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
1、懒汉式
**是否 Lazy 初始化:**是
**是否多线程安全:**否
**实现难度:**易
public class Singleton {
private static Singleton instance; //注意private和static修饰单例类的成员实例
private Singleton (){} //注意private修饰构造函数,外部无法使用
public static Singleton getInstance() { //注意这是一个类方法
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在这种实现方法中给getInstance()加synchronized修饰符可以实现线程安全
public static synchronized Singleton getInstance() {...}
2、饿汉式
**是否 Lazy 初始化:**否
**是否多线程安全:**是
**实现难度:**易
**描述:**这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
public class Singleton {
private static Singleton instance = new Singleton(); //在声明单例时创建单例类的对象
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
装饰器模式
装饰模式职责是动态的为一个对象增加新的功能。
装饰模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。
实现细节:
- Component抽象构件角色:真实对象和装饰对象有相同的接口。这样,客户端对象就能够以与真实对象相同的方式同装饰对象交互。
- Concrete Component具体构件角色(真实对象):例如io流中的FileInputStream、FlieOutputStream
- Decorator装饰角色:持有一个抽象构件的引用。装饰对象接受所有客户端的请求,并把这些请求转发给真实的对象。这样,就能在真实对象调用前后增加新的功能。
- Concrete Decorator具体装饰角色:负责给结构对象增加新的责任。
public interface Shape { //抽象构件角色Component
void draw();
}
public class Rectangle implements Shape { //具体构件角色
public void draw() {
System.out.println("Shape: Rectangle");
}
}
public class Circle implements Shape { //具体构件角色
public void draw() {
System.out.println("Shape: Circle");
}
}
public abstract class ShapeDecorator implements Shape{ //装饰角色(通过关联关系)
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}
public void draw(){
decoratedShape.draw();
}
}
public class RedShapeDecorator extends ShapeDecorator { //具体装饰角色
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}
public class DecoratorPatternDemo { //调用
public static void main(String[] args) {
Shape circle = new Circle();
ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());
//也可以通过接口回调
//Shape redCircle = new RedShapeDecorator(new Circle());
//Shape redRectangle = new RedShapeDecorator(new Rectangle());
System.out.println("Circle with normal border");
circle.draw();
System.out.println("\nCircle of red border");
redCircle.draw();
System.out.println("\nRectangle of red border");
redRectangle.draw();
}
}
外观模式
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。
比如去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uEJKChiq-1634267070877)(https://www.runoob.com/wp-content/uploads/2014/08/20201015-facade.svg)]