Java基础—面向对象(一)

面向对象(一)

一、理解面向对象

面向对象作为一种编程思想,其本质是基于面向过程的。相对于强调功能行为的面向过程变成方式而言,面向对象强调的是功能的封装,形成具备一定功能的对象。面向对象的思维方式,符合人们的思考习惯,可以将复杂的事情简单化。从面向过程到面向对象,程序员完成了从执行者到指挥者的角色转变。

在使用面向对象概念的时候:
1.先找具有所需要功能的对象是否存在,存在即可使用;
2.如果不存在,那么创建一个具备所需功能的对象;
3.创建具备不用功能对象的过程,就是简化开发,提高代码复用性的过程

二、面向对象的特征:

1.封装:Java的封装是一种信息隐藏技术,将属性和方法封装在一个类当中,只对调用者开放相应接口来访问该类的成员属性和方法。封装提高了程序的复用性和可维护性;

2.继承:Java的继承特性,能使一个派生类类拥有其父类的属性和方法,并扩展其特有的方法;

3.多态:指允许不同类的对象对同一方法做出不同响应。即同一方法可以根据调用对象的不同而表现出多种不同的行为方式。多态的发生必须满足1)继承;2)复写;3)父类引用指向子类对象;这三个条件

三、类与对象的关系

Java中通过定义类的形式,来描述生活中的具体事物。类是具体事物的抽象定义。对象则是该类在生活中的具体体现,是实实在在的个体。

这里写图片描述

四、类的定义

生活中描述事物,就是要描述事物的不同属性和行为。如:人有身高,体重,年龄等属性;有起床,刷牙等行为。在Java中,用类(class)来描述事物。

属性:就是类中声明的成员变量;
行为:就是类中声明的成员方法;

定义一个类,其实就是在定义类中的成员变量和成员方法。

类的示例代码:

class Car {
    String color = "red";
    int num = 4;

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

创建对象的代码示例:

class Test {
    public static void main(String[] args) {
        Car c1 = new Car();
        c1.color = "blue";
        Car c2 = new Car();
    }
}

该对象的内存结构图如下:

这里写图片描述

new一个Car对象的时候:
1.在堆内存中开辟空间,储存Car()对象及其成员变量colornum
2.将内存地址赋给栈内存中该对象的引用c1c1即指向Car()对象;
3.c1修改color的值,将新值赋给color

五、成员变量和局部变量的不同

1.作用域不同:
1)成员变量作用域为整个类;
2)局部变量作用域为方法或者语句

2.在内存中的位置不同:
1)成员变量在堆内存中;对象实例被new出来之后,才开辟内存空间给成员变量;
2)局部变量在栈内存中

六、匿名对象:

1.应用一:

new Car().num = 5;
new Car().color = "green";
new Car().run();

如果使用多个匿名对象,第一行执行完之后,由于没有引用指向这个对象,该对象立刻成为垃圾,等待回收。

这里写图片描述

用匿名对象调用方法是通常做法,调用成员属性没有意义。所以,当对对象方法只调用一次时,可以用匿名对象完成。如果对对象进行多个成员调用,必须给这个对象起名字。

2.应用二:
将匿名对象作为实际参数进行传递。

不使用匿名对象作为参数的示例代码:

class Test {
    public static void main(String[] args) {
        Car c = new Car();
        show(c);
    }

    public static void show(Car c) {
        c.num = 3;
        c.color = "black";
        c.run();
    }
}

上述代码内存运行过程如图:

这里写图片描述

运行过程:
1.将创建的对象的内存地址赋给引用cc即指向该对象;
2.将引用c传给该对象的show()方法,即将该对象的地址赋给show()方法的参数cshow()方法中的参数c即指向该对象;
3.show()方法访问该对象的成员变量

使用匿名对象作为参数的示例代码:

public static void main(String[] args) {
    show(new Car());
}

public static void show(Car c) {
    c.num = 3;
    c.color = "black";
    c.run();
}

上述代码内存运行过程如图:

这里写图片描述

使用匿名对象作为参数,直接将对象地址赋给show()方法的参数cc再访问该对象的成员变量。

七、封装

1.定义
封装是指,隐藏对象的属性和实现细节,仅对外提供公共访问方式。

