Java基础 之四 接口

1. 接口

1) 定义

public interface Comparable{
	int compareTo(Object other);
}
  • 接口中所有方法自动属于public,所以定义的时候不能添加public
  • 接口不能含有实例域,但是可以定义常量,Java8之后还可以提供简单的方法
  • 可以将接口看成没有实例域的抽象类

一个类实现接口,需要以下两个步骤

1)将类声明为实现指定接口

2)对接口中所有的方法进行定义

class Employee implements Comparable<Employee>{
	 @Override
    public int compareTo(Employee other)
    {
      return Double.compare(salary, other.salary);      
    }
}
//compareTo返回一个整形数值
//整形是Integer.compare(x,y);   
//Double.compare(x,y); x<y 返回-1; x>y 返回1。    

想要使用Arrays.sort()排序,类必须实现Comparable接口

//可能sort()方法中有下面的语句
if(a[i].comparaTo(a[j])>0){
	...
}

2) 接口特性

1.不能用new运算符实例化一个接口(函数式接口除外):

x = new Comparable();	//error

2.可以声明接口的变量

Comparable x;

3.可以使用instanceOf检查一个对象是否实现了某个接口

if(obj instanceOf Comparable){...}

4.接口也可以被继承,还可以包含常量

public interface Moveable{
	void move(double x,double y);	
}

//继承
public interface Powered extends Moveable{
    double function();	//方法自动变为public
    double SPEED = 5;	//接口的域自动变成public static final;
}

5.类不可以被多继承,但是接口可以被多继承

class Employee implements Cloneable,Comparable

3) 静态方法

//java8中 允许在接口中添加静态方法
public interface Path{
    public static Path get(){
        ...
    }
}

4) 默认方法

//可以为接口方法提供一个默认实现,必须用default修饰符标记
public interface Collection{
    int size();	//抽象方法
	default boolean isEmpty(){
		return size() = 0;
	}
}

5) 解决默认方法冲突

  1. 超类优先。如果超类提供了一个具体方法,同名而且有系统参数类型的默认方法会被忽略。

  2. 接口冲突。如果一个超接口提供了一个默认方法,另一个接口提供了一个 同名而且参数类型相同的方法必须,必须覆盖整个方法来解决冲突。

//第二个规则举例
interface Named1{
    default String getName{
        return getClass().getName()+"_"+hashCode();
    }
}
interface Named2{
    default String getName{
        return getClass().getName()+"_"+hashCode();
    }
}

//如果一个类同时继承这2个接口怎么办,直接继承编译器会报告一个错误
class Student implements Named1,Named2{
    public String getName{
        return Named1.super.getName();	//选择其中一个调用
    }
}

//当一个类扩展一个超类,同时又实现一个接口,接口和超类有相同签名的方法
//这种情况就只会考虑超类方法,接口的所有默认方法都会被忽略
class Student extends Person implements Named1{...}

2. Comparable和Comparator

1) Comparable

@Data//lombok
public class Person implements Comparable<Object>{
    private int num;
    private String name;
    private int age;
    
    //重载compareTo方法
    @Override
    public int compareTo(Object o){
		return this.age - ((Person) o).getAge();
	}
}

2) Comparator

// 实现Comparator接口 重新写一个类用来比较

public class AscComparator implements Comparator<Object>
{
    //重写compare方法
	@Override
    public int compare(Student stu1, Student stu2) {
        // 根据成绩降序排列
        return stu1.getRecord() - stu2.getRecord();
    }
}

//他是一个 函数式接口,也可以用lambda表达式传入一个比较器
public class ComparatorTest {

    public static void main(String[] args) {
        List<Student> studentList = Arrays.asList(new Student("liming", 90),
                new Student("xiaohong", 95),
                new Student("zhoubin", 88),
                new Student("xiaoli", 94)
        );
        // 1. 可以实现自己的外部接口进行排序
        Collections.sort(studentList,new AscComparator());

        System.out.println(studentList);

        // 2、 可以匿名内部类实现自定义排序
        Collections.sort(studentList, new Comparator<Student>() {
            @Override
            public int compare(Student stu1, Student stu2) {
                return stu2.getRecord() - stu1.getRecord();
            }
        });
        System.out.println(studentList);
    }
}

3) 对比

Comparable 和 Comparator 的对比
1、Comparable 更像是自然排序
2、Comparator 更像是定制排序

同时存在时采用 Comparator(定制排序)的规则进行比较。

对于一些普通的数据类型(比如 String, Integer, Double…),它们默认实现了Comparable 接口,实现了 compareTo方法,我们可以直接使用。

