12、Interface (接口)&克隆

(一)Interface Note

1、概念

  • 接口反映某一类事物具有什么能力

    • 关注该类事物可以完成什么操作
    • 但是不关注操作的具体实现细节
  • 接口的目的是指明相关或者不相关类的多个对象的共同行为

  • 接口体现了程序设计的多态和高内聚低耦合的设计思想

  • 为接口命名时建议采用 可以什么的、提供什么支持的、有什么能力的

  • 尽可能在一个接口里提供少的方法,接口更多时候不是提供一种方法,而是一种标记 - 什么可以产生什么。(如Cloneable接口,此接口内部没有方法,只是作为标记接口,标记某一个对象能否被克隆。)

2、基本语法

声明接口
  • [ 修饰符 ] interface 接口名称 { }
继承接口
  • [ 修饰符 ] interface 子接口名称 extends 父接口名 [ , 父接口名 , ... ] { }
实现接口
  • [ 修饰符 ] class 类名 implements 接口名 [ , 接口名 , ... ] { }

3、特点

1、接口不是类,没有构造方法,不能实例化

2、接口之间的继承也使用 extends 实现,但接口可以继承多个父接口

3、接口中所有的 “ 没有方法体 ” 方法 都是公开的抽象方法(默认被 public abstract 修饰)

  • “没有方法体”的方法 有:抽象方法,本地方法(本地方法不可以在接口中声明)

4、从 Java 8 开始允许在接口中定义static修饰的方法

  • 访问修饰符可以是 private 或 public
  • 访问修饰符不可以是 protected 或 默认

5、从 Java 8 开始允许在接口中定义 default修饰的、非抽象的、公开的实例方法

6、在接口中只能声明常量(所有的成员变量默认都是 public static final 修饰的)

4、抽象类与接口的区别

共同点

1、抽象类和接口都是抽象的,都不能被实例化

  • 抽象类有构造但不允许实例化,构造是供子类的构造调用
  • 接口没有构造,因此绝对不可能被实例化

2、抽象类和接口都可以包含常量

3、抽象类和接口都可以包含抽象方法,用于实现他们的子类都必须覆写抽象方法

4、抽象类和接口(从Java8开始)中都可以包含static方法,比如main方法

5、抽象类和接口(从Java9开始)都可以包含私有方法

6、抽象类和接口(从Java8开始)都可以包含由default修饰的、非抽象的、公开的实例方法

7、抽象类和其他类之间、接口和接口之间的继承都使用extends关键字实现

8、修饰抽象方法时,abstract关键字都不能跟static或者final联用

9、都位于继承的顶端,用于被其他实现或继承

区别

1、抽象类是类,接口不是类

2、抽象类只能单继承(一个类最多继承extends一个其他类);接口可以多继承(一个类可以继承implements多个接口)

3、抽象类中可以有构造方法;接口中没有构造方法

4、抽象类中可以有static代码块和实例代码块;接口中没有代码块

5、抽象类中可以有常量;接口中只能有常量

6、抽象类中可以有普通字段(类字段和实例字段);接口中所有的字段默认都是public static final修饰的

7、所有的抽象类的继承顶端是Object类;接口的继承顶端没有限制,可以是父接口也可以没有父接口

8、抽象类可以有非抽象方法(从Object类继承的都是非抽象的);接口从jdk1.8开始可以有static或default修饰的非抽象方法(引入了类方法和默认方法)

9、抽象类中的普通方法书写时没有限制;接口中的default修饰的方法一定是非抽象的、公开的,static方法只能由public或private修饰

10、抽象类可以定义本地(native)方法;接口不能定义本地方法

11、子类用extends关键字继承抽象类;子类用implements关键字实现接口

12、抽象类用abstract关键字声明;接口用interface关键字声明

12、一个类(如抽象类)可以实现多个接口,但不能继承接口;接口可以继承多个接口,但不能实现接口

13、一个类在继承类的同时,也可以继承接口

14、抽象的内容不同

16、抽象类中的方法可以是任意访问修饰符;接口中的方法默认是public修饰符,访问修饰符可以是 private

选择

