Java面向对象

1.对象

(1)造车:

1.画图纸

​ 定义车的属性:color,speed,seat

​ 定义车的动作:车能跑

2.拿着图纸找工厂造车

面向对象的世界里:

1.类就是图纸:

​ 属性:这一类事物拥有的共同属性

​ 动作:这一类事物共同执行的功能

2.对象:使用类创建的具体的某一个东西

对象能干什么完全取决于类是如何定义的 !

代码:

1.类:要使用class来定义

2.属性:要用成员变量来描述, 直接写在类中的变量

3.动作:成员方法,不写static就是成员方法

4.对象:类 引用 = new 类();

public class Car {
    //成员变量
    String color; //颜色
    int speed;  //速度
    int seat = 5;  //每辆车的座位都是5
    //成员方法
    public void  run(){
        System.out.println("车能跑");
    }

    //main函数是程序的入口,可以出现在任何Java类中
    public static void main(String[] args) {
        int a = 10;  //写在方法里的变量,局部变量

        //创建对象,Car是一个引用类型,它和int一样也是类型的一种
        //Java分两种数据类型:1.基本数据类型  2.引用数据类型 String和我们创建的所有类
        Car c = new Car();  //创建对象, 创建了一辆车,想要使用这俩车就得用c来访问

        //让车去跑,对象或者引用.方法() ,.表示调用,可以把它翻译成"的"
        c.run();
        c.color = "绿色";
        c.speed = 120;
        //c.pailiang = 1.5;  //图纸中没有定义的内容不可以使用

        //第二辆车
        Car c2 = new Car();
        c2.color = "红色";
        c2.speed = 180;
        System.out.println(c.color+","+c.speed+","+c.seat);
        System.out.println(c2.color+","+c2.speed+","+c2.seat);
    }
}

总结:

面向对象中:你想让事物Car有什么属性,就来写变量。你想让事物Car去做什么事,就来写方法,造了对象之后,想让它干嘛,就去调用它的方法

(2)this关键字:

this表示当前类的对象,就是当前正在执行的某个方法。

this可以在方法内部获取到对象中的属性信息

this还可以区分局部变量成员变量。(加了this.就是成员变量,若是直接写个变量他会先去找局部)

public class Car2 {
    String color;
    int speed;
    int seat = 5;

    public void run(){
        //想要获取到车的颜色和速度
        System.out.println("车能跑");
    }

    public void fly(String color){
        System.out.println(this.color+"颜色的车会飞,飞在"+color+"颜色的云彩里");
        //变量查找顺序,先找自己方法内,如果自己没有,就去this里面找
    }
    public static void main(String[] args){
        Car2 c = new Car2(); //车中的属性就是类中定义好的成员变量
        c.color = "红色";
        c.speed = 120;
        c.run();  //在调用方法的时候,Java会自动把对象传递给方法,在方法中由this来接收对象

        Car2 c2= new Car2();
        c2.color = "绿色";
        c2.speed = 180;
        c2.run();

        //this可以帮我们区分成员变量和局部变量
        Car2 c3 = new Car2();
        c3.color = "黄色";
        c3.fly("黑色");
        // System.out.println(this.color+"颜色的车会飞,飞在"+color+"颜色的云彩里");
        // 前面这个加了this他会去找对象里面的颜色,后面color没加this默认先找他自己的方法的颜色。
    }
}


2.构造

(1)大侠:

在创建对象的时候,自动调用的方法。

语法:

public 类名(传参){ }

注意:

  1. 没有返回值这一项。
  2. 在我们new的时候,自动调用构造方法。

作用:在创建对象的时候,给对象设置属性信息

Java会默认自动的送给每一个类一个无参数的构造方法,但是,如果你写了构造方法,系统就不再赠送了。

public class Car3 {
    String color;
    int speed;
    int seat = 5;

    //Java会自动赠送给每一个类一个无参数的构造方法
    //如果你自己定义了这个构造方法,那么Java就不再赠送了
    //在创建对象的时候,自动调用方法
    public Car3(String color, int speed){
        this.color = color;
        this.speed = speed; //需要用this进行初始化
    }


    public void run(){
        System.out.println(this.color+"颜色的车在跑。");
    }

