抽象类
引例
◼ 父类 Shape
:无属性,面积为 0;
◼ 子类:矩形类(Rectangle
)和圆(Circle
)类,有自有属性,能求面积
◼ 多态方式测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class Shape { public double getArea() { return 0.0; } } class Rectangle extends Shape { private int length; private int width; public Rectangle() { } public Rectangle(int length, int width) { this.length = length; this.width = width; } public double getArea() { return length * width; } } class Circle extends Shape { private double r; public Circle() { } public Circle(double r) { this.r = r; } public double getArea() { return Math.PI * r * r; } } class Test { public static void main(String[] args) { Shape s = new Rectangle(3,4); System.out.println( s.getArea() ); } }
|
抽象类概念
◼ Java类可定义 一些不含方法体的方法,它的实现交给 子类 根据自己的情况去实现,这样的方法就是 抽象方法,包含抽象方法的类叫 抽象类。
简单来说:如果一个类中没有包含足够的信息来描绘一个具体对象,那么这样的类称为抽象
◼ 抽象关键字:abstract
抽象类特点
◼ 抽象类只是一个类型的部分实现,所以不能被实例化(不能 new 对象)。 (那有啥用?)
◼ 抽象方法没有方法体,必须存在于抽象类中。
◼ abstract 不能用于 static 方法或者构造函数,也不能与 private、final 共同修饰同一个方法。 (想想)
◼ 子类可以继承抽象父类,一般要重写父类所有的抽象方法,如果没有,则该子类还要声明为抽象类。
抽象类示例
(带构造函数的抽象类)
◼ 抽象类 Animal:
◼ 公有属性:animalName(String)
◼ 2 个构造函数:Animal()、Animal(String)
◼ 抽象方法:eat()
◼ 子类: Dog 和 Cat 类
◼ 各自都有构造函数: Dog()/Dog(String)、Cat()/Cat(String)
◼ 各自实现抽象方法:eat()
◼ 分别输出:某某狗吃骨头!, 某某猫吃老鼠!
抽象类 -- Animal
1 2 3 4 5 6 7 8 9
| public abstract class Animal { public String animalName; public Animal() { } public Animal(String animalName) { this.animalName = animalName; } public abstract void eat(); }
|
Dog类和Cat类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Dog extends Animal { public Dog() { } public Dog(String s) { super(s); } public void eat() { System.out.println(animalName + "吃骨头!"); } } public class Cat extends Animal { public Cat() { } public Cat(String s) { super(s); } public void eat() { System.out.println(animalName + "吃老鼠!"); } }
|
测试类
1 2 3 4 5 6 7 8
| public class Test { public static void main(String args[]) { Animal a = new Dog("哈士奇"); a.eat(); } }
|
抽象类归纳:
◼ 含有抽象方法的类必须被声明为抽象类。但抽象类中不一定包含的都是抽象方法
一个抽象类,可以有 0n 个抽象方法,以及 0n 个具体方法
◼ 抽象类可以有构造方法,但构造方法不能声明为抽象★
◼ 抽象类提供一个类型的部分实现,所以不能被实例化(即不能用 new 去产生对象),但可声明对象(用于多态) ★
◼ 抽象类不能用 final 来修饰,即一个类不能既是最终类又是抽象类
◼ 抽象类的子类必须重写所有抽象方法后才能被实例化,否则子类还是个抽象类
◼ 抽象方法只需声明,而不需实现
◼ abstract 不能与 private、static、final 并列修饰同一个方法
接口
接口概念
◼ 接口是一种特殊的抽象类:
◼ 如果一个抽象类中的所有方法都是抽象的==Java8 以前版本==,这个类就定义为接口 (interface)
◼ 接口的所有方法通常由子类全部实现 (implements)==不是extends==,不同子类的实现可以具有不同的功能
◼ 如果一个类没有全部实现某个接口的所有方法,则这个类必须声明为抽象的
接口示例1
◼ 定义Shape接口,包含两个抽象方法:
◼ double getPerimeter(); //求周长
◼ double getArea(); //求面积
◼ 两个实现类:
◼ 矩形类(Rectangle)和圆(Circle)类
◼ 有自有属性,能求周长和面积
Shape 接口
1 2 3 4 5
| public interface Shape { public abstract double getPerimeter(); public abstract double getArea(); }
|
Rectangle 和 Circle 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Rectangle implements Shape { private double height; private double width; public Rectangle(double height,double width) { this.height = height; this.width = width; } @Override public double getPerimeter() { return 2 * (height + width); } @Override public double getArea() { return height * width; } } class Circle implements Shape { private double radius; public Circle(double radius) { this.radius = radius; } @Override public double getPerimeter() { return 2 * Math.PI * radius; } @Override public double getArea() { return Math.PI * radius * radius; } }
|
测试类
1 2 3 4 5 6 7 8 9
| public class Test { public static void main(String[] args) { Shape r = new Rectangle(10,10); System.out.println("矩形面积="+ r.getArea() ); System.out.println("矩形周长="+ r.getPerimeter() ); } }
|
思考:什么时候用抽象类,什么时候用接口?
接口示例2
◼ 定义一个整形堆栈 IntStack 接口:
◼ 包含 2 个方法:void push(int item)
和 int pop()
◼ 编写两个实现类:
(1)FixedStack
实现类:实现一个固定长度的整数堆栈
(2)DynStack
实现类:实现一个动态栈,每个栈都以一个初始长度构造,如果初始化长度被超出,则堆栈的大小将成倍增加(原有数据要保留)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public interface IntStack { void push(int item); int pop(); } class FixedStack implements IntStack { private int stck[]; private int pos; public FixedStack( int size ) { stck = new int[size]; pos = -1; } @Override public int pop() { if (pos < 0) { System.out.print("Stack is underflow."); return -1; } else return stck[pos--]; } @Override public void push(int item) { if (pos == stck.length - 1) System.out.print("Stack is full."); else stck[++pos] = item; } } class TestStack1 { public static void main(String[] args) { IntStack fixedStack = new FixedStack(5); for (int i = 0; i < 5; i++) fixedStack.push(i); System.out.print( "Stack in mystack:" ); for (int i = 0; i < 5; i++) System.out.print( fixedStack.pop() + " " ); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class DynStack implements IntStack { private int stck[]; private int pos; public DynStack( int size ) { stck = new int[size]; pos = -1; } @Override public int pop() { if (pos < 0) { System.out.print("Stack is underflow."); return -1; } else return stck[pos--]; } @Override public void push(int item) { if (pos == stck.length - 1) { int temp[] = new int[stck.length * 2]; for (int i = 0; i < stck.length; i++) temp[i] = stck[i]; stck = temp; stck[++pos] = item; } else stck[++pos] = item; } } class TestStack2 { public static void main(String[] args) { IntStack dynStack = new DynStack(5); for (int i = 0; i < 10; i++) dynStack.push(i); System.out.print("Stack in mystack:"); for (int i = 0; i < 10; i++) System.out.print(dynStack.pop() + " "); } }
|
接口常量
◼ 接口中不能有变量,但可有常量
◼ 常量默认是 public static final 类型(公有全局静态常量),且必须被显示初始化。
why👇
◼ 为什么不能定义变量:由于接口中的方法都是抽象的,如果接口可以定义变量,那么在接口中无法通过行为(方法)来修改属性
◼ 为什么是 public static:接口提供的是一种统一的”协议”,接口中的属性也属于”协议”成员,所有实现类都可以共享这一”协议”
◼ 为什么是 final:实现接口的对象如果修改了接口中的属性,那么所有对象也都会自动拥有这一改变后的值,这和接口提供的统 一的抽象这种思想相抵触
接口常量示例
1 2 3 4 5 6 7 8 9
| public interface A { public static final int END=0; private final int OK=1; int a; int b=1; public final int NO=-1; static int share=0; }
|
接口常量用法
1 2 3 4
| public interface Person { public static final int MALE = 1; public static final int FEMALE = 2; }
|
简写👇
1 2 3 4
| public interface Person { int MALE = 1; int FEMALE = 2; }
|
使用接口常量:
1 2 3 4 5 6
| if( gender == Person.MALE ) { … } if( gender == Person.FEMALE ) { … }
|
接口default/static方法
◼ Java 8 以后,接口可添加 default 或者 static 方法 (有实现的)
default
方法:实现接口的类 可以继承,可以选择重写 (注:多接口实现如有同名 default 方法则必须重写)
static
方法:实现接口的类或者子接口 不会继承(不能重写),用接口.方法名()调用
接口 default 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public interface A { public default void foo(){ System.out.println("这是default方法"); } } class B implements A { public void foo() { System.out.println("重写default方法"); } } class Test { public static void main(String[] args) { A a = new B(); a.foo(); } }
|
接口 static 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public interface A { public static void a(){ System.out.println("这是A"); } } public class B implements A { } public class test { public static void main(String[] args) { A a = new B(); a.a(); A.a(); } }
|
多接口
◼ Java一个类可以实现多个接口,从而间接的实现多继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| interface A { double fun1(); } Interface B { double fun2(); } class C implements A , B { public double fun1() { } public double fun2() { } }
|
多接口编程
◼ 将 Shape 接口用多接口实现:
Rectangle 类实现多接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Rectangle implements Shape1, Shape2 { private double height; private double width; public Rectangle(double height,double width) { this.height = height; this.width = width; } @Override public double getArea() { return height * width; } @Override public double getPerimeter() { return 2 * (height + width); } }
|
注:内部代码没有任何改变
Circle 类实现多接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Circle implements Shape1, Shape2 { private double radius; public Circle(double radius) { this.radius = radius; } @Override public double getArea() { return Math.PI * radius * radius; } @Override public double getPerimeter() { return 2 * Math.PI * radius; } }
|
注:内部代码没有任何改变
接口继承多接口
Java 类只支持单继承,不支持多继承,但接口可以继承多个其他接口
1 2 3 4 5 6 7 8 9 10 11 12
| public interface A { void fa(); } public interface B { void fb(); } public interface C extends A, B { void fc(); } public class E implements C { … }
|
接口特点总结
◼ 接口中的方法默认都是 public abstract
类型(可省略),没有方法体
◼ 接口不能有构造函数,不能被实例化
◼ 接口中不能有变量,常量默认是 public static final
,必须被显示初始化
◼ 接口中不能包含静态抽象方法,可以有 default
或者 static
方法 (Java 8以后)
1 2 3 4 5 6 7 8 9
| public interface A { int var=1; public A() { } void method1() { } protected void method2(); static void method3(); static void method4() { } default void method5() { } }
|
◼ 当一个类实现某个接口时,它必须重写这个接口的所有抽象方法(包括这个接口的父接口中的方法),否则 这个类必须声明为抽象的。
◼ 一个类可以实现多个接口 class C implements A , B { … }
◼ 一个接口可继承多个其它的接口 ★ interface C extends A, B { … }
◼ 一个接口不能实现(implements)另一个接口 interface a implements b{ … } //错,接口不能实现接口
◼ 类在继承父类的同时,可实现一个或多个接口,但 extends 关键字必须位于 implements 关键字之前:
1
| public class A extends B implements C, D {...}
|
◼ 接口不能实例化,但允许定义接口类型的引用变量,该引用变量可引用实现该接口的类的实例(多态)
抽象类和接口对比
匿名类
◼ 匿名类:是指没有类名的内部类==编译成功后还是会安排一个特殊名字==,必须在创建时使用 new 语句来声明
◼ 语法形式如下:
匿名类特点:
◼ 匿名类总是一个内部类,并且不能是 static 的
◼ 匿名类 不能是抽象的,必须要实现继承的类或者实现的接口的所有抽象方法
◼ 匿名类总是隐式的 final(不能继承)
◼ 匿名内部类中是不能定义构造函数的 (但可用 { 代码块 } 进行初始化)
◼ 匿名内部类中 不能存在静态成员变量和静态方法(静态常量可以有 static final)
◼ 匿名类的 class 文件命名是:主类$1,2,3….
Lambda表达式
◼ Lambda 表达式是一个匿名函数(也称为箭头函数),基于数学中的 λ 演算得名,现在很多语言都支持 Lambda 表达式
◼ 使用 Lambda 表达式能使代码更简洁紧凑,Lambda 语法格式:
示例:
1 2 3 4 5
| ◼ () -> 5 ◼ (int x, int y) -> x + y ◼ (x, y) -> x – y ◼ x -> 2 * x ◼ (String s) -> System.out.print(s)
|
省略说明:
◼ 类型:不需要声明参数类型,编译器可以统一识别参数值。
◼ 参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
◼ 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
◼ return 关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
函数式接口
◼ 函数式接口:有且只有 一个 抽象方法的接口
◼ @FunctionalInterface
注解:当接口中声明的抽象方法多于或少于一个时就会报错
函数式接口的匿名类实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @FunctionalInterface interface Person { void speak(); } public class Test { public static void main(String[] args) { Person p = new Person() { @Override public void speak() { System.out.println("boy"); } }; p.speak(); } }
|
函数式接口的 Lambda 表达式实现
◼ Lambda 表达式简化了匿名类使用的方法,给予 Java 强大的函数化编程能力
1 2 3 4 5 6 7 8 9 10
| @FunctionalInterface interface Person { void speak(); } public class Test { public static void main(String[] args) { Person p = ()->System.out.println("boy") ; p.speak(); } }
|
用法理解说明:
◼ “=”左边确定是 Person 这个函数式接口,由于函数式接口只有 1 个抽象方法, 因此”=”右边 Lambda 表达式这个匿名函数就只能实现这个 speak() 方法。
◼ 另外如果接口方法有形参(类型),Lambda 表达式也能自动侦测,所以 Lambda 表达式的参数类型都可以省略
Lambda 表达式示例 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @FunctionalInterface interface MathOperation { int operation(int a, int b); } public class Test { public static void main(String[] args) { MathOperation add = (int a, int b) -> { return a + b; } ; MathOperation sub = (a, b) -> a - b ; MathOperation multi = (a, b) -> a * b ; MathOperation div = (a, b) -> a / b ; System.out.println("10 + 5 = " + add.operation(10, 5) ); System.out.println("10 - 5 = " + sub.operation(10, 5) ); System.out.println("10 x 5 = " + multi.operation(10, 5) ); System.out.println("10 / 5 = " + div.operation(10, 5) ); } }
|
【附录】枚举类:enum
◼ 枚举的本质是类,枚举屏蔽了枚举值的类型信息
◼ enum 类型继承自 java.lang.Enum,且无法被继承
◼ 枚举是用来构建 常量数据结构的模板,这个模板可扩展
◼ 枚举的使用增强了程序的健壮性,比如在引用一个不存在的枚举值的时候,编译器会报错(针 对接口常量)
枚举类示例:
Gender.java
1 2 3 4 5 6 7
| public enum Gender { MALE, FEMALE; } Gender gender = Gender.MALE; if( gender == Gender.MALE ) { System.out.println("男生"); }
|
枚举类在 switch 中使用:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static void show( Gender gender ) { switch ( gender ) { case MALE: System.out.println("男生"); break; case FEMALE: System.out.println("女生"); break; default: System.out.println("性别不明"); break; } }
|
枚举类的两个方法
◼ name()
:返回枚举常量名
◼ ordinal()
:返回常量的序号值,默认从 0 开始
1 2 3
| Gender gender = Gender.FEMALE; System.out.println( gender.name() ); System.out.println( gender.ordinal() );
|
枚举类的扩展
和 class 定义一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public enum Gender { MALE(100, "男生"), FEMALE(200, "女生"); private int index; private String name; private Gender(int index, String name) { this.index = index; this.name = name; } public String getName() { return name; } public int getIndex() { return index; } }
|
枚举类扩展测试
1 2 3 4 5
| Gender gender = Gender.FEMALE; System.out.println( gender.getName() ); System.out.println( gender.getIndex() ); System.out.println( gender.name() ); System.out.println( gender.ordinal() );
|