本章主要介绍对象、封装、构造方法、构造代码块、内存分享、this关键子、static、main方法。
面向对象
变量
123456789101112131415161718192021222324252627282930313233343536 > 成员变量: 定义在类中变量> 局部变量: 定义在方法中变量>> 成员变量与局部变量的区别:> 1. 应用范围> 1. 成员变量在整个类内都有效> 2. 局部变量只在其声明的方法内有效> 2. 生命周期> 1. 成员变量: 它属于对象,它随着对象的创建而创建,随着对象的消失而消失> 2. 局部变量: 使用完马上释放空间。> void show(int id){> for(int i=0;i<10;i++){> for(int j=0;j<10;j++){> System.out.println(id);> }> }> }> 这时候 id,i,j者是在方法内声明的,全是局部变量> j当里层for循环执行它的生命周期开始,当里层for结束,j消失> i当外层for循环执行它的生命周期开始,当外层for结束,j消失> id在方法被调用时开始,方法结束时,id消失.> 3. 存储位置 成员变量属于对象,它存储在堆内,堆内的实体,当没有引用指向其时,才垃圾回收清理 局部变量存在栈内存中,当不在使用时,马上就会被释放。> 4. 初始值> 成员变量它存储在堆中,如果没有赋初值,它有默认值。> 1. 整数byte、short、int、long =0;> 2. char='\uoooo';> 3. boolean =flase;> 4. String =null;> 5. 类类型 =null;> 6. 数组 =null;> 局部变量,如果要想使用必须手动初始化.> i. 方法中,参数列表中,语句中。> ii. 必须给初始化值,没有初始值,不能使用> iii. 在栈内存中>>
对象
匿名对象
定义及使用场景
1234567891011121314151617 > 2.1匿名对象:没有名字的实体,也就是该实体没有对应的变量名引用。> 2.2匿名对象的用途> 1,当对象对方法进行一次调用的时候,可以使用匿名对象对代码进行简化。> 为什么只对方法,而不调用属性呢?因为匿名对象调用属性没意义。> 如果对象要多成员进行多次调用,必须给对象起个名字。不能在使用匿名对象。> 2,匿名对象可以实际参数进行传递。> 2:匿名对象的简单演示> 1:new Car().run();> 3:内存结构图> 1:new Car().num=5;> 2:new Car().clor="blue";> 两个new 是两个不同的对象,在堆内存中有不同的空间,相互不相互干扰。> 4:匿名对象的使用> 1:当只使用一次时可以使用匿名对象。执行完毕到;后该对象就变成了垃圾。> new Car().run();> 2:执行方法时,可以将匿名对象作为实际参数,传递进去。>总结:
1234 > 1. 匿名对象设置的属性永远无法获取? 没有引用变量指向那个对象。> 2. 任何两个匿名对象使用==比较,永远返回false。> 3. 匿名对象主要应用于实参。>
>
封装
定义
12 > 封装从字面上来理解就是包装的意思,专业点就是信息隐藏,是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。>
封装的好处
对于封装而言,一个对象它所封装的是自己的属性和方法,所以它是不需要依赖其他对象就可以完成自己的操作。使用封装有三大好处:
12 > 1、良好的封装能够减少耦合。2、使用比较简单。3、提高对象数据的安全性。4、隐藏了类的属性和实现细节。>
没有封装的案例
123456789 > class Employee {> String name;> String id;> String gender;> public void work() {> System.out.println(id + ":" + name + ":" + gender + " 努力工作中!!!");> }> }>
>
1234567891011121314151617 > public class EmployeeDemo {> public static void main(String[] args) {> // 创建对象> Employee jack = new Employee();> // 进制通过类名.成员的形式调用成员。初始化实例变量> jack.name = "jack";> jack.id = "123456";> jack.gender = "男";> // 调用成员方法> jack.work();> System.out.println();> // 传入非法的参数> jack.gender = "不是男人";> jack.work();> }> }>
封装之后
1234567891011121314151617181920212223242526272829303132 > class Employee {> private String name;> private String id;> private String gender;> // 提供公有的get set方法> public String getName() {> return name;> }> public void setName(String n) {> name = n;> }> public String getId() {> return id;> }> public void setId(String i) {> id = i;> }> public String getGender() {> return gender;> }> public void setGender(String gen) {> if ("男".equals(gen) || "女".equals(gen)) {> gender = gen;> } else {> System.out.println("请输入\"男\"或者\"女\"");> }> }> public void work() {> System.out.println(id + ":" + name + ":" + gender + " 努力工作中!!!");> }> }>
构造方法
使用场景
1234 > 我们人出生的时候,有些人一出生之后再起名字的,但是有些人一旦出生就已经起好名字的。那么我们在java里面怎么在对象一旦创建就赋值呢?>> 问题:要求每个小孩出生都会哭,这份代码有两个构造函数,如果需要每个小孩出生都要哭的话,那么就需要在不同的构造函数中都调用cry()函数,但是这样子的话造成了代码重复问题,那么怎么解决呢?构造代码块。>
>
构造方法的作用就是对对象的初始化
与普通的函数的比较
123456 > 1.一般函数是用于定义对象应该具备的功能。而构造函数定义的是,对象在调用功能之前,在建立时,应该具备的一些内容。也就是对象的初始化内容。> 2.构造函数是在对象建立时由jvm调用, 给对象初始化。一般函数是对象建立后,当对象调用该功能时才会执行。> 3.普通函数可以使用对象多次调用,构造函数就在创建对象时调用。> 4.构造函数的函数名要与类名一样,而普通的函数只要符合标识符的命名规则即可。> 5.构造函数没有返回值类型。>
构造函数注意的细节
123456789101112 > 1. 当类中没有定义构造函数时,系统会指定给该类加上一个空参数的构造函数。这个是类中默认的构造函数。当类中如果自定义了构造函数,这时默认的构造函数就没有了。> 备注:可以通过javap命令验证。> 2.在一个类中可以定义多个构造函数,以进行不同的初始化。多个构造函数存在于类中,是以重载的形式体现的。因为构造函数的名称都相同。> 4:this只能在非静态中(没有static修饰的)函数使用> 3:递归构造函数调用> 1:构造函数的相互调用> 在编译时期会报错> 5:构造函数间相互调用必须放在构造函数的第一个语句中,否则编译错误>> 6:可以解决构造函数中对象属性和函数形参的同名问题。>>
构造代码块
12345678910111213141516171819202122232425262728293031 > //构造代码块作用:给所有的对象进行统一的初始化。>> class Boy {> String name;> int age;> String gender;> // 构造代码块,给所有对象进行初始化。> {> System.out.println("哭。。。");> }> Boy() {> System.out.println("无参构造");> }> Boy(String n, int a, String g) {> name = n;> age = a;> gender = g;> System.out.println("有参构造");> }> void run() {> System.out.println("跑...");> }> }> class Demo9 {> public static void main(String[] args) {> System.out.println();> Boy b = new Boy();> Boy b2 = new Boy("jack", 1, "男");> }> }>
作用
1234567 > 2:作用> 1:给对象进行初始化。对象一建立就运行并且优先于构造函数。> 2:与构造函数区别> 1:构造代码块和构造函数的区别,构造代码块是给所有对象进行统一初始化, 构造函数给对应的对象初始化。> 2:构造代码块的作用:它的作用就是将所有构造方法中公共的信息进行抽取。> 例如孩子一出生统一哭>
>
12345678910 > //构造函数见相互调用> Student() {> this(null);> }> //构造函数见相互调用> Student(String name) {> this();> this.name = name;> }>
>
内存分析
案例
12345678910111213141516171819202122 > //汽车> class Car {> //汽车应该具备的属性> int num;> //汽车具备的颜色> String color;> //汽车跑的行为> public void run(){> System.out.println(num+"轮子的汽车跑起来啦");> }> }> public class CarDemo{> public static void main(String[] args)> { //创建实体,并且给该实体起一个名字> Car c = new Car();> c.color = "red";> c.num = 4;> c.run();//指挥车进行运行。调用格式:对象.对象成员> }> }>>
>
1234567891011 > public static void main(String[] args)> { //创建实体,并且给该实体起一个名字> Car c = new Car();> Car c1 = new Car();> c.color = "red";> c1.num = 4;> System.out.println(c1.color);> c.run();//指挥车进行运行。调用格式:对象.对象成员> }>>
>
123456789101112 > public static void main(String[] args)> { //创建实体,并且给该实体起一个名字> Car c = new Car();> Car c1 = c;> c.color = "red";> c1.num = 4;> c1.color = "green";> System.out.println(c1.color);> c.run();//指挥车进行运行。调用格式:对象.对象成员> }>>
>
this关键字
定义
this关键字代表是对象的引用。也就是this在指向一个对象,所指向的对象就是调用该函数的对象引用。
this设计的使用场景
1:没有this会出现什么问题
1:定义Person类
1:有姓名年龄成员变量,有说话的方法。
2:定义构造方法,无参的,多个有参的。都要实现。
1234567891011121314151617181920212223242526272829 > class Person {> String name;> int age;> //无参数构造函数> Person() {> System.out.println("这是无参的构造函数");> }> //有参数构造函数> Person(int a) {> age = a;> System.out.println("有参构造1");> }> //有参数构造函数> Person(String n) {> name = n;> System.out.println("有参构造2");> }> //有参数构造函数> Person(int a, String n) {> age = a;> name = n;> System.out.println("有参构造");> }> //普通函数> void speak() {> System.out.println("hah");> }> }>
>
假设定义40个成员变量,第一个有参构造初始化20个变量,第二个有参构造需要初始化40个变量。
1:第二个有参构造想要使用第一个有参构造。
2:成员函数相互之间可以调用。构造函数可以吗?
3:编译失败,那么构造函数之间应该存在相互调用的模式。this就可以完成这个工作。
12345678910111213141516 > class Person {> String name;> int age;> Person() {> }> Person(String n){> name=n;> }> Person(String n, int a) {> //编译报错> Person(n);> age = a;> }> }>>
1234567891011121314151617181920212223242526272829303132333435 > class Student {> String name;> String gender;> int age;> Student() {> }> Student(String name) {> this();> this.name = name;> }> Student(String name, String gender, int age) {> this(name);> System.out.println(this); // Student@c17164> this.gender = gender;> this.age = age;> }> void speak() {> run();> System.out.println("姓名:" + name + " 性别:" + gender + " 年龄:" + age> + " 哈哈!!!");> }> void run() {> System.out.println("run.....");> }> }> class Demo2 {> public static void main(String[] args) {> Student p = new Student("jack", "男", 20);> System.out.println(p); // Student@c17164> Student p2 = new Student("rose", "女", 18);> System.out.println(p2);> p.speak();> }> }>
>
总结
实际工作中,存在着构造函数之间的相互调用,但是构造函数不是普通的成员函数,不能通过函数名自己接调用,所以sun公司提供this关键字。
this是什么
1:在构造函数中打印this
2:创建对象,打印对象名p
3:this和p是一样的都是内存地址值。
4:this代表所在函数所属对象的引用。
Static关键字
定义
java中static可以修饰成员变量、方法、类、代码块.
static变量
12345678 > 按照是否静态的对类成员变量进行分类可分两种:> 一种是被static修饰的变量,叫静态变量或类变量;> 另一种是没有被static修饰的变量,叫实例变量。>> 两者的区别是:>> 对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。对于实例变量,每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。>
>
静态方法
12 > 静态方法可以直接通过类名调用,任何的实例也都可以调用,因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。因为实例成员与特定的对象关联!因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。>
>
static修饰类
12 > 一般静态内部类可以用static修饰(java内部类分为四种:常规内部类、静态内部类、局部内部类、匿名内部类)。只能访问外部类的static成员,不能直接访问外部类的实例变量与实例方法。>
>
static语句块
1234 > 可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块。> (1)当一个类中有多个static{}的时候,按照static{}的定义顺序,从前往后执行;> (2)先执行完static{}语句块的内容,才会执行调用语句;>
内存分析
1:定义Person类
1:姓名、年龄、国籍,说话行为
2:多个构造,重载形式体现
2:中国人的国籍都是确定的
1:国籍可以进行显示初始化
123456789101112131415161718 > class Person {> String name;> int age;> String gender;> String country = "CN";> Person() {> }> Person(String name, int age, String gender, String country) {> this.name = name;> this.age = age;> this.gender = gender;> this.country = country;> }> void speak() {> System.out.println("国籍:" + country + " 姓名:" + name + " 性别:" + gender + " 年龄:" + age + " 哈哈!!!");> }> }>
>
3:new Person 对象
1:分析内存
2:每个对象都维护实例变量国籍也是。
12345678910 > public class PersonDemo {> public static void main(String[] args) {> Person p1 = new Person("jack", 20, "男");> p1.speak();> Person p2 = new Person("rose", 18, "女");> p2.speak();> }> }>>
>
4:内存分析
1:栈,堆、共享区(方法区)
2:Demo.class加载近共享区(方法区)
1:Demo类的main方法进栈
2:Person p1=new Person();
1:Person.class 加载进方法区
2:堆内存开辟空间,实例变量进行默认初始化,显示初始化。
3:内存地址传给变量p1,栈和堆建立连接
3:person p2=new Person();
1:堆内存开辟空间,实例变量进行默认初始化,显示初始化。
2:内存地址传给变量p2,栈和堆建立连接
4:如果建立多个Person对象发现问题
1:每个对象都维护有国籍。
5:解决问题,内存优化
1:为了让所有Person对象都共享一个country ,可以尝试将country放入共享区。
2:country变量如何放入共享区?对象如何访问?
1:使用static
2:static
1:为了实现对象之间重复属性的数据共享
3:static使用
1:主要用于修饰类的成员
1:成员变量
1:非静态成员变量:需要创建对象来访问
2:静态成员变量:使用类名直接调用,也可以通过对象访问
12345678910111213141516171819202122232425262728293031 > class Person {> String name;> int age;> String gender;> //static 修饰成员变量> static String country = "CN";> Person() {> }> Person(String name, int age, String gender) {> this.name = name;> this.age = age;> this.gender = gender;> }> //非静态方法> void speak() {> //非静态方法可以访问静态成员> System.out.println("国籍:" + country );> System.out.println("国籍:" + country + " 姓名:" + name + " 性别:" + gender> + " 年龄:" + age + " 哈哈!!!");> }> //静态方法> static void run(){> //静态方法只能访问静态成员变量。> System.out.println("国籍:"+country);> //静态方法访问非静态成员变量,编译报错。> System.out.println(" 姓名:" + name);> //静态方法中不可以出现this,编译报错> this.speak();> }> }>
>
2:细节:
1:静态函数中不能使用非静态变量
2:非静态函数可以访问静态变量
3:为什么静态函数中不能访问非静态成员
1:static修饰的成员在共享区中。优先于对象存在
2:验证
1:使用静态代码块验证
1:静态代码块
static{
静态代码块执行语句;
}
1:静态代码块特点
随着类的加载而加载。只执行一次,优先于主函数。用于给类进行初始化。
static的特点
static特点
1 随着类的加载而加载,静态会随着类的加载而加载,随着类的消失而消失。说明它的生命周期很长。
2 优先于对象存在。–>静态是先存在,对象是后存在。
3 被所有实例(对象)所共享。
4 可以直接被类名调用
5:静态变量(类变量)和实例变量的区别:
1存放位置
1:类变量随着类的加载而加载存在于方法区中.
2:实例变量随着对象的建立而存在于堆内存中.
2生命周期
1:类变量生命周期最长,随着类的消失而消失.
2:实例变量生命周期随着对象的消失而消失.
6:静态优缺点
1: 优点:对对象的共享数据进行单独空间的存储,节省空间 例如Person 都有
国籍。该数据可以共享可以被类名调
2:缺点:生命周期过长
访问出现局限性。(静态只能访问静态)
7: 什么时候定义静态变量
1:静态变量(类变量)当对象中出现共享数据
例如:学生的学校名称。学校名称可以共享
对象的数据要定义为非静态的存放在对内存中(学生的姓名,学生的年龄)
8:什么时候定义静态函数
如果功能内部没有访问到非静态数据(对象的特有数据。那么该功能就可以定义为静态)
9:静态的应用
自定义数组工具类
1234567891011 > // 1:定义一个遍历数组的函数> public static void print(int[] arr) {> for (int x = 0; x < arr.length; x++) {> if (x != (arr.length - 1)) {> System.out.print(arr[x] + ",");> } else {> System.out.print(arr[x]);> }> }> }>
>
123456789 > // 2:定义一个求数组和的功能函数> public static int getSum(int[] arr) {> int sum = 0;> for (int x = 0; x < arr.length; x++) {> sum += arr[x];> }> return sum;> }>
>
static加载顺序
(1)当一个类中有多个static{}的时候,按照static{}的定义顺序,从前往后执行;
(2)先执行完static{}语句块的内容,才会执行调用语句;
12345678910111213141516171819 > public class A {>> public static void main(String args[]){> System.out.println(5);> }> static{> System.out.println(1);> }> static {> System.out.println(2);> }> static {> System.out.println(3);> }> static {> System.out.println(4);> }> }//运行结果为1 2 3 4 5>
>
如果静态变量在定义的时候就赋给了初值(如 static int X=100),那么赋值操作也是在类加载的时候完成的,并且当一个类中既有static{}又有static变量的时候,同样遵循“先定义先执行”的原则。
123456789101112 > public class A {> public static int x = 10;> static{> System.out.println(x);> x = 20;> System.out.println(x);> }> public static void main(String args[]){> int y = A.x;> }> }//结果为10 20>
>
通过static来理解类的加载顺序
12345678910111213141516171819202122232425 > public class A {> static{> System.out.println("Static A");> }> public A(){> System.out.println("constructor A");> }> public static void main(String args[]){> new B();> }> }> class B {> static{> System.out.println("Static B");> }> public B(){> System.out.println("constructor B");> }> }> 运行结果:>> Static A> Static B> constructor B>
>
类A加载之前首先加载其父类
1234567891011121314151617181920 > public class A extends B {> static{> System.out.println("Static A");> }> public A(){> System.out.println("constructor A");> }> public static void main(String args[]){> new A();> }> }> class B {> static{> System.out.println("Static B");> }> public B(){> System.out.println("constructor B");> }> }>
>
类A加载之前首先加载其父类
12345678910111213141516171819202122232425 > public class A extends B {> static{> System.out.println("Static A");> }> public A(){> System.out.println("constructor A");> }> public static void main(String args[]){> new A();> }> }> class B {> static{> System.out.println("Static B");> }> public B(){> System.out.println("constructor B");> }> }> 结果:> Static B> Static A> constructor B> constructor A>
>
只有直接定义静态字段X的类才会被初始化
123456789101112131415161718192021222324252627 > public class A extends B{> static{> System.out.println("Static A");> }> public A(){> System.out.println("constructor A");> }>> }> class B {> public static int x = 10;> static{> System.out.println("Static B");> }> public B(){> System.out.println("constructor B");> }> }> public class C {> public static void main(String args[]){> System.out.println(A.x);> }> }> 结果:> Static B> 10>
Main方法详解
java虚拟机通过main方法找到需要启动的运行程序,并且检查main函数所在类是否被java虚拟机装载。如果没有装载,那么就装载该类,并且装载所有相关的其他类。因此程序在运行的时候,第一个执行的方法就是
main()
方法。通常情况下, 如果要运行一个类的方法,必须首先实例化出来这个类的一个对象,然后通过”对象名.方法名()“的方式来运行方法,但是因为main是程序的入口,这时候还没有实例化对象,因此将main方法声明为static的,这样这个方法就可以直接通过“类名.方法名() ”的方式来调用。
先说类
HelloWorld 类中有
main()
方法,说明这是个java应用程序,通过JVM直接启动运行的程序。
既然是类,java允许类不加public关键字约束,当然类的定义只能限制为public或者无限制关键字(默认的)。main()方法
这个
main()
方法的声明为:public static void main(String args[])
。必须这么定义,这是Java的规范。为什么要这么定义,和JVM的运行有关系。
当一个类中有
main()
方法,执行命令“java 类名”则会启动虚拟机执行该类中的main方法。由于JVM在运行这个Java应用程序的时候,首先会调用main方法,调用时不实例化这个类的对象,而是通过类名直接调用因此需要是限制为public static。
对于java中的main方法,jvm有限制,不能有返回值,因此返回值类型为void。
main方法中还有一个输入参数,类型为String[],这个也是java的规范,
main()
方法中必须有一个入参,类细必须String[]
,至于字符串数组的名字,这个是可以自己设定的,根据习惯,这个字符串数组的名字一般和sun java规范范例中mian参数名保持一致,取名为args。因此,
main()
方法定义必须是:“public static void main(String 字符串数组参数名[])
”。main()方法中可以抛出异常
1234567 > class ExceptionDemo{> public static void main(String args[])throws Exception {> System.out.println("hello");> throw new Exception("error");> }> }>
>
main()方法中字符串参数数组作用
main()
方法中字符串参数数组作用是接收命令行输入参数的,命令行的参数之间用空格隔开。下面给出一个例子,看看如何初始化和使用这个数组的。
123456789101112 > /**> * 打印main方法中的输入参数> */> public class TestMain {> public static void main(String args[]){> System.out.println("打印main方法中的输入参数!");> for(int i=0;i<args.length;i++){> System.out.println(args[i]);> }> }> }>
>
实例
虚拟机通过调用某个指定类的方法main启动,传递给main一个字符串数组参数,使指定的类被装载,同时链接该类所使用的其它的类型,并且初始化它们。例如对于程序:
123456789 > public class HelloApp {> public static void main(String[] args) {> System.out.println("Hello World!");> for (int i = 0; i < args.length; i++) {> System.out.println(args);> }> }> }>
>
编译后在命令行模式下键入: java HelloApp run virtual machine
将通过调用HelloApp的方法main来启动java虚拟机,传递给main一个包含三个字符串”run”、”virtual”、”machine”的数组。现在我们略述虚拟机在执行HelloApp时可能采取的步骤。
开始试图执行类HelloApp的main方法,发现该类并没有被装载,也就是说虚拟机当前不包含该类的二进制代表,于是虚拟机使用ClassLoader试图寻找这样的二进制代表。如果这个进程失败,则抛出一个异常。类被装载后同时在main方法被调用之前,必须对类HelloApp与其它类型进行链接然后初始化。链接包含三个阶段:检验,准备和解析。检验检查被装载的主类的符号和语义,准备则创建类或接口的静态域以及把这些域初始化为标准的默认值,解析负责检查主类对其它类或接口的符号引用,在这一步它是可选的。类的初始化是对类中声明的静态初始化函数和静态域的初始化构造方法的执行。一个类在初始化之前它的父类必须被初始化。