面向对象
1 构造代码块
构造代码块也叫成员代码块,是在所有的构造方法执行之前执行的。
可以把构造方法中都有的代码抽取到构造代码块中
public class TestDemo {
public static void main(String[] args) {
Baby baby = new Baby(3.5);
Baby baby1 = new Baby("小宝贝");
}
}
// 定义一个代表婴儿的类
class Baby{
// 姓名
private String name;
// 体重
private double weight;
{
// 构造代码块 是先与构造方法执行的
// 在每次创建对象的构造方法执行之前都会执行
this.cry();
this.eat();
}
public Baby(){
System.out.println("构造方法执行了...");
}
public Baby(double weight){
System.out.println("有参数的构造方法执行了");
this.weight = weight;
}
public Baby(String name){
System.out.println("有参数的构造方法执行了");
this.name = name;
}
public Baby(double weight,String name){
System.out.println("有参数的构造方法执行了");
this.weight = weight;
this.name = name;
}
public void cry(){
System.out.println("婴儿哇哇哇的哭~");
}
public void eat(){
System.out.println("婴儿要吃奶~");
}
}
补充:局部代码块
public static void main(String[] args) {
{
// 局部代码块
// 可以限制变量的作用域和生命周期
// 提高栈内存的利用率
int a = 10;
}
}
2 继承
面向对象的三大特征:封装、继承、多态
如果一些类中的属性和方法是重复的,可以将这些重复的属性和方法抽取到一个新的类里面,利用 extends 关键字让原来的类和新的类产生联系,这种联系就称之为继承。
抽取出来的新的类是superclass 父类(超类/基类),原来的类是subclass子类(派生类).
子类通过继承父类可以使用父类中的一部分方法和属性。
子类通过继承可以继承父类的全部的属性和方法,但是只能有一部分属性和方法可以使用,因为有一些属性和方法是私有的,不能使用。
java里面只有单继承,没有多继承。但是有多层继承。
继承可以提高代码的复用性。
public class TestDemo {
public static void main(String[] args) {
Cat cat = new Cat();
cat.color = "橘色";
cat.kind = "狸花猫";
cat.shout();
cat.eat();
cat.name = "喵喵";
Dog dog = new Dog();
dog.color = "黑色";
dog.kind = "中华田园犬";
dog.shout();
dog.eat();
}
}
// 宠物
class Pet extends Animal{
// 品种
String kind;
// 颜色
String color;
// 叫
public void shout(){
System.out.println("叫~");
}
// 吃
public void eat(){
System.out.println("吃~");
}
}
class Animal{
String name;
}
class Cat extends Pet{
}
class Dog extends Pet{
}
3 super关键字
super关键字是子类中用来调用父类的属性和方法
- super.成员变量
- 获取父类中的成员变量
- super.成员方法
- 获取父类中的成员方法
- super()
- 调用父类中的构造方法
- 在每一个构造方法中都默认会有一个super()
public class TestDemo2 {
public static void main(String[] args) {
Pig pig = new Pig(9.8);
pig.sleep();
}
}
// 子类
class Pig extends BigPig{
double weight;
// super代表在子类中调用父类的方法和属性
public Pig(){
// super()表示调用父类的无参构造方法
// 在每一个构造方法中都隐藏了一个super()
// super()调用父类构造方法时,必须是在构造方法的有效代码的第一行
// super()和this()不可能同时出现在一个方法体中
System.out.println("子类构造方法");
}
public Pig(double weight){
this();
}
// 睡觉
public void sleep(){
System.out.println("猪喜欢睡觉");
// 调用父类的方法
super.eat();
// 调用父类的属性
super.name = "佩奇";
System.out.println(super.name);
}
}
// 父类
class BigPig{
String name;
public BigPig(){
System.out.println("父类构造方法");
}
public BigPig(String name){
System.out.println("父类构造方法");
this.name = name;
}
// 吃东西
public void eat(){
System.out.println("猪喜欢吃东西");
}
}
4 权限修饰符
权限修饰符就是修饰属性和方法在哪些地方可以访问。
权限修饰符 | 本类中 | 子类中 | 同包类中 | 其他类中 |
---|---|---|---|---|
public | 可以 | 可以 | 可以 | 可以 |
protected | 可以 | 可以 | 可以 | 不可以 |
默认(default) | 可以 | 同包子类可以 | 可以 | 不可以 |
private | 可以 | 不可以 | 不可以 | 不可以 |
5 重写(覆盖)
重写就是在父子类中出现了方法签名相同的非静态方法。重写也称之为覆盖。
方法签名就是方法名和参数列表。和权限修饰符和返回值类型都无关。
方法的重写遵循"两等两小一大"五个规则:
- 方法签名要一致
- 如果父类中的方法的返回值类型是基本数据类型和void,那么子类在重写时的方法的返回值类型必须和父类的方法返回值类型一致。
- 子类重写的方法的权限修饰符的范围要大于等于父类对应方法的权限修饰符范围
- 如果父类方法的返回值类型是引用数据类型,那么子类重写的方法的返回值类型要么与父类方法的返回值类型一致,要么子类方法的返回值类型是父类方法返回值类型的子类。
class A{}
// B类是A类的子类
class B extends A{}
class C{
public A m(){
return null;
}
}
// D类是C类的子类
class D extends C{
public B m(){
return null;
}
}
public class TestDemo {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat();
}
}
// 宠物 父类
class Pet{
String name;
public Pet(String name){
}
public void eat(){
System.out.println("吃东西");
}
}
// 狗 子类
class Dog extends Pet{
public Dog(){
// this();
super("小狗");
}
// 重写父类的方法
public void eat(){
System.out.println("小狗吃骨头");
}
}
class Cat extends Pet{
public Cat(){
super("小猫");
}
// 重写
@Override
public void eat() {
System.out.println("小猫吃鱼");
}
}
6 多态
多态是继封装、继承之后,面向对象的第三大特性。
现实事物经常会体现出多种形态。比如学生是人的一种,也就意味着张三既是学生,也是人。
java语言是一个面向对象的高级语言。同样可以描述一个事物的多种形态。比如: Student 类继承了Person 类,一个 Student 的对象既是Student,也是 Person。
多态分为
- 编译时多态:
- 重载
- 运行时多态:
- 方法的重写
- 向上转型(向上造型) 格式: 父类/父接口 对象名 = new 子类/实现类();
public class TestDemo {
public static void main(String[] args) {
// 多态的向上转型
// 格式: 父类 对象名 = new 子类();
// 用父类来声明对象 用子类来创建对象
// 在使用向上转型创建对象的时候
// 编译期间只会检查声明类和创建类之间是否有继承关系
// 并不会确定到底是哪一个子类
// 到了运行期间才会确定到底是哪一个子类
// 编译看左边 运行看右边
Pet pet = new Dog();
// 使用向上转型时是不能调用子类特有的方法
pet.sleep();
Dog dog = new Dog();
test(dog);
}
// 要针对所有的宠物进行处理
public static void test(Pet pet){
System.out.println(pet);
// 子类(小) = (子类)父类(大)
// 引用数据类型的强制类型转换
Dog dog = (Dog) pet;
dog.kanJia();
}
}
// 宠物类
class Pet{
public void eat(){
System.out.println("宠物都要吃东西");
}
public void sleep(){
System.out.println("宠物要睡觉");
}
}
// 狗类
class Dog extends Pet{
@Override
public void eat() {
System.out.println("狗要吃骨头");
}
public void sleep(){
System.out.println("狗要睡觉");
}
public void kanJia(){
System.out.println("狗看家护院");
}
}
class Cat extends Pet{
}
7 关于重写规则的理解
- 为什么子类重写的方法的权限修饰符的范围要大于等于父类对应方法的权限修饰符范围?
public class TestDemo {
public static void main(String[] args) {
// 向上转型
/*
a对象的声明类是A类,所以a对象能够干什么看的是A类,
A类告诉a对象有一个m方法可以使用,而且m方法可以在任何地方使用。
* */
A a = new B();
/*
a对象本质上是B类来创建的,所以m方法的执行要看B类。
B类对象的m方法不能在其他类中使用。
所以下面运行是访问不到m方法,但是上面声明时认为可以访问到m方法。如果java允许这样写,运行时可能会报错。
java提供编译不通过的机制,把错误提前到了编译阶段。
* */
a.m();
}
}
class A{
public void m(){
}
}
class B extends A{
private void m() {
}
}
- 如果父类方法的返回值类型是引用数据类型,那么子类重写的方法的返回值类型要么与父类方法的返回值类型一致,要么子类方法的返回值类型是父类方法返回值类型的子类。为什么要这样设计?
public class TestDemo {
public static void main(String[] args) {
// 多态的向上转型
/**
* c对象的声明类型是C类型,所以C类型告诉c对象有一个m方法可以使用,并且m方法的返回值类型的B
*/
C c = new D();
/**
* c对象的实际类型是D类型,D类型的m方法执行后返回的是A类型-->子类接收了一个父类
*
*/
B b = c.m();
/**
* b对象实际类型是A类,A类中有mb方法吗?没有
*
*/
b.mb();
}
}
class A{}
// B是A的子类
class B extends A{
public void mb(){
}
}
class C {
public B m(){
return null;
}
}
class D extends C{
public A m() {
return null;
}
}