Java基础预计写十个系列…
持续更新中…
1.基本概念
1.1 Java语言有哪些优点?
- Java为纯面向对象的语言;
- 平台无关性、可移植性。“一次编译,到处执行”!java为解释型语言;
- Java提供了很多内置类库,提高了开发效率;
- 提供了对Web应用开发的支持,如:Applet、Servlet和JSP开发Web应用;Socket、RMI开发分布式应用程序的类库;
- 具有很好的安全性和健壮性。如:Java的强类型机制、垃圾回收器、异常处理和安全检查机制;
- 去除了C++中难以理解、容易混淆的特性,例如头文件、指针、结构、单元、运算符重载、虚拟基础类、多重继承等,使得程序更简洁、严谨。
1.2 Java和C++有什么异同
- Java为解释型语言。C++为编译型语言;
- Java为纯面向对象语言,所有代码必须在类中实现,除基本数据类型外,所有的类型都是类。C++兼具面向对象和面向过程编程的特点,可以定义全局变量和全局函数;
- Java没有 指针 操作,程序更加安全;
- Java语言不支持多 继承 ,但java引入了接口,可以同时实现多个接口。由于接口具有多态性,因此在Java语言中可以通过实现多个接口来实现C++中的多重继承类似的目的;
- Java语言提供了垃圾回收器来实现垃圾的自动回收,当 垃圾回收器 释放无用对象的内存时,首先会调用该对象的finalize()方法;C++需要手动的管理内存分配,通常会把释放资源的代码放到 析构函数 中。
1.3 为什么需要public static void main(String[] args)这个方法?
程序的入口方法,main()JVM识别的特殊方法名。 public是权限修饰符,表示任何类和对象都可以访问这个方法,static 表明 main() 是一个静态方法,表示方法中的代码是存储在静态存储区的,只要类被加载,就可以使用该方法而不需要通过实例化对象来访问,必须有public static修饰,返回值为void;JVM找不到,就会报错。
程序运行时,第一个执行的方法就是main()方法。通常来讲,执行一个类的方法,先必须实例化一个类的对象,然后通过对象来调用这个方法。但由于main是程序的入口方法,此时还无实例化对象,因此 在编写main()方法时就不需要实例化对象就可以调用这个方法 ,so,main()方法要被定义成public与static。
main方法定义的其他格式 ?
static public void main(String[] args)
public static final void main(String[] args)
public static synchronized void main(String[] args)
由于main()方法作为程序的入口方法,因此 不能用abstract关键字 来修饰。
同一个.java文件中是否可以有多个main() 方法 ?
public class Test {
public static synchronized void main(String[] args) {
System.out.println("Test main!");
}
}
class T{
public static void main(String[] args) {
System.out.println("T main!");
}
}
运行结果:Test main!
1.4 如何实现在main()方法执行前输出 “Hello World!” ?
程序运行时,最先加载的就是main()方法,是否意味着main()方法就是程序运行时第一个被执行的模块呢?
不对。 静态块在类被加载时就会被调用。和代码顺序无关!
public class Test {
static {
System.out.println("Hello World1!");
}
public static synchronized void main(String[] args) {
System.out.println("Hello World2!");
}
}
运行结果:Hello World1!
Hello World2!
1.5 Java程序初始化的顺序是怎样的?
Java程序的初始化一般遵循的原则:
- 静态对象(变量)优先于非静态的对象(变量)初始化,其中,静态对象(变量)只初始化一次,而非静态对象(变量)可能会初始化多次。
- 父类优先于子类进行初始化。
- 按照成员变量的定义顺序进行初始化。
初始化顺序:
- 父类静态变量、静态代码块
- 子类静态变量、静态代码块
- 父类非静态变量、非静态代码块、构造器 & 子类非静态变量、非静态代码块、构造器
package org.base;
public class Derived extends Base {
static {
System.out.println("Derived static block");
}
{
System.out.println("Derived block");
}
public Derived() {
System.out.println("Derived constructor");
}
public static void main(String[] args) {
new Derived();
}
}
class Base {
static {
System.out.println("Base static block");
}
{
System.out.println("Base block");
}
public Base() {
System.out.println("Base constructor");
}
}
运行结果:Base static block
Derived static block
Base block
Base constructor
Derived block
Derived constructor
1.6 Java中的作用域有哪些?
{ } 决定了其定义的变量名的可见性与生命周期。
变量类型: 成员变量、静态变量、局部变量。
类成员变量 与 类的实例化对象 的作用范围相同。当类被实例化时,成员变量就会在内存中分配空间并初始化,直到这个被实例化对象的生命周期结束时,成员变量的声明周期才结束。被 static修饰的变量 成为静态变量或全局变量,与成员变量不同的是,静态变量不依赖于任何实例,而是被所有实例所 共享,也就是说,只要有 一个类被加载,JVM就会给类的静态变量分配存储空间。 因此,就可以通过 类名.变量名 来访问静态变量。
作用域对比
作用域与可见性 | 当前类 | 同一package | 子类 | 其他package |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
- public。当前项目。 表明该成员变量和方法对所有类或对象都是可见的,所有类或对象都可以直接访问。
- protected。当前包。 表明成员变量或方法对该类自身,与它在同一包中的其他类,在其他包中该类的子类都可见。
- default。当前包无子类。 表明该成员变量或方法只有自己和与其位于同一包内的类可见。若父类与子类位于同一个包内,则子类对父类的default成员变量或方法都有访问权限;若父类与子类位于不同package内,则无访问权限。
- private。当前类。表明该成员变量或方法是私有的,只有当前类对其有访问权限。
default方法使用: 1.switch语句;2.接口中使用;
1.7 一个Java文件中是否可以定义多个类
可以。但最多只能有一个类被public修饰并这个类名必须与文件名相同,若这个文件没有public类,则文件名是任意一个类名即可。javac 指令编译 .java 文件,会给每一个类生成一个对应的 .class 文件。
1.8 什么是构造函数?
一种特殊函数。用来对象实例化时 初始化对象 的成员变量。
构造函数特点:
- 构造函数必须与类名相同,无返回值,也不能有void;
- 每个类可以有多个构造函数,无手写构造函数,调用默认构造器;有手写构造函数,调用手写构造函数;
- 构造函数参数可以有任意个;
- 构造函数 伴随着new操作一起调用,由系统调用。只运行一次,而普通的方法是在程序执行到它时被调用,且可以被调用多次。
- 构造器不能被继承,因此不能被覆盖,可以重载(参数个数和参数类型 )。
- 当有父类时,在实例化对象时会先执行父类的构造函数,然后执行子类的构造函数。 当父类没有提供无参构造器时,子类的构造函数中必须显示的调用父类的构造器,否则会编译出错。 如果父类提供了无参数构造器,子类就可以不显示的调用,默认调用无参构造器。
package org.base;
public class Test extends B {
public static void main(String[] args) {
Test test = new Test();
Test test2 = new Test(1);
}
public Test() {
System.out.println("Test Constructor1.");
}
public Test(int a) {
super(a); // super(); 也OK
System.out.println("Test Constructor2.");
}
}
class B {
public B() {
System.out.println("B Constructor1.");
}
public B(int a) {
System.out.println("B Constructor2.");
}
}
运行结果:B Constructor1.
Test Constructor1.
B Constructor2.
Test Constructor2.
1.9 为什么Java中有些接口没有任何方法?
- 接口是抽象方法定义的集合,是一种特殊的抽象类;
- 一个类通过实现接口的方式,来继承接口的抽象方法;
- 接口中只包含方法的定义,没有方法的实现;
- 除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
package org.base;
public class Test implements H{
public static void main(String[] args) {
}
public void m(){ // 实现接口中所有方法,否则定义此类为abstract类
}
}
interface H{ // 隐式抽象接口
public void m();// 默认是abstract类,权限修饰符只能为public
}
接口有以下特性:
- 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
- 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
- 接口中的方法都是公有的。
接口与类的区别:
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法。
- 接口不能包含成员变量,除了 static 和 final 变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承。
抽象类和接口的区别 :
- 抽象类中的方法可以有 方法体,就是能实现方法的具体功能,但是接口中定义的方法必须是抽象方法,如:(abstract) void m();
- 抽象类中的 成员变量 可以是各种类型的,而接口中的成员变量只能是 public static final 类型的并被初始化。
- 接口中 不能含有静态代码块 以及
静态方法,而抽象类是可以有静态代码块和静态方法。 - 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
注: Java8开始,接口可以有默认方法与静态方法;
package org.base;
public class Default implements T {
@Override
public void m2() {// 必须实现所有抽象方法,否则声明此类为abstract类
System.out.println("This is m2 method.");
}
public static void main(String[] args) {
T.m();
Default test1 = new Default();
test1.m1();
test1.m2();
}
}
interface T {
static void m() {// Java8新增的静态方法
System.out.println("This is m method.");
}
default void m1() {// 默认方法
System.out.println("This is m1 method.");
}
void m2();// 没有实现的抽象方法
}
标识接口:接口内部没有声明任何方法,仅充当一个标识作用。Java类库中已存在的标识接口中有 Cloneable 和 Serializable 等。使用时会经常用instanceof来判断实例对象的类型是否实现了一个给定的标识接口。
例子:开发一款游戏,有一个专门负责出去寻找有用的材料,假设这个人物只收集矿石和武器,而不会收集垃圾,通过接口标识实现这个功能。
package org.base;
import java.util.ArrayList;
interface Stuff{}
// 矿石
interface Ore extends Stuff{}
// 武器
interface Weapon extends Stuff{}
// 垃圾
interface Rubbish extends Stuff{}
// 金矿
class Gold implements Ore{
public String toString() {
return "Gold";
}
}
// 铜矿
class Copper implements Ore{
public String toString() {
return "Copper";
}
}
// 枪
class Gun implements Weapon{
public String toString() {
return "Gun";
}
}
// 榴弹
class Grenade implements Weapon{
public String toString() {
return "Grenade";
}
}
class Stone implements Rubbish{
public String toString() {
return "Stone";
}
}
public class TestInterface {
public static ArrayList<Stuff> collectStuff(Stuff[] s){
ArrayList<Stuff> al = new ArrayList<>();
for (int i = 0; i < s.length; i++) {
if(!(s[i] instanceof Rubbish))
al.add(s[i]);
}
return al;
}
public static void main(String[] args) {
Stuff[] s = {new Gold(),new Copper(), new Gun(), new Grenade(), new Stone()};
ArrayList<Stuff> al = collectStuff(s);
System.out.println("The usefull Stuff collected is:");
for (int i = 0; i < al.size(); i++) {
System.out.println(al.get(i));
}
}
}
修饰外部 interface 的修饰符有:public、abstract
1.10 Java中的clone 方法有什么作用?
Java中没有明确提供指针的概念与用法,实质上每个 new 语句返回的都是一个指针的引用,由于Java取消了指针概念,在编程中会忽略对象和引用的区别。
Java在处理基本数据类型时,都是按值传递进行处理的,除此之外的其他类型都是按引用传递的方式。
package org.base;
public class Clone {
public static void main(String[] args) {
Obj a = new Obj();
Obj b = a; // b指向a的内容
b.changeInt();
System.out.println("a:" + a.getaInt());
System.out.println("b:" + b.getaInt());
}
}
class Obj {
private int aInt = 0;
public int getaInt() {
return aInt;
}
public void setaInt(int aInt) {
this.aInt = aInt;
}
public void changeInt() {
this.aInt = 1;
}
}
运行结果:a:1
b:1
clone() 方法: 已有的对象A创建出另外一个与A具有相同状态的对象B,并且对B改变不会影响A的状态。Object类中提供了一个clone()方法。这个方法的作用是返回一个Object对象的复制(新的对象而非引用)。
clone方法的使用步骤:
- 实现clone的类首先需要 继承 Cloneable 标识接口;
- 重写Object 类中的clone()方法;
- 在clone方法中 调用super.clone();
- 把浅复制的引用指向原型对象新的克隆体。
package org.base;
public class Clone {
public static void main(String[] args) {
Obj a = new Obj();
Obj b = (Obj) a.clone();
b.changeInt();
System.out.println("a:" + a.getaInt());
System.out.println("b:" + b.getaInt());
}
}
class Obj implements Cloneable{
private int aInt = 0;
public int getaInt() {
return aInt;
}
public void setaInt(int aInt) {
this.aInt = aInt;
}
public void changeInt() {
this.aInt = 1;
}
public Object clone() {
Object o = null;
try {
o = (Obj)super.clone(); // throws或try-catch抛异常
} catch (CloneNotSupportedException e) {
// TODO: handle exception
e.printStackTrace();
}
return o;
}
}
运行结果:a:0
b:1
浅复制和深复制:如何选择?
- 检查类中有无基本类型(对象的数据成员)。若无,返回super.clone()。
- 若有,确保类中包含的所有非基本类型的成员变量都实现了深复制。
执行过程:
Object o = super.clone(); // 执行浅复制
对每一个对象attr执行以下语句:// 执行深复制
o.attr = this.getAttr().clone();
最后返回 o
package org.base;
import java.util.Date;
public class Clone {
public static void main(String[] args) {
Obj a = new Obj();
Obj b = (Obj) a.clone();
b.changeDate();
System.out.println("a:" + a.getBirth());
System.out.println("b:" + b.getBirth());
}
}
class Obj implements Cloneable {
private Date birth = new Date();
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public void changeDate() {
this.birth.setMonth(4); // 五月
}
public Object clone() {
Obj o = null;
try {
o = (Obj) super.clone();
} catch (CloneNotSupportedException e) {
// TODO: handle exception
e.printStackTrace();
}
// 实现深复制
o.birth = (Date) this.getBirth().clone();
return o;
}
}
运行结果:a:Sun Oct 13 18:16:50 CST 2019
b:Mon May 13 18:16:50 CST 2019
浅复制和深复制有什么区别?
浅复制(Shallow Clone):仅仅复制所考虑的对象,而不复制它所引用的对象。
深复制(Deep Clone):深复制把复制的对象所引用的对象都复制了一遍。
假如定义如下一个类:
public Test {
public int i;
public StringBuffer s;
}
深复制与浅复制的区别。
1.11 什么是反射机制
反射机制允许程序在运行时进行自我检查,同时也允许对其内部的成员进行操作。实际开发用的不多。
反射机制提供的功能主要有:
- 得到一个对象所属的类;
- 获取一个类的所有成员变量和方法;
- 在运行时调用对象的方法。
- 在运行时动态的创建类的对象;
package org.base;
public class Reflect {
public static void main(String[] args) {
try { // 使用反射机制加载类
Class c = Class.forName("org.base.Sub");
Base b = (Base)c.newInstance();
b.f();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Sub extends Base{
public void f(){
System.out.println("Sub");
}
}
class Base{
public void f(){
System.out.println("Base");
}
}
运行结果:Sub
获取Class类的三种方法:
- Class.forName(“类的路径”);
- 类名.Class;
- 实例.getClass()
Java创建对象的方式有几种?
- new语句实例化一个对象;
- 反射机制创建对象;
- clone()方法创建一个对象;
- 反序列化的方式创建对象。
1.12 package有什么用?
package:包含.java源文件、.class编译文件、resource文件(.xml、.avi、.mp3、.txt文件)条理性进行组织。类似于Linux文件系统。
简单来讲 package由 class 和 interface组成 。
作用:
- 提供多层命名冲突。不同package可以存在相同名。
- 对类按功能进行分类。若不使用package,代码可读性差、可维护性差。