而对于一些自定义类,它们可能在不同情况下需要实现不同的比较策略,我们可以新创建 Comparator 接口,然后使用特定的 Comparator 实现进行比较

3. 对象clone

Employee original = new Employee("John Public",50000);
Employee copy = original;
//此时实际上original和copy引用的是同一个对象,并没有真正地复制一个对象

clone方法是Object的一个protected方法,clone分为浅拷贝和深拷贝。

1)浅拷贝

浅拷贝只拷贝基本类型,不拷贝对象中引用的其他对象。浅拷贝的典型实现方式是:让被复制对象实现Cloneable接口,并重写clone方法。

看如下例子(B站程序羊的例子CodeSheep)

有两个类

img img

为了复制学生类,重写Cloneable接口

img

然后写个测试代码

img

运行获得如下结果:

img

从结果看出

  • student1 == student2为false,说明clone的确克隆出一个新对象;
  • 修改值类型的字段不影响clone出来的新对象,即值类型字段被克隆;
  • 而修改student1中的引用对象,student2也被修改,说明引用对象并没有被克隆;
img

2)深拷贝

深拷贝就会把引用类型也克隆一份。想要实现深拷贝,首先,要对引用类型的类重写clone方法;

img

然后在顶层的调用类中重写clone方法来调用引用类型字段的clone方法实现深度拷贝,例子中就是Student类;

img

测试代码不变,运行获得如下结果:

img

原理图如下

img

3)说明

String类型是一个特殊,String虽然属于引用类型,但是String类是不可改变的,它是一个常量,一个对象调用clone方法,克隆出一个新对象,这时候两个对象的同一个String类型的属性是指向同一片内存空间的,但是如果改变了其中一个,会产生一片新的内存空间,此时该对象的这个属性的引用将指向这片新的内存空间,此时两个对象的String类型的属性指向的就是不同的2片内存空间,改变一个不会影响到另一个,可以当做基本类型来使用

4. 内部类

内部类可以分为多种;主要以下4种:静态内部类,成员内部类,局部内部类,匿名内部类

1.静态内部类
静态内部类是指被声明为static的内部类,他可以不依赖内部类而实例,而通常的内部类需要实例化外部类,从而实例化。静态内部类不可以有与外部类有相同的类名。不能访问外部类的普通成员变量,但是可以访问静态成员变量和静态方法(包括私有类型)

2.成员内部类
一个静态内部类去掉static 就是成员内部类,他可以自由的引用外部类的属性和方法,无论是静态还是非静态。但是不可以有静态属性和方法

3.局部内部类
定义在一个代码块的内部类,他的作用范围是所在代码块,是内部类中最少使用的一类型。局部内部类跟局部变量一样,不能被public ,protected,private以及static修饰,只能访问方法中定义final类型的局部变量

4.匿名内部类
匿名内部类是一种没有类名的内部类,不使用class,extends,implements,没有构造函数,他必须继承其他类或实现其他接口。匿名内部类的好处是使代码更加简洁,紧凑,但是带来的问题是易读性下降。

函数式接口:只包含一个方法的函数

interface Azhe{	//函数式接口 阿这
    void f(int num);
}
package com.tyy.demo1;

public class Lambda {

    //内部类,私密性好,隐藏操作,可以调用外部类对象的内容,甚至包括私有变量
    //1.静态内部类
    static class innerClass1 implements Azhe{
        @Override
        public void f(int num) {
            System.out.println("这是静态内部类"+num);
        }
    }

    //2.成员内部类
    class innerClass2 implements Azhe{
        @Override
        public void f(int num) {
            System.out.println("这是成员内部类"+num);
        }
    }

    public static void main(String[] args) {
        //局部内部类
        class innerClass3 implements Azhe{
            @Override
            public void f(int num) {
                System.out.println("这是局部内部类"+num);
            }
        }

        //匿名内部类
        Azhe azhe = new Azhe() {
            @Override
            public void f(int num) {
                System.out.println("这是匿名内部类"+num);
            }
        };

        //1.静态
        innerClass1 innerClass1 = new innerClass1();
        innerClass1.f(1);
        //2.成员内部类
        innerClass2 innerClass2 = new Lambda().new innerClass2();
        innerClass2.f(2);
        //3.局部
        innerClass3 innerClass3 = new innerClass3();
        innerClass3.f(3);
        //匿名
        azhe.f(4);
    }
}

5. lambda表达式

