Java基础知识(第一部分)
在初学Java的过程中,一直在漫无目的看书写代码,最近一段时间,想把以前的内容回过头来总结一遍,温故而知新,也是对Java基础知识再次学习。
一、总体架构
以书籍的目录为例,市场上大多数的Java书籍从两部分来阐述Java的内容,第一部分则是Java的基础知识,而第二部分则是Java的深入部分。今天就将Java的基础部分进行一个简单的总结。首先,它的大体框架如下:
(1)Java基本语法(基本数据类型、数组的建立)
(2)字符串(String、StringBuffer、StringBuilder)
(3)面向对象之三国鼎立(封装、继承、多态)(Java的核心)
(4)面向对象之群雄争霸
(5)集合的楚汉争霸(Collection、Map)
(6)异常(Throwable)
(7)多线程(Thread、Runnable)
二、内容讲解
(1)Java基本语法
(1.1)基本数据类型
Java有八大基本数据类型,分别是字节、短整型、整型、长整型、字符型、浮点型、双精度型、布尔型
基本数据类型 | 字节 | 取值范围 |
---|---|---|
byte | 1 | -2^7 - 2^7-1 |
short | 2 | -2^15 ~ 2^15-1 |
int | 4 | -2^31 ~ 2^31-1 |
long | 8 | -2^63 ~ 2^63-1 |
char | 2 | |
float | 4 | |
double | 8 | |
boolean | 4 | true、false |
(1.2)数组的建立
基本数据类型[] 数据名 = new 基本数据类型[定义数组的长度]
以整型数据为例,创建数组从语法上来说两步走
(1)定义个数组的引用变量
(2)将创建的数组赋给这个引用变量,也就是这个引用指向它
int[] arr;
arr = new int[10];
//合起来就是
int[] arr = new int[10];
//此时初始化数据
由于传统建立的数据需要初步定义好数组的长度,因此创建数组长度过小则会不够保存数据,而数组长度过大则会导致浪费资源的现象,因此动态数组ArrayList则比传统数组更有优势,这一点会在集合部分进行介绍。除此之外,字符串String.toCharArray()也能够转换为字符数组,这个方法在很多编程题中用到,因此,贴上此方法的源码:
public char[] toCharArray() {
return this.isLatin1() ? StringLatin1.toChars(this.value) : StringUTF16.toChars(this.value);
}
(2)字符串
(2.1)String
线程安全不可变的字符串
- String类包含许多方法使用String类最大特性是不可变性,因此要使用String.split()时要赋予新的String对象。
- String.toCharArray()将String对象转换为字符数组
public static void main(String[] args) {
String str="abcde";
char[] ch = str.toCharArray(); //ch{a,b,c,d,e}
}
- String.charAt(int index)方法,获取字符串的单个字符
- String.valueOf(int i),获取int型的字符串
- String.length(),获取字符串的长度
(2.2)StringBuffer
线程安全可变的字符串,效率低于StringBuilder
- append()方法,写入数据
- charAt()返回指定索引的数据
- reverse,字符串翻转
- toString,返回字符串
(2.3)StringBuider
线程不安全的可变字符串,但是效率高于StringBuffer,其使用方法与StringBuffer一样
- append()方法,写入数据
- charAt()返回指定索引的数据
- reverse,字符串翻转
- toString,返回字符串
(3)面向对象之三国鼎立
在面试的过程中,很多面试官会首先问Java面向对象的特点是什么?(答:封装、继承和多态)那你回答一下什么是封装、继承和多态,以及它们的使用场景?(答:…)
(3.1)封装
Java提供了四个修饰符来保护Java类中的成员变量、方法不受外界所破坏,这四个访问修饰符如下所示:
访问修饰符 | 访问权限 |
---|---|
public | 全局范围 |
protected | 子类及同一包的类 |
默认(空) | 同一包中的类 |
private | 同一个类中 |
一般来说,使用private来封装类中的成员变量的时候,这个成员只能在当前类的内部被访问,该类会提供set、get方法来供其它类进行调用。
//用户类
public class User {
private String name;
private int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
//----------------------------------------------
//学生类
public class Student {
public String name;
public int id;
}
//测试类
public class Test {
public static void main(String[] args) {
User user = new User();
Student student = new Student();
String name = student.name;
String uname = user.name;//这行会报错,因为User类的name变量是私有的。外部类无法访问
//以下是正确的调用
user.setName("张三");
System.out.println(user.getName());
}
}
(3.2)继承
Java只能单继承,其继承关键字为extends
public class Student {
public String name;
public int id;
public void sayHello(){
System.out.println("Hello");
}
}
public class StudentChild extends Student {
public void sayHelloAlso(){
super.sayHello();
System.out.println(super.name);
System.out.println(super.id);
}
@Override
public void sayHello(){
System.out.println("GoodBye");
}
public static void main(String[] args) {
StudentChild studentChild = new StudentChild();
studentChild.sayHelloAlso();
studentChild.sayHello();
}
}
//输出
Hello
null
0
GoodBye
总结:子类继承父类能够得到父类的成员变量和方法,子类中的方法可以用关键字super.成员变量或方法来得到父类的成员变量或方法,当父类的方法不满足子类的需求时,子类可以重写父类的方法,方法重写(Override)需要满足“两同两小一大”原则。
- 两同:方法名与参数列表都相同
- 两小:返回值类型、抛出的异常
- 一大:访问权限类型
(3.2)多态
首先介绍一下Java引用变量的类型,其包括编译时的类型和引用时的类型。
- 编译时类型:由声明变量时使用的类型所决定
- 运行时类型:实际赋给该变量的对象所决定
多态的场景:当编译时类型与运行时类型不一致时,此时就会发生多态情况。例如,下面程序的
Student s1 = new StudentChild(); 子类和父类都包含sayHello()方法,当s1调用sayHello方法时,会运行子类的sayHello方法。父类的引用变量指向子类的对象。
相同类型的变量、调用同一个方法时呈现出多种不同的行为特征,这就是多态。
总结:编译看左边,运行看右边,运行一个Java程序的时候,javac会首先编译.java程序为 .class文件,当编译的时候识别出左边的变量是否包含该变量所调用的方法,若包含该方法,则编译通过,Jvm会将编译的.class文件进行加载给机器能够识别的语言;反之,编译失败,则JVM无法识别出.class文件。
public class Student {
public String name;
public int id;
public void sayHello(){
System.out.println("Hello");
}
public void noByeBye(){
System.out.println("Bye");
}
}
public class StudentChild extends Student {
public void sayHelloAlso(){
System.out.println(super.name);
System.out.println(super.id);
super.sayHello();
}
public void sayHello(){
System.out.println("GoodBye");
}
public static void main(String[] args) {
Student s1 = new StudentChild();
s1.sayHello();
}
}
//输出
GoodBye
Hello
(4)面向对象之群雄逐鹿
(4.1)构造器与初始化
- 构造器是为了初始化类中的成员变量
- 构造器无返回值,不能声明为void,一般为public
- 构造器方法名相同,参数列表不同,就会形成重载
- 在构造器中调用其它方法,直接调用方法名即可;在构造器中调用其它构造器,需要this(方法名),且必须在第一行
- 当在构造器中调用父类的构造方法时,使用super(方法名),必须在程序第一行
(4.2)this与super
- this关键字用的比较多的是局部变量给成员变量赋值
public class Student {
public String name;
public int id;
public Student(String name,int id){
this.name = name;
this.id = id;
}
public void sayHello(){
String name = "小明";
System.out.println(this.name+"你好");
}
public static void main(String[] args) {
Student student = new Student("小王",17);
student.sayHello();
}
}
//输出
小王你好
- super子类调用父类的方法或成员变量
public class ChildTest extends FatherTest {
public void sayHello(){
System.out.println("子类调用父类的方法");
super.sayBye(super.name,super.id);
//super.sayBye(name,id);这种方法也一样,可以省略super
}
public static void main(String[] args) {
ChildTest childTest = new ChildTest();
childTest.sayHello();
}
}
public class FatherTest {
String name = "小明";
int id = 1;
public void sayBye(String name,int id) {
System.out.println(id + ":"+ name +" "+ "byebye!");
}
}
//输出
子类调用父类的方法
1:小明 byebye!
(4.3)抽象类与接口
- 抽象类
- 抽象类必须要有关键字abstract修饰
- 抽象类中含有抽象方法和普通方法
- 抽象类中的成员变量随意修饰
public abstract class AbstractTest {
public static final int tel = 456123156;
private int id;
int num;
protected int hands;
public String name;
public void sayHello(){
System.out.println("This is a abstract class");
}
public abstract void say();
}
- 接口
- 接口中的方法必须为抽象方法,且修饰符为public 或者默认不写
- 接口中的变量必须为public static final类型,一般不用写,系统会自动识别。
- 接口中的变量必须初始化。
public interface InterfaceTest {
public static final int id =1;
public abstract void say();
abstract void speak();
}
(5)集合的楚汉争霸
Java集合分为两大阵营:Collection集合和Map集合。老规矩,从全局观进入学习,先附上一张集合的全局结构图
(5.1)Collection
Collection:属于集合,继承于Iterable
public interface Collection<E> extends Iterable<E>
- List:它是接口,继承Collection集合,要实现List接口,可以创建接口的引用变量来指向ArrayList对象或者LinkedList对象。特点:有序,元素可重复。
public interface List<E> extends Collection<E>
- ArrayList
继承AbstractList,实现List接口,底层由数组来实现的,包含了数组的特点,查询快,增删慢。 - LinkedList
继承AbstractSequentialList,实现List接口,底层是有链表实现的,查询慢,增删快。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CollectionTest {
//List集合的增删改查
public void listTest(){
List<Integer> list = new ArrayList<>();
//添加数据(增)
for(int i = 0;i<10;i++){
list.add(((int)Math.floor(Math.random()*i)));
}
//遍历list(查)
System.out.println("生成的原始随机数组:");
//System.out.println("使用foreach遍历集合:");
for(Integer i:list){
System.out.print(i+" ");
}
//删除集合中的元素
System.out.println();
list.remove(2);
System.out.println("将list中的第3个元素删除");
//System.out.println("使用for循环遍历集合:");
for(int i = 0;i<list.size();i++){
System.out.print(list.get(i)+" ");
}
//更改集合中的元素
System.out.println();
list.set(0,1);
System.out.println("将集合中第一个元素改为1");
//System.out.println("使用Iterator遍历集合:");
Iterator it= list.iterator();
while(it.hasNext()){
System.out.print(it.next()+" ");
}
}
public static void main(String[] args) {
CollectionTest collectionTest = new CollectionTest();
collectionTest.listTest();
}
}
测试结果:
生成的原始随机数组:
0 0 1 2 2 3 2 2 4 1
将list中的第3个元素删除
0 0 2 2 3 2 2 4 1
将集合中第一个元素改为1
1 0 2 2 3 2 2 4 1
Process finished with exit code 0
- Set:接口,继承Collection集合。特点:无序,元素不可重复。
public interface Set<E> extends Collection<E>
- HashSet:采用哈希表结构存储数据,它是通过判断hashcode和equals方法来保证元素的唯一性。
原理:先判断hashcode值,若相同,在判断equals()内容是否相同,若相同,则不会将该对象存储。
由于hashcode是Object类的方法,任何类都是Object的子类,因此任何类都含有hashcode方法。
(1)首先,当向哈希表存放对象时,会调用对象的hashcode方法从而算出对象存放在哈希表中的(2)其次,当算出的hashcode值与另一个对象相同时,这个时候会发生“哈希冲突”,此时会调用对象的equals方法来比较两个对象的内容是否相同从而判定是不是同一个对象。
(3)最后,如果equals方法返回的是true,则不会把新增的对象存放在哈希表中,如果为false,则把新增的对象存放在哈希表中。
注意:对于HashSet,当HashSet存储元素是JAVA API提供的类型元素时,此时不需要重写hashcode与equals方法;当HashSet存储元素是自定义的元素时,则必须重写HashCode与equals方法。
/**
* Set集合是无序的,元素不可重复的
*/
//当HashSet存储Java API提供的元素时,此时不需要重写hashcode与equals方法。
public class HashSetTest {
public void hashSet(){
HashSet<User> set = new HashSet();
set.add(new User("张三",18,"南京"));
set.add(new User("李四",19,"上海"));
set.add(new User("王五",20,"合肥"));
set.add(new User("赵六",17,"杭州"));
Iterator<User> it = set.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
public static void main(String[] args) {
HashSetTest hash = new HashSetTest();
hash.hashSet();
}
}
/**
* 创建User实体类,并重写hashcode和equals方法
*/
public class User {
private String userName;
private int userAge;
private String userAddress;
public User(String userName,int userAge,String userAddress){
this.userName = userName;
this.userAge = userAge;
this.userAddress = userAddress;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getUserAge() {
return userAge;
}
public void setUserAge(int userAge) {
this.userAge = userAge;
}
public String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", userAge=" + userAge +
", userAddress='" + userAddress + '\'' +
'}';
}
@Override
public int hashCode() {
int result = 1;
result = result * 31 + userAge;
result = result * 31 + ((userName == null) ? 0 : userName.hashCode());
result = result * 31 + ((userAddress == null) ? 0 : userAddress.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof User)) {
return false;
}
User user = (User) obj;
return user.userName.equals(userName) && user.userAge == userAge
&& user.userAddress.equals(userAddress);
}
}
- treeSet:提供有序的Set集合
(5.2)Map
Map<k,v>接口,以键值对的形式存储元素(key-value),key是唯一且不可为null,value可为null且有且只有一个为null,value也可重复。
Map的实现类,主要是使用HashMap,下面介绍HashMap的三种遍历方式。
- for循环
(Map.Entry<> e:map.entrySet)
entrySet()+iterator
keySet()+iterator
public class MapTest {
public void mapTest(){
Map<Integer,String> map = new HashMap<>();
map.put(1,"张三");
map.put(2,"李四");
map.put(3,"王五");
map.put(4,"赵六");
map.put(5,"张三");
System.out.println("1.使用for循环进行遍历:");
for(Map.Entry<Integer,String> entry:map.entrySet()){
System.out.print(entry.getKey()+":"+entry.getValue()+" ");
}
System.out.println();
System.out.println("2.用迭代器iterator遍历:");
/**
*
*原理:(1)将map集合中的所有键key放到set集合中(key不重复,满足set集合元素不重复规则),
* (2)使用迭代器iterator取出每一个键,然后再根据get方法取出每一个键对应的value。
*/
Set set = map.entrySet();
Iterator it1 = set.iterator();
while(it1.hasNext()){
Map.Entry<Integer,String> map1 = (Map.Entry)it1.next();
System.out.print(map1.getKey()+":"+map1.getValue()+" ");
}
System.out.println();
System.out.println("3.用keySet()迭代遍历:");
Iterator it = map.keySet().iterator();
while(it.hasNext()){
int key;
String value;
key = (int)it.next();
value = map.get(key);
System.out.print(key+":"+value+" ");
}
}
public static void main(String[] args) {
MapTest mt = new MapTest();
mt.mapTest();
}
}
- 数组与集合遍历的区别
数组是基本类型,没有重写toString方法,需要调用Arrays.toString()方法。
List和集合对象都已经重写了toString方法,因此可以直接打印。
public class Main {
public static void main(String[] args) {
int[] arr = {0,1,2,3,4};
List<Integer> list = new ArrayList<>();
Set<Integer> set = new HashSet<>();
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0;i<5;i++){
list.add(i);
set.add(i);
map.put(i,i+1);
}
System.out.println("arr输出:"+arr);
System.out.println("list输出:"+list);
System.out.println("set:"+set);
System.out.println("map输出:"+map);
}
}
输出:
arr输出:[I@1b2c6ec2
arr输出:[0, 1, 2, 3, 4]
list输出:[0, 1, 2, 3, 4]
set输出:[0, 1, 2, 3, 4]
map输出:{0=1, 1=2, 2=3, 3=4, 4=5}
(6)异常
Java异常的结构图如下
异常主要分为:Error和Exception。
- Error 错误, 一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等
- Exception:程序异常,需要使用try,catch代码块捕获。
public static void main(String[] args) {
try {
StudentChild studentChild = new StudentChild();
studentChild.sayHello();
}catch (IndexOutOfBoundsException ie){
System.out.println("数组越界:运行程序时输入的参数个数不够");
}catch (NumberFormatException ne){
System.out.println("数字格式异常: 程序只能接收整数参数");
}catch (ArithmeticException ae){
System.out.println("算数异常");
}catch (Exception e){
System.out.println("未知异常");
e.printStackTrace();
}finally {
System.out.println("不管怎么样,我就是能运行");
//资源回收代码块
}
}
Java 的异常机制主要依赖于try、catch 、finally 、throw 和throws 五个关键字
- throws:用于方法声明,使用throws 声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理。
- throw:自行抛出异常,在方法内、代码块中执行。
(7)多线程
创建多线程有三种方式:继承Thread、实现Runnable、使用Callable和Future创建线程
- 继承Thread类:(1)重写run方法;(2) 创建Mythread实例;(3)调用start方法
public class MyThread extends Thread{
@Override
public void run(){
for(int i = 0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
}
}
-输出结果-
Thread-1 0
Thread-1 1
Thread-0 0
Thread-1 2
Thread-1 3
Thread-0 1
Thread-1 4
Thread-0 2
Thread-1 5
Thread-0 3
Thread-1 6
Thread-0 4
Thread-1 7
Thread-0 5
Thread-1 8
Thread-0 6
Thread-1 9
Thread-0 7
Thread-0 8
Thread-0 9
- 实现Runnable接口
public class MyRunnable implements Runnable {
@Override
public void run(){
for(int i = 0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
MyRunnable m = new MyRunnable();
MyRunnable m1 = new MyRunnable();
new Thread(m).start();
new Thread(m1).start();
}
}
//输出
Thread-0 0
Thread-0 1
Thread-0 2
Thread-0 3
Thread-0 4
Thread-0 5
Thread-0 6
Thread-0 7
Thread-0 8
Thread-1 0
Thread-0 9
Thread-1 1
Thread-1 2
Thread-1 3
Thread-1 4
Thread-1 5
Thread-1 6
Thread-1 7
Thread-1 8
Thread-1 9
- 使用Callable和Future
(7.2)线程的生命周期
线程的生命周期:新建、就绪、运行、阻塞、死亡。
(7.3)控制线程
方法名称 | 方法作用 |
---|---|
join() | 让一个线程等待另一个线程完成的方法 |
sleep() | 使线程暂停多少毫秒,并使其进入阻塞状态 |
yield() | 与sleep类似,但是不会阻塞该线程,只是将该线程转入就绪状态 |
wait() | 调用该方法的线程进入waitting状态,只有等待其它线程的通知或被中断才会返回,调用该方法后,会释放对象的锁。 |
notify() | 通知一个在对象上的线程,使其从wait()状态返回,返回的前提是该线程获得了对象的锁 |
总结
以上总结了Java基础的部分内容,对于文章中的不足之处,还请大家批评指正,一起学习!