抽象类和接口和Lambda
内部类
匿名内部类也就是没有名字的内部类,匿名内部类就是重写父类或接口的方法。
正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写
但使用匿名内部类还有个前提条件:必须继承一个抽象类或实体类或接口
实例1:不使用匿名内部类来实现抽象方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
abstract class Person { public abstract void eat(); } class Child extends Person { public void eat() { System.out.println( "eat something" ); } } public class Demo { public static void main(String[] args) { Person p = new Child(); p.eat(); } } |
运行结果:eat something
可以看到,我们用Child继承了Person类,然后实现了Child的一个实例,将其向上转型为Person类的引用
但是,如果此处的Child类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?
这个时候就引入了匿名内部类
实例2:匿名内部类的基本实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
abstract class Person { public abstract void eat(); } public class Demo { public static void main(String[] args) { Person p = new Person() { public void eat() { System.out.println( "eat something" ); } }; p.eat(); } } |
运行结果:eat something
可以看到,我们直接将抽象类Person中的方法在大括号中实现了
这样便可以省略一个类的书写
并且,匿名内部类还能用于接口上
实例3:在接口上使用匿名内部类
interface Person { public void eat(); } public class Demo { public static void main(String[] args) { Person p = new Person() { public void eat() { System.out.println( "eat something" ); } }; p.eat(); } } |
运行结果:eat something
由上面的例子可以看出,只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现
最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是继承Runnable接口
实例4:Thread类的匿名内部类实现
public class Demo { public static void main(String[] args) { Thread t = new Thread() { public void run() { for ( int i = 1 ; i <= 5 ; i++) { System.out.print(i + " " ); } } }; t.start(); } } |
运行结果:1 2 3 4 5
实例5:Runnable接口的匿名内部类实现
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class Demo { public static void main(String[] args) { Runnable r = new Runnable() { public void run() { for ( int i = 1 ; i <= 5 ; i++) { System.out.print(i + " " ); } } }; Thread t = new Thread(r); t.start(); } } |
运行结果:1 2 3 4 5
为什么引入抽象类?
抽象类 : 几何图形class Figure
子类: 矩形class Rectangle 圆class Circle三角形class Triangle 等... 属性: 曲边 ,直边, 几条边, 求面积, 求周长...
如果没有抽象类,父类提取出来的共性必须是唯一的,实现的,稳定的,那么求面积,求周长,都不能提取,当一个方法的功能是处理几何图形,参数是Figure f .当接收一个图形时,求其周长还要事先把 f 向下转型,很是麻烦.但是如果引入抽象类,可以把求周长的公式提取出来,但是不必实现,当然也无法实现.这样就不用向下转型了.增加了灵活性,而且子类继承了抽象类,必须把抽象类的所有抽象方法必须全实现,才能new实例,这样抽象类带来了另外一个好处:模板,规定子类必须要干什么!
抽象类原则
(1)抽象类用abstract修饰,抽象类不能new出实体
(2)抽象类提取出的共性,能实现的直接实现,不能实现的用abstract修饰,不必实现,由子类继承重写
(3)抽象类应该尽可能提取最多的共性,不用在乎能不能实现.
(4)抽象类不能new出实体,就是用来且必须被继承的,继承者必须重写其全部的抽象方法,才能new出实体,否则,继承者也是个抽象类.
(5)abstract修饰的类被继承才有意义,abstract修饰的方法被重写才能new出实体,而final修饰的类不能被继承,方法不能被重写,所以abstract和final不能同时使用.
(6)private修饰的方法子类无法触及,所以private和abstract也不通用.
(7)抽象类也有构造器,但是这个构造器并不是为了new出对象,而是给继承者的构造器调用的.
抽象方法和普通方法 形式上的区别: public abstruct void abstructFun(); public void fun() {}
接口
/*接口的权限是public或friendly,能继承一个或多个接口,不能继承类,不含构造器,不含初始化块*/
interface JieKou extends 接口1,接口2... {
/* 接口的所有成员都是public ,就像C语言的const修饰的变量,不再改变,就是"模板" */
public static final int VAR = 20;
/*static 省空间,final 是规范就不要再修改,必须定义时直接初始化*/
//int VAR = 20;
/*普通方法必须是抽象的 public abstruct,不用实现 */
void f();
/*类方法 public static ,必须实现 */
static void g() {
System.out.println("我是类方法");
}
/*默认方法也必须实现 public */
default void print(String... msgs) {
for(String msg : msgs) {
System,out.println(msg);
}
}
/*其他成员:内部类,内部接口,枚举类*/
}
接口不能new出实体,接口可以作为引用变量,指向类new出的实体.
<1>定义引用变量,也可以进行强制类型转换
<2>调用接口的常量
<3>被其他类实现
一个类可以多继承很多接口,但是只能继承一个类.
class extends 类 implements 接口1,接口2... {
}
一个类继承了接口,必须实现接口中所有的抽象方法,不然仍旧只能当抽象类用.
继承接口的类重写接口的抽象方法时,权限只能是public,因为子类重写父类时,只能用相同或者更大的权限.
抽象类和接口的区别
接口的使用:
某个方法需要完成一个行为,但是这个行为暂时不知道是什么,只有执行方法时,才知道.比如,一个方法时处理数组,遍历数组的时候,对数组元素执行什么操作暂时不确定,需要临时规定行为,解决方法就是定义一个接口.
1 import java.util.*; 2 public class TestJava { 3 public static void main(String[] args) { 4 int[] a = new int[10]; 5 for(int i = 0;i < a.length;++i) 6 a[i] = i; 7 8 for(int i = 0;i < a.length;++i) 9 System.out.print(a[i]); 10 11 TestFun tf = new TestFun(); 12 tf.f(a,new Set0()); 13 14 for(int i = 0;i < a.length;++i) 15 System.out.print(a[i]); 16 17 tf.f(a,new Set1()); 18 for(int i = 0;i < a.length;++i) 19 System.out.print(a[i]); 20 } 21 } 22 23 interface JieKou { 24 void set(int[] a); 25 } 26 27 class Set0 implements JieKou { 28 public void set(int[] a) { 29 for(int i = 0;i < a.length;++i) { 30 a[i] = 0; 31 } 32 } 33 } 34 35 class Set1 implements JieKou { 36 public void set(int[] a) { 37 for(int i = 0;i < a.length;++i) { 38 a[i] = 1; 39 } 40 } 41 } 42 43 class TestFun { 44 void f(int[] a,JieKou jk) { 45 jk.set(a); 46 } 47 }
Lambda表达式
如何动态的传入一段代码,作为具体的处理行为?
(1) 动态处理代码封装成一个接口a的抽象方法.定义一个继承接口的类a,重写抽象方法.传入类,在使用动态处理代码的方法中通过类名.方法使用代码.
(2)直接把接口的引用作为方法参数,void f(int [ ] a, new JieKou { 重写抽象方法} ); 匿名内部类
(3)使用Lambda表达式
Lambda表达式支持将代码块作为方法参数,Lmabda表达式可以更简洁的创建函数式接口的一个实体.
函数式接口: 可以有多个默认方法,类方法,只有一个抽象方法的接口叫函数式接口.
使用匿名内部类.
1 import java.util.*; 2 public class TestJava { 3 public static void main(String[] args) { 4 int[] a = new int[10]; 5 6 TestFun tf = new TestFun(); 7 tf.f(a,new JieKou() { 8 public void set(int[] a) { 9 for(int i = 0;i < a.length;++i) 10 a[i] = 2; 11 } 12 }); 13 for(int i = 0;i < a.length;++i) 14 System.out.print(a[i]); 15 } 16 } 17 18 interface JieKou { 19 void set(int[] a); 20 } 21 22 class TestFun { 23 void f(int[] a,JieKou jk) { 24 jk.set(a); 25 } 26 }
使用Lambda表达式
Lambda表达式:只有一个形参时,可以省略圆括号,只有一条语句时,可以省略{ }, 若方法体是 c = a + b; return c; 可以简写成 c = a + b; 它会自动返回.
Lambda与匿名内部类的相同点和区别
1 interface Displayable { 2 void Display(int i); 3 default void f() { 4 System.out.println("我是接口的默认方法"); 5 } 6 } 7 8 public class TestLambda { 9 private String name = new String("哈哈哈"); 10 void test() { 11 Displayable dis = i->{ 12 System.out.println("我是接口的抽象方法,我要显示"+i); 13 System.out.println("我要访问name,name是:"+name); 14 }; 15 dis.f(); 16 dis.Display(3); 17 } 18 19 public static void main(String[] args) { 20 TestLambda tl = new TestLambda(); 21 tl.test(); 22 } 23 }
1 问题: 方法A需要处理一个数组,怎样处理临时起意,不确定. 2 (1)把处理方法封装成接口 3 interface JieKou { 4 处理方法process; 5 } 6 (2)写方法A 7 A(数组引用,JieKou引用) 8 (3)使用方法A 9 1.匿名内部类 10 A(数组引用,接口引用() {重写处理方法process}); 11 2.Lambda表达式 12 A(数组引用,(process的参数列表)->{方法体}) 13 两种方法都避免了为了一个动态处理代码块创建一个类,优化了项目. 14 注意:Lambda表达式返回一个引用.接口是函数式接口时,才能用Lambda表达式简写. 15 Lambda表达式就是为函数式接口创建实体的. 16 Runnable是个函数式接口,只含有一个抽象方法 17 Runnable r = ()->{System.out.println("Lambda表达式实现函数式接口的实体")}; 18 Object r = ()->{System.out.println("Lambda表达式实现函数式接口的实体")};//error 19 因为Object不是一个函数式接口,用函数式接口强制类型转换后可以用Object引用指向. 20 Object r = (Runnable)()->{System.out.println("Lambda表达式实现函数式接口的实体")};//OK 21 将Lambda表达式赋值给一个函数式接口的引用变量 22 将Lambda表达式作为函数式接口的形参 23 使用函数式接口对Lamable表达式进行强制类型转换.
匿名内部类是接口就能创建实体,但是Lambda表达式只能为函数式接口创建实体.
匿名内部类还可以为抽象类,实体类创建实体.
匿名内部类在实现抽象方法时可以调用接口的默认方法,但是Lambda表达式不可以.