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) 解决默认方法冲突
-
超类优先。如果超类提供了一个具体方法,同名而且有系统参数类型的默认方法会被忽略。
-
接口冲突。如果一个超接口提供了一个默认方法,另一个接口提供了一个 同名而且参数类型相同的方法必须,必须覆盖整个方法来解决冲突。
//第二个规则举例
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)
有两个类
为了复制学生类,重写Cloneable接口
然后写个测试代码
运行获得如下结果:
从结果看出
- student1 == student2为false,说明clone的确克隆出一个新对象;
- 修改值类型的字段不影响clone出来的新对象,即值类型字段被克隆;
- 而修改student1中的引用对象,student2也被修改,说明引用对象并没有被克隆;
2)深拷贝
深拷贝就会把引用类型也克隆一份。想要实现深拷贝,首先,要对引用类型的类重写clone方法;
然后在顶层的调用类中重写clone方法来调用引用类型字段的clone方法实现深度拷贝,例子中就是Student类;
测试代码不变,运行获得如下结果:
原理图如下
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作为对象一部分的方法myComparisonProvider。JRE推断方法类型参数,在这种情况下为(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),其中a和b是用于更好地描述此示例的任意名称。方法引用将调用该方法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);

浙公网安备 33010602011771号