JavaSE-面向对象(final,多态,抽象)

本文介绍了Java中的final关键字,包括其修饰变量、方法和类的意义,以及多态的概念、实现条件、应用场景、优缺点。同时讨论了抽象类和抽象方法的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. final      

        1.1 什么是final

              final是java的一个关键字,它可以用来修饰类,方法和变量,表示最终的,无法修改的

        1.2 final 能做什么

             1. final修饰变量时,表示这个变量的值不能被修改,不能二次赋值,相当于一个常量

             2. final修饰方法时,表示这个方法不能被重写

             3. final修饰类时,表示该类不能被继承

        1.3 final怎么用

                修饰变量

public class Final {
	final int a = 1;
	// a = 2; 修饰变量时不能二次赋值,不能修改
}

                修饰方法

class A {
	public final void m1() {
	}
}
class B extends A {
	// 修饰方法,不能被重写
	// @Override
	// public final void m1() {
	// }
}

                修饰类

final class Sup {
}
// 修饰类时,当前类不能被继承
//class Sub extends Sup{} 
        1.4 修饰引用类型
public class Final_01 {
	public static void main(String[] args) {
		final Customer c = new Customer(11);
		// 由于引用类型变量存储的是堆内存中的地址,跟c指向的age没有关系
		//	,改变age的值不会改变从中存储的地址,age也没有被final修饰,所以age可以改变
		c.age = 14;
		
		// 重新创建对象赋值给c会改变c中存储的地址,而被final修饰的c不能二次赋值,所以会报错
		// c = new Customer(15);
	}
}
class Customer{
	int age;
	
	public Customer(int age) {
		this.age = age;
	}
}

2. 多态

        2.1 什么是多态

                多态是面向对象的一个重要概念,它指的是同一对象在不同情况下有不同的表现形式和功能。 在java中,多态指的是同一类型的对象,在不同的情况下表现出不同的行为。

                简单来说:多态即父类引用指向子类对象,能够使用父类的地方就一定能使用子类。(父类引用:使用父类类型创建的引用类型变量。子类对象:通过子类创建的堆内存对象)

        2.2 实现多态的三个必要条件

                1. 继承:必须要有子类继承父类的继承关系,才能实现多态。

                2. 重写:子类需要对父类中的一些方法进行重写,然后调用方法时就会调用子类重写的方法,而不是原本父类的方法。

                3. 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备调用父类的方法和子类的方法。例如,Animal a = new Cat(); 这样就把Cat类型的引用向上转型为Animal类型的引用。

        2.3 多态的应用场景

                当一个需求对应多种不同的实现的时候,一定使用多态。

                当一个人需求只有一种实现的时候,就不必使用多态

                例如:人养宠物,宠物可以不只一种,可以定义一个Pet类,然后让Cat类,Dog类等具体的宠物继承Pet类,并重写Pet类中的成员方法,这样就可以让Pet的引用类型指向Cat或Dog的对象,并调用它们重写的方法

public class Poly_01 {
	public static void main(String[] args) {
		People p = new People();
		Cat cat = new Cat();
		// 传入子类对象
		p.raise(cat);
		Dog dog = new Dog();
		p.raise(dog);
		Pig pig = new Pig();
		p.raise(pig);
	}

}

// 用户类
class People{
	// 以父类参数接受子类对象,即父类引用指向子类对象
	public void raise(Pet pet){
		pet.kinds();
	}
}
// 定义宠物类

