Java基础——(五)接口与内部类

143 阅读7分钟

1. 接口

在Java中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。以Arrays类中的sort方法为例,sort方法承诺可以对对象数组进行排序,但要求满足下列前提:对象所属的类必须实现了Comparable接口。

public interface Comparable<T> {
	int compareTo(T other);
}

这就是说,任何实现Comparable接口的类都需要包含compareTo方法,并且返回一个整型数值。 接口中的所有方法自动地属于public,因此在接口中声明方法时不需要提供关键字public。 一个接口可能有多个方法,但一个类实现某个接口时,必须实现该接口的所有方法(否则这个类只能定义为抽象类)。要将类声明为实现某个接口,需要使用关键字implements。以Employee类为例,这里我们根据雇员的薪水进行比较,实现内容如下:

public class Employee extends Person implements Comparable{
   ...
   
    @Override
    public int compareTo(Object o) {
        Employee other = (Employee) o;
        return Double.compare(this.salary, other.getSalary());
    }
}
 // 或者
public class Employee extends Person implements Comparable<Employee>{
   ...
   
    @Override
    public int compareTo(Employee other) {
        return Double.compare(this.salary, other.getSalary());
    }
}

在这里插入图片描述

1.1 接口的特性

接口不是类,不能用new实例化一个接口,但是我们可以声明接口的变量,接口变量必须引用实现了接口的类对象:

Comparable x;
x = new Employee("test", 100);

与使用instanceof检查一个对象是否属于某个特定类一样,也可以使用instanceof检查一个对象是否实现了某个特定的接口。 与可以建立类的继承关系一样,接口也可以被扩展,这里允许存在多条从具有较高通用性的接口到较高专用性的接口的链。

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

public interface Powered extends Moveable {
	double milesPerGallon();
}

接口不能包含实例域或静态方法,但却可以包含常量,如:

public interface Powered extends Moveable {
	double milesPerGallon();
	double SPEED_LIMIT = 95; // java会自动认为这是一个public static final的域
}

Java不支持多继承,也就是每个类只能拥有一个超类,但是却可以实现多个接口,这为定义类的行为提供了极大的灵活性。

1.2 接口默认实现

在接口中,我们可以使用default关键字来定义接口的默认方法。在一个典型的基于抽象的设计中,一个接口有一个或多个实现类,接口要新增方法,所有的实现都要新增这个方法的实现,否则就不满足接口的约束,默认接口方法是处理这类问题的解决方案,通过在接口添加默认方法不需要修改实现类,接口新增的默认方法在实现类中直接可用。

public interface MobilePhone {
	String getBandName();

	default String description() {
		return "hello world";
	}
}

public class IPhone implement MobilePhone {
	@Override
	public String getBandName() {
		return "iPhone";
	}
	// 不需要实现接口的默认方法
}

public class Huawei implement MobilePhone {
	@Override
	public String getBandName() {
		return "huawei";
	}

	// 可用根据需要,重写默认方法
	@Override	
	public String description() {
		return "yaoyaolingxian,骁龙680";
	}
}

2. 对象克隆

当拷贝一个变量时,原始变量与拷贝变量引用同一个对象,如果希望创建一个对象的新的copy,且不引用同一个对象,那么就需要使用clone方法。 在Object类中,有一个clone方法,但这个方法是一个protected方法,也就是说,在用户编写的代码中不能直接调用它,只有Empoloyee类才能克隆Empoloyee对象。 在Object类的clone方法中,会对各个域进行对应的拷贝,如果对象中所有数据域都属于数值或基本类型,那么这样拷贝没问题,但如果在对象中包含了其他对象的引用,这样拷贝会使得两个域引用同一个对象,这也就是我们常说的浅拷贝。 在这里插入图片描述 如果原始对象域浅拷贝对象共享的子对象是不可变的,那么浅拷贝不会产生任何问题,例如子对象属于String类这样的不允许改变的类。 但如果子对象可变,我们就需要重新定义clone方法,以实现克隆子对象的深拷贝。

