第三节 Java 面向对象
1. 对面向对象的认识
面向对象编程使得我们可以将现实世界存在的物体抽象为对象,从而使得我们能够更加易于去理解我们的代码和方便我们进行编程。我们可以把我们身边任何东西都作为一个对象,它可以有行为和特征,对象之间可以进行通信,他们通过先创建对象,然后通过(对象点的方式去发送消息),当另一个对象收到消息之后就可以做出相应的回应。
2. 面向对象的三个特性
2.1 特性一:继承
我们可以通过extends关键字来实现继承,当一个类继承另一个类之后,这个类就可以使用它继承的类里面的成员方法和成员变量(除了私有的之外),这样实现了代码的复用,提高了开发的效率,同时为后期维护提供了便利。
如何实现封装:
我们通过private来修饰我们要封装的成员变量或方法,然后我们提供方法来进行访问和设置,在设置的时候我们可以进行值的检查从而确保程序的安全性。
2.2 特性二:封装
就像我们的手机和和充电器一样我们不需要知道他们的构造,怎么制作的,我们能知道他们有什么作用就可以了,它的内部细节就是被封装了起来。在面向对象中所谓的封装就是将对象的属性和行为封装起来不让外界去访问具体的细节,我们通过我们给定的入口让他们去间接的去访问,这样我们能够对程序的安全性有一定的保证。
2.3 特性三:多态
这个特性在面向对象编程中是非常的重要的,它要有三个步骤,第一个是必须存在继承,第二个是必须有重写,第三个就是必须有向上转型。比如一个形状类,一个三角形类,一个圆类,他们都有清除这么一个功能,而且他们都继承形状这个类,但是他们的清除方式又有些不同,所以他们都重写了清除的这个方法。然后当我们进行清楚的时候我们只需要对他们进行向上转型我们不需要知道这个形状是什么,系统会自动的进行发现它是什么然后去执行对应的清除功能。
3. 类的创建与对象的使用
3.1 类的定义
类是对象的抽象,它用来描述一组对象的相同的行为与特性。
语法格式:
[修饰符] class 类名 [extends 父类名] [implements 接口名]{
//类体(成员变量和成员方法)}
3.2 成员变量
用来描述类的特征
语法格式:
[修饰符] 数据类型 变量名[=值];
3.3 成员方法
用来描述类的行为。
语法格式:
[修饰符] [返回值类型] 方法名([参数类型 参数名1,参数类型 参数名2....){
//方法体
....
return 返回值;
}
3.4 对象的创建与使用
对象创建的语法格式:
类名 对象名称=new 类名();
对象使用的语法格式:
成员变量的数据类型 变量名称 = 对象名称.成员变量;
成员方法的返回值的数据类型 变量名称 = 对象名称.成员方法;
//当成员方法或成员变量为静态时我们可以通过类名.方法名或变量名
创建对象内存发生的变化
当新建一个对象时,对象名称是一个引用,会存放在栈区,里面的基本数据类型也会存放在栈区,而new出来的对象和数组是存放在了堆区。
3.5 访问控制符
(1)private:如果一个成员被它修饰,那么这个成员只能被该类自己去访问,其他类无法访问,进行封装用的。
(2)default:如果一个成员被它修饰,那么这个成员能被本包下的其他类去访问。(如果不加访问控制修饰符,默认是这个)
(3)protected:如果一个成员被它修饰,那么这个成员能被同一包下的其他类访问,也能被不同包下的子类访问。
(4)public:如果一个成员变量被它修饰,那么这个类能被所有类访问。
5. 方法的重载和递归
5.1 方法的重载
方法的重载是为了解决方法的功能相同但是方法名不一样的问题,我们通过方法的重载就可以解决这样的问题。
方法重载的要求:返回值、参数类型,参数个数三者中有一个或一个以上不同的两个方法可以取相同的名字。
5.2 方法的递归
方法的递归是指在一个方法内调用自己本身的一种过程,递归一定要有结束条件不然永远无法结束。
6. 构造方法
如果我们想在进行实例化对象的时候就进行对我们的成员变量进行赋值我们就可以个通过构造方法。由于所有的类都继承超类Object,因此也就继承了它的无参构造方法,这也就是为什么当我们什么都不写的时候是可以创建对象的,因为类继承了超类的无参构造。但是 如果我们给一个类有了有参构造,那么它继承的无参构造就会自己消失,如果我们还想使用无参我们需要自己创建一个无参构造。
6.1 构造方法的定义
定义格式:
[修饰符] 方法名[参数列表]{//方法体}
(1)方法名必须和类名相同
(2)方法名前面没有返回值
(3)在方法体中不能使用return语句返回一个值,但是可以单独写个return语句来作为方法的结束
6.2 构造方法的重载
构造方法和普通方法一样也是可以进行重载的,进行重载的两个构造方法它的参数个数或参数类型要不同。
7. this关键字
(1)目的是用来解决构造方法里的参数名称和成员变量冲突的问题。
例如:存在这样一个类和构造方法
public class Example{
private int age;
public Example(int age){
this.age=age;}//解决了命名冲突问题
}
(2)通过this来调用构造方法
例如:
public class ExampDemo{
private int age;
public ExampDemo(){//无参构造
}
public ExampDemo(int age){//有参构造
this();}//通过this关键字去调用无参构造。
}
注意:只能在一个构造方法中去调用另一个构造方法,不能在成员方法中去调用。在构造方法中使用this去调用,必须是该方法的第一条语句。在一个类的两个构造方法中不能使用this相互调用。
8. static关键字
8.1 static关键字修饰成员变量
目的就是所有对象去共享该类用static修饰的变量。
注意:static关键字只能用来修饰成员变量不能用来修饰局部变量。
8.2 static关键字修饰成员方法
目的是我们可以不通过实例化对象而直接的去调用方法,直接类名.静态方法名就可以使用。
注意:被static修饰的方法不能去访问非静态的成员变量,因为我们要访问非静态的成员变量的时候我们要先实例化对象,而我们访问静态方法的时候可以不实例化,静态的是随着类加载就先存在的,所以你不能用已经存在的去访问还没有存在的,这样会报错。
8.3 静态代码块
格式如下:
static{…}
当类加载时,静态代码块会执行,类只加载一次,所以静态代码块也只执行一次,一般用静态代码块来对类的成员变量进行初始化。
9. 类的继承
当一个类通过extends去继承另一个类,那么该类就会自动拥有父类所有可继承的属性和方法。
基本语法格式:
[修饰符] class 子类名 extends 父类名{
//核心代码}
注意:Java中,类只支持单继承,一个类只能有一个父亲,但是多个类可以有一个父亲。
9.1 重写父类方法
在子类对父类的方法进行重写时,子类中重写的方法要和父类方法具有相同的方法名、参数列表以及返回值。
注意:子类重写的方法的修饰符不能比父类中被重写的方法更加严格。
9.2 super关键字
目的是在子类中去访问父类的成员(比如:构造方法、成员变量、成员方法)
9.2.1 使用super去调用父类的成员变量和成员方法
super.成员变量;
super.成员方法(参数1,参数2…);
9.2.2 使用super去调用父类的构造方法
super(参数1,参数2…);
注意:通过super()关键字去调用父类的构造方法,super()必须在子类构造方法中的第一行,并且只能出现一次。
注意:当我们子类去继承的时候,如果父类有了有参构造,那么父类的无参构造如果我们没有设置,那么就没有了,这时候如果子类去继承父类,就会找不到无参构造就会报错。所以最好的办法就是当我们定义个类的时候他有了有参构造我们都为他添加一个无参构造,这样就可以避免继承的时候出错了。
9.3 Object类
它是所有类的父类,每个类都直接或间接的继承他,因此每个类都可以使用他的可继承的成员方法。
常用方法:
方法声明 | 功能描述 |
---|---|
boolean equals(Object obj) | 判断某个对象与此对象是否相等 |
final Class<?> getClass() | 返回此Object的运行时类 |
int hashCode() | 返回该对象的哈希码值 |
String toString() | 返回该对象的字符串表示 |
void finalize() | 垃圾回收器调用此方法来清理没有被任何引用变量所引用对象的资源 |
Object类的toString()方法中输出的信息具体格式为:
getClass().getName()+'@'+Integer.toHexString(hashCode());
具体说明:
getClass().getName()代表返回对象所属类的类名,即包名+类名的全限定名称
hashCode()代表返回该对象的哈希值
Integer.toHexString(hashCode())代表将对象的哈希值用十六进制表示
实际开发中会重写同toString()方法
10. final关键字
10.1 final关键字修饰的类
被final修饰的类不能够被继承
10.2 final关键字修饰的方法
被final修饰的方法不能被重写
10.3 final修饰的变量
final修饰的变量是常量,只能赋值一次,系统不会给final修饰的变量赋与默认值。
11. 抽象类和接口
11.1 抽象类
当一个类中的方法不能确定其具体行为表现时,但是他又有这个行为,这个时候我们可以把这个方法定义为抽象类,只需在方法的返回值类型前面加上abstract,当一个类中有抽象方法了,那么这个类一定是抽象类,在class前面也要加上abstract。但是作为一个抽象类,可以没有抽象方法。
抽象类不能被实例化 ,所以它天生就是用来继承的。如果想调用里面的抽象方法,我们需要创建一个类去继承抽象类然后重写抽象方法,由于Java中类只能单继承,所以我们这样定义就会显得有些鸡肋,但是好处就是使结构更加的清晰,同时方便函数式编程。
语法格式:
[修饰符] abstract class 类名{
[修饰符] abstract 方法返回值类型 方法名(参数列表);}
11.2 接口
在定义接口时我们将class换成interface,在接口里我们不仅可以定义抽象方法,我们还可以定义默认方法和静态方法 ,以及常量。
在接口中如果我们不写修饰符,那么对于变量来说就是常量,相当于前面加上public static final,对于方法来说就是抽象方法。如果是默认方法和静态方法,前面要加上default和static。
其中,在接口中的默认方法和抽象方法需要在接口的实现类的实例化对象去调用,而静态方法可以通过接口名.静态方法名。
接口之间可以多继承,一个类可以通过implements去实现多个接口。
接口定义语法格式:
[修饰符] interface 接口名 [extends 父接口1,父接口2...]{
[public][static][final] 常量类型 常量名=常量值;
[public][abstract]方法返回值 方法名(参数列表);
[public]default 方法返回值类型 方法名(参数列表){方法体};
[public]static 方法返回值类型 方法名(参数列表){方法体};
定义接口的实现类语法格式:
[修饰符] class 类名 [extends 父类名][implements 接口1,接口2.....]{...}
注意:当一个类实现接口时,如果这个类是抽象类那么只需实现接口的部分抽象方法即可,否需要实现接口中所有抽象方法。
12. 多态
定义:不同类在调用相同方法时表现出不同的行为。
要完成多态必须满足的条件:(1)继承(2)重写(3)向上转型
12.1 对象类型的转换
格式:父类类型 对象名称=new 子类();(这是向上转型)
注意:不能通过父类变量去调用子类特有的方法
向下转型:目标对象 对象名=(目标对象)要转换的对象。
注意:向下转型必须是本质类型才可以。
我们可以通过instanceof关键字判断一个对象是否为某个类(或接口)的实例或子类实例。
语法格式:
对象(或者对象引用变量) instanceof 类(或接口)
13. 内部类
根据内部类的位置、修饰符和定义方式的不同分为:成员内部类、局部内部类、静态内部类和匿名内部类
13.1 成员内部类
成员内部类可以访问外部类的成员变量和成员方法。
使用方式如下:
class Outer{
int a=0;
void test(){
System.out.println("外部类成员方法");
}
class Inner{
int n=1;
void show(){
System.out.println("内部类成员方法");}
}
}
在外部类中可以通过实例化内部类来访问内部类成员变量和方法。
创建内部类的语法格式:
外部类名.内部类名 变量名=new 外部类名().new 内部类名();
13.2 局部内部类
在方法中的类,有效范围只在方法内。局部内部类可以访问外部类的成员变量和成员方法而内部类里面的变量和方法只能在创建该局部内部类的方法里使用。
好处:完成了在方法内对对象的操作
13.3 静态内部类
就是使用static修饰的成员内部类。不同于成员内部类的地方就是它只能访问外部类的由static修饰的成员变量和成员方法。
访问静态内部类方法格式:
外部类名.静态内部类名 变量名=new 外部类名.静态内部类名();
13.4 匿名内部类
在调用一个方法时,如果一个方法的参数时接口类型,那么我们除了可以传如一个接口的实现类,我们还可以使用匿名内部类的形式来实现接口作为方法的参数。
基本语法格式:
new 父接口(){//匿名内部类实现部分};
例如:
//定义接口
public interface Animal {
void shout();
}
//匿名内部类的使用
public class Example04 {
public static void main(String[] args) {
String name="大黄";
animalShout(new Animal() {
@Override
public void shout() {
System.out.println(name+"汪汪汪......");
}
});
}
public static void animalShout(Animal an){
an.shout();
}
}
注意:这里的匿名内部类访问了局部变量name是可以的,因为并没有用final修饰。
14. Lambda表达式
目的:简化代码,但是带来了阅读性差的缺点。
主要针对只有一个抽象方法的抽象类或接口。
组成部分:
([数据类型 参数名,数据类型 参数名,…])->{表达式主体}
(1)第一部分:用来向主体部分传递接口方法需要的参数,可以省略参数的数据类型,如果只有一个参数括号可以去掉
(2)第二部分->:不能省略,用来指定参数数据的指向
(3)第三部分:如果表达式主体只有一条语句,那么可以省略花括号,同时要省略这句后面的分号,如果不去掉花括号则不能省略分号。当只有一条return语句时,可以省略return关键字。
14.1 函数式接口
接口里有且只有一个抽象方法就叫函数式接口,Lambda表达式是函数式接口的体现,为此有一个注解为@FunctionalInterface,它会显式的标注这是一个函数式接口,这样编辑器就会进行更严格的检查,确保是一个函数式接口,否则就会报错。
14.2 方法引用和构造器引用
核心就是我们可以在别的类写好方法,然后在另一个类里面直接用方法的引用用它已经写好的方法体,前提就是这个类中的这个方法的返回值类型和参数类型要一样。就比如我写好了A类,它里面有个方法的返回值类型为int,参数也是int类型,用来做两数之和,B接口中有一个抽象方法它的返回值类型和参数类型也和A中的这个方法一样,那么我们就可以用A中的这个方法的方法体。
目的:进一步简化代码。。。
(1)类名引用静态方法
类名::类静态方法
@FunctionalInterface
public interface Calcable {
int calc(int num);
}
public class Math {
public static int abs(int num){
if(num<0){
return -num;
}else {
return num;
}
}
}
public class Example05 {
public static void printAbs(int num,Calcable calcable){
System.out.println(calcable.calc(num));
}
public static void main(String[] args){
//使用Lambda表达式
printAbs(-10,n->Math.abs(n));
//使用方法的引用
printAbs(-10,Math::abs);
}
}
(2)对象名引用方法
格式:对象名::实例方法
示例如下:
@FunctionalInterface
public interface Printable {
void print(String str);
}
public class StringUtils {
public void pringUpperCase(String str){
System.out.println(str.toUpperCase());
}
}
public class Example06 {
private static void printUpper(String text,Printable pt){
pt.print(text);
}
public static void main(String[] args) {
StringUtils stu=new StringUtils();
//通过Lambda表达式
printUpper("Hello",t->stu.pringUpperCase(t));
//通过方法引用的方式
printUpper("Hello",stu::pringUpperCase);
}
}
(3)构造器引用方法
格式:类名::new
示例:
@FunctionalInterface
interface PersonBuilder {
Person buiderPerson(String name);
}
public class Person {
private String name;
public Person(String name){
this.name=name;
}
public String getName(){
return name;
}
}
public class Example07 {
public static void printName(String name,PersonBuilder builder){
System.out.println(builder.buiderPerson(name).getName());
}
public static void main(String[] args) {
//使用Lambda表达式
printName("赵四",name->new Person(name));
//使用构造器引用方式
printName("赵四",Person::new);
}
}
(4)类名引用普通方法
格式:类名::类普通方法名
示例:
@FunctionalInterface
public interface Printable02 {
void print(StringUtils su,String str);
}
public class StringUtils {
public void pringUpperCase(String str){
System.out.println(str.toUpperCase());
}
}
public class Example08 {
private static void printUpper(StringUtils su,String text,Printable02 pt){
pt.print(su,text);
}
public static void main(String[] args) {
//通过Lambda表达式
printUpper(new StringUtils(),"hello",(Object,t)->{Object.pringUpperCase(t);});
//通过方法的引用
printUpper(new StringUtils(),"hello",StringUtils::pringUpperCase);
}
}
15. 异常
Error异常是它表示Java运行时产生的系统内部错误或资源耗尽的错误,仅靠修改程序本身是不能回复执行的,例如,系统崩溃。
Throwable类的常用方法:
方法声明 | 功能描述 |
---|---|
String getMessage() | 返回此Throwable的详细消息字符串 |
void printStackTrace() | 将此Throwable及其追踪输出至标准错误流 |
void printStackTrace(PrintStream s) | 将此Throwable及其追踪输出到指定输出流 |
在try代码块中,发生异常语句后面的代码是不会被执行的,系统会将异常封装成一个异常对象,这样它就可以调用相关的信息。
我们用finally来执行无论程序是否发生异常都要执行的工作。但是在这种情况下是不会执行的,就是在try…catch中执行了System.exit(0)语句,它表示退出当前Java虚拟机,所以任何代码都会停止执行。
15.1 throws关键字
用于有时候并不明确异常出现在哪,但是会有异常出现,这个时候我们通过以下格式显式的抛出异常,让后续调用者去处理。但是后续调用者还是要处理的不然程序仍然可能非正常终止。比如下面的程序。
//方法
public static int divide(int x,int y){
return x/y;}
在这个里面我们是很清楚的可以看到y不能为0否则就会报异常,但是有时候有些异常我们是不容易看出来的,我们就可以向上抛出
public static int divide(int x,int y) throws Exception{
return x/y;
}
这样是不会报错的,但是当我们在main方法里面去调用的时候我们如果继续抛不管它,那么程序就会非正常终止。
15.2 throw关键字
与throws不同,throw关键字用在方法体内,并且抛出的是一个异常对象,而throws 用在方法声明之中用来指明方法可能抛出的多个异常。
使用:
public class Example09 {
public static void printAge(int age) throws Exception{
if(age<0){
throw new Exception("输入的年龄有误,必须是正整数!");
}else{
System.out.println("年龄为:"+age);
}
}
public static void main(String[] args) {
int age=-1;
try {
printAge(age);
}catch (Exception e){
System.out.println("捕获的信息为:"+e.getMessage());
}
}
}
15.3 自定义异常类
我们可以自定义异常类,这个类必须继承自Exception或其子类。
自定义异常类的使用:
//自定义异常类
public class DivideByMinusException extends Exception{
public DivideByMinusException(){
super();
}
public DivideByMinusException(String message){
super(message);
}
}
public class Example10 {
public static int divide(int x,int y) throws DivideByMinusException{
if(y==0){
throw new DivideByMinusException("除数是零");
}else{
return x/y;
}
}
public static void main(String[] args){
try {
int result=divide(4,0);
} catch (DivideByMinusException e) {
System.out.println("捕获的信息为:"+e.getMessage());
}
}
}
16. 垃圾回收
我们将对象分为三种状态:
(1)可用状态:指一个对象被创建之后有一个或多个引用变量指向它。
(2)可恢复状态:指一个对象不再有引用变量指向它,但是在finalize()调用他之前又有引用变量指向它。
(3)不可用状态:指一个对象失去了所有引用的关联,同时系统已经调用finalize()方法,这时系统会收回这个对象所占用的空间。
在Java中引入了垃圾回收机制(Java GC),系统会自动的进行垃圾收回,我们可以通过以下两种方式强制系统进行垃圾回收
(1)调用System类的gc()方法:System.gc()
(2)调用Runtime对象的gc()方法:Runtime.getRuntime.gc()
其实第一种方法执行的也是Runtime.getRuntime.gc()。但是调用完并不代表系统会立马进行垃圾回收,大多数情况下是有效果的。
注意:只有当程序需要更多内存时,垃圾回收器才会自动进行垃圾回收。