2.封装的好处
1)类的内部变化被隔离,调用者无须知道这些变化,照样能实现功能;
2)类内部细节的实现无须调用者了解,便于使用;
3)封装的代码可以被不同调用者使用,提高复用性;
4)不想对外开放的属性,通过封装能很好隐藏起来,提高安全性

3.private关键字
private:用于修饰类中的成员变量和成员方法,被修饰的成员只能在本类被访问。private是封装的一种表现形式。当使用private修饰成员变量后,应设置相应的getset方法,让外部访问私有变量。

示例代码:

class Person {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void getName() {
        return name;
    }

    public void getAge() {
        return age;
    }
}

八、构造方法

1.构造方法特点
1)方法名与类名相同;
2)不用定义返回值类型;
3)不可以写return语句;
4)在创建对象的时候,自动调用

2.构造方法的作用
构造方法可以对对象进行初始化。对象在创建的时候,就应该具备其基本特性。构造方法就初始化了这些基本特性。

3.构造方法小细节
1)当一个类中没有定义构造方法时,Java默认会为该类加入一个空参数的构造方法;
2)当在类中声明了自定义的构造方法后,Java不再为该类添加构造方法;
3)对象创建时就具备的特性和行为,可以定义在构造函方法中,以便创建时初始化;
4)构造方法中初始化过了的私有变量,还是需要定义setget方法,因为在对象的使用过程中,可能涉及改变其私有变量的值,此时就要用到set方法,获取该私有变量的值,需要用到get方法

示例代码:

class Person {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void getName() {
        return name;
    }

    public void getAge() {
        return age;
    }

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

class Test {
    public static void main(String[] args) {
        Person p = new Person("Lucy", 20);
        p.setName("Linda");
    }
}

九、构造代码块

1.构造代码块的定义
构造代码块是定义在类中的一个独立代码区间。构造代码块的作用是为对象进行初始化。对象一旦创建就立刻运行构造代码块,且运行优先级高于该类的构造方法。

2.构造代码和构造方法的区别
1)构造代码是给所有对象进行统一初始化;
2)构造方法是给对应的对象初始化

3.构造代码块的应用
将所有对象的共性属性或行为定义在构造代码块中,那么所有对象创建的时候同一执行构造代码块中的内容。

示例代码:

class Person {
    private String name;
    private int age;

    {
        this.name = "Travis";
        this.age = 20;
    }

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

十、this关键字

1.this的概念
this关键字代表调用this所在方法的对象的引用,即哪个对象调用了this所在的方法,this就指向那个对象。

2.this关键字的应用
1)this关键字用于区分局部变量和成员变量重名的情况;并且,定义类中方法时,如果需要在方法内部使用本类对象的时候,用this指向这个对象

示例代码:

package test;

public class Animal {
    private String type;
    private int leg;

    Animal(String type, int leg) {
        this.type = type; //this区分了成员变量和局部变量,并代表本类的对象
        this.leg = leg;
    }
}

2)this关键字可以用于构造方法间互相调用

示例代码:

package test;

public class Animal {
    private String type;
    private int leg;

    Animal(String type) {
        this.type = type; //this区分了成员变量和局部变量,并代表本类的对象
    }

    Animal(String type, int leg) {
        this(type); //this关键字以type为参数调用了第一个构造函数
        this.leg = leg;
    }
}

3)注意:this关键字调用其他构造方法的语句只能写在构造方法的第一行,因为初始化动作要先执行

十一、static关键字

1.static关键字基本用法
static关键字是一个修饰符,用于修饰成员变量和成员方法。被static修饰的成员,称为静态成员,或类成员。当一个成员是所有对象所共享的时候,用static关键字修饰,那么每次创建对象的时候,该成员就不会被初始化在堆内存中。

class Person {
    String name;
    static String country = "CN";
}

上述代码中,country被声明成静态变量,则该变量不再存在于堆内存中,而作为共享数据储存于方法区,如下图:

这里写图片描述

2.static关键字的特点
1)随着类的加载而加载
类一旦加载到内存,static成员就已经在内存中被分配空间。同时,static成员随着类的消失而消失,也就是说,static成员的生命周期最长

