Java面向对象知识总结-下 1
文章目录
一, 继承
1.1, 什么叫继承?
- 在Java中, 类的继承是
指在一个现有类的基础上去构建一个新的类
,构建出来的新类被称为子类,现有类被称为父类.子类会自动的拥有父类所有可继承的方法和属性
. 子类还可以重写父类的方法,对父类进行扩展,提高代码的复用性.
继承的特点:
- 在Java中,类只支持单继承,不允许多重继承,即
一个类只能有一个直接父类
;class A extends B;///一个类只能有一个父类 class A extends B,C; ///不行!
多个类可以继承一个父类
;class A extends B; class C extends B;
- Java
支持多层继承
,即一个类的父类可以再去继承另外的父类;class A extends B; class B extends C;
- java中的
子类和父类是一种相对概念
,一个类的父类可能是别的类的子类.
1.2, (重写)对父类方法进行改写 ✔
public class Animal{
public void shout(){
System.out.println("animals all shout");
}
}
public clss Dog extnds Animal{
///重写父类.同名同参同返回
public void shout(){
System.out.println("wang, wang");
}
public static void main(String[] args){
Animal dog = new dog();
dog.shout();
}
}
1.3, super关键字 (子类访问父类的成员属性, 被重写的成员方法, 父类构造方法等)
当子类重写父类的方法后,子类对象将无法访问父类被重写的方法,为了解决这个问题,在Java中提供
super关键字让子类或子类对象访问父类的成员
.
- 调用
父类的成员变量和成员方法
- 格式:
- super.成员变量名
- super.成员方法名(参数1, 参数2)
代码示例:
- 格式:
class Animal{
String name = "动物";
///定义动物叫的方法
public void shout(){
System.out.println("动物发出叫声");
}
}
///子类
class Dog extends Animal{
public void shout() {
super.shout();
}
public String printName() {
return super.name;
}
public static void main(String[] args){
Dog dog = new Dog();
dog.shout();
System.out.println(dog.printName());
}
}
- 调用
父类的构造方法
通过super调用父类构造方法的代码必须位于
子类构造方法的第一行
,并且只能出现一次
;
示例代码:
///定义Animal类
class Animal{
//定义Animal类的有参构造方法
public animal(String name){
System.out.println("I am "+name);
}
}
class Dog{
public dog(){
super.animal("dog");
}
public static void main(String[] args){
Dog dog = new Dog(); //实例化子对象
}
}
注意:
子类对象一定会调用父类的某个构造方法
,我们可以通过super关键字指定调用的是哪一个构造方法, 如果不指定,在实例化子类对象时,则是默认调用父类的无参构造方法.
1.3, final关键字
- final关键字可用于修饰类,方法和变量. 被final关键字修饰的类,变量和方法具有以下特征:
- final关键字修饰的
类不能被继承
; - final关键字修饰的
方法不能被重写
; - final关键字修饰的
变量(局部,全局变量)是常量,只能赋值一次
.
- final关键字修饰的
使用final关键字修饰的成员变量,
虚拟机不会对其进行初始化
.因此使用final关键字修饰成员变量时,需要在声明变量的同时就赋予一个初始值
.
二, 抽象类和接口
2.1 抽象类定义
当定义一个类时,常常需要定义一些方法来描述该类的行为特征,但是有时候这些方法的实现方式是无法确定的. 比如定义了一个Animal类,类中有一个shout()方法表示动物叫声,但是不同的动物叫声是不同的,所以animal类中的shout()无法具体实现.
- 针对以上情况, Java允许在定义方法时不写方法体,而在具体实现的时候考虑具体情况再进行方法的实现,
不包含方法体的方法叫抽象方法
,包含抽象方法的类叫做抽象类
抽象方法必须使用 abstract关键字修饰
. 比如: abstract void shout(); - 当一个类中包含了抽象方法,该类也必须用abstract修饰, 所以相应的, 被abstract 修饰的类就叫做抽象类.
//抽象类示例
abstract class Animal{
///定义抽象方法
abstract int shout();
}
2.2 抽象类特点
- 可以有,也可以没有;
抽象类中可以有抽象方法和普通方法,也可以什么方法都没有
,而只用abstract修饰一下意思意思.但是! 包含抽象方法的类必须声明为抽象类
. - 不能实例化;
抽象类可不能用new()实例化
,因为没有意义啊,抽象类中抽象方法都是没有方法体的. 所以吧, 要实现抽象类中定义的方法,我们需要创建一个子类,然后让子类去继承抽象类并对其中的抽象方法一一进行实现
. - 子类全实现或同为抽象类; 抽象类的
子类必须给出抽象类中抽象方法的实现,除非子类同为抽象类
. - 构造, static方法不能抽象;
构造方法,类方法
(static修饰的方法)不能声明为抽象方法
;
示例代码:
抽象父类
public abstract class Animal{
public abstract void shout();
public abstract void eat(String food);
public void sleep() {
Sys tem.out.println("Aniamls all need go to sleep");
}
}
///继承抽象父类的子类
class Dog extends Animal{
@Override
public void shout() {
// TODO Auto-generated method stub
System.out.println("I am dog and shout with wangwang");
}
@Override
public void eat(String food) {
// TODO Auto-generated method stub
System.out.println("As a dog, I eat "+food);
}
public void sleep() {
super.sleep();//复习一下.子类改写父类方法后还想用父类的这个方法的话,就 super一下哟
System.out.println("I am dog. I go to sleep at dog house");
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.shout();
dog.eat("shit");
dog.sleep();
}
}
运行结果:
2.3 接口定义
嘛是接口? 害,就是一个
全是抽象方法的抽象类
罢了. 定义接口也简单, class 换成 interface就行.
2.4 接口特点
- 既然咱都知道interface
里面的方法都是抽象方法
了,那abstract也就没必要一直带着了. 接口中的方法默认使用 public abstract修饰
,变量默认使用 public static final
修饰.与抽象类相同, 不能直接实例化接口
. 我们需要一个类去implements接口,然后再去一一实现接口内的方法.- 当一个类实现接口时, 如果这个类是
抽象类
,则实现接口中的部分方法
即可,否则
需要实现接口的所有方法
.- 一个类
只能继承一个抽象类,却可以实现多个接口
(class A implements B,C{})
示例代码:
///接口
public interface Animal{
//int sleepHour = 8;//因为变量默认是public static final修饰,所以在声明的时候我们就需要赋值噢!(final修饰变量需当场赋值)
void shout();
void eat(String food);
void sleep(int sleepHour);
}
///实现接口
class Dog implements Animal{
@Override
public void shout() {
System.out.println("I am a dog and shout with 'wangwang'.");
}
@Override
public void eat(String food) {
// TODO Auto-generated method stub
System.out.println("As a dog, I eat "+food+" for food." );
}
@Override
public void sleep(int hour) {
// TODO Auto-generated method stub
System.out.println("Dog like people, "+hour+" hour's sleep good for it too");
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.shout();
dog.eat("shit");
dog.sleep(8);
}
}
三, 多态(待深入)(▷)
3.1 多态概述(对于同一个方法的调用)
- 对同一个方法的调用,由于对象的不同可能会有不同的行为,这就叫做多态(具体来说是运行时多态)
- 在Java中为了实现多态,允许使用一个父类类型的变量来引用一个子类类型的对象,根据被引用子类对象特征的不同,得到不同的运行结果.
3.1.1 多态的两种类型
- 编译时期多态:其又被称为静态多态,
编译时期的多态是靠重载实现
的,根据参数个数,类型和顺序决定的(必须在同一个类中) - 运行时的多态:
运行时期的多态是靠方法的重写实现的
,在编译期间被视作相同方法,但是运行期间根据对象的不同调用不同的方法(运行时动态决定调用哪个方法)
示例代码:
///接口
public interface Animal {
void shout();
}
///dog实现类
public class Dog implements Animal {
@Override
public void shout() {
// TODO Auto-generated method stub
System.out.println("Dog, wangwang");
}
}
//cat 实现类
public class Cat implements Animal{
@Override
public void shout() {
// TODO Auto-generated method stub
System.out.println("cat, miaomiao");
}
}
///测试程序
public class Test {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
dog.shout();
cat.shout();
}
}
3.2 多态实现的必要条件:
- 需要
存在继承关系
- 需要有
方法的重写
父类引用指向子类对象(向上转型)
3.3 多态的类型转换
3.3.1 向上转型
- 向上转型(自动转型, 隐式转型)–> 父类引用指向子类对象
- 格式:
父类名 变量名 = new 子类名()
; - 特点: 父类引用指向子类实例, 可以调用子类重写父类的方法, 父类派生的其他方法,以及父类的成员变量,但是无法调用子类内部的独有方法.
代码示例:
- 格式:
///只定义了shout()方法的父类 Animal
class Animal{
Int animalFeet = 4;
void shout();
///Animal父类的静态方法
static void sleep() {
System.out.println("All Animals must go to sleep");
}
}
///子类
class Dog extends Animal{
final String food = "bone";
void shout(){
System.out.println("wangwang");
}
//dog子类独有的eat()
void eat();
System.out.println("Dogs likes eat "+food);
}
///测试类
public class Test{
public static void main(String[] args){
Animal dog = new Dog();
dog.shout();
///调用父类中的成员变量
System.out.println(""+dog.animalFeet);
///上面两句正常执行,下面这两句会报错
dog.eat();
System.out.println(""+dog.food);
}
}
- 而且呢, 具体怎样访问还有下面这个特点: 对于成员变量和静态方法,
- 为什么dog子类的实例对象无法调用Dog类内部的独有方法和成员变量呢?
- 首先给出一个结论:
成员变量,静态方法:编译和运行都看左边;非静态方法:编译看左边,运行看右边
。- 具体来说就是:当使用多态方式调用方法时,首先检查父类(编译看左边)中是否有该方法,如果没有则编译错误; 如果有,再去调用子类中的同名方法(运行看右边)
- 也就是说,在这个父类引用变量animal指向的对象中,他的成员变量和静态方法与父类是一致的,他的非静态方法,在编译时是与父类一致的,运行时却与子类一致(如果发生了复写)。
再次总结一次:
- 向上转型(即父类引用指向子类对象), 比如 Animal xyz = new Dog(), 这个父类类型的变量xyz 只能访问 父类的所有变量和方法(无论是静态,非静态的成员变量, 还是被子类重写的方法, 又或者是父类特有的方法), 子类的成员变量和特有方法无法访问到!!! ------------------------>此外, 还有以下特点, 对于成员变量和静态方法, 编译和运行结果都是以父类的为准(称为编译运行都看左边); 而对于非静态方法, 编译时先看父类有没有这个方法, 运行时再去执行子类的这个同名方法并得到运行结果(称为编译看左边, 运行看右边)在向上转型这一概念中, 我们在上面红字中提到了
- Java在成员变量和静态方法中是所谓的编译和运行都看左边,这也是等同于另外一个概念(静态绑定, 即方法,变量和所属的类在程序执行之前就关联在一起)
- 而所谓的一般方法遵循编译和执行都看右边, 这等同于另外的概念(动态绑定, 即方法和所属的类在执行时才进行关联)
关于以上的更具体的说明, 请参阅Scala专栏文章: Scala面向对象基础知识
3.3.2 向下转型
- 向下转换(强制类型转换)–> 子类引用指向父类对象
- 格式:
子类类名 变量名 = (子类类名)父类引用
; - 特点: 子类引用指向父类对象,必须进行强转,之后便可以
调用子类特有的方法.
- 格式:
代码示例:
Animal sb = new Dog(); //变量sb指向一个子类dog对象
Dog dogSon = (Dog)sb;
之所以子类的引用可以指向父类对象,是因为在之前我们就固定下来了父类和子类的对应关系,比如父类Animal和子类Dog
3.4 Object类
JDK中提供了一个Object类, 它是所有类的父类,即每个类都直接或间接继承自该类.
Object类中常用的几个方法:
方法名 | 用途 |
---|---|
toString() | 输出对象的基本信息 |
hashCode() | 返回该对象的哈希值 |
getClass().getName() | 返回对象所属类的类名 |
相应的,所有类都可以使用或重写上面的方法奥!
3.5 匿名内部类
- 存在前提:
存在一个类或接口
,这里的类既可以是具体类也可以是抽象类.- 特点和作用:类的创建与重写方法放在一起完成,简化抽象类和接口实现的操作
- 本质: 是一个继承了该类或者实现了该接口的子类匿名
对象
- 适用场景:
- 只用到类的一个实例;
- 类在定义后马上用到;
- 给类命名并不会导致代码更容易理解(所以我们选择匿名)
- 匿名内部类的格式:
new 匿名类=父类类名或者接口名(){
重写方法;
}.调用上面重写的方法;
说明:
interface Animal{
void shout();
}
public class Cat{
public void catStyle(Animal animal) {
animal.shout();
}
public static void main(String[] args) {
Cat cat = new Cat();
///匿名内部类的创建的结果实质上是一个对象
Animal an =new Animal() {
@Override
public void shout() {
// TODO Auto-generated method stub
System.out.print("miao miao");
}
};
cat.catStyle(an);
}
- 对上述代码的继续改进
public interface Animal{
void shout();
}
public class Test{
///定义静态方法,调用animal类的shout()
public static void animalStyle(Animal animal) {
animal.shout();
}
///main函数
public static void main(String[] args) {
animalStyle(new Animal(){
//重写shout()方法
public void shout() {
System.out.println("wang wang");
}
});
}
再来个极致的栗子:
四, 异常
4.1, 异常定义和结构
以异常类的方式对遇到的非正常现象进行封装,然后通过异常处理机制对程序运行时发生的各种问题进行处理.
- Throwable类中的常用方法:
方法 功能 String getMessage() 返回此throwable的详细消息字符串 void printStackTrace() 将此throwable及其追踪输出到标准错误流 void printStackTrace(PrintStream s) 将此throwable及其追踪输出到指定的输出流
拓展: JVM的默认处理方案
如果程序出现了问题,我们没有做任何处理,最终JVM会做默认的处理
- 把异常的名称,异常原因及异常出现的位置等信息输出在了控制台;
- 程序停止执行
4.2, try…catch 和 finally
4.2.1 try…catch块
- 执行流程:
当try代码块中的程序发生了异常,系统会将这个异常的信息封装成一个异常对象,该异常对象将被提交给Java运行时系统. 当Java运行时系统接收到异常对象时,会到catch中去找匹配的异常类,找到后进行异常的处理执行完毕之后,程序还可以继续往下执行
///格式
try{
///可能会发生异常的java语句
}catch(ExcepType(Exception及其子类) e){
针对异常ExceptionType的处理
}
示例代码:
public class ExceptionTest{
public static void main(String[] args){
int a=0;
int b=1;
try{
///可能会异常的地方
int x=b/a;
}catch(Exception e){
//输出错误信息
System.out.println(e.getMessage());
}
程序跳出 try...catch块后继续执行下去哦!
System.out.println("程序继续向下执行");
}
}
执行结果:
- 使用try…catch处理异常可能发生的情况:
- 无异常, 程序从try块执行后未有异常,则跳过catch块继续执行下去.
- 有异常,且能正常匹配
- 有异常,但不能正常匹配
4.2.1.1, 多重catch结构
- 对于有多个catch子句的异常程序而言,应该尽量
将捕获底层异常类(具体的异常)的catch子句放在前面
,同时尽量将捕获相对高层(比如Exception类等较宽泛的异常)的异常类的catch子句放在后面
。否则,捕获底层异常类的catch子句将可能会被屏蔽。- 一旦某个catch捕获到匹配的异常类型,将进入异常处理代码。一经处理结束,就意味着整个try-catch语句结束。其他的catch子句不再有匹配和捕获异常类型的机会。
4.2.2 finally关键字
在程序中,有时候我们
希望有些语句无论是否发生异常都要执行
,我们可以在try…catch块后加一个finally代码块.
try块后可跟零个或多个catch块, 但try后没有catch块时,必须跟一个finally块.
-
含有return的try…catch
-
在try…catch块中加入
return;
可用于结束当前方法, 此时try…catch块后的语句就不会再执行了. 而finally中的代码仍会执行, 即finally中的语句,无论是遇到异常情况还是使用return语句结束都要执行
-
finally 在try块或catch块中含有return语句时仍会执行,并且在return语句执行后, return语句返回前执行. 且finally里面的修改语句结果可能影响也可能不影响 try或catch中 return已经确定的返回值. 若finally里也有return语句,则无视掉try或catch中的return值,直接返回finally的值!.
举个栗子:
4.2.3, throw, throws 关键字 (▷ throw的示例要补充!)
- throws—>
声明可能要抛出的异常
, 用于在方法定义时(具体来说是方法的括号后)声明该方法抛出的异常类型,特点是谁调用谁就要 **处理异常** 或 **继续抛出这个异常**
如何处理? 当然还是tyr…catch了
- throw----> 在
方法体内部抛出异常对象
,抛出的异常只能是Throwable类及其子类的实例对象.
实例代码:
//抛出异常
public class Test{
public static int divide(int a, int b) throws Exception{
return a/b;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
int b = sc.nextInt();
///谁调用谁处理
try {
System.out.println(divide(a,b));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
4.2.4, throw, throws的区别
4.3, 异常的类别
- 在实际开发中,经常会在
程序编译时产生一些异常,而这些异常必须要进行处理
, 这种异常被称为编译时异常(checked异常)- 还有一种异常是
在程序运行时产生的,这种异常即便不编写异常处理代码,仍然可以通过编译
,因此称为运行时异常(unchecked异常)
4.3.1, 编译时异常
- Java中, Exception类中除了RuntimeException类及其子类外都是编译时异常.
- 编译时异常的特点: Java编译器会对其进行检查,如果出现异常就必须对异常进行处理.否则程序无法通过编译
- 处理编译时异常的两种方式:
- 使用try…catch语句对异常进行捕获;
- 使用throws关键字声明抛出异常,让调用者处理
4.3.2, 运行时异常
- RuntimeException 类及其子类都是运行时异常.
- 运行时异常的特点: Java编译器不检查,也就是说,程序出现这类异常时,即便没有使用try…catch语句捕获或使用throws关键字声明抛出,程序也能编译通过.
- 运行时异常一般有程序中的逻辑错误引起,在程序运行时无法恢复.
4.3.3, 自定义异常
为了描述特定业务产生的异常类型, 我们可以使用自定义异常.
自定义异常必须继承throwable类或者它的子类.
- 格式:
public class 自定义异常类名 extends Exception{
///无参构造方法
public 自定义异常类名(){};
///带参构造方法
public 自定义异常类名(String message){
//Exception的带参构造方法,参数是错误信息
super(message);
}
}
4.3.3.1, 自定义异常的两种处理方式
- throw自定义异常对象,自己进行异常处理
public void method(){
try{
代码段...
throw new 自定义异常(参数);
}catch{
对上面异常的处理代码段
}
}
示例代码:
- throw自定义异常,然后用throws交给别人处理(上抛)
public void method() throws 自定义异常{
代码段...
throw new 自定义异常();
}
示例代码
//自定义异常类,提供 一个有参构造和一个无参构造
public class ScoreException extends Exception{
///
public ScoreException() {};
public ScoreException(String message) {
///
super(message);
}
}
//新建自定义异常对象并向上抛出
public class Teacher extends ScoreException{
int score;
public void scoreJudge(int score) throws ScoreException {
if(score < 100 && score >0) {
System.out.println("分数格式正确");
}else {
throw new ScoreException("异常已抛出 \n 你的格式不对噢!");
}
}
}
//测试类
public class TeacherTest {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int score = sc.nextInt();
Teacher teacher = new Teacher();
try {
teacher.scoreJudge(score);
} catch (ScoreException e) {
// TODO Auto-generated catch block
e.printStackTrace();
e.getMessage();
}
}
Q: super(message)的作用?
A: 调用父类的带参数构造方法, 每个异常类及其子类在书写带参构造方法时一般都会调用这个构造方法, 那么具体实现这个构造方法的父类是谁呢? 当然是异常类的终极父类 Throwable类了, 下图为带参构造的具体实现(作用就是打印错误信息);
五, 包
- Java中,针对类,成员方法和属性提供的访问控制级别:
private(类访问级别) --> default(包访问级别) --> protected(不同包子类访问级别)–> public(公共访问级别) - 辨析:
权限名 介绍 private 一个类的成员被private修饰后,只能在这个类中被访问 default 类或类的成员默认为default级别,在同一个包内可以互相访问 protected 类的成员被protected修饰,可以被同一包下的其他类访问,也可以不同包下该类的子类访问 public 类或类的成员被public修饰,可以被其他所有的类访问,无论同包不同包
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)