Java学习之路(九)抽象类、接口与内部类

说到接口,有的人就会想起抽象类,这两者之间有什么异同点呢?让我们慢慢道来。

一、抽象类

什么是抽象类呢?其实从它的名字上我们就可以看出来了,抽象类就是不允许实例化的类。

抽象类一般运用于某些父类只是知道其子类应该包含怎么的方法,但是无法准确知道这些子类如何实现这些方法的场景下。

比如说“动物”这个父类,我们知道动物会叫,但是不同的动物的叫声是不一样的,所以,“动物”的“叫”方法是抽象的,这时定义了“叫”方法的“动物”就是抽象类。

包含抽象方法的类一定是抽象类,但抽象类中不一定有抽象方法。

实现抽象类与方法是通过abstract关键字进行修饰。

public abstract class Test {
	// 抽象类中不一定有抽象方法
	public void myTest() {
		System.out.println("这是非抽象方法");
	}
}
public abstract class Test2 {
    // 包含抽象方法的类一定是抽象类
	public abstract void myTest();
    
}
public class TestMain {
	
	public void myTest() {
        // 抽象类不允许直接实例化
		Test test = new Test() ;	// 编译报错
		Test2 test2 = new Test2();	// 编译报错
	}
	
}

抽象类经常和继承一起使用,因此我们可以通过向上转型的方式或者匿名内部类的式来实现实例化操作(如果真的有必要的话)。

public class TestMain {
	
	public static void main(String[] args) {
		// 1.向上转型
		Test test = new TestSon();
		test.myTest();	// output: 这是非抽象方法
		
		// 2.匿名内部类
		Test2 test2 = new Test2() {
			
			@Override
			public void myTest() {
				System.out.println("匿名内部类的方式实现");
				
			}
		};
		
		test2.myTest();	// output: 匿名内部类的方式实现
	}
	
}

定义抽象类与抽象方法需注意:

  1. 抽象方法不需具体实现,在方法后加“;”。
  2. 子类必须对父类的抽象方法进行重写,如果不想重写,那么子类也将是抽象类。

二、接口

接口,这个名字看出起挺抽象的东西,其实本质上里面就是定义了一堆抽象方法的集合。我们只需要知道,接口中所定义的是“行为”就行。

接口和抽象类一样不能直接实例化,但是却可以通过类实现接口中的抽象方法,当然,和匿名内部类类似的方式也是可以的。

接口定义关键字:interface,实现接口关键字:implements

// 定义接口,接口命名通常在最前方加大写I
public interface ITest {
	// 常量
	final boolean FLAG = true;
	
	// 抽象方法,必须被实现类实现,实例化调用
	public void show();
	
	// 默认方法,可不被实现类实现,实例化调用
	default void showDefault() {
		System.out.println("接口默认方法");
	}
	
	// 静态方法,可不被实现类实现,类名调用
	static void showStatic() {
		System.out.println("接口静态方法");
	}

}
public class TestImplement implements ITest {
	
	@Override
	public void show() {
		// 实现接口方法
		System.out.println("接口抽象方法实现");
		
	}

}
public class TestMain {
	
	public static void main(String[] args) {
		
		TestImplement testImplement = new TestImplement();
		testImplement.show();
		testImplement.showDefault();
		ITest.showStatic();
	}
	
}

/* 运行结果如下:
接口抽象方法实现
接口默认方法
接口静态方法
*/

和抽象类一样,实现接口就必须实现接口中的所有抽象方法。

定义接口时需注意:

  1. 接口中只能定义抽象方法、默认方法、静态方法、常量;
  2. 接口中的默认访问权限修饰符是public,且不能使用其他访问权限修饰符。
  3. 与类继承不同,接口允许多继承;

说到类的继承和接口继承,有个问题就来了,如果说,父类、子类、接口中都定义同名的属性和方法时,那么系统是怎么进行调用的呢?

public class Father{
	static final int X = 1;
	
	public void showDefault() {
		System.out.println("父类方法");
	}
	
}
public interface ITest1 {
	static final int X = 1;
	
	// 默认方法,可不被实现类实现,实例化调用
	default void showDefault() {
		System.out.println("Itest1接口默认方法");
	}
}

public interface ITest2{
	static final int X = 2;
	
	// 默认方法,可不被实现类实现,实例化调用
	default void showDefault() {
		System.out.println("Itest2接口默认方法");
	}
}

public class Son extends Father implements ITest1, ITest2{
	
	public void myTest() {
		System.out.println(x);	// 编译报错
	}
}

public class TestMain {

	public static void main(String[] args) {
		Son son = new Son();
		son.showDefault();	// output: 父类方法
	}

}

