一、接口的思想(接口的第二个做用)(掌握) java
二、抽象类和接口的区别(了解)编程
三、多态技术(掌握) api
四、Object类概述(掌握) app
接口的第一个做用:给事物体系增长额外功能(扩展功能)。函数
接口的第二个做用:给事物双方定义规则。一方使用规则,另外一方实现规则。工具
说明:笔记本就是在使用这个规则,外围设备(鼠标、键盘等)在实现这个规则。学习
接口和抽象类都是描述事物的共性行为,而且描述的行为通常都是抽象的。须要子类或实现类对这些行为进行实现或复写。测试
接口和抽象类的区别:this
一、接口中只能定义抽象方法;抽象类中除了具备抽象方法外,还能够定义其它方法;spa
二、接口能够多实现;而抽象类只能单一继承;
三、接口用来描述事物扩展功能(额外功能);抽象类用来描述事物的共性内容(描述不清楚);
四、接口中没有构造函数;抽象类中具备构造函数;
抽象类和接口的区别代码体现以下:
//定义接口
interface Inter
{
void show();
//接口中只能有抽象方法
/*void test()
{
System.out.println("test");
}*/
}
interface InterA
{
void show();
}
//定义一个类来实现接口
class InterfaceImpl implements Inter,InterA//接口能够多实现
{
public void show()
{
System.out.println("接口show");
}
}
//定义一个抽象类
abstract class Abs
{
//抽象类中除了定义抽象方法还能够定义其余方法
void demo()
{
System.out.println("demo");
}
//抽象类中能够有构造函数,接口中没有
Abs()
{
System.out.println("抽象类中的构造函数");
}
}
class AbstractDemo extends Abs
{
}
class AbstractAndInter
{
public static void main(String[] args)
{
InterfaceImpl ip=new InterfaceImpl();
ip.show();
AbstractDemo abs=new AbstractDemo();
}
}
面向对象语言三大特征:封装、继承和多态。
多态:表示的是一个事物的多种表现形态。同一个事物,以不一样的形态表现出来。
多态来源于生活,在生活中咱们常常会对某一类事物使用它的共性统称来表示某个具体的事物,这时这个具体的事物就以其余的形式展现出来。
苹果:说苹果,说水果。
狗:说狗,说动物。
猫:说猫,说动物。
在Java中的多态代码体现:
使用父类的引用,表示本身的子类对象。
Cat c = new Cat(); 使用猫类型表示本身,这里不会发生多态现象
Animal a = new Cat(); 使用动物的类型再表示猫,这时就发生的多态的现象。
在Java中要使用多态技术:
前提:必需要有继承/实现;
好处:能够经过父类统一管理子类;
多态技术在java中的代码体现:
//演示多态技术
abstract class Animal
{
abstract void eat();
void show()
{
System.out.println("show run .....");
}
void show2()
{
System.out.println("show2 run .....");
}
}
class Cat extends Animal
{
void eat()
{
System.out.println("猫吃鱼");
}
}
class Dog extends Animal
{
void eat()
{
System.out.println("狗啃骨头");
}
}
class DuoTaiDemo
{
public static void main(String[] args)
{
Cat c = new Cat();
demo(c);
Dog d = new Dog();
demo(d);
}
/*
在调用方法的时候发生了多态的现象
Animal a = new Cat(); 这里发生了多态
Animal a = new Dog(); 这里也是多态
Dog a=new Animal();子类引用是不能够指向父类对象的
咱们在使用多态的时候,永远只能使用父类的类型接受子类的对象,而不能使用
子类的类型接受父类的对象。
*/
public static void demo( Animal a )
{
a.eat();
a.show();
a.show2();
}
}
注意:咱们在使用多态的时候,永远只能使用父类的类型接受子类的对象,而不能使用子类的类型接受父类的对象。
//演示多态弊端
abstract class Animal
{
abstract void eat();
}
class Cat extends Animal
{
void eat()
{
System.out.println("猫吃鱼");
}
//猫有本身的特有行为 抓老鼠
void catchMouse()
{
System.out.println("猫抓老鼠");
}
}
class Dog extends Animal
{
void eat()
{
System.out.println("狗啃骨头");
}
//狗也有本身的行为 看家
void lookHome()
{
System.out.println("狗看家");
}
}
class DuoTaiDemo2
{
public static void main(String[] args)
{
Cat c = new Cat();
c.eat();
c.catchMouse();
Dog d = new Dog();
d.eat();
d.lookHome();
//使用多态调用方法
Animal a = new Dog();
a.eat();
a.lookHome();
}
}
多态的弊端:
把一个子类类型提高成了父类的类型,那么在程序编译的过程当中,编译不会考虑具体是哪一个子类类型,而只会根据当前的父类类型去操做,经过父类的引用在调用方法的时候,只会去父类类型所属的类中找有没有这些成员,
若是有编译经过,若是没有编译失败。
多态弊端总结:
在使用多态技术的时候,程序在编译的时候,使用多态调用成员(变量和函数),要求被调用的成员在父类中必定要存在,若是父类中没有编译就会失败。(不能使用子类特有功能或者属性)
注意:只要有多态的地方,必定发生类型的提高(确定是把子类对象使用父类类型在表示)。
在使用多态时,存在一个弊端:不能使用子类中特有的功能(函数)。
若是在多态中,必需要使用子类特有的功能,须要在多态操做时进行类型的转换。
复习下以前学习过的类型转换:
自动类型提高 例: byte b=10; int num=b;
强制类型转换 例: double d=3.14; int n=(int)d;
Animal an = new Dog();
Animal是父类类型(父引用类型) an是父引用 new Dog()是子类对象
在以上代码中,已存在了类型的转换(向上转型):父类 父引用=new子类();
若是必定要在父类引用中使用子类对象特有的功能,就须要向下转型(大类型向下转换):
说明:子类对象中特定的功能只能子类对象本身调用。
若是已经发生多态现象,可是咱们还想调用子类的特有属性或者行为,这时须要使用强制类型转换,把当前父类类型转成具体的子类类型。
多态中的类型转换有两种:
1)向上转型(隐式的类型提高) 父引用指向子类对象 例:Animal an = new Dog();
2)向下转型(强制类型转换或者把父类类型转成子类类型) 把父引用强制转为子类引用 例:Dog d=(Dog) an;
强制类型转换格式:子类类型 子类引用名=(子类类型)父类引用名;
多态类型转换的代码体现:
abstract class Animal
{
abstract void eat();
}
class Cat extends Animal
{
void eat()
{
System.out.println("猫吃鱼");
}
//猫有本身的特有行为 抓老鼠
void catchMouse()
{
System.out.println("猫抓老鼠");
}
}
class Dog extends Animal
{
void eat()
{
System.out.println("狗啃骨头");
}
//狗也有本身的行为 看家
void lookHome()
{
System.out.println("狗看家");
}
}
class DuoTaiDemo3
{
public static void main(String[] args)
{
Cat c = new Cat();
c.eat();
c.catchMouse();
Dog d = new Dog();
d.eat();
d.lookHome();
//使用多态调用方法
Animal a = new Dog();
a.eat();
/*
若是已经发生多态现象,可是咱们还想调用子类的特有属性或者行为,这时须要使用
强制类型转换,把当前父类类型转成具体的子类类型。
在多态中的类型转换问题:
一、隐式的类型提高。只要有多态就会发生类型提高(向上转型)。
二、把父类类型转成子类类型(强制类型转换,向下转型)。
何时使用向下转型:
只要在程序中咱们须要使用子类的特有属性或行为(方法、函数)的时候,才会使用向下转型。
*/
//(Dog)(a).lookHome();
Dog dd = (Dog)a; //多态的转型
dd.lookHome();
注意:
1)何时使用向下转型:
只要在程序中咱们须要使用子类的特有属性或行为(方法、函数)的时候,才会使用向下转型。
在多态类型转换时常常会发生一个异常错误:ClassCastException(类型转换异常)。
多态类型转换常见异常代码演示:
abstract class Animal
{
abstract void eat();
}
class Cat extends Animal
{
void eat()
{
System.out.println("猫吃鱼");
}
//猫有本身的特有行为 抓老鼠
void catchMouse()
{
System.out.println("猫抓老鼠");
}
}
class Dog extends Animal
{
void eat()
{
System.out.println("狗啃骨头");
}
//狗也有本身的行为 看家
void lookHome()
{
System.out.println("狗看家");
}
}
class DuoTaiDemo3
{
public static void main(String[] args)
{
Animal a= new Cat();
demo(a);//传递的是猫的对象
}
public static void demo( Animal a )
{
a.eat();
//把Animal类型的a转成 Dog类的d
Dog d=(Dog)a;//将传递过来的猫的对象强制转换为狗是不能够的,会发生转换异常
/*
向下转型有风险,使用需谨慎。
在Java中要使用向下转型,必须先作类型的判断,而后在转型
Java中的类型判断 须要使用关键字 instanceof
格式:
被转的引用变量名 instanceof 被转成的类型
若是引用变量所在的那个对象 和被转成的类型一致,这个表达式返回的是true,不然是false
在多态中使用转型的时候,必定要判断,防止类型转换异常的发生:
若是在程序发生ClassCastException,必定是把不是这种类型的对象转成了这种类型。
*/
if( a instanceof Dog )
{
Dog d = (Dog)a;
d.lookHome();
}
else if( a instanceof Cat )
{
Cat c = (Cat)a;
c.catchMouse();
}
}
}
向下转型有风险,使用需谨慎。在Java中要使用向下转型,必须先作类型的判断,而后在转型Java中的类型判断 须要使用关键字 instanceof。
格式:
被转的引用变量名 instanceof 被转成的类型
若是引用变量所在的那个对象 和被转成的类型一致,这个表达式返回的是true,不然是false。
在多态中使用转型的时候,必定要判断,防止类型转换异常的发生:
若是在程序发生ClassCastException,必定是把不是这种类型的对象转成了这种类型。
总结:
只要有多态,就会有类型的转换。
把子类对象赋值给父类的引用,这时发生了向上的转型(隐式类型转换)。
若是咱们须要使用子类的特有行为或属性,这时必须向下转型,须要把父类的引用转成具体所指的那个对象的类型。
在向下转型的时候必定要作类型的判断,防止ClassCastException异常的发生。
判断格式:
if( 父类引用变量名 instanceOf 子类对象所属的类名 )
{
进行转换。
}
学习多态中的成员使用规律:须要掌握的是以多态形式使用成员,须要考虑程序的编译和运行2个阶段。
3.6.1多态调用成员变量
在使用多态时,子父类中存在相同的成员变量:
结论:
在多态中,使用父类的引用(f)访问成员变量,子父类中存在如出一辙的成员变量时:
1)代码在编译的时期(javac 源文件):须要查看父类中有没有这个成员变量,若是有,编译经过,没有编译失败。
2)编译经过的前提下,运行(java 类文件)时期:这时操做的依然是父类中的成员变量。
记住:多态时,子父类中存在如出一辙的成员变量时,引用变量,编译运行都看引用类(父类)中的变量。(编译时以等号左边做为参考,运行时也是以等号左边做为参考)
注意:若是发生多态时,只要是在其余类中使用成员变量,那么这个成员变量必须存在于父类中,不管子类中是否含有对应的成员变量,若是父类中没有成员变量,那么编译和运行都不会经过,和子类中是否含有成员变量没有关系。
3.6.2多态调用非静态成员函数
在多态中,使用父类引用调用成员函数的时候,通常函数都复写存在。
在使用多态时,子父类中存在如出一辙的成员方法时:
|
结论:
在多态中,使用父类的引用(f)调用函数的时候,子父类中存在如出一辙的成员方法时:
1)代码在编译的时期(javac 源文件):要看父类中有没有这个函数,有,编译经过,没有编译失败。
2)编译经过的前提下,运行(java 类文件)时期:运行的是子类中复写父类以后的那个函数。若是没有复写,运行的确定仍是父类的函数。
记住:多态时,子父类中存在如出一辙的成员方法时,编译时以等号左边做为参考,运行时是以等号右边做为参考。
3.6.3多态调用静态成员函数
在使用多态时,子父类中存在如出一辙的静态方法时:
静态的成员是随着类的加载而存在,和建立的对象没有任何关系,只跟类有关系。在java中,使用对象去调用静态成员,底层JVM仍是会以对象所属的类型(类)去调用静态成员。所以使用多态调用静态函数的时候,编译运行都要看父类中的函数。
结论:
在使用多态时,子父类中存在如出一辙的静态方法时:
编译时期是以等号左边做为参考,运行时期也是以等号左边做为参考。
也就是说,在使用多态时,子父类中存在如出一辙的静态方法时,与子类是否存在静态函数没有关系,只和父类中有关系。
总结多态中成员使用规律:
成员变量和静态成员函数,编译运行都看左边(父类中的)。只有非静态成员函数,编译看父类,运行看子类对象。
练习的目的:须要掌握多态中,自始至终只有子类对象存在,没有父类的对象,而且把子类对象交给父类的引用在使用。
练习黑旋风和黑旋风老爸的故事。
黑旋风:
讲课(){}
看电影(){}
黑旋风老爸:
讲课(){}
钓鱼(){}
//多态练习
class Dad
{
void teach()
{
System.out.println("黑旋风老爸讲论语");
}
void fish()
{
System.out.println("黑旋风老爸钓鱼");
}
}
class Hxf extends Dad
{
void teach()
{
System.out.println("黑旋风讲Java");
}
void lookFilm()
{
System.out.println("黑旋风在看速7");
}
}
class DuoTaiTest
{
public static void main(String[] args)
{
/*
Hxf hxf = new Hxf ();
hxf .teach();
hxf .fish();
*/
Dad dad = new Hxf (); //多态
dad .teach();
dad .fish();
//dad.lookFilm();//编译报错,由于父类中没有lookFilm这个函数
/*
如今我就想调用子类中特有的函数,怎么办?
咱们这里须要使用强制类型转换,将父类转换为子类
*/
Hxf hxf = (Hxf)dad; //黑旋风卸妆 向下转型
hxf .lookFilm();
}
}
在学习面向对象编程思想,遇到需求时,先去找有没有解决问题的功能存在。这些解决问题的功能一般是封装在类中(功能类),使用这些功能类基本能够解决开发中大部分的问题(例:折半查找、选择排序等)。
问题:这些解决问题的功能类都在哪里?
在java设计时,已经提供了不少解决问题的封装类。这些解决问题的封装类,咱们统称为:API
在开发中,只要去相应的包(文件夹)中去找对应的封装类就能够解决问题。
API:application programming interface。应用程序接口。咱们这里一般把api简称为帮助文档。
想要使用java提供的解决各类问题的API,就须要先学会如何查阅API文档。
使用"索引"查找相应的信息
以下图操做,点击选项,选择显示标签
点击完显示标签后,会出现以下图所示界面:
而后点击索引,会出现以下图所示的界面:
在查找框里输入要查找的类或者接口便可。
在搜索框里输入要查找的类,选中并双击或者回车。
在开发中,除了查阅API之外,还常常会查看JDK的源代码,帮助解决开发中的问题。
在安装JDK时,随着JDK版本的安装,在JDK安装目录也存在一个当前版本的JDK源码文件
查看源代码的步骤:(前提:须要知道要查找的功能类属于哪一个包)
在全部类中的构造函数中有个隐式的super语句,找父类。若是一个类没有显示指定它的父类,那么这个类的父类默认就是Object类。Object类的构造函数中是没有隐式的super的。
经过API的查阅,能够获得:
一、Object是java提供的功能类(API中的类)和开发人员本身书写的类的父类;
二、由于全部的类都继承了Object类,因此继承了Object类的子类可使用Ojbect类中的功能(函数);
疑问:既然本身定义的类也要继承Object类,那为何在代码中没有显式书写继承Object?
Object类属于java.lang包下。而java.lang包会被JVM在运行时自动加载,继承了Object的子类也不须要显式书写,JVM会自动为书写的类添加继承。
Object类中的经常使用函数:
equals 方法 toString 方法
需求:判断学生是否为同龄人
/*
判断两个学生是不是同龄人
*/
//定义一个学生类
class Student
{
//属性
String name;
int age;
//定义构造函数给属性初始化值
Student(String name,int age)
{
this.name=name;
this.age=age;
}
/*
定义一个函数根据外界传递过来的值比较年龄是否相等,
使用return关键字将比较的结果返回给调用者
Student a=new Student("技导",18)
由于compareAge函数是s对象调用的,因此在这个函数中的隐式变量this
记录着s对象的堆内存地址名
*/
public boolean compareAge(Student a)
{
/*
this.age表示黑旋风的年龄17
a.age表示技导的年龄18
*/
return this.age==a.age;
}
}
class ObjectDemo1
{
public static void main(String[] args)
{
/*
建立两个对象
下面的两个对象表示在对空间中开辟两个不一样的空间
一个空间叫作s,另外一个空间叫作s1
*/
Student s=new Student("黑旋风",17);
Student s1=new Student("技导",17);
//使用黑旋风的对象s调用compareAge函数
//使用flag来接受返回回来的值
boolean flag=s.compareAge(s1);
/*
若是返回回来的值是true,说明是同龄人
若是返回回来的值是false,说明不是同龄人
*/
if(flag==true)
{
System.out.println("是同龄人");
}else
{
System.out.println("不是同龄人");
}
}
}
使用以上方式能够解决问题。
面向对象:遇到需求时,先去找有没有存在已经解决问题的功能(功能是封装在类中)。
有,就直接使用封装了功能的功能类解决问题。
以上需求中,是须要解决判断是否为同龄人的功能。(其实就是一个判断是否相等的功能)
首先,去找java API中是否有比较功能。
问题:Student类中不具有比较功能,可是,Student类继承了Object类,因此能够去Object类中找是否存在解决问题的功能
Object类中的功能:
使用Object类中的eqauls函数解决需求中的问题:
以上程序运行结果不正确。
分析:为何使用Object类中的equals功能会存在结果不正确呢?
查看Object类中的equals功能的源代码
上述代码中的Object类中的this 表示的是调用这个equals函数的那个对象,obj是调用equals方法时传递进来的那个对象,而this中保存的是对象的内存地址,obj中接受的也是传递进来的那个对象内存地址。因此这里使用== ,实际上是在比较2个对象的内存地址是否相等。(就是堆内存中的地址)
结论:Object类中的equals方法中,比较的是堆中的地址是否相等
而咱们真正在开发中要比较2个对象是否相等,不该该去比较内存地址,而应该比较的是对象中的数据是否相同。单单使用Object类中的equals功能,并不能直接解决咱们需求中的问题。遇到这种状况,在开发中的作法是:重写Object类中的equals函数,所以全部的程序中都应该复写Object类中的equals。
/*
判断两个学生是不是同龄人
*/
//定义一个学生类
class Student
{
//属性
String name;
int age;
//定义构造函数给属性初始化值
Student(String name,int age)
{
this.name=name;
this.age=age;
}
//重写Object类中的equals函数(重写:和父类中的方法如出一辙)
public boolean equals(Object obj) {
/*
由于这里发生了多态,因此不能使用父类的对象obj调用父类中不存在的属性age,
因此会报错。因此咱们应该使用子类Student对象来调用子类中的属性age
而这里obj是父类对象,咱们须要使用向下转型将父类对象obj转换为子类对象
由于发生向下类型转换,为了防止发生转换异常,因此咱们要判断子类对象类型
*/
Student s=null;
if(obj instanceof Student)
{
s=(Student)obj;
}
return this.age==s.age;
}
}
class ObjectDemo2
{
public static void main(String[] args)
{
/*
建立两个对象
下面的两个对象表示在对空间中开辟两个不一样的空间
一个空间叫作s,另外一个空间叫作s1
*/
Student s=new Student("黑旋风",17);
Student s1=new Student("技导",17);
//使用黑旋风的对象s调用compareAge函数
//使用flag来接受返回回来的值
//boolean flag=s.compareAge(s1);
/*
public boolean equals(Object obj) {
这里的this记录着调用这个方法的对象s的堆中内存地址名
obj表示传递进来的参数对象s1,Object obj=new Student("技导",17);这里发生多态
obj里面存放的也是s1中的堆中内存地址
s和s1的堆中内存地址名不一样
return (this == obj);
}
public boolean compareAge(Student a)
{
return this.age==a.age;
}
*/
boolean flag=s.equals(s1);
System.out.println(flag);
/*
若是返回回来的值是true,说明是同龄人
若是返回回来的值是false,说明不是同龄人
*/
if(flag==true)
{
System.out.println("是同龄人");
}else
{
System.out.println("不是同龄人");
}
}
}
总结:
关系运算中的==和equals的区别:
需求:输出Student类的具体信息,也就是根据输出Student类的对象来输出Student的具体名字和姓名。
|
以上程序的运行结果,不符合需求。要求是想要输出学生类中的具体信息,好比经过打印对象的名字stu,咱们但愿打印出具体的stu对象所拥有的名字和年龄,而打印一串地址名在开发中没有什么太大意义。
问题:为何输出stu时,显示的结果:Student@7ea06d25?为何输出的是一个引用地址而不是咱们想要的对象的属性的值呢?咱们应该怎么作才能打印出属性的值而不是打印一串地址的值呢?
这里咱们须要借助Object类中的toString方法来解决,toString是一个方法,它须要对象来调用,toString的意思是将调用它的对象转换成字符串形式。
咱们在打印对象的时候也能够按照以下方法去作:
System.out.println(stu.toString());打印的结果和咱们写System.out.println(stu);是同样的。
在上述打印语句中println()的方法中打印对象stu的时候,若是不加 .toString()方法,在println()的方法中默认也是使用对象stu调用toString()方法,因此在开发中写与不写toString()方法是同样的。
根据以上分析咱们想要创建自定义对象的表现形式,咱们须要覆盖超类(全部类的父类)中的toString()方法就能够了。
在Student类中能够重写toString方法,让程序输出学生类的具体信息,代码实现以下:
小结:
在开发中,若是子类继承父类时,父类中已经存在了解决问题的功能,可是父类中的功能并不能知足子类的需求,这时,子类就须要重写(覆盖)父类中的方法。