//abstract class Pet{
//	public void kinds();
//}
class Pet{
	public void kinds(){
		System.out.println("一个宠物");
	}
}
// 具体宠物继承宠物类
class Cat extends Pet {
	@Override
	public void kinds() {
		System.out.println("一只猫");
	}
}
class Dog extends Pet {
	@Override
	public void kinds() {
		System.out.println("一条狗");
	}
}
class Pig extends Pet {
	@Override
	public void kinds() {
		System.out.println("一头猪");
	}
}
        2.4 多态的优缺点

                优点:

                        1. 提高代码的可重用性,使用父类类型的引用来指向不同的子类对象,从而减少重复的代码。

                        2. 提高代码的可扩展性,可以在不修改原有代码的基础上,增加新的子类和方法,从而增加程序的功能。

                        3. 提高代码的可维护性,可以使用统一的接口来操作不同的对象,从而降低代码的耦合度和复杂度。

                        4. 提高代码的多态性,可以实现动态绑定,即在运行时根据对象的实际类型来调用相应的方法,从而增加程序的灵活性和智能性。

                缺点:

                        1. 不能直接访问子类特有的属性和方法,使用父类类型的引用只能调用父类中定义的成员,如果要访问子类特有的成员,需要进行向下转型,这可能会引起类型转换异常。

                        2. 不能使用子类中重载的方法,使用父类类型的引用只能调用父类中定义的方法,如果子类中重载了父类的方法,那么在多态中无法使用重载的方法。

                        3. 不能使用子类中静态的方法,使用父类类型的引用调用静态方法,会优先使用父类中的静态方法,而不是子类中的静态方法。

        补充:为什么父类引用指向子类对象后不能访问子类特有的属性和方法?

                编译器在编译时,只能根据引用的类型来判断可以调用哪些方法,而不会考虑实际对象的类型。因此,如果引用的类型是父类,那么编译器只能看到父类中定义的方法,而不能看到子类中独有的方法,即使实际对象是子类的。运行时环境在运行时,会根据实际对象的类型来决定调用哪个方法,这就是动态绑定的过程。因此,如果引用的类型是父类,但实际对象是子类,那么运行时环境会调用子类中重写或覆盖父类的方法,而不会调用父类中的方法

        2.5 使用语法和形式

        通过多态进行属性调用:

                1. 父类没有的,都调用不了,直接报错

                2. 父类有的,子类没有的都执行父类的

                3. 父类有,子类也有,除成员方法执行子类的,其他的都执行父类的

public class Poly_02 {
	public static void main(String[] args) {
		// 父类引用指向子类对象
		Sup s = new Sub();
		// 调用父类的
		System.out.println(s.a);
		// 调用父类的
		System.out.println(s.b);
		// 调用父类的
		System.out.println(s.c);
		// 子类特有的,报错,不能调用
		// System.out.println(s.d);
		
		// 静态方法调用父类的
		s.m1();
		// 成员方法调用子类重写的
		s.m2();
	}
}
class Sup {
	int a = 1;
	static int b = 2;
	int c = 3;
	public static void m1() {
		System.out.println("父类静态");
	}
	public void m2() {
		System.out.println("父类成员");
	}
}
class Sub extends Sup {
	int a = 11;
	static int b = 22;
	int d = 44;
	public static void m1() {
		System.out.println("子类静态");
	}
	public void m2() {
		System.out.println("子类成员");
	}
}

        多态的几种形式

                1. 直接多态:即 父类 变量名 = new 子类();

                2. 形参实参多态:参数列表用父类声明,调用方法时传入子类对象

                3. 返回值多态:返回值类型为父类类型,但是返回值是子类对象

                简单来说:只要父类的引用类型变量,保存到是子类的对象,就是多态,不需要考虑对象是怎么来的

public class Poly_03 {
public static void main(String[] args) {
	// 1. 直接多态:父类 变量名 = new 子类();
	SupClass s = new SubClass(); 
	// 2. 形参实参多态:参数列表用父类声明,调用方法时传入子类对象
	m1(new SubClass());
	// 3. 返回值多态:返回值类型为父类类型,但是返回值是子类对象
	SupClass a = m2();
		
}

public static void m1 (SupClass sup) {
}

public static SupClass m2() {
	return new SubClass();
}

}

class SupClass{
	int age = 12;
}
class SubClass extends SupClass{
	
}
        2.6 隐式多态(this)
public class Poly_04 {
public static void main(String[] args) {
	Sub1 s = new Sub1();
	s.m1(); // 输出: 1  子类m2
}
	
}

