封装
封装的作用
封装操作和我们利用遥控器调节音量控制电视机很类似
封装的步骤
person.salary=2000;//报错了因为salary是private的所以只能在本类使用
这里就体现出来封装的重要性,对于私有属性的数据不能随意更改和访问
package Java_base_study;
public class Encap_example {
public static void main(String[] args) {
Person person = new Person();
//尝试去对于person 对象赋初值
//使用传统方法
person.name="zlh";
//person.salary=2000;//报错了因为salary是private的所以只能在本类使用
//这里就体现出来封装的重要性,对于私有属性的数据不能随意更改和访问
//既然如此我们该怎么办呢?
//这里我们就要借助Person类的set()和get()方法,它们是可以访问private属性的数据的呀
person.setSalary(2000);//成功!!!
}
}
class Person {
//定义一个Person 类要求除了人名其他都对外保密
public String name;
private int age;
private int salary;
private String job;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
}
继承
继承的作用
继承的格式:
继承细节(子类必须先去调用父类的构造器因为会隐藏代替默认调用所以特容易忘):
这里需要注意非私有属性和方法可以在子类直接访问(为什么默认属性的数据也可以直接访问呢?)
注意点1:默认访问修饰符的特殊性
在同一个包下默认访问修饰符可以访问父类
但是假如跨包了就不能访问
打个比方:
Package1:
Class Student (属性有默认的int age,私有的private int score)
Class grduate(extends Student)------>这时候由于他俩在一个包下所以子类可以访问父类的默认属性信息int age
Package2:
Class pupil(继承可以跨包吗?不管能不能反正现在pupil子类一定没办法访问到父类的默认属性信息int age)
注意点2(子类构造器在调用时必须先去调用父类的构造器!!!):
子类构造器在调用时必须先去调用父类的构造器
顺序:
父类构造器被调用()
子类构造器被调用()
这张图很重要记住
注意点3:
假如父类没有提供无参构造器,必须在子类构造器中用super关键字指定调用父类的哪个构造器
比如:父类只有一个有参构造器 public Student (String name,int age){
this.name=name; this.age=age;
}
此时由于父类没有提供无参构造器,我们需要在子类构造器中加入super(“小张”,100);
表明我们调用的是public Student (String name,int age)这个构造器
注意点4:
super()只能在构造器中使用,且必须放在第一行
注意点5:
在构造器中可以使用this()调用另一个构造器但是必须放在第一行,所以this()和super()语句不能共存
注意点6:
java所有类都是Object类的子类, Object是所有类的基类.
注意点7:
子类最多只能直接继承一个父类(单继承机制)
继承的本质分析(重要)
继承的内存布局
属性访问(就近原则)
在就近原则查找属性是假如因为一个数据是私有的而无法访问会直接报错,不会再到父类查找数据
比如:
son类的age我们定义为private那么访问son.age时,会直接报错,不会去找father.age然会返回father类的年龄!!!
继承例子
=Computer类=
package ExtendsExercise;
public class Computer {
// 编写Computer类,包含CPU、内存、硬盘等属性,getDetails方法用于返回Computer的详细信息
// 编写PC子类,继承Computer类,添加特有属性【品牌brand】
private String cpu;
private int neicun;
private int disk;
//构造器
public Computer(String cpu, int neicun, int disk) {
this.cpu = cpu;
this.neicun = neicun;
this.disk = disk;
}
//输出computer的详细信息(这里试过了默认和public都可以不影响)
public String getDetails(){
return("cpu"+cpu+"neicun"+neicun+"disk"+disk);
}
//get 和 set 函数
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public int getNeicun() {
return neicun;
}
public void setNeicun(int neicun) {
this.neicun = neicun;
}
public int getDisk() {
return disk;
}
public void setDisk(int disk) {
this.disk = disk;
}
}
=ipad类=
package ExtendsExercise;
public class ipad extends Computer{
//编写NotePad子类,继承Computer类,添加特有属性【color】
private String color;
//==很有意思的是假如这里我们不编写ipad(...)构造器,会报错Computer类没有默认构造器这是为啥?==
//==我们知道ipad类中默认有ipad()构造器里面默认有super()调用Computer的无参构造器==
//假如这里我们不写有参构造器,他就去调用Computer的无参构造器,但Computer类里没无参构造器啊,当然会报错.
public ipad(String cpu, int neicun, int disk, String color) {
super(cpu, neicun, disk);
this.color = color;
}
public String getInfo(){
//注意getDetails()虽然是Computer类中方法但是是public的,所以ipad也能调用。
return getDetails()+"color"+color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
super方法
基本方法:
super.属性名和super.方法名访问父类的属性和方法
super()访问父类的构造器
使用super()方法的好处
-
调用父类的构造器的好处(分工明确,父类属性由父类初始化,子类的属性由子 类初始化)
-
当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super()
如果没有重名,使用super、this、直接访问是一样的效果!
分工明确
public ipad(String cpu, int neicun, int disk, String color) {
super(cpu, neicun, disk);
this.color = color;
}
三种方法
public void sum() {
System.out.println("B类的sum()");//希望调用父类-A 的cal方法
//这时,因为子类B没有cal方法,可以使用下面三种方式
//找cal方法时,顺序是:
// (1)先找本类,如果有,则调用
// (2)如果没有,则找父类(如果有,并可以调用,则调用)
//(3)如果父类没有,则继续找父类的父类,整个规则,就是一样的,直到0bject类
//提示:如果查找方法的过程中,找到了,但是不能访问,则报错,如果查找方法的过程中,没有找到,则提示方法不存在
//cal();//法一
//this.cal();//法二:等价cal()
super.cal();// 法三:找cal方法的顺序是直接查找父类,其他的规则一样
例子:
多态
多态就是指方法和对象具有多种形态
小动物喂食问题引出多态
方法的多态就是方法的重载和重写
代码对比实现
package Java_base_study.homework.poly;
public class Test {
public static void main(String[] args) {
Master zhang = new Master("zhang");
Animal animal = new Cat("cjb");
Food food = new fish("鳕鱼");
zhang.feed(zhang,animal,food);
}
}
class Master{
public Master(String name) {
this.name = name;
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//实现主人给具体动物喂具体食物的功能,一种动物就得写一种方法,就很烦,这时就可以使用多态
// public void feed(Master master,Cat cat,fish fish){
// System.out.println("主人"+master.name+"给小猫"+cat.getName()+"喂"+fish.getName());
// }
//使用多态
public void feed(Master master,Animal animal,Food food){
System.out.println("主人"+master.name+"给动物"+animal.getName()+"喂"+food.getName());
}
}
class Animal{
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
}
class Food {
private String name;
public Food(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class fish extends Food{
public fish(String name) {
super(name);
}
}
对象的多态(核心)
(1)一个对象的编译类型和运行类型可以不一致
(2)编译类型在定义对象时,就确定了,不能改变
(3)运行类型是可以变化的.
(4)编译类型看定义时=号的左边,运行类型看=号的右边
Animal animal = new Dog(); 【animal编译类型是Animal,运行类型Dog】
animal = new Cat(); 【animal的运行类型变成了Cat,编译类型仍然是 Animal】
向上转型(解决给小动物喂食问题)
Animal animal = new Cat();
向上转型:父类的引用指向了子类的对象
语法:父类类型引用名= new子类类型();
0bject obj = new Cat();//可以 Object 也是 Cat的父类
向上转型调用方法规则
向上转型可以调用父类中的所有成员(需遵守访问权限)
但是不能调用子类的特有的成员
因为在编译阶段,能调用哪些成员,是由编译类型(Animal)来决定的------>animal.catchMouse();错误,catchMouse是猫类特有的方法
最终运行效果看子类的具体实现,即调用方法时,采用就近原则(子类有就要子类)
但注意了假如调用的是属性,属性没有重写的说法,它将调用根据编译类型调用属性,具体的看下面的属性重写问题一栏
总而言之记住一句话:属性看编译类型,方法看运行类型
animal.eat();//猫吃鱼,.
animal.run();
animal.show() ;//eat()、run()都是animal父类中的方法
向下转型(解决无法调用子类成员问题)
写在前面:此处向下转型的代码是建立在前面向上转型代码的基础上的
1)语法:子类类型 引用名=(子类类型)父类引用;
2)只能强转父类的引用,不能强转父类的对象
3)要求父类的引用必须原先指向的就是当前目标类型的对象
4)当向下转型后,可以调用子类类型中所有的成员
一、首先语法:Cat cat=(Cat) animal;-------->将animal(父类的引用)强转为Cat(子类类型)
二、接下来不能强转父类的对象怎么理解?
父类的对象可以理解为一个具体的人(这个对象是永远不变的)----->不可能把一个特定人变成一只特定狗
但是他的引用是可以变的,比如他的身份是不断变的,从小学生—>大学生这是没关系的
三、要求父类的引用必须原先指向的就是当前目标类型的对象
在向上转型过程中,我们进行了如下操作:Animal animal = new Cat();
就是说animal这个引用原先指向的就是Cat这个对象
错误示范:Dog dog=(Dog)animal(这句话就是将一只猫强转为一直狗,这是不允许的)
例子:
属性的重写问题(属性没有重写之说)
很搞!!!这句话很重要属性没有重写之说!属性的值看编译类型Base base = new Sub();
属性和方法是不一样的假如这里是base.count()调用一个方法而不是调用base.count 属性那么,base.count()采用就近原则执行Sub类中方法
总而言之记住一句话:属性看编译类型,方法看运行类型
public class PolyDetail02 {
public static void main(String[] args){
//属性没有重写之说!属性的值看编译类型Base base = new Sub();//向上转型
Base base=new Sub();
System.out.println(base.count);// ?看编译类型10
Sub sub = new Sub();
System.out.println(sub.count);//?输出20
}
}
class Base {//父类
int count = 10;//属性
}
class Sub extends Base{
int count =20;
}
instanceof 方法
instanceOf比较操作符,用于判断对象的运行类型是否为XX类型或XX类型的子类型
public static void main(String[l args) {
BB bb = new BB();
System.out.println(bb instanceof BB);// true
System.out.println(bb instanceof AA);// true
//aa 编译类型AA,运行类型是BB
AA aa = new BB();
system.out.println(aa instanceof AA);//true
System.out.println(aa instanceof BB);//true
}
假如说是判断对象的引用类型那么 aa instanceof AA 返回的是false;
动态绑定机制(非常重要)
动态绑定机制
1.当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
2.当调用对象属性时,==没有动态绑定机制,==哪里声明,那里使用
代码演示
class A{
public int i= 10;
public int sum() {return get1()+ 10;}
public int sum1(){return i+ 10;}
public int getl(){return i;}
}
class B extends A {//子类
public int i = 20;
//public int sum() {return i+ 20;}
public int getl() {return i;}
//public int sum1(){return i + 10;}
}
//main方法中
{A a = new B();//向上转型
System.out.printIn(a.sum());//a.sum()调用编译类型(类A)的方法sum()调用get1()方法
//这时根据调用对象方法时,该方法会和该对象的内存地址/运行类型(类B)绑定原则调用类B的get1()方法return i=20;最终a.sum()=30
System.out.println(a.sum1());//a.sum1()调用编译类型(类A)的方法sum1()return i+10;
//这时根据调用对象属性时,没有动态绑定机制,哪里声明,那里使用原则i+10=20
}
动态数组(多态运用)
如何调用子类特有的方法?
组合
组合是通过对现有对象进行拼装即组合产生新的具有更复杂的功能。如:
/**
* 动物
*/
public class Animal {
public void breathing() {
System.out.println("呼气...吸气...");
}
}
/**
* 爬行动物
* 组合
*/
public class Reptilia {
private Animal animal;
public Reptilia(Animal animal) {
this.animal = animal;
}
public void crawling() {
System.out.println("爬行...");
}
public void breathing() {
animal.breathing();
}
public static void main(String[] args) {
Animal animal = new Animal();
Reptilia reptilia = new Reptilia(animal);
reptilia.breathing();;
reptilia.crawling();
}
}
组合体现的是整体和部分,强调的是has-a的关系。所以组合更多的用于下面这样的场景:
/**
* 轮胎
*/
class Tire {
public void run() {
System.out.println("轮胎转动...");
}
}
/**
* 车灯
*/
class Light {
public void light() {
System.out.println("灯亮...");
}
}
/**
* 交通工具
* 组合
*/
public class Vehicle {
private Tire tire;
private Light light;
public Vehicle(Tire tire,Light light) {
this.tire = tire;
this.light = light;
}
public void operation() {
light.light();
tire.run();
}
public static void main(String[] args) {
Tire tire = new Tire();
Light light = new Light();
Vehicle vehicle = new Vehicle(tire,light);
vehicle.operation();
}
}
继承与组合的区别与联系
继承与组合都是面向对象中代码复用的方式。父类的内部细节对子类可见,其代码属于白盒式的复用,而组合中,对象之间的内部细节不可见,其代码属于黑盒式复用。继承在编码过程中就要指定具体的父类,其关系在编译期就确定,而组合的关系一般在运行时确定。继承强调的是is-a的关系,而组合强调的是has-a的关系。