面向对象高级
一、Object类
1.概述:
Object类是所有类的父类(基类),如果一个类没有明确的继承某一个具体的类,则将默认继承Object类。
例如我们定义一个类:
public class Person{
}
其实它被使用时是这样的:
public class Person extends Object{
}
可以认为Java代码中所出现的所有东西,都可以看做是Object类的对象。
Object除了可以作为接收任意的引用数据类型以外,还具有很多系统已经写好的方法,这些方法可以解决很多问题。使用Ctrl+鼠标左键,可以查看类的源码。
2.toString:
Object的toString方法,返回对象的内存地址。
建议重写Object中的toString方法。
此方法的作用:返回对象的字符串表示形式。
//IDEA使用Alt+Insert快捷键,选择toString(),
//选择要返回的对象,即可快速重写toString方法。
public String toString() {
return "Express{" +
"number='" + number + '\'' +
", company='" + company + '\'' +
", code=" + code +
'}';
}
3.equals:
Object的equals方法:实现了对象上最具区别的可能等价关系;也就是说,对于任何非空引用值x和y,当且仅当x和y引用同一对象(x == y具有值true)时,此方法返回true。
建议重写Object中的equals(Object obj)方法,根据对象之间一定不相同的属性来比较即可(例如:判断是否是同一个用户,只需比较手机号是否相同即可,无需比较用户名、年龄、性别等)。
此方法的作用:指示某个其他对象是否“等于”此对象。
//IDEA使用Alt+Insert快捷键,选择equals() and hashCode(),
//选择一个对象之间一定不相同的属性,即可快速重写equals方法。
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Express express = (Express) o;
return Objects.equals(number, express.number);
}
equals方法重写时的五个特性:
1.自反性:对于任何非空的参考值x,x.equals(x)应该返回true。
2.对称性:对于任何非空引用值x和y ,x.equals(y)应该返回true当且仅当y.equals(x)回报true。
3.传递性:对于任何非空引用值x,y和z ,如果x.equals(y)回报true且y.equals(z)回报true,然后x.equals(z)应该返回true。
4.一致性:对于任何非空引用值x和y,多次调用x.equals(y)始终返回true或始终返回false,前提是未修改对象上的equals比较中使用的信息。
5.非空性:对于任何非空的参考值x,x.equals(null)应该返回false。
总结:非空的自己和自己一定相同(自反性);y和x相同则x和y相同(对称性);y和x相同且z和y相同则z和x相同(传递性);非空的x和y不改信息则这次相同下次还相同(一致性);非空的x一定和空不相同(非空性)。
二、抽象类
1.概述:
一个类中如果存在抽象方法(不确定的方法),称这个类为抽象类,必须使用abstract来修饰。一个抽象类中可以没有抽象方法,但抽象方法必须写在抽象类或者接口中。
抽象类的行为是模糊的,抽象类本身是不能直接进行实例化操作的,即:不能直接使用关键词new完成。
一个抽象类必须被子类所继承,被继承的子类(如果不是抽象类)则必须覆写(重写)抽象类中的全部抽象方法。
abstract class 类名{ // 抽象类
}
2.抽象方法:
只声明而未实现的方法称为抽象方法(未实现指的是:没有大括号),抽象方法必须使用abstract关键字声明。
abstract class 类名 { //抽象类
public abstract void 方法名(); //抽象方法,只声明而未实现
}
3.常见问题:
1.抽象类是不能直接创建对象的;
2.抽象类不能使用final声明,因为final声明的类不能被继承;
3.抽象类可以有构造方法,不能被我们创建,但是可以被Java虚拟机创建,而且子类对象实例化的时候的流程与普通类的继承是一样的,都是要先调用父类中的构造方法(默认是无参的),之后再调用子类自己的构造方法。
4.抽象类和普通类的区别:
1.抽象类必须用public或protected修饰(如果为private修饰,那么子类则无法继承,也就无法实现其抽象方法),默认缺省为public;
2.抽象类不可以使用new关键字创建对象,但是在子类创建对象时,抽象父类也会被JVM实例化;
3.如果一个子类继承父类,那么必须实现其所有的抽象方法,如果有未实现的抽象方法,那么子类也必须定义为abstract类。
三、接口
1.概述:
和抽象类类似,他们都有抽象的部分,但是接口中的全部方法都是抽象方法,全部属性都是全局常量。
interface 接口名称 {
全局常亮;
抽象方法;
}
在描述一个项目的时候可能这个项目有很多模块,这些模块组合起来组成一个项目,如果这些模块一个个去设计去实现,比如说一个项目有10个模块,当写到第6个模块的时候发现第6个模块需要和前5个模块进行搭配、协同,那么这个时候就要修改之前的代码,非常麻烦。再比如,当程序已经写好时,想对其升级,提升某一个环节要换掉,这个时候必须把之前的删掉再写一份,可能对原有代码造成不可维护的操作。删除一个地方,很多地方都使用到了,就都得改,出现很多的BUG。
2.面向接口编程思想:
关注抽象部分,一个项目在没有写的时候就通过抽象的方式把规范和约束先给定义出来,把程序的大概功能描述出来,从宏观的角度把程序先设计好,但是程序流程中没有任何一行已经实现的代码。这种思想是接口是定义(规范、约束)与实现(名字分离的原则)的分离。
优点:
1.降低程序的耦合性
2.易于程序的扩展
3.有利于程序的维护
3.全局常量和抽象方法的简写:
因为接口本身都是由全局常量和抽象方法组成,所以接口中的成员定义可以简写:
1.全局常量编写时,可以省略public static final 关键字,例如:
public static final String INFO = "内容";
String INFO = "内容";//简写后
2、抽象方法编写时,可以省略 public abstract 关键字,例如:
public abstract void print();
void print();//简写后
4.接口的实现 implements:
接口可以多实现:
class 子类 implements 父接口1,父接口2...{
}
以上的代码称为接口的实现。
那么如果一个类即要实现接口,又要继承抽象类的话,则按照以下的格式编写即可:
class 子类 extends 父类 implements 父接口1,父接口2...{
}
5.接口的继承:
接口因为都是抽象部分,不存在具体的实现,所以允许多继承,例如:
interface C extends A,B{
}
6.注意:
如果一个接口要想使用,必须依靠子类。子类(如果不是抽象类的话)要实现接口中的所有抽象方法。
7.接口和抽象类的区别:
1.抽象类要被子类继承,接口要被类实现;
2.接口只能声明抽象方法,抽象类中可以声明抽象方法,也可以写非抽象方法;
3.接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量;
4.抽象类使用继承来使用,无法多继承,接口使用实现来使用,可以多实现;
5.抽象类中可以包含static方法,但是接口中不允许(静态方法不能被子类重写,因此接口中不能声明静态方法);
6.接口不能有构造方法,但是抽象类可以有。
四、多态
1.概述:
多态就是对象的多种表现形式(多种体现形态),多态在使用时父类引用指向子类对象,父类的对象名称 = 子类的对象名称表示右边这个对象是左边这个类型的其中一种形态。
2.多态的体现:
对象的多态性,从概念上非常好理解,在类中有子类和父类之分,子类就是父类的一种形态,对象多态性就从此而来。
方法的重载和重写也是多态的一种,不过是方法的多态(相同方法名的多种形态)
重载:一个类中方法的多态性体现
重写:子父类中方法的多态性体现
3.对象的类型转换:
类似于基本数据类型的转换:
向上转型:将子类实例变为父类实例(自动转换)
父类 父类对象 = 子类实例;
向下转型:将父类实例变为子类实例(强制转换)
子类 子类对象 = (子类)父类实例;
4.类型转换举例:
Student a = new Student();//a是学生
Nurse b = new Nurse();//b是护士
Person p1 = a;//a是人,用p1表示,原本是学生
Person p2 = b;//b是人,用p2表示,原本是护士
Student a2 = (Student)p1;//p1原本是学生,可以强制从人转换为学生
Student a3 = (Student)p2;//p2原本是护士,无法从人转换为学生,报错
当把学生转成人,再把人转成学生没有任何问题;而当把护士转成人,再把人转成学生就会报错:ClassCastException。
那么,如何判断传入的对象是此类型的哪种形态(哪个子类的对象)呢?instanceof!
5.instanceof:
判断某个对象是否是指定类的实例,则可以使用instanceof关键字。
实例化对象 instanceof 类 //此操作返回boolean类型的数据
if (p instanceof Student) {
Student s = (Student)p;
s.say();
} else {
System.out.println("必须传入学生形态,才可以执行");
}
五、内部类
1.概述:
在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
广泛意义上的内部类一般来说包括这四种:
1.成员内部类
2.局部内部类
3.匿名内部类
4.静态内部类
2.成员内部类:
成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:
class Outer {
private double x = 0;
public Outer(double x) {
this.x = x;
}
class Inner { //内部类
public void say() {
System.out.println("x="+x);
}
}
}
特点:成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
//内部访问外部类的同名成员
外部类.this.成员变量;
外部类.this.成员方法;
//外部使用成员内部类
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner();
3.局部内部类:
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
例如:
class Person{
public Person() {
}
}
class Man{
public Man(){
}
public People getPerson() {
class Student extends People { //局部内部类
int age = 0;
}
return new Student();
}
}
注意:局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
4.匿名内部类:
匿名内部类由于没有名字,所以它的创建方式有点儿奇怪。创建格式如下:
new 父类构造器(参数列表)| 实现接口()
{
//匿名内部类的类体部分
}
在这里我们看到使用匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用,当然这个引用是隐式的。
在使用匿名内部类的过程中,我们需要注意如下几点:
1.使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
2.匿名内部类中是不能定义构造函数的。
3.匿名内部类中不能存在任何的静态成员变量和静态方法。
4.匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
5.匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
6.只能访问final型的局部变量(为什么?因为内部类会被单独的编译成一个字节码文件,那为了保证这个单独文件中用到的变量a(备份的变量a)与外面的变量a值绝对是一致的,系统在规则上限制了这个值不可更改。)
5.静态内部类:
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。
静态内部类是不需要依赖于外部类对象的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法。
格式:
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
public Outter() {
}
static class Inner {
public Inner() {
}
}
}
Java面向对象从基础到高级到这里就完结了,作为一名初学者,接触面向对象难免会充满迷茫,这是很正常的,思想的转变需要大量的练习,希望大家都能很好的理解面向对象的思想并能在做项目时应用其中,谢谢!