    public static void main(String[] args) {
        Car3 c1 = new Car3("绿色",120);
//        c1.color = "绿色";
//        c1.speed = 120;   因为有了构造方法,可以直接在创建对象时传参,因此不需要在下面手动传参了

        Car3 c2 = new Car3("红色",180);
//        c2.color = "红色";
//        c2.speed = 180;

        c1.run();
        c2.run();
    }
}

(2)重载:

构造方法也是方法,也可以进行重载

作用:可以有更多的方式去创建对象

使用this可以访问当前类中其他的构造方法

public class DaXia {
    String name;
    String waihao;
    int age;
    String bangPai;
    
    //当构造武松有外号,而岳不群没有外号时,一个构造函数产生分歧,我们需要两个构造函数
    //第一个构造方法来满足武松,第二个构造方法来满足岳不群
    public DaXia(String name, int age, String bangPai){
        this.name = name;
        this.age = age;
        this.bangPai = bangPai;
    }

    //构造方法也是方法,他也可以重载
    //可以让我们有更多的方式可以创建对象
    public DaXia(String name, int age, String bangPai, String waihao){
        this(name, age, bangPai);//this还可以调用当前类中其他的构造方法
        this.waihao = waihao;
    }

    public static void main(String[] args) {

        //岳不群
        DaXia dx1 = new DaXia("岳不群", 18, "华山派");

        //武松
        DaXia dx2 = new DaXia("武松", 19, "梁山", "行者")


    }
}

3.封装

(1)static静态

static:静态

需要将国籍两人的国籍都改成民国。

有多少人就要改多少次, 所以能不能把国家这个属性公共出来, 当提到改国籍时只改一份就可以了

我们可以使用static静态,静态的内容在内存中时是只保留一份的,并且各个对象之间进行共享

public class Person {
    String name;
    static String country = "大清";  //它是共享的
    String address;

    public Person(String name,  String address){
        this.name = name;
        this.address = address;
    }

    public static void main(String[] args) {
        Person p1 = new Person("赵铁柱", "八大胡同");
        Person p2 = new Person("薛白","朝阳门");
        //大清亡了,需要换国籍
        //推荐使用类名去访问静态的内容
        Person.country = "民国";
    }
}

使用static将国籍改为静态:

static String country = "大清"; //它是共享的

修改时使用类名去访问静态的内容:

Person.country = "民国";

static静态特点:

1.数据共享

2.属于类的,并不属于对象

3.静态是优先于对象产生的

通用构造器,静态构造器

public class Test{
    {
        System.out.println("这里是通用构造器");
    }
    static{
        System.out.println("这里是静态构造器");
    }
    public static void main(String[] args){
        new Test();
    }
}  
/*
打印出的结果为:
这里是静态构造器
这里是通用构造器
构造方法
*/

由此可得:创建对象的过程(简单):

​ 1.先执行静态构造器

​ 2.然后执行通用构造器

​ 3.最后执行构造方法

(2)包和导包

新建包: 右键new 找到Package

//包的声明,表示当前类,从属于com.cnblogs.`ningyao-wenrou`这个包
package com.cnblogs.`ningyao-wenrou`
public class project {
    
}

使用import导入包到当前文件,必须先写package声明,再写import导入。

也就是说package必须写在有效代码的第一行。

包的本质上就是文件夹,在代码中需要写package 包名;

在自己的包里则不需要导包。

java.lang包下的东西都不需要导包:StringSystem.out.println()都属于java.lang包里.

(3)权限访问

Java里提供了四个访问权限:

​ 1.public 任何类都可以访问。适用于需要被其他任何类使用的类、方法或变量

​ 2.default 包访问权限,在自己包内可以随意访问。适用于仅希望在同一包内共享的成员

​ 3.private 私有的,只有定义该成员的类可以访问。适用于需要封装的成员,以防止外部访问

​ 4.protected只有同一个包中的类和所有子类可以访问。适用于需要在继承关系中共享的成员

package com.xyq.entity;
public class Person2 {
    
    public String pub = "public"; //公共的
    private String pri = "private"; //私有的
    String def = "default"; //包内的,默认的

    public static void main(String[] args) {
        Person2 p = new Person2;
    }
}

(4)getter和setter

成员变量使用private来声明,保护成员变量不被胡乱的赋值

Setter:主要是给成员变量赋值,做一定的保护