综上所述:

  1. 当父类、接口中出现同名属性时,必须在子类中重新定义该属性。
  2. 当父类、接口中出现同名方法时,默认调用父类方法。
  3. 当多接口出现同名方法时,必须在子类或者父类中重定义该方法。

三、抽象类与接口的异同点

说了抽象类、说了接口,那么让我们来解析一下它们之间的相同点和不同点。

  • 相同点:
    • 包含抽象特征,无法被直接实例化
    • 都可以通过匿名内部类或类似的方式进行实例化
    • 在定义时都允许定义抽象方法、默认方法、静态方法
  • 不同点:
    • 抽象类是类,有构造器,单继承,接口是一种集合,不能有构造器,可以多继承
    • 抽象类允许使用public、protected和default这些修饰符,但接口方法默认修饰符是public,且不能使用上述其它修饰符

四、内部类

既然我们在上面的文章中提到内部类,那么顺便,我们来说一下什么是内部类?有哪些内部类。

什么是内部类?首先,有内必有外,内外是对应的,所以有了内部类的概念,那必然有外部类的概念,同时,内与外也是统一的,因此,我们可以说,隐藏在外部类中的类,就是内部类。

那么,有哪些内部类呢?有四种:

  • 成员内部类
  • 静态内部类
  • 方法内部类
  • 匿名内部类

(一)成员内部类

成员内部类是内部类中最常见的,因此也被称为普通内部类。

// 外部类
public class Person{
    
    // 内部类
    class Heart{
        
    }
}

那么成员内部类是如何进行调用的呢?

public class Person {
	
	public Heart getHeart() {
		return new Heart();
	}
	
	// 成员内部类
    class Heart{
        public String beat() {
        	return "心脏在跳动";
        }
    }
}


public class TestMain {

	public static void main(String[] args) {
		// 方法1:new 外部类.new 内部类
		Person.Heart pHeart = new Person().new Heart();
		
		// 方法2:外部类对象.new 内部类
		Person person = new Person();
		pHeart = person.new Heart();
		
		// 方法3:外部类对象.获取方法
		pHeart = person.getHeart();
	}

}

注意:

  1. 内部类在外部使用时,无法直接实例化,需要借由外部类信息才能完成实例化;
  2. 内部类的访问修饰符,可以任意,但访问范围会受到影响;
  3. 内部类可以直接访问外部类的成员;如果出现同名属性,优先访问内部类中定义的;
  4. 可以使用“外部类.this.成员”的方式,访问外部类中同名的信息;
  5. 外部类访问内部类信息,需要通过内部类实例,无法直接访问;
  6. 内部类编译后.class文件命名:外部类$内部类.class

(二)静态内部类

// 外部类
public class Person{
    
    // 静态内部类
    public static class Heart {
		
	}
}

调用方式:

public class Person {
	
	public Heart getHeart() {
		return new Heart();
	}
	
	
	public static class Heart {
		public String beat() {
			return "心脏在跳动";
		}
	}

}

public class TestMain {

	public static void main(String[] args) {
		// new 外部类.内部类
		Person.Heart pHeart = new Person.Heart();
		
		// 外部类对象.获取方法
		Person person = new Person();
		pHeart = person.getHeart();
	}

}

注意:

  1. 静态内部类中,只能直接访问外部类的静态成员,如果需要调用非静态成员,可以通过对象实例
  2. 静态内部类对象实例时,可以不依赖于外部类对象
  3. 可以通过外部类.内部类.静态成员的方式,访问内部类中的静态成员
  4. 当内部类属性与外部类属性同名时,默认直接调用内部类中的成员;
  5. 如果需要访问外部类中的静态属性,则可以通过 外部类.属性 的方式;
  6. 如果需要访问外部类中的非静态属性,则可以通过 new 外部类().属性的方式。

(三)方法内部类

// 外部类
public class Person {
	
    // 方法内部类
	public Object getHeart() {
		
		class Heart {
			
			public String beat() {
				return "心脏在跳动";
			}
		}
		
		return new Heart().beat();
	}
}

调用方式:

public class TestMain {

	public static void main(String[] args) {
		
		// 外部类对象.获取方法
		Person person = new Person();
		System.out.println(person.getHeart());
	}

}

注意:

  1. 定义在方法内部,作用范围也在方法内
  2. 和方法内部成员使用规则一样,class前面不可以添加public、private、protected、static
  3. 类中不能包含静态成员
  4. 类中可以包含final、abstract修饰的成员

(四)匿名内部类

案例代码及使用参考上述案例

注意:

  1. 匿名内部类没有类型名称、实例对象名称
  2. 编译后的文件命名:外部类$数字.class
  3. 无法使用private、public、protected、abstract、static修饰
  4. 无法编写构造方法,可以添加构造代码块
  5. 不能出现静态成员

