java之面向对象

作者 : admin 本文共63718个字,预计阅读时间需要160分钟 发布时间: 2024-06-10 共3人阅读

1 面向对象介绍

1.面向过程:自己的事情自己干,代表语言C语言
          洗衣服:每一步自己要亲力亲为 -> 找个盆,放点水,找个搓衣板,搓搓搓
2.面向对象:自己的事情别人帮忙去干,代表语言Java语言    
          洗衣服:自己的事情别人干 -> 全自动洗衣机
              
3.为啥要使用面向对象思想编程:懒
  很多功能别人都给我们实现好了,我们只需要直接拿过来使用即可,简化了我们自己的编写过程,减少了我们的代码量
    
4.什么时候使用面向对象思想编程:
  调用别人的功能时
  在一个类中想使用别的类中的成员时,就使用面向对象思想编程
  至于我们使用的功能人家怎么实现的,我们不需要关心,我们只需要知道怎么使用即可
      
5.怎么使用面向对象思想编程:
  a.new呀,new完点呀-> 点代表的是调用
  b.特殊:如果调用的成员带static关键字,我们不需要new,我们直接类名点即可

2 类与对象

2.1 类的定义

类的定义使用关键字:class。格式如下:

[修饰符] class 类名{
    属性声明;
    方法声明;
}

举例1:

public class Person{
    //声明属性age
    int age ;                      
    
    //声明方法showAge()
    public void eat() {        
        System.out.println("人吃饭");
    }
}

举例2:

public class Dog{
    //声明属性
    String type; //种类
    String nickName; //昵称
    String hostName; //主人名称
    
    //声明方法
    public void eat(){ //吃东西
        System.out.println("狗狗进食");     
    }
}
public class Person{
    String name;
    char gender;
    Dog dog;
    
    //喂宠物
    public void feed(){
        dog.eat();
    }
}

2.2 对象的创建

创建对象,使用关键字:new

创建对象语法:

//方式1:给创建的对象命名

//把创建的对象用一个引用数据类型的变量保存起来,这样就可以反复使用这个对象了

类名 对象名 = new 类名();

//方式2:

new 类名()//也称为匿名对象

举例:

class PersonTest{
public static void main(String[] args){
//创建 Person 类的对象
Person per = new Person();
//创建 Dog 类的对象
Dog dog = new Dog();
}
}

2.3 对象调用属性或方法

对象是类的一个实例,必然具备该类事物的属性和行为(即方法)。

使用”对象名.属性” 或 “对象名.方法“的方式访问对象成员(包括属性和方法)

举例1:

//声明 Animal 类
public class Animal { //动物类
 public int legs;
 public void eat() {
 System.out.println("Eating.");
 }
 public void move() {
 System.out.println("Move.");
 }
}
//声明测试类
public class AnimalTest {
 public static void main(String args[]) {
 //创建对象
 Animal xb = new Animal();
 xb.legs = 4;//访问属性
 System.out.println(xb.legs);
 xb.eat();//访问方法
 xb.move();//访问方法
 }
}

举例2:针对前面步骤1的举例2:类的实例化(创建类的对象)

public class Game{
 public static void main(String[] args){
 Person p = new Person();
 //通过 Person 对象调用属性
 p.name = "康师傅";
 p.gender = '男';
 p.dog = new Dog(); //给 Person 对象的 dog 属性赋值
 
 //给 Person 对象的 dog 属性的 type、nickname 属性赋值
 p.dog.type = "柯基犬";
 p.dog.nickName = "小白";
 
 //通过 Person 对象调用方法
 p.feed();
 }
}

2.4匿名对象 (anonymous object)

我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。

– 如:new Person().shout();

使用情况

– 如果一个对象只需要进行一次方法调用,那么就可以使用匿名对象。

– 我们经常将匿名对象作为实参传递给一个方法调用。

3 成员变量(field)

3.1 如何声明成员变量

语法格式:

[修饰符1] class 类名{

[修饰符2] 数据类型 成员变量名 [= 初始化值];

}

说明:

– 位置要求:必须在类中,方法外

– 修饰符2(暂不考虑)

• 常用的权限修饰符有:private、缺省、protected、public

• 其他修饰符:static、final

– 数据类型

• 任何基本数据类型(如int、Boolean) 或 任何引用数据类型。

– 成员变量名

• 属于标识符,符合命名规则和规范即可。

– 初始化值

• 根据情况,可以显式赋值;也可以不赋值,使用默认值

示例:

public class Person{
    private int age;             //声明private变量 age
    public String name = “Lila”;    //声明public变量 name
}

3.2 成员变量 vs 局部变量

1、变量的分类:成员变量与局部变量

在方法体外,类体内声明的变量称为成员变量。

在方法体内部等位置声明的变量称为局部变量。

java之面向对象插图
java之面向对象插图(1)

其中,static可以将成员变量分为两大类,静态变量和非静态变量。其中静态变量又称为类变量,非静态变量又称为实例变量或者属性。接下来先学习实例变量。

2、成员变量 与 局部变量 的对比

相同点:

– 变量声明的格式相同: 数据类型 变量名 = 初始化值

– 变量必须先声明、后初始化、再使用。

– 变量都有其对应的作用域。只在其作用域内是有效的

不同点:

1、声明位置和方式 (1)实例变量:在类中方法外 (2)局部变量:在方法体{}中或方法的形参列表、代码块中

2、在内存中存储的位置不同 (1)实例变量:堆 (2)局部变量:栈

3、生命周期 (1)实例变量:和对象的生命周期一样,随着对象的创建而存在,随着对象被GC回收而消亡, 而且每一个对象的实例变量是独立的。 (2)局部变量:和方法调用的生命周期一样,每一次方法被调用而在存在,随着方法执行的结束而消亡, 而且每一次方法调用都是独立。

4、作用域 (1)实例变量:通过对象就可以使用,本类中直接调用,其他类中“对象.实例变量” (2)局部变量:出了作用域就不能使用

5、修饰符(后面来讲) (1)实例变量:public,protected,private,final,volatile,transient等 (2)局部变量:final

6、默认值 (1)实例变量:有默认值 (2)局部变量:没有,必须手动初始化。其中的形参比较特殊,靠实参给它初始化。

3、对象属性的默认初始化赋值

当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。

java之面向对象插图(2)

4、举例

class Person {//人类
 //1.属性
 String name;//姓名
 int age = 1;//年龄
 boolean isMale;//是否是男性
 public void show(String nation) {
 //nation:局部变量
 String color;//color:局部变量
 color = "yellow";
 }
}
//测试类
class PersonTest {
 public static void main(String[] args) {
 Person p = new Person();
 p.show("CHN");
 }

4 方法(method)

4.1方法(method、函数)的理解

方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数过程

将功能封装为方法的目的是,可以实现代码重用,减少冗余,简化代码

Java里的方法不能独立存在,所有的方法必须定义在类里。

举例1:

– Math.random()的random()方法

– Math.sqrt(x)的sqrt(x)方法

– System.out.println(x)的println(x)方法

– new Scanner(System.in).nextInt()的nextInt()方法

– Arrays类中的binarySearch()方法、sort()方法、equals()方法

举例2:

public class Person{
 private int age;
 public int getAge() { //声明方法 getAge()
return age;
 }
 public void setAge(int i) { //声明方法 setAge
age = i; //将参数 i 的值赋给类的成员变量 age
 }
}

4.2 如何声明方法

1 声明方法的语法格式

[修饰符] 返回值类型 方法名([形参列表])[throws 异常列表]{

方法体的功能代码

}

(1)一个完整的方法 = 方法头 + 方法体。

方法头就是[修饰符] 返回值类型 方法名([形参列表])[throws 异常列表],也称为方法签名。通常调用方法时只需要关注方法头就可以,从方法头可以看出这个方法的功能和调用格式。

方法体就是方法被调用后要执行的代码。对于调用者来说,不了解方法体如何实现的,并不影响方法的使用。

(2)方法头可能包含5个部分

修饰符:可选的。方法的修饰符也有很多,例如:public、protected、private、static、abstract、native、final、synchronized等,后面会一一学习。

– 其中,权限修饰符有public、protected、private。在讲封装性之前,我们先默认使用pulbic修饰方法。

– 其中,根据是否有static,可以将方法分为静态方法和非静态方法。其中静态方法又称为类方法,非静态方法又称为实例方法。咱们在讲static前先学习实例方法。

返回值类型: 表示方法运行的结果的数据类型,方法执行后将结果返回到调用者。

– 无返回值,则声明:void

– 有返回值,则声明出返回值类型(可以是任意类型)。与方法体中“return 返回值”搭配使用

方法名:属于标识符,命名时遵循标识符命名规则和规范,“见名知意”

形参列表:表示完成方法体功能时需要外部提供的数据列表。可以包含零个,一个或多个参数。

– 无论是否有参数,()不能省略

– 如果有参数,每一个参数都要指定数据类型和参数名,多个参数之间使用逗号分隔,例如:

• 一个参数: (数据类型 参数名)

• 二个参数: (数据类型1 参数1, 数据类型2 参数2)

– 参数的类型可以是基本数据类型、引用数据类型

throws 异常列表:可选,在【第09章-异常处理】章节再讲

(3)方法体:方法体必须有{}括起来,在{}中编写完成方法功能的代码

(4)关于方法体中return语句的说明:

return语句的作用是结束方法的执行,并将方法的结果返回去

如果返回值类型不是void,方法体中必须保证一定有 return 返回值; 语句,并且要求该返回值结果的类型与声明的返回值类型一致或兼容。

如果返回值类型为void时,方法体中可以没有return语句,如果要用return语句提前结束方法的执行,那么return后面不能跟返回值,直接写return ; 就可以。

return语句后面就不能再写其他代码了,否则会报错:Unreachable code

补充:方法的分类:按照是否有形参及返回值

java之面向对象插图(3)

2 代码示例:

/**
 * 方法定义案例演示
 */
public class MethodDefineDemo {
    /**
     * 无参无返回值方法的演示
     */
    public void sayHello(){
        System.out.println("hello");
    }
​
    /**
     * 有参无返回值方法的演示
     * @param length int 第一个参数,表示矩形的长
     * @param width int 第二个参数,表示矩形的宽
     * @param sign char 第三个参数,表示填充矩形图形的符号
     */
    public void printRectangle(int length, int width, char sign){
        for (int i = 1; i <= length ; i++) {
            for(int j=1; j  b ? a : b;
    }
}

4.3 如何调用实例方法

方法通过方法名被调用,且只有被调用才会执行。

1、方法调用语法格式

对象.方法名([实参列表])

2、示例

举例1:

/**
 * 方法调用案例演示
 */
public class MethodInvokeDemo {
    public static void main(String[] args) {
        //创建对象
        MethodDefineDemo md = new MethodDefineDemo();
​
        System.out.println("-----------------------方法调用演示-------------------------");
​
        //调用MethodDefineDemo类中无参无返回值的方法sayHello
        md.sayHello();
        md.sayHello();
        md.sayHello();
        //调用一次,执行一次,不调用不执行
​
        System.out.println("------------------------------------------------");
        //调用MethodDefineDemo类中有参无返回值的方法printRectangle
        md.printRectangle(5,10,'@');
​
        System.out.println("------------------------------------------------");
        //调用MethodDefineDemo类中无参有返回值的方法getIntBetweenOneToHundred
        md.getIntBetweenOneToHundred();//语法没问题,就是结果丢失
​
        int num = md.getIntBetweenOneToHundred();
        System.out.println("num = " + num);
​
        System.out.println(md.getIntBetweenOneToHundred());
        //上面的代码调用了getIntBetweenOneToHundred三次,这个方法执行了三次
​
        System.out.println("------------------------------------------------");
        //调用MethodDefineDemo类中有参有返回值的方法max
        md.max(3,6);//语法没问题,就是结果丢失
        
        int bigger = md.max(5,6);
        System.out.println("bigger = " + bigger);
​
        System.out.println("8,3中较大者是:" + md.max(8,9));
    }
}

举例2:

//1、创建Scanner的对象
Scanner input = new Scanner(System.in);//System.in默认代表键盘输入
​
//2、提示输入xx
System.out.print("请输入一个整数:"); //对象.非静态方法(实参列表)
​
//3、接收输入内容
int num = input.nextInt();  //对象.非静态方法()

4.4 使用的注意点

(1)必须先声明后使用,且方法必须定义在类的内部

(2)调用一次就执行一次,不调用不执行。

(3)方法中可以调用类中的方法或属性,不可以在方法内部定义方法。

正确示例:

类{
    方法1(){
        
    }
    方法2(){
        
    }
}

错误示例:

类{
    方法1(){
        方法2(){  //位置错误
        
        }
    }
}

4.5 关键字return的使用

return在方法中的作用:

– 作用1:结束一个方法

– 作用2:结束一个方法的同时,可以返回数据给方法的调用者

注意点:在return关键字的直接后面不能声明执行语句

4.6 方法的重载(overload)

4.6.1 概念及特点

方法重载:在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可。

– 参数列表不同,意味着参数个数或参数类型的不同

重载的特点:与修饰符、返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。

重载方法调用:JVM通过方法的参数列表,调用匹配的方法。

– 先找个数、类型最匹配的

– 再找个数和类型可以兼容的,如果同时多个方法可以兼容将会报错

4.6.2 示例

举例1:

//System.out.println()方法就是典型的重载方法,其内部的声明形式如下:
public class PrintStream {
    public void println(byte x)
    public void println(short x)
    public void println(int x)
    public void println(long x)
    public void println(float x)
    public void println(double x)
    public void println(char x)
    public void println(double x)
    public void println()
​
}
​
public class HelloWorld{
    public static void main(String[] args) {
        System.out.println(3);
        System.out.println(1.2f);
        System.out.println("hello!");
    }
}

举例2:

//返回两个整数的和
public int add(int x,int y){
    return x+y;
}
​
//返回三个整数的和
public int add(int x,int y,int z){
    return x+y+z;
}
//返回两个小数的和
public double add(double x,double y){
    return x+y;
}

举例3:方法的重载和返回值类型无关

public class MathTools {
    //以下方法不是重载,会报错
    public int getOneToHundred(){
        return (int)(Math.random()*100);
    }
    
    public double getOneToHundred(){
        return Math.random()*100;
    }
}

4.6.3 练习

练习1:判 断与void show(int a,char b,double c){}构成重载的有: a)void show(int x,char y,double z){} // no

b)int show(int a,double c,char b){} // yes

c) void show(int a,double c,char b){} // yes