Getter:从成员变量中获取数据

私有变量无法访问,可以给它定义在方法里面,让别人可以调用这个方法访问给它赋值

package com.xyq.entity;

public class Person {
    //成员变量用private给私有化
    private String name;
    private int age;

    // 这个时候成员变量被private给私有化了,其它人无法访问成员变量
    // 这个时候我们可以把私有化给变成一个方法,这就是setter
    public void setName(String name){
        this.name = name;
    }
    //还可以在setter中加入逻辑判断
    public void  setAge(int age){
        if (age<0){
            this.age = 0;
        }else{
            this.age = age;
        }
    }

    //getter
    public String getName(){
        return this.name;
    }
    public int getAge(){
        return this.age;
    }
    public void chi(){
        System.out.println(this.name+"在吃东西");
    }
}

调用方法给私有变量放值和拿值

package com.xyq.entity;

public class TestPerson {
    public static void main(String[] args) {
        Person p = new Person();
        //p.name = "薛白";
        //p.age = -1;  //访问不到私有变量

        //现在调用方法就可以访问私有变量了
        p.setName("薛白");
        p.setAge(18);
        System.out.println(p.getName());
        System.out.println(p.getAge());
        p.chi();
    }
}

​ 一般情况下,我们需要用idea自动生成get和set的代码,否则很容易写错:

​ 在空白处右键 => Generate => Getter and Setter =>选中想要生成的get和set的私有变量,就可以靠idea自动生成get和set的代码。

(5)final

final表示最终的,不可以再改变的。

​ 1.被final修饰的变量不可以被改变,又被称为常量

​ 2.被final修饰的方法不可以被重写。

​ 3.被final修饰的类 不可以被继承。也就是说,被final修饰过的类不能当做父类被继承,因为子类继承后可以对父类进行扩展和修改,但被final修饰的类已经是最终版本了,不能继承后进行修改。连继承都不准!

代码 –> 不可以被改变:

public class Diamouds{
    final int weight = 10; // 用final修饰的变量
    public static void main(String[] args){
        Diamouds d = new Diamonds();
        d.weight = 5 //!!此处会报错,因为weight已经被final修饰了,10已经是他的最终的值,不可以再改变。
    }
}

代码 –> 不可以被重写

public final void bling(){   //用final修饰父类的方法
    System.out.println("不灵不灵的")
}  //这是在父类中定义的方法

//下面在子类中进行重写
public void bling(){
    System.out.println("布鲁布鲁的")  //!! 此处会报错,因为在父类中bling这个方法被final修饰了,它在父类中已经是最终的结果,不可以再改变
}

代码 –> 不可以被继承

//父类
public final class Diamonds{  //这个类被final修饰了
   ...... 
}

//子类
public class PinkDiamonds extends Diamods{ //!!此处会报错,因为父类被final修饰之后代表了最终的结果,不能被继承
    
}

总结: 就是final不可变

4.继承

(1)概念:

​ 子类可以自动拥有父类中除了私有内容外的其他所有内容。

​ 当出现x是一种y的时候,x就可以继承y

语法:

public class 类 extends 父类{
    
}

子类可以继承父类里除私有内容外的所有内容。

作用:简化代码开发

相当于子类对父类进行了扩展

(2)super关键字:

super:表示父类中的内容

语法:

System.out.println(super.name+"在吃桃子");

this:表示自己类中的内容

superthis来区分父类和子类中重名的内容

public class SunWuKong extends Hero{   //继承父类Hero
    String name = "孙大圣";
    
    public void chi(){
        System.out.println(this.name+"在吃桃子");  //这句代码会打印出孙大圣在吃桃子,name会先找自己类,然后找父类
        //想要看到父类中的name,则使用super.name
        System.out.println(super.name+"在吃桃子"); //打印出父类的name,英雄在吃桃子
    }
}

super(传参);可以还原程序,在子类构造方法的第一行,默认调用父类的构造方法

public class SunWuKong extends Hero{   //继承父类Hero
    String name = "孙大圣";
    
