Java基础教程(自学用,持续更新)

文章目录

  • 一句话目标
  • 类与对象
    • 编程语言的几个发展阶段
      • 成员变量的赋值问题
    • 对象的创建与构造方法
      • 对象的内存模型
    • 类与程序的基本结构
    • 参数传值
    • 对象的组合
    • 实例成员与类成员
    • static关键字
      • static关键字的用途
      • static方法
    • 方法重载
    • this关键字
    • import语句
    • 访问权限
    • 对象数组
    • 小结
  • 子类与继承
    • 子类与父类
      • 人类
        • 属性:
        • 行为:
    • 子类的继承性
    • 子类与对象
    • 成员变量的隐藏和方法重写
      • 继承中成员变量的访问特点:
      • 方法重写
      • 总结:
    • 重写与重载
      • 方法的重写规则
      • 重载规则:
    • super关键字(super≈父类)
    • final关键字(不变)
    • Java 转型问题
      • 对象的上转型对象
      • 对象的下转型对象
    • 构造器
    • 继承与多态
    • abstract类和abstract方法
  • 接口与实现
    • 接口
    • 接口的特点
    • 接口定义与实现
      • 接口定义
      • 接口实现
    • abstract类与接口的比较
  • 内部类与异常类
    • 内部类
    • 异常类
    • 断言
    • 小结

一句话目标


对于一些比较复杂或者第一眼看上去不太好理解的概念,我信奉的观念就是,用一句话把它解释清楚,而且是用很通俗的语言,当然了,如果你已经能够很好的理解了,还是建议用不是那么正式但又不是很通俗的语言解释。
所以我接下来我会用一句话这个标签来解释这些难懂的概念。

C/C++与Java编译运行过程对比:
在这里插入图片描述
数据溢出:
byte是一个字节的数据类型,所以它的表示范围是-128~127
当我们此范围之外的数赋值给byte类型变量时,会发生数据溢出,溢出的方式就是在这里插入图片描述
比127大的数要接着顺时针转,假如是128,比127大1,那就是移动一个数变成-128假如是129,比127大2,那就是移动两个数变成-127,这样以此类推;
比-128小的数要接着逆时针,假如是-129,比-128小1,那就移动一个数变成127,假如是-130,比-128小2,那就是移动两个数变成126,其他数据类型也以此类推。

类与对象

编程语言的几个发展阶段


面向机器语言 ——》面向过程语言——》面向对象语言
(二进制、汇编)——》C语言 ——》Java语言

在面向过程编程中我们是以“方法”为主体的
而面向对象编程汇总我们是以“对象”为主体的
在面向对象语言的学习过程中,一个简单的理念就是,需要完成某种任务的时候,我们首先想到是谁去完成(对象);提到某个数据的时候,想到是谁的数据,这样也更符合我们日常生活中的描述。


类?类是干什么的呢?
一句话:类是用来描述和抽象具有相同属性和行为的一类事物的概念,就比如说人类,猫类,狗类,家禽类,这些类中的动物都有相同的属性和行为。
那么如何声明一个类呢?

class People{		//声明了一个人类//成员变量String Name;char sex;int Age;String Id;//成员方法void drink(){}void run(){}void walk(){}void eat(){}	
}

成员变量的赋值问题


Java规定不能在类中对成员变量进行赋值,但是你可以在类中对成员变量赋初始值,要想对成员变量赋值必须要在方法体内部进行
举个列子:

class People{int a = 10;		//声明的同时赋初始值,正确int b;			b = 27;			//非法void a(){b = b-19;	//在类中赋值,正确}
}

对象的创建与构造方法


类也可以看做是一种数据类型,也可以用来声明变量,而用类声明的变量被称为对象。
既然类有了,总得有对象来体现这个类的属性和行为吧,那我们就可以创建对象来体现了
创建对象要用关键字new

People zs;						//声明对象张三
People zs = new People();		//创建对象张三

构造方法的名称必须与它坐在的类名称完全一致,而且没有类型,可以有参数,参数一般就是对对象的成员变量进行赋值。
所以这样看起来构造方法在创建对象以及对对象进行初始化起到了至关重要的作用。
构造方法可以有多个,但是其参数一定不同(参数类型、参数个数)。
构造方法不就是把对象的值赋值给实例变量。

