Java面向对象编程-高级篇

Java面向对象编程-高级篇,第1张

Java面向对象编程-高级篇 1. 类变量(静态变量)

语法:访问修饰符 static 数据类型 变量名static 访问修饰符 数据类型 变量名如何访问:类名.类变量名或者对象名.类变量名。前提是满足访问修饰符的访问权限和范围static变量是同一个类所有对象共享static变量在类加载的时候就生成了,所以即使没有创建对象实例也可以访问。其生命周期随着类的加载开始,类的消亡而销毁


2. 类方法(静态方法)

当方法不涉及到任何和对象相关的成员,可以将该方法设计为静态方法,提高开发效率(不想创建对象但是又想调用某个方法,比如各种工具类中存在许多静态方法)类方法是随着类加载而加载,将结构信息存储在方法区(所以类方法中无this参数)类方法中不允许使用和对象有关的关键字,比如this和super类方法只能访问静态成员(类变量和类方法),要访问非静态成员要创建该类的实例对象后再通过该对象去访问

tips:

类方法只能访问静态成员不是说类方法中只能存在静态成员,而是说如果不通过创建对象来直接调用(不是b.sum()的形式)的话只能直接调用当前类的静态成员


3. main方法

对于public static void main(String[] args){}:

main方法是Java虚拟机调用的

Java虚拟机需要调用类的main方法,所以访问权限必须为public

Java虚拟机在执行main方法时不必创建对象,所以该方法为静态方法

String[] args的参数是在Java执行程序时传入,比如java hello psj psw表示执行hello文件并传入参数psj和psw


4. 代码块

概念:属于类中的成员,但是没有方法名、返回以及参数,只有方法体。所以不通过对象或类显示调用,而是加载类时或创建对象时隐式调用基本语法:[修饰符]{—代码—};修饰符可选,要写只能写static优点:

相当于另一种形式的构造器(对构造器的补充),可以做初始化的 *** 作如果多个构造器有重复的语句,可以抽取到代码块中 注意:

static代码块随着类的加载而执行,且只会执行一次。对于普通代码块,每创建一个对象就执行一次创建一个对象时,在类中的调用顺序:

调用静态代码块和静态属性的初始化(两个的优先级一样,谁先定义谁先被调用)调用普通代码块和普通属性的初始化(两个的优先级一样,谁先定义谁先被调用)调用构造方法 构造器前面其实隐含了super()和调用普通代码块,所以普通代码块优先于构造器。而静态代码块以及属性初始化在类记载时就执行完毕,所以优先于普通代码块和构造器静态代码块只能直接调用(比如n1是静态成员,可以直接访问,也可以类名.n1或者对象名.n1;但是n2是普通成员,只能类名.n2或者对象名.n2)静态成员,普通代码块可以调用任何成员

static {
    System.out.println(i);  
    System.out.println(base.i);
    System.out.println(j);  // 报错
}
{
    System.out.println(i);
    System.out.println(j);
}

tips:

创建对象时会先调用代码块的内容再去调用构造器类什么时候被加载?

创建对象实例时创建子类对象实例时,父类也会被加载,且父类会先被加载使用类的静态成员时普通代码块在new对象时才被调用,和类加载是没关系的,如果直接执行如类.静态成员是不会执行普通代码块的 在执行super()时,不是只执行父类构造器显示的内容,还是会先执行父类的静态/普通代码块和静态/普通属性初始化

在下面代码中,执行顺序:父类的静态属性和静态代码块(因为在加载子类时会先加载父类,而静态代码块和静态属性会随着父类的加载执行) ------> 子类的静态属性和静态代码块 ------> 父类的普通属性和普通代码块 ------> 父类的构造方法 ------> 子类的普通属性和普通代码块 ------> 子类的构造方法

前面两个是因为类的加载(类的加载是先加载父类后加载子类)中间两个是因为调用了隐含的super(),而父类构造器同样会有隐含的super()和调用普通代码块步骤倒数第二个是隐含的调用普通代码块步骤最后完成子类的构造方法

base b = new Sub();
// 输出内容如下:
//1000
//father static codeblock
//20
//son static codeblock
//10
//father codeblock
//father
//2000
//son codeblock
//son
// ---------------类---------------
class base {
    static int i = 1000;
    int j = 10;