    public SunWuKong(){
        super(name:"武大郎")  //还原程序,在子类构造方法的第一行,默认调用父类的构造方法
        System.out.println("我是子类的构造方法");
    }

总结:

​ 1.super可以获取到父类中的内容

​ 2.可以调用父类中的构造方法,调用的时候必须写在子类构造方法的第一行,如果父类的构造是无参数的,可以不写参数;如果父类没有无参数的构造,必须要进行传参

(3)方法的重写

重写:子类对父类中提供的方法进行重新定义

语法:子类和父类中的方法声明完全一致

父类:

public class LiYuan{
    
    public void makeCountry(){
        System.out.println("李渊想建立一个自己的国家")
    }
}

子类重写:

public class LiShiMin extends LiYuan{
    //重写
    public void makeCountry(){
        super.makeCountry();  //利用super调用父类中被重写的方法
        System.out.println("李世民想建立一个自己的国家")
    }
    
    public static void main(Stirng[] args){
        LiShiMing lsm = new LiShiMing();
        lsm.makeCountry();  //若没有重写,打印出来的依旧是李渊而不是李世民
    }
    
}

重写又被成为方法的覆盖。

半覆盖:

​ 重写后,若还想调用父类中被重写的方法,可以使用super:super.makeCountry();

5.多态

(1)概念:

​ 同一个对象拥有多种形态。

(2)应用场景:

​ 假设有猫、狗、大象三种动物,三种动物都有吃东西这个方法

​ 而人要投喂这三个动物,则需要写三个方法,即:喂猫、喂狗、喂大象

​ 但有了多态之后,把猫、狗、大象的形态转化为动物,则人只需要写一个方法即可,即:喂动物

​ 有了这个想法,此时我们就可以创建一个动物类,然后让猫、狗、大象全部去继承动物类,然后在创建对象时可以用父类的变量去创建子类的对象,即:Animal ani = new Cat(); 这叫做向上转型

​ 向上转型的结果就是把猫、狗、大象当成了动物来看。

(3)作用:

​ 把不同的数据类型进行统一,让程序具有超强的可扩展性。

public class Person{
    public void feed(Animal ani){    //传参传过来的是什么动物,就让这个动物去吃
        ani.eat();    //Animal ani = new Cat();因为将一个猫对象赋值给ani,所以这一段表面上是动物在吃,但本质上仍旧是猫在吃。
    }
}

1.把子类的对象赋值给父类的变量 –> 向转型

2.把父类的变量转化为子类的变量 -> 向转型

​ 向下转型有风险,Java要求必须要写强制类型转换,即(转换后的数据类型)变量

​ 缺点: 向上转型,会屏蔽掉子类中特有的方法,因为站在动物的角度上去看一只猫,猫会抓老鼠,但是动物不一定会抓老鼠,所以这个时候需要用到向下转型,将动物转换回来猫。

向下转型

Cat cc = (Cat)ani1;

cc.catchMouse(); 此时猫又可以调用抓老鼠的方法,如果没有向下转型,直接用ani.CatchMouse();就会报错,因为站在动物的角度动物是不能抓老鼠的。

6.抽象

(1)概念:

​ 在现实中:就是不存在的东西,只通过想象出来的。

​ 在Java中:只声明,不实现。

(2)使用:

抽象方法:使用abstract来修饰,不可以有方法体,直接分号结束即可。

抽象类: 如果一个和类中有抽象方法,那么这个类也必须是一个抽象类,否则程序报错

特点:

​ 1.抽象类不可以创建对象

​ 2.抽象类的子类,必须重写父类中的构建方法。否则,子类必须也是抽象类

作用:

​ 因为子类必须重写父类的构造方法。所以可以通过抽象类可以强制的要求子类中必须有哪些方法

小知识点:抽象类里面可以有正常的方法,不一定全是抽象的方法。但只要有一个抽象的方法,那么这个类就必须是一个抽象类

(3)代码:

//在类中如果有一个抽象方法,那么这个类就必须是一个抽象类
public abstract class Animal{
    //abstract修饰方法,这个方法就是一个抽象方法,抽象方法没有方法体,直接分号结束
    public abstract void eat();
}

子类里必须重写父类里边的构建方法:

public class Cat extends Animal{ 
    public void eat(){
        //抽象方法的子类,必须重写父类里边的构建方法
    }
}

7.接口

(1)概念:

​ 1.接口实际上是一种特殊的抽象类

​ 2.接口中所有的方法都是抽象方法

​ 3.接口使用interface来声明

(2)特点:

1.能继承接口的只能是接口

2.接口和类只能是实现关系implements去继承接口

3.相同的,继承接口时也要实现父类接口的构造方法(重写和实现是一个意思,但为了区分接口和抽象类所以创建了实现这个新词)

4.接口也具有多态性: Valuable g = new Gold();

(3)使用:

类只能单继承,接口支撑多实现,其他方面接口和类一模一样。

定义接口的代码:

public interface Valuable{  //接口用interface来声明,它已经不再是类了
    