d) boolean show(int c,char b){} // yes

e) void show(double c){} // yes

f) double show(int x,char y,double z){} // no

g) void shows(){double c} // no

练习2:编写程序,定义三个重载方法并调用。 方法名为mOL。 三个方法分别接收一个int参数、两个int参数、一个字符串参数。分别执行平方运算并输出结果,相乘并输出结果,输出字符串信息。 在主类的main ()方法中分别用参数区别调用三个方法。

public class OverloadExample {  
  
    // 第一个方法:接收一个int参数,执行平方运算并输出结果  
    public static void mOL(int num) {  
        int square = num * num;  
        System.out.println("The square of " + num + " is: " + square);  
    }  
  
    // 第二个方法:接收两个int参数,相乘并输出结果  
    public static void mOL(int num1, int num2) {  
        int product = num1 * num2;  
        System.out.println("The product of " + num1 + " and " + num2 + " is: " + product);  
    }  
  
    // 第三个方法:接收一个字符串参数,输出字符串信息  
    public static void mOL(String message) {  
        System.out.println("The string message is: " + message);  
    }  
  
    public static void main(String[] args) {  
        // 调用第一个方法,接收一个int参数  
        mOL(5);  
  
        // 调用第二个方法,接收两个int参数  
        mOL(3, 4);  
  
        // 调用第三个方法,接收一个字符串参数  
        mOL("Hello, Overloading!");  
    }  
}

练习3:定义三个重载方法max(),第一个方法求两个int值中的最大值,第二个方法求两个double值中的最大值,第三个方法求三个double值中的最大值,并分别调用三个方法。

public class MaxValueFinder {  
  
    // 第一个方法:求两个int值中的最大值  
    public static int max(int a, int b) {  
        return a > b ? a : b;  
    }  
  
    // 第二个方法:求两个double值中的最大值  
    public static double max(double a, double b) {  
        return a > b ? a : b;  
    }  
  
    // 第三个方法:求三个double值中的最大值  
    public static double max(double a, double b, double c) {  
        return Math.max(Math.max(a, b), c);  
    }  
  
    public static void main(String[] args) {  
        // 调用第一个方法  
        int maxInt = max(5, 10);  
        System.out.println("The maximum integer is: " + maxInt);  
  
        // 调用第二个方法  
        double maxDouble1 = max(5.5, 10.2);  
        System.out.println("The maximum double (two arguments) is: " + maxDouble1);  
  
        // 调用第三个方法  
        double maxDouble2 = max(5.5, 10.2, 15.8);  
        System.out.println("The maximum double (three arguments) is: " + maxDouble2);  
    }  
}

4.7 可变个数的形参

JDK 5.0 中提供了Varargs(variable number of arguments)机制。即当定义一个方法时,形参的类型可以确定,但是形参的个数不确定,那么可以考虑使用可变个数的形参。

格式:

方法名(参数的类型名 …参数名)

举例:

//JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量

public static void test(int a ,String[] books);

//JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量

public static void test(int a ,Stringbooks);

特点:

可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个

可变个数形参的方法与同名的方法之间,彼此构成重载

可变参数方法的使用与方法参数部分使用数组是一致的,二者不能同时声明,否则报错。

方法的参数部分有可变形参,需要放在形参声明的最后

在一个方法的形参中,最多只能声明一个可变个数的形参

案例分析:

案例1:n个字符串进行拼接,每一个字符串之间使用某字符进行分割,如果没有传入字符串,那么返回空字符串””

public class StringTools {
    String concat(char seperator, String... args){
        String str = "";
        for (int i = 0; i < args.length; i++) {
            if(i==0){
                str += args[i];
            }else{
                str += seperator + args[i];
            }
        }
        return str;
    }
}
​
​
public class StringToolsTest {
    public static void main(String[] args) {
        StringTools tools = new StringTools();
​
        System.out.println(tools.concat('-'));
        System.out.println(tools.concat('-',"hello"));
        System.out.println(tools.concat('-',"hello","world"));
        System.out.println(tools.concat('-',"hello","world","java"));
    }
}

案例2:求n个整数的和

public class NumberTools {
    public int total(int[] nums){
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }
        return sum;
    }
​
    public int sum(int... nums){
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }
        return sum;
    }
}
public class TestVarParam {
    public static void main(String[] args) {
        NumberTools tools = new NumberTools();
​
        System.out.println(tools.sum());//0个实参
        System.out.println(tools.sum(5));//1个实参
        System.out.println(tools.sum(5,6,2,4));//4个实参
        System.out.println(tools.sum(new int[]{5,6,2,4}));//传入数组实参
​
        System.out.println("------------------------------------");
        System.out.println(tools.total(new int[]{}));//0个元素的数组
        System.out.println(tools.total(new int[]{5}));//1个元素的数组
        System.out.println(tools.total(new int[]{5,6,2,4}));//传入数组实参
    }
}

案例3:如下的方法彼此构成重载

public class MathTools {
    //求两个整数的最大值
    public int max(int a,int b){
        return a>b?a:b;
    }
​
    //求两个小数的最大值
    public double max(double a, double b){
        return a>b?a:b;
    }
​
    //求三个整数的最大值
    public int max(int a, int b, int c){
        return max(max(a,b),c);
    }
    
    //求n个整数的最大值
    public int max(int... nums){
        int max = nums[0];//如果没有传入整数,或者传入null,这句代码会报异常
        for (int i = 1; i  max){
                max = nums[i];
            }
        }
        return max;
    }
    /*    //求n整数的最大值
    public int max(int[] nums){  //编译就报错,与(int... nums)无法区分
        int max = nums[0];//如果没有传入整数,或者传入null,这句代码会报异常
        for (int i = 1; i  max){
                max = nums[i];
            }
        }
        return max;
    }*/
​
/*    //求n整数的最大值
    public int max(int first, int... nums){  //当前类不报错,但是调用时会引起多个方法同时匹配
        int max = first;
        for (int i = 0; i  max){
                max = nums[i];
            }
        }
        return max;
    }*/
}

4.8 方法的参数传递机制

4.8.1 形参和实参

形参(formal parameter):在定义方法时,方法名后面括号()中声明的变量称为形式参数,简称形参。

实参(actual parameter):在调用方法时,方法名后面括号()中的使用的值/变量/表达式称为实际参数,简称实参。

4.8.2 参数传递机制:值传递

Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。

形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参

形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参

4.8.3 举例

1、形参是基本数据类型

案例:编写方法,交换两个整型变量的值

public class Test {
    public static void main(String[] args) {
        int m = 10;
        int n = 20;
        
        System.out.println("m = " + m + ", n = " + n);
        //交换m和n的值
//      int temp = m;
//      m = n;
//      n = temp;
        
        ValueTransferTest1 test = new ValueTransferTest1();
        test.swap(m, n);
        
        System.out.println("m = " + m + ", n = " + n);
    }
    
    public void swap(int m,int n){
        int temp = m;
        m = n;
        n = temp;
    }
    
}

2、形参是引用数据类型

public class Test {
    public static void main(String[] args) {
        
        Data d1 = new Data();
        d1.m = 10;
        d1.n = 20;
        
        System.out.println("m = " + d1.m + ", n = " + d1.n);
        
        //实现 换序
        ValueTransferTest2 test = new ValueTransferTest2();
        test.swap(d1);  
        System.out.println("m = " + d1.m + ", n = " + d1.n);
        
    }
    public void swap(Data data){
        int temp = data.m;
        data.m = data.n;
        data.n = temp;
    }
}
class Data{
    int m;
    int n;
}

4.8.4 练习

练习1:判断如下程序输出的结果

public class AssignNewObject {
    public void swap(MyData my){
        my = new MyData(); //考虑堆空间此新创建的对象,和main中的data对象是否有关
        int temp = my.x;
        my.x = my.y;
        my.y = temp;
     
    }
​
    public static void main(String[] args) {
        AssignNewObject tools = new AssignNewObject();
        
        MyData data = new MyData();
        data.x = 1;
        data.y = 2;
        System.out.println("交换之前:x = " + data.x +",y = " + data.y);//
        tools.swap(data);//调用完之后,x与y的值交换?
        System.out.println("交换之后:x = " + data.x +",y = " + data.y);//
    }
}
​
class MyData{
    int x ;
    int y;
}

练习2:如下操作是否可以实现数组排序

public class ArrayTypeParam {
​
    //冒泡排序,实现数组从小到大排序
    public void sort(int[] arr){
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j  arr[j+1]){
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }
    //打印数组的元素
    public void print(int[] arr){
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i]+" ");
        }
        System.out.println();
    }
​
    public static void main(String[] args) {
        ArrayTypeParam tools = new ArrayTypeParam();
​
        int[] nums = {4,3,1,6,7};
        System.out.println("排序之前:");
        tools.print(nums);
​
        tools.sort(nums);//对nums数组进行排序
​
        System.out.println("排序之后:");
        tools.print(nums);//输出nums数组的元素
​
    }
} 

练习3:貌似是考查方法的参数传递

java之面向对象插图(4)

