- 1.接口
- 1.1 接口中成员的访问特点
- 1.2 默认方法
- 1.3 静态方法
- 1.4 私有方法
- 1.5 小结
- 2.多态
- 2.1 多态中成员的访问特点
- 2.2 多态的好处和弊端
- 2.3 多态中的转型
- 3.内部类
- 3.1 成员内部类
- 3.2 局部内部类
- 3.3 匿名内部类
- 4.Lambda表达式
- 5.Lambda 表达式和匿名内部类的区别
当一个类中的所有方法都是抽象方法的时候,我们就可以将其定义为接口。
接口也是一种引用数据类型,它比抽象类还要抽象。
接口存在的两个重要意义:
- 规则的定义
- 程序的扩展性
接口的定义格式:public interface 接口名 {}
接口和类之间是实现关系:public class 类名 implements 接口名 {}
接口和类可以单实现,也可以多实现:public class 类名 implements 接口名1, 接口名2 {}
接口的子类(实现类),要么重写接口中的所有抽象方法,要么将自己变成一个抽象类。
接口不能实例化。
package com.qdu.test1;
public interface Inter {
public abstract void study();
}
package com.qdu.test1;
public interface InterA {
public abstract void print1();
public abstract void print2();
public abstract void study();
}
package com.qdu.test1;
public class InterImpl implements Inter, InterA {
@Override
public void study() {
System.out.println("我是实现类中的study方法");
}
@Override
public void print1() {
}
@Override
public void print2() {
}
}
package com.qdu.test1;
public class Test1Interface {
public static void main(String[] args) {
// Inter i = new Inter();
InterImpl ii = new InterImpl();
ii.study();
}
}
1.1 接口中成员的访问特点
接口中的成员变量只能是常量,默认修饰符:public static final。
接口没有构造方法。
接口中的成员方法只能是抽象方法(JDK7及其之前),默认修饰符:public abstract。
package com.qdu.test2;
public interface Inter {
public static final int NUM = 10;
// public Inter(){}
public abstract void show();
}
package com.qdu.test2;
public class TestInterface {
public static void main(String[] args) {
System.out.println(Inter.NUM);
}
}
class InterImpl extends Object implements Inter {
public InterImpl() {
super();
}
public void method() {
// NUM = 20;
System.out.println(NUM);
}
@Override
public void show(){
}
}
1.2 默认方法
JDK8 版本后,Java 只对接口的成员方法进行了改进。
JDK8 版本后允许在接口中定义非抽象方法,但是需要使用关键字 default 修饰,这些方法就是默认方法。
作用:解决接口升级的问题。
接口中默认方法的定义格式:public default 返回值类型 方法名(参数列表) { }
接口中默认方法的注意事项:
- 默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉 default 关键字。
- public 可以省略,default 不能省略。
- 如果实现了多个接口,多个接口中存在相同的方法声明,子类就必须对该方法进行重写。
package com.qdu.test3;
public interface InterA {
public default void show() {
System.out.println("我是A接口中的show方法");
}
}
package com.qdu.test3;
public interface InterB {
public default void show() {
System.out.println("我是B接口中的show方法");
}
}
package com.qdu.test3;
public class TestInterface {
public static void main(String[] args) {
InterAImpl ia = new InterAImpl();
ia.show(); // 我是实现类的show方法
}
}
class InterAImpl implements InterA, InterB {
@Override
public void show() {
System.out.println("我是实现类的show方法");
}
}
1.3 静态方法
JDK8 版本后,接口中允许定义 static 静态方法。
接口中静态方法的定义格式:public static 返回值类型 方法名(参数列表) { }
接口中静态方法的注意事项:
- 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用。
- public 可以省略,static 不能省略。
package com.qdu.test4;
public interface InterA {
public static void show() {
System.out.println("InterA...show");
}
}
package com.qdu.test4;
public interface InterB {
public static void show() {
System.out.println("InterB...show");
}
}
package com.qdu.test4;
public class TestInterface {
public static void main(String[] args) {
// InterAImpl ia = new InterAImpl();
// ia.show();
// InterAImpl.show();
InterA.show(); // InterA...show
InterB.show(); // InterB...show
}
}
class InterAImpl implements InterA , InterB {
}
1.4 私有方法
JDK9 接口中私有方法的定义格式:
-
private 返回值类型 方法名(参数列表) { }
-
private static 返回值类型 方法名(参数列表) { }
package com.qdu.test5;
public interface Inter {
public default void start() {
System.out.println("start方法执行了...");
log();
}
public default void end() {
System.out.println("end方法执行了...");
log();
}
private void log() {
System.out.println("日志记录 ( 模拟 )");
}
private static void check() {
System.out.println("权限校验 ( 模拟 )");
}
public static void open() {
check();
System.out.println("open方法执行了");
}
public static void close() {
check();
System.out.println("close方法执行了");
}
}
package com.qdu.test5;
public class TestInterface {
public static void main(String[] args) {
InterImpl ii = new InterImpl();
ii.start();
ii.end();
Inter.open();
Inter.close();
}
}
class InterImpl implements Inter {
}
1.5 小结
如果发现一个类中所有的方法都是抽象方法,那么就可以将该类改进为一个接口。
涉及到了接口大面积更新方法,而不想去修改每一个实现类,就可以将更新的方法定义为带有方法体的默认方法。
希望默认方法调用的更加简洁,可以考虑设计为 static 静态方法。(需要去掉 default 关键字)
默认方法中出现了重复的代码,可以考虑抽取出一个私有方法。(需要去掉 default 关键字)
(1) 类和类的关系:继承关系,只能单继承,但是可以多层继承。
(2) 类和接口的关系:实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口。
如果直接父类和接口中出现了相同的方法声明,但是代码逻辑不一样,优先使用直接父类的代码逻辑。
package com.qdu.test6;
public class Fu {
public void show() {
System.out.println("Fu...show");
}
}
package com.qdu.test6;
public interface Inter {
public default void show() {
System.out.println("Inter....show");
}
}
package com.qdu.test6;
public class TestInterface {
public static void main(String[] args) {
InterImpl ii = new InterImpl();
ii.show(); // Fu...show
}
}
class InterImpl extends Fu implements Inter {
}
(3) 接口和接口的关系:继承关系,可以单继承,也可以多继承。
package com.qdu.test7;
public interface InterA {
public abstract void showA();
public default void method() {
System.out.println("InterA...method方法");
}
}
package com.qdu.test7;
public interface InterB {
public abstract void showB();
public default void method() {
System.out.println("InterB...method方法");
}
}
package com.qdu.test7;
public interface InterC extends InterA, InterB {
@Override
public default void method() {
System.out.println("InterC接口解决代码逻辑冲突问题, 重写method方法");
}
}
package com.qdu.test7;
public class InterImpl implements InterC {
@Override
public void showA() {
}
@Override
public void showB() {
}
}
package com.qdu.test7;
public class TestInterface {
public static void main(String[] args) {
InterImpl ii = new InterImpl();
ii.method(); // InterC接口解决代码逻辑冲突问题, 重写method方法
}
}
2.多态
同一个对象,在不同时刻表现出来的不同形态。
我们可以说猫是猫:猫 cat = new 猫();
我们也可以说猫是动物:动物 animal = new 猫();
这里猫在不同的时刻表现出来了不同的形态,这就是多态。
多态的前提和体现
- 有继承/实现关系
- 有方法重写
- 有父类引用指向子类对象
package com.qdu.test1;
public class Test1Polymorphic {
public static void main(String[] args) {
Cat c = new Cat();
Animal a = new Cat();
a.eat(); // 猫吃鱼
}
}
class Animal {
public void eat() {
System.out.println("动物吃饭");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
2.1 多态中成员的访问特点
构造方法:同继承一样,子类会通过 super 访问父类构造方法。
成员变量:编译看左边(父类),执行看左边(父类)。
成员方法:编译看左边(父类),执行看右边(子类)。
为什么成员变量和成员方法的访问不一样呢?
因为成员方法有重写,而成员变量没有。
package com.qdu.test2;
public class Test2Polymorpic {
public static void main(String[] args) {
Fu f = new Zi();
System.out.println(f.num); // 10
f.method(); // Zi.. method
}
}
class Fu {
int num = 10;
public void method() {
System.out.println("Fu.. method");
}
}
class Zi extends Fu {
int num = 20;
public void method() {
System.out.println("Zi.. method");
}
}
2.2 多态的好处和弊端
多态的好处:提高了程序的扩展性。定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的任意子类对象。
多态的弊端:不能使用子类的特有功能。此时要么直接创建子类对象,要么向下转型。
package com.qdu.test3;
public class Test3Polymorpic {
public static void main(String[] args) {
useAnimal(new Dog()); // Animal a = new Dog(); // 狗吃肉
useAnimal(new Cat()); // Animal a = new Cat(); // 猫吃鱼
}
public static void useAnimal(Animal a) {
a.eat();
//a.watchHome();
}
}
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("猫吃鱼");
}
}
2.3 多态中的转型
向上转型:从子到父,即父类引用指向子类对象。
向下转型:从父到子,即父类引用转为子类对象。
package com.qdu.test4;
public class Test4Polymorpic {
public static void main(String[] args) {
// 1.向上转型:父类引用指向子类对象
Fu f = new Zi();
f.show(); // Zi..show...
// 2.向下转型:从父类类型转换回子类类型
Zi z = (Zi) f;
z.method(); // 我是子类特有的方法method
}
}
class Fu {
public void show() {
System.out.println("Fu..show...");
}
}
class Zi extends Fu {
@Override
public void show() {
System.out.println("Zi..show...");
}
public void method() {
System.out.println("我是子类特有的方法method");
}
}
如果被转的引用类型变量对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现 ClassCastException。
解决:变量名 instanceof 类型,判断关键字左边的变量是否是右边的类型,返回 boolean 类型结果。
package com.qdu.test3;
public class Test3Polymorpic {
public static void main(String[] args) {
useAnimal(new Dog());
useAnimal(new Cat());
}
public static void useAnimal(Animal a) {
a.eat();
// 判断a变量记录的类型是否是Dog
if(a instanceof Dog) {
Dog dog = (Dog) a;
dog.watchHome();
}
}
}
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("猫吃鱼");
}
}
3.内部类
内部类:就是在一个类中定义一个类。例如,在一个 A 类的内部定义一个 B 类,B 类就被称为内部类。
内部类的访问特点:
- 内部类可以直接访问外部类的成员,包括私有。
- 外部类要访问内部类的成员,必须创建对象。
按照内部类在类中定义的位置不同,可以分为如下两种形式:
- 在类的成员位置:成员内部类
- 在类的局部位置:局部内部类
对于成员内部类,外界如何创建对象使用呢?
格式:外部类名.内部类名 对象名 = new 外部类对象().new 内部类对象();
package com.qdu.test1;
public class Test1Inner {
public static void main(String[] args) {
Outer.Inner i = new Outer().new Inner();
System.out.println(i.num);
i.show();
}
}
class Outer {
private int a = 10;
class Inner {
int num = 10;
public void show() {
System.out.println("Inner..show");
// 内部类可以直接访问外部类的成员,包括私有
System.out.println(a);
}
}
}
成员内部类也属于成员,既然是成员就可以被一些修饰符所修饰。
(1) private
私有成员内部类访问:在自己所在的外部类中创建对象访问。
package com.qdu.test2;
public class Test2Innerclass {
public static void main(String[] args) {
// Outer.Inner oi = new Outer().new Inner();
Outer o = new Outer();
o.method();
}
}
class Outer {
private class Inner {
public void show() {
System.out.println("inner..show");
}
}
public void method() {
Inner i = new Inner();
i.show();
}
}
(2) static
静态成员内部类访问格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
静态成员内部类中的静态方法:外部类名.内部类名.方法名();
package com.qdu.test3;
public class Test3Innerclass {
public static void main(String[] args) {
Outer.Inner oi = new Outer.Inner();
oi.show(); // inner..show
Outer.Inner.method(); // inner..method
}
}
class Outer {
static class Inner {
public void show() {
System.out.println("inner..show");
}
public static void method() {
System.out.println("inner..method");
}
}
}
3.2 局部内部类
局部内部类是在方法中定义的类,所以外界是无法直接使用,需要在方法内部创建对象并使用。
该类可以直接访问外部类的成员,也可以访问方法内的局部变量。
package com.qdu.test4;
public class Test4Innerclass {
public static void main(String[] args) {
Outer o = new Outer();
o.method();
}
}
class Outer {
int a = 10;
public void method() {
int b = 20;
class Inner {
public void show() {
System.out.println("show...");
System.out.println(a);
System.out.println(b);
}
}
Inner i = new Inner();
i.show();
}
}
3.3 匿名内部类
概述:匿名内部类本质上是一个特殊的局部内部类(定义在方法内部)。
前提:需要存在一个接口或类。
理解:匿名内部类是将继承/实现、方法重写、创建对象三个步骤放在了一步进行。
package com.qdu.test5;
public class Test5Innerclass {
public static void main(String[] args) {
InterImpl ii = new InterImpl();
ii.show();
new Inter() {
@Override
public void show() {
System.out.println("匿名内部类...show");
}
}.show();
// 接口中存在多个方法,多态
Inter2 i = new Inter2() {
@Override
public void show1() {
System.out.println("show1...");
}
@Override
public void show2() {
System.out.println("show2...");
}
};
i.show1();
i.show2();
}
}
interface Inter {
void show();
}
interface Inter2 {
void show1();
void show2();
}
class InterImpl implements Inter {
@Override
public void show() {
System.out.println("InterImpl...show");
}
}
当方法的形式参数是接口或者抽象类时,可以将匿名内部类作为实际参数进行传递。
package com.qdu.test6;
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();
}
}
interface Swimming {
void swim();
}
4.Lambda表达式
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”。
面向对象思想强调“必须通过对象的形式来做事情”。
函数式思想则尽量忽略面向对象的复杂语法“强调做什么,而不是以什么形式去做”。
Lambda 表达式就是函数式思想的体现。
Lambda 表达式的格式:(形式参数) -> {代码块}
- 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可。
- ->:由英文中画线和大于符号组成,固定写法,代表指向动作。
- 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容。
Lambda 表达式的使用前提:
- 有一个接口
- 接口中有且仅有一个抽象方法
(1) 无参数无返回值
package com.qdu.test2;
public class TestLambda {
public static void main(String[] args) {
useShowHandler(new ShowHandler() {
@Override
public void show() {
System.out.println("我是匿名内部类中的show方法");
}
});
// Lambda实现
useShowHandler( () -> {System.out.println("我是Lambda中的show方法");} );
}
public static void useShowHandler(ShowHandler showHandler){
showHandler.show();
}
}
interface ShowHandler {
void show();
}
(2) 带参数无返回值
package com.qdu.test3;
public class StringHandlerDemo {
public static void main(String[] args) {
useStringHandler(new StringHandler() {
@Override
public void printMessage(String msg) {
System.out.println("我是匿名内部类" + msg);
}
});
// Lambda实现
useStringHandler( (String msg) -> {System.out.println("我是Lambda表达式" + msg);} );
}
public static void useStringHandler(StringHandler stringHandler) {
stringHandler.printMessage("qdu");
}
}
interface StringHandler {
void printMessage(String msg);
}
(3) 无参数有返回值
如果 lambda 所操作的接口中的方法有返回值,一定要通过 return 语句将结果返回,否则会出现编译错误。
package com.qdu.test4;
import java.util.Random;
public class RandomNumHandlerDemo {
public static void main(String[] args) {
useRandomNumHandler(new RandomNumHandler() {
@Override
public int getNumber() {
Random r = new Random();
int num = r.nextInt(10) + 1;
return num;
}
});
useRandomNumHandler( () -> {
Random r = new Random();
int num = r.nextInt(10) + 1;
return num;
} );
}
public static void useRandomNumHandler(RandomNumHandler randomNumHandler){
int result = randomNumHandler.getNumber();
System.out.println(result);
}
}
interface RandomNumHandler {
int getNumber();
}
(4) 带参数有返回值
package com.qdu.test5;
public class CalculatorDemo {
public static void main(String[] args) {
useCalculator(new Calculator() {
@Override
public int calc(int a, int b) {
return a + b;
}
});
useCalculator( (int a, int b) -> {
return a + b;
});
}
public static void useCalculator(Calculator calculator) {
int result = calculator.calc(10,20);
System.out.println(result);
}
}
interface Calculator {
int calc(int a, int b);
}
(5) Lambda 表达式的省略规则:
- 参数类型可以省略,但是有多个参数的情况下,不能只省略一个。
- 如果参数有且仅有一个,那么小括号可以省略。
- 如果代码块的语句只有一条,可以省略大括号和分号,甚至是 return。
package com.qdu.test6;
public class Test6 {
public static void main(String[] args) {
useInter((a, b) ->
a + b
);
}
public static void useInter(Inter i) {
double result = i.method(12.3, 22.3);
System.out.println(result);
}
}
interface Inter {
// 用于计算 a + b 的结果并返回
double method(double a, double b);
}
5.Lambda 表达式和匿名内部类的区别
(1) 所需类型不同
匿名内部类:可以是接口,也可以是抽象类,还可以是具体类。
Lambda 表达式:只能是接口。
(2) 使用限制不同
如果接口中有且仅有一个抽象方法,可以使用 Lambda 表达式,也可以使用匿名内部类。
如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用 Lambda 表达式。
(3) 实现原理不同
匿名内部类:编译之后,产生一个单独的 .class 字节码文件。
Lambda 表达式:编译之后,没有一个单独的 .class 字节码文件,对应的字节码会在运行的时候动态生成。



