黑马程序员
---------------- <a href="http://edu.csdn.net/heima" target="blank">android培训</a>、<a href="http://edu.csdn.net/heima" target="blank">java培训</a>、期待与您交流! ----------------------
1. 面向对象概念
什么是面向对象
面向对象(Object Oriented)是一种思想,90年代以后软件开发的主流思想。
由于现实社会是由各种各样的事物所组成的,而我们编程又是在模拟现实社会,那么在程序也要用一些东西来表示现实社会中的事物,这些东西就是程序中的对象。我们在程序中使用这些对象,对其特征和行为进行操作进行编程,这就是面向对象编程。
在使用面向对象编程思想之前,我们通常用面向过程的思想编程,先分析出解决问题的步骤,然后按照步骤一步一步实现。
面向对象编程的优点
提高代码复用性。
使用者无需关心具体细节。
转变程序员角色,更加符合人的思维习惯。
2. 类与对象
什么是类
类是用来描述对象的。由于对象是虚拟出来的东西,是看不见摸不着的,我们需要在程序中使用对象,就需要用一种方式来描述对象,然后根据这个描述来创建对象。
类和对象的关系
对象是类的实例,类是对象的描述。
怎么定义类
将一系列特征相似的对象的共同特征及行为抽取出来进行描述,写在一个class中,用成员变量描述对象的特征,用成员方法来描述对象的行为。
class Person {
String name;
int age;
void speak(){
System.out.println("My name is " + name);
System.out.println("I am " + age + " years of age");
}
}
怎么使用类创建对象
使用new关键字和指定类名来创建一个对象。
对象的产生
Person p = new Person();
这句话先在堆内存中创建了一个对象,然后栈内存中创建一个变量引用了对象的地址。
成员变量初始化
当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。基本数据类型初始化值为0,引用数据类型初始化值为null。
对象的使用
当我们创建对象之后可以使用点语法来访问对象的属性和方法。例如:
Person p = new Person();
p.name = "张三"; // 访问属性(成员变量)
p.age = 20;
p.speak(); // 访问方法
对象的生命周期
对象的生命周期从new关键字创建时开始,到没有任何引用到达对象时结束(成为垃圾)。
匿名对象
我们可以不定义变量引用对象,使用new关键字创建对象后直接使用,这样的对象没有名字,所以叫匿名对象。
匿名对象因为没有任何引用到达,在使用一次之后即成为垃圾。
通常我们需要使用一个对象且只使用一次的时候,就可以使用匿名对象。比如将对象作为一个参数传递给另外一个函数。
class PersonDemo { public static void main(String[] args){ /* int x = 5; System.out.println(x); // 创建一个人对象 Person p1 = new Person(); Person p2 = new Person();
p1.name = "张三"; p1.age = 19; p2.name = "李四"; p2.age = 20; p1.eat(); p2.sleep(); // System.out.println(p1); // System.out.println(p2); */ /* Person p = new Person(); System.out.println(p.name); System.out.println(p.age); System.out.println(p.gender); Person p1 = p; p = null; */ Person p = new Person(); p.work(); p.sleep(); new Person().work(); // 匿名对象, 如果一个对象只使用一次, 那么就可以使用匿名对象 fun(new Person()); System.out.println("程序结束"); } static void fun(Person p){ p.eat(); p.work(); p.sleep(); } }
class Person { // 定义一个类用来描述人 String name; // 定义一个成员变量(属性,字段,Field), 用来保存人的姓名. int age; // 定义一个成员变量, 用来保存人的年龄 boolean gender; void eat(){ // 定义一个成员方法, 用来描述人的功能(行为) System.out.println(name + ":" + age + " 吃饭"); } void sleep(){ System.out.println(name + ":" + age + " 睡觉"); } void work(){ System.out.println("工作"); } } } }t.println("工作"); } }
3. 封装(Encapsulation)
什么是封装
封装是指隐藏对象的属性和一些实现细节,仅对外提供必须的访问方式。
怎么封装
将所有属性隐藏,提供公有方法对其访问。
将不需要对外提供的方法隐藏。
封装的优点
提高安全性:在访问对象的属性时候通过方法实现,在方法中可以进行校验。隐藏不必要提供的方法避免错误的调用。
简化编程:使用者无需关心对象内部具体实现细节,只要根据对象功能调用指定方法。
class EncapsulationComputerDemo { public static void main(String[] args){ Computer c = new Computer(); // 创建电脑对象 MainBoard mb = new MainBoard(); // 创建主板对象 c.setName("T420s"); // 给电脑对象的name属性赋值 mb.setName("Xxx主板"); // 给主板对象的name属性赋值 c.setMainBoard(mb); // 给电脑的mainBoard属性赋值 c.turnOn(); }
}
class Computer { // 电脑 private String name; // 电脑的名字 private MainBoard mainBoard; // 电脑的主板 void turnOn(){ // 电脑开机 if(mainBoard == null) // 检查该电脑是否有主板, 没有的话不能开机 System.out.println("电脑没有安装主板, 不能开机"); else // 如果安装了主板, 那么打印电脑的名字, 打印主板的名字 System.out.println(name + "开机成功, " + mainBoard.getName() + "开始运行."); } public void setName(String n){ name = n; } public void setMainBoard(MainBoard m){ mainBoard = m; } public String getName(){ return name; } public MainBoard getMainBoard(){ return mainBoard; } }
class MainBoard { // 主板 private String name; // 主板的名字 public void setName(String n){ name = n; } public String getName(){ return name; } }
4. 构造函数(Constructor)
什么是构造函数
构造函数(Constructor)是一个特殊的函数。
函数名和类名相同。
没有返回值类型。注意:没有返回值类型不等同于void,void也是一种返回值类型。不能使用return关键字返回任何值。
在使用new关键字创建对象之后自动调用。
构造函数的重载
构造函数的重载和普通函数相同,函数名相同,参数列表不同即可。
构造函数的调用
构造函数在new关键字创建对象时调用。
构造函数可以在该类其他构造函数的第一个语句使用this关键字调用。
所有类都有构造函数
每一个类都有构造函数,即使我们没有显式定义构造函数,也会生成一个默认无参的构造函数,其中没有任何内容。
注意:这个自动生成的构造函数只在未定义任何构造函数时生成,如果我们定义了一个有参的构造函数,那么就不会生成无参的了。
构造函数的访问权限
在定义构造函数时,如无特殊需要,应使用public关键字修饰构造函数。
在一些特定情况下,我们不想让别人创建该类对象,那么可以使用private修饰构造函数,例如单态设计模式。
class ConstructorShoppingDemo { public static void main(String[] args){ Person p = new Person("小李"); Market m = new Market("家乐福", new Product[]{new Product("洗衣机"), new Product("电视机"), new Product("空调机") , new Product("豆浆机"), new Product("打印机")});
Product result = p.shopping(m, "打印机"); if(result != null) System.out.println("谢谢" + p.getName() + "帮我在" + m.getName() + "买到了" + result.getName()); else System.out.println("很遗憾" + p.getName() + "在" + m.getName() + "什么都没买到"); } }
class Person { // 人 private String name; Person(String n){ name = n; } public void setName(String n){ name = n; } public String getName(){ return name; } Product shopping(Market market, String name){ return market.sell(name); } }
class Market { // 超市 private String name; private Product[] products; Market(String n, Product[] p){ name = n; products = p; } public void setProducts(Product[] p){ products = p; } public Product[] getProducts(){ return products; } public void setName(String n){ name = n; } public String getName(){ return name; } Product sell(String name){ for(int i = 0; i < products.length; i++) if(products[i].getName() == name) return products[i]; return null; } }
class Product { // 商品 private String name; Product(String n){ name = n; } public void setName(String n){ name = n; } public String getName(){ return name; } }
5. this关键字
this关键字除了在构造函数中调用其他构造函数以外,还可以当做一个引用使用。其用于方法中,哪个对象调用该方法,this就引用哪个对象。例如:
方法中局部变量和成员变量重名,我们想调用成员变量时就可以使用this.变量名形式访问成员变量。
在方法中要将调用该方法的对象作为参数传递给另一个方法时,可以将this作为实参传给该方法。
在内部类中访问外部类的成员时,需要使用外部类名.this.成员名形式访问。
/* this可以作为一个引用在方法中使用. 哪个对象调用该方法, this就引用哪个对象. */ class ThisDemo { public static void main(String[] args){ Person p = new Person(); p.name = "张三"; p.run(); Person p1 = new Person(); p1.name = "王五"; p1.run(); // System.out.println(p); // System.out.println(p1); } }
class Person { String name; // 成员变量 void run(){ String name = "李四"; // 局部变量, 可以和成员变量重名 System.out.println(this.name + "跑步"); // 直接访问变量, 是先找局部, 再找成员 } void eat(){ System.out.println(name + "吃饭"); } }
6. 函数的参数传递
基本数据类型的变量作为实参传入函数之后,在函数中将形参改变,调用处的实参不变。
因为基本数据类型的值是直接存在变量中,传入函数之后函数中的形参也同样存了一个值,这两个值是没有联系的,所以函数中将形参改变时修改的只是函数中的变量的值,和调用处的实参无关。
引用数据类型的变量作为实参传入函数之后,在函数中将形参改变,调用处的实参改变。
因为引用数据类型变量中存储的是地址,传入函数之后函数中的形参存储的也是同样一个地址,函数中将这个形参改变时改变的都是同一个地址上的对象,所以一边改变两边都变。
/* 构造函数在new关键字创建对象的时候, 根据传入的实参, 执行其中一个 代码块则是只要创建对象就会执行
加载类, 创建对象, 构造函数
代码块和初始化成员变量都是在创建对象之后执行的. 哪个在上面就先执行哪个.
*/ class BlockDemo { public static void main(String[] args){ Person p = new Person(); System.out.println(p.name); } }
class Person { { name = "xxx"; System.out.println("代码块"); // 在创建对象的时候执行 } String name = "ooo"; Person(){ System.out.println("无参构造函数"); } Person(int i){ System.out.println("有参构造函数"); } }
7. static关键字
static关键字用来修饰类的成员,被这个关键字修饰的成员都和类加载有关。
JVM运行时不会将所有类加载到内存,因为无法确定程序中要使用哪些。类在第一次使用时加载,只加载一次。
静态变量
用static修饰的变量就是静态变量。
静态变量在类加载后就初始化。
静态变量被类的所有实例所共享。
静态变量可以使用 类名.变量名 形式访问。
如果在定义一个类的时候,发现一个成员变量需要被所有实例所共享,那么这个成员变量就需要定义为static的。
/* 类在第一次使用的时候被加载, 访问类中的静态变量, 静态方法, 创建对象
被该类的所有对象共享 可以使用"类名.变量名"形式直接访问 不用创建对象就能使用, 在类加载的时候初始化
当我们定义一个类的时候, 如果一个变量需要被所有对象所共享, 那么就使用staitc来修饰 */ class StaticVariable { public static void main(String[] args){ Person p; // 这里不会加载类 System.out.println("Person.name = " + Person.name); Person p1 = new Person(); Person p2 = new Person(); p1.name = "张三"; System.out.println(p1.name); System.out.println(p2.name); p2.name = "李四"; System.out.println(p1.name); System.out.println(p2.name); p1.name = "王五"; System.out.println(p1.name); System.out.println(p2.name);
System.out.println("Person.name = " + Person.name); Student s1 = new Student(); Student s2 = new Student(); Student.schoolName = "itcast"; System.out.println(s1.schoolName); System.out.println(s2.schoolName); } }
class Person { static String name; }
class Student { String name; int age; static String schoolName; }
静态方法
用static修饰的方法就是静态方法。
静态方法在类加载后就可以使用。
静态方法可以使用 类名.方法名 形式访问。
静态方法不能直接访问外部非静态成员。
因为外部非静态成员必须在类创建对象之后才能使用,而静态方法可以在没创建对象时就使用。
如果要在静态方法内部访问外部非静态成员,需要先创建该类对象,通过对象访问。
静态方法中不能使用this关键字。
因为this是个引用,哪个对象调用方法就引用哪个对象。 而静态方法有可能不是被对象调用的,this无从引用。
如果一个方法不用访问对象的非静态成员,那么就可以定义为静态的,这样使用者就不需要创建对象,直接用类名调用。
静态方法通常是作为工具方法或者一个可以产生对象的方法被声明,目的是为了让调用者更方便的使用,不必创建对象。
静态代码块
用static修饰的代码块就是静态代码块。
静态代码块在类加载后执行。
静态代码块和静态方法相同,不能使用外部非静态成员。
静态代码块执行和静态变量的初始化顺序由代码从上到下顺序决定。
如果我们有一段代码想在别人使用某个类的时候就运行, 而且只运行一次, 那么就可以写在静态代码块中.
/* 在类加载后就可以使用 可以通过"类名.方法名"调用 不能使用外部非静态成员
如果我们定义一个类时, 某个方法不需要访问该类非静态成员, 那么就可以定义为静态的, 这样别人不用创建对象就能使用.
Arrays.sort() Arrays.toString() System.arraycopy() Integer.parseInt()
通常一些工具方法都是静态的, 为了方便他人使用, 无需创建对象, 直接调用方法即可
*/ class StaticMethod { public static void main(String[] args){ Person.run();
/* Person p = new Person(); p.name = "张三"; p.run(); */ } }
class Person { String name; // 创建对象时才初始化, 才能使用 static void run(){ System.out.println("跑步"); } void eat(){ // 必须创建对象之后才能调用该方法 System.out.println("吃饭"); } }
静态内部类
用static修饰的内部类就是静态内部类。
静态内部类在类加载后就可以创建对象,无需创建外部类对象。
/* 一个类中如果没有写任何构造函数, 那么编译时会自动生成一个无参的空的构造函数 这个无参的构造函数只有在没写任何构造函数时才会自动生成, 如果写了任何一个就不会自动生成了.
以后在写一个有参的构造函数的时候, 需要考虑是否还允许调用无参构造函数, 如果还需要使用无参构造函数, 那么需要显式声明.
构造函数在创建对象时自动调用, 还可以在一个构造函数中调用该类其他构造函数
在构造函数的第一个语句中可以使用this()形式调用该类其他构造函数 */ class ConstructorDemo1 { public static void main(String[] args){ Person p = new Person("李四", 19); // 使用无参的构造函数创建对象 p.show(); } }
class Person { private String name; // private修饰的成员变量只能在该类内部访问 private int age; public Person(){ this("未录入", -1); } public Person(String n){ this(n, -1); } public Person(int a){ this("未录入", a); } public Person(String n, int a){ name = n; age = a; } public void show(){ System.out.println("name = " + name + ", age = " + age); }
}
8. 垃圾回收
对象在没有任何引用可以到达时,生命周期结束,成为垃圾。
所有对象在被回收之前都会自动调用finalize()方法。
一个对象在成为垃圾之后不会被马上回收,JVM会检测内存中的垃圾堆积到一定程度时才会回收,如果我们不想等到这个时候才回收,可以使用System.gc()方法来通知虚拟机回收垃圾。调用该方法之后JVM会开启新线程做处理垃圾的工作,这需要一定时间。
/* 对象成为垃圾之后不会马上被回收掉的. 是虚拟机中垃圾占用空间到达一定数量才回收. 如果我们向立即销毁内存中的垃圾, 可以调用System.gc()方法 */ class GcDemo { public static void main(String[] args) throws Exception{ for(int i = 0; i < 1000; i++) new Person(); System.gc(); // 通知虚拟机销毁垃圾, 需要一点时间
Thread.sleep(1); // 控制当前线程休眠, 单位为毫秒 System.out.println(Person.i); } }
class Person{
static int i; Person(){ i++; }
public void finalize(){ // 对象在被回收之前, 会调用finalize()方法 i--; } }
9. 单态设计模式(SingletonPattern)
什么是设计模式
在编程过程中我们经常会遇到一些典型的问题或需要完成某种特定需求,而这些问题和需求前人也曾经遇到过,他们经过大量理论总结和实践验证之后优选出的代码结构、编程风格、以及解决问题的思考方式,这就是设计模式(Design pattern)。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免得我们自己再去思考和摸索。
单态(单例)设计模式
单态设计模式(Singleton pattern)就是要保证在整个程序中某个类只能存在一个对象,这个类不能再创建第二个对象。
单态设计模式的写法
私有化构造函数,阻止创建新对象。
由于需要返回一个对象,那么我们就需要在类内部自己创建一个对象,并使用成员变量记住它。
由于该类不能创建对象,所以这个成员变量不能是普通的成员变量,需要静态,这样在类加载之后就可以创建一个唯一的对象了。
我们不希望其他类修改这个成员变量,所以将其私有。
提供一个公有的方法用来获取唯一的一个对象。
这个方法由于需要在不创建对象的情况下使用,所以需要静态。
/* 在我们设计一个类的时候, 希望这个类只能创建一个对象, 那么就可以使用单态设计模式. */ class SingletonDemo{ public static void main(String[] args){ /* Teacher t1 = Teacher.getInstance(); Teacher t2 = Teacher.getInstance(); System.out.println(t1); System.out.println(t2); System.out.println(t1 == t2); */ System.out.println(Teacher.s); // 在这种情况下, 我们没有使用Teacher对象, 但是也会创建 } }
// 延迟加载的单态设计模式, 只有在真正获取对象的时候才创建对象. 目前这个版本在多线程并发的时候有可能出问题. class LazyTeacher { private static LazyTeacher t; private LazyTeacher(){ } public static LazyTeacher getInstance(){ if(t == null) t = new LazyTeacher(); return t; } }
class Teacher { // 2.在类内部创建一个对象, 使用成员变量引用. 为了不让类外部对其进行修改, 将其私有化. private static Teacher t = new Teacher(); public static String s = "XXX"; // 1.将构造函数私有化, 用private修饰, 这时类外部就不能再创建对象了 private Teacher(){ } // 3.提供一个公有的方法, 获取该对象 public static Teacher getInstance(){ return t; } }
10. 继承(Inherit)
什么是继承
在程序中,可以使用extends关键字让一个类继承另外一个类。
继承的类为子类(派生类),被继承的类为父类(超类, 基类)。
子类会自动继承父类所有的方法和属性。
为什么要使用继承
当我们发现一个类的功能不行,方法不够用时,就可以派生子类,增加方法。
当我们需要定义一个能实现某项特殊功能的类时,就可以使用继承。
最终还是为了一个目的,实现代码的复用性。
当我们定义一个类时,发现另一个类的功能这个类都需要,而这个类又要增加一些新功能时,就可以使用extends关键字继承那个类,这样那个被继承类的功能就都有了,不必重写编写代码。这时只要在新的类中编写新的功能即可,原有代码就被复用了。
class InheritDemo1 { public static void main(String[] args){ /* Person p = new Person(); p.name = "张三"; p.run(); Student s = new Student(); s.name = "李四"; s.run(); s.study(); */ /* Driver d = new Driver(); d.name = "王五"; d.run(); d.study(); d.drive(); System.out.println(d.hashCode()); */ /* Student s = new Student(); s.name = "张三"; s.run(); s.study(); System.out.println(s.hashCode()); */ new Driver().run(); }
}
class Person extends Object { String name; void run(){ System.out.println(name + "跑步三圈(Person)"); } }
class Student extends Person { // Student使用extends关键字继承Person类, 会继承其所有成员 void study(){ System.out.println(name + "学习"); } void run(){ System.out.println(name + "跑步三圈(Student)"); } }
class Driver extends Student { void drive(){ System.out.println(name + "开车"); } }
class Cat { String name; void run(){ System.out.println(name + "小猫跑步"); } }
继承的特点
Java只支持单继承,不支持多继承,但是可以多重继承
因为如果一个类继承多个类,多个类中有相同的方法,子类调用该方法时就不知道该调用哪一个类中的方法了。
向上转型
把一个子类当做父类来用是可以的,因为父类有的子类都有
把一个父类当做子类来用就不可以了,因为子类有的父类不一定有
可以定义一个父类类型的变量来记住子类对象,这在程序中称之为向上转型
强制类型转换
把一个子类当做父类来用的时候,不能调用子类特有方法。
因为编译时编译器会做语法检查,看到变量是父类类型那么就会到父类中查找是否有该方法,没有则报错。
这种情况下,就需要强制类型转换,将父类类型强转成子类类型。
以(子类名)变量名形式进行强制类型转换
强制类型转换时,无论类型是否匹配编译都不会报错,但如果类型不匹配运行会报错,我们可以使用instanceof进行判断,编译时预知错误。
在子类当做父类来用时,不能调用特有方法,如果一定要调用,就需要强制类型转换回子类。在做转换时最好instanceof判断一下类型是否匹配。
/* 向上转型, 子类可以当做父类来用, 因为父类有的功能(方法)子类都有 在一个父类变量引用子类对象的时候, 用父类变量调用方法, 这时先访问的子类的方法, 如果子类没有该方法才调用父类的 父类变量引用子类对象时, 不能调用子类的特有方法, 编译器编译时会进行语法检查, 父类没有的方法则不能调用 强制类型转换, 可以将一个父类类型引用的子类对象, 转回子类类型. 如果父类变量中存储的不是子类类型, 那么运行时会抛出异常 ClassCastException, 但编译不会报错 为了避免这种运行时的错误, 我们在强制类型转换之前通常都会进行instanceof判断 */ class InheritDemo2 { public static void main(String[] args){ /* Typist t = new Coder(); // 向上转型 t.type(); Coder c = (Coder)t; // 强制类型转换 c.code(); */ /* Typist t = new Typist(); t.type(); Coder c = (Coder)t; c.code(); */ /* Typist t1 = new Typist(); Typist t2 = new Coder(); System.out.println(t1 instanceof Coder); // 判断是否是制定类的实例 System.out.println(t2 instanceof Coder); */ /* Typist t = new Coder(); t.type(); if(t instanceof Coder){ Coder c = (Coder)t; c.code(); }else System.out.println("这不是一个程序员, 不能编程"); */ work(new Typist()); } public static void work(Typist t){ t.type(); t.type(); t.type(); } }
class Typist { // 打字员 void type(){ System.out.println("打字员打字"); } }
class Coder extends Typist { // 程序员 void type(){ System.out.println("程序员打字"); } void code(){ System.out.println("编程"); } }
class Student extends Typist { void type(){ System.out.println("学生打字"); } }
子类覆盖(Override)父类方法
覆盖方法必须和被覆盖方法具有相同的方法名称、参数列表和返回值类型。
子类的方法返回值类型可以是父类方法返回值类型的子类。
如果在子类中想调用父类中的那个被覆盖的方法,我们可以用super.方法的格式。
如果直接调用方法,是在当前子类中先查找,如果子类有会调用子类的。使用super形式只在父类中查找,子类有没有都不调用。
覆盖方法时,不能使用比父类中被覆盖的方法更严格的访问权限。
因为有可能将子类对象当做父类对象来使用,那么能获取到的父类对象中的方法在子类中必须都能获取到。
覆盖方法时,不能比父类抛出更多的异常。
子类只能比父类强,不能比父类弱。
重载(Overload)和重写(Override)的区别:
重载是方法名相同,参数列表不同,和返回值类型无关。
重写是方法名、参数列表、返回值类型全相同。
@Override 注解,可以检查覆盖是否成功
/* 重载Overload: 两个函数的函数名相同, 但参数列表不同. 重写Override: 子类继承父类时, 将父类的方法覆盖, 返回值类型, 函数名, 参数列表全部相同. 子类覆盖父类的方法时, 返回值类型必须一致, 因为父类的功能子类必须都有 子类覆盖父类的方法时, 访问权限不能更严格 子类覆盖父类的方法时, 不能抛出更多的异常 子类必须拥有父类所有的功能, 只能更强大, 不能更弱. 因为子类有可能被当做父类来用. 访问权限: public 公有 所有类都可以使用 protected 保护 当前包中的类, 或者子类 default 默认 当前包中的类 private 私有 只有当前类可以使用 */ class OverrideDemo { public static void main(String[] args){ // System.out.println(getArea(10)); Student s = new Student(); s.run(); } // 计算矩形面积 public static int getArea(int a, int b){ return a * b; } public static double getArea(int r){ return 3.14 * r * r; } }
class Person { public void run(){ System.out.println("Person 跑步"); } }
class Student extends Person { @Override // 检查覆盖是否成功, 如果该方法不是覆盖父类方法, 则会报错 public void run(){ System.out.println("热身"); System.out.println("Student 跑步"); } void eat(){ System.out.println("吃饭"); super.run(); // 调用父类方法 } }
继承的应用细节
子类不继承父类私有成员
父类中私有成员对外不可见,子类对象中无法访问这些成员。
构造函数不被继承
构造函数通常用来初始化类的成员变量,父类和子类的成员变量不同,初始化方式也不同,构造函数的名字也不同。
/* 子类当做父类来用时, 父类变量引用子类对象, 这是访问方法是访问子类方法, 访问属性是访问父类属性. 通过一个变量访问一个对象的方法时, 会先找当前地址所引用的对象的方法. 这种机制叫做"动态分配" 而这种机制没有被应用在类的属性上, 因为Java语言希望我们将所有的属性私有化, 通过get和set方法访问. 通常我们在正常编写代码的时候所有属性都是私有的, 都会写get和set方法, 不会遇到这种问题. */ class InheritDemo3 { public static void main(String[] args){ Typist t = new Coder(); System.out.println(t.name); // 这样会找父类 System.out.println(t.getName()); // 这样是找子类自己的 t.type(); } }
class Typist { public String name = "打字员"; public void type(){ System.out.println("打字员打字"); } public String getName(){ return name; } }
class Coder extends Typist { public String name = "程序员"; public void type(){ System.out.println("程序员打字"); } public String getName(){ return name; } }
子类对象实例化过程
子类构造函数中可以使用super关键字调用父类构造函数。
在子类创建对象时一定会调用父类构造函数。即使没有显式调用,也会默认调用父类无参构造函数。
在子类中第一行用this关键字去调其他的构造方法,这时系统将不再自动调父类的。但其他构造函数中会调用父类构造函数。
在构造方法中this和super关键字只能出现一次,而且必须是第一个语句。
以后在设计类的时候,最好定义一个无参的构造方法,不然子类实例化的时候就容易出错。
子类当做父类使用时需要注意
当我们在调用某个类的一个方法时,此方法声明需要一个父类对象,这时我们可以将一个子类对象作为实参传递过去,注意此时方法定义的形参为父类,在方法中使用父类变量调用方法时,其实是调用子类的方法。
思考:上述情形下,在方法中用父类变量访问属性,访问的是子类还是父类的属性 ?
在把子类当做父类来用时,使用父类变量访问方法,访问的是子类的方法,因为虚拟机会找到变量引用的地址,根据这个地址来访问方法,这叫动态分配。
这种机制没有被使用到类的成员变量上,如果用父类变量访问属性,那么会直接找到父类的属性,不会看地址是哪个对象。
/* 在类加载后就可以使用 可以通过"类名.方法名"调用 不能使用外部非静态成员
如果我们定义一个类时, 某个方法不需要访问该类非静态成员, 那么就可以定义为静态的, 这样别人不用创建对象就能使用.
Arrays.sort() Arrays.toString() System.arraycopy() Integer.parseInt()
通常一些工具方法都是静态的, 为了方便他人使用, 无需创建对象, 直接调用方法即可
*/ class StaticMethod { public static void main(String[] args){ Person.run();
/* Person p = new Person(); p.name = "张三"; p.run(); */ } }
class Person { String name; // 创建对象时才初始化, 才能使用 static void run(){ System.out.println("跑步"); } void eat(){ // 必须创建对象之后才能调用该方法 System.out.println("吃饭"); } }
------------ <a href="http://edu.csdn.net/heima" target="blank">android培训</a>、<a href="http://edu.csdn.net/heima" target="blank">java培训</a>、期待与您交流! -----------
面向对象 第一部分
最新推荐文章于 2024-09-17 17:33:22 发布