类和对象
1.为什么属性以变量的形式存在:
是因为属性对应的是数据,数据在程序中只能放到变量中,属性其实就是变量;
2.null是一个java关键字,是引用类型的默认值;
3.对象是通过new出来的,在堆内存中存储,
引用是:但凡是变量,并且该变量中保存了内存地址指向了堆内存当中的对象的;
java当中是没有指针的概念的,程序员是没有办法操纵堆内存的,只能通过引用去访问堆内存
4.java中的垃圾回收器GC主要针对回收的是堆内存当中的垃圾数据,什么时候数据会被GC回收呢?:没有任何引用指向该对象的时候。
5.出现空指针异常的前提条件是:
”空引用”访问实例相关【对象相关】的数据时,都会出现空指针异常;
6.java中规定:参数传递的时候,和类型无关,不管是基本数据类型还是引用数据类型。统一都是将盒子中保存的那个值复制一份,传递下去,所以java只有值传递。
内存地址也是值,也是盒子中保存的东西。
7.什么是构造方法,有什么用:
构造方法是一个比较特殊的方法,通过构造方法可以完成对象的创建,以及实例变量的初始化,换句话说:构造方法是用来创建对象,并且同时给对象的属性赋值(实例变量没有手动赋值的时候,系统会自动赋默认值);
当一个类没有提供任何构造方法,系统会默认提供一个无参的构造方法(缺省构造器);
当一个类中手动提供了构造方法,那么系统不再提供无参数的构造方法;
手动定义有参数的构造方法,系统提供的无参构造方法将消失,建议将无参构造方法手动写出来
怎么调用构造方法?
使用new运算符调用构造方法 new 构造方法名(实际参数列表);
构造方法的语法结构:
【修饰符列表】 构造方法名 (形式参数列表){构造方法体;}
第一:修饰符列表目前统一写public,不要写public static;
第二:构造方法名和类名必须一致;
第三:构造方法不需要指定返回值类型,也不能写void,写void就是普通方法,不是构造方法了;
通常在构造方法体当中给属性赋值,完成属性的初始化;
构造方法支持重载;
实例变量没有手动赋值的时候,实际上系统会赋默认值,那么这个默认赋值操作是在什么时候进行的:是在构造方法执行过程中完成初始化的,完成赋值的,不是在类加载(方法区)执行的;
在一个类体当中,定义的方法是没有先后顺序的;
8.一个类中可以出现的:
体{
实例变量;
实例方法;
静态变量;
静态方法;
构造方法;
静态代码块;
实力语句块;
方法(){
局部变量
}
}
9.类加载机制中,是这样的:在程序执行之前,凡是需要加载的类全部加载到JVM中,先完成类加载才会执行main方法。
10.只要你想“点”,“点”前面要么是一个类名,要么是一个引用
11.private修饰的方法是为了只供本类使用。
方法的覆盖(override)
1.回顾方法重载(overload):
什么时候用overload:当在同一个类中,方法完成的功能是相似的,建议方法名相同,这样方便程序员的编程,就像在调用同一个方法,代码美观;
什么条件满足构成方法重载:1.在同一个类中;2.方法名相同;3.参数列表不同(类型、顺序、个数);
overload和什么无关:和方法的返回值类型无关,和方法的修饰符列表无关;
2.override:
方法覆盖又被称为方法重写(override/overwrite)
什么时候使用方法重写:当父类中的方法已经无法满足当前子类的业务需求,子类有必要将父类中继承过来的方法进行重新编写,这个重新编写的过程称为方法重写/方法覆盖。
什么条件满足之后方法会发生重写呢【代码满足什么条件之后,就构成方法的重写呢?】:
override发生在具有继承关系的父子类之间;
方法重写的时候:返回值类型相同、方法名相同、参数列表相同;
方法重写的时候:访问权限不能更低,可以更高;
方法重写的时候:抛出异常不能更多,可以更少;
建议方法重写的时候尽量复制粘贴,不要编写,容易出错,导致没有产生覆盖。
注意:
私有方法不能继承,所以不能覆盖;
构造方法不能继承,所以不能覆盖;
静态方法不存在覆盖
覆盖只针对方法,不谈属性;
Eclipse中override可以工具生成:右键->Source->Override
静态(static)
实例是对象行为,静态是类的行为
静态代码块
静态代码是放在方法区当中的
实例代码块
this
1.this是一个关键字,全部小写;
2.this在内存中的结构
一个对象,一个this;
this是一个变量,是一个引用,this保存当前对象的内存地址,指向自身;
所以,严格意义上说,this代表的是“当前对象”;
this存储在堆内存当中的对象的全部
3.this只能使用在实例方法中;谁调用这个实例方法,this就是谁,所以this代表的是:当前对象;
this不能使用在静态方法中;
4.“this.”大部分情况下是可以省略的;
this什么时候不能省略呢?
在实例方法中,或者构造方法中,为了区分局部变量和实例变量,这种情况下,this.是不能省略的。
为什么this不能使用在静态方法中:
this代表的是当前对象,而静态方法的调用不需要对象,矛盾了;
6.为什么this不能使用在静态方法中?
this代表当前对象,静态方法中不存在当前对象;
如果方法中直接访问了实例变量,该方法必须是实例方法;
7.this除了使用在实例方法中,还可以使用在构造方法中,在构造方法中对实例变量赋默认值;
8.新语法:通过当前的构造方法去调用另一个本类的构造方法,可以使用以下语法格式:this(实际参数列表)
通过一个构造方法1去调用构造方法2,可以做到代码复用。,但需要注意的是,构造方法1和构造方法2都是在同一个类中。
9.this()这个语法作用是什么:代码复用。
对this()的调用必须是构造器中的第一个语句。
public class Date {
private int year;
private int month;
private int day;
public Date() {
// this.year = 1970;
// this.month = 1;
// this.day = 1;
this(1970, 1, 1);
}
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
}
内存图
封装
继承
1.什么事继承,有什么用?
继承:在现实世界当中也是存在的,例如:父亲很有钱,儿子不用努力也很有钱;
继承的作用:
基本作用:子类继承父类,代码可以得到复用(这个不是重要的作用,是基本的作用)
主要(重要)作用:有了继承关系,才有了后期的方法覆盖和多态机制。
2.子类继承父类,会将父类中除了构造方法之外的所有东西全部继承过来;
3.继承的相关特性:
4.继承也是存在缺点的:耦合度高,父类修改,子类受到牵连。
5.子类继承父类之后,能使用子类对象调用父类的方法吗?
可以,因为子类继承了父类之后,这个方法就属于子类了,当然可以使用子类对象来调用。
本质上,子类继承父类中后,是将弗雷继承过来的方法归为自己所有,实际上调用的也不是父类的方法,是字类自己的方法(因为已经继承过来了,就属于自己的了)
6.在实际开发中,满足什么条件的时候,可以使用继承呢?
凡是采用“is a”能描述的,都可以继承,例如:Cat is a Animal,Dog is a Animal
假设以后的开发中有一个A类,有一个B类,A类和B类确实也有重复的代码,那么他们两个之间就可以继承吗?不一定,还是要看一看他们之间是否能够使用is a来描述。
我们研究了一下Object类当中有很多方法,大部分看不懂,其中有一个叫做toString()的,我们进行了测试,发现:
System.out.pringln(引用);
当直接输出一个“引用”的时候,println()方法会先自动调用“引用.toString()”,然后输出toString()方法的执行结果。
super关键字
3.super()
表示通过子类的构造方法调用父类的构造方法
模拟现实世界中这样的场景:要想有儿子,需要先有父亲。
4.重要结论:
当一个构造方法第一行:
既没有this()又没有super()的话,默认会有一个super();
表示通过当前子类的构造方法调用父类的无参数构造方法
所以必须保证父类的无参数构造方法是存在的。
5.注意:this()和super()不能共存,他们都是只能出现在构造方法第一行。
6.无论构造方法怎样折腾,父类的构造方法是一定会执行的(百分百)。
7.在Java语言中不管是new什么对象,最后老祖宗的Object类的无参数构造方法一定会执行(Object类中的无参数构造方法是处于“栈顶部”)
栈顶的特点:最后调用,但是最先执行结束,后进先出原则。
8.注意:以后写代码的时候,一个类的无参数构造方法还是建议大家手动写出来,如果无参数构造方法丢失的话,可能会影响到“子类对象的构建”。
9.super代表的是“当前对象(this)”的"父类型特征"。
10.注意:在构造方法执行过程中一连串向上调用了父类的构造方法,父类的构造方法又继续向上调用它的父类的构造方法(调用的顶级的构造方法是Object类方法),但是实际上对象创建了一个,思考super(实参)用来干啥的
11.super(实参)的作用是:初始化当前对象的父类型特征,并不是创建父类型对象,初始化子类型继承过来的父类型的特征,实际上对象只创建了一个。
12.super关键字代表的是什么呢?
super关键字代表的是:“当前对象”的那部分父类型的特征,super是this指向的那个对象中的一块空间。
13.java中允许在子类中出现和父类一样的同名变量/同名属性。这就涉及到super.什么时候不能省略
14.super.什么时候不能省略?
如果父类和子类中有同名属性/方法,并且想通过子类访问父类同名属性,super.则不能省略,用super.加以区分。
15.java是怎么区分子类和父类的同名属性的?
this.name:当前对象的name属性;
super.name:当前对象的父类型特征中的name属性。
16.super不是引用,super也不保存内存地址,super也不指向任何对象,super只是代表当前对象内部的那一块父类型的特征
17.super.属性名:访问父类的属性
super.方法名(实参):访问父类的方法
super(实参):调用父类的构造方法
18.在子类中访问父类私有的数据,使用super,是没有权限的。私有的只能在本类中访问,出了本类就不能访问了。
方法覆盖
1.回顾方法重载,什么时候考虑使用方法重载(overload)?
当在一个类中,如果功能相似的话,建议将名字定义为一样,这样代码美观,并且方便编程
什么条件满足之后能够构成方法重载?
条件一:在同一个类中;
条件二:方法名相同;
条件三:参数列表不同(个数,顺序,类型)
2.子类继承父类中,有一些“行为”(方法)可能不需要改进,有一些“行为”(方法)可能面临着必须改进,因为从父类中继承过来的方法已经无法满足子类的业务需求。子类对继承过来的父类方法不满意,所以要对父类方法进行重写,但是有些继承过来的方法可能不需要改动。看业务需求。
3.什么时候我们会考虑使用“方法覆盖”呢?
子类继承父类之后,当继承过来的方法无法满足当前子类的业务需求时,子类有权利堆这个方法进行重写,有必要进行“方法的覆盖”。
4.方法覆盖又叫做方法重写(override,overwrite)
5.方法覆盖时,建议将父类中的方法原封不动地复制过来(不建议手动编写)。
6.方法覆盖就是将继承过来的那个方法给覆盖掉了,继承过来的方法没有了。
也就是:当子类对从父类继承过来的方法进行“方法覆盖”之后,当子类对象调用该方法的时候,一定执行覆盖之后的方法。
7。当我们代码怎么编写的时候,在代码级别上构成了方法覆盖呢?
条件一:两个类必须要有继承关系;
条件二:重写之后的方法和之前的方法具有:相同的返回值类型、相同的方法名、相同的形式参数列表
也就是和继承过来的方法除了方法体不一样,其他一样
条件三:访问权限不能更低,只能更高。
条件四:重写之后的方法不能比之前的方法抛出更多的异常,可以更少。
8.注意事项(这几个注意事项,当学习了多态语法之后自然就明白了):
注意一:方法覆盖只是针对于方法,和属性无关;
注意二:私有方法无法覆盖;
注意三:构造方法不能被继承,所以构造方法也不能被覆盖;
注意四:方法覆盖只是针对于实例方法,静态方法覆盖没有意义(这是因为方法覆盖通常和多态联合起来)。
9.一定要注意:方法覆盖/方法重写的时候,建议将父类的方法复制粘贴,这样比较保险。
10.一般重写的时候都是复制粘贴,不要动,不要改。
但是意义不大,实际开发中没人这样写。
重写toString()方法
关于Object类中的toString()方法
1.toString()方法的作用是什么?作用是将“java对象”转换成字符串的形式
2.Object类中的toString()方法的默认实现是什么?
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString:方法名的意思是转换成String
含义:调用一个java对象的toString()方法就可以将该java对象转换成字符串的表示形式;
3.System.out.println(引用),默认输出的是System.out.println(引用.toString());
如果没有重写继承过来的Object类中的toString()方法,则默认是使用Object类中的toString()方法输出对象的字符串表现形式。
4.大多数的java类中的toString()方法都是需要覆盖的,因为Object类中提供的toString()方法输出的是一个java对象的内存地址。
至于toString()方法具体怎么进行覆盖?格式可以自己定义,或者听需求的(听项目要求的)。
5.方法重载和方法覆盖有什么区别?
方法重载发生在同一个类中;
方法覆盖是发生在具有继承关系的父子类之间
方法重载是一个类中,方法名相同,参数列表不同;
方法覆盖是具有继承关系的父子类,并且重写之后的方法必须和之前的方法一致:方法名一致,参数列表一致,返回值类型一致。
多态
1.多态中涉及到的概念:
向上转型(upcasting):子类型转换成父类型,又被称为自动类型转换
向下转型(downcasting):父类型转换成子类型,又被称为强制类型转换【需要加强制类型转换符】
*java中允许向上转型,也允许向下转型,无论是向上转型还是向下转型,两种类型之间必须要有继承关系,没有继承关系程序是无法编译通过的。
以后在工作过程中,和别人聊天的时候,要专业一些,说向上转型和向下转型,不要说自动类型转换,也不要说强制类型转换,因为自动类型转换和强制类型转换是使用在基本数据类型方面的,在引用类型转换这里只有向上和向下转型。
2.多态指的是:
父类型引用指向子类型对象
包括编译阶段和运行阶段
编译阶段:绑定父类的方法;
运行阶段:动态绑定子类型对象的方法。
多种形态。
3.父类型的引用允许指向子类型的对象
class Anaimal{
public void move() {
System.out.println("Animal");
}
}
class Cat extends Anaimal{
public void move() {
System.out.println("Cat");
}
public void catchMouse() {
System.out.println("Catch Mouse");
}
}
Animal a2 = new Cat();
a2.move;//调用的永远是Cat的move方法,即使Cat的move方法注释掉了,调用的是Cat继承后的move方法;
//在程序运行阶段,JVM堆内存当中真实创建的对象是Cat对象,那么以上程序在运行阶段一定会调用Cat对象的move()方法,此时发生了程序的动态绑定,运行阶段绑定
//无论是Cat类有没有重写move方法,运行阶段一定调用的是Cat对象的move方法,因为底层真实的对象就是Cat对象
a2.catchMouse();//编译报错,因为编译阶段编译器检查到a2的类型是Animal,从Animal.class字节码文件当中查找catchmouse()方法,最终没有找到该方法,导致静态绑定失败,没有绑定成功,也就是说编译失败了,别谈运行了。
//Java程序永远分为编译阶段和运行阶段,先分析编译阶段,再分析运行阶段,编译无法通过,根本是无法运行的,编译阶段编译器检查a2这个引用的数据类型为Animal,由于在Animal.class字节码当中有move()方法,所以编译通过这个过程称为静态绑定,编译阶段绑定,只有静态绑定成功之后才有后续的运行
//Animal 和Cat之间存在继承关系,Animal 是父类,Cat是子类;
//new Cat()创建的对象类型是Cat,a2这个引用的数据类型是Animal,可见他们进行了类型转换,子类型转换成父类型,称为向上转型(自动类型转换)
// Java中允许这种语法,父类型引用指向子类型对象
//父类型引用指向子类型对象这种机制导致程序存在编译阶段绑定和运行阶段绑定两种不同的形态/状态,这种机制可以称为一种多态语法机制
//想让a2执行catchMouse()方法,怎么办?
//a2是无法直接调用此方法,因为a2是Animal类型,Animal中没有此方法,可以将a2强制类型转换为Cat类型,向下转型也需要两种类型之间必须有继承关系,不然编译报错
什么时候需要使用向下转型呢?不要随便做强制类型转换
当调用的方法或属性是子类型中特有的,在父类型中不存在,必须向下转型
Cat c2 = (Cat)a2;
a2.catchMouse();
1>以上异常只有在强制类型转换的时候会发生,也就是说“向下转型”存在隐患(编译通过,但是运行错误),容易出现ClassCastException(类型转换异常),怎么避免这个风险:instanceof运算符,可以在程序运行阶段动态的判断某个引用指向的对象是否为某一类型,养成好习惯,向下转型之前一定要使用instanceof运算符进行判断;
2>向上转型只要编译通过,运行一定不会出现问题:Animal a = new Cat();
3>向下转型编译通过,运行可能出现错误:Animal a3 = new Bird();Cat c3 = (Cat)a3;
4>怎么避免向下转型出现的“ClassCastException”呢?使用instanceof运算符可以避免出现以上的异常。
instanceof (运行阶段动态判断)
1>语法格式:(引用 instanceof 数据类型名)
instanceof 可以在运行阶段动态判断引用指向的对象的类型
2>instanceof 运算符的执行结果类型是布尔类型,结果可能是true/false;
3>关于运算结果(true/false):
假设:(a instanceof Animal):a是一个引用,a变量保存了内存地址指向了堆中的对象
true表示:a这个引用指向的堆内存中的java对象是一个Animal类型;
false表示:aa这个引用指向的堆内存中的java对象不是一个Animal类型;
程序员要养成一个好习惯:任何时候,任何地点,对类型进行向下转型时,一定要使用instanceof运算符进行判断(java规范中要求的),避免出现classCastException异常的发生。
if (a3 instanceof Cat) {
Cat c3 = (Cat)a3;
//调用子类对象中特有的方法
c3.catchMouse();
}else if(a3 instanceof Bird) {
Bird b2 = (Bird)a3;
b2.fly();
}
2.多态的作用(多态会天天用,到处用)
1.软件在扩展过程当中,修改的越少越好,修改的越多,你的系统当前的稳定性就越差,未知的风险就越多。
其实这里涉及到一个软件的开发原则:软件开发原则有7大原则(不属于java,这个开发原则属于整个软件业):
其中有一条最基本的原则:OCP(开闭原则)。什么是开闭原则:对扩展开放(你可以额外添加,没问题),对修改关闭(最好很少的修改现有程序),在软件的扩展过程当中,修改的越少越好。
高手开发项目不仅仅是为了实现客户的需求,还需考虑软件的扩展性。
2.面向父类型编程,面向更加抽象编程,不建议面向具体编程,因为面向具体编程会让软件的扩展力变差
作用1.降低程序的耦合度,提高程序的扩展力,
能使用多态尽量使用多态
父类型引用指向子类型对象
核心:面向抽象编程,尽量不要面向具体编程
public class Pet {
public void eat() {
System.out.println("动物在吃饭");
}
}
class Dog extends Pet{
public void eat() {
System.out.println("狗狗在吃饭");
}
}
class Cat extends Pet{
public void eat() {
System.out.println("猫猫在吃饭");
}
}
class Master{
public void feed(Pet pet) {
pet.eat();
}
}
class Test{
public static void main(String[] args) {
Master zhangsanMaster = new Master();
Dog zangaoDog = new Dog();
zhangsanMaster.feed(zangaoDog);
Cat cat = new Cat();
zhangsanMaster.feed(cat);
}
}
*方法覆盖需要和多态机制联合起来使用才有意义。
总结两句话:私有不能覆盖,静态不谈覆盖
final关键字
1.final是一个关键字,表示最终的,不可变的,final可以修饰变量,方法以及类等。
2.final修饰的类无法被继承
3.final修饰的方法无法被覆盖
4.final修饰的变量一旦赋值之后,不可重新赋值(不可二次赋值),即final修饰的变量只能赋一次值。
5.final修饰的实例变量,必须手动赋值,不能采用系统默认值
6.final修饰的引用,一旦指向某个对象之后,不能再指向其他对象(),那么被指向的对象无法被垃圾回收器回收,知道当前方法结束,才会释放空间。final修饰的引用虽然指向某个对象之后不能指向其他对象,但是所指向的对象内部的内容(堆内存)是可以被修改的
7.final修饰的实例变量,一般和static联合使用,被称为常量
如果希望一个类不被继承,则将它修饰成final类,如果一个方法不希望被别人修改(覆盖),则修饰为final.
8.实例变量如果没有手动赋值的话,系统会赋默认值
Eclipse中重写方法左边有个绿色小箭头
final修饰的实例变量必须重新赋值
终极结论:final修饰的实例变量一般添加static修饰,static final联合修饰的变量称为“常量”,常量名建议全部大写,每个单词之间采用下划线连接,常量无法重新赋值
实际上常量和静态变量一样,区别在于:常量的值不能变常量和静态变量,都是存储在方法区,并且都是在类加载是初始化。
常量一般都是公开的(public),因为不怕被修改,因为别人根本改不了,所以常量不需要封装。
package机制
Eclipse导入类的快捷键:ctrl+shift+o
访问控制权限修饰符
类只能采用public 和 缺省的修饰符进行修饰【内部类除外】
总结
1.程序再怎么变化,万变不离其宗,有一个固定的规律:
所有实例相关的都是先创建对象,通过“引用.”来访问;
所有静态相关的都是直接采用“类名.”来访问。
//static在类加载的时候就有了,而实例方法和实例变量是需要有了实例才有;
//在实例方法中可以访问static变量/方法,在static方法中不可以直接访问实例变量/方法,如果想要访问,必须先创建对象,然后才能访问。
大结论:
只要负责调用的方法a和被调用的方法b在同一个类当中:
this. 可以省略
类名. 可以省略
跨类不能省略。
集成开发工具IDED
15.单行注释:ctrl+/
16.多行注释:ctrl+shift+/
17.复制一行:ctrl+d
进程和线程(598-)
1.一个栈中自上而下的顺序依次执行。
2.实现线程的第一种方式:
编写一个类,直接继承java.lang.Thread,重写run方法
3.怎么创建线程呢?
new就对了
怎么启动线程呢?调用线程对象的start()方法
4.注意:亘古不变的道理:
方法体当中的代码永远都是自上而下的执行顺序依次逐行执行
public class MyThread extends Thread {
@Override
public void run() {
//编写程序,这段程序运行在分支栈中
for(int i = 0;i < 1000;i++) {
System.out.println("分支线程---" + i);
}
}
}
实现线程的第二种方式
实现线程的第二种方式
注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其他类,更灵活。
关于线程对象的生命周期
新建状态
就绪状态
运行状态
阻塞状态
死亡状态
线程在就绪和运行两个状态之间频繁切换,给人一种多线程并发的感觉,就好像两件事同时在做一样,多线程并发其实是人的-种错觉,:如a线程的cpu时间片用完就回到就绪,如果b线程抢到b就执行,b用完回到就绪,哪个线程抢到哪个线程就执行,执行完时间片后,回到就绪状态,也可以回到就绪又立马抢到执行的情况,因为计算机的处理速度非常快。
主线程的名字叫main
9.获取当前线程的信息
10.线程sleep
11.线程终止sleep
12.在Java中强行终止线程的执行
线程对象.stop//已过时,不建议使用(类似于结束进程】)
这种方式存在很大的缺点:容易丢失数据,因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失,不建议使用
那如何合理的终止线程的执行?这种方式比较常用
**线程调度
优先级高的,抢占到的 CPU时间片相对多一些,处于运行的时间状态多一些。
线程让位
1.让当前线程暂停,回到就绪状态,让给其他线程
静态方法:Thread.yield();
合并线程
1.线程对象.join()
合并是意味着两个栈之间发生等待关系,栈协调了
并发环境下线程安全问题
线程同步
lockpool不属于线程的生命周期部分,但是可以认为是阻塞。阻塞的时间长短不一定,拿到锁就走,没拿到锁就阻塞一直等。
常量没有线程安全问题,因为常量不可修改
类锁静态变量安全
synchronized出现在实例方法上表示锁this
synchronized加在静态方法上,表示类锁,不管创建了几个对象,类锁只有一把
死锁
synchronized在开发中最好不要嵌套使用,一不小心就可能导致死锁发生。
线程中的其他内容
生产者消费者模式