0
点赞
收藏
分享

微信扫一扫

6.接口与内部类


文章目录

  • ​​1. compare比较接口​​
  • ​​2. comparator又是啥?​​
  • ​​3. 默认方法冲突​​
  • ​​4. 四个内部类​​
  • ​​4.1 普通内部类​​
  • ​​4.2 局部内部类​​
  • ​​4.3 匿名内部类​​
  • ​​4.4 静态内部类​​

1. compare比较接口

这个接口其实用到的地方蛮多的,一般就是针对数组元素的排序,经常要用到,简单介绍下。

看如下代码,对我们自定义的​​Employee​​对象数组进行正序排列。

public class Demo1 {
public static void main(String[] args) {
Employee e1 = new Employee(5, "c");
Employee e2 = new Employee(2, "b");
Employee e3 = new Employee(1, "a");
List<Employee> employees = new ArrayList<>(Arrays.asList(e1, e2, e3));
//使用工具类传入list集合
Collections.sort(employees);
System.out.println(employees);
//[Employee(id=1, name=a), Employee(id=2, name=b), Employee(id=5, name=c)]
}
}

@Data
@AllArgsConstructor
class Employee implements Comparable<Employee>{
private int id;
private String name;

@Override
public int compareTo(Employee other) {
return this.id - other.id;
}
}

其实排序很简单,需要我们实现​​Comparable​​​泛型填入自定义对象即可,对于​​compareTo​​​方法,我们是需要通过id来正序排列,所以当前对象的​​this.id​​​与​​other.id​​进行比较,比如两个id分别是5,2,相减是正数,那么就需要调换位置,反之相减如果是负数,则不需要调换位置,排序就是根据两者的大小来比较的。

当然这个比较不是那么简单的,还有几点需要注意。
1.数据溢出的风险

两者相减不要超过int类型的范围,防止int溢出的情况,比较推荐的方法是​​Integer.compare(this.id, other.id);​​这就不会有溢出的风险了。

2.保证对称性
对于两数的比较,若​​​x.compareTo(y)​​​抛出异常,​​y.compareTo(x)​​​也同样要抛出异常,这点跟​​equals​​类似。

可能有同学会问,这哪会出现这样的问题嘛,那我们来看下这个例子。

public class Demo1 {
public static void main(String[] args) {
Employee e1 = new Manager(5, "c");
//注意 这里将对象换成了Employee
Employee e2 = new Employee(2, "b");
Employee e3 = new Manager(1, "a");
List<Employee> employees = new ArrayList<>(Arrays.asList(e1, e2, e3));
//使用工具类传入list集合
Collections.sort(employees);
System.out.println(employees);
}
}

@Data
@AllArgsConstructor
class Employee implements Comparable<Employee>{
private int id;
private String name;

@Override
public int compareTo(Employee other) {
return Integer.compare(this.id, other.id);
}
}

class Manager extends Employee {

public Manager(int id, String name) {
super(id, name);
}

@Override
public int compareTo(Employee other) {
Manager manager = (Manager) other;//ClassCastException Employee不能被强转为Manager
return this.getId() - manager.getId();
}
}

这样排序就是有问题的,因为在父子类之间进行比较的话,Employee不能被强转为Manager,那就违反了上面的原则,补救办法有两个。
1.不同子类之间不能进行比较。

那就很简单,在​​compareTo​​比较时,首先判断下类型是否一致即可。

@Override
public int compareTo(Employee other) {
if (getClass() != other.getClass()) {
throw new ClassCastException("类型不一致不能比较");
}
Manager manager = (Manager) other;
return this.getId() - manager.getId();
}

2.不同类之间可以比较

那就需要在父类里定义一个通用的比较方法,且定义成final,不允许重写,哪个子类都通过这个方法比较即可。

2. comparator又是啥?

明明有上面的比较接口就够了呀,为啥还要有这个接口?相信不少同学有这样的疑问,我之前也有,不过现在可以给大家解释,因为某些类已经定死了,比如String类,本身就是jdk自带的类,我们想让某些字符串按照特定规则排序,那原有的​​Compare​​接口是做不到的,它只能做到让字符串根据字典顺序排列,但我们要是想让字符串数组按长度排列呢?

实现也很简单,我们只要重写​​Comparator#compare​​​,将字符串的长度进行比较,最终调用​​list.sort(Comparator<? super E> c)​​排序即可。

public class Demo4 {
public static void main(String[] args) {
List<String> list = new ArrayList() {
{
add("cccc");
add("a");
add("bb");
}
};
LenComparator comparator = new LenComparator();
list.sort(comparator);
System.out.println(list);//[a, bb, cccc]
}
}

class LenComparator implements Comparator<String> {

@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();//按长度正序排列
}
}

这个比较器能够很大程度上丰富我们的排序需求,如无必要,勿增实体,大佬们也不太想要增加些新的类来实现一些已有的功能,只不过如果一个类不可变,不可再实现接口了,这个比较器就是一个好的选择。