class Sup1 {
	int i = 1;
	public void m1() {
		/*
		 * this发生多态
		 * this是当前对象中第一个成员变量,保存当前对象的内存地址
		 * 所以this的类型可以为当前类类型或当前类的父类类型,当前类为Sup1,Sup1的父类为Object
		 * 由于this可以调用当前类特有属性,所以this为当前类型,即Sup1
		 * 所以this.i输出为当前类的i的值
		 */		
		System.out.println(this.i);
		/*
		 * 由于子类对象Sub1调用m1,所以运行时this指向子类对象的内存地址
		 * 得知:Sup1 this = 子类对象;
		 * 所以this.m2遵循多态的规则,调用的为子类重写的成员方法
		 */
		this.m2();
	}
	public void m2() {
		System.out.println("父类m2");
	}
}

class Sub1 extends Sup1{
	int i = 11;
	public void m2() {
		System.out.println("子类m2");
	}
} 
        2.7 Instanceof

                instanceof判断某个对象是某个类实例化而来

                (多态发生在赋值的时候,不赋值不会发生多态.多态又叫向上转型,即子到父,类似于自动类型转换。向下转型,父到子,类似于强制类型转换,在多态中必须先发生向上转型才能向下转型)

public class Poly_05 {
	public static void main(String[] args) {
		// 向上转型
		Sup2 s = new Sub2();
		System.out.println(s.age);
		// 向下转型(已经发生过向上转型)
		Sub2 s1 = (Sub2) s;
		System.out.println(s1.age);

		Sup2 sSup2 = new Sup2();
		// 下面运行会报错,因为没有发生向上转型,当强制转型为子类时,会出现错误
		// java.lang.ClassCastException: _11_Poly.Sup1 cannot be cast to  _11_Poly.Sub1
		// 类型转换异常
		// Sub2 sSub2 = (Sub2) sSup2;
		
		// instanceof判断某个对象是否由某个类实例化而来
		// 当true的时候,在进行向下转型,就可以避免类型转换异常
		System.out.println(sSup2 instanceof Sub2);
	}
}
class Sup2{
	int age = 2;
}
class Sub2 extends Sup2{
	int age = 12;
}

3. abstract

        3.1 abstract是什么

                abstract是修饰符,表示抽象的

                1. abstract修饰的成员方法为抽象方法,抽象方法没有方法体

                2. abstract修饰的类为抽象类,抽象类不能被实例化,即不能创建对象

                3. 抽象方法一定在抽象类中(有抽象方法的类一定是抽象类),但抽象类中不一定有抽象方法。

                4. abstract和final不能同时出现,因为抽象类主要用于继承,而final修饰的类不能被继承

                5. 如果一个类继承一个抽象类,需要实现抽象类中所有抽象方法

                    一个抽象方法继承另一个抽象方法,需要继承0-N个抽象方法

        3.2 使用语法
// 抽象类
abstract class A{
	// 普通类能写什么,抽象类就能写什么,只不过会有抽象方法
	// 抽象方法
	public abstract void m1();
	// 成员方法
	public void m2() {}
	// 构造方法
	// 抽象类虽然不能创建对象,但是有构造方法,用于让子类创建对象时调用
	public A() {}
	//静态方法
	public static void m3(){}
}
// 一个普通类继承一个抽象类,需要实现抽象类中所有抽象方法
class B extends A{
	@Override
	public void m1() {};
}
// 一个抽象方法继承另一个抽象方法,需要继承0-N个抽象方法
abstract class C extends A {
	
}

3.3 应用场景

例如:先前的宠物类,我们只知道有某种宠物,却不知道具体种类,引入Pet的目的是降低耦合度,Pet类只需要定义功能,而具体的实现由子类进行方法的重写实现,所有该方法可以定义为抽象方法。

class Pet{
	public void kinds(){
		System.out.println("一个宠物");
	}
}

修改为抽象类

abstract class Pet{
	public void kinds();
}
// 具体宠物继承宠物类
class Cat extends Pet {
	@Override
	public void kinds() {
		System.out.println("一只猫");
	}
}
class Dog extends Pet {
	@Override
	public void kinds() {
		System.out.println("一条狗");
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值