【Java笔记】09 面向对象
一、类与对象
- 属性
成员属性=属性=field 访问修饰符 属性类型 属性名;
访问修饰符:public protected 默认 private
属性不赋值有默认值,跟数组默认值相同 - 方法(成员方法)
当程序执行到方法时,开辟一个独立的栈空间;
执行完毕,或者到return语句时,返回;
返回到调用方法的语句,继续执行后面的代码
方法的定义:
访问修饰符 返回数据类型 方法名(形参列表..){//方法体
语句;
return 返回值;
}
同一个类中的方法调用,直接调用即可,不需要创建对象
class A{
//同一个类中的方法调用,直接调用即可,不需要创建对象
pubilc void print(int n){
System.out.println("输出");
}
public void test(){
print();
}
}
跨类中的方法调用:A类调用B类方法,需要通过对象名调用。即创建B类的对象,再调用方法
二、方法重载
java中允许同一个类中,多个同名方法的存在,但要求形参列表不一致
方法名:必须相同
形参列表:必须不同(形参类型或个数或顺序,至少有一样不同,参数名无要求)
返回类型:无要求
三、可变参数
java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法
可变参数的实参可以为0个或任意多个
可变参数的实参可以为数组
可变参数的本质就是数组
可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
一个形参列表中最多只能出现一个可变参数
public class VarParameter{
public static void main(String[] args){
//求2个、3个、4个...参数的和
HspMethod m = new HspMethod();
System.out.println(m.sum(1,5,100));
System.out.println(m.sum(1,19));
}
}
class HspMethod{
//int...表示接受的是可变参数,类型是int,即可接收多个int(0-多)
//使用可变参数时,可以当做数组来使用,即nums可以当做数组
//遍历nums求和即可
public int sum(int... nums){
int res = 0;
for(int i =0;i < nums.length;i++){
res += nums[i];
}
return res;
}
}
四、作用域
1.java中,主要的变量就是属性(成员变量)和局部变量
2.局部变量一般是指在成员方法中定义的变量
3.Java作用域分类
全局变量:即属性,作用域为整个类
局部变量:除了属性以外的其他变量,作用域为定义它的代码块中
4.属性可以不赋值,直接使用,因为有默认值,局部变量必须赋值后才能使用,没有默认值
5.作用域范围
全局变量/属性:可以被本类使用,或其他类使用(通过对象调用)
局部变量:只能在本类中对于的方法中使用
6.全局变量可以加修饰符,局部变量不能加修饰符
五、构造方法/构造器
构造方法又叫构造器,是类的一种特殊的方法,主要作用是完成对新对象的初始化
方法名和类名相同
没有返回值
创建对象时,系统自动的调用该类的构造器完成对象的初始化
[修饰符] 方法名(形参列表){
方法体;
}
1.构造器的修饰符可以默认,也可以时public protected private
2.构造器没有返回值
3.方法名和类名字必须一样
4.参数列表和成员方法规则一样
5.一个类可以定义多个不同的构造器,即构造器重载
6.定义了自己的构造器,默认构造器就覆盖了,除非显式定义一下
public class Constructor{
public static void main(String[] args){
//new一个对象时,直接通过构造器初始化
Person p1 = new Person("jack",3);
}
}
class Person{
String name;
int age;
public Person(String pName,int pAge){
name = pName;
age = pAge;
}
}
六、this
java虚拟机给每个对象分配this,代表当前对象
this 关键字可以用来访问本类的属性、方法、构造器
this 用于区分当前类的属性和局部变量
访问成员方法的语法:this.方法名(参数列表);
class T{
public void f1(){
System.out.println("f1()");
}
public void f2(){
System.out.println("f2()");
//调用本类的f1
//第一种方法
f1();
//第二种方法
this.f1();
}
}
访问构造器语法:this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一 条语句)
class T{
public T(){
this("jack",20);//访问构造器语法:this(参数列表);必须放置第一条语句
System.out.println("T()构造器");
}
public T(String name,int age){
System.out.println("T(String name,int age)构造器");
}
}
this 不能在类定义的外部使用,只能在类定义的方法中使用
七、访问修饰符
公开 public
受保护 protected 对子类和同一包的类公开
默认 对同一个包的类公开
私有 private 不对外公开
只有默认和public可以修饰类
八、封装
把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作[方法],才能对数据进行操作。
步骤:
1.先对属性私有化,让外部不能直接修改属性
2.提供一个公共的set方法,用于对属性判断并赋值
public void setXxx(类型 参数名){
//验证
属性 = 参数名;
}
3.提供一个公共的get方法,用于获取属性的值
public getXxx(){//权限判断
return xx;
}
- 有构造器时想使用set方法,在构造器中使用set
public Person(String name, int age, double salary) {
// this.name = name;
// this.age = age;
// this.salary = salary;
setSalary(salary);
setAge(age);
setName(name);
}
九、继承
多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类(基类,超类),在父类中定义这些相同的属性和方法,子类(派生类)通过extends声明继承父类
子类继承了所有的属性和方法,但私有属性和方法不能在子类直接访问,要通过父类公共的方法;
创建子类时,不管使用子类的哪个构造器,默认都会调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super指定使用父类的哪个构造器,即在子类构造器中写super(对应参数列表);
super()使用时放在构造器的第一行,super只能在构造器中使用;
super()和this()都只能放在第一行->不能同时存在;
java所有类都是Object类的子类,Object是所有类的基类;
父类构造器的调用不限于父类,而是一直往上追溯;
java单继承,子类最多只能直接继承一个父类;
子类和父类之间要满足is-a关系;
public class Encap {
public static void main(String[] args) {
Son son = new Son();
//内存的布局
// 要按照查找关系来返回信息
// (1) 首先看子类是否有该属性
// (2) 如果子类有这个属性,并且可以访问,则返回信息
// (3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息..)
// (4) 如果父类没有就按照(3)的规则,继续找上级父类,直到 Object...
System.out.println(son.name);//返回就是大头儿子
//System.out.println(son.age);//会报错,age是私有的属性
System.out.println(son.getAge());//返回的就是 39
System.out.println(son.hobby);//返回的就是旅游
}
}
class GrandPa {//爷类
String name = "大头爷爷";
String hobby = "旅游";
}
class Father extends GrandPa {//父类
String name = "大头爸爸";
private int age = 39;
public int getAge() {
return age;
}
}
class Son extends Father { //子类
String name = "大头儿子";
}
十、super关键字
访问父类的属性、方法、构造器:
1.访问属性(不能访问父类的private属性)
super.属性名;
2.访问父类的方法,不能访问父类的private方法
super.方法名(参数列表);
3.访问父类的构造器
super(参数列表); 只能放在第一句
子类、父类方法有重名时,为了访问父类的成员,用super。没有重名,super,this,直接访问一样
找方法时,顺序:
1.先找本类,如果有,调用
2.如果没有,则找父类。父类如果有,并可以调用,则调用
3.如果父类没有,继续找父类的父类,知道Object
查找方法过程中,找到了但不能访问(比如private),报错
十一、方法重写/覆盖
子类有一个方法和父类的某个方法的名称、返回类型、参数一样,子类的这个方法覆盖了父类的方法
返回类型父类的返回类型是子类的父类也可以。比如父类返回类型是Object,子类是String
子类方法不能缩小父类方法的访问权限
- 比较方法重载和重写 | 名称 | 发生范围 | 方法名 | 形参列表 | 返回类型 | 修饰符 | | —- | —- | —- | —- | —- | —- | | 重载 | 本类 | 必须一样 | 类型,个数或顺序至少有一个不同 | 无要求 | 无要求 | | 重写 | 父子类 | 必须一样 | 相同 | 子类重写的方法,返回类型和父类的返回类型一直或者是其子类 | 子类方法不能缩小父类方法的访问范围 |
多态
问题:代码复用性不高
解决方案:多态—>方法或对象有多种形态
- 方法的多态
方法重载、重写体现多态 - 对象的多态
(1)一个对象的编译类型和运行类型可以不一致
父类的引用指向子类的对象 Animal an = new Dog(); an的编译类型是Animal,运行类型是Dog
(2)编译类型在定义对象时就确定了,不能改变
(3)运行类型时可以变化的
an = new Cat(); an的运行类型变成了Cat,编译类型仍然是Animal
(4)编译类型看定义时=的左边,运行类型看=的右边
public class Poly{
//animal编译类型就是Animal,运行类型Dog
Animal animal = new Dog();
animal.cry(); //狗叫
//animal编译类型就是Animal,运行类型Cat
animal = new Cat();
animal.cry(); //猫叫
}
多态的前提时两个对象/类存在继承关系
- 向上转型
(1)本质:父类的引用指向了子类的对象
(2)语法:父类类型 引用名 = new 子类类型();
(3)可以调用父类中的所有成员(遵循访问权限),不能调用子类中的特有成员
编译阶段 调用哪些方法是编译类型决定的,比如animal想调用Cat类特有的catchMouse()方法编译报错
运行阶段 调用是先从子类开始的,遵循方法调用规则 - 向下转型
子类类型 引用名 = (子类类型)父类引用;
Cat cat = (Cat) animal;
cat.catchMouse();
只能强转父类的引用,不能强转父类的对象
父类的引用必须指向的是当前目标类型的对象
Animal animal = new Cat(); //此时的animal指向的是Cat
Cat cat = (Cat) animal; //这样才能向下转型成Cat 如果是转成Dog类 不对
向下转型后,可以调用子类类型中所有的成员 - 属性不重写,属性的值看编译类型
public class Poly01 {
public static void main(String[] args) {
//属性没有重写之说!属性的值看编译类型
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 比较操作符,用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型
- 动态绑定机制
当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
public class DynamicBinding {
public static void main(String[] args) {
//a 的编译类型 A, 运行类型 B
A a = new B();//向上转型
System.out.println(a.sum());//?40 -> 30
System.out.println(a.sum1());//?30-> 20
}
}
class A {//父类
public int i = 10;
//动态绑定机制:
public int sum() {//父类sum()
return getI() + 10;//20 + 10
}
public int sum1() {//父类sum1()
return i + 10;//10 + 10
}
public int getI() {//父类getI
return i;
}
}
class B extends A {//子类
public int i = 20;
// public int sum() {
// return i + 20;
// }
public int getI() {//子类getI()
return i;
}
// public int sum1() {
// return i + 10;
// }
}
- 多态数组
数组的定义类型为父类类型,里面保存的实际元素为子类类型
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
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 String say(){
return name + '\t' + age;
}
}
public class Student extends Person{
private double score;
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String say(){
return super.say() + "score=" + score;
}
public void study(){
System.out.println(getName()+"正在学习");
}
}
public class Teacher extends Person{
private double salary;
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String say(){
return super.say()+"salary="+salary;
}
public void teach(){
System.out.println(getName()+"正在讲课");
}
}
public class PolyArray {
public static void main(String[] args) {
Person[] persons = new Person[5];
persons[0] = new Person("jack",20);
persons[1] = new Student("jack",18,100);
persons[2] = new Student("smith",19,30.1);
persons[3] = new Teacher("s",30,20000);
persons[4] = new Teacher("king",50,25000);
//循环遍历多态数组,调用say()
for(int i = 0;i < persons.length;i++){
System.out.println(persons[i].say());//动态绑定,编译类型是Person,运行类型根据实际情况
if(persons[i] instanceof Student){ //运行类型是不是Student
((Student)persons[i]).study(); //向下转型
} else if(persons[i] instanceof Teacher){
Teacher teacher = (Teacher) persons[i];
teacher.teach();
} else if(persons[i] != null){
System.out.println("");
}else{
System.out.println("类型有误");
}
}
}
}
- 多态参数
形参为父类类型,实参为子类类型
public class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public double getAnnual() {
return 12.0D * this.salary;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return this.salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
public class Manager extends Employee {
private double bonus;
public Manager(String name, double salary, double bonus) {
super(name, salary);
this.bonus = bonus;
}
public double getBonus() {
return this.bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
public void manage() {
System.out.println("经理 " + this.getName() + " is managing");
}
public double getAnnual() {
return super.getAnnual() + this.bonus;
}
}
public class Worker extends Employee {
public Worker(String name, double salary) {
super(name, salary);
}
public void work() {
System.out.println("普通员工 " + this.getName() + " is working");
}
public double getAnnual() {
return super.getAnnual();
}
}
public class PloyParameter {
public PloyParameter() {
}
public static void main(String[] args) {
Worker tom = new Worker("tom", 2500.0D);
Manager milan = new Manager("milan", 5000.0D, 200000.0D);
PloyParameter ployParameter = new PloyParameter();
ployParameter.showEmpAnnual(tom);
ployParameter.showEmpAnnual(milan);
ployParameter.testWork(tom);
ployParameter.testWork(milan);
}
public void showEmpAnnual(Employee e) {
System.out.println(e.getAnnual());
}
public void testWork(Employee e) {
if (e instanceof Worker) {
((Worker)e).work();
} else if (e instanceof Manager) {
((Manager)e).manage();
} else {
System.out.println("");
}
}
}
Object类
是类层次结构的根类,每个类都使用Object作为超类,所有对象都实现这个类的方法。equals方法
==和equals的对比:
==是一个比较运算符,既可以判断基本类型,也可以判断引用类型。基本类型:值是否相等,引用类型:判断地址是否相等。
equals只能判断引用类型,默认判断地址是否相等,子类中往往重写该方法,判断内容是否相等,如String类型。//Object类的equals方法
public boolean equals(Object obj){
return (this == obj); // 比较对象地址是否相同
}
//Integer类的equals方法
public boolean equals(Object obj){
if(obj instanceof Integer){
return value == ((Integer)obj).intValue();
}
return false;
}
Integer i1 = new Integer(1000);
Integer i1 = new Integer(1000);
System.out.println(i1 == i2); // false
System.out.println(i1.equals(i2)); //true
hashCode()
提高具有哈希结构的容器的效率
两个引用,指向同一个对象,哈希值一样
两个引用,指向不同对象,哈希值不一样
哈希值主要依据地址号,不能完全将哈希值等价于地址
```java public class HashCode_ { public static void main(String[] args) { A a = new A(); A a1 = new A(); A a2 = a; System.out.println(a.hashCode()); //1163157884 System.out.println(a1.hashCode()); //1956725890 System.out.println(a2.hashCode()); //1163157884 } }
class A {}
3. toString()<br />默认返回:全类名+@+哈希值的十六进制 子类往往重写toString方法,用于返回对象的属性信息 <br />重写toString方法,打印对象或拼接对象时,都会自动调用该对象的toString形式 <br />直接输出一个对象时,toString方法会被默认的调用
```java
public class ToString_ {
public static void main(String[] args) {
/*
Object的toSting() 源码
1. getClass().getName() 类的全类名(包名+类名)
2. Integer.toHexString(hashCode()) 将对象的hashCode值转成16进制字符串
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
*/
M m = new M("小妖怪", "巡山", 1000);
System.out.println(m.toString()+" hashCode="+m.hashCode());
// object.M@4554617c hashCode=1163157884
}
}
class M{
private String name;
private String job;
private double sal;
public M(String name, String job, double sal) {
this.name = name;
this.job = job;
this.sal = sal;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
}
// 重写toString方法,输出对象的属性
// 使用快捷键即可 alt+insert -> toString
@Override
public String toString() { // 重写后,默认把对象的属性输出
return "M{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
", sal=" + sal +
'}';
}
M m = new M("小妖怪", "巡山", 1000);
System.out.println(m.toString()); // M{name='小妖怪', job='巡山', sal=1000.0}
System.out.println("当直接输出一个对象时,toString方法会被默认调用");
System.out.println(m); // 等价 m.toString()
/*
当直接输出一个对象时,toString方法会被默认调用
M{name='小妖怪', job='巡山', sal=1000.0}
*/
finalize()
当对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法,释放资源
什么时候被回收:当某个对象没有任何引用时。jvm认为这个对象是一个垃圾对象,使用垃圾回收机制销毁该对象,销毁该对象前,先调用finalize方法
垃圾回收机制的调用,是由系统决定,也可以通过System.gc()主动触发垃圾回收机制public class Finalize_ {
public static void main(String[] args) {
Car c = new Car("车");
// 这时,car对象就是一个垃圾,回收器会回收对象,在销毁对象前,会调用该对象的finalize方法
// 在finalize中,写自己的业务逻辑,比如释放资源:数据库连接,或打开文件..
// 如果不重写,就会调用Object类的finalize,即默认处理
c = null;
System.gc();// 主动调用垃圾回收机制
System.out.println("退出");
}
}
class Car {
private String name;
public Car(String name) {
this.name = name;
}
// 重写finalize
@Override
protected void finalize() throws Throwable {
System.out.println("销毁");
}
}