Java基础复习:面向对象与异常

这篇博客深入探讨了面向对象编程的核心概念,包括类与对象的创建、封装、继承、多态以及抽象类的使用。通过实例详细解释了如何定义和使用构造器,以及如何通过getter和setter方法实现属性的封装。同时,介绍了如何通过继承扩展类的功能,以及重写方法的概念。最后,讨论了接口在抽象和多态中的作用,强调了面向对象设计的灵活性和模块化。
摘要由CSDN通过智能技术生成

面向对象

什么是面向对象

以往面向过程的思想,思路比较清晰简单,适合处理加单的问题。

面向对象(Object Oriented Programming)先考虑解决问题需要分哪些类,适合处理复杂的、多人协作的问题。

  • 其本质是——以类的方式组织代码,以对象的方式封装数据。

  • 抽象

  • 三大特性:封装、继承、多态

对象是具体的事物,类是抽象的事物。

代码先定义类,再实例化为对象。

类与对象的创建

创建一个Student类:

public class Student {
    //类与对象的创建
    String name;
    int age;
​
    public void study(){
        System.out.println(name + "在学习!");
    }
}

其中包含变量姓名、年龄,与方法学习。

实例化两个学生:

    public static void main(String[] args) {
        Student xiaoming = new Student();
        Student xiaohong = new Student();
    }

给小明初始化,并打印:

xiaoming.name = "小明";
xiaoming.age = 13;
​
System.out.println(xiaoming.name);
System.out.println(xiaoming.age);

当然,正常不需要一条条初始化,可以用构造器初始化。

构造器

实例化对象的时候,一定会调用构造方法,即构造器,

如果没有定义,那么会默认调用空的构造器:

public Student() {
}

如实例化小明时,要同时传入其属性name、age的默认值,则需要有参构造器:

public Student(String name, int age) {
    this.name = name;
    this.age = age;
}

定义了有参构造器,就默认不能无参实例化了,除非把空的构造器自己写一遍。

我们用有参构造器重新实例化一下小红:

Student xiaohong = new Student("小红",14);

这样就不需要一个个赋值了,可以打印出来看一下。

构造器的快捷键:Alt + Insert。

构造器两个特点:

  • 必须与类的名字相同

  • 必须没有返回值

构造器也可以定义多个进行重载。

各种数据类型的默认值:

  • int:0

  • boolean:false

  • char:u0000

  • 引用类型:null

对象的属性:student.name

对象的方法:student.study()

注意对象一般是小写开头。

封装

一个类对其中的属性进行封装,一般使用private声明,要查询或更改属性的值,

一般要通过getter、setter方法来实现。

封装就是黑盒子,使用者不必知道类中的结构,只需要知道如何使用!

高内聚低耦合:大部分数据操作封装起来自己搞,只留少量方法供外部调用。

封装示例:

public class PrivateStudent {
    //封装
    private String name;
    private int id;
    private boolean Male;
​
    public PrivateStudent(String name, int id, boolean gender) {
        this.name = name;
        this.id = id;
        this.Male = gender;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public int getId() {
        return id;
    }
​
    public void setId(int id) {
        this.id = id;
    }
​
    public boolean isMale() {
        return Male;
    }
​
    public void setMale(boolean male) {
        this.Male = male;
    }
​
    @Override
    public String toString() {
        return "PrivateStudent{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", Male=" + Male +
                '}';
    }
}

看起来挺多东西,其实只有属性是自己敲的,后面的都是Alt + Insert里面自动生成的。

有构造器、gettersetter、toString方法(这个自动生成要生成无参构造器要再点一遍)。

调用一下:

public static void main(String[] args) {
    PrivateStudent student = new PrivateStudent("小明", 1, true);
    System.out.println(student.toString());
​
    student.setName("小红");
    student.setId(10);
    student.setMale(false);
    System.out.println(student.toString());
    System.out.println(student.getName());
    System.out.println(student.getId());
    System.out.println(student.isMale());
}

先new一个小明,使用toString方法打印,在改成小红,

并在打印后使用了一下getter方法。

输出结果:

PrivateStudent{name='小明', id=1, Male=true}
PrivateStudent{name='小红', id=10, Male=false}
小红
10
false

为什么要做看起来多此一举的封装呢?因为可以在setter里面做安全性检查,

比如setAge中,可以要求年龄不小于0岁才让set,就不会让属性被随便操作。

列举封装的优点:

