日报 18/06/18
复习:
构造方法是一种特殊的方法 他是一个与类同名 且没有返回值类型的方法
对象的创建就是通过无参构造来完成
public class ExceptionTest {
/**
* 生活中的异常:---》摔跤
* 下楼梯的时候,我也不想摔跤,但是确实摔了!
* 然后呢??难道后半辈子就不过了??
* 之后的生活肯定还得继续!!!
*
* 程序中的异常 : 在程序运行期间,发生了不正常的事件(代码的问题),中断了程序执行!
* 从出现异常的地方,之后的代码都不会执行!
* 显然不符合我们的需求!
* 我们的需求---》继续执行后续的代码!
* 怎么执行后续的代码?
* 使用异常处理机制:
* 给程序提供了一种处理错误的能力! 出现异常之后,程序还能运行!
*
*
* int num = 5.0; 这行代码不能编译成功! 5.0是double类型 不能自动转换成int
* Type mismatch: cannot convert from double to int
* 这时候出现的问题,我们称之为 编译时异常!
*
*
*
* 异常处理机制中:常用的五个关键字!
* 1.try:
01.把可能会出现异常的代码块 放在try中 程序就可以继续执行
02.try不可以单独使用!必须和catch/finally 联合使用
03.try代码块中声明的变量,仅限于本代码块中!
* 2.catch:
* 01.捕捉try代码块中出现的异常信息
* 02.如果在catch的参数中写的是具体的某个异常
* 那么catch块只能捕获这一个具体的异常
* 03.如果想同时捕捉多个异常,我们可以书写多个catch代码块
* 04.多个catch并存的时候!有书写顺序! 类型必须是 由小到大!
* 父类异常只能放在最后!
* 3.finally:
* 01.无论如何都要执行的代码块
* 02.System.exit(0); 正常退出
* 如果参数是非0!代表异常退出!
*/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
System.out.println("请您输入第一个数字:");
int num1 = scanner.nextInt();
System.out.println("请您输入第二个数字:");
int num2 = scanner.nextInt();
/**
*java.lang.ArithmeticException: / by zero 算术异常
*/
System.out.println("两个数字的商是:" + (num1 / num2));
} catch (ArithmeticException e) {
System.out.println("除数不能为0!");
System.exit(0); // 正常退出
} catch (InputMismatchException e) {
System.out.println("亲!输入格式有误!");
} catch (Exception e) {
e.printStackTrace(); // 打印出捕获到的异常信息
} finally {
System.out.println("这是finally代码块!");
}
System.out.println("程序结束");
}
}
/**
*学生的实体类
*
*4.throw
* 01.抛出异常
* 02.必须位于方法体内
* 03.只能抛出一个异常
*5.throws
* 01.声明异常 出现在声明方法的后边(参数列表之后)
* 02.可以有N个
* 03.多个异常之间使用,隔开
*
*/
public class Student {
private String name; // 姓名
private String sex; // 性别
private int age; // 年龄
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
/**
* 如果年龄不在0-150之间 我们就抛出异常
* @param age 用户传递过来的参数
* @throws Exception 声明的异常
* 01.声明的异常类 必须是抛出的异常类 或者其父类
* 02.小例子:
* 比如说,我们去买一辆自行车!
* 本身整个车都有问题,然后卖家说 之后车轮有毛病! 不行!
* 本身车轮有毛病! 卖家说车有毛病! 行!
* 整个车以及车轮 我们理解成 抛出异常!
* 卖家说的 都是 声明的异常!
*
* 03.别人都给我们说有毛病了!
* 然后我们必须去解决这个问题!
*/
public void setAge(int age) throws Exception {
if (age < 0 || age > 150) {
// 抛出异常
throw new Exception("年龄不合法!");
}
this.age = age;
}
public Student(String name, String sex, int age) {
super();
this.name = name;
this.sex = sex;
this.age = age;
}
public Student() {
super();
}
// 测试方法
public static void main(String[] args) {
// 创建学生对象
Student stu = new Student();
// 给对象的 属性赋值
try {
stu.setAge(-100);// 不健康的输入
} catch (Exception e) {
e.printStackTrace();
}
/**
* 对于我们调用的方法 有异常需要处理!
* 两种方式:
* 01.我们自己使用异常处理机制解决
* 02.继续抛出异常,最后是交给了JVM来处理!
*/
}
}
/**
*学生的实体类
*
*/
public class Student {
private String name; // 姓名
private String sex; // 性别
private int age; // 年龄
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
/**
* 性别只能是男和女
* @param sex 性别
* @throws StudentException
*/
public void setSex(String sex) throws StudentException {
if (sex.equals("男") || sex.equals("女")) {
this.sex = sex;
} else {
throw new StudentException("性别输入异常!");
}
}
public int getAge() {
return age;
}
/**
* 使用我们自定义的异常类 来处理
* @throws StudentException
*/
public void setAge(int age) throws StudentException {
if (age < 0 || age > 150) {
throw new StudentException("年龄异常!");
}
}
public Student(String name, String sex, int age) {
super();
this.name = name;
this.sex = sex;
this.age = age;
}
public Student() {
super();
}
// 测试方法
public static void main(String[] args) {
// 创建学生对象
Student stu = new Student();
try {
stu.setAge(-90);
stu.setSex("小人");
} catch (StudentException e) {
e.printStackTrace();
}
}
}
/**
* 自定义异常类 ----》针对于Student
* 01.继承RunTimeException
* 02.继承Exception
* 03.继承Throwable
*/
public class StudentException extends Exception {
// 给用户一个提示信息
public StudentException(String msg) {
super(msg);
}
public StudentException() {
}
}
public class ExceptionTest {
/**
* 面试题:
* 存在return的时候,finally的执行顺序!
* 01.首先会执行finally中的代码块
* 02.执行完毕之后再去执行return
*
*
* 想把某段代码块进行 try catch:快捷键
* 01.选中需要捕获的代码块
* 02.shift +alt +z
*
*/
public static void main(String[] args) {
try {
System.out.println(111);
System.out.println(222);
System.out.println(333);
return;
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("finally代码块!");
}
System.out.println("程序结束");
}
}
Java重写的7个规则
几年前你可能会遇到这样一个面试题:“重写和重载的区别”、而现在随着科技的更迭、面试的问题越来越高级、面试官的问题也越来越深入、此文是上述面试题的一个延伸、让你从简单的重写规则中更加深入的理解其软件工程与面向对象的思想。
1、重写规则之一:
访问修饰符的限制一定要不小于被重写方法的访问修饰符
比如:Object类有个toString()方法,开始重写这个方法的时候我们总容易忘记Public修饰符,出错的原因就是:没有加任何访问修饰符的方法具有包访问权限,Default访问权限小于Public访问权限,所以编译器出错。
2、重写规则之二:
参数列表必须与被重写方法的相同。
重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。
3、重写规则之三:
C-1:返回类型必须与被重写方法的返回类型相同。
父类方法A:void catch(){} 子类方法 B:int catch(){} 两者虽然参数相同,返回类型不同,所以不是重写。
父类方法A:int catch(){} 子类方法 B:long catch(){} 返回类型虽然兼容父类,但是不同就是不同,所以不是重写。
C-1補足1:如果在没有加注@Override的情况下,方法名和参数列表完全相同,且满足规则A的情况下,返回值类型必须完全一致的情况下、才不会出现编译错误(即为该方法为强制重写方法)。如果以上条件中参数列表不同,且返回值类型不同这样编译并不会出现错误(这个方法为在子类的新方法,且不是重写方法)。// 2016/11/21 19:44 Tata 追記标注補足1(下接博文后追記)
C-1補足2:当子类的方法重写或实现父类的方法时,方法的后置条件(即方法的返回值)要比父类更严格。[参照2]// 2016/11/22 17:56 Tata 追記
即:如果重写方法的参数列表和方法名相同、且其他条件满足的情况下、方法的返回值为父类的子类、那么该方法也为重写方法
- // C-1補足2の例を挙げります:
- package com.ibm.dietime1943.test;
- public class Computer {
- public Computer sale() { return new Computer(); }
- public HP make() { return new HP(); }
- }
- class IBM extends Computer {
- @Override
- public IBM sale() { return new IBM(); }
- }
- class HP extends Computer {
- @Override
- public Computer make() { return new Computer(); } // compilation error
- }
4、重写规则之四:
重写方法不能抛出新的异常或者比被重写方法声明的检查异常更广的检查异常。但是可以抛出更少,更有限或者不抛出异常。
举个简单的例子:父类异常好比父亲偷盗抢掠、那么儿子不能比父亲更坏、要学好、自然异常就要少。虽然举得例子与社会主义核心价值观有冲突、但是比较形象。// 2016/12/10 10:55 Meta 追記 add
5、重写规则之五:
如果一个方法不能被继承,则不能重写它。
比较典型的就是父类的Private方法。因为Private说明该方法对子类是不可见的,子类再写一个同名的方法并不是对父类方法进行复写(Override),而是重新生成一个新的方法,也就不存在多态的问题了。同理也可以解释final,因为方法同样是不可覆盖的。
6、重写规则之六:
不能重写被标识为final的方法。
// 2016/12/01 17:05 Tata 追記 add Start
final方法可以被继承、但是不能被重写、一个方法如果被final修饰、那么也就意味着、这个方法不会被改动(声明一个final方法的主要目的是防止方法的内容被修改)。// 2016/12/01 17:12 Tata 追記 add End
7、重写规则之七:
静态方法不能被重写。
《JAVA编程思想》中多次的提到:方法是静态的、他的行为就不具有多态性。静态方法是与类、而非单个对象相关联的。
父类的普通方法可以被继承和重写,不多作解释,如果子类继承父类,而且子类没有重写父类的方法,但是子类会有从父类继承过来的方法。静态的方法可以被继承,但是不能重写。如果父类中有一个静态的方法,子类也有一个与其方法名,参数类型,参数个数都一样的方法,并且也有static关键字修饰,那么该子类的方法会把原来继承过来的父类的方法隐藏,而不是重写。通俗的讲就是父类的方法和子类的方法是两个没有关系的方法,具体调用哪一个方法是看是哪个对象的引用;这种父子类方法也不在存在多态的性质。《JAVA编程思想》:只有普通的方法调用可以是多态的,静态方法是与类而不是与某个对象相关联。
// 2016/11/22 16:45 Tata 追記 add Start
補足1:父类的静态方法不能被子类覆盖为非静态方法。
子类可以定义于父类的静态方法同名的静态方法、以便在子类中隐藏父类的静态方法(满足覆盖约束)、而且Java虚拟机把静态方法和所属的类绑定、而把实例方法和所属的实例绑定。
如果在上记的方法上追记@Override注解的话、该方法会出编译错误。应为该方法实际不是重写方法。
補足2:父类的非静态方法不能被子类覆盖为静态方法。
// 2016/11/22 16:45 Tata 追記 add End
補足3:面试可能会遇到的此处相关问题(与静态相关)
1、abstract方法能否被static修饰?
不能被因为抽象方法要被重写、而static和子类占不到边、即上述。// 2016/12/06 20:59 Meta 追記
2、为什么静态方法不能被覆盖?// 2016/12/15 午后 追記
可以参看上面从java编程思想摘出的话、另外在总结下:覆盖依赖于类的实例,而静态方法和类实例并没有什么关系。而且静态方法在编译时就已经确定,而方法覆盖是在运行时确定的(动态绑定)(也可以说是java多态体现在运行时、而static在编译时、与之相悖)。
3、构造方法能否被重写、为什么? // 2016/12/15 晚 追記
不能、构造方法是隐式的static方法、同问题2。其实这个问题回答切入点很多、首先构造方法无返回值、方法名必须和所在类名相同、这一点就必杀了子类无法重写父类构造方法。另外多态方面、重写是多态的一种提现方式、假设在子类重写了构造方法是成立的、那么子类何谈实例成父类。另外重要得一点、子类可以使用super();调用父类的构造方法、且必须放在子类构造方法内的第一行。 请参看另一篇博文: <<Super和this用法,对象的加载顺序>>
XX01、重写规则補足:
補足1:父类的抽象方法可以被子类通过两种途径覆盖(即实现和覆盖)。
補足2:父类的非抽象方法可以被覆盖为抽象方法[2]。
[2]子类必须为抽象类。
補足2の例を挙げります:
- package com.ibm.dietime1943.test;
- public class Computer {
- public Computer send() { return new Computer();}
- }
- abstract class Lenovo extends Computer {
- @Override
- public abstract Computer send();
- }
以上规则更加详细的说明请参看另一篇博文: <<JAVA中 @Override 的作用>>
// 2016/11/21 20:27 Tata 追記标注補足1(上接博文后追記)
举例(来源于OCJP题库):
- Given:
- 1. public class Blip {
- 2. protected int blipvert(int x) { return 0; }
- 3. }
- 4. class Vert extends Blip {
- 5. // insert code here
- 6. }
- Which five methods, inserted independently at line 5, will compile? (Choose five.)
- A. public int blipvert(int x) { return 0; }
- B. private int blipvert(int x) { return 0; }
- C. private int blipvert(long x) { return 0; }
- D. protected long blipvert(int x) { return 0; }
- E. protected int blipvert(long x) { return 0; }
- F. protected long blipvert(long x) { return 0; }
- G. protected long blipvert(int x, int y) { return 0; }
- Answer: A,C,E,F,G
Explanation:继承关系后,子类重写父类的方法时,修饰符作用域不能小于父类被重写方法,所以A正确,B不正确。选项CEFG均不满足重写规则,不是重写方法(在子类的普通方法)。选项D即为不满足C-1补足。
// 2016/11/22 11:08 Tata 追記補足2
里氏替换原则(リスコフの置換原則(りすこふのちかんげんそく)、英:Liskov Substitution Principle)
这项原则最早是在1987年、由麻省理工学院的由芭芭拉·利斯科夫(Barbara Liskov)在一次会议上名为“数据的抽象与层次”的演说中首先提出。
里氏替换原则的内容可以描述为: “派生类(子类)对象能够替换其基类(超类)对象被使用。” 以上内容并非利斯科夫的原文,而是译自罗伯特·马丁(Robert Martin)对原文的解读。其原文为:
Let q(x) be a property provable about objectsx of type T. Thenq(y) should be true for objectsy of typeS whereS is a subtype ofT.
严格的定义:如果对每一个类型为T1的对象o1、都有类型为T2的对象o2、使得以T1定义的所有程序P在所有的对象o1都换成o2时、程序P的行为没有变化、那么类型T2是类型T1的子类型。
通俗的定义:所有引用基类的地方必须能透明地使用其子类的对象。
更通俗的定义:子类可以扩展父类的功能,但不能改变父类原有的功能。
里氏替换原则包含以下4层含义:
1、子类可以实现父类的抽象方法、但是不能[1]覆盖父类的非抽象方法。(核心)[参照1]
在我们做系统设计时、经常会设计接口或抽象类、然后由子类来实现抽象方法、这里使用的其实就是里氏替换原则。子类可以实现父类的抽象方法很好理解、事实上子类也必须完全实现父类的抽象方法、哪怕写一个空方法、否则会编译报错。
里氏替换原则的关键点在于不能覆盖父类的非抽象方法。父类中凡是已经实现好的方法、实际上是在设定一系列的规范和契约、虽然它不强制要求所有的子类必须遵从这些规范、但是如果子类对这些非抽象方法任意修改、就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。
[1]处的说明:该处的不建议原则、并不是硬性规定无法不能的含义。增加新功能时、尽量添加新方法实现、而不是(不建议)去重写父类的方法、也不建议重载父类的方法。// 2016/11/22 15:33 Tata 追記
2、子类中可以增加自己特有的方法。
3、当子类重写或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
4、当子类的方法重写或实现父类的方法时,方法的后置条件(即方法的返回值)要比父类更严格。[参照2]
// 2016/11/22 18:54 Tata 追記 add Start - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
追记来源/Michael727(简书作者)。
原文链接:http://www.jianshu.com/p/2aa66a36af26
里氏替换原则的核心是抽象,抽象又依赖于继承这个特性,在OOP当中,继承的优缺点都相当的明显。
继承的优点:
①、代码重用,减少创建的成本,每个子类拥有父类的方法和属性。
②、子类和父类基本相似,但又与父类有所区别。
③、提高代码的可扩展性,实现父类的方法就可以了,很多开源框架的扩展接口都是通过继承父类完成的。
④、提高产品或项目的开放性。
继承的缺点:。
①、继承是侵入性的,只要继承就必须拥有父类的所有属性和方法。
②、可能造成子类代码冗余、灵活性降低,因为子类必须拥有父类的属性和方法。
③、增强了耦合性。当父类的常量、变量和方法被修改时,必须考虑子类的修改,而且在缺乏规范的环境下,这种修好可能带来非常糟糕的结果:大片的代码需要重构。