面向对象的学习笔记(Java)

218 阅读14分钟

1.区别

面向过程

优点:容易理解

缺点:代码量越大越难维护

面向对象:

优点:可扩展性强,易维护

缺点:复杂,难理解

2.this关键字

(以下例子和部分截图均来自B站“老男孩IT教育”的视频)

  • main方法中默认把类的实例化对象传给了实例方法(run),在实例方法(run)中用this表示,打印对象c的地址和实例方法中打印this的地址是相同的。如下:

image-20211214152137586.png

  • 另外注意点:先赋值实例变量再调用方法先调用方法再给实例变量赋值 结果是不一样的(效果如下图)。

image-20211214153241961.png

  • this可以帮我们区分实例变量和局部变量:

  • this省略不写时,先找方法里面的局部变量中同名的变量,方法中没有局部变量则默认是找实例变量

image-20211214154528328.png

image-20211214154621651.png

  • 所以,想明确调用实例变量时,应该得加上this更好,代码可读性也更好(易于区分局部变量和实例变量)

  • 另外,如果方法中不定义局部变量,而是在参数中传过来一个跟实例变量同名的变量,没有this时,则这个变量调用的是参数中的变量。(但是加上this的话他就是指向实例变量)

image-20211214155003975.png

3.构造方法

3.1新建构造方法

(用alt+insert 可以选择有参,无参构造方法)

实例化对象时(new 的时候)自动调用的方法

3.2语法特点:

public 同类名(传参(实例变量)){

​ this.实例变量 = 参数;

​ ...

}

3.3小提示:

(只有构造方法的public之后是不用指定返回类型的)

java会自动为每个类建一个看不到的无参构造方法(只有编译之后打开class文件才能看到),如果自己建了一个有参构造方法,在new一个对象时,java则不会帮我们新建无参构造方法,可以自己手动创建。

3.4 重载,this()

  • 有参构造方法也是方法,它也可以重载

  • 当两个构造方法有很多相同参数时,可以在一个构造方法中调用另一个构造方法,用 this(参数) 就可以调用该类中其他构造方法。(如下图)

  • 而 this() 则是调用无参构造方法。

image-20211214165422454.png

4.static

4.1使用

  • static修饰的变量是类变量,修饰的方法就是类方法,修饰之后虽然可以用该类的对象来调用,修改其值,但是一般用 类名. 变量名/方法 来调用。(类名.变量名/方法 不能调用非static,会报错)

image-20211214173629918.png

  • tip:它在类加载的时候最先加载,所以它的加载会先于new类对象的加载

4.2总结static的特点

  • 数据共享,每个类对象都共用同一个static修饰的类变量/方法,

  • 属于类,不属于对象

  • 优先于非static变量/方法的加载,所以也优先于对象的产生。

  • static(static方法/static代码块/main方法)中都不能用"this." 来调用变量/方法

  • 在实例化一个类对象时以下三者的加载顺序:

    先加载static静态代码块(静态构造器) 、静态方法、静态变量

    再加载匿名代码块(通用构造器),如下格式:

    {

    ​ ...(代码)...

    }

    后加载构造方法。

    tip:①在类加载全过程只执行一次,②和③会在每次new一个对象时执行。(效果如下图控制台输出)

image-20211214201428812.png

  • 因此,在static(静态)的代码块/方法(包括main方法)中不能调用this.非static的部分。(因为"this"关键字是在加载非静态资源的时候才有的,所以在还没加载出来的时候就去调用它肯定会报错)

image-20211214205112658.png

(在method2中则可以通过 类名.method1()或者 this.method1()或者method1()调用method1)

  • 若想在本类中调用static方法,可以直接写方法名,省略"this." 或者 "类名." ,但是想在其他类中的方法调用该类的static方法,则需要在其他类的方法中调用时加上该类的 "类名."(前提是方法有public修饰) 。所以,为了统一,一般在自己类调用自己的static方法时也用"类名." 或者"this."。

image-20211214220858573.png

因为疏忽,上图的

public static void method1(){
    method1();
}

会导致无限递归,不能这样写

5 访问权限

5.1 public