//法一:
    public static void method(int a, int b) {
        // 在不改变原本题目的前提下,如何写这个函数才能在main函数中输出a=100,b=200? 
        a = a * 10;
        b = b * 20;
        System.out.println(a);
        System.out.println(b);
        System.exit(0);
    }
​
    //法二:
    public static void method(int a, int b) {
​
        PrintStream ps = new PrintStream(System.out) {
            @Override
            public void println(String x) {
​
                if ("a=10".equals(x)) {
                    x = "a=100";
                } else if ("b=10".equals(x)) {
                    x = "b=200";
                }
                super.println(x);
            }
        };
​
        System.setOut(ps);
​
    }
​

5 关键字:package、import

5.1 package(包)

package,称为包,用于指明该文件中定义的类、接口等结构所在的包。

5.1.1 语法格式

import pack1.pack2.Test;   //import pack1.pack2.*;表示引入pack1.pack2包中的所有结构
​
public class PackTest{
    public static void main(String args[]){
        Test t = new Test();          //Test类在pack1.pack2包中定义
        t.display();
    }
}

说明:

• 一个源文件只能有一个声明包的package语句

• package语句作为Java源文件的第一条语句出现。若缺省该语句,则指定为无名包。

• 包名,属于标识符,满足标识符命名的规则和规范(全部小写)、见名知意

– 包通常使用所在公司域名的倒置:com.atguigu.xxx。

– 大家取包名时不要使用”java.xx“包

• 包对应于文件系统的目录,package语句中用 “.” 来指明包(目录)的层次,每.一次就表示一层文件目录。

• 同一个包下可以声明多个结构(类、接口),但是不能定义同名的结构(类、接口)。不同的包下可以定义同名的结构(类、接口)

5.1.2 包的作用

• 包可以包含类和子包,划分项目层次,便于管理

• 帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式

• 解决类命名冲突的问题

• 控制访问权限

5.2 import(导入)

为了使用定义在其它包中的Java类,需用import语句来显式引入指定包下所需要的类。相当于import语句告诉编译器到哪里去寻找这个类

5.2.1 语法格式

import 包名.类名;

5.2.2 应用举例

import pack1.pack2.Test;   //import pack1.pack2.*;表示引入pack1.pack2包中的所有结构
​
public class PackTest{
    public static void main(String args[]){
        Test t = new Test();          //Test类在pack1.pack2包中定义
        t.display();
    }
}

5.2.3 注意事项

• import语句,声明在包的声明和类的声明之间。

• 如果需要导入多个类或接口,那么就并列显式多个import语句即可

• 如果使用a.**导入结构,表示可以导入a包下的所有的结构。举例:可以使用java.util.的方式,一次性导入util包下所有的类或接口。

• 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。

• 如果已经导入java.a包下的类,那么如果需要使用a包的子包下的类的话,仍然需要导入。

• 如果在代码中使用不同包下的同名的类,那么就需要使用类的全类名的方式指明调用的是哪个类。

• (了解)import static组合的使用:调用指定类或接口下的静态的属性或方法

6 封装性(encapsulation)

6.1 为什么需要封装?

  1. 模块化:封装使得代码可以被组织成独立的模块,每个模块负责处理特定的功能。这有助于将复杂的问题分解为更易于管理的部分,提高代码的可读性和可维护性。

  2. 隐藏实现细节:封装允许将对象的内部实现细节隐藏起来,只暴露必要的接口给外部使用。这样可以保护对象的数据不被外部直接访问,降低出错的风险。同时,这也使得代码更容易修改和扩展,因为内部实现的变化不会影响到外部调用的代码。

  3. 提高代码的安全性:通过封装,可以限制对对象内部数据的访问,确保数据的完整性和一致性。这有助于防止潜在的安全风险,如数据泄露、非法修改等。

  4. 代码复用:封装有助于创建可重用的代码。通过将通用功能封装在类中,可以在多个项目中重用这些类,减少重复代码的编写,提高开发效率。

  5. 简化接口:封装使得代码更加简洁和易于理解。通过将相关的属性和行为组织在一起,可以简化接口,使得代码更容易阅读和维护。

  6. 提高抽象程度:封装有助于提高代码的抽象程度,使得开发人员可以关注对象的行为,而不是具体的实现细节。这有助于降低代码的复杂性,提高开发效率。

6.2 Java如何实现数据封装

• 实现封装就是控制类或成员的可见性范围。这就需要依赖访问控制修饰符,也称为权限修饰符来控制。

• 权限修饰符:publicprotected缺省private。具体访问范围如下:

修饰符本类内部本包内其他包的子类其他包非子类
private×××
缺省××
protected×
public

• 具体修饰的结构:

– 外部类:public、缺省

– 成员变量、成员方法、构造器、成员内部类:public、protected、缺省、private

6.3 封装性的体现

6.3.1 成员变量/属性私有化

概述:私有化类的成员变量,提供公共的get和set方法,对外暴露获取和修改属性的功能。

实现步骤:

1 使用 private 修饰成员变量

private 数据类型 变量名 ;

代码如下:

public class Person {
    private String name;
    private int age;
    private boolean marry;
}

2 提供 getXxx方法 / setXxx 方法,可以访问成员变量,代码如下:

public class Person {
    private String name;
    private int age;
    private boolean marry;
​
    public void setName(String n) {
        name = n;
    }
​
    public String getName() {
        return name;
    }
​
    public void setAge(int a) {
        age = a;
    }
​
    public int getAge() {
        return age;
    }
    
    public void setMarry(boolean m){
        marry = m;
    }
    
    public boolean isMarry(){
        return marry;
    }
}

3 测试:

public class PersonTest {
    public static void main(String[] args) {
        Person p = new Person();
​
        //实例变量私有化,跨类是无法直接使用的
        /* p.name = "张三";
        p.age = 23;
        p.marry = true;*/
​
        p.setName("张三");
        System.out.println("p.name = " + p.getName());
​
        p.setAge(23);
        System.out.println("p.age = " + p.getAge());
​
        p.setMarry(true);
        System.out.println("p.marry = " + p.isMarry());
    }
}

成员变量封装的好处:

• 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。

便于修改,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问方式不变的话,外部根本感觉不到它的修改。例如:Java8->Java9,String从char[]转为byte[]内部实现,而对外的方法不变,我们使用者根本感觉不到它内部的修改。

6.3.2 私有化方法

public class ArrayUtil {
public int max(int[] arr) {
int maxValue = arr[0];
for(int i = 1;i < arr.length;i++){
if(maxValue < arr[i]){
maxValue = arr[i];
}
}
return maxValue;
}
public int min(int[] arr){
int minValue = arr[0];
for(int i = 1;i  arr[i]){
minValue = arr[i];
}
}
return minValue;
}
public int sum(int[] arr) {
int sum = 0;
for(int i = 0;i < arr.length;i++){
sum += arr[i];
}
return sum;
}
public int avg(int[] arr) {
int sumValue = sum(arr);
return sumValue / arr.length;
}
// 创建一系列重载的上述方法
// public double max(double[] arr){}
// public float max(float[] arr){}
// public byte max(byte[] arr){}
public void print(int[] arr) {
for(int i = 0;i < arr.length;i++){
System.out.print(arr[i] + " ");
}
System.out.println();
}
public int[] copy(int[] arr) {
int[] arr1 = new int[arr.length];
for(int i = 0;i < arr.length;i++){
arr1[i] = arr[i];
}
return arr1;
}
public void reverse(int[] arr) {
for(int i = 0,j = arr.length - 1;i < j;i++,j--){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
public void sort(int[] arr,String desc) {
if("ascend".equals(desc)){//if(desc.equals("ascend")){
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j  arr[j + 1]) {
// int temp = arr[j];
// arr[j] = arr[j + 1];
// arr[j + 1] = temp;
swap(arr,j,j+1);
}
}
}
}else if ("descend".equals(desc)){
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] < arr[j + 1]) {
// int temp = arr[j];
// arr[j] = arr[j + 1];
// arr[j + 1] = temp;
swap(arr,j,j+1);
}
}
}
}else{
System.out.println("您输入的排序方式有误!");
}
}
private void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
/**
*
* @param arr
* @param value
* @return 返回 value 值出现的位置 或 -1:未找到
*/
public int getValue(int[] arr, int value) {
//方法:线性查找
for(int i = 0;i < arr.length;i++){
if(value == arr[i]){
return i;
}
}
return - 1;
}
}
​

注意: 开发中,一般成员实例变量都习惯使用 private 修饰,再提供相应的 public 权限的 get/set 方法访问。 对于 final 的实例变量,不提供 set()方法。(后面 final 关键字的时候 讲) 对于 static final 的成员变量,习惯上使用 public 修饰。

6.4 练习

练习1:

创建程序:在其中定义两个类:Person和PersonTest类。定义如下:

用setAge()设置人的合法年龄(0~130),用getAge()返回人的年龄。在PersonTest类中实例化Person类的对象b,调用setAge()和getAge()方法,体会Java的封装性。

java之面向对象插图(5)

// Person类  
public class Person {  
    // 私有属性:年龄  
    private int age;  
  
    // 设置年龄的方法,确保年龄在0到130之间  
    public void setAge(int age) {  
        if (age >= 0 && age <= 130) {  
            this.age = age;  
        } else {  
            System.out.println("年龄必须在0到130之间。");  
        }  
    }  
  
    // 获取年龄的方法  
    public int getAge() {  
        return age;  
    }  
}  
  
// PersonTest类  
public class PersonTest {  
    public static void main(String[] args) {  
        // 实例化Person类的对象b  
        Person b = new Person();  
  
        // 调用setAge()方法设置年龄  
        b.setAge(25);  
  
        // 调用getAge()方法获取年龄并打印  
        System.out.println("人的年龄是: " + b.getAge());  
  
        // 尝试设置一个不合法的年龄  
        b.setAge(-5);  
  
        // 再次调用getAge()方法获取年龄并打印(应该还是25)  
        System.out.println("再次获取人的年龄是: " + b.getAge());  
  
        // 设置一个合法的年龄  
        b.setAge(30);  
  
        // 再次调用getAge()方法获取年龄并打印(现在应该是30)  
        System.out.println("设置新年龄后,人的年龄是: " + b.getAge());  
    }  
}

练习2:

自定义图书类。设定属性包括:书名bookName,作者author,出版社名publisher,价格price;方法包括:相应属性的get/set方法,图书信息介绍等。

public class Book {  
    // 属性  
    private String bookName;  
    private String author;  
    private String publisher;  
    private double price;  
  
    // 构造方法  
    public Book(String bookName, String author, String publisher, double price) {  
        this.bookName = bookName;  
        this.author = author;  
        this.publisher = publisher;  
        this.price = price;  
    }  
  
    // Getter 方法  
    public String getBookName() {  
        return bookName;  
    }  
  
    public String getAuthor() {  
        return author;  
    }  
  
    public String getPublisher() {  
        return publisher;  
    }  
  
    public double getPrice() {  
        return price;  
    }  
  
    // Setter 方法  
    public void setBookName(String bookName) {  
        this.bookName = bookName;  
    }  
  
    public void setAuthor(String author) {  
        this.author = author;  
    }  
  
    public void setPublisher(String publisher) {  
        this.publisher = publisher;  
    }  
  
    public void setPrice(double price) {  
        if (price >= 0) {  
            this.price = price;  
        } else {  
            System.out.println("价格不能是负数!");  
        }  
    }  
  
    // 图书信息介绍方法  
    public void introduce() {  
        System.out.println("书名: " + bookName);  
        System.out.println("作者: " + author);  
        System.out.println("出版社: " + publisher);  
        System.out.println("价格: " + price);  
    }  
  
