java程序员从笨鸟到菜鸟之(六)继承详解

一. 代码块

概念:
使用{} 括起来的代码,称为代码块

分类:
根据它位置和声明的不同,我们可以将代码块分为局部代码块构造代码块静态代码块同步代码块(多线程涉及)

 

 

a  局部代码块(方法内)

 

 

 

1)特点:

限定了变量的生命周期,变量在局部代码块中定义的,那么出了局部代码块之后,就访问不到了;在局部代码块中定义的变量,在出了代码块之后,内存就会释放掉
 

2)作用

主要就是节约内存


局部代码块中定义的变量,虽然说作用域是局部的,但是如果存在外部嵌套的代码块,且在局部代码块定义之前就定义了某个变量,那么在我们局部的代码块中就不可以定义相同名称的变量;但是如果在局部代码块执行之后,去定义一个和局部代码块中相同名字的变量是可以的,因为局部代码块中的变量已经失去了作用域范围。

 

实例1:

 

 

public class Demo1{
	
	public static void main(String[] args){
		//int x=5;//此时会报错
		{
			int x=10;		
		}
		
		int x = 20;
		
		System.out.println(x);	
	}
}

 

 

 

b  构造代码块(方法外)

 

1)概念:
类中方法外出现每次调用构造方法的时候,都会优先先调用构造代码块

2)特点:
每创建一个对象,都会调用一次我们的构造代码块(与创建对象伴生

3)作用:

 

如果存在很多重载的构造方法,而且每个里面需要执行相同的逻辑,那么就可以考虑将这些代码提取到构造代码块中来执行;让我们代码结构更简练,增强了维护性

 

4)补充:

使用场景其实不多见

 

c  静态代码块(伴随static)

 

1)概念:
类中方法外出现
,但是用static 来进行修饰

2)特点:
随着类加载的时候执行

 

3)格式:

 

static{

//内容

}

用处:适用于整个软件的生命周期中,只需要执行一次的业务逻辑代码;比如我们之后数据库的操作

补充:

这几个代码块的优先顺数:静态代码块>构造代码块>局部代码块

实例2:

 

public class Demo2{
	
	static{
		System.out.println("demo2 静态代码块");
	}
	
	public static void main(String[] args){
		
		Person person = new Person();
		System.out.println("--------------------");
		Person person1 = new Person(20);
	}
	
}

class Person{
	
	int age;
	
	int[] num;
	
	static{
		System.out.println("调用了静态代码块");
	}
	
	{
		//构造代码块
		System.out.println("构造代码块");
	}
	
	//默认的构造方法
	Person(){
		System.out.println("默认构造");
		
		// 数组成员变量我们依次赋值
	}
	
	Person(int age){
		this.age = age;
		System.out.println("非默认构造");
		
		// 数组成员变量我们依次赋值
	}
	
	
	
}

编译结果:

 

原因:首次遇到main方法时会首次加载main方法所在的类Demo2,执行其静态方法(随类的加载而加载),然后返回到main方法中创建Person的对象,又因为创建对象首先要构造方法,而构造代码块优先于构造方法;静态代码块在所在类首次加载后不再加载静态代码块

 

二. 继承

特点:
1)
子类继承父类,继承父类的成员变量和成员方法;但是他们各自拥有的各自的成员变量,所以他们的值并不会继承
2) 对于父类中的私有成员变量和私有方法,子类是无法继承的

 

格式:

 

子类 extends 父类

优点:
a.提高了代码的复用性
b.提高了代码的维护性

继承缺点:
类的耦合性增强了,破坏封装性
开发的原则:
高内聚,低耦合;通俗的讲:自己能完成的东西尽量自己完成不要依赖别人
耦合:类和类之间的关系
内聚:自己独立完成某件事情的能力

生活中的举例:
电视类: 品牌, 价格 , 尺寸
手机类: 品牌, 价格 , 尺寸, 操作系统

这两个类不能直接继承,但可以从上层抽出来一个类【共性】: 电器类(品牌,价格,尺寸)

 

实例3:

 

 

//主要目的:子类和父类各自拥有的各自的成员变量,所以他们的值并不会继承
public class ExtendsDemo1{
	
	public static void main(String[] args){
		Father father = new Father();
		father.setName("小诸葛");//外部方法赋值
		Son son = new Son();
		/*
		 * 子类和父类各自拥有的各自的成员变量,所以他们的值并不会继承
		 * 可以这样理解:父类创建一个对象后给对象属性赋值
		 * 此时子类继承父类,但是此时子类继承的是父类的原始属性和方法
		 * (可以理解为未创建对象之前的属性和方法)
		 */
		son.speak();
	}
}

