引例
回想Animal继承体系的案例,现在我们把Animal设计成抽象类
产品经理给了新需求
现在有一部分猫和狗,经过了特殊训练
那么该怎么去描述这群特殊的猫狗呢?
显然,如果直接将这些特殊的行为加入到猫、狗的类定义中是不合适的
因为,不是所有的猫、狗都会这些特殊技能
可以考虑重新定义特殊的猫狗类(SpecialCat)然后将这些特殊的行为加入到新定义的类中
这么做不是不行,但是没有对代码进行复用,如果又有一批特殊训练的猴子、狮子等动物呢?
可能你会想到定义一个装着特殊行为的类,然后让SpecialCat继承它
但是很可惜Java不支持多继承,我们以往的知识已经不能处理这种情况了
总结:
于是:
public class Demo {
public static void main(String[] args) {
//测试一下接口的使用
//有接口后,接口也可以作为"父类",用来实现多态
IA ia = new SuperDog();
//ia.test()
ISkills iss = new SuperDog();
iss.rideBike();
iss.walkUpright();
}
}
abstract class Animal {
public abstract void shout();
}
class Cat extends Animal {
@Override
public void shout() {
System.out.println("猫叫");
}
}
class Dog extends Animal {
@Override
public void shout() {
System.out.println("狗叫");
}
}
class SuperCat extends Cat implements ISkills {
//增加额外功能,扩展父类
public void walkUpright() {
System.out.println("直立行走");
}
public void rideBike() {
System.out.println("骑自行车");
}
}
class SuperDog extends Dog implements ISkills, IA{
//增加额外功能,扩展父类
public void walkUpright() {
System.out.println("直立行走");
}
public void rideBike() {
System.out.println("骑自行车");
}
public void test(){
}
}
abstract class AbstractSkills {
public abstract void walkUpright();
public abstract void rideBike();
}
interface ISkills{
public abstract void walkUpright();
public abstract void rideBike();
}
interface IA{
}
接口的定义
语法
[访问权限修饰符] interface 接口名{}
接口不是类,而是一种独立的数据类型,和class并列
一个类继承接口,称之为实现接口,使用关键字implements
语法
class 类名 implements 接口名 {}
当一个类继承另一个类的同时,又实现接口
接口概述
接口的声明特征
接口的成员特征:
- 成员变量
- 成员方法
- 构造方法
接口中的所有成员变量都默认是由public static final修饰的
接口中的所有方法都默认是由public abstract修饰的,所以你可以省略修饰,比如直接写void test();
(了解即可)在JDK8中引入了默认方法
语法
default 返回值类型 方法名{
}
默认是public修饰
不能用static、abstract修饰
和抽象类中的具体方法一样,接口中的方法是给子类提供了一个默认实现
抽象类中有普通方法是很正常的,但是接口中不应该有具体方法,除非
(了解即可)在JDK8中引入了静态方法
语法
static 返回值类型 方法名{
}
默认是public修饰
不能用abstract、default修饰
可以使用接口名点方法名访问
从技术角度来说,以上实现方法是完全合法的
关于构造方法
接口的子类特征:
这个类必须实现所有的接口当中的抽象方法,不然会报错
这个抽象子类可以不实现接口当中的抽象方法,也可以选择实现某几个 ,类去实现接口,用的是implements关键字,不能用继承extends
现在做一个接口和类之间的继承与实现的关系总结:
1,同种族之间只能extends
类之间单继承
接口之间多继承
1,类可以实现接口用implements
3,接口不能对类做继承或者实现操作
接口使用注意事项:
定义Java类的语法格式:先写extends,后写implements
class SubClass extends SuperClass implements InterfaceA{
}
接口和抽象类的异同
编号 | 区别点 | 抽象类 | 接口 |
---|---|---|---|
1 | 定义 | 包含抽象方法的类 | 抽象方法和全局常量的集合 |
2 | 组成 | 构造方法、抽象方法、普通方法、常量、变量 | 常量、抽象方法、(jdk8:默认方法、静态方法) |
3 | 使用 | 子类继承抽象类(extends) | 子类实现接口(implements) |
4 | 关系 | 抽象类可以实现多个接口 | 接口不能继承抽象类,但允许继承多个接口 |
5 | 对象 | 不能创建对象,但是有构造方法 | 不能创建对象,也没有构造方法 |
6 | 局限 | 抽象类不能被多继承 | 接口之间能多继承,能被多实现 |
7 | 思想 | 作为模板或对共性抽象,is-a | 作为标准或对共性能力抽象,like-a |
8 | 选择 | 如果抽象类和接口都可以使用的话,优先使用接口,因为避免单继承的局限 |
Java提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:
接口就是这样的一种规范,它抽取了事物的相似行为,而不关心事物有什么关系
相比于继承抽象类的“is-a”关系,体现的是“是XX”思想,而实现接口体现的是“能够XX”的思想
接口允许多实现。实现多个接口,继承它们所有的属性和方法
接口的本质是契约、规范、标准
小试牛刀
请用所学知识分析:
这个案例中有哪些抽象类,哪些接口,哪些具体类。
教练(Coach)和运动员(Sportsman)案例
乒乓球(TableTennis)运动员和篮球(basketball)运动员
乒乓球教练和篮球教练
为了出国交流,跟乒乓球相关的人员都需要学习英语
做一个高中学生管理系统,高一,高二,高三
public class Demo {
public static void main(String[] args) {
//简单测试一下
AbstractSportsman as = new TableTennisSportsman();
as.play();
//((TableTennisSportsman) as).sing();
ILearning il = new TableTennisSportsman();
il.LearningEnglish();
//((TableTennisSportsman) il).play();
}
}
interface ILearning{
//接口定义都很简单,直接写抽象方法
void LearningEnglish();
}
//定义抽象类
abstract class AbstractCoach{
String name;
int age;
double salary;
public abstract void teach();
}
abstract class AbstractSportsman{
String name;
int age;
double prizeMoney;
public abstract void play();
}
//定义具体类
class BasketballCoach extends AbstractCoach{
@Override
public void teach() {
System.out.println("我是篮球教练,我就教篮球");
}
}
class TableTennisSportsman extends AbstractSportsman implements ILearning{
@Override
public void LearningEnglish() {
System.out.println("我出国了,需要学习嘤语");
}
@Override
public void play() {
System.out.println("我打乒乓球天下无敌");
}
//乒乓球运动员额外的行为
public void sing(){
System.out.println("rap");
}
}
任何时候,先理解记忆,等到后面忘记了,很正常
等到碰到了 再翻笔记,查百度 查博客
没碰到 当不知道
学习不要犹豫 一往无前,学就完事了
接口和抽象类形式参数和返回值问题
方法的形式参数(formal)在传值的时候
1.基本类型: 对于基本数据类型的方法传参,存在自动类型提升
2.引用类型: 对于引用数据类型的方法传参,存在自动向上转型
public class Demo {
public static void main(String[] args) {
test(0.1F);
test(new A());
test(new ASon());
test(new BSon());
test(new IAImpl());
}
public static void test(double a) {
System.out.println(a);
}
public static void test(A a) {
}
public static void test(B b) {
}
public static void test(IA ia) {
}
}
class A {
}
class ASon extends A {
}
abstract class B {
}
class BSon extends B {
}
interface IA {
}
class IAImpl implements IA {
}
父子类方法重写中,方法声明中返回值类型的书写
1.基本类型: 必须保持一模一样,不存在类型提升
2.引用类型: 不必保持一模一样,存在自动向上转型,
在方法中书写一个具体的返回值
1.基本类型: 方法体中,返回一个具体的值的时候,存在自动类型提升
2.引用类型: 方法体中,返回一个具体对象的时候,存在自动向上转型
C.链式调用(chain calls)
StudentDemo sd = new StudentDemo();
Student s = sd.getStudent();
s.show();
//类似这种形式的代码,可以写成链式调用的形式,就变成了
new StudentDemo().getStudent().show();
//结果是一样的
class Student{
public Student getStudent(){
return new Student();
}
public Teacher getTeacher(){
return new Teacher();
}
}
class Teacher{
public void show(){
System.out.println("秀一波~~~");
}
}
引入
我们现在做一个应用程序,需要描述一台电脑(Computer)中的CPU,对于电脑而言
该怎么去描述这个CPU呢?
这个CPU具有以下特点:
内部类的定义
内部类的概述
按照内部类在类中定义的位置不同:
为了上课方便,我们需要统一口径,在内部类课程当中,我们统一规定
成员内部类概述
成员内部类是最普通的内部类,它定义在另一个类的成员位置, 可以看成该类的一个成员
语法
[访问权限修饰符] class EnclosedClazz{ //外围(普通)类的访问权限修饰符,只有两个,public和缺省
[访问权限修饰符] class InnerClazz{//内部类访问权限修饰符,有四个,和普通成员一样
}
}
成员内部类自身的特点
1,访问权限修饰符
2,成员特点
3,继承和实现
成员内部类的访问权限修饰符可以是
成员内部类的成员特点
成员变量
成员方法
构造器
成员内部类类加载机制(重要)
类加载,什么时候成员内部类会类加载?
类加载: 类加载是懒加载,只有到迫不得已了才会加载一个类,类加载的时机:
1,运行main方法
2,new对象
3,访问静态成员
4,触发子类类加载会先触发父类类加载
成员内部类的直接类加载时机只有一种:
就是创建成员内部类对象
(了解)成员内部类的继承和实现
成员内部类的访问特点
- 成员内部类内部访问外围类
- 外围类访问成员内部类成员
- 外部类访问成员内部类成员
- 成员内部类访问外部类成员(了解)
需要理解的是,成员内部类相当于外围类的成员
并且内部类对象依赖外围类,在成员内部类的成员方法中一定存在一个外围类对象(重要)
理解这一点,就好理解下面的现象了
成员内部类可以无条件访问外围类的所有成员属性和成员方法(包括private成员和静态成员)
需要注意的是,当外围类中有同名的属性或者方法时,都会发生类似“隐藏”的现象
如果要访问外围类的同名成员,需要以下面的形式进行访问:
EnclosedClazz.this.成员变量
EnclosedClazz.this.成员方法
这里有一个类似this、super一样的隐式引用,默认传给成员内部类的所有成员方法
虽然成员内部类可以无条件地访问外围类的成员,而外围类想访问成员内部类的成员却不是这么随心所欲了
原因在于内部类访问外围类时,内部类对象和外围类对象一定都已经存在;
但外围类访问内部类时,内部类对象还不存在,所以必须手动创建内部类对象
当然如果想要访问内部类中的全局常量,直接内部类名点常量名即可
在外围类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象
再通过这个内部类的引用去访问内部类的成员
在外围类中创建内部类语法:最容易理解,最万能的形式如下:
EnclosedClazz oz = new EnclosedClazz();
InnerClazz ic = oz.new InnerClazz();//该形式适合在外围类中创建内部类
//或者
InnerClazz ic2 = new EnclosedClazz().new InnerClazz();//该形式适合在外围类中创建内部类
如果访问内部类的方法是静态的,则只能使用上述格式,因为静态方法中外围类对象可能不存在
如果访问内部类的方法是普通成员方法,则可以省略创建外围类对象的过程,其语法如下
InnerClazz ic = new InnerClazz();
在外围类中一旦创建内部类对象,使用该对象可以访问内部类的所有成员,包括私有
外部类要访问内部类成员,条件要苛刻的多
由于内部类属于外围类的一个成员,所以内部类受访问权限的限制
如果该内部类是private修饰,那么显然在任何外部类中都无法访问该内部类成员
如果外部类拥有内部类的访问权限,可以创建该内部类对象,来访问该内部类的成员
语法
//该方式最全面,适合任何位置
EnclosedClazz.InnerClazz ic3 = new EnclosedClazz().new InnerClazz();
和外围类创建内部类对象不同,在外部类中创建内部类对象,不能够访问内部类的私有成员
在成员内部类中访问外部类成员,和在普通类中访问其它类成员别无二致
静态成员直接类名点访问
普通成员需创建外部类对象
受访问权限控制
牛刀小试
试着说一说下述访问,在成员内部类的情况下,能否进行,怎么进行
能访问,可以直接访问,外围类的对象已经存在了,也不受访问权限限制
this表示内部类自身对象
外围类类名.this表示外围类对象
能访问,需要创建对象,受访问权限限制
直接创建内部类对象即可,不受访问权限限制
this表示外围类对象
内部类对象是我们创建出来
不能直接创建内部类对象,因为没有外围类对象支撑,需要先创建外围类对象,不受访问权限限制
先创建外围类对象,然后在此基础上创建内部类对象,注意这整个过程都受访问权限限制
对象创建完之后,访问受权限限制
和普通方法是一样的
思考:怎么把一个普通成员变成一个静态成员?
静态内部类也是处在外围类成员位置的内部类,不同的是它需要使用static修饰
语法:
[访问权限修饰符] class EnclosedClazz{ //外围(普通)类的访问权限修饰符,只有两个,public和缺省
[访问权限修饰符] static class InnerClazz{//内部类访问权限修饰符,有四个,和普通成员一样
}
}
静态内部类自身的特点
1,访问权限修饰符
2,成员特点
3,继承和实现
静态内部类和成员内部类的最大区别就是static关键字
Oracle公司官网有一段文字解释静态内部类和成员内部类的区别
Nested classes that are declared static are called static nested classes. Non-static nested classes are called inner classes.
嵌套的static类就是静态内部类 然后不用static修饰的嵌套类 就是成员内部类
什么意思呢?解释一下
静态内部类的类加载机制(重要)
静态内部类的访问权限修饰符可以是
静态内部类的成员特点
成员变量
成员方法
构造器
静态内部类的访问特点
- 静态内部类内部访问外围类
- 外围类访问静态内部类成员
- 外部类访问静态内部类成员(了解)
- 静态内部类访问外部类成员(了解)
静态内部类创建对象的时候,完全可能没有外围类对象
理解这一点和成员内部类的不同,就好理解下述现象了
静态内部类是相对外围类独立的类,在外围类中访问静态内部类成员
除了不受访问权限限制外,和访问其他一般类并无差别
如果访问静态内部类中的静态成员,可以直接内部类名点,不受访问权限限制
如果访问静态内部类中的普通成员,需要创建内部类对象,不受访问权限限制
在外围类的任何地方创建静态内部类对象,都可以用以下语法
最大的区别是不需要创建外围类对象,因为是相互独立的
InnerClazz ic = new InnerClazz();
静态内部类在外部创建对象,可以完全独立于外围类对象,不会触发外围类的类加载
创建静态内部类对象
语法
EnclosedClazz.InnerStaticClazz ecisc = new EnclosedClazz.InnerStaticClazz();
和普通类对象访问成员一样,受访问权限限制
访问静态成员,无需创建对象,直接
EnclosedClazz.InnerStaticClazz.静态成员名
受访问权限限制
在静态内部类中,访问外部类成员,和在普通类中访问其他类成员别无二致
牛刀小试
试着说一说下述访问,在静态内部类的情况下,能否进行,怎么进行
创建外围类对象,然后随便访问
直接类名点访问,不受访问权限限制
创建对象然后随便访问
创建对象然后随便访问
public class Demo {
public static void main(String[] args) {
//创建静态内部类对象
EnclosedClazz.StaticInnerClazz ecsic = new EnclosedClazz.StaticInnerClazz();
//System.out.println(ecsic.b);
}
}
class EnclosedClazz {
//定义外围类成员
int a;
private int b = 100;
static int c = 200;
static final int D = 300;
public void testEnclosed() {
StaticInnerClazz sic = new StaticInnerClazz();
}
public static void testStatic() {
StaticInnerClazz sic = new StaticInnerClazz();
}
//定义静态内部类
static class StaticInnerClazz {
//成员变量
int a;
private int b = 10;
static int c = 20;
static final int D = 30;
public void testStaticM() {
//反正没有外围类对象,需要创建对象
//直接创建外围类对象即可
EnclosedClazz ec = new EnclosedClazz();
System.out.println(ec.a);
System.out.println(ec.b);
System.out.println(a);
System.out.println(this.b);
}
public static void testStaticS() {
//直接创建外围类对象
EnclosedClazz ec = new EnclosedClazz();
}
}
}
局部内部类是定义在一个方法或者一个作用域里面的类
它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内
将局部内部类看成是局部变量即可
局部内部类自身的特点
1,访问权限修饰符
2,成员特点
3,定义位置
4,继承和实现
局部内部类和局部变量一样,没有访问修饰权限,因为毫无意义,大括号已经限制了它的访问范围
局部内部类不能用static关键字修饰,原因和局部变量一样
几乎所有的局部位置都可以使用局部内部类
包括但不限于
方法
代码块
if分支
for循环内部
局部内部类的作用:当我们在局部位置,碰到了一个麻烦的问题,需要使用类来解决
局部内部类在使用前要有合适的理由,不然会自找麻烦
前提:
局部内部类相当于方法的局部变量,只在方法内部生效
要想触发局部内部类的类加载,必须在该方法内部创建该内部类对象才可以
若该方法是普通成员方法
若该方法时静态成员方法
要想使用局部内部类的功能,必须在该方法内部创建内部类对象,然后调用方法
值得注意的是:
局部内部类最大的优势是对方法外部完全隐藏,除了方法本身,即便是当前类也不知道它,不能访问它
匿名内部类和lambda本质依然是局部内部类,这一条注意事项仍然生效
写方法返回接口或者抽象类的具体子类
class Enclosed3Clazz{
public A getA(){
class AImpl implements A{
@Override
public void test() {
}
}
return new AImpl();
}
}
interface A{
void test();
}
鸡刀小试
补全程序,使得可以输出三个num
class Outer {
public int num = 10;
class Inner {
public int num = 20;
public void show() {
int num = 30;
System.out.println();
System.out.println();
System.out.println();
}
}
}
写在后面的话
Java 1.1版本就引入了内部类(原属于C++)的概念,虽然很多人觉得内部类的引入让Java程序的扩展性提升了
但也有很多人觉得内部类的引入违背了Java简化C++的初衷,因为内部类的语法过于复杂
以至于让人提不起兴趣去使用,那么这样看似精致巧秒、实则复杂没必要的设计,到底有什么意义呢?
这个问题大家可以自己去思考,接下来我们讲几个内部类的使用场景以及内部类的缺点
内部类的缺点