java_系列_面向对象_学习笔记
封装
1. 封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被
保护在内部,程序的其它部分只有通过被授权的操作[方法],才能对数据进行操作。
2. 封装的理解和好处
① :隐藏实现细节
②: 可以对数据进行验证,保证安全合理
3. 如何体现封装
①对类中的属性进行封装
②:通过成员方法,包实现封装
4. 封装的实现步骤:
①: 将属性进行私有化 【不能直接修改属性】
②:提供一个公共的set方法,用于对属性判断并赋值public void setXxx(类型 参数名){//加
入数据验证的业务逻辑 属性 = 参数名;}
③: 提供一个公共的get方法,用于获取属性的值public XX getXxx(){ //权限管理
return xx;
}
封装举例
/*
创建程序,在其中定义两个类:Account和AccountTest类体会Java的封装性。
Account类要求具有属性:姓名(长度为2位3位或4位)、余额(必须>20)、密码(必须是六位), 如果不满足,则给出提示信息,并给默认值
通过setXxx的方法给Account 的属性赋值。
在AccountTest中测试
*/
private
this
构造方法
构造方法的注意事项
- 如果我们没有给出构造方法,系统将自动提供一个无参构造方法。
class Student {
private String name; //null
private int age; //0
public Student() {
System.out.println("这是构造方法");
}
}
class ConstructDemo {
public static void main(String[] args) {
//创建对象
Student s = new Student();//这里使用了构造方法。但是我们没有自定义构造方法。
System.out.println(s); //Student@e5bbd6 是一个地址值
}
}
通过反编译工具可查看得:
- 构造方法可以重载。
class Student {
private String name;
private int age;
public Student() {
//System.out.println("我给了,你还给不");
System.out.println("这是无参构造方法");
}
//构造方法的重载格式
public Student(String name) {
System.out.println("这是带一个String类型的构造方法");
this.name = name;
}
public Student(int age) {
System.out.println("这是带一个int类型的构造方法");
this.age = age;
}
public Student(String name,int age) {
System.out.println("这是一个带多个参数的构造方法");
this.name = name;
this.age = age;
}
}
1. 如果我们给出了构造方法,系统将不再提供默认的无参构造方法。
2. 一个小坑:再我们给了构造方法之后,想再使用无参构造方法时,需要我们自己手动自己给出构造方法。否则会报错!!
3. 构造方法可以给成员变量赋值。
4. 给成员变量赋值的方式有两种:① setXxx() ② 构造方法
static关键字
- static关键字的引入
class Person {
//姓名
String name;
//年龄
int age;
//国籍
//String country;
static String country;
public Person(){}
public Person(String name,int age) {
this.name = name;
this.age = age;
}
public Person(String name,int age,String country) {
this.name = name;
this.age = age;
this.country = country;
}
public void show() {
System.out.println("姓名:"+name+",年龄:"+age+",国籍:"+country);
}
}
class PersonDemo {
public static void main(String[] args) {
//创建对象1
Person p1 = new Person("邓丽君",16,"中国");
p1.show();
//创建对象2
//Person p2 = new Person("杨幂",22,"中国");
//p2.show();
Person p2 = new Person("杨幂",22);
p2.show();
//创建对象3
//Person p3 = new Person("凤姐",20,"中国");
//p3.show();
Person p3 = new Person("凤姐",20);
p3.show();
p3.country = "美国";
p3.show();
p1.show();
p2.show();
}
}
//姓名和年龄成员变量的值都是不一样的,不可以用静态修饰。
//选取的对象的country的值一样的“中国”,可以用静态修饰。
//如果不用静态修饰,在创建每个对象的时候,在堆内存中,都会创建一个country这个成员变量,很浪费内存空间。针对这种情况,java提供了一个关键字,static来修饰这个成员变量。
- static关键字的特点:
1. 随着类的加载而加载
2. 优先于对象存在
3. 被类的所有对象共享:如果某个成员变量是被所有对象共享的,那么它就应该定义为静态的。
4. 可以通过类名调用
5. 静态修饰的内容一般我们称其为:与类相关的,类成员
class Student {
//非静态变量
int age = 10;
//静态变量
static Stirng country = "中国";
}
class StudentDemo {
public static void main(String[] args) {
Student s = new Student();
System.out.println(s.age);//非静态变量,必须使用对象调用。
System.out.println(Student.country);//可以通过类名调用
System.out.println(s.country);//也可以通过对象名调用
}
}
- static的内存图解
- static关键字注意事项
1.在静态方法中是没有this关键字的。静态的不能访问非静态的。
理解:静态是随着类的加载而加载,this是随着对象的创建而存在。静态比对象先存在。
2.静态方法只能访问静态的成员变量和静态的成员方法。
class Teacher {
public int num = 10;
public static int num2 = 20;
public void show() {
System.out.println(num); //隐含的告诉你访问的是成员变量
System.out.println(this.num); //明确的告诉你访问的是成员变量
System.out.println(num2);
//function();
//function2();
}
public static void method() {
//无法从静态上下文中引用非静态 变量 num
//System.out.println(num);
System.out.println(num2);
//无法从静态上下文中引用非静态 方法 function()
//function();
function2();
}
public void function() {
}
public static void function2() {
}
}
class TeacherDemo {
public static void main(String[] args) {
//创建对象
Teacher t = new Teacher();
t.show();
System.out.println("------------");
t.method();
}
}
- 静态变量和成员变量的区别
1.所属不同
静态变量属于类,所以也称为为类变量
成员变量属于对象,所以也称为实例变量(对象变量)
2.内存中位置不同
静态变量存储于方法区的静态区
成员变量存储于堆内存
3.内存出现时间不同
静态变量随着类的加载而加载,随着类的消失而消失
成员变量随着对象的创建而存在,随着对象的消失而消失
4.调用不同
静态变量可以通过类名调用,也可以通过对象调用
成员变量只能通过对象名调用
//关于main方法的说明
public static void main(String[] args) {}
1.public 被jvm调用,访问权限足够大。
2.static 被jvm调用,不用创建对象,直接类名访问。
3.void被jvm调用,不需要给jvm返回值。
4.main 一个通用的名称,虽然不是关键字,但是被jvm识别。
5.String[] args 以前用于接收键盘录入的。这是一个字符串数组。
如何使用String[] args?
格式:java main方法的类名 传入的数据
java MainDemo hello world java【这里就是传入三个参数】
代码块
在Java中,使用{}括起来的代码被称为代码块,根据其位置和声明的不同,可以分为
局部代码块:
构造代码块:
静态代码块:
同步代码块:
class Student {
//这个class文件的加载时机是main方法被调用后,才会被加载
static {
System.out.println("Student 静态代码块");
}
{
System.out.println("Student 构造代码块");
}
public Student() {
System.out.println("Student 构造方法");
}
}
class StudentDemo {
//jvm还没有调用main方法
static {
System.out.println("StudentDemo 静态");
}
//main方法应该调用方法,加载class文件时还没有调用方法,接着虚拟机调用main方法【因为main方法是函数的入口,由jvm调用】
public static void main(String[] args) {
System.out.println("我是main方法");
Student s1 = new Student();
Student s2 = new Student();
}
}
/*
写程序的执行结果。
StudentDemo 静态
我是main方法
Student 静态代码块
Student 构造代码块
Student 构造方法
Student 构造代码块
Student 构造方法
*/
继承
继承的引入
//定义学生类
class Student {
String name;
int age;
public Student(){}
//getXxx()/setXxx()
public void eat() {
System.out.println("吃饭");
}
}
//定义老师类
class Teacher {
String name;
int age;
public Teacher(){}
//getXxx()/setXxx()
public void eat() {
System.out.println("吃饭");
}
}
//name,age成员变量,以及getXxx()/setXxx(),还有eat()等都是相同的。
//如果后来我们继续定义其他相关的类[家长类...],他们也具备这些内容。
//我们每一次定义这样的类的时候,都要把这些重复的内容都重新定义一遍。
//把这些相同的内容给定义到一个独立的类中。
//让这多个类和这个独立的类产生一个关系,有了这个关系后,这多个类就可以具备这个独立的类的功能。为了实现这个效果,java就提供了一个技术:继承。
//格式
class Fu {}
class Zi extends Fu {
}
//所以上面的代码就等价于:
class Person {
String name;
int age;
public Person(){}
//getXxx()/setXxx()
public void eat() {
System.out.println("吃饭");
}
}
class Student extends Person {
public Student(){}
}
class Teacher extends Person {
public Teacher(){}
}
- 继承概述
1.多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,
那么多个类无需再定义这些属性和行为,只要继承那个类即可。
2.通过extends关键字可以实现类与类的继承
class 子类名 extends 父类名 {}
3.单独的这个类称为父类,基类或者超类;这多个类可以称为子类或者派生类。
4.有了继承以后,我们定义一个类的时候,可以在一个已经存在的类的基础上,
还可以定义自己的新成员。
- 继承的好处
1.提高了代码的复用性:多个类相同的成员可以放到同一个类中。
2.提高了代码的维护性:如果功能的代码需要修改,修改一处即可。
3.让类与类之间产生了关系,是多态的前提。
其实这也是继承的一个弊端:类的耦合性很强。
- 继承的弊端
1.类与类产生了关系,其实也是继承的一个弊端:类的耦合性增强了。
2.开发的原则:低耦合,高内聚。
耦合:类与类的关系
内聚:就是自己完成某件事情的能力
- 继承的特点
1.Java类只支持单继承,不支持多继承。
一个类只能有一个父类,不可以有多个父类。
class SubDemo extends Demo{} //ok
class SubDemo extends Demo1,Demo2...//error
2.Java支持多层继承(继承体系)
class A{}
class B extends A{}
class C extends B{}
- 继承的注意事项
1.子类只能继承父类所有非私有的成员(成员方法和成员变量)。
2.子类不能继承父类的构造方法,但是可以通过super关键字去访问父类构造方法。
3.不要为了部分功能而去继承。
4.继承其实体现的是一种关系:"is a"。
5.何时使用继承关系。采用假设法:如果有两个类A,B。
只有他们符合A是B的一种,或者B是A的一种,就可以考虑使用继承。
- 继承中成员变量的关系
1.子类中的成员变量和父类中的成员变量名称不一样
2.子类中的成员变量和父类中的成员变量名称一样
在子类方法中访问一个变量的查找顺序:[就近原则]
a:在子类方法的局部范围找,有就使用
b:在子类的成员范围找,有就使用
c:在父类的成员范围找,有就使用
d:如果还找不到,就报错。
class Father {
public int num = 10;
public void method() {
int num = 50;
}
}
class Son extends Father {
public int num2 = 20;
public int num = 30;
public void show() {
int num = 40;
System.out.println(num);//先在该方法内寻找num变量
System.out.println(num2);
// 找不到符号 层层往上找都没找到
System.out.println(num3);
}
}
class ExtendsDemo4 {
public static void main(String[] args) {
//创建对象
Son s = new Son();
s.show();
}
}
- this 和 super的区别
1. this代表本类对应的引用。
2. super代表父类存储空间的标识(可以理解为父类引用,可以操作父类的成员)。
3. 用法:
1.调用成员变量
this.成员变量 调用本类的成员变量
super.成员变量 调用父类的成员变量
2. 调用构造方法
this(...) 调用本类的构造方法
super(...) 调用父类的构造方法
3.调用成员方法
this.成员方法 调用本类的成员方法
super.成员方法 调用父类的成员方法
- 几道面试题
//1.
class Fu{
public int num = 10;//显示初始化
public Fu(){
System.out.println("fu");
}
}
class Zi extends Fu{
public int num = 20;
public Zi(){
System.out.println("zi");
}
public void show(){
int num = 30;
System.out.println(num); //30
System.out.println(this.num); //20
System.out.println(super.num); //10
}
}
class ExtendsTest {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
//结果:fu zi 30 20 10
/*
1.子类构造方法执行前默认先执行父类的无参构造方法
2.一个类的初始化过程
成员变量进行初始化
默认初始化
显示初始化
构造方法初始化
*/
//2.静态先行,由父及子
class Fu {
static {
System.out.println("静态代码块Fu");
}
{
System.out.println("构造代码块Fu");
}
public Fu() {
System.out.println("构造方法Fu");
}
}
class Zi extends Fu {
static {
System.out.println("静态代码块Zi");
}
{
System.out.println("构造代码块Zi");
}
public Zi() {
System.out.println("构造方法Zi");
}
}
class ExtendsTest2 {
public static void main(String[] args) {
Zi z = new Zi();//会先加载其父类的class文件,再加载自己的class文件。
}
}
/*
1,jvm调用了main方法,main进栈
2,遇到Zi z = new Zi();会先将Fu.class和Zi.class分别加载进内存,再创建对象,
当Fu.class加载进内存,父类的静态代码块会随着Fu.class一起加载,当Zi.class加载进内存,子类的
静态代码块会随着Zi.class一起加载第一个输出,静态代码块Fu,第二个输出静态代码块Zi
3,走Zi类的构造方法,因为java中是分层初始化的,先初始化父类,再初始化子类,所以先走的父类构造,但是在执行
父类构造时,发现父类有构造代码块,构造代码块是优先于构造方法执行的所以
第三个输出构造代码块Fu,第四个输出构造方法Fu
4,Fu类初始化结束,子类初始化,第五个输出的是构造代码块Zi,构造方法Zi
这里引用了一个博主的解析:--> 原文链接:https://blog.csdn.net/leozuosj/article/details/80550375
*/
/*
结果是:
静态代码块Fu
静态代码块Zi
构造代码块Fu
构造方法Fu
构造代码块Zi
构造方法Zi
*/
- 几道面试题
class X {
Y b = new Y();
X() {
System.out.print("X");
}
}
class Y {
Y() {
System.out.print("Y");
}
}
public class Z extends X {
Y y = new Y();
Z() {
//super
//分层初始化第一步找父类并不是找super() 去访问。
//初始化的时候不是,不是按照函数运行完成 返回执行下一行的模式,而是分层初始化
//它仅仅表示要先初始化父类数据,再初始化子类数据。
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
// 结果:YXYZ
/*
相关知识:
A:成员变量的问题
int x = 10; //成员变量是基本类型
Student s = new Student(); //成员变量是引用类型
B:一个类的初始化过程
成员变量的初始化
默认初始化
显示初始化
构造方法初始化
C:子父类的初始化(分层初始化)
先进行父类初始化,然后进行子类初始化。
*/
/*
1.jvm调用main方法,main进栈
2.遇到new Z();他会先初始化父类。父类初始化过程:默认初始化-->显示初始化-->构造方法初始化,
所以先初始化Y b = new Y();打印出Y,然后初始化构造函数打印出X.
3.初始化完父类,再初始化子类数据。Y y = new Y();打印出Y;再初始化构造函数。打印出Z;
*/
方法的重写
继承
继承性的好处
① 减少了代码的冗余,提高了代码的复用性
② 便于功能的扩展
③ 为之后多态性的使用,提供了前提
继承的格式
1. class A extends B{}
A:子类、派生类、subclass
B:父类、超类、基类、superclass
2. 体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。
3. 特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。
只是因为封装性的影响,使得子类不能直接调用父类的结构而已。
4. 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
子类和父类的关系,不同于子集和集合的关系。
继承性的规定
1.一个类可以被多个子类继承。
2.Java中类的单继承性:一个类只能有一个父类
3.子父类是相对的概念。
4.子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
5.子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
继承性的拓展
1. 如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
2. 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
3. 意味着,所有的java类具有java.lang.Object类声明的功能。
继承性的代码演示
//1.Circle类
class Circle {
private double radius;//半径
public Circle(){
radius = 1.0;
}
//
public Circle(double radius){
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
//返回圆的面积
public double findArea(){
return Math.PI * radius * radius;
}
}
//2.Cylinde类
public class Cylinder extends Circle{
private double length;//高
public Cylinder(){
length = 1.0;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
//返回圆柱的体积
public double findVolume(){
// return Math.PI * getRadius() * getRadius() * getLength();
return super.findArea() * getLength();
}
@Override
public double findArea() {//返回圆柱的表面积
return Math.PI * getRadius() * getRadius() * 2 +
2 * Math.PI * getRadius() * getLength();
}
}
//3.测试类
public class CylinderTest {
public static void main(String[] args) {
Cylinder cy = new Cylinder();
cy.setRadius(2.1);
cy.setLength(3.4);
double volume = cy.findVolume();
System.out.println("圆柱的体积为:" + volume);
//没有重写findArea()时:
// double area = cy.findArea();
// System.out.println("底面圆的面积:" + area);
//重写findArea()以后:
double area = cy.findArea();
System.out.println("圆柱的表面积:" + area);
System.out.println("******************");
Cylinder cy1 = new Cylinder();
double volume1 = cy1.findVolume();
System.out.println("圆柱的体积为:" + volume1);
}
}
方法重写
1. 重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作。
2. 应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
3. 重写的规定:
方法的声明: 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{
//方法体
}
约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
① 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同【这就可以表示父类和子类的方法是同一个方法】
② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
>特殊情况:子类不能重写父类中声明为private权限的方法
③ 返回值类型:
>父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
>父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void,【因为void不存在子类】
>父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)【不可以是int,因为不是double的子类,两种类型之间不存在子父的关系】【基本类型之间是并列关系,不是子父关系】
④ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)【 异常的类型也存在子父类 】
注意:子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。如果子类的方法和父类的方法一样,但是父类被静态修饰了,子类没有,如果子类调用的话,
总结: 返回值类型 异常的类型 都不能大于父类。权限修饰符可以大于等于父类;【两小一大】
区分方法的重载与重写
- 不要犯傻,如果它不是晚绑定,它就不是多态。–Bruce Eckel
从编译和运行的角度来看:
1. 重载早绑定
2. 重写晚绑定,多态。
细节不同:
3. 方法重写:
在子类中,出现和父类中一模一样的方法声明的现象。
4. 方法重载:
同一个类中,出现的方法名相同,参数列表不同的现象。
权限修饰符
1. Java权限修饰符public、protected、 (缺省)、 private置于类的成员定义
前,用来限定对象对该类成员的访问权限。
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | YES | NO | NO | NO |
默认 | YES | NO | NO | NO |
protected | YES | NO | NO | NO |
public | YES | YES | YES | YES |
super
1. super理解为父类的。
2.super可以用来调用:属性、方法、构造器
3.super的使用:调用属性和方法
super 调用属性和普通方法
1 我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,
显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
2 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中
声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性。
3 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中
被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被
重写的方法。
super 调用构造器
1 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,
调用父类中声明的指定的构造器。
2. "super(形参列表)"的使用,必须声明在子类构造器的首行!
3. 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,
不能同时出现
4 在构造器的首行,没有显式的声明"this(形参列表)"或"super(形参列表)",
则默认调用的是父类中空参的构造器:super()
5 在类的多个构造器中,至少有一个类的构造器中使用了"super(形参列表)",调用父类中的构造器
代码演示
子类对象实例化过程
1. 从结果上来看:(继承性)
子类继承父类以后,就获取了父类中声明的属性或方法。
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
2. 从过程上来看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,...
直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有
父类中的结构,子类对象才可以考虑进行调用。
代码演示
class Creature {
public Creature() {
System.out.println("Creature无参数的构造器");
}
}
class Animal extends Creature {
public Animal(String name) {
//这里默认有个super()
System.out.println("Animal带一个参数的构造器,该动物的name为" + name);
}
public Animal(String name, int age) {
this(name);//调用本类空参构造器
System.out.println("Animal带两个参数的构造器,其age为" + age);
}}
public class Wolf extends Animal {
public Wolf() {
super("灰太狼", 3);
System.out.println("Wolf无参数的构造器");
}
public static void main(String[] args) {
new Wolf();
}}
解释图
多态[向上转型]
1. 理解多态性:可以理解为一个事物的多种形态。
2. 何为多态性:对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
3.多态的作用:提高了代码的通用性,长称作接口重用。
4. 多态的使用:虚拟方法调用
5. 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。编译,看左边;运行,看右边。
6. 多态性的使用前提: ① 类的继承关系 ② 方法的重写
7. 对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
8. 可以调用父类类型中的所有成员(需要遵守访问权限),不能调用子类类型中特有成员;最终运行效果看子类的具体实现!
9. 代码中如何实现多态
实现多态主要有以下三种方式:
①接口实现
②继承父类重写方法
③同一类中进行方法重载
10. 虚拟机是如何实现多态的
动态绑定技术(dynamic binding),执行期间判断所引用对象的实际类型,根据实际类型调用对应的方法。
11. 特点:编译看左边,运行看右边。可以调用父类类型中的所有成员(需要遵守访问权限),不能调用子类类型中特有成员;最终运行效果看子类的具体实现!
12. 总结多态的特点: 编译看左边-->多态只能调用左边有的方法和属性。运行看右边-->如果子类重写了父类的方法,则运行时执行的是子类重写了父类的哪个方法。如果子类没有重写父类的方法,则运行的是父类的那个方法。A a=new B(); [①查看调用的方法再B类中有没有重写,没有重写就调用的是子类A的方法]
class A{
}
class B extends A{
}
- 12的例子
/**
* @author Hwang
* @create 2020-07-06 20:45
*/
public class Test {
public static void main(String[] args) {
A a = new B();
System.out.println(a.sum());//40
System.out.println(a.sum1());//30
System.out.println(a.getI());//20
//sum sum1 getI 再子类B 中都有 所以执行的是B类里的方法
}
}
class A {
public int i = 10;
public int sum() {
return getI()+10;
}
public int sum1() {
return i + 10;
}
public int getI() {
return i;
}
}
class B extends A {
public int i = 20;
public int sum() {//
return i + 20;
}
public int sum1() {//
return i + 10;
}
public int getI() {
return i;
}
}
- 12-1
/**
* @author Hwang
* @create 2020-07-06 20:45
*/
public class Test {
public static void main(String[] args) {
A a = new B();
System.out.println(a.sum());//30
System.out.println(a.sum1());//20
System.out.println(a.getI());//20
//子类只有重写了getI()这个方法 所以调用的是这个方法
}
}
class A {
public int i = 10;
public int sum() {
return getI()+10;
}
public int sum1() {
return i + 10;
}
public int getI() {
return i;
}
}
class B extends A {
public int i = 20;
public int getI() {
return i;
}
}
-12-2
public class Test {
public static void main(String[] args) {
A a = new B();
System.out.println(a.sum());//20
System.out.println(a.sum1());//20
System.out.println(a.getI());//10
//子类三个方法都没有重写 所以多态时 调用的是A里的方法。
}
}
class A {
public int i = 10;
public int sum() {
return getI()+10;
}
public int sum1() {
return i + 10;
}
public int getI() {
return i;
}
}
class B extends A {
public int i = 20;
}
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.eat();
Man man = new Man();
man.eat();
man.age = 25;
man.earnMoney();
//对象的多态性:父类的引用指向子类的对象
Person p2 = new Man();
//编译时 把p2看程是Person类型的引用。
//解释:Person p2 = new Man(); --> Person p2表示在栈中创建了一个Person类型的引用(变量),编译时他会他会指向Person类型的对象
//new Man() 在堆中创建了一个对象
//运行时,会把new Man()的值(地址值)赋值给p2
//总解释:综上当p2调用对象的方法时,编译时他会先在Peroson对象中找有没有该方法,但是运行时,赋值给他的是Man类型的地址值,
//他会找Man类型的对象中有没有p2调用的该方法。所以p2不能调用Man特有的方法
// Person p3 = new Woman();
//多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法 ---虚拟方法调用
p2.eat();
p2.walk();
// p2.earnMoney();
System.out.println(p2.id);//1001
}
}
class Person {
String name;
int age;
int id = 1001;
public void eat(){
System.out.println("人:吃饭");
}
public void walk(){
System.out.println("人:走路");
}
}
class Man extends Person{
boolean isSmoking;
int id = 1002;
public void earnMoney(){
System.out.println("男人负责挣钱养家");
}
public void eat(){
System.out.println("男人多吃肉,长肌肉");
}
public void walk(){
System.out.println("男人霸气的走路");
}
}
class Woman extends Person{
boolean isBeauty;
public void goShopping(){
System.out.println("女人喜欢购物");
}
public void eat(){
System.out.println("女人少吃,为了减肥");
}
public void walk(){
System.out.println("女人窈窕的走路");
}
}
多态是运行时行为
//编译时行为就是编译之前就能看出结果
class Animal {
protected void eat() {
System.out.println("animal eat food");
}
}
class Cat extends Animal {
protected void eat() {
System.out.println("cat eat fish");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("Dog eat bone");
}
}
class Sheep extends Animal {
public void eat() {
System.out.println("Sheep eat grass");
}
}
public class InterviewTest {
public static Animal getInstance(int key) {
switch (key) {
case 0:
return new Cat ();
case 1:
return new Dog ();
default:
return new Sheep ();
}
}
public static void main(String[] args) {
int key = new Random().nextInt(3);
System.out.println(key);
Animal animal = getInstance(key);//编译时 都看不出来输出的结果
animal.eat();
}
}
虚拟方法调用
class Person {
protected String name = "person";
protected int age = 50;
public String getInfo() {
return "Name: " + name + "\n" + "age: " + age;
}
}
class Student extends Person {
protected String school = "Java";
public String getInfo() {
return "Name: " + name + "\nage: " + age + "\nschool: " + school;
}
}
class Graduate extends Student {
public String major = "人工智能";
public String getInfo() {
return "Name: " + name + "\nage: " + age + "\nschool: " + school + "\nmajor:" + major;
}
}
1. 正常的方法调用
Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
2. 虚拟方法调用(多态情况下):子类中定义了与父类同名同参数的方法,
在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同
子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法
3.编译时类型和运行时类型:编译时e为Person类型,而方法的调用是在运行时确定
的,所以调用的是Student类 的getInfo()方法。——动态绑定
向下转型
1. 语法:子类类型 引用名 = (子类类型)父类引用;
2. 只能强转父类的引用,不能强转父类的对象
3. 要求父类的引用必须指向的是当前目标类型的对象
4. 可以调用子类类型中所有的成员
多态与向下转型的注意点
1. 属性没有重写之说!属性的值看编译类型
2. instanceOf 比较操作符,用于判断某个对象的类型是否为XX类型或XX类型的子类型
动态绑定机制
1. 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
2. 当调用对象属性时,没有动态绑定机制,哪里声明,那里使用
关键字
static
1. static:静态的
2. static可以用来修饰:属性、方法、代码块、内部类
3. 使用static修饰属性:静态变量(或类变量)
静态属性(类变量):我们创建了类的多个对象,多个对象共享同一个静态变量。当通过
某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
非静态属性(实例变量):我们创建了类的多个对象,每个对象都独立的拥有一套类中的
非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性
值的修改。
4. static修饰属性的说明:
① 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用
② 静态变量的加载要早于对象的创建。