class Father{
	//JavaBean类
	private  int age;
	private  String name;
	/*
	 * 若在此直接赋值,则子类也继承此值
	 * 即:private  String name="小花";
	 * 若修改成private static String name;
	 * 调用外部方法对父类赋值后子类仍然能继承此值
	 */
	void setAge(int age){
		this.age = age;
	}
	
	void setName(String name){
		this.name = name;
	}
	
	int getAge(){
		return age;
	}
	
	String getName(){
		return name;
	}
	
	void speak(){
		System.out.println("爸爸大声喊出我的名字:"+name);
	}
}

class Son extends Father{
	
}

 

3 )只支持单继承,但是可以支持多层继承

 

继承是相互叠加的,子类继承的时候,会递归似的寻找父类中是否还存在继承,会继承整个层级直到最根部类的属性和方法

 

实例4:

 

/*
 * 多层继承:
 * Son类继承Father类,Father继承GrandFather类...甚至继承到根类
 */
public class ExtendsDemo1{
	
	public static void main(String[] args){
		GrandFather gFather = new GrandFather();
		gFather.speak();
		
		Father father = new Father();
		father.speak();
		father.eat();
		
		Son son = new Son();
		son.eat();
		son.sleep();
		son.speak();
	}
}

class GrandFather{
	void speak(){
		System.out.println("祖父类speak");
	}
}

class Father extends GrandFather{
	void eat(){
		System.out.println("父亲类eat");
	}
}

class Son extends Father{
	void sleep(){
		System.out.println("儿子类sleep");
	}
}

 

4)对于构造方法是无法继承的,但是有办法可以调用父类的构造方法

继承关系中访问成员变量

a.不同名的成员变量
子类可以直接访问和使用父类继承的非私有的成员变量

b.同名的成员变量
优先原则: 如果子类中定义了和父类中相同名字的成员变量,会优先访问子类的该变量
如果想要依然访问到父类中的同名变量,我们需要加上super关键字来获取

this::当前对象的引用
super::当前父类对象的引用

this:可以访问到子类中的变量,也可以访问到父类中的变量
super::只能访问到父类中的变量,若父类没有则报错

 

实例5:

 

 

public class ExtendsDemo3 {

	public static void main(String[] args) {
		Son son = new Son();
		son.print();
	}
}

class Father {

	int num1 = 10;
	int num2 = 20;

}

// num1是子类中定义的一个和父类同名的变量
class Son extends Father {

	int num1 = 40;

	void print() {

		// this:可以访问到子类中的变量,也可以访问到父类中的变量
		// 说明:this.属性(如果子类没有此属性,则访问父类属性)
		System.out.println(this.num1 + " .. " + this.num2);
		System.out.println(super.num1 + " .. " + super.num2);
	}
}

 

继承关系中访问成员方法:

 

a  不同名的方法:
子类可以直接调用到父类的非私有方法

b  同名的方法:
当父类和子类出现同名方法的时候(同名方法: 指的返回值类型(父子类关系是可以的),方法名和方法签名要一样)

在这种情况下:
当我们子类调用该方法时,会有优先原则的处理,就是会调用子类的该方法


方法的重写(覆盖):
存在于继承关系中,子类中定义的方法和父类中的方法完全一样的时候(返回值类型父子类关系是可以的),我们在通过子类对象来访问该方法的时候,就会调用到子类的方法

注意事项
子类不能重写父类的私有方法
子类重写父类方法的时候,提供的访问权限不能更低
子类覆盖父类方法,如果父类是静态方法的话,子类也必须是静态方法才可以成功覆盖,也就是重写

方法重载
同一个类中,如果我们有多个相同方法名的方法,但是他们的形参列表是不同的,那么这种方式我们就称为方法的重载
在调用的时候,jvm能够通过方法签名来区分到我们到底调用的是哪个方法

关于方法重写和方法重载的返回值约束:
方法重载:与返回值类型无关,必须参数列表不同
方法重写:子类与父类返回值类型(父子类关系是可以的) 必须相同

this:可以访问子类的方法,也可以访问父类的方法
super:只能够访问父类的方法

实例6:

 

/*
定义长方形类和正方形类, 长方形继承正方形并且重写计算面积和周长的方法.
*/

public class ClassTest1{
	
