《Java核心技术 卷1》 第六章 接口、Lambda表达式与内部类

接口

接口用来描述类应该做什么,而不指定它们具体应该怎么做。

一个类可以实现(implement)一个或多个接口。

接口的概念

接口不是类,而是对希望符合这个接口的类的一组需求。

接口中,不能有实例字段和实例方法,这些内容都应由实现接口的具体实现类去完成。

接口中,所有方法自动都是public,所以该关键字可以省略。

public interface InterfaceDemo {

    // 常量
    int id = 1;
    String name = "hello world";

    // 抽象方法
    void method1();
    void method2();
    void method3();

    // 静态方法
    static void static_method1(){
        System.out.println("静态方法");
    }

    // 默认方法
    default void default_method(){
        System.out.println("默认方法");
    }

    // Java9中,接口支持私有方法
    // 私有方法可以是静态方法或实例方法
    /*private void private_method(){
        System.out.println("私有方法");
    }*/
}

接口的属性

接口中不能包含实例字段,但可以包含常量,使用public static final修饰,并且这三个关键字省略。

接口与抽象类

抽象类由于是继承关系,Java中只能单继承:继承了抽象类后,就无法继承其他的类了,也就无法再通过继承来扩展其他功能。

但是Java中的类却可以多实现接口,来根据需求来扩展功能。

静态方法和私有方法

Java8中,允许在接口中增加静态方法。

一个小个人理解:静态方法由于无需实例化即可使用,所以个人觉得放在接口中更符合其定义。

Java9中,允许在接口中添加private方法,私有方法可以是静态方法或者实例方法。他们作用非常有限,只能作为接口中其他方法的辅助方法。

默认方法

可以为接口中添加提议默认方法。默认方法必须用default关键字标记出来

解决默认方法冲突

在继承或实现过程中,有可能出现父类中的实例方法与接口中的默认方法冲突问题,规则如下:

  1. 父类优先:若父类的实例方法与实现接口中的默认方法冲突,以父类为准。
  2. 若实现的多个接口中,存在两个接口的默认方法有冲突,则需在实现类中重写该方法。

Comparable与Comparator

Comparable和Comparator都是用于排序的接口。并且都需要配合Arrays或者Collections中的sort方法使用。

Comparable接口需要实现compareTo方法,而Comparator接口需要实现compare方法。但是两个方法的的返回值均为int类型,并且逻辑完全相同,只是实现稍有不同:这两个接口都是使用两个元素相减:

  1. 如果参数1大于参数2,返回一个整数;
  2. 如果参数1小于参数2,返回一个负数;
  3. 若两个参数相等,返回0.

⭐️每个包装类都有compare方法,并且返回的规则完全符合这两个接口return的规则,建议配合使用

Comparable

使用Comparable接口时,需要修改数组或集合的元素类,让元素类继承Comparable接口,并重写compareTo方法

public class UserComparable implements Comparable<UserComparable>{
    private Integer id;
    private String name;
	
    // 中间代码省略。。。

    @Override
    public int compareTo(UserComparable o) {
        return Integer.compare(this.getId(),o.getId());
    }
}
Collections.sort(users)
  • 以上例子就是实现List排序的具体操作,我们可以看到,我们需要对一个User对象的List进行排序。先让User对象实现Comparable接口,并重写compareTo方法。compareTo方法的返回值就是告诉我们根据什么规则排序。
  • 排序时,使用的是单参数的sort方法,只需传入集合即可。因为集合中的元素类中已经定义好我们的排序规则。

Comparator

使用Comparator接口,我们无需对数组或集合的元素类信息进行任何修改,而是需要写一个比较器类,改类需要实现Comparator接口:

public class UserComparator implements Comparator<User> {

    @Override
    public int compare(User o1, User o2) {
        return Integer.compare(o1.getId(),o2.getId());
    }
}

有了比较器之后,无需对代码进行任何的修改,只是在排序的时候,调用两个参数的sort方法:第一个参数为集合,第二个参数为比较器:

Collections.sort(users,new UserComparator());

对比

从上面的案例可以看到:使用Comparator无需对现有代码进行任何修改。耦合度较低。建议该方式。

Lambda表达式

为什么引入Lambda表达式

在开发中, 经常会将一个代码块传递到某个对象中,这个代码块会在将来的某个时间调用。

在引入Lambda表达式之前,处理这个问题,我们需要先构建一个对象,这个对象需要有一个方法来包括这个代码块。

Lambda则是一个可以传递的代码块,我们无需再去创建多余的对象,直接传递一个Lambda表达式,这个表达式可以在以后执行一次或者多次。

Lambda表达式语法

