09--面向对象OOP--04
1、关键字:static
1.1 什么是static关键字
它可以用来修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属 于某个对象的。也就是说,既然属于类,就可以不靠创建对象来调用了。
使用范围:
????????在Java类中,可用static修饰属性、方法。代码块、内部类、
1.2 静态变量
static的特点:(它可以修饰成员变量,还可以修饰成员方法)
????????A:随着类的加载而加载
????????B:优先于对象存在
????????C:被类的所有对象共享
????????D:可以通过类名调用 推荐使用类名调用。
public class Student {
//非静态变量
int num1 = 10;
//静态变量
static int num2 = 20;
}
public class StudentDemo {
public static void main(String[] args) {
Student s = new Student();
System.out.println(s.num1);
System.out.println(s.num2);//也可以用对象来调用静态变量,不推荐
//可以直接用类名直接调用静态变量
System.out.println(Student.num2);
}
}
1.2.1 静态变量和成员变量的区别
所属不同
????????静态变量属于类,所以也称为类变量
????????成员变量属于对象,所以也称之为实例变量(对象变量)
内存中位置不同
????????静态变量存储于方法区的静态区
????????成员变量存储于堆内存中
内存出现的时间不同
????????静态变量随着类的加载而加载,随着类的消失而消失
????????成员变量随着对象的创建而创建,随着对象的消失而消失
调用不同
????????静态变量可以通过类名调用,也可以通过对象来调用
????????成员变量只能通过对象名调用
1.3 静态方法
static关键字注意事项
????????A:在静态方法中是没有this关键字的
????????????????如何理解呢?
????????????????????????静态是随着类的加载而加载,this是随着对象的创建而存在。
????????????????????????静态比对象先存在。
????????B:静态方法只能访问静态的成员变量和静态的成员方法
????????????????静态方法:
????????????????????????成员变量:只能访问静态变量
????????????????????????成员方法:只能访问静态成员方法
????????????????非静态方法:
????????????????????????成员变量:可以是静态的,也可以是非静态的
????????????????????????成员方法:可是是静态的成员方法,也可以是非静态的成员方法。
public class Student {
//非静态变量
int num1 = 10;
//静态变量
static int num2 = 20;
//非静态方法
public void show(){
System.out.println(num1);
System.out.println(this.num1);
System.out.println(num2);
//方法:非静态的可以访问静态的也可以访问非静态的
function();
function2();
}
//静态方法
public static void method(){
//静态不能访问非静态的成员变量
// System.out.println(num1);
//静态中没有this
// System.out.println(this.num1);
//静态方法可以访问静态的成员变量
System.out.println(num2);
//静态不能访问非静态的
// function();
//静态可以访问静态的
function2();
}
public void function(){
System.out.println("function");
}
public static void function2(){
System.out.println("function2");
}
}
1.4?静态代码块
位置:类中方法外。
执行:随着类的加载而执行且执行一次,优先于main方法和构造方法的执行。
public class Game {
static {
age = 27;
name = "江一燕";
System.out.println(name+": "+age);
}
static int age;
static String name;
}
// 测试类
public class GameTest {
public static void main(String[] args) {
Game g = new Game();
Game g2 = new Game();
}
}
2、关键字:final
2.1 final特点
final:不可改变。可以用于修饰类、方法和变量。
类:被修饰的类,不能被继承。
方法:被修饰的方法,不能被重写。
变量:被修饰的变量,不能被重新赋值,变成了常量
2.2 final的使用
2.2.1 修饰类
final修饰类表示这个类不能被继承,没有子类。提高安全性,提高程序的可读性。
例如:String类、System类、StringBuffer类
//final修饰类
public final class Fu {
public void show(){
System.out.println("fu..show..");
}
}
//子类不能继承final所修饰的父类
public class Zi extends Fu {
}
2.2.2 修饰方法
final修饰方法表示这个方法不能被子类重写。
例如:Object类中的getClass()
public class Fu {
public final void show(){
System.out.println("fu..show..");
}
}
public class Zi extends Fu {
//子类继承父类中的final修饰的方法
public void show(){
System.out.println("fu..show..");
}
}
2.2.3 修饰变量
final修饰某个变量(成员变量或局部变量),一旦赋值,它的值就不能被修改,即常量,常量名建议使用大写字母。
例如:final double MY_PI = 3.14;
public class Fu {
// 修饰成员变量
final int num = 10;
public static int totalNumber = 5;
public final int ID;
public void show(){
System.out.println("fu..show..");
// 修饰局部变量
final int MIN_SCORE ;
MIN_SCORE = 0;
final int MAX_SCORE = 100;
MAX_SCORE = 200; //非法
}
}
public class Test {
public static void main(String[] args) {
Fu f = new Fu();
System.out.println(f.num);//10
// f.num = 20; final修饰的变量,变成常量,不能进行修改
}
}
3、权限修饰符
3.1 权限概述
在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限。
public? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?公共的
protected? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 受保护的
default(不写) ????????????????????????????????默认的(缺省的)
private? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 私有的
3.2 不同权限的访问范围
3.3 总结
在四大权限中,public是最大的权限,private是最小的权限
在编写代码时,如果没有特殊的考虑,建议以下使用方式;
????????成员变量使用 private ,隐藏细节。
????????构造方法使用 public ,方便创建对象。
????????成员方法使用 public ,方便调用方法
4、面向对象特征三--多态性
4.1 多态的形式和体现
4.1.1 对象的多态性
多态性,是面向对象中最重要的概念,在Java中的体现:对象的多态性:父类的引用指向子类的对象
格式:(父类类型:指子类继承的父类类型,或者实现的接口类型)
父类类型 变量名 = 子类对象;
举例:
Person p = new Student();
Object o = new Person();//Object类型的变量o,指向Person类型的对象
o = new Student(); //Object类型的变量o,指向Student类型的对象
对象的多态:在Java中,子类的对象可以替代父类的对象使用。所以,一个引用类型变量可能指向(引用)多种不同类型的对象
4.1.2 多态的理解
Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。
- 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
- 多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法) “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
多态的使用前提:① 类的继承关系 ② 方法的重写
4.1.3 举例
public class Pet {
private String nickname; //昵称
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public void eat(){
System.out.println(nickname + "吃东西");
}
}
public class Cat extends Pet {
//子类重写父类的方法
@Override
public void eat() {
System.out.println("猫咪" + getNickname() + "吃鱼仔");
}
//子类扩展的方法
public void catchMouse() {
System.out.println("抓老鼠");
}
}
public class Dog extends Pet {
//子类重写父类的方法
@Override
public void eat() {
System.out.println("狗子" + getNickname() + "啃骨头");
}
//子类扩展的方法
public void watchHouse() {
System.out.println("看家");
}
}
1、方法内局部变量的赋值体现多态
public class TestPet {
public static void main(String[] args) {
//多态引用
Pet pet = new Dog();
pet.setNickname("小白");
//多态的表现形式
/*
编译时看父类:只能调用父类声明的方法,不能调用子类扩展的方法;
运行时,看“子类”,如果子类重写了方法,一定是执行子类重写的方法体;
*/
pet.eat();//运行时执行子类Dog重写的方法
// pet.watchHouse();//不能调用Dog子类扩展的方法
pet = new Cat();
pet.setNickname("雪球");
pet.eat();//运行时执行子类Cat重写的方法
}
}
2、方法的形参声明体现多态
public class Person{
private Pet pet;
public void adopt(Pet pet) {//形参是父类类型,实参是子类对象
this.pet = pet;
}
public void feed(){
pet.eat();//pet实际引用的对象类型不同,执行的eat方法也不同
}
}
public class TestPerson {
public static void main(String[] args) {
Person person = new Person();
Dog dog = new Dog();
dog.setNickname("小白");
person.adopt(dog);//实参是dog子类对象,形参是父类Pet类型
person.feed();
Cat cat = new Cat();
cat.setNickname("雪球");
person.adopt(cat);//实参是cat子类对象,形参是父类Pet类型
person.feed();
}
}
3、方法返回值类型体现多态
public class PetShop {
//返回值类型是父类类型,实际返回的是子类对象
public Pet sale(String type){
switch (type){
case "Dog":
return new Dog();
case "Cat":
return new Cat();
}
return null;
}
}
public class TestPetShop {
public static void main(String[] args) {
PetShop shop = new PetShop();
Pet dog = shop.sale("Dog");
dog.setNickname("小白");
dog.eat();
Pet cat = shop.sale("Cat");
cat.setNickname("雪球");
cat.eat();
}
}
4.2 为什么需要多态
开发中,有时我们在设计一个数组、或一个成员变量、或一个方法的形参、返回值类型时,无法确定它具体的类型,只能确定它是某个系列的类型。
案例:
(1)声明一个Dog类,包含public void eat()方法,输出“狗啃骨头”
(2)声明一个Cat类,包含public void eat()方法,输出“猫吃鱼仔”
(3)声明一个Person类,功能如下:
- 包含宠物属性
- 包含领养宠物方法 public void adopt(宠物类型Pet)
- 包含喂宠物吃东西的方法 public void feed(),实现为调用宠物对象.eat()方法
public class Dog {
public void eat(){
System.out.println("狗啃骨头");
}
}
public class Cat {
public void eat(){
System.out.println("猫吃鱼仔");
}
}
public class Person {
private Dog dog;
//adopt:领养
public void adopt(Dog dog){
this.dog = dog;
}
//feed:喂食
public void feed(){
if(dog != null){
dog.eat();
}
}
/*
问题:
1、从养狗切换到养猫怎么办?
修改代码把Dog修改为养猫?
2、或者有的人养狗,有的人养猫怎么办?
3、要是还有更多其他宠物类型怎么办?
如果Java不支持多态,那么上面的问题将会非常麻烦,
代码维护起来很难,扩展性很差。
*/
}
4.3 多态的好处和弊端
好处:变量引用的子类对象不同,执行的方法就不同,实现动态绑定。代码编写更灵活、功能更强大,可维护性和扩展性更好了。
弊端:一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法。
Student m = new Student();
m.school = "pku"; //合法,Student类有school成员变量
Person e = new Student();
e.school = "pku"; //非法,Person类没有school成员变量
// 属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编译错误。
开发中:
使用父类做方法的形参,是多态使用最多的场合。即使增加了新的子类,方法也无需改变,提高了扩展性,符合开闭原则。
【开闭原则OCP】
- 对扩展开放,对修改关闭
- 通俗解释:软件系统中的各种组件,如模块(Modules)、类(Classes)以及功能(Functions)等,应该在不修改现有代码的基础上,引入新功能
4.4 虚方法调用
在Java中虚方法是指在编译阶段不能确定方法的调用入口地址,在运行阶段才能确定的方法,即可能被重写的方法。
Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
举例:
前提:Person类中定义了welcome()方法,各个子类重写了welcome()。
执行:多态的情况下,调用对象的welcome()方法,实际执行的是子类重写的方法。
拓展:
静态链接(或早起绑定):当一个字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知,且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。那么调用这样的方法,就称为非虚方法调用。比如调用静态方法、私有方法、final方法、父类构造器、本类重载构造器等。
动态链接(或晚期绑定):如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接。调用这样的方法,就称为虚方法调用。比如调用重写的方法(针对父类)、实现的方法(针对接口)。
4.5 成员变量没有多态性
- 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。
- 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
public class TestVariable {
public static void main(String[] args) {
Base b = new Sub();
System.out.println(b.a); // 1
System.out.println(((Sub)b).a); // 2
Sub s = new Sub();
System.out.println(s.a); // 2
System.out.println(((Base)s).a); // 1
}
}
class Base{
int a = 1;
}
class Sub extends Base{
int a = 2;
}
4.6 向上转型与向下转型
首先,一个对象在new的时候创建是哪个类型的对象,它从头至尾都不会变。即这个对象的运行时类型,本质的类型用于不会变。但是,把这个对象赋值给不同类型的变量时,这些变量的编译时类型却不同。
4.6.1 为什么要类型转换
因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间,就会出现类型转换的现象。
但是,使用父类变量接收了子类对象之后,我们就不能调用子类拥有,而父类没有的方法了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做类型转换,使得编译通过。
- 向上转型:当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型
-
- 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了
- 但是,运行时,仍然是对象本身的类型,所以执行的方法是子类重写的方法体。
- 此时,一定是安全的,而且也是自动完成的
- 向下转型:当左边的变量的类型(子类)<右边对象/变量的编译时类型(父类),我们就称为向下转型
-
- 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了
- 但是,运行时,仍然是对象本身的类型
- 不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过isInstanceof关键字进行判断
4.6.2 如何向上或向下转型
向上转型:自动完成
向下转型:(子类类型)父类变量
public class ClassCastTest {
public static void main(String[] args) {
//没有类型转换
Dog dog = new Dog();//dog的编译时类型和运行时类型都是Dog
//向上转型
Pet pet = new Dog();//pet的编译时类型是Pet,运行时类型是Dog
pet.setNickname("小白");
pet.eat();//可以调用父类Pet有声明的方法eat,但执行的是子类重写的eat方法体
// pet.watchHouse();//不能调用父类没有的方法watchHouse
Dog d = (Dog) pet;
System.out.println("d.nickname = " + d.getNickname());
d.eat();//可以调用eat方法
d.watchHouse();//可以调用子类扩展的方法watchHouse
Cat c = (Cat) pet;//编译通过,因为从语法检查来说,pet的编译时类型是Pet,Cat是Pet的子类,所以向下转型语法正确
//这句代码运行报错ClassCastException,因为pet变量的运行时类型是Dog,Dog和Cat之间是没有继承关系的
}
}
4.6.3 instanceof关键字
为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验。如下代码格式:
//检验对象a是否是数据类型A的对象,返回值为boolean型
对象a instanceof 数据类型A
- 说明:
-
- 只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。
- 如果对象a属于类A的子类B,a instanceof A值也为true。
- 要求对象a所属的类与类A必须是子类和父类的关系,否则编译错误。
代码:
package com.atguigu.polymorphism.grammar;
public class TestInstanceof {
public static void main(String[] args) {
Pet[] pets = new Pet[2];
pets[0] = new Dog();//多态引用
pets[0].setNickname("小白");
pets[1] = new Cat();//多态引用
pets[1].setNickname("雪球");
for (int i = 0; i < pets.length; i++) {
pets[i].eat();
if(pets[i] instanceof Dog){
Dog dog = (Dog) pets[i];
dog.watchHouse();
}else if(pets[i] instanceof Cat){
Cat cat = (Cat) pets[i];
cat.catchMouse();
}
}
}
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!