目录
一、多态简介:
- 是面向对象最重要的特征。允许不同类的对象对同一消息做出不同的响应。
- 可分为:
①编译时多态(设计时多态):在编译状态就可以进行不同行为的区分,通常通过方法重载实现。
②运行时多态:直到程序运行时,系统才能动态地决定调用哪个方法。
注意此处指的是运行时多态
- 实现多态的必要条件:
①满足继承关系。
②父类引用指向子类对象。
二、向上转型和向下转型:
- 向上转型(隐式转型/自动转型):把一个子类对象转型为父类对象。由小向大转。此时父类引用指向子类实例,可以调用子类重写父类的方法和父类派生下去、可被子类直接使用的方法,但无法调用子类独有方法。注意:父类中static修饰的类方法是不允许被子类重写的!故向上转型后只能调用到父类原有的静态方法。
- 向下转型(强制类型转换):子类引用指向父类对象,必须强制类型转换,可以调用子类特有的方法。必须满足转换条件(向上转型的还原)才能进行转换。
可以通过instanceof运算符来判断引用对象的类型,避免类型转换出现安全性问题、提高代码的强壮性。
举例:
package com.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;
}
//方法:吃东西
public void eat() {
System.out.println("动物都有吃东西的能力!");
}
}
package com.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
public void eat() {
System.out.println("猫吃鱼!");
}
}
package com.animal;
import org.ietf.jgss.Oid;
public class Dog extends Animal {
// 属性:性别
private String sex;
// 无参构造
public Dog() {
super();
}
// 带参构造
public Dog(String name, int month, String sex) {
this.setName(name);
this.setMonth(month);
this.setSex(sex);
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
// 方法:睡觉
public void sleep() {
System.out.println("小狗要午睡!");
}
// 方法:吃东西(重写父类方法)
@Override
public void eat() {
System.out.println("狗吃骨头!");
}
}
package com.animal;
public class Master {
/*
* 喂宠物
* 喂猫咪:吃完东西后,主人带猫跑步
* 喂狗:吃完东西后,主人带狗去睡觉
*/
// //方案1:编写方法,传入不同类型的动物,调用各自的方法
// public void feed(Cat cat) {
// cat.eat();
// cat.run();
// }
//
// public void feed(Dog dog) {
// dog.eat();
// dog.sleep();
// }
//
//方案2:编写方法传入动物的父类,方法中通过类型转换调用指定子类的方法
public void feed(Animal a) {//向上转型
if (a instanceof Cat) { //如果满足Cat类的实力条件
Cat x=(Cat)a; //向下转型
x.eat();
x.run();
}else if (a instanceof Dog) {
Dog x=(Dog)a;
x.eat();
x.sleep();
}
}
}
方案1和方案2皆可,方案2用了向上转型和向下转型。
package com.test;
import com.animal.Cat;
import com.animal.Dog;
import com.animal.Master;
public class MasterTest {
public static void main(String[] args) {
Master master=new Master();
Cat one=new Cat();
Dog two=new Dog();
master.feed(one);
master.feed(two);
}
}
三、abstract关键字:
- 在class前面添加abstract关键字(abstract关键字可在访问修饰符前或后,随意),使该类变成抽象类。抽象类不允许直接实例化、只能被继承,可以通过向上转型指向其子类实例。
- 应用场景:某个父类只是知道其子类应包含怎样的方法,但无法准确知道这些子类如何实现这些方法。这样借由父类和子类的继承关系,限制子类的设计随意性,也一定程度上避免了无意义的父类实例化。
- 在方法的返回值前面添加abstract关键字,使该方法变成抽象方法。抽象方法不能具有方法体(不需要具体实现),子类必须去实现(重写)父类的抽象方法。如果子类也是抽象类,则不用实现父类的抽象方法。
public abstract void eat();
- 包含抽象方法的类一定是抽象类,但抽象类中可以没有抽象方法。
- static(静态不能重写)、final(不允许重写)、private(私有)不可以与abstract关键字并存!
四、接口类(interface关键字):
- 接口的必要性:因Java只支持单继承,故接口就是实现继承的另外一种方式!来替代继承的作用!
- 接口定义了一批类需要遵守的规范。
- 接口不关心这些类的内部数据和这些类的方法的实现细节,接口只规定这些类必须提供的方法。
- 描述不同类型具有相似的行为特征,接口引用指向实现类来描述不同类型对于接口行为的实现。
①若接口的实现类中存在与接口类同名的信息(如常量),当用接口的引用指向实现类时,输出的仍是接口自己定义的常量信息;若调用实现类的实例,通过实现类对象输出的是实现类的信息。
例:
package com.phone;
/**
* 接口的访问修饰符通常是public或默认
* @author dell
*
*/
public interface Inet {
/* 接口中方法可以不写abstract关键字,
* 访问修饰符默认是public,
* 当类实现接口时,需要实现接口中的所有抽象方法,否则需要将该类设置成抽象类
* */
void network();
//接口中可以包含常量,默认public、static、final
int TEMP=10;
}
package com.phone;
public class SmartWatch implements Inet{
@Override
public void network() {
System.out.println("可以上网!");
}
public static final int TEMP=20; //定义常量的格式
}
package com.test;
import com.phone.Inet;
import com.phone.SmartWatch;
public class PhoneTest {
public static void main(String[] args) {
System.out.println(Inet.TEMP); //接口名.常量名 访问接口中的常量
Inet b=new SmartWatch();
System.out.println(b.TEMP); //也可通过接口的引用 访问接口中的常量
SmartWatch c=new SmartWatch();
System.out.println(c.TEMP);
}
}
②有时接口中的方法并非皆为实现类所需要的,但实现类却必须实现接口中的所有方法,有什么解决方法吗?当然有!JDK1.8中提供了一种默认方法,这种默认方法由default关键字修饰,可带有方法体,因此子类无需去实现。
例:
public interface Inet {
//default:默认方法,可以带方法体
default void connection() {
System.out.println("我是接口中的默认链接");
}
}
默认方法可通过接口引用去调用。默认方法也是可以由实现类重写的,此时系统生成的重写方法没有了default关键字,且加上了public修饰符:
package com.phone;
public class SmartWatch implements Inet{
@Override
public void connection() {
// TODO Auto-generated method stub
Inet.super.connection(); //调用接口中的默认方法:connection()
}
}
③JDK1.8中还提供了一种静态方法,这种静态方法由static关键字修饰,可带有方法体。
例:
package com.phone;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter.DEFAULT;
public interface Inet {
//static:静态方法,可以带方法体
static void stop() {
System.out.println("我是接口中的静态方法");
}
}
调用静态方法的格式为:接口名.静态方法名。静态方法不能在实现类中重写。
④在Java中,一个类可以实现多个接口,接口名用逗号隔开即可。对于多接口中相同的方法名,实现类中无法调用(因为不能确定是哪个接口中的方法),可以通过在实现类中重写该方法来调用。
⑤一个类可以在继承父类的同时实现(多个)接口。
子类 extendds 父类 implements 接口
若此时,继承的父类和实现的接口中也出现了同名的方法,子类默认调用的是父类中的方法。
当父类中的属性与接口中的常量同名时,子类中调用会无法分辨,此时需要在子类中去定义它独有的成员去解决该问题。
⑥Java中的接口可以继承,而且存在多继承。子接口去继承父接口。此时子接口的实现类就需要去实现子接口和父接口中的所有方法。当多个父接口中存在同名方法时,子接口调用时会无法分辨,解决方法还是在子接口中定义一个自己的同名方法。
⑦总结接口的重要性:
- 在Java编程,abstract class 和interface是支持抽象类定义的两种机制。正是由于这两种机制的存在,才使得Java成为面向对象的编程语言。
- 定义接口有利于代码的规范:对一个大型项目而言,架构师往往会对一些主要的接口来进行定义,或者清理一些没有必要的接口。这样做的目的一方面是为了给开发人员一个清晰的指示,告诉他们哪些业务需要实现;同时也可防止开发人员随意命名而导致的命名不清晰和代码混乱,影响开发效率。
- 利于对代码维护:比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类。可是在不久将来,你突然发现现有的类已经不能够满足需要,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦。如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护、拓展的方便性。
- 保证代码的安全和严密:一个好的程序一定符合高内聚低耦合的特征,那么实现低耦合,定义接口是一个很好的方法,能够让系统的功能较好地实现,而不涉及任何具体的实现细节。这样就比较安全、严密一些,这一思想一般在软件开发中较为常见。
五、内部类:
- 在Java中,可以将一个类定义在另一个类里面或一个方法里面,这样的类称之为内部类。内部类隐藏在外部类内部,更好地实现了信息隐蔽、不允许其他类去随意访问。
- 内部类分为:①成员内部类 ②静态内部类 ③方法内部类 ④匿名内部类
- 包含内部类的类成为外部类。
- 内部类的信息获取需要借助外部类去访问。
1.成员内部类:
- 是最常见的内部类,也称之为内部类。
package com.people;
public class Person {
public int age;
public void eat() {
System.out.println("吃东西!");
}
//成员内部类
/*
* 1.内部类在外部使用时,无法直接实例化,需要借助外部类信息才能完成实例化。
* 2.内部类的默认访问权限是同包访问。内部类的访问修饰符可以任意,但访问范围会受影响。
* 3.内部类可以直接访问外部类的成员(包括成员属性和成员方法),若出现同名属性,优先访问内部类中定义的。
* 4.可以使用:外部类.this.成员 来访问外部类中的同名信息。
*
*/
class Heart{
int age=12;
public String beat() {
eat();
return age+"岁的心脏在跳动!"+Person.this.age+"岁";
}
}
public Heart getHeart() { //用于获取内部类的方法。
/* 编码习惯,通常会在外部类中设置一个获取内部类的方法,以便于内部类对象的实例化操作。 */
new Heart().age=1; //5.外部类访问内部类信息需要通过内部类实例,无法直接访问
return new Heart();
}
}
package com.people;
import com.people.Person.Heart;
public class PeopleTest {
public static void main(String[] args) {
Person Lily=new Person();
Lily.age=18;
/* Heart myHeart=new Heart(); */ //出错!不能直接访问内部类!
//获取内部类实例:
//法1:new 外部类.new 内部类
Person.Heart myHeart1=new Person().new Heart();
System.out.println(myHeart1.beat());
//法2:外部类对象.new 内部类
Person.Heart myHeart2=Lily.new Heart();
System.out.println(myHeart2.beat());
//法3:外部类对象.获取方法
Person.Heart myHeart3=Lily.getHeart();
System.out.println(myHeart3.beat());
}
}
注意内部类编译文件(.class文件)的名字格式:外部类名$内部类名
2.静态内部类:
- 由static修饰。
- 静态内部类对象可以不依赖于外部类对象,直接创建。
package com.people;
public class Person {
public int age;
public static void eat() {
System.out.println("吃东西!");
}
public static void run() {
System.out.println("跑步!");
}
//静态内部类
/**
* 1.静态内部类中,只能直接访问外部类的静态成员①。若想调用非静态成员,可通过对象实例去调用②。
* 2.静态内部类对象实例化时,可以不依赖外部类对象③。
* 3.可通过:外部类.内部类.静态成员 去访问内部类中的静态成员④。
* 4.当内部类属性与外部类属性同名时,默认直接调用内部类中的成员。
* 若要访问外部类中的静态属性,可通过:外部类.属性 去访问。
* 若要访问外部类中的非静态属性,可通过:new 外部类( ).属性 去访问。
* @author dell
*
*/
static class Heart{
static int age=12; //静态成员
static void say() { //静态方法
System.out.println("你好!");
}
public String beat() {
eat(); //①
new Person().run(); //②
return age+"岁的心脏在跳动!"+new Person().age+"岁"; //②
}
}
}
package com.people;
import com.people.Person.Heart;
public class PeopleTest {
public static void main(String[] args) {
Person Lily=new Person();
Lily.age=18;
//获取静态内部类对象实例 ③
Person.Heart myHeart=new Person.Heart();
System.out.println(myHeart.beat());
Person.Heart.say(); //④
}
}
3.方法内部类:
- 定义在外部类方法中的内部类,也称为局部内部类。故方法成员的约束对方法内部类同样有效。
package com.people;
public class Person {
public int age;
public static void eat() {
System.out.println("吃东西!");
}
public static void run() {
System.out.println("跑步!");
}
public Object getHeart() {
//方法内部类
/*
* 1.定义在方法内部,作用访问也在方法内部。
* 2.和方法内部成员使用规则一致:在class前面不能添加public、private、protected、static。
* 3.类中不能包含静态成员。
* 4.类中可包含final,abstract(不推荐)修饰的成员。
*/
class Heart {
int age = 12;
void say() {
System.out.println("你好!");
}
public String beat() {
eat(); // ①
new Person().run(); // ②
return age + "岁的心脏在跳动!" + new Person().age + "岁";
}
}
return new Heart().beat();
}
}
package com.people;
public class PeopleTest {
public static void main(String[] args) {
Person Lily=new Person();
Lily.age=18;
System.out.println(Lily.getHeart());
}
}
注意内部类编译文件(.class文件)的名字格式发生了变化:
4.匿名内部类:
- 没有类名
- 有时在程序中对某个类的实例只使用1次,此时该类的名字对整个程序而言就可有可无了。此时可将类的定义和类的创建放在一起 完成来简化程序。
例:
package com.annoymous;
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();
}
package com.annoymous;
public class Woman extends Person {
@Override
public void read() {
System.out.println("女人来自金星。");
}
}
package com.annoymous;
public class Man extends Person {
@Override
public void read() {
System.out.println("男人来自火星!");
}
}
要求根据不同的人的类型调用对应的read方法:
方案A:
package com.test;
import com.annoymous.Man;
import com.annoymous.Woman;
import com.annoymous.Person;
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 a=new Man();
Woman b=new Woman();
test.getRead(a);
test.getRead(b);
}
}
方案B:
package com.test;
import com.annoymous.Man;
import com.annoymous.Woman;
import com.annoymous.Person;
public class PersonTest {
public void getRead(Person person) {
person.read();
}
public static void main(String[] args) {
PersonTest test=new PersonTest();
Man a=new Man();
Woman b=new Woman();
test.getRead(a);
test.getRead(b);
}
}
方案C:利用匿名类
package com.test;
import com.annoymous.Man;
import com.annoymous.Woman;
import com.annoymous.Person;
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() {
System.out.println("男人来自火星!");
}
});
test.getRead( new Person() {
@Override
public void read() {
System.out.println("女人来自金星。");
}
});
}
}
- 匿名内部类没有类型名称、实例对象名称。
- 实例对象的同时完成对对象的内容的编写。
- 无法使用private、public、protected、abstract、static修饰符。
- 无法编写构造方法,可以添加构造代码块。
- 不能有静态成员。
- 匿名内部类可以实现接口也可继承父类,但是不可兼得!
- 优点:对内存的损耗相对较小。
- 缺电:不能重复调用。
- 适用场景:①只用到类的一个实例。 ②类在定义后马上用到。 ③当给类命名并不会使代码更容易理解时。
此时的类编译文件(.class文件)命名是:外部类$数字.class
分别对应:测试类、通过匿名类生成的man和woman类