---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------
java的封装原因和好处
class Person {
private int age;
public void setAge(int a) {
if (a > 0 && a < 130) {
age = a;
speak();
} else
System.out.println("feifa age");
}
public int getAge() {
return age;
}
private void speak() {
System.out.println("age=" + age);
}
}
class PersonDemo {
public static void main(String[] args) {
Person p = new Person();
// p.age = -20;
p.setAge(-40);
// p.speak();
}
}
在PersonDemo类当中,直接操作new了一个Person,那么可以直接通过对象名.属性去给这个对象赋值,但是为什么这样不好,需要把类内部的属性封装起来,不然外部直接操作呢。这点一直不太理解,总有多此一举的感觉。因为封装了,通过set或get也一样外部能够操作和更改值,而且这样还比较麻烦。之前看视频的时候,说原因是可以添加检查,排除一些非法的输入,不怎么理解,现在理解了,如果设置一个set方法的话,那么方法体里面可以增加一些if语句去检查输入是否合乎常理,避免出现非法数据,这样程序就没意义,而暴露在外部的话,if语句不能添加这样的功能,区别就在这。
主函数的定义:
public:代表着该函数访问权限是最大的。
static:代表主函数在类的加载前就已经存在了。
void:主函数没有具体的返回值。
main:不是关键字,但是是一个特殊的单词,可以被jvm识别。
(String[] arr):函数的参数,参数类型是一个数组,该数组中的元素是字符串。字符串类型的数组。
jvm在调用主函数时,传入的是new String[0];
主函数是固定格式的:jvm识别。(除开args这个变量可以不一样之外,其余都必须一样)
class MainDemo {
public static void main(String[] args) {
main(1);
}
public static void main(int args)
{
System.out.println(args);
}
}
这个算不算重载呢,或者会不会发生重载?
那么到底虚拟机运行的时候会执行哪一个
主函数的格式是固定的,所以在一个类当中,虚拟机首先会查找存不存在main(String[] args),如果不存在,则不会启动这个类,上面的代码提示,,所以不算重载,程序也不能运行
当句子执行时候,虚拟机的内部的运行执行机制是如何的?
例:Person p1 = new Person("zhangsan" ,17);
运行时,
(1)从左到右执行,先在栈内存中为变量p1开辟一个空间
(2)因为new用到了person.class,所以虚拟机会先找到person.class这个文件加载进来
(3)首先进行的是静态初始化,为类进行初始化
(4)然后为对象在堆内存分配相应的空间,分配内存地址
(5) 在堆内存中建立对象的特有属性,然后进行默认初始化
(6)对象内部的成员属性开始显式初始化
(7)对象开始执行构造代码块,为对象初始化
(8)构造函数开始执行,为特定对象初始化
(9)把该对象的堆内存地址传给栈内存中的p1
继承extends
被final修饰的类不能被继承
不是父类中所有方法都被继承,构造方法不能继承
父类的私有属性不能被继承(实际上是继承了,只是不能被子类直接使用)
如果子类中存在与父类同名的属性或者方法时,在子类创建对象的时候,默认调用的是子类中的属性和方法,但是如果想要调用父类的属性或者方法,可以通过super()关键字
Instancedof关键字:
判断一个对象到底属于哪个类的实例
我的理解:我是不是你产生的
格式:对象 instanceof 类 返回值是boolean型
假设存在class A,存在class B extends A
即A是B 的父类
A a1 = new B();
System.out.println(a1instanceof A);
System.out.println(a1instanceof B);
A a2 = new A();
System.out.println(a2instanceof A);
System.out.println(a2instanceof B);
输出为:
True
True
False
False
(1)多态的体现
父类的引用指向了自己的子类对象
换句话说,父类的引用也可以接收自己的子类对象
语法实现形式:Animal a = new cat();
(2)
多态的前提,什么时候使用多态
必须是类与类之间有关系,要么继承,要么实现,通常可以把多个类的共同属性封装好,然后再去进行继承,继而实现多态
还有一个大前提,就是覆盖
class Cat
{
public void eat()
{
System.out.println("吃鱼");
}
public void catchMouse()
{
System.out.println("抓老鼠");
}
}
class Dog
{
public void eat()
{
System.out.println("吃骨头");
}
public void kanJia()
{
System.out.println("看家");
}
}
// 观察以上两个类,作为动物,都具有吃的本能,所以这个功能可以抽取出来,封装成抽象类,然后再进行继承
abstract class classAnimal
{
abstract void eat();
}
(3)多态的好处
多态的出现大大提高了程序的扩展性
(4)多态的应用
(5)多态的弊端:多态有好处也有局限性
提高了扩展性的同时,弊端在于只能使用父类的引用访问父类中的成员
abstract class Animal
{
abstractvoid eat();//抽象方法不用实现,也没有大括号
}
抽象类作为父类,里面只有一个成员函数,局限性就在此体现,不能为别的动物提供特有的个性化的成员函数
可以说优点因为这个,缺点也是因为这个
类里面的方法如果不确定的话,就定义为抽象类,由使用者自己去覆写实现
多态的编译和运行的结果情况分析
class Fu
{
static int num = 5;
static void method1()
{
System.out.println("fumethod_1");
}
static void method2()
{
System.out.println("fumethod_2");
}
static void method4()
{
System.out.println("fumethod_4");
}
}
class Zi extends Fu {
static int num = 8;
static void method1()
{
System.out.println("zimethod_1");
}
static void method3()
{
System.out.println("zimethod_3");
}
static void method4()
{
System.out.println("zimethod_4");
}
}
class DuoTaiDemo4
{
public static void main(String[] args)
{
Fu f = new Zi();// 用父类的引用接收子类对象
f.method1();
f.method2();
f.method3();
}
}
此时编译应该是不通过的,提示错误f.method3();找不到
那是为什么会出现这个情况呢(重点理解记忆)
应该要从编译的机制解释, java虚拟机编译的时候,语法检查首先会在f他的Fu类中查找method1();method2();method3();这三个方法,如果都存在,并且语法没有错误,则编译通过,但是在实际运行时,再检查类中的方法有没有被static修饰,如果被static修饰,(假设编译通过的情况下)调用的是父类中的method1();method3();,如果没有被static修饰,调用的是子类Zi中的method1();method3();而不是父类中的
所以这一点应该是有所区别的,我们在思考的时候应该注意区分编译和运行时代码的情况
重点注意,关于向上转型和向下转型:
在对对象发生向下转型的时候,必须是已经发生了向上转型的前提下,才可以向下转型,否则编译会报错。打个比方,一个对象好比一个人,转型就好比上楼梯,刚开始都是在平地上,我必须上了一级楼梯,我才能往下走一级楼梯,在平地上是不能再往下的了
错误的写法:
假设A是B的父类
A a = new A();
B b = (B)a;
此时编译报错
正确的写法:
A a = new B();//必须先存在向上转型,才可以向下转型
B b = (B)a;
在多态中成员函数的特点:(需要区分静态函数和非静态函数)
在编译时期:参阅引用型变量所属的类中是否有调用的方法如果有,编译通过,如果没有编译失败。
在运行时期:参阅对象所属的类中是否有调用的方法
简单总结就是:静态成员函数在多态调用时,编译看左边,运行时看左边。
非静态成员函数在多态调用时,编译看左边,运行时看右边。
以上总结只正对类中的方法来说,如果是针对属性变量,下面再讲解
class Fu
{
static int num = 5;
}
class Zi extends Fu
{
static int num = 8;
}
class DuoTaiDemo4
{
public static void main(String[] args)
{
Fu f = new Zi();// 父类引用接收子类对象
System.out.println(f.num);// 输出结果为父类的nun值
Zi z = new Zi();
System.out.println(z.num);
}
}
这个的输出是:
5
8
其实这里是多态需要注意一下的地方
准确来说,继承后子类实现要分情况讨论,一般是指没有发生转型的情况下,子类引用接收子类对象,如今发生了向上转型,父类引用接收子类对象,那么在编译和运行的时候,因为左右不统一了,所以要参考左边的类所属变量,这个是比较特殊的情况
结论:
在多态中,成员变量的特点:
无论编译和运行,都参考左边(其实静态和非静态都适用)。
在多态中,静态成员函数的特点:(区分静态和非静态)
静态:编译时期,检查参考左边引用变量的类,在运行阶段,还是访问左边
非静态:编译时期,检查参考左边引用变量的类,但在运行阶段,访问的实际上是右边对象中的方法
java中的继承中的问题,不支持多继承,但是支持多层继承,两者有什么区别呢,下面代码表示的是多继承,但是会出问题
class A
{
void show()
{
System.out.println("a");
}
}
class B
{
void show()
{
System.out.println("b");
}
}
class C extends A,B
{}
C c = new C();
c.show();
//如果java允许支持这个多继承的话,上面的代码会让虚拟机出错,因为并不知道C中的show()是继承哪一个类,所以会报错
//下面说到多层继承,把以上代码修改一下
class A
{
void show()
{
System.out.println("a");
}
}
class B extends A
{
void mianPrint()
{
System.out.println("b");
}
}
class C extends B
{}
C c = new C();
c.show();
这个比较好理解,B继承了A,那么B里面具有A的功能,接着C继承了B,那么C里面也就有了B的内容,那么这个继承里面就相当于有了三层,可以理解为C直接继承了B间接继承了A
这个举个例子会比较好理解,一个人只能有一个生物学父亲,不可能同时有几个生物学上的父亲,但是,你父亲也有自己的父亲,也就是你爷爷,你父亲继承你爷爷,然后你继承你父亲,这就是多层继承
事物间的关系
除开继承之外,还有聚合和组合
聚合:学生是班级的一部分
组合:手是身体的一部分
两者在逻辑上并没有严格的区分,但是两者在联系程度上组合比聚合要紧密地多,有不可分割的意思
重写(覆盖)
当子类继承父类,沿袭了父类的功能,到子类中,
但是子类虽具备该功能,但是功能的内容却和父类不一致,
这时,没有必要定义新功能,而是使用覆盖特殊,保留父类的功能定义,并重写功能内容。
覆盖:
(1)子类覆盖父类,必须保证子类权限大于等于父类权限,才可以覆盖,否则编译失败。
(2)静态只能覆盖静态,
重载:只看同名函数的参数列表。
重写:子父类方法要一模一样。(除开方法体内容以外)
子父类中的构造函数。
在对子类对象进行初始化时,会先行自动调用父类的构造函数,然后再调用子类的构造函数
那是因为子类的构造函数默认第一行有一条隐式的语句 super();
super():会访问父类中空参数的构造函数。
而且子类中所有的构造函数默认第一行都是super();
所以有个建议,如果要自定义一个父类,这个父类的构造函数是带参数的,然后用子类继承,那么最好给父类手动加上一个空参数的构造函数,否则,子类在初始化的时候可能会报错,因为子类在实例化时会自动执行父类中的空参构造函数
示例:
class Car
{
StringMyColor;
Car(){} //此处可能需要定义一个空参的构造方法,否则下面子类创建对象时可能会编译不通过
Car(StringMyColor) //有参数的构造方法
{
this.MyColor= MyColor;
}
publicvoid run()
{
System.out.println("模型开动");
}
}
class BaoMaCar extends Car
{
BaoMaCar(StringMyColor)
{
this.MyColor= MyColor;//如果用这一句,则对象初始化的时候会调用父类的空参构造方法,因为父类中已经定义了有参的构造方法,所以在编译的时候,编译器不会自动添加空参构造方法,所以必须要给父类定义一个空参的构造方法,否则编译报错
//super(MyColor);//可以如果用这一句,父类中可以没有空参的构造方法
}
publicvoid run()
{
System.out.println("宝马开动");
}
}
class MyCarDemo
{
publicstatic void main(String[] args)
{
Carc = retCar(1);
c.run();
}
publicstatic Car retCar(int num)
{
if(num==1)
returnnew BaoMaCar("白色宝马");
if(num==2)
returnnew BaoShiJieCar("红色保时捷");
elsereturn new Car("白色模型车");
}
}
为什么子类一定要访问父类中的构造函数。
因为父类中的数据子类可以直接获取。所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的。
所以子类在对象初始化时,要先访问一下父类中的构造函数。
如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。
注意:super语句一定定义在子类构造函数的第一行。
结论:
子类的所有的构造函数,默认都会访问父类中空参数的构造函数。
因为子类每一个构造函数内的第一行都有一句隐式super();
当父类中没有空参数的构造函数时,子类必须手动通过super语句形式来指定要访问父类中的构造函数。
当然,子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数。
子类中至少会有一个构造函数会访问父类中的构造函数。
---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------详细请查看:http://edu.youkuaiyun.com