Java基础问题
-
JDK:Java Development Kit,Java开发工具包,提供了Java的开发环境和运行环境;包含了编译Java源文件的编译器Javac,还有调试和分析的工具。
-
JRE:Java Runtime Environment,Java运行环境,包含Java虚拟机及一些基础类库
-
JVM:Java Virtual Machine,Java虚拟机,提供执行字节码文件的能力
JVM是实现Java跨平台的核心,由其可实现java程序 “一处编译,到处运行”,但JVM本身不是跨平台的,不同的平台需要安装不同的JVM

Java基本数据类型和变量类型有哪些?
-
基本数据类型:boolean, char, byte, short, int, long, float, double
注意:String是引用类型
-
变量类型:
-
局部变量:定义在方法体内部的变量
-
成员变量:
-
类变量(静态变量):独立于方法之外的变量,属于类本身。需要static修饰,类变量就是以static修饰的独立于方法之外的成员变量
-
实例变量(非静态变量):独立于方法之外的变量,依赖实例存在。不需要static修饰,实例变量就是没有static修饰的独立于方法之外的成员变量
-
常量
-
-
值传递和引用传递
-
基本数据类型使用的时值传递,方法结束栈帧出栈,数据消失
-
引用类型(String、数组、对象)使用的时引用传递,传递的实际是指向同一内存区域的地址
== 和 equals 的区别
-
== 对比的是栈中的值,基本数据类型是变量值,引用类型是堆中内存对象的地址
-
equals:Object中默认也是采用 == 比较,通常会重写;比如在String中,equals比较的就是内容,即字符串的值
public class StringDemo{
public static void main(){
String str1 = "Hello"; // JVM会将其分配到常量池
String str2 = new String("Hello"); // 分配到堆内存
String str3 = str2; // 引用传递
System.out.println(str1 == str2); //false
System.out.println(str1 == str3); //false
System.out.println(str2 == str3); // true
System.out.println(str1.equals(str2)); //true
System.out.println(str1.equals(str3)); //true
System.out.println(str2.equals(str3)); //true
}
}
重载和重写
-
重载:生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同(混淆点:跟返回类型没关系),发生在编译时
public int add(int a, int b);
public String add(int a, int b);
// 不是重载,编译报错 -
重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果分类方法访问修饰符为private则子类就不能重写该方法
谈谈对面向对象的理解
-
“面向对象” vs “面向过程”
二者是思考角度的差异,面向过程以“执行者”的角度来思考问题,更看重事情的每一个步骤及顺序;而面向对象更多以“组织者”的角度来思考问题,更注重事情有哪些参与者(对象)、及各自需要做什么.
举个简单的生活例子,比如用洗衣机洗衣服:
面向过程会将任务拆解为一系列步骤(函数):打开洗衣机 -> 放衣服 -> 倒洗衣液 -> 清洗 -> 甩干
面向对象会拆解出人和洗衣机两个对象:人有三个方法:打开洗衣机,放衣服,倒洗衣液;洗衣机有两个方法:清洗,甩干
由此可以看出面向对象的更多是考虑如何去选择合适的工具,然后组织到一起干一件事;在程序世界中,这个思维无处不在,比如,我们要开发项目,以三层架构的模式来开发,那么这个时候,我们不需要重复造轮子,只需要选择市面上的主流框架即可,比如:Spring,Spring MVC,Mybatis等等
正因如此,面向对象更易于复用、扩展和维护
面向对象编程的本质就是:以类的方式组织代码,以对象的形式组织(封装)数据
-
面向对象的三大特性:封装性、继承性、多态性
-
封装性:封装的意义在于明确标识出允许外部使用的所有成员函数和数据项;内部细节对外部调用透明,外部调用无需修改或者关心内部表现
两个经典的封装性的运用:
-
javabean的属性私有,一组get/set对外访问,因为属性的赋值或者获取逻辑只能由javabean本身决定,而不能由外部胡乱修改
// 假设有一个Javabean有如下属性与其对应的set方法
// 可以看出该name有自己的命名规则,外部调用必须遵从其命名规范,无法直接赋值
private String name;
public void setName(String name){
this.name = "Jarreet_" + name;
} -
orm框架,操作数据库,我们不需要关心链接是如何建立的、sql是如何执行的,只需要引入mybatis,调方法即可
-
-
继承性:继承基类的方法,并做出自己的改变和/或扩展;子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需扩展自己个性化的;便于代码复用
-
多态性:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同;易于程序维护和扩展
多态的三个条件:继承,方法重写,父类引用指向子类
// 注意:无法调用子类特有的功能
父类类型 变量名 = new 子类对象;
变量名.方法名();
// 原生的javaweb开发中就有经典的多态性的运用,如
UserDao userDao = new UserDaoImpl();
-
向上转型和向下转型
-
向上转型:将子类对象转换为父类对象,此处父类对象可以是接口(安全的)
-
向下转型:把父类对象转换为子类对象(不安全的)
// 有两个类 Father是父类 Son是子类
Father f1 = new Son(); // f1指向一个Son对象,向上转型
Son s1 = (Son)f1 // f1指向Son对象
Father f2 = new Father();
Son s2 = (Son)f2; // 报错;子类引用不能指向父类对象
-
注意点:
-
父类引用指向子类对象,而子类引用不能指向父类对 象
-
把子类对象直接赋给父类引用叫upcasting向上转型,向上转型不用强制转化
-
把指向子类对象的父类引用赋给子类引用叫downcasting向下转型,需要强制转换
-
this关键字和super关键字
-
this代表本身调用者这个对象;this()代表本类的构造。
-
super代表父类对象的引用;super()代表父类的构造;子类初始化时会自动调用父类的无参构造器,若父类没有无参构造器,则子类需要手动调用父类的有参构造器
super(param)且该语句应放在子类构造器的首行,否则报错。
接口和抽象类的区别
-
接口和抽象类的特性
-
抽象类可以存在普通成员函数,而接口只能存在 public abstract 方法
-
抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的
-
抽象类只能继承一个,接口可以实现多个
-
-
接口和抽象类的设计目的和使用场景
-
接口的设计目的,是对类的行为进行一种“有”约束,也就是提供一种机制,可以强制要求不同的类具有相同的行为;它只是约束了行为的有无,但不对如何实现行为进行限制
-
抽象类的设计目的,是代码复用;当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方法一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类;在这个抽象类中实现了B,避免让所有的子类来实现B,这就达到了代码复用的目的;而A减B的部分,留给各个子类自己实现;正是因为A-B在这里没有实现,所以抽象类不允许实例化出来(否则当调用到 A-B 时,无法执行)
-
抽象类是对类的本质的抽象,表达的是 is a 的关系,比如:BWM is a Car;抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现
-
接口是对行为的抽象,表达的是 like a 的关系;比如:Bird like a AirCraft,但其本质上 is a Bird;接口的核心是定义行为,即实现类可以做什么,至于实现类主体是谁、是如何实现的,接口并不关系
-
使用场景:当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口
抽象类的功能要远超过接口;但是,定义抽象类的代价太高;因为对高级语言来说,每个类只能继承一个类;在这个类中,你必须继承或编写出其所有子类的所有共性;虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述;而且你可以在一个类中同时实现多个接口;在设计阶段会降低难度
-
包装类
-
什么是包装类?
包装类是java提供的一组类,专门用来创建8哥基本数据类型对应的对象,一共有8哥包装类,存放在java.lang包中,基本数据类型和其对应的包装类如下:
基本数据类型 包装类 byte Byte short Short int Integer long Long float Float double Double char Character boolean Boolean -
包装类的体系结构
-
Object有三个子类:Character、Number、Boolean
-
Number又衍生出六个子类:Byte、Short、Integer、Long、Float、Double
-
-
装箱和拆箱
装箱和拆箱是包装类的特有名词,装箱时指将基本数据类型转为对应的包装类对象,拆箱就是将包装类对象转为对应的基本数据类型。
-
装箱:
每个包装类都提供了一个有参构造函数:public Type(type value),用来实例化包装类对象(该方法已过期,不推荐使用)
// int类型数据的装箱,其余类型类似
int i = 8;
Integer integer = new Integer(i);每个包装类还有一个重载的构造函数,Character的重载构造函数:public Type(char value),其他包装类的重载构造函数:public Type(String value)(该方法已过期,不推荐使用)
// Character的重载构造函数
Character character = new Character("a");
// Integer的重载构造函数,其余包装类相似
Integer integer = new Integer("8");
// 在Boolean的重载构造函数中,党参数为"true"时,Boolean值为true,否则为flase
Boolean flag = new Boolean("a");每一个包装类都有一个valueOf(type value)静态方法(推荐使用本方法)
// Integer的valueOf()方法,其余包装类类似
int i = 8;
Integer integer = Integer.valueOf(i);同样,valueOf也有重载方法,valueOf(char value)专门为Character服务,其他7个包装类则使用valueOf(String value)
Character character = Character.valueOf("a");
Integer integer = Integer.valueOf("8");
Boolean flag = Boolean.valueOf("a"); -
拆箱:
每个包装类都有一个 *Value()方法,通过该方法可以将包装类转为基本数据类型
// Integer的*value()方法,其余类似
int i = integer.intValue();除了Character外,每个包装类都有一个parse*(String value)方法可以将字符串类型转为基本数据类型
// Integer的parse*()方法,其余类似
int i = Integer.pareInt("8");
-
-
自动装箱和自动拆箱
-
自动装箱:自动把基本数据类型转为包装类
// 自动装箱
Integer integer = 0; -
自动拆箱:自动把包装类转为基本数据类型
// 自动拆箱
int i = integer;
-
-
经典面试题:
@Test
public void testInteger() {
Integer i1 = new Integer(12);
Integer i2 = new Integer(12);
System.out.println(i1 == i2); // false
Integer i3 = 126;
Integer i4 = 126;
int i5 = 126;
System.out.println(i3 == i4); // true
System.out.println(i3 == i5); // true
Integer i6 = 128;
Integer i7 = 128;
int i8 = 128;
System.out.println(i6 == i7); // false
System.out.println(i6 == i8); //true
}
内部类
一般情况下,类和类之间是相互独立的,内部类的意思是打破这种独立,让一个类成为另外一个类的内部成员,和成员变量、成员方法同等级别。
采用内部类这种技术,可以隐藏细节和内部结构,封装性更好,让程序的结构更加合理。
基本的内部类既可以在外部类中直接定义,也可以在外部类中的方法体中定义。
-
非静态内部类
package com.jarreet.test;
public class OuterClass{
// 成员变量
private String outerName;
// 成员方法
public void display(){
System.out.println("OuterClass display");
System.out.println(outerName);
}
// 内部类
public class InnerClass{
private String innerName;
public void display(){
System.out.println("InnerClass display");
System.out.println(innerName);
}
public InnerClass(){
innerName = "inner Class";
}
}
public static void main(String[] args){
OuterClass outerClass = new OuterClass();
outerClass.display();
}
}非静态内部类的使用就是将内部类当作外部类的一个成员变量/成员方法来使用,所以必须依赖于外部类的对象才能调用,用法和成员变量/成员方法是一致的。
-
静态内部类
静态内部类的构造不需要依赖于外部类对象,类中的所有静态组件都不需要依赖于任何对象,可以直接通过类本身进行构造。
package com.jarreet.test;
public class OuterClass{
// 成员变量
private String outerName;
// 成员方法
public void display(){
System.out.println("OuterClass display");
System.out.println(outerName);
}
// 静态内部类
public static class InnerClass{
private String innerName;
public InnerClass(){
innerName = "inner class";
}
public void display(){
System.out.println("InnerClass display");
System.out.println("innerName");
}
}
public static void main(String[] args){
OuterClass outerClass = new OuterClass();
outerClass.display();
InnerClass innerClass = new InnerClass();
innerClass.display();
}
} -
匿名内部类
-
定义接口
package com.jarreet.test;
public interface MyInterface {
public void test();
} -
使用匿名内部类
package com.jarreet.test;
public class Test {
public static void main(Stirng[] args){
// 匿名内部类
MyInterface myInterface1 = new MyInterface() {
@Override
public void test(){
System.out.println("test");
}
};
myInterface1.test();
}
}
匿名内部类在多线程中用到的比较多,且多和lambda表达式配合使用
-
final关键字
-
final关键字的作用
final可以用来修饰类、方法以及变量;修饰类表示该类不可被继承;形式方法表示方法不可被子类覆盖,但是可以重载;修饰变量表示变量一旦被赋值就不可以更改它的值。
-
修饰成员变量
如果final修饰的是类变量,只能在静态初始化块中指定初始值或者声明该类变量时指定初始值;如果final修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值
-
修饰局部变量
系统不会为局部变量进行初始化,局部变量必须由程序员显示初始化;因此使用final修饰局部变量时,即可以在定义时指定默认值(后面的代码不能对变量再赋值),也可以不指定默认值,而在后面的代码中对final变量赋值初值(仅一次)
public class FinalVar{
final static int a = 0; // 在声明的时候就要赋值 或者静态代码块赋值
/**
static{
a = 0;
}
*/
final int b = 0; // 在声明的时候就要赋值 或者代码块中赋值 或者构造器赋值
/**
{
b = 0;
}
*/
public void main(String[] args){
final int localA; // 局部变量只声明没有初始化,不会报错,与final无关
localA = 0; // 在使用前一定要赋值
// localA = 1; // 但是不允许第二次赋值
}
} -
修饰基本数据类型和引用数据类型
如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象;但是引用的值是可变的
public class FinalReferenceTest(){
public static void main(){
final Person p = new Person(25);
p.setAge(24); // 合法
// p = null; // 非法
}
}
-
-
为什么局部内部类和匿名内部类只能访问局部final变量?
// 编译之后,会生成两个class文件,Test.class Test1.class
public class Test{
public static void main(String[] args){
// 局部final变量a,b
public void test(final int b){
final int a = 10;
// 匿名内部类
new Thread(){
public void run(){
System.out.println(a);
Ststem.out.println(b);
};
}.start();
}
}
class Outclass{
private int age = 12; // 成员变量不用经过final修饰也能访问
public void outPrint(final int x){ // 局部变量需要final修饰才能访问
class InClass{
public void InPrint(){
System.out.println(x);
Ststem.out.println(age);
}
}
new InClass().InPrint();
}
}
}首先我们要知道的一点是:内部类和外部类是处于同一级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁.
这里就会产生问题:当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象还可能存在(只有没人再引用它时,才会死亡);这里就出现了一个矛盾:内部类对象访问了一个不存在的变量;为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样党局部变量死亡后,内部类仍然可以访问它,实际访问的是局部变量的 "copy"。这样就好像延长了局部变量的生命周期。
将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变,怎么解决问题呢?

浙公网安备 33010602011771号