在接口和抽象类的选择上,遵守:

  • 行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
  • 选择抽象类时通常是:需要定义子类的行为,又要为子类提供通用的功能。
  • 抽象类可以定义一些子类的公共方法,子类只需要增加新的功能,不需要重复写已经存在的方法;而接口中只是对方法的声明和常量的定义。

5、克隆

  • 被克隆对象对应的类需要实现Cloneable接口
  • 被克隆对象对应的类需要重写从Object类中继承的clone方法
  • 在重写后的clone方法中首先调用Object的clone方法完成对对象的 浅克隆
  • 克隆一个对象时,将对象所关联的其它对象也克隆,称作 深克隆

附:抽象类 PK 接口

抽象类接口
构造有构造(供子类构造调用)没有构造
代码块可以有代码块(静态代码块和普通代码块)不能有
具体方法可以有非抽象方法
(从Object类继承的方法都是非抽象的)
JDK 1.8 开始,这个可以有
( static 修饰或 default 修饰)
抽象方法可以有抽象方法,也可以没有抽象方法
可使用除了private以外的任意权限修饰符
可以有抽象方法,也可以没有抽象方法
只能是 public 修饰的
常量(Field)可以有只能有常量
普通字段可以有
可以是各种权限修饰符
不能有
( 因所有字段默认都是 public static final 修饰的)
继承一个子类只能继承一个父类一个接口可以继承多个父接口
实现一个类可以实现多个接口接口只能继承接口,不能实现接口

抽象类 可以包含 native 方法,而 接口 不允许包含 native 方法

共同点: 都不能被实例化

  • 抽象类有构造但不允许实例化,构造供子类构造调用
  • 接口没有构造,因此绝对不可能被实例化

修饰方法时:

  • abstract 不能跟 static 连用
  • abstract 不能跟 final 连用

(二)Interface Code

1、体验声明接口和用类来实现接口

Animate.java

package cn.edu.ecut;
// 有生命的
public interface Animate { //所有接口默认都是抽象的,无论写不写abstract
	public abstract void breathe() ; // 呼吸
    //可写为void breathe();接口中所有没有方法体的方法默认是抽象方法,由public abstract修饰。(本地方法除外)
}

Removeable.java

package cn.edu.ecut;
// 可移动的
public interface Removable {
	public abstract void move(); 
    //public native void hello(); //接口中不可定义native方法
}

Animal.java

package cn.edu.ecut;
//定义一个抽象类实现多个接口(如果此类中无法实现抽象方法,则类继续定义为抽象类,此时接口中的抽象方法可不必声明)
public abstract class Animal implements Animate , Removable {
	protected String name ;
    public native void hello();//抽象类中可以定义native方法
}

Fish.java

package cn.edu.ecut;
//定义一个具体类继承抽象类,间接实现两个接口
public class Fish extends Animal {

	@Override //可限定重写,告知编译器可以提供相应错误而写的一个注解(java.lang.Override)
	public void breathe() {
		System.out.println( "鱼类是用鳃呼吸的" );
	}

	@Override
	public void move() {//具体类中,一定要实现抽象方法
		System.out.println( "鱼在水中游动" );
	}
	
}

Tiger.java

package cn.edu.ecut;

public class Tiger extends Animal {

	@Override
	public void breathe() {
		System.out.println( "虎类是用肺呼吸的" );
	}

	@Override
	public void move() {
		System.out.println( "虎类可以在陆地上奔跑" );
	}

}

1.1、从Java8开始,接口中允许定义static方法和default方法

Tiger.java

package cn.edu.ecut;

public class Tiger extends Animal {

	@Override
	public void breathe() {
		System.out.println( "虎类是用肺呼吸的" );
	}

	@Override
	public void move() {
		System.out.println( "虎类可以在陆地上奔跑" );
	}
	
	@Override
	public void welcome() { // 实现类可以重写 由接口提供的 default 方法
		System.out.println( "Tiger#welcome()" );
	}

}

Removeable.java

package cn.edu.ecut;

// 可移动的
public interface Removable {
	
	// 在 接口 中只能声明 常量 ,不能声明 普通成员变量
	char UNIT = '米' ; // 等同于 public static final char UNIT = '米' ;
	
	// 在 接口 中所有的 没有方法体 的方法默认都是 public abstract 修饰的
	void move(); // 等同于 public abstract void move() ;
	