2)优先于对象存在
静态随着类的加载而存在,对象创建后才存在

3)被所有对象共享
所有对象共享一份静态成员

4)可以直接被类名所调用
由于类刚加载的时候,还没有对象,而静态成员已经加载到内存,所以可以用类名直接调用

3.静态变量和成员变量的区别
1)存放位置不同
静态变量随着类的加载而存在与方法区中
成员变量随着对象的创建而存在于堆内存中

2)生命周期不同
静态变量生命周期等于类的生命周期,随着类存在消亡
成员变量生命周期随着对象存在消亡,生命周期较静态变量短

4.static关键字注意事项
不能把所有成员都定义成static,因为:
1)不是所有成员都能被所有对象共享;
2)定义成static的成员,生命周期随着类存在消亡,对象使用完毕,这些成员还存在于内存,浪费资源;
3)静态方法中不能引用非静态成员,即静态方法只能访问静态成员(非静态方法可以访问静态成员);
4)静态方法中不能出现thissuper关键字

5.静态的利弊
利:
1)存储所有对象共享的成员于方法区,节省资源;
2)可以直接被类名调用,使用简单

弊:
1)生命周期过长,可能出现浪费资源的情况;
2)访问出现局限性(静态方法只能访问静态成员)

6.使用static关键字的时机
1)当对象中出现需要共享的数据时,使用static关键字。这里的数据,指的是成员的值或功能,不是成员本身。成员本身称为共享的属性,而不是共享的数据,如name成员变量,是Person类的对象的共享属性,而非共享数据;

2)当成员方法没有对类中任何非静态成员变量进行操作的时候,该方法可以定义成静态成员方法

7.静态的应用
定义了ArrayTools类,包含各种操作数组的方法。由于所有的方法都没有操作该类中的成员变量,根据static关键字使用原则,声明该类中所有方法为静态成员方法。

示例代码:

package test;

/**
 * 利用ArrayTools类实现冒泡排序,选择排序,普通查找和二分查找
 * @author jeremy
 *
 */

public class TestArrayTools {
    public static void main(String[] args) {
        int[] arr = new int[]{3, 98, 56, 4, 10, 66, 27, 17};

        //冒泡排序
        System.out.println("Bubble Sort:"); 
        ArrayTools.bubbleSort(arr);
        ArrayTools.printArray(arr);
        System.out.println("----------------------------------------------");

        //选择排序
        System.out.println("Select Sort:");
        ArrayTools.selectSort(arr);
        ArrayTools.printArray(arr);
        System.out.println("----------------------------------------------");

        //选择排序2
        System.out.println("Select Sort Version 2:");
        ArrayTools.selectSort_2(arr);
        ArrayTools.printArray(arr);
        System.out.println("----------------------------------------------");

        //普通查找
        System.out.println("Search array for 10:");
        System.out.println(ArrayTools.search(arr, 10));
        System.out.println("----------------------------------------------");

        System.out.println("Search array for 11:");
        System.out.println(ArrayTools.search(arr, 11));
        System.out.println("----------------------------------------------");

        //二分查找
        System.out.println("Half Search array for 27:");
        System.out.println(ArrayTools.halfSearch(arr, 27));
        System.out.println("----------------------------------------------");

        System.out.println("Half Search array for 99:");
        System.out.println(ArrayTools.halfSearch(arr, 99));
        System.out.println("----------------------------------------------");

        //二分查找2
        System.out.println("Half Search array Version 2 for 56:");
        System.out.println(ArrayTools.halfSearch(arr, 56));
        System.out.println("----------------------------------------------");

        System.out.println("Half Search array Version 2 for -9:");
        System.out.println(ArrayTools.halfSearch(arr, -9));
        System.out.println("----------------------------------------------");

    }
}

class ArrayTools {

    /**
     * 交换数组中两个值的位置
     * @param arr 接受数组作为第一参数
     * @param a 接受整型数据作为第二参数,用于标识数组元素
     * @param b 接受整型数据作为第三参数,用于标识数组元素
     */
    private static void swap(int[] arr, int a, int b) {
            int tmp = arr[b];
            arr[b] = arr[a];
            arr[a] = tmp;
    }

