目录
面向过程与面向对象的区别
* 面向过程:【主要关注点是:实现的具体过程,因果关系】
- 优点:对于业务逻辑比较简单的程序,可以达到快速开发,前期投入成本较低。
- 缺点:采用面向过程的方式开发很难解决非常复杂的业务逻辑,另外面向过程的方式导致软件元素之间的"耦合度"非常高,只要其中一环出问题,整个系统受到影响,导致最终的软件扩展力差。另外,由于没有独立体的概念,所以无法达到组件复用。
* 面向对象:【主要关注点是:主要关注对象(独立体)能完成哪些功能。】
- 优点:耦合度低,扩展力强。更容易解决现实世界当中更复杂的业务逻辑。组件复用强。
- 缺点:前期投入成本较高,需要进行独立体的抽取,大量的系统分析与设计。
* C语言是纯面向过程的、C++半面向对象、Java纯面向对象
* 现在出现的一些新的编程语言多数都是面向对象的,人在认识现实世界的时候以面向对象的方式。面向对象更符合人的思维方式。
面向对象的三大特征
【面向对象是Java语言的核心机制,最重要的内容,Java语言的特色】
- 封装
- 继承
- 多态
所有面向对象的编程语言都有这三大特性。
采用面向对象的方式开发一个软件,生命周期当中:【整个生命周期中贯穿使用oo面向对象方式】
- 面向对象的分析:OOA
- 面向对象的设计:OOD
- 面向对象的编程:OOP
类和对象的概念
* 什么是类?
- 类在现实世界当中是不存在的,是一个模板,是一个概念。是人类大脑思考抽象的结果。
- 类代表了一类事物。
- 在现实世界当中,对象A与对象B之间具有共同特征,进行抽象总结出一个模板,这个模板被称为类。
* 什么是对象?
- 对象是实际存在的个体。现实世界当中实际存在。
* 类 ---【实例化】---> 对象(对象又被称为实例/instance)
* 对象 ---【抽象】---> 类
* 【重点】:
类描述的是对象的共同特征。
共同特征例如: 身高特征
这个身高特征在访问的时候,必须先创建对象,通过对象去访问这个特征。
因为这个特征具体的某个对象上之后,值不同。
有的对象身高1.80,有的对象身高2.80
* 一个类主要描述的是--【状态 + 动作】
状态信息:名字、身高、性别、年龄...
动作信息:吃、唱歌、跳舞、学习...
状态 --> 一个类的属性
动作 --> 一个类的方法
类{
属性; //描述对象的状态信息
方法; //描述对象的动作信息
}
【注意】:
状态和动作当具体到某个对象之上,发现最终的结果可能不一样。
对象和对象之间有共同特征,但是具体到对象之后有数据的差异。
类的定义
- 语法结构:
[修饰符列表] class 类名{
属性;
方法;
}
学生类,描述所有学生对象的共同特征:
学生对象有哪些状态信息:
* 学号【int】、名字【String】、性别【boolean】
* 年龄【int】【年龄是一个属性,也是一个数据,是数据就应该有数据类型】
.....
学生对象有哪些动作信息:
* 吃饭、睡觉、学习、唱歌 ......
重点:属性通常是采用一个变量的形式来完成定义的。
int no;
int age;
String name;
String address;
boolean sex;
* java语言中包括两种数据类型:
- 基本数据类型
byte、short、int、long、float、double、boolean、char
- 引用数据类型
java语言中所有的class都属于引用数据类型。
String.class、System.class ...... SUN提供的
Student.class、User.class ..... 程序员自定义的
举个简单的例子:
//定义一个类,类名为Student
//Student是一个类,代表了所有的学生对象。是一个学生模板。
//描述了所有学生的共同特征【状态+行为】
public class Student{ //定义一个公开的类,起名Student
//类体=属性+方法
//属性【描述的是对象的状态信息】,属性通常采用变量的方式来定义
//在类体当中,方法体之外定义的变量被称为"成员变量"
//成员变量没有赋值,系统赋默认值:一切向0看齐。
int no; //学号
String name; //姓名
boolean sex; //性别
int age; //年龄
String address; //住址
//方法描述的是对象的动作信息
//当前例子只描述属性了,不描述方法。
}
对象的创建和使用-内存分配
- 什么是对象?
new运算符在堆内存中开辟的内存空间称为对象。
- 先介绍一下JVM的内存管理
创建一个学生类Student
public class Student{
int no; //编号
String name; //姓名
}
上述代码解析;
Student是学生类,学生类是一个模板,描述了所有学生的共同特征【状态+行为】,属于引用数据类型,这个类型名就是:Student
类体=属性+方法
属性【存储数据采用变量的形式】,由于变量定义在类体当中,方法体之外,这种变量称为成员变量。所有学生都有学号信息,但是每一个学生的学号都是不同的,所以要访问这个学号必须先创建对象,通过对象去访问学号信息,学号信息不能直接通过"类"去访问,所以这种成员变量又被叫做:实例变量。
对象又被称为实例,实例变量又被称为对象变量。【对象级别的变量】,不创建对象,这个no变量的内存空间是不存在的,只有创建了对象,这个no变量内存空间才会创建。
创建一个测试类
public class Test01{
public static void main(String[] args){
Student s = new Student();
System.out.println("学号 = " + s.no); //0
System.out.println("姓名 = " + s.name); //null
s.no = 10;
s.name = "jack";
System.out.println("学号 = " + s.no); //10
System.out.println("姓名 = " + s.name); //jack
Student stu = new Student();
System.out.println(stu.no); //0
System.out.println(stu.name); //null
}
}
上述代码解析:
通过一个类可以实例化N个对象,实例化对象的语法:
new 类名();
new是java语言当中的一个运算符,new运算符的作用是创建对象,在JVM堆内存当中开辟新的内存空间
方法区内存:在类加载的时候,class字节码代码片段被加载到该内存空间当中。
堆内存(局部变量):方法代码片段执行的时候,会给该方法分配内存空间,在栈内存中压栈。堆内存:new的对象在堆内存中存储
s是一个变量名,也是一个局部变量【在栈内存中存储】
new Student()是一个学生对象
什么是引用?
引用是一个变量,只不过这个变量中保存了另一个java对象的内存地址。
java语言中,程序员不能直接操作堆内存,java中没有指针。【不像C语言】,只能通过"引用"去访问堆内存当中对象内部的实例对象。
- 访问实例变量的语法格式:
读取数据:引用.变量名
修改数据:引用.变量名 = 值
局部变量在栈内存中存储,成员变量中的实例变量在堆内存的java对象内存存储
实例变量是一个对象一份,100个对象有100份,再通过类实例化一个全新的对象,stu是一个引用,同时也是一个局部变量,Student是变量的数据类型
【注意】:no这个实例变量不能直接采用"类名"的方式访问,因为no是实例变量,对象级别的变量,变量存储在java对象的内部,必须先有对象,通过对象才能访问no这个实例变量,不能直接通过"类名"访问
System.out.println(Student.no); // 编译错误
也可以在Student类中,加入Address数据类型的实例变量
public class Student{
int no; //编号
String name; //姓名
Address addr; // 地址
}
Address 类
public class Address{
String city; //城市
String street; //街道
}
上述代码解析:
Student类中的Address数据类型和String数据类型,都属于引用类型,但String是SUN提供的,Address是自定义的。
测试类
public class Test02{
public static void main(String[] args){
//创建Student对象,stu是局部变量,也是一个引用
//stu保存内存地址指向堆内存的Student对象
Student stu = new Student();
//输出User对象内部实例变量的值
System.out.println(stu.no); //0
System.out.println(stu.name); //null
System.out.println(stu.addr); //null
//修改User对象内部实例对象变量的值
stu.no = 110;
stu.name = "jack"; //"Jack"是一个java对象,属于String对象
stu.addr = new Address();
//在main方法当中目前只能看到一个引用"stu",一切都是只能通过stu来进行访问
System.out.println(stu.name + "居住在哪个城市:" + stu.addr.city);
System.out.println(stu.name + "居住在哪个街道:" + stu.addr.street);
stu.addr.city = "北京";
stu.addr.street = "朝阳";
System.out.println(stu.name + "居住在哪个城市:" + stu.addr.city);
System.out.println(stu.name + "居住在哪个街道:" + stu.addr.street);
}
}
上述代码解析:
Student中实例变量addr的赋值,还可以使用下面方式
Address a = new Address(); //a是引用,也是局部变量 stu.addr = a;
两个类相互引用
丈夫类
//丈夫类
public class Husband{
String name; //姓名
Wife w; //丈夫对象当中含有妻子引用
}
妻子类
//妻子类
public class Wife{
String name; //姓名
Husband h; //妻子对象当中含有丈夫引用
}
测试类
public class Test03{
public static void main(String[] args){
//创建一个丈夫对象
Husband xiaoMing = new Husband();
xiaoMing.name = "小明";
//创建一个妻子对象
Wife xiaoHong = new Wife();
xiaoHong .name = "小红";
//结婚【能通过丈夫找到妻子,通过妻子也可以找到丈夫】
xiaoMing.w = xiaoHong ;
xiaoHong .h = xiaoMing;
System.out.println(xiaoMing.name + "的妻子名字叫:" + xiaoMing.w.name);
}
}
【注意】:
一定要注意空指针异常
Husband xiaoMing = new Husband();
System.out.println(xiaoMing.w.name); // 编译成功,运行失败
上面会抛出java.lang.NullPointerException这个异常,空指针异常。
面向对象的封装性
当前主要讲解的是封装机制。什么是封装?封装有什么好处? 封装的好处:
1、封装之后,对于那个事物来说,看不到这个事物比较复杂的那一面,只看到该事物简单的那一面。复杂性封装,对外提供简单的操作入口。照相机就是一个很好的封装的案例,照相机的实现原理非常复杂,但是对于使用照相机的人来说,操作起来是非常方便的是非常便捷的;还有像电视机也是封装的,电视机内存实现非诚复杂,但是对于使用者来说不需要关心内部的实现原理,只需要会操作遥控器就行。
2、封装之后才会形成真正的“对象”,真正的“独立体”
3、封装就意味着以后的程序可以重复使用,并且这个事物应该适应性比较强,在任何场合都可以使用。
4、封装之后,对于事物本身,提高了安全性。【安全级别提高】
对于下面代码来说:
User类中的age属性在外部程序中可以随意访问,导致age属性的不安全。
public class User {
int age; //年龄
}
还可能赋予一些不符合常识的值,比如
User user = new User();
user.age = -100;
这里的age属性显然是完全暴露给外部程序的,对于程序员来说可以操作User对象当中所有的细节,导致User中部分数据不安全。不建议这样,建议User类型进行封装,建议在外部程序中不能随意访问User对象当中的属性。这样可以保证属性的安全。
对User类进行封装处理
public class User {
//属性私有化
private int age;
//setter set方法没有返回值,因为set方法只负责修改数据。
public void setAge(int a) {
if(a < 0 || a > 150) {
System.out.println("对不起,您提供的年龄不合法!");
return;
}
//程序可以进行到这里的话,说明a的年龄是合法的,则进行赋值运算
this.age = a;
}
//getter
public int getAge() {
// 安全控制
return this.age;
}
}
【注意】:
java有就近原则,下面代码其实并没有给age属性赋值,这里的age都是局部变量
public void setAge(int age) {
age = age;
}
封装的步骤:
1、所有属性私有化,使用private关键字进行修饰,private表示私有的,修饰的所有数据只能在本类中访问。
2、对外提供简单的入口,也就是以后外部程序要想访问age属性,必须通过这些简单的入口进行访问:
- 对外提供两个公开的方法,分别是set方法和get方法
- 想修改age属性,调用set方法
- 想读取age属性,调用get方法
3、set方法的命名规范:
public void setAge(int a){
age = a;
}
4、get方法的命名规范:
public int getAge(){
return age;
}
使用idea快捷键,快速生成set get 方法 ,alt + insert ,选择set/get
一个属性通常访问的时候包括几种访问形式?
- 第一种方式:想读取这个属性的值,读取get
- 第二种方式:想修改这个属性的值,修改set
注意以下:
setter and getter方法没有static关键字
有static关键字修饰的方法怎么调用:类名.方法名(实参);
没有static关键字修饰的方法怎么调用:引用.方法名(实参);
将User类进行封装,实例变量私有化,外部彻底访问不到了
System.out.println(user.age); // 报错
只能下面这样访问了
User user = new User();
System.out.println(user.getAge());
再给age赋值的时候,也是加了一些安全控制的,本程序中这样赋值,会有提示信息打印出来。
user.setAge(-100);
一般现实中,set get 方法都是加上this关键字使用的
public void setAge(int age){
this.age = age;
}public int getAge(){
return this.age;
}
构造方法
1、构造方法又被称为构造函数/构造器/Constructor
2、构造方法语句结构:
[修饰符列表] 构造方法名 (形式参数列表){
构造方法体;
}
3、普通方法的语句结构:
[修饰符列表] 返回值类型 构造方法名 (形式参数列表){
构造方法体;
}
4、对于构造方法来说,“返回值类型”不需要指定,并且也不能写void,只要写上void,那么这个方法就变成普通方法了。
5、对于构造方法来说,构造方法的方法名必须和类名保持一致。
6、构造方法的作用?
构造方法存在的意义是,通过构造方法的调用,可以创建对象。
7、构造方法应该怎么调用?
* 普通方法是这样调用的:
- 方法修饰符中有static的时候:类名.方法名(实参列表)
- 方法修饰符列表中没有static的时候:引用.方法名(实参列表)
* new 构造方法(实参列表)
创建一个User类,并创建构造方法(无参数和有参数)
public class User {
//实例变量/对象变量,也就是说,必须先有对象才能有对应的实例变量。
private String name;
private int i;
//无参数的构造方法
public User() {
//初始化实例变量的内存空间
//name = null;
//i = 0;
System.out.println("User's Default Constructor Invoke!");
}
//有参数的构造方法
public User(int i, String name) {
this.i = i;
this.name = name;
System.out.println("带int,String类型参数的构造器");
}
}
调用User构造方法
//创建User对象,调用User类的构造方法来完成对象的创建
//以下程序创建了2个对象,只要构造函数调用就会创建对象,并且一定是在“堆内存”中开辟内存空间。
User u1 = new User();
User u4 = new User(10, "zhangsan");
8、构造方法调用执行之后,会返回值么?
每一个构造方法实际上执行结束之后都会有返回值,但是这个“return 值;”这样的语句不需要写。构造方法结束的时候java程序自动返回值。并且返回值类型是构造方法所在类的类型。由于构造方法的返回值类型就是本身,所以返回值类型不需要编写。
9、当一个类中没有定义任何构造方法的话,系统默认给该类提供一个无参数的构造方法,这个构造方法被称为缺省构造器。
10、当一个类显示的将构造方法定义出来了,那么系统则不再默认为这个类提供缺省构造器。建议开发中手动的为当前类提供无参数构造方法。因为无参数构造方法太常用了。
11、构造方法支持重载机制。在一个类当中编写多个构造方法,这多个构造方法显然已经构成方法重载机制。
12、构造方法的作用:
- 创建对象
- 创建对象的同时,初始化实例变量的内存空间。【给实例变量赋值】
成员变量之实例变量,属于对象级别的变量,这种变量必须先有对象才能有实例变量。
【注意】:
实例变量没有手动赋值的时候,系统默认赋值,那么这个系统默认值是在什么时候完成的呢?
不是在类加载的时候,因为类加载的时候只加载了代码片段,还没来得及创建对象,所以此时实例变量并没有初始化。实际上,实例变量的内存空间是在构造方法执行的过程当中完成开辟的,完成初始化的。系统在默认赋值的时候,也是在构造方法执行过程当中完成的赋值。
参数的传递
对象和引用的概念:
* 对象:目前在使用new运算符在堆内存中开辟的内存空间称为对象。
* 引用:是一个变量,不一定是局部变量,还可能是成员变量。引用保存了内存地址,指向了堆内存当中的对象。
* 所有访问实例相关的数据,都需要通过“引用.”的方式访问,因为只有通过引用才能找到对象。
* 只有一个空的引用,访问对象的实例相关的数据会出现空指针异常。
java语言当中方法调用的时候涉及到参数传递的问题,参数传递实际上传递的是变量中保存的具体值。
第一种情况
public class Test01 {
public static void main(String[] args) {
int i = 10;
add(i); //add(i); 等同于:add(10);
System.out.println("main-->" + i); //10
}
public static void add(int i) {
i++;
System.out.println("add-->" + i); //11
}
}
第二种情况
先创建一个User类
public class User{
int age;
public User(int i) {
age = i;
}
}
public class Test02 {
public static void main(String[] args) {
User u = new User(20);
//User u = 0x1234;
//传递u给add方法的时候,实际上传递的是u变量中保存的值,只不过这个值是一个java对象的内存地址。
add(u); //等同于:add(0x1234);
System.out.println("main-->" + u.age); //21
}
public static void add(User u) {
u.age++;
System.out.println("add-->" + u.age); //21
}
}
最终结论:
方法调用的时候,涉及到参数传递的问题,传递的问题,java只遵循一种语法机制,就是将变量中保存的“值”传递过去了,只不过有的时候这个值是一个字面值10(第一种情况),有时候这个值是另一个java对象的内存地址0x1234(第二种情况)
this关键字
1、this是一个引用,this是一个变量,this变量中保存了内存地址指向了自身,this存储在JVM堆内存java对象内部。
2、创建多少个java对象,而每一个对象都有this,也就是说有多少不同的this
3、this可以出现在“实例方法”当中,this指向当前正在执行这个动作的对象。(this代表当前对象)
创建一个Customer 类,添加一个实例方法(不带static关键字的一个方法)
public class Customer {
String name;
public Customer() {
}
public void shopping() {
System.out.println(this.name + "在购物!");
}
}
每一个顾客购物最终的结果是不一样的,所以购物这个行为是属于对象级别的行为,由于每一个对象在执行购物这个动作的时候最终结果不同,所以购物这个动作必须有“对象”的参与。
【重点】:没有static关键字的方法被称为“实例方法”,没有static关键字的变量被称为“实例变量”。
实例方法怎么访问? “引用.”
【注意】:当一个行为/动作执行的过程当中需要对象参与的,那么这个方法一定要定义为“实例方法”,不带static关键字。
上面代码中方法定义实例方法,因为每一个顾客在真正购物的时候,最终的结果是不同的。所以这个动作在完成的时候必须有对象的参与。
测试类
public static void main(String[] args) {
//创建Customer对象
Customer c1 = new Customer();
c1.name = "zhangsan";
c1.shopping(); //zhangsan在购物
//再创建Customer对象
Customer c2 = new Customer();
c2.name = "lisi";
c2.shopping(); //lisi在购物
}
4、this可以使用在构造方法当中,通过当前的构造方法调用其他的构造方法
【语法格式】:this(实参列表);
【重点】:this()这种语法只能出现在构造函数第一行
创建Date类
public class Date {
private int year;
private int month;
private int day;
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public Date() {
this(1970, 1, 1);
}
public void print() {
System.out.println(this.year + "年" + this.month + "月" + this.day + "日");
}
}
测试main方法中
Date time1 = new Date();
time1.print(); // 1970年1月1日
采用以上的语法来完成构造方法的调用,不会创建新的java对象,但是同时又可以达到调用其他的构造方法。
下面这种方式,也可以来调用另一个构造方法,但是创建了新的对象
new Date(1970, 1, 1);
5、this在多数情况下都是可以不用写的
由于name是一个实例变量,所以这个name访问的时候一定访问的是当前对象的name,所以多数情况下“this.”是可以省略的。写成下面这样:
System.out.println(name + "在购物!");
6、"this."什么时候不能省略?
用来区分局部变量和实例变量的时候,“this.”不能省略。
public void setId(String name) {
name = name;
}
以上程序的name和实例变量name无关,不能使用这种方式,需要使用下面的方法
public void setId(String name) {
this.name = name;
}
解析:等号前面的this.name是实例变量name,等号后面name是局部变量
7、this不能使用在带有static的方法当中
在Customer类中添加带有static关键字的方法
//带有static方法
public static void doSome() {
//System.out.println(name); //编译错误
//System.out.println(this); //编译错误
//可以采用以下方案,但是以下方案,绝对不是访问的当前对象的name
//创建对象
Customer c = new Customer();
System.out.println(c.name); //这里访问的name是c引用指向的对象的name
}
【注意】:
这个执行过程中没有“当前对象”,因为带有static的方法是通过类名的方式访问的或者说这个“上下文”当中没有“当前对象”,自然也不存在this(this代表的是当前正在执行这个动作的对象)
分析以上程序为什么编译错误呢?
doSome方法调用不是对象去调用,是一个类名去调用,执行过程中没有“当前对象”,
name是一个“实例变量”,以下代码的含义是:访问当前对象的name,没有当前对象,自然也不能访问当前对象的name。
System.out.println(name);
调用上述方法,通过“类名.”的方式
Customer.doSome();
【结论】:
在带有static的方法当中不能“直接”访问实例变量和实例方法。因为实例变量和实例方法都需要对象的存在。而static的方法当中是没有this的,也就是说当前对象是不存在的。自然也是无法访问当前对象的实例变量和实例方法。
super关键字
1、super是一个关键字,全部小写。
2、super和this对比着学习。
* this:
- this能出现在实例方法和构造方法中。
- this的语法是:“this.”、“this()”
- this不能使用在静态方法中。
- this.大部分情况下是可以省略的。
- this.什么时候不能省略呢?在区分局部变量和实例变量的时候不能省略。
public void setName(String name){
this.name = name;
}
- this()只能出现在构造方法第一行,通过当前的构造方法去调用“本类”中其他的构造方法,目的是:代码复用。
* super:
- super能出现在实例方法和构造方法中。
- super的语法是:“super.”、“super()”
- super不能使用在静态方法中。
- super.大部分情况下是可以省略的。
- super.什么时候不能省略呢?
父类和子类有同名属性,或者有同样的方法,想在子类中访问父类的,super. 不能省略。
创建Customer类
public class Customer {
String name;
public Customer() {
}
public Customer(String name) {
this.name = name;
}
public void doSome() {
System.out.println(this.name + "do Some!");
System.out.println(name + "do Some!");
}
}
创建Vip类继承Customer类
public class Vip extends Customer {
String name; //实例变量
public Vip() {
}
public Vip(String name) {
super(name);
}
public void shopping() {
System.out.println(this.name + "正在购物!"); //null正在购物!
System.out.println(super.name + "正在购物!"); //zhangsan正在购物!
System.out.println(name + "正在购物!"); //null正在购物!
}
}
main方法中测试
Vip v = new Vip("zhangsan");
v.shopping();
v.doSome();
结果
null正在购物!
zhangsan正在购物!
null正在购物!
zhangsando Some!
zhangsando Some!
java是怎么来区分子类和父类的同名属性的?
this.name:当前对象的name属性
super.name:当前对象的父类型特征中的name属性。
- super()只能出现在构造方法第一行,通过当前的构造方法去调用“父类”中的构造方法,目的是:创建子类对象的时候,先初始化父类型特征。
- super的使用:
super.属性名 【访问父类的属性】
super.方法名(实参) 【访问父类的方法】
super(实参) 【调用父类的构造方法】
3、super(实参)
表示通过子类的构造方法调用父类的构造方法。
模拟现实世界中的这种场景:要想有儿子,需要先有父亲。
创建一个A类
public class A {
public A() {
System.out.println("A类的无参数构造方法!");
}
public A(int i) {
System.out.println("A类的有参数构造方法(int)!");
}
}
创建一个B类继承A类
public class B extends A {
public B() {
super(); //调用父类中无参数的构造方法
System.out.println("B类的无参数构造方法!");
}
}
测试
new B();
输出
A类的无参数构造方法!
B类的无参数构造方法!
将B类改变一下
public class B extends A {
public B() {
super(123); //调用父类中无参数的构造方法
System.out.println("B类的无参数构造方法!");
}
}
结果就发生了变化
A类的有参数构造方法(int)!
B类的无参数构造方法!
【注意】:
在构造方法执行过程中一连串调用了父类的构造方法,父类的构造方法又继续向下调用它的父类的构造方法,但是实际上对象只创建了一个。
super(实参)的作用:
初始化当前对象的父类型特征,并不是创建新对象。实际上对象只创建了1个。
super关键字代表什么呀?
super关键字代表的就是“当前对象”的那部分父类型特征。
4、重要结论:
当一个构造方法第一行:既没有this(),又没有super()的话,默认会有一个super();表示通过当前子类的构造方法调用父类的无参数构造方法。所以必须保证父类的无参数构造方法是存在的。
super 不是引用。super也不保存内存地址,super也不指向任何对象。
super 只是代表当前对象内部的那一块父类型的特征。
使用"引用."的方式来访问时
System.out.println(this); // 不会报错
程序对自动调用引用的toString()方法
System.out.println(this.toString());
但是super就会报错
System.out.println(super); // 编译错误
需要调用super的方法或者属性。
System.out.println(super.toString());
5、 【注意】:
this()和super()不能共存,它们都是只能出现在构造方法第一行。
父类的构造方法是一定会执行的。
在java语言中不管是new什么对象,最后老祖宗的Object类的无参数构造方法一定会执行。(Object类中的无参数构造方法是处于“栈顶部”)
栈顶的特点:
最后调用,但是最先执行结束。后进先出原则。
static关键字
1、static修饰方法是静态方法
2、static修饰的变量是静态变量
3、所有static修饰的元素都称为静态的,都可以使用“类名.”的方式访问,当然也可以用“引用.”的方式访问【不建议】
4、static修饰的所有元素都是类级别的特征,和具体的对象无关。
什么时候成员变量声明为实例变量呢?
- 所有对象都有这个属性,但是这个属性的值会随着对象的变化而变化【不同对象的这个属性具体的值不同】
什么时候成员变量声明为静态变量呢?
- 所有对象都有这个属性,并且所有对象的这个属性的值是一样的,建议定义为静态变量,节省内存的开辟。
静态变量在类加载的时候初始化,内存在方法区中开辟。访问的时候不需要要创建对象,直接使用“类名.静态变量名”的方式访问。
举个例子
public class Chinese {
String id; //身份证号【每一个对象的身份证号都不同】
String name; // 姓名
static String country = "中国"; // 国籍
public Chinese() {
}
public Chinese(String id, String name) {
this.id = id;
this.name = name;
}
}
上述代码中变量country为静态变量,静态变量在类加载的时候初始化,不需要创建对象,内存就开辟了。静态变量存储在方法区内存当中。所有对象的country属性一样,这种特征属于类级别的特征,可以提升为整个模板的特征,可以在变量前添加static关键字修饰
下面的操作结果都是一样的
Chinese zhangsan = new Chinese("1", "zhangsan");
System.out.println(zhangsan.country);
System.out.println(Chinese.country);
方法什么时候定义为静态的?
方法描述的是动作,当所有的对象执行这个动作的时候,最终产生影响是一样的,那么这个动作已经不再属于某一个对象动作了,可以将这个动作提升为类级别的动作,模板级别的动作。
静态方法中无法直接访问实例变量和实例方法。
大多数方法都定义为实例方法,一般一个行为或者一个动作在发生的时候,都需要对象的参与。但是也有例外,例如:大多数“工具类”中的方法都是静态方法,因为工具类就是方便编程,为了方便方法的调用,自然不需要new对象是最好的。
创建一个工具类
public class MathUtil {
public static int sumInt(int a, int b) {
return a + b;
}
}
在main方法中调用工具类
System.out.println(MathUtil.sumInt(10, 20)); // 30
静态代码块
可以使用static关键字来定义“静态代码块”:
1、语法格式:
static{
java语句;
}
2、静态代码快在类加载时执行,并且只执行一次。
3、静态代码快在一个类中可以编写多个,并且遵循自上而下的顺序依次执行。
4、静态代码块的作用是什么?怎么用?用在哪?什么时候用?
- 和具体的需求有关,例如项目中要求类加载的时刻/时机执行代码完成日志的记录。那么这段记录日志的代码就可以编写到静态代码块当中,完成日志记录。
- 静态代码是java为程序员准备一个特殊的时刻,这个特殊的时刻被称为类加载时刻。若希望在此时刻执行一段特殊的程序,这段程序可以直接放到静态代码块当中。
5、通常在静态代码块当中完成准备工作,先完成数据的准备工具,例如:初始化连接池,解析XML配置文件....
public class StaticTest01 {
static {
System.out.println("类加载-->1");
}
static {
System.out.println("类加载-->2");
}
public static void main(String[] args) {
System.out.println("main begin");
}
}
运行结果如下
类加载-->1
类加载-->2
main begin
实例语句块/代码块【了解】
1、实例代码块可以编写多个,也遵循自上而下的顺序依次执行
2、实例代码块在构造方法执行之前执行,构造方法执行依次,实例代码块对应执行一次。
3、实例代码块也是Java语言为程序员准备一个特殊的时机,这个特殊时机被称为:对象初始化时机。
public class Test {
//构造函数
public Test(){
System.out.println("Test类的缺省构造器执行");
}
//代码块
{
System.out.println(1);
}
{
System.out.println(2);
}
//主方法
public static void main(String[] args) {
System.out.println("main begin");
new Test();
new Test();
}
}
运行结果
main begin
1
2
Test类的缺省构造器执行
1
2
Test类的缺省构造器执行
继承
1、继承是面向对象三大特征之一,三大特征分别是:封装、继承、多态
2、继承“基本”的作用是:代码复用。
继承最“重要”的作用是:有了继承才有以后“方法的重写”和“多态机制”。
3、继承的语法格式:
[修饰符列表] class 类名 extends 父类名 {
类体 = 属性 + 方法
}
4、java语句当中的继承只支持单继承,一个类不能同时继承很多类,只能继承一个类,在c++中支持多继承。
5、关于继承中的一些术语:
B类继承A类,其中:
A类称为:父类、基类、超类、superclass
B类称为:子类、派生类、subclass
6、在java语言当中子类继承父类都继承哪些数据呢?
- 构造方法不支持继承
子类有自己的构造器
- 私有的不支持继承,
还有另一种说法私有的支持继承,只是不能直接访问而己。
- 其他数据都可以继承
7、虽然java语言当中只支持单继承,但是一个类也可以间接继承其他类,例如:
C extends B{
}
B extends A{
}
A extends T{
}
C直接继承B类,但是C类间接继承A、T类。
8、java语言中假设一个类没有显示的继承任何类,该类默认继承JavaSE库当中提供的java.lang.Object类。java语言中任何一个类中都有Object类的特征。
创建Account类
public class Account {
private String actno;
private double balance;
public Account() {
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
创建CreditAccount类继承Account类
public class CreditAccount extends Account{
private double credit;
public CreditAccount() {
}
public double getCredit() {
return credit;
}
public void setCredit(double credit) {
this.credit = credit;
}
}
在main方法中调用
CreditAccount act = new CreditAccount();
act.setActno("act-001");
act.setBalance(-1000.0);
act.setCredit(0.99);
System.out.println(act.getActno() + "," + act.getBalance() + "," + act.getCredit());
输出结果为
act-001,-1000.0,0.99
方法重写
回顾java语言当中方法的重载:
1、方法重载又被称为Overload
2、方法重载什么时候使用?
当在同一个类当中,方法完后的功能是相似的,建议方法名相同,这样方便程序员的编程,就像在调用一个方法似的。代码美观。
3、什么条件满足之后构成方法的重载?
* 在同一个类当中
* 方法名相同
* 参数列表不同:类型、顺序、个数
4、方法重载和什么无关?
* 和方法的返回值类型无关
* 和方法的修饰符列表无关
关于java语言当中方法的重写:
1、方法重写又被称为方法覆盖,英文单词:override【官方的】/overwrite
2、什么时候使用方法重写?
当父类中的方法已经无法满足子类业务需求,
子类有必要将父类中继承过来的方法进行重新编写,
这个重新编写的过程称为方法重写/方法覆盖。
3、什么条件满足之后方法会发生重写呢?【代码满足什么条件之后,就构成方法的覆盖呢?】
* 方法重写反生在具有继承关系的父子类之间
* 方法重写的时候:返回值类型相同,方法名相同,形参列表相同
* 方法重写的时候:访问权限不能更低,可以更高。
* 方法重写的时候:抛出异常不能更多,可以更少。
4、建议方法重写的时候尽量复制粘贴,不要编写,容易出错,导致没有产生覆盖。
5、注意:
私有方法不能继承,所以不能覆盖。
构造方法不能继承,所以不能覆盖。
静态方法不存在覆盖。
覆盖只针对方法,不谈属性。
创建一个父类Animal
public class Animal {
public void move() {
System.out.println("动物在移动!");
}
}
创建子类Bird,并继承Animal,重写其方法
public class Bird extends Animal {
public void move() {
System.out.println("鸟在飞翔!");
}
}
创建子类YingWu,并继承Bird,重写其方法
public class YingWu extends Bird {
//这里的move方法覆盖的是Bird当中的move方法
public void move() {
System.out.println("鹦鹉飞不起来!");
}
}
在main方法中调用
public static void main(String[] args) {
//创建动物对象
Animal a = new Animal();
a.move();
//创建飞禽类动物对象
Bird b =new Bird();
b.move();
YingWu y = new YingWu();
y.move();
}
运行结果
动物在移动!
鸟在飞翔!
鹦鹉飞不起来!
多态
1、面向对象三大特征:封装、继承、多态
2、多态的作用?
* 降低程序的耦合度,提高程序的扩展力。
* 能使用多态尽量使用多态。
* 父类型引用指向子类型对象。
3、多态的核心:面向对象编程,尽量不要面向具体编程。
4、面向对象编程的核心:
定义好类,然后将类实例化为对象,给一个环境驱使一下,让各个对象之间协作起来形成一个系统。
5、多态中的几个概念:
* 向上转型(upcasting)
子类型 --> 父类型,又被称为:自动类型转换
* 向下转型(downcasting)
父类型 --> 子类型,又被称为:强制类型转换。【需要加强制类型转换符】
【注意】:
无论是向上转型还是向下转型,两种类型之间必须要有继承关系。没有继承关系,程序是无法编译通过的。
创建Animal类
public class Animal {
public void move() {
System.out.println("动物在移动!");
}
}
创建Bird类继承Animal类
public class Bird extends Animal {
//重写从父类继承继承过来的方法
public void move() {
System.out.println("鸟儿在飞翔!");
}
//子类对象特有的行为/动作
public void fly() {
System.out.println("Bird fly!");
}
}
创建Cat类继承Animal类
public class Cat extends Animal {
//重写父类中继承过来的方法
public void move() {
System.out.println("猫在走猫步!");
}
//这个方法是子类对象特有的行为
public void catchMouse() {
System.out.println("猫抓老鼠!");
}
}
因为Animal和Cat之间存在继承关系,Java中允许下面这种语法:父类型引用指向子类型对象。
Animal a = new Cat();
new Cat()创建的对象的类型是Cat,a这个应用的数据类型是Animal,可见他们进行了类型转换,子类型转换成父类型,称为向上转型/upcasting,或者被称为自动类型转换。
但是下面这种语法是错误的,因为两种类型之间不存在任何集成关系。无法向上或者向下转型。
Bird b = new Cat(); //编译报错
a.move(); //猫走猫步!
解析上面代码:
1、java程序永远都分为编译阶段和运行阶段。
2、先分析编译阶段,再分析运行阶段,编译无法通过,根本是无法运行的。
3、编译阶段编译器检查a这个应用的数据类型为Animal,由于Animal.class字节码当中有move()方法,所以编译通过了。这个过程我们成为静态绑定,编译阶段绑定。只有静态绑定成功之后才有后续的运行。
4、在程序运行阶段,JVM堆内存当中真实创建的对象是Cat对象,那么以下程序在运行阶段,一定会调用Cat对象的move()方法,此时发生了程序的动态绑定,运行阶段绑定。
5、无论是Cat类有没有重写move()方法,运行阶段一定调用的是Cat对象的move方法,因为底层真实对象就是Cat对象。
6、父类型引用指向子类型对象这种机制导致程序在编译阶段绑定和运行阶段绑定两种不同的形态/状态,这种机制可以成为一种多态语法机制。
a.catchMouse(); //编译失败
分析以上代码为什么不能调用?
因为编译阶段编译器检查到a的类型是Animal类型,从Animal.class字节码文件当中查找catchMouse()方法,最终没有找到该方法,导致静态绑定失败,没有绑定成功,也就是说编译失败了。更别谈运行了。
假设想让以上的对象执行catchMouse()方法,怎么办?
Cat c = (Cat)a;
c.catchMouse(); // 猫抓老鼠!
a是无法直接调用的,因为a的类型Animal,Animal中没有catchMouse()方法。我们可以带a强制类型转换为Cat类型。a的类型是Animal(父类),转换成Cat类型(子类),被访问向下转型/downcasting/强制类型转换。
什么时候需要使用向下转型呢?
当调用的方法是子类型中特有的,在父类型当中不存在,必须进行向下转型。
【注意】:向下转型也需要两种类型之间必须有继承关系。不然编译错误。
以下程序编译是没有问题的,但是程序在运行阶段会出现异常
Bird b1 = (Bird)a; //编译通过,运行错误
1、以上代码编译通过,因为编译器检查到a的数据类型是Animal,Animal和Bird之间存在继承关系,并且Animal是父类型,Bird是子类型,父类型转换成子类型叫做向下转型,语法合格。
2、程序虽然编译通过了,但是程序在运行阶段会出现异常,因为JVM堆内存当中真实存在的对象是Cat类型,Cat对象无法转换成Bird对象,因为两种类型之间不存在任何继承关系,此时出现了著名的异常:java.lang.ClassCastException,类型转换异常,这种异常总是在“向下转型的时候”会发生。
【注意】:
以上异常只有在强制类型转换的时候反生,也就是说“向下转型”时存在隐患(编译通过了,但是运行出错了)!
怎么避免向下转型出现的ClassCastException呢?
使用instanceof运算符可以避免出现以上的异常。
instanceof运算符
1、语法格式:
(引用 instanceof 数据类型名)
2、以上运算符的执行结果类型是布尔类型,结果可能是true/false
3、关于运算结果true/false:
假设:(a instanceof Animal)
true表示:
a这个引用指向的对象是一个Animal类型。
false表示:
a这个引用指向的对象不是一个Animal类型。
4、Java规范中要求:
在进行强制类型转换之前,建议采用instanceof运算符进行判断,避免ClassCastException异常的反生,这种一种编程好习惯。
//父类型引用指向子类型对象【多态】
Animal a2 = new Bird();
if(a2 instanceof Cat) { //a2是一个Cat类型的对象
Cat c2 = (Cat)a2;
c2.catchMouse(); //调用子类对象中特有的方法
}else if(a2 instanceof Bird) { //a2是一个Bird类型的对象
Bird b2 = (Bird)a2;
b2.fly(); //调用子类对象中特有的方法
}
运行结果
Bird fly!
final关键字
1、final是一个关键字,表示最终的,不可变的。
2、final修饰的类无法被继承。
3、final修饰的方法无法被覆盖
4、final修饰的变量“一旦”赋值之后,不可重新赋值【不可二次赋值】
final int k = 100; k = 200; //编译错误:无法为最终变量k分配值
5、final修饰的实例变量,必须手动赋值,不能采用系统默认值。
final int age; //编译错误
6、final修饰的引用,一旦指向某个对象之后,不能再指向其他对象,那么被指向的对象无法被垃圾回收器回收。
创建一个User类
public class User {
int id;
public User() {
}
public User(int id) {
this.id = id;
}
}
final User user = new User(30);
//user = new User(50);
System.out.println(user.id);
final修饰的引用,虽然指向某个对象之后不能指向其他的对象,但是所指向的对象内部的内存是可以被修改的。
user.id = 50;
System.out.println(user.id);
7、final修饰的实例变量,一般和static联合使用,被称为“常量”
常量的定义语法格式:
public static final 类型 常量名 = 值;
java规范中要求所有常量的名字全部大写,每个单词之间使用下划线连接
public static final double PI = 3.141592653;
调用方式:"类名.常量名"
包和import
包
1、包又被称为package,java中引入package这种语法机制主要是为了方便程序的管理。
不同功能的类被分门别类到不同的软件包中,查找比较方便,管理比较方便,易维护。
2、怎么定义package呢?
- 在java源程序的第一行上编写package语句。
- package只能编写一个语句。
- 语法结构:
package 包名;
3、包名的命名规范:
* 公司域名倒叙 + 项目名 + 模块名 + 功能名;
采用这种方式重名的几率较低。因为公司域名具有全球唯一性。
例如:
org.apache.tomcat.core; 【共4个目录,目录之间使用“.”隔开】
4、包名要求全部小写,包名也是标识符,必须遵守标识符的命名规范。
5、一个包将来对应的是一个目录。
6、使用package机制之后,应该怎么编译?怎么运行呢?
- 使用了package机制之后,类名不再是“原来的类名”了,而是"包名.类名",例如:test01.Test01(下面用这个作为案例)
- 编译:javac java源文件路径(在硬盘上生成一个.class文件:Test01.class)
- 手动方式创建目录,将Test01.class字节码文件放到指定的目录下
- 运行:java test01.Test01
-另一种方式(编译 + 运行):
* 编译:
java -d 编译之后存放路径 java源文件的路径
* 例如:将F:\Hello.java文件编译之后放到C:\目录下
javac -d C:\ F:\Hello.java
* javac -d . *.java
将当前路径中*.java编译之后存放到当前目录下。
* 运行:JVM的类加载器ClassLoader默认从当前路径下加载。
保证DOS命令窗口的路径先切换到test01所在的路径下,执行:
java test01.Test01
import
import语句用来完成导入其他类,同一个包下的类不需要导入,不再同一个包下需要手动导入。
import语法格式:
import 类名;
import 包名.*;
【注意】:import语句需要编写到package语句之下,class语句之上。
什么时候需要import?
* 不是java.lang包下,并且不在同一个包下的时候,需要使用import进行引入。
import java.*;
【注意】:上面这样是不允许的,因为在java语言中规定在,这里的 * 只代表某些类的名字。
import java.util.*;
上面这种方式效率不低,因为编译器在编译的时候,会自动把 * 变成具体的类名。
如果没有使用import,就需要使用类的全限定名称,例如日期类
java.util.Date d = new java.util.Date();
访问控制权限
访问控制权限修饰符:
1、访问控制权限修饰符来控制元素的访问范围
2、访问控制权限修饰符包括:
public 表示公开的,在任何位置都可以访问
protected 同包,子类
缺省 同包
private 表示私有的,只能在本类中访问
3、访问控制权限修饰符可以修饰 类、变量、方法...
4、当某个数据只希望子类使用,使用protected进行修饰。
5、修饰符的范围:
private < 缺省 < protected < public
抽象类和接口
抽象类和接口有什么区别(语法上的区别)?
抽象类是半抽象的。接口是完全抽象的。
抽象类中有构造方法。接口中没有构造方法。
接口和接口之间支持多继承。类和类之间只能单继承。
一个类可以同时实现多个接口。一个抽象类只能继承一个类(单继承)。
接口中允许出现常量合格抽象方法。
注意:
以后接口使用的比抽象类多,一般抽象类使用的还是比较少。接口一般都是对“行为”的抽象。
类到对象是实例化。对象到类是抽象。
抽象类
1、什么是抽象类?
类和类之间具有共同特征,将这些共同特征提取出来,形成的就是抽象类。
类本身是不存在的,所以抽象类无法创建对象【无法实例化】
2、抽象类属于什么类型?
抽象类也属于引用数据类型。
3、抽象类怎么定义?
语法:
[修饰符列表] abstract class 类名 {
类体;
}
4、抽象类是无法实例化的,无法创建对象的,所以抽象类使用来被子类继承的。
5、final和abstract不能联合使用,这两个关键字是对立的。
public final abstract class Account{ }
6、抽象类虽然无法实例化,但是抽象类有构造方法,这个构造方法是供子类使用的。
7、抽象类关联到一个概念:抽象方法
什么是抽象方法?
抽象方法表示没有实现的方法,没有方法体的方法。例如:
public abstract void doSome();
抽象方法非特点:
特点1:没有方法体,以分号结尾。
特点2:前面修饰符列表中有abstract关键字。
8、抽象类的子类可以是抽象类,也可以是非抽象类。
创建抽象类
public abstract class Account{
//非抽象方法
public void doOther(){
}
//抽象方法
public abstract void withdraw();
}
子类是抽象类,可以不重写父类的方法
public abstract class CreditAccount extends Account{
}
子类是非抽象类,子类必须重写父类的抽象方法
public class CreditAccount extends Account{
public void withdraw() {
}
}
【重要结论】:
一个非抽象的类继承抽象类,必须将抽象类中的抽象方法实现了。这是java语法上强行规定的,必须的,不然编译器就会报错。这里的覆盖或者说重写,也可以叫做实现。(对抽象的实现。)
面试题(判断题):java语言中凡是没有方法体的方法都是抽象方法。
不对,是错误的。
因为Object类中就有很多方法都没有方法体,都是以“;”结尾的,但是他们都不是抽象方法,例如:public native int hashCode();这个方法底层调用了C++写的动态链表库程序。前面修饰符列表中没有:abstract。有一个native,表示调用JVM本地程序。
9、【重点】抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中。
接口
1、接口也是一种“引用数据类型”。编译之后也是一个class字节码文件。
2、接口是完全抽象的。(抽象类是半抽象。)或者可以说接口是特殊的抽象类。
3、接口怎么定义,语法是什么?
[修饰符列表] interface 接口名 {}
4、【重要】接口支持多继承,一个接口可以继承多个接口。
A,B,C均为接口
public interface C extends A, B{
}
5、【重要】一个类可以同时实现多个接口
类D实现A,B,C接口
public class D implements A, B, C{
}
这种机制弥补了java中类和类只支持单继承。实际上单继承是为了简单而出现的,现实世界中存在多继承,java的接口弥补了单继承带来的缺陷。
6、接口中只包括两部分:
一部分是:常量。另一部分是:抽象方法。
7、接口中所有的元素都是public修饰的。(都是公开的。)
接口中的抽象方法定义时:public abstract修饰符可以省略。
//public abstract int sum(int a, int b); int sum(int a, int b);
接口中的常量的public static final修饰符可以省略。注意有final修饰符。
//public static final double PI = 3.1415926; double PI = 3.1415926;
8、接口中的方法都是抽象方法,所以接口中的方法不能有方法体。
9、类与类之间叫做“继承”,类与接口之间叫做“实现”。
继承使用extends关键字完成。实现使用implements关键字完成。
10、【重要结论】:
当一个非抽象的类实现接口的话,必须将接口中所有的抽象方法全部实现(覆盖、重写)
但是不能给方法分配更低的访问权限,会报错。
public interface MyMath{
int sum(int a, int b);
int sub(int a, int b);
}
实现MyMath接口类
public class MyMathImpl implements MyMath{
// 错误:正在尝试分配更低的访问权限;以前是public
/*int sum(int a, int b) {
return a + b;
}*/
public int sum(int a, int b) {
return a + b;
}
public int sub(int a, int b) {
return a - b;
}
}
11、【重要】接口A和接口B虽然没有继承关系。编译器可以通过,但是运行时可能出现异常:ClassCastException
创建接口K,M
public interface K{
}
public interface M{
}
类E实现M接口
public class E implements M{
}
测试
M m = new E();
K k = (K)m;
经过测试:接口和接口之间在进行强制类型转换的时候,没有继承关系,也可以强转。但是一定要注意,运行时可能会出现java.lang.ClassCastException异常。编译没问题,运行有问题。
使用instanceof运算符进行判断。向下转型养成好习惯,转型之间先if + instanceof进行判断。
if (m instanceof K){
K k = (K) m;
}
12、继承和实现都存在的话,代码怎么写?
extends 关键字在前。implements 关键字在后。
创建A类,继承B类,实现C接口
public class A extends B implements C{
}
13、接口在开发中的作用:
接口在开发中的作用,类似于多态在开发中的作用。
多态:面向抽象编程,不要面向具体编程,降低程序的耦合度,提高程序的扩展力。
接口是完全抽象的,而我们以后正好要求:面向抽象编程。面向抽象编程这句话可以改成为:【面向接口编程】。有了接口扩展力很强
总结一句话:
【重点】---三个字“解耦合”
面向接口编程,可以降低程序的耦合度,提高程序的扩展力。符合OCP开发原则。接口的使用离不开多态机制。(接口+多态才可以达到降低耦合度。)接口可以解耦合,任何一个接口都有调用者和实现者。接口可以将调用者和实现者解耦合。调用者面向接口调用。实现者面向接口编写实现。
以后进行大项目的开发,一般都是将项目分离成一个模块一个模块的,模块和模块之间采用接口衔接。降低耦合度。
附加
类型和类型之间的关系:
is a(继承)、 has a(关联)、 like a(实现)
is a:Cat is a Animal(猫是一个动物)
凡是能够满足is a的表示“继承关系”
A extends B
has a:I has a Pen(我有一支笔)
凡是能够满足has a关系的表示“关联关系”
关联关系通常以“属性”的形式存在。
A{
B b;
}
like a:Cooker like a FoodMenu(厨师像一个菜单一样)
凡是能够满足like a 关系的表示“实现关系”
实现关系通常是:类实现接口。
A implements B
Object类
这个根类(Object)中的这些方法,都是所有子类通用的。任何一个类默认继承Object,就算没有直接继承,最终也会间接继承。
toString
将对象转换成字符串形式
1、源代码长什么样?
public String toString(){
return getClass().getName() + '@' + Integer.toHexString(hashCode());
}
源代码上toString()方法的默认实现是:
类名@对象的内存地址转换为十六进制的形式。
创建User类,没有重写toString方法
public class User{
}
main方法中测试
User user = new User();
System.out.println(user.toString());
输出
User@1b6d3586
给User类添加toString方法
public class User{
public String toString() {
return "User的toString方法";
}
}
这个时候的输出结果就变成
User的toString方法
输出引用的时候,会自动调用该引用的toString()方法。
System.out.println(user);
equals()
判断两个对象是否相等
1、equals方法的源代码
public boolean equals(Object obj){
return (this == obj);
}
以上这个方法是Object类的默认实现。
2、以后编程的过程当中,都要通过equals方法来判断两个对象是否相等。
3、Object类中给出的这个默认的equals方法不够用的,需要重写。
在Object类中的equals方法当中,默认采用的是“==”判断两个java对象是否相等。而“==”判断的是两个java对象的内存地址,我们应该判断两个java对象的内容是否相等,所以老祖宗的equals方法不够用,需要重写equals。
4、【重要】判断两个java对象是否相等,不能使用“==”, 因为“==”比较的是两个对象的内存地址。
java中什么类型的数据可以使用“==”判断?
java中基本数据类型比较是否相等,使用==
java中什么类型的数据需要使用equals判断?
java中所有的引用数据类型统一使用equals方法判断是否相等。【这是规矩。】
下面代码中,那个“==”是判断变量a中保存的100和b中保存的100是否相等。
int a = 100;
int b = 100;
System.out.println(a == b); // true
但是用"==",来比较两个对象是否相等时,比较的引用保存的对象内存地址
创建User类,没有重写equals方法
public class User{
int id;
public User(int id) {
this.id = id;
}
}
main测试类
User u1 = new User(1);
User u2 = new User(1);
System.out.println(u1 == u2); //false
User类,重写equals方法
public class User{
int id;
public User(int id) {
this.id = id;
}
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
User user = (User) obj;
return id == user.id;
}
}
测试
System.out.println(u1.equals(u2)); //true
User u3 = new User(2);
System.out.println(u1.equals(u3)); //false
java语言当中的字符串String重写了toString()方法,重写了equals方法。
1、String类已经重写了equals方法,比较两个字符串不能使用 ==,必须使用equals,equals是通用的。
2、String类已经重写了toString方法。
String s1 = new String("Test1");
String s2 = new String("Test1");
//System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true
System.out.println(s1); //Test1
System.out.println(s1.toString()); //Test1
实际上String也是一个类,不属于基本数据类型,一定存在构造方法。
其他自定义的类也是类似的使用方式。
如果需要判断字符串类型的引用与一个已知的字符串是否相等
String username = "admin";
System.out.println("admin".equals(username));
这种方式可以有效的避免空指针异常
finalize()
垃圾回收器负责调用的方法
1、在Object类中的源代码:
protected void finalize() throws Throwable{}
GC:负责调用finalize()方法。
2、finalize()方法只有一个方法体,里面没有代码,而且这个方法是protected修饰的。
3、这个方法不需要程序员手动调用,JVM的垃圾回收器负责调用这个方法。不像equals和toString()方法是需要写代码调用的。finalize()只需要重写,重写完将来自动会有程序来调用。
4、finalize()方法的执行时机:
当一个java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用finalize()方法。
5、finalize()方法实际上是SUN公司为java程序员准备的一个时机,垃圾销毁时机。
如果希望在对象销毁时机执行一段代码的话,这段代码要写到finalize()方法当中。
6、静态代码块的作用是什么?
静态代码块在类加载时刻执行,并且只执行一次。这是一个SUN准备的类加载时机。
finalize()方法同样也是SUN为程序员准备的一次时机。这个时机是垃圾回收时机。
7、提示:
java中的垃圾回收器不是轻易启动的,垃圾太少,或者时间没到,种种条件下,有可能启动,也有可能不启动。
在实际开发中有这样的业务要求:所有对象在JVM中被释放的时候,记录一下释放的时间。负责记录对象被释放的时间的代码,写到finalize()方法中。
public class Person{
protected void finalize() throws Throwable {
System.out.println(this + "即将被销毁!");
}
}
将对象变成垃圾
Person p = new Person();
p = null;
在main方法中测试
for (int i = 0; i < 10; i++){
Person p = new Person();
p = null;
System.gc();
}
hashCode()
获取对象哈希值的一个方法
在Object中的hashCode方法源码
public native int hashCode();
这个方法不是抽象方法,带有native关键字,底层调用C++程序。
hashCode()方法返回的是哈希码:
实际上就是一个Java对象的内存地址,经过哈希算法,得出的一个值。所以hashCode()方法的执行结果可以等同看做一个java对象的内存地址。
public class Myclass{
}
在main方法中测试
Object o = new Object();
System.out.println(o.hashCode());
Myclass mc = new Myclass();
System.out.println(mc.hashCode());
测试结果
460141958
1163157884
clone()
负责对象克隆
内部类
1、什么是内部类?
内部类:在类的内部又定义了一个新的类,被称为内部类。
2、内部类的分类:
静态内部类:类似于静态变量
public class Test {
static class Inner1{
}
}
实例内部类:类似于实例变量
public class Test {
class Inner2{
}
}
局部内部类:类似于局部变量
public class Test {
public void doSome(){
class Inner3{
}
}
}
3、匿名内部类是局部内部类的一种。
因为这个类没有名字而得名,叫做匿名内部类。
4、匿名内部类有两个缺点:
缺点1:太复杂,太乱,可读性差。能不用尽量不用。
缺点2:类没有名字,以后不能重复使用。
创建一个接口
interface Compute{
int sum(int a, int b);
}
一般的方式就是创建类实现这个接口
class ComputeImpl implements Compute{
public int sum(int a, int b) {
return a + b;
}
}
然后在main方法中创建对象调用方法,下面使用内部类的方法调用方法
使用下面方式创建类
class MyMath{
public void mySum(Compute c, int x, int y){
int retValue = c.sum(x, y);
System.out.println(x + " + " + y + " = " + retValue);
}
}
在main方法测试
MyMath mm = new MyMath();
mm.mySum(new Compute(){
public int sum(int a, int b){
return a + b;
}
}, 100, 200);
使用匿名内部类,这里表面看上去好像是接口可以直接new了,实际上并不是接口可以new了。后面的()代表了对接口的实现。一般不建议使用匿名内部类。