    static {
        System.out.println(i);
        System.out.println("father static codeblock");
    }

    {
        System.out.println(j);
        System.out.println("father codeblock");
    }

    public base() {
        System.out.println("father");
    }

}

class Sub extends base {
    public static int i = 20;
    int j = 2000;

    static {
        System.out.println(i);
        System.out.println("son static codeblock");
    }

    {
        System.out.println(j);
        System.out.println("son codeblock");
    }

    public Sub() {
        System.out.println("son");
    }
}

5.静态成员使用-单例设计模式

概念:采取一定的方法保证在整个软件系统中,对某一类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法饿汉式:

    将构造器私有化,防止直接new对象类的内部创建对象向外暴露一个静态的公共方法
SingleTon instance1 = SingleTon.getInstance();
SingleTon instance2 = SingleTon.getInstance();
System.out.println(instance1 == instance2);  // true,因为静态成员在随着类的加载而加载,且只加载一次
// -------------类---------------
class SingleTon{
    // 1. 将构造器私有化
    private SingleTon(){ }

    // 2. 类的内部创建对象
    private static SingleTon instance = new SingleTon();

    // 3. 向外暴露一个静态的公共方法
    public static SingleTon getInstance(){
        return instance;
    }
}

懒汉式:

    将构造器私有化,防止直接new对象定义但是不创建静态属性对象向外暴露一个静态的公共方法
SingleTon instance1 = SingleTon.getInstance();
SingleTon instance2 = SingleTon.getInstance();
// true,因为第二次调用getIntance方法判断到instance!=null,就不会再创建对象,直接返回创建好的instance
System.out.println(instance1 == instance2);  
// -------------类---------------
class SingleTon{
    // 1. 将构造器私有化
    private SingleTon(){ }

    // 2. 定义一个静态属性对象,此处不创建对象
    private static SingleTon instance;

    // 3. 向外暴露一个静态的公共方法
    public static SingleTon getInstance(){
        if (instance == null){
            SingleTon instance = new SingleTon();
        }
        return instance;
    }
}

区别:

创建对象时机不同:饿汉式在类加载时就创建了对象,懒汉式在使用时才创建饿汉式不存在线程安全问题,懒汉式存在该问题

tips:

为什么称为”饿汉“:比如在类中有其他静态属性n1,使用SingleTon.n1后,并没有使用对象instance,但是因为静态成员随着类的加载而加载,还是创建了该对象(不管一个人想不想吃东西都把吃的先买好,如同饿怕了一样),造成资源的浪费为什么称为”懒汉“:当程序需要这个实例的时候才去创建对象,就如同一个人懒的饿到不行了才去吃东西如果一个对象使用频率不高且占用内存还特别大,明显就不合适用饿汉式java.lang.Runtime类是典型的单例模式


6.final关键字

final可以修饰类、属性、方法和局部变量

使用场景:

不希望类被继承不希望父类的某个方法被子类覆盖不希望类的某个属性的值被修改不希望某个局部变量被修改(被final修饰后还是称为局部变量)

注意事项:

final修饰的属性又称为常量,一般用xx_xx_xx命名final修饰的属性在定义时必须赋初值,且之后不能再修改,赋值可以在以下位置:

定义时赋值,如public final double TAX_RATE = 0.07定义后在构造器赋值定义后在代码块赋值 final修饰的属性是静态时,初始化的位置只能是定义时或者静态代码块中,不能在构造器中赋值final类不能继承,但是可以实例化对象如果类不是final类,但是含有final方法,则该方法不能被重写,但是可以被继承(可以被子类使用)一个类已经是final类就没有必要将该类中的方法修饰为final(类都无法被继承如何重写方法)final不能修饰构造方法final和static往往一起使用,效率更高(底层编译器做了优化,不会导致类的加载)

// System.out.println(Final_.NUM_TEST_1);  // 会执行static代码块,即类被加载
System.out.println(Final_.NUM_TEST_2);  // 直接打印200,即类不会被加载
// --------------类------------------
class Final_{
    public static int NUM_TEST_1 = 100;
    public final static int NUM_TEST_2 = 200;
    static {
        System.out.println("类被加载");
    }
}

