文章目录
this
this 关键字最大的作用就是让类中的一个方法,访问该类里的另一个方法或者实例变量。假设定义了一个 Dog 类,这个 Dog 对象的 run() 方法 需要调用它的 jump() 方法。
Dog.java
public class Dog {
//定义一个jump() 方法
public void jump()
{
System.out.println("正在执行jump方法");
}
//定义一个run()方法, run()方法需要借助jump()方法
public void run()
{
Dog d = new Dog();
d.jump();
System.out.println("正在执行run方法");
}
}
DogTest.java
public class DogTest {
public static void main(String[] args) {
//创建Dog对象
Dog dog = new Dog();
//调用Dog对象的run方法
dog.run();
}
}
1️⃣ 在 run() 方法中调用 jump() 方法时是否一定需要一个 Dog 对象?
没有
static修饰的成员变量和方法都必须使用对象来调用
2️⃣ 是否一定需要重新创建一个 Dog 对象?
因为当程序调用 run() 方法时,一定会提供一个 Dog 对象,这样就可以直接使用这个已经存在的 Dog 对象,而无须重新创建新的 Dog 对象。
this 可以代表任何对象,当 this 出现在某个方法体中时,它所代表的对象是不确定的,但它的类型是确定的:它所代表的只能是当前类的实例;只有当这个方法被调用时,它所代表的对象才被确定下来:谁在调用这个方法,this 就代表谁。
它所代表的对象是不确定的,但它的类型是确定的
Dog d = new Dog(); Dog dog = new Dog();
比如
Dog.java类型是确定的—— Dog所代表的对象,可能是 d,可能是 dog
Dog.java
public void run()
{
//使用this 引用调用 run()方法的对象
this.jump();
//省略 this 前缀
jump();
System.out.println("正在执行run方法");
}
-
大部分时候,一个方法访问该类中定义的其他方法,成员变量时加不加 this 前缀的效果是完全一样的。
省略 this 前缀只是一种假象,虽然我们人为的省略了调用 jump() 方法之前的 this,但实际上这个 this 依然是存在的。
如果调用 static 修饰的成员(包括方法、成员变量)时省略了前面的主调,那么默认使用该类作为主调;如果调用没有 static 修饰的成员(包括方法、成员变量)时省略了前面的主调,那么默认使用 this 作为主调
static
StaticAccessNonStatic.java
public class StaticAccessNonStatic {
public static void main(String[] args) {
//因为 main() 是静态方法, 而 f() 是非静态方法
//调用 main() 方法的是该类本身, 而不是该类的实例
//因此省略的 this 无法指向有效的对象
StaticAccessNonStatic.info();
info();
//f();
}
public static void info()
{
System.out.println("info()");
}
public void f()
{
System.out.println("f()");
}
}
上面的 main() 方法中直接调用 f() 方法时,系统相当于使用 this 作为该方法的调用者,而 main() 方法是一个 static 修饰的方法,static 修饰的方法属于类,而不属于对象,因此调用 static 修饰的方法主调总是类本身;如果允许在 static 修饰的方法中出现 this 引用,那将导致 this 无法引用有效的对象。(没有对象作为主调)
1️⃣ 如果确实需要在静态方法中访问另一个普通方法,则只能重新创建一个对象。例如上面的 StaticAccessNonStatic.java
//创建一个对象作为调用者来调用 f() 方法。
new StaticAccessNonStatic().f();
匿名对象
例如 DogTest.java
public class DogTest {
public static void main(String[] args) {
//创建Dog对象
Dog dog = new Dog();
//调用Dog对象的run方法
dog.run();
//使用匿名对象
new Dog().jump();
}
}
- 匿名对象在没有指定其引用变量时,只能使用一次。
AnnoymouseObject.java
public class AnnoymouseObject {
public static void main(String[] args) {
new Circle().setR(10);
new Circle().getR();
}
}
class Circle
{
private double r;
public void getR() {
System.out.println(r);
}
public void setR(double r) {
this.r = r;
}
}
方法
方法和函数差不多~
在结构化编程语言(C语言等)函数是是一等公民,整个软件由一个个的函数组成;
在面向对象编程语言(Java等)类才是一等公民,整个系统由一个个的类组成。
因此在 Java 语言里,方法不能独立存在,方法必须属于类或对象。
方法的属性体现
- 方法不能独立定义,方法只能在类体里定义
- 从逻辑意义上来看,方法要么属于该类本身,要么属于该类的一个对象
- 永远不能独立执行方法,执行方法必须使用类或对象作为调用者
使用 static 修饰的方法属于这个类本身,使用 static 修饰的方法既可以使用类作为调用者来调用,也可以使用对象作为调用者来调用。但值得指出的是,因为使用 static 修饰的方法还是属于这个类的,因此使用该类的任何对象来调用这个方法时将会得到相同的执行结果,这是由于底层依然是使用这些实例所属的类作为调用者。
(简单点说,使用该类的任何对象来调用这个方法,相当于使用这个类调用这个方法,所以该类的任何对象来调用这个方法时将会得到相同的执行结果)
没有 static 修饰的方法则属于该类的对象,不属于这个类的本身,因此没有 static 修饰的方法只能使用对象作为调用者来调用,不能使用类作为调用者来调用。使用不同对象作为调用者来调用同一个普通方法,可能得到不同的结果。
StaticTest.java
public class StaticTest {
static int number = 2;
public static void setNum(int number)
{
StaticTest.number = number;
}
public static void getNum()
{
System.out.println(number);
}
public static void main(String[] args) {
getNum();
setNum(4);
getNum();
new StaticTest().getNum();
new StaticTest().getNum();
}
}
和 AnnoymouseObject.java 对比一下 解释上面两段话
方法参数
值传递和引用传递
balabala
可变长参数
balabala
成员变量 局部变量
balabala
封装
| 修饰符 | 同类中 | 同一个包中(子类和无关类) | 不同包(子类) | 不同包(无关类) |
|---|---|---|---|---|
| public | √ | √ | √ | √ |
| protected | √ | √ | √ | |
| default 缺省 | √ | √ | ||
| private | √ |
如果一个 Java 源文件里定义的所有类都没有使用 public 修饰,则这个 Java 源文件的文件名可以是一切合法的文件名;但如果一个 Java 源文件里定义了一个 public 修饰的类,则这个源文件的文件名必须与 public 修饰的类的类名相同 (main方法写在 public的那个类里面)
设置原则
- 类里的绝大部分成员变量都应该使用 private 修饰,只有一些 static 修饰的、类似全局变量的成员变量,才能考虑使用 public 修饰。除此之外,有些方法只用于辅助实现该类的其他方法, 这些方法被称为工具方法,工具方法也应该使用 private 修饰。
- 如果某个类主要用做其他类的父类,该类里包含的大部分方法可能仅希望被其子类重写,而不想被外界直接调用,则应该使用 protected 修饰这些方法。
- 希望暴露出来给其他类自由调用的方法应该使用 public 修饰。因此,类的构造器通过使用 public 修饰,从而允许在其他地方创建该类的实例。因为外部类通常都希望被其他类自由使用,所以大部分外部类都是用 public 修饰。
构造器
balabala
继承
extends 扩展:子类扩展了父类,将可以获得父类的全部成员变量和方法。
Java 的子类不能获得父类的构造器
Fruit.java
public class Fruit
{
public double weight;
public void info()
{
System.out.println("我是一个水果!重"+weight+"g! ");
}
public static void main(String[] args) {
//创建Apple对象
Apple a = new Apple();
//Apple 对象本身没有weight 成员变量
//因为Apple 的父类有weight 成员变量,也可以访问Apple 对象的weight 成员变量
a.weight = 56;
a.info();
}
}
class Apple extends Fruit
{
}
Apple 类基本只是一个空类,它只包含了一个 main() 方法,但程序中创建了 Apple 对象之后,可以访问该 Apple 对象的 weight 实例变量和 info() 方法,这表明 Apple 对象也具有了 weight 实例变量和 info() 方法,这就是继承的作用。
Java单继承!
严格来讲,Java 类只能有一个直接父类
如果定义一个Java 类时并未显示指出这个类的直接父类,则这个类默认扩展
java.lang.Object类。因此
java.lang.Object类是所有类的父类,要么是其直接父类,要么是其间接父类。因此所有的Java 对象都可调用java.lang.Object类所定义的实例方法。
重写
大多数时候,子类总是以父类为基础,额外增加新的成员变量和方法。但有一种情况例外:子类需要重写父类的方法。
例如鸟类都包含了飞翔的方法,其中鸵鸟是一种特殊的鸟类,因此鸵鸟应该是鸟类的子类,因为它也将从鸟类获得飞翔的方法,但这个飞翔方法明显不适合鸵鸟,为此,鸵鸟需要重写鸟类的方法。
Bird.java
public class Bird {
//Bird类的fly() 方法
public void fly()
{
System.out.println("我在天空里自由自在地飞翔");
}
public static void main(String[] args) {
//创建Ostrich 对象
Ostrich os = new Ostrich();
//执行Ostrich 对象的fly() 方法,将输出“我只能在地上奔跑...”
os.fly();
}
}
class Ostrich extends Bird
{
//重写Bird类的fly() 方法
@Override
public void fly()
{
System.out.println("我只能在地上奔跑...");
}
}
执行上面程序,将看到执行 os.fly() 时执行的不再是 Bird类的 fly() 方法,而是执行 Ostrich 类的 fly() 方法。
这种子类包含与父类同名方法的现象被称为方法重写(Override),也被称为方法覆盖,可以说子类重写了父类的方法,也可以说子类覆盖了父类的方法。
方法重写的规则——两同两小一大
两同:方法名相同、形参列表相同
两小:子类方法返回值类型应比父类方法放回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等。
一大:子类方法的访问权限应比父类方法的访问权限更大或相等。
注意:覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法。如下代码将会引发编译错误
class BaseClass { public static void test(){} } classs SubClass extends BaseClass { public void test(){} }
@Override
@Override是伪代码,表示方法重写。
-
作为注释,帮助自己检查是否正确的复写了父类中已有的方法
2.便于别人理解代码
3.编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错.
当子类覆盖了父类方法后,子类的对象将无法访问父类中被覆盖的方法,但可以在子类方法中调用父类中被覆盖的方法。如果需要在子类中调用父类被覆盖的方法,则可以使用 super(被覆盖的是实例方法)或者父类类名(被覆盖的是类方法)作为调用者调用父类中被覆盖的方法。
重写 static方法时,不能使用 @Override,也就是说此时,不是重写方法,而是在子类中重新定义了一个新的方法
Java 中 static 方法不能被覆盖,private修饰不支持继承,因此被私有的方法不能重写。
static方法是与类绑定的与任何实例都无关,随着类的加载而加载, static是编译时静态绑定的,override是运行时动态绑定的。形式上static 可以 override,但是实际上并不能被override。
private只能够被自身类访问,子类不能访问private修饰的成员,所有不能override一个private方法
重写和重载
没有关系!
super
super 是Java 提供的一个关键字,super 用于限定改对象调用它从父类继承得到的实例变量或方法。正如 this 不能出现在 static 修饰的方法中一样,super 也不能出现在 static 修饰的方法中。static 修饰的方法是属于类的, 该方法的调用者可能是一个类,而不是对象,因而 super 限定也就是失去了意义。
为了子类方法中访问父类中定义的、被隐藏的实例变量,或为了在子类方法中调用父类中定义的,被覆盖(Override)的方法,可以通过 super. 作为限定来调用这些实例变量和实例方法。
子类中定义与父类同名的实例变量并不会完全覆盖父类中定义的实例变量,它只是简单地隐藏了父类中的实例变量。
HideTest.java
public class HideTest {
public static void main(String[] args) {
Derived d = new Derived();
//程序不可访问 d 的私有变量,所以下面的语句将引起编译错误
//System.out.println(d.tag);
//将 d 变量显式d地向上转型为 Parent 后,即可访问 tag 实例变量
//程序将输出:“父类 tag”
System.out.println(((Parent) d).tag);
}
}
class Parent
{
public String tag = "父类 tag";
}
class Derived extends Parent
{
//定义一个私有的 tag实例变量来隐藏父类的 tag 实例变量
private String tag = "Derived 的 tag";
}
多态
Java 引用变量有两个类型,一个是编译时类型,一个是运行时类型,编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态。我2
(编译看左边,运行看右边)
PolymorphismMain.java
public class PolymorphismMain {
public static void main(String[] args) {
// 下面编译时类型和运行时类型完全一样,因此不存在多态
Animal animal = new Animal();
// 输出 “animal 父类”
System.out.println(animal.text);
// 下面两次调用 Animal 方法
animal.methodFather();
animal.test();
// 下面编译时类型和运行时类型完全一样,因此不存在多态
Dog dog = new Dog();
// 输出 “dog 子类”
System.out.println(dog.text);
// 下面调用将执行从父类继承到的methodFather() 方法
dog.methodFather();
// 下面调用将执行当前类的test() 方法
dog.test();
// 下面编译时类型和运行时类不一样,多态发生
Animal ploymophicAnimal = new Dog();
// 输出 “animal 父类”
System.out.println(ploymophicAnimal.text);
// 下面调用将执行从父类继承到的 methodFather() 方法
ploymophicAnimal.methodFather();
// 下面调用执行 当前类的 test() 方法
ploymophicAnimal.test();
// 因为ploymophicAnimal 的编译时类型是 Animal
// Animal类没有提供 methodSon() 方法,所以下面代码编译会出错
//ploymophicAnimal.methodSon();
}
}
class Animal
{
public String text = "animal 父类";
public void methodFather()
{
System.out.println("父类的普通方法");
}
public void test()
{
System.out.println("父类的被覆盖的方法");
}
}
class Dog extends Animal
{
public String text = "dog 子类";
@Override
public void test()
{
System.out.println("子类的覆盖父类的方法");
}
public void methodSon()
{
System.out.println("子类的普通方法");
}
}
上面程序的 main() 方式中显式创建了三个引用变量,对于前两个引用变量 animal 和 dog,它们编译时类型和运行时类型完全相同,因此调用它们的成员变量和方法非常正常,完全没有任何问题。但第三个引用变量 ploymophicAnimal 比较特殊,它的编译时类型是 Animal ,而运行时类型是 Dog,当调用该变量的 test() 方法(Animal 类中定义了该方法,子类的 Dog 覆盖了父类的该方法)时,实际执行的是 Dog 类中覆盖后得到 test() 方法,这就可能出现多态了。
ploymophicAnimal.methodFather(); 运行是 Dog类型,调用父类的方法 methodFather()
Animal ploymophicAnimal = new Dog(); 这就是向上转型(upcasting),向上转型由系统自动完成。
当把一个子类对象直接赋给父类引用变量时,例如上面的
Animal ploymophicAnimal = new Dog();这个 ploymophicAnimal 引用变量的编译时类型是 Animal,而运行时类型时 Dog,当运行时调用该引用变量的方法是,其方法行为总是表现出子类的行为特征,而不是父类方法的行为特征,这就可能出现:相同类型的变量、调用同一个方法时呈现出多种不同的行为特征,这就是多态。简单点说,
Animal ploymophicAnimal = new Dog();调用继承的方法test()会表现出多态
上面的 main() 方法注释了 ploymophicAnimal.methodSon(); 这行代码会在编译时引发错误,虽然 ploymophicAnimal 引用变量时间上确实包含 methodSon() 方法(Dog 类),但因为它的编译时类型为 Animal ,因此编译时无法调用 methodSon() 方法。
注意:
与方法不同是是,对象的实例变量不具备多态性,比如 ploymophicAnimal.text 输出的是 Animal 类里面定义的实例变量,而不是 Dog 类的
通过 Object p = new Person() 代码定义一个变量 p,则这个 p 只能调用 Object 类的方法,而不能调用 Person 类里定义的方法
编译时,与父类一致,也就是说只能调用父类的方法,
运行时,与子类一致,如果遇到重写的方法,那么运行的是子类的
编译看左边,运行看右边
引用变量的强制类型转换
ConversionTest.java
public class ConversionTest {
public static void main(String[] args) {
double d = 13.4;
long l = (long)d;
System.out.println(l);
int in = 5;
// 试图把一个数值类型的变量转换为 boolean类型,下面代码编译出错
// 编译时会提示:不可转换的类型
//boolean b = (boolean)in;
Object obj = "Hello";
// obj 变量的编译时类型为 Object, Object与 String 存在继承关系, 可以强制类型转换
String objStr = (String)obj;
System.out.println(objStr);
// 定义一个 objPri 变量,编译时类型为 Object, 实际类型为 Integer
Object objPri = new Integer(5);
// Object 变量的编译时类型为 Object, objPri 的运行类型为 Integer
// Object 与 Integer 存在继承关系
// 可以强制类型转换,而 objPri 变量的实际类型是 Integer
// 所以下面代码运行时引发 ClassCastException 异常
String str = (String)objPri;
// Object ----String
// Object ----Integer
}
}
强制类型转换注意:
- 基本类型之间的转换只能在数值类型(整数型、字符型、浮点型)之间进行。但整值类型和布尔类型之间不能进行类型转换。
- 引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出现错误。如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(即编译时类型为父类类型,而运行时类型是子类类型),否则将会在运行时 引发 ClassCastException 异常
向上转型(upcasting) 、向下转型(downcasting)
Father f1 = new Son(); //这就叫 upcasting (向上转型)
// f1 指向 son对象 ,编译为 Father ,实际为 Son
Son s1 = (Son) f1; // 这就叫 downcasting (向下转型)
// f1 仍指向Son 对象
Father f2 = new Father();
Son s2 = (Son) s2;//出错。子类引用不能指向父类对象
1️⃣ 为什么 Son s1 = (Son) f1; 可以成功
因为 f1 指向 子类对象,Father f1 = new Son(); 子类 s1 引用 当然可以指向子类对象。
2️⃣ Son s2 = (Son) s2; 失败
因为 f2 指向的是 Father 对象。子类 s2 引用不能指向父类对象。
Father f1 = new Son();
- f1 称为 父类引用变量,向上转型
- new Son() 称为子类对象。
这种转型只是声明这个引用变量的编译时类型是父类,但实际执行它的方法是,依然表现出子类对象的行为方式(是不是和多态的表现很像~)。
但是把应该父类对象赋给子类引用变量时,就需要强制类型转换,而且还可能在运行时 尝试 ClassCastException 异常 (报错!)。
回到 ConversionTest.java
Object objPri = new Integer(5);
String str = (String)objPri;
因为 objPri 编译为 Object 为父类对象
在 String str = (String)objPri; 中,赋给子类引用对象。为向下转型,可能会出错
所以使用 instanceof 运算符来判断是否可以成功转换,从而避免 ClassCastException y异常
if (objPri instanceof String) {
String str = (String) objPri;
}
多态存在的三个必要条件
- 继承
- 重写
- 父类引用指向子类对象
Father f1 = new Son();
instanceof
instanceof 运算符的前一个操作数通常是一个引用类型变量,后一个操作数通常是一个类(也可以是接口,可以把接口理解成一种特殊的类),它用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是,则返回 true,否则返回 false。
在使用 instanceof 运算符时需要注意:instanceof 运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则会引起编译错误。
final
balabala
75万+

被折叠的 条评论
为什么被折叠?