    // 示例 main 方法,用于测试 Book 类  
    public static void main(String[] args) {  
        Book book = new Book("Java编程思想", "Bruce Eckel", "机械工业出版社", 99.99);  
        book.introduce();  
        book.setPrice(100.00);  
        book.introduce();  
    }  
}

7 构造器(Constructor)

7.1 构造器的作用

new对象,并在new对象的时候为实例变量赋值。

举例:Person p = new Person(“Peter”,15);

解释:如同我们规定每个“人”一出生就必须先洗澡,我们就可以在“人”的构造器中加入完成“洗澡”的程序代码,于是每个“人”一出生就会自动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们要“洗澡”了。

7.2 构造器的语法格式

[修饰符] class 类名{
 [修饰符] 构造器名(){
 // 实例初始化代码
 }
 [修饰符] 构造器名(参数列表){
 // 实例初始化代码
 }
}

说明:

构造器名必须与它所在的类名必须相同。

它没有返回值,所以不需要返回值类型,也不需要void。

构造器的修饰符只能是权限修饰符,不能被其他任何修饰。比如,不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值。

代码如下:

public class Student {
 private String name;
 private int age;
 // 无参构造
 public Student() {}
 // 有参构造
 public Student(String n,int a) {
 name = n;
 age = a;
 }
 public String getName() {
 return name;
 }
 public void setName(String n) {
 name = n;
 }
 public int getAge() {
 return age;
 }
 public void setAge(int a) {
 age = a;
 }
 public String getInfo(){
 return "姓名:" + name +",年龄:" + age;
 }
}
public class TestStudent {
 public static void main(String[] args) {
 //调用无参构造创建学生对象
 Student s1 = new Student();
 //调用有参构造创建学生对象
 Student s2 = new Student("张三",23);
 System.out.println(s1.getInfo());
 System.out.println(s2.getInfo());
 }
}

7.3 使用说明

当我们没有显式的声明类中的构造器时,系统会默认提供一个无参的构造器并且该构造器的修饰符默认与类的修饰符相同

当我们显式的定义类的构造器以后,系统就不再提供默认的无参的构造器了。

在类中,至少会存在一个构造器。

构造器是可以重载的。

7.4 练习

练习1:编写两个类,TriAngle和TriAngleTest,其中TriAngle类中声明私有的底边长base和高height,同时声明公共方法访问私有变量。此外,提供类必要的构造器。另一个类中使用这些公共方法,计算三角形的面积。

class TriAngle {
    private double base;
    private double height;
​
    public TriAngle(double base, double height) {
        this.base = base;
        this.height = height;
    }
​
    public double getBase() {
        return base;
    }
​
    public void setBase(double base) {
        this.base = base;
    }
​
    public double getHeight() {
        return height;
    }
​
    public void setHeight(double height) {
        this.height = height;
    }
​
    public double getArea() {
        return 0.5 * base * height;
    }
}
​
class TriAngleTest {
    public static void main(String[] args) {
        TriAngle triangle = new TriAngle(10, 5);
        System.out.println("Triangle area: " + triangle.getArea());
    }
}

练习2:

(1)定义Student类,有4个属性: String name; int age; String school; String major;

(2)定义Student类的3个构造器:

• 第一个构造器Student(String n, int a)设置类的name和age属性;

• 第二个构造器Student(String n, int a, String s)设置类的name, age 和school属性;

• 第三个构造器Student(String n, int a, String s, String m)设置类的name, age ,school和major属性;

(3)在main方法中分别调用不同的构造器创建的对象,并输出其属性值。

lass Student {
    String name;
    int age;
    String school;
    String major;
​
    Student(String n, int a) {
        name = n;
        age = a;
    }
​
    Student(String n, int a, String s) {
        name = n;
        age = a;
        school = s;
    }
​
    Student(String n, int a, String s, String m) {
        name = n;
        age = a;
        school = s;
        major = m;
    }
​
    public void printInfo() {
        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
        System.out.println("School: " + school);
        System.out.println("Major: " + major);
    }
}
​
public class Main {
    public static void main(String[] args) {
        Student student1 = new Student("Alice", 20);
        student1.printInfo();
​
        Student student2 = new Student("Bob", 21, "XYZ University");
        student2.printInfo();
​
        Student student3 = new Student("Charlie", 22, "ABC University", "Computer Science");
        student3.printInfo();
    }
}

8 关键字:this

8.1 this是什么?

  • 在Java中,this关键字不算难理解,它的作用和其词义很接近。

    • 它在方法(准确的说是实例方法或非static的方法)内部使用,表示调用该方法的对象

    • 它在构造器内部使用,表示该构造器正在初始化的对象。

  • this可以调用的结构:成员变量、方法和构造器

8.2 什么时候使用this

8.2.1 实例方法或构造器中使用当前对象的成员

在实例方法或构造器中,如果使用当前类的成员变量或成员方法可以在其前面添加this,增强程序的可读性。不过,通常我们都习惯省略this。

但是,当形参与成员变量同名时,如果在方法内或构造器内需要使用成员变量,必须添加this来表明该变量是类的成员变量。即:我们可以用this来区分成员变量局部变量。比如:

java之面向对象插图(6)

另外,使用this访问属性和方法时,如果在本类中未找到,会从父类中查找。这个在继承中会讲到。

举例1:

class Person{       // 定义Person类
    private String name ;   
    private int age ;           
    public Person(String name,int age){ 
        this.name = name ;   
        this.age = age ;  
    }
    public void setName(String name){
        this.name = name;
    }
    public void setAge(int age){
        this.age = age;
    }
    public void getInfo(){  
        System.out.println("姓名:" + name) ;
        this.speak();
    }
    public void speak(){
        System.out.println(“年龄:” + this.age);   
    }
}
​

举例2:

public class Rectangle {
    int length;
    int width;
​
    public int area() {
        return this.length * this.width;
    }
​
    public int perimeter(){
        return 2 * (this.length + this.width);
    }
​
    public void print(char sign) {
        for (int i = 1; i <= this.width; i++) {
            for (int j = 1; j <= this.length; j++) {
                System.out.print(sign);
            }
            System.out.println();
        }
    }
​
    public String getInfo(){
        return "长:" + this.length + ",宽:" + this.width +",面积:" + this.area() +",周长:" + this.perimeter();
    }
}
​

测试类:

public class TestRectangle {
    public static void main(String[] args) {
        Rectangle r1 = new Rectangle();
        Rectangle r2 = new Rectangle();
​
        System.out.println("r1对象:" + r1.getInfo());
        System.out.println("r2对象:" + r2.getInfo());
​
        r1.length = 10;
        r1.width = 2;
        System.out.println("r1对象:" + r1.getInfo());
        System.out.println("r2对象:" + r2.getInfo());
​
        r1.print('#');
        System.out.println("---------------------");
        r1.print('&');
​
        System.out.println("---------------------");
        r2.print('#');
        System.out.println("---------------------");
        r2.print('%');
    }
}

8.2.2 同一个类中构造器互相调用

this可以作为一个类中构造器相互调用的特殊格式。

  • this():调用本类的无参构造器

  • this(实参列表):调用本类的有参构造器

public class Student {
    private String name;
    private int age;
​
    // 无参构造
    public Student() {
//        this("",18);//调用本类有参构造器
    }
​
    // 有参构造
    public Student(String name) {
        this();//调用本类无参构造器
        this.name = name;
    }
    // 有参构造
    public Student(String name,int age){
        this(name);//调用本类中有一个String参数的构造器
        this.age = age;
    }
​
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
​
    public String getInfo(){
        return "姓名:" + name +",年龄:" + age;
    }
}

注意:

  • 不能出现递归调用。比如,调用自身构造器。

    • 推论:如果一个类中声明了n个构造器,则最多有 n – 1个构造器中使用了”this(形参列表)”

  • this()和this(实参列表)只能声明在构造器首行。

    • 推论:在类的一个构造器中,最多只能声明一个”this(参数列表)”

8.3 练习

练习1:添加必要的构造器,综合应用构造器的重载,this关键字。

java之面向对象插图(7)

案例1

public class Boy {
    private String name;
    private int age;
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public int getAge() {
        return age;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
​
    public Boy() {
    }
​
    public Boy(String name, int age) {
        this.name = name;
        this.age = age;
    }
​
    public void marry(Girl girl){
        System.out.println("我想娶" + girl.getName());
    }
​
    public void shout(){
        if(this.age >= 22){
            System.out.println("我终于可以结婚了!");
        }else{
            System.out.println("我只能多谈恋爱了");
        }
    }
​
}
​

案例2

public class Girl {
    private String name;
    private int age;
​
    public Girl() {
​
    }
​
    public Girl(String name, int age) {
        this();
        this.name = name;
        this.age = age;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public void marry(Boy boy){
        System.out.println("我想嫁给" + boy.getName());
​
        boy.marry(this);
    }
​
    /**
     * 比较两个Girl对象的大小。
     * @param girl
     * @return 正数:当前对象大 ; 负数:当前对象小(或形参girl大) ; 0:相等
     */
    public int compare(Girl girl){
        if(this.age > girl.age){
            return 1;
        }else if(this.age < girl.age){
            return -1;
        }else{
            return 0;
        }
​
    }
}
public class BoyGirlTest {
    public static void main(String[] args) {
​
        Boy boy1 = new Boy("杰克",24);
        Girl girl1 = new Girl("朱丽叶",20);
​
        girl1.marry(boy1);
​
        boy1.shout();
​
        Girl girl2 = new Girl("肉丝",18);
        int compare = girl1.compare(girl2);
        if(compare > 0){
            System.out.println(girl1.getName() + "大");
        }else if(compare < 0){
            System.out.println(girl2.getName() + "大");
        }else{
            System.out.println("一样大");
        }
​
​
    }
}
​

9 继承(Inheritance)

9.1 继承的概述

9.1.1 生活中的继承

  1. 财产继承:当一个人去世时,其财产(如房产、土地、金钱、股票等)通常会按照法律规定或遗嘱的指示分配给其继承人。这种继承方式有助于确保财产在家族内部传承。

  2. 知识继承:长辈向年轻一代传授知识、技能和经验,使他们能够在社会中更好地生存和发展。这种继承方式包括家庭教育、师徒制度等。

9.1.2 Java中的继承

  1. 单继承:Java 中的继承是单继承,即一个子类只能继承一个父类。但是,可以通过多层继承实现类似多继承的效果。

  2. 继承关系:子类通过关键字 extends 继承父类。继承关系是从上到下的,即子类位于继承层次的下层,父类位于上层。

  3. 访问控制:子类可以访问父类的公共(public)和受保护(protected)属性和方法。对于私有(private)属性和方法,子类无法直接访问,但可以通过公共或受保护的getter和setter方法间接访问。

  4. 方法覆盖(Override):子类可以覆盖父类的方法,以实现不同的功能。覆盖方法时,子类方法的返回类型、方法名、参数列表必须与父类方法相同,且子类方法的访问权限不能低于父类方法。

  5. 构造方法:子类可以通过 super 关键字调用父类的构造方法。如果子类没有显式地定义构造方法,编译器会自动生成一个默认构造方法,该方法会调用父类的无参构造方法。

  6. 多态:继承允许子类对象可以被当作父类对象使用,即子类对象可以赋值给父类引用变量。这种特性称为多态,它使得程序更具灵活性和可扩展性。

9.1.3 继承的好处

  • 继承的出现减少了代码冗余,提高了代码的复用性。

  • 继承的出现,更有利于功能的扩展。

  • 继承的出现让类与类之间产生了is-a的关系,为多态的使用提供了前提。