包装类和String类都是final类


7.抽象类

当父类的某些方法需要声明,但是不确定如何实现时,可以将其声明为抽象方法,这个类就是抽象类,该方法没有方法体

注意事项:

抽象类不能被实例化

抽象类可以没有抽象方法,并且还可以有非抽象方法

当一个类中存在抽象方法时,需要将该类声明为abstract类

abstract只能修饰类和方法,不能修饰属性和其他

抽象类可以有任意成员(抽象类还是类)

一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为抽象类

抽象方法不能使用private、final和static(和类相关,没有对象所以不存在重写)修饰,因为这些关键字都是和重写违背

tips:

static为什么不能和abstract一起使用?抽象类不可以被实例化,所以没有内存,而static方法在实例化之前就已经被分配了内存,相互矛盾定义抽象类是要把它被子类继承,然后在子类中重写抽象类中的方法,但是重写只适用于实例方法,不能用于静态方法


8.抽象类使用场景-模板设计模式
A a = new A();
a.calculateTime();
B b = new B();
b.calculateTime();
// ---------类----------
abstract class Abstract_ {
 abstract void job();

    void calculateTime() {
     long start = System.currentTimeMillis();
        job();  // 动态绑定机制
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
   }
   
class A extends Abstract_ {

 @Override
 void job() {
        System.out.println("A实现job方法");
    }
   }
   
class B extends Abstract_{

 @Override
 void job() {
        System.out.println("B实现job方法");
    }
   }

在执行a.calculateTime()时是运用到动态绑定机制的。首先直到编译类型和运行类型都是A类,所以执行该方法时,由于子类A没有该方法就去父类找该方法,在执行其中的job方法时就运用到动态绑定机制,去执行运行类型A类中的job方法


9.接口

快速入门:现实生活中,可以把手机、相机等设备插入电脑的usb接口上让电脑去识别设备,而usb接口由于制定了统一的标准所以不用管哪个口接哪个设备

// -------接口---------
public interface USBInterface {
    // 制定统一规范
    public void start();
    public void stop();
}
// ------手机类------
public class Phone implements USBInterface {
    // 手机要插入USB接口就要满足接口制定的标准
    @Override
    public void start() {
        System.out.println("手机插入");
    }

    @Override
    public void stop() {
        System.out.println("手机拔出");
    }
}
// ------相机类------
public class Camera implements USBInterface {
    @Override
    public void start() {
        System.out.println("相机插入");
    }