Lambda表达式形式:

() -> {}

其中:

  • ()中为形参
    • 即使没有形参,()也不能省略。
    • 如果可以推导出表达式中的参数类型,则可以忽略参数类型。
  • {}中为表达式
    • 表达式中,无需指定返回类型。Lambda表达式会自动推断出返回类型。

下面上一个案例:

在上节的Comparator示例中,我们使用Collections的sort方法,传入了两个参数:

Collections.sort(users,new UserComparator());

这里,我们创建了UserComparator这个对象。因为我们需要这个对象中的compare方法里的代码段。所以这里我们可以直接传入代码段即可:

Collections.sort(users,(o1,o2)->{
    return Integer.compare(o1.getId(),o2.getId());
});

使用Lambda之后,无需再去手动创建对象,而是在创建对象指明需要运行的代码即可。

函数式接口

对于只有一个抽象方法的接口,需要这种接口口的对象时,就可以提供一个Lambda表达式。而这种接口也被称为函数式接口。

简单来说,一个只有抽象方法的接口,称为函数式接口。函数式接口可以使用@FunctionalInterface注解进行标记

常用函数式接口

Java API在java.util.function包中定义了很多非常常用的函数式接口。最为常用的为一下四种:

  • Supplier:供给型接口。没有参数,有返回值。
  • Consumer:消费型接口。有一个参数,没有返回值
  • Predicate:断言型接口。有一个参数,返回boolean类型的返回值。
  • Function<T,R>:功能型接口。有一个参数,并且有返回值。

方法引用

待建设

构造器引用

待建设

变量作用域

Lambda表达式可以捕获外围作用域中变量的值,但是这个值不能改变

实际上,Lambda表达式捕获的变量必须是事实最终变量。意思是这个i扮靓初始化之后,不能再为它赋新值。

内部类

内部类就是定义在一个类中的类。它主要有一下作用:

  1. 内部类可以对同一个包中的其他类隐藏。
  2. 内部类可以访问本类作用域中的数据,包括私有数据
public class OuterClazz {

    // 外部类属性
    private String outerProp = "outer prop";
    // 外部类方法
    private void outerMethod(){
        System.out.println("outer method");
    }


    /**
     * 内部类
     */
    class InnerClazz{

        private String innerProp = "inner prop";
        public void main(String[] args) {
            
            // 调用内部属性
            System.out.println(innerProp);
            // 外部属性
            System.out.println(outerProp);
            // 调用外部类方法
            outerMethod();
            // 得到外部类对象
            // OuterClazz.this
        }
    }
}

使用内部类访问对象状态

从上面的例子中我们可以看到,一个内部类方法可以访问自身的字段;也可以访问它外围类对象的数据字段。

内部类的对象总有一个隐式引用,指向创建它外部类的对象。这个引用在内部类的定义中是不存在的。

内部类的特殊语法规则

实际上,在内部类中使用外部类引用可以使用如下表达式:

外部类名.this

在外围类的作用域之外,如果想调用内部类,可使用:

OuterClass.InnerClass

内部类中声明的所有静态阻断都必须是final, 并初始化为一个编译时常量

内部类不能有static方法

局部内部类

如果一个类只会在一个特定的方法中出现,就可以在这个方法中局部定义这个类。

/**
 * 局部内部类
 */
public class InnerClazz1 {
    public void method1(){
        // 局部内部类
        class InnerClazz{
            public void InnerClazzMethod(){
                System.out.println("局部内部类方法");
            }
        }

        InnerClazz1 innerClazz1 = new InnerClazz1();
        innerClazz1.method1();
    }
}
  • 声明局部类时候不能有访问修饰符。

  • 局部类的作用域被限定在声明这个局部类的块中。

局部内部类优点

  1. 局部类有一个很大的优势,就是对外部完全隐藏。
  2. 局部类不仅可以访问外部类的字段,还可以访问局部变量。不过这些局部变量必须是事实最终变量。

匿名内部类

假如只想创建这个类的一个对象,甚至不需要为类指定名字。这样一个类被称为匿名内部类。

之前使用的Comparator接口进行排序,在调用Collection.sort方法时,我们使用的Lambda表达式传入的比较器。除此之外,我们也可以使用匿名内部类

Collections.sort(users, new Comparator<User>() {
    @Override
    public int compare(User o1, User o2) {
        return Integer.compare(o1.getId(),o2.getId());
    }
});

传入的比较器我们无需为其指定名字,所以可以使用匿名内部类。

静态内部类

有时候使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类有外部类对象的引用。为此,可以将内部类声明为static。

加载服务器

待建设。。。

代理

待建设。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值