Java SE 4th day:Object-oriented 03

(材料源于网络)

Java SE 4th day:Object-oriented 03

1、本次课程知识点

1、static关键字的作用;

2、代码块的作用;

3、内部类的使用,并且使用内部类再次修改链表操作

2、具体内容

2.1 static关键字(重点

在之前定义方法的时候都是使用了:public static开头;

后来到了类之中后定义方法使用:public开头,不再使用static了。

在java之中使用static可以完成两个操作:一个是定义属性,另外一个是定义方法。

2.1.1 static定义属性

每一个对象都要保存自己的属性信息,即:每个对象的属性都是自己所拥有的,但是如果现在假设有如下一道程序。

范例:定义一个表示北京人的操作类,所以人的城市都属于北京。

class Person {

    private String name;

    private int age;

    String city = "北京";

 

    // 按照简单java类的要求,应该提供无参,但是本程序为了简便不再写与本程序无关的代码

    public Person(String name, int age) {

       this.name = name;

       this.age = age;

    }

 

    public String getPersonInfo() {

       return "姓名: " + this.name + ",年龄: " + this.age + ",城市: " + this.city;

    }

}

 

public class Demo {

    public static void main(String args[]) {

       Person per1 = new Person("张三", 20);

       Person per2 = new Person("李四", 25);

       Person per3 = new Person("王五", 30);

       System.out.println(per1.getPersonInfo());

       System.out.println(per2.getPersonInfo());

       System.out.println(per3.getPersonInfo());

    }

}

姓名: 张三,年龄: 20,城市: 北京

姓名: 李四,年龄: 25,城市: 北京

姓名: 王五,年龄: 30,城市: 北京

 

如果说现在的city信息要进行修改,由于每个实例化对象都要各自占着自己的内存空间,所以肯定要修改所以的city属性信息,但是如果说现在已经产生了1000w个Person对象,那么这个工作量肯定很大,而且所以的Person对象的city属性既然都一样,这样分开保存也浪费空间,那么这个时候就希望可以将city定义成一个公共的属性,每一个对象都拥有,而且可以单独的在一块空间内保存的,完成此功能就需要static关键字的支持了。

class Person {

    private String name;

    private int age;

    staticString city = "北京";

 

    // 按照简单java类的要求,应该提供无参,但是本程序为了简便不再写与本程序无关的代码

    public Person(String name, int age) {

       this.name = name;

       this.age = age;

    }

 

    public String getPersonInfo() {

       return "姓名: " + this.name + ",年龄: " + this.age + ",城市: " + this.city;

    }

}

 

public class Demo {

    public static void main(String args[]) {

       Person per1 = new Person("张三", 20);

       Person per2 = new Person("李四", 25);

       Person per3 = new Person("王五", 30);

       System.out.println(per1.getPersonInfo());

       System.out.println(per2.getPersonInfo());

       System.out.println(per3.getPersonInfo());

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

       per1.city = "北平";   // 只修改了一个对象的city属性

       System.out.println(per1.getPersonInfo());

       System.out.println(per2.getPersonInfo());

       System.out.println(per3.getPersonInfo());

    }

}  

姓名: 张三,年龄: 20,城市: 北京

姓名: 李四,年龄: 25,城市: 北京

姓名: 王五,年龄: 30,城市: 北京

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

姓名: 张三,年龄: 20,城市: 北平

姓名: 李四,年龄: 25,城市: 北平

姓名: 王五,年龄: 30,城市: 北平

一个对象修改了city属性之后,发现其他的都变化了,所以此时的内存关系图如下:

 

通过程序和内存关系图可以发现,使用static定义的属性将成为公共属性,每一个对象都拥有它,一个对象修改了内容,其他所有的对象都要修改,可是这样的代码也不合适,既然static定义的公共属性,那么公共属性由一个对象直接修改合适吗?很明显不合适,类的公共属性应该由类进行修改是最合适的,因为用户不知道一个类到底有多少个对象产生。

使用static定义的属性有时候又被成为类属性而且类属性的最大特点是可以直接通过类名称调用,而且调用的时候可以没有实例化产生的情况下进行。

类属性调用格式:

类名称.static属性

范例:使用类名称直接调用类属性

    ……

    static String city = "北京";

    ……

    public static void main(String args[]) {

       Person.city = "北平"; // 现在还没有实例化对象产生

       System.out.println(Person.city);

    }

    北平

下面可以给出一些常见的内存分配:

栈内存:保存的是一块堆内存的访问地址;

堆内存:保存的是类中普通属性;

全局数据区:保存所有的全局数据,例如:static就是全局数据;

全局代码区:保存所有的操作方法;

虽然static属性在类中定义,但是独立于类之外的产物。

2.1.2 static定义方法

使用static除了可以进行类属性的定义之外,也可以进行类方法的定义,而且定义的类方法可以由类名称直接调用。

class Person {

    private String name;

    private int age;

    static String city = "北京";

 

    public Person(String name, int age) {

       this.name = name;

       this.age = age;

    }

 

    public static void setCity(String c) {

       city = c;

    }

 

    public String getPersonInfo() {

       return "姓名: " + this.name + ",年龄: " + this.age + ",城市: " + this.city;

    }

}

 

public class Demo {

    public static void main(String args[]) {

       Person.setCity("北平"); // 现在还没有实例化对象产生

       System.out.println(Person.city);

    }

}

北平

与static属性一样,对于static方法实际上也应该是独立于类之外的产物,也可以在没有实例化对象的情况下进行调用,而且现在对于类中的方法就分为了两种:

● 非static方法:必须由实例化对象进行调用,而且使用非static方法可以调用static方法或属性;

●  static方法:可以由类名称调用,或者由实例化对象调用,而且所有的static方法不能调用非static属性或方法;

分析:为何会存在此类限制呢?

很明显对于所有的普通属性和普通方法,前提是必须有一个实例化对象产生,这样才能为属性分配堆内存空间,而当一个实例化对象产生之后,很明显可以直接调用static属性或方法,因为这些方法可以在没有实例化对象的时候就被调用。

使用static定义的方法,由于可以在没有实例化对象产生的情况下被调用,所以操作中如果调用了普通属性,而又没有实例化对象产生的时候,普通属性根本就没有堆内存开辟,所以无法调用。

而学习完了此概念之后,就可以解决下面的一个问题了,在最早的时候曾经强调过,如果现在一个方法由主方法直接调用的话,则方法的定义格式如下:

public static 返回值类型 方法名称(参数列表){

[return [返回值] ]

}

后来学习类之后,方法的定义格式又变为:

public 返回值类型 方法名称(参数列表){

[return [返回值] ]

}

范例:主方法直接调用

public class Demo {

    public static void main(String args[]) {

       print();

    }

 

    public staticvoid print() {

       System.out.println("Hello World. ");

    }

}

Hello World.

之所以由主方法直接调用的方法前必须使用static,主要的原因也是因为主方法使用了static,即:static方法可以直接调用static操作,而不能调用非static操作。

范例:调用非static操作

public class Demo {

    public static void main(String args[]) {

        new Demo().print();

    }

 

    public void print() {

       System.out.println("Hello World. ");

    }

}

Hello World.

对于普通方法肯定要由实例化对象进行调用,可是从开发而言,在主类之中,一般不建议定义其他方法,主类中最好就只有一个主方法。

2.1.3 主方法

下面解释一下主方法的组成:public static void main(String args[]):

● public:表示的是一种操作权限,指的是此方法被所有人所看见;

● static:表示方法可以有类名称直接调用,执行类:java 类名称;

● void:主方法是作为程序的起点存在,一旦开始了就没有回头路;

● main:是一个系统规定好的方法名称,执行类的时候默认调用此方法;

● String args[]:表示传递的接收参数;

范例:接收参数,接收的所有参数的类型都是String型的数据

public class Demo {

    public static void main(String args[]) {

       for (int i = 0; i < args.length; i++) {

           System.out.println(args[i]);

       }

    }

}

而参数是在运行类的时候输入的,每一个参数之间使用“”区分,例如:java Demo first second

如果参数本身包含空格,则需要使用“"”把参数引起来,例如:java Demo "hello world" "hello mldn"。

对于参数,大部分情况下不会使用的,可是主方法的组成一定要记住,包括其每一个关键字的作用,对于public是一种访问权限,以后会有更多访问权限的介绍:public > private。

2.1.4 static应用

实际上对于开发而言,static的应用主要有两点:

● 使用static属性主要用于表示公共变量的保存;

● 使用static方法一般都是在用于取得类的实例化对象的操作上,或者是一些不希望有对象的类中使用。

1、  static属性的应用

Static属性的最大特点就在于其是公共属性,那么开发之中可以利用这个属性进行实例化对象个数的统计,例如,每一个新的实例化对象都要执行构造方法,所以可以在构造方法中进行统计。

class Person {

    private static int count = 0; // 统计个数

 

    public Person() {

       System.out.println("实例化对象的个数:" + ++count);

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Person();

       new Person();

       new Person();

    }

}

实例化对象的个数:1

实例化对象的个数:2

实例化对象的个数:3

对于对象的回收,暂时没有办法统计。

范例:现在也可以利用static进行属性的自动命名

class Person {

    private static int count = 0; // 统计个数

    private String name;

 

    public Person() {

       this("无名氏 - " + count++); // 自动命名

    }

 

    public Person(String name) {

       this.name = name;

    }

 

    public String getName() {

       return this.name;

    }

}

 

public class Demo {

    public static void main(String args[]) {

       System.out.println(new Person("张三").getName());

       System.out.println(new Person().getName());

       System.out.println(new Person().getName());

       System.out.println(new Person("李四").getName());

       System.out.println(new Person().getName());

       System.out.println(new Person("王五").getName());

    }

}

张三

无名氏 - 0

无名氏 - 1

李四

无名氏 - 2

王五

对于这种代码理解即可,以后如果看到某些系统的程序中存在自动命名的功能,就是如上代码的实现。

2、定义方法,通过方法取得本类的实例化对象

class Person {

    private String name;

 

    public Person(String name) {

       this.name = name;

    }

 

    public String getName() {

       return this.name;

    }

 

    public static Person getInstance() {

       return new Person("默认的名称");

    }

}

 

public class Demo {

    public static void main(String args[]) {

       System.out.println(new Person("张三").getName());

       System.out.println(Person.getInstance().getName());

    }

}

张三

默认的名称

对于这种操作的取得应该来讲只是以后一个核心程序的一小部分,重点是必须清楚:如果以后类中发现无法使用构造方法的时候,肯定会提供一个static方法供用户使用。

2.2 代码块(了解)

代码块指的是在java之中使用“{}”定义的程序段,根据程序段的位置及所声明的关键字的不同,代码块一共分为四种:普通代码块、构造块、静态代码块、同步代码块。

2.2.1 普通代码块

普通代码块指的是直接在方法或语句中定义的代码块

    public static void main(String args[]) {

       if (true) {

           int x = 10;

           System.out.println("x=" + x);

       }

       int x = 100;

       System.out.println("x=" + x);

    }

x=10

x=100

此时的两个x变量之间并不会互相影响,因为第一个x是定义在if语句之中,属于局部变量,而普通代码块的形式与之类似:

    public static void main(String args[]) {

        {   //普通代码块

           int x = 10;

           System.out.println("x=" + x);

        }

       int x = 100;

       System.out.println("x=" + x);

    }

x=10

x=100

对于普通代码块一般用处不大,知道就行了。

2.2.2 构造块

如果将一个代码块定义在了类之中,则就表示构造块

class Person {

    { // 类中定义是构造块

       System.out.println("A、构造块。");

    }

 

    public Person() {

       System.out.println("B、构造方法。");

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Person();

       new Person();

    }

}

A、构造块。

B、构造方法。

A、构造块。

B、构造方法。

通过运行可以发现,构造块优先于构造方法执行,而且每一个新的实例化对象产生,都会重复执行构造块,但是构造块在开发之中基本上是不可能使用,知道就行了。

2.2.3 静态块

静态块指的就是使用static关键字定义的代码块,但是静态块的定义要考虑两种情况:

情况一:在非主类中定义静态类

class Person {

    static {

       System.out.println("X、静态块");

    }

    { // 类中定义是构造块

        System.out.println("A、构造块。");

    }

 

    public Person() {

       System.out.println("B、构造方法。");

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Person();

       new Person();

    }

}

X、静态块

A、构造块。

B、构造方法。

A、构造块。

B、构造方法。

通过运行可以发现,静态块优先于构造块执行,而且不管有多个新对象产生,静态块只执行一次,静态块的主要功能是为静态属性初始化的。

情况二:在主类中定义静态块

public class Demo {

    static {

       System.out.println("静态块的输出。");

    }

 

    public static void main(String args[]) {

       System.out.println("主方法中的输出。");

    }

}

静态块的输出。

主方法中的输出。

通过运行可以发现,静态块的执行优先于主方法执行,而这个时候就可以利用静态块来取代主方法;

public class Demo {

    static {

       System.out.println("Hello World.");

       System.exit(1); // 程序直接退出

    }

}

错误: 在类 course_2.Demo 中找不到主方法, 请将主方法定义为:

   public static void main(String[] args)

注意,可能有些JDK版本可以无主方法,而直接输出“Hello World.”。

所以一定要记住,主方法并非无可替代,可以使用静态块替代,但是这种程序只是一个娱乐,实际中使用不了。

2.3 思考题

模拟一个用户登录程序,例如登录的操作类是login,通过初始化参数传递用户名和密码;

例如:程序运行格式:java Login 用户名 密码;

默认的用户名和密码:mldn       hello

要求:考虑类的设计结构

范例:直接在主方法中编写基本实现

public class Demo {

    public static void main(String args[]) {

       String username = args[0]; // 取得第一个参数

       String password = args[1]; // 取得第二个参数

       if ("mldn".equals(username) && "hello".equals(password)) {

           System.out.println("用户登录成功");

       } else {

           System.out.println("用户登录不成功");

       }

    }

}

java Demo mldn hello

用户登录成功

此时已经实现了程序的基本功能,而且在现在的程序中存在以下的几个问题:

● 问题一:主方法中的代码过多;

● 问题二:如果输入的参数个数不是两个呢?

那么下面进行问题的解决,思考:打卡流程:

保证输入的内容正确:伸出手指头放进扫描区;

进行验证:一个专门匹配指纹库的系统进行指纹的验证;

显示结果:验证的结果通过指纹的机器进行显示;

可以设计出三个操作类来。

程序一:进行数据验证

class LoginCheck { // 负责验证

    private String username;

    private String password;

 

    public LoginCheck(String username,String password) {

       this.username = username;

       this.password = password;

    }

 

    public boolean isLogin() {

       if ("mldn".equals(username) && "hello".equals(password)) {

           return true;

       }

       return false;

    }

}

本类不负责数据的接收,只是根据数据执行相关的验证操作。

程序二:进行操作体的编写

class Operate {

    private String args[] = null;

 

    public Operate(String args[]) {

       this.args = args;

       if (this.args.length != 2) { // 输入的参数不是两个

           System.out.println("输入的参数错误!");

           System.exit(1); // 程序退出

       }

    }

 

    public String getInfo() {

       if (new LoginCheck(this.args[0], this.args[1]).isLogin()) {

           return "用户登录成功!";

       } else {

           return "用户登录失败!";

       }

    }

}

本类只是负责连接输入操作和验证程序,下面使用主程序输入内容:

public class Demo {

    public static void main(String args[]) {

       System.out.println(new Operate(args).getInfo());

    }

}

java Demo mldn hello

用户登录成功!

客户端程序开发之中肯定越少越好;

对于类的设计而言,肯定不是一蹴而就的,需要长时间代码积累,而且类的设计和表的设计一样,只有相对合理的解决方案。

2.4 内部类(重点

对于内部类的操作而言,如果在几年前基本上是使用不到,但是这几年的技术发展,在很多地方上都开始更多的应用内部类操作了,像Spring框架,或者是以后使用的Android开发等等都有内部类的应用。

2.4.1 内部类的基本概念

所谓的内部类指的就是一个类的内部又定义了其他的操作类的形式,例如,如下的代码:

class Outer { // 定义类

    private String name = "Hello";// 外部类的私有属性

 

    class Inner { // 定义内部类

       public void print() {

           System.out.println(name);

       }

    }

 

    public void fun() {

       new Inner().print();

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Outer().fun();

    }

}

Hello

如果单独从语法上来讲,内部类的定义操作并不麻烦,但是使用内部类有一个缺点和一个优点:

缺点:从java的定义结构上来讲,一个类中只能有属性和方法所组成,此时又增加了一个类,这样的做法会破坏程序的结构;

优点:可以方便的访问外部类的私有操作,如果要想进行本概念的验证,则需要将以上的类拆分为两个。

范例:将内部类和外部类拆分

class Outer {

    private String name = "Hello";

 

    public void fun() {

       new Inner(this).print();

    }

 

    public String getName() {

       return this.name;

    }

}

 

class Inner {

    private Outer out = null;

 

    public Inner(Outer out) {

       this.out = out;

    }

 

 

    public void print() {

       System.out.println(this.out.getName());

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Outer().fun();

    }

}

Hello

此时增加了不少的代码,所以内部类的最方便之处就是可以和外部类之间进行方便的互操作。

既然是类,那么肯定会产生字节码文件,所以下面观察一下内部类的字节码文件:Outer$Inner.class,可以发现现在在内部类的字节码文件中存在了一个“$”符合,而此符合在程序之中会被自动的替换为:“.”那么通过这样的替换也就发现,内部类的完整名称应该是“外部类.内部类”,所以此时,如果要想在外部进行内部类的对象实例化,则格式如下:

外部类.内部类内部类对象 = new 外部类( ) . new 内部类( ) ;

内部类有可能操作外部类中的普通属性,而普通属性是在对象实例化之后才会被分配空间的,所以就证明在实例化内部类的对象之前,必须先有外部类的实例化对象。

class Outer { // 定义类

    private String name = "Hello";// 外部类的私有属性

 

    class Inner { // 定义内部类

       public void print() {

           System.out.println(name);

       }

    }

}

 

public class Demo {

    public static void main(String args[]) {

       Outer.Inner in = new Outer().new Inner();

       in.print();

    }

}

Hello

以上完成了内部类的基本开发,但是在定义内部类的时候有以下三点说明:

说明一:如果现在一个内部类只希望被外部类所操作,则可以在类的定义上使用private声明

class Outer { // 定义类

    private String name = "Hello";// 外部类的私有属性

 

    private class Inner { // 定义内部类

       public void print() {

           System.out.println(name);

       }

    }

}

说明二:对于类中属性的访问都应该在前面加上“this”关键字,但是以上的程序并没有使用“this.name”,因为如果加上则表示访问的是当前类中的属性,但是如果访问的属性是外部类的话,则应该使用“外部类.this”表示。

class Outer { // 定义类

    private String name = "Hello";// 外部类的私有属性

 

    class Inner { // 定义内部类

       public void print() {

           System.out.println(Outer.this.name);

       }

    }

}

以后在进行Android开发的时候,这种语法基本上都会一直见到。

说明三:内部类除了可以方便的访问外部类的私有操作,外部类也能方便的访问内部类的私有操作

class Outer { // 定义类

    private String name = "Hello";// 外部类的私有属性

 

    class Inner { // 定义内部类

       private String var = "World";

 

       public void print() {

           System.out.println(Outer.this.name);

       }

    }

 

    public void fun() {

       new Inner().print();

       System.out.println(new Inner().var); // 直接访问私有

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Outer().fun();

    }

}

Hello

World

以上的这一特征,完全可以在链表中实现。

2.4.2 使用static定义内部类

内部类也可以使用static关键字进行定义,但是一旦使用了static定义内部类的话,则表示这个内部类就变成了外部类,而内部类的完整名称也就变为“外部类.内部类”,而且此时也只能访问外部类中的static操作了。

class Outer { // 定义类

    private static String name = "Hello";// 外部类的私有属性

 

    static class Inner { // 定义外部类

       public void print() {

           System.out.println(Outer.name);

       }

    }

}

 

public class Demo {

    public static void main(String args[]) {

       Outer.Inner in = new Outer.Inner();

       in.print();

    }

}

Hello

注意:使用static定义类的形式,只适合于出现在内部类上,外部类是无法使用的。

2.4.3 在方法中定义内部类

理论上讲,内部类可以在类的任意位置上进行定义,包括代码块之中都可以,可是这之中以方法定义内部类的形式出现的是最多的,所以下面直接在方法中定义内部类。

范例:在方法中定义内部类

class Outer { // 定义类

    private static String name = "Hello";// 外部类的私有属性

 

    public void fun() {

       class Inner {

           public void print() {

              System.out.println(Outer.this.name);

           }

       }

       new Inner().print();

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Outer().fun();

    }

}

Hello

另外,如果在一个方法中定义的内部类,要想访问方法中的参数或者是变量,则参数或变量前必须使用“final”关键字定义

class Outer {

    private static String name = "Hello";

 

    public void fun(final int i) {// 参数

       final String y = "World"; // 变量

       // static String xi = "wolex";   --错误!方法内不允许定义static属性。

       class Inner {

           public void print() {

              System.out.println(Outer.this.name);

              System.out.println(i);

              System.out.println(y);

           }

       }

       new Inner().print();

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Outer().fun(10);

    }

}

Hello

10

World

对于内部类的所以操作语法,必须熟悉。

2.5 链表的内部类实现(理解)

在链表程序之中,Node节点类是Link类自己所需要的,很明显如果按照之前的编写,现在肯定Node类可以被任意的类所使用,这样也不方便,所以使用内部类修改。

package course_2;

 

class Link { // 表示链表的操作类,主要就是操作Node

    private Node root; // 将根节点定义为类中的属性

 

    private class Node {

       private String data; // 要包装的数据

       private Node next; // 保存它的下一个节点

 

       public Node(String data) { // Node类的功能一定要包装数据

           this.data = data;

       }

 

       public void addNode(Node newNode) { // 将节点保存在合适的位置上

           if (this.next == null) { // 当前节点后没有其他节点

              this.next = newNode; // 保存

           } else { // 当前节点之后有内容

              this.next.addNode(newNode); // 当前节点的下一个节点保存

           }

       }

 

       public void printNode() {

           System.out.println(this.data); // 输出当前节点的数据

           if (this.next != null) { // 还有后面的节点

              this.next.printNode();

           }

       }

 

       public boolean containsNode(String data) {

           if (this.data.equals(data)) { // 当前节点的数据为查找数据

              return true;

           } else { // 当前节点的数据不是查找数据,继续向下

              if (this.next != null) { // 现在没有查找到最后的节点

                  return this.next.containsNode(data);

              } else { // 后面没有节点

                  return false;

              }

           }

       }

 

       public void removeNode(Node previous, String data) {

           if (this.data.equals(data)) { // 当前 节点的数据满足删除

              previous.next = this.next; // 空出当前节点

           } else {

              this.next.removeNode(this, data); // 向下继续删除

           }

       }

    }

 

    public void add(String data) { // 设置要增加的数据

       if (data == null) { // 如果没有数据

           return; // 返回到被调用处

       }

       Node newNode = new Node(data); // 将数据包装在节点

       if (this.root == null) { // 现在没有根节点

           this.root = newNode; // 第一个节点作为根节点

       } else { // 如果不是根节点,则通过Node类指定保存的位置

           this.root.addNode(newNode);

       }

    }

 

    public void print() {

       if (this.root != null) { // 现在有节点有数据,可以保存

           this.root.printNode(); // 输出还是交给Node

       }

    }

 

    public boolean contains(String data) {

       if (data == null || this.root == null) {

           return false; // 没有内容就不查

       }

       return this.root.containsNode(data);// 交给Node类完成

    }

 

    public void remove(String data) {

       if (this.contains(data)) { // 要删除存在的节点

           if (this.root.data.equals(data)) { // 如果删除的是根节点

              this.root = this.root.next; // 根节点的下一个节点为根

           } else { // 交给Node类完成

              // 从根节点的下一个节点开始,判断要删除的节点

              this.root.next.removeNode(this.root, data);

           }

       }

    }

}

 

public class Demo {

    public static void main(String args[]) {

       Link all = new Link();

       all.add("A");

       all.add("B");

       all.add("C");

       all.remove("B");

       all.remove("A");

       all.print();

       System.out.println(all.contains("A"));

       System.out.println(all.contains("X"));

    }

 

}

C

false

false

这种实现唯有的好处就在于减少了程序中的getter方法的使用,这样也可以让Node类只为Link类提供使用。

2.6 继承性(重点

继承性作为面向对象的第二大特性,其核心表示的含义是:继承已有类的功能继续使用。

2.6.1 继承的引出

为了更好的理解继承的作用,下面通过一个简单的程序来观察,例如,现在要求定义两个类:Person、另外一个是Student,按照之前的做法,现在的代码开发如下:

Person.java

Student.java

class Person {

    private String name;

    private int age;

 

    public void setName(String name) {

        this.name = name;

    }

 

    public void setAge(int age) {

        this.age = age;

    }

 

    public String getName() {

        return this.name;

    }

 

    public int getAge() {

        return this.age;

    }

}

class Student {

    private String name;

    private int age;

    private String school;

 

    public void setName(String name) {

        this.name = name;

    }

    public void setAge(int age) {

        this.age = age;

    }

    public void setSchool(String school) {

        this.school = school;

    }

    public String getName() {

        return this.name;

    }

    public int getAge() {

        return this.age;

    }

    public String getSchool() {

        return this.school;

    }

}

通过程序的运行,可以发现现在里面存在了大量的重复代码,但是现在抛开程序不谈,学生和人的关系是:学生就是一个人,但是学生类比人类的定义更加严格,此时按照之前的概念肯定无法解决问题。

2.6.2 继承的实现

在java中,如果想明确的实现继承的关系,则可以使用extends关键字完成,格式如下:

class 子类 extends 父类{

}

但是在这里需要注意的是:

● 子类又被称为派生类;

● 父类也被称为超类(SuperClass)

范例:观察继承的操作

class Person {

    private String name;

    private int age;

 

    public void setName(String name) {

       this.name = name;

    }

 

    public void setAge(int age) {

       this.age = age;

    }

 

    public String getName() {

       return this.name;

    }

 

    public int getAge() {

       return this.age;

    }

}

 

class Student extends Person { // 学生是人的子类

    // 现在的学生类之中没有定义任何的新操作

}

 

public class Demo {

    public static void main(String args[]) {

       Student stu = new Student();

       stu.setName("张三"); // 直接使用继承来的方法

       stu.setAge(20); // 继承来的方法

       System.out.println(stu.getName() + "-->" + stu.getAge());// 继承而来

    }

}

张三-->20

通过程序的运行可以发现,现在在子类之中,没有定义任何的方法,而是可以直接将父类中定义的操作继续使用,所以证明在子类之中可以对父类进行重用,当然,子类也可以只属于自己的操作。

class Person {

    private String name;

    private int age;

 

    public void setName(String name) {

       this.name = name;

    }

 

    public void setAge(int age) {

       this.age = age;

    }

 

    public String getName() {

       return this.name;

    }

 

    public int getAge() {

       return this.age;

    }

}

 

class Student extends Person { // 学生是人的子类

    private String school; // 子类自己的属性

 

    public void setSchool(String school) { // 子类扩充的方法

       this.school = school;

    }

 

    public String getSchool() { // 子类扩充的方法

       return this.school;

    }

}

 

public class Demo {

    public static void main(String args[]) {

       Student stu = new Student();

       stu.setName("张三"); // 直接使用继承来的方法

       stu.setAge(20); // 继承来的方法

       stu.setSchool("清华大学"); // 子类自己扩充的方法

       System.out.println(stu.getName() + "-->" + stu.getAge() + ","

              + stu.getSchool());// 继承而来

    }

}

张三-->20,清华大学

通过以上的程序可以发现,在开发语言之中,子类最基本的功能也是维持父类的原本操作,所以在程序之中并不会存在所谓的现实生活中的“败家子”的概念。

所以通过程序可以发现,继承的功能就是对已有类功能的一种扩充。

2.6.3 继承的限制

对于继承,实际上一旦使用会带来许多的注意事项。

注意事项一:在java之中不允许多重继承,即一个子类只能继承一个父类

不允许多重继承,但是现在的C子类又需要同时继续A和B类,那么可以采用多层继承解决:

多重继承(×):

多层继承():

Class A {}

Class B {}

Class C extends A,B{}//同时继承两个父类

Class A {}

Class B extends A{}

Class C extends B{}

使用多层继承之后,C类就同时具备了A类和B类的操作功能。

注意事项二:当子类继承的时候,实际上是将父类的全部内容继承下来,但是有以下两点区别:

● 所有的非私有操作采用显式继承的方式; → 子类可以直接使用

● 所有的私有操作采用隐式继承的方式;   → 子类可以间接使用

按照之前的开发所讲,一个类中的全部内容肯定都被子类继承,所以对于之前学生和人之间的操作,学生类也继承了Person类中的name和age两个属性,这两个属性是间接继承(隐式继承),只能通过方法访问;

注意事项三:子类对象在进行实例化操作之前,会默认调用父类中的无参构造方法,为父类属性分配空间,之后再调用子类的构造方法,为本类属性分配空间。

class Person {

    public Person() { // 无参构造

       System.out.println("**Person类的构造。");

    }

}

 

class Student extends Person { // 学生是人的子类

    public Student() {

       System.out.println("**Student类的构造。");

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Student(); // 实例化子类对象

    }

}

**Person类的构造。

**Student类的构造。

通过运行可以发现,很明显现在是父类的构造方法先执行,而后再执行子类的构造方法,即,现在对于子类的构造而言就相当于隐藏了一个“super( )”语句。

class Person {

    public Person() { // 无参构造

       System.out.println("**Person类的构造。");

    }

}

 

class Student extends Person { // 学生是人的子类

    public Student() {

       super();

       System.out.println("**Student类的构造。");

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Student(); // 实例化子类对象

    }

}

**Person类的构造。

**Student类的构造。

而如果说父类中有明确的构造方法,这个时候就可以利用super指定要调用的父类构造参数。

class Person {

    public Person(String name, int age) {

       System.out.println("**Person类的构造。");

    }

}

 

class Student extends Person {

    public Student(String name, int age, String school) {

       super(name, age);  //super()必须在第一行

       System.out.println("**Student类的构造。");

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Student("", 1, ""); // 实例化子类对象

    }

}

**Person类的构造。

**Student类的构造。

所以通过以上的代码就可以证明出,子类和父类是永远也分不开的,而且不管子类如何操作,最终也都要调用父类中的构造方法,但是这个时候有人会提出疑问了,如果代码换种形式呢?

既然现在的操作是进行构造方法的调用,所以现在肯定“super( )”的语句要放在构造方法的首行上,但是现在不使用super( ),换成this( )。

class Person {

    public Person() {

       System.out.println("**Person类的构造。");

    }

}

 

class Student extends Person {

    public Student(String name) {

       this("", 0, "");

    }

 

    public Student(String name, int age) {

       this(name);

    }

 

    public Student(String name, int age, String school) {

       this(name, age);

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Student("", 1, ""); // 实例化子类对象

    }

}

不管环境如何改变,父类构造永远会被子类调用,这也就是在之前讲解this关键字所说的一个问题了:不管如何进行构造方法的互调用,最终肯定有一个构造要作为程序的出口,那么这个出口肯定要调用父类构造。

2.7 覆写(重点

当继承关系发生之后,对于子类而言很有可能定义了与父类相同的方法或属性,所以这个时候就指会发生覆写的问题,当然覆写分为两种情况讨论:方法的覆写,属性的覆写。

2.7.1 方法的覆写

所谓方法的覆写指的就是子类定义了与父类相同的方法的时候(方法名称相同、参数的类型或个数相同、返回值类型相同)。

class Person {

    public void say() {

       System.out.println("我是一个人。");

    }

}

 

class Student extends Person {

    public void say() { // 此时表示方法的覆写

       System.out.println("我是一个学生。");

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Student().say();

    }

}

我是一个学生。

现在实例化的是子类对象,所以所操作的方法肯定是被子类所覆写过的方法。

但是在进行方法的覆写的时候也需要注意权限的问题,对于权限现在一共学习过三种:

● public > default(什么都不写)> private;

被子类所覆写的方法不能拥有比父类更严格的访问控制权限。

● 例如:父类中的方法是default权限,子类中的方法权限:public、default;

● 例如:父类中的方法是public权限,子类中的方法权限:public

       Note:这个结合生活也很好理解,即父亲有一笔estate,他向社会公开(即public)数目地传给儿子,此时儿子是不可能再将其变为秘密(即只有自己知道,private),因为父亲(即父类)已经公之于众;On the contrary,如果父亲是秘密地(private)传给儿子,此时社会并不知道,儿子是有权将其公开(public)或者保留父亲意愿将其作为秘密(private)。

就开发而已,在开发之中,基本上的方法90%都是使用public权限定义的。

思考题:如果现在父类中的方法权限是private,子类的方法权限是default叫覆写吗?

● 首先从它的描述上可以发现,权限由private变为default,属于扩大权限;

class Person {

    private void say() {

       System.out.println("我是一个人。");

    }

 

    public void fun() {

       this.say();

    }

}

 

class Student extends Person {

    void say() { // 此时表示方法的覆写

       System.out.println("我是一个学生。");

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Student().fun();

    }

}

我是一个人。

通过验证可以发现,如果父类的方法定义成private访问权限,则子类根本就不可能覆写,而在实际的开发之中,此类问题也不会出现,因为只要是方法90%都是public。

如果现在一个子类覆写了父类的某个方法,而且又希望在子类中调用父类中被子类所覆写过的方法,可以使用“super.方法()”的形式完成。

class Person {

    void say() {

       System.out.println("我是一个人。");

    }

 

}

 

class Student extends Person {

    void say() { // 此时表示方法的覆写

       super.say(); // 直接找到父类中的指定方法

       System.out.println("我是一个学生。");

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Student().say();

    }

}

我是一个人。

我是一个学生。

同时也需要注意一点,关于this和super的操作范围:

this.方法( )指的是调用本类中的方法,如果本类中不存在此方法在从父类中查找使用;

super.方法( )指的是直接调用父类中的方法,不再查找子类。

建议:如果以后在编写程序的时候,如果调用的是父类方法最好加上“super”。

2.7.2 属性的覆盖(了解)

当一个子类的属性名称与父类的属性名称相同(不需要类型相同)的时候就表示发生了属性的覆写,下面通过代码演示一下。

class Person {

    public int msg = 100; //如果改为private,则子类不能覆写

}

 

class Student extends Person {

    private String msg = "Hello"; // 属性名称相同

 

    public void say() {

       System.out.println("msg = " + this.msg);

       System.out.println("msg = " + super.msg);

    }

}

 

public class Demo {

    public static void main(String args[]) {

       new Student().say();

    }

}

msg = Hello

msg = 100

但是这种代码根本就没有任何意义,因为所以的属性都必须封装,封装之后的就无法覆写了。

2.8 两个重要的概念区别(重点

学习完了以上的知识点之后,现在就存在了两个区别:方法的重载与覆写的区别、this和super的区别;

2.8.1 区别:方法的重载与覆写

No.

区别

重载

覆写

1

单词

Overloading

Override

2

概念

方法名称相同,参数的类型或个数不同

方法名称、返回值类型、参数的个数及类型全部相同

3

范围

发生在一个类之中

发生在继承关系中

4

权限

重载的时候没有权限的限制

子类覆写的方法不能拥有比父类更严格的访问控制权限

面试题:请解释方法重载和覆写的区别,并且当重载的时候是否可以改变方法的返回值类型?

所有的区别就使用如上的表格回答;重载的时候返回值类型可以不相同,但是没有意义。

2.8.2 区别:this和super

No

区别

this

super

1

属性

this.属性指的是找到本类的属性,如果本类没有找到则继续查找父类

super.属性直接在子类之中查找父类中的指定属性,不再查找子类本身

2

方法

this.方法()指的是找到本类的方法,如果本类没有找到则继续查找父类

super.方法()直接在子类之中查找父类中的指定方法,不再查找子类本身

3

构造

都必须放在构造方法的首行,所以两个操作不能同时出现

4

特殊

表示当前对象

 

建议:为了方便开发的维护,以后要使用this和super明确的划分出子类和父类的操作。

2.9 核心思考题(重点

现在要求定义一个整形数组的操作类,数组的大小由外部决定,用户可以向数组之中增加数据,以及取得数组中的全部数据,也可以根据外部提供的数组的增加大小,在原本的数组之上扩充指定的容量,另外,在此类上派生两个子类:

● 排序类:取得的数组内容是经过排序出来的结果;

● 反转类:取得的数组内容是反转出来的结果;

提示:在完成程序的时候根本就没有必要去考虑子类的问题,先完成一个父类。

范例:先开发父类 —— Array

class Array { // 操作数组

    private int data[] = null; // 要操作的是整形数组

    private int foot = 0; // 增加数据的脚标

 

    public Array(int len) {

       if (len > 0) {

           this.data = new int[len];// 开辟新数组

       } else {

           this.data = new int[1]; // 维持一个长度

       }

    }

 

    public void increment(int len) { // 可扩充容量

       int[] newArr = new int[this.data.length + len]; // 开辟新数组

       System.arraycopy(this.data, 0, newArr, 0, this.data.length);

       this.data = newArr;

    }

 

    public boolean add(int num) {

       if (this.foot < data.length) { // 还有地方增加数据

           this.data[this.foot++] = num;// 保存数据

           return true;

       }

       return false;

    }

 

    public int[] getData() {

       return this.data;

    }

}

 

public class Thought {

    public static void main(String args[]) {

       Array arr = new Array(5);

       System.out.println(arr.add(21));

       System.out.println(arr.add(87));

       System.out.println(arr.add(43));

       System.out.println(arr.add(12));

       System.out.println(arr.add(84));

       System.out.println(arr.add(6));

       arr.increment(3);

       System.out.println(arr.add(6));

       System.out.println(arr.add(7));

       System.out.println(arr.add(8));

       int temp[] = arr.getData();

       for (int i = 0; i < temp.length; i++) {

           System.out.print(temp[i] + "、");

       }

    }

}

true

true

true

true

true

false

true

true

true

21、87、43、12、84、6、7、8、

范例:开发排序类 —— SortArray

class Array { // 操作数组

    private int data[] = null; // 要操作的是整形数组

    private int foot = 0; // 增加数据的脚标

 

    public Array(int len) {

       if (len > 0) {

           this.data = new int[len];// 开辟新数组

       } else {

           this.data = new int[1]; // 维持一个长度

       }

    }

 

    public void increment(int len) { // 可扩充容量

       int[] newArr = new int[this.data.length + len]; // 开辟新数组

       System.arraycopy(this.data, 0, newArr, 0, this.data.length);

       this.data = newArr;

    }

 

    public boolean add(int num) {

       if (this.foot < data.length) { // 还有地方增加数据

           this.data[this.foot++] = num;// 保存数据

           return true;

       }

       return false;

    }

 

    public int[] getData() {

       return this.data;

    }

}

 

class SortArray extends Array {

    public SortArray(int len) {

       super(len);

    }

 

    public int[] getData() {

       java.util.Arrays.sort(super.getData());

       return super.getData();

    }

}

 

public class Thought {

    public static void main(String args[]) {

       SortArray arr = new SortArray(5);

       System.out.println(arr.add(21));

       System.out.println(arr.add(87));

       System.out.println(arr.add(43));

       System.out.println(arr.add(12));

       System.out.println(arr.add(84));

       System.out.println(arr.add(6));

       arr.increment(3);

       System.out.println(arr.add(16));

       System.out.println(arr.add(7));

       System.out.println(arr.add(58));

       int temp[] = arr.getData();

       for (int i = 0; i < temp.length; i++) {

           System.out.print(temp[i] + "、");

       }

    }

}

true

true

true

true

true

false

true

true

true

7、12、16、21、43、58、84、87、

范例:开发反转类 —— ReverseArray

class Array { // 操作数组

    private int data[] = null; // 要操作的是整形数组

    private int foot = 0; // 增加数据的脚标

 

    public Array(int len) {

       if (len > 0) {

           this.data = new int[len];// 开辟新数组

       } else {

           this.data = new int[1]; // 维持一个长度

       }

    }

 

    public void increment(int len) { // 可扩充容量

       int[] newArr = new int[this.data.length + len]; // 开辟新数组

       System.arraycopy(this.data, 0, newArr, 0, this.data.length);

       this.data = newArr;

    }

 

    public boolean add(int num) {

       if (this.foot < data.length) { // 还有地方增加数据

           this.data[this.foot++] = num;// 保存数据

           return true;

       }

       return false;

    }

 

    public int[] getData() {

       return this.data;

    }

}

 

class SortArray extends Array {

    public SortArray(int len) {

       super(len);

    }

 

    public int[] getData() {

       java.util.Arrays.sort(super.getData());

       return super.getData();

    }

}

 

class ReverseArray extends Array {

    public ReverseArray(int len) {

       super(len);

    }

 

    public int[] getData() {

       int center = super.getData().length / 2;

       int head = 0;

       int tail = super.getData().length - 1;

       for (int i = 0; i < center; i++) {

           int temp = super.getData()[head];

           super.getData()[head] = super.getData()[tail];

           super.getData()[tail] = temp;

           head++;

           tail--;

       }

       return super.getData();

    }

}

 

public class Thought {

    public static void main(String args[]) {

       ReverseArray arr = new ReverseArray(5);

       System.out.println(arr.add(1));

       System.out.println(arr.add(2));

       System.out.println(arr.add(3));

       System.out.println(arr.add(4));

       System.out.println(arr.add(5));

       System.out.println(arr.add(6));

       arr.increment(3);

       System.out.println(arr.add(6));

       System.out.println(arr.add(7));

       System.out.println(arr.add(8));

       int temp[] = arr.getData();

       for (int i = 0; i < temp.length; i++) {

           System.out.print(temp[i] + "、");

       }

    }

}

true

true

true

true

true

false

true

true

true

8、7、6、5、4、3、2、1、

本程序之中融合数组的操作,以及继承中的各个操作概念,还有覆写的应用。

3、总结

1、在进行继承开发的时候,重点的部分放在父类上完成,子类可以根据自己的需要选择对操作方法进行覆写;

2、static声明的属性和方法,可以在没有实例化对象的时候进行调用;

3、内部类的操作及特点;

4、继承性和方法的覆写是一个极为重要的概念组合,一定要记住一句话:“使用哪个子类就使用被这个子类所覆写的方法”;


      


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值