3. 默认方法冲突

我们知道,在Java8里,为接口新增了​​default​​​与​​static​​修饰符,但如果默认方法冲突了该怎么办?我们来看下列几种情况。

1.接口与类包含同名方法

看下面的代码就很容易知道了,以具体类中的方法为准,毕竟接口只是提供了某种规范。

public class Demo2 {
public static void main(String[] args) {
new Dad().run();//Dad run...
}
}

interface Person {
default void run() {
System.out.println("Person run...");
}
}

class Dad implements Person{
public void run() {
System.out.println("Dad run...");
}
}

2.两个接口有相同默认方法签名,且该类实现了两个接口

给出结论,需要我们强制覆盖对应的​​run​​方法,自己决定最终的实现。

同理,如果一个接口是默认方法,一个是抽象方法,但签名相同(签名相同就是方法名和形参相同),也是需要我们自己重写对应的方法,解决二义性。

public class Demo2 {
public static void main(String[] args) {
new Dad().run();//Dad run...
}
}

interface Person {
default void run() {
System.out.println("Person run...");
}
}

interface People {
default void run() {
System.out.println("People run...");
}
}
//可以看到 两个接口有相同的方法签名 则需要强制覆盖
class Dad implements Person,People{
@Override
public void run() {
System.out.println("Dad run...");
}
}

3.某类继承了一个类,且实现了一个接口,但两者方法签名相同

给出结论,还是以类为准,父类其实已经对该方法做出了实现,子类可以选择是否重写,不重写编译器也不会报错。

public class Demo2 {
public static void main(String[] args) {
new Dad().run();
}
}

interface People {
default void run() {
System.out.println("People run...");
}
}

class Human {
public void run() {
System.out.println("Human run...");
}
}
class Dad extends Human implements People{

}

所以面对方法冲突的原则是,类优先,有冲突就自己解决。

4. 四个内部类

4.1 普通内部类

我们说下普通内部类的几个特点吧。

1.首先是权限修饰符,可以是​​private​​或者是默认的,这取决于你是否需要外部访问该内部类。

2.内部类里可以直接访问外部类成员和方法,调用可以省略​​外部类.this​

3.内部类的创建需要依赖外部类的对象,通过​​外部类对象.new 内部类()​​的方式。

4.外部类不能直接访问内部类,只能通过内部类的对象来访问。

内部类的好处在于防止类重名,如果

class Outer {
private int id;

public void play() {
System.out.println("111");
}

public static void main(String[] args) {
Outer outer = new Outer();

//创建需要通过外部类的对象来创建
Outer.Inner inner = outer.new Inner();
inner.run();
}

private class Inner {

public void run() {
//可以访问外部类的成员和方法
System.out.println(id);//id等同于 Outer.this.id
Outer.this.play();//等同有 play()等同于 Outer.this.play()
}
}
}

4.2 局部内部类

与普通内部类不同的是,它存在于方法的内部,且class不能有任何修饰符,它的好处在于,对外部世界完全隐藏,当这个方法结束的时候,这个类也随之消亡了。

看示例,在方法内部通过创建内部类的对象完成调用,且能够直接访问run方法的形参。

public class Demo1 {
public static void main(String[] args) {
run("bb");
}

public static void run(String s) {
class Bb {
public void action() {
System.out.println(s);
}
}
//内部创建对象完成调用
new Bb().action();
}
}

4.3 匿名内部类

一般多用于抽象类或者接口的实例化,只要实现对应的抽象方法,就能完成实例化,但这个内部类是没有名称的,所以自然而然可以想到,它没有构造器。
需要注意的是,普通类对象的创建也可以在后面加括号,在类中重写原有的方法。所以匿名内部类功能很是强大。

public class Demo1 {

public static void main(String[] args) {
Listener listener = new Listener() {
@Override
public void run() {
System.out.println("监听启动~~");
}
};
//多数采用lambda的方式
Listener listener2 = () -> System.out.println("监听启动~~");

Hello hello = new Hello() {
@Override
void run() {
System.out.println("监听启动~~");
}
};

Dd dd = new Dd() {
@Override
public void run() {
System.out.println("run");
}
};
}
}

class Dd {
public void run() {

}
}

abstract class Hello {
abstract void run();
}


interface Listener {
void run();
}

4.4 静态内部类

静态内部类的创建可以不依赖与外部类对象,而是直接通过外部类的类名创建,它还可以任意访问外部类的静态或非静态成员和方法。

public class Demo6 {
public static void main(String[] args) {
//可以直接通过类名创建
Watch watch = new Demo6.Watch();
watch.say();
}

static final String WORDS = "hello bb";

static class Watch {
public void say() {
System.out.println(WORDS);
}
}
}

你很难注意到你拥有什么,除非它消失。


举报

相关推荐

0 条评论