java基础知识
1.基础
1.1 java语言特性
1.1.1 Java语言是编译型还是解释型语言?
答:Java的执行经历了编译和解释的过程,是一种先编译,后解释执行的语言,不可以单纯归到编译性或者解释性语言的类别中。
1.1.2 java常量和变量之间的区别
常量:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q6gEkXWp-1618314716395)(C:\Users\33066\AppData\Roaming\Typora\typora-user-images\image-20201125214926169.png)]
变量:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pl4JF2Lv-1618314716398)(C:\Users\33066\AppData\Roaming\Typora\typora-user-images\image-20201125214954730.png)]
1.1.4 面向对象三大特性-封装继承多态
**答:**面向对象是一种思想,可以将复杂问题简单化,让我们从执行者变为了指挥者。
面向对象的三大特性为:封装,继承与多态。
- **封装:**将事物封装成一个类,减少耦合,隐藏细节。保留特定的接口与外界联系,当接口内部发生改变时,不会影响外部调用方。
- 继承:从一个已知的类中派生出一个新的类,新类可以拥有已知类的行为和属性,并且可以通过覆盖/重写来增强已知类的能力。
- **多态:**多态的本质就是一个程序中存在多个同名的不同方法,主要通过三种方式来实现:
- 通过子类对父类的覆盖来实现
- 通过在一个类中对方法的重载来实现
- 通过将子类对象作为父类对象使用来实现
解析:
这算是一个相当基础的问题,面向对象的思想以及其三大特性我们均需要有较好的理解。接下来,我们对三大特性进行一个详细的阐述与解析吧。
1.1.4.1 关于封装
封装主要是为了增加程序的可读性,解耦合并且隐藏部分实现细节。让我们来看下边的案例,看看该如何实现封装。
案例:
package com.pak1;
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 = 120;
student2.printStudentAge();
}
}
class Student {
String name;
int age;
public void printStudentAge() {
System.out.println(name + "同学的年龄:" + age);
}
}
程序输出如下:
我们看到小白同学的年龄120,(假设)不符合业务逻辑需要,所以我们需要做一些内部逻辑的处理。所以需要进行代码封装,将内部逻辑进行一个隐藏。
封装之后的代码如下:
package com.pak1;
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(120);
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 > 60)
throw new RuntimeException("年龄设置不合法");
this.age = age;
}
public void printStudentAge() {
System.out.println(name + "同学的年龄:" + age);
}
}
程序输出结果如下:
通过将Student这个类的name和age属性私有化,只有通过公共的get/set方法才能进行访问,在get/set方法中我们可以对内部逻辑进行封装处理,外部的调用方不必关心我们的处理逻辑。
1.1.4.2 继承:
我们需要注意Java中不支持多继承,即一个类只可以有一个父类存在。另外Java中的构造函数是不可以继承的,如果构造函数被private修饰,那么就是不明确的构造函数,该类是不可以被其它类继承的,具体原因我们可以先来看下Java中类的初始化顺序:
- 初始化父类中的静态成员变量和静态代码块
- 初始化子类中的静态成员变量和静态代码块
- 初始化父类中的普通成员变量和代码块,再执行父类的构造方法
- 初始化子类中的普通成员变量和代码块,再执行子类的构造方法
如果父类构造函数是**私有(private)**的,则初始化子类的时候不可以被执行,所以解释了为什么该类不可以被继承,也就是说其不允许有子类存在。我们知道,子类是由其父类派生产生的,那么子类有哪些特点呢?
- 子类拥有父类非private的属性和方法
- 子类可以添加自己的方法和属性,即对父类进行扩展
- 子类可以重新定义父类的方法,即方法的覆盖/重写
既然子类可以通过方法的覆盖/重写以及方法的重载来重新定义父类的方法,那么我们来看下什么是方法的覆盖/重写吧。(其实这也是一个高频的面试热身题目)
1.1.4.3 覆盖(@Override):
覆盖也叫重写,是指子类和父类之间方法的一种关系,比如说父类拥有方法A,子类扩展了方法A并且添加了丰富的功能。那么我们就说子类覆盖或者重写了方法A,也就是说子类中的方法与父类中继承的方法有完全相同的返回值类型、方法名、参数个数以及参数类型。
Demo展示如下:
package niuke;
public class OverrideTest {
public static void main(String[] args) {
new Son().say();
}
}
class Parent {
public void say(){
System.out.println("我是父类中的say方法");
}
}
class Son extends Parent {
@Override
public void say(){
System.out.println("我是子类中的say方法,我覆盖了父类的方法");
}
}
我们可以看到,子类本身继承了父类的say方法,但是其想重新定义该方法的逻辑,所以就进行了覆盖。
1.1.4.4多态:
通过方法的覆盖和重载可以实现多态,上边我们介绍了何为方法的覆盖,这里我们先来介绍下何为方法的重载:
1.1.5 Java 重载与重写是什么?有什么区别?
答:
重载(Overload)是让类以统一的方式处理不同类型数据的一种手段,实质表现就是多个具有不同的参数个数或者类型的同名函数(返回值类型可随意,不能以返回类型作为重载函数的区分标准)同时存在于同一个类中,是一个类中多态性的一种表现(调用方法时通过传递不同参数个数和参数类型来决定具体使用哪个方法的多态性)。
重写(Override)是父类与子类之间的多态性,实质是对父类的函数进行重新定义,如果在子类中定义某方法与其父类有相同的名称和参数则该方法被重写,不过子类函数的访问修饰权限不能小于父类的;若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法,如需父类中原有的方法则可使用 super 关键字
package niuke;
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("");
}
}
1.1.6如果只有方法返回值不同,可以构成重载吗?
答:不可以。因为我们调用某个方法,有时候并不关心其返回值,这个时候编译器根据方法名和参数无法确定我们调用的是哪个方法。
举例:如果我们分别定义了如下的两个方法:
public String Test(String userName){
}
public void Test(String userName){
}
在调用的时候,直接 Test(“XiaoMing”); 那么就会存在歧义。
我们再来看看如何通过将子类对象作为父类对象使用来实现多态。
把不同的子类对象都当作父类对象来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。这样操作之后,父类的对象就可以根据当前赋值给它的子类对象的特性以不同的方式运作。
对象的引用型变量具有多态性,因为一个引用型变量可以指向不同形式的对象,即:子类的对象作为父类的对象来使用。在这里涉及到了向上转型和向下转型,我们分别介绍如下:
向上转型:
子类对象转为父类,父类可以是接口。
公式:Father f = new Son(); Father是父类或接口,Son是子类。
向下转型:
父类对象转为子类。公式:Son s = (Son) f;
在向上转型的时候我们可以直接转,但是在向下转型的时候我们必须强制类型转换。并且,如案例中所述,该父类必须实际指向了一个子类对象才可强制类型向下转型,即其是以这种方式Father f = new Son()创建的父类对象。若以Father f = new Father()这种方式创建的父类对象,那么不可以转换向下转换为子类的Son对象,运行会报错,因为其本质还是一个Father对象。
1.1.7 构造器是否可被重写
答:构造器不能被继承,因此不能被重写,但可以被重载。
一个类会有一个默认的构造函数,这个构造函数没有内容也没有返回值,一般都回略去不写.这种情况下,编译器在编译的时候会默认加上一个无参且方法体为空的构造函数.但是,如果类的构造函数被重写了,如上例,Person类已经有了一个有参数有方法体的构造函数,这时编译器就不会再给它默认加上一个无参且方法体为空的构造函数.可以理解为无参的构造函数被覆盖了.这种情况称为没有默认构造函数.
而在函数的继承里,子类必须调用父类的构造函数。但是,子类只能继承父类的默认构造函数,如果父类没有默认的构造函数,那子类不能从父类继承默认构造函数.这时子类必须使用super来实现对父类的非默认构造函数的调用.
在创建对象时,先调用父类默认构造函数对对象进行初始化,然后调用子类自身自己定义的构造函数。
1.1.8 java创建对象的几种方式
1.用new语句创建对象,这是最常用的创建对象的方式。
2.运用反射手段,调用Java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。
3.调用对象的clone()方法。
4.运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法.
1.1.7.1 利用反射创建对象:
class Per{
public String name;
public int age;
public Per(){
}
public Per(String name){
}
public String toString() {
return "对象!!!";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Demo04 {
public static void main(String[] args) throws Exception{
Class<Per> c = Per.class;
Constructor<Per> con = c.getDeclaredConstructor();
Per p = con.newInstance();
/**
* 从JVM的角度看,我们使用关键字new创建一个类的时候,
* 这个类可以没有被加载。但是使用newInstance()方法的时候,
* 就必须保证:
* 1、这个 类已经加载;
* 2、这个类已经连接了。而完成上面两个步骤的正是Class的静态方法forName()所完成的,
* 这个静态方法调用了启动类加载器,即加载 java API的那个加载器。
*/
p.setName("zhangsan");
System.out.println(p.getName());
}
}
1.1.7.2 clone方法创建对象:
Person p = new Person(23, "zhang");
Person p1 = (Person) p.clone();
System.out.println(p);
System.out.println(p1);
1.1.7.3 序列化的方法创建对象:
package org.westos.Demo;
import java.io.*;
public class Demo2 {
public static void main(String[] args) throws IOException {
ObjectOutputStream obji = new ObjectOutputStream(new FileOutputStream("Object1.txt"));
Teacher teacher