    public abstract void getMoney();
        //接口中所有的方法都是抽象方法,如果不加abstract则会报错,方法体也不能写
    
    // 接口中所有的方法都是抽象方法,所以可以省略abstract
    // 接口中所有的方法都是公开的,所以也可以省略public,也就是说前面可以啥也不写
    void getMoney();  => 这样也是可以的
    
}

继承接口的代码

public class Gold implements Valuable{  // 只能用implements来继承接口
    //继承抽象类时必须要重写父类的构造方法
    //相同的,继承接口时也要实现父类接口的构造方法(重写和实现是一个意思,但为了区分接口和抽象类所以创建了实现这个新词)
    public void getMoney(){
        System.out.println("黄金可以换钱");
	}
}

类只能但继承,但接口可以多实现:

//类可以继承一个类,实现多个接口
public class Panda excends Animal implenments Valuable, Protectable{
    //多实现,熊猫继承了动物,熊猫还实现了它是一个值钱的东西、熊猫还实现了它是一个受保护的东西
}

​ 因为继承一个类又实现了多个接口,所以要重写抽象方法时代码量很多,很容易出错,我们可以使用idea的快捷方法来快速重写父类或接口的抽象方法:

右键 => Generate… => Override Methods… => 把需要重写的内容选定点ok

(4)意义:

​ 例如熊猫可以是一个值钱的东西,金子也可以是一个值钱的东西,但是熊猫只方便继承动物类,金子方便继承金属类。也就是说熊猫是一个动物和金子是一个金属完全是两样东西,我们可以通过接口,把很多完全不想关的东西进行了统一整合。

8.内存

(1)内存分析

​ 1.堆, 主要存放对象 一般new出来的东西都存放在堆里

​ 2.栈, 局部变量以及一些基本数据类型的变量

​ 3.代码区, 放类和方法

​ 4.数据区, 常量池(一般放的字符串)和静态变量

(2)参数传递的问题

值传递:把变量的值作为参数进行传递

引用传递:直接把变量作为参数进行传递

Java使用的的值传递,这个问题在Java里特别多,很重要!

9.补充

(1)成员变量初始值:

​ Java中所有的变量都必须先声明,后赋值才能使用。

​ Java中的成员变量,在创建对象的的时候,都会执行一次初始化操作,都会给一个默认值。

​ 基本数据类型默认值都是0,包括boolean值是 => false

​ 引用数据类型是 => null表示空,什么都没有,占位

(2)object

​ 万事万物皆为对象,所有东西都是对象

​ 在Java中所有的类都要继承object

object是一个类,所有类的根,我们写的类既使不写继承关系,那么也会默认继承object,也就是说Java中默认的方法就是object中的方法。

(3)equals和双等号==的区别

​ ==: 用来判断左右两端的数据是否一致

​ equals: object类提供的一个方法,用来判断两个对象是否相等

System.out.println(c1 == c2); –> false

​ 双等号==默认判断的是两个对象的内存地址是否一致,若内存地址一样则返回真,否则都是false。换句话说双等号判断的是两只猫是否是同一只猫而不是猫的颜色或名字一样。一般用在基本数据类型上。

System.out.println(c1.equals(c2)); -> 也是false

​ equals默认情况下和双等号没区别,但是equals可以自己在子类中重写。

困扰程序员的难题:

​ 1.当使用字符串string来定义一个事物时,若两个事物一样,则两个会使用同一个内存空间,这是Java为了提高执行效率所做的改变,这是string跟基本数据类型不一样的地方。

此时因为两个字符串用的都是同一个内存地址,所以相同,打印出来的都是true

​ 2.字符串string里面是重写了equals方法的,他只会判断两个字符串的内容是否一致。

​ 而双等号判断的是地址是否相等,使用new创建对象会新建出一个字符串的空间,但它们指向的其实依旧是同一个小红。

​ 所以字符串的判断一定要用equals来判断,它可以帮我们判断内容是否相等,我们使用字符串判断时,一般都是判断字符串的内容。

(4)toString方法

​ 直接打印一个对象,相当于默认执行了toString方法

​ 默认的toString() 返回的是 包名+类名@内存地址

(5)instanceof关键字

instanceof:判断xxx对象是否是xxx类型的,就是用来判断类型的

​ 例如当一只猫向上转型成动物时,我们就可以用instanceof来判断它到底是不是一只猫:

public class Cat extends Animal{
    
