目录
抽象类和接口
抽象类:
1.什么是抽象类?
1.1在一个类中没有包含足够的信息去描述一个对象,并且这个类被abstract修饰,这个类就被称为抽象类。
2.抽象类有什么作用和意义?
2.1作用:
首先,抽象类在面向对象方法中是不能被实例化的,并且抽象类是用于了类型隐藏的。
其次,抽象类是对一系列看上去不同,但本质上相同的具体概念的抽象。
比如:在抽象类中构造一个抽象方法但不具体实现,通过子类的继承实现抽象类中构造的抽象方法(这个时候继承中的抽象方法可以有任意种具体的实现方式),此时的这种描述就是抽象类,像这样一种任意个可能的具体实现则表现为所有可能的派生类。(本质上相同但需要的功能不同,就各自实现某种功能)。
2.2意义:
在实践工作中,我觉得抽象类的意义就是代码的维护和重用。
2.2.1.我们学习到抽象类是不能被实例化的,如果非要去使用的话,那么我们必须要有子类去实现它之后才能被使用(new子类)。我们这样的话就能把相同属性和相同的方法进行抽象,此时我们在日常开发中就更有利于代码和程序的维护了。
比如一个狗和一个猫都能被称作为动物,他们可能相同的属性的方法。这样你对其中的某个类进行修改的时候,就会受到基类(父类)的限制,在日常开发中就会提醒程序猿这个东西不能随便修改哦,这样的话可能对一些重要的代码维护有一个很大的帮助。
2.2.2.当有一个相似的组件产生时,就可以实现像上面的抽象类,然后获得抽象类的那些抽象类那些属性和方法。
此时来了一位猴子老铁,那么猴子老铁就可以直接继承动物这个类,然后对自己特有的属性和方法进行补充即可,此时此刻代码的重用在抽象类中就体现的淋漓尽致。
综上所述:java中的抽象类对于代码的维护和重用就有很大的帮助,也是java面向对象的一个重要的体现。
3.抽象类语法特点?
3.1.被abstract修饰的类称为抽象类,在抽象类中被abstract修饰的方法称为抽象方法,并且抽象方法不用给出具体的实现。
abstract class Person{
public String name;//普通方法
public int age;
public abstract void Run();//抽象方法
//普通方法
public void Speak(){
}
}
注意:抽象类中也是可以包含普通方法(构造方法)和属性的,当然只要是在类中用abstract修饰过的方法或属性都是抽象方法或属性(此时此刻该类只能是抽象类,在普通类中是不会有抽象方法或属性的)。
3.2.抽象类不能直接实例化并且抽象方法不能是private修饰,因为抽象类存在的最大意义就是被继承(不论是抽象方法还是普通方法在没有加访问限定符的时候,都默认为public)。
abstract class Person{
abstract private void run();
//此时此刻一定会出现编译报错
}
3.3.抽象类存在最大的意义就是被继承,所以抽象类在日常开发中必须被继承,并且继承之后子类B必须重写基类A(父类)中的抽象方法(如果子类B不想重写基类中的抽象方法,那么子类必须变成抽象类,也就是必须要使用abstract来修饰 但是如果子类B被其他类继承,那么这个其他类就要重写子类B以及基类A中的抽象方法)
abstract class Person{
public String name;//普通方法
public int age;
public abstract void Run();//抽象方法
//普通方法
public void Speak(){
}
}
class Student extends Person{
public void Run(){
System.out.println("抽象类必须被重写");
}
}
abstract Student extends Person {
public void Run(){
System.out.println("此时抽象类就不用被重写");
}
}
3.4.抽象方法中是不能被final 和 static 中修饰的,因为抽象方法存在的意义就是被子类继承重写。
abstract class Person{
abstract final void Run();
abstract static void draw();
System.out.println("此时程序一定会编译报错,出现非法组合");
}
4.抽象类的应用举例?
abstract class Person{
public abstract String getDescription();
private String name;
public Person(String name){
this.name = name;
}
public String getName(){
return name;
}
}
class Student extends Person{
private String major;
public Student(String name,String major){
super(name);//先帮父类构造
//这是一种特殊的情况
/*
也有一种情况例外,就是存在this(),调用本类其它构造函数,但是按照递归调用,最终还是会调用 父类构造函数;如果this()和super()都存在,那么就会出现:初始化父类两次的不安全操作,因为当super()和this()同时出现的时候,在调用完了super()之后 还会执行this(),而this()中又会自动调用super(),这就造成了调用两次super()的结果。
*/
this.major = major;
}
//进行方法重写
public String getDescription(){
return "a studnet majoring in " + major;
}
}
5.抽象类的小总结?
以下语句是对抽象类的一个小结,如果有漏缺,请大家积极指出。
1.抽象类是使用abstract修饰类,并且抽象类是不能实例化的。
2.抽象类当中可以包含普通类所能包含的成员,两者不一样的是,抽象类中可以包含抽象方法,普通类不行。
3.抽象方法是abstract修饰的,这个方法是没有具体实现的。
4.抽象类存在的最大的意义就是被继承。
5.如果一个普通类继承了一个抽象类,那么这个普通类就必学重写抽象类中的抽象方法。如果是抽象类A继承抽象类B的话,此时抽象类A就不用 重写抽象类B的抽象方法,如果抽象类A再次被普通类继承的话,那么这个普通类就需要去重写抽象类A和抽象类B 的抽象方法。
6.抽象方法不能是私有的(不能被private所修饰,因为要继承的子类所重写),当然在重写方法中 abstract 和 final ,static是不能同时出现的,还是因为要满足重写要求。
7.抽象类中可以有构造方法,为了方便子类能够调用,来初始化抽象类当中的成员。
Object类:
1.什么是Object类?
Object的类是java中所有类的超类(父类,基类),在java中的每个类当中都扩展了Object类--这就是Object类。
2.我们又该如何理解Object类在java中的使用?
2.1.在刚刚的概念中我们都知道了Object类是所有类的超类,我们可能会感觉到奇怪,因为在前面的学习中,我们好像都没有见过这个Object类,也没有在使用的时候明确的指出那个方法用到了这个类?此时此刻我们就会带领大家深入的了解这个Object类在java中的应用。
如果一个类没有明确的指出超类,Object就被认为是这个类的超类。Object类又是在java.lang包下的,所以它不需要手动导入类(都是在编译期间系统自动导入的)。
public class Person{
//此时此刻虽然没有明确的指出Object类被person类继承,但是系统会默认person类继承了Object类,(是可以省略继承这个操作的)
public String name;
public int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
}
2.2.上面的代码可能还是会让我们初学者有一定的难理解,我们在这通过一个实践应用的举列,再次感受一下Object类为所有类的超类。
class Person{
public void run1(){
System.out.println("成人跑步");
}
}
class Student{
public void run2(){
System.out.println("学生跑步");
}
}
public class Test{
public static void Run(Object obj){
Person per = (Person) obj;
per.run1();
Student stu = (Student) obj;
stu.run2();
}
public static void main(String args[]){
//向上转型
Run(new Person);
Run(new Student);
}
}
相信大多数学者都注意到了,在主方法中调用Run()这个方法的时候,传参传的是新new的对象,而在Run()方法中接收的类型为Object类型,我们就会发现这是一个很明显的向上转型的操作,我们都知道在java中只有是子类的父类才会有这样的操作。那么此时此刻你对Object类为所有类的超类有了新的理解嘛!
注意:细心的学者应该会发现在Run()方法中会有像(Person)obj或(Student)obj这样的强转型操作,我们的学者可能会提出一个疑问为什么?
虽然说Object类是所有类的超类(其中也具有一定的方法),但是要对某个类中的内容进行具体操作的时候(内容:某类中自身定义的方法,在Object类中没有的方法),还是需要清楚对象的原始类型,所以需要进行相对应的强制类型转换。
在这个地方提出一个小建议:如果用Object类型接收的话,在调用对象的方法的时候,都进行强制类型转换,有便于理解。
3.Object类当中有哪些方法需要我们学习?
不论是在其他类中还是在Object类中,都会有一些需要掌握的方法,经过刚刚的学习,Object类就是所有类的超类,那么其中的方法一定是非常重要的。在下列的图像中列举出了,Object类中所有的方法。我们在此段的学习中会讲解一下几个平时常用的方法(equals()方法,hashCode()方法,toString()方法),其他的方法我们会在之后的学习中一 一讲解到。
3.1.equals()方法
在学习运算符的时候我们应该了解过equals()方法和运算符“==”之间的比较,而两者最大的区是:“==”是比较两个对象之间的引用是否相等,而equals()方法比较的则是两个对象中的实际内容,我们可以在下面的代码感受一下。
public class CompareEquals{
public static void main(String args[]){
String s1 = "123";
String s2 = "123";
//true
System.out.println(s1.equals(s2));//比较的是内容
Person per1 = new Person();
Person per2 = new Person();
//false
System.out.println(per1.equals(per2));//比较的是引用的两个对象
}
}
此时我们会发现我们得到的结果都是false,为什么?刚刚不是说,equals比较的是对象中的内容,现在怎么就变false。因为有这样一个规定,在自定义类中使用equals()方法进行比较的时候,equals()方法默认是使用“==”运算符比较两个对象的引用地址,而不是比较对象的内容,所以我们要想真正做到比较两个对象的内容时,需要在自定义类中重写equals()方法。
优化之后的代码:
class CompareEquals{
public String name;
public int age;
public CompareEquals(String name,int age){
this.name = name;
this.age = age;
}
public boolean equals(Object obj){
if(obj == null){
return false;
}
if(obj == this){
return true;
}
//instaceof -->就是为了检测它们是不是同种数据类型
if(!obj instanceof CompareEquals){
return false;
}
CompareEquals CE = (CompareEquals) obj;
if(this.name.equals(CE.name) && this.age == CE.age){
return true;
}
return false;
}
}
public class Test{
public static void main(String args[]){
CompareEquals CE1 = new CompareEquals("张三",16);
CompareEquals CE2 = new CompareEquals("张三",16);
System.out.println(CE1.equals(CE2));//true
}
}
注意:instanceof是用来检测 两个比较的对象是不是同种数据类型,如果是程序继续执行,如果不是程序抛出false。
3.2.hashcode()方法
hashcode()方法是大量运用在散列表中的,而散列表是在数据结构中学习的,所以我们在这了解感受一下就行,在后期学习数据结构的时候我们再着重讲解。
hashcode()方法可以帮我们算出对象的具体位置,因为我们还没学习到散列表,所以我们暂且把hashcode()认为是计算内存地址的,并且以16进制输出。
那么学者可能会提问,难道就是通过比较引用的地址得出两个对象是否相等吗?也不全是,我们暂且可以这样理解(暂且这样理解,在后面的学习中我们再来深究)
我们可以在下面的举例中感受一下:
我们认为,有着相同的名字和年龄的对象,将会储存在同一个位置!
class CompareHash{
public String name;
public int age;
public CompareEquals(String name,int age){
this.name = name;
this.age = age;
}
}
public class Test{
public static void main(String[] args){
CompareHash Ch1 = new CompareHash("孙悟空",18);
CompareHash Ch2 = new CompareHash("孙悟空",18);
System.out.println(Ch1.hashCode());
System.out.println(Ch2.hashCode());
//执行结果:
//460141958
//1163157884
}
}
学者们会再次发现,为什么出现的内存地址是不一样的?原理其实是跟equals()方法类似,在自定义的类中使用hashcode()方法的时候,也是需要重写的,要不然它就会调用默认的方法,就会出现上面的结果。
优化后的代码:
class CompareHash{
public String name;
public int age;
public CompareEquals(String name,int age){
this.name = name;
this.age = age;
}
public int hashCode(){
return Objects.hash(name,age);
}
}
public class Test{
public static void main(String[] args){
CompareHash Ch1 = new CompareHash("孙悟空",18);
CompareHash Ch2 = new CompareHash("孙悟空",18);
System.out.println(Ch1.hashCode());
System.out.println(Ch2.hashCode());
//执行结果:
//460141958
//460141958
}
}
3.3.toString()方法
toString()方法的功能是将一个对象返回为字符串的形式,就是返回一个像String的实例,在前面的学习中我们学习过,就不在过多的讲述这个方法,直接上代码感受一下。
class FeeltoString{
public String name;
public int age;
public FeeltoString(String name,int age){
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:"+name+" 年龄:"+age;
}
}
public class Test{
public static void main(String[] args){
FeeltoString FS1 = new FeeltoString("沙悟净",18);
FeeltoString FS2 = new FeeltoString("猪八戒",18);
System.out.println(FS1);
System.out.println(FS2);
//执行结果
//[姓名:沙悟净 年龄:18]
//[姓名:猪八戒 年龄:18]
}
}
注意:此处的toString()方法可以重写,也可以不重写,根据自行需求定义。
接口:
1.什么是接口?
我们在下面的学习中会了解到接口和抽象类会非常相似,所以我们一般会把接口叫做是抽象类的延伸,并且也可以把接口看作是一个纯粹的抽象类,但是接口他不是类或抽象类,他只能看作为抽象类,因为接口只是类中的一组需求,并且接口中的方法是没有方法体的(不能有具体的实现),只是为类服务的一个组件,这就是接口。
2.接口在java中的作用和意义?
作用:
2.1.解决了java中的单继承缺陷和多实现优点
比如:此时此刻有一个父类A和一个子类B,那么这个子类B继承这个父类A ,这个子类B就得到了这个父类A所具有的功能,这个时候子类B再想继承另一个类,是无法做到的,因为类是单继承的,一个类只能继承一个类,那么此刻子类B想要另一个类中的新功能,我们该怎么办?此时,我们就可以实现一个接口,在接口中定义一个这样的新功能,然后由子类B去实现这个接口就行了。
这个时候我们就会出现一个疑虑为什么要用一个接口去扩展这样一个新功能呢!为什么不直接在父类中写一个这样的新功能呢?那样岂不是更方便嘛,此时此刻接口意义便产生了。
意义:
2.2.其实在某种程度上,不论是接口的出现还是抽象类的出现都是为了降低代码的维护成本和代码程序安全性,稳定性,以及避免编码的随意性。我们可以在举例中感受接口的意义。
比如:有一个子类想扩展某些新功能,新的功能就可以规范定义在一个接口中,子类只需要实现这个接口,在子类中重写接口中的方法,就能得到这个新功能。当使用者想了解这个类的功能用法的时候,就不用看源码,看接口就知道了这个类的具体用法和功能,此时就很好的保护了代码程序的安全性,避免了源代码的泄漏,保证了开发者的权益。当然,也不用担心代码冗余问题,不会在各个类中重新命名并定义很多相同功能的方法,只用重写接口中的方法就行,也避免了编码的随意性。更是在开发者开发的时候能提醒开发者在某些类中需要重写的方法而没有写带来的报错警告,在一定程度上提升了开发的效率和降低了代码后期维护的成本。这可能就是接口存在意义。
3.接口的语法特性和使用规则?
3.1.首先定义接口和平时定义类的时候关键字是不同的,普通类的定义是由class关键字定义的,但是接口就必须是interface定义的。我们刚刚上面的例子中提到了实现一个接口,那么该如何实现了这个接口,是像继承那样用extends实现还是用有别的方式?我们实现接口是会用implements关键字实现的(这个千万不能搞混淆)。
public interface Person {
void Run();
public abstract void Swim();
public void Func();
abstract void miss();
}
public class Student implements Person{
public Run(){
System.out.println("实现一个接口,就必须重写接口中的方法");
}
public void Swim(){
System.out.println("Swim");
}
public void Func(){
System.out.println("Func");
}
public void miss(){
System.out.println("miss");
}
}
3.2.我们在上面的代码中会发现,在接口中出现了很多方法,并且他们是没有具体的方法体的而且这些方法在声明定义的时候,前面会有有一些修饰符,我们暂时可能会有一个疑虑,为什么会出现这些这些类似相同的修饰符?接下来我会带领大家解出咋们心中的疑惑。
我们刚刚的概念中提到过,接口其实就是抽象类的延伸,所以在接口中声明定义的方法都是抽象方法,抽象方法一般都是abstract修饰的,所以我们会在刚刚的代码中看到会有abstract修饰符的出现,当然在代码中还有未被abstract修饰符修饰的方法,那么我们该如何理解这些方法?我们是把他们当做普通方法看待,还是会有属于他们的专属理解!我给出的解答是第二种。在接口中,所有的方法都默认为public抽象方法,就是不管你前面加不加修饰符(在接口中修饰符只能是public 不能是其他的,因为需要满足被继承被重写要求),最后都会被程序默认指定为public abstract修饰符。
public interface Person{
void Run();//默认是抽象方法,并且省略了public abstract
public abstract void Swim();
public void Func();//省略了abstract
abstract void miss();
//上述写法都是抽象方法
}
3.3.我们刚刚也说到接口是抽象类的一种延伸,那么我们是不是也能在接口中定义一些普通的构造方法和和静态代码块呢?答案是否定的!在接口中只能有抽象方法,而在抽象类中不仅有抽象方法也可以有其他的普通方法和构造方法,这可能就是两者最大的区别。这个时候有的学者可能就会钻牛角尖,我偏要在接口中实现一个具体的方法!这个时候也不是不行的,在后来jdk8中也是给大家带来了这样的一个功能,就是在你需要具体实现的方法前面加上一个default修饰符就行了(不建议大家使用这个东东哦!)。
public interface Person{
void Run();
default public void Func(){
System.out.println("在接口中被default修饰后的方法就能写方法体");
}
public void func(){
System.out.println("此时将会报错,接口中不能有构造方法");
}
static{
System.out.ptintln("程序报错,接口中不能有静态代码块");
}
}
class Student implements Person{
//此时此刻是不能在重写的方法前面用default修饰的
void Run(){
System.out.println("学生在跑步");
}
}
3.4.在使用接口的时候我们需要特别注意的是,接口它虽是一个引用数据类型,但是接口是不能实例化的(new对象),当然接口中的方法也是不能在接口中实现的,要是想调用这个接口中的方法和实现接口中的方法只能通过子类来实现。
public interface Person{
void Run();
void Swim();
}
class Student implements Person{
public void Run(){
System.out.println("学生正在跑步");
}
public void Swim(){
System.out.println("学生正在游泳");
}
}
public class Test{
public static void main(String args[]){
Student student = new Student();
student.Run();
student.Swim();
}
}
3.5.在接口中只能有抽象方法的原则直到jdk8中出现的default打破了这一原则,此时有学者可能又会思考,这个原则都能被打破,那么这个接口也不是啥密不透风的墙嘛,那么会不会在接口中出现变量呢?嚯,我跟你说,还真就能出现,还真能有,咋们在接口中是这样定义的,接口中的变量会被隐式指定为public static final变量,就是跟前面声明方法的时候一样,不管前面加不加这个修饰符,最后都会被指定为这个public static final变量,并且在定义这个变量的同时一定要赋值。
public interface Person{
//此时是可以直接用接口名访问的
int brand = 30;//默认为 public static final
void Run();
}
public class Test{
public static void main(String[] args){
System.out.println(Person.brand);
}
}
注意:上面的代码大家可能也感受到了,就是接口中定义的方法,在一个类中实现一个接口的时候,这个类中都重写了这个接口中的方法。记住,接口出现就是为了被实现被继承,并且在某个类实现接口的时候必须重写这个接口中的所有抽象方法(在重写方法的时候方法前面必须加上public)--这是规定也是接口存在的意义所在。作者在学习的时候也想到,我用一个类实现了这个接口但是我不想重写这个接口中的方法行不行?当然可以,只需要把那个实现接口的类变成抽象类就行,但是如果这个类被其他的普通类继承后,那么这个普通类就必须重写接口中所有的抽象方法。最后一点就是接口方法必须是public修饰的。
4.接口在实践中常见的问题?
4.1.我们知道类和类之间是单继承关系,没办法实现多继承,这个时候接口的多实现优点的出现就能弥补java中单继承的缺陷。
比如:定义一个图形类,这个类中可以包含任何图形所具备的基础特点,然后出现了一个三角形,它只用继承这个图形类就能实现三角形的基础特点,那么我们该如何实现三角形的独有特点?此时我们就可以声明定义一个或多个接口,在接口中声明你要添加新功能的方法,然后通过类去实现这样的一个接口,在类中重写你要的新功能就能得到三角形独有的特点。(为什么不在图形类中添加三角形的特点,在接口的意义中已经做出解释,这里不在讲解)。
public interface Test{
void draw();
}
public interface Test2{
void draw2();
}
public class Figure{
System.out.println("这是一个图形类");
}
class Tragine extends Figure implements Test,Test2{
public void draw(){
System.out.println("第一个Test中的draw");
}
public void draw2(){
System.out.println("第二个Test2中的draw2");
}
}
此时此刻通过接口的多实现优点就很好的弥补了java中单继承的缺陷。
那在接口中还有没有其他方法解决单继承的问题了,还是有的,在刚刚的例子中是通过接口的多实现优点解决的单继承问题!其实在接口和接口之间是可以多继承的(也是用extends继承),此时我们的学者可以思考一下,我们是如何用接口间的多继承解决问题的(下面的代码举例会给出问题的答案!)。
public interface Person{
void draw();//画画
}
public interface IRunning{
void run();//跑步
}
interface Student extends Person,IRunning{
System.out.println("此时此刻是不需要重写实现的接口中的方法的");
}
class Students implements Student{
System.out.println("此时此刻就需要重写上面所有的接口方法,就能解决单继承问题!");
}
5.接口的应用举例?
创建一个名为Test项目,然后Test中创建一个TestDemo四边形类,然后在类中再创建两个TestDemo1类 和 TestDemo2 类 继承这个TestDemo类,最后创建一个drawTest接口,并使前面的TestDemo1,TestDemo2实现这个接口,最后在主方法中最后调用这个接口中的draw()方法。
public interface drawTest{
void draw();//实现接口方法
}
class TestDemo1 extends TestDemo implements drawTest{
public void draw(){
System.out.println("平行四边形.draw");
}
public void doAnyThing(){
System.out.println("定义四边形类");
}
}
class TestDemo2 extends TestDemo implements drawTest{
public void draw(){
System.out.println("正方形.draw");//实现自己独有的功能
}
public void doAnyThing(){
System.out.println("定义四边形类");//四边形都有的基础功能
}
}
class TestDemo {
public void doAnyThing(){
System.out.println("定义四边形类");
}
}
public class Test{
public static void main(String[] args){
//接口也是可以进行向上转型的操作的呦!
drawTest[] d = {new TestDemo1,new TestDemo2};
for(int i = 0; i < d.length; i++){
d[i].draw();
}
}
}
思考:通过这个例子,你有什么收获?
6.接口的小总结?
1.使用interface来定义接口。
2.接口当中的方法是没有具体实现的,并且在接口中默认是抽象方法,除了在jdk8开始允许使用default来修饰你要有具体实现的方法外,还有一个变量默认为public static final的变量,并且在定义的时候必须赋值。
3.接口是不能被实例化的,,接口和类之间是通过implements 关键字实现的。如果要调用接口中的方法必须通过类来实现这个接口,用类的实例化来调用这个接口中的方法。当然在类中,必须重写接口中所有的抽象方法,重写方法的时候前面一定要加上public(原因在类与继承中有描述,这里不做解释)。
4.在接口中是不能有构造方法和普通方法的,除了jdk8开始的default修饰的方法。
5.一个类可以实现一个或多个接口吗,使用 implements 用逗号隔开。(解决java中单继承缺陷)。
6.如果你不想实现接口中的方法。那么就把这个类定义为抽象类。但是如果这个类 被其他类继承 那么必须重写。
7.(扩展问题):接口和抽象类的相同点和不同点?
我们在以上的学习中,学完了抽象类和接口的应用,可能会发现两者有很多的相同点当然也有不同点,此刻我们来一个两者的比较总结:
相同点:
1.都是为了被继承。
2.都不能被实例化。
3.都可以包含方法声明。
4.子类必须重写被继承接口或抽象类中抽象方法。
不同点:
1.关键字不同
抽象类定义的时候是用abstract class 关键字,而定义接口的时候是用interface关键字。
继承抽象类的关键字为extends,实现接口的关键字为imples。
2.抽象类中可以有抽象方法也可以有其他的构造方法和普通方法,而在接口中只能有抽象方法(jdk8中用default修饰的方法是特列)。
3.在抽象类中增加方法可能不会影响子类,但是在接口中增加方法一定会影响子类(子类必须重写接口中的方法)。
4.接口中的变量只能是默认的public static final 变量,而在抽象类中可以是普通变量。
5.抽象类中的修饰符可以有public,protected和default,而在接口中只能有public(jdk8中出现了default修饰符)。