- 1. this 关键字
- (1)引入例子
- (2)this 的用法
- 2. 封装
- 3. 包
- (1)包访问权限
- 4. import 和 package 的区别
- 5. 继承
- (1)继承的规则
- (2)子类构造的同时,要先帮助父类进行构造
- (3)super 关键字
- (4)protected 关键字
- (5)final 关键字
- 6. 向上转型
- 7. 动态绑定
在说 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 中,包名应该为小写。
包就相当于文件夹,文件夹里面可以放很多类,创建多个包,也可以防止某些类发生重名的情况带来的冲突,这就相当于你用 windows 系统在C盘创建了一个文件夹,而这个文件夹创建了一个名叫 “123.txt 文件”,那么,为了防止重名情况的发生,我们就必须创建另一个文件夹,再创建一个“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
}
}
4. import 和 package 的区别
import 只能导入一个具体的类,不能导入一个包。
包 package 是一组类的集合。
import java.util.*;
上面代码块中,java.util 表示包,* 表示通配符,导入这个包底下所有的类,而 Java 在处理的时候,什么类被需要,就会拿什么包。
5. 继承 (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 编译器试着调试一下,理解在整个过程中,到底代码是怎么跑的。这很关键,因为你会更清晰地知道代码每一步编译出来到底是干什么用的!
这里先提一下,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,那么当在创建对象的时候赋值 “阿拉斯加”,这时候,如果在编译器访问的情况下,会以子类本身变量为优先,此时,我们就能更深入地理解上面代码中的 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 权限最小
在程序清单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 关键字修饰的变量,不能被改变值
向上转型的三种情况:
(1)直接赋值
(2)方法传参
(3)方法返回
(1)直接赋值
程序清单17:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
}
public class Test {
public static void main(String[] args) {
Animal animal1 = new Dog("哈士奇"); //父类的引用 引用了 子类对象
System.out.println(animal1.name);
Dog dog = new Dog("阿拉斯加");
Animal animal2 = dog; //父类的引用 引用了 子类对象
System.out.println(animal2.name);
}
}
输出结果:
在程序清单17中,父类的引用 引用了 子类对象 这一 *** 作被称为向上转型。
(2)方法传参
程序清单18:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(){
System.out.println(name + " 正在吃烤肉");
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
}
public class Test {
public static void func(Animal animal){
animal.eat();
}
public static void main(String[] args) {
Dog dog = new Dog("阿拉斯加");
func(dog); //方法传参
}
}
输出结果:
在程序清单18中,我们创建一个 func 方法,接收的参数类型是 Animal,接着我们在主函数调用 func(dog),奇怪的是,可以看见我们传参传的是 Dog 类型,然而不会报错。
也就是说传参传的是子类类型,接收的是父类类型,那么这个步骤也称为向上转型。
这个时候我们思考一下,如果我们传参传的是父类,接收的是子类,可以通过编译吗?答案是否,因为不符合继承的原则,编译器会报错。
(3)方法返回
程序清单19:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
}
public class Test {
public static Animal func(){
Dog dog = new Dog("阿拉斯加");
return dog;
}
public static void main(String[] args) {
System.out.println(func().name);
}
}
输出结果:
在程序清单19中,我们创建一个 func 方法,在创建方法的时候,定义的返回类型是 Animal 类型,然而,可以看见我们返回的却是 Dog 类型,接着,我们在主函数调用 func( ).name,这就等价于 dog.name,最终也可以得出想要的结果,那么这个步骤也称为向上转型。
在说动态绑定之前,我们先来看程序清单20和程序清单21.
程序清单20:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(){
System.out.println("某个动物正在吃东西");
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog("小柴犬");
animal.eat();
}
}
输出结果:
程序清单21:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(){
System.out.println("某个动物正在吃东西");
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
public void eat(){
System.out.println(name + " 正在吃东西");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog("小柴犬");
animal.eat();
}
}
我们发现程序清单21中,在父类和子类中,出现了同名的方法,而当通过父类引用调用 eat( ) 方法时,输出结果竟然是子类的方法。那么我们就来总结几个概念。
动态绑定:
① 父类引用 引用子类的对象
② 通过这个父类 调用父类和子类同名的覆盖方法
重写:
重写表示,在继承的关系下,子类创建了父类已有的某个方法。
重写的规则:
① 方法名相同
② 参数的个数和类型都必须相同
③ 返回值相同
④ 方法名不能被 static 关键字修饰,方法名同时不能被 final 修饰
⑤ 子类方法的修饰限定符的范围必须大于父类方法的修饰限定符范围
如:子类方法使用 protected,父类方法使用 public,这就会使编译器报错。当然,如果使用 private,也是无法重写的,因为 private 修饰的方法只能在同一个类下才能使用。
重载:
① 方法名相同
② 方法的参数个数不同 / 方法的参数类型不同
③ 方法的返回值类型不作要求
在程序清单22中,有一行代码被编译器报错,我已通过注释标明出来了。
程序清单22:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly(){
System.out.println(name + " 正在飞翔");
}
}
public class Test1 {
public static void main(String[] args) {
Animal animal = new Bird("杜鹃");
animal.fly(); //error
}
}
结论:通过父类的引用,只能访问父类自己的成员
程序清单23:
class A {
public A() {
func();
}
public void func() {
System.out.println("abc");
}
}
class B extends A {
@Override
public void func() {
System.out.println("xyz");
}
}
public class Test1 {
public static void main(String[] args) {
B d = new B();
}
}
输出结果:
在程序清单23中,程序也发生了动态绑定,因为子类重写了父类的成员方法,这就会导致我们在父类中遇到 func( ) 方法的时候,会直接选择子类的 func( )方法,详见下面的代码运行顺序。
代码运行顺序:
最后一个程序,比较复杂但并不难,我们一起看一下,就结束本篇博客。
程序清单24:
class X{
Y y=new Y();
public X(){
System.out.print("X");
}
}
class Y{
public Y(){
System.out.print("Y");
}
}
public class Z extends X{
Y y=new Y();
public Z(){
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
输出结果:
代码运行顺序:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)