面向对象之封装
类和对象的关系
- 类就是:对现实生活中事物的描述。
- 对象:就是这类事物,实实在在存在的个体。
比如:
现实生活中的对象:张三,李四。
想要描述:提取对象中共性内容。对具体对象的抽象。
描述时,这些对象的共性有:姓名,年龄,性别,学习java的功能。映射到java中,描述就是class定义的类。具体对象就是对应java在堆内存中用new建立的实体。
(其实定义类)描述事物,其实就是在描述事物的属性和行为。属性对应类中变量,行为对应类中的函数(方法)。属性和行为共同成为类中的成员(成员变量和成员函数)。
成员变量和局部变量的区别:
1、作用域
成员变量作用于整个类中。
局部变量作用于函数中,或者语句中。
2、在内存中的位置
成员变量:在堆内存中,因为对象的存在,才在内存中存在。
局部变量:存在栈内存中。
匿名对象
匿名对象使用方式:
- 当对对象的方法只调用一次时,可以用匿名对象来完成,这样写比较简化。如果对一个对象进行多个成员调用,必须给这个对象起个名字。
- 可以将匿名对象作为实际参数进行传递。
示例代码如下:
class Car { //描述颜色 String color = "红色"; //描述轮胎数 int num = 4; //运行行为 void run() { System.out.println(color+".."+num); } public static void main(String[] args) { Car c = new Car(); c.num = 10; //run(); } } class CarDemo { public static void main(String[] args) { //生产汽车,在java中通过new操作符来完成 //其实就是在堆内存产生一个实体 Car c = new Car();//c就是一个类类型变量。记住:类类型变量指向对象。 c.color = "蓝色"; c.run(); // Car c1 = new Car(); // c1.run(); // Car c = new Car(); // c.num = 5; // new Car().num = 5; // new Car().color = "blue"; // new Car().run(); //匿名对象使用方式一:当对对象的方法只调用一次时,可以用匿名对象来完成,这样写比较简化 //如果对一个对象进行多个成员调用,必须给这个对象起个名字 //匿名对象使用方式二:可以将匿名对象作为实际参数进行传递 // Car q = new Car(); // show(q); show(new Car()); } public static void show(Car c) { c.num = 3; c.color = "black"; c.run(); } }
封装
private:私有,权限修饰符,用于修饰类中的成员(成员变量、成员函数)。私有只在本类中有效。
注意:私有仅仅是封装的一种表现形式。
构造代码块
构造代码块——定义的是不同对象共性的初始化内容。
作用:给对象进行初始化,对象一建立就运行,而且优先于构造函数执行。
和构造函数的区别:构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化。
对象一建立就会调用与之对应的构造函数。构造函数和一般函数在写法上有不同,在运行上也有不同。构造函数是在对象一建立就运行,给对象初始化。而一般函数是对象调用才执行,是给对象添加对象具备的功能。一个对象建立,构造函数只运行一次,而一般函数可以被该对象调用多次。
示例代码:
class Person { private String name; private int age; /* * 构造代码块——定义的是不同对象共性的初始化内容 * 作用:给对象进行初始化,对象一建立就运行,而且优先于构造函数执行 * 和构造函数的区别:构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化 */ { //System.out.println("person code run"); cry(); } Person() { System.out.println("A: name="+name+", age="+age); //cry(); } Person(String n) { name = n; System.out.println("B: name="+name+", age="+age); //cry(); } public void setName(String n) { name = n; } public String getName() { return name; } Person(String n, int a) { name = n; age = a; System.out.println("C: name="+name+", age="+age); //cry(); } public void cry() { System.out.println("cry...."); } public void setAge(int a) { if(a > 0 && a < 130) { age = a; speak(); } else System.out.println("非法年龄"); } public int getAge() { return age; } void speak() { System.out.println("age="+age); } } public class PersonDemo { public static void main(String[] args) { Person p1 = new Person();//对象一建立就会调用与之对应的构造函数 // p1.cry();//构造函数和一般函数在写法上有不同,在运行上也有不同。构造函数是在对象一建立就运行,给对象初始化 //而一般函数是对象调用才执行,是给对象添加对象具备的功能 //一个对象建立,构造函数只运行一次,而一般函数可以被该对象调用多次 Person p2 = new Person("lisi"); // p2.setName("libusi"); // p2.setName("LIBUHSI"); // System.out.println(p2.getName()); // Person p2 = new Person("LISI"); // Person p3 = new Person("WNAGWU", 10); } }
this
this:看上去,是用于区分局部变量和成员变量同名情况。
this:代表本类的对象,this代表它所在函数所属对象的引用,简单说:哪个对象在调用this所在的函数,this就代表哪个对象。
示例代码:
class Person { private String name; private int age; /* * this:看上去,是用于区分局部变量和成员变量同名情况 * this:代表本类的对象,this代表它所在函数所属对象的引用,简单说:哪个对象在调用this所在的函数,this就代表哪个对象 */ Person() { } Person(String name) { this(); this.name = name; } Person(int age) { this.age = age; } public Person(String name, int age) { // this.name = name; this(name); //p(name);用于构造语句之间互相调用 this.age = age; } public void setName(String n) { name = n; } public String getName() { return name; } public void setAge(int a) { age = a; } public int getAge() { return age; } public boolean compare(Person per) { if(this == per) { return true; } if(this.getName().equals(per.getName()) && this.getAge() == per.getAge()) { return true; } else { return false; } } public void speak() { System.out.println("name="+name+"...age="+age); show(); } public void show() { System.out.println(this.name); } /* * this的应用 * 比较年龄是否相同的? */ public boolean compareAge(Person per) { return this.age == per.age; } } public class ThisDemo { public static void main(String[] args) { // Person p1 = new Person(20); // Person p2 = new Person(25); // boolean b = p1.compareAge(p2); // System.out.println(b); Person p = new Person("lisi", 30); // Person p1 = new Person("zhangsan"); // p.speak(); // p1.speak(); } }
static
static:用法:是一个修饰符,用于修饰成员(成员变量、成员函数)。
特点:
- 随着类的加载而加载,类的消失而消失,说明它的生命周期最长。
- 优先于对象存在,明确一点:静态是先存在的,对象是后存在的。
- 被所有对象所共享。
- 可以直接被类名所调用。
静态的成员变量,也叫类变量。成员变量,也叫实例变量。
实例变量和类变量的区别:
1、存在位置
类变量随着类的加载而存在于方法区中。
实例变量随着对象的建立而存在于堆内存中。
2、生命周期
类变量生命周期最长,随着类的消失而消失。
实例变量生命周期随着对象的消失而消失。
静态使用注意事项:
- 静态方法只能访问静态成员,非静态方法既可以静态也可以访问非静态。
- 静态方法中不可以定义this、super关键字。因为静态优先于对象存在,所以静态方法中不可以出现this。
- 主函数是静态的。
静态有利有弊:
- 利处:对对象的共享数据进行单独空间的存储,节省空间,没有必要每一个对象中都存储一份;可以直接类名调用。
- 弊端:生命周期过长,访问出现局限性(静态虽好,只能访问静态)。
示例代码:
class S_Person { String name;//成员变量,也叫实例变量 int age; /* * static:用法:是一个修饰符,用于修饰成员(成员变量、成员函数) * 特点: * 1、随着类的加载而加载,类的消失而消失,说明它的生命周期最长 * 2、优先于对象存在,明确一点:静态是先存在的,对象是后存在的 * 3、被所有对象所共享 * 4、可以直接被类名所调用 */ static String country = "CN";//静态的成员变量,也叫类变量 /* * 实例变量和类变量的区别: * 1、存在位置——类变量随着类的加载而存在于方法区中 * 实例变量随着对象的建立而存在于堆内存中 * 2、生命周期——类变量生命周期最长,随着类的消失而消失 * 实例变量生命周期随着对象的消失而消失 */ /* * 静态使用注意事项: * 1、静态方法只能访问静态成员 * 非静态方法既可以静态也可以访问非静态 * 2、静态方法中不可以定义this、super关键字 * 因为静态优先于对象存在,所以静态方法中不可以出现this * 3、主函数是静态的 * * 静态有利有弊 * 利处:对对象的共享数据进行单独空间的存储,节省空间,没有必要每一个对象中都存储一份 * 可以直接类名调用 * 弊端:生命周期过长,访问出现局限性(静态虽好,只能访问静态) */ /*static public void setCountry(String country) { //this.country = country; //Cannot use this in a static context S_Person.country = country; }*/ public S_Person() { } public S_Person(String name, int age) { this.name = name; this.age = age; } public static void show() { System.out.println("::"+country); } public void info() { System.out.println("姓名:"+this.name+",年龄:"+this.age+",城市:"+this.country); } } public class StaticDemo { public static void main(String[] args) { S_Person.show(); // System.out.println(S_Person.country); // S_Person p = new S_Person(); // System.out.println(p.country); } }
主函数(main)
主函数:是一个特殊的函数,作为程序的入口,可以被JVM调用。
主函数的定义:
- public:代表着该函数访问权限是最大的。
- static:代表主函数随着类的加载就已经存在了。
- void:主函数没有具体的返回值。
- main:不是关键字,但是是一个特殊的单词,可以被JVM设别。
- String[] args:函数的参数,参数类型是一个数组,该数组中的元素是字符串,字符串类型的数组。
主函数是固定格式的:JVM设别。JVM在调用主函数时,传入的是new String[0];。
示例代码:
public class StaticDemo1 { public static void main(String[] args) { System.out.println(args[0]);//会出现ArrayIndexOutOfBoundsException异常 } }
静态代码块
格式:
static { 静态代码块中的执行语句 }
特点:随着类的加载而执行,只执行一次,并优先于主函数。用于给类进行初始化。
示例代码如下:
class StaticCode { int num = 9; StaticCode() { System.out.println("b"); } static { System.out.println("a"); } /* * 构造代码块 */ { System.out.println("c"+this.num); } StaticCode(int x) { System.out.println("d"); } public static void show() { System.out.println("show run"); } } public class StaticCodeDemo { static { //System.out.println("b"); } public static void main(String[] args) { //new StaticCode();//将StaticCode.class这个类加载进内存 //new StaticCode();//StaticCode.class已经加载进内存,不会再次打印输出语句!!! //System.out.println("Hello World!!!"); //StaticCode.show(); //StaticCode s = null;//特别注意:StaticCode类没有被加载进内存 //s = new StaticCode(); new StaticCode(4); //打印 a c d } static { // System.out.println("c"); } }
练习:通过以下程序代码,试说明Nei_Person p = new Nei_Person("张三", 23);该句话都做了什么事情?
代码:
class Nei_Person { private String name = "haha"; private int age; private static String country = "CN"; Nei_Person(String name, int age) { this.name = name; this.age = age; } { System.out.println(name+".."+age); } public void setName(String name) { this.name = name; } public void speak() { System.out.println(this.name+"..."+this.age); } public static void showCountry() { System.out.println("country="+country); method(); } public static void method() { System.out.println("method run"); } } public class Nei_PersonDemo { public static void main(String[] args) { Nei_Person p = new Nei_Person("张三", 23); p.setName("lisi"); } }
解:
Nei_Person p = new Nei_Person("张三", 23);该句话都做了什么事情?
- 因为new用到了Nei_Person.class,所以会找到Nei_Person.class文件并加载到内存。
- 执行该类中的static代码块,如果有的话,给Nei_Person.class类进行初始化。
- 在堆内存中开辟空间,分配内存地址。
- 在堆内存中建立对象的特有属性,并进行默认初始化。
- 对属性进行显示初始化。
- 对对象进行构造代码块初始化。
- 对对象进行对应的构造函数初始化。
- 将内存地址赋给栈内存中的p变量。
什么时候使用静态?
要从两方面下手:因为静态修饰的内容有成员变量和函数。
1、什么时候定义静态变量(类变量)呢?
当对象中出现共享数据时,该数据被静态所修饰。对象中的特有数据要定义成非静态,存在于堆内存中。
2、什么时候定义静态函数呢?
当功能内部没有访问到非静态数据(对象的特有数据),那么该功能可以定义成静态的。
静态的应用——以下例说明
每个应用程序中都有共性的功能,可以将这些功能进行抽取,独立封装,以便复用。
示例:
/** * 这是一个可以对数组进行操作的工具类,该类中提供了获取最值、排序等功能 * @author 李阿昀 * @version V1.1 */ class ArrayTool { /** * 空参数构造函数 * 一个类中默认会有一个空参数的构造函数,这个默认的构造函数的权限和所属类一致。 * 如果类被public修饰,那么默认的构造函数也带public修饰 * 如果类没有被public修饰,那么默认的构造函数也没有public修饰 * * 简而言之:默认构造函数的权限是随着类的变化而变化的 */ private ArrayTool() {} /** * 获取一个整型数组中的最大值 * @param arr 接受一个int类型的数组 * @return 会返回一个该数组中的最大值 */ public static int getMax(int[] arr) { int max = 0; for(int x = 1; x < arr.length; x++) if(arr[x] > arr[max]) max = x; return arr[max]; } /** * 获取一个整型数组中的最小值 * @param arr 接受一个int类型的数组 * @return 会返回一个该数组中的最小值 */ public static int getMin(int[] arr) { int min = 0; for(int x = 1; x < arr.length; x++) if(arr[x] < arr[min]) min = x; return arr[min]; } /** * 给int数组进行选择排序 * @param arr 接受一个int类型的数组 */ 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); } } } } /** * 给int数组进行冒泡排序 * @param arr 接受一个int类型的数组 */ public static void bubbleSort(int[] arr) { for(int x = 0; x < arr.length - 1; x++) { for(int y = 0; y < arr.length - x - 1; y++) { if(arr[y] > arr[y+1]) { swap(arr, y, y+1); } } } } /** * 给数组中的元素进行位置的置换 * @param arr 接受一个int类型的数组 * @param a 要置换的位置 * @param b 要置换的位置 */ private static void swap(int[] arr, int a, int b) { int temp = arr[a];//置换私有化 arr[a] = arr[b]; arr[b] = temp; } /** * 用于打印数组中的元素。打印形式是:[element1, element2, ...] * @param arr 接受一个int类型的数组 */ 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] + "]"); } } }
虽然可以通过建立ArrayTool的对象使用这些工具方法,对数组进行操作,但是依然发现了问题:
- 对象是用于封装数据的,可是ArrayTool对象并未封装特有数据。
- 操作数组的每一个方法都没有用到ArrayTool对象中的特有数据。
这时就考虑,让程序更严谨,是不需要对象的。可以将ArrayTool中的方法都定义成static的。直接通过类名调用即可。
将方法都静态后,可以方便于使用,但是该类还是可以被其他程序建立对象的。为了更为严谨,强制让该类不能建立对象。可以通过将构造函数私有化完成。。
接下来,将ArrayTool.class文件发送给其他人,其他人只要将该文件设置到classpath路径下,就可以使用该工具类。但是,很遗憾,该类中定义了多少个方法,对方不清楚,因为该类并没有使用说明书。开始制作程序的说明书,java的说明书通过文档注释来完成。
单例设计模式
设计模式:解决某一类问题最行之有效的方法。JAVA中有23种设计模式。
单例设计模式:解决一个类在内存只存在一个对象。
想要保证对象唯一:
- 为了避免其他程序过多建立该类对象,先禁止其他程序建立该类对象。
- 还为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象。
- 为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式。
这3步怎么用代码体现?
- 将构造函数私有化。
- 在类中创建一个本类对象。
- 提供一个方法可以获取到该对象。
对于事物该怎么描述,还怎么描述。当需要将该事物的对象保证在内存中唯一时,就将以上的3步加上即可。
示例代码:
class Single { private int num; public void setNum(int num) { this.num = num; } public int getNum() { return num; } private Single() { } private static Single s = new Single(); public static Single getInstance() { return s; } } class Student { private int age; private static Student s = new Student(); private Student() { } public static Student getStudent() { return s; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } } public class SingleDemo { public static void main(String[] args) { Single ss = Single.getInstance(); Single s1 = Single.getInstance(); ss.setNum(20); System.out.println(s1.getNum()); Student stu1 = Student.getStudent(); stu1.setAge(30); Student stu2 = Student.getStudent(); stu1.setAge(3); } }
单例设计模式可分为饿汉式与懒汉式(面试常考)(以下例代码说明):
class Single1 { /* * 这个是先初始化对象。称为:饿汉式 * Single1类一进内存,就已经创建好了对象 * 开发原则:定义单例,建议使用饿汉式 private static Single1 s1 = new Single1(); private Single1() { } public static Single1 getInstance() { return s1; } */ /* * 对象是方法被调用时,才初始化,也叫做对象的延时加载。称为:懒汉式 * Single1类进内存,对象还没存在,只有调用getInstance()方法时,才建立对象 */ private static Single1 s1 = null; private Single1() { } //synchronized相当于上了一个锁,但程序效率降低了 public static Single1 getInstance() { if(s1 == null) { //双重判断,可解决这个问题(涉及多线程) //--->B(挂着了) synchronized (Single1.class) { if(s1 == null) { //--->A(挂着了) s1 = new Single1(); } } } return s1; } }