Java基础入门【第六章 static、继承、重写、多态】(二)

5.访问控制

对象中的属性和方法,可以根据不同的权限修饰符(public > protected > default > private)来进行访问控制。

1)概述

类中的属性和方法,可以使用以下四种权限修饰符进行访问控制:

public > protected > default > private

public,公共的,所有地方都可以访问

protected,受保护的,当前类中、子类中,同一个包中其他类中可以访问default,默认的,当前类中、同一个包中的子类中可以访问

注意,default默认指的是没有修饰符,并不是使用default关键字修饰例如, String  name;  在类中,这种情况就是默认的修饰符private,私有的,当前类的成员方法的内部(类内)可以访问

默认:同一个包中,都可以访问

protected:同一个包 + 不同包子类,可以访问我们重点记忆publicprivate即可!

2)案例验证

定义A(基础类)、B(同包无关类)、CA(同包子类)、D(其他包无关类)EA(不同子类)。

在其他类中访问A类中四种数据成员,验证上表种成员访问权限。

体类定义如下:A

public class A {

public String pubStr = "public_str";

protected String proStr = "protected_str"; String defStr = "default_str";

private String priStr = "private_str";

//在当前类中访问

public void disp(){ 

System.out.println(pubStr); 

System.out.println(proStr);

 System.out.println(defStr);

 System.out.println(priStr);

}

}

B

package com.briup.chap06.perm;

public class B {

public void disp(){

 A a = new A();

//在同一个包,非子类中,可以访问:public protected 默认System.out.println(a.pubStr); System.out.println(a.proStr); System.out.println(a.defStr);

//private不可以直接访问

//System.out.println(a.priStr); }

}

CA

package com.briup.chap06.perm;

public class CA extends A { 

public void show() {

A a = new A();

//在同一个包,子类中访问:public protected 默认System.out.println(a.pubStr); System.out.println(a.proStr); System.out.println(a.defStr);

//private不可以直接访问

//System.out.println(a.priStr); }

}

D

import com..chap06.perm.A;

public class D {

public void disp(){ A a = new A();

//在不同包,非子类中,可以访问:public

System.out.println(a.pubStr);

//除了public可以访问,其他都不可以

//System.out.println(a.proStr);

//System.out.println(a.defStr);

//System.out.println(a.priStr); }

}

EA

package com.briup.chap06.bean; import com.briup.chap06.perm.A; public class EA extends A {

public void show() {

//不同包,子类中,可以访问:public protected System.out.println(pubStr); System.out.println(proStr);

//不可以访问 默认  private

//System.out.println(defStr);

//System.out.println(priStr); }

}

测试类Test05_AccessControl.java:

package com.briup.chap06.test;

import com.briup.chap06.bean.D; 

import com.briup.chap06.bean.EA; 

import com.briup.chap06.perm.A; 

import com.briup.chap06.perm.B;

 import com.briup.chap06.perm.CA;

public class Test05_AccessControl {

public static void main(String[] args) {

  1. = new A(); a.disp();

System.out.println("-----------");

  1. = new B(); b.disp();

System.out.println("-----------");

CA c = new CA(); c.show();

System.out.println("-----------");

D d = new D(); d.disp();

System.out.println("-----------");

EA e = new EA(); e.show();

}

}

6.方法重写

1)重写应用场景

父子类继承关系中,当子类需要父类的功能,而继承的方法不能完全满足子类的需求,子类里面有特殊的功能,此时可以重写父类中的方法,这样即沿袭了父类的功能,又定义了子类特有的内容。

2)方法重写细节

提:父子类继承关系中

子类新增方法,和从父类继承的方法,方法名完全相同两个方法的参数列表完全相同

重写方法的访问权限修饰符可以被扩大,但是不能被缩小

public > protected > default > private

方法的返回类型可以相同,也可以不同(暂时先按相同处理,后续再补充)

方法抛出异常类型的范围可以被缩小,但是不能被扩大(超纲内容,暂时先忽略)

注意:一般情况下,子类进行方法重写时,最好方法声明完全一致

3)案例展示

定义一个基础的 员工类Employee ,再定义 程序员子类Programmer ,再定义 经理子类Manager ,要求两个子类中分别 重写父类中doWork() 方法。

//定义父类:员工class Employee {

//工号、姓名

private String id; private String name;

public Employee() {}

public Employee(String id, String name) { this.id = id;

this.name = name; }

public void doWork() {

System.out.println("员工开始工作...");

}

}

