面对对象进阶- 下(九)
抽象方法和抽象类
抽象方法:将共性的行为(方法)抽取到父类之后,由于每个子类的执行的内容不一样,所以在父类中不能确定具体的方法体。该方法就可以定义为抽象方法。
抽象类:若一个类中存在抽象方法,那么该类就必须声明为抽象类。
定义格式
抽象方法的定义格式:
public abstract 返回值类型 方法名(参数列表);
抽象类的定义格式:
public abstract class 类名{}
注意事项
- 抽象类不能实例化(不能创造对象)
- 抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类
- 可以有构造方法,交给子类,通过super访问
- 抽象类的子类
- 要么重写抽象类中的所有抽象方法
- 要么是抽象类
abstract关键字冲突:
- final:被abstract修饰的方法,强制要求子类重写,被final修饰的方法子类不能重写
- private:被abstract修饰的方法,强制要求子类重写,被private修饰的方法子类不能重写
- static:被static修饰的方法可以类名调用,类名调用抽象方法没有意义
范例
//需求
青蛙frog 属性:名字,年龄 行为:吃虫子,喝水
狗dog 属性:名字,年龄 行为:吃骨头,喝水
山羊sheep 属性:名字,年龄 行为:吃草,喝水
//Animal.java
package com.ljsblog.domain2;
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 void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age=age;
}
public int getAge(){
return age;
}
public abstract void eat();
public void drink(){
System.out.println("喝水");
}
}
//Frog.java
package com.ljsblog.domain2;
public class Frog extends Animal {
public Frog(){}
public Frog(String name,int age){
super(name,age);
}
@Override
public void eat(){
System.out.println("吃虫子");
}
}
//Dog.java
package com.ljsblog.domain2;
public class Dog extends Animal{
public Dog(){}
public Dog(String name,int age){
super(name,age);
}
@Override
public void eat(){
System.out.println("吃骨头");
}
}
//Sheep.java
package com.ljsblog.domain2;
public class Sheep extends Animal{
public Sheep(){}
public Sheep(String name,int age){
super(name,age);
}
@Override
public void eat(){
System.out.println("吃草");
}
}
//Test.java
package com.ljsblog.domain2;
public class test {
public static void main(String[] args) {
Dog g = new Dog("傻狗", 18);
Frog f = new Frog("癞蛤蟆", 20);
Sheep s = new Sheep("喜洋洋", 3);
g.eat();
g.drink();
f.eat();
f.drink();
s.eat();
s.drink();
}
}
/*
吃骨头
喝水
吃虫子
喝水
吃草
喝水
*/
接口
接口就是一种规则,是对行为的抽象。
思路:若存在一个类,所有的组成都是抽象方法。没有成员变量和普通方法,则该类通常会被设计成Java中的接口。因为现在这个类存在的唯一价值,就只是声明规则了。
定义和使用
- 接口用关键字interface定义:
- public interface 接口名{}
- 接口不能实例化。
- 接口与类之间是实现关系,通过implements关键字表示:
- public class 类名 implements 接口名{}
- 接口的子类(实现类):
- 要么重写接口类中的所有抽象方法
- 要么是抽象类
注意事项
- 接口与类可以单实现,也可以多实现
- public class 类名 implements 接口名1,接口名2…{}
- 实现类可以在继承一个类的同时实现多个接口
- public class 类名 extends 父类名 implements 接口名1,接口名2…{}
范例
//需求
青蛙 属性:名字,年龄 行为:吃虫子,蛙泳
狗 属性:名字,年龄 行为:吃骨头,狗刨
兔子 属性:名字,年龄 行为:吃胡萝卜
//父类
//Animal.java
package com.ljsblog.domain2;
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 void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age=age;
}
public int getAge(){
return age;
}
public abstract void eat();
}
//接口
//Regularity
package com.ljsblog.domain2;
public interface Regularity{
public abstract void swim();
}
//子类
//Frog.java
package com.ljsblog.domain2;
public class Frog extends Animal implements Regularity {
public Frog(){}
public Frog(String name,int age){
super(name,age);
}
@Override
public void eat(){
System.out.println("吃虫子");
}
@Override
public void swim(){
System.out.println("蛙泳");
}
}
//Dog.java
package com.ljsblog.domain2;
public class Dog extends Animal implements Regularity{
public Dog(){}
public Dog(String name,int age){
super(name,age);
}
@Override
public void eat(){
System.out.println("吃骨头");
}
@Override
public void swim(){
System.out.println("狗刨");
}
}
//Rabbit.java
package com.ljsblog.domain2;
public class Rabbit extends Animal{
public Rabbit(){}
public Rabbit(String name,int age){
super(name,age);
}
@Override
public void eat(){
System.out.println("吃胡萝卜");
}
}
//测试类
//Test.java
package com.ljsblog.domain2;
public class Test{
public static void main(String[] args){
Frog f=new Frog();
f.eat();
f.swim();
Dog d=new Dog();
d.eat();
d.swim();
Rabbit r=new Rabbit();
r.eat();
}
}
/*
吃虫子
蛙泳
吃骨头
狗刨
吃胡萝卜
*/
接口中成员的特点
- 成员变量
- 只能是常量,默认修饰符:public static final,修饰符可以省略
- 构造方法-无
- 成员方法
- 只能是抽象方法,默认修饰符:public abstract,修饰符可以省略
接口与类的关系
- 类与类的关系
- 继承关系。只能单继承,不能多继承,但可多层继承
- 类与接口的关系
- 实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
- 若多个接口中有重名的方法,只需重写一次
- 接口与接口的关系
- 继承关系,可以单继承,也可以多继承
- 若实现最下方的子接口,则需要重写所有的抽象方法
抽象类和接口的对比
- 成员变量
- 抽象类:可定义变量和常量
- 接口:只可定义常量
- 成员方法
- 抽象类:可定义具体方法和抽象方法
- 接口:只能定义抽象方法
- 构造方法
- 抽象类:有
- 接口:无
抽象类对事务做抽象(描述事物)
接口对行为抽象(指定规则)
范例
//需求
乒乓球运动员: 姓名,年龄,学打乒乓球,说英语
篮球运动员: 姓名,年龄,学打篮球
乒乓球教练: 姓名,年龄,教打乒乓球,说英语
篮球教练: 姓名,年龄,教打篮球
//Person.java
package com.ljsblog.domain3;
public class Test {
public static void main(String[] args){
PingAthlete pa=new PingAthlete("张三",16);
pa.study();
pa.speakEnglish();
BasketballAthele ba=new BasketballAthele("李四",16);
ba.study();
PingTeacher pt=new PingTeacher("王五一",26);
pt.teach();
pt.speakEnglish();
BasketBallTeacher bt=new BasketBallTeacher("王五二",26);
bt.teach();
}
//Athlete.java
package com.ljsblog.domain3;
public abstract class Athlete extends Person{
public Athlete(){}
public Athlete(String name,int age){
super(name,age);
}
public abstract void study();
}
//Teacher.java
package com.ljsblog.domain3;
public abstract class Teacher extends Person{
public Teacher(){}
public Teacher(String name,int age){
super(name,age);
}
public abstract void teach();
}
//Speak.java
package com.ljsblog.domain3;
public interface Speak{
public abstract void speakEnglish();
}
//BasketballAthele.java
package com.ljsblog.domain3;
public class BasketballAthele extends Athlete{
public BasketballAthele(){}
public BasketballAthele(String name,int age){
super(name,age);
}
@Override
public void study(){
System.out.println("年龄为"+getAge()+"的篮球运动员"+getName()+"学打篮球");
}
}
//BasketBallTeacher.java
package com.ljsblog.domain3;
public class BasketBallTeacher extends Teacher{
public BasketBallTeacher(){}
public BasketBallTeacher(String name,int age){
super(name,age);
}
@Override
public void teach(){
System.out.println("年龄为"+getAge()+"的篮球教练"+getName()+"教打篮球");
}
}
//PingAthlete.java
package com.ljsblog.domain3;
public class PingAthlete extends Athlete implements Speak{
public PingAthlete(){}
public PingAthlete(String name,int age){
super(name,age);
}
@Override
public void study(){
System.out.println("年龄为"+getAge()+"的乒乓球运动员"+getName()+"学打乒乓球");
}
@Override
public void speakEnglish(){
System.out.println("乒乓球运动员在说英语");
}
}
//PingTeacher.java
package com.ljsblog.domain3;
public class PingTeacher extends Teacher implements Speak{
public PingTeacher(){}
public PingTeacher(String name,int age){
super(name,age);
}
@Override
public void teach(){
System.out.println("年龄为"+getAge()+"的乒乓球教练"+getName()+"教打乒乓球");
}
@Override
public void speakEnglish(){
System.out.println("乒乓球教练在说英语");
}
}
//测试类
package com.ljsblog.domain3;
public class Test {
public static void main(String[] args){
PingAthlete pa=new PingAthlete("张三",16);
pa.study();
pa.speakEnglish();
BasketballAthele ba=new BasketballAthele("李四",16);
ba.study();
PingTeacher pt=new PingTeacher("王五一",26);
pt.teach();
pt.speakEnglish();
BasketBallTeacher bt=new BasketBallTeacher("王五二",26);
bt.teach();
}
}
/*
年龄为16的乒乓球运动员张三学打乒乓球
乒乓球运动员在说英语
年龄为16的篮球运动员李四学打篮球
年龄为26的乒乓球教练王五一教打乒乓球
乒乓球教练在说英语
年龄为26的篮球教练王五二教打篮球
*/
JDK8后接口新增方法
默认方法
允许在接口中定义默认方法,需使用关键字default修饰,以此解决接口升级的问题。
接口中默认方法定义格式:
public default 返回值类型 方法名(参数列表){}
注意事项:
- 默认方法不是抽象方法,不强制被重写;若要重写,重写时去掉default关键字。
- public可省,default不可省
- 若实现多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写。
- 若重写默认方法需要使用接口的原代码,需要在重写时编写:接口名.super.方法名()
范例:
//接口
public interface Inter{
public abstract void show1();
public default void show2(){
System.out.println("默认方法");
}
}
public class A implements Inter{
@Override
public void show1(){
System.out.println("抽象方法");
}
}
public class Test{
public static void main(String[] args){
A a=new A();
a.show1();
a.show2();
}
}
/*
抽象方法
默认方法
*/
静态方法
允许在接口中定义静态方法,需要用static修饰。
定义格式:
public static 返回值类型 方法名(参数列表){}
注意事项:
- 静态方法只能用接口名调用,不能通过实现类名或者对象名调用
- public可省,static不能省
- 静态方法不能被重写
范例
//接口
public interface Inter{
public static void show(){
System.out.println("静态方法");
}
}
//测试类
//Test.java
public class Test{
public static void main(String[] args){
Inter.show();//静态方法
}
}
JDK9接口特性
接口中允许定义private私有方法,以提高代码复用性。
格式1:
private 返回值类型 方法名(参数列表){}
格式2:
private static 返回值类型 方法名(参数列表){}
范例:
public interface Test {
public abstract void a();
int b();
//格式1
public default void print1(){
print();
System.out.println("1");
}
default void print2(){
print();
System.out.println("2");
}
private void print(){
System.out.println("重复代码");
}
//格式2
public static void print3(){
printStatic();
System.out.println("1");
}
static void print4(){
printStatic();
System.out.println("2");
}
private static void printStatic(){
System.out.println("静态");
}
}
应用
- 接口代表规则,是行为的抽象,想让哪个类拥有一个行为,就让这个类实现对应的接口就可以了。
- 当一个方法参数是接口时,可传递接口所有的实现类的对象,这种方式称为接口多态。
设计模式
设计模式简单来说就是解决问题的各种套路。
适配器设计模式
当一个接口中抽象方法过多,但只要使用其中一部分时,就可以使用适配器设计模式。
书写步骤:
- 编写中间类XXXAdapter,实现对应的接口
- 对接口中的抽象方法进行空实现,即方法体为空
- 让真正的实现类继承中间类,并重写需要用的方法
- 为避免其他类创建适配器类的对象,适配器类用abstract修饰。
范例:
//接口
public interface Inter{
public abstract void show1();
public abstract void show2();
public abstract void show3();
public abstract void show4();
public abstract void show5();
public abstract void show6();
}
//适配器类
public abstract class InterAdapter implements Inter{
@Override
public void show1() {
}
@Override
public void show2() {
}
@Override
public void show3() {
}
@Override
public void show4() {
}
@Override
public void show5() {
}
@Override
public void show6() {
}
}
//实现类
public class Im extends InterAdapter{
@Override
public void show3(){
System.out.println("方法3");
}
}
//测试类
package com.ljsblog.domain5;
public class Test {
public static void main(String[] args){
Im i=new Im();
i.show3();//方法3
}
}
模板设计模式
模板设计模式:把抽象类整体就可以看成一个模板,模板中不能决定的东西定义成抽象方法,让使用模板的类(继承抽象类的类)去重写抽象方法实现需求。
作用:模板已经定义通用结构,使用者只需关心自己需要实现的功能即可。
范例:
//模板
public abstract class Eat {
//固定模板,子类不能重写,加final
public final void eatFood(){
System.out.println("做饭");
food();
System.out.println("刷碗");
}
public abstract void food();
}
//使用模板的类
public class EatFruit extends Eat{
@Override
public void food() {
System.out.println("吃水果");
}
}
public class EatMeat extends Eat{
@Override
public void food() {
System.out.println("吃肉");
}
}
//测试类
public class Test {
public static void main(String[] args) {
EatFruit eatFruit=new EatFruit();
eatFruit.eatFood();
System.out.println("=================");
EatMeat eatMeat=new EatMeat();
eatMeat.eatFood();
}
}
/*
做饭
吃水果
刷碗
=================
做饭
吃肉
刷碗
*/
内部类
类的五大成员:
- 属性
- 方法
- 构造方法
- 代码块
- 内部类
内部类定义:在一个类中,再定义一个类,例如,在A类内部定义B类,B类称为内部类。
内部类表示的事物是外部类的一部分,内部类单独出现没有任何意义。
访问特点:
- 内部类可以直接访问外部类的成员,包括私有
- 外部类要访问内部类的成员,必须创建对象
应用场景:B类表示的事物是A类的一步,且B类存在没有任何意义。例如:ArrayList迭代器,汽车的发动机,人的心脏。
分类:
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类(重点)
成员内部类
写在成员位置,即类中方法外,属于外部类的成员。
成员内部类可以被一些修饰符修饰,比如:private,默认,protected,public,static等。
在成员内部类中,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。
获取内部类对象方式:
- 在外部类中编写方法,对外提供内部类的对象。(private)
- 直接创建,格式:外部类名.内部类名 对象名=外部类对象.内部类对象;
范例:
方法一
package com.ljsblog.domain5;
public class Outer{
public Inner getInner(){
return new Inner();
}
private class Inner{
public void show(){
}
}
}
package com.ljsblog.domain5;
public class Test{
public static void main(String[] args){
Outer o=new Outer();
System.out.println(o.getInner());
}
}
方法二
package com.ljsblog.domain5;
public class Outer{
public Inner getInner(){
return new Inner();
}
public class Inner{
public void show(){
}
}
}
package com.ljsblog.domain5;
public class Test{
public static void main(String[] args){
Outer.Inner oi=new Outer().new Inner();
System.out.println(oi);
}
}
当外部类成员变量和内部类成员变量重名时,内部类访问外部类成员变量:
System.out.println(Outer.this.变量名);
范例:
package com.ljsblog.domain5;
public class Outer{
private int a=1;
class Inner{
private int a=2;
public void show(){
int a=3;
System.out.println(Outer.this.a);//1
System.out.println(this.a);//2
System.out.println(a);//3
}
}
}
package com.ljsblog.domain5;
public class Test{
public static void main(String[] args){
Outer.Inner oi=new Outer().new Inner();
oi.show();
}
}
静态内部类
静态内部类只能访问外部类中的静态变量和静态方法,若要访问非静态的需要创建对象。
创建静态内部类对象的格式:
外部类名.内部类名 对象名=new 外部类名.内部类名();
调用非静态方法的格式:先创建对象,用对象调用。
调用静态方法的格式:
外部类名.内部类名.方法名();
范例:
package com.ljsblog.domain5;
public class Outer{
int a=10;
static int b=20;
static class Inner{
public static void show1(){
System.out.println("静态方法");
Outer o=new Outer();
System.out.println(o.a);
System.out.println(b);
}
public void show2(){
System.out.println("非静态方法");
Outer o=new Outer();
System.out.println(o.a);
System.out.println(b);
}
}
}
package com.ljsblog.domain5;
public class Test{
public static void main(String[] args){
Outer.Inner oi=new Outer.Inner();
Outer.Inner.show1();
oi.show2();
}
}
/*
静态方法
10
20
非静态方法
10
20
*/
局部内部类
- 将内部类定义在方法,代码块,构造器等执行体中就叫局部内部类,类似于方法中的局部变量
- 外界是无法直接使用,需要在方法内部创建对象并使用
- 该类可以直接访问外部类的成员,也可方法方法内部的局部变量
- 因为局部内部类没啥用,所以就不举例了。
匿名内部类
匿名内部类本质上就是隐藏了名字的内部类,并非真的没有名字。
格式:
new 类名/接口名(){
重写方法;
}
//其中包含了继承或实现,方法重写,创建对象,整体就是一个类的子类或者接口的实现类对象
使用场景:当方法的参数是接口或类时,以接口为例,可传递该接口的实现类对象,若实现类仅使用一次,可用匿名内部类简化代码。
范例
//接口
public abstract interface Inter{
public abstract void speakEnglish();
}
//父类
public abstract class Animal{
public abstract void eat();
}
//测试类
public class Test{
public static void main(String[] args){
Inter i=new Inter(){
@Override
public void speakEnglish(){
System.out.println("讲英语");
}
};
i.speakEnglish();
Animal a=new Animal(){
@Override
public void eat(){
System.out.println("狗吃骨头");
}
};
a.eat();
}
}
/*
讲英语
狗吃骨头
*/
Lambda表达式
Lambda表达式是JDK8开始后出现的一种新语法形式。
格式:
()->{
}
//() 对应方法的形参
//-> 固定格式
//{} 对应着方法的方法体
基本作用:简化函数化接口的匿名内部类的写法。
函数化接口(函数式编程接口):有且仅有一个抽象方法的接口叫做函数化接口,接口上方可以加@FunctionalInterface注解。
使用前提:必须是接口的匿名内部类,接口中只能有一个抽象方法。
好处:Lambda是一个匿名函数,可以把Lambda表达式理解为一段可以传递的代码,它可以写成更简洁,更灵活的代码。
省略写法
省略核心:可推导,可省略。
- 参数类型可省略
- 若只有一个参数,参数类型和()均可省(若没有参数,小括号不能省略)
- 若Lambda表达式的方法体只有一行,大括号,分号,若这行代码是return语句,则return可省略,但必须同时省略。
对比范例一
import java.util.Arrays;
import java.util.Comparator;
public class Test{
public static void main(String[] args){
Integer[] arr1={69,42,65,89,26,32,16};
//原版
Arrays.sort(arr1,new Comparator<Integer>(){
@Override
public int compare(Integer o1,Integer o2){
return o1-o2;
}
});
System.out.println(Arrays.toString(arr1));
//Lambda表达式
Integer[] arr2={69,42,65,89,26,32,16};
Arrays.sort(arr2,(Integer o1,Integer o2)->{
return o1-o2;
});
System.out.println(Arrays.toString(arr2));
//Lambda表达式省略版
Integer[] arr3={69,42,65,89,26,32,16};
Arrays.sort(arr3,(o1,o2)->o1-o2);
System.out.println(Arrays.toString(arr3));
}
}
/*
[16, 26, 32, 42, 65, 69, 89]
[16, 26, 32, 42, 65, 69, 89]
[16, 26, 32, 42, 65, 69, 89]
*/
对比范例二
public class Test{
public static void main(String[] args){
//原版
method("小白",new Swim(){
@Override
public void swiming(String name){
System.out.println(name+"游泳");
}
});
//Lambda表达式
method("小白",(String name)->{
System.out.println(name+"游泳");
});
//Lambda表达式省略版
method("小白",name->System.out.println(name+"游泳"));
}
public static void method(String name,Swim s){
s.swiming(name);
}
}
@FunctionalInterface
interface Swim{
public abstract void swiming(String name);
}
/*
小白游泳
小白游泳
小白游泳
*/
范例
/*
定义数组并存储一些字符串,利用Arrays中的sort方法进行排序
要求:
按照字符串中的长度排序,从短到长。
*/
import java.util.Arrays;
import java.util.Comparator;
public class Test{
public static void main(String[] args) {
String[] arr={"aaaa","a","aaaaa","aaa","aaaaaaaaaaa"};
//原版
/*
Arrays.sort(arr,new Comparator<String>(){
@Override
public int compare(String o1, String o2) {
return o1.length()-o2.length();
}
});
*/
//Lambda版
/*
Arrays.sort(arr,(String o1,String o2)->{
return o1.length()-o2.length();
});
*/
//Lambda省略版
Arrays.sort(arr,(o1,o2)->o1.length()-o2.length());
System.out.println(Arrays.toString(arr));
}
}
/*
[a, aaa, aaaa, aaaaa, aaaaaaaaaaa]
*/
Lambda表达式和匿名内部类的区别
- 使用限制不同
- 匿名内部类:可以操作类,接口
- Lambda表达式:只能操作函数式接口
- 实现原理不同
- 匿名内部类:编译之后,会产生一个单独的.class字节码文件
- Lambda表达式:编译之后,没有一个单独的.class字节码文件