对象与类(二)
1. this 关键字
(1)引入例子
在说 this 之前,我们先来看一下程序清单1,当我们构造一个方法的时候,传参的变量名和成员变量名同名,都是 name,这会造成编译器输出 null ,因为编译器会认为,你把 name 传给了自己,这会带来麻烦。解决办法有两个,一个是程序清单2,另一个是程序清单3. 这两个程序都输出 “Jack”.
程序清单1:
class Person{ public String name; public Person(String name){ name = name; } } public class Test { public static void main(String[] args) { Person person = new Person("Jack"); System.out.println(person.name); } }
程序清单2:
class Person{ public String name; public Person(String newname){ name = newname; } } public class Test { public static void main(String[] args) { Person person = new Person("Jack"); System.out.println(person.name); } }
程序清单3:
class Person{ public String name; public Person(String name){ this.name = name; //this.name = newname; } } public class Test { public static void main(String[] args) { Person person = new Person("Jack"); System.out.println(person.name); } }
(2)this 的用法
this关键字本身代表的是:当前对象的引用
① this.data:调用当前对象的成员变量
② this.function( ):调用当前对象的成员方法
③ this( ):调用当前对象的其他构造方法
在刚刚的程序清单3中,我们可以看到,使用 this.[ 成员变量 ] 会避免我们出错。
而在程序清单4中,this.function( )的用法和 this.data 是一样的,使用 this 会让整个程序更加安全,不使用也是可以的。
程序清单4:
class Person{ public String name; public void swim(){ System.out.println(name + " 正在游泳"); } public void run(){ System.out.println(name + " 正在跑步"); } public void exercise(){ this.run(); this.swim(); } } public class Test { public static void main(String[] args) { Person person = new Person(); person.name = "吉姆"; person.exercise(); } }
使用 this( ) 调用其他构造方法的规则:
① this( ) 语句只能放在某一个构造函数中的第一行被使用。
② 也就是说:在一个构造函数中,仅可以调用其他任意一个构造函数。注意:有且仅有一个。
③ this( ) 语句不能被用来自己调用自己。
程序清单5:
class Person{ public String name; public int age; public Person(){ this("露丝"); System.out.println("调用不带参数的构造方法"); } public Person(String name){ this.name = name; System.out.println("调用带有一个参数的构造方法"); } } public class Test { public static void main(String[] args) { Person person = new Person(); } }
2. 封装
关键字 private
private 修饰的变量和方法只能在当前类中被使用
在程序清单6中,有一行代码会被编译器报错,我已经注释出来了,原因是:在一个类中,name 被 private 修饰,只能在当前类中被使用。
程序清单6:
class Person{ private String name; public int age; } public class Test { public static void main(String[] args) { Person person = new Person(); person.name = "露丝"; //error person.age = 18; } }
那么我们怎么才能访问 private 修饰的变量呢?请接着往下看:
在程序清单7中,Person 是类的实现者,我们可以通过 IDEA 编译器生成 Getter and Setter 方法。
而 Test 是类的调用者,可以创建对象调用这两个方法。其中,
【 set 表示设置值,get 表示拿到值。】
程序清单7:
class Person{ 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 class Test { public static void main(String[] args) { Person person = new Person(); person.setName("露丝"); System.out.println(person.getName()); person.setAge(10); System.out.println(person.getAge()); } }
输出结果:
3. 理解包的含义
包 (package) 是组织类的一种方式,在 IDEA 中,包名应该为小写。
包就相当于文件夹,文件夹里面可以放很多 " .java " 文件。
创建多个包,也可以防止某些类发生重名的情况带来的冲突。这就相当于你用 windows 系统在 C 盘创建了一个名为 " ABC " 的文件夹,而你又在这个文件夹中创建了一个名为 “123.txt ”的文件,那么,为了防止重名情况的发生,我们只好创建另一个名为 " XYZ " 的文件夹,在 " XYZ " 文件夹中创建一个“123.txt 文件”,这样就不会被系统提示错误。
(1)包访问权限
我们创建一个包 demo1,然后在这个包中创建两个类,一个类是 Test1,另一个类是 Test2.
可以看到,在同一个包中,可以调用另一个类的成员变量。
程序清单8:
public class Test1 { int a = 10; }
public class Test2 { public static void main(String[] args) { Test1 test1 = new Test1(); System.out.println(test1.a); //10 } }
(2)import 和 package 的区别
import 只能导入一个包下某个具体的类,不能导入一整个包。
包 package 是一组类的集合。
import java.util.*;
上面代码块中,java.util 表示包,* 表示通配符,导入这个包底下所有的类,而 Java 在处理的时候,什么类被需要,就会拿什么包。
举个例子:
import java.sql.Timestamp; import javax.sql.DataSource;
上面的是关于 MySQL 数据库的包,那么【 java.sql 】就表示 sql 下的所有包,而 【 Timestamp 】是一个类,【 DataSource 】是一个接口。
所以说,import 归根结底,导入的是:某个包下的某个类 / 接口。
4. 继承
(1)继承的语法
class A:子类 / 派生类 class B:父类 / 超类
class A extends B { }
继承规则:
① 使用 extends 指定父类
② 在Java 中,一个子类只能继承一个父类 ( 而C++/Python等语言支持多继承 )
③ 子类可以继承父类的所有 public 的成员变量和成员方法
④ 对于父类的 private 的字段和方法,子类中是无法访问的
⑤ 子类的实例中,也包含着父类的实例,可以使用 super 关键字得到父类实例的引用
我们来看一个例子,比如说,我定义一个类 Dog,另一个类 Bird,那么这两个都是动物,都会吃东西,那么我就新创建一个类 Animal,把 Dog 和 Bird 的共性放在类 Animal 中,那么此时我们可以分别创建两个对象 new Dog( ) 和 new Bird( ),以此来访问父类的成员变量和成员方法。详见程序清单9.
程序清单9:
class Animal{ public String name; public void eat(){ System.out.println("动物正在吃东西"); } } class Bird extends Animal{ public void fly(){ System.out.println(name + " 正在飞翔"); } } class Dog extends Animal{ } public class Test { public static void main(String[] args) { Dog dog1 = new Dog(); dog1.name = "哈士奇"; System.out.println(dog1.name); Bird bird1 = new Bird(); bird1.name = "老鹰"; bird1.fly(); } }
输出结果:
看完输出结果,我来说明一下这个程序是怎么跑的。
毋庸置疑,程序一定是从主函数开始的,请看下图:
大家也可以自己通过 IDEA 编译器试着调试一下,理解在整个过程中,到底代码是怎么跑的。这很关键,因为你会更清晰地知道代码每一步编译出来到底是干什么用的!
(2)子类构造的同时,要先帮助父类进行构造
这里先提一下,super( ) 表示调用父类对象的构造方法,后面会详细提到,这里先记住。
程序清单10:
class Animal { public String name; public int age; public Animal(String name, int age) { this.name = name; this.age = age; } } class Dog extends Animal{ //调用父类带有两个参数的构造方法 public Dog(String name, int age) { super(name, age); } }
在程序清单10中,如果类 Animal 使用了带参数的构造方法,而类 Dog 没有使用构造方法,那么就会出现编译错误。
程序清单11:
class Animal { public Animal(){ System.out.println("父类的构造方法"); } } public class Test extends Animal{ public static void main(String[] args) { new Test(); } }
输出结果:
在程序清单11中,编译不会出错的原因:当子类没有使用构造方法时,那么编译器就会默认有一个不带参数的构造方法,这在我的上一篇博客中提到过。这个默认构造方法如下:
public Test() { super(); //调用父类不带参数的构造方法 }
在程序清单12中,可以让我们更深刻地理解上面的问题
程序清单12:
class Base{ public Base(String s){ System.out.print("B"); } } public class Test3 extends Base { public Test3(String s) { super(s); System.out.print("D"); } public static void main(String[] args) { new Test3("C"); } }
输出结果:
代码运行顺序:
程序清单13:
class Animal { public String name; public int age; public Animal(String name, int age) { this.name = name; this.age = age; } } class Dog extends Animal { public Dog(String name, int age) { super(name, age); } } class Bird extends Animal{ String wing; public Bird(String name, int age, String wing) { super(name, age); this.wing = wing; } } public class Test { public static void main(String[] args) { Dog dog1 = new Dog("阿拉斯加",6); System.out.println(dog1.name); System.out.println(dog1.age); System.out.println("-----------------"); Bird bird1 = new Bird("杜鹃",3,"黑色的翅膀"); System.out.println(bird1.name); System.out.println(bird1.age); System.out.println(bird1.wing); } }
输出结果:
我们来看一下这个程序是怎么跑的:
底层图理解:
子类 dog1 和 bird1 分别继承父类 Animal 的成员变量 name 和 age,为自己的对象所用,但是 wing 变量属于子类 bird1 自己。
在程序清单13中,类 Dog 和类 Bird 的构造方法,我们通过创建对象,给两个成员变量赋了初值,此时继承了 Animal 类中的成员变量。可以想象,万千世界,有很多动物,它们都有对应的名字,它们都会吃东西…
所以在上面的程序设计过程中,我们省略了定义每个动物类的成员变量和成员方法这一步骤,所以说,继承就是对类共性的提取。
(3)super 关键字
super关键字本身代表的是:父类对象的引用
(1)super.data:调用父类对象的成员变量
(2)super.function( ):调用父类对象的成员方法
(3)super( ):调用父类对象的构造方法
super 和 this 的语法很相似,但是第三种情况,也就是 super 调用父类对象的构造方法用的比较多。
程序清单14:
class Animal { public String name; public Animal(String name) { this.name = name; } } class Dog extends Animal { public String name; public Dog(String name) { super(name); } public void sleep(){ System.out.println(name + " 正在跳"); } public void run(){ System.out.println(super.name + " 正在跑"); } } public class Test1 { public static void main(String[] args) { Dog dog1 = new Dog("阿拉斯加"); System.out.println(dog1.name); dog1.sleep(); dog1.run(); } }
输出结果:
在程序清单14中,当子类 Dog 继承父类 Animal 的时候,会获取父类 Animal 的成员变量 name 和 age。
然而,如果子类 Dog 在自己的类中创建了一个同名变量 name,那么当我们通过 Dog 创建对象的时候并进行赋值,编译器会以子类本身变量为优先,此时,我们就能更深入地理解上面代码中的 name 和 super.name 的区别。
(4)protected 关键字
在程序清单15中,有一行代码会被编译器报错,我已经通过注释表明出来了,造成错误的原因就是:被 private 关键字修饰的变量构成了封装,只能在自己的类中使用,而使用 public 和 protected 就不一样了。
程序清单15:
class Clothes { public int size; private int number; protected String color; } class Jacket extends Clothes { } public class Test1 { public static void main(String[] args) { Jacket jacket = new Jacket(); jacket.size = 175; jacket.number = 30; //error jacket.color = "蓝色"; } }
在上面表格中,我列出了四个关键字对应修饰变量的范围。
① public 构成的权限在任意地方都可以使用变量
② protected 关键字就是为继承这一机制设定的
③ default 就是默认的包访问权限
④ private 权限最小
(5)final 关键字
在程序清单16中,有两个地方会被编译器报错,我已通过注释标明出来了。
程序清单16:
final class Clothes { public int size; private int number; protected String color; } //error class Jacket extends Clothes { } public class Test2 { public static void main(String[] args) { final int a = 5; a = 10; //error int b = 6; b = 11; } }
总结:
① 被 final 关键字修饰的类,不能被继承
② 被 final 关键字修饰的变量,不能被改变值
Over. 谢谢观看哟~