面向对象可以解释下吗?都有哪些特性?
面向对象是一种思想,可以将复杂问题简单化,让我们从执行者变为了指挥者(因为我们只需要去调用类的方法了)。面向对象的三大特性为:封装,继承与多态。
封装
将抽象的一类事物封装成一个类,减少耦合(防止直接在业务代码中修改代码,应该尽量在方法的代码修改代码)和代码重复(外部只需要调用方法,而不用在业务代码中写多余的代码),隐藏细节。保留特定的接口与外界联系(即不要直接访问成员变量,通过方法调用),当接口内部发生改变时,不会影响外部调用方。(因为外部只是调用方法嘛,细节是在内部实现的)
案例:
public class Test {
public static void main(String[] args) {
Student student = new Student();
student.name = "小明";
student.age = 16;
student.printStudentAge();
Student student2 = new Student();
student2.name = "小白";
student2.age = 200;
student2.printStudentAge();
}
}
class Student {
String name;
int age;
public void printStudentAge() {
System.out.println(name + "同学的年龄:" + age);
}
}
小白200岁显然不合理(每一个人的年龄都应该有一个合法范围的),如果我们是在小白下面检查一下的话,那假设小明的年龄也不合法呢,难道在每一个人的下面都写这样一段检查的逻辑代码?这样做的话代码耦合度高,重复度也高,显然不合理!
所以我们需要对Student这个类再次进行封装,对内部进行一些逻辑处理,把内部逻辑做一个隐藏,这样外部每次就只需要调用一下方法就可以了。(这样做的话,代码重复度低,而且修改代码都不用改业务代码,只需要方法里面的实现细节的代码就可以了,耦合度也低)
上面的逻辑封装是建立在Student类的成员变量为private的基础之上。这个原因就是:私有化属性后,成员变量就只能通过set,get方法来调用。然后我们才可以通过在set和get方法里面增加逻辑,实现我们想要的效果。
案例:
把成员变量弄成private是封装的一种形式。便于控制对成员变量的赋值。使用set方法,提供对外访问方式,就是因为可以在访问方式中添加逻辑判断语句。对访问数据进行操作,提高代码健壮性。 而get方法多用于取值。
public class People {
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
if(age<0){
System.out.println("年龄不能为负数!");
}
this.age = age;
}
}
由此可知:在set方法中就可以添加判断,使赋值合法化。如果没有封装和set方法中的判读,就可能出现为人的年龄赋负值,并且赋值成功的不合理性出现!
在java中使用set与get方法只是通用形式便于阅读理解,当然我们也可以自己定义方法方法名来控制对成员变量的赋值。
public class Test {
public static void main(String[] args) {
Student student = new Student();
student.setName("小明");
student.setAge(16);
student.printStudentAge();
Student student2 = new Student();
student.setName("小白");
student.setAge(200);
student2.printStudentAge();
}
}
class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age < 0 || age > 150)
throw new RuntimeException("年龄设置不合法");
this.age = age;
}
public void printStudentAge() {
System.out.println(name + "同学的年龄:" + age);
}
}
通过将Student这个类的name和age属性私有化,只有通过公共的get/set方法才能进行访问,在get/set方法中我们可以对内部逻辑进行封装处理,外部的调用方不必关心我们的处理逻辑。
继承
从一个已知的类中派生出一个新的类,新类可以拥有已知类的行为和属性,并且可以通过覆盖即重写来增强已知类的能力。
Java中不支持多继承,即一个类只可以有一个父类存在。另外Java中的构造函数是不可以继承的,如果空构造函数被private修饰,那么就是不明确的构造函数,该类是不可以被其它类继承的
原因我们可以先来看下Java中类的初始化顺序:
- 初始化父类中的静态成员变量和静态代码块
- 初始化子类中的静态成员变量和静态代码块
- 初始化父类中的普通成员变量和代码块,再执行父类的构造方法
- 初始化子类中的普通成员变量和代码块,再执行子类的构造方法
如果父类的**空构造函数是私有(private)**的(如果父类的其它构造函数为private,空构造不为private,那么子类也可以初始化),则初始化子类的时候(即第二步)不可以被执行(因为子类初始化时会去调用父类的空的构造方法,而私有化后,子类无法调用了)。
子类的特点:
- 子类拥有父类非private的属性和方法
- 子类可以添加自己的方法和属性,即对父类进行扩展
- 子类可以重新定义父类的方法,即方法的覆盖/重写
覆盖(@Override):
覆盖也叫重写,是指子类和父类之间方法的一种关系,比如说父类拥有方法A,子类扩展了方法A并且添加了丰富的功能。那么我们就说子类覆盖或者重写了方法A,也就是说子类中的方法与父类中继承的方法有完全相同的返回值类型、方法名、参数个数以及参数类型。
多态
多态的本质就是一个程序中存在多个同名的不同方法,主要通过三种方式来实现:
通过子类对父类的覆盖来实现
通过在一个类中对方法的重载来实现
通过将子类对象作为父类对象使用来实现
上面已经介绍了覆盖,下面先介绍重载
重载:
重载是指在一个类中**(包括父类)存在多个同名的不同方法,这些方法的参数个数**,参数顺序以及类型不同均可以构成方法的重载。如果仅仅是修饰符、返回值、抛出的异常不同,那么这是2个相同的方法。
Demo
public class OverLoadTest {
public void method1(String name, int age){
System.out.println("");
}
// 两个方法的参数顺序不同,可以构成方法的重载
public void method1(int age, String name){
System.out.println("");
}
//---------------------------------------------
public void method2(String name){
System.out.println("");
}
// 两个方法的参数类型不同,可以构成方法的重载
public void method2(int age){
System.out.println("");
}
//---------------------------------------------
public void method3(String name){
System.out.println("");
}
// 两个方法的参数个数不同,可以构成方法的重载
public void method3(int age, int num){
System.out.println("");
}
}
面试官追问:如果只有方法返回值不同,可以构成重载吗?
不可以。因为我们调用某个方法,有时候并不关心其返回值,这个时候编译器根据方法名和参数无法确定我们调用的是哪个方法。
举例:如果我们分别定义了如下的两个方法:
public String Test(String userName){ }
public void Test(String userName){ }
在调用的时候,直接 Test(“XiaoMing”); 那么就会存在歧义。
再来看看如何通过将子类对象作为父类对象使用来实现多态。
把不同的子类对象都当作父类对象来看,可以屏蔽不同子类对象之间的差异,**写出通用的代码,做出通用的编程,以适应需求的不断变化。**这样操作之后,父类的对象就可以根据当前赋值给它的子类对象的特性以不同的方式运作。
案例:
没贴完代码,但剩余代码猜都能猜出是些啥
public class Test {
public static void LetAnimalsay(Animal animal){
animal.say();
}
public static void main(String[] args) {
Animal animal=new Bird();
Animal animal2=new Dog();
LetAnimalsay(animal);
LetAnimalsay(animal2);
}
}
我们可以看到,这里传入什么动物,它都能够识别出来,这就是通用代码。这就是多态的好处。如果不这样做,那么很多种动物就要写很多种不同的方法(方法需要不同的参数),这样就很麻烦。
对象的引用型变量具有多态性,因为一个引用型变量可以指向不同形式的对象,即:子类的对象作为父类的对象来使用。在这里涉及到了向上转型和向下转型,我们分别介绍如下:
向上转型:
子类对象转为父类,父类可以是接口。
公式:Father f = new Son(); Father是父类或接口,Son是子类。
向下转型:
父类对象转为子类。公式:Son s = (Son) f;
在向上转型的时候我们可以直接转,但是在向下转型的时候我们必须强制类型转换。并且,如案例中所述,该父类必须实际指向了一个子类对象才可强制类型向下转型,即其是以这种方式Father f = new Son()创建的父类对象。若以Father f = new Father()这种方式创建的父类对象,那么不可以转换向下转换为子类的Son对象,运行会报错,因为其本质还是一个Father对象。
JDK,JRE和JVM的区别与联系有哪些?
三者基本概念
JDK(Java Development Kit)是一个开发工具包,是Java开发环境的核心组件,并且提供编译、调试和运行一个Java程序所需要的所有工具,可执行文件和二进制文件,是一个平台特定的软件
JRE(Java Runtime Environment)是指Java运行时环境,提供了运行Java程序的平台。JRE包含了JVM,但是不包含Java编译器/调试器之类的开发工具
JVM(Java Virtual Machine)是指Java虚拟机,当我们运行一个程序时,JVM负责将字节码转换为特定机器代码,JVM提供了内存管理/垃圾回收和安全机制等
区别与联系:
- JDK是开发工具包,用来开发Java程序,而JRE是Java的运行时环境
- JDK和JRE中都包含了JVM
- JVM是Java编程的核心,独立于硬件和操作系统,具有平台无关性,而这也是Java程序可以一次编写,多处执行的原因
Java的跨平台性是如何实现的呢?
Java程序都是运行在Java虚拟机,即JVM之上。JVM屏蔽了底层操作系统和硬件的差异。
案例:
Hello Word程序都是在文本文件中写的,然后我们通过javac来编译.java文件,生成了一个.class文件,最后再通过java命令来运行.class文件。其实这就是经历了一个先编译,再解释执行的过程,即先将java文件编译成了字节码.class文件,然后交给Java虚拟机解释成特定平台上的机器码。
总结为三点:
- JVM屏蔽了操作系统和底层硬件的差异
- Java面向JVM编程,先编译生成字节码文件,然后交给JVM解释成机器码执行
- 通过规定基本数据类型的取值范围和行为
java语言是编译型还是解释型语言?
Java的执行经历了编译和解释的过程,是一种先编译,后解释执行的语言,不可以单纯归到编译性或者解释性语言的类别中。