//只有一个方法的接口叫做函数式接口
//只有函数式接口可以用lambda表达式来实现
interface Azhe{	//函数式接口 阿这
    void f(int num);
}
       //匿名内部类创建接口对象
        Azhe azhe = new Azhe() {
            public void f(int num) {
                System.out.println("这是匿名内部类"+num);
            }
        };
		//或者
        Azhe azhe = (int num)->{	//lambda表达式
            public void f(int num) {
                System.out.println("这是匿名内部类"+num);
            }
        };

        //简化
        //1.简化参数类型
        Azhe azhe1 = (num)-> { System.out.println("这是匿名内部类1"+num); };
        //2.简化括号,只有一个参数才可以去掉圆括号
        Azhe azhe2 = num-> { System.out.println("这是匿名内部类2"+num); };
        //3.简化花括号,只有一行代码才能这样简化花括号
        Azhe azhe3 = num->  System.out.println("这是匿名内部类3"+num);

6.方法引用

::关键字提供了四种语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,::关键字可以使语言更简洁,减少冗余代码。

语法种类 示例
引用静态方法 ContainingClass::staticMethodName
引用特定对象的实例方法 containingObject::instanceMethodName
引用特定类型的任意对象的实例方法 ContainingType::methodName
引用构造函数 ClassName::new

1) 引用静态方法

public class Person {

    public enum Sex {
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    public int getAge() {
        // ...
    }
    
    public Calendar getBirthday() {
        return birthday;
    }    

    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }
}

假设您的社交网络应用程序的成员包含在一个数组中,并且您想按年龄对数组进行排序。您可以使用以下代码(在示例中找到本节中描述的代码摘录 MethodReferencesTest):

Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);

class PersonAgeComparator implements Comparator<Person> {
    public int compare(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
        
Arrays.sort(rosterAsArray, new PersonAgeComparator());

调用的方法签名如下:

static <T> void sort(T[] a, Comparator<? super T> c)

请注意,该接口Comparator是功能接口。因此,您可以使用lambda表达式,而不是定义并创建一个新类的实例,该实例实现Comparator

Arrays.sort(rosterAsArray,
    (Person a, Person b) -> {
        return a.getBirthday().compareTo(b.getBirthday());
    }
);

但是,这种用于比较两个Person实例的出生日期的方法已经存在Person.compareByAge。您可以改为在lambda表达式的主体中调用此方法:

Arrays.sort(rosterAsArray,
    (a, b) -> Person.compareByAge(a, b)
);

由于此lambda表达式会调用现有方法,因此您可以使用方法引用代替lambda表达式:

Arrays.sort(rosterAsArray, Person::compareByAge);

方法引用Person::compareByAge在语义上与lambda表达式相同(a, b) -> Person.compareByAge(a, b)。每个都有以下特征:

  • 它的形参列表是从复制Comparator<Person>.compare,这是(Person, Person)
  • 它的主体调用该方法Person.compareByAge

2) 引用特定对象的实例方法

以下是对特定对象的实例方法的引用示例:

class ComparisonProvider {
    public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }
        
    public int compareByAge(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);

方法引用myComparisonProvider::compareByName调用compareByName作为对象一部分的方法myComparisonProviderJRE推断方法类型参数,在这种情况下为(Person, Person)

3) 引用特定类型的任意对象的实例方法

以下是对特定类型的任意对象的实例方法的引用示例:

String[] stringArray = { "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

方法参考的等效lambda表达式String::compareToIgnoreCase将具有形式参数列表(String a, String b),其中ab是用于更好地描述此示例的任意名称。方法引用将调用该方法a.compareToIgnoreCase(b)

4) 引用构造函数

您可以使用name以与静态方法相同的方式引用构造函数new。以下方法将元素从一个集合复制到另一个:

public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
   DEST transferElements(SOURCE sourceCollection,  Supplier<DEST> collectionFactory) {
        
        DEST result = collectionFactory.get();
        for (T t : sourceCollection) {
            result.add(t);
        }
        return result;
}

功能接口Supplier包含一个get不带任何参数并返回一个对象的方法。因此,您可以transferElements使用lambda表达式调用该方法,如下所示:

Set<Person> rosterSetLambda =
    transferElements(roster, () -> { return new HashSet<>(); });

您可以使用构造函数引用代替lambda表达式,如下所示:

Set<Person> rosterSet = transferElements(roster, HashSet::new);

Java编译器推断您要创建一个HashSet包含type元素的集合Person。或者,您可以指定以下内容:

Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);
posted @ 2020-11-25 10:45  半泽直树  阅读(149)  评论(0)    收藏  举报