Java基础
为什么说Java语言是编译与解释共存?
高级编程语言按照程序的执行方式分为==编译型和解释型==两种。
编译型语言
是指编译器针对特定的操作系统将源代码一次性翻译成可被该平台执行的机器码;
解释型语言
是指解释器对源程序逐行解释成特定平台的机器码并立即执行。
Java 语言既具有编译型语言的特征,也具有解释型语言的特征,因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译器(javac)编译步骤,生成字节码(*.class 文件),这种字节码必须由 Java 解释器来解释执行。因此,我们可以认为 Java 语言编译与解释并存。
JDK与JRE的区别:
JDK与JRE
JDK是JavaJDK 是 Java Development Kit 缩写,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。
==JDK=JRE + javac + 工具==
JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。
==JRE=JVM+Java类库+Java命令+基础构件==
Java与C语言的区别
都是面向对象的的语言,均支持封装、继承、多态
- Java不提供指针访问直接访问内存,程序内存相对安全
- Java单继承(接口支持多继承,类不支持),C语言支持多继承。
- Java有自动的内存管理垃圾回收机制,不需要程序员手动释放无用的内存。
Java语法
java访问修饰符
修饰符 | 类内部 | 同个包(package) | 子类 | 其他范围 |
---|---|---|---|---|
public | Y | Y | Y | Y |
protected | Y | Y | Y | N |
无修饰符 | Y | Y | N or Y(同一个包) | N |
private | Y | N | N | N |
Java基本类型
4类8种
- 整型:byte-->short-->int-->long
- 浮点型:float-->double
- char类型:char
- boolean类型:boolean(true/false)
==注:1B=8bit==
基本类型 | 位数 | 字节 | 默认值 | 引用数据类型 |
---|---|---|---|---|
byte | 8 | 1 | 0 | Byte |
short | 16 | 2 | 0 | Short |
int | 32 | 4 | 0 | Integer |
long | 64 | 8 | 0L | Long |
float | 32 | 4 | 0f | Float |
double | 64 | 8 | 0d | Double |
char | 16 | 2 | 'u0000' | Character |
boolean | 1 | false | Boolean |
自增自减运算符(++ --)
自增自减,常见于算法题中用于对某一变量做自增1,或自减1的操作 ,是Java提供的一种特殊的运算符。
++和--运算符可以放在变量之前,也可以放在变量之后,当运算符放在变量之前时(前缀),先自增/减,再赋值;当运算符放在变量之后时(后缀),先赋值,再自增/减。
==符号在前先加减,符号在后后加减。==
continue、break、和 return 的区别是什么?
在循环结构中,当循环条件不满足或者循环次数达到要求时,循环会正常结束。但是,有时候可能需要在循环的过程中,当发生了某种条件之后 ,提前终止循环,这就需要用到下面几个关键词:
continue
:指跳出当前的这一次循环,继续下一次循环。break
:指跳出整个循环体,继续执行循环下面的语句。
return
用于跳出所在方法,结束该方法的运行。return 一般有两种用法:
return;
:直接使用 return 结束方法执行,用于没有返回值函数的方法return value;
:return 一个特定值,用于有返回值函数的方法
java中泛型
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。
== 和 equals的区别:
== 比较两个对象的引用地址是否相同,或基本数据类型的值是否相同。
equals比较两个对象时需要根据对象实现的equals()方法
== : 它的作用是判断两个对象的地址是否相等。即判断两个对象是否是同一个对象。(基本类型比较的值,引用数据类型比较的是内存地址值。)
equals()
:是判断两个对象是否相等,不能用于比较基本类型的数据变量。equals()
方法存在于Object类中。// Object类中代码 public boolean equals(Object obj) { return (this == obj); }
equals()
方法存在两种使用情况:
- 情况 1:类没有覆盖
equals()
方法。则通过equals()
比较该类的两个对象时,等价于通过“==”比较这两个对象。使用的默认是Object
类equals()
方法。 - 情况 2:类覆盖了
equals()
方法。一般,我们都覆盖equals()
方法来使两个对象的内容相等;若它们的内容相等,则返回 true(即,认为这两个对象相等)。
hashCode()与equalse()
常见面试题:
你重写过
hashcode
和equals
么,为什么重写equals
时必须重写hashCode
方法?hashCode():作用是获取哈希码,实际上返回的是一个int整数,此哈希码作用是确定对象在哈希表中的索引位置。hashCode()是Object的方法。
- 散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
为什么重写
equals
时必须重写hashCode
方法? 如果两个对象相等,则 hashcode 一定也是相同的。两个对象相等,对两个对象分别调用 equals 方法都返回 true。但是,两个对象有相同的 hashcode 值,它们也不一定是相等的 。因此,equals 方法被覆盖过,则
hashCode
方法也必须被覆盖。hashCode()
的默认行为是对堆上的对象产生独特值。如果没有重写hashCode()
,则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)java编程里有关约定:如果两个对象根据equals方法比较是相等的,那么调用这两个对象的任意一个hashcode方法都必须产生相同的结果。
为什么两个对象有相同的 hashcode 值,它们也不一定是相等的?
因为
hashCode()
所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的hashCode
。我们刚刚也提到了
HashSet
,如果HashSet
在对比的时候,同样的 hashcode 有多个对象,它会使用equals()
来判断是否真的相同。也就是说hashcode
只是用来缩小查找成本。hashCode()与 equals()的相关规定
- 如果两个对象相等,则 hashcode 一定也是相同的
- 两个对象相等,对两个对象分别调用 equals 方法都返回 true
- 两个对象有相同的 hashcode 值,它们也不一定是相等的
- 因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
- hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
重写(overwrite)与重载(overload)区别
override(重写,覆盖)
- 方法名、参数、返回值相同
- 子类方法不能缩小父类方法的访问权限
- 子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)
- 存在于父类和子类之间
- 方法被定义为final不能被重写
overload(重载,过载)
重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。
- 参数类型、个数、顺序至少有一个不相同。
- 不能重载只有返回值不同的方法名。
- 存在于父类和子类、同类中。
方法的重写(Overriding)和重载(Overloading)是Java多态性的不同表现。 重写(Overriding)是父类与子类之间多态性的一种表现,而重载(Overloading)是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。
深拷贝 vs 浅拷贝
- 浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
- 深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
final,static,this,super关键字总结
final关键字
final关键字主要用在三个地方:变量、方法、类。
变量: 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
类:当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。
使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。
static关键字
- 修饰成员变量和成员方法: 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:类名.静态变量名 类名.静态方法名()
- 静态代码块: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
- 静态内部类(static修饰类的话只能修饰内部类): 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。
- 静态导包(用来导入类中的静态资源,1.5之后的新特性): 格式为:import static 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。
this关键字
this关键字用于引用类的当前实例。 例如:
class Manager {
Employees[] employees;
void manageEmployees() {
int totalEmp = this.employees.length;
System.out.println("Total employees: " + totalEmp);
this.report();
}
void report() { }
}
在上面的示例中,this关键字用于两个地方:
- this.employees.length:访问类Manager的当前实例的变量。
- this.report():调用类Manager的当前实例的方法。 此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。
super关键字
super关键字用于从子类访问父类的变量和方法。 例如:
public class Super {
protected int number;
protected showNumber() {
System.out.println("number = " + number);
}
}
public class Sub extends Super {
void bar() {
super.number = 10;
super.showNumber();
}
}
在上面的例子中,Sub 类访问父类成员变量 number 并调用其其父类 Super 的 showNumber() 方法。 使用 this 和 super 要注意的问题:
- 在构造器中使用 super() 调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。
- this、super不能用在static方法中。
面向对象和面向过程的区别
- 面向过程 :面向过程性能比面向对象高。 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发。但是,面向过程没有面向对象易维护、易复用、易扩展。
- 面向对象 :面向对象易维护、易复用、易扩展。 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,面向对象性能比面向过程低。
参见 issue : 面向过程 :面向过程性能比面向对象高??
这个并不是根本原因,面向过程也需要分配内存,计算内存偏移量,Java 性能差的主要原因并不是因为它是面向对象语言,而是 Java 是半编译语言,最终的执行代码并不是可以直接被 CPU 执行的二进制机械码。
而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比 Java 好。
在 Java 中定义一个不做事且没有参数的构造方法的作用
java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用服务的无参构造。此时,如果父类只定义有参构造,没有定义无参构造,而子类又没有用super()来调用父类的特定构造方法,则编译时将会发生错误,因为父类中找不到无参构造可供执行。
解决办法则是在父类中加上一个不做事且无参数的构造方法。
成员变量与局部变量区别?
从语法形式上~:成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;
成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;
但是,成员变量和局部变量都能被 final 所修饰。
从变量在内存中的存储方式来看:如果成员变量是使用
static
修饰的,那么这个成员变量是属于类的,如果没有使用static
修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?
主要作用是完成对类对象的初始化工作。
若没有声明构造方法程序也是可以执行的。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。如果我们自己添加了类的构造方法(无论是否有参),Java 就不会再添加默认的无参数的构造方法了,这时候,就不能直接 new 一个对象而不传递参数了,所以我们一直在不知不觉地使用构造方法,这也是为什么我们在创建对象的时候后面要加一个括号(因为要调用无参的构造方法)。如果我们重载了有参的构造方法,记得都要把无参的构造方法也写出来(无论是否用到),因为这可以帮助我们在创建对象的时候少踩坑。
String、StringBuffer、StringBuilder
String是不可被更改的 ,StringBuffer和StringBuilder是可以被更改的。
StringBuffer是线程安全的 ,StringBuild是线程不安全的。
继承与实现
抽象类和接口提供现成的实现方法,我们只需要根据需求重写方法,或新增方法,就可以实现自己的功能需求,大大降低工作流。(类在同时继承类和实现接口时,先写继承【extends】后写实现【implement】。)
接口与抽象类
抽象类和接口都不能实例化,但可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或实现了某个接口,都需要对其抽象方法进行全部实现,否则该类仍需要被定义为抽象类。
接口和抽象类定义的目的:公共的方法进行抽象抽取,非公共的方法有具体的实现类或子类去定义,达到代码复用的目的。
抽象类:(代码复用)
含有abstract修饰的类即为抽象类,抽象类不能创建实例对象。含有abstract方法的类必须定义为抽象类,抽象类中的方法不必全是抽象方法。抽象类中定义的抽象方法必须在具体的子类中实现,因此不能有抽象构造方法和抽象静态方法。
接口:(行为约束)
接口是一种特殊的抽象类,接口中所有的方法都必须是抽象的(1.7及以前)。接口中成员变量默认类型为:public static final ,即常量。(1.8及以后,Java新特性中增加接口默认方法,及静态方法。)
相同点:
- 都不能实例化
- 接口的实现类或抽象类的子类都只有实现了接口或重写了抽象类中的方法后才能实例化。
区别
:- 抽象类可以有构造方法;接口中不能有构造方法
- 抽象类中可以有普通成员变量;接口中没有普通的成员变量。
- 抽象类中可以有非抽象的普通方法;接口中所有的方法都是抽象的(1.7及之前的定义)。
- 抽象类中抽象方法的访问类型可以是public 、protected和默认类型;接口中抽象方法只能是public,并且默认修饰为public abstract类型。
- 抽象类和接口中都可以包含静态成员变量,抽象类的成员变量的访问类型可以是任意的,但是接口中的静态成员变量只能是public static final 类型的即常量。
- 一个类可以实现多个接口但是只能继承一个抽象类。(接口算是对Java单继承的完善)
jdk8中对接口增加新特性
JDK1.8中对接口增加了新的特性:
(1)、默认方法(default method):JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;
(2)、静态方法(static method):JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)。
java核心
反射机制
JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。
静态编译和动态编译
- 静态编译: 在编译时确定类型,绑定对象
- 动态编译: 运行时确定类型,绑定对象
反射机制优缺点
- 优点: 运行期类型的判断,动态加载类,提高代码灵活度。
- 缺点: 1,性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 java 代码要慢很多。2,安全问题,让我们可以动态操作改变类的属性同时也增加了类的安全隐患。
反射的应用场景
反射是框架设计的灵魂。
在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
举例:
- 我们在使用 JDBC 连接数据库时使用
Class.forName()
通过反射加载数据库的驱动程序; - Spring 框架的 IOC(动态加载管理 Bean)创建对象以及 AOP(动态代理)功能都和反射有联系;
- 动态配置实例的属性;
- ......