	// 接口不可以有构造方法
	//public Removable() { } // Interfaces cannot have constructors
	
	// 从 Java 8 开始 允许在 接口中声明 default 修饰的 、非抽象的 公开的 实例方法
	public default void welcome() { // 实现类可以重写 由接口提供的 default 方法
		System.out.println( this ); // 实例方法中可以使用 this 关键字获得实例
        // Removeable没有父类,因此无法用super关键字来调用方法或字段,super关键字不能单独使用
		System.out.println( "Removable#welcome()" );
	}
	
	
	// 从 Java 8 开始 允许在接口中声明 static 修饰的方法 ( 被 static 修饰的方法绝对不可能是抽象的 )
	public static void hello() {
		System.out.println( "Removable.hello()" );
	}
	
	// 因为  从 Java 8 开始 可以在接口中声明 static 修饰的方法,所以定义一个 main 方法也不足为奇
	public static void main(String[] args) {
		Removable.hello();  // 通过 接口名 来调用 接口中声明的 static 方法
		
		Removable r = null ;
		// r.hello(); // 对于接口来说,不允许使用 接口类型的引用变量 来调用 其内部的 static 方法
        // r.welcome(); //出现NPE异常
		r = new Tiger(); // 实例方法由实例确定,指向谁就调用谁的方法
		r.welcome(); // 调用在 接口中声明的 default 修饰的 实例方法
	}

}

输出:

Removable.hello()
Tiger#welcome()
若把Tiger类里的welcome方法注释,则输出:
Removable.hello()
cn.edu.ecut.Tiger@5e265ba4
Removable#welcome()

Test.java

Class类中的getInterfaces()方法
package cn.edu.ecut;

public class Test {

	public static void main(String[] args) {
		
		Removable r = null ;
		
		// 接口不是类,没有构造方法,不能实例化
		// r = new Removable() ; // Cannot instantiate the type Removable
		
		// 接口类型的引用变量 指向了 其实现类类型的实例
		r = new Tiger();
		r.move(); // 移动
		
		Class<?> c = r.getClass() ; // 获得 运行时类型
        // r指向Tiger的对象,运行时类型就是Tiger
		System.out.println( c.getName() );//输出cn.edu.ecut.Tiger
		
		// Class 类的 getSuperclass() 方法用于获得 某个类的直接父类
		Class<?> p = c.getSuperclass(); // 获得 c 所表示的类的父类,即Tiger的父类
		System.out.println( p.getName() );// 输出cn.edu.ecut.Animal
		
		System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
		
		// 任意一个类型都可以通过 .class 来获取该类型对应的 Class 对象
		Class<?> rc = Removable.class ; // rc是Removable接口对应的Class类型的对象
		System.out.println( rc.getName() ); // 输出cn.edu.ecut.Removable
		
		Class<?>  rp = rc.getSuperclass(); // 获得 rc 所表示的类的父类
		System.out.println( rp == null ? "没有父类" : rp.getName() ); // 输出 没有父类
		
		// Class 类中的 getInterfaces() 方法 用于 获得 某个接口继承的所有父接口对应的数组 或 获得某个类直接实现的所有接口组成的数组
		Class<?>[] interfaces = rc.getInterfaces();
		System.out.println( interfaces ); // 输出 [Ljava.lang.Class;@123a439b
        // 每个接口都是一个类型,这些类型组成数组,一个Class类型的数组。(凡是说类型,都用Class)
		System.out.println( interfaces.length ); // 输出 0 ,说明没有实现任何接口,也没有继承任何接口

	}

}

2、接口可以继承接口,类可以实现接口

Chargeable.java

package org.malajava;

/**
 * 可充电的
 */
public interface Chargeable {
	
	void charge() ; // 充电

}

Transfer.java

package org.malajava;

/**
 * 可以传输数据的
 */
public abstract interface Transfer { // 所有的接口都是抽象的 ( 接口默认有abstract )
	
	void transmission() ; // 接口中所有的"没有方法体的"方法都是 public abstract 修饰的

}

USB.java

package org.malajava;

/**
 * 1、在 Java 语言中一个接口 可以同时继承多个 父接口 
 * 2、当一个接口继承多个父接口时,父接口名称 之间使用 逗号 隔开
 */
