目录
1. 局部变量和成员变量
局部变量 | 成员变量 | |
位置 | 方法中或者{ }中 | 类中 |
内存中的位置 | 栈内存的方法中 | 堆内存的对象中 |
生命周期 | 随着方法的运行而出现在栈中;随着方法的弹栈而消失 | 随着对象的出现而出现在堆中;随着对象的消失而从堆中消失 |
初始化 | 无默认初始值; 需手动赋值才可用 | 可不用初始化 所有默认的初始化 |
2. 参数传递
2.1 基本数据类型作为参数传递
class Demo{
public static void main(String[] args){
int x=4;
show(x);
System.out.println("x="+x);
}
public static void show(int x){
x=5;
}
}
运行结果:
分析:
将基本类型变量来那个x空间中的值复制了一份传递给调用的方法show();
在show()方法中的x接收到了复制的值,再对x进行操作,只会影响到show中的x;
当show()执行完弹栈后,程序又回到main()执行;
main()中的x还是原来的值。
2.2 引用类型作为参数传递
public class DemoTest {
int x;
public static void main(String[] args){
DemoTest d=new DemoTest();
d.x=5;
show(d);
System.out.println("x="+d.x);
}
public static void show(DemoTest d){
d.x=5;
}
}
运行结果:
分析:
将引用变量空间中的内存地址(引用)复制了一份传递给了show()的d引用变量;
这时两个引用同时指向堆中的同一个对象
当执行了shou()中的d.x=6时,会根据d所持有的引用找到堆中的对象,并将其x属性的值改为6,show()弹栈。
由于两个引用指向同一个对象,不管那个引用改变了引用所指向的对象中的值,其他引用再次使用都是改变后的值。
3. 面向对象的三个特征
3.1 封装
- 表现:方法就是一个最基本的封装体,类也是一个封装体,用private修饰的变量也是封装体;
- 好处:提高了代码的复用性;隐藏了具体实现细节,需对外提供可访问的方式(setter()/getter());提高了安全性
3.2 继承
通过继承可以是多种事物之间形成一种关系体系。
关键字:extends
子类在继承父类后,会自动拥有的父类的成员
- 好处:提高了代码的复用率,提高软件开发率;让类与类之间出现了关系提供了多态的前提
注意:
- Java中只支持单继承;
- 多个类可以继承一个类;
- Java中可多层继承;
- Java中,子类和父类是一种相对的概念。
成员变量
当子父类出现同名成员变量时,若子类要访问父类中的成员变量,需使用关键字super;
- super表示当前对象中包含的父类对象空间的引用。
当在程序中通过对象调用方法时:
- 会先在子类中查找有没有对应的方法;
- 若子类中存在,则执行子类中的方法;
- 若子类中没有,则执行父类中响应的方法。
成员方法
override:重写、覆盖
- 子类中出现与父类一模一样的方法
- 用@Override标注
- 子类覆盖父类的方法必须保证权限大于等于父类权限
- 必须一模一样:方法名、参数列表
- 弊端:类和类之间耦合度过高
- 优点:提高改吗重用性,可维护性,是多态前提之一
所有的类都直接或间接继承了Object类
overload:重载
- 在同一个类中,多个方法名称相同,参数列表(个数、数据类型)不同。
3.3 多态
表示当同一个操作作用在不同对象上时,会有不同的语义,从而会产生不同的结果。
最终多态体现为:父类引用可指向子类对象
前提
- 必须有子父类关系或者类实现接口的关系
- 方法的重写
- 父类引用指向子类对象
在使用多态后的父类引用变量调用方法时,会调用子类重写后的方法。(若多个子类重写,则调用的是各个子类自己重写的方法(动态绑定))
表现方式
- 方法的重载(overload):同一个类中有多个同名的方法,但这些方法有着不同的参数(个数、类型、顺序),在编译时可以确定到底调用哪一个方法——编译时多态。重载可看做是一个类中的方法多态性。
- 方法的覆盖(override):子类可以覆盖父类的方法,因此同样的方法在子类和父类中会有着不同的表现形式。
- 基类的引用变量不止可以指向基类的实例对象,还可以指向子类的实例对象。
- 接口的引用变量也可以指向其实现类的实例对象。
- 程序运行的方法在运行期才动态绑定(绑定:将一个方法调用和一个方法主体连接到一起 )。
- 引用变量所指向的具体实例对象(内存里正在运行的那个对象)的方法,而不是引用变量的类型中定义的方法。
- 通过这种动态绑定的方法实现了多态。
- 只有在运行时再能确定调用那个方法,所以这个多态叫做——运行时多态
class Base{
public Base() {
g();
}
public void f() {
System.out.println("Base f()");
}
public void g() {
System.out.println("Base g()");
}
}
class Derived extends Base{
public void f() {
System.out.println("Derived f()");
}
public void g() {
System.out.println("Derived g()");
}
}
public class DBTest{
public static void main(String[] args) {
Base b=new Derived();
b.f();
b.g();
}
}
运行结果
Base b=new Derived();会调用Base类的构造方法。而在Base类的构造方法中执行了g()。由于多态,此时会调用子类Deriverd的g(),而非Base类的g(),因此会输出第一个Derived g()。
定义格式
父类 变量名 = new 子类();
抽象类 变量名 = new 抽象类子类();
接口 变量名 = new 接口实现类();
当子父类出现同名的成员变量时,多态调用该变量时,编译运行看左边(父类);
- Dad d=new Son();
- d.num; 调用的是父类的成员变量
- 因为Java中成员变量没有重写的概念。
class Base{
public int i=1;
}
class Derived extends Base{
public int i=2;
}
public class DBTest{
public static void main(String[] args) {
Base b=new Derived();
System.out.println(b.i);
}
}
运行结果
1
当子父类出现同名的成员方法时,多态调用该方法时,编译看左边,运行看右边(子类);
- 编译时会检查左边父类中有无对应的成员
当子父类出现同名的静态方法时,多态调用该方法,编译运行看左边(父类)(相当于类名调用,所以也是看父类);
- 都看左边
缺点
无法直接访问子类的特有成员,需向下转型。
优点
继承的优点(可维护性);
可扩展性。
4. this关键字
可在成员变量上加上this,来区别成员变量和局部变量
class Person{
private int age;
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age=age;
}
}
public class DemoTest{
public static void main(String[] args) {
Person p=new Person();
p.setAge(30);
System.out.println(p.getAge());
}
}
运行结果:
分析:
- 先执行main(),压栈,执行其中的Person p=new Person();
- 在堆内存中开辟空间,并为其分配内存地址0x1234,成员变量默认初始化(age=0);
- 执行p.setAge(30);
- 调用setAge(int age),将30赋值给setAge()中的age变量
- 执行this.age=age,将age变量的值30赋值给成员变量this.age;
- setAge()执行完,弹栈,回到main()执行输出语句System.out.println(),输出p对象中的age值。
若局部变量名和成员变量名相同,在使用时采用的是就近原则。
通过this实现构造方法的相互引用
this(参数列表)
package ObjectOriented;
class Person{
private int age;
private String name;
Person(){
}
Person(String nm){
name=nm;
}
Person(int a,String nm){
this(nm);
age=a;
}
}
public class DemoTest{
public static void main(String[] args) {
Person p=new Person(30,"张三");
}
}
分析:
- 先执行main(),压栈,执行其中的new Person(30,"张三");
- 堆内存中开辟空间,并未其分类内存地址0x33,成员变量默认初始化(name=null,age=0);
- 拥有两个参数的构造方法(Person(int,String))压栈,在这个构造方法中有一个隐式的this,指向堆中的那个对象
- 因为构造方法是给对象初始化的,那个对象调用这个构造方法
- 由于Person(int,String)中使用了this(nm),构造方法Person(String)压栈,并将“张三”传给nm。
- 在Person(String)同样也有this执行0x33.执行name=nm,将“张三”赋值给成员name。
- Person(String)弹栈
- Person(int,String)将30赋值给成员age,弹栈;
- Person对象在内存中构建完成,并将0x33赋值给main()中的p引用变量。
5. super关键字
子类中的所有构造方法的第一行由默认的隐式的super();语句;
- 原因:因为子类继承了父类的内容,所以创建对象时,必须先要看父类是如何对其内容进行初始化的
当父类中没有空参构造方法时,子类的构造方法必须有显示的super语句,指定要访问的父类有参的构造方法;
若第一行用this调用了本类的其他构造方法,则此时无super();
- 因为它们都在第一行,初始化动作要先执行
- super,this不共存
父类构造方法中也有隐式的super
- 只要是构造方法,第一行默认都是super();
父类的父类是Object,它是所有对象的父类。
由于有super()调用父类无参构造方法,所以父类的构造方法既可以给自己的对象初始化,也可以给子类的对象初始化化
super(arg);——也可以有参数。
如果子类构造方法的第一行没有调用父类的构造方法,则会默认调用副类的无参构造(有的话),也可用super(arg)在子类构造方法中的第一行显式调用父类的有参构造。
父类的构造方法会先执行,因为先初始化父类中的成员变量,自诶后面可能要用到。
- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。
- 访问父类的成员:如果子类重写了父类的中某个方法的实现,可以通过使用super 关键字来引用父类的方法实现。