文章目录
一、继承格式
非常重要的部分!!!
-
格式:先描述父类,再描述子类,子类通过extends继承父类。
class Person() { } class Student() extends Person { }
继承了父类的属性和方法,自己也可以拥有属性和方法。
二、子类实例化内存分析
继承的概念:继承是子类继承父类的特征和行为(属性和方法),使得子类对象(实例)具有父类的实例域和方法,使子类具有父类相同的行为。
-
继承的规则:
Java中只有单继承和多重继承,没有多继承
。 -
生活例子理解:
- 单继承:一个孩子有一个父亲,继承了父亲的行为和特征;
- 多重继承:一个孩子有一个父亲,他的父亲还有一个父亲,孩子继承了爷爷的行为和特征;
- 多继承:一个孩子有多个父亲(错误示范),继承哪个父亲的行为和特征?继承会发生错乱。
注意:
一个子类只能有一个父类
。
- 子类和父类的创建过程:当我们创建子类对象时,内部会首先创建父类对象,父类对象创建完毕之后才会创建子类对象,并且父类对象会作为子类对象中的super存在。
- 简单来说就是子类拥有了一个父类的内存地址,两个类互相持有对象名称,可以操作父类中public和protected修饰的属性和方法,私有的无法直接操作,可以使用get、set方法获取和设置。
三、super详解
通过supper操作内存中的父对象,创建子对象时自动创建父对象。通过supper可以访问:
-
父类构造方法
-
调用
super构造方法
的代码,必须写在子类构造方法的第一行。
-
调用
- 父类属性
- 父类的方法
默认通过无参构造方法来访问,如果父类没有无参构造方法,那子类继承时必须明确写出super来进行调用。
四、重写
1、重写的概念
方法的重写,在类的继承中发生。当继承的某方法不适用子类执行程序时,就把该方法重写一下,当程序执行时,就运行的是重写的这段逻辑。
重写(override)规则:
-
参数列表:必须完全与被重写方法的相同
class Person { public void say() { System.out.println("父类中的内容"); } } class Student extends Person { public void say() { System.out.println("子类中的内容"); } }
此时在main方法中调用say()方法:
public class Test { public static void main(String[] args) { Student s = new Student(); s.say(); } }
输出结果为:
-
子类父类中存在同样的方法,运行的是子类,这种情况就是重写。
参数列表完全一样
。
-
返回类型:必须完全与被重写方法相同
-
访问权限:子类不能比父类权限更低
子>=父的访问权限
-
父类的成员方法只能被它的子类重写
必须存在继承关系
-
声明为static和private的方法不能被重写,但是能再次声明
静态的跟对象没有关系,本身不存在继承,可以再次声明。
私有的方法不能重写,不能继承更不能重写。
- 作用:设计了一个父类,包含了程序的执行流程,但另一个类对服务器进行的指令不同,此时这个类就需要继承父类,并重写子类中的方法。
2、重写和重载的区别
-
面试题:Java中重写(override)和重载(overload)的区别?
-
发生的位置
重载:一个类中
重写:子父类中
-
参数列表限制
重载:必须不同
重写:必须相同
-
返回值类型
重载:与返回值类型无关
重写:返回值类型必须一致
-
访问权限
重载:与访问权限无关
重写:子的访问权限必须不小于父的访问权限
-
异常处理
重载:与异常无关
重写:异常范围可以更小,但是不能抛出新的异常
-
五、final关键字
-
final用于修饰属性、变量
-
变量成为了常量,无法对其再次进行赋值。
-
final修饰的局部变量只能赋值一次(可以先声明后赋值)
-
final修饰的是成员属性,必须在声明时赋值
-
全局常量(public static final)
任何位置都可以通过类名直接访问的常量。
常量的命名规范:
- 由1个或多个单词组成,单词与单词之间用_隔开,单词中所有字母大写
- 例如:SQL_INSERT
-
-
-
final用于修饰类
- final修饰的类,不可以被继承。
-
final用于修饰方法
- final修饰的方法,不能被子类重写。
六、抽象类
1、抽象类的概念
一个类中有一个抽象方法(不确定的方法),就称其类为抽象类。
-
抽象类格式:不写修饰符默认是public,default效果不存在。
abstract class 类名{ // 抽象类 }
-
抽象方法格式:abstract修饰且没有方法体,就是抽象方法。
public abstract void 方法名();//抽象方法,只声明而未实现
-
编码规范:一个.java文件只包含一个类,且类必须通过public修饰。
-
子类继承抽象父类
:-
将子类变成抽象类
public abstract class Student extends Person { }
-
给抽象的、不确定的方法确定下来,给其写具体的执行逻辑
-
自己编写继承
-
new的时候直接指定superClass,自动生成重写方法
@override public class Student extends Person { public void say() { //如何执行 } }
-
-
-
抽象类也可以包含不抽象的部分:
假设我们要做一个仓库进销存系统,描述如何获取存储数据及数据显示时的读取,问题是不确定采用什么存储方式,为不能耽误开发进度,将不确定的先编写抽象部分,将确定的先实现。
(后续会学到接口,抽象类就较少使用了,接口比抽象类更抽象。)
2、抽象类常见问题
-
抽象类不能直接创建对象(不能被实例化)
-
抽象类不能使用final声明
final修饰的属性不能被继承,final修饰的方法不能被重写,抽象类不被继承就没有意义,所以直接不能使用final,使用final会报错。
-
抽象类能否有构造方法?
能有构造方法
,抽象类只是我们不能通过new来创建对象,但是Java虚拟机会自动创建super构造方法。
3、抽象类和普通类的区别
- 抽象类必须要使用public(公共的)或protected(受保护的)修饰,不能用private修饰的原因是私有后子类无法继承。
- 抽象类不能通过new对象被实例化,但在子类创建对象时,抽象父类会被JVM实例化。
- 如果子类继承了抽象父类,在子类中必须实现父类中所有的抽象方法,除非子类是抽象类。
七、接口
接口中只能存在
抽象方法
和
全局常量
。
-
接口的语法格式:
interface 接口名称{ 全局常量; 抽象方法; }
接口必须得依赖于一个子,也就是实现类。
没有子类就无意义。子类必须全部实现接口中的抽象方法,除非这个子类是抽象类。
-
实现类语法格式:可以多实现
class 子类 implements 父接口1,父接口2...{ }
-
接口允许多继承:
interface C extends A, B{ }
1、面向接口编程思想
调用接口,定义和实现分离,又称名实分离的原则。就是先定好规范,把程序先设计好,后续按照这个规范对其功能进行实现。某一处出现bug只需要在某一处查找,将那一处进行修改就好了。
优点
:
- 降低程序的耦合性
- 易于程序的扩展
- 有利于程序的维护
建议写程序时先写接口。
2、接口的编写
-
接口:
public interface Person { int a = 10;//默认是全局常量,省略了public static final void say();//默认为抽象方法,省略了abstract,不能加方法体,否则会报错 }
-
实现类:
public class Student implements Person { @override public void say() { System.out.println("显示内容"); } }
-
测试类:
public calss Test { public static void main(String[] args) { Student s = new Student(); s.say();//通过学生调用say方法 } }
3、接口和抽象类的区别
-
抽象类要被子类
继承
,接口要被类
实现
。 -
接口
只能声明抽象方法
,抽象类中
可以声明抽象方法,也可以写非抽象方法
。 -
接口里定义的变量
只能是公共的静态的常量
,抽象类中的变量是
普通变量
。 -
抽象类使用
继承
来使用,
无法多继承
。 接口使用
实现
来使用, 可以
多实现
-
抽象类中可以包含
static方法
,但是
接口中不允许
(静态方法不能被子类重写,因此接口中不能声明静态方法) -
接口不能有
构造方法
,但是抽象类可以有
八、多态
对象的多种形态,多种表现形式。利于我们更好地设计程序。
-
向上转型格式
父类 父类对象 = 子类实例 ;
-
向下转型格式:父类要转子类,需要加子类类型进行强转。
子类 子类对象 = (子类)父类实例 ;
1、多态的体现
对象的多态性:子类是父类的一种形态
-
学生对象被称作
学生
,这个
学生
是人的一种形态。Preson p = null; Student s = new Student(); p = s;
-
此时,虽然p是Person类型,但具体创建的实例对象是Student,指向的内容是s中的,调用的方法也是s中的方法。
-
总结:父类引用指向子类对象
理解:s是p的一种形态
-
总结:父类引用指向子类对象
-
学生对象被称作
人
,这个
人
是学生的一种形态。护士对象被称作
人
,这个
人
不是学生的一种形态。
有这样一段代码:
Student a = new Student();
Nurse b = new Nurse();
-
例1:
Person p1 = a; Person p2 = b;
- 此时p1可以正常调用Student类中的方法;p2可以调用Nurse类中方法。
-
但是,例2,
错误示范
:Student a2 = (Student)p1; Student a3 = (Student)p2;
- 此时,a2调用方法时没有问题,但是a3调用时就会报错,提示护士类无法转为学生类。
理解:学生转为学生类没问题,而护士不能转为学生类。
ps: 方法的重载和重写也是多态的一种,不过是方法的多态(相同方法名的多种形态)。
重载:一个类中方法的多态性体现;
重写: 子父类中方法的多态性体现。
2、instanceof
如上错误示范,为避免将护士类转为学生类这种错误发生,可以使用instanceof判断传入类型是哪个子类对象。
-
语法格式:
实例化对象 instanceof 类 //此操作返回boolean类型的数据
九、Object类概述
如果没有明确声明一个类的父类,那么此时Object就是这个类的父类,默认省略了extends Object。
Java中所有内容都是Object类的对象。
Object的多态
使用Object可以接收任意的引用数据类型。Object本身包含了许多实用方法。可以下个JDK 11 API查看对应类里的方法。
1、toString
Object的toString方法, 返回对象的内存地址。
返回对象的字符串表示形式,建议所有子类都重写toString方法,便于阅读。
2、equals
==比较对象的内存地址,内存地址相同,比较结果为true。
equals比较对象内容,内容相等结果为true,与地址无关。
-
建议
所有子类都重写equals方法
,重写时有五个特性:- 自反性:自己与自己相比,结果为true
- 对称性:比较对象顺序可以颠倒
- 传递性:一个对象与两个对象比较结果为true,那两个对象比较结果也为true
- 一致性:比较时对象内容无法修改
- 非空性:比较对象与空值相比结果返回false
-
总结:符合常理。
public boolean equals(Object o) { if (this == o) { return true; } if (o == null) { return false; } if (o instanceof Person) { Person p2 = (Person)o; //比较操作 } return false; }
十、内部类概述
概念较好理解,但是实际操作中较难应用,使用不多,理解即可。
在一个类中又定义了一个类,在里面的这个类就称作内部类。
分类:
- 成员内部类
- 局部内部类
- 匿名内部类(局部内部类的一种)
- 静态内部类
1、成员内部类(了解)
在一个类中像写属性一样再写一个类,
外部需要创建对象才可以使用
。
-
语法格式:
class Outer { private double x = 0; public Outer(double x) { this.x = x; } class Inner { //内部类 public void say() { System.out.println("x="+x); } } }
成员内部类可以无条件使用外部类中的成员属性和方法
,不过出现同名的成员变量或者方法时,会发生隐藏现象,会访问最近的,即默认情况下访问的是成员内部类的成员。
-
如果要访问外部类的同名成员,可以通过:
外部类.this.成员变量
外部类.this.成员方法Outter outter = new Outter(); Outter.Inner inner = outter.new Inner();
2、局部内部类
定义在一个方法、作用域里面的类,就像一个局部变量,
不能使用public等权限修饰符进行修饰
。
class Person{
public Person() {
}
}
class Man{
public Man(){
}
public People getPerson(){
class Student extends People{
//局部内部类
int age =0;
}
return new Student();
}
}
3、匿名内部类
只使用一次的内部类。
-
格式比较奇怪,没名字,直接用new就行:
new 父类构造器(参数列表)|实现接口(){ //匿名内部类的类体部分 }
-
使用匿名内部类的限制:
-
必须继承一个类
或者
实现一个接口。也只能继承一个类或实现一个接口。 -
匿名内部类中是
不能定义构造函数
的。 -
匿名内部类中
不能存在任何的静态成员变量和静态方法
。 -
匿名内部类为
局部内部类
,所以局部内部类的所有限制同样对匿名内部类生效。 -
匿名内部类不能是抽象的
,它必须要实现继承的类或者实现的接口的所有抽象方法。 -
只能访问final型的局部变量
- 所有的局部内部类都只能访问final型的局部变量
- 内部类会单独编译成一个字节码文件,为保证单独文件中用到的变量a与外部值绝对是一致的,系统从规则限制这个值不能更改
-
必须继承一个类
4、静态内部类
与普通成员内部类很像,只是加了一个static修饰。
通过类名.操作
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
public Outter() {
}
static class Inner {
public Inner() {
}
}
}
强调:内部类应用非常少,不是重点!
十一、包装类
Java为8种基本数据类型都提供了包装类。利于基本数据类型和引用数据类型之间的转换。
1、8种基本数据类型
序号 | 基本数据类型 | 包装类 |
---|---|---|
1 | int | Integer |
2 | char | Character |
3 | float | Float |
4 | double | Double |
5 | boolean | Boolean |
6 | byte | Byte |
7 | short | Short |
8 | long | Long |
这些类在类型上分为两大类:
-
Number:Integer、Short、Long、Double、Float、Byte都是Number的子类,表示是一个数字。
- Object:Character、Boolean都是Object的直接子类。
2、装箱和拆箱操作
- 装箱:从基本数据类型变成包装类
-
拆箱:从包装类变为基本数据类型
Number类种定义了许多拆箱操作的方法:
序号 | 方法 | 描述 |
---|---|---|
1 | public byte byteValue() | 用于Byte->byte |
2 | public abstract double doubleValue() | 用于Double->double |
3 | public abstract float floatValue() | 用于Float->float |
4 | public abstract int intValue() | 用于Integer->int |
5 | public abstract long longValue() | 用于Long->long |
6 | public short shortValue() | 用于Short->short |
//手动装箱
Integer i = new Integer(200);
//手动拆箱
int a = i.intValue();
- 在JDK1.4之前,还是手动装箱和手动拆箱。
- 在JDK1.5,Java新增了自动装箱和自动拆箱,而且可以直接通过包装类进行四则运算和自增自建操作。例如:
Float f = 10.3f ; // 自动装箱
float x = f ; // 自动拆箱
System.out.println(f * f) ; // 直接利用包装类完成
System.out.println(x * x) ; // 直接利用包装类完成
3、字符串转换
使用包装类可以将一个字符串变为指定的基本数据类型,此点一般在接收输入数据上使用较多。
- parseInt(String s) :将String变为int型数据
- parseFloat(String s):将String变为Float
- parseBoolean(String s) :将String变为boolean
十二、可变参数
方法的重载。当不确定传入的参数有几个,在JDK1.5之后,有一个可变参数的功能,可以根据需要自动传入任意个数的参数。
语法格式:
返回值类型 方法名称(数据类型… 参数名称) {
//参数在方法内部 , 以数组的形式来接收
}
-
注意
:可变参数只能出现在参数列表的最后。
public static int sum(int... nums) {
int n = 0;
for (int i = 0; i < nums.length; i++) {
n += nums[i];
}
return n;
}
- int…nums:表示可变参数,调用时能传入0~n个参数。方法内部可变参数以数组体现。
十三、递归
递归,在数学与计算机科学中,是指
在方法的定义中使用方法自身
。也就是说,递归算法是一种
直接或者间接调用自身方法
的算法。
递归流程图如下:递归实现五的阶乘:public static int
fact(int n) {
if (n == 1) {
return 1;
} else {
return n * fact(n - 1);
}
}
能用循环的不用递归,效率太低。
总结
Java是一门纯面向对象的编程语言,万物皆可对象。这是一个十分重要的知识点,所以分为了三个章节——基础、进阶、高级,这是最后一章节,一定要多多复习。