class People{		//创建了一个人类//成员变量//注意:当没有指定成员变量的值时,其都有默认值,String为null,int为0,float为0.0,boolean为flaseString Name;char sex;int Age;String Id;//成员方法void drink(){}void run(){}void walk(){}void eat(){}	People(String N){	//构造函数Name = N;}People(String s){	//构造函数sex = s;}People(){			//构造函数}}

对象的内存模型


声明对象和创建对象是有区别的:

People zs;						//声明对象张三
//声明对象,内存为空People zs = new People();		//创建对象张三,可以看做是new一个构造方法
/*创建对象,分配内存,先为成员变量String Name;boolean sex;int Age;String Id;
分配内存,如果未赋值再赋给默认值,最后new运算符会计算出一个引用值(地址值),即表达式new People()是一个值。

声明对象
创建对象

这个内存模型啊,告诉我们一件事,声明和创建区别很大

类与程序的基本结构


一个应用程序可以有多个源文件,一个源文件可以有多个类,但是一定要有一个主类
在这里插入图片描述

参数传值


说一下引用型参数
当参数是数组、对象、接口的时候,称之为引用型参数,传的是引用(地址)而不是实体
举例:

class Battery{			//电池类int e;		//电量Battery(int a){e = a;}
}class Radio{void openRadio(Battery battery){battery.e = battery.e - 10;		//消耗了电量}
}public class Example{public static void main(String args[]){Battery b = new Battery(100);		//创建一个电池对象,电量初始化为100Radio r = new Radio();			//创建一个收音机对象r.openRadio(b);					//参数为电池对象b,此时就相当于把b的值赋给Battery Battery//而b = new Battery(),所以在方法openRadio中就相当于Battery battery = new Battery()//由上可知道对象b和battery他们两个是一模一样的,他们具有相同的引用,故具有完全相同的变量//并且battery就相当于b的复印件,复印件的改变不会影响原件}}

对象的组合


一句话:将其他类的对象作为自己的成员变量
对象组合的本质就是一个类的数据成员变量存的不是基本的数据类型,而是一个对象的地址
举例:

class Circle{double rad;	//半径double area;	//面积double getArea(){area = 3.14*r*r;return area;}Circle(double r){rad = r;}
}class Circular{		//圆锥类Circle bottom;double h;Circular(double height){h = height;}void setBottom(Circle c){bottom = c;}double	getVolme(){if(bottom == null)return -1;elsereturn bottom.getArea()*h/3.0;}}public class Example0{public static void main(String args[]){Circle c = new Circle(100);Circular cu = new Circular(20);cu.setBottom(c);int V = cu.getVolme();System.out.println("圆锥的体积为:"+V);}}

实例成员与类成员


首先类成员中的类变量被所有对象共享,也就是所有的对象的类变量是相同的一处内存空间,并且通过类名访问类变量和通过对象访问类变量都可以改变类变量的值。
加载类的字节码文件的时候,类变量已经分配了内存,而成员变量没有;当该类创建对象时,才会给实例对象分配内存

实例成员就是正常的变量和方法
类成员就是静态的变量和方法(前面加了static修饰的)
在这里插入图片描述

类变量也叫做static变量、静态变量、全局变量,我叫它共享变量,分配给这些对象的类变量占有相同的一处内存,改变其中一个对象的这个类变量,其他对象的这个类变量也会跟着变;static方法可以重写,重写就是子类可以重写父类已有的方法。

static关键字

在类中,用static声明的成员变量为静态成员变量,也成为类变量。类变量的生命周期和类相同,在整个应用程序执行期间都有效。

这里要强调一下:

  • static修饰的成员变量和方法,从属于类
  • 普通变量和方法从属于对象
  • 静态方法不能调用非静态成员,编译会报错

static关键字的用途

一句话描述就是:方便在没有创建对象的情况下进行调用(方法/变量)。

显然,被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。

static可以用来修饰类的成员方法、类的成员变量,另外也可以编写static代码块来优化程序性能

static方法

static方法也成为静态方法,由于静态方法不依赖于任何对象就可以直接访问,因此对于静态方法来说,是没有this的,因为不依附于任何对象,既然都没有对象,就谈不上this了,并且由于此特性,在静态方法中不能访问类的非静态成员变量和非静态方法,因为非静态成员变量和非静态方法都必须依赖于具体的对象才能被调用。

虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法和静态成员变量。

代码示例
在这里插入图片描述

从上面代码里看出:

  • 静态方法test2()中调用非静态成员变量address,编译失败。这是因为,在编译期并没有对象生成,address变量根本就不存在。
  • 静态方法test2()中调用非静态方法test1(),编译失败。这是因为,编译器无法预知在非静态成员方法test1()中是否访问了非静态成员变量,所以也禁止在静态方法中调用非静态成员方法
  • 非静态成员方法test1()访问静态成员方法test2()/变量name是没有限制的

所以,如果想在不创建对象的情况下调用某个方法,就可以将这个方法设置为static。最常见的静态方法就是main方法,这就是为什么main方法是静态方法就一目了然了,因为程序在执行main方法的时候没有创建任何对象,只有通过类名来访问。

举例:

class A{static int a;		//类变量float b;			//成员变量set_a(int A){a = A;}static void c(){	//类方法System.out.println("Hello World!");}int d(int x,int y){	//成员方法int c;return c = x*y;}}
public class B{public static void main(String args[]){A.a = 10; 		//通过类名给类变量a赋值,10A a1 = new A();System.out.println(A.a);	//输出10a1.set_a(20);	//通过对象调用给类变量赋值,20System.out.println(A.a);	//输出20}}

实例方法和类方法的区别:

  • 有对象才有实例方法的入口地址;将类的字节码文件加载至jvm内存时,不会为实例方法分配内存;
  • 但是会给类方法分配入口地址。多个对象的实例方法的入口是相同的,也就是实例方法的入口地址被共享;
  • 只有在所有的对象被回收时,入口地址才会被取消;
  • 而类方法的入口地址在程序退出的时候才被取消。
  • 可以把类变量放在实例方法中(把有static修饰的变量放在没有static修饰的方法中),也就是实例方法可以操作类变量;
  • 但是类方法不可以操作实例变量;原因是:再类创建对象之前,实例成员变量还没有分配内存空间。
  1. static方法中只能有static变量

方法重载


顾名思义是对方法进行重新加载。
那么对哪些方法会重新加载呢?
同一个方法名,但是它们参数的类型不同,它们参数的个数不同。
这种例子很多啊,比如说

f(int a,int b);
f();
f(double a,double b);
//这三个方法如果放在同一个类中,那么就叫做对f进行方法重载

this关键字


this是当前对象的引用,就是说当前用构造函数建的对象是谁,这个this就代表谁,它是一个引用。
this可以出现在:

  • 实例方法
  • 构造方法
  • 但是不可以出现在类方法中

不就是你的方法的参数名跟你的成员变量名一样的时候,你要是执行语句:

成员变量名=参数名

就必须写成:

this.成员变量名=参数名

注:

  1. 通常情况下:可以省略成员变量名字前的“this.”,以及static变量前的“类名.”;
  2. this不能出现在类方法中, 因为类方法可以通过类名直接调用,这时可能还没有对象诞生。


一句话说明包的目的:区分不同文件中相同类

import语句


一句话:相当于C语言中的#include,就是导入在源程序中要用的各种库或者自己写好的接口。
也就是在同一目录下的Java文件是互通的,要通过import与其他包文件进行通讯。
在java中使用类库就是创建相应的对象(所以说Java是面向对象的语言)。
如果使用import导入了包中的所有类,那么会增加编译时间,但是不会影响程序的性能,因为jvm只加载自己程序要用的(可Java本来就慢啊= =)。

访问权限


在这里插入图片描述
类方法总是可以操作类中的类变量,与访问控制符没有关系。

  1. public
    被public标记的变量和方法在任何地方的对象都可以访问(类内部、本包、子类、外部包)
  2. protected
    被propected标记的变量和方法仅在本包内可以访问。
  3. 友好的
    同上
  4. private
    只有类内部使用

注:

  • 只能用public来修饰类
  • 权限由高到低:public——>protected——>友好的——>private
    protected和友好型的区别:
    当子类和父类不在同一个包中时,父类中的private和友好访问权限的成员变量和方法不会被子类继承;在同一个包中时,子类会将父类的变量和方法除private之外全部继承。
    如果子类和父类不在同一个包中,子类不继承父类的友好成员变量和方法

对象数组


如果要一次定义很多对象,建议使用对象数组而不是定义多个对象。

小结

子类与继承

子类与父类


既然我们的代码要描述我们的现实生活那么应该怎么做呢?我们显示生活中父亲生儿子这种事,那可是几乎都存在的啊,不管是人类,还是其他生物,几乎都存在,那就我们也让我们的代码可以继承,让我们原本定义的类,可以让他派生自己的子类,也就是让子类来继承父类的一些属性以及行为。(儿子只能有一个爹,爹却可以生很多儿子)
这里问一个面试常问的问题:

为什么 Java 语⾔不⽀持多重继承?

  • 为了程序的结构能够更加清晰从⽽便于维护。假设 Java 语⾔⽀持多重继承,类 C 继承⾃类 A 和类 B,如果类 A 和 B 都有⾃定义的成员⽅法 f() ,那么当代码中调⽤类 C 的 f() 会产⽣⼆义性。
  • Java 语⾔通过实现多个接⼝间接⽀持多重继承,接⼝由于只包含⽅法定义,不能有⽅法的实现,类 C 继承接⼝ A 与接⼝ B 时即使它们都有⽅法 f() ,也不能直接调⽤⽅法,需实现具体的 f() ⽅法才能调⽤,不会产⽣⼆义性。
  • 多重继承会使类型转换、构造⽅法的调⽤顺序变得复杂,会影响到性能。

好了,言归正传,继承的关键字是什么呢?

extends

最简单的例子啊,人类是个父类,学生类是个子类,那可以这么说:学生类继承了人类

人类

属性:

  • 姓名
  • 性别
  • 年龄
  • 身份证号

行为:

  • 喝水
  • 跑步
  • 散步
  • 吃饭
class People{String Name;char sex;int Age;String Id;void drink(){}void run(){}void walk(){}void eat(){}
}

那么学生类要继承人类的话,就要把人类所有的属性都原封不动的继承过来,然后如果学生类有需求的话再在学生类中填入自己需要的属性或行为

class Student{String strID;		//新添的属性学号String Subject;		//课程void exercise(){}	//广播体操void finalExam(){}	//期末考试
}

上面这些属性和行为是新添加的,都是学生所特有的

子类的继承性


在同一包中:
在同一个包中子类继承父类时,不会继承private标记的成员变量和方法,但是会继承友好的成员变量和方法。(父亲的隐私可不兴看啊)
不在同一包中:
不在同一个包中的子类继承父类时,private和友好的成员变量和方法都不会继承。

protected进一步说明的举例:
我们有一个类D,还有一个类C

  • D类中自己声明的protected成员变量和方法,只要D跟C在同一包中,则在C类中创建的D对象可以访问这些protected成员变量和方法;
  • D类继承自己父类的那些protected成员变量和方法,需要追溯到这些protected成员变量和方法所在的祖先类跟C类是否在同一包中,若在,则在C类中创建的D对象可以访问这些protected成员变量和方法,反之,则不可以。

子类与对象


创建子类对象时,jvm不仅会为子类的成员变量分配内存还会为父类的成员变量分配内存。
但是我们知道子类继承父类时,并不会全部东西都继承过来,有时候会因为不在同一个包中,protected、友好型和private都不能继承,那么父类这个时候为什么还要为这些不能继承的成员变量分配内存呢?
答案是:子类会用那些从父类继承过来的方法来操作这部分未继承的变量。

成员变量的隐藏和方法重写


在子类继承父类的过程中,因为要继承两部分:变量和方法

  • 自己再写一遍父类有的变量就叫做隐藏
  • 自己再写一遍父类有的方法就叫重写

继承中成员变量的访问特点:

在父子 类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:

  1. 直接通过子类对象访问成员变量:也就是对象.成员变量,规则是:创建对象时,等号左边是谁,就优先使用谁,没有则向上找。
  2. 间接通过成员方法访问成员变量:该方法属于谁,就优先用谁,没有则向上找。
public class Fu {int numFu = 10;int num = 100;public void methodFu() {// 使用的是本类当中的,不会向下找子类的System.out.println(num);}}
public class Zi extends Fu {int numZi = 20;int num = 200;public void methodZi() {// 因为本类当中有num,所以这里用的是本类的numSystem.out.println(num);}}

/*
在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:直接通过子类对象访问成员变量:等号左边是谁,就优先用谁,没有则向上找。
间接通过成员方法访问成员变量:该方法属于谁,就优先用谁,没有则向上找。*/
public class Demo01ExtendsField {public static void main(String[] args) {Fu fu = new Fu(); // 创建父类对象System.out.println(fu.numFu); // 只能使用父类的东西,没有任何子类内容System.out.println("===========");Zi zi = new Zi();System.out.println(zi.numFu); // 10System.out.println(zi.numZi); // 20System.out.println("===========");// 等号左边是谁,就优先用谁System.out.println(zi.num); // 优先子类,200
//        System.out.println(zi.abc); // 到处都没有,编译报错!System.out.println("===========");// 这个方法是子类的,优先用子类的,没有再向上找zi.methodZi(); // 200// 这个方法是在父类当中定义的,zi.methodFu(); // 100}}

方法重写

在父子类的继承关系当中,创建子类对象,访问成员方法的规则:

创建的对象是谁,就优先用谁,如果没有则向上找。

注意事项:
无论是成员方法还是成员变量,如果没有都是向上找父类,绝对不会向下找子类的。

如何理解这个隐藏呢?

就是我子类不是会继承你父类的部分变量吗?
比如父类有一个变量:
public a;
如果我在子类中也声明了public a ;
那么子类从父类继承的a就会被隐藏。
说白了就是你子类要是有的话就用自己的,可以不用父类的了(这个“不用”就可以理解隐藏),要是没有,那就用父类的。
)

总结:

  • 方法:看等号右边(创建的是谁),就优先调用谁
  • 变量:看等号左边是谁,就优先调用谁;看方法属于谁,就优先调用谁

重写与重载


在这里插入图片描述
重写就是我子类重新写父类的一些同名方法,返回值和形参都不能改变。即外壳不变,核心重写

方法的重写规则

  • 参数列表与被重写方法的参数列表必须完全相同。
  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
  • 父类的成员方法只能被它的子类重写。
  • 声明为 final 的方法不能被重写。声明为 static 的方法不能被重写,但是能够被再次声明。
  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
  • 构造方法不能被重写。
  • 如果不能继承一个类,则不能重写该类的方法。

重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。

重载规则:

  • 被重载的方法必须改变参数列表(参数个数或类型不一样);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。

super关键字(super≈父类)


super关键字的用法有三种:
1.在子类的成员方法中,访问父类的成员变量。
2.在子类的成员方法中,访问父类的成员方法。
3.在子类的构造方法中,访问父类的构造方法。

一句话说明作用:
当你在子类的方法中需要访问父类的变量或者方法时,就需要使用super关键字来访问。
很显然super这个关键字也是跟继承、父类和子类有关的概念。

public class Fu {int num = 10;public void method(){System.out.println();}
}

public class Zi extends Fu {int num = 20;public  Zi(){super();}public void  methodZi(){System.out.println(super.num); //父类中的num}public void method(){super.method(); //访问父类中的methodSystem.out.println("子类方法");}
}

注意:super 语句必须是子类构造方法的第一条语句。不能在子类中使用父类构造方法名来调用父类构造方法。 父类的构造方法不被子类继承。调用父类的构造方法的唯一途径是使用 super 关键字,如果子类中没显式调用,则编译器自动将 super(); 作为子类构造方法的第一条语句。静态方法中不能使用 super 关键字。

调用父类的方法语法:

super.方法名(参数列表);

如果是继承的方法,是没有必要使用 super 来调用,直接即可调用。但如果子类覆盖或重写了父类的方法,则只有使用 super 才能在子类中调用父类中的被重写的方法。

final关键字(不变)