    public static void main(String[] args){
        Animal ani = new Cat();
        if(ani instanceof Cat){  //用instanceof来判断ani是否是一只猫
            System.out.println("是一只猫");
        }else{
            System.out.println("不是一只猫");
        }
    }
}

10.异常

###### (1)异常处理

​ 1.异常: 异常是运行时的错误,程序的编译是通过的,但是运行却出了异常

​ 2.抛异常: 创建一个错误对象,把错误对象丢出来

​ 3.捕获异常: 默认由JVM来把错误信息进行捕获,打印出来。JVM会终止程序的执行

(2)异常分类

Throwable(所有异常的根)

​ 1.Error(系统级的错误,一般出了Error都是大事)

​ 2.Exception(异常)

​ 1.RuntimeException :运行时异常

一大堆Exception

​ 2.其他Exception:正常的一个错误,必须手动去处理,不然编译器通不过

(3)try…catch

try{
    尝试执行可能会出现异常的代码
}catch(Exception e){
    处理异常的代码
}finally{
    最终的,出不出错都要执行它,它可以把文件的一个打开状态直接关上,非常有用,建议使用。
    否则不把文件打开状态关闭会一直占用内存空间。
}

(4)throws和throw

抛异常处理

1.throws表示方法准备要扔出来一个异常

​ 产生的错误尽可能的自己处理,少向外抛出异常

2.throw表示向外抛出异常

throws:

public class Example {
    public void readFile() throws IOException {
        // 模拟文件读取操作,可能抛出IOException
        throw new IOException("文件读取错误");
    }

    public static void main(String[] args) {
        Example example = new Example();
        try {
            example.readFile(); // 需要处理IOException
        } catch (IOException e) {
            System.out.println("捕获到异常: " + e.getMessage());
        }
    }
}

throw:

public class Example {
    public void checkAge(int age) {
        if (age < 18) {
            throw new IllegalArgumentException("年龄必须大于或等于18");
        }
        System.out.println("年龄符合要求");
    }

    public static void main(String[] args) {
        Example example = new Example();
        example.checkAge(16); // 这将抛出IllegalArgumentException
    }
}

(5)自定义异常

在Java中,自定义异常类的创建通常是通过扩展现有的异常类来实现。自定义异常可以帮助你处理特定于应用程序的错误情况。以下是自定义异常的基本语法和示例。

  1. 创建一个类:创建一个新类,用extends关键字继承自Exception类(或其子类),以创建检查型异常;如果不需要检查型异常,可以继承自RuntimeException类。
  2. 构造方法:为异常类添加构造方法,通常包括无参构造、包含错误信息的构造,以及包含错误信息和错误原因的构造。

下面是一个简单的自定义异常类的示例:

javaCopy Code// 自定义异常类
public class CustomException extends Exception {
    // 无参构造
    public CustomException() {
        super("默认错误信息");
    }

    // 带错误信息的构造
    public CustomException(String message) {
        super(message);
    }

    // 带错误信息和原因的构造
    public CustomException(String message, Throwable cause) {
        super(message, cause);
    }
}

创建自定义异常后,可以在方法中抛出该异常,并在调用该方法时处理它。

javaCopy Codepublic class Example {
    // 一个可能抛出自定义异常的方法
    public void doSomething(int value) throws CustomException {
        if (value < 0) {
            throw new CustomException("值不能小于0");
        }
        System.out.println("值是: " + value);
    }

    public static void main(String[] args) {
        Example example = new Example();
        try {
            example.doSomething(-1); // 这将抛出CustomException
        } catch (CustomException e) {
            System.out.println("捕获到异常: " + e.getMessage());
        }
    }
}