//定义子类:程序员

class Programmer extends Employee {

//等级:初级、中级、高级、资深

private String grade;

public Programmer() {

//默认会调用父类无参构造器

//super(); }

public Programmer(String id,String name,String grade) {

//显式调用父类构造器super(id,name); this.grade = grade;

}

//重写方法

// @Override是注解,用于标识该方法为重写的方法,大家可以不用管@Override

public void doWork() {

//super.doWork();

System.out.println("我是程序员,级别为" + grade + ", 开始工作:写代码");

}

}

//定义子类:经理

class Manager extends Employee {

//奖金

private double bonus;

public Manager() { super();

}

public Manager(String id,String name,double bonus) { super(id,name);

this.bonus = bonus; }

//重写方法

@Override

public void doWork() {

System.out.println("我是经理,有津贴" + bonus + ", 开始工作:安排部门员工任务");

}

}

//测试类

public class Test06_Override {

public static void main(String[] args) {

//父类对象 调用 doWork方法

Employee e = new Employee("008", "larry"); e.doWork();

System.out.println("------------");

//程序员子类对象 调用 重写方法

Programmer p = new Programmer("010", "kevin", "初级");

p.doWork();

System.out.println("------------");

//经理子类 调用 重写方法

Manager m = new Manager("006", "robin", 201.5); m.doWork();

}

}

结论:子类继承父类,在调用方法的时候,如果子类中没用重写,那么调用从父类继承的方法,如果子类重写了这个方法,那么调用到子类重写的方法。( 非常重要 

特殊情况:

注意1:父类中的静态方法(属于类方法)不能被子类重写

static静态方法算不上重写,它属于父类所独有,只是书写格式上跟重写相

注意2:父类中的私有方法(未被继承)不能被子类重写

4)重写Object类中toString()

在之前的课程中,我们学过:

Java中的类,如果类定义时没有指定父类,那么这个类会默认继承Object

Java中的每个类都直接或间接继承Object类,Object类是Java承体系中的最顶层父类

故而,在Java的除Object之外的所有类中,都有存在一个 toString() 法,要么直接或间接从Object类中继承。

Object类源码:

package java.lang;

public class Object {

//省略...

/**

* @return   a string representation of the object. */

public String toString() {

//返回 "类的全包名@该对象堆空间地址(十六进制形式)"

return getClass().getName() + "@" + Integer.toHexString(hashCode());

}

/**

* Returns a hash code value for the object. This method is * supported for the benefit of hash tables such as those

provided by

* {@link java.util.HashMap}.

* @return   a hash code value for this object. */

public native int hashCode(); }

当输出对象时,会先执行 对象.toString() 获得对象的字符串形式,然后输出该字符串!

案例展示:

class Teacher {

private String name; private double balance;

public Teacher() {

//super(); }

public Teacher(String name, double balance) { super();

this.name = name; this.balance = balance;

}

//省略get|set方法... }

public class Test06_toString {

public static void main(String[] args) { Teacher t = new Teacher("jack",12345.6);

String str = t.toString(); System.out.println(str);

//当输出对象时,默认输出的是:对象.toString()

//下面两行代码效果一样System.out.println(t); System.out.println(t.toString());

//输出内容:对象所属类的全包名@十六进制地址值

}

}

添加代码,重写Teacher类中的toString方法:

class Teacher {

//...和原来一样,省略

//重写toString方法@Override

public String toString() {

return "Teacher [name=" + name + ", balance=" + balance + "]";

}

}

次运行输出:

常见面试题:请简述重载和重写!

7.final

final翻译后是"最终"的意思,这个修饰符可以用来修饰变量、类、方法

final修饰类

则类不能被继承final修饰方法

方法不能被重写final修饰变量

则变量就成了常量,初始化以后其值不能改变变量是基本类型:数据值不能发生改变

变量是引用类型:地址值不能发生改变,但是地址标识的那块内存里面的容可以改变

修饰类

用final修饰的类不能被继承,也就是说这个类是没有子类的。例如:

public final class Action {

}

//译报错

class Go extends Action {

}

思考,我们能否让一个类去继承String

String源码:

修饰方法

用final修饰的方法可以被子类继承,但是不能被子类的重写例如,

public class Person {

public final void print() {} }

//译报错

class Student extends Person {

//从父类继承的final法,不可以被重写

public void print() {

}

}

思考:我们能否在子类中重写从父类Object中继承过来的wait方法?Object类wait()源码:

    1. 修饰变量

用final饰的变量就变成了常量,并且它只能被赋一次值,第二次赋值就会报错

1final修饰局部变量

则局部变量赋初值后,不能再次赋值,否则编译报错!案例1

案例2

2final修饰普通成员变量

final成员变量初始化,可采用下面3方式:

式初始化

匿名代码块中初始化造方法中初始化

注意:如果使用构造方法对final成员初始化,则类中所有构造方法都要对final成员进行初始化,否则编译报错!

案例展示:

提示:测试某种初始化方式时,请注释掉另外2种初始化方式

class F {

//第一种初始化方式:显式初始化

//private final int num = 10;

//第二种初始化方式:构造代码块初始化

//  private final int num;

//  {

// num = 20;

//  }

//第三种初始化方式:所有构造器中都对final成员进行初始化

private final int num; public F() {

//必须给num初始化

this.num = 30; }

public F(int num) {

//必须给num初始化

this.num = num;

}

@Override

public String toString() {

return "F [num=" + num + "]"; }

}

//试代码

public class Test07_Final {

public static void main(String[] args) { F f = new F();

System.out.println(f); }

}

一种运行效果:

二种运行效果:

三种运行效果:

3final修饰静态成员变量

final静态成员变量初始化以后其值不能再修改,初始化方式有2种:显式初始化:声明的同时赋值

态代码块中赋值

案例展示:

package com.briup.chap06.test;

class SF {

//第一种初始化方式:显式初始化

//private final static int num = 10;

//第二种初始化方式

private final static int num; static {

num  =  20; }

//定义static方法,专门操作static数据成员

public static int getNum() { return num;

}

}

//试代码

public class Test07_Final {

public static void main(String[] args) { System.out.println("SF.num: " + SF.getNum());

}

}

2.修饰引用

final饰引用类型变量,则该变量的引用值不能改变,但所引用内存空间中的是可以改变的。

案例描述:

存描述:

8.多态

多态(Polymorphism)的字面意思为"一种事物,多种形态"

态小笑话:

某老外苦学汉语十年,到中国参加汉语考试,试题为:请解释下面中每个“意思”的意思。

阿呆给领导送红包,两人的对话颇有意思,对话如下:  领导:“你这是什么意思?”  

阿呆:“没什么意思,意思意思.”   领导:“你这就不够意思了.”  

阿呆:“小意思,小意思.”   领导:“你这人真有意思.”  

阿呆:“其实也没有别的意思.”   领导:“那我就不好意思了.”   阿呆:“是我不好意思.

结果:老外泪流满面,交白卷回国.

上述案例中,相同的一个词"意思",在不同的语境中代表的含义是不同的,这是多态的体现!

Java多态理解: 引用变量.方法(实参列表) 完全相同的这行代码,出现在不同的位置,其执行的结果是不同的。

多态前提:

类继承父类

类重写父类中的方法

类的引用指向子类对象

案例描述:

//类:点

class Point { public int x; public int y;

public Point() {}

public Point(int x,int y) { this.x = x;

this.y = y; }

public void show() {

System.out.println(" x: " + x + " y: " + y);

}

}

//子类1:圆

class Circle extends Point {

//半径

private int radius;

public Circle() {

//super(); }

public Circle(int x,int y,int radius) { super(x,y);

this.radius = radius; }

//写方法

public void show() {

System.out.println("Circle x: " + x //this.x

+ " y: " + super.y

+ " radius: " + radius);

}

}

//子类2:椭圆

class TCircle extends Point {

//平半径

private int xRadius;

//直半径

private int yRadius;

public TCircle() {}

public TCircle(int x,int y,int xr,int yr) { super(x,y);

this.xRadius = xr; this.yRadius = yr;

}

//写方法

public void show() { System.out.println("TCircle x: " + x

//this.x

+ " y: " + super.y

+ " xRadius: " + xRadius

+ " yRadius: " + yRadius);

}

}

//测试类

public class Test08_Poly {

public static void main(String[] args) {

//多态前提条件:

// 1.父子类继承关系

// 2.子类重写方法

// 3.父类引用 指向子类对象Point p = new Circle(1,1,2);

// 4.父类引用调用重写方法【调用到子类重写的方法】

p.show(); //Circle::show()

p = new TCircle(2, 2, 3, 2); p.show(); //TCircle::show()

}

}

注意,一个父类型的引用,可以指向它的任何一个子类对象

多态优缺点

优点

提高程序的扩展性、灵活性

定义方法时候,使用父类型作为参数,在使用的时候,使用具体的子类型参与操作

弊端

能使用子类的特有成员

案例实现1:不使用多态

//篮球类

class BasketBall { public void play() {

System.out.println("开始篮球游戏...");

}

}

//足球类

class Football {

public void play() {

System.out.println("开始足球游戏...");

}

}

//第一步:新增乒乓球类

//class PingPong {

//  public void play() {

// System.out.println("开始乒乓球游戏...");

//  }

//}

//定义游戏类

class Game {

//动篮球游戏

public void start(BasketBall basketBall) { basketBall.play();

}

//启动足球游戏

public void start(Football football) { football.play();

}

//第二步:Game类新增重载start方法

// public void start(PingPong pingpong) {

// pingpong.play();

// } }

//传统方式实现(非多态):代码的扩展性很差public class Test08_Game {

public static void main(String[] args) {

//1.建游戏对象

Game  game  =  new  Game();

//2.创建篮球对象,然后开始游戏

BasketBall basketBall = new BasketBall(); game.start(basketBall);

//3.创建足球对象,然后开始游戏

Football football = new Football(); game.start(football);

//第三步:测试类创建乒乓球对象,然后开始游戏

// PingPong pingpong = new PingPong();

// game.start(pingpong);

}

}

在这种情况下, 如果多了一种乒乓球游戏要运行,则必须通过3个步骤实现:

新增乒乓球类 PingPong

Game类中新增重载方法 void  start(PingPong  p); main方法测试实现

问题分析:如果后续要不断扩展游戏种类,则每次都要额外修改Game类中的代码,这很不方便,且不安全(每次要改动之前的代码,容易出Bug),违反开闭原则!

开闭原则(Open-Closed Principle,OCP)是面向对象设计中的一条基本原则。指的是"软件实体(类、模块、函数等)应该对扩展开放、对修改关"

换句话说,当需求发生变化时,应该通过增加新的代码来扩展现有功能,不是直接修改现有代码。

用多态的思想,就可以很好的解决上述问题,使得程序的扩展性、灵活性大大加强!

案例实现2:使用多态

package com.briup.chap06.test;

//抽取父类:球类

class Ball {

public void play() {} }

//定义子类:篮球类

class BasketBall2 extends Ball {

//写方法

public void play() {

System.out.println("开始篮球游戏..."); }

}//定义子类:足球类

class Football2 extends Ball { public void play() {

System.out.println("开始足球游戏...");

}

}

// 第一步:新增乒乓球子类

class PingPong2 extends Ball { public void play() {

System.out.println("开始乒乓球游戏...");

}

}

// 定义游戏类

class Game2 {

//这里只需要定义一个方法即可,要求参数类型是父类Ball

//调用该方法时,需要传递一个该父类引用值,可以指向任何一个子类对象

public void start(Ball ball){ ball.play();

}

//Game类中代码不需要任何改动

}

//多态实现:代码的扩展性很强

public class Test08_Game2 {

public static void main(String[] args) {

// 1.创建游戏对象

Game2  game  =  new  Game2();

// 2.创建篮球对象,然后开始游戏

BasketBall2 basketBall = new BasketBall2(); game.start(basketBall);

// 3.创建足球对象,然后开始游戏

Football2 football = new Football2(); game.start(football);

//  第三步:测试类创建乒乓球对象,然后开始游戏PingPong2 pingpong = new PingPong2(); game.start(pingpong);

}

}

代码分析:

用多态实现功能时,如果后续要不断扩展游戏种类,根本不需要修改Game类中的代码,符合开闭原则,大大提高了程序的扩展性和灵活性!

运行效果:

1.引用类型转换

学习基本数据类型时,我们学过隐式类型转换、强制类型转换。

学习多态时,我们用过引用类型转换: 父类 引用名 =  子类对象;接下来我们更细致的讨论引用类型转换:

上转型(隐式转换)

父类引用指向子类对象,多态部分我们已经大量使用例如, Person  p  =  new  Student();

向下转型(显式转换)子类引用指向父类对象

格式: 子类型 引用名 =  (子类型)父类引用;前提:父类引用本身就指向子类对象

Person p = new Student();

Student  s  =  (Student)p; //向下转型注意:必须先有向上转型,才能向下转型

1)向上转型访问特点

前提:使用父类引用指向子类对象,然后通过父类引用访问成员变量或成员方

操作成员变量:编译看左边 (父类), 运行看左边 (父类)操作成员方法:编译看左边 (父类), 运行看右边 (子类)

案例展示1

package com.briup.chap06.test;

//定义基类

class Base { int n = 1;

public void show() {

System.out.println("in Base, n: " + n); }

}

//定义派生类

class Derived extends Base {

//增成员

int n = 2;   //同名成员

int v = 20;

//写方法

public void show() {

System.out.println("in Derived, n: " + n);   //this.n > super.n

System.out.println("in Derived, v: " + v); }

//增方法

public void disp() {

System.out.println("in Derived, v: " + v); }

}

//试代码

public class Test09_Trans {

/*

* 多态(向上转型)访问特点:

* 成员变量: 编译看左边 (父类), 运行看左边 (父类)

*

*/

成员方法: 编译看左边 (父类), 运行看右边 (子类)

public static void main(String[] args) {

//1.向上转型:用子对象  父类引用赋值

Base b = new Derived();

//2.访问成员变量: 编译看左边 (父类), 运行看左边 (父类) System.out.println(b.n); //Base: n

//父类中没有v个成员,编译失败

//System.out.println(b.v); //error

//3.方法成员方法: 编译看左边 (父类), 运行看右边 (子类)

//调用的是子类重写以后的方法

b.show();

//父类中没有disp()方法,编译失败

//b.disp(); //error

}

}

注意:向上转型(多态)时,父类引用无法调用到子类中独有的成员和方法!

2)向下转型功能测试案例展示2

注意:先有向上转型,然后才能向下转型成功

//BaseDerived代码不变,修改main法测试代码,具体如下:

public class Test09_Trans {

// 注意事项:先有向上转型,然后才能有向下转型

public static void main(String[] args) {

//1.向上转型:用子对象  父类引用赋值

Base b = new Derived();

//父类中没有v个成员,编译失败

//System.out.println(b.v); //error

//父类中没有disp()方法,编译失败

//b.disp();

//2.借助向下转型可以解决上述问题

Derived d = (Derived)b;

//操作子类独有成员

System.out.println(d.v);

//操作子类独有方法

d.disp();

//error

}

}

3)引用类型强换异常

在类型强制转换的过程中,可能会遇到类型转换异常。案例展示:

在上述案例的基础上,新增派生类Fork,然后模拟类型转换异常的情况

//新增派生类

class Fork extends Base {

//增独有方法

public void out() {

System.out.println("in Fork, n" + n);

}

}

//试代码

public class Test09_Trans {

public static void main(String[] args) {

//1.上转型

Base b = new Derived();

//2.向下转型,思考:编译能否成功,运行能否成功?

Fork f = (Fork)b;

//3.用独有方法

f.out();

}

}

报错分析:

如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现ClassCastException异常。

Fork  f  =  (Fork)b; 强制转换时,b实际指向Derived对象,DerivedFork

类型不是子父类关系,所以它是不能转为Fork对象的!

那么如何避免这样的问题呢?使用 instanceof关键字 可以解决。

4instanceof关键字

instanceof键字能告诉我们,当前引用到底指向哪种子类型对象

格式:

引用名 instanceof  类型名

作用:

判断引用名实际指向的对象,其所属类型是否为右边的类型,返回truefalse

案例展示:

 instanceof关键字 解决上述案例中的异常问题。

//试代码

public class Test09_Trans {

public static void main(String[] args) {

//1.上转型

Base b = new Derived();

//2.下转型

if(b instanceof Fork) {

//强转为合适的类型

Fork f = (Fork)b;

//3.用独有方法

f.out();

}else if(b instanceof Derived) { 

Derived d = (Derived)b;

 d.disp();

}

}

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时肆 知还

你的鼓励将是我创作的最大动力,

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值