  • 修饰类:不可以有子类
  • 修饰方法:可以被继承,但继承后不能被重写。(老老实实继承)
  • 修饰变量:变为常量

final 修饰类中的属性或者变量

无论属性是基本类型还是引用类型,final 所起的作用都是变量里面存放的"值"不能变。

这个值,对于基本类型来说,变量里面放的就是实实在在的值,如 1,“abc” 等。

而引用类型变量里面放的是个地址,所以用 final 修饰引用类型变量指的是它里面的地址不能变,并不是说这个地址所指向的对象或数组的内容不可以变,这个一定要注意。

例如:类中有一个属性是 final Person p=new Person(“name”); 那么你不能对 p 进行重新赋值,但是可以改变 p 里面属性的值 p.setName(‘newName’);

final 修饰属性,声明变量时可以不赋值,而且一旦赋值就不能被修改了。对 final 属性可以在三个地方赋值:声明时、初始化块中、构造方法中,总之一定要赋值。

Java 转型问题


Java 转型问题其实并不复杂,只要记住一句话:父类引用指向子类对象。
什么叫父类引用指向子类对象,且听我慢慢道来。

从 2 个名词开始说起:向上转型(upcasting)向下转型(downcasting)

举个例子:有2个类,Father 是父类,Son 类继承自 Father。

第 1 个例子:

Father f1 = new Son();   // 这就叫 upcasting (向上转型)
// 现在 f1 引用指向一个Son对象Son s1 = (Son)f1;   // 这就叫 downcasting (向下转型)
// 现在f1 还是指向 Son对象

第 2 个例子:

Father f2 = new Father();
Son s2 = (Son)f2;       // 出错,子类引用不能指向父类对象

你或许会问,第1个例子中:Son s1 = (Son)f1; 问为什么是正确的呢。

很简单因为 f1 指向一个子类对象,Father f1 = new Son(); 子类 s1 引用当然可以指向子类对象了。

而 f2 被传给了一个 Father 对象,Father f2 = new Father(); 子类 s2 引用不能指向父类对象。

总结:

1、父类引用指向子类对象,而子类引用不能指向父类对象。

2、把子类对象直接赋给父类引用叫upcasting向上转型,向上转型不用强制转换吗,如:

Father f1 = new Son();

3、把指向子类对象的父类引用赋给子类引用叫向下转型(downcasting),要强制转换,如:

f1 就是一个指向子类对象的父类引用。把f1赋给子类引用 s1 即 Son s1 = (Son)f1;

其中 f1 前面的(Son)必须加上,进行强制转换。

对象的上转型对象


通俗地讲即是将子类对象转为父类对象。

上转型对象的特点:

