1、多态的概念
1、生活中的多态:同样的行为在不同对象上产生的效果是不一样的。
2、程序上的多态:以为这允许不同类的对象对同一消息作出不同的响应。
3、多态分为:编译时多态和运行时多态。
4、多态的必要条件:(1)满足继承关系。(2)父类引用指向子类对象。
5、我们在代码中写一段很简单的代码:
package com.imooc.animal;
public class Animal {
private String name; //昵称
private int month;//月份
public Animal(){
}
public Animal(String name,int month){
this.name=name;
this.month=month;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
protected void eat() {
System.out.println("动物都有吃东西的能力");
}
}
然后写两个子类分别测试它们。
package com.imooc.animal;
public class Cat extends Animal{
private double weight;//体重
public Cat() {
}
public Cat(String name,int month,double weight) {
super(name,month);
this.weight=weight;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public void run() {
System.out.println("小猫快乐的奔跑");
}
@Override
protected void eat() {
System.out.println("猫吃鱼");
}
}
再写一个子类狗:
package com.imooc.animal;
public class Dog extends Animal {
private String sex;//性别
public Dog() {
}
public Dog(String name,int month,String sex) {
this.setMonth(month);
this.setName(name);
this.setSex(sex);
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public void sleep() {
System.out.println("小狗有午睡的习惯");
}
@Override
protected void eat() {
// TODO Auto-generated method stub
System.out.println("小狗吃肉");
}
}
2、向上转型
1、一个父类的引用指向子类的具体对象,称之为向上转型。可以调用子类重写父类的方法和父类派生的方法,子类特有的方法无法被调用。比如说我们下面的代码
//向上转型,隐式转型,自动转型吗,小型向大类转型(孩子向父亲的方向转型)
Animal two=new Cat();
two.eat();
two.run();// 报错,说在父类中没有定义run方法
对于two这个父类动物的引用指向了子类猫的对象,这two引用只能调用父类动物中的方法和子类当中重写父类的eat方法,对于子类独有的run方法是调用不了的。
3、向下转型
1、子类引用指向父类的对象,此处必须有强制类型转换,可以调用子类特有的方法。
Animal one=new Animal();
//向上转型,隐式转型,自动转型吗,小型向大类转型(孩子向父亲的方向转型)
Animal two=new Cat();
//向下转型,强制类型转换
Cat temp=(Cat)two;
temp.eat();
Dog temp1=(Dog)two;
temp1.eat();
上面这段代码的运行结果是:
猫吃鱼
Exception in thread "main" java.lang.ClassCastException: com.imooc.animal.Cat cannot be cast to com.imooc.animal.Dog
at com.imooc.animal.Test.main(Test.java:11)
为什么,因为two虽然是父类的引用,但一开始就是指向Cat子类的对象,所以强制转换为Cat是没有问题的,相当于还原了。但是强制转化为Dog的类型就不行,Dog和Cat虽然附属同一个父类,但是两者原本不是一个东西。
2、所以向下转型不是随意的,父类引用最开始就要指向将要转化的类型的对象才能转化。
4、instanceof运算符
1、所以在强制转化的时候你必须先要看看,是否能满足转化条件。但是当代码有很多很复杂的时候,我们不能一个一个去找吧?我们需要借助instanceof这运算符去帮助我们判断,对象和类的类型是否一致。也就是左边的对象是否是右边类型的实例。是就返回true,不是返回false。
2、所以我们在强制类型转化的时候是使用if判断,将instanceof运算符写进if判断语句中:
//向下转型,强制类型转换
if(two instanceof Cat) {
Cat temp=(Cat)two;
temp.eat();
}
if(two instanceof Dog) {
Dog temp=(Dog)two;
temp.eat();
}
这样我们的判断条件满足才能去转化。instanceof可以提高向下转型的安全性
3、通过上面的例子我们很容易认为instanceof就是判断对象属于什么类型,但是下面的代码很让你产生疑惑:
if(two instanceof Animal) {
System.out.println("Animal");
}
if(two instanceof Object) {
System.out.println("Object");
}
这段代码也能运行,那意思是two属于Cat、Animal和Object三者类型么?并不是,instanceof是判读左侧对象是否满足右侧类型的特征。不能简单的理解为属于和不属于。
5、类型转化中的重写函数问题:
1、在父类中有这样的代码:
public static void say(){
//.....
}
那么在子类当中我们这样写:这样就是错的,父类不允许子类重写static方法
@Override
public static void say(){
//....
}
但是把@Override去掉这段代码却是正确的,所以不使用@Override,虽然在父类和子类都被static修饰,但是不构成重写关系;
2、所以对于父类引用指向子类对象的实例中,如下代码,只能调用到父类原有的静态方法,想调子类的还需向下转型;
Animal two=new Cat();
two.say(); //调用的是父类中的静态方法
Cat three=(Cat)two;
three.say(); //调用的是子类中的静态方法
6、现实的多态实例
1、对于主人喂动物可能喂狗,或者喂猫,我们会在喂养的函数参数中统一写父类,在里面具体判断这父类引用到底指向什么类型的对象
public void feed(Animal obj) {
if(obj instanceof Cat) {
Cat temp=(Cat)obj;
temp.eat();
temp.playBall();
}else if(obj instanceof Dog) {
Dog temp1=(Dog)obj;
temp1.eat();
temp1.sleep();
}
}
那实际上你在调用者函数,外部肯定要实例化一个猫或者狗的对象,作为参数传进来,那么传进来的时候就是一次向上转型,在函数中会使用instanceof的类型判断,再强制转化,这又是一次向下转型。
2、对于主人有没有空闲的时间去判断主人应该养猫还是养狗
//是父类Animal统一去接收返回值
public Animal raise(boolean isManyTime){
if(isManyTime){
return new Dog(); //时间充裕就返回一个狗的对象实例
}else{
return new Cat(); //时间紧迫就返回一个猫的对象实例
}
}
7、抽象类
1、使用 abstract 来修饰我们写的类就能将其定义是一个抽象的类。
2、抽象类是不能实例化具体对象,但是可以通过向上转型,使其指向子类实例:
public abstract class Animal{
//....
}
Animal animal=new Animal(); //错误,不能抽象类不能直接实例化对象
Animal animal=new Cat(); //正确,可以使用父类引用指向子类对象
8、抽象方法
1、使用abstract来修饰类方法,这个方法是不能拥有方法体的,而且子类必须重写这个方法。
//父类中的代码
public void abstract eat();
//子类中的代码
@Override
public void eat(){
//重写代码
}
2、子类中没有重写父类的方法,那除非子类也是抽象类,那子类的代码就只能是这样:
public abstract class Dog extends Animal{
//...
}
3、包含抽象方法的一定是抽象类,但是抽象类中可以没有抽象方法。
4、static、final、private这些修饰符是不能和abstract同时存在,因为前面三个修饰的方法不允许在子类中重写,而abstarct需要重写。
9、通过接口进行相同行为的关联
1、通过接口去描述相似的行为特征,从而建立关系,使用接口引用指向实现类的方式来描述不同类型与接口的具体表现;建立接口
package com.imooc.phoneinterface;
/**
* 具有照相能力的接口
* @author Administrator
*
*/
public interface IPhoto {
public void photo();
}
2、在具体类型里面去实现这个接口,使用implements关键字:
package com.imooc.phoneinterface;
public class Camera implements IPhoto{
@Override
public void photo() {
System.out.println("相机可以拍照");
}
public void photography() {
System.out.println("相机可以摄影");
}
}
3、测试:我们可以看到使用接口引用只能调用对象中重写接口的函数;就是说Camera类实现接口IPhoto,在类型中重写IPhoto中的抽象接口,那么使用IPhoto这个接口类型的引用,就只能调用类中重写接口的抽象函数,其他的调用不了,photography是Camera自己的函数,就调用不了。
package com.imooc.phoneinterface;
public class Test {
public static void main(String[] args) {
IPhoto ip=new Camera();
ip.photo();
ip.photography(); //这句代码报错
}
}
10、接口成员--抽象方法&常量
10.1关于抽象方法的一些约定
1、接口定义了某一批类所需要遵守的规范。
2、接口不关心这些类的内部数据,也不关心这些类里方法的实现细节,只规定这些类里必须提供的某些方法。
3、接口的名字通常以I打头表明是一个接口。
4、接口的修饰符通常是public或者默认的修饰符。
5、接口中抽象方法不用写abstract,但是访问修饰符就是public,你不写系统也按照public去实现的。
10.2、关于常量的一些约定
1、接口中会包含常量,默认会帮你加上public static final ;
2、可以通过接口名.常量的方式直接去访问接口中的常量,但是不能去修改常量,因为是final去修饰过的。
3、假如在类实现接口的过程中,在类中也有和接口中同名的常量,那么使用接口引用,调用的一定是接口中的值,使用类引用,就是类中的值。可以从下面代码的输出结果中去看到:
package com.imooc.phoneinterface;
/**
* 可以上网的接口
* @author Administrator
*
*/
public interface INet {
void network();
void connection();
int TEMP=20;
}
写一个Computer的类去实现这个接口:
package com.imooc.phoneinterface;
public class Computer implements INet{
public static final int TEMP=30;
@Override
public void network() {
System.out.println("电脑也可以上网");
}
@Override
public void connection() {
System.out.println("电脑也能连接");
}
}
当接口中TEMP和Computer中的TEMP都存在,如下所示,使用不同类型的引用,结果是不一样的。
package com.imooc.phoneinterface;
public class Test {
public static void main(String[] args) {
INet ip=new Computer();
System.out.println(ip.TEMP); //20
Computer iip=new Computer();
System.out.println(iip.TEMP); //30
}
}
11、接口成员--默认方法&静态方法
11.1默认方法
1、接口中有很多抽象函数,但是我们的类在实现接口时,有时只需要实现一些接口,按照我们之前说的类实现接口必须要重写接口中的所有抽象方法,但是由于默认方法的存在我们在类中可以不去重写不需要的抽象函数,接收接口中的默认方法就行了。
package com.imooc.phoneinterface;
/**
* 可以上网的接口
* @author Administrator
*
*/
public interface INet {
void network();
int TEMP=20;
//默认方法:实现这个接口的类如果不用去实现这个抽象函数,那么就会接受这个带方法体的默认方法
default void connect() {
System.out.print("我是接口中的默认连接");
}
}
在类中我们不去写connect这个抽象函数也不会报错:
package com.imooc.phoneinterface;
public class Computer implements INet{
public static final int TEMP=30;
@Override
public void network() {
System.out.println("电脑也可以上网");
}
}
11.2静态方法
1、静态方法在类中实现接口的时候也不用去写,但是静态方法和默认方法调用不一样。默认方法可以通过接口的引用去调用,静态方法只能通过 接口名.方法名 的方式去调用,如下所示:
public interface INet {
void network();
int TEMP=20;
//默认方法:实现这个接口的类如果不用去实现这个抽象函数,那么就会接受这个带方法体的默认方法
default void connect() {
System.out.println("我是接口中的默认连接");
}
//静态方法:只能通过接口名.方法名来调用
static void stop() {
System.out.println("我是接口中的静态方法");
}
}
正确使用两者的方式:
package com.imooc.phoneinterface;
public class Test {
public static void main(String[] args) {
INet ip=new Computer();
ip.connect();
INet.stop();
ip.stop(); //这种方法错误
}
}
2、接口中能写默认方法和静态方法的形式是在jdk1.8以后才能写的,版本低的就不能再接口中写着两个东西;
3、那么对于一些类它们不用实现默认方法,但是有些类还是要重写默认方法,那么怎么写?
如果你用Alt+/的方式去让系统自动帮你写,代码是这样子的;
@Override
public void connect() {
// TODO Auto-generated method stub
INet.super.connect();
}
为什么要使用INet.super.connect()?为什么不用INet.connect()?这里要注意:使用不用super的话,INet只能调用到接口中的静态方法和静态常量。也就说在前面我们写的例子中只能调到TEMP和stop;
4、默认方法可以在实现类中重写,并可以通过接口的引用调用;但是静态方法不可以在实现类中去重写,也只能通过 接口名.静态方法名
的方式去调用。
12、关于多接口中重名默认方法处理的解决方案
1、当我们一个类同时实现两个或多个接口,接口中存在同名的默认方法,我们不知道去重写哪个,解决方案:自己写一个。
//一个接口中有connect
public interface IPhoto {
public void photo();
default void connect() {
System.out.println("我是IPhoto中的connection方法");
}
}
//另一个接口中也有connect的默认方法
default void connect() {
System.out.println("我是接口中的默认连接");
}
//实现类中都惹不起,干脆自己写一个
public class Computer implements INet,IPhoto{
public void connect() {
// TODO Auto-generated method stub
INet.super.connect();
}
}
2、当我们的实现类,它的父类中、实现的接口中都有同名的方法,记住,调用子类的同名方法是继承父类的。所以你可以看到为什么声明一个类的时候,继承写在前面,接口实现写在后面,也是有先后顺序的。当然子类当中也能重写同名的方法。
13、关于多重接口名常量处理的解决方案
1、当我们一个类同时实现两个或多个接口,接口中存在同名的常量,在实现类中是可以分辨的,通过接口名.常量就能访问,但是如果子只写常量,那是分辨不出你到底用的哪个接口中的常量。
interface one{
static int x=11;
}
interface two{
final int x=22;
}
class Three{
public static final int x=33;
}
public class TestOne implements One,two{
public void test(){
System.out.println(x); // 错误,识别不出来
System.out.println(one.x); // 11
System.out.println(two.x); // 22
}
public static void main(String[] args){
new TestOne().test();
}
}
2、当我们的子类继承父类的时候,之前说同名方法是调用的父类的同名方法,但是常量这里,父类的常量就不占什么优势了。这个时候最好用谁的就写谁的名字,假如是子类自己的常量,那就在自己的类中定义一个常量,用父类的就用父类名.常量。
14、接口的继承
1、java中的接口可以实现继承,可以继承多个父接口。
2、在多个父接口中含有同名的默认方法,子接口用继承哪个?答案:哪个都不用,子接口自己写一个同名的默认方法。
15、内部类的概述
1、在java中,允许将一个类定义在一个类里面或者一个方法里面,这样的类称之为内部类。包含内部类的称之为外部类。
2、内部类的信息是要借助外部类去访问,提供了更好的封装性。
3、内部类的类的分类:成员内部类,静态内部类,方法内部类,匿名内部类。
16、成员内部类
1、成员内部类的定义:
package com.imooc.person;
public class Person {
public int age;
//通常会在外部类中去写一个获取内部类对象的方法
public Heart getHeart() {
return new Heart();
}
//成员内部类
class Heart{
public String bear() {
return age+"心脏在跳动";
}
}
}
2、成员内部类的实例化的三种方式:
package com.imooc.person;
public class PersonTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
Person lili=new Person();
lili.age=12;
//第一种实例化内部类的方式:new 外部类.new 内部类
Person.Heart myHeart=new Person().new Heart();
System.out.println(myHeart.bear());
//第二种实例化内部类的方式:外部类对象.new 内部类
myHeart=lili.new Heart();
System.out.println(myHeart.bear());
//获取内部类对象实例:外部类对象.获取方法
myHeart=lili.getHeart();
System.out.println(myHeart.bear());
}
}
3、内外部类同名变量访问方法:内部类就直接写变量名,使用外部类要使用 外部类名.this.变量名 的方式
return "外部类的同名变量访问"+Person.this.age+":内部类的同名变量访问"+age;
4、内部类在编译后的文件名称:外部类$内部类.class。比如说这样:Person$Heart.class。
5、对于内外部类的同名的函数,也可以通过 外部类名.this.方法名 的方法去调用外部内的同名方法。如下代码表示:
//外部类中:
public void eat() {
System.out.println("外部类的eat");
}
//内部类中:
public void weat() {
Person.this.eat();
}
17、静态内部类
1、静态内部类,是用static来修饰内部类。静态内部类也不需要依赖外部类对象,可以直接创建。
2、静态内部类可以直接访问外部类的静态成员,对于外部非静态成员,需要创建外部对象来访问非静态成员:
//静态内部类:
static class Heart{
public int age=33;
public void weat() {
eat(); //可以直接访问到外部类中的静态成员(这里的外部类中的eat是static修饰的)
}
public int bear() {
return new Person().age; //访问外部非静态成员必须要去实例化外部类的对象
}
}
3、静态内部类的实例化不需要依靠外部类的实例化对象
//第一种实例化内部类的方式:new 外部类.内部类
Person.Heart myHeart=new Person.Heart();
myHeart.weat();
//获取内部类对象实例:外部类对象.获取方法
myHeart=lili.getHeart();
myHeart.weat();
18、方法内部类
1、定义在外部类方法中的内部类,也成局部内部类。
2、方法内定义的规则同样适用于方法内部类:比如(1)方法内定义的局部变量只能在方法里使用,(2)方法内不能定义静态成员,(3)不能使用public、private、protected这些访问修饰符
3、方法内部类的定义:
package com.imooc.person;
public class Person {
public int age;
public Object getHeart(Person obj) {
class Heart{
public int age=33;
public void weat() {
eat();
}
public int bear() {
return obj.age;
}
}
return new Heart().bear(); //实际代码中我们都是将某个方法的结果返回出去,不经常写return new Heart();
}
static public void eat() {
System.out.println("外部类的eat");
}
}
4、测试我们写的方法内部类:
package com.imooc.person;
public class PersonTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
Person lili=new Person();
lili.age=12;
System.out.println(lili.getHeart(lili));
}
}
5、还有最后特别注意的是在编译的时候,这个编译的文件是什么:在上面的例子中是:Person$1Heart.class。和成员内部类不太一样的
19、匿名内部类
1、将类的定义和类的创建放在一起完成。
2、下面代码向我们展示了怎么写一个匿名内部类:首先创建一个人的抽象类,在类中有一个抽象方法。
package com.imooc.person;
public abstract class Person {
private String name;
public Person() {}
public String getName() {
return name;
}
public void setName(String name) {
this.name=name;
}
public abstract void read();
}
3、现在我们没有显示的去写一个类继承一个抽象类,但是我们现在就要使用这个抽象类的实例去调用read这个抽象方法,但是抽象类由不能实例化,怎么办?现在我们使用匿名内部类:我们使用 new Person(){//重写抽象方法} 这样的匿名类作为一个Person类型的对象放在getRead的参数化中。
package com.imooc.person;
public class PersonTest {
public static void main(String[] args) {
PersonTest test=new PersonTest();
//创建一个匿名内部类
test.getRead(new Person() {
{
//通过构造代码块的形式去初始化一些属性
}
@Override
public void read() {
System.out.println("男生喜欢看科幻小说");
}
});
}
public void getRead(Person person) {
person.read();
}
}
4、那么这样写的好处是什么?Person的继承类假如在整个程序里出现一次,你还要再写一个类,那么对内存和性能的损耗是大于匿名类的,所以我们这里只是为了临时创建一个抽象类的实例,以后也就用不到了。所以适用场景:(1)只用到类的一个实例,(2)类在定义后马上用到,(3)给类命名并不会导致代码更容易理解。
5、再去看看匿名类编译生成的文件,我们上面的例子在编译后生成的除了PersonTest.class这个文件还有那么匿名类生成的编译文件,叫做:PersonTest$1.class。因为没有名字,所以没有名字。
6、注意事项:(1)静态内部类无法通过public、private,abstract、static这些修饰符修饰。
(2)在匿名内部类中无法写构造函数,但是可以通过构造代码块来初始化一些必要的属性。
(3)不能出现静态成员。
(4)匿名内部类可以实现接口也能继承父类,但是不可兼得。