一、抽象类(AbstractClass)
1、抽象:就是从多个事物中将共性的,本质的内容抽取出来。
简单说:就是看不懂的、模糊的、不具体的事物。
2、抽象类:表示具体功能不明确的,被abstract关键字修饰,且包含抽象方法的类。
3、抽象方法:只有功能声明,没有功能主体的方法称为抽象方法。
4、抽象类的特点:
①、抽象方法一定要定义在抽象类中。
②、抽象方法和抽象类都必须被abstract关键字修饰。
③、抽象类不可以用new创建对象。因为调用抽象方法没意义。
④、抽象类中的抽象方法要被使用,必须由子类覆写其所有的抽象方法后,建立子类对象调用。
如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。
5、抽象类、抽象方法的定义格式:
abstract class 抽象类名
{
访问权限 abstract 返回值类型 方法名称(参数);
}
6、抽象类和一般类的区别:
①.抽象类比一般类多了抽象函数。就是在类中可以定义抽象方法。
②.抽象类不可以被实例化。
特殊:抽象类中可以不定义抽象方法,这样做仅仅是不让该类建立对象。
7、注意:在使用abstract关键字修饰抽象方法时不能使用private修饰。因为抽象方法必须要被子类覆写,而如果使用了private修饰,则子类是无法覆写的。
代码示例:
abstract class Student{
abstract void study(); //抽象方法。
}
class BaseStudent extends Student{
void study(){
System.out.println("base study"); //覆写抽象方法。
}
}
class AdvStudent extends Student{
void study(){
System.out.println("adv study"); //覆写抽象方法。
}
}
class AbstractDemo {
public static void main(String[] args) {
newBaseStudent().study(); //创建子类匿名对象并调用覆写后的方法。
}
}
二、接口(interface)
1、接口:一种特殊的抽象类,由全局常量和公共的抽象方法组成。
当抽象类中的方法都是抽象的,那么该类可以通过接口的形式来表示。
2、接口的定义格式:用interface关键字来定义
interface 接口名称
{
public static final返回值类型常量名 =初始化值; //全局常量
public abstract返回值类型抽象方法名(); //公共抽象方法
}
代码示例:
interface Inter
{
public static final int NUM = 3;
public abstract void show();
}
3、接口在定义时的格式特点:
①、接口中常见定义:常量、抽象方法。
②、接口中的成员都有固定修饰符。
常量:public static final
方法:public abstract
4、接口的注意事项:
①、接口中的成员都是public权限的,绝对不可以改变。
②、接口中全局常量前面的public static final和抽象方法前面的public abstract都是可以省略掉的,即使不写,系统默认也会给它加上。
③、接口不可以被创建对象,因为有抽象方法。
④、需要被子类实现,子类将接口中的抽象方法全都覆盖后,子类才可以实例化。
否则子类是一个抽象类。
⑤、接口可以被类多实现,也是对多继承不支持的转换形式。java支持多实现。
5、接口之间的关系
类和类之间是继承关系 —— extends
类和接口之间是实现关系 —— implements
接口和接口之间是继承关系。
示例代码:
interface A{
void methodA();
}
interface Bextends A{
void methodB();
}
interface Cextends B{
void methodC();
}
class Dimplements C{
pubilc void methodA(){}
pubilc void methodB(){}
pubilc void methodC(){}
}
注意:接口和接口之间支持多继承。因为接口里面都是抽象方法。
示例代码:
interface A{
void methodA();
}
interface B{
void methodB();
}
interface Cextends B,A{
void methodC();
}
6、接口的特点(和好处):
- 接口是对外暴露的规则。
- 接口是程序的功能扩展。
- 接口的出现降低耦合性(即:降低紧密联系程度)。
- 接口可以用来多实现。
- 类与接口之间是实现关系,而且类可以继承一个类的同时实现多个接口。
- 接口与接口之间可以有继承关系。
7、接口与抽象类的区别:
共性 | 都是不断抽取出来的抽象的概念 |
区别1 | 抽象类体现继承关系,一个类只能单继承 接口体现实现关系,一个类可以多实现 |
区别2 | 抽象类是继承,是 "is a "关系 接口是实现,是 "like a"关系 |
区别3 | 抽象类中可以定义非抽象方法,供子类直接使用 接口的方法都是抽象,接口中的成员都有固定修饰符 |
三、多态
1、多态:可以理解为事物存在的体现形态。
例如:人→ 男人、女人
动物 → 猫、狗
创建一个猫的对象可以通过以下两种方式:
猫 x = new 猫();
动物 x = new 猫();
2、多态的体现:
父类的引用指向了自己的子类对象。 //动物 x= new 猫();
父类的引用也可以接收自己的子类对象。(将父类的引用做为函数的参数进行传递)
3、多态的前提:
必须是类与类之间有关系。要么继承,要么实现。
通常还有一个前提:存在覆盖。
4、多态的好处:多态的出现大大的提高了程序的扩展性。
5、多态的弊端:提高了扩展性,但是只能使用父类的引用访问父类中的成员。
6、多态转型:
Java中多态性的体现形式主要有两种:
①、方法的重载和覆写
②、对象的多态性
对象的多态性主要分为两种类型:
向上转型:子类对象→父类对象。用子类去实例化父类对象,程序会自动完成。
向下转型:父类对象→子类对象。强制将父类的引用转成子类类型。
对象转型格式:
向上转型:父类父类对象 =子类实例; //例如:Animal a = newCat();
向下转型:子类子类实例 = (子类)父类对象; //例如:Cat c =(Cat)a;
注意:千万不要出现将父类对象转成子类类型的操作。
示例代码:
Animal a = newAnimal();
Cat c = (Cat)a;
我们能转换的是父类引用指向了自己的子类对象时,该引用可以被提升,也可以被前者转换。
总结一句话:多态自始至终都是子类对象在做着变化。
注意:在进行对象的向下转型前,必须首先发生对象的向上转型,否则将会出现对象类型转换异常。
示例代码:
abstract class Animal{
public abstract void eat(); //动物共有的行为方法。
}
class Cat extends Animal{
public void eat(){
System.out.println("吃鱼");
}
public void catchMouse(){ //猫特有的行为方法。
System.out.println("抓老鼠");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("吃骨头");
}
public void kanJia(){ //狗特有的行为方法。
System.out.println("看家");
}
}
class DuoTaiDemo2{
public static void main(String[] args) {
Animal a = new Animal();
Cat c = (Cat)a; //报错。会出现类型转换异常。
/*
正确的方式:
Animal a = new Cat();
Cat c = (Cat)a;
*/
}
}
报错原因:父类用其本身类实例化自己的对象,但它并不知道谁是自己的子类,在转换的时候肯定就会出现类型转换异常。
解决方式:只要将这两个对象建立好关系即可,即在声明父类对象时先发生向上转型,相当于是用子类去实例化父类对象,也就是说这个时候父类知道有这么一个子类。
7、多态中成员的特点:
①—1、多态中(非静态)成员函数的特点:
在编译时期:参阅引用型变量所属的类中是否有调用的方法,如果有,编译通过;如果没有则编译失败。
在运行时期:参阅对象所属的类中是否有调用的方法。
简单总结就是:成员函数在多态调用时,编译看左边,运行看右边。
示例代码:
class Fu{
void method1(){
System.out.println("fu method_1");
}
void method2(){
System.out.println("fu method_2");
}
}
class Zi extends Fu{
void method1(){
System.out.println("zimethod_1");
}
void method3(){
System.out.println("zimethod_3");
}
}
class DuoTaiDemo4 {
public static void main(String[] args) {
Fu f = new Zi();
f.method1(); //结果是zi method_1。打印的是被子类覆写之后的方法。
f.method2();
//f.method3(); //会报错,因为父类中没有method3这个方法。
}
}
个人总结:当发生向上转型之后,用父类对象调用的方法一定是被子类覆写之后的方法。
为什么上面示例中f.method1();的结果是子类中的zi method_1呢?
因为一般方法存在于内存的方法区的非静态方法区,是通过对象或者this、super等调用,而当用子类实例化父类对象的时候,再用父类对象去调用子父类中的同名方法,则相当于是以【new子类().方法名();】的形式在调用,所以运行的结果是子类中同名方法的结果。
①—2、多态中静态成员函数的特点:
无论编译和运行,都参考左边。
示例代码:
class Fu{
static void method4(){
System.out.println("fumethod_4");
}
}
class Zi extends Fu{
static void method4(){
System.out.println("zimethod_4");
}
}
class DuoTaiDemo4 {
public static void main(String[] args) {
Fu f = new Zi();
f.method4(); //结果是:fumethod_4。开发中一般不会出现,几乎不会去覆盖静态。面试中有可能遇到。
Zi z = new Zi();
z.method4(); //结果是:zimethod_4
}
}
为什么上面示例中f.method4();的结果是父类中的fu method_4呢?
因为当用子类去实例化父类对象的时候,JVM会将父类、子类都加载进内存,但是这个时候静态方法已经在内存中优先于对象存在了,也就是说这个时候需不需要对象都没什么特别的意义,父类对象调用的就是本类中的静态方法。
②、多态中成员变量的特点:
无论编译和运行,都参考左边(引用型变量所属的类)。
换句话说就是:当父类和子类中出现同名变量的时候,多态情况下看的是左边。
示例代码:
class Fu{
int num = 5;
}
class Zi extends Fu{
int num = 8;
}
class DuoTaiDemo4 {
public static void main(String[] args) {
Fu f = new Zi();
System.out.println(f.num); //结果是:5
Zi z = new Zi();
System.out.println(z.num); //结果是:8
}
}
四、instanceof关键字
1、作用:在Java中,可以用instanceof关键字判断一个对象到底是哪个类的实例。
2、使用格式:
对象 instanceof类 ——> 返回值类型是boolean型
示例代码:
public static void function(Animal a) //将父类的引用做为函数的参数接收进来。
{
a.eat();
if(a instanceof Cat) //判断传进来的对象是否是Cat类的对象。
{
Cat c = (Cat)a; //如果是就执行向下转型操作。
c.catchMouse(); //执行Cat类对象的特有行为方法。
}
else if(a instanceof Dog)
{
Dog d = (Dog)a;
d.kanJia();
}
}
五、Object类
1、Object类:是Java中所有类的直接或者间接的父类,位于Java继承体系的根节点。
一个类如果没有明确的继承一个类,则它肯定是Object的子类。
该类中所具备的功能是所有对象都具备的。
2、Object类中只有一个无参数的构造方法,所有没有明确父类的类默认都隐式的调用了Object类的构造方法。
3、所有的对象都可以向Object进行转换,即一切的引用数据类型都可以使用Object进行接收。
4、Object类中的常用方法:
序号 | 方法名称 | 类型 | 描述 |
1 | public Object() | 构造 | 构造方法 |
2 | public boolean equals(Object obj) | 普通 | 对象比较 |
3 | public int hashCode() | 普通 | 取得哈希值 |
4 | public String toString() | 普通 | 对象打印时调用 |
equals()方法:Object类中equals()方法默认比较的是对象的内存地址值。
代码示例:
class Demo {}
class ObjectDemo{
public static void main(String[] args) {
Demo d1 = new Demo();
Demo d2 = new Demo();
Demo d3 = d1;
System.out.println(d1.equals(d3)); //结果是:true
System.out.println(d1.equals(d2)); //结果是:false
System.out.println(d1==d2); //结果是:false
System.out.println(d1==d3); //结果是:true
}
}
其实equal()方法底层的比较原理就是使用的比较运算符“==”
注意:
Object类中已经提供了对对象是否相同的比较方式。如果自定义类中也有比较相同的功能,没有必要重新定义。
只要沿袭父类中的功能,建立自己特有的比较内容即可。这就是覆盖。
示例代码:
class Demo{
private int num;
Demo(int num){
this.num= num;
}
public boolean equals(Object obj){ //在自定义类中覆写Object类中已经存在的比较方法以达到沿袭父类功能的效果。
if(!(obj instanceof Demo)) //加入判断来控制传入对象是否具有可比性。
return false;
Demo d = (Demo)obj; //向下转型。
return this.num == d.num;
}
}
class Person{}
class ObjectDemo{
public static void main(String[] args) {
Demo d1 = new Demo(4);
Demo d2 = new Demo(4);
Person p = new Person();
System.out.println(d1.equals(p)); //两种不同类型的对象比较。会报错。类型转换异常。
}
}
toString()方法:将功能返回的结果以字符串形式返回。
Java中,对象输出时一定会调用Object类中的toString()方法,但是加与不加toString()的最终打印结果是一样的。
示例代码:
class Demo(){}
class ObjectDemo
{
public static void main(String[] args)
{
Demo d1 = new Demo();
System.out.println(d1); //结果是:Demo@18b3364
System.out.println(d1.toString()); //结果是:Demo@18b3364
}
/*
Demo@18b3364:Demo表示对象所属类的名称,@后面的部分是该对象在内存中的哈希地址值。
*/
//对象的哈希地址值可以通过Object类中的hashCode()方法获取。由于hashCode()方法返回的默认是int类型的值,所以需要被转换成16进制的形式才能跟上面的结果中的哈希值部分一样。
Class c =d1.getClass(); //获取Demo类的类文件对象。
System.out.println(c.getName()); //输出获取到的类文件的类名。
//通过hashCode()方法获取Demo类对象的哈希值,并转成16进制表现形式。
System.out.println(c.getName()+"@"+Integer.toHexString(d1.hashCode()));
//输出结果是:Demo@18b3364
}