说到接口,有的人就会想起抽象类,这两者之间有什么异同点呢?让我们慢慢道来。

一、抽象类

什么是抽象类呢?其实从它的名字上我们就可以看出来了,抽象类就是不允许实例化的类。

抽象类一般运用于某些父类只是知道其子类应该包含怎么的方法,但是无法准确知道这些子类如何实现这些方法的场景下。

比如说“动物”这个父类,我们知道动物会叫,但是不同的动物的叫声是不一样的,所以,“动物”的“叫”方法是抽象的,这时定义了“叫”方法的“动物”就是抽象类。

包含抽象方法的类一定是抽象类,但抽象类中不一定有抽象方法。

实现抽象类与方法是通过abstract关键字进行修饰。

public abstract class Test {
	// 抽象类中不一定有抽象方法
	public void myTest() {
		System.out.println("这是非抽象方法");
	}
}
public abstract class Test2 {
    // 包含抽象方法的类一定是抽象类
	public abstract void myTest();
    
}
public class TestMain {
	
	public void myTest() {
        // 抽象类不允许直接实例化
		Test test = new Test() ;	// 编译报错
		Test2 test2 = new Test2();	// 编译报错
	}
	
}

抽象类经常和继承一起使用,因此我们可以通过向上转型的方式或者匿名内部类的式来实现实例化操作(如果真的有必要的话)。

public class TestMain {
	
	public static void main(String[] args) {
		// 1.向上转型
		Test test = new TestSon();
		test.myTest();	// output: 这是非抽象方法
		
		// 2.匿名内部类
		Test2 test2 = new Test2() {
			
			@Override
			public void myTest() {
				System.out.println("匿名内部类的方式实现");
				
			}
		};
		
		test2.myTest();	// output: 匿名内部类的方式实现
	}
	
}

定义抽象类与抽象方法需注意:

  1. 抽象方法不需具体实现,在方法后加“;”。
  2. 子类必须对父类的抽象方法进行重写,如果不想重写,那么子类也将是抽象类。

二、接口

接口,这个名字看出起挺抽象的东西,其实本质上里面就是定义了一堆抽象方法的集合。我们只需要知道,接口中所定义的是“行为”就行。

接口和抽象类一样不能直接实例化,但是却可以通过类实现接口中的抽象方法,当然,和匿名内部类类似的方式也是可以的。

接口定义关键字:interface,实现接口关键字:implements

// 定义接口,接口命名通常在最前方加大写I
public interface ITest {
	// 常量
	final boolean FLAG = true;
	
	// 抽象方法,必须被实现类实现,实例化调用
	public void show();
	
	// 默认方法,可不被实现类实现,实例化调用
	default void showDefault() {
		System.out.println("接口默认方法");
	}
	
	// 静态方法,可不被实现类实现,类名调用
	static void showStatic() {
		System.out.println("接口静态方法");
	}

}
public class TestImplement implements ITest {
	
	@Override
	public void show() {
		// 实现接口方法
		System.out.println("接口抽象方法实现");
		
	}

}
public class TestMain {
	
	public static void main(String[] args) {
		
		TestImplement testImplement = new TestImplement();
		testImplement.show();
		testImplement.showDefault();
		ITest.showStatic();
	}
	
}

/* 运行结果如下:
接口抽象方法实现
接口默认方法
接口静态方法
*/

和抽象类一样,实现接口就必须实现接口中的所有抽象方法。

定义接口时需注意:

  1. 接口中只能定义抽象方法、默认方法、静态方法、常量;
  2. 接口中的默认访问权限修饰符是public,且不能使用其他访问权限修饰符。
  3. 与类继承不同,接口允许多继承;

说到类的继承和接口继承,有个问题就来了,如果说,父类、子类、接口中都定义同名的属性和方法时,那么系统是怎么进行调用的呢?

public class Father{
	static final int X = 1;
	
	public void showDefault() {
		System.out.println("父类方法");
	}
	
}
public interface ITest1 {
	static final int X = 1;
	
	// 默认方法,可不被实现类实现,实例化调用
	default void showDefault() {
		System.out.println("Itest1接口默认方法");
	}
}

public interface ITest2{
	static final int X = 2;
	
	// 默认方法,可不被实现类实现,实例化调用
	default void showDefault() {
		System.out.println("Itest2接口默认方法");
	}
}

public class Son extends Father implements ITest1, ITest2{
	
	public void myTest() {
		System.out.println(x);	// 编译报错
	}
}

public class TestMain {

	public static void main(String[] args) {
		Son son = new Son();
		son.showDefault();	// output: 父类方法
	}

}