  • 提高安全性,保护数据

  • 隐藏实现细节

  • 统一接口

  • 提高可维护性

继承

继承的关键词是extends,意思是扩展,子类是父类的扩展。

Java中只有单继承,即一个子类只能继承一个父类。

子类继承了父类,就会拥有父类的全部方法:

public class Animal {
​
    public void isAnimal(){
        System.out.println("是动物");
    }
​
}

建立一个Animal类,其中有一个方法isAnimal,

创建一个Cat类继承他:

public class Cat extends Animal{
}

在main方法中new一个cat,并调用其父类的方法:

public static void main(String[] args) {
    Cat cat = new Cat();
    cat.isAnimal();
}

可以打出“是动物”,即Cat类中虽然没有这个方法,但可以使用父类的同名方法。

此外,父类中的属性也同样可以继承。

但是private是继承不了的,protected是子类可以继承,别人不能调用的,

public是谁都可以调用的。

快捷键F4可以打开继承树。

注意:在Java中,所有的类默认继承Object类(直接或间接)。

使用super关键词可以调用父类的变量,先在Animal中定义一个变量:

protected String name = "动物";

在Cat中加些东西:

    private String name = "猫猫";
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public void test(String name){
        System.out.println(name);
        System.out.println(this.name);
        System.out.println(super.name);
    }

主程序调用:

cat.test("周六");

输出“周六、猫猫、动物”,即方法test中:

name是传参,this.name是Cat类中的名字,super.name是父类中的名字。

当然了super调用不了private的东西。

构造器也可以用super继承:

public Animal(String name) {
    this.name = name;
}

Cat中:

public Cat(String name) {
    super(name);
}

主程序实例化时:

Cat cat = new Cat("周六");

这样Animal的name就被构造器变成周六了,而Cat的name仍是猫猫。

方法的重写

重写和重载不一样,重写是子类重新写一遍父类的同名方法,调用子类的这个方法时,执行子类重写的部分。

新建一个包package06。

(重写都是方法的重写,跟属性无关)

写一个B类:

public class B{
​
    public static void test(){
        System.out.println("B=>test");
    }
​
}

写一个A类继承B类:

public class A extends B{
​
    public static void test(){
        System.out.println("A=>test");
    }
​
}

主程序测试一下:

    public static void main(String[] args) {
        A a = new A();
        a.test();
​
        B b = new A();
        b.test();
    }

发现可以new一个子类A,定义为父类B,调用其中静态方法时,调用的是B类的方法。

将B类的static去掉,A类整个方法去掉,改为:

@Override
public void test() {
    System.out.println("A=>test");
}

快捷键Alt + Insert,生成的是默认的。

再运行主程序,发现结果不同了,两次输出的都是A=>test,

这是因为重写跟静态方法无关,而现在是非静态方法,子类重写了父类的test。

private方法无法重写。

为什么重写:

  • 父类的功能子类不需要或不满足

多态

一个对象的实际类型是确定的,如new Student,就是Student类:

Student student = new Student();

而可以指向的引用类型就不确定了:

Person person = new Student();

前提是Student继承了Person。

对象能执行哪些方法,看定义对象时左边的类型。

父类型指向子类,不能调用子类独有的方法。

多态注意事项:

  • 多态是方法的多态

  • 要存在继承关系

  • 只能是父类引用指向子类

instenceof和类型转换

instanceof判断两个类是否存在继承关系。

用上节的包,在主程序里测试:

Object object = new A();
System.out.println(object instanceof A);
System.out.println(object instanceof B);
System.out.println(object instanceof Object);

输出结果,三个true。

类型转换,跟前面的数据类型一样,在前面加个括号即可。

抽象类

abstract关键字,用来修饰类就是抽象类,用来修饰方法就是抽象方法。

public abstract class Action {
​
    //抽象方法,没有方法体
    public abstract void doSomething();
​
}

没有方法体就是留给子类实现的,相当于一个约束,

即抽象类的子类必须实现其中所有的抽象方法,除非子类也是抽象类。

抽象类特点:

  • 不能new出来,只是给子类的约束

  • 有抽象方法的类只能说是抽象类,但其中可以有普通方法

抽象类可以提高开发效率。

接口的定义与实现

  • 普通类:只有具体实现

  • 抽象类:具体实现和规范都有

