多态简介
概念:多态意味着允许不同类的对象对同一消息做出不同的响应
分类:多态分为编译时多态和运行时多态,一般我们说java的多态,就是指运行时多态
— 编译时多态,通过方法重载在设计时实现多态
— 运行时多态,程序运行时动态决定调用哪个方法
条件:满足继承关系,父类引用指向子类对象
实现:通过子类重写父类方法,实现多态
package com.test;
import com.animal.Animal;
import com.animal.Cat;
import com.animal.Dog;
public class Test {
public static void main(String[] args) {
Animal one = new Animal();
Animal two = new Cat();
Animal three = new Dog();
one.eat(); // 动物,会吃东西
two.eat(); // 猫,会吃东西
three.eat(); // 狗,会吃东西
}
}
通过这个例子可以看到,对象one,two,three虽然都是Animal类型,但是由于指向的对象的类型是不同的,导致eat方法执行不同的处理,这就是多态
向上转型
- 又称向上转型,隐式转型,自动转型
- 意思是父类引用指向子类实例
- 可以调用父类派生的方法
- 可以调用子类重写父类的方法,且执行的是子类重写后的处理
- 无法调用子类独有的方法
- 父类中的静态方法无法被子类重写,所有向上转型之后,只能调用到父类原有的静态方法
package com.test;
import com.animal.Animal;
import com.animal.Cat;
public class Test {
public static void main(String[] args) {
Animal one = new Animal();
Animal two = new Cat();
two.setAge(2); // setAge是父类派生的方法,可以调用
two.eat(); // eat是子类重写的方法,可以调用,且执行的是子类重写后的处理
// two.setWeight(17); // 报错,无法调用子类独有的方法
}
}
向下转型
- 意思是子类引用指向父类实例
- 需要进行强制类型转换
- 可以调用子类特有的方法
- 跟用子类直接实例化子类对象没有什么区别
package com.test;
import com.animal.Animal;
import com.animal.Cat;
public class Test {
public static void main(String[] args) {
Animal one = new Cat();
Cat two = (Cat) one;
two.eat(); // eat是子类重写的方法,可以调用,且执行的是子类重写后的处理
two.setWeight(12.0); // setWeight是子类特有的方法,可以调用
System.out.println(two.getWeight()); // getWeight是子类特有的方法,可以调用
// 下面这样是不行的,会编译报错
// Cat three = (Cat) new Animal();
}
}
instanceof
- 判断对象是否是某个类的实例,如果是返回true,反之返回false
- 如果 A instanceof B == ture,那么A instanceof B的父类,结果也是 true
- 常用于强制类型转换之前的类型判断
package com.imooc.test;
import com.imooc.animal.Animal;
import com.imooc.animal.Cat;
public class Test {
public static void main(String[] args) {
Animal one = new Cat();
if (one instanceof Cat) {
Cat two = (Cat) one;
two.eat();
two.setWeight(12.0);
System.out.println(two.getWeight());
}
// 条件成立
if (one instanceof Animal) {
System.out.println("Animal");
}
// 条件成立
if (one instanceof Object) {
System.out.println("Object");
}
}
}
抽象类和抽象方法
我们先来看一种场景,以下代码编译是不会出错的,因为父类的确有eat这个方法,但是这样做没有实际意义。
一般来说,子类(比如Cat)会重写eat方法,所以调用子类的eat方法才有实际意义。
// 定义父类 Animal
public class Animal {
private String name;
public Animal() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void eat() {
System.out.println("动物,会吃东西");
}
}
// 使用Animal类进行实例化
Animal pet = new Animal("花花",2);
pet.eat();
怎么才能在编码阶段就提示这种没有意义的做法呢,这就要用到抽象类里了,请继续往下看。
作用:Java中使用抽象类,可以限制实例化
应用场景:
某个父类只是知道其子类应该包含怎样的方法,但无法准确知道这些子类如何实现这些方法
使用规则:
- 使用abstract关键字定义抽象类
- 抽象类不能直接实例化,只能被继承,可以通过向上转型完成对象实例
- 使用sbstract关键字定义抽象方法,不需要具体实现
- 包含抽象方法的类是抽象类
- 抽象类中可以没有抽象方法
- static final private不能与abstract并存
// 定义抽象父类 Animal
public abstract class Animal {
private String name;
public Animal() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void eat(); // 抽象方法
}
// 使用Animal类进行实例化
// Animal pet = new Animal("花花",2); => 这样是会报错的
Animal pet = new Cat("花花",2); => 这样是这正确的用法
接口
有些类不能抽取出一个公共的父类,但是它们之间又具有一些相同的行为,怎么才能使它们关联起来?
在java中,根据行为建立它们之间的联系,就需要用到接口。
eclipse创建接口的方法:New => Interface
#一般接口的名字以I开头
比如:我们来看看怎样根据行为建立手机,智能手表,照相机之间的联系
智能手机拥有的行为:发短信,打电话,拍照
照相机拥有的行为 :拍照
拍照接口:IPhoto.java
package com.dev;
/**
* 具有照相能力的接口
* @author 57681
*
*/
public interface IPhoto {
public void photo();
}
照相机类:Camera.java
package com.dev;
public class Camera implements IPhoto{
@Override
public void photo() {
System.out.println("照相机可以拍照");
}
// public void photo() {
// System.out.println("照相机可以拍照");
// }
}
智能手机类:SmartPhone.java
package com.dev;
public class SmartPhone implements IPhoto {
public void message() {
System.out.println("智能手机可以发短信");
}
public void call() {
System.out.println("智能手机可以打电话");
}
// public void photo() {
// System.out.println("智能手机可以拍照");
// }
@Override
public void photo() {
System.out.println("智能手机可以拍照");
}
}
测试接口
package com.imooc;
import com.dev.Camera;
import com.dev.IPhoto;
import com.dev.SmartPhone;
public class PhotoTest {
public static void main(String[] args) {
IPhoto ip = new Camera();
ip.photo(); // 照相机可以拍照
ip = new SmartPhone();
ip.photo(); // 智能手机可以拍照
}
}
接口成员(抽象方法&常量)
- 接口定义了某一批类所需要遵守的规范
- 接口不关心这些类的内部数据,也不关心这些类里方法的实现细节,它只规定这些类里必须提供某些方法
接口:INet
package com.dev;
// 接口访问修饰符:public 默认
public interface INet {
// 接口类中的抽象方法可以不写abstract关键字
// 方法的访问修饰符默认public
void network();
void connection();
// 接口中可以包含常量,默认public static final
int TEMP = 20;
}
电脑类:Compute
#必须实现接口中所有的抽象方法
package com.dev;
public class Compute implements INet {
@Override
public void network() {
System.out.println("network");
}
@Override
public void connection() {
System.out.println("connection");
}
}
测试接口
package com.imooc;
import com.dev.Compute;
import com.dev.INet;
public class NetTest {
public static void main(String[] args) {
INet net = new Compute();
net.network(); // network
net.connection(); // connection
System.out.println(net.TEMP); // 20
}
}
接口成员(默认方法&静态方法)
接口:INet
package com.dev;
// 接口访问修饰符:public 默认
public interface INet {
// default:默认方法 可以带方法体 jdk1.8后新增
// 可以在实现类中重写,并可以通过接口的引用调用
default void start() {
System.out.println("我是接口中的默认方法");
}
// static:静态方法 可以带方法体 jdk1.8后新增
// 不可以在实现类中重写,可以通过接口名调用
static void stop() {
System.out.println("我是接口中的静态方法");
}
}
电脑类:Compute
package com.dev;
public class Compute implements INet {
@Override
public void start() {
INet.super.start(); // 调用接口中默认的方法
System.out.println("custom");
}
}
测试接口
package com.imooc;
import com.dev.Compute;
import com.dev.INet;
public class NetTest {
public static void main(String[] args) {
INet net = new Compute();
net.start(); // 我是接口中的默认方法 custome
INet.stop(); // 我是接口中的静态方法
System.out.println(net.TEMP); // 20
}
}
多接口中重名默认方法的解决方案
接口INet和接口IPhoto同时拥有默认方法start
接口INet
package com.dev;
// 接口访问修饰符:public 默认
public interface INet {
default void start() {
System.out.println("我是接口中的默认方法");
}
}
接口IPhoto
package com.dev;
/**
* 具有照相能力的接口
* @author 57681
*
*/
public interface IPhoto {
default void start() {
System.out.println("我是IPhoto接口中的默认方法");
}
}
电脑类:Compute
#实施接口INet和接口IPhoto
package com.dev;
public class Compute implements INet, IPhoto {
// 这里如果不定义自己的start方法,是会报错的
public void start() {
System.out.println("Compute中的默认方法");
}
}
注意:比如Compute类继承了一个父类,然后父类中有start方法,那么即使不定义自己的start方法,也是不会报错的。实例对象调用start方法的话,执行的就是父类的start方法。
测试接口
package com.imooc;
import com.dev.Compute;
import com.dev.INet;
import com.dev.IPhoto;
public class NetTest {
public static void main(String[] args) {
INet net = new Compute();
net.start(); // Compute中的默认方法
IPhoto photo = new Compute();
photo.start(); // Compute中的默认方法
}
}
多接口中重名常量的解决方案
测试结果:出错
原因分析:接口One和接口Two中有相同的常量x,FinalTest类中不知道该使用哪个,所以会出错
package com.imooc;
interface One{
static int x = 11;
}
interface Two{
static int x = 22;
}
class Three{
public static final int x = 33;
}
public class FinalTest implements One, Two {
public void test() {
System.out.println(x);
}
public static void main(String[] args) {
new FinalTest().test();
}
}
测试结果:出错
原因分析:跟重名方法不同,即使父类中存在重名常量,也不会使用父类的常量。在这里父类不好使。
package com.imooc;
interface One{
static int x = 11;
}
interface Two{
static int x = 22;
}
class Three{
public static final int x = 33;
}
public class FinalTest extends Three implements One, Two {
public void test() {
System.out.println(x);
}
public static void main(String[] args) {
new FinalTest().test();
}
}
测试结果:正确
package com.imooc;
interface One{
static int x = 11;
}
interface Two{
static int x = 22;
}
class Three{
public static final int x = 33;
}
public class FinalTest extends Three implements One, Two {
public static final int x = 44;
public void test() {
System.out.println(x);
}
public static void main(String[] args) {
new FinalTest().test();
}
}
接口的继承
IFather1.java
package com.dev;
public interface IFather1 {
void run();
}
IFather2.java
package com.dev;
public interface IFather2 {
void eat();
}
ISon.java:继承IFather1,IFather2
#子类只能继承一个父类,但是接口可以继承多个接口
package com.dev;
public interface ISon extends IFather1, IFather2 {
void fly();
}
Demo类:实现接口ISon,需要重写所有的方法
package com.dev;
public class Demo implements ISon {
@Override
public void run() {
// TODO Auto-generated method stub
}
@Override
public void eat() {
// TODO Auto-generated method stub
}
@Override
public void fly() {
// TODO Auto-generated method stub
}
}
内部类
内部类的定义:
- 在java中,可以将一个类定义在另一个类的里面或者一个方法里面,这样的类成为内部类
- 与之对应,包含内部类的类被成为外部类
内部类的好处:
因为内部类隐藏在外部类之内,更好的实现了数据隐藏。
内部类的分类:
成员内部类,静态内部类,方法内部类,匿名内部类
成员内部类(普通内部类)
Person.java
package com.people;
// 外部类
public class Person {
public int age;
public Heart getHeart() {
new Heart().temp = 12;
// temp; 无法识别
return new Heart();
}
public void eat() {
System.out.println("人会吃东西");
}
// 成员内部类
// 1. 内部类在外部使用时,无法直接实例化,需要借由外部类信息才能完成实例化
// 2. 内部类的访问修饰符,可以任意,但是访问范围会受到影响
// 3. 内部类可以直接访问外部类的成员,如果出现同名属性,优先访问内部类中定义的
// 4. 可以使用外部类.this.成员的方式,访问外部类中同名的信息
// 5. 外部类访问内部类信息,需要通过内部类实例,无法直接访问
// 6. 内部类编译后.class文件命名:外部类$内部类.class
public class Heart {
int age = 13;
int temp = 22;
public void eat() {
System.out.println("测试eat()");
}
public String beat() {
Person.this.eat();
// return age + "心脏在跳动";
return Person.this.age + "岁的心脏在跳动";
}
}
}
PeopleTest.java
package com.people;
public class PeopleTest {
public static void main(String[] args) {
Person lili = new Person();
lili.age = 12;
// 获取内部类对象实例,方式1:new 外部类.new 内部类
Person.Heart myHeart = new Person().new Heart();
System.out.println(myHeart.beat());
// 获取内部类对象实例,方式2:外部类对象.new 内部类
myHeart = lili.new Heart();
System.out.println(myHeart.beat());
// 获取内部类对象实例,方式3:外部类对象.获取方法
myHeart = lili.getHeart();
System.out.println(myHeart.beat());
}
}
静态内部类
Person.java
package com.people;
// 外部类
public class Person {
int age;
public Heart getHeart() {
new Heart().temp = 12;
return new Heart();
}
public void eat() {
System.out.println("人会吃东西");
}
// 成员内部类
// 1. 静态内部类中,只能直接访问外部类的静态方法,如果需要调用非静态方法,可以通过对象实例
// 2. 静态内部类对象实例时,可以不依赖于外部类对象
// 3. 可以通过外部类.内部类.静态成员的方式,访问内部类中的静态成员
// 4. 当内部类属性与外部类属性同名时,默认直接调用内部类中的成员
// 5. 如果需要访问外部类的静态属性,则可以通过 外部类.属性的方式
// 5. 如果需要访问外部类的非静态属性,则可以通过 new 外部类().属性的方式
static class Heart {
public static int age = 13;
int temp = 22;
public static void say() {
System.out.println("hello");
}
public String beat() {
new Person().eat();
return new Person().age + "岁的心脏在跳动";
}
}
}
PeopleTest.java
package com.people;
public class PeopleTest {
public static void main(String[] args) {
Person lili = new Person();
lili.age = 12;
// 获取静态内部类对象实例
Person.Heart myHeart = new Person.Heart();
System.out.println(myHeart.beat());
Person.Heart.say();
}
}
方法内部类(局部内部类)
Person.java
package com.people;
// 外部类
public class Person {
int age;
// 方法内部类
// 1. 定义在方法内部,作用范围也在方法内
// 2. 和方法内部成员使用规则一样,class前面不可以添加public,private,protected,static
// 3. 类中不能包含静态成员
// 4. 类中可以包含final,abstract修饰的成员
public Object getHeart() {
class Heart {
public int age = 13;
public void say() {
System.out.println("hello");
}
public String beat() {
new Person().eat();
return new Person().age + "岁的心脏在跳动";
}
}
return new Heart().beat();
}
public void eat() {
System.out.println("人会吃东西");
}
}
PeopleTest.java
package com.people;
public class PeopleTest {
public static void main(String[] args) {
Person lili = new Person();
lili.age = 12;
System.out.println(lili.getHeart());
}
}
匿名内部类
将类的定义与类的创建,放到一起完成
用匿名内部类可以简化抽象类和接口实现的操作
适用场景:
- 只用到类的一个实例
- 类在定义后马上用到
- 给类命名并不会导致代码更容易被理解
例子:根据传入的不同的人的类型,调用对应的read方法
Person.java
package com.anonymous;
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();
}
Man.java
package com.anonymous;
public class Man extends Person {
@Override
public void read() {
// TODO Auto-generated method stub
System.out.println("男生喜欢看科幻类书籍");
}
}
Woman.java
package com.anonymous;
public class Woman extends Person {
@Override
public void read() {
// TODO Auto-generated method stub
System.out.println("女生喜欢言情小说");
}
}
PersonTest.java
#方案1:
package com.anonymous;
public class PersonTest {
public void getRead(Man man) {
man.read();
}
public void getRead(Woman woman) {
woman.read();
}
public static void main(String[] args) {
PersonTest test = new PersonTest();
Man one = new Man();
Woman two = new Woman();
test.getRead(one);
test.getRead(two);
}
}
方案2
package com.anonymous;
public class PersonTest {
public void getRead(Person person) {
person.read();
}
public static void main(String[] args) {
PersonTest test = new PersonTest();
Man one = new Man();
Woman two = new Woman();
test.getRead(one);
test.getRead(two);
}
}
方案3(匿名内部类)
- 匿名内部类没有类型名称,实例对象名称
- 编译后的文件命名:外部类$数字.class
- 无法使用private,public,protected,abstract,static修饰
- 无法编写构造方法,可以添加构造代码块
- 不能出现静态成员
- 匿名内部类可以实现接口也可以继承父类,但是不可兼得 ```java package com.anonymous;
public class PersonTest {
public void getRead(Person person) {
person.read();
}
public static void main(String[] args) {
PersonTest test = new PersonTest();
test.getRead(new Person() {
@Override
public void read() {
// TODO Auto-generated method stub
System.out.println("男生喜欢看科幻类书籍");
}
});
test.getRead(new Person() {
@Override
public void read() {
// TODO Auto-generated method stub
System.out.println("女生喜欢看科幻类书籍");
}
});
}
}
<a name="os3TJ"></a>
## 知识巩固
问题:请写出程序输出结果<br />答案:1,4,2,3,5,6<br />分析:<br />//1. 静态优先<br />//2. 父类优先<br />//3. 非静态块优先于构造函数
```java
package com.imooc.interview;
//请写出程序输出结果
//1. 静态优先
//2. 父类优先
//3. 非静态块优先于构造函数
public class ExecutionSequence {
public static void main(String[] args) {
new GeneralClass();
}
}
class ParentClass{
static {
System.out.println("①我是父类静态块");
}
{
System.out.println("②我是父类非静态块");
}
public ParentClass(){
System.out.println("③我是父类构造函数");
}
}
class GeneralClass extends ParentClass{
static{
System.out.println("④我是子类静态块");
}
{
System.out.println("⑤我是子类非静态块");
}
public GeneralClass(){
System.out.println("⑥我是子类构造函数");
}
}