抽象类
抽象类跟普通类差不多,却又有特殊的区别。
1:abstract 关键字修饰
abstract class stu{
//抽象类
}
2:抽象类不能实例化(最大意义被继承;能防止你继承后忘记重写方法)
3:抽象类里边可以加抽象方法(0-多个)
抽象方法(abstract 关键字修饰)
一:抽象方法是没有具体的实现的;后面直接跟引号;不能用花括号。
二:抽象类被继承必须得重写抽象方法,如果是抽象类继承抽象类则不需要重写抽象方法,但是当被普通子类继承时得把他们两的抽象方法都得重写。
三:private ,final,static,构造方法不能声明为抽象方法(因为这些修饰的不能被重写)
四:如果不加修饰则默认是public。
五:抽象类的其它就跟普通类一样,能有普通类的成员,构造方法(初始化抽象类成员)
public class test2 {
public static void main(String[] args) {
son son=new son();
son.stu();
}
}
abstract class ikun{
public int age ;
public abstract void stu();//抽象方法没有具体实现
public ikun() {
this.age = 100; //构造方法初始化抽象类成员
}
}
class son extends ikun{
@Override
public void stu() {
System.out.println("我是重写的抽象方法");
System.out.println(age);
}
}
输出结果:
我是重写的抽象方法
100
这里也是能发生多态:
//son son=new son();
//son.stu();
ikun ikun=new son();向上转型,通过ikun.调用子类重写父类的方法
ikun.stu();
抽象类的作用:预防错误
例如工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类, 使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误。
接口
接口的组成
接口是一个抽象类型(不能实例化),是抽象方法的集合,接口通常以interface声明。
先介绍接口的组成可以有哪些:
1:成员变量:默认是public static final修饰,可以通过接口名访问。只读不改
2:抽象方法:被非抽象方法实现此接口后必须重写,不加 abstract也默认是 public abstract
3:jdk1.8以后就允许有静态方法,default方法,可以有多个。
接口里的静态方法的调用是:接口名调用
public class test3 {
public static void main(String[] args) {
a.f3();//接口的静态方法,通过接口名调用
}
}
interface a{
public static final int a=100;
int b=100;//默认也是public static final修饰的..只读不改
abstract void fun();//抽象方法
void fun1(); //默认修饰的抽象方法
public static void f3(){
System.out.println("我是静态方法");
}
default void f4(){
System.out.println("我是默认方法");
}
}
class stue implements a{//接口的实现使用implements关键字
@Override
public void fun() {
//重写的抽象方法
}
@Override
public void fun1() {
//重写的抽象方法
}
}
输出结果:
我是静态方法
接口其它特性
1:接口的方法和声明都是隐式:方法和接口都可以不加abstract关键字,当然写上也行。所以接口也不能不能实例化
abstract interface a{
}
2:接口中不能有静态代码块和构造方法(这里抽象类是可以有的;接口是定义一组规范和行为;可以看做是抽象方法的集合)
3:接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class。所有我们可以姑且把它当做特殊的类。
4:接口也能实现多态(可以向上转型和向下转型两种实现)
5:子类重写的抽象方法,必须加上public(方法重写:要求“子类方法”的修饰符权限必须大于等于“父类方法”的修饰符权限)
因为这里抽象类默认是public。
接口解决不能多继承问题
1:类实现多个接口,中间使用,隔开 。。。必须重写两个接口的所有抽象方法。
public class tset5 {
public static void main(String[] args) {
}
}
interface c{
abstract int c();
}
interface x{
abstract int t();
}
class k implements c,x{
@Override
public int c() {
return 1000;
}
@Override
public int t() {
return 10000;
}
}
2:接口继承接口
public class tset5 {
public static void main(String[] args) {
}
}
interface c{
abstract int p();
}
interface x{
abstract int t();
}
interface k extends x,c{
//当我实现k接口,必须重写这里所有的抽象方法
}
3:类既要继承类又要实现接口(一定是先类的继承再实现接口)
public class tset5 {
public static void main(String[] args) {
}
}
class tea{
}
interface x{
}
class stu1 extends tea implements x{
}
标记接口
标记接口是一种不包含方法声明的接口,其唯一目的是作为标记或标识的存在。各司其职;不要滥用为好。
接口的使用实例
接口1:Comparable
Comparable接口作用:可以让实现它的类的对象进行比较,具体的比较规则是按照 compareTo 方法中的规则进行。
Comparable打开源码可以看到只有一个compareTo方法(接口里默认是抽象方法)
可以发现,当我们在自定义类型比较时候不指定比较方式就会报类型转化异常的错误。(使用 Arrays.sort和Collections.sort时它会自己调用compareTo方法)其它如果我们使用就自己调用。
例如:
需要自己指定比较规则,姓名,年龄??
可以看到,如果是String类型就直接可以比较。
在源码我们可以看到它是实现Comparable接口,然后就可以比较,所以我们模仿源码让我们的自定义也实现可比较的功能
public class test6 {
public static void main(String[] args) {
student s=new student(18,"zhangsan");
student s1=new student(19,"lisi");
if(s.compareTo(s1)>0){//重写后调用,谁调用谁就是this
System.out.println("张三年龄比李四大");
}
}
}
class student implements Comparable<student> { //<student>是泛型,意味你调用
//compareTo方法传入的得是student类型
int age;
String name;
public student(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public int compareTo(student o) {//根据你具体需求进行重写
return this.age-o.age;
}
}
如果要根据姓名比大小:
//把我们自定义的类型里重写的compareTo改成如下
@Override
public int compareTo(student o) {
return this.name.compareTo(o.name);
//String、Integer、或者其他的已经实现Comparable接口,可以直接使用compareTo比较。
}
但是这种方法并不好:总不可能我已经使用很久的年龄比较,如果要姓名比较还得把我们之前用的年龄比较的方式删掉。所以java还有个Comparator:比较器接口
Comparator:比较器接口
1:使用Arrays.sort传比较器。
使用idea找到Arrays.sort发现重载了很多方法,根据我们传入的参数决定调用哪一个,这是可以传比较器的方法
import java.util.Arrays;
import java.util.Comparator;
public class test6 {
public static void main(String[] args) {
student s=new student(18,"zhangsan");
student s1=new student(19,"lisi");
student stu[]={s,s1};
c1 c11=new c1();
c2 c22=new c2();
Arrays.sort(stu,c11);
System.out.println(Arrays.toString(stu));
Arrays.sort(stu,c22);
System.out.println(Arrays.toString(stu));
}
}
class student {
int age;
String name;
public student(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
class c1 implements Comparator<student> {//比较器1根据年龄比较
@Override
public int compare(student o1, student o2) {
return o1.age-o2.age;
}
}
class c2 implements Comparator<student> {//比较器2根据姓名比较
@Override
public int compare(student o1, student o2) {
return o1.name.compareTo(o2.name);
}
}
输出:
2:手动调用比较器
目前我的学习暂且接触到堆排序和Arrays.sort知道可以传比较器,然后它自动调用方法去比较。
可以调用比较器的compare方法把两个要比较的对象传进去即可实现比较
3:可以发现这个比较器接口明明有两个抽象方法,为什么我们只重写compare方法就可以呢????
我的理解是Java中类都继承于Object类,而Object类默认实现了equals方法,不需要必须去实现equals方法。
克隆接口
我们数组可以有自己的克隆方法,如下,但是如果要克隆一个我们自定义的对象怎么做呢???
我们想直接参照数组的这种方式去克隆却发现没有这个方法可以使用。
而下面明显看到Object确实有这个方法,我们的类默认继承于Object类。这不是有点矛盾了????
问题在于:克隆得需要前提的,就是你的对象得具备可克隆的功能
所以我们得Cloneable接口(jdk的接口都是able结尾,代表一种可以…的能力)
然后我们点开Cloneable接口源码,发现里面的接口什么都没有(标记接口,代表可以被克隆)
实现克隆接口还没完,我们的本质还是想通过调用Object的克隆方法。得重写Object的克隆方法。
整体代码
import java.util.Arrays;
public class test7 {
public static void main(String[] args) throws CloneNotSupportedException {
int [] arr={1,2,3,4};
int [] arr2=arr.clone();
System.out.println(Arrays.toString(arr));//输出[1, 2, 3, 4]
System.out.println(Arrays.toString(arr2));//输出[1, 2, 3, 4]
liao1 liao1=new liao1();
liao1 liao11=(liao1)liao1.clone();
System.out.println(liao1);// 输出liao1{age=21}
System.out.println(liao11);// 输出liao1{age=21}
}
}
class liao1 implements Cloneable{
int age=21;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "liao1{" +
"age=" + age +
'}';
}
}
总结一下:(克隆就产生一个副本,克隆后进行两两的修改,不会影响彼此)
1:先实现克隆接口,标记说明可被克隆的
2:重写Object的克隆方法
3:重写后声明异常,并且注意克隆后的类型是Object类型
浅拷贝
简单说就是拷贝的太浅了,如下代码:stu类里面有tea对象。进行上述拷贝方式时;原来的对象和克隆的对象的这个他们的引用类型但是指向同一个位置,所以改一个就会两个发生改变。
public class test1 {
public static void main(String[] args) throws CloneNotSupportedException {
stu stu1=new stu();
stu stu2=(stu)stu1.clone();
stu2.tea1.id=100000;
System.out.println(stu1.tea1.id);
System.out.println(stu2.tea1.id);
}
}
class tea{
int id=100;
}
class stu implements Cloneable{
int age=21;
tea tea1=new tea();
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
输出结果:100000
100000
我通过画图解析一下吧!
这并不是我们想要的效果;我们要的结果是我拷贝后修改stu2/stu1的值他们是互不影响的,也是就是我们要把这个ox67也另外拷贝一份;让他们最终指的各不同,这就是深拷贝。
深拷贝
在我调用拷贝stu的时候;把tea也先拷贝一次(深拷贝的另一种实现可以是序列化和反序列化)
public class test1 {
public static void main(String[] args) throws CloneNotSupportedException {
stu stu1=new stu();
stu stu2=(stu)stu1.clone();
stu2.tea1.id=100000;
System.out.println(stu1.tea1.id);
System.out.println(stu2.tea1.id);
}
}
class tea implements Cloneable{
int id=100;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class stu implements Cloneable{
int age=21;
tea tea1=new tea();
@Override
protected Object clone() throws CloneNotSupportedException {
//这两个已经变成新的一份
stu tmp=(stu) super.clone();
tmp.tea1=(tea) this.tea1.clone();//把我们的tea1对象也克隆一份,this就是我们当前这里的tea1对象
return tmp;
}
}
我们在写好这个tea1对象的克隆,得调用一下,使我们调用一个stu1克隆它就会把这个tea1对象也调用克隆方法克隆一份
equals简介一下:
可以使用equals方法去验证一下(比较两个引用变量的地址是否一样)我们也可以重写此方法,使得比较规则符合我们需求(比如我们有时候需要根据年龄,姓名比较是否相同)
hashCode方法:计算对象的位置,这个方法一般是使用默认的