	public static void main(String[] args){
		Square square =new Square();
		System.out.println("正方形周长"+square.getSumLength());
		System.out.println("正方形面积"+square.getArea());
		
		Rectangle rectangle = new Rectangle();
		System.out.println("长方形周长"+rectangle.getSumLength());
		System.out.println("长方形面积"+rectangle.getArea());
	}
}

class Square{
	
	private int lengthOfSide = 10;
	
	int getSumLength(){
		return 4*lengthOfSide;
	}
	
	int getArea(){
		return lengthOfSide*lengthOfSide;
	}
}

class Rectangle extends Square{
	int width = 4;
	int height = 5;
	
	int getSumLength(){
		return (width+height)*2;
	}
	
	int getArea(){
		return width*height;
	}
}

 

补充:
我们一个文件在编译之后产生的.class 文件的数量,取决于在该文件中定义的class的数量(非内部类),而且是不引入其他类的情况下

继承中构造方法的调用:


特点:
1  创建子类对象的时候,一定会优先去创建父类的对象;因为要创建父类对象,所以就需要去调用到父类的构造方法
2  对于我们所有的类的构造方法的第一行,第一行在我们没有自己添加this(...)或者super(...)的情况下都会去帮我们默认的添加super()

如果父类中不存在默认的构造,子类依然要创建对象,那么子类就需要显示的指明调用个父类哪个构造方法,才能保证父类对象创建的成功。

明确一点:
a  一定要保证父类对象的创建成功
b  构造方法的第一行,如果我们添加了自己调用的this(...),或者super(...),系统就不会为我们默认的添加super()
c  我们的this(...)或者super(...) 必须放到第一行,否则编译错误,且二者只能有其一
对c说明:this(...)调用的是子类的另一个构造方法,在此构造方法中调用父类super(参数),所以二者只需其一

 

通俗理解:子类要想创建对象,首先要创建父类对象,所以就需要调用父类的构造方法当程序执行到new去创建子类对象的时候,首先会去调用子类的构造方法,若子类构造方法中没有his(...)或者super(...) ,jvm会默认添加super()调用父类的构造方法,进而完成父类对象的创建,然后再完成子类构造方法,进而创建子类对象

 

实例7:

 

/*
在创建子类对象的时候,会先调用到父类的构造方法,再去创建父类的对象
构造方法调用构造方法
this(参数)---调用同类
super(参数)--调用父类
*/

public class ExtendsDemo5 {

	public static void main(String[] args) {
		Son son = new Son();
	}
}

class Father {

	Father() {
		System.out.println("调用father 默认构造");
		System.out.println("father对象创建完毕");
	}

	Father(int num) {
		System.out.println("调用father 有参构造");
		System.out.println("father对象创建完毕");
	}
}

class Son extends Father {

	Son() {
		this(10);// 不要此行的编译结果
		// this(10)调用同类的构造方法,此时就不会帮我们再去默认添加super(),
		System.out.println("son 默认构造");
	}

	Son(int num) {
		super(10);// 可以删除此行看看编译结果
		// 第一行没有this或者super,就会帮我们默认添加super()
		System.out.println("son 有参构造");
		System.out.println("-------------");
	}

}

 

三. final关键字


a  final修饰类
final如果修饰一个类,该类不能被继承


b  final修饰类的成员变量

 

该成员变量的值不可修改

问题: 那么什么时候初始化这个值?
答:在对象创建之前都可以
a  构造代码块中可以进行初始化
b  构造方法中可以进行初始化
c  定义成员变量的时候,可以直接进行初始化

 

 

 

注意一点

 

 

1)这三种方式同时只能用一种

2)在大多数的场景下,我们的final成员变量都会配合public static 一起使用

 

 

//final修饰的常量类,变量命名都要大写,而且单词之间要用_分割开

 

 

形如:

class A{
   public static final double PI = 3.14;
   public static final double RATE = 2.86;
}

使用方式:类名.变量名
A.PI
A.RATE

final修饰的static变量的初始化时机:
a  在创建静态final成员变量的时候,直接进行初始化
b  在静态代码块中进行初始化

实例8:

 

public class FinalDemo1 {
         //final修饰的static变量初始化的一种方式
	public static void main(String[] args) {
		Person person = new Person();
		person.sayNum();
	}

}

class Person {

	// final 修饰成员变量,该值不可修改.
	static final int num;//第二种方式,定义的时候直接赋值

	static {
		num = 10;   //第一种方式:通过静态代码块赋值
	}

	Person() {

	}

	void sayNum() {
		System.out.println("静态变量初始化:" + num);
	}
}

一些比较好的博客super详解文章:点击打开链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值