  • 接口:只有规范

面向对象的精髓,就是对对象的抽象,设计模式研究的就是如何合理地抽象。

接口中的方法默认都是public abstract的,属性默认都是public static final的常量。

建立一个接口UserService:

public interface UserService {
    void add();
    void delete();
    void update();
    void query();
}

定义一个类去实现他:

public class UserServiceImpl implements UserService{
    @Override
    public void add() {
​
    }
​
    @Override
    public void delete() {
​
    }
​
    @Override
    public void update() {
​
    }
​
    @Override
    public void query() {
​
    }
}

不同于抽象类,这里使用的是implements关键字,不是继承是实现!

最大区别是一个类可以实现多个接口,用逗号分隔即可。

内部类

在类里面写一个类就行内部类了,不过分为很多种。

  • 成员内部类

  • 静态内部类

  • 局部内部类

  • 匿名内部类

成员内部类与静态内部类:

public class Outer {
​
    public void out(){
        System.out.println("外部类");
    }
​
    public class Inner1{
​
        public void in(){
            System.out.println("成员内部类");
        }
​
    }
​
    public static class Inner2{
​
        public void in(){
            System.out.println("静态内部类");
        }
​
    }
​
}

实例化:

    public static void main(String[] args) {
        Outer outer = new Outer();
        Outer.Inner1 inner1 = outer.new Inner1();
        Outer.Inner2 inner2 = new Outer.Inner2();
        outer.out();
        inner1.in();
        inner2.in();
    }

实例化内部类的方法比较特殊啊。

局部内部类就是在外部类的方法里面的类。

匿名内部类就是不把实例保存到变量中,直接new出来点方法。

异常

Error和Exception

程序运行中可能遇到一些异常,异常处理机制是让我们的程序在遇到异常时不至于崩溃。

public static void main(String[] args) {
    new Demo01().a();
}
​
public void a(){
    b();
}
public void b(){
    a();
}

程序无限循环调用,会栈溢出,出现StackOverflowError。

public static void main(String[] args) {
    System.out.println(11/0);
}

0作为除数,会出现ArithmeticException异常。

三种类型的异常:

  • 检查性异常:程序员无法预见,如用户要打开一个文件。但文件不存在。

  • 运行时异常:程序员可以预见,编译时可以被忽略。

  • 错误:错误不是异常,他是程序脱离程序员控制,如栈溢出。

Java语言设计之初就考虑到这些问题,可以将这些异常当作对象来处理,具有如下结构:

Error是由JVM生成并抛出的,大多数与代码执行者的操作无关。

运行时异常一般是程序逻辑错误导致的,程序员应尽量避免此类异常的发生。

Error和Exception的区别,Exception是可以被程序处理的。

异常的捕获和抛出

异常处理有五个关键字:

try、catch、finally、throw、throws

我们对上节除数为0的异常进行一下处理:

public static void main(String[] args) {
    try {
        System.out.println(11/0);
    }catch (ArithmeticException e){
        System.out.println("程序出现异常,被除数不能为0");
    }finally{
        System.out.println("finally");
    }
}

try下面的代码块是监控区域,后面必须跟catch,监控区域出现了catch后面括号内标明的异常类型,

则会执行catch下面的代码块。

无论是否被捕获到了异常,finally下面的代码块都会被执行,finally不是必须的,但try后面必须有catch。

上节的循环调用栈溢出错误,我们从异常结构图中可以看到,是比Throwable低的,可以用来捕获:

public static void main(String[] args) {
    try {
        new Demo01().a();
    }catch (Throwable e){
        System.out.println("发生了错误!");
    }
}

如果catch里的参数是要捕获的异常类型。

catch后面还能接catch,上面写小的类型,下面写大的类型,

这样可以在捕获的同时判断异常类型。

异常及其他一些包裹代码的快捷键:Ctrl + Alt + T。

除了捕获异常,我们还可以主动地抛出异常:

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
​
    System.out.println("请输入被除数:");
    int num1 = scanner.nextInt();
    System.out.println("请输入除数:");
    int num2 = scanner.nextInt();
​
    if(num2==0){
        throw new ArithmeticException();
    }else {
        System.out.println(num1+"/"+num2+"="+num1/num2);
    }
​
    scanner.close();
}

测试发现,报了ArithmeticException,即使将后面的除法运算注释掉,

还是会报这个错,这是因为num2等于0时我们主动抛出了这个异常。

抛出异常的为了处理和检查异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值