综上所述:

  1. 当父类、接口中出现同名属性时,必须在子类中重新定义该属性。
  2. 当父类、接口中出现同名方法时,默认调用父类方法。
  3. 当多接口出现同名方法时,必须在子类或者父类中重定义该方法。

三、抽象类与接口的异同点

说了抽象类、说了接口,那么让我们来解析一下它们之间的相同点和不同点。

  • 相同点:
    • 包含抽象特征,无法被直接实例化
    • 都可以通过匿名内部类或类似的方式进行实例化
    • 在定义时都允许定义抽象方法、默认方法、静态方法
  • 不同点:
    • 抽象类是类,有构造器,单继承,接口是一种集合,不能有构造器,可以多继承
    • 抽象类允许使用public、protected和default这些修饰符,但接口方法默认修饰符是public,且不能使用上述其它修饰符

四、内部类

既然我们在上面的文章中提到内部类,那么顺便,我们来说一下什么是内部类?有哪些内部类。

什么是内部类?首先,有内必有外,内外是对应的,所以有了内部类的概念,那必然有外部类的概念,同时,内与外也是统一的,因此,我们可以说,隐藏在外部类中的类,就是内部类。

那么,有哪些内部类呢?有四种:

  • 成员内部类
  • 静态内部类
  • 方法内部类
  • 匿名内部类

(一)成员内部类

成员内部类是内部类中最常见的,因此也被称为普通内部类。

// 外部类
public class Person{
    
    // 内部类
    class Heart{
        
    }
}

那么成员内部类是如何进行调用的呢?

public class Person {
	
	public Heart getHeart() {
		return new Heart();
	}
	
	// 成员内部类
    class Heart{
        public String beat() {
        	return "心脏在跳动";
        }
    }
}


public class TestMain {

	public static void main(String[] args) {
		// 方法1:new 外部类.new 内部类
		Person.Heart pHeart = new Person().new Heart();
		
		// 方法2:外部类对象.new 内部类
		Person person = new Person();
		pHeart = person.new Heart();
		
		// 方法3:外部类对象.获取方法
		pHeart = person.getHeart();
	}

}

注意:

  1. 内部类在外部使用时,无法直接实例化,需要借由外部类信息才能完成实例化;
  2. 内部类的访问修饰符,可以任意,但访问范围会受到影响;
  3. 内部类可以直接访问外部类的成员;如果出现同名属性,优先访问内部类中定义的;
  4. 可以使用“外部类.this.成员”的方式,访问外部类中同名的信息;
  5. 外部类访问内部类信息,需要通过内部类实例,无法直接访问;
  6. 内部类编译后.class文件命名:外部类$内部类.class

(二)静态内部类

// 外部类
public class Person{
    
    // 静态内部类
    public static class Heart {
		
	}
}

调用方式:

public class Person {
	
	public Heart getHeart() {
		return new Heart();
	}
	
	
	public static class Heart {
		public String beat() {
			return "心脏在跳动";
		}
	}

}

public class TestMain {

	public static void main(String[] args) {
		// new 外部类.内部类
		Person.Heart pHeart = new Person.Heart();
		
		// 外部类对象.获取方法
		Person person = new Person();
		pHeart = person.getHeart();
	}

}

注意:

  1. 静态内部类中,只能直接访问外部类的静态成员,如果需要调用非静态成员,可以通过对象实例
  2. 静态内部类对象实例时,可以不依赖于外部类对象
  3. 可以通过外部类.内部类.静态成员的方式,访问内部类中的静态成员
  4. 当内部类属性与外部类属性同名时,默认直接调用内部类中的成员;
  5. 如果需要访问外部类中的静态属性,则可以通过 外部类.属性 的方式;
  6. 如果需要访问外部类中的非静态属性,则可以通过 new 外部类().属性的方式。

(三)方法内部类

// 外部类
public class Person {
	
    // 方法内部类
	public Object getHeart() {
		
		class Heart {
			
			public String beat() {
				return "心脏在跳动";
			}
		}
		
		return new Heart().beat();
	}
}

调用方式:

public class TestMain {

	public static void main(String[] args) {
		
		// 外部类对象.获取方法
		Person person = new Person();
		System.out.println(person.getHeart());
	}

}

注意:

  1. 定义在方法内部,作用范围也在方法内
  2. 和方法内部成员使用规则一样,class前面不可以添加public、private、protected、static
  3. 类中不能包含静态成员
  4. 类中可以包含final、abstract修饰的成员

(四)匿名内部类

案例代码及使用参考上述案例

注意:

  1. 匿名内部类没有类型名称、实例对象名称
  2. 编译后的文件命名:外部类$数字.class
  3. 无法使用private、public、protected、abstract、static修饰
  4. 无法编写构造方法,可以添加构造代码块
  5. 不能出现静态成员
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值