面向对象(一)
一、理解面向对象
面向对象作为一种编程思想,其本质是基于面向过程的。相对于强调功能行为的面向过程变成方式而言,面向对象强调的是功能的封装,形成具备一定功能的对象。面向对象的思维方式,符合人们的思考习惯,可以将复杂的事情简单化。从面向过程到面向对象,程序员完成了从执行者到指挥者的角色转变。
在使用面向对象概念的时候:
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()
对象及其成员变量color
,num
;
2.将内存地址赋给栈内存中该对象的引用c1
,c1
即指向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.将创建的对象的内存地址赋给引用c
,c
即指向该对象;
2.将引用c
传给该对象的show()
方法,即将该对象的地址赋给show()
方法的参数c
,show()
方法中的参数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()
方法的参数c
,c
再访问该对象的成员变量。
七、封装
1.定义
封装是指,隐藏对象的属性和实现细节,仅对外提供公共访问方式。
2.封装的好处
1)类的内部变化被隔离,调用者无须知道这些变化,照样能实现功能;
2)类内部细节的实现无须调用者了解,便于使用;
3)封装的代码可以被不同调用者使用,提高复用性;
4)不想对外开放的属性,通过封装能很好隐藏起来,提高安全性
3.private关键字private:
用于修饰类中的成员变量和成员方法,被修饰的成员只能在本类被访问。private
是封装的一种表现形式。当使用private
修饰成员变量后,应设置相应的get
和set
方法,让外部访问私有变量。
示例代码:
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)构造方法中初始化过了的私有变量,还是需要定义set
和get
方法,因为在对象的使用过程中,可能涉及改变其私有变量的值,此时就要用到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)静态方法中不能出现this
,super
关键字
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
对象s
将Single
对象的内存地址赋给栈内存中的变量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()
方法时,才创建对象
开发时,用饿汉式