    /**
     * 打印数组的方法
     * @param arr 接受数组作为参数
     */
    public static void printArray(int[] arr) {
        System.out.print("[");
        for(int x = 0; x < arr.length; x ++) {
            if(x != arr.length - 1) {
                System.out.print(arr[x] + ", ");
            } else {
                System.out.println(arr[x] + "]");
            }
        }
    }

    /**
     * 冒泡排序,相邻两书比较,小的数左移
     * @param arr 接受数组作为参数
     */
    public static void bubbleSort(int[] arr) {
        for(int x = 0; x < arr.length; x ++) {
            for(int y = 0; y < arr.length - x - 1; y ++) {
                if(arr[y] > arr[y + 1]) {
                    swap(arr, y, y + 1);
                }
            }
        }
    }

    /**
     * 选择排序一,前一个数和后一个做比较,若大于后一个数,则立刻交换位置
     * @param arr 接受数组作为参数
     */
    public static void selectSort(int[] arr) {
        for(int x = 0;x < arr.length - 1; x ++) {
            for(int y = x + 1; y < arr.length; y ++) {
                if(arr[x] > arr[y]) {
                    swap(arr, x, y);
                }
            }
        }
    }

    /**
     * 选择排序二,前一个数和后一个做比较,如果大于后一个,则交换下标值,让小的数继续比较
     * 如果较小数的下标发生过变化,则和原数交换位置
     * @param arr 接受数组作为参数
     */
    public static void selectSort_2(int[] arr) {
        int k;
        for(int x = 0; x < arr.length - 1; x ++) {
            k = x;
            for(int y = x + 1; y < arr.length; y ++) {
                if(arr[k] > arr[y]) {
                    k = y;
                }
            }
            if(k != x) {
                int tmp = arr[k];
                arr[k] = arr[x];
                arr[x] = tmp;
            }
        }
    }

    /**
     * 普通查找,遍历数组,将每个元素和目标值作比较
     * @param arr 接受数组作为第一参数
     * @param key 接受整型目标值作为第二参数
     * @return 匹配成功,返回下标;匹配失败,返回-1
     */
    public static int search(int[] arr, int key) {
        for(int x = 0; x < arr.length; x ++) {
            if(arr[x] == key) {
                return x;
            }
        }
        return -1;
    }

    /**
     * 二分法查找一
     * @param arr 接受数组作为第一参数
     * @param key 接受整型目标值作为第二参数
     * @return 匹配成功,返回下标;匹配失败,返回-1
     */
    public static int halfSearch(int[] arr, int key) {
        int min = 0;
        int max = arr.length - 1;
        int mid = (min + max) / 2;

        while(arr[mid] != key) {
            if(arr[mid] < key) {
                min = mid + 1;
            } else {
                max = mid - 1;
            }
            if(min > max) {
                return -1;
            }
            mid = (min + max) / 2;
        }
        return mid;
    }

    /**
     * 二分法查找二
     * @param arr 接受数组作为第一参数
     * @param key 接受整型目标值作为第二参数
     * @return 匹配成功,返回下标;匹配失败,返回-1
     */
    public static int halfSearch_2(int[] arr, int key) {
        int min = 0;
        int max = arr.length - 1;
        int mid = (min + max) / 2;

        while(min <= max) {
            if(arr[mid] < key) {
                min = mid + 1;
            } else if(arr[mid] > key) {
                max = mid - 1;
            } else {
                return mid;
            }
        }
        return -1;
    }
}

8.静态代码块
1)格式

static {
statement;
}

2)特点
随着类的加载而执行,并且只执行一次。用于对类进行初始化。

示例代码:

class TestStatic {

    TestStatic() {
        System.out.print("b ");
    }

    static {
         System.out.print("a ");
    }

    {
        System.out.print("c ");
    }

    TestStatic(int x) {
        System.out.print("d ");
    }
}

class TestStaticMain {
    public static void main(String[] args) {
        new TestStatic(5);
    }
}

上述代码的输出结果为:a c d

执行过程为:
1> 先执行static代码块,由于其要初始化类,优先级最高,输出a
2> 后执行构造代码块,由于其要初始化对象,优先级介于构造方法和静态代码块间,输出c
3> 最后执行相应的构造方法,输出 d