    • 继承描述事物之间的所属关系,这种关系是:is-a 的关系。可见,父类更通用、更一般,子类更具体。

注意:不要仅为了获取其他类中某个功能而去继承!

9.2 继承的语法

9.2.1 继承中的语法格式

通过 extends 关键字,可以声明一个类B继承另外一个类A,定义格式如下:

[修饰符] class 类A {
    ...
}
​
[修饰符] class 类B extends 类A {
    ...
}
​

9.2.2 继承中的基本概念

类B,称为子类、派生类(derived class)、SubClass

类A,称为父类、超类、基类(base class)、SuperClass

9.3 代码举例

1、父类

/*
 * 定义动物类Animal,做为父类
 */
public class Animal {
    // 定义name属性
    String name;
    // 定义age属性
    int age;
​
    // 定义动物的吃东西方法
    public void eat() {
        System.out.println(age + "岁的"
                + name + "在吃东西");
    }
}
​

2、子类

/*
 * 定义猫类Cat 继承 动物类Animal
 */
public class Cat extends Animal {
    int count;//记录每只猫抓的老鼠数量
​
    // 定义一个猫抓老鼠的方法catchMouse
    public void catchMouse() {
        count++;
        System.out.println("抓老鼠,已经抓了"
                + count + "只老鼠");
    }
}

3、测试类

public class TestCat {
    public static void main(String[] args) {
        // 创建一个猫类对象
        Cat cat = new Cat();
        // 为该猫类对象的name属性进行赋值
        cat.name = "Tom";
        // 为该猫类对象的age属性进行赋值
        cat.age = 2;
        // 调用该猫继承来的eat()方法
        cat.eat();
        // 调用该猫的catchMouse()方法
        cat.catchMouse();
        cat.catchMouse();
        cat.catchMouse();
    }
}

9.4 继承性的细节说明

1、子类会继承父类所有的实例变量和实例方法

从类的定义来看,类是一类具有相同特性的事物的抽象描述。父类是所有子类共同特征的抽象描述。而实例变量和实例方法就是事物的特征,那么父类中声明的实例变量和实例方法代表子类事物也有这个特征。

  • 当子类对象被创建时,在堆中给对象申请内存时,就要看子类和父类都声明了什么实例变量,这些实例变量都要分配内存。

  • 当子类对象调用方法时,编译器会先在子类模板中看该类是否有这个方法,如果没找到,会看它的父类甚至父类的父类是否声明了这个方法,遵循从下往上找的顺序,找到了就停止,一直到根父类都没有找到,就会报编译错误。

所以继承意味着子类的对象除了看子类的类模板还要看父类的类模板。

2、子类不能直接访问父类中私有的(private)的成员变量和方法

子类虽会继承父类私有(private)的成员变量,但子类不能对继承的私有成员变量直接进行访问,可通过继承的get/set方法进行访问。

3、在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”

子类在继承父类以后,还可以定义自己特有的方法,这就可以看做是对父类功能上的扩展。

4、Java支持多层继承(继承体系)

java之面向对象插图(8)

class A{}
class B extends A{}
class C extends B{}

说明:

  • 子类和父类是一种相对的概念

  • 顶层父类是Object类。所有的类默认继承Object,作为父类。

5、一个父类可以同时拥有多个子类

class A{}
class B extends A{}
class D extends A{}
class E extends A{}

6、Java只支持单继承,不支持多重继承

java之面向对象插图(9)

public class A{}
class B extends A{}
​
//一个类只能有一个父类,不可以有多个直接父类。
class C extends B{}     //ok
class C extends A,B...  //error

9.5 练习

练习1:定义一个学生类Student,它继承自Person类

java之面向对象插图(10)

public class Person {
    //属性
    String name;
    private int age;
​
    //方法
    public void eat(){
        System.out.println("人吃饭");
    }
    public void sleep(){
        System.out.println("人睡觉");
    }
​
    public void show(){
        System.out.println("age : " + age);
    }
​
    public int getAge() {
        return age;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
}
​
public class Student extends Person{
    //属性
//    String name;
//    int age;
​
    String school;
​
    //方法
//    public void eat(){
//        System.out.println("人吃饭");
//    }
//    public void sleep(){
//        System.out.println("人睡觉");
//    }
​
    public void study(){
        System.out.println("学生学习");
    }
​
    public void show1(){
//        System.out.println("age : " + age);
        System.out.println("age : " + getAge());
​
    }
}
​

练习2:

(1)定义一个ManKind类,包括

  • 成员变量int sex和int salary;

  • 方法void manOrWoman():根据sex的值显示“man”(sex==1)或者“woman”(sex==0);

  • 方法void employeed():根据salary的值显示“no job”(salary==0)或者“ job”(salary!=0)。

(2)定义类Kids继承ManKind,并包括

  • 成员变量int yearsOld;

  • 方法printAge()打印yearsOld的值。

public class ManKind {
    private int sex;
    private int salary;
​
    public ManKind() {
    }
​
    public ManKind(int sex, int salary) {
        this.sex = sex;
        this.salary = salary;
    }
​
    public int getSex() {
        return sex;
    }
​
    public void setSex(int sex) {
        this.sex = sex;
    }
​
    public int getSalary() {
        return salary;
    }
​
    public void setSalary(int salary) {
        this.salary = salary;
    }
​
    public void manOrWoman(){
        if(sex == 1){
            System.out.println("Man");
        }else if(sex == 0){
            System.out.println("Woman");
        }
    }
​
    public void employeed(){
        if(salary == 0){
            System.out.println("no job!");
        }else{
            System.out.println("job!");
        }
    }
}
​
public class Kids extends ManKind{
    private int yearsOld;
​
    public Kids(){
​
    }
​
    public Kids(int yearsOld){
        this.yearsOld = yearsOld;
    }
​
    public Kids(int sex, int salary,int yearsOld){
        this.yearsOld = yearsOld;
        setSex(sex);
        setSalary(salary);
    }
​
​
    public int getYearsOld() {
        return yearsOld;
    }
​
    public void setYearsOld(int yearsOld) {
        this.yearsOld = yearsOld;
    }
​
    public void printAge(){
        System.out.println("I am " + yearsOld + " years old.");
    }
}
public class KidsTest {
    public static void main(String[] args) {
​
        Kids kid = new Kids();
​
        kid.setSex(1);
        kid.setSalary(100);
        kid.setYearsOld(12);
​
        //来自于父类中声明的方法
        kid.employeed();
        kid.manOrWoman();
​
        //Kids类自己声明的方法
        kid.printAge();
    }
}
​

(3)定义类KidsTest,在类的main方法中实例化Kids的对象someKid,用该对象访问其父类的成员变量及方法。

练习3:根据下图实现类。在CylinderTest类中创建Cylinder类的对象,设置圆柱的底面半径和高,并输出圆柱的体积。

java之面向对象插图(11)

public class Circle {
    private double radius;//半径
​
    public Circle(){
        this.radius = 1;
    }
​
    public double getRadius() {
        return radius;
    }
​
    public void setRadius(double radius) {
        this.radius = radius;
    }
    //求圆的面积
    public double findArea(){
        return 3.14 * radius * radius;
    }
}
​
public class Cylinder extends Circle{
​
    private double length;//高
​
    public Cylinder(){
        length = 1;
    }
​
    public double getLength() {
        return length;
    }
​
    public void setLength(double length) {
        this.length = length;
    }
​
    //求圆柱的体积
    public double findVolume(){
//        return 3.14 * getRadius() * getRadius() * getLength();
​
        return findArea() * getLength();
    }
}
public class CylinderTest {
    public static void main(String[] args) {
​
        Cylinder cy = new Cylinder();
​
        cy.setRadius(2.3);
        cy.setLength(1.4);
​
        System.out.println("圆柱的体积为:" + cy.findVolume());
​
        System.out.println("圆柱的底面积:" + cy.findArea());
​
    }
}

10 方法的重写(override/overwrite)

父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于自己当前的类,该怎么办呢?子类可以对从父类中继承来的方法进行改造,我们称为方法的重写 (override、overwrite)。也称为方法的重置覆盖

在程序执行时,子类的方法将覆盖父类的方法。

10.1 方法重写举例

比如新的手机增加来电显示头像的功能,代码如下:

public class Phone {
    public void sendMessage(){
        System.out.println("发短信");
    }
    public void call(){
        System.out.println("打电话");
    }
    public void showNum(){
        System.out.println("来电显示号码");
    }
}
​
//SmartPhone:智能手机
public class SmartPhone extends Phone{
    //重写父类的来电显示功能的方法
    @Override
    public void showNum(){
        //来电显示姓名和图片功能
        System.out.println("显示来电姓名");
        System.out.println("显示头像");
    }
    //重写父类的通话功能的方法
    @Override
    public void call() {
        System.out.println("语音通话 或 视频通话");
    }
}
public class TestOverride {
    public static void main(String[] args) {
        // 创建子类对象
        SmartPhone sp = new SmartPhone();
​
        // 调用父类继承而来的方法
        sp.call();
​
        // 调用子类重写的方法
        sp.showNum();
    }
}

@Override使用说明:

写在方法上面,用来检测是不是满足重写方法的要求。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留,这样编译器可以帮助我们检查格式,另外也可以让阅读源代码的程序员清晰的知道这是一个重写的方法。

10.2 方法重写的要求

  1. 子类重写的方法必须和父类被重写的方法具有相同的方法名称参数列表

  2. 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型。(例如:Student < Person)。

注意:如果返回值类型是基本数据类型和void,那么必须是相同

  1. 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限。(public > protected > 缺省 > private)

注意:① 父类私有方法不能重写 ② 跨包的父类缺省的方法也不能重写

  1. 子类方法抛出的异常不能大于父类被重写方法的异常

此外,子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。

10.3 小结:方法的重载与重写

方法的重载:方法名相同,形参列表不同。不看返回值类型。

方法的重写:见上面。

(1)同一个类中

public class TestOverload {
    public int max(int a, int b){
        return a > b ? a : b;
    }
    public double max(double a, double b){
        return a > b ? a : b;
    }
    public int max(int a, int b,int c){
        return max(max(a,b),c);
    }
}

(2)父子类中

public class TestOverloadOverride {
    public static void main(String[] args) {
        Son s = new Son();
        s.method(1);//只有一个形式的method方法
​
        Daughter d = new Daughter();
        d.method(1);
        d.method(1,2);//有两个形式的method方法
    }
}
​
class Father{
    public void method(int i){
        System.out.println("Father.method");
    }
}
class Son extends Father{
    public void method(int i){//重写
        System.out.println("Son.method");
    }
}
class Daughter extends Father{
    public void method(int i,int j){//重载
        System.out.println("Daughter.method");
    }
}

10.4 练习

练习1:如果现在父类的一个方法定义成private访问权限,在子类中将此方法声明为default访问权限,那么这样还叫重写吗? (NO)

练习2:修改继承内容的练习2中定义的类Kids,在Kids中重新定义employeed()方法,覆盖父类ManKind中定义的employeed()方法,输出“Kids should study and no job.”

public class Kids extends ManKind {
    private int yearsOld;
​
    public Kids(){
​
    }
​
    public Kids(int yearsOld){
        this.yearsOld = yearsOld;
    }
​
    public Kids(int sex, int salary,int yearsOld){
        this.yearsOld = yearsOld;
        setSex(sex);
        setSalary(salary);
    }
​
​
    public int getYearsOld() {
        return yearsOld;
    }
​
    public void setYearsOld(int yearsOld) {
        this.yearsOld = yearsOld;
    }
​
    public void printAge(){
​
        System.out.println("I am " + yearsOld + " years old.");
    }
    public void employeed(){
        System.out.println("Kids should study and no job.");
    }
}
public class KidsTest {
    public static void main(String[] args) {
​
        Kids kid = new Kids();
​
        kid.setSex(1);
        kid.setSalary(100);
        kid.setYearsOld(12);
​
        //来自于父类中声明的方法
        kid.employeed();
        kid.manOrWoman();
​
        //Kids类自己声明的方法
        kid.printAge();
    }
}
public class ManKind {
    private int sex;
    private int salary;
​
    public ManKind() {
    }
​
    public ManKind(int sex, int salary) {
        this.sex = sex;
        this.salary = salary;
    }
​
    public int getSex() {
        return sex;
    }
​
    public void setSex(int sex) {
        this.sex = sex;
    }
​
    public int getSalary() {
        return salary;
    }
​
    public void setSalary(int salary) {
        this.salary = salary;
    }
​
    public void manOrWoman(){
        if(sex == 1){
            System.out.println("Man");
        }else if(sex == 0){
            System.out.println("Woman");
        }
    }
​
    public void employeed(){
        if(salary == 0){
            System.out.println("no job!");
        }else{
            System.out.println("job!");
        }
    }
}
​

11 多态性

11.1 多态的形式和体现

11.1.1 对象的多态性

多态性,是面向对象中最重要的概念,在Java中的体现:对象的多态性:父类的引用指向子类的对象

格式:(父类类型:指子类继承的父类类型,或者实现的接口类型)

父类类型 变量名 = 子类对象;

举例:

Person p = new Student();
​
Object o = new Person();//Object类型的变量o,指向Person类型的对象o = new Student(); //Object类型的变量o,指向Student类型的对象

对象的多态:在Java中,子类的对象可以替代父类的对象使用。所以,一个引用类型变量可能指向(引用)多种不同类型的对象

11.1.2 多态的理解

Java引用变量有两个类型:编译时类型运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。

