【第10天】Java面向对象的高阶特征(修饰符的介绍)

版权声明:©2018 本文為博主來秋原創,轉載請註明出處,博主保留追責權利。 https://blog.csdn.net/qq_30257081/article/details/84145106

1 访问权限

  • 修饰符:(√:可访问/×:不能访问)
修饰符 当前类 同一个包 子孙类(包内/外) 其他包
public(公共的)
protected(受保护的) ×
default/friendly(默认的) × ×
private(私有的) × × ×
protected在包外有继承关系的子类中可以访问。
  • 各自能修饰什么:(√:可修饰/×:不可修饰)
修饰符 成员(属性) 方法 局部变量
public(公共的) ×
protected(受保护的) × ×
default/friendly(默认的) ×
private(私有的) × ×
  • 总结:

    • public/default:可修饰类/成员/方法
    • protected/private:成员/方法
  • 为什么局部变量不需要访问权限修饰符修饰?
    因为局部变量本身象征一个访问权限:只能在局部调用。也就是说在局部变量的生命周期中,除这个方法外,外界无法访问。所以不需要用任何修饰符修饰,也不能加static,但可以加final。

  • 包的含义是文件系统里的文件夹,同一个包也就是在同一个文件夹下。

2 static

2.1 静态成员

  • static能修饰什么?(√:可修饰/×:不可修饰)
修饰符 成员(属性) 方法 局部变量
static × ×
  • Java类加载的过程:

    1. 加载静态成员/代码块(先递归地加载父类的静态成员/代码块,再依次加载到本类的静态成员。同一个类里的静态成员/代码块,按代码的前后顺序加载。 如果其间调用静态方法,则调用时会先运行静态方法,再继续加载)
    2. 加载非静态成员/代码块(先递归地加载父类的非静态成员/代码块,再依次加载到本类的非静态成员。同一个类里的非静态成员/代码块,按代码的前后顺序加载)
    3. 调用构造方法(递归地调用父类的构造方法,先父后子)
  • 类点属性或者类点方法时,类加载只进行了第一步。当这个类被new时,第二、三步才会执行,此时就不算类第一次被加载了。(在 2.3 加载顺序细说)

  • 静态成员与类相关,非静态成员与对象相关
    在这里插入图片描述

  • 静态属性

    特点:整个类型共享一份的属性而不是每个对象都有一份的属性,可以使用类名直接调用。(普通属性:每个对象都有一份,需要使用实例化后的引用调用)

  • 静态方法

    特点:可以使用类名调用,静态方法里面只能直接访问类的静态成员,如果访问非静态成员,需在类中创建其对象调用。

  • 需要厘清逻辑:因为静态成员在类刚加载时就已经加载了,此时普通成员都还没有加载,所以无法访问到。 但是可以在普通方法中访问静态的类成员。

  • static为什么不能修饰局部变量
    static修饰的变量要求类一加载就要找到,但是局部变量在方法被调用的时才会开辟内存空间。类的加载永远在前面,方法调用永远在后面,这两个时间点赶不上一起,于是static不能修饰局部变量。就算是静态方法中的局部变量也不行。

  • 既然静态方法调用简单,为什么不把一个类里所有方法都定义成静态的?
    静态方法里只能直接访问静态成员,如果在静态的方法里面访问非静态成员,需在类中创建其对象调用。

  • 静态方法不可能出现this,因为静态方法与类相关,用类调用时无法匹配this所说的对象。

  • 方法 优点 缺点
    普通方法 普通方法里面既可以直接访问普通、静态成员 普通方法调用比较复杂,需要创建并使用对象调用
    静态方法 静态方法调用简单,直接使用类名.方法 静态方法里面只能直接访问静态成员,如果想在静态的方法里访问非静态成员,需在类中创建其对象调用
  • 访问非静态成员,需在类中创建其对象调用*

    • 在静态方法中实例化这个类后调用
    • 在静态方法的参数列表中传入普通成员的类,调用静态方法前先实例化这个普通成员的类并传入这个引用。

public class Test4{
		
	static int  i=0;
		
	public int aMethod(){
		i++;
		return i;
	}
	public static void main(String[] args){
		Test4 test4 = new Test4();
		test4.aMethod();//1
		int j = test4.aMethod();//2
		System.out.println("j=" + j);//--->2
	}
}

2.2 代码块

       使用代码块进行属性的初始化。(初始化:定义变量并赋值)

名称 作用 份数 何时执行 执行次数
普通代码块 初始化普通属性 每个对象都有一份 创建对象时(new XXX)执行 创建几个对象执行几次
静态代码块 初始化静态属性 整个类型共享一份 类第一次被加载的时候执行 只执行一次
class A{
	//下面两句编译报错,i=45是赋值语句,语句只允许出现在方法中,不能出现在类中。类中只允许出现属性、方法。
	int i;
	i = 45;

