1 接口
序言:我们还延续介绍是抽象类的时候所举的例子,我们知道继承抽象类的所有子类,需要将抽象类中的所有抽象方法进行重写,这样在多态的机制中,就可以将父类修改为抽象类,将draw()方法设置为抽象方法,然后每个子类都重写这个方法来处理。但这又会使代码变的冗余,同样这样的父类局限性很大,也许某个不需要draw()方法的子类也不得不重写draw()方法。如果将draw()方法放置在另一个类中,让那些需要draw()方法的类继承该类,不需要draw()方法的类继承图形类,又会产生新的问题:所有的子类都需要继承图形类,因为这些类是从图形类中导出的,同时某些类还需要draw()方法,而Java中规定类不能同时继承多个父类。为了应对这一个概念,接口的概念便出现了。
总纲: 我们可以把接口当成一种特殊的抽象类。
1.1 接口的组成
接口用关键字interface修饰
public interface 接口名 { }
类实现接口用implements表示
public class 类名 implements 接口名 { }
(1)注意事项:
★接口不能实例化,但我们可以创建接口的实现类对象使用。
★接口的子类:要么重写接口中的所有抽象方法,要么子类也是抽象类。
(2)接口的成员组成:
- 成员变量:
只能是常量
默认修饰符:public static final
- 构造方法:
没有,因为接口主要是扩展功能的,而没有具体存在的
- 成员方法:
只能是抽象方法
默认修饰符:public abstract
(3)代码演示:
接口
public interface Inter {
public static final int NUM = 10;
public abstract void show();
}
实现类
class InterImpl implements Inter{
public void method(){
// NUM = 20;
System.out.println(NUM);
}
public void show(){
}
}
测试类
public class TestInterface {
/*
成员变量: 只能是常量 系统会默认加入三个关键字
public static final
构造方法: 没有
成员方法: 只能是抽象方法, 系统会默认加入两个关键字
public abstract
*/
public static void main(String[] args) {
System.out.println(Inter.NUM);
}
}
1.2类和接口的关系
- 类与类的关系
继承关系,只能单继承,但是可以多层继承
- 类与接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
- 接口与接口的关系
继承关系,可以单继承,也可以多继承
1.3 接口中默认方法
- 格式
public default 返回值类型 方法名(参数列表) { }
- 作用
解决接口升级的问题
范例
public default void show3() {
}
注意事项
-
默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉default关键字
-
public可以省略,default不能省略
-
如果实现了多个接口,多个接口中存在相同的方法声明,子类就必须对该方法进行重写
1.4接口中静态方法【应用】
- 格式
public static 返回值类型 方法名(参数列表) { }
- 范例
public static void show() {
}
注意事项
-
静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
-
public可以省略,static不能省略
1.5接口中私有方法
- 私有方法产生原因
Java 9中新增了带方法体的私有方法,这其实在Java 8中就埋下了伏笔:Java 8允许在接口中定义带方法体的默认方法和静态方法。这样可能就会引发一个问题:当两个默认方法或者静态方法中包含一段相同的代码实现时,程序必然考虑将这段实现代码抽取成一个共性方法,而这个共性方法是不需要让别人使用的,因此用私有给隐藏起来,这就是Java 9增加私有方法的必然性
- 定义格式
格式1: private 返回值类型 方法名(参数列表) {
}
案例: private void show() {
}
格式2: private static 返回值类型 方法名(参数列表) {
}
案例: private static void method() {
}
- 注意事项
默认方法可以调用私有的静态方法和非静态方法
静态方法只能调用私有的静态方法
3.多态
3.1 多态的相关用法
(1)多态的好处与弊端
好处:提高程序的拓展性,定义方法的时候,使用父类型作为参数,在使用的时候,使用具体的子类型参数操作。
弊端:不能使用子类的特有成员。
(2)多态中的转型:
向上转型:当有子类对象赋值给父类应用时,便是向上转型。多态本身就是向上转型的过程。
向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型。(注意前提是有向上转型的子类对象,如果是直接创建父类对象是无法向下转型的)。
为什么要有向下转型:
这是由于多态的弊端,通过向上转型不能使用子类的特有功能。
如果就想使用子类的特有功能,怎么做?
A:最原始的创建对象调用方法呗。(但是很多时候不合理。而且建了太多对象会占内存)
B: 把父类的引用强制转换为子类的引用。(向下转型)
(3)我们最好理解下这里的方法重写,以及继承的意义就会很好理解
public class fu {
int age =42;
public void teach(){
System.out.println("我是一名教Java的老师");
}
}
public class zi extends fu {
int age =19;
public void teach(){
System.out.println("我也会教Java,我从我爸那学的");
}
public void student(){
System.out.println("我还是一名学生");
}
}
public class Test01 {
public static void main(String[] args) {
//向上转型 (只能调出子类的重写方法)
fu f= new zi();
f.teach(); //我也会教Java,我从我爸那学的
System.out.println(f.age); //*******注意 42*******注意
//向下转型 //就是为了方便能调用子类所有的对象,实质和父类没啥太大关系
zi z =(zi)f;
System.out.println(z.age); //19
z.student(); //我还是一名学生
z.teach(); //我也会教Java,我从我爸那学的
}
}
3.2 instanceof关键字
instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。
注意:
- 类的实例包含本身的实例,以及所有直接或间接子类的实例
- instanceof左边显式声明的类型与右边操作元必须是同种类或存在继承关系,也就是说需要位于同一个继承树,否则会编译错误
abstract class Animal {
public abstract void eat();
}
class Dog extends Animal {
public void eat() {
System.out.println("狗吃肉");
}
public void watchHome(){
System.out.println("看家");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test4Polymorpic {
public static void main(String[] args) {
useAnimal(new Dog());
useAnimal(new Cat());
}
public static void useAnimal(Animal a){ // Animal a = new Dog();
// Animal a = new Cat();
a.eat();
//a.watchHome();
// Dog dog = (Dog) a;
// dog.watchHome(); // ClassCastException 类型转换异常
// 判断a变量记录的类型, 是否是Dog
if(a instanceof Dog){
Dog dog = (Dog) a;
dog.watchHome();
}
}
}
结果:
狗吃肉
看家
猫吃鱼
4 内部类
内部类的概念:在一个类中定义一个类。举例:在一个类A的内部定义一个类B,类B就被称为内部类
class Outer {
public class Inner {
}
}
内部类的访问特点 :
-
内部类可以直接访问外部类的成员,包括私有
-
外部类要访问内部类的成员,必须创建对象
public class Outer {
private int num = 10;
public class Inner {
public void show() {
System.out.println(num);
}
}
public void method() {
Inner i = new Inner();
i.show();
}
}
4.1 成员内部类
位置:在类中方法,跟成员变量一个位置
外界创建成员内部类格式(万能):
-
格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
-
举例:Outer.Inner oi = new Outer().new Inner();
(1)私有成员内部类
将一个类,设计为内部类的目的,大多数都是不想让外界去访问,所以内部类的定义应该私有化,私有化之后,再提供一个可以让外界调用的方法,方法内部创建内部类对象并调用。
class Outer {
private int num = 10;
private class Inner {
public void show() {
System.out.println(num);
}
}
public void method() {
Inner i = new Inner();
i.show();
}
}
public class InnerDemo {
public static void main(String[] args) {
//Outer.Inner oi = new Outer().new Inner();
//oi.show();
Outer o = new Outer();
o.method();
}
}
(2)静态成员内部类
-
静态成员内部类访问格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
-
静态成员内部类中的静态方法:外部类名.内部类名.方法名();
class Outer {
static class Inner {
public void show(){
System.out.println("inner..show");
}
public static void method(){
System.out.println("inner..method");
}
}
}
public class Test3Innerclass {
public static void main(String[] args) {
// 外部类名.内部类名 对象名 = new 外部类名.内部类名();
Outer.Inner oi = new Outer.Inner();
oi.show();
Outer.Inner.method();
}
}
4.2 局部内部类
局部内部类定义位置
- 局部内部类是在方法中定义的类
局部内部类方式方式
- 局部内部类,外界是无法直接使用,需要在方法内部创建对象并使用
- 该类可以直接访问外部类的成员,也可以访问方法内的局部变量
class Outer {
private int num = 10;
public void method() {
int num2 = 20;
class Inner {
public void show() {
System.out.println(num);
System.out.println(num2);
}
}
Inner i = new Inner();
i.show();
}
}
public class OuterDemo {
public static void main(String[] args) {
Outer o = new Outer();
o.method();
}
}
4.3 匿名内部类
★匿名类:是只在创建对象时才会编写类体的一种写法。匿名类的特点是“现用现写”。
★语法:最后一个大括号之后有分号
// 标 准 语 法
new 父类/父接口(){
子类实现的内容
};
//案例
new Inter(){
@Override
public void method(){}
}
★本质:匿名内部类的本质是匿名的对象,这个对象继承了这个类,或者实现了这个接口吗,new标志了这是一个对象,new后面的那个是该对象继承的那个类或者是实现的那个接口。
(1)匿名内部类可以通过多态的形式接受:
Inter i = new Inter(){
@Override
public void method(){
}
}
(2)匿名内部类直接调用方法
interface Inter{
void method();
}
class Test{
public static void main(String[] args){
new Inter(){
@Override
public void method(){
System.out.println("我是匿名内部类");
}
}.method(); // 直接调用方法
}
}
(3)综合运用
interface Swimming {
void swim();
}
public class TestSwimming {
public static void main(String[] args) {
goSwimming(new Swimming() {
@Override
public void swim() {
System.out.println("老铁, 我们去游泳吧");
}
});
}
public static void goSwimming(Swimming swimming){
/*
Swimming swim = new Swimming() {
@Override
public void swim() {
System.out.println("老铁, 我们去游泳吧");
}
}
*/
swimming.swim();
}
}
5 Lambda表达式
★函数式接口:仅包含一个抽象方法的接口。
★lambda表达式不能独立执行,因此必须实现函数式接口,并且会返回一个函数式接口的对象。
★lambda表达式的理解:
( ) -> {代码块}
这个方法 按照 这样的代码来执行
5.1 lambda表达式调用无参抽象方法
无参抽象方法在lambda表达式中使用“()”表示
//通过多态的形式
public interface Eatable {
void eat();
}
public class EatableDemo {
public static void main(String[] args) {
useEatable(() -> {
System.out.println("一天一苹果,医生远离我");
});
}
//lambda表达式不能独立执行,因此必须实现函数式接口,并且会返回一个函数式接口的对象(子类)。
private static void useEatable(Eatable e) {
e.eat();
}
}
//***************************************************************
//通过创建对象的形式
public interface Eatable {
void eat();
}
public class EatableDemo {
public static void main(String[] args) {
Eatable e = () -> {
System.out.println("一天一苹果,医生远离我");
};
}
System.out.println(e.eat());
}
5.2 lambda表达式实现有参抽象方法
public interface Addable {
int add(int x,int y);
}
public class AddableDemo {
public static void main(String[] args) {
useAddable((int x,int y) -> {
return x + y;
});
}
private static void useAddable(Addable a) {
int sum = a.add(10, 20);
System.out.println(sum);
}
}
5.3 lambda表达式调用外部变量
(1)lambda表达式无法更改局部变量(注意局部变量的定义)
interface defin{
void method();
}
public class s2{
public static void main(String[] args) {
int value =100; //创建局部变量
defin v=()->{
int num =value -90;
value =12; //更改局部变量,报错
};
}
}
(2)lambda 表达式可以更改类成员变量
interface face2{
void method();
}
public class Server {
int value =100;
public void action(){
face2 v =()->{
value =-12;
};
System.out.println("运行前"+value); //100
v.method();
System.out.println("运行后"+value); //-12
}
public static void main(String[] args) {
Server d =new Server();
d.action();
}
}
总结:
lambda表达式只是描述了函数式接口的抽象方法是如何实现的,在抽象方法没有调用前,lambda表达式中的代码并没有被执行
5.4 lambda表达式和匿名内部类的区别
- 所需类型不同
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
- Lambda表达式:只能是接口
- 使用限制不同
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
- 实现原理不同
- 匿名内部类:编译之后,产生一个单独的.class字节码文件
- Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成