访问方式
静态内部类访问外围类成员:普通方法和静态方法都需要直接创建外围类对象然后直接对象名访问即可
外围类访问静态内部类成员:普通方法和静态方法都需要直接创建静态内部类对象,
外部类访问静态内部类成员(了解)
静态内部类访问外部类成员(了解)
局部内部类(看成局部变量即可)
局部内部类的访问特点:
* 1,内部类去访问外围类
* a,在外围类的成员方法中写一个局部内部类
* 可以直接访问,因为成员方法中有this指向当前对象(外围类对象)
* 如果出现同名的成员,用外围类类名.this区分同名
*
* b,在外围类的静态方法中写一个局部内部类
* 需要创建外围类对象才能够访问成员,如果同名就用相应的对象名区分
* 以上访问都不受访问权限限制
* 2,外围类访问内部类
* 访问不到
* 3,外部类访问内部类
* 访问不到
* 4,内部类访问其他类
* 可以访问,创建对象就行
* 都受访问权限限制
以下代码是通过常规的做法和使用局部内部类的方法
public class Demo4 {
public static void main(String[] args) {
method(new IAImpl());
//稍微简洁的方法是使用局部内部类
class LocalIAImpl implements IA{
@Override
public void test() {
System.out.println("高级点的写法");
}
}
method(new LocalIAImpl());
}
public static void method(IA a){
a.test();
}
}
interface IA{
void test();
}
//常规的做法是在在接口的子类实现方法中重写
class IAImpl implements IA{
@Override
public void test() {
System.out.println("常");
}
}
匿名内部类
以上写法依然不够简洁,所以引入匿名内部类
* 考虑如何进行简化它,考虑它哪些地方实际上是不需要写的? 显然可以把局部内部类当中,类的名字给省略掉
* 因为我们只想要一个子类对象,并不需要知道它叫啥
* 这种只关注对象,不关注类名的局部内部类就是匿名内部类
* 匿名内部类:
* 语法: new 类名(普通类和抽象类)/接口名 (参数){
* //类体
* //在类体可以重写方法
* };
*作用:
* 生成一个该类或接口的子类对象,也就是说匿名内部类的语法隐含了继承或者实现,它得到的一定是子类对象
* 思考:
* 1,匿名内部类到底是什么东西匿名了?
* a,把子类的类名给匿名了
* b,如果单单是这个语法,实际上对象也匿名了(当然,这个对象实际上可以用父类引用接收,可以有名字)
* 2,匿名内部类实际上是一个类吗?
* 当然不是,实际上是一个对象
* 综上,很多人会把匿名内部类叫做,匿名的局部内部类的对象
*
*
* 匿名内部的使用方式:
* 1,直接用对象访问成员
* 2,引用接收
* 总结两种方式,方式一比较简洁可以访问子类独有成员,缺点是只能用一次
* 方式二可以多次使用,但是无法访问子类独有成员并且相对麻烦
* 普遍来说,方式一还是更常见
首先明确两点使用匿名内部类的前提
-
匿名内部类是特殊的局部内部类,所以匿名内部类必须定义在局部位置
-
匿名内部类是局部内部类的更进一步
-
匿名内部类的成员特征、访问特征和局部内部类没有区别
-
匿名内部类访问方法的局部变量时,该变量仍然是常量
-
-
存在一个已定义类或者接口,这里的类可以是具体类也可以是抽象类
-
匿名内部类的本质是继承了一个(抽象)类或者实现了接口的子类对象
-
匿名内部类使用方式有两种—代码如下
public class Demo4 {
public static void main(String[] args) {
method(new IAImpl());
//稍微简洁的方法是使用局部内部类
class LocalIAImpl implements IA{
@Override
public void test() {
System.out.println("稍微高级点的写法");
}
}
method(new LocalIAImpl());
//使用匿名内部类
method(new IA() {
@Override
public void test() {
System.out.println("高级写法");
}
});
//创建匿名内部类对象去使用 匿名的IA接口的实现类对象
//使用方式一:直接用匿名的内部类对象
//优点:不需要引用接收,更简洁 而且由于它就是子类对象,所以他可以访问子类独有的成员
//缺点是,没有引用指向,只能使用一次
new IA(){
@Override
public void test() {
System.out.println("hello interface");
}
}.test();
//创建抽象类的子类对象
new AbstractPerson(){
@Override
public void test() {
System.out.println("hello absrtact");
}
//定义匿名子类独有的成员
public void testSon(){
System.out.println("你好儿子");
}
}.testSon();
//使用方式二:为了解决只能使用一次的问题,使用引用接收该子类对象
//优点:可以多次使用该对象
//缺点:无法访问子类独有成员,甚至不能强转,所以是真正的无法访问,除了这个大缺点外,它还比较麻烦
//创建一个NormalClazz类的子类对象
NormalClazz nc = new NormalClazz(){
@Override
public void test() {
System.out.println("你好,师范");
}
public void testSon(){
System.out.println("独有方法");
}
};
nc.test();
// nc.testSom()
}
public static void method(IA a){
a.test();
}
}
interface IA{
void test();
}
//常规的做法是在在接口的子类实现方法中重写
class IAImpl implements IA{
@Override
public void test() {
System.out.println("常规做法");
}
}
abstract class AbstractPerson{
public abstract void test();
}
class NormalClazz{
public void test(){
System.out.println("Normal");
}
}
整个小题练下
public class Demo{
public static void main(String[] args) {
Outer.method().show();
}
}
interface Inter {
void show();
}
class Outer {
public static Inter method(){
// return new InterImpl();
//使用匿名内部类的做法
return new InterImpl(){
@Override
public void show() {
System.out.println("hello wodeworld");
}
};
}
}
class InterImpl implements Inter{
@Override
public void show() {
System.out.println("hello world");
}
}
-
匿名内部类开发中的使用场景
-
* - 作为方法的实际参数 * - 学习多态的时候我们曾经讲过,可以将父类作为形式参数,而将子类作为实际参数在方法调用的时候传入 * - 匿名内部类的匿名对象就是一个子类匿名对象,所以,可以使用匿名内部类改进以前的做法 * - 在方法体中,作为方法的返回值 * - 方法体中,return 返回值只会执行一次,如果方法返回一个接口类型的对象,我们也可以使用匿名内部类 *
public class Demo1 { public static void main(String[] args) { //A()这个括号可以填参数,前提是要有构造方法,并且这个构造方法是父类的构造方法 test(new A(999){ @Override public void test() { System.out.println("这是我重写的方法"); } }); test(new B(10000) { @Override public void test() { System.out.println("这是我使用匿名内部类重写的方法"); System.out.println(b); } }); test(new C() {//接口的是实现类对象()里无法填东西,因为接口没有构造方法 @Override public void test() { System.out.println("使用接口"); } }); } public static void test(A a){ a.test(); } public static void test(B a){ a.test(); } public static void test(C a){ a.test(); } } class A{ int a = 100; { a = 900; } public A() { } public A(int a) { this.a = a; } public void test(){ System.out.println("A"); } } abstract class B{ { b = 20; } int b = 10; public B() { } public B(int b) { this.b = b; } public abstract void test(); } interface C{ void test(); }
-
lambda表达式-在集合中广泛使用
* lambda表达式是匿名内部类的更进一步,它比匿名内部类更加简洁,
* 并且lambda表达式有自己独特的用法,这里讲一些最最基本的使用
* > 仅做参考与了解:lambda表达式
* > Lambda 表达式是 JDK8 的一个新特性,可以取代接口的匿名内部类,写出更优雅的 Java 代码
* > 匿名内部类实际上是局部内部类的更进一步,简化了局部内部类
* > 那么lambda就是匿名内部类更进一步,语法上更简洁了,代码更优雅了,是高端的玩法
* > 是人上人的玩法
*
* lambda表达式不是必须要使用的,更多的时候使用lambda表达式是为了更加简化的代码
* 通过这段描述性的话语,我们得到两个结论:
* 1,lambda表达式是对接口的匿名内部的简化,对抽象类和普通类不能简化
* 2,lambda表达式仍然是特殊的局部内部类,局部内部类的final局部常量仍然存在
*
* lambda到底咋用?
* lambda表达式是对接口的匿名内部类的简化,所以首先需要一个接口
* 并且lambda表达式对接口有自己独特的要求,不是什么接口都能使用lambda表达式
*
* lambda表达式对接口的要求:
* 要求该接口当中,只能有一个必须要实现的抽象方法
* Java当中把这类接口称之为功能接口(Functional Interface),用@FunctionalInterface注解标记它
* 思考:
* 1.功能接口当中只能有一个方法吗?
* 不是,因为java8之后接口中有默认方法和静态方法,它们不需要实现
* 2.功能接口当中只能有一个抽象方法吗?
* 不是,因为有些抽象方法不需要子类实现(挖个坑)
* 如果抽象方法是Object类当中存在的方法,那么就无需子类实现,因为Object类已经有实现了
*
* 虽然功能接口中不要求仅有一个抽象方法,但是多数情况还是不要搞太复杂,多数功能接口中就是只有一个抽象方法
*
* 现在功能接口有了,那么怎么使用lambda表达式呢?
* 语法:
* (形式参数) -> {
* //方法体
* };
* 形式参数: 是功能接口中那个必须要实现的抽象方法的形参列表
* -> : 表示lambda表达式运算符,读到"goes to"
* 方法体: 是功能接口中那个必须要重写的抽象方法的重写的方法体
*
* 为什么lambda表达式对接口要求必须是功能接口呢?
* 因为语法限制了它,它只能重写一个方法,并且是不能增加自己额外的成员的
*
* lambda表达式的本质和匿名内部类是一样的,都是创建出了一个对象(lambda表达式创建出的是接口的实现类对象)
*
* 显然我们根据语法写完lambda表达式就报错了
* () -> {
* System.out.println("hello world!");
* };
* 思考它为什么会报错?
* Java是强类型语言,任何数据都有它的数据类型,
* 但是如果光秃秃的写出lambda表达式的语法,编译器就无法得知它的数据类型了
* 于是程序员必须通过一定的手段让编译器明确lambda表达式表示的对象的数据类型,这个过程称之为lambda表达式的类型推断
* 在生活中,我们是通过一定的额外信息来进行推断,所以我们也需要写一定的代码,让编译器能够通过额外信息来进行类型推断
* 这叫做根据"上下文"推断,主要有下面四种常见的方式:
* 1,直接用父类引用去接收这个对象
* 2,直接告诉编译器lambda表达式的数据类型,但是不接收它
* 语法上和强制类型转换很像
* 在lambda表达式之前用
* (接口的名字)lambda表达式
* 3,如果方法的形参是一个接口,那么编译器就已经知道了这里要传什么类型对象
* 所以可以直接把lambda表达式写在实参的位置,借助方法完成类型推断
*
* 4,如果方法的返回值类型是一个接口,那么编译器就已经知道了这里要返回什么类型对象
* 所以可以直接把lambda表达式写在返回值的位置,借助方法完成类型推断
*
* 以上四种,就是最常见的lambda表达式类型推断的方式
* 到此,lambda表达式的简单使用,接下来我们看一下lambda表达式的简化
*代码如下所示:
public class Demo2 {
public static void main(String[] args) {
//方式一
ITest it1 = ()->{
System.out.println("你好喜羊羊");
};
it1.test();
//也可以使用IA接口接收
IA ia = () -> {
System.out.println("你好慢羊羊");
};
ia.test();
//方式二 直接告诉lambda表达式的数据类型 但是不接收
((ITest) () -> {
System.out.println("你好沸羊羊");
}).test();
//方式三
method(() -> {
System.out.println("你好羊村");
});
//方式四
method().test();
}
public static void method(ITest i){
i.test();
}
public static IA method(){
return () -> {
System.out.println("你好红太狼");
};
}
}
@FunctionalInterface
interface ITest{
void test();
}
interface IA{
void test();
}
lambda表达式的简化
这里要强调一下lambda表达式不是一个类,它整体表示一个对象,你要实现的功能接口的子类对象。
* lambda表达式的简化原则: 是在不影响理解的情况下,尽量少写代码
* lambda表达式的简化前提: 是功能接口只有一个要实现的抽象方法
我们观察一下语法:
* (形参)
* 这里能不能简化?
* 能简化,因为抽象方法只有一个,该方法参数的数据类型是固定的,无需在lambda表达式中写出
* 更进一步,当形参只有一个时,可以直接省略小括号
* 如果形参是空的或者多于1个,小括号不可省略
*
* {
* //方法体
* }
* 这里能不能简化?
* 这里也是可以简化的,我们回想一下if判断和for循环,当语句只有一条时,可以把大括号省略掉
* 更进一步,如果抽象方法有返回值,并且返回值语句也只有一条,那么可以省略return
*
* ->
* lambda运算符能不能简化?
* 在某些情况也是可以简化的
* lambda表达式除了可以自定义方法实现外,还可以指定一个方法作为实现
* 语法: (形参) -> 已实现的方法
* 这种指向已实现方法的方式需要给方法传入抽象方法的实参
* 这种方式比较少用
* 更常用的方式:
* 直接用
* 接口的名字 实现子类对象名 = 方法的归属者::方法名
* 怎么判断方法的归属者:
* 1,静态方法,属于类
* 2,普通成员方法,属于对象
* lambda指向一个已实现的方法,不仅可以指向我们自定义的方法,还可以指向jdk当中别人定义好的方法
给出六个功能接口,以后就基于这六个进行lambda表达式的简化
//无返回值无参数的功能接口
@FunctionalInterface
interface INoReturnNoParam {
void test();
}
//无返回值有一个参数的功能接口
@FunctionalInterface
interface INoReturnOneParam {
void test(int a);
}
//无返回值两个参数的功能接口
@FunctionalInterface
interface INoReturnTwoParam {
void test(int a, int b);
}
//有返回值无参数的功能接口
@FunctionalInterface
interface IHasReturnNoParam {
int test();
}
//有返回值一个参数的功能接口
@FunctionalInterface
interface IHasReturnOneParam {
int method(int a);
}
//有返回值两个参数的功能接口
@FunctionalInterface
interface IHasReturnTwoParam {
int test(int a, int b);
}
以下是练习代码:
public class Demo3 {
public static void main(String[] args) {
//这里是没返回值有两个形参的接口
INoReturnTwoParam ip1 = (int a,int b) -> {
System.out.println("a+b= " + (a+b));
};
ip1.test(45,56);
//简化一下形参
INoReturnTwoParam ip2 = (a,b) -> {
System.out.println("a - b = "+(a-b));//不知道a和b的数据类型直接做减法不行
};
ip2.test(56,12);
//无返回值只有一个形参的接口
INoReturnOneParam ip3 = a -> {
System.out.println("a的值是:"+a);
};
ip3.test(10);
//无返回值无参数的接口
/*把这段代码的大括号简化下
((INoReturnNoParam)() -> {
System.out.println("凤姐是最美的女孩子");
}).test();*/
((INoReturnNoParam)()->System.out.println("林志玲才最美!")).test();//去掉大括号就有高端感觉了
//有返回值一个参数的功能接口
/* IHasReturnOneParam ip4 = a -> {
return ++a;
};
System.out.println(ip4.test(12));
试着简化下大括号
*/
System.out.println(((IHasReturnOneParam)a -> a).test(99));
//有返回值两个参数的接口;省略->符号
IHasReturnTwoParam ip5 = (a,b)->method(a,b);
System.out.println(ip5.test(12, 15));
IHasReturnTwoParam ip6 = Demo3::method;
System.out.println(ip6.test(3, 20));
System.out.println(((IHasReturnTwoParam) (Demo3::method)).test(12,15));
//以无返回值有一个参数的接口为例
INoReturnOneParam ip7 = new Demo3()::method;
ip7.test(666);
//lambda表达式除了可以指向自定义的方法还可以指定一个方法作为实现
/*INoReturnNoParam ip8 = System.out::println;
ip8.test();*/
INoReturnOneParam ip8 = System.out::println;
ip8.test(56898);
}
//定义一个方法作为IHasReturnTwoParam的功能接口的抽象方法的实现
//该实现方法要求和抽象方法形参一致,返回值类型一致
//如果是静态方法归属者就是类,普通方法归属就是对象
//使用普通方法
public void method(){
System.out.println("你好"+var1);
}
//使用静态方法
public static int method(int var1,int var2){
return var1*var2;
}
}
//无返回值无参数的功能接口
@FunctionalInterface
interface INoReturnNoParam {
void test();
}
//无返回值有一个参数的功能接口
@FunctionalInterface
interface INoReturnOneParam {
void test(int a);
}
//无返回值两个参数的功能接口
@FunctionalInterface
interface INoReturnTwoParam {
void test(int a, int b);
}
//有返回值无参数的功能接口
@FunctionalInterface
interface IHasReturnNoParam {
int test();
}
//有返回值一个参数的功能接口
@FunctionalInterface
interface IHasReturnOneParam {
int test(int a);
}
//有返回值两个参数的功能接口
@FunctionalInterface
interface IHasReturnTwoParam {
int test(int a, int b);
}
接下来是个小练习题
/**
* @Description:需求:希望有一个功能接口完成所有对象数组的遍历
* @author: kerwin
*/
public class Demo4 {
public static void main(String[] args) {
Student[] studs = new Student[3];
studs[0] = new Student(10,"小周");
studs[1] = new Student(15,"小王八");
studs[2] = new Student(16,"小梦");
ITraverObjectArr i = Demo4::traverseObjectArr;
i.traverse(studs);
}
//自定义对象数组遍历方法
public static void traverseObjectArr(Object[] arr){
for(Object o : arr){
System.out.println(o);
}
}
}
//定义遍历对象数组的功能接口
interface ITraverObjectArr{
void traverse(Object[] o);
}
class Student{
int age;
String name;
public Student() {
}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
}