  • 上转型对象不能操作子类新增的变量和方法
  • 上转型对象可以访问子类继承或者隐藏的成员变量,也可以调用子类继承的方法或者重写的方法。
  • 如果子类重写了父类的某个实例方法后,当上转型对象调用这个方法时,一定调用的是子类重写的,若子类重写了父类的static方法,则调用时只能调用父类的。
  • 不可以将父类创建的对象的应用赋值给子类声明的对象(不能说:人是中国人)
    举例:
public class Animal {public void eat(){System.out.println("animal eatting...");}
}
class Bird extends Animal{public void eat(){System.out.println("bird eatting...");}public void fly(){System.out.println("bird flying...");}
}
class Main{public static void main(String[] args) {Animal b=new Bird(); //向上转型b.eat(); //! error: b.fly(); b虽指向子类对象,但此时丢失fly()方法dosleep(new Male());dosleep(new Female());}

注意这里的向上转型:

Animal b=new Bird(); //向上转型
b.eat();

此处将调用子类的 eat() 方法。原因:b 实际指向的是 Bird 子类,故调用时会调用子类本身的方法。

需要注意的是:
向上转型时 b 会遗失除与父类对象共有的其他方法。如本例中的 fly 方法不再为 b 所有。
因此输出结果:bird eatting...

对象的下转型对象


与向上转型相反,即是把父类对象转为子类对象。

public class Girl {public void smile(){System.out.println("girl smile()...");}
}
class MMGirl extends Girl{@Overridepublic void smile() {System.out.println("MMirl smile sounds sweet...");}public void c(){System.out.println("MMirl c()...");}
}
class Main{public static void main(String[] args) {Girl g1=new MMGirl(); //向上转型g1.smile();MMGirl mmg=(MMGirl)g1; //向下转型,编译和运行皆不会出错mmg.smile();mmg.c();Girl g2=new Girl();
//    MMGirl mmg1=(MMGirl)g2; //不安全的向下转型,编译无错但会运行会出错
//    mmg1.smile();
//    mmg1.c();
/*output:
* CGirl smile sounds sweet...
* CGirl smile sounds sweet...
* CGirl c()...
* Exception in thread "main" java.lang.ClassCastException: com.wensefu.other1.Girl
* at com.wensefu.other1.Main.main(Girl.java:36)
*/if(g2 instanceof MMGirl){MMGirl mmg1=(MMGirl)g2; mmg1.smile();mmg1.c();}}
}
Girl g1=new MMGirl(); //向上转型
g1.smile();
MMGirl mmg=(MMGirl)g1; //向下转型,编译和运行皆不会出错

这里的向下转型是安全的。因为 g1 指向的是子类对象。

Girl g2=new Girl();
MMGirl mmg1=(MMGirl)g2; //不安全的向下转型,编译无错但会运行会出错

运行出错:

Exception in thread "main" java.lang.ClassCastException: com.wensefu.other1.Girlat com.wensefu.other1.Main.main(Girl.java:36)

构造器


子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。

如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
子类构造方法总是先调用父类的构造方法,你没写super(有参数),则默认你写了super(无参数)。
举例:

class SuperClass {private int n;SuperClass(){System.out.println("SuperClass()");}SuperClass(int n) {System.out.println("SuperClass(int n)");this.n = n;}
}
// SubClass 类继承
class SubClass extends SuperClass{private int n;SubClass(){ // 自动调用父类的无参数构造器System.out.println("SubClass");}  public SubClass(int n){ super(300);  // 调用父类中带有参数的构造器System.out.println("SubClass(int n):"+n);this.n = n;}
}
// SubClass2 类继承
class SubClass2 extends SuperClass{private int n;SubClass2(){super(300);  // 调用父类中带有参数的构造器System.out.println("SubClass2");}  public SubClass2(int n){ // 自动调用父类的无参数构造器System.out.println("SubClass2(int n):"+n);this.n = n;}
}
public class TestSuperSub{public static void main (String args[]){System.out.println("------SubClass 类继承------");SubClass sc1 = new SubClass();SubClass sc2 = new SubClass(100); System.out.println("------SubClass2 类继承------");SubClass2 sc3 = new SubClass2();SubClass2 sc4 = new SubClass2(200); }
}

输出结果为:

------SubClass 类继承------
SuperClass()
SubClass
SuperClass(int n)
SubClass(int n):100
------SubClass2 类继承------
SuperClass(int n)
SubClass2
SuperClass()
SubClass2(int n):200

好像有点感觉了,我们将代码做一点改动,再次感受一下:
subClass()构造方法中添一句

super(200);

然后将父类有参数构造方法改为:

    SuperClass(int n) {this.n = n;System.out.println("SuperClass(int n)"+this.n);

再次运行,得到结果:

------SubClass 类继承------
SuperClass(int n)200
SubClass
SuperClass(int n)300
SubClass(int n):100
------SubClass2 类继承------
SuperClass(int n)300
SubClass2
SuperClass()
SubClass2(int n):200

继承与多态


首先多态是个跟继承有关的概念,
什么叫多态?
多态就是在描述生活
举个例子
多态就是我有一个动物类,然后动物类派生了两个子类,猫类和狗类
我当初在动物类中写了一个方法:发出叫声
很显然猫跟狗的叫声不一样,一个喵喵,一个汪汪,那么我当初在动物类中写的这个发出叫声的方法现在如果这两个子类要用的话,是不是就得在猫类和狗类里面重写了,重写完之后,这整个过程就叫做多态
多态就是 同一个方法的不同实现。

abstract类和abstract方法


在java中我们用abstract关键字来表达抽象。举个例子:
我们说车子都可以跑(run)。但有几个轮子,怎么跑,对于不同的车有不同的结果。自行车需要人踩着跑,汽车发动机推动跑等等,那么我们可以车表达为抽象类。

/*** 车子类*/
public abstract class Car {public abstract void run();
}
/*** 自行车*/
class Bicycle extends Car{@Overridepublic void run() {System.out.println("人踩着跑。。。");}}
/**** 汽车*/
class Automobile extends Car{@Overridepublic void run() {System.out.println("发动机驱动跑。。。");}}

我的理解是抽象更像是一种概念,只要是抽象的就不需要具象,抽象类不需要实例化,抽象方法不需要在本类中实现。
抽象方法是一个概念,不用实现!!只要你在一个方法前冠以abstract,那这个方法就变成了一个概念,你不需要去实现一个概念。抽象方法跟接口中的方法是类似的,都不需要实现,所以我们没办法直接调用抽象方法

abstract类中声明的abstract方法要在子类中实现,如果子类未实现必须要将子类也声明为abstract:
在这里插入图片描述

abstract类:

1、用abstract关键字来表达的类,其表达形式为:(publicabstract class 类名{}2、抽象类不能被实例化,也就是说我们没法直接new 一个抽象类。抽象类本身就代表了一个类型,无法
确定为一个具体的对象,所以不能实例化就合乎情理了,只能有它的继承类实例化。3、抽象类虽然不能被实例化,但有自己的构造方法(这个后面再讨论)4、抽象类与接口(interface)有很大的不同之处,接口中不能有实例方法去实现业务逻辑,而抽象类
中可以有实例方法,并实现业务逻辑,比如我们可以在抽象类中创建和销毁一个线程池。5、抽象类不能使用final关键字修饰,因为final修饰的类是无法被继承,而对于抽象类来说就是
需要通过继承去实现抽象方法,这又会产生矛盾。(后面将写一篇关于finally的文章详细讨论)

abstract方法:

1、从上面的例子中我们可以看到抽象方法跟普通方法是有区别的,它没有自己的主体(没有{}包起来的
业务逻辑),跟接口中的方法有点类似。所以我们没法直接调用抽象方法2、抽象方法不能用private修饰,因为抽象方法必须被子类实现(覆写),而private权限对于子类来
说是不能访问的,所以就会产生矛盾3、抽象方法也不能用static修饰,试想一下,如果用static修饰了,那么我们可以直接通过类名调
用,而抽象方法压根就没有主体,没有任何业务逻辑,这样就毫无意义了。

总结:

/*
抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。
抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可。如何使用抽象类和抽象方法:
1. 不能直接创建new抽象类对象。
2. 必须用一个子类来继承抽象父类。
3. 子类必须覆盖重写抽象父类当中所有的抽象方法。
覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号。
4. 创建子类对象进行使用。*/

接口与实现

接口


官方解释:Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。


个人理解:接口可以理解为一种特殊的类,里面全部是由全局常量和公共的抽象方法所组成。接口是解决Java无法使用多继承的一种手段,但是接口在实际中更多的作用是制定标准的。或者我们可以直接把接口理解为100%的抽象类,既接口中的方法必须全部是抽象方法。(JDK1.8之前可以这样理解)

接口的特点


就像一个类一样,一个接口也能够拥有方法和属性,但是在接口中声明的方法默认是抽象的。(即只有方法标识符,而没有方法体)。

如果一个类实现了一个接口中要求的所有的方法,然而没有提供方法体而仅仅只有方法标识,那么这个类一定是一个抽象类。(必须记住:抽象方法只能存在于抽象类或者接口中,但抽象类中却能存在非抽象方法,即有方法体的方法。接口是百分之百的抽象类)

//定义子类,继承父类,实现接口
public class Zi extends Fu implements MyInterface {

接口定义与实现


接口定义

定义接口用关键词interface

/*
在任何版本的Java中,接口都能定义抽象方法。
格式:
public abstract 返回值类型 方法名称(参数列表);注意事项:
1. 接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract
2. 这两个关键字修饰符,可以选择性地省略。(今天刚学,所以不推荐。)
3. 方法的三要素,可以随意定义。*/
public interface MyInterfaceAbstract {// 这是一个抽象方法public abstract void methodAbs1();// 这也是抽象方法abstract void methodAbs2();// 这也是抽象方法public void methodAbs3();// 这也是抽象方法void methodAbs4();}
/*
接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰。
从效果上看,这其实就是接口的【常量】。
格式:
public static final 数据类型 常量名称 = 数据值;
备注:
一旦使用final关键字进行修饰,说明不可改变。注意事项:
1. 接口当中的常量,可以省略public static final,注意:不写也照样是这样。
2. 接口当中的常量,必须进行赋值;不能不赋值。
3. 接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则)*/
public interface MyInterfaceConst {// 这其实就是一个常量,一旦赋值,不可以修改public static final int NUM_OF_MY_CLASS = 12;}

接口实现

public class MyInterfaceAbstractImpl implements MyInterfaceAbstract {@Overridepublic void methodAbs1() {System.out.println("这是第一个方法!");}@Overridepublic void methodAbs2() {System.out.println("这是第二个方法!");}@Overridepublic void methodAbs3() {System.out.println("这是第三个方法!");}@Overridepublic void methodAbs4() {System.out.println("这是第四个方法!");}
}

abstract类与接口的比较

在Java语言中,abstract class和interface是支持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。
在这里插入图片描述

  1. 1.相同点
    A. 两者都是抽象类,都不能实例化。
    B. interface实现类及abstrct class的子类都必须要实现已经声明的抽象方法。

  2. 不同点
    A. interface需要实现,要用implements,而abstract class需要继承,要用extends。
    B. 一个类可以实现多个interface,但一个类只能继承一个abstract class。
    C. interface强调特定功能的实现,而abstract class强调所属关系。
    D. 尽管interface实现类及abstrct class的子类都必须要实现相应的抽象方法,但实现的形式不同。interface中的每一个方法都是抽象方法,都只是声明的 (declaration, 没有方法体),实现类必须要实现。而abstract class的子类可以有选择地实现。
    这个选择有两点含义:
    一是Abastract class中并非所有的方法都是抽象的,只有那些冠有abstract的方法才是抽象的,子类必须实现。那些没有abstract的方法,在Abstrct class中必须定义方法体。
    二是abstract class的子类在继承它时,对非抽象方法既可以直接继承,也可以覆盖;而对抽象方法,可以选择实现,也可以通过再次声明其方法为抽象的方式,无需实现,留给其子类来实现,但此类必须也声明为抽象类。既是抽象类,当然也不能实例化。
    E. abstract class是interface与Class的中介。
    interface是完全抽象的,只能声明方法,而且只能声明pulic的方法,不能声明private及protected的方法,不能定义方法体,也 不能声明实例变量。然而,interface却可以声明常量变量,并且在JDK中不难找出这种例子。但将常量变量放在interface中违背了其作为接 口的作用而存在的宗旨,也混淆了interface与类的不同价值。如果的确需要,可以将其放在相应的abstract class或Class中。
    abstract class在interface及Class中起到了承上启下的作用。一方面,abstract class是抽象的,可以声明抽象方法,以规范子类必须实现的功能;另一方面,它又可以定义缺省的方法体,供子类直接使用或覆盖。另外,它还可以定义自己 的实例变量,以供子类通过继承来使用。

  3. interface的应用场合
    A. 类与类之前需要特定的接口进行协调,而不在乎其如何实现。
    B. 作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识。
    C. 需要将一组类视为单一的类,而调用者只通过接口来与这组类发生联系。
    D. 需要实现特定的多项功能,而这些功能之间可能完全没有任何联系。

  4. abstract class的应用场合
    一句话,在既需要统一的接口,又需要实例变量或缺省的方法的情况下,就可以使用它。最常见的有:
    A. 定义了一组接口,但又不想强迫每个实现类都必须实现所有的接口。可以用abstract class定义一组方法体,甚至可以是空方法体,然后由子类选择自己所感兴趣的方法来覆盖。
    B. 某些场合下,只靠纯粹的接口不能满足类与类之间的协调,还必需类中表示状态的变量来区别不同的关系。abstract的中介作用可以很好地满足这一点。
    C. 规范了一组相互协调的方法,其中一些方法是共同的,与状态无关的,可以共享的,无需子类分别实现;而另一些方法却需要各个子类根据自己特定的状态来实现特定的功能。

内部类与异常类

内部类

异常类

断言

小结

Published by

风君子

独自遨游何稽首 揭天掀地慰生平