目录
面向对象
方法(类似C语言中的函数)
可变参数
全局变量与局部变量
构造方法/构造器
方法/构造方法重载
this关键字
包
封装
继承
super 关键字
方法重写/覆盖
多态
多态的向上转型
Java常识及编写规范
运行机制
.java文件 编译javac 生成.clsss文件 通过JVM运行Java
源文件 编译 字节码文件 运行Java
相对路径:从当前文件夹(目录)开始定位,形成的路径
绝对路径:从根文件夹(目录)开始定位,形成的路径
JDK = JRE + 开发工具包
JRE = JVM + 核心类库
//Java编写步骤
1.编写java的源代码
2.java.c编译,得到对应的.class字节码文件
3.java运行,本质就是把.class文件加载到JVM运行
1.类,方法的注释,使用javadoc的方式,即文档注释
2.非Javadoc注释,往往是对代码的说明(程序的维护者)说明如何修改,注意事项
3.使用tab,整体将代码右移,使用shift+tab整体左移
4.运算符和 = 的两边,给空格,使代码看上去清楚
5.源文件编码使用utf-8编码
6.行宽字符不要超过80
7.编码风格:次行风格 行尾风格
// public class Hello是一个类,是一个public(公有)的类
// Hello{}表示一个类的开始和结束
// public static void main(String[] args)表示一个主方法,即程序的入口
// main(){}方法的开始和结束
// System.out.println("");表示输出内容到屏幕
// ;表示语句结束
public class Hello {
//编写一个main方法
public static void main(String[] args){
System.out.println("hello,world!");
}
}
一个源文件中最多只有一个public类。其他类的个数不限
编译后每个类都生产一个对应的.class文件
公有类的名称必须是文件名的名称
类名必须以英文字母开头,后接字母数字下划线的组合,习惯以大写字母开头
方法名命名和类一样,但首字母小写
定义常量的时候加上final修饰符这个变量就成了常量
常量定义之后不可以赋值改变常量的值
Java数据类型
long型占用8个字节 long age = 10L;
float型占用4个字节 float age = 10.0F
byte数据类型是8位、有符号的,以二进制补码表示的整数;
最小值是-128(-2^7);
最大值是127(2^7-1);
默认值是0;
通常情况下,应该使用double型,应为它比float型更精确
对运算结果是小数的进行相等判断,要小心
应该是以两个数差值的绝对值,在某个精度范围类判断
常用转义字符t 制表位,实现对齐,在控制台可以实现命令对齐
n 换行
r 回车
定义变量时,要遵循作用域最小化原则,尽量将变量定义在尽可能小的作用域,并且,不要重复使用变量名。
面向对象
类是对象的抽象,对象是类的实例。
在Java中,创建一个类,例如,给这个类命名为Person,就是定义一个class:
class Person {
public String name;
public int age;
}
一个class可以包含多个字段(field),属性用来描述一个类的特征。上面的Person类,我们定义了两个属性,一个是String类型的属性,命名为name,一个是int类型的属性,命名为age。因此,通过class,把一组数据汇集到一个对象上,实现了数据封装。
public是用来修饰字段的,它表示这个字段可以被外部访问。
创建实例(对象)
定义了class,只是定义了对象模版,而要根据对象模版创建出真正的对象实例,必须用new操作符。
new操作符可以创建一个实例,然后,我们需要定义一个引用类型的变量来指向这个实例:
Person ming = new Person();
上述代码创建了一个Person类型的实例,并通过变量ming指向它。
注意区分Person ming是定义Person类型的变量ming,而new Person()是创建Person实例。
有了指向这个实例的变量,我们就可以通过这个变量来操作实例。访问实例变量可以用变量.字段,例如:
ming.name = "Xiao Ming"; // 对字段name赋值 ming.age = 12; // 对字段age赋值 System.out.println(ming.name); // 访问字段name Person hong = new Person(); hong.name = "Xiao Hong"; hong.age = 15;
┌──────────────────┐
ming ──────>│Person instance │
├──────────────────┤
│name = "Xiao Ming"│
│age = 12 │
└──────────────────┘
┌──────────────────┐
hong ──────>│Person instance │
├──────────────────┤
│name = "Xiao Hong"│
│age = 15 │
└──────────────────┘
两个instance(实例)拥有class定义的name和age属性,且各自都有一份独立的数据,互不干扰。
一个Java源文件可以包含多个类的定义,但只能定义一个public类,且public类名必须与文件名一致。如果要定义多个public类,必须拆到多个Java源文件中。
在OOP(面向对象)中,class和instance是“模版”和“实例”的关系;
定义class就是定义了一种数据类型,对应的instance是这种数据类型的实例;
class定义的field,在每个instance都会拥有各自的field,且互不干扰;
通过new操作符创建新的instance,然后用变量指向它,即可通过变量来引用这个instance;
访问实例字段的方法是变量名.字段名;
指向instance的变量都是引用变量。
public class Haer
{
public static void main(String[] args)
{
//创建Person对象
//p1对象名
//new Person()创建的对象空间才是真正的对象
//对象属性默认值和数组一样且都是引用类型
// 先声明在创建
// Person p1;
// p1 = new Person();
Person p1 = new Person();
p1.speak();//调用方法
}
}
class Person
{
int age;
String name;
double sal;
boolean isPass;
//表示方法公开的 没有返回值 speak()方法名 里面没有行参
public void speak()
{
System.out.println("天下第一!");
}
}
方法(类似C语言中的函数)
修饰符 方法返回类型 方法名(方法参数列表) {
若干方法语句;
return 方法返回值;
}
方法返回值通过return语句实现,如果没有返回值,返回类型设置为void,可以省略return。
调用方法
实例变量.方法名(参数);
public class Main {
public static void main(String[] args) {
Person ming = new Person();
ming.setName("Xiao Ming"); // 设置name
ming.setAge(12); // 设置age
System.out.println(ming.getName() + ", " + ming.getAge());
}
}
class Person {
private String name;
private int age;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
if (age < 0 || age > 100) {
throw new IllegalArgumentException("invalid age value");
}
this.age = age;
}
}
方法不能嵌套定义
public class Haer
{
public static void main(String[] args)
{
A a = new A();
a.m1();
}
}
class A
{
// 同一个类中的方法调用:直接调用即可
// public void print(int n)
// {
// System.out.print("" + n);
// }
// public void sayOk()
// {
// print(10);
// }
//跨类中的方法 A类调用B类方法:需要通过对象名调用
public void m1()
{
//创建B类对象,然后在调用方法即可
B b = new B();
b.hi();
}
}
class B
{
public void hi()
{
System.out.println("执行B类的hi()");
}
}
基本数据类型,传递的是值(值拷贝),形参的改变不影响实参
引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参!
可变参数
可变参数用类型...定义,可变参数相当于数组类型:
public class Haer
{
public static void main(String[] args)
{
Methous m = new Methous();
System.out.println(m.sum(10,1,2,2,2,2,2,2,2));
}
}
// int...表示接收可变参数,类型是int,即可以接收多个int(0~多)
// 使用可变参数时,可以当作数组来使用,即 nums 可以当作数组
// 遍历nums求和即可
class Methous
{
public int sum(int...nums)
{
int res = 0;
for(int i = 0; i < nums.length; i++)
{
res += nums[i];
}
return res;
}
}
全局变量与局部变量
全局变量(属性/成员变量)可以不赋值直接使用(有默认值),局部变量(方法体)必须先赋值在使用
构造方法/构造器
1.方法名和类相同
2.没有返回值
3.在创建对象时,系统会自动调用该类的构造器完成对象初始化
4.构造器是完成对象的初始化,并不是创建对象
5.在创建对象时,系统自动的调用该类的构造方法
6.如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器
7.一旦定义了自己的构造器,默认的构造器就覆盖了,就不能使用默认的无参构造器
除非显示定义一下 即 : Cat(){} 声明
[修饰符]方法名(形参列表)
{
方法体;
}
public class Haer
{
public static void main(String[] args)
{
//当我们创建对象时,直接通过构造器初始化
Person p = new Person("jack",90);
}
}
class Person
{
String name;
int age;
// 构造器没有返回值,也不能写void
// 构造器的名称和类Person一样
public Person(String pName, int pAge;)
{
this.name = pName;
this.age = pAge;
}
}
没有在构造方法中初始化字段时,引用类型的字段默认是null,数值类型的字段用默认值,int类型默认值是0,布尔类型默认值是false:
一个构造方法可以调用其他构造方法,这样做的目的是便于代码复用。调用其他构造方法的语法是this(…):
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name) {
this(name, 18); // 调用另一个构造方法Person(String, int)
}
public Person() {
this("Unnamed"); // 调用另一个构造方法Person(String)
}
}
方法/构造方法重载
在一个类中,我们可以定义多个方法。如果有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名方法。例如,在Hello类中,定义多个hello()方法:
class Hello {
public void hello() {
System.out.println("Hello, world!");
}
public void hello(String name) {
System.out.println("Hello, " + name + "!");
}
public void hello(String name, int age) {
if (age < 18) {
System.out.println("Hi, " + name + "!");
} else {
System.out.println("Hello, " + name + "!");
}
}
}
这种方法名相同,但各自的参数不同,称为方法重载(Overload)。
方法重载是指多个方法的方法名相同,但各自的参数不同;
重载方法返回值类型应该相同。
一个类可以定义多个不同的构造器,即构造器重载
和使用方法重载一致
public class Main
{
public static void main(String[] args)
{
Person p1 = new Person("haer");
Person p2 = new Person("sufei",19);
System.out.println(p1.name);
System.out.println(p2.name + "t" + p2.age);
}
}
class Person
{
String name;
int age;
//第一个构造器
public Person(String pName, int pAge)
{
name = pName;
age = pAge;
}
//构造器的重载
public Person(String pName)
{
name = pName;
}
}
this关键字
1.this关键字可以用来访问本类的属性,方法,构造器
2.this用于区分当前类的属性和局部变量
3.访问成员方法的语法: this.方法名(参数列表)
4.访问构造器语法:this(参数列表);注意只能在构造器中使用(即只能在构造器中访问另外一个构造器,必须放在第一条语句)
5.this不能在类定义的外部使用,只能在类定义的方法中使用
在方法内部,可以使用一个隐含的变量this,它始终指向当前实例。因此,通过this.field就可以访问当前实例的字段。
public class Main
{
public static void main(String[] args)
{
Dog dog = new Dog("haer",19);
dog.info();
}
}
class Dog
{
String name;
int age;
public Dog(String name, int age)
{
//this.成员变量 就是当前对象的属性
//哪个调用this this就代表哪个对象
//this指向自己这个对象
this.name = name;
this.age = age;
}
public void info()
{
System.out.println(name + "t" + age);
}
}
包
Java定义了一种命名空间,称之为包:package 一个类总是属于某个包 类名(比如Person)只是一个简写,真正的完整类名是包名.类名
包的命名:
只能是字母,数字,下划线,小圆点 (不能用数字开头,不能是关键字或保留字)
命名规范:
小写字母 + 小圆点 + 模块名
eg:com.公司名.项目名.业务模块名
package 的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package
import 指令 位置放在package的下面,在类定义前面,可以有多句且没有顺序要求
在定义class的时候,我们需要在第一行声明这个class属于哪个包。
比如小明的 Person.java文件:
package ming; // 申明包名ming
public class Person {
}
package com.hspedu.pkg;
import java.util.Arrays; //导入数组包
public class import01 {
public static void main(String[] args) {
int[] arr = {9,8,7,6,5,4,3,2,1};
Arrays.sort(arr); // 排序
System.out.println(Arrays.toString(arr)); //打印数组内容
}
}
在Java虚拟机执行的时候,JVM只看完整类名,因此,只要包名不同,类就不同。
位于同一个包的类,可以访问包作用域的字段和方法。不用public、protected、private修饰的字段和方法就是包作用域。例如,Person类定义在hello包下面:
package Hello;
public class Person {
// 包作用域:
void hello() {
System.out.println("Hello!");
}
}
Main类也定义在Hello包下面:
package Hello;
public class Main {
public static void main(String[] args) {
Person p = new Person();
p.hello(); // 可以调用,因为Main和Person在同一个包
}
}
package_sample
└─ bin
├─ hong
│ └─ Person.class
│ ming
│ └─ Person.class
└─ com
└─ jun
└─ Arrays.class
在一个class中,我们总会引用其他的class。例如,小明的ming.Person类,如果要引用小军的com.jun.Arrays类,他有三种写法:
// Person.java
package ming;
public class Person {
public void run() {
com.jun.Arrays arrays = new com.jun.Arrays();
}
}
第二种写法是用import语句,导入小军的Arrays,然后写简单类名:
/ Person.java
package ming;
// 导入完整类名:
import com.jun.Arrays;
public class Person {
public void run() {
Arrays arrays = new Arrays();
}
}
包的作用:
1.区分名字相同的类
2.当类很多时,可以更好的管理类
3.控制访问范围
*实际上就是创建不同的文件来/目录保存类文件
访问修饰符
公开级别:public
受保护级别:protected 对子类和同一个包中的类公开
默认级别:没有修饰符,向同一个包的类公开
私有级别:private 只有类本身可以访问,不对外公开
在同一类中可以访问 public protected 默认 private修饰的属性或方法
在同一包下,可以访问 public protected 默认 修饰的属性或方法,不能访问private修饰的属性方法
在不同的包下只能访问 public 修饰的属性或方法
封装
(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据保护在内部,程序的其他部分只有通过被授权的操作[方法],才能对数据进行操作。
public class Encapsulation01{
public static void main(String[] args){
Person person = new Person("qqqqq",345,4444,"程序员");
// person的信息
System.out.println(person.info());
}
}
class Person {
public String name;
private int age;
private double salary;
private String job;
//无参构造器
Person(){
}
public Person(String name, int age, double salary, String job) {
//可以将set方法写在构造器中
setName(name);
setAge(age);
setSalary(salary);
setJob(job);
}
public String getName() {
return name;
}
public void setName(String name) {
// 数据的校验
if(name.length() >= 0 && name.length() <= 6) {
this.name = name;
} else {
System.out.println("姓名信息错误!");
this.name = null;
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
// 数据的校验
if(age >=1 && age <= 120) {
this.age = age;
} else {
System.out.println("年龄数据错误!");
this.age = 0;
}
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public String info(){
return "信息为:" + " 名字 " + name + " 年龄 " + age + " 薪水 " + salary + " 工作 " + job;
}
}
继承
继承可以解决代码复用,让我们的编程更加靠近人类思维,当多个类存在相同的属性
和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类
不需要重新定义这些属性和方法,只需要通过extends来声明继承即可
Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object(祖师爷类)特殊,它没有父类。
类似的,如果我们定义一个继承自Person的Teacher,它们的继承树关系如下:
┌───────────┐
│ Object │
└───────────┘
▲
│
┌───────────┐
│ Person │
└───────────┘
▲ ▲
│ │
│ │
┌───────────┐ ┌───────────┐
│ Student │ │ Teacher │
└───────────┘ └───────────┘
继承基本语法
class 父类{
}
class 子类 ectends 父类{
}
1.子类会自动拥有父类定义的属性和方法
2.父类又叫超类,基类
3.子类又叫派生类
1.子类继承了所有父类的属性和方法 (严禁定义与父类重名的属性方法),非私有的属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问,要通过公共的方法去访问
2.子类必须调用父类的构造器,完成父类的初始化
3.当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器如果父类是有参构造器,则必须在子类的构造器中用super() 去指定父类的有参构造器完成对父类的初始化工作,否则编译器不通过。(子类不会继承任何父类的构造方法,子类默认的构造方法是编译器自动生成的,不是继承)
4.如果希望指定去调用父类的某个构造器,则显示的调用一下:super(参数列表)
5.super在使用时,需要放在构造器的第一行,(super() 只能在构造器中使用)
6.super() 和 this() 都只能放在构造器的第一行,因此这两个方法不能存在于一个构造器中
9.子类最多只能继承一个父类(指直接继承),只有 Object 特殊 ,它没有父类 ,即 java 中是单继承机制
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son();
//如果子类有这个属性,并且可以访问,则返回此信息
//如果子类没有这个属性,就看父类有没有这个属性(如果父类有这个属性,并且可以访问,则返回此信息)
//如果父类没有这个属性,就继续找上级父类,直到 Object
System.out.println(son.name + "t" + son.age + "t" + son.hobby);
}
}
class GrandPa{//父类
String name = "大头爷爷";
String hobby = "旅游";
}
class Father extends GrandPa{
String name = "大头爸爸";
int age = 39;
}
class Son extends Father{
String name = "大头儿子";
}
// 思考题目
public class ExtendsExercise01 {
public static void main(String[] args) {
B b = new B();
}
}
class A{
A(){
System.out.println("a");
}
A(String name){
System.out.println("a name");
}
}
class B extends A{
B(){ //在调用子类的构造器时第一句会默认执行super();对父类完成初始化 这里this()把super()占用了
this("abc"); // 调用本类的有参构造器
System.out.println("b");
}
B(String name){ //有一个隐藏的super()
System.out.println("b name");
}
}
super 关键字
super代表父类的引用,用于访问父类的属性,方法,构造器
// super 是父类的引用 this 是本对象的引用
public class Main{
public static void main(String[] args) {
B b = new B();
b.Hi();
}
}
class A {
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public A(){}
public A(int n1,int n2, int n3, int n4){}
public void test100(){
}
protected void test200(){
}
void test300(){
}
private void test400(){
}
}
class B extends A{
//访问父类的属性,但不能访问 private 的属性 super.属性名
public void Hi(){
System.out.println(super.n1 + " " + super.n2 + " " + super.n3);
}
//访问父类的方法,但不能访问 private 的方法 super.方法名(参数列表)
public void Hi0(){
super.test100();
super.test200();
super.test300();
}
public B(){
super(1,1,1,1);
}
}
1.调用父类的构造器的好处(父类字段由父类初始化,子类的字段由子类初始化)
2.当子类中有和父类中的 字段/方法 重名时,为了访问父类的字段,必须通过super,如果没有重名,使用 super, this 直接访问是一样的效果。
public class Main{
public static void main(String[] args) {
B b = new B();
b.sum();
}
}
class A { // 父类
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public void cal(){
System.out.println("A类的cal()方法");
}
}
class B extends A{ //子类
public int n1 = 888;
public void cal(){
System.out.println("B类的cal()方法");
}
public void sum(){
System.out.println("B类的sum()");
// 希望调用父类 A 的 cal() 方法
// 因为B类没有cal()方法
// 找cal()方法时 先找本类,如果有,并且可以调用,则调用。如果没有则找父类(如果有并且可以调用,则调用)
// 如果父类没有,则继续找父类的父类 直到 Object 规则一致
// cal();
this.cal(); // 等价 cal()
// super.cal(); // 直接到父类中去找 cal() 方法
// 访问属性的规则 和方法一致
System.out.println(n1);
System.out.println(this.n1);
System.out.println(super.n1);
}
}
这是因为在Java中,任何class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();,所以,Student类的构造方法实际上是这样:
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(); // 自动调用父类的构造方法
this.score = score;
}
}
因此我们得出结论:如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。
这里还顺带引出了另一个问题:即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
方法重写/覆盖
方法覆盖(重写)就是子类有一个方法,和父类的某个方法的名称,返回类型,参数一样,那么就说子类的这个方法覆盖了父类的那个方法
方法重写的原理:子类中的方法和父类中方法名字相同,对象在调用的时候默认会从子类中开始查找,找到后就调用,然后不会再往父类继续查找
子类的方法的参数,方法名称,要和父类方法的参数,方法名称完全一致
public class Main{
public static void main(String[] args) {
Dog dog = new Dog();
dog.cry();
}
}
class Animal { //父类
public void cry() {
System.out.println("动物呼唤!");
}
}
class Dog extends Animal{ // 子类
// Dog 的 cry方法和 Animal 的 cry方法定义的形式一样 (名称,返回类型,参数)
// 这时我们就说 Dog 的 cry 方法,重写了 Animal 的 cry 方法
public void cry() {
System.out.println("小狗汪汪叫~");
}
}
子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类 比如 父类的返回类型 是 Object ,子类方法返回类型是 String
子类方法不能缩小父类方法的访问权限(可以扩大)
public class Main{
public static void main(String[] args) {
Dog dog = new Dog();
dog.cry();
}
}
class Animal { //父类
public void cry() {
System.out.println("动物呼唤!");
}
// 返回值类型为父子关系
public Object m1() {
return null;
}
}
class Dog extends Animal{ // 子类
// Dog 的 cry方法和 Animal 的 cry方法定义的形式一样 (名称,返回类型,参数)
// 这时我们就说 Dog 的 cry 方法,重写了 Animal 的 cry 方法
public void cry() {
System.out.println("小狗汪汪叫~");
}
public String m1() {
return null;
}
// 子类方法不可以缩小父类方法的访问权限
// private void cry() {
// System.out.println("小狗汪汪叫~");
// }
}
多态
多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。
// 重写和重载体现多态
public class Main {
public static void main(String[] args) {
// 方法重载体现多态
A a = new A();
// 传入不同的参数,就会调用不同的sum方法,体现多态
System.out.println(a.sum(10,20));
System.out.println(a.sum(10,20,30));
// 方法重写体现多态
B b = new B();
a.say();
b.say();
}
}
class B {
public void say() {
System.out.println("B say() 方法别调用...");
}
}
class A extends B {
public int sum(int n1, int n2) {
return n1 + n2;
}
public int sum(int n1, int n2, int n3) {
return n1 + n2 + n3;
}
public void say() {
System.out.println("A say() 方法别调用...");
}
}
多态的特性就是,运行期才能动态决定调用的子类方法。对某个类型调用某个方法,执行的实际方法可能是某个子类的覆写方法
Java的方法调用总是作用于运行类型对象的实际类型,这种行为称为多态;
多态的前提条件:两个对象 (类) 存在继承关系
多态的向上转型
父类的引用指向了子类的对象
父类类型 引用名 = new 子类类型();
编译类型看左边, 运行类型看右边
public class Main {
public static void main(String[] args) {
// 向上转型:父类的引用指向了子类的对象
Animal animal = new Cat();
animal.eat();
// Object obj = new Cat(); // Objict 也是 Cat 的父类
// 向上转型以后 可以访问父类的属性和方法(在访问范围之内)
// 但是不能访问子类特有的属性和方法, 在编译阶段,能调用哪些成员,由编译类型决定
System.out.println(animal.name);
animal.show();
// animal.catage; error
// animal.catchMouse(); erroe
// 最终运行效果看子类(运行类型)的具体表现,即调用方法时,按照从子类(运行类型)开始查找方法,然后调用
}
}
class Animal {
String name = "动物";
int age = 10;
public void sleep() {
System.out.println("睡");
}
public void run() {
System.out.println("跑");
}
public void eat() {
System.out.println("吃");
}
public void show() {
System.out.println("你好,世界!");
}
}
class Cat extends Animal{
public int catage = 100;
public void eat() { //方法重写
System.out.println("猫吃鱼");
}
public void catchMouse() { //Cat特有方法catchMouse()
System.out.println("猫抓老鼠");
}
}
父类的引用指向了子类的对象
父类类型 引用名 = new 子类类型();
编译类型看左边, 运行类型看右边



