一、多态
Java中的多态我们可以通过字面上的意思去理解,多态顾名思义就是多种形态,什么多种形态呢?比如我们的水就有固态、液态、气态三种形态,这就是一种多态,那我们是不是可以这样去理解,多态就是同一个物种的多种形态,那么在Java中是如何去表示的呢?通过之前的笔记我们可以知道继承的相关要点,我们的狗(dog)这个类就继承自动物类(animal),那么在Java中我们可以用狗来表示它自己,也可以向上造型animal来引用指向子类的对象,具体如何去做我们看下面。
总之我们只需要知道多态是指多种形态就好了,有两种方式去做:
1、向上造型/自动类型转换
就是说我们用超类引用指向子类对象,那么怎么做呢?如下代码:
Animal o1 = new Dog("小黑",2,"黑");
可以看到在上面代码,我们首先声明了一个Animal引用类型的值为o1,并且把o1进行new(实例化)对象为dog类型,那么我们就可以 认为这就是一种多态,我们的dog类向上造型为了animal,但是我们的o1这个变量可以访问到声明方法呢,就看Animal类有什么,o1就能用什么,这是我需要注意的,即我们能点出来(o1.方法),看我们的引用类型(animal这里是)--记住啦!
2、向下转型/强制类型转换
这有什么用呢?我们从上面是不是发现了,我们向上造型只能访问到超类的方法,那我们想访问或者用到dog独有的方法(animal没有)怎么做呢?这种情况我们就用到了向下转型,代码如下:
//Animal o = new Animal(); //编译错误,抽象类不能被实例化
Animal[] animals = new Animal[5];
animals[0] = new Dog("小黑",2,"黑"); //向上造型
if(animals[0] instanceof Dog){
Dog dog = (Dog)animals[i];
dog.lookHome();
}
那么instanceof这个关键字有什么用呢?在说之前我们需要知道向下转型的条件:
条件有两个:
1、引用所指向的对象,就是该类型
2、引用所指向的对象,实现了该接口或继承了该类
强转时若不符合如上条件,则发生ClassCastException类型转换异常
建议:在强转之前先通过 instanceof 来判断引用的对象是否是该类型
注意: instanceof 返回 boolean 结果,它为 true 的条件就是强转成功的条件
何时需要强转:若想访问的属性 / 行为在超类中没有,则需要强制类型转换
所以我们通过instanceof关键字先进行判断,如果可以符合条件再强制转换以避免异常,这样我们就可以进行强制转换来访问我们的dog类中特有的方法,这就是我们多态的向下转型。
那我们是不是就又有疑问啦,既然我们向上造型的多态不能访问我们子类的方法,我们统一用子类引用去实例化子类对象不就好了,为什么多此一举呢?实际上我们多态是为了减少代码的重复性,从前面的例子(没有学习多态之前),我们可以知道当我们想创建多个dog,fish,chick类对象,并且访问其方法的时候,我们需要依次的去调用,有多少个对象与方法,就应该有多少调用方法,那么你就会发现会有大量的重复代码比如:dog1.eat();dog2.eat();dog3.eat();fish1.eat();chick1.eat();chick2.eat()........方法,这么相似的方法为什么不可以用一个for循环去解决呢?在我们没有学多态之前我们是不可以用for的,但是学了之后我们就可以用到for循环了代码如下:
Animal[] animals = new Animal[5];
animals[0] = new Dog("小黑",2,"黑"); //向上造型
animals[1] = new Dog("小白",1,"白");
animals[2] = new Fish("小金",1,"金");
animals[3] = new Fish("小花",2,"花");
animals[4] = new Chick("小灰",3,"灰");
for(int i=0;i<animals.length;i++){ //遍历所有动物
System.out.println(animals[i].name); //输出每个动物的名字
animals[i].eat(); //每个动物吃饭
animals[i].drink(); //每个动物喝水
if(animals[i] instanceof Dog){
Dog dog = (Dog)animals[i];
dog.lookHome();
}
if(animals[i] instanceof Chick){
Chick chick = (Chick)animals[i];
chick.layEggs();
}
if(animals[i] instanceof Swim){ //适用于所有实现Swim接口的(会游泳的)
Swim s = (Swim)animals[i];
s.swim();
}
}
输出结果如下:
通过上面代码我们可以看到,我们用向上造型来创建了诸多对象 ,并且通过共有的超类方法来做for循环以此来减少代码的重复性,提高复用性!并且我们还可以通过向下转型的方式来访问子类特有的方法,因此我们可以看到,多态的存在是十分有必要的,与继承,封装一起构成了Java最重要的三大特性。
整体的代码如下:定义一个抽象的animal类,其下有dog,fish,chick类去继承,已经有个Test去做测试。
public abstract class Animal {
String name;
int age;
String color;
Animal(){
}
Animal(String name,int age,String color){
this.name = name;
this.age = age;
this.color = color;
}
void drink(){
System.out.println(color+"色的"+age+"岁的"+name+"正在喝水...");
}
abstract void eat();
}
public interface Swim {
/** 游泳 */
void swim();
}
public class Dog extends Animal implements Swim {
Dog(){
}
Dog(String name,int age,String color){
super(name,age,color);
}
void lookHome(){
System.out.println(color+"色的"+age+"岁的狗狗"+name+"正在看家...");
}
void eat(){
System.out.println(color+"色的"+age+"岁的狗狗"+name+"正在吃肯头...");
}
public void swim(){
System.out.println(color+"色的"+age+"岁的狗狗"+name+"正在游泳...");
}
}
public class Fish extends Animal implements Swim {
Fish(){
}
Fish(String name,int age,String color){
super(name,age,color);
}
void eat(){
System.out.println(color+"色的"+age+"岁的小鱼"+name+"正在吃小虾...");
}
public void swim(){
System.out.println(color+"色的"+age+"岁的小鱼"+name+"正在游泳...");
}
}
public class Chick extends Animal {
Chick(){
}
Chick(String name,int age,String color){
super(name,age,color);
}
void layEggs(){
System.out.println(color+"色的"+age+"岁的小鸡"+name+"正在下蛋...");
}
void eat(){
System.out.println(color+"色的"+age+"岁的小鸡"+name+"正在吃小米...");
}
}
public class Master {
void feed(Animal animal){ //喂动物,将范围扩大了,所有动物都可以
animal.eat();
}
}
package ooday04;
/**
* 演示多态
*/
public class Test {
public static void main(String[] args) {
/*
Animal o1 = new Dog("小黑",2,"黑");
//o1能强转为:Dog,Swim,Animal
Animal o2 = new Fish("小黑",2,"黑");
//o2能强转为:Fish,Swim,Animal
Animal o3 = new Chick("小黑",2,"黑");
//o3能强转为:Chick,Animal
*/
/*
//强转成功的条件:
//1.引用所指向的对象,就是该类型
//2.引用所指向的对象,实现了该接口或继承了该类型
Animal o = new Dog("小黑",2,"黑"); //向上造型
Dog g = (Dog)o; //引用o所指向的对象,就是Dog类型
Swim s = (Swim)o; //引用o所指向的对象,实现了Swim接口
//Fish f = (Fish)o; //运行时会发生ClassCastException类型转换异常
System.out.println(o instanceof Dog); //true
System.out.println(o instanceof Swim); //true
System.out.println(o instanceof Fish); //false
if(o instanceof Fish){ //false
Fish f = (Fish)o;
}
*/
/*
//演示向上造型(多态)的第2点应用:
Master master = new Master();
Dog dog = new Dog("小黑",2,"黑");
Chick chick = new Chick("小花",3,"花");
Fish fish = new Fish("小金",1,"金");
master.feed(dog); //在传参的同时,系统自动做了向上造型
master.feed(chick);
master.feed(fish);
*/
//演示向上造型(多态)的第1点应用:
//Animal o = new Animal(); //编译错误,抽象类不能被实例化
Animal[] animals = new Animal[5];
animals[0] = new Dog("小黑",2,"黑"); //向上造型
animals[1] = new Dog("小白",1,"白");
animals[2] = new Fish("小金",1,"金");
animals[3] = new Fish("小花",2,"花");
animals[4] = new Chick("小灰",3,"灰");
for(int i=0;i<animals.length;i++){ //遍历所有动物
System.out.println(animals[i].name); //输出每个动物的名字
animals[i].eat(); //每个动物吃饭
animals[i].drink(); //每个动物喝水
if(animals[i] instanceof Dog){
Dog dog = (Dog)animals[i];
dog.lookHome();
}
if(animals[i] instanceof Chick){
Chick chick = (Chick)animals[i];
chick.layEggs();
}
if(animals[i] instanceof Swim){ //适用于所有实现Swim接口的(会游泳的)
Swim s = (Swim)animals[i];
s.swim();
}
}
}
}
二、内部类
1、成员内部类
在实际开发中我们的成员内部类应用率低,了解一下就行
成员内部类就是类中套类,外面的称为外部类,里面的称为内部类
内部类通常只服务于外部类,对外不具备可见性
内部类对象通常在外部类中创建,如下面的name属性
内部类可以直接访问外部类的成员,在内部类中有个隐式的引用指向创建它的外部类对象
隐式的引用:外部类名 .this,就是你不写,也默认为用到了
何时用:若 A 类 (Baby) 只让 B 类 (Mama) 用,并且 A 类 (Baby) 还想访问 B 类 (Mama) 的成员时,可
以设计成员内部类
//成员内部类
public class InnerClassDemo {
public static void main(String[] args) {
Mama m = new Mama();
//Baby b = new Baby(); //编译错误,内部类对外不具备可见性
}
}
//Baby类只能Mama类用,并且Baby类中还想访问Mama类的成员,可以将Baby设计为成员内部类
class Mama{ //外部类
String name;
void create(){
Baby b = new Baby(); //正确,内部类对象通常在外部类中创建
}
class Baby{ //成员内部类
void show(){
System.out.println(name); //简写
System.out.println(Mama.this.name); //完整写法,Mama.this指代外部类对象
//System.out.println(this.name); //编译错误,this指当前Baby对象
}
}
}
2、匿名内部类
匿名内部类的应用率比较高,需要我们去掌握
那么匿名内部类是什么去创建的呢?代码如下:
//1)创建了InterInter的一个派生类,但是没有名字
//2)为该派生类创建了一个对象,名为o3,向上造型为InterInter类型
//3)大括号中的为派生类的类体
InterInter o3 = new InterInter(){
public void show(){
System.out.println("showshow");
//num = 6; //编译错误,匿名内部类中不能修改外面局部变量的值
//因为该变量在此处会默认为final的
}
};
o3.show();
//定义一个类,接口也是类哦!
interface InterInter{
void show();
}
匿名内部类就是new对象差不多,只是我们在其后面叫了大括号,以来写出这个内部类具体干什么,即派生类的类体,那么我们什么时候去使用匿名内部类呢?在我们若想创建一个派生类的对象,并且对象只创建一次,这时我们就可以设计为匿名内部类,还可以大大简化代码。
但是我们需要注意的是匿名内部类中不能修改外面局部变量的值,上面注释的num就是个外部变量,我们不能在匿名内部类中去进行修改;还有就是我们要记住内部类有独立的class。
三、package和import
package与import是两关键字,具体是干嘛的:
package :声明包
作用:避免类的命名冲突
规定:同包中的类不能同名,但不同包中的类可以同名。
类的全称:包名. 类名。包名常常有层次结构
建议:包名所有字母都小写
import :导入类
同包中的类可以直接访问,但不同包中的类不能直接访问,若想访问:
先import 导入类,再访问类 -------------- 建议
类的全称--------------------------------------- 太繁琐、不建议(就是在我们要用到其它包的类时,我们通过包名.类名这种全称来访问,但是就有一个弊端,当我使用频繁的时候,要一直写全称,这是不是有的麻烦,所以我们建议在要用到其他包的类时,我们就可以先导包再访问类)
package day04;
import java.util.Scanner; //导包
//Scanner的演示
public class ScannerDemo {
public static void main(String[] args) {
/*
package java.util;
class Scanner{
Scanner(InputStream s){ ... }
int nextInt(){ ... }
double nextDouble(){ ... }
}
*/
Scanner scan = new Scanner(System.in); //2
int age = scan.nextInt(); //3
double price = scan.nextDouble(); //3
}
}
四、补充
1. 多态的实际应用:
将不同对象( 狗、鱼、鸡 ) 统一封装到超类数组 ( 动物数组 ) 中来访问,实现代码复用 ,将超类型(Animal) 作为参数或返回值类型,传递或返回派生类 (Dog/Fish/Chick) 对象,以扩大 方法的应用范围( 所有 Animal) ,实现代码复用
2. 隐式的引用:
this:指代当前对象
super:指代当前对象的超类对象
外部类名.this :指代当前对象的外部类对象
3. 关键字顺序:先 package ,再 import ,最后 class
4. 抽象类和接口的区别: ----------- 常见面试题
抽象类:
由abstract修饰
可以包含变量、常量、构造方法、普通方法、抽象方法、静态方法
派生类通过extends来继承
只能继承一个(单一继承 )
抽象类中的成员任何访问权限都可以
接口:
由interface定义
可以包抽象方法、常量、静态方法、默认方法
实现类通过implements来实现
可以实现多个(多实现 )
接口中的成员的访问权限都是public
ok啦!就写到这了,建议把案例看一看哦!