    @Override
    public void stop() {
        System.out.println("相机拔出");
    }
}
// ------电脑类---------
public class Computer {
    public void work(USBInterface usbInterface){
        // ***通过接口来调用方法***
        usbInterface.start();
        usbInterface.stop();
    }
}
// -------main--------
Phone phone = new Phone();
Camera camera = new Camera();
Computer computer = new Computer();
computer.work(phone);  // 把手机接入到电脑
computer.work(camera);  // 把相机接入到电脑

概念:接口就是将出没有实现的方法封装到一起(接口也可以包含属性),某个类需要使用时就根据具体情况实现注意事项:

接口不能被实例化接口中的所有方法是public方法且都是抽象方法,可以省略public和abstract关键字普通类实现接口必须实现接口所有方法,抽象类可以不用实现接口方法一个类可以同时实现多个接口,之间用逗号连接接口中的属性是public static final修饰的,所以必须要初始化,访问接口属性使用接口名.属性名接口不能继承类,但是可以继承多个接口接口的修饰符只能是public和默认,和类的修饰符一致 实现接口 vs 继承类:

继承解决代码的复用性和可维护性,接口在于设计各种规范继承满足is-a关系,接口满足like-a的关系,所以接口更加灵活。比如小猴子继承父类爬树的功能,又通过实现鱼的接口实现了游泳功能,即小猴子是猴子所以可以爬树,并且像一条鱼可以游泳接口一定程度上实现代码解耦子类继承父类就拥有父类的功能,当子类需要扩展功能就通过实现接口(实现接口是对Java单继承机制的一种补充) 接口的多态特性:

多态参数,在上述代码中work方法接受了实现USBInterface接口的类的对象实例,如果使用USBInterface ui = new Phone()也是没问题的多态传递:

CC cc = new CC();
AIF aif = cc;
aif.f1();  //打印"执行f1"
BIF bif = new CC();
bif.f1();  //打印"执行f1"
// -----------接口和类--------------
interface AIF{
    void f1();
}
interface BIF extends AIF{}
class BB implements BIF{
    @Override
    public void f1() {
        System.out.println("执行f1");
    }
}
class CC extends BB{}

tips:

JDK7.0之前接口中所有方法没有方法体,即都是抽象方法;JDK8.0之后可以有静态方法和默认方法(加上default关键字)使用了接口的类可以使用接口中的属性,还是可以有自己的属性和方法,并不是只能包含接口定义的方法一个文件中只能有一个public修饰的接口或者类,如果一个类为public,同一个文件中就不能有public修饰的接口接口和父类属性命名一样时会出现冲突,如下代码:

interface AA{
    int x = 0;
}
class BB{
    int x = 1;
}
class CC extends BB implements AA{
    void test(){
        System.out.println(x);  // 报错,因为x有两个定义
    }
}

10.内部类

概念:一个类的内部完整嵌套了另一个类结构,被嵌套的类称为内部类(和正常类的结构一样),包含内部类的类为外部类

分类:

定义在外部类局部位置上的(比如方法内或者代码块中):

局部内部类(有类名)匿名内部类(没有类名)* 定义在外部类的成员位置上:

成员内部类(没有static修饰)静态内部类(使用static修饰)

局部内部类:

可以直接访问外部类的所有成员(包括私有的)

不能添加访问修饰符(内部类就是一个局部变量,局部变量不能使用访问修饰符),但是可以使用非访问修饰符final

作用域仅仅在定义它的方法或代码块中(内部类就是一个局部变量)

外部类在定义内部类的方法中创建内部类实例即可访问局部内部类的成员

如果外部类和局部内部类的成员重名时,遵循就近原则。要想访问外部类的成员,可以使用外部类名.this.成员访问

Outer01 outer01 = new Outer01();
outer01.m1();  // 先打印20,后打印10
// ---------------类-----------------
class Outer01{
    private int n1 = 10;

    public void m1(){
        class Inner01{
            private int n1 = 20;
            public void m2(){
                System.out.println(n1);
                System.out.println(Outer01.this.n1);
            }
        }
        Inner01 inner01 = new Inner01();
        inner01.m2();
    }
}

匿名内部类:

基本语法:new 类/接口(参数列表) { 类体 };

基于接口的匿名内部类:想要使用某接口并创建对象,传统方法是写一个类,实现该接口并创建对象,但是如果只使用一次该类会导致开发过程繁琐。为了简化开发,可以将代码该为:

// ---------类和接口----------
class Outer01{
    private int n1 = 10;

    public void m1(){
        IA ia = new IA() {

            @Override
            public void m3() {
                System.out.println("基于接口的匿名内部类");
            }
        };
        ia.m3();
        System.out.println(ia.getClass());  // class com.atguigu.java.Outer01
    }
}

interface IA{
    void m3();
}

​ 此时对象ia的运行类型和编译类型分别是什么?编译类型是接口IA,运行类型为匿名内部类Outer01$1。为什么IA ia = new IA(){...};后运行类型为匿名内部类?其实底层实现如下:

// 1. 先创建匿名内部类,名为Outer01(外部类名+$+数字)
class Outer01 implements IA{
    @Override
    public void m3() {
        System.out.println("基于接口的匿名内部类");
    }
}
// 2. 创建对象实例并把地址返回给ia
IA ia = new Outer01();

基于类的匿名内部类:和基于接口的匿名内部类类似,注意和Father f = new Father();不同,该匿名内部类的运行类型并不是Father类,而是Outer01$1,因为底层实现为class Outer01$1 extends IA{...}

class Outer01{
    private int n1 = 10;