无论哪个包哪个类都可以访问到。

5.2 private

除了自己类里面, 无论哪个包哪个类都不可以访问。

5.3 default

只有完全相同的包路径(同文件夹)下才能访问到,就算子继承父,他们不在同一包路径下,子也不能访问父的默认变量。

image-20211215162141928.png

image-20211215162501016.png

6 Getter,Setter

是封装的核心

一个类的所有属性都用私有private修饰,对外只提供Get和Set方法,所以其他类只能通过这些方法来修改此类中的属性,不能直接访问,保护了这些属性。同时,这个类的每个对象都有对应自己的空间,一个对象通过Setter定义了值不会影响其他对象下这个属性的值(如下图)。而这个类一般就是我们熟悉的实体类。

image-20211215164549569.png

因为在前面static部分可以知道,static修饰的变量都指向同一个地址,所以如果对同一个static变量进行set方法赋值,最终其值都是最后一次调用Setter时所设置的值。(如下图,两个对象输出的sta变量的值相同)

image-20211215175914005.png

7 继承

所有类都继承Object类。

7.1特点

子类对象能访问常量final、公共public变量/方法、保护protected变量/方法、默认default变量/方法、static静态变量/方法,不能访问私有private变量/方法。

即非私有的变量/方法都能访问(继承)。

image-20211215174024171.png

子类中通过 "super."能访问非私有变量/方法 和 非静态变量/方法。(static部分直接用类名.就可以访问)

image-20211215174052232.png

7.2 回顾static:

​ 虽然可以用 "对象." 来调用static 的东西,但不建议。因为static属于类,1个类中的static的东西只有一个地址,所以无论多少个对象去调用它,也都只对应一个地址。就跟类一样,只有一个,所以一般都用"类名."来调用static的东西。

​ 而且编程工具中也不会推荐用"对象."来调用静态的东西,因为当你输入"对象."时是不会提示static的部分给你选择的。

7.3 重写的东西的访问权限

子类重写时修饰符范围(public,protected,default)(访问权限)可以在父类以上,也可以相同,但不能在父类以下。

重写范围:public>protected>default

子类重写的范围可以放大父类的,不能缩小。

  • 抛出异常的范围则相反,可以被缩小,不能被放大。

8 super关键字

  • 跟this的联系

在子类中的方法中使用"this."调用变量,如果子类中没有这个同名变量,则调用父类中的变量,否则调用子类里的。

所以为了更好区分,一般调用父类的东西就用"super."来调用。

image-20211216114826571.png

子类在加载时先加载隐含的父类对象(super()调用父类的无参构造方法),后加载子类对象(运行子类的构造方法),先后顺序是

public 子类名(){

​ super() //调用父类构造方法,加载父类对象

​ ...然后才是自己的构造方法... //加载子类对象

}

(所以父类的构造方法一定在子类构造方法的最前面执行),此时this可以调用子类的东西,super可以调用父类的东西。

  • 补充:如果父类中写了有参构造方法,则java不会隐含提供无参构造方法,需要在子类的构造方法中最前面显式写出父类的有参构造方法。另外super.还可以调用父类被子类重写了的方法。

9 多态

9.1 概念

同一对象拥有多种形态

9.2 作用

把不同数据类型进行统一

9.3 特点

父类的引用指向子类对象 (父类 xxx = new 子类() )

上面 xxx 可以调用子类重写父类的非静态方法,可以调用父类所有属性和静态方法,但不能直接调用子类独有方法,必须先强转为子类 ((子类)xxx).子类独有方法(); 才能这样调用到。

↑解释
在进行 父类 xxx = new 子类()时
是低转高(子转父),由于子类已经继承了父类的所有,所以此时子类把自己独有的方法删除后,自然而然就可以转化为父类对象。
而当 xxx调用子类独有的方法时,则需要重新开辟一个只属于子类的空间为((子类)xxx),所以需要强制转,此时((子类)xxx)相当于开辟了一个子类对象的空间,用它就能调用子类独有的方法。

因此,父类引用指向子类对象的变量xxx 不能直接调用子类独有方法

9.4 代码:

父类 Father.java

public class Father {
    public String name = "fathername";
    public static String name2 = "fathername2";

    public void  run(){
        System.out.println("f run");
    }

    public static void run2(){
        System.out.println("f static run2");
    }

    public void fathermethod(){
        System.out.println("f method");
    }
}

子类 Son.java

public class Son extends Father{
   public String name = "sonname";
    public static String name2 = "sonname2";
    
    public void  run(){
        System.out.println("s run");
    }

    public static void run2(){
        System.out.println("s static run2");
    }

    public void sonmethod(){
        System.out.println("子类独有方法");
    }
}

主程序类 Application.java

public class Application {
    public static void main(String[] args) {
        Father f = new Son();

        f.run();                     //调用的是子类重写的run方法(非static方法看右边)
        f.run2();                          //父类的run2(static方法看左边)
        System.out.println(f.name2);       //父类的静态属性name2(属性看左边)
        System.out.println(f.name); 	   //父类非静态属性name (属性看左边)

        ((Son) f).sonmethod();      //调用子类独有的方法(非static方法看右边)
        							//等价于Son xxx = ((Son)f); xxx.xonmethod();
        f.fathermethod();           //调用子类中继承的,只是没重写,所有结果为f method(非static方法看右边)
        
		System.out.println("------------------------------");
        
        /* 
        Father father = new Father();
        	这块注解的代码运行都报错
        ((Son)father).run();
        ((Son)father).fathermethod();
        ((Son)father).sonmethod();
        System.out.println(((Son) father).name);
            所以父类引用指向父类对象的变量强转后也不能调用子类的东西。
         */
        Son s = new Son();
        ((Father)s).run();      //还是子类重写的(非static方法看右边)
        Father b = ((Father) s);
        System.out.println(b.name);  //父类的(属性看左边)
        s.run();                       //非static方法看右边, 右边是new Son 所以打印结果也是Son里重写的
        s.run2();                     //子类的run2(static方法看左边)
        System.out.println(s.name2);//子类的name2(属性看左边)
        System.out.println(s.name);//子类重写的 (属性看左边)
    }
}

9.5 运行结果

image-20211217122537337.png

9.6 总结

父类引用指向子类对象的变量调用 静态方法和所有属性时看左边(父类),只有调用 非静态方法时看右边(某子类)。

所以 “父 xxx = new 子() ”中的xxx,简单说就是一个去掉子类独有方法的子类对象。

10 关于子类能不能重写父类静态方法

不能

10.1 解释

(先看代码)

父类 Father.java:

public class Father {

    public static String test = "父类static变量";

    public static void test(){
        System.out.println("这是父类 public static void方法");
    }

}
复制代码

子类 Son.java 继承父类

public class Son  extends Father {
    public static String test = "子类static变量";

    public static void test(){
        System.out.println("子类static方法");
    }

}
复制代码

二儿子 Son2.java 也继承父类

public class Son2 extends Father {

    public static String test = "二儿子static变量";

    public static void test() {
        System.out.println("二儿子static方法");
    }
}
复制代码

主程序类 Application.java:

public class Application {
    public static void main(String[] args) {
        Father father = new Father();
        Son son = new Son();
        Son2 son2 = new Son2();

        System.out.println(father.test);
        System.out.println(son.test);
        System.out.println(son2.test);
        System.out.println("------------------------------");
        father.test();
        son.test();
        son2.test();

        Father.test();
    }
}
复制代码

输出结果:

image.png

10.2 分析

上面子类看似重写了父类的static方法,但其实是各自都开辟了一个空间,各自有各自的方法体(前面博客有说到,静态的东西都是属于类的,一个类只有一个空间专门放静态的东西),所以输出的结果不同。

但是子类不"重写"父类static方法时会继承,验证过了,在主程序类中可以用子类对象直接调用父类的静态变量和方法,打印的就是父类写的内容。

10.3 总结

所以子类可以继承父类的静态方法,但不能重写,之所以显示不同结果,是因为子类也开辟了一个空间去放自己的静态变量和方法,此时相当于写了一个子类独有的方法,只是它是用static修饰的。