	//这样初始化变量i
	int i = 45;
	
	//底层会执行如下代码:
	int i;
	//代码块:初始化普通属性,创建对象的时执行
	//每创建一个对象时执行一次,每个对象都有一份
	{
		i = 45;
	}

	static int i;
	{
		//因为普通代码块在创建对象时调用,
		//如果直接使用类名调用变量i因没有初始化对象,调用A.i打印默认值0,
		//此时应使用静态代码块
		i = 45;
	}

	int i;
	//静态代码块:初始化静态属性,类第一次被加载的时候执行
	//只在类第一次加载时执行,整个类型共享一份
	static {
		//A.i 输出45
		i = 45;
	}
}

2.3 加载顺序

       静态代码块中的代码从上向下依次执行,所有的大括号可以从上向下合并为一个来看,非静态同理。

//1 不存在继承关系且直接创建对象时的加载顺序
public class Test1{
	public static void main(String[] args){
		//第一次读到A类时,先加载A.class,此时执行静态代码块static{}
		//第二步new A,创建了对象,所以第二步执行普通静态代码块
		//第三步构造代码为收尾工作
		A a = new A();
	}
}

class A{
	public A(){
		System.out.println("A类构造方法");//S3
	}

	{
		System.out.println("A类普通代码块");//S2
	}

	static{
		System.out.println("A类静态代码块");//S1
	}
}

/*
输出顺序
A类静态代码块
A类普通代码块
A类构造方法

静态代码块:当类第一次被加载的时候执行

普通代码块:当创建对象的时候执行

构造方法:当创建对象的时候执行(收尾工作)
*/
//1 存在继承关系且直接创建对象时的加载顺序
public class Test2{
	public static void main(String[] args){
		//第一步加载B.class,发现extends A,加载A.class
		//第二步加载A的静态代码块,然后加载B的静态代码块
		//第三步执行new B,创建B对象,在创建B对象时,构造方法中的super()优先级高于普通代码块和构造方法
		//第四部执行构造方法中super(),递入父类,父类也会执行Object类的super(),但是这里不作了解,这一步也不会被打印
		//第五步执行父类中普通代码块、构造方法
		//第六步归回子类B,执行子类普通代码块、构造方法
		B b = new B();
	}
}

class A{
	public A(){
		System.out.println("A类构造方法");
	}

	{
		System.out.println("A类普通代码块");
	}

	static{
		System.out.println("A类静态代码块");
	}
}
class B extends A{
	public B(){
		super();
		System.out.println("B类构造方法");
	}

	{//5
		System.out.println("B类普通代码块");
	}

	static{
		System.out.println("B类静态代码块");
	}
}
/*
输出顺序
A类静态代码块
B类静态代码块
(先执行B类的super(),递入父类A类)
A类普通代码块
A类构造方法
B类普通代码块
B类构造方法

注意:当执行完静态方法,创建对象时

先执行B类构造方法中的super(),
再到执行A类的普通代码块。
*/
//3 不存在继承关系且不创建对象时的加载顺序(如果存在继承不创建对象,只执行父类、子类的静态成员)
public class Test3{
	public static void main(String[] args){
		//第一次读到A类时,先加载A.class,此时执行静态代码块static{}
		//与Test1不同的是,这里没有new一个新对象,所以不会加载普通代码块和构造方法
		//A.test()和A.a,静态成员加载顺序按照书写的前后顺序执行
		A.test();
		System.out.println(A.a);
	}
}

class A{
	static int a = 40;
	public A(){
		System.out.println("A类构造方法");//S3
	}

	{
		System.out.println("A类普通代码块");//S2
	}

	static{
		System.out.println("A类静态代码块");//S1
	}

	static void test(){
		System.out.println("静态方法");
	}
}
/*
输出顺序
A类静态代码块
静态方法
40
*/
  • 例题

    • 分析这种题可以将普通成员和静态成员的变量拆分成代码块的形式。
    • 注意:super.xxx调用的是父类的成员不会被是否重写影响。super()是直接“复制”了父类的内容直接来到子类使用,代码共享。
public class Example1{
	public static void main(String[] args){
		//A -> A.class -> static{} -> y = 20 -> 50
		//忽略非静态成员和构造方法
		System.out.println(A.y);//--->50
	}
}

class A{
	int x = 10;
	static int y = 20;
	int x;
	{
		x = 10;
	}
	{
		x = 30;
		y = 40;
	}
	static{
		y = 50;
	}
	public A(){
		x = 60;
		y = 70;
	}
}
public class Example2{
	public static void main(String[] args){
		//创建了对象,还有构造方法,只需要看构造方法即可
		A aa = new A();
		System.out.println(aa.x);//--->60
		System.out.println(A.y);//--->70
	}
}

