当前位置:网站首页 > Java基础 > 正文

Java提供的基础类存储在



* 以下内容是我在准备java面试的时候觉得有用,面试官很可能会问的一些问题

* 内容(除了代码)详情来自网络(学习的时候遇到不会的百度的 (*^__^*) )

* 如果大家发现有什么地方不对,请告诉我。谢啦!!☆⌒(*^-゜)v

1:java的基础类型

Java语言提供了八种基本语言

boolean char     byte       8位 short 16位 int float 32位 long double 64位

注意:1:String本身就是一个对象而不是基本数据类型,String的变量名是对String类的引用

   2:基本类型转化为包装类,类似于int这样的转化为Integer等表示自动装箱,反之为自动拆箱

   3:int的默认值为0,而Integer的默认值为null,即Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况

2:String、StringBuffer、StringBuilder之间的区别

2.1:三者的执行速度: String<StringBuffer<StringBulider

2.2:String是字符串常量,也就是说值是不会改变的,StringBuffer和StringBuilder是字符串变量,是可以对值进行操作的。

public class StringTest  {   

public static void main(String argv[]){

String s = "abc";

s = s+"def";

System.out.println("s="+s);//s=abcdef

/*

 * 我们明明就是改变了String型的变量s的,为什么说是没有改变呢?

 * 其实这是一种欺骗,JVM是这样解析这段代码的:

 * 首先创建对象s,赋予一个abc

 * 然后再创建一个新的对象s用来执行第二行代码,

 * 也就是说我们之前对象s并没有变化,所以我们说String类型是不可改变的对象了,

 * 由于这种机制,每当用String操作字符串时,实际上是在不断的创建新的对象,

 * 而原来的对象就会变为垃圾被GC回收掉,可想而知这样执行效率会有多低。

 */

    }  

}

2.3:StringBulider和StringBuffer的区别

StringBulider是线程非安全的,StringBuffer是线程安全的。JVM不能保证StringBuilder操作是安全的,虽然他速度快,但可以保证StringBuffer是可以确定操作的。但是因为大多数操作是在单线程环境下完成的,所以大多数情况下建议使用StringBuilder是因为速度问题。

2.4:对三者进行总结:如果操作少量的数据,使用String

单线程操作字符串缓冲区下大量的数据使用StringBulider

多线程操作字符串缓冲区下大量的数据使用StringBuffer

3:类型之间的转换

public class StringTest  {   

  public static void main(String argv[]){

    //String转化为int float double

    String s = "123";

    int i=Integer.parseInt(s);

    float f = Float.parseFloat(s);

    double d = Double.parseDouble(s);

    System.out.println("i="+i+" f="+f+" d="+d);

    System.out.println(Integer.valueOf(s));

    //注意:虽然valueOf也可以将字符串转化为int等,但是valueOf调用的是parse的方法,返回的是已经封装的对象,如Integer等

    //将int float double转化为String

    String s1 = String.valueOf(i);

    String s2 = String.valueOf(f);

    String s3 = String.valueOf(d);

    System.out.println("s1="+s1+" s2="+s2+" s3="+s3);

    }  

}

4:日期的用法

4.1:calendar和Date的区别

Date是类,Calendar是抽象类。在JDK1.0中,Date是唯一的代表时间的类,但因为Date不便实现国际化,所以JDK1.1版本开始,推荐使用Calendar类进行时间和日期的处理。

import java.util.Calendar;

import java.util.Date;

public class DateTest  {   

  public static void main(String argv[]){

    Calendar calendar = Calendar.getInstance();

    Date date = calendar.getTime();

    //Sun Jul 23 10:33:44 CST 2017(解释:星期天  7月 23号 10点33分44秒  2017年)

    System.out.println(date);

    //获取年月

    int year = calendar.get(Calendar.YEAR);

    int month = calendar.get(Calendar.MONTH)+1;//日期是0~11 所以要加1

    System.out.println("year="+year+" month="+month);//year=2017 month=7

    //获取日期

    int day1 = calendar.get(Calendar.DAY_OF_YEAR);//本年的第204天

    int day2 = calendar.get(Calendar.DAY_OF_MONTH);//这个月的第23天

    int day3 = calendar.get(Calendar.DAY_OF_WEEK);//本周第一天(每周星期天开始)

    int day4 = calendar.get(Calendar.DATE);

    System.out.println("day1="+day1+" day2="+day2+" day3="+day3+" day4="+day4);//day1=204 day2=23 day3=1 day4=23

    //获取时分秒

    int hour1 = calendar.get(Calendar.HOUR_OF_DAY);

    int hour2 = calendar.get(Calendar.HOUR);

    int minute = calendar.get(Calendar.MINUTE);

    int second = calendar.get(Calendar.SECOND);

    //hour1=10 hour2=10 minute=59 second=26 10点59分26秒

    System.out.println("hour1="+hour1+" hour2="+hour2+" minute="+minute+" second="+second);

    }  

}

4.2:DateFormat和SimpleDateFormat的区别

DateFormat可以直接使用、但其本身是一个抽象类,可以根据Locate指定的区域得到不同的日期显示效果。SimpleDateFormat是DateFormat的子类,一般情况下DateFormat类很少直接使用,而都是由SimpleDateFormat类完成。

import java.text.DateFormat;

import java.text.SimpleDateFormat;

import java.util.Calendar;

import java.util.Date;

public class DateTest  {   

