文章目录
面向对象基础复习笔记
1.对象与类基础
1.1利用内存图初步理解Java程序运行机制
Address
public class Address {
public String city;
public String street;
public int zipCode;
}
User
public class User {
public int id;
public String username;
public Address address;
}
Test
public class Test {
public static void main(String[] args) {
Address a= new Address();
a.city="北京";
a.street="大兴区";
a.zipCode=666;
System.out.println("---------------");
User u= new User();
u.id=1;
u.username="lee";
u.address=a;
}
}
对应的内存图及步骤如下所示
内存图可加深对Java程序的运行机制。其中
方法区存储的是类编译之后的字节码片段(代码片段)。由类加载器(ClassLoader)从硬盘中将编译好的字节码文件加载到JVM中的方法区。
当方法区中的方法执行时,会在栈中分配空间以存储相应方法需要的局部变量,方法结束时,释放掉内存。方法间可互相调用,需要的空间内存的分配满足FILO顺序(即栈)。
堆内存用于存储 new 出来的对象,当无引用指向对象时,对象将等待被GC回收。
注意:JVM只了解了个皮毛,以后有时间有需要去看深入了解Java虚拟机。
1.2构造方法
通过new关键字对构造方法进行调用,其目的是创建对象并给实例变量进行赋值(并非类加载时给实例变量赋值),缺省构造方法是无参构造方法,该方法在由程序员添加任意构造方法后消失,为了避免破坏已有的程序,请手动添加无参构造方法。
2.封装
2.1封装基本要素
- 属性私有
- 根据情况,提供set和get方法
2.2封装好处
- 隐藏细节
- 安全可靠
e.g 以大学生的6级英语成绩和年龄为例
public UnderGraduatet{
private age;
private int CET6score;
//隐藏细节
public void setScore(){
//按照应届参加英语考试的学生占比和题型占比进行计算
//.............
CET6score =??? ;
}
//安全可靠
public void setAge(int age){
if(age<= 0 || age >=200){
//异常处理
}
this.age = age;
}
}
3.static And this
3.1static的基本语法
static是一个关键字,可用于修饰变量和方法,被static修饰的变量叫做静态变量,修饰的方法叫做静态方法。
public Student{
static String schoolName="JiaLiDun";//静态变量
static String getSchoolName(){//静态方法
return schoolName;
}
}
访问静态变量和静态方法,常用类名.静态变量和类名.静态方法,如Student.schoolName
和Student.getSchoolName
。对象.静态变量和对象.静态方法不是不可以,但是这样会让其他人误认为该变量和方法是实例变量和实例方法。
注意:对象.静态变量和对象.静态方法编译时会被自动转化为引用类型类名.静态变量和引用类型类名.静态方法.也就会有下面一个奇异现象:
public static void main(String[] args) {
Student student = null;
System.out.println(student.schoolName);//equals to Student.schoolName;
}
结果
JiaLiDun
并没有出现空指针异常。
3.2静态变量&实例变量&局部变量
局部变量存储在栈中,实例变量存储在堆中,静态变量存储在方法区中。当类加载时,静态变量就会被初始化并保存在方法区中。
那么什么时候用静态变量,什么时候用实例变量呢?
- 如果该变量是属于该类所有对象所共有的,用于描述这个类的特征,那么就使用静态变量,如类Chinese的nationality就应该定义为静态变量。
- 如果该变量不是属于该类所有对象的,仅用来描述某个对象的特征,那就是用实例变量,如类Chinese的name,age就应该定义为实例变量。
public class Chinese{
public static final String nationality = "China";// 均为China
private String name;
private int age;
// .............
}
3.3类内部静态&实例方法调用关系及其对静态&实例变量调用关系
简记如下:
- 静态方法只能调用静态方法和静态变量
- 实例方法都可以调用实例方法,实例变量,静态方法,静态变量。
此处所说的相互调用不是说通过new一个对象然后调用相关方法,详情见案例。
分析如下:
-
实例方法和实例变量的调用必须依赖于对象。即对象.实例方法/实例变量。
-
静态方法和静态变量的调用无需依赖对象,即使通过对象调用的静态方法和静态变量最终都等价于类名.静态方法/静态变量。
-
承接1,2在类的方法内部,如果是实例变量和实例方法则等价于this.实例变量和this.实例方法;如果是静态变量和静态方法则等价于类名.静态变量和类名.静态方法。其中this表示当前对象。
public Class Man{ public static String gender="MAN"; public static String getGender(){ //getAge(); equals to this.getAge() ,but static methods can be called by TypeName,and that's to say ,you can't get instance variables or call instance methods before instancing a object. return gender;//equals to return Man.gender; } public int age; public int getAge(){ getGender();//equals to call Man.getGender(); return age;//equals to return this.age; } }
-
核心就是:实例变量和实例方法必须由对象调用。静态方法中不存在this指向自身对象,自然也就不能调用实例变量和实例方法。
3.4静态代码块&实例代码块
静态代码块在类加载时执行(只执行一次),与静态变量的分配空间的时机是相同的。因此如果想在静态代码块中对静态变量赋值,那么静态变量一定要放在静态代码块上面。静态代码块执行的时机也说明了静态代码块不能对实例变量和实例方法进行访问。
实例代码块在创建对象时,构造方法前执行,可以对实例变量进行初始赋值等等。
public class Main{
public static void main(String [] args){
Student student = new Student();
student = new Student();
}
}
class Student{
public static String schoolName;
public int age;
static{
schoolName="Peking University";
System.out.println(schoolName);
}
{
age = 18;
System.out.println(age);
}
public Student(){
age=20;
System.out.println(age);
}
}
执行结果
Peking University
18
20
18
20
3.5 this and this()
this用于指向本身对象,this不能出现在静态方法中,在实例方法内部实例变量一般可以省略this,但是当有同名的局部变量出现时,需要用this.实例变量显式声明实例变量。
this()务必写在构造方法第一行代码,用于调用其他构造方法,其目的就是代码复用。
class Student{
public int age;
public String name;
public String addr;
public Student(){
this(0," Donald John Trump","The White House");
}
public Student(int age ,String name,String addr){
this.age=age;
this.name=name;
this.addr=addr;
}
}
4.继承
4.1继承基本概念
类之间单继承,接口之间多继承。仅类之间而言,继承谈的是子类可以继承父类的所有属性和方法。(包括私有属性,私有方法,静态属性,静态方法)。
其中,私有属性和私有方法也可以被继承,只是子类对象无法直接调用,如果父类另外提供方法实现对私有属性的访问和私有方法的访问,那么子类对象可以调用此方法访问由父类继承而来的属性和方法。(重点是理清,可见性修饰符的作用是控制属性和方法的访问权限,与继承是两个概念)
此外,静态属性和静态方法也继承了,但是静态方法不存在重写。见后文重写。
测试代码如下:
package com.lordbao.demo;
/**
* @Author Lord_Bao
* @Date 2020/8/26 17:52
* @Version 1.0
*/
public class Main {
public static void main(String[] args) {
Son x = new Son();
System.out.println(Son.GENDER);
Son.sayHello();
System.out.println( x.getAge());
x.callSayHi();
}
}
class Son extends Father{
}
class Father{
private int age=12;
public int getAge() {
return age;
}
private void sayHi(){
System.out.println("sayHi");
}
public void callSayHi(){
System.out.println("callSayHi");
sayHi();
}
public int getAge() {
return age;
}
public final static String GENDER="MALE";
public static void sayHello(){
System.out.println("Father");
}
}
测试结果:
Father
12
callSayHi
sayHi
4.2方法重写和方法重载
当父类的方法在子类不再适合的时候,就需要对继承而来的方法重写。
重写要求:
- 方法名和方法参数列表(即签名不变)
- 访问修饰符可以更宽松(如父类protected,子类public)
- 返回类型可以更精准(如父类返回Object,子类返回Son)
- 抛出的异常不能更多,可以更少,抛出的异常类型可以更精细。(受检异常满足4,非受检异常可不遵守此规则)
事实上,通常把需要重写的方法复制粘贴,然后改改方法体完事。
注意:
- 重写存在于子类和被继承的父类或是接口之间。
- 重写的概念针对的是方法,准确来说是非private的实例方法。
- 构造方法和private和静态方法无法被重写。
- 即使子类写了和父类private或静态方法一模一样的方法,那也不当做是重写的方法。(你可以加个@Override注解试一试)
至于第三点为什么成立,我个人理解重写的目的是继承而来的方法不适合时,自己再重新改造,private方法压根就不能用,谈不上适不适合。而静态方法是类级别的方法,子类就算重写也没什么价值。实在不行,记住就好。
至于方法重载,就是同一个类的方法名相同,参数列表不同(比如参数个数不同,参数类型不同,参数顺序不同)的一堆方法,跟访问修饰符和返回类型,抛出异常没关系。
5.多态
5.1多态基本概念
多态表象:父类型的引用变量可以指向子类型的实例对象。
多态实质:
-
静态绑定:当一个对象调用实例方法时,编译阶段编译能不能通过是看引用类型有没有这个 可见的(不管是继承而来的或是自己写的) 实例方法。核心:可见性。
-
动态绑定:运行阶段实际调用的方法按照继承链逐步往上找,找到了就执行。核心:继承链。
多态前提:多态是建立在继承类(或是实现接口)和重写上面的,后面有例题进一步阐述。
多态目的:增强可扩展性,减少代码耦合度。
下面的例子是为了说明多态的可扩展性。
public class Main1 {
public static void main(String[] args) {
Man a = new Man();
a.eat(new GuangDongCai());
}
}
class Man{
public void eat(Food food){
food.cooked();
}
// 这种方式扩展性太差
// public void eat(GuangDongCai guangDongCai){
// guangDongCai.cooked();
// }
}
class Food{
public void cooked(){
}
}
class HeiLongJiangCai extends Food{
@Override
public void cooked() {
System.out.println("东北乱炖");
}
}
class GuangDongCai extends Food{
@Override
public void cooked() {
System.out.println("福建仁好吃");
}
}
class ChongQingCai extends Food{
@Override
public void cooked() {
System.out.println("火锅最爽");
}
}
5.2方法在多态中调用顺序
方法编译能不能通过,要看引用数据类型有没有可见的方法,一般情况下,实际运行结果将按照实际类型逐步往上查,直到查到相关方法正常结束。
出个例题,醒一醒神
class Daddy{
private String name="Daddy";
public String email="Daddy@163.com";
private void printName(){
System.out.println(name);
}
public void printEmail(){
System.out.println(email);
}
public static void main(String[] args) {
Daddy a = new Kid();
a.printEmail();
a.printName();
Kid k = new Kid();
k.printEmail();
k.printName();
}
}
class Kid extends Daddy{
private String name="Kid";
public String email="Kid@163.com";
public void printName(){
System.out.println(name);
}
}
答案
Daddy@163.com
Daddy
Daddy@163.com
Kid
这里总结如下思路
1.方法编译能不能通过,看引用类型是不是有可见的实例方法,重点是可见性,甭管是继承而来的或是自己写的,总之可见性这一关要通过。
2.方法实际的运行结果首先看该方法由引用类型变量那里能不能够被重写,如果不能被重写,比如private方法,那么结果与多态无关,答案是 父类对象调用方法的结果(比如上文的Daddy)。如果是能被重写的,那么就按照实际类型沿继承链逐步往上面找,找到了,是什么就是什么。
6.super
super主要有两个语法,一个语法是super.属性或方法来调用继承而来的子类可见的属性和方法,另一个语法是super()放在构造方法第一行用来初始继承而来的父类的实例变量。
如果没有在构造方法第一行显式地调用this()或super()方法,那么默认调用super()方法,因此每一个类提供一个无参构造方法很有必要,其次super()和this()只能出现一个,this表示指向本对象的引用,而super只是一个关键字而已,它不是一个引用。这里可以得出一个结论,只要创建对象那么Object的构造方法总是最先被调用。
7.final
final一句话,任意被final修饰的东西都是不可变的。
-
final修饰的类是不可继承的。(如String类)
-
final修饰的方法是无法被重写的(所以final和abstract不能一起出现)
-
final修饰的变量只能初始化一次
- 实例变量:java默认地赋 null ,0的机制不再适合,要求程序员必须手动在如下三个地方中挑一个赋值(实例域赋值,实例代码块赋值,构造方法赋值)
- 静态变量:在静态实例域必须手动赋值。不能在静态代码块对其初始化。
- 局部变量:局部变量可以不赋值,前提是你不访问,当然这样没有任何意义。局部变量也是只能赋值一次。
事实上,final 常与static连用,用于修饰静态常量。
8.抽象类
抽象类可以看做是接口和实现类的纽带,抽象类是半抽象的。抽象类是对事物的抽象,也是对接口的进一步细化。比如 HttPServelt 和 Servlet的区别。抽象类的主要作用是两个:1.面向抽象编程,配合多态,以迎合更多的场景。2.简化编程,重写接口会导致太多代码冗余,而且有些方法是不需要实现的。
抽象类的基本语法:抽象类必须有abstract关键字,抽象类有构造方法,不一定有抽象方法。抽象方法是没有方法体的。抽象类不能new对象,非抽象类继承抽象类必须实现抽象方法。
public abstract class A{
public abstract void play();
}
除了抽象方法以外,还有一种方法也没有方法体,比如 public native int hashcode()
。
9.接口
接口类似于一种能力,一种行为。接口中定义两种东西,一种是公开的静态常量,一种是公开的抽象方法。(有个default的默认实现语法,这就不管了,不是核心)。由于是约定俗成,public static final 和 public abstract可以省略。
接口之间多继承,一个非抽象类可以实现多个的接口的方法。
package com.lordbao.demo;
interface Waterable{
void waterAttack();
}
interface Flyable{
void fly();
}
interface Fireable{
void fireAttack();
}
public class SuperMan implements Waterable,Flyable,Fireable{
@Override
public void waterAttack() {
System.out.println(" 我可以吐水");
}
@Override
public void fly() {
System.out.println(" 我可以飞");
}
@Override
public void fireAttack() {
System.out.println(" 我可以喷火");
}
}
10.访问修饰符
访问修饰符 | 类 | 子类 | 同包 | 任意位置 |
---|---|---|---|---|
private | 是 | 否 | 否 | 否 |
protected | 是 | 是 | 否 | 否 |
是 | 是 | 是 | 否 | |
public | 是 | 是 | 是 | 是 |
访问修饰符可修饰成员变量和成员方法,而class和interface仅能被默认的和public修饰。
11.匿名内部类
一次性用品,不推荐使用,具体如下所示。
格式
new 接口名(){
实现方法
}
public class Main {
public static void main(String[] args) {
BossMeeting.bossMeeting(new Boss() {
@Override
public String bossMeetingSpeech() {
return "老板不在,我替大家发个言,大家辛苦";
}
@Override
public String toString() {
return "代理老板";
}
});
}
}
interface Boss{
//首脑会议致辞
String bossMeetingSpeech();
}
class BossMeeting{
public static void bossMeeting(Boss boss){
System.out.println(boss+"致辞:"+boss.bossMeetingSpeech());
}
}