    public void m1(){
        Father f = new Father() {
            // 如果要重写
            @Override
            public void m3() {
                System.out.println("基于类的匿名内部类");
            }
        };
        f.m3();
        System.out.println(f.getClass());  // class com.atguigu.java.Outer01
    }
}

class Father{
    public void m3(){}
}

注意事项:

匿名内部类既是一个类,也是一个对象,所以调用匿名内部类中的方法有如下两种方式:

Father f = new Father() {
    @Override
    public void m3() {
        System.out.println("基于类的匿名内部类");
    }
};
// 方式1
f.m3();
// 方式2
new Father(){
    public void m2() {
        System.out.println("基于类的匿名内部类12");
    }
}.m2();

可以直接访问外部类的所有成员(包括私有的)不能添加访问修饰符(匿名内部类就是一个局部变量,局部变量不能使用访问修饰符)作用域仅仅在定义它的方法或代码块中(匿名内部类就是一个局部变量)如果外部类和局部内部类的成员重名时,遵循就近原则。要想访问外部类的成员,可以使用外部类名.this.成员访问

实践场景:

// -------main中使用匿名内部类----------
Outer01 outer01 = new Outer01();
outer01.f1(new IF() {
    @Override
    public void show() {
        System.out.println("匿名内部类实践场景");
    }
});
// -------------类和接口--------------
class Outer01{
    public void f1(IF i){
        i.show();
    }
}
interface IF{
    void show();
}

成员内部类:

可以直接访问外部类的所有成员(包括私有的)可以添加任意修饰符(包括非访问修饰符,因为本质就是一个成员)作用域为外部类的整个类体中外部类要使用成员内部类的属性/方法,先创建内部类的对象,然后调用内部类属性/方法外部其他类要使用成员内部类的方式:

// -------------main-----------
Outer01 outer01 = new Outer01();
// 方法1:把new inner()视为outer01的一个成员
Outer01.Inner inner1 = outer01.new Inner();
inner1.show();
// 方法2:在外部类编写一个方法返回内部类对象
Outer01.Inner inner2 = outer01.getInner();
inner2.show();
// -----------类------------
class Outer01{
    private int a;
    class Inner{
        public void show(){
            System.out.println("成员内部类");
        }
    }

    public Inner getInner(){
        return new Inner();
    }
}

如果外部类和成员内部类的成员重名时,遵循就近原则。要想访问外部类的成员,可以使用外部类名.this.成员访问

静态内部类:

可以直接访问外部类的所有静态成员(包括私有的)可以添加任意修饰符(包括非访问修饰符,因为本质就是一个成员)作用域为外部类的整个类体中外部类要使用静态内部类的属性/方法,先创建内部类的对象,然后调用内部类属性/方法:

外部其他类要使用静态内部类的方式:

// 方法1:通过类名直接访问
Outer01.Inner1 inner1 = new Outer01.Inner1();  // Outer01是类名,不是实例化的对象名
inner1.show();
// 方法2:编写方法返回静态内部类的对象实例
Outer01 outer01 = new Outer01();
Outer01.Inner1 inner2 = outer01.getInner();
inner2.show();
//------------类--------------
class Outer01{
    static class Inner1{
        public void show(){
            System.out.println("成员内部类");
        }
    }
    
    public Inner1 getInner(){
        return new Inner1();
    }
}

如果外部类和静态内部类的成员重名时,遵循就近原则。要想访问外部类的成员,可以使用外部类名.成员访问

tips:

类的五大成员:属性、方法、构造器、代码块、内部类修饰符分为访问修饰符和非访问修饰符:

访问修饰符:public、private、default、protected非访问修饰符:static、final、abstract、synchronized、volatile getClass()用于获取对象的运行类型外部类只能使用public修饰,内部类可以使用其他修饰符静态/成员内部类使用abstract修饰后依旧无法被实例化:

class Outer01{
    private int a;
    abstract class Inner{
        public void show(){
            System.out.println("成员内部类");
        }
    }
    public void UseInner(){
        Inner inner = new Inner();  // 报错
    }
}

外部其他类使用静态内部类的方式,如下代码会报错:

Outer01 outer01 = new Outer01();
Outer01.Inner1 inner1 = outer01.Inner1(); // 报错
Outer01.Inner1 inner1 = new Outer01().Inner1();  // 报错
Outer01.Inner1 inner1 = (new Outer01()).Inner1();  // 报错

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5716758.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存