  public static void main(String argv[]){

    Calendar calendar = Calendar.getInstance();

    Date date = calendar.getTime();

    DateFormat df1 = DateFormat.getDateInstance();

    DateFormat df2 = DateFormat.getDateTimeInstance();

    System.out.println(df1.format(date));//2017-7-23

    System.out.println(df2.format(date));//2017-7-23 12:18:51

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy年mm月dd日");

    System.out.println(sdf.format(date));//2017年18月23日

    }  

}

5:集合List Set Map的区别

5.1:List

集合List有序,其中的元素可以重复。List主要有两种实现方式ArrayList和LinkedList

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class ListTest{
  public static void main(String[] args) {
    List<String> addList = new ArrayList<String>();
    addList.add("a");
    addList.add("b");
    addList.add("c");
    List<String> list = new ArrayList<String>();
    list.add("hello");//添加一个元素
    list.addAll(addList);//将集合addList添加到集合list
    System.out.println(list);//[hello, a, b, c]
    list.contains("hello");//判断是否存在这个对象
    System.out.println(list.contains("hello"));//true
    System.out.println(list.get(0));//hello
    list.remove(2);//移除第三个元素“b”  set没有这个方法

    list.remove("a");//移除元素a 
    System.out.println(list);//[hello, c]

    System.out.println(list);//[hello, a, c]
    list.removeAll(list);//移除list集合里面的所有元素
    System.out.println(list.size());//0
  }
}

ArrayList在查询的时候比LinkedList快,占用内存也比LinkedList小。但是,LinkedList的插入、删除操作比ArrayList快

5.2:Set

集合Set无序,其中元素不可以重复。Set主要有两种实现方式HashSet和TreeSet

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class SetTest{
  public static void main(String[] args) {
    List<String> addList = new ArrayList<String>();
    addList.add("a");
    addList.add("b");
    addList.add("c");
    Set<String> set = new HashSet<String>();
    set.add("g");//添加一个元素
    set.addAll(addList);//将集合addList添加到集合set
    //利用迭代器输出Set
    Iterator it = set.iterator();
    while(it.hasNext())
    System.out.print(it.next()+" ");//a b c g 
    set.remove("c");//删除对象c 
    System.out.println(set);//[a, b, g]
    set.contains("g");//判断是否存在这个对象
    System.out.println(set.contains("g"));//true
    set.removeAll(set);//删除集合set里面的全部元素
    System.out.println(set.size());//0
    System.out.println(set.isEmpty());//true
  }
}

HashSet是有一个hash表来实现的,TreeSet是由一个树型的结构实现的,里面的元素全部都是有序的。因此HashSet的add() remove() contains()方法更快

5.3:Map

集合Map是一种键对象和值对象映射的集合,每个元素都包含一个键和一个值,Map集合中的键对象不允许重复,也就说,任意两个键对象通过equals()方法比较的结果都是false.,但是可以将任意多个键映射到同一个值对象上。Map主要有两种实现方式HashMap和Hashtable

虽然HashMap和Hashtable都实现了Map接口,但他们还是有不一样的地方

两种方式的区别:

A:HashMap的迭代器(Iterator)是fail-fast迭代器(快速失败),而Hashtable的迭代器不是fail-fast的

B:Hashtable是同步的而HashMap不是,因此HashMap适合单线程而Hashtable适用于多线程,在单线程环境下它比HashMap要慢

C:HashMap不能保证随着时间的推移Map中的元素次序是不变的

6:IO(输入输出)

文件

1:使用FileWriter向文件输入数据