class A{
	int x = 10;
	static int y = 20;
	int x;
	{
		x = 10;
	}
	{
		x = 30;
		y = 40;
	}
	static{
		y = 50;
	}
	public A(){
		x = 60;
		y = 70;
	}
}
public class Example3{
	public static void main(String[] args){
		//执行普通代码块
		//执行构造方法,构造方法中执行test(),输出a,与B类没什么关系
		A aa = new A();//--->a
	}
}
class A{
	String str = "a";
	public A(){
		test();
	}

	public void test(){
		System.out.println("A类的" + str);
	}
}
class B{
	String str = "b";
	public B(){
		//super();默认执行
		test();
	}
	@Override
	public void test(){
		System.out.println("B类的" + str);
	}
}
/*输出:
A类的a
*/

===============================================

public class Example4{
	public static void main(String[] args){
		//结合Test2的结论,在加载完静态变量之后,直接去B类加载super()
		//加载进来A类构造方法的内容test()
		//但是并不是要在A类执行,相当于只是复制了代码来B执行,此时变量str未加载,所以输出为null
		//执行完普通代码块再执行构造方法时,可以输出b
		B b = new B();
	}
}
class A{
	String str = "a";
	public A(){
		test();
	}
	public void test(){
		System.out.println("A类的" + str);
	}
}
class B{
	String str = "b";
	//当涉及到子类方法一定注意方法体如果首行没有super()或者this(),先补一个
	//直接调用B类时先读B类构造方法,super()使构造方法递到父类构造方法,执行后再归下来继续执行其他方法
	public B(){
		//super();默认执行
		test();
	}
	@Override
	public void test(){
		System.out.println("B类的" + str);
	}
}

/*输出:
B类的null
B类的b
*/

3 final

       最终的,也被称为“骡子类”。修饰类时,该类不允许被继承;修饰方法时,该方法不允许被重写,但是允许重载,允许被继承;修饰变量时,该变量是最终变量/常量,不允许被重新赋值

  • final能修饰什么?(√:可修饰/×:不可修饰)
修饰符 成员(属性) 方法 局部变量
final
  • 为什么Sun公司在定义String类和Math类的时候都用final修饰的?

    • String类代表字符串类,是项目开发里面的很基础,核心类。越是基础、核心的类,所有的程序员越应该保持一致。
    • Math类表示数学类,里面定义的都是公理、定理,不能按照程序员的意愿随意的进行修改。
  • 注意: final修饰的基本数据类型,其数值不变(报错无法为最终变量x分配值);final修饰的引用数据类型,其地址不变。

public class Test{
	public static void main(String[] args){

		final Student stu = new Student("张三",22);
		stu.name = "张三丰";
		System.out.println(stu.name + "	" + stu.age);
	}
}

class Student{
	String name;
	int age;

	public Student(String name,int age){
		this.name = name;
		this.age = age;
	}
}

final内存图

4 abstract

       抽象的。父类定义了方法有内容但因为太抽象大多都需要继承重写使用,写一些内容也没什么意义。但不定义这个方法又不符合java的继承特点,定义为抽象方法可以解决这个问题。抽象类中既可以有抽象方法,又可以有普通方法供子类进行代码共享提高代码重用率

  • abstract的特点?

    不能有方法实现,省去方法体。

  • abstract能修饰什么?(√:可修饰/×:不可修饰)

修饰符 成员(属性) 方法 局部变量
abstract × ×
  • 抽象类:表示这个类型不形象不具体,不能创建对象

  • 抽象方法:表示这个类型一定会这个方法,但是现在给不出明确定义,待留给子类去实现。不能有方法实现(方法体)。

  • 一个类里面只要出现了抽象方法,那么这个类一定要定义成抽象类。

  • 抽象类里面既可以定义抽象方法,又可以定义普通方法。

  • 抽象类有构造方法(Java中只要是类就一定有构造方法),其作用是?

    抽象类是类,但是不能创建创建对象,这里的构造方法是用来给子类构造方法首行的super()使用

public class Test{
	public static void main(String[] args){
		Cat c = new Cat();
		c.sleep();
		c.eat();
	}
}

abstract class Animal{
	public abstract void eat();
	public void sleep(){
		System.out.println("都需要睡觉");
	}
}

class Cat extends Animal{
	@Override
	public void eat(){
		System.out.println("猫吃鱼");
	}
}

class Dog extends Animal{
	@Override
	public void eat(){
		System.out.println("狗吃骨头");
	}
}
/*输出
都需要睡觉
猫吃鱼
*/
  • abstract和final能不能同时修饰一个方法?
    不能,因为abstract修饰的方法表示这个类型一定会这个方法,但是现在给不出具体的实现,留给子类去重写实现。final修饰的方法表示最终方法,可以被继承,但不能被重写。

猜你喜欢

转载自blog.csdn.net/qq_30257081/article/details/84145106