  • 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)

  • 多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法) “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)

多态的使用前提:① 类的继承关系 ② 方法的重写

11.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();
    }
}

11.2 为什么需要多态性(polymorphism)?

开发中,有时我们在设计一个数组、或一个成员变量、或一个方法的形参、返回值类型时,无法确定它具体的类型,只能确定它是某个系列的类型。

案例:

(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不支持多态,那么上面的问题将会非常麻烦,代码维护起来很难,扩展性很差。
    */
}

11.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)等,应该在不修改现有代码的基础上,引入新功能

11.4 成员变量没有多态性

  • 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。

  • 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量

public class TestVariable {
    public static void main(String[] args) {
        Base b = new Sub();
        System.out.println(b.a);
        System.out.println(((Sub)b).a);
​
        Sub s = new Sub();
        System.out.println(s.a);
        System.out.println(((Base)s).a);
    }
}
class Base{
    int a = 1;
}
class Sub extends Base{
    int a = 2;
}

11.5 向上转型与向下转型

首先,一个对象在new的时候创建是哪个类型的对象,它从头至尾都不会变。即这个对象的运行时类型,本质的类型用于不会变。但是,把这个对象赋值给不同类型的变量时,这些变量的编译时类型却不同。

11.5.1 为什么要类型转换

因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间,就会出现类型转换的现象。

但是,使用父类变量接收了子类对象之后,我们就不能调用子类拥有,而父类没有的方法了。这也是多态给我们带来的一点”小麻烦”。所以,想要调用子类特有的方法,必须做类型转换,使得编译通过

java之面向对象插图(12)

  • 向上转型:当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型

    • 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了

    • 但是,运行时,仍然是对象本身的类型,所以执行的方法是子类重写的方法体。

    • 此时,一定是安全的,而且也是自动完成的

  • 向下转型:当左边的变量的类型(子类)<右边对象/变量的编译时类型(父类),我们就称为向下转型

    • 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了

    • 但是,运行时,仍然是对象本身的类型

    • 不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过isInstanceof关键字进行判断

11.5.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之间是没有继承关系的
    }
}

11.5.3 instanceof关键字

为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验。如下代码格式:

//检验对象a是否是数据类型A的对象,返回值为boolean型
对象a instanceof 数据类型A 
  • 说明:

    • 只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。

    • 如果对象a属于类A的子类B,a instanceof A值也为true。

    • 要求对象a所属的类与类A必须是子类和父类的关系,否则编译错误。

代码:

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();
            }
        }
    }
}

11.6 练习

练习1:笔试&面试

题目1:继承成员变量和继承方法的区别

class Base {
    int count = 10;
    public void display() {
        System.out.println(this.count);
    }
}
​
class Sub extends Base {
    int count = 20;
    public void display() {
        System.out.println(this.count);
    }
}
​
public class FieldMethodTest {
    public static void main(String[] args){
        Sub s = new Sub();
        System.out.println(s.count);
        s.display();
        Base b = s;
        System.out.println(b == s);
        System.out.println(b.count);
        b.display();
    }
}
​

题目2:

//考查多态的笔试题目:
public class InterviewTest1 {
​
    public static void main(String[] args) {
        Base base = new Sub();
        base.add(1, 2, 3);
​
//      Sub s = (Sub)base;
//      s.add(1,2,3);
    }
}
​
class Base {
    public void add(int a, int... arr) {
        System.out.println("base");
    }
}
​
class Sub extends Base {
​
    public void add(int a, int[] arr) {
        System.out.println("sub_1");
    }
​
//  public void add(int a, int b, int c) {
//      System.out.println("sub_2");
//  }
​
}
​

题目3:

//getXxx()和setXxx()声明在哪个类中,内部操作的属性就是哪个类里的。
public class InterviewTest2 {
    public static void main(String[] args) {
        Father f = new Father();
        Son s = new Son();
        System.out.println(f.getInfo());//atguigu
        System.out.println(s.getInfo());//尚硅谷
        s.test();//尚硅谷  atguigu
        System.out.println("-----------------");
        s.setInfo("大硅谷");
        System.out.println(f.getInfo());//atguigu
        System.out.println(s.getInfo());//大硅谷
        s.test();//大硅谷  atguigu
    }
}
​
class Father {
    private String info = "atguigu";
​
    public void setInfo(String info) {
        this.info = info;
    }
​
    public String getInfo() {
        return info;
    }
}
​
class Son extends Father {
    private String info = "尚硅谷";
    
    public void setInfo(String info) {
        this.info = info;
    }
​
    public String getInfo() {
        return info;
    }
    
    public void test() {
        System.out.println(this.getInfo());
        System.out.println(super.getInfo());
    }
}

题目4:多态是编译时行为还是运行时行为?

//证明如下:
class Animal  {
    protected void eat() {
        System.out.println("animal eat food");
    }
}
​
class Cat  extends Animal  {
    protected void eat() {
        System.out.println("cat eat fish");
    }
}
​
class Dog  extends Animal  {
    public void eat() {
        System.out.println("Dog eat bone");
    }
}
​
class Sheep  extends Animal  {
    public void eat() {
        System.out.println("Sheep eat grass");
    }
}
​
public class InterviewTest {
    public static Animal  getInstance(int key) {
        switch (key) {
        case 0:
            return new Cat ();
        case 1:
            return new Dog ();
        default:
            return new Sheep ();
        }
​
    }
​
    public static void main(String[] args) {
        int key = new Random().nextInt(3);
        System.out.println(key);
​
        Animal  animal = getInstance(key);
        animal.eat(); 
    }
}

练习2:

class Person {
    protected String name="person";
    protected int age=50;
    public String getInfo() {
              return "Name: "+ name + "
" +"age: "+ age;
    }
}
class Student extends Person {
    protected String school="pku";
    public String getInfo() {
              return  "Name: "+ name + "
age: "+ age 
              + "
school: "+ school;
    }   
}
class Graduate extends Student{
    public String major="IT";
    public String getInfo()
    {
        return  "Name: "+ name + "
age: "+ age 
              + "
school: "+ school+"
major:"+major;
    }
}
​

12 关键字:super

12.1 super的理解

在Java类中使用super来调用父类中的指定操作:

  • super可用于访问父类中定义的属性

  • super可用于调用父类中定义的成员方法

  • super可用于在子类构造器中调用父类的构造器

注意:

  • 尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员

  • super的追溯不仅限于直接父类

  • super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识

12.2 super的使用场景

12.2.1 子类中调用父类被重写的方法

  • 如果子类没有重写父类的方法,只要权限修饰符允许,在子类中完全可以直接调用父类的方法;

  • 如果子类重写了父类的方法,在子类中需要通过super.才能调用父类被重写的方法,否则默认调用的子类重写的方法

举例:

public class Phone {
    public void sendMessage(){
        System.out.println("发短信");
    }
    public void call(){
        System.out.println("打电话");
    }
    public void showNum(){
        System.out.println("来电显示号码");
    }
}
​
//smartphone:智能手机
public class SmartPhone extends Phone{
    //重写父类的来电显示功能的方法
    public void showNum(){
        //来电显示姓名和图片功能
        System.out.println("显示来电姓名");
        System.out.println("显示头像");
​
        //保留父类来电显示号码的功能
        super.showNum();//此处必须加super.,否则就是无限递归,那么就会栈内存溢出
    }
}

总结:

  • 方法前面没有super.和this.

    • 先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯

  • 方法前面有this.

    • 先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯

  • 方法前面有super.

    • 从当前子类的直接父类找,如果没有,继续往上追溯

12.2.2 子类中调用父类中同名的成员变量

  • 如果实例变量与局部变量重名,可以在实例变量前面加this.进行区别

  • 如果子类实例变量和父类实例变量重名,并且父类的该实例变量在子类仍然可见,在子类中要访问父类声明的实例变量需要在父类实例变量前加super.,否则默认访问的是子类自己声明的实例变量

  • 如果父子类实例变量没有重名,只要权限修饰符允许,在子类中完全可以直接访问父类中声明的实例变量,也可以用this.实例访问,也可以用super.实例变量访问

举例:

class Father{
    int a = 10;
    int b = 11;
}
class Son extends Father{
    int a = 20;
    
    public void test(){
        //子类与父类的属性同名,子类对象中就有两个a
        System.out.println("子类的a:" + a);//20  先找局部变量找,没有再从本类成员变量找
        System.out.println("子类的a:" + this.a);//20   先从本类成员变量找
        System.out.println("父类的a:" + super.a);//10    直接从父类成员变量找
        
        //子类与父类的属性不同名,是同一个b
        System.out.println("b = " + b);//11  先找局部变量找,没有再从本类成员变量找,没有再从父类找
        System.out.println("b = " + this.b);//11   先从本类成员变量找,没有再从父类找
        System.out.println("b = " + super.b);//11  直接从父类局部变量找
    }
    
    public void method(int a, int b){
        //子类与父类的属性同名,子类对象中就有两个成员变量a,此时方法中还有一个局部变量a      
        System.out.println("局部变量的a:" + a);//30  先找局部变量
        System.out.println("子类的a:" + this.a);//20  先从本类成员变量找
        System.out.println("父类的a:" + super.a);//10  直接从父类成员变量找
​
        System.out.println("b = " + b);//13  先找局部变量
        System.out.println("b = " + this.b);//11  先从本类成员变量找
        System.out.println("b = " + super.b);//11  直接从父类局部变量找
    }
}
class Test{
    public static void main(String[] args){
        Son son = new Son();
        son.test();
        son.method(30,13);  
    }
}

总结:起点不同(就近原则)

  • 变量前面没有super.和this.

    • 在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的局部变量

    • 如果不是局部变量,先从当前执行代码的本类去找成员变量

    • 如果从当前执行代码的本类中没有找到,会往上找父类声明的成员变量(权限修饰符允许在子类中访问的)

  • 变量前面有this.

    • 通过this找成员变量时,先从当前执行代码的==本类去找成员变量==

    • 如果从当前执行代码的本类中没有找到,会往上找==父类声明的成员变量(==权限修饰符允许在子类中访问的)

  • 变量前面super.

    • 通过super找成员变量,直接从当前执行代码的直接父类去找成员变量(权限修饰符允许在子类中访问的)

    • 如果直接父类没有,就去父类的父类中找(权限修饰符允许在子类中访问的)