package test;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class WriteToTxt {
    public static void main(String[] args) {
        String data = "我们分担寒潮、风雷、霹雳;我们共享雾霭、流岚、虹霓。";
        File file = new File("D:\java\myeclipse_workspace\test\src\file\a.txt");
        FileWriter fw = null;
        try {
            //如果目标文件所在的目录不存在,则创建目录
            //如果文件不存在,则创建文件
            if(!file.getParentFile().exists()) {  
              System.out.println("目标文件所在目录不存在,准备创建它!");  
              file.getParentFile().mkdirs(); 
            }  
            fw=new FileWriter(file);//如果文件不存在,则创建文件,但不能创建路径
            fw.write(data);//将字符串写入到指定的路径下的文件中
            System.out.println("Done");
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2:通过BufferedWriter向文件写入数据

package test;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class WriteToTxt {
    public static void main(String[] args) {
        String data = "我们分担寒潮、风雷、霹雳;我们共享雾霭、流岚、虹霓。";
        File file =new File("D:\java\myeclipse_workspace\test\src\file\a.txt");
        BufferedWriter bufferWritter = null;
        try{
              //如果目标文件所在的目录不存在,则创建目录
              //如果文件不存在,则创建文件
              if(!file.getParentFile().exists()) {  
                System.out.println("目标文件所在目录不存在,准备创建它!");  
                file.getParentFile().mkdirs(); 
              }  
              bufferWritter = new BufferedWriter(new FileWriter(file.getAbsoluteFile()));
              bufferWritter.write(data);
              System.out.println("Done");
         }catch(IOException e){
              e.printStackTrace();
         }finally{
             try {
                bufferWritter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
         }
    }
}

3:通过FileReader以字符流的方式读取文件

package test;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class ReadFromTxt {
    public static void main(String[] args) {
        File file = new File("D:\java\myeclipse_workspace\test\src\test\a.txt");
        FileReader fr=null;
        try {
            fr = new FileReader(file);
            char[] buf=new char[20];
            int end=0;
            while((end=fr.read(buf))!=-1){
                //read(char[])返回的是读入的字符的个数,读入的字符不足20个的时候返回读入的字符 的长度
                System.out.print(new String(buf,0,end));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            try{
                fr.close();
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
}

4:通过BufferedReader以行为单位读取文件

package test;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class ReadFromTxt {
    public static void main(String[] args) {
        File file = new File("D:\java\myeclipse_workspace\test\src\test\a.txt");
        BufferedReader reader = null;  
        try {  
            //FileReader返回的是字符流,BufferedReader类从字符输入流中读取文本 
            reader = new BufferedReader(new FileReader(file));  
            String tempString = null;  
            // 一次读入一行,直到读入null为文件结束  
            while ((tempString = reader.readLine()) != null)
                System.out.println(tempString);   
        } catch (IOException e) {  
            e.printStackTrace();  
        } finally {  
            if (reader != null) {  
                try {  
                    reader.close();  
                } catch (IOException e1) {  
                    e1.printStackTrace();
                }  
            }  
        }  
    }

}

控制台

1:Scanner取得一个字符串或一组数字

需要注意的是:如果不按照要求输入的话会出现错误。

package test;

import java.util.Scanner;

public class ReadFromConsole {
    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        System.out.println("输入一个整数:");
        int integer = s.nextInt();
        System.out.println("你输入控制台的整数是:"+integer);
        System.out.println("输入一个浮点数:");
        float fl = s.nextFloat();
        System.out.println("你输入控制台的浮点数是:"+fl);
        System.out.println("输入一个双精度浮点型数:");
        double doub = s.nextDouble();
        System.out.println("你输入的双精度浮点型数是:"+doub);
        System.out.println("输入一个布尔型数据:");
        boolean b = s.nextBoolean();
        System.out.println("你输入的布尔型数据是:"+b);
        System.out.println("输入一个字符串:");
        String str = s.nextLine();//数次之外还可以直接使用 s.next()
        System.out.println("你输入的字符串是:"+str);
    }
} 

2:使用BufferedReader取得控制台输入的数据

package test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class ReadFromConsole {
    public static void main(String[] args) {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String read = null;
        System.out.print("输入数据:");
        try {
           read = br.readLine();
        } catch (IOException e) {
           e.printStackTrace();
        }
        System.out.println("输入的数据是:"+read);
    }
}

字节流

写文件

package com.iobyte.test;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class ByteTest {
    public static void main(String[] args) {
        String data = "我们分担寒潮、风雷、霹雳;我们共享雾霭、流岚、虹霓。";
        File file = new File("D:\java\myeclipse_workspace\iotest\src\file\a.txt");
        OutputStream  out = null;
        try {
            //如果目标文件所在的目录不存在,则创建目录
            //如果文件不存在,则创建文件
            if(!file.getParentFile().exists()) {  
              System.out.println("目标文件所在目录不存在,准备创建它!");  
              file.getParentFile().mkdirs(); 
            }  
            out=new FileOutputStream(file);
            byte[] b=data.getBytes();//因为是字节流,所以要转化成字节数组进行输出
            out.write(b);
            /*也可以一个字节一个字节的写
             * byte[] b=data.getBytes();
             * for(int i=0;i<b.length;i++){
             *         out.write(b[i]);
             * }
             */
        }catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

读文件

package com.iobyte.test;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class ByteTest {
    public static void main(String[] args) {
        File file = new File("D:\java\myeclipse_workspace\iotest\src\file\a.txt");
        InputStream in=null;
        try {
            in=new FileInputStream(file);
            byte[] b=new byte[(int)file.length()];
            in.read(b);
            System.out.println(new String(b));
            /*一个字节一个字节的读
             * byte[] b=new byte[(int)file.length()];
             * for(int i=0;i<b.length;i++){
             *          b[i]=(byte) in.read();
             * }
             * System.out.println(new String(b));
             */
        }catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}     

字符流

写文件

package com.iochar.test;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class CharTest {
    public static void main(String[] args) {
        String data = "我们分担寒潮、风雷、霹雳;我们共享雾霭、流岚、虹霓。";
        File file = new File("D:\java\myeclipse_workspace\iotest\src\file\a.txt");
        Writer out = null;
        try {
            //如果目标文件所在的目录不存在,则创建目录
            //如果文件不存在,则创建文件
            if(!file.getParentFile().exists()) {  
              System.out.println("目标文件所在目录不存在,准备创建它!");  
              file.getParentFile().mkdirs(); 
            }  
            out=new FileWriter(file);//追加  FileWriter(f,true) 字节流也一样
            out.write(data);
        }catch (Exception e) {
            e.printStackTrace();
        }finally{
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

读文件

package com.iochar.test;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

public class CharTest {
    public static void main(String[] args) {
        File file = new File("D:\java\myeclipse_workspace\iotest\src\file\a.txt");
        Reader input = null;
         Java提供的基础类存储在try {
            //如果目标文件所在的目录不存在,则创建目录
            //如果文件不存在,则创建文件
            if(!file.getParentFile().exists()) {  
              System.out.println("目标文件所在目录不存在,准备创建它!");  
              file.getParentFile().mkdirs(); 
            }  
            input=new FileReader(file);
            char[] buf=new char[20];
            int end=0;
            while((end=input.read(buf))!=-1){
                System.out.print(new String(buf,0,end));
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally{
            try {
                input.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

7:面向对象编程的特性

面向对象编程语言的优点:

代码开发模块化,更易维护和修改

代码复用

增强代码的可理解性

7.1:封装

封装即把对象的属性及操作结合成为一个独立的整体,并尽可能的隐藏对象内部实现细节

public class User {
    private int id;
    private String name;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

这是一个典型的封装案例,user类的属性id和name都是用private隐藏起来了的,只能通过get和set方法来对属性进行操作。

使用封装的优点:

通过隐藏对象的属性来保护对象内部的状态

提高了代码的可用性和可维护性,因为对象的行为可以被单独的改变或者扩展

禁止对象之间的不良交互,提高了模块化

7.2:抽象

使用了关键词abstract声明的类叫作“抽象类”。如果一个类里包含了一个或多个抽象方法,类就必须指定成abstract(抽象)。“抽象方法”,属于一种不完整的方法,只含有一个声明,没有方法主体。

public abstract class AbstractTest {
    public abstract void get();
    public String set(){
        return "这是一个抽象类";
    }
}

需要注意的是:抽象类与普通类的唯一区别就是不能创建实例对象和允许有abstract方法

7.3:继承

继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力

public class TestExtends { 
  public static void main(String argv[]){ 
    B b = new B();
    b.get();
    //测试结果:正在执行子类…… 这是父类
  } 
}
class A{
  public String toString(){
    return "这是父类";
  }
}
class B extends A{
  public void get(){
    System.out.print("正在执行子类…… "+toString());
  }
}

7.4:多态

多态可以分成编译多态(主要体现在重载上面)和运行多态(主要体现在继承上面)

8:重载和重写的区别

重载是同一个类里面有相同名称不同参数列表的方法

重写是子类里面创建一个除了方法体其余的(方法名,参数列表,返回值)都和父类一样的方法,覆盖父类方法

需要注意的是一个重写的方法不能抛除没有在基类中定义的非运行时异常,但是它可以抛出没有在基类中定义的运行时异常

原因:1:就算有运行时异常也是可以通过编译的,而如果出现非运行时异常就必须进行处理(throws或者try{}catch{})

   2:子类重写父类方法所抛出的异常不能超过父类的范畴

9:接口和抽象类的区别

9.1:抽象类里面的方法可以有具体实现,但接口里面所有的方法都是抽象方法。

9.2:抽象类可以有构造器,接口不能有构造器

9.3:抽象方法可以有public、protected和default这些修饰符,但是接口只能使用public修饰符

9.4:接口中声明的变量默认都是final的,抽象类可以含非final变量

9.5:抽象方法可以继承一个类和实现多个接口,接口只可以继承一个或多个其它接口

10:java存储数据

10.1:寄存器:最快的存储区,位于处理器内部,寄存器数量非常有限。所以它根据需求进行分配,不能直接控制。

10.2:堆栈:位于通用RAM(随机访问存储器)中,堆栈指针向下移动,则分配新的内存,若向上移动,则释放内存。速度仅次于寄存器。需要注意的是:对象引用存储于堆栈。

10.3:堆:一种通用内存池(也位于RAM区)存放所有的java对象。与堆栈相比的优点:编译器不需要知道存储的数据在堆里存活多长时间,因此,在堆里分配存储有很大的灵活性,new创建的对象都在堆里面。

10.4:常量存储:常量通常直接存放在程序代码内部,这样做是为了安全,因为他们永不改变。有时候在嵌入系统中,常量本身会与其他部分隔开,所以在这种情况下可以将其存放在ROM(只读存储器)中。

10.5:非RAM存储:如果数据完全存活于程序之外,那么它不受程序的任何控制,在程序没有运行的时候也可以存放。这种存储方式的技巧在于:把对象转化成可以存放在其他没媒介上的事物,在需要的时候,可以恢复成常规的基于RAM的对象。java提供了对轻量级持久化的支持,而诸如JDBC和Hibernate这样的机制提供了更加复杂的对在数据库中存储和读取对象信息的支持。

11:关系操作符

值得注意的是“==”和“!=”比较的是对象的引用。如果比较两个对象的实际内容是否相同,必须使用所有对象都适用的特殊方法equals,但是这个方法不适用于“基本类型”,“基本类型”直接使用“==”和“!=”就可以了。

public class equalsTest{
  public static void main(String[] args) {
    String s1 = "123";
    String s2 = "123";
    String s3 = new String("123");
    String s4 = new String("123");
    System.out.println("s1=s2?"+(s1==s2));//true
    System.out.println("s1=s3?"+(s1==s3));//false
    System.out.println("s3=s4?"+(s3==s4));//false
    System.out.println("s1=s2?"+(s1.equals(s2)));//true
    System.out.println("s1=s3?"+(s1.equals(s3)));//true
    System.out.println("s3=s4?"+(s3.equals(s4)));//true
  }
}

12:goto、break、continue

尽管goto是java的保留字,但语言中并没有使用它(goto的标签让程序产生的错误越来越多,并且标签和goto使程序难以分析)java没有goto然而java也能完成一些类似于跳转的操作,这与continue和break有关

12.1:一般的break会中断并跳出当前程序

public class bcTest{
  public static void main(String[] args) {
    for(int i=0;i<3;i++){
      for(int j=0;j<3;j++){
        System.out.println("i="+i+" j="+j);
        if(j==1)
          break;
      }
    }
  }
}

结果

i=0 j=0
i=0 j=1
i=1 j=0
i=1 j=1
i=2 j=0
i=2 j=1

12.2:带标签的break会中断并跳出标签所指循环

public class bcTest{
  public static void main(String[] args) {
    BreakTest:
    for(int i=0;i<3;i++){
      for(int j=0;j<3;j++){
        System.out.println("i="+i+" j="+j);
        if(j==1)
          break BreakTest;
      }
    }
  }
}

结果

i=0 j=0
i=0 j=1

12.3:一般的continue会退回最内层循环的开头,并继续执行

public class bcTest{
  public static void main(String[] args) {
    for(int i=0;i<3;i++){
      for(int j=0;j<3;j++){
        System.out.println("i="+i+" j="+j);
        if(j==1)
          continue;
      }
    }
  }
}

结果

i=0 j=0
i=0 j=1
i=0 j=2
i=1 j=0
i=1 j=1
i=1 j=2
i=2 j=0
i=2 j=1
i=2 j=2

12.4:带标签的continue会到达标签所指的位置,并重新进入紧接在哪个标签后面的循环

public class bcTest{
  public static void main(String[] args) {
    ContinueTest:
    for(int i=0;i<3;i++){
      for(int j=0;j<3;j++){
        System.out.println("i="+i+" j="+j);
        if(j==1)
          continue ContinueTest;
      }
    }
  }
}

结果

i=0 j=0
i=0 j=1
i=1 j=0
i=1 j=1
i=2 j=0
i=2 j=1

java的标签不会造成goto的问题,是因为他们的应用场合已经受到了限制,没有特别的方式用于改变程序的控制

13:构造器

在java中,通过提供构造函数,类的设计者可确保每个对象都会得到初始值,创建对象的时候,如果其类具有构造器,java就会在用户有能力操作对象之前自动调用响应的构造器,从而保证了初始化的进行。

如果你写的类里面没有构造器,则编译器会自动帮你创建一个默认构造器。

什么是构造器:

构造器就是和类名相同但无返回类型的方法
ps:构造器能不能被重写

  构造器不能被继承,因此不能重写,但可以被重载

14:清理:终结处理和垃圾回收

java有垃圾回收器自动的不定期的回收无用对象占据的内存资源(垃圾回收的目的:识别并丢弃应用不再使用的对象来释放和重用资源)。但也有特殊情况,假定你的对象获取了一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由new分配的内存,所以它不知道该如何释放该对象的这块“特殊”内存,为了应对这种情况。java允许在类里面定义一个名为finalize()的方法。

finalize()方法的工作原理:

一但垃圾回收器准备好释放对象占用的存储空间,将首次调用其finalize()方法,并且在下一次垃圾回收动作发生的时候才会真正回收对象占用的内存。所以要是你打算使用finalize()就能在垃圾回收时做一些重要的清理工作。需要注意的是Java里面的对象并不总是被垃圾回收。

使用垃圾回收器的唯一原因是程序不再使用内存,所以对于与垃圾回收有关的任何行为来说(尤其是finalize()方法)。他们必须同内存及其回收有关。

System.gc()和Runtime.gc()用来提示垃圾回收。但是什么时候回收取决于虚拟机。

15:数组

编译器不允许指定数组的大小。现在拥有的只是对数组的一个引用,而且也没有给数组对象本身分配任何空间。为了给数组创建相应的存储空间,必须写初始化表达式。

int [] i1 = new int[5] (数组元素中的基本类型会自动初始化为控制,对于数字和字符来说是0,对于布尔型来说是false)

int [] i2 = {1,2,3,4,5}

16:java访问权限修饰词

17:什么是java虚拟机

Java虚拟机是一个可以执行java字节码的虚拟机进程。

18:为什么java称作是“平台无关的编程语言”

Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。

19:JDK和jre的区别

JDK是Java开发工具包,包含了jre。Jre是Java运行时环境。

20:“static”关键字是什么意思

当声明一个事物是static时,意味着这个域或方法不会与包含他的那个类的任何对象实例关联在一起,所以,即使没有创建某个类的任何对象也可以调用其static方法或者访问其static域

ps:final

用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的

ps:静态变量和实例变量的区别

1:静态变量前要加static关键字,而实例变量前则不加。

2:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。

21:Java能否覆盖一个private或者static的方法

Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法编译时是静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。

Private的作用域是当前类。所以Java中的类不可以继承父类当中由private修饰的方法,所以也就不存在覆盖这样的说法。

22:创建线程的方法

方法一:继承Thread类

class ThreadTest extends Thread{
    public void run() {}
}

方法二:实现Runnable接口

class ThreadTest implements Runnable{
    public void run() {}
}

一般使用方法二。因为java类是单继承的

23:线程执行的几种可用状态

24:同步方法和同步代码块有什么区别

同步方法和同步代码块本质上没有区别,如果硬要说区别的话,就是同步方法持有的锁匙是this,即本类对象,同步作用的范围是整个方法,所以效率更低。同步代码块可以由synchronize自定义一把锁,作用范围是被synchronize修饰的包围代码块的大括号。

25:什么是死锁

两个进程都在等待对方执行完毕才能继续向下执行的时候就会发生死锁。结果就是两个程序都陷入了无限的等待中。

26:如何确保N个线程可以访问N个资源同时又不至于死锁

指定获取所的顺序,并强制线程按照指定的顺序获取锁,这样所有的线程都以同样的顺序加锁和释放锁,就不会死锁了。

27:什么是迭代器

迭代器,有时又称“游标”,是程序设计的软件设计模式,可以在容器物件上遍访接口,而设计人员无需关心容器物件的内容

28:Iterator和ListIterator的区别

4.1:Iterator可以遍历set和list集合,但是ListIterator只能遍历List集合

4.2:Iterator对集合只能向前遍历,ListIterator又可以向前遍历又可以向后遍历

4.3:ListIterator实现了Iterator接口,并包含了其他功能,比如增加元素,替换元素等

29:java中Exception和Error的区别

Exception和Error都是Throwable的子类

Exception是程序自身可以处理的异常,可以捕获并且回复。遇到这类的异常应该尽可能的处理异常,使程序恢复运行,而不应该随意终止异常

Error:程序无法处理的与虚拟机相关的异常。对于这类错误导致的应用程序中断,紧靠程序本身是无法恢复的,建议让程序终止。

30:throw和throws的区别

throw用在方法里面,用于抛出一个异常

throws用在方法的声明上,用来声明该方法可能抛出的异常

31:连接数据库

import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Statement;

public class SQLTest {  
    public static void main(String[] args) {  
            //驱动程序名
            String driver="com.mysql.jdbc.Driver";
            //url指向要访问的数据库名
            String url="jdbc:mysql://localhost:3307/test";
            //MySQL配置时的用户名和密码
            String username="root";
            String password="root";
            
            //声明connection对象
            Connection conn=null;
            //声明Statement对象
            Statement statm=null;
            //声明ResultSet对象
            ResultSet rs=null;
            
            //连接数据库
            try {
                Class.forName(driver);
                conn = DriverManager.getConnection(url,username,password);
            } catch (Exception e) {
                e.printStackTrace();
            }
            
            //sql更新语句
            try {
                String sql = "Update user set user_name='jack' where user_id=4";
                statm = conn.createStatement();
                statm.execute(sql);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            
            //sql查询语句
            try {
                String sql = "select * from user";
                statm = conn.createStatement();
                rs=statm.executeQuery(sql);
                while(rs!=null&rs.next()){
                       System.out.println("id:"+rs.getInt(1)+" name:"+rs.getString(2)+" age:"+rs.getInt(3));
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
    }  
}  

使用PreparedStatement

import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Statement;

public class SQLTest {  
    public static void main(String[] args) {  
            //驱动程序名
            String driver="com.mysql.jdbc.Driver";
            //url指向要访问的数据库名
            String url="jdbc:mysql://localhost:3307/test";
            //MySQL配置时的用户名和密码
            String username="root";
            String password="root";
            
            //声明connection对象
            Connection conn=null;
            //创建PreparedStatement 对象
            PreparedStatement ps=null;    
            //声明ResultSet对象
            ResultSet rs=null;
            
            //连接数据库
            try {
                Class.forName(driver);
                conn = DriverManager.getConnection(url,username,password);
            } catch (Exception e) {
                e.printStackTrace();
            }
            
            //sql添加语句
            try {
                //User类里面有三个属性(id、name、age )
                User user = new User("张珊",15);//ID自增
                String sql= "insert into user (user_name,user_age) values(?,?)";
                ps=conn.prepareStatement(sql);
                ps.setString(1, user.getName()); 
                ps.setInt(2, user.getAge()); 
                ps.executeUpdate();
                //查看插入的值
                sql = "select * from user where user_name=?";
                ps=conn.prepareStatement(sql);
                ps.setString(1, "张珊"); 
                rs=ps.executeQuery();
                while(rs.next()){
                   User u= new User();
                   //在数据库里,字段的顺序是id、name、age
                   u.setId(rs.getInt(1));
                   u.setName(rs.getString(2));
                   u.setAge(rs.getInt(3));
                   System.out.println(u.toString());
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
    }  
}  

32:Class.forName(driver); 方法有什么作用?

用来载入跟数据库建立连接的驱动

33:驱动(Driver)在JDBC中的作用

JDBC驱动提供了特定厂商对JDBC API接口的实现,驱动必须要提供java.sql包下面的一些重要的类(Connection、Statement、PreparedStatement、ResultSet等)的实现。

34:JDBC是什么

JDBC是java DateBase Connectivity(java数据库连接)的简称。是一种用于执行SQL语句的java API。可以为多种关系数据库提供统一访问,它由一组用java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更加高级的工具和接口,使编程开发人员能够编写数据库应用程序。

35:Statement与PreparedStatement有什么区别

35.1:PreparedStatement是预编译的,对于批量处理可以大大提高效率,也叫JDBC存储过程

35.2:PreparedStatement对象的开销更大,对于一次性操作不会带来额外的好处

35.3:PreparedStatement对象更加的安全,可以防止SQL注入式攻击。传递给PreparedStatement对象的参数可以被强制进行类型转换,使开发人员可以确保在插入或者查询数据的时候与底层的数据格式匹配。

36:连接数据库的步骤

36.1:加载JDBC驱动程序

36.2:提供JDBC连接的URL

36.3:创建数据库的连接

36.4:创建一个Statement或者PreparedStatement

36.5:执行SQL语句

36.6:处理结果

36.7:关闭JDBC连接

37:什么是数据库连接池

每一次与数据库的连接都需要连接数据库,所以非常费时,尤其是客户端数量增加的时候,这会消耗大量的资源,成本非常高。数据库连接池就是在初始化的时候创建一定数量的数据库连接放到连接池里面,这些数据库连接的数量由最小数据库连接数制约,,无论是否被使用连接池都至少拥有这么多的连接数量。连接池的最大数据连接数量限制了这个连接池能占有的最大连接数,当用程序向连接池请求的连接数量超过最大连接数量的时候,这些请求将被加入到等待队列中。

38:什么是Servlet

Servlet是用来处理客户端请求并产生动态网页内容的java类。Servlet主要是用来处理或是存储HTML表单提交的数据,产生动态内容,在无状态的HTTP协议下管理状态信息。

39:说一下Servlet的体系结构

所有的Servlet都必须要实现的核心的接口是javax.servlet.Servlet。每一个Servlet都必须要直接或者间接实现这个接口,或者继承javax.servlet.GenericServlet或者javax.servlet.http.HTTPServlet。最后,servlet使用多线程可以并行的为多个请求服务。

40:解释一下Servlet的生命周期

40.1:Servlet通过调用init()方法进行初始化

40.2:Servlet调用service()方法来处理客户端的请求,service又根据具体的问题调用doPost或者doGet

40.3:Servlet通过destroy()方法来终止

40.4:最后,Servlet是由JVM的垃圾回收器进行垃圾回收

41:Servlet在什么时候调用destroy

当容器注销的时候会注销应用程序,然后应用程序就会依次执行里面的servlet的destroy方法

42:doGet和doPost的区别

GET方法会把“名值对”追加在请求的URL后面,因为URL对字符数目的限制,进而限制了用在客户端请求的参数值的数目。并且请求中的参数可见,不安全。

POST方法通过把请求参数值放在请求体里面来克服GET方法的限制,因此,可以发送的参数的数目没有限制并且参数对外部客户端不可见,更安全。

43:创建一个jsp-servlet项目

43.1:创建一个web project并导入相应的包

43.2:创建servlet:com.gx.servlet.ServletTest

package com.gx.servlet;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ServletTest extends HttpServlet{
    public void service(HttpServletRequest request,HttpServletResponse response){
        //治疗中文乱码
        try {
                request.setCharacterEncoding("UTF-8");
        } catch (UnsupportedEncodingException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
        }
        String name = request.getParameter("username");
        System.out.println("name="+name);
        try {
            String mess = "这是来自servlet的数据";
            request.setAttribute("mess", mess);
            request.getRequestDispatcher("/index.jsp").forward(request, response);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

43.3:修改web.xml文件内容

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <servlet>
      <servlet-name>ServletTest</servlet-name>
      <servlet-class>com.gx.servlet.ServletTest</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>ServletTest</servlet-name>
      <url-pattern>/test</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

43.4:修改inde.jsp的内容

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="https://www.cnblogs.com/minuobaci/p/<%=basePath%>">
    
    <title>测试servlet-jsp</title>
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">    
    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    <meta http-equiv="description" content="This is my page">
    <!--
    <link rel="stylesheet" type="text/css" href="https://www.cnblogs.com/minuobaci/p/styles.css">
    -->
  </head>
  
  <body>
    <form action="test" method="post">
        <input type="text" name="username" value="${mess}"/>
        <input type="submit" value="提交" />
    </form>
  </body>
</html>

43.5:启动tomcat,并在浏览器输入网址

43.6:控制台的结果

43.7:页面的结果

44:什么是jsp

JSP是java Servlet Pages(java服务器页面)的简称,其根本是一个简化的Servlet设计,在第一次编译的时候会被编译成Servlet,驻留内存中以供调用,它是在传统的网页HTML(后缀为*.jsp)文件中插入java程序段和JSP标记(tag),从而形成JSP文件(后缀为*.jsp)。JSP和Servlet一样,都只是在服务器端执行,通常返回客户端的是一个HTML文本,因此客户端只要有浏览器就能浏览。JSP是一种动态页面技术,它将页面逻辑与网页设计显示分离,支持可重用的基于组件的设计。它的主要目的是将表示逻辑从servlet里面分离出来。

45:jsp与servlet的区别

45.1:JSP是运行在服务器端的脚本语言,Servlet是服务器端运行的小程序,我们访问JSP页面的时候,服务器会将JSP页面转化成Servlet小程序运行,得到结果后反馈给用户端的浏览器。

45.2:Servlet更多的是处理业务,而JSP更多的是进行页面显示

46:JSP的九个内置对象

46.1:request对象

代表客户端请求的信息,主要用于接受通过HTTP协议传送到服务器的数据

46.2:response对象

代表客户端的响应,主要是将JSP容器处理过的对象传回客户端

46.3:Session对象

客户端与服务器的一次会话,由服务器自动创建的与客户请求相关的对象,用于保存用户的信息,跟踪用户端操作,内部用Map类来保存数据。生命从客户端连到服务器的webApplication开始,至到客户端与服务器断开连接为止

46.4:Application对象

Application对象可以将信息保存在服务器,至到服务器关闭,与Session想比,生命周期更长。

46.5:out对象

用于在web浏览器内输出信息。

46.6:pageContext对象

取得任何范围的参数,通过它可以获取JSP页面的out、request、response、session、application等对象。

46.7:config对象

主要用于获取服务器的配置信息,通过pageContext对象的getServletConfig()方法可以获取一个config对象

46.8:Page对象

即JSP本身

46.9:Exception对象

显示异常信息

47:JSP的六个动作

47.1:include:当JSP页面被请求的时候包含一个文件

47.2:forward:把请求转发到新的页面

47.3:useBean:创建一个javabean

47.4:getProperty:获取JavaBean的属性

47.5:setProperty:设置JavaBean的属性

47.6:plugin:产生特定的浏览器代码

48:forward和redirect的区别

48.1:forward是服务器请求资源,服务器直接请求对应的URL并获取响应内容,然后将这些内容发给浏览器,所以地址栏还是原来的地址。redirect是服务器端根据逻辑发送一个状态码,让浏览器自己去请求地址,所以地址栏显示新URL

48.2:forward转发页面和转发目的页面可以共享request里面的数据。redirect不能共享数据

48.3:forward效率更高(因为redirect中间还要让服务器返回状态码,浏览器再次请求,所以效率低)。​​

48.4:forward一般用于登录之后进入之后的模块。redirect一般用于注销之后跳转回主页面或者其他网站。

49:浏览器和servlet通信使用的是什么协议

浏览器和servlet通信使用的是HTTP协议

50:什么是HTTP隧道

HTTP隧道是一种利用HTTP或者HTTPS把多种网络协议封装起来进行通信的技术。因此,HTTP协议扮演了一个打通用于通信的网络协议的管道的包装器的角色。把协议的请求掩盖成HTTP的请求就是HTTP隧道

51:http响应的结构

响应由状态码、http头部和主体三部分组成

51.1:状态码:描述了响应的状态。可以用来检查是否成功完成了请求。请求失败的情况下,状态码可以用来找出失败的原因。如果Servlet没有返回状态码,默认会返回成功的状态码。

51.2:http头部:他们包含了更多关于响应的信息,比如指定响应过期的过期日期或者用来给用户安全传输实体内容的编码格式

51.3:主体:由传输在HTTP消息中紧跟在头部后面的数据字节组成,包含了响应的内容。可以包含HTML代码,图片等。

52:什么是cookie?

cookie是web服务器发送给浏览器的一块信息。浏览器会在本地文件中给每一个web服务器存储cookie。以后浏览器在给特定的web服务器发送请求的时候,同时会发送所有为该服务器存储的cookie。

53:application、session和cookie有什么区别?

session:HttpSession类型的对象,存在于服务器端,描述一个客户端与服务器之间的一次通话时段,该时段(最后一次响应之后的30分钟如果没有交流就会失效)内包含客户端的若干次请求和服务器的响应相应,整个时间段session对象都存在,常用来实现购物车之类的存储当前用户数据。不同用户有各自的不同session对象,session能够存储任意的java对象

cookie:Cookie 是在 HTTP 协议下,服务器或脚本维护客户工作站上信息的一种方式。以文件的形式保存在客户端-->本地硬盘(可被阻止)的请求信息。通常情况下,当用户结束浏览器会话的时候,系统将终止所有的cookie。当web服务器创建cookie后,只要在有效的时间内,当用户访问同一个web服务器的时候,浏览器首先检查本地的cookie,并将其原样发送给web服务器。cookie只能存储String类型的对象,cookie可以存放到本地也可以存放到内存

application:application对象时ServletContext类型的对象,描述的是本身web程序,该对象在web程序部署到tomcat服务器时由容器产生,其生命周期至web程序从tomcat服务器卸载出去的时候消失。是所有客户端能共享的一个全局对象,整个系统只有一份,用于保存所有用户的公共数据信息。当网站访问量大的时候会产生严重性能瓶颈,因此最好不要用此对象保存大的数据集合

54:单例模式

1:懒汉式(线程不安全)

public class Singleton{
    private static Singleton instance;
    private Singleton(){}
    public static Singleton getInstance(){
          if(instance==null){
               instance = new Singleton();
          }      
          return instance;
    }
}

2:懒汉式(线程安全)

public class Singleton{
    private static Singleton instance;
    private Singleton(){}
    public static Synchronized Singleton getInstance(){
          if(instance==null){
               instance = new Singleton();
          }      
          return instance;
    }
}

3:饿汉式

public class Singleton{
    private static Singleton instance = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
          return instance;     
    }
}

55:观察者模式

观察者模式介绍原网址:http://www.jianshu.com/p/d55ee6e83d66

观察者模式面向的需求是:A对象(观察者)对B对象(被观察者)的某种变化高度敏感,需要在B变化的一瞬间做出反应。举个例子,新闻里喜闻乐见的警察抓小偷,警察需要在小偷伸手作案的时候实施抓捕。在这个例子里,警察是观察者、小偷是被观察者,警察需要时刻盯着小偷的一举一动,才能保证不会错过任何瞬间。程序里的观察者和这种真正的【观察】略有不同,观察者不需要时刻盯着被观察者(例如A不需要每隔1ms就检查一次B的状态),二是采用注册(_Register_)或者成为订阅(_Subscribe_)的方式告诉被观察者:我需要你的某某状态,你要在它变化时通知我。采取这样被动的观察方式,既省去了反复检索状态的资源消耗,也能够得到最高的反馈速度。

56:观察者模式的优缺点

观察者模式的优缺点原网址:http://blog.csdn.net/u011327981/article/details/53096963

观察者模式的效果有以下几个优点:

1.观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。

2.观察者模式支持广播通信。被观察者会向所有的登记过的观察者发出通知。

观察者模式有下面的一些缺点:

1.如果一个被观察者对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

2.如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察考模式时要特别注意这一点。

3.虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。

56:switch语句能否作用在byte上,能否作用在long上,能否作用在String上

1:switch可作用于char byte short int
2:switch可作用于char byte short int对应的包装类
3:switch不可作用于long double float boolean,包括他们的包装类
4:switch中可以是字符串类型,String(jdk1.7之后才可以作用在string上)
5:switch中可以是枚举类型

57:Math类中提供的三个与取整有关的方法

ceil的英文意义是天花板,该方法就表示向上取整,所以,Math.ceil(11.3)的结果为12,Math.ceil(-11.3)的结果是-11

floor的英文意义是地板,该方法就表示向下取整,所以,Math.floor(11.6)的结果为11,Math.floor(-11.6)的结果是-12

round表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整,所以,Math.round(11.5)的结果为12,Math.round(-11.5)的结果为-11。

版权声明


相关文章:

  • java的50道基础算法题2024-10-19 13:34:04
  • vscode如何查看java基础类2024-10-19 13:34:04
  • 锻炼Java基础的游戏2024-10-19 13:34:04
  • java基础入门大纲2024-10-19 13:34:04
  • 计算机基础与java2024-10-19 13:34:04
  • 北京java零基础学习2024-10-19 13:34:04
  • java基础语言笑脸2024-10-19 13:34:04
  • 有一定java编程基础2024-10-19 13:34:04
  • java基础的周报2024-10-19 13:34:04
  • 尚学堂的java基础书2024-10-19 13:34:04