Java巅峰之路---进阶篇---面向对象(三)
- 抽象类和抽象方法
- 抽象类和方法的介绍
- 抽象类和抽象方法的注意事项
- 小练习
- 接口
- 接口的介绍
- 接口的注意事项:
- 小练习
- 成员特点与接口的各类关系
- 接口中成员的特点
- 补充:JDK8与JDK9新特性
- 接口与类之间的关系
- 接口与抽象类的综合练习
- 适配器设计模式
- 内部类
- 内部类的介绍
- 成员内部类(了解)
- 静态内部类和局部内部类(了解)
- 静态内部类
- 局部内部类
- 匿名内部类
抽象类和抽象方法
抽象类和方法的介绍
- 抽象方法:将共性的行为(方法)抽取到父类之后,由于每一个子类执行的内容是不一样的,所以,在父类中不能确定具体的方法体。该方法就可以定义为抽象方法。
解释:例如定义一个吃东西的方法,一个程序员认为应该定义为void eat(String name){…},另一个程序员又认为应该定义为String eat(){…}。这种无法确定方法体,那就在父类中统一确定一种格式,即public abstract void eat();就行
- 抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类。
作用:
抽取共性时,无法确定方法体,就把方法定义为抽象类。强制让子类按照某种格式重写。抽象类所在的类,必须是抽象类。
格式:
public abstract class 类名{}
public abstract 返回值类型 方法名(参数列表);
抽象类和抽象方法的注意事项
- 抽象类不能实例化(不能new ())
- 抽象类中不一定有抽象方法,有抽象方法的一定是抽象类
- 可以有构造方法
- 抽象类的子类需满足:
要么重写抽象类的所有抽象方法
要么是子类是抽象类(不推荐)
演示demo:
父类:
public abstract class Person {
private String name;
private int age;
//作用:当创建子类对象的时,给属性进行赋值的。
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public abstract void work();
public void sleep(){
System.out.println("睡觉");
}
}
子类:
public class Student extends Person{
public Student() {
}
public Student(String name, int age) {
super(name, age);
}
//必须重写work()方法
@Override
public void work() {
System.out.println("学生的工作是学习");
}
}
测试类:
public class Test {
public static void main(String[] args) {
//创建对象
//Person p = new Person();报错
Student s = new Student("zhangsan",23);
System.out.println(s.getName() + ", " + s.getAge());
}
}
小练习
需求:
编写带有抽象类的标准lavabean类
青娃frog 属性:名字,年龄 行为:吃虫子,喝水
狗Dog 属性:名字,年龄 行为:吃骨头,喝水
山羊Sheep 属性:名字,年龄 行为:吃+,喝水
所作图:
代码如下:
父类:
public abstract class Animal {
private String name;
private int age;
public Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void drink(){
System.out.println("动物在喝水");
}
public abstract void eat();
}
所有子类:
public class Dog extends Animal{
public Dog() {
}
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
public class Frog extends Animal{
public Frog() {
}
public Frog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("青蛙在吃虫子");
}
}
public class Sheep extends Animal{
public Sheep() {
}
public Sheep(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("山羊在吃草");
}
}
测试类:
public class Test {
public static void main(String[] args) {
//创建对象
Frog f = new Frog("小绿",1);
System.out.println(f.getName() + ", " + f.getAge());
f.drink();
f.eat();
}
}
接口
接口的介绍
接口就是一种规则,是对行为的抽象
格式:
单独定义:
public interface 接口名{}
类链接接口:
public class 类名 implements 接口名{}
注意:
接口和类的实现关系,可以单实现,也可以多实现。
public class 类名 implements 接口名1,接口名2{}
实现类还可以在继承一个类的同时实现多个接口。
public class 类名 extends 父类 implements 接口名1,接口名2{}(若多接口有重名方法,子类只需重写一次即可)
接口的应用
接口的应用场景(接口多态):
- 接口代表规则,是行为的抽象。想让哪个类拥有一个行为,就让这个类实现对应的接口就可以了。
- 当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口多态。
接口的注意事项:
- 接口不能实例化(没有new())
- 接口和类之间是实现关系,通过implements关键字表示
- 接口的子类(实现类)
要么重写接口中的所有抽象方法
要么是抽象类(不推荐)
小练习
需求:
编写带有接口和抽象类的标准avabean类
青蛙 属性:名字,年龄 行为:吃虫子,蛙泳
狗 属性:名字,年龄 行为:吃骨头,狗创
免子 属性:名字,年龄 行为:吃胡萝卜
所作图:
代码如下:
接口:
public interface Swim {
public abstract void swim();
}
父类:
public abstract class Animal {
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public Animal() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public abstract void eat();
}
所有子类:
public class Dog extends Animal implements Swim {
public Dog(String name, int age) {
super(name, age);
}
public Dog() {
}
@Override
public void eat() {
System.out.println("狗吃骨头");
}
@Override
public void swim() {
System.out.println("狗刨");
}
}
public class frog extends Animal implements Swim {
public frog(String name, int age) {
super(name, age);
}
public frog() {
}
@Override
public void eat() {
System.out.println("青蛙在吃虫子");
}
@Override
public void swim() {
System.out.println("蛙泳中");
}
}
public class Rubbish extends Animal{
public Rubbish(String name, int age) {
super(name, age);
}
public Rubbish() {
}
@Override
public void eat() {
System.out.println("兔子在吃胡萝卜");
}
}
测试类:
public class Text {
public static void main(String[] args) {
frog f = new frog("小绿",1);
System.out.println(f.getName() + ", " + f.getAge());
f.eat();
f.swim();
Rubbish r = new Rubbish("小白",2);
System.out.println(r.getName() + ", " + r.getAge());
r.eat();
}
}
成员特点与接口的各类关系
接口中成员的特点
- 成员变量:
只能是常量
默认修饰符:public static final - 构造方法:
无
接口不能创建对象,而且接口当中也不需要给子类的成员变量去赋值,所以接口用不着构造方法
- 成员方法:
JDK7以前:接口中只能定义抽象方法
默认修饰符:public abstract
public interface Inter {
//public static final
int a = 10;
//public abstract
void method();
}
补充:JDK8与JDK9新特性
JDK8新特性:接口中可以定义有方法体的方法(默认、静态)
作用:解决接口升级的问题
接口中的默认方法:
格式:public default 返回值类型 方法名(参数列表){}
注意:
- 默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候去掉default关键字
- public 可以省略,default不能省略
- 如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写
public interface InterA {
public abstract void method();
public default void show(){
System.out.println("InterA接口中的默认方法 ---- show");
}
}
public interface InterB {
public default void show(){
System.out.println("InterB接口中的默认方法 ---- show");
}
}
public class InterImpl implements InterA,InterB{
//抽象方法必须重写
@Override
public void method() {
System.out.println("实现类重写的抽象方法");
}
//必须重写show()
@Override
public void show() {
System.out.println("重写接口中的默认方法");
}
}
接口中的静态方法:
格式:public static 返回值类型 方法名(参数列表){}
注意:
- 该静态方法只能通过接口名调用(毕竟接口不能实例化),不能通过实现类名或者对象名调用
- public 可以省略,static不能省略
public interface Inter {
public abstract void method();
public static void show(){
System.out.println("Inter接口中的静态方法");
}
}
public class InterImpl implements Inter{
@Override
public void method() {
System.out.println("InterImpl重写的抽象方法");
}
//不叫方法重写,只是该实现类刚好有个重名的静态方法而已
public static void show() {
System.out.println("InterImpl重写的抽象方法");
}
}
JDK9新特性:接口中可以定义私有方法
作用:此方法只为inter接口提供服务,不需要外类访问
格式1:private 返回值类型 方法名(参数列表){}
格式2:private static 返回值类型 方法名(参数列表){}(为本接口静态方法服务的)
接口与类之间的关系
- 类和类的关系
继承关系,只能单继承,不能多继承,但是可以多层继承 - 类和接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实多个接口 - 接口和接口的关系
继承关系,可以单继承,也可以多继承
如果实现类是实现了最下面的子接口的话,那么该实现类需要重写所有的抽象方法
public interface Inter3 extends Inter1,Inter2{
public abstract void method3();
}
接口与抽象类的综合练习
需求:
编写带有接口和抽象类的标准lavabean类
我们现在有乒乓球运动员和篮球运动员,乒乓球教练和篮球教练。
为了出国交流,跟乒乓球相关的人员都需要学习英语。
请用所有知识分析,在这个案例中,哪些是具体类,哪些是抽象类,哪些是接口?
乒乓球运动员:姓名,年龄,学打乒乓球,说英语
篮球运动员:姓名,年龄,学打篮球
乒乓球教练:姓名,年龄,教打乒乓球,说英语
篮球教练:姓名,年龄,教打篮球
所作图1:
两层继承结构+三个接口(繁琐)
所作图2:
三层继承结构+两个接口(还能简化)
所作图3:
三层继承结构+一个接口(正解!)
代码如下:
接口:
public interface English {
public abstract void speakEnglish();
}
Person类:
//因为现在我不想让外界去直接创建人的对象
//因为直接创建顶层父类人的对象此时是没有意义的
//所以我就把他写为抽象的。
public abstract class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
子类(最底层子类就不写了):
public abstract class Sporter extends Person{
public Sporter() {
}
public Sporter(String name, int age) {
super(name, age);
}
public abstract void study();
}
public abstract class Coach extends Person{
public Coach() {
}
public Coach(String name, int age) {
super(name, age);
}
public abstract void teach();
}
测试类:
public class Test {
public static void main(String[] args) {
//创建运动员或教练的对象
PingPangSporter pps = new PingPangSporter("刘诗雯",23);
System.out.println(pps.getName() + ", " + pps.getAge());
pps.study();
pps.speakEnglish();
}
}
适配器设计模式
-
设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
简单理解:设计模式就是解决问题的各自套路。 -
适配器模式:解决接口与接口实现类之间的矛盾问题。
总结:
1.当一个接口中抽象方法过多,但是我只要使用其中一部分的时候,就可以适配器设计模式
2.书写步骤:
- 编写中间类XXXAdapter 实现对应的接口
- 对接口中的抽象方法进行空实现
- 让真正的实现类继承中间类,并重写需要用的方法
- 为了避免其他类创建适配器类的对象,中间的适配器类用abstract进行修饰
若真正的实现类还有“爹”怎么办?只需要让适配器继承那个爹就行了
演示demo:
public interface Inter {
public abstract void method1();
public abstract void method2();
public abstract void method3();
public abstract void method4();
public abstract void method5();
public abstract void method6();
public abstract void method7();
public abstract void method8();
public abstract void method9();
public abstract void method10();
}
//用适配器空实现所有抽象方法
public abstract class InterAdapter implements Inter{
@Override
public void method1() {
}
@Override
public void method2() {
}
@Override
public void method3() {
}
@Override
public void method4() {
}
@Override
public void method5() {
}
@Override
public void method6() {
}
@Override
public void method7() {
}
@Override
public void method8() {
}
@Override
public void method9() {
}
@Override
public void method10() {
}
}
public class InterImpl extends InterAdapter{
//我需要用到那个方法,就重写哪个方法就可以了
@Override
public void method5() {
System.out.println("只要用第五个方法");
}
}
内部类
内部类的介绍
什么是内部类?
在一个类里边,再定义一个类。
什么时候用到内部类?
B类表示的事物是A类的一部分,且B单独存在没有意义
比如:汽车的发动机、ArrayList的迭代器、人的心脏等
内部类的访问特点
- 内部类可以直接访问外部类的成员,包括私有
- 外部类要访问内部类的成员,必须创建对象
演示demo:
public class Car {
String carName;
int carAge;
private String carColor;
public void show(Car this){//Car this是默认存在的
//是打印调用者车的名字:宾利
System.out.println(this.carName);
Engine e = new Engine();
System.out.println(e.engineName);
}
class Engine{
String engineName;
int engineAge;
public void show(){
System.out.println(engineName);
System.out.println(carName);
}
}
}
成员内部类(了解)
- 写在成员未知的,属于外部类的成员(与外部类的成员变量和方法地位是一模一样的)
- 可以被一些修饰符修饰,比如:private、默认、protected、public、static(静态内部类,JDK16之后)等
获取成员内部类对象
- 直接创建格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
- 被private修饰时:在外部类中编写方法,对外提供内部类的对象
演示demo:
public class Outer {
String name;
private class Inner{
static int a = 10;
}
public Inner getInstance(){
return new Inner();
}
}
public class Test {
public static void main(String[] args) {
// Outer.Inner oi = new Outer().new Inner();非私有化修饰时
Outer o = new Outer();
System.out.println(o.getInstance());
}
}
成员内部类如何获取外部类的成员变量
- 当外部类成员变量和内部类成员变量重名时,在内部类可以用Outer.this.变量名进行访问
演示demo:
public class Outer {
private int a = 10;
class Inner {
private int a = 20;
public void show() {
int a = 30;
//Outer.this 获取了外部类对象的地址值
System.out.println(Outer.this.a);//10
System.out.println(this.a); //20
System.out.println(a); //30
}
}
}
public class Test {
public static void main(String[] args) {
//创建内部类的对象,并调用show方法
Outer.Inner oi = new Outer().new Inner();
oi.show();
}
}
内存图:
静态内部类和局部内部类(了解)
静态内部类
静态内部类也是成员内部类中的一种,只能访问外部类中的静态变量和静态方法,如果想访问非静态的需要创建对象。
- 创建静态内部类对象的格式:
外部类名.内部类名 对象名 = new 外部类名.内部类名(); - 调用静态内部类中静态方法的格式:
外部类名.内部类名.方法名();
演示demo:
public class Outer {
//静态内部类
static class Inner {
public void show1(){
System.out.println("非静态的方法被调用了");
}
public static void show2(){
System.out.println("静态的方法被调用了");
}
}
}
public class Test {
public static void main(String[] args) {
//创建静态内部类的对象
//只要是静态的东西,都可以用类名点直接获取
Outer.Inner oi = new Outer.Inner();
oi.show1();
//静态方法
Outer.Inner.show2();
}
}
局部内部类
- 将内部类定义在方法里面就叫做局部内部类,类似于方法里面的局部变量。
- 外界是无法直接使用局部内部类,需要在方法内部创建对象并使用。
- 该类可以直接访问外部类的成员,也可以访问方法内的局部变量。
演示demo:
public class Outer {
int b = 20;
public void show(){
int a = 10;
//局部内部类
class Inner{
String name;
int age;
public void method1(){
System.out.println(a);
System.out.println(b);
System.out.println("局部内部类中的method1方法");
}
public static void method2(){
System.out.println("局部内部类中的method2静态方法");
}
}
//创建局部内部类的对象
Inner i = new Inner();
System.out.println(i.name);
System.out.println(i.age);
i.method1();
Inner.method2();
}
public class Test {
public static void main(String[] args) {
Outer o = new Outer();
o.show();
}
}
匿名内部类
什么是匿名内部类?
隐藏了名字的内部类,可以写在成员位置,也可以写在局部位置。
细节:
包含了继承或实现、方法重写、创建对象。
整体就是一个类的子类对象或接口的实现类对象
格式:
使用场景:
当方法的参数是接口或者类时,以接口为例,可以传递这个接口的实现类对象。如果现实类只要实现一次,就可以用匿名类简化代码
演示demo:
public abstract class Animal {
public abstract void eat();
public void drink(){
System.out.println("喝水");
}
}
public abstract class Animal {
public abstract void eat();
public void drink(){
System.out.println("喝水");
}
}
public class Test {
public static void main(String[] args) {
//编写匿名内部类的代码
new Swim(){
@Override
public void swim() {
System.out.println("重写了游泳的方法");
}
};
new Animal(){
@Override
public void eat() {
System.out.println("重写了eat方法");
}
}.drink();//其本质就是一个子类对象,可以调用父类方法的。
method(
new Animal() {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
);
}
public static void method(Animal a){//Animal a = 子类对象 多态
a.eat();//编译看左边,运行看右边
}
}