一 、抽象类
1.1 、什么是抽象类?
- 就是当一个类不能描述具体的对象时,那么这个类就可以写成抽象类。比如说 Animal ,我们知道 Animal 不能非常清楚的描述一个具体的动物,所以可以把 Animal 写成抽象类。还有就是我们知道父类中的方法,我们在子类中可能需要重写,我们想,当调用重写方法时,肯定是去调用子类中的重写方法,所以父类中的重写方法中的方法体(即方法的实现那部分代码)就会显得很多余(就是用不到),那么我们就可以使用抽象类来让这个方法变为抽象方法,此时就不需要实现方法体了
1.2、抽象类的语法
- 毕竟还是个类,所以还需要class修饰
- 还需要加上 abstract关键字修饰
public abstract class Animal(){}
1.2.1、抽象类中的抽象方法
- 使用abstract 修饰成员方法就能把这个方法变为抽象方法
- 当我们把抽象类中的成员方法变为抽象方法时,那么这个抽象方法就不需要再实现方法体(就是不需要写方法里面的代码)
- 并且在子类中必须要重写父类中的抽象方法,除非这个子类是抽象类,如果是抽象类就不需要重写,直到有普通类继承,就要重写
public abstract void eat();
1.3、抽象类的特征
- 使用 abstract 关键字修饰类
- 抽象类是不能实例化对象的,但是他有什么用呢? 他是只能被继承,那么作为父类有什么优点呢?下文知晓,虽然不能实例化对象,但是可以用抽象类来定义引用,这点和普通类一样,都是为了实现类型统一,即让代码发生向上转型,进而发生多态
- 为什么不能实例化对象呢? 因为抽象类是不能很具体的描述一个对象?并且 里面的抽象方法没有方法体
- 抽象类中可以有和普通类一样的 普通的成员(包含成员变量和成员方法),也可以有抽象方法,也可以含有构造方法,但是构造方法不能变成抽象方法,因为抽象方法是被重写的,而构造方法是不能重写。
- 因为抽象方法需要重写,所以不能使用 static ,private,final,修饰
public abstract void Animal(){public int a ;public void eat(){sout("正在吃法....")
}
// 抽象方法不需要实现方法体public abstract void run();}
- 含有抽象方法的类 ,一定是抽象类,否则报错
- 抽象类中不一定必须得有抽象方法,没有抽象方法这个抽象类也能定义
- 当一个普通类继承了抽象类后,这个普通类需要重写父类抽象类中的抽象方法
- 如果一个抽象类A 继承了抽象类B,那么在抽象类A中可以不重写抽象类B中的方法,但是当一个普通类继承C 继承了这个抽象类A,此时在普通类C中不仅要重写抽象类B当中的方法还要重写抽象类A的方法
1.4、抽象类的好处
- 抽象类中的抽象方法不需要实现方法体,使得代码变得简单
- 抽象类中的抽象方法在(非抽象类)子类中一定需要重写,如果子类中没有重写抽象方法就会报错,所以帮我们效验代码的合法性。如果这个子类是抽象类,那么抽象类就不需要重写,直到有普通类继承,就要重写
1.5、抽象类和普通类的区别
- 抽象类需要使用 abstract 修饰
- 抽象类有抽象方法,并且抽象方法不需要实现方法体
- 抽象类不能实例化对象
二、接口
- 为什么要有接口?
我们知道,Java 是不支持多继承的(就是一个类继承多个类),为了弥补这个缺点,便有了接口,接口是支持实现多个的,并且接口是支持多继承的(就是一个接口继承多个接口)
// 类和接口之间使用implements,就一个类实现俩个接口public class Dog implements IRunning,ISwimming{}
//接口与接口之间用extends,就是用一个接口继承(实现)两个接口
interface IAmphibious extends IRunning,ISwimming{}
// 和上面的Dog 效果一样
public class Dog implements IAmphibious{}
2.1、什么是接口?
- 接口:顾名思义,就好像电脑的USB接口一样,全部的USB接口都是遵循一个通讯协议的,就像鼠标,键盘连接电脑的USB线,只要需要使用,拿起来插入电脑就行。
- 所以接口是公共的一个功能,如哪个类需要使用,实现这个功能的接口就可以。我们知道 Dog 会跑, Fish 会游泳。那么我们知道,实现这两个方法不难啊,在Dog类中把 running 这个方法写为 Dog类中的特色方法不就行了吗,在Fish类中把swimming这个方法写成 Fish 类中的特色方法也不就行了吗,确实是可以,但是我们想一下,如果再有Cat呢? Cat 也会跑,那我们又要在Cat 类在中写一个running的特色方法,那么不就是会跑的动物,都需要我们写running的方法吗。那么有什么方法能让我们不写这个running呢?有人又说,那我们把 running 和 swimming 都写在父类Animal中,不就解决了吗?但是我们要知道,一个父类中的成员是所有子类所共有的成员,那么我问你,所有的动物都会游泳和跑吗?答案是不行的。所以此时就有了接口,一个接口我们可以设置一个功能,比如说跑,游泳,当我们需要用这个功能时,实现接口就行,比如说Dog ,Cat 实现跑这个接口,Fish 实现游泳这个接口就行
2.2 、接口的语法
- 创建接口 用 interface 关键字 比如创建一个IRunning接口
interface IRunning {}
- 在类实现接口时,使用 implements 关键字 类可以实现多个接口
public class Dog implements IRunning,ISwimming{}
- 在接口继承接口时,使用extends关键字 接口可以继承多个接口
interface IAmphibious extends IRunning,ISwimming{}
2.3、接口的规则
- 接口的名字一般第一个和第二个字母大写,并且第一个字母是I 。如IRunnig ,ISwimming。还有结尾是able的
- 还有接口的访问权限和修饰符,默认是 public abstract interface,也就是说接口是抽象的
public abstract interface IRunning{}
2.4、接口的特性
- 接口也是不能实例化对象的
- 接口一般都是 public abstract interface 修饰的,也就是说是抽象的 ,换句话说,类实例化了这个接口,就必须重写接口 里面 的方法,除非这个类是抽象类就不需要重写,但是出来混迟早要还,什么意思呢?就是当有类继承这个抽象类时,就必须重写抽象类中的方法,还要重写接口里面的方法
- 依然像类一样可以发生向上转型,动态绑定,多态
2.4.1、接口中的成员
- 成员方法是 默认public abstract + 返回值类型 修饰的.换句话说接口里面的方法默认是抽象方法 ,抽象方法不需要实现方法体,并且在实例化接口的类中,必须重写接口里面的方法。在类中的重写方法,访问修饰限定符只能是public。但是如果非要实现这个抽象方法的话,使用static 或 default修饰后就能实现方法体
public abstract void run();
- 成员变量是默认 public static final 修饰的。换句话说,这个变量是常量,并且是静态的,所以定义的同时就要初始化 ,所以接口里面不需要构造方法并且也不能有静态代码块
public static final a = 10;
2.5、类和接口的关系
- 类实现接口需要使用 implements 关键字
public class Dog implements IRunning,ISwimming{}
- 因为接口是abstract修饰的,里面的成员方法也是abstract修饰的。所以在类中需要重写接口里面的方法
- 如果是抽象类实现接口,就不需要重写,但是有类继承这个抽象类后,这个类就要重写抽象类里面的方法还有接口里面的方法
- 换句话说,一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类
2.6、接口和接口的关系
- 接口和接口之间的关系是继承,所用的关键字是 extends
- 因为接口是抽象的,所以在 IAmphibious 接口中不需要重写 IRunning 中的方法,当有类实现了IAmphibious这个接口,就要重写IAmphibious接口和IRunning中的方法
- 接口间的继承相当于把多个接口合并在一起
interface IAmphibious extends IRunning{}
2.7、普通类 和接口 的区别
- 普通类是 访问修饰限定符 + class + 类名
- 接口是默认(系统指定) public abstract interface + 接口名
- 普通类中:有成员变量,成员方法,成员变量是 访问修饰限定符 + 类型 + 变量名,成员方法是 访问修饰限定符 + 返回值类型 + 方法名
- 接口中 :有成员变量,成员方法,成员变量默认(系统指定)是 public + static + final +类型 +变量名 + 初始化,成员方法默认(系统指定)是 public abstract 返回值类型 + 方法名
- 普通类中有构造方法
- 接口中没有构造方法
- 都不能够实例化对象
2.8、抽象类 和接口的区别
- 抽象类是 public abstract class + 类名
- 接口是默认(系统指定) public abstract interface + 接口名
- 区别是 类用class 接口用interface
- 抽象类中:可以有普通成员变量,普通成员方法,抽象方法
- 接口中:有默认(系统指定)是 public + static + final +类型 +变量名 + 初始化 的成员变量,和 抽象方法
- 抽象类中可以有构造方法
- 接口中没有构造方法
- 都不能实例化对象
2.9、Comparable接口
- Comparable 是一个接口,他里面有个 compareTo 的抽象方法,作用是比较两个对象,我们如果需要这个接口的功能就可以实现这个接口
- 可以简单的认为这个接口是功能接口
- 有很多的类型都实现有这个接口,比如 String,Integer 等等,换句话说,这些类型都有属于自己的比较规则。
- 但是有个缺点,你们发现没,就是一旦你的比较规则写好后,那么这个类实例化出来的对象就只能按照你的比较规则来走,什么意思呢?就是如果我在这个Student类中需要实现两个比较规则,一个比较规则是比较age,一个比较规则就是name。那么compareTo 就很难实现这个要求。
- 还有就是这个比较规则写好后,这个类中其他的方法也调用这个比较规则,那么我想要改变这个比较规则的话,其他的方法中的代码也需要改变。
- 那怎么办呢?,此时就有了下面的 Comparator 接口
public class Student implements Comparable<Studen>{public int age;public String name;public int compareTo(Student o){//根据自己需求实现return o.age - this.age;}
}
- 根据自己的需求实现 重写方法 comparaTo 的方法体
- 还有就是在实现接口时,要加上比较比较 对象的类型 < Student >,如果没加重写方法的形参的 类型就是Object类型
还有注意 compareTo 方法是一个形参的
2.10、Comparator接口
- Comparator 也是一个用来比较对象的接口,里面有两个抽象方法,一个是compare ,另一个是equals。
- compare 方法是两个形参的,那么我说这个有什么用呢? 我的意思是有两个形参,那么就意味着可以传两个对象,换句话说不像compareTo 那样需要依赖对象。这里的不依赖对象和static那个不依赖对象有点区别。这里的不依赖对象是不依赖对象去调用。
- 所以说我们可以创建一个类专门用来实现一个比较规则,那么这种类就叫做比较器
class AgeCompare implements Comparator<Student>{public int compare(Student o1, Student o2) {return o1.age - o2.age;}
}
- 实现这个接口时,也需要告诉这个接口,你要比较的对象的类型,否则重写方法中的形参的类型是Object类型
- 这样写的好处是,你想有多少个比较规则都没问题
2.10.1、Comparable接口 和 Comparator接口 的区别
- 我们知道上文所说,Comparable 中的compareTo是只有一个形参的
- 而 Comparator 中的compare是有两个形参的。那么我说 compareTo 是需要依赖对象的 ,而compare是不依赖对象的。(这里的依赖对象和static的依赖对象不一样)
- Comparable 和 Comparator 是可以共存的,默认的话就是调用comparable中的compareTo ,有需要的话就调用比较器。在什么时候用到这些比较规则呢?比如说,我实例化了三个对象,分别是 student1 ,student2,student3。并用students 数组接收这三个对象。当我们需要这个数组排序时,Student类中有两个成员变量,那么排序方法怎么知道我们要排序哪个变量呢? 此时的比较规则就起到了关键作用。排序方法会根据Student类中的compareTo 来进行排序。默认情况下,排序方法会判断要排序的类 有没有实现Comparable并重写compareTo方法,如果没有,就会抛异常。如果没有实现Comparable并重写compareTo方法,还可以通过给排序方法传入我们自己写的比较器。一句话:默认调compareTo ,调用传compare;
public class Tect {public static void main(String[] args) {Student student1 = new Student(1,"1");Student student2 = new Student(2,"2");Student student3 = new Student(3,"3");Student[] students ={student1 ,student2 ,student3 };//默认使用Student类中的compareTo的比较方法Arrays.sort(students);//传入比较器,进行排序AgeCompare ageCompare = new AgeCompare();Arrays.sort(students,ageCompare);}
}
class Tect {main(){Student student1 = new Student();Student student2 = new Student();//比较方法一:默认使用重写比较规则student1.compareTo(student2);//比较方法二:传入比较器,按比较器规则来AgeCompare agecompare = new AgeCompare();compare(student1,student2);}
}
这里如果我还需要比较Student中的name 的话,Comparable 中的compareTo就难以实现了,用Comparator 中的 compare 的话,还可以定义一个比较器,要使用就new对象,调用compare 传两个要比较的对象即可。这便弥补了Comparable的缺点
2.11、Clonable接口和深拷贝
- 了解深拷贝我们先知道浅拷贝,那什么是拷贝呢?
2.11.1、什么是拷贝?
- 拷贝就是复制出与原来一摸一样的东西
- 那么代码怎么实现拷贝呢?
- 我们知道Object中有一个拷贝方法,那么既然有了拷贝方法那么我们该如何调用呢?那不简单吗,因为Object是所有类的父类,所以new个对象直接调用clone方法就可以了啊。
class Tect{public static void main(String[] args) {Student student1 = new Student(12,"张三");Student student2 = student1.clone;}}
按道理来说,这个代码应该就能执行了。但是实际是没有clone方法。这是为什么呢?原因很简单,因为Object类中的 clone 方法是 protected 修饰的,父类和子类不在一个包,虽然是子类,但不是在自己本身的类(Student)中使用,而是在其他的类(Tect)中使用 ,所以 protected Object clone() throws CloneNotSupportedException {
return super.clone();
}用不了。那怎么办呢?
- 既然在Student中能使用,那么就重写clone方法 ,在方法中返回父类Object中的clone方法 不就行了吗
class Student{protected Object clone() throws CloneNotSupportedException {return super.clone();}}
此时我们还发现clone源码中,有throw…,这其实是异常,所以我们需要在main方法的后面说明这个方法是合法的
class Tect{public static void main(String[] args) throws CloneNotSupportedException{Student student1 = new Student(12,"张三");Student student2 = student1.clone;}}
此时还没完,我们看源码,发现调用完父类的clone后,返回值是Object,所以需要强转
- 当我们以为拷贝成功了,但实际是运行时抛了CloneNotSupportedException这个异常,那有异常就需要处理,那怎么处理呢?
在需要拷贝的类 实现 Clonable接口,这个接口里面什么东西都没有,盲猜,他是用来标记这个类能被拷贝的。
public class Student implements Cloneable{}
- 到这里就拷贝完成了
2.11.2、什么是浅拷贝?
- 比如说,一个对象中有成员变量,成员方法,还有一个引用指向另一个对象。浅拷贝就是把成员变量,成员方法,引用,都拷贝到另一个对象,但是对象中的引用所指的那个对象没有拷贝
- 这样就是浅拷贝,这样的话,我们看图,发现通过people1 ,people2 都能改变 对象的money所指的对象 ,那么就会非常不安全了
2.11.1、怎么深拷贝?
- 深拷贝就是把对象里的引用所指的对象一起拷贝了
- 那么这种深拷贝怎么通过代码实现呢?简单,就是在拷贝了的前提,再对money进行拷贝
class Student implements Cloneable{
protected Object clone() throws CloneNotSupportedException {Student tem = (Student) super.clone();tem.money = (Money) this.money.clone();return tem;}}
在Student 中的重写clone中,在对象的拷贝的前提下,再拷贝一次对象里面的引用所指的对象
深拷贝和浅拷贝本质区别是代码怎么写,而不是取决于某个工具
三、内部类
- 日常使用最多的是匿名内部类。
- 内部类也是封装的一种体现
- 什么是内部类,顾名思义是在内部里的类,那在谁的内部呢?在类的内部。
- 那什么情况下需要定义内部类呢?
- 我们知道类是对一个真实的事物进行描述。在一个类中,还有一部分可以抽取出来当做一个类的,此时就可以定义内部类了
class OutClass {class InClass {}
}
3.1、什么是内部类?
- 内部类是类中类,或者方法中类
- 我们知道类中的成员有,成员变量,成员方法。如今增加了成员类,我们知道成员有静态成员,和非静态成员,那么内部类是否也是有静态内部类 和非静态内部类的呢?内部类的位置那么特殊,是成员,又是类,那么他是否具有这两种的特点呢?我们拭目以待吧。
3.2、内部类的分类
- 静态内部类
- 实例内部类
- 局部内部类
- 匿名内部类
3.3、静态内部类
- 静态内部类和静态成员一样,都是使用static修饰的
class OutClass{static class InClass{}
}
- 作为成员,是被static修饰的,被static修饰后,最大的特点就是不依赖对象,变成类类了。所以可以通过外部类直接点就能使用静态内部类。
- 作为类,那么他是可以实例化对象的,那么如何实例化对象呢?
OutClass.InClass inClass = new OutClass.InClass();
此时引用能访问静态内部类里面的非静态成员,因为内部类里面静态成员是通过类名直接点访问的,而外部类的非静态成员变量,需要外部类实例化对象,并通过外部类的对象加点才能访问。外部的静态成员,是外部类类名直接点就访问。所以综上,通过内部类对象不能访问外部类的成员
- 在静态内部类当中不能直接访问外部类当中的非静态的成员变量(硬要访问只能通过外部类对象来访问),换句话说在静态内部类中只能访问外部类中的静态成员
3.4、实例内部类
- 什么是实例内部类,我们知道类中的非静态成员,也叫做实例成员。
- 所以说内部类是需要依赖对象的,依赖谁的对象呢?依赖外部类的对象
- 作为成员,是外部类的对象加点才能使用
- 作为类,那么他是怎么实例化对象的呢?
//方法一OutClass2.InClass inClass2 = new OutClass2().new InClass();//方法二OutClass2 outClass2 = new OutClass2();OutClass2.InClass inClass3 = outClass2.new InClass();
实例化出来的对象,也是只能调用内部类直接的非静态成员。因为内部类里面静态成员是通过类名直接点访问的,而外部类的非静态成员变量,需要外部类实例化对象,并通过外部类的对象加点才能访问。外部的静态成员,是外部类类名直接点就访问。所以综上,通过内部类对象不能访问外部类的成员
- 外部类中的任何成员都可以在实例内部类中直接访问
- 我们知道类是只能被 public 或者默认修饰的,但现在作为成员,因此也受protected private等访问限定符修饰
- 在实例内部类中的非静态方法是默认实现了外部类实例化对象。也就是说,如果实例内部类中的成员变量 和外部类的成员变量重名了,我们知道,如果重名了肯定优先使用自己的(实例内部类的),但是我偏要访问外部类重名的变量。此时就可以直接外部类名 + this + 点 + 变量名,就能调用外部类的成员变量
- 在外部类中,不能直接访问实例内部类的成员,如果非要访问,必须先创建实例内部类的对象。
3.5、匿名内部类
- 顾名思义,匿名所以是没有名的内部类,在声名的同时完成实例化,通常在只使用一次的情况下定义。
- 怎么定义?
// SuperType 可以是接口,抽象类和普通类
new SuperType(){}
- 可以定义一个Super Type的引用去接收他
SuperType superType = new SuperType(){}
- 匿名类中可以定义和正常类一样的成员变量。
3.6、局部内部类
- 是定义在外部类的方法体中,这种类只能在定义的范围中使用( { } )。
public class OutClass {public void tect(){class InClass{}}
}
- 局部内部类只能在所定义的方法体内部使用
- 不能被public static 等修饰
四、Object类
一、toStiring 方法
- 是用来打印的
二、equals 方法
- 是用来比较对象的,根据自己的需求重写。