Java基础巩固
1.封装性
1.1修饰符
修饰符一共有4个,分别是private,default,protected,public,其中private的作用范围是只能在自己类中访问(很局限的),default在正常类中是不用写的,但是在interface中是需要我们进行声明为default的,它的作用范围是当前包中,当然包括自己的类中,对于protected,他的范围是从当前包中,到可以继承他的子类中都可以进行访问。对于public中我们可以在任何地方进行访问!
private中就是子类也无法访问,但是我们可以通过setter方法和gettter方法进行访问!(好像任何类访问都是如此)
注意:protected方法的这个同一个包下,只是指代我创建方法的这个类所属于的包之下,即使是有子类继承,也只能是子类可以获取到这个方法的使用权,子类的包写是无法得到的(例子:clone( );)
基本运算
如下Java语句 double x=2.0; int y=4; x/=++y; 执行后,怎么执行的 X/=Y 是X/Y的,而不是Y/X
琐碎
1.线程来说,必须调用start方法,才会以线程的方式来调用run方法,如果只是简单的调用run,那就是一个简单的run方法,不具备线程的功能;
2.执行顺序:父类静态代码块 ->子类静态代码块 ->父类非静态代码块 -> 父类构造函数 -> 子类非静态代码块 -> 子类构造函数。调用子类生成新的对象的时候!
3.对于构造器来说,父类只有有参构造,那子类在生成新的对象的时候,就必须要有一个构造器,也可以是无参数的,但是要在构造器内部,调用super方法,传入对应的参数!
4.Java的标识符,可以由数字,字母,下划线,和$ 组成,但是开头不可以是数字
public static void main(String[] args) {
int $1 = 0;
int _1 = 0;
}
5.一个类中可以存在多个静态的,非静态的代码块。他们的执行顺序,看2;所有静态的都是类的,非静态的都是对象的!在反射中一样,如果我们不newInstance,就不会调用非静态代码块,或者无参构造器!
6.将一种加载类的方法,运行时进行加载,我们可以使用Class.forname(String class); 来在执行的过程中去生成类,因为在main 方法中我们每一个new 的对象或者去调用类的方法最终都会导致在我们编译的时候就会对类进行提前加载了!
7.这样的表达式不合法
int i = 0;
i = ++(i++);
8.线程中加锁的条件在于,我们要对于变量进行读写操作。
9.修饰为final的属性必须赋值,不赋值要在构造器(代码块,静态的不行!)中赋值,且值不可更改;
什么是抽象类?
_抽象类是我们在Java面向对象当中遇到的一种特殊类,通过**abstarct**关键字进行修饰,一般abstract用来修饰方法或者是类,如果说一个类中有抽象方法,那么它必然是一个抽象类,但是如果说一个类是一个抽象类,它的方法未必就是要是抽象的,说到底**抽象类**和普通的类有啥区别呢?_
区别在主要在于 抽象类不能实例化对象,如果想获得一个抽象类型的对象,那必须借助与多态的方法,把一个子类对象伪装成抽象类,(多态不过是把金子给当成沙子用了不是 )
另外,就是抽象方法是没有方法体的写完方法名就可以结束了;
public class abstract_ {
public static void main(String[] args) {
Programmer coder1 = new Programmer("摸鱼");
coder1.job();
person.count();
}
}
abstract class person{
static int count ;
private String sex;
private String name;
public person(){
count();
}
public person(String sex, String name) {
this.sex = sex;
this.name = name;
count();
}
//这是抽象的方法,子类必须实现
public abstract void job();
protected abstract void demo1();
abstract void demo2();
//抽象类一样可以拥有静态方法
public static void count(){
count++;
System.out.println("当前人口"+count);
}
}
class Programmer extends person{
private String job;
public Programmer(String job) {
this.job = job;
}
public Programmer(String sex, String name, String job) {
super(sex, name);
this.job = job;
}
@Override//子类可以拥有比父类更低的访问权限,不可以更高
//public < protected < default < private
public void job() {
System.out.println("我的工作是"+" "+job);
}
@Override
public void demo1() {
}
@Override
public void demo2() {
}
}
总结:什么是抽象类?一种不可以自己实例化对象的类,可以通过多态来获取一个编译类型是抽象类,而运行类型是子类的对象;
抽象类可以没有抽象方法,但是只要是类被abstarct修饰了那就无法自己实例化,如果说抽象类中有abstract修饰的方法,
那么对于继承了抽象类的子类,就必须去实现父类的抽象方法。抽象类可以有自己的构造器。可以有自己的类方法,但是类方法
可以直接拿来调用;
什么是interface ?
interface 接口这一设计是非常棒的的,可以很好的减少继承带来的不必要的麻烦,接口如果说简单的理解就是规范了某些类中的某个或者某些方法的名字,我们调用时只要需要记得这一类的操作就是这个名字就好,这么说听起来和类继承又差不多,但是接口有个好处是,你需要则加,给了我们选择的余地,但是类继承会把它祖宗十八代的家产都给你,有时候就显得代码不够轻量化,换句话说,我觉得 interface就像IDEA插件,用就安装就好,不用就卸掉。废了这么多话,看看什么是接口吧
interface plugins{
int i = 0;
int num = 0 ;
default void init(){
System.out.println("我是插件");
}
void func();
}
abstract class os implements plugins{}
class system implements plugins{
@Override
public void func() {
System.out.println("安装插件");
}
}
class windows extends system{}
接口其实是一种特殊的抽象类,它所支持的方法类型在 jdk1.8 之后有2种,一种是public abstract ,另一种需要声明为default;(补充,还有静态的方法。访问权限是public)
接口中的变量类型都是public final static类型 即不可修改的常量。如果正常类继承接口,那么需要重写里面的abstract方法。如果是抽象类就不需重写啦,留给抽象类的子类去写吧;
内部类
内部类分类,内部类有很多种,其实本质都只是在类中定义的一个类,这里我准备总结3点:
1.成员内部类 2.静态内部类 3.方法内部类,看完书再写
先说什么是局部内部类,局部内部类的使用范围是在一个类中的方法,或者一个代码块中,这个类的作用范围是所在的方法,或者代码块,所以出了所在的方法都是没有姓名的存在(太痛苦了)。这个局部的内部类,可以享受一切所在的外部类的属性,方法等等
class dog{
private String name;
private static int count ;
public dog(String name) {
this.name = name;
}
void method(){
class dogson{
void method(){
System.out.println(dog.this.toString());
System.out.println(count);
}
}
dogson dogson = new dogson();
dogson.method();
}
@Override
public String toString() {
return "dog{" +
"name='" + name + '\'' +
'}';
}
}
所以这就是局部内部类,在类中的一个方法中声明一个局部类,然后我们可以在局部类中调用一切类中有的方法和属性,然后因为作用域只是当前方法,所以我们可以(也只能)通过当前方法生成一个局部类对象并调用其中的属性,方法等等。其实本质上就是一个局部变量,不要抱有太多想法。
匿名内部类
这是一个很眼熟的名词,其实这个东西在我看来并不是用在类中的,而是用来快速的创建一个我们要使用的实例化对象,用于实现接口,继承抽象类,中的抽象方法的,它的语法格式是
bell bell = new bell() {
@Override
public void ring() {
System.out.println("我是你爸爸");
}
};
interface bell{
void ring();
}
//我们可以通过这种方式快速的创建一个实现类bell接口的子类,和一个它的实例化对象;
本质就是在原有功能上进行了快速的拓展,但是只会生效一次,或者并不会持久的保留,只能在当前的方法,或者main方法中进行使用。这种方式应该是比较常见的,然后下面是一个例子
package com.zjl.innerclass;
public class work1 {
public static void main(String[] args) {
cellphone cellphone = new cellphone();
cellphone.alarmclock(new bell() {
@Override
public void ring() {
System.out.println("懒猪起床了");
}
});
cellphone.alarmclock(new bell() {
@Override
public void ring() {
System.out.println("上课了");
}
});
}
}
class cellphone{
public void alarmclock(bell bell){
bell.ring();
}
}
interface bell{
void ring();
}
上面两个就是局部类,他们充当的属性也就仅仅限制在于一个方法体中,并不能成为类的一部分,而像成员内部类,它们创建了就成了类的一个属性,也可以得到外界的访问;
成员内部类
其实这个是最好理解的一个内部类,它就是一个类类型的成员变量,可以使用修饰符,public private protected 等等,也没有什么也别要注意的,类似于HashMap中的entry,出了类本身,也是可以调用它的,使用它的方法,它的属性等等。举个例子:这个成员内部类的访问权限还是很大的,这个例子就是一个酒店有一个餐馆,我们可以跳过酒店来用餐馆做一些操作了;
public class work4 {
public static void main(String[] args) {
hotel.restruant r = new hotel().new restruant();
r.order();
}
}
class hotel{
private String manager;
private String location;
public class restruant{
private String dishes;
public void order(){
System.out.println("送到"+location+" "+dishes);
}
}
}
静态内部类
静态内部类就相当于一个静态的成员变量,属于类的成员。然后在使用的时候,要注意静态内部类是不能访问外部类的非静态成员的,这和静态方法的注意点一样,然后有意思的是,在类的内部使用这个静态类的时候,我们也需要new 类名出来,甚至每次new出来的都是不同的类。在类外使用的时候是 new 外部类.内部类 ( );这样的语法格式;这里准备用单例模式作为一个例子;
clone(原型模式)
clone 来自object的一个方法,修饰符是protected,所以子类如果想要拥有clone功能,需要重写这个方法!
clone的原理,它不再是简单的指向一个空间,而是按照原版的对象去开辟出一个新的空间,但是如果说这个对象中的成员变量有类对象,那么这个类对象是不能得到克隆的,还是会和原来的对象指向同一个空间,所以我们如果想要更改这个属性,可以改变克隆的对象的指向,让它指向其他的空间。
具体的使用方法,1.我们可以在子类中去调用object类中的clone方法,或者选择重写,但是要使用clone方法,需要注意的是要让类实现Cloneable接口,这是一个空接口,其功能只不过是声明这个类可以进行clone操作!不声明就会报异常。然后在 子类中使用super.clone();就可以实现克隆操作了,当子类中有类成员变量,那么我们如果想进行完全clone(),那么首先确保子类也实现了Cloneable,并重写了clone ,或者有类似的方法,这样才能够完成深拷贝!
ackage com.zjl.clone;
import java.util.Date;
public class emp01 implements Cloneable{
private String name;
private int age;
private Date birthday;
private phone phone;
public emp01(String name, int age, Date birthday, com.zjl.clone.phone phone) {
this.name = name;
this.age = age;
this.birthday = birthday;
this.phone = phone;
}
public com.zjl.clone.phone getPhone() {
return phone;
}
public void setPhone(com.zjl.clone.phone phone) {
this.phone = phone;
}
public emp01 cloneplus() throws CloneNotSupportedException {
return (emp01)super.clone();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "emp01{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
", phone=" + phone +
'}';
}
}
class phone{
String brand;
public phone(String brand){
this.brand=brand;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
@Override
public String toString() {
return "phone{" +
"brand='" + brand + '\'' +
'}';
}
}
解读:上面是一个具有类变量较多的emp类 ,其中的cloneplus方法,完成了对象的克隆,但是这只是一个浅拷贝!
package com.zjl.clone;
import java.util.Date;
public class clone_ {
public static void main(String[] args) throws CloneNotSupportedException {
emp01 zjl = new emp01(new String("zjl"), 24, new Date(),new phone("motuoluola"));
emp01 cloneplus = zjl.cloneplus();
System.out.println(zjl);
System.out.println(cloneplus);
System.out.println(zjl==cloneplus);
System.out.println(zjl.getPhone()==cloneplus.getPhone());
phone p = cloneplus.getPhone();
p.setBrand("huawei");
System.out.println(zjl.getPhone()==cloneplus.getPhone());//在这里进行了修改
System.out.println(zjl);
System.out.println(cloneplus);
}
}
解读:这是测试方法,首先我们创建一个emp对象,然后克隆一个emp对象,我们比较2者的地址发现是不一样的,然后我们再比较二者的类对象成员变量,发现是一样的,我们取出类对象的成员变量,对其进行重新赋值(不改变指向,改变的是指向的成员变量的空间中的值),然后发现比较结果还是相等的,输出两个对象,结果2者的成员属性均发生了改变
这大致就是clone的用法!
重写和重载
重写(@Override)
首先说一下子类和父类在权限上的一些问题
1.父类的修饰符如果是public ,那么子类重写的方法的修饰符号也必须是public 不然在某种情况下是无法使用多态的,所以说,子类的方法访问权限要高于父类的,这里准确的来说是保持兼容性,子类永远要以父类为参照,但是不可以超出父类的范围,除了封装性
2.在方法有返回值的情况下,子类的返回值可以是父类返回值的子类,总之子类不能扯上一群无关紧要的返回类型出来。
3.如果有参数,要保证参数的一致性。这里其实要注意一点,我作为子类是可以重写父类的方法,当然也可以重载父类的方法,但是重写只有一个,重载却有多个,在代码中,只有最终声明为@override的方法,才是唯一的重写,当然只有它需要保证和父类的参数名字和类型的一致性。
总结子类去重写父类的方法时,可以有以下几种特殊的情况
1.子类的权限修饰符高于父类。2.子类的返回类型可以是父类返回值的子类。3.方法名和里面的参数名以及类型是必须一模一样的,
所以说,保障重写不出错,那就父类是什么,子类就是什么,2333333
class A{
public int setage(int a){
return a;
}
}
class B extends A{
@Override
public int setage(int a) {
return a+1;
}
public String setage(String name){
return name;
}
}
方法的重写规则
- 参数列表与被重写方法的参数列表必须完全相同。
- 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
- 父类的成员方法只能被它的子类重写。
- 声明为 final 的方法不能被重写。
- 声明为 static 的方法不能被重写,但是能够被再次声明。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个类,则不能重写该类的方法。
重载
重载最常见的就是构造器重载,重载的规则上得限制是没有重写那么复杂的,重载只是针对于当前类中的方法来讲述的,我们可以是不同的权限修饰符,不同的返回类型,不同的参数类型,这些任意搭配就可以完成方法的重载,但是要注意的就是每种参数类型只能有一个,而且返回值的类型不能让父类和子类同时出现的,但是还可以通过改变形参传入的顺序,来进行重载方法!
//就是因为已经有int的参数类型了,所以说我们无法去再次重写一个int作为形参的方法!
private int setage(int name){
return 0;
}
public void setage(int age){
}
super关键字
_super就是代表父类,和this属于类似的两个东西,我们可以使用super去更加精准的调用父类中的变量(受到权限修饰符的影响),也可以调用其中的方法,以及构造器,其实在这里super主要是解决了2个东西。_
1.子类和父类的方法重名怎么办,因为在类中重写了某些方法,如果还想用父类方法中的功能,并对其进行改写,就可以用super来进行调用,这样可以实现代码的复用!(属性也是一个道理)
2.构造器,这个需要结合构造器在父类和子类之间的关系来进行决策,主要是因为我们大多时候都会把属性封装成private,所以说在子类中是无法对父类属性进行访问的,也就不能直接的使用this.属性来进行赋值了,这时就可以利用super(参数),来实现对于父类构造器的使用了,并进行赋值操作
//使用方法
super.test(1);
super.name = "zhangsan";
super("coder");//这个要在构造方法体的第一行
子类和父类的构造器的联系
_我的认知:父类没有无参构造器,那么在子类中必须去声明构造器,当然可以是无参数的,但是必须要调用super来实现,将父类构造器需要的参数传回去;(子类在new一个新的对象的时候,首先就要通过父类的构造器进行重载,当父类有无参数构造器的时候,子类就会直接调用父类的无参数构造器,当父类是有参数构造器时,子类就需要通过super来传回参数)。_
关于方法中传入参数的思考
怎么去修改传入的参数?
1.如果我们传入的是基本数据类型,很遗憾的告诉你,没法修改,传进来是什么样,返回去就是什么样子的
2.如果说是对象类型,可以修改其内部的数据,但是无法彻底的更改其本身,为什么,因为方法传递参数进来的时候,形参会和实参保持同一个堆空间地址的指向的,但是当我们进行修改方法中形参的指向时,那就等于彻底改变了形参的指向,也就和我们传进来的实参没有任何关系了,所以怎么去修改,最终的作用域也只是在方法体中,而与方法体外没有任何的关系了!
3.Java中主要有3个空间,常量池,堆空间,和栈空间,常量池装着常量 ?这个不太清楚。堆空间放着类对象,各种形形色色的对象都存储在对空间中,而栈中存放着的是方法,举个例子,当我们在运行main方法时,首先main方法会入栈,程序向下运行的时候,如果有其他方法,那就把其他方法一同入栈,当方法执行结束之后,自动弹栈,继续向下执行main方法。
这里说不太清,不过大致理解了这些运行空间之间交互的逻辑问题!
String
String可以说是这三个空间的集大成者。
首先说说String的基本使用方法
String str = "zhangsan";
String str = new String("zhangsan")
以上的这两种方式是都可以为我们创建一个”zhangsan”的字符串。但是在底层运行的时候,并不是相同的,因为如果我们用==来进行判断,那它一定是false ,这种结果的原因就是String是一个类,并不是基本的数据类型,但是它却可以当作基本数据类型来进行使用,
我们直接对其进行赋值的时候,他就会从常量池中找这个字符串,如果没有则会创建后放入常量池,但是最终的指向还是常量池中的一个对象。即使是直接赋值,但还是会创建一个对象的。二种方式的区别在于我们第一种省略了指向value的一步。
当我们采用new的方式进行创建时,我们传入的 “zhangsan”只是我们的String对象中的一个value属性,而String str的直接指向是对空间的对象空间,然后value再直接指向常量池。
所以说二者进行==比较的时候,因为指向的地址不同,自然结果就是false;
String类的常用方法
基本的equals 和 length 方法已经用过很多次了,这里介绍一些不常用的
String a = " Stringgggg ";
System.out.println(a.indexOf("tr"));
System.out.println(a.lastIndexOf("g"));
System.out.println(a.substring(0,a.length()-1));
System.out.println(a.trim());
System.out.println(a.charAt(5));
indexof 用来查询某个字符或者某段字符串在原字符串中第一次出现的位置,没有返回-1
lastIndexof 用来查询某个字符或者某段字符串在原字符串中最后出现的位置,
substring 分割字符串,遵循左闭右开原则
trim去除字符串左右两端的空格
charAt 把字符串当作一个数组,查询某个索引位置下的字符;
还有concat 连接字符串,相当于+
replace( )这个方法可以完成字符串的替换,但是并不是修改原来的字符串,方法的返回值会修改这个字符串
StringBuffer相当于一个可以进行增删改的字符串,可以append , delete ,insert ,replace 不再像原来String 一样不能数据本身进行修改了!
StringBulider和他的用法类似,都是new 一个对象然后传入字符串进行相关的操作;
Date类的常用方法
所以说这个Date类如果直接构造的话就是获取当前的时间,如果使用有参数构造,传入的值就是距离1970年1月1日+传入的毫秒数,所以说在开发的时候记录创建时间只是需要new 一个对象出来就行了。重点是字符串怎么转换成date,date怎么转换成字符串
生成一个SimpleDateFormat的对象,然后传入我们要规定的时间格式,一般就是 年月日 时分秒 对应的就是 yyyy MM dd hh mm ss
然后调用format方法传入日期就可以完成格式的转换;个人觉得掌握这么多就足够了!
public void date(){
Date date = new Date();
System.out.println(date);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
System.out.println(simpleDateFormat.format(date));
}
}
IO流
这里我认为需要掌握的就是3点!
1.字节流和字符流的使用
2.装饰器模式
3.序列化
字节流
InputStream / OutputStream 就是字节的输入和输出流,字节流主要是用于二进制文件的传输,例如图片,视频,声音等等,使用方法也很简单。先看一段代码
package com.zjl.IO;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class Stream {
public static void main(String[] args) throws FileNotFoundException {
String path = "C:\\Users\\12473\\Desktop\\计划.txt";
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = new FileOutputStream(path,true);
int a;
byte[] b = new byte[1024];
try {
String name = "zhujinlong";
fileOutputStream.write(name.getBytes(StandardCharsets.UTF_8));
fileInputStream = new FileInputStream(path);
while((a=fileInputStream.read(b,0,b.length))>-1){
//放在数组中
System.out.println(new String(b,0,a));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
解读:这里我们定义了一个txt的文本文件,还有2个字节的输入和输出流,首先我们要知道,在定义输入流时,传入文件路径后,其实时还有一个选项的,如果我们输入true ,输入流则会在当前文件的末尾进行append操作,但是如果不写,会默认为false,这样就会对之前的内容进行直接覆盖。写入的操作也很简单,调用write方法,传入的参数可以是int类型,在写入的时候会自动对应成char类型写入文件,还可以传入Byte[]类型,我们可以通过写入字符串,str.getBytes()方法完成写入。当然也可以传入一个byte[ ],然后设置起始和结束位置进行写入(这个一般用于文件的拷贝,从一个位置读取数据,然后再写入数据)在InputStream中,我们直接声明文件所在路径,然后就可以进行读取了,但是在这里有一点需要注意的是,我们在进行读取时,文件时有穷尽的,所以要规定还,读取结束的条件,这里就说最常见的一种数组读取数据方法。首先开辟一个byte[ ]类型的数组,然后设置合理的长度,我们读取的方法是 fileinputsteam.read( ),这里我们传入定义的数组,然后定义一个int类型的数,来承接这个方法的返回值,这个返回值的含义是,通过当前方法写入到数组中的长度,如果返回值是-1,代表没有读取到任何数据。这就是字节流的大概使用方法
字符流
FileReader 和 FileWriter在使用上和字节流的使用大同小异,要注意的一点就是FileWriter在写入完成后,必须要关闭流,不关闭所有的写入都不会实质性的存储到文件中,在FileReader中我们开辟的数组不再是byte[ ],而是换成了char[ ],这也是需要注意的。
@Test
public void reader() throws IOException {
FileReader fileReader = new FileReader(path);
String writepath = "C:\\Users\\12473\\Desktop\\新计划.txt";
FileWriter fileWriter = new FileWriter(writepath, true);
char[] chars = new char[1024];
int tag;
while((tag = fileReader.read(chars))>-1){
System.out.println(new String(chars,0,tag));
fileWriter.write(chars,0,tag);
}
fileWriter.close();
}
包装流
包装流中并没有什么特别的,仅仅是使用了一个readline( ) 方法来实现读取一行的数据;然后写入包装流也会更加方便的完成字符串的写入,可以在写入一行之后,完成换行的操作;(这里主要应该去了解的是装饰器模式)
@Test
public void buffered() throws IOException {
BufferedReader bufferedReader = new BufferedReader(new FileReader(path));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("C:\\Users\\12473\\Desktop\\新计划.txt"));
String line;
while ((line = bufferedReader.readLine())!=null){
System.out.println(line);
bufferedWriter.write(line);
bufferedWriter.newLine();
}
bufferedWriter.close();
bufferedReader.close();
}
这里主要要说的就是一种设计模式,是一种非常不错的设计思想,
装饰者模式
这种模式也就是包装流的本质,这个模式主要角色有4个 ,分别是 ,一个最根本的抽象类,所有的类都要继承与它,然后是直接实现它的众多实体类,接着是一个继承他的装饰者抽象类,同时这个抽象类中也有一最根本的抽象类的一个对象,剩下的就是继承这个抽象类的装饰类,其实这个模式的本质就是,对一个实体类进行修饰,给他添加一些新的东西,但是也没有改变实体类的本质
外观模式
外观模式的设计并没有装饰者模式那么复杂,简单来说,它就是把客户端和服务端划分出一条界线,这个界线的转换成一个门,外面的人只需要告诉里面的人,我发出什么指令,里面的人会自动的帮我们完成一些列的处理(这里感觉也有些像代理模式??)
主要角色是2种,一个是复杂的各种类,对于客户端来说学习成本特别高,另一种是直接面向客户端的一个类,这个类可以接受客户端的一些指令,然后根据这些指令去调用各种类来完成功能的实现(说实话我欣赏不来),例如==小米音箱可以通过语音,控制全部的家中家电。==但是小米音箱需要绑定全部的家电信息;
对象流
所谓对象流是一种更加高级的流,可以把数据的类型写入文本文件中,然后再读出来;实现这个功能的类是ObjectInputStream 和 ObjectOutputStream 这两个方法,大致功能如下,注意的事情是写完要记得关闭输出流,不然没法读取文件;然后如果说我们要序列化自定义对象,要确保我们所写的类实现了序列化接口 Serializable;
@Test
public void objectio() throws Exception {
//这里要先写在读,要确保读写一致性
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
oos.writeInt(100);
oos.writeUTF("我是周杰伦");
oos.writeObject(new singer("张杰",44);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
System.out.println(ois.readInt());
System.out.println(ois.readUTF());
System.out.println(ois.readObject());//会抛出一个类不存在异常;
ois.close();
}
class singer implements Serializable{
String name;
int age;
public singer(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "singer{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
比较器
在Java中的比较器就相当于运算符号重载,主要有2个接口用来完成这个操作,一个是Comparable接口,这个接口是要给进行比较的对象的类去实现的,另一个是Comparator这个接口我们完全可以称之为一个比较器,我们往往把他当作参数传递进去,当然不管是实现的方法是什么样子的,它return的结果都是一样的,怎么说呢,如果返回一个正数,代表着两个数是o1>o2,如果是一个负数,代表o1