注意:
静态代码块中不能访问非静态成员变量,构造代码块可以访问非静态变量。

示例代码:

class TestStatic {

    int num = 9;

    static {
        //错误,静态代码块先于非静态成员存在,无法访问非静态成员
        System.out.print("a " + num); 
    }

    {
        //正确,构造代码块初始化本类对象,可以访问该类的成员变量
        System.out.print("c " + num);
    }
}

class TestStaticMain {
    public static void main(String[] args) {
        new TestStatic();
    }
}

输出:c9

9.主函数的属性
主函数的定义:
主函数是一个特殊函数,作为程序的入口,被jvm调用。主函数格式固定。

public:主函数无访问限制
static:主函数为静态,随着类的加载就存在于方法区
void:主函数没有返回值
mian:不是关键字,是能被jvm识别的特殊标识符
String[] args:函数的参数,参数类型为字符串数组

十二、对象的初始化过程

1.对象的初始化过程如下:

这里写图片描述

1)通过虚拟机,加载类的class文件到内存;
2)执行方法区静态代码块(如有),对类进行初始化;
3)在堆内存中开辟对象new Person(),及其类成员变量的储存空间,并对成员变量进行初始化;在栈内存中开辟main方法和该对象引用的储存空间;
4)对类成员变量进行显式初始化(如有);
5)执行构造代码块(如有),对对象进行初始化;
6)执行构造方法,调用相应的构造方法对成员变量进行初始化;
7)将内存地址赋给栈内存中该对象的引用

2.对象调用非静态成员方法过程
1)从方法区将要调用的非静态方法加载到栈内存;
2)将该对象的引用的内存地址,赋给要调用的方法中自带的this关键字,此时,this指代的就是该对象的引用;
3)内存地址赋值完毕,this指向该对象,将栈内存中变量的值,赋给堆内存中对象的成员变量,完成赋值,结束对非静态方法的调用

十三、设计模式之单例设计模式

设计模式是解决某一类问题最行之有效的方法。作为Java23种设计模式中的一种,单例设计模式解决了一个类在内存中只能存在一个对象的问题,即保证一个类,在内存中只有一个对象。

想要保证对象唯一性:
1.禁止其他程序建立该类的对象;
2.在该类中自定义一个对象;
3.开放端口,让其他程序访问该对象

实现:
1.私有化构造方法;
2.在该类中创建一个本来对象;
3.提供方法获取该对象

饿汉式:

class Single {

    //私有化构造方法,其他程序不能创建该类对象
    private Single() {}

    //在本类中,创建静态的本类对象
    private static Single s = new Single();

    //对其他程序提供静态方法,获取该类对象
    public static Single getInstance() {
        return s;
    }
}

class TestSingle {
    public static void main(String[] args) {
        //调用Single类的静态方法,获取Single对象
        Single s = Single.getInstance();
    }
}

单例模式内存图:

这里写图片描述

图示说明:
1.Single类在加载的时候,其静态成员变量s,静态成员函数getInstance()以及构造函数都已加载到方法区;在Single类中创建Single对象的时候,已经将Single对象的内存地址赋给方法区中的静态变量s

2.mian()方法中调用了getInstance()方法后,返回的Single对象sSingle对象的内存地址赋给栈内存中的变量s,即栈内存中的变量s现在指向堆内存中的Single对象,是Single对象的引用;

3.由于Single类的构造方法被声明为私有,则外部程序无法创建该类的对象;若有代码Single s1 = Single.getInstance();则继续执行上述两个步骤,结果是s1仍然指向堆内存中的同一Single对象,这保证了Single对象的唯一性

懒汉式:

class Single {

    //私有化构造方法,其他程序不能创建该类对象
    private Single() {}

    //延迟创建本类对象
    private static Single s = null;

    //对其他程序提供静态方法,获取该类对象
    public static Single getInstance() {
        //判断,如果s为空,则创建本类对象
        if(s == null) {
            s = new Single();
        }
        //返回该对象
        return s; 
    }
}

饿汉式和懒汉式的不同:
饿汉式:Single类一旦加载到内存,就马上创建对象
懒汉式:Single类加载到内存时,对象没有被创建。只有调用了getInstance()方法时,才创建对象

开发时,用饿汉式