public interface USB extends Chargeable , Transfer { // USB : Universal Serial Bus ( 通用串行总线 )
	// USB 接口继承了 Chargeable , Transfer 接口中的抽象方法
}

UGreen.java

package org.malajava;

public class UGreen implements USB { //具体类实现接口

	@Override
	public void charge() { //实现抽象方法,注意此处一定要显式书写public
		System.out.println( "UGreen : charge" );
	}

	@Override
	public void transmission() {
		System.out.println( "UGreen : transmission" );
	}

}

HuaWei.java

package org.malajava;

public class HuaWei implements USB {

	@Override
	public void charge() {
		System.out.println( "HuaWei : charge" );
	}

	@Override
	public void transmission() {
		System.out.println( "HuaWei : transmission" );
	}

}

Main.java

package org.malajava;

public class Main {

	public static void main(String[] args) {
		
		// USB u = new USB(); // Cannot instantiate the type USB
		
		USB usb = null ; // USB 是 usb 的 编译时类型
		
		// 接口类型的引用变量 指向了 其实现类类型的实例
		usb = new UGreen() ;
		usb.charge(); // 充电
		usb.transmission(); // 传输数据
		
		usb = new HuaWei();
		usb.charge(); // 充电
		usb.transmission(); // 传输数据
		
	}

}

输出:

UGreen:charge
UGreen:transmission
HuaWei:charge
HuaWei:transmission

TransferTest.java

Class类中的getSuperclass()方法
package org.malajava;

public class TransferTest {

	public static void main(String[] args) {
		
		Transfer t = null ; // 变量 t 的编译时类型是 Transfer 类型
		
		t = new UGreen();
		
		// 通过 变量 t 只能调用直接在 接口Transfer 中声明的方法 和 Object 类中声明的方法
		t.transmission();
		
		String s = t.toString() ; // 调用 Object 类中声明的方法
		System.out.println( s );
		
		Class<?> c = t.getClass(); // 获得 运行时类型
		System.out.println( c.getName() );
        
        Class<?> p = c.getSuperclass();// 获得c所表示的类的父类
        System.out.println(p.getName());
		
	}

}

输出:

UGreen:tranmission
org.malajava.UGreen@6a6824be
org.malajava.UGreen
java.lang.Object

说明

  • 所有的接口类型的引用变量除了可以调用接口中的方法外,还可以调用Object类中的方法;

super_class : java.lang.object ;this_class : 实现类(如:TransferTest)

  • 接口的实现类的父类是Object,任意一个接口类型的引用变量指向了一个具体的类,或通过接口的实现类造了一个对象,则此对象一定继承了Object类;

  • 我们说接口有父类Object,其实是指接口的实现类有父类Object。

3、实现Cloneable接口完成克隆操作

  • 实现Cloneable接口:public interface Cloneable{}

    • 是一个标记接口,用于标记某一个对象能否被克隆;此接口里没有方法。

    • 若类实现了这个接口,则说明可以克隆,若没有实现这个接口,则不可克隆。

      • 注意:接口是反映某种事物可以干什么,更多时候不是提供一种方法,而是一种标记,什么可以干什么
      • 尽可能在一个接口里提供少的方法
  • 重写克隆方法:protected native Object clone() throws CloneNotSupportedException;

    • clone()是本地方法,调用JVM内部的C++代码实现
    • protected的访问权限与当时声明的方法的类所在的包有关,这个类的子类也可用。(本包及子类)
      • 子类中直接访问 或者 子类中通过子类对象访问 父类的protected方法可以,但 子类中调用父类的对象 或 子类中通过其他子类的对象 或 另一个包中同包下其他类通过子类的对象 访问父类的protected方法是不行的。
      • 子类可以访问和可以在子类中访问是不同概念
    • 如果clone()是Object类中声明的方法,在java.lang包下可用,如果想在其他包中使用,需要重写clone方法

Master.java

package cn.oracle;
//类一定要实现Cloneable接口,才可被克隆
public class Master extends Object implements Cloneable {
	
	protected String name ;

	public Master(String name) {
		super();
		this.name = name;
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
	// 一定要重写 从 Object 继承的 clone 方法,才能保证本包中的其它类能够调用本类实例的 clone 方法
		// protected 修饰的方法 的访问权限 与 声明该方法的类所在的包 有关
		return super.clone(); // 子类可以调用父类中的 protected 方法
	}
	
}

