类和对象
前面记录的都是java面向过程的记录,而现在起,则是面向对象的开始。也正是java的魅力所在。
类是对象的抽象化,对象是类的实例化。怎么说呢,类,定义某个事物的特征,比如球,有半径,有材质,有厚度,有弹性。而对象呢,根据具体事物来说明,半径2cm,材质塑料,厚度1mm,弹性一般,这可能就是乒乓球,而其他球也可以根据类的定义来实例化(对象化)。所以才有万物皆对象,我们可以抽象任何事物的特征,根据不同的特征组合来表述现有的事物。
定义一个类
从我们学习java开始,就已经接触类。
1 | public class HelloWorld { //一个类只要唯一一个公共类 |
怎么用对象呢,都说new一个女朋友,我们把程序改改
1 | public class HelloWorld { |
我们分析为这两个的区别。当然首先我们应该也知道,我们写.java的代码,编译器编译为.class的字节码,最后在虚拟机(JVM)运行。这是java之所以流行的原因,跨平台。一次编译,处处运行。
我们的代码都在内存上运行,所以要涉及栈,堆,方法区。栈的特点,连续空间,先进后出,就想乒乓球桶一样,最后放进来的一定最先出来。堆在内存中是随机,无序存放的,不要与数据结构的堆混淆。方法区存放代码块的地方,类,静态的,常量,方法名等等,而方法区是在堆里的。
第一个代码,我们的代码名为HelloWorld,编译器会先找到HelloWorld的类,把该类的相关代码放进方法区,并在栈中开辟一个空间。接着编译器找到main方法,程序运行的入口。一步步运行,只有一个输出,打印,程序运行结束,关闭栈,关闭该程序其它占用的内存(堆)。
第二个代码,前面都一样,从main开始,遇到hi的变量名,放入栈中,hi = null; 然后new一个HelloWordl类的引用,既然new,则又在栈中开辟第二个空间,存放helloWordl类的引用地址,并new的类的引用放到了堆里,里面有printHello方法,使用hi.printHello方法后,关闭第二个栈和该类占用的内存,接着程序结束,关闭第一个栈,关闭该程序其他占用的内存。
构造方法
我们在new HelloWorld()方法时,其实调用了HelloWorld的默认构造方法。构造方法也可以重写,下面的例子。
1 | public class HelloWorld { |
构造方法的注意:
1、构造方法没有返回类型、返回值。
2、如果没有重写(重载:除了方法名和内容其它都可以改变;重写:除了方法名和返回类型其它都可以改变),只会调用默认构造方法。
3、重写构造方法,默认构造方法也就没有了,编译器觉得你都自己写了,那我给你的,你也没必要用了。所以还想要默认构造方法,自己写一个。
其它注意:
在以前写代码,没有赋值的变量如果被使用,编译器会报错,但在这hello2中,我new的默认构造方法,hi变量没赋值,所以输出String的默认值”null”。简单说明下,在方法内的变量称为局部变量,只归该方法使用,默认没有初始化。而在方法外,类内的的变量称为成员变量(全局变量),能在该类任意一个位置使用,自带初始化,String引用类型,默认值null。
关键字this
this指向当前类,可以用当前类的变量或构造方法。
1 | package io.github.java04; |
注意:this. ,注意’.’,指代调用当前类的变量。this()调用本类的其他构造方法时,只能放在构造方法的第一行。与this()对应的还有super()指代父类,下面会有记录。
关键字static和final
静态的,第三章也简单带过,这里在带过一下
1 | public class HelloWorld { |
static:静态的,一开始加载到方法区,可以在方法中直接使用,如第21行直接调用printHello()方法,没有用类去调用。
第12行把a变为16,对应的第19行输出非静态变量c编译器直接报错。
既然static是在方法区,如果静态的值改变,那么其它引用的类的a值也改变,参考代码。
final:会用就行,常量,一开始也加载到方法区,当final修饰,所以不可修改,修饰方法也就没有继承,比如Math类,String类等等。
方法区
1 | public class HelloWorld { |
1 | 你好,static方法块 |
我们慢慢分析,静态、常量、方法名、类一开始加载到方法区,所以最新输出static方法块,之后开始执行main方法,p引用HelloWorld对象,开辟新的栈,堆加载一个HelloWorld对象,由于static已经加载,所以不在执行static方法块,输出普通的方法块,接着画出横线,使用sayHi()方法.又画出横线,又新建一个对象,开辟新的栈,加载新对象,使用普通方法块,程序结束,关闭所有占用的内存。
面向对象
以上是类的简单示例,和对象的基本认识。现在开始就是面向对象的三大特性:封装,继承,多态。
封装
在求面积那,就简单封装了一下,用构造方法设置边长,面积只能getArea()获取,我们可以在这些设置或获取的方法中做相应的限制。比如输出一个年龄,如果没有封装,可以随意输入,-1,-10,1000000,可现实中,生物中生命不可能为负数,也不可能活的非常非常的长,可以设置if年龄在0到150之间为正确输入,如果超过,提示用户错误信息。
1 | public class years { |
封装基本就这样,就像我们现在看电视手机,用户不需要知道内部是怎么样的,怎么运行的,我们也不可以把内部代码放出来给用户修改。只需要给用户一个遥控器,一个触摸屏,保持一个合理的使用方法即可。也像我们的身体,内部怎样工作不清楚,我们只要观四方听八面走万里就行。
继承
子类继承父类,为啥,简单一句,提高开发效率,避免代码冗余。但在java中,继承只能单继承,并且所有类都默认继承Object,只有接口才能多继承。
1 | public class AnimalTest { |
父类Animal,子类dog,父类的自己的任何属性可以自己调用,但父类不可以调用子类的属性,就像人一样,我们的身体是从父母得来的,而父母的身体自然也不可能是从子女来。子类可以用父类除了私有的任何属性,也可以用自己的属性,也可以定义和父类一样的变量,也可以重写父类的方法。这就是继承。
关键字super
super()调用的是父类的构造方法,在子类中使用,也是放到构造方法的第一行。所以,this()和super()只能调用任意一个。
1 | public class superTest { |
上面只是new cat类,程序运行时,默认调用cat的默认构造方法,在cat的构造方法里默认调用了super(),也就是父类的默认构造方法,无论有没有super(),都调用父类的默认构造方法,而父类又向上调用super(),直到Object类的默认构造方法,最后一层层返回,返回到animal的默认构造方法的内容,运行结束又返回到cat的默认方法的内容,最后结束运行。整体就是递归调用的思想。
而横线下面,调用了eat(),在子类中,super.eat()调用了父类的方法。
当然如果父类没有默认的构造方法,但又想用父类的构造方法,就可以用super(参数名)的形式。
多态
同种物,不同的形态,就像陆地哺乳动物,眼鼻耳口肢体,外形都可以类似,但各自习惯,能力,体型等不同而产生的各种形态。
1 | public class AnimalTest { |
把代码改改,用父类的引用指向了子类,简单意义就是多态,有父类的共同特点,也只可以用父类的属性。但是,子类自己定义的属性不能使用,子类与父类一样的变量名,也只使用父类的。这就是动态绑定,如果子类重写父类的方法,则用子类的,没有向父类找,直到找到最后一次重写的方法,如果找到了Object类也没有找到,则编译器自动报错。
toString()
toString()是Object类的方法,下面是它默认的方法,我的jdk是1.8,所以可能与其它版本有差异,但大致都差不多。
1 | public String toString() { |
可以看的出来,返回了类的名称+@+类的哈希地址下面是示例。
1 | package io.github.java04; |
1 | //这是得到的答案 |
我们一般会重写toString()方法,返回我们想得到的信息,比如类名,结果,而不是一串看不懂的哈希值。
equals()
equals()在后面String学习,我们都是通过equals()来判断String的内容是否相等,而不用 == ,因为在java中, == 是判断两个对象是否一样,或基本数据类型才比较值。而不是对象的内容。但是在String中是重写了该方法。下面是Object类的equals()。
1 | public boolean equals(Object obj) { |
看的出来,返回的是对象是否相等,并没有比较值,所以我们在用equals(),想比较值是否相等,一定要重写该方法。下面是错误示例。
1 | public class equalsTest { |
下面是重写后的equals()方法。
1 | public class equalsTest { |
好久没更了,但一直在想办法进步,努力,提升自己,大家一起加油吧!!!