特别说明:应该避免子类声明和父类重名的成员变量

在阿里的开发规范等文档中都做出明确说明:

java之面向对象插图(13)

12.2.3 子类构造器中调用父类构造器

① 子类继承父类时,不会继承父类的构造器。只能通过“super(形参列表)”的方式调用父类指定的构造器。

② 规定:“super(形参列表)”,必须声明在构造器的首行。

③ 我们前面讲过,在构造器的首行可以使用”this(形参列表)”,调用本类中重载的构造器, 结合②,结论:在构造器的首行,”this(形参列表)” 和 “super(形参列表)”只能二选一。

④ 如果在子类构造器的首行既没有显示调用”this(形参列表)”,也没有显式调用”super(形参列表)”, ​ 则子类此构造器默认调用”super()”,即调用父类中空参的构造器。

⑤ 由③和④得到结论:子类的任何一个构造器中,要么会调用本类中重载的构造器,要么会调用父类的构造器。 只能是这两种情况之一。

⑥ 由⑤得到:一个类中声明有n个构造器,最多有n-1个构造器中使用了”this(形参列表)”,则剩下的那个一定使用”super(形参列表)”。

开发中常见错误:

如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有空参的构造器,则编译出错

情景举例1:

class A{
​
}
class B extends A{
​
}
​
class Test{
    public static void main(String[] args){
        B b = new B();
        //A类和B类都是默认有一个无参构造,B类的默认无参构造中还会默认调用A类的默认无参构造
        //但是因为都是默认的,没有打印语句,看不出来
    }
}

情景举例2:

class A{
    A(){
        System.out.println("A类无参构造器");
    }
}
class B extends A{
​
}
class Test{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个无参构造,
        //B类默认有一个无参构造,
        //B类的默认无参构造中会默认调用A类的无参构造
        //可以看到会输出“A类无参构造器"
    }
}

情景举例3:

class A{
    A(){
        System.out.println("A类无参构造器");
    }
}
class B extends A{
    B(){
        System.out.println("B类无参构造器");
    }
}
class Test{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个无参构造,
        //B类显示声明一个无参构造,        
        //B类的无参构造中虽然没有写super(),但是仍然会默认调用A类的无参构造
        //可以看到会输出“A类无参构造器"和"B类无参构造器")
    }
}

情景举例4:

class A{
    A(){
        System.out.println("A类无参构造器");
    }
}
class B extends A{
    B(){
        super();
        System.out.println("B类无参构造器");
    }
}
class Test{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个无参构造,
        //B类显示声明一个无参构造,        
        //B类的无参构造中明确写了super(),表示调用A类的无参构造
        //可以看到会输出“A类无参构造器"和"B类无参构造器")
    }
}

情景举例5:

class A{
    A(int a){
        System.out.println("A类有参构造器");
    }
}
class B extends A{
    B(){
        System.out.println("B类无参构造器");
    }
}
class Test05{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
        //B类显示声明一个无参构造,        
        //B类的无参构造没有写super(...),表示默认调用A类的无参构造
        //编译报错,因为A类没有无参构造
    }
}

java之面向对象插图(14)

情景举例6:

class A{
    A(int a){
        System.out.println("A类有参构造器");
    }
}
class B extends A{
    B(){
        super();
        System.out.println("B类无参构造器");
    }
}
class Test06{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
        //B类显示声明一个无参构造,        
        //B类的无参构造明确写super(),表示调用A类的无参构造
        //编译报错,因为A类没有无参构造
    }
}

java之面向对象插图(15)

情景举例7:

class A{
    A(int a){
        System.out.println("A类有参构造器");
    }
}
class B extends A{
    B(int a){
        super(a);
        System.out.println("B类有参构造器");
    }
}
class Test07{
    public static void main(String[] args){
        B b = new B(10);
        //A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
        //B类显示声明一个有参构造,        
        //B类的有参构造明确写super(a),表示调用A类的有参构造
        //会打印“A类有参构造器"和"B类有参构造器"
    }
}

情景举例8:

class A{
    A(){
        System.out.println("A类无参构造器");
    }
    A(int a){
        System.out.println("A类有参构造器");
    }
}
class B extends A{
    B(){
        super();//可以省略,调用父类的无参构造
        System.out.println("B类无参构造器");
    }
    B(int a){
        super(a);//调用父类有参构造
        System.out.println("B类有参构造器");
    }
}
class Test8{
    public static void main(String[] args){
        B b1 = new B();
        B b2 = new B(10);
    }
}

12.3 小结:this与super

1、this和super的意义

this:当前对象

  • 在构造器和非静态代码块中,表示正在new的对象

  • 在实例方法中,表示调用当前方法的对象

super:引用父类声明的成员

2、this和super的使用格式

  • this

    • this.成员变量:表示当前对象的某个成员变量,而不是局部变量

    • this.成员方法:表示当前对象的某个成员方法,完全可以省略this.

    • this()或this(实参列表):调用另一个构造器协助当前对象的实例化,只能在构造器首行,只会找本类的构造器,找不到就报错

  • super

    • super.成员变量:表示当前对象的某个成员变量,该成员变量在父类中声明的

    • super.成员方法:表示当前对象的某个成员方法,该成员方法在父类中声明的

    • super()或super(实参列表):调用父类的构造器协助当前对象的实例化,只能在构造器首行,只会找直接父类的对应构造器,找不到就报错

12.4 练习

练习1:修改方法重写的练习2中定义的类Kids中employeed()方法,在该方法中调用父类ManKind的employeed()方法,然后再输出“but Kids should study and no job.”

public class Kids extends ManKind {
    private int yearsOld;
​
    public Kids(){
​
    }
​
    public Kids(int yearsOld){
        this.yearsOld = yearsOld;
    }
​
    public Kids(int sex, int salary,int yearsOld){
        this.yearsOld = yearsOld;
        setSex(sex);
        setSalary(salary);
    }
​
​
    public int getYearsOld() {
        return yearsOld;
    }
​
    public void setYearsOld(int yearsOld) {
        this.yearsOld = yearsOld;
    }
​
    public void printAge(){
​
        System.out.println("I am " + yearsOld + " years old.");
    }
    public void employeed(){
        super.employeed();
        System.out.println("but Kids should study and no job.");
    }
}
​
public class ManKind {
    private int sex;
    private int salary;
​
    public ManKind() {
    }
​
    public ManKind(int sex, int salary) {
        this.sex = sex;
        this.salary = salary;
    }
​
    public int getSex() {
        return sex;
    }
​
    public void setSex(int sex) {
        this.sex = sex;
    }
​
    public int getSalary() {
        return salary;
    }
​
    public void setSalary(int salary) {
        this.salary = salary;
    }
​
    public void manOrWoman(){
        if(sex == 1){
            System.out.println("Man");
        }else if(sex == 0){
            System.out.println("Woman");
        }
    }
​
    public void employeed(){
        if(salary == 0){
            System.out.println("no job!");
        }else{
            System.out.println("job!");
        }
    }
}
public class KidsTest {
    public static void main(String[] args) {
​
        Kids kid = new Kids();
​
//        kid.setSex(1);
//        kid.setSalary(100);
//        kid.setYearsOld(12);
//
//        //来自于父类中声明的方法
//        kid.employeed();
//        kid.manOrWoman();
//
//        //Kids类自己声明的方法
//        kid.printAge();
//
        //
        System.out.println("**********");
        kid.employeed();
    }
}

练习2:修改继承中的练习3中定义的Cylinder类,在Cylinder类中覆盖findArea()方法,计算圆柱的表面积。考虑:findVolume方法怎样做相应的修改?

在CylinderTest类中创建Cylinder类的对象,设置圆柱的底面半径和高,并输出圆柱的表面积和体积。

附加题:在CylinderTest类中创建一个Circle类的对象,设置圆的半径,计算输出圆的面积。体会父类和子类成员的分别调用。

public class Cylinder extends Circle {
​
    private double length;//高
​
    public Cylinder(){
        length = 1;
    }
​
    public double getLength() {
        return length;
    }
​
    public void setLength(double length) {
        this.length = length;
    }
​
    //求圆柱的体积
    public double findVolume(){
//        return 3.14 * getRadius() * getRadius() * getLength();
        //错误的
        return super.findArea() * getLength();
    }
​
    //求表面积
    public double findArea(){
        return 3.14 * getRadius() * getRadius() * 2 +
                2 * 3.14 * getRadius() * getLength();
    }
}
​
public class CylinderTest {
    public static void main(String[] args) {
​
        Cylinder cy = new Cylinder();
​
        cy.setRadius(2.3);
        cy.setLength(1.4);
​
        System.out.println("圆柱的体积为:" + cy.findVolume());
​
        System.out.println("圆柱的表面积:" + cy.findArea());
​
    }
}
​
public class Circle {
    private double radius;//半径
​
    public Circle(){
        this.radius = 1;
    }
​
    public double getRadius() {
        return radius;
    }
​
    public void setRadius(double radius) {
        this.radius = radius;
    }
    //求圆的面积
    public double findArea(){
        return 3.14 * radius * radius;
    }
}

练习3:

1、写一个名为Account的类模拟账户。该类的属性和方法如下图所示。该类包括的属性:账号id,余额balance,年利率annualInterestRate;包含的方法:访问器方法(getter和setter方法),返回月利率的方法getMonthlyInterest(),取款方法withdraw(),存款方法deposit()。

java之面向对象插图(16)

写一个用户程序测试Account类。在用户程序中,创建一个账号为1122、余额为20000、年利率4.5%的Account对象。使用withdraw方法提款30000元,并打印余额。 再使用withdraw方法提款2500元,使用deposit方法存款3000元,然后打印余额和月利率。

提示:在提款方法withdraw中,需要判断用户余额是否能够满足提款数额的要求,如果不能,应给出提示。 运行结果如图所示:


public class Account {
    private int id;
    private double balance;
    private double annualInterestRate;//年利率
​
//    public Account(){}
​
    public Account(int id, double balance, double annualInterestRate) {
//        super();
        this.id = id;
        this.balance = balance;
        this.annualInterestRate = annualInterestRate;
    }
​
    public int getId() {
        return id;
    }
​
    public void setId(int id) {
        this.id = id;
    }
​
    public double getBalance() {
        return balance;
    }
​
//    public void setBalance(double balance) {
//        this.balance = balance;
//    }
​
    public double getAnnualInterestRate() {
        return annualInterestRate;
    }
​
    public void setAnnualInterestRate(double annualInterestRate) {
        this.annualInterestRate = annualInterestRate;
    }
​
    /**
     * 获取月利率
     * @return
     */
    public double getMonthlyInterest(){
        return annualInterestRate / 12;
    }
​
    /**
     * 取钱操作
     * @param amount  要取的钱数
     */
    public void withdraw(double amount){
        if(balance >= amount){
            balance -= amount;
        }else{
            System.out.println("余额不足!");
        }
    }
​
    /**
     * 存钱操作
     * @param amount  要存的额度
     */
    public void deposit(double amount){
        if(amount > 0){
            balance += amount;
        }
    }
}
​
public class AccountTest {
    public static void main(String[] args) {
​
        Account acct = new Account(1122,20000,0.045);
​
        acct.withdraw(30000);
        System.out.println("您的账户余额为:" + acct.getBalance());
​
        acct.withdraw(2500);
        acct.deposit(3000);
        System.out.println("您的账户余额为:" + acct.getBalance());
​
        System.out.println("月利率为:" + acct.getMonthlyInterest());
​
   

2、创建Account类的一个子类CheckAccount代表可透支的账户,该账户中定义一个属性overdraft代表可透支限额。在CheckAccount类中重写withdraw方法,其算法如下:

如果(取款金额账户余额),
    计算需要透支的额度
    判断可透支额overdraft是否足够支付本次透支需要,如果可以
        将账户余额修改为0,冲减可透支金额
    如果不可以
        提示用户超过可透支额的限额

要求:写一个用户程序测试CheckAccount类。在用户程序中,创建一个账号为1122、余额为20000、年利率4.5%,可透支限额为5000元的CheckAccount对象。

使用withdraw方法提款5000元,并打印账户余额和可透支额。

再使用withdraw方法提款18000元,并打印账户余额和可透支额。

再使用withdraw方法提款3000元,并打印账户余额和可透支额。

提示:

(1)子类CheckAccount的构造方法需要将从父类继承的3个属性和子类自己的属性全部初始化。

(2)父类Account的属性balance被设置为private,但在子类CheckAccount的withdraw方法中需要修改它的值,因此应修改父类的balance属性,定义其为protected。

运行结果如下图所示:


public class CheckAccount extends Account{
    private double overdraft;//可透支限额
​
    public CheckAccount(int id, double balance, double annualInterestRate){
        super(id,balance,annualInterestRate);
    }
​
    public CheckAccount(int id, double balance, double annualInterestRate,double overdraft){
        super(id,balance,annualInterestRate);
        this.overdraft = overdraft;
    }
​
    public double getOverdraft() {
        return overdraft;
    }
​
    public void setOverdraft(double overdraft) {
        this.overdraft = overdraft;
    }
​
    /**
     * 针对于可透支的账户的取钱的操作
     * @param amount  要取的钱数
     */
    public void withdraw(double amount){
        if(getBalance() >= amount){
            //错误的:
//            getBalance() = getBalance() - amount;
            //正确的
            super.withdraw(amount);
        }else if(getBalance() + overdraft >= amount){
            overdraft -= amount - getBalance();
            super.withdraw(getBalance());
        }else{
            System.out.println("超过可透支限额");
        }
    }
}
​

13 关键字:static

回顾类中的实例变量(即非static的成员变量)

class Circle{
    private double radius;
    public Circle(double radius){
        this.radius=radius;
    }
    public double findArea(){
        return Math.PI*radius*radius;
    }
}

创建两个Circle对象:

Circle c1=new Circle(2.0);  //c1.radius=2.0
Circle c2=new Circle(3.0);  //c2.radius=3.0

Circle类中的变量radius是一个实例变量(instance variable),它属于类的每一个对象,c1中的radius变化不会影响c2的radius,反之亦然。

如果想让一个成员变量被类的所有实例所共享,就用static修饰即可,称为类变量(或类属性)!

13.1 类属性、类方法的设计思想

当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份。例如,所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。

java之面向对象插图(17)

此外,在类中声明的实例方法,在类的外面必须要先创建对象,才能调用。但是有些方法的调用者和当前类的对象无关,这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。

这里的类变量、类方法,只需要使用static修饰即可。所以也称为静态变量、静态方法。

13.2 static关键字

  • 使用范围:

    • 在Java类中,可用static修饰属性、方法、代码块、内部类

  • 被修饰后的成员具备以下特点:

    • 随着类的加载而加载

    • 优先于对象存在

    • 修饰的成员,被所有对象所共享

    • 访问权限允许时,可不创建对象,直接被类调用

13.3 静态变量

13.3.1 语法格式

使用static修饰的成员变量就是静态变量(或类变量、类属性)

[修饰符] class {
    [其他修饰符] static 数据类型 变量名;
}

13.3.2 静态变量的特点

  • 静态变量的默认值规则和实例变量一样。

  • 静态变量值是所有对象共享。

  • 静态变量在本类中,可以在任意方法、代码块、构造器中直接使用。

  • 如果权限修饰符允许,在其他类中可以通过“类名.静态变量”直接访问,也可以通过“对象.静态变量”的方式访问(但是更推荐使用类名.静态变量的方式)。

  • 静态变量的get/set方法也静态的,当局部变量与静态变量重名时,使用“类名.静态变量”进行区分。

13.3.3 举例

举例1:

class Chinese{
    //实例变量
    String name;
    int age;
    //类变量
    static String nation;//国籍
​
    public Chinese() {
    }
​
    public Chinese(String name, int age) {
        this.name = name;
        this.age = age;
    }
​
    @Override
    public String toString() {
        return "Chinese{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", nation='" + nation + '\'' +
                '}';
    }
}
public class StaticTest {
    public static void main(String[] args) {
        Chinese c1 = new Chinese("康师傅",36);
        c1.nation = "中华人民共和国";
​
        Chinese c2 = new Chinese("老干妈",66);
​
        System.out.println(c1);
        System.out.println(c2);
​
        System.out.println(Chinese.nation);
    }
}

举例2:

​
​
public class Employee {
    private static int total;//这里私有化,在类的外面必须使用get/set方法的方式来访问静态变量
    static String company; //这里缺省权限修饰符,是为了方便类外以“类名.静态变量”的方式访问
    private int id;
    private String name;
​
    public Employee() {
        total++;
        id = total;//这里使用total静态变量的值为id属性赋值
    }
​
    public Employee(String name) {
        this();
        this.name = name;
    }
​
    public void setId(int id) {
        this.id = id;
    }
​
    public int getId() {
        return id;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public static int getTotal() {
        return total;
    }
​
    public static void setTotal(int total) {
        Employee.total = total;
    }
​
    @Override
    public String toString() {
        return "Employee{company = " + company + ",id = " + id + " ,name=" + name +"}";
    }
}
public class TestStaticVariable {
    public static void main(String[] args) {
        //静态变量total的默认值是0
        System.out.println("Employee.total = " + Employee.getTotal());
​
        Employee e1 = new Employee("张三");
        Employee e2 = new Employee("李四");
        System.out.println(e1);//静态变量company的默认值是null
        System.out.println(e2);//静态变量company的默认值是null
        System.out.println("Employee.total = " + Employee.getTotal());//静态变量total值是2
​
        Employee.company = "尚硅谷";
        System.out.println(e1);//静态变量company的值是尚硅谷
        System.out.println(e2);//静态变量company的值是尚硅谷
​
        //只要权限修饰符允许,虽然不推荐,但是也可以通过“对象.静态变量”的形式来访问
        e1.company = "超级尚硅谷";
​
        System.out.println(e1);//静态变量company的值是超级尚硅谷
        System.out.println(e2);//静态变量company的值是超级尚硅谷
    }
}

13.4 静态方法

13.4.1 语法格式

用static修饰的成员方法就是静态方法。

[修饰符] class {
    [其他修饰符] static 返回值类型 方法名(形参列表){
        方法体
    }
}

13.4.2 静态方法的特点

  • 静态方法在本类的任意方法、代码块、构造器中都可以直接被调用。

  • 只要权限修饰符允许,静态方法在其他类中可以通过“类名.静态方法“的方式调用。也可以通过”对象.静态方法“的方式调用(但是更推荐使用类名.静态方法的方式)。

  • 在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构。

  • 静态方法可以被子类继承,但不能被子类重写。

  • 静态方法的调用都只看编译时类型。

  • 因为不需要实例就可以访问static方法,因此static方法内部不能有this,也不能有super。如果有重名问题,使用“类名.”进行区别。

13.4.3 举例

public class Father {
    public static void method(){
        System.out.println("Father.method");
    }
​
    public static void fun(){
        System.out.println("Father.fun");
    }
}
package com.atguigu.keyword;
​
public class Son extends Father{
//    @Override //尝试重写静态方法,加上@Override编译报错,去掉Override不报错,但是也不是重写
    public static void fun(){
        System.out.println("Son.fun");
    }
}
public class TestStaticMethod {
    public static void main(String[] args) {
        Father.method();
        Son.method();//继承静态方法
​
        Father f = new Son();
        f.method();//执行Father类中的method
    }
}

13.5 练习

笔试题:如下程序执行会不会报错

public class StaticTest {
    public static void main(String[] args) {
        Demo test = null;
        test.hello();
    }
}
​
class Demo{
    public static void hello(){
        System.out.println("hello!");
    }
}

练习:

编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的方法。账号要自动生成。

编写主类,使用银行账户类,输入、输出3个储户的上述信息。

考虑:哪些属性可以设计成static属性。

public class Account {
​
    private int id; //账号
​
    private String password;//密码
​
    private double balance; //余额
​
    private static double interestRate;//利率
​
    private static double minBalance = 1.0;//最小余额
​
    private static int init = 1001;//用于自动生成id的基数
​
    public Account() {
        this.id = init;
        init++;
        password = "000000";
    }
​
    public Account(String password, double balance) {
        this.password = password;
        this.balance = balance;
        this.id = init;
        init++;
    }
​
    public String getPassword() {
        return password;
    }
​
    public void setPassword(String password) {
        this.password = password;
    }
​
    public double getBalance() {
        return balance;
    }
​
    public void setBalance(double balance) {
        this.balance = balance;
    }
​
    public static double getInterestRate() {
        return interestRate;
    }
​
    public static void setInterestRate(double interestRate) {
        Account.interestRate = interestRate;
    }
​
    public static double getMinBalance() {
        return minBalance;
    }
​
    public static void setMinBalance(double minBalance) {
        Account.minBalance = minBalance;
    }
​
    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", password='" + password + '\'' +
                ", balance=" + balance +
                '}';
    }
}
public class AccountTest {
    public static void main(String[] args) {
​
        Account acct1 = new Account();
        System.out.println(acct1);
​
        Account acct2 = new Account("123456",2000);
        System.out.println(acct2);
​
        Account.setInterestRate(0.0123);
        Account.setMinBalance(10);
​
        System.out.println("银行存款的利率为:" + Account.getInterestRate());
        System.out.println("银行最小存款额度为:" + Account.getMinBalance());
    }
}
​

14 final关键字

14.1 final的意义

final:最终的,不可更改的

14.2 final的使用

14.2.1 final修饰类

表示这个类不能被继承,没有子类。提高安全性,提高程序的可读性。

例如:String类、System类、StringBuffer类

final class Eunuch{//太监类
    
}
class Son extends Eunuch{//错误
    
}

14.2.2 final修饰方法

表示这个方法不能被子类重写。

例如:Object类中的getClass()

class Father{
    public final void method(){
        System.out.println("father");
    }
}
class Son extends Father{
    public void method(){//错误
        System.out.println("son");
    }
}

14.2.3 final修饰变量

final修饰某个变量(成员变量或局部变量),一旦赋值,它的值就不能被修改,即常量,常量名建议使用大写字母。

例如:final double MY_PI = 3.14;

如果某个成员变量用final修饰后,没有set方法,并且必须初始化(可以显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)

  • 修饰成员变量

public final class Test {
    public static int totalNumber = 5;
    public final int ID;
​
    public Test() {
        ID = ++totalNumber; // 可在构造器中给final修饰的“变量”赋值
    }
    public static void main(String[] args) {
        Test t = new Test();
        System.out.println(t.ID);
    }
}
​
  • 修饰局部变量:

public class TestFinal {
    public static void main(String[] args){
        final int MIN_SCORE ;
        MIN_SCORE = 0;
        final int MAX_SCORE = 100;
        MAX_SCORE = 200; //非法
    }
}
  • 错误演示:

class A {
    private final String INFO = "atguigu";  //声明常量
​
    public void print() {
        //The final field A.INFO cannot be  assigned
        //INFO = "尚硅谷"; 
    }
}
​

14.3 笔试题

题1:排错

public class Something {
    public int addOne(final int x) {
        return ++x;
        // return x + 1;
    }
}

题2:排错

public class Something {
    public static void main(String[] args) {
        Other o = new Other();
        new Something().addOne(o);
    }
    public void addOne(final Other o) {
        // o = new Other();
        o.i++;
    }
}
class Other {
    public int i;
}
​

本站无任何商业行为
个人在线分享 » java之面向对象
E-->