Monkey.java

package cn.oracle;

/**
 * 1、如果期望克隆某个类的对象,则该类必须实现 java.lang.Cloneable 接口
 *      否则会抛出 java.lang.CloneNotSupportedException
 *      
 * 2、java.lang.Cloneable 接口 中没有定义任何方法,可以理解为某个类实现该接口就是做了一个标记
 * 
 * 3、浅克隆 : 仅仅克隆当前对象本身,而当前对象关联的其它对象是不克隆的
 */
public class Monkey extends Object implements Cloneable {
	
	private String name ;
	private int age ;
	
	private Master master ;

	public Monkey(String name , int age , Master master ) {
		super();
		this.name = name;
		this.age = age ;
		this.master = master ;
	}
	
	// Object 类中的 clone 方法: protected native Object clone() throws CloneNotSupportedException ;
	@Override
	public Object clone() throws CloneNotSupportedException { 
		// 子类重写后的方法 的 访问修饰符 的范围 不能比父类相应方法的 权限小
        // 子类重写后的方法 的 异常抛出 的范围 不能比父类相应方法的 范围大
		return super.clone() ; // 仍然是调用由 Object 提供的 clone 支持 ( 一定要调用 )
	}

	public static void main(String[] args) throws CloneNotSupportedException {
		
		Master a = new Master( "唐三藏" );
		
		Monkey m = new Monkey( "孙悟空"  , 1000 , a );
		
		
		Object o = m.clone();
		
		System.out.println( m == o ); // false,说明克隆后重新new了一个对象,字段值一起复制
		
		Monkey n = (Monkey) o ;
		System.out.println( m == n ); // false
		
		System.out.println( "~ ~ ~ ~ ~ ~ ~" );
		
		// == 比较两个变量所存储的值
		System.out.println( m.name == n.name ); // true
		System.out.println( m.age == n.age ); // true : 比较基本数据类型的值
		System.out.println( m.master == n.master ); // true : 说明内部关联的对象没有被克隆
		
	}

}

Pig.java

package cn.oracle;
/**
 * 深克隆 : 不仅克隆当前对象本身,连当前对象关联的其它对象也克隆
 */
public class Pig extends Object implements Cloneable {
	
	private String name ; // 引用类型
	
	private int age ; // 基本类型
	
	private Master master ; // 引用类型

	public Pig(String name , int age , Master master ) {
		super();
		this.name = name;
		this.age = age ;
		this.master = master ;
	}
	
	@Override
	public Object clone() throws CloneNotSupportedException { 
		
		Object o = super.clone() ; // 仍然是调用由 Object 提供的 clone 支持 ( 一定要调用 )
		
		if( o != null ) {
			// 将 变量 o 中存储的 地址 赋值给 变量 p 并转换类型
			Pig p = (Pig) o ;
			
			// name 是 java.lang.String 类型,而 String 从 Object 继承的 clone 方法 修饰符是 protected
			// String 类没有子类,因此 不可能通过 String 子类来调用其 clone 方法 ( Pig 也不是 String 的子类 )
			// Pig 与 String 类不在同一个包中,因此不能够调用 Stirng 类的 clone 方法
			// p.name = (String)this.name.clone();
			
			p.master = (Master)this.master.clone(); // 把 master 也克隆
		}
		
		return o ; // 要理解 变量 o 和 变量 p 指向的 是同一个对象
	}

	public static void main(String[] args) throws CloneNotSupportedException {
		
		Master a = new Master( "唐三藏" );
		
		Pig m = new Pig( "猪悟能"  , 1200 , a );
		
		
		Object o = m.clone();
		
		System.out.println( m == o ); // false
		
		Pig n = (Pig) o ;
		System.out.println( m == n ); // false
		
		System.out.println( "~ ~ ~ ~ ~ ~ ~" );
		
		// == 比较两个变量所存储的值
		System.out.println( m.name == n.name ); // true
		System.out.println( m.age == n.age ); // true : 比较基本数据类型的值
		System.out.println( m.master == n.master ); // false : 说明内部关联的对象被克隆
		
	}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值