关于这一点的证明:在子类中,在"重写"的static方法前面加注解 @Override,此时会直接编译报错:

image.png

翻译为“方法不重写其超类中的方法”,大意就是该方法没有从父类方法中重写。 所以说明这个方法不是在重写,而是子类自己的,独有的,只是跟父类那个static方法(test)同名了而已。

11 final关键字

  • final可以修饰变量

    被final修饰的变量不可用改变,称为常量

  • final也可以修饰方法

    父类中final修饰的方法不能被子类重写

  • final还可以修饰类

    被final修饰的类不可以被继承

12 抽象abstract

① 不能直接new一个抽象类的对象,只能new一个继承了抽象类的子类对象。

格式:          (继承了抽象类的)子类 xxx = new 子类();(继承了抽象类的子类)

或者像多态那样   父类(父类是抽象类)  xxx = new 子类();(继承了抽象类的子类)

②抽象类中的抽象方法只能写 (访问权限) abstract (返回类型) (方法名)(); ——>没有方法体!

(方法体的具体实现,交给继承它的子类来写。)

抽象类中可以写普通方法,但是普通类不能写抽象方法,写了抽象方法的必须是抽象类

④ 抽象类也有无参构造方法。

image-20211217131605289.png

抽象类初学用的少,一般用接口,到了架构师阶段对抽象类才有进一步了解

13 接口

能继承接口的只能是接口,接口和类只能是实现关系

接口也具有多态性

接口所有变量都默认是用public static final修饰的,所以都是全局静态常量。

接口的方法默认都是public abstract 修饰,只需要写 返回类型 方法名(); 即可定义一个方法

用抽象类去实现接口后,抽象类可以不用重写接口中的方法,交给继承抽象类的子类去重写(子类不重写会报错)

14 成员变量初始值

变量默认值
booleanfalse
char'\u0000' (null),将这个char强转int后初始值是0
byte(byte)0
short(short)0
int0
long0L
float0.0f
double0.0d
Stringnull

类引用的变量(只有"类名 xxx",不给""= new 类名()"时的xxx)跟String一样,初始值为null

如果指向了具体的实例化对象( = new 类名() )后,它的初始值是一个地址。

image-20211218114057358.png

15 equals和==

15.1 变量

基本类型(boolean,byte,char,short,int,long,float,double)只有==能比较两变量是否相等,没有equals方法。

15.2 对象

放在 x.equals(y) 前面的x没实例化——报错,前面x有实例化,y无论有没有实例化——false

15.2 字符串string

==比较地址,.equals比较值

  • 用String strx = “xxx(值)”赋值

    str1 == str2 比较:

    两个都有初始值且初始值相同——true**(地址相同,说明2个字符串对象指向同一个地址,因为两者的值相同,所以就用一个地址来放这个值(java节约资源的机制),然后让两个string对象都指向这个地址)**;其中一个没有赋初值(静态字符串)——false;初始值不同——false。

    str1.equals(str2) 比较:

    两个都有初值且相同——true;不相同——false(比较字符串的值相不相等);str1没有给初值——报错。

image-20211218123945541.png

  • 用String strx = new String("xxx(值)")赋值

    str3 == str4 比较:

    两个无论有没有初始值,初始值相不相等都是false**(因为String对象地址不同,虽然两者的值所在地址是相同的,但是他们都有new 一个地址来存入 str,所以两个str指向的地址是不同的,先指向这个地址,这个地址再指向值所在地址,而比较的时候是比较这个两个str所指向的地址而不是值所在地址,所以地址不同,结果为false)**

    str3.equals(str4)比较:

    两个都有初始值且相同——true;不同——false(equals方法比较字符串的值);str3不给初值——报错。

image-20211218123807684.png

对象的equals方法跟==作用一样,只有字符串String的equals方法是比较值,因为String类继承了Object之后重写了里面的equals方法,将其重写为判断两个字符串的内容(值)是否一致,所以两个字符串一般用equals来判断内容。

所以,字符串String的== 比较地址,equals方法比较值

16 toString

image-20211218210013165.png

17 持续更新中。。。