class Employee implements Cloneable {
	 public Employee clone() throws CloneNotSupportedException { 
        Employee cloned = (Employee) super.clone();
        cloned.hireDay = (Date) hireDay.clone();
        return cloned;
    }
}

只要在clone中含有没有实现Cloneable接口的对象,那么Object类的clone方法就会抛出一个CloneNot-SupportException异常,因此我们在方法定义的时候需要声明异常:

public Employee clone() throws CloneNotSupportedException

3. 内部类

内部类(inner class)是定义在另一个类中的类,使用内部类的主要原因有:

  • 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据
  • 内部类可以对同一个包中的其他类隐藏起来
  • 当想定义一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷。

3.1 使用内部类访问对象状态

假设我们现在有一个订单类,要求每次在支付订单的时候,能打印相关的日志,以方便后续去排查信息,在打印日志的时候,我们希望是异步打印的,一般情况下,我们可以在支付订单后,手动创建相关的线程并提交给线程池进行执行。但内部类给我们提供了另外一种思路,因为内部类可以访问对象状态,所以我们可以直接在订单类中声明一个内部的日志线程类,在这个内部类里,完成日志的打印,代码如下所示:

public class Order {
   private static ExecutorService executorService = Executors.newFixedThreadPool(2);
   private String id;
   private String productName;
   
   private String productId;
   
   private Integer price;
   
   public Order(String productName, String productId, Integer price) {
       this.productName = productName;
       this.productId = productId;
       this.id = UUID.randomUUID().toString();
       this.price = price;
   }
   
   public void payOrder() {
       System.out.println("下单成功");
       executorService.submit(new OrderLogThread());
   }
   
   public class OrderLogThread implements Runnable {
       @Override
       public void run() {
           // 打印日志
           System.out.printf("productName:%s, productId:%s, orderId:%s, price:%d",
                   productName, productId, orderId, price);
       }
   } 
}

在这里插入图片描述 假设在OrderLogThread类中,也有一个id属性,那么如果它想获取外部类的id,可以通过外部类名.this.id的方式来获取,如下所示:

  public class OrderLogThread implements Runnable {
        private String id;
        
        @Override
        public void run() {
            // 打印日志
            System.out.printf("productName:%s, productId:%s, orderId:%s, price:%d",
                    productName, productId, Order.this.id, price);
        }
    }

3.2 静态内部类

有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象,为此,可以将内部类声明为static,以便取消产生的引用。静态内部类,被广泛应用到创建者模式当中,对于一些比较复杂的对象,我们经常需要在创建的时候,加上一些条件判断,以确保该对象的属性值符合预期,这个时候,我们可以把这些判断逻辑等交给静态内部类,并且将目标类的构造器设置为private,也就是说在静态内部类完成该目标类的构造。 以矩形为例,矩形有长度和宽度,长度要大于宽度,如果使用静态内部类,我们的定义如下:

public class Rectangle {
    private Long length;

    private Long width;

    private Rectangle(Long length, Long width) {
        this.length = length;
        this.width = width;
    }

    public Long getLength() {
        return length;
    }

    public Long getWidth() {
        return width;
    }

    public Long area() {
        return this.length * this.width;
    }

    public static class RectangleBuilder {
        private Long length;
        private Long width;

        public RectangleBuilder setLength(Long length) {
            if (length <= 0) {
                throw new RuntimeException("length should larger than 0");
            }
            this.length = length;
            return this;
        }
        
        public RectangleBuilder setWidth(Long width) {
            if (width <= 0) {
                throw new RuntimeException("width should larger than 0");
            }
            this.width = width;
            return this;
        }
        
        public Rectangle build() {
            if (this.width >= this.length) {
                throw new RuntimeException("width should less than length");
            }
            return new Rectangle(this.length, this.width);
        }
    }
}

在这里插入图片描述 静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样。当我们在内部类不需要访问外围类对象的时候,应该将这个内部类定义为静态内部类。

参考链接

www.cnblogs.com/huahuayu/p/… blog.csdn.net/weixin_4276… 《Java核心技术卷I》(网盘链接:pan.quark.cn/s/06c58d47d…