目录
抽象类
作用:校验
被abstract修饰的方法叫做抽象方法,含有抽象方法的类必须加abstract,叫做抽象类,抽象类可以含有不抽象方法
除了不能被实例化和加abstract以外,其他成员变量方法等规则均与普通类相同。
抽象方法必须被子类重写(前提是这个子类不是抽象类,若子类是抽像类不需要重写方法),并且抽象方法必须要写成:void func();
不能指代具体事物的均可以抽象为抽象类,比如:Animal,People;
注:抽象方法不能被private,static,final修饰,因为抽象方法必须被重写;抽象方法的构造方法的作用是让子类调用,帮助其初始化。
接口:
定义接口:
使用关键字:interface加接口名
一般定义接口名的时候以大写“I”开头,并且接口名最好是形容词
接口特性:
接口中不加static或者default都会默认为是抽象方法,不能有具体实现,必须写成void func();的形式(即不能加{}),因此接口理所当然不能被实例化
在接口中定义的成员变量默认为:public static final;
在接口中定义的方法默认为 :public abstract;
因此为了代码简洁,可以对成员变量和方法进行简写:
接口同样可以发生向上转型和动态绑定;
结果为:
接口不能有静态代码块,和构造方法
如果一个类不能实现接口内所有的抽象方法,那这个类必须定义为抽象类
实现接口:
通过关键字:implements
一个类可以实现多个接口,实现接口后,必须重写里面的抽象方法
设置接口的好处:
拓展接口:
接口可以拓展接口,关键词:exdends,接口A拓展了接口B后,就可以拥有B中的方法;
抽象类和接口的主要区别:
1,接口内不能含有普通方法和字段;
2,解决了java当中不能多继承的缺陷;
Object
所有类的父类,所有类都直接或间接继承于Object,因此可以用Object接收所有类的对象
我们来实现其中比较常用的方法:
toString
该方法之前已经实现过,详见toString实现
equal
equal可以比较字符串内容是否相等,但是当euqal被对象引用时,那么就会调用该对象内equal的方法,当该类中没有这个方法,那么就会调用Object的方法,查看Object内的原码:
可知,Object中的方法是比较两个对象的地址,那如果我们想要通过对象引用来比较他们的name属性值是否相同的时候就需要重写equal方法
未重写前结果为false:
重写后,结果为true,重写后的equals:
强调:在这里,新人可能会搞混,为什么重写equals,还要用equals方法去做比较,前面我已经说过了,对象调用方法,那肯定是去从该对象内找方法进行调用,this.name这个是字符串也就是String类,String类里有自己的equals,因此此处调用的是String类中的equals方法,可以自行查看String中equals的方法
注意向下转型可以访问子类特有的方法和字段,但是在向上转型中,只有子类方法重写后才会调用子类的方法,不存在子类字段重写后调用子类字段(因为向上转型需要动态绑定,字段没有动态绑定,因此字段也没有重写这一说)
hashCode:
Object方法中使用hashCode返回的是对象地址值,因为其最底层是用C++写的,所以无法看到,但当我们有类似这样的需求
当名字和年龄相同时,那么我们调用per1.hashCode(),和per2....时,两者值相同那么我们可以重写hashCode
知识补充:内部类
当一个事物内部,需要一个完整的结构需要描述,而这个内部完整结构,又只为外部事物提供服务,那么称之为内部类,就是一个类定义在类或者方法的内部,那这个类成为内部类,后者成为外部类
比如:火车车厢是火车的一部分,就可以在火车这个类中定义车厢
内部类分为:
静态内部类,实例,,,内部,,,匿名,,,,
静态内部类:
静态内部类和普通类一样可以有构造方法,自己的成员变量和方法,静态内部类能实例化,格式为:外部类.内部类(),即可调用静态的内部类的成员
静态内部类不能直接访问外部类的非静态成员和方法,因为依赖外部类对象,创建外部类对象即可
实例内部类:
命名方式:class+类名
实例内部类中不能定义静态变量(因为内部类依赖对象,static不依赖),如果想被static修饰,那必须为常量,该常量必须在定义的时候赋值,常量在编译的时候就会确定
实例内部类中可以直接调用外部类的成员,但必须在方法内;当内部类中有和外部类相同的时候,在内部类中会优先使用内部类成员;如果想在内部类中调用外部类的重名的成员可以在内部类中创建外部类对象进行引用;或者通过this,但this具体指外部类还是内部类取决去谁调用了this
实例内部类依赖外部类对象,所以要想调用实例内部类,就要先创建外部类对象;
注意:new Test()所创建的对象可以不被接收,因此之只写new Test()也不会编译异常
相较于静态内部类,实例内部类需要创建对象较为繁琐
匿名内部类
匿名对象
同样调用func调用了两次,上面的只创建了一个对象,而下面的创建了两个对象,同时Test().func被称为匿名对象
匿名内部类:
此时就可以用a去调用接口中func()方法。可以理解为,使用匿名内部类可以单独实现接口中的方法,不需要再创建其他类实现接口再去调用;(相当于为实例化接口创造了一个方法)
匿名内部类应该写在方法里,不能写在类里方法外,否则会报错
局部内部类很少用这里就不多赘述
常用接口实现
Comparable
对象的比较
如果想对student1和2进行名字或者年龄排序,不能直接用>等符号进行比较(引用类型不能直接比较)
如果想通过名字进行比较,可以通过字符串中compareTo方法进行比较,返回类型是int,查看源码可知,String实现了Comparable中的方法
因此当我们想让compareTo直接去比较对象时,我们就要在该对象中重写该方法
仔细观察发现,参数处不是接口类型,不需要用到向下类型转换,这部分知识后面咋学习(实在看不行,自己写接口重写方法,甚至如果只是为了比较这一个类的对象都无需写接口,只需要在该类中写对应方法,自己调用对应方法即可)
下图是通过自己写接口来重写方法的
言归正传:要想实现系统中comparable的写法
“<>”内写相比较的类型,这部分知识后续探究
对象的排序
当我们有一个数组,数组里的元素是自定义对象时,我们想对数组里的对象进行排序;
当我们用数组中Arrays.sort()方法的时候,系统出现了类型不可转换异常
当我们去看Arrays.sort的原码时,发现比较前会被强转为Comparable类型,但因为Student未实现Comparable接口,所以抛出异常,因此我们需要实现Comparable接口,Arrays.sout中用到的比较方法是compareTo,因此重写这个方法后就可以通过名字或者年龄进行排序。需要注意的是comapreTo返回的类型是int。
此时数组是通过年龄排序的,并且如果想要逆序可以写成o.age - this.age
拓展:
Arrays.sort()可以传第二个参数,第二个参数是个比较器,可以通过传入比较器从而来选择用年龄还是名字排序
自己实现排序功能
1.学会冒泡排序;2用compareTo就行比较;3创建接口
比较器
由于compareTo只能选择一种方式进行比较,所以有了Comparator
创建这两个类实现Comparator接口,就可以通过name或者age进行比较
实现Comparator的好处是固定写法,省事(目前个人拙见)
Cloneable接口和深拷贝
当我们想要将某一个对象克隆给另一个对象的时候我们会用到clone();
遇到的问题
1 该方法在Object中存在,但直接用子类调用会报错,是因为父类中这个方法被protected修饰,因此需要在子类中重写
2 要被克隆的类需要实现一个空接口——Cloneable,这个接口又叫标记接口,实现了这个接口说明这个类是可以被克隆的(深度学习以后探究)
3 使用该方法的时候必须要在main函数后抛出异常
4 见代码第三行,发生了强转,因为clone()返回的是Object对象
深拷贝
clone()是浅拷贝:
可见通过对象的引用clone后,对象中的对象并没有创建新的对象,因此不能达到深拷贝的效果
其中Money是类
当我们在Student类中定义Money类字段的时候,要注意在构造方法中不能这样写
运行时会抛出空指针异常,因为此时this这个对象并没有实例化Money这个对象;所以正确写法是要在构造函数内就实例化好对象
要想深拷贝,不仅要clone()对象,还要clone对象里的对象,因为我们要clone() Money的对象,所以也要在Money这个类中实现clone()接口,并抛出异常。接下来我们就要修改Student中clone方法以达到深拷贝。
因为retrun只能返回一个对象,所以我们通过中间引用变量去返回,第一步是克隆Student对象,第二步是克隆Student对象中的Money对象;
具体为什么第一行是super.clone(),第二行是却用this
记住就行,因为父类的clone方法是用C++写的,所以看不见,而this.m要确定克隆的对象,后面的clone()也是通过父类的clone实现的,因此要在Money类中重写clone().
以上是常见接口的使用,一定要自己动手实现。