对于java来说,最重要的就是面对对象,而如何体现这个,在其中三个概念极为重要,封装、继承、多态而无论考试还是面试通常都会考察这几个概念及其原理用法。
面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。在实际使用类和对象的过程中,会出现的几个问题,比如我们定义了一个猫和狗的类。它们有着共同的一些性质,或者说是行为。比如他们都是动物,都有名字,也都会吃饭。我们在描述它们时,就会将这些行为或者属性通过方法或者成员进行描述。这样我们就发现了他们,在某些情况下,代码是重复的。为了避免代码复写,就将一个类的共有属性进行封装。在出现这个类的子类时(什么是子类?比如“动物”他就父类,而“动物”下面会细分其他“动物”比如“狗”,那么“动物”就是“狗”的父类,“狗”是“动物”的子类),我们只需要描述,这个子类特有的特点就好。而他们的共同特点,可以通过继承他的父类的公共方法进行使用。
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
父类:又叫基类,也叫超类
子类:又叫派生类
语法:
修饰符 class 子类 extends 父类 {
// ...
}
父类
// Animal.java
public class Animal{
String name;
int age;
public void eat(){
System.out.println(name + "正在吃饭");
}
public void sleep(){
System.out.println(name + "正在睡觉");
}
}
子类
// Dog.java
//子类
public class Dog extends Animal{
void bark(){
System.out.println(name + "汪汪汪~~~");
}
}
测试类
// TestExtend.java
public class TestExtend {
public static void main(String[] args) {
Dog dog = new Dog();
// dog类中并没有定义任何成员变量,name和age属性肯定是从父类Animal中继承下来的
System.out.println(dog.name);
System.out.println(dog.age);
// dog访问的eat()和sleep()方法也是从Animal中继承下来的
dog.eat();
dog.sleep();
dog.bark();
}
}
在子类方法中 或者 通过子类对象访问成员时:
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
问题:如果子类中存在与父类中相同的成员时,那如何在子类中访问父类相同名称的成员呢?没错,通过super关键字。与this关键字相似,不过前者是声明调用的是父类中的方法。
父类
public class Base {
int a;
int b;
public void methodA(){
System.out.println("Base中的methodA()");
}
public void methodB(){
System.out.println("Base中的methodB()");
}
}
子类
public class Derived extends Base{
int a; // 与父类中成员变量同名且类型相同
char b; // 与父类中成员变量同名但类型不同
// 与父类中methodA()构成重载
public void methodA(int a) {
System.out.println("Derived中的method()方法");
}
// 与基类中methodB()构成重写(即原型一致,重写后序详细介绍)
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
// 对于同名的成员变量,直接访问时,访问的都是子类的
a = 100; // 等价于: this.a = 100;
b = 101; // 等价于: this.b = 101;
// 注意:this是当前对象的引用
// 访问父类的成员变量时,需要借助super关键字
// super是获取到子类对象中从父类继承下来的部分
super.a = 200;
super.b = 201;
// 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
methodA(); // 没有传参,访问父类中的methodA()
methodA(20); // 传递int参数,访问子类中的methodA(int)
// 如果在子类中要访问重写的基类方法,则需要借助super关键字
methodB(); // 直接访问,则永远访问到的都是子类中的methodA(),父类的无法访问到
super.methodB(); // 访问父类的methodB()
}
}
构造方法
子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。
父类构造
public class Base {
public Base(){
System.out.println("Base()");
}
}
子类构造
public class Derived extends Base{
public Derived(){
super();
// 注意子类构造方法中默认会调用基类的无参构造方法:super(),
// 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
// 并且只能出现一次
System.out.println("Derived()");
}
}
public class Test {
public static void main(String[] args) {
Derived d = new Derived();
}
}
结果打印:
Base()
Derived()
super与this的区别
相同点:
构造方法
不同点:
执行优先分析(重要)
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person:构造方法执行");
}
{
System.out.println("Person:实例代码块执行");
}
static {
System.out.println("Person:静态代码块执行");
}
}
class Student extends Person{
public Student(String name,int age) {
super(name,age);
System.out.println("Student:构造方法执行");
}
{
System.out.println("Student:实例代码块执行");
}
static {
System.out.println("Student:静态代码块执行");
}
}
/*
Person:静态代码块执行
Student:静态代码块执行
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
*/
继承的方式种类
这个图是我从其他地方摘抄来的。以问过作者了,主要是这个图确实好用。
final关键可以用来修饰变量、成员方法以及类。
1.修饰变量
修饰变量或字段,表示常量(即不能修改)
final int a = 10;
a = 20; // 编译出错
2.修饰类
表示此类不能被继承
final public class Animal {
…
}
public class Bird extends Animal {
…
}
3.修饰方法
表示该方法不能被重写
**通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态。**比如动物,它有猫这个子类,也有狗这个子类,但是他们吃的东西不同,猫吃猫粮,狗吃狗粮,在父类中出现吃这个动作。而子类将这些具象化了,呈现了不同类型不同状态的形式。(多态)
public class Animal {
String name;
int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name + "吃饭");
}
}
public class Cat extends Animal{
public Cat(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name+"吃鱼~~~");
}
}
}
重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。也就是说子类能够根据需要实现父类的方法。
规则:
区别 | 重写(override) | 重载(override) |
---|---|---|
参数列表 | 一定不能修改 | 必须修改 |
返回类型 | 一定不能修改【除非可以构成父子类关系】 | 可以修改 |
访问限定符 | 一定不能做更严格的限制 | 可以修改 |
重写:方法名和参数必须一致
重载:方法名相同,参数不同
重载
向上转型:
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
父类类型 对象名 = new 子类类型()
Animal animal = new Cat("将军");
向下转型:
将父类引用再还原为子类对象,即向下转换。
public class TestAnimal {
public static void main(String[] args) {
Cat cat = new Cat("将军");
Dog dog = new Dog("虞姬");
// 向上转型
Animal animal = cat;
animal.eat();
animal = dog;
animal.eat();
if(animal instanceof Cat){
cat = (Cat)animal;
cat.mew();
}
if(animal instanceof Dog){
dog = (Dog)animal;
dog.bark();
}
}
注意:
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换。
1.多态能够降低代码的 “圈复杂度”(形容代码的复杂程度,一般if-else不超过10), 避免使用大量的 if - else
2.多态可扩展能力更强
3.向上转型用的更多也更安全,向下转型,会横跨两个子类,可能会出现跨类调用
如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类,他没有具体的实现方法,只提供了一个方法或成员
被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。
// 抽象类:被abstract修饰的类
public abstract class Shape {
// 抽象方法:被abstract修饰的方法,没有方法体
abstract public void draw();
abstract void calcArea();
// 抽象类也是类,也可以增加普通方法和属性
public double getArea(){
return area;
}
protected double area; // 面积
}
抽象类
特点:
接口可以看成是:多个类的公共规范,是一种引用数据类型。接口你可理解成父类也就是父类,接口,可以理解为一类东西,只是运用的范围不同而已。
抽象类---->接口==(约等于)父类
接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口。
public interface 接口名称{
// 抽象方法
// public abstract 是固定搭配,可以不写
public abstract void method1();
public void method2();
abstract void method3();
// 注意:在接口中上述写法都是抽象方法,跟推荐方式4,代码更简洁
void method4();
}
接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。
子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系。
// USB接口
public interface USB {
void openDevice();
void closeDevice();
}
// 鼠标类,实现USB接口
public class Mouse implements USB {
@Override
public void openDevice() {
System.out.println("打开鼠标");
}
}
接口
其实对于接口和类,java自带的库中有很多好用的方法。这里先不赘述,在文件末尾,我会对这些进行总结(用链接的方式)(就复习而言这些足以)
在Java中,将程序执行过程中发生的不正常行为称为异常。
1.算术异常
System.out.println(10 / 0);
java.lang.ArithmeticException
2.数组越界异常
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
java.lang.ArrayIndexOutOfBoundsException
3.空指针异常
int[] arr = null;
System.out.println(arr.length);
java.lang.NullPointerException
抛出异常
public static int getElement(int[] array, int index){
if(null == array){
//抛出异常
throw new NullPointerException("传递的数组为null");
}
if(index < 0 || index >= array.length){
//抛出异常
throw new ArrayIndexOutOfBoundsException("传递的数组下标越界");
}
return array[index];
}
public static void main(String[] args) {
int[] array = {1,2,3};
getElement(array, 3);
}
捕获异常:
语法格式:
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2…{
}
try-catch捕获并处理
语法格式:
try{
// 将可能出现异常的代码放在这里
}catch(要捕获的异常类型 e){
// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的父类时,就会被捕获到
// 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码
}[catch(异常类型 e){
// 对异常进行处理
}finally{
// 此处代码一定会被执行到
}]
// 后序代码
// 当异常被捕获到时,异常就被处理了,这里的后序代码一定会执行
// 如果捕获了,由于捕获时类型不对,那就没有捕获到,这里的代码就不会被执行