【Java】第一阶段练习题

字符串

  1. == 判断引用类型的地址而不是值
  2. 当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量进行检查。
    如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回。否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用。
        String s1 = new String("abc");
		String s2 = "abc";
		System.out.println(s1 == s2);     	//false
		System.out.println(s1.equals(s2));  //true

		String s1 = "abc";
      	String s2 = "abc";
		System.out.println(s1 == s2);     	//true
		System.out.println(s1.equals(s2)); 	//true

		String s1 = "a" + "b" + "c";
      	String s2 = "abc";
		System.out.println(s1 == s2);    	 //true
		System.out.println(s1.equals(s2));	 //true
	
		String s1 = "ab";
     	String s2 = "abc";
     	String s3 = s1 + "c";
		System.out.println(s3 == s2);     	//false
      	System.out.println(s3.equals(s2));  //true

第三段s1和s2都是以字面量的形式创建,s1首先创建,s2创建的时候会在常量池中找是否相同内容的字符串对象,找到了s1,把s1的引用返回给s2。所以s1和s2的地址是相同的。

凡是String s=“a”+“b”;或者String s=“ab”;产生一个字符串常量在栈中。


第四段【s1+“c”】是调用stringBuffer操作并创建一个String对象也就是说+操作符使用StringBuffer的append方式实现的最后返回一个新创建的String对象,而不是string常量。String s3=s1+“c”;保存在堆中。

凡是字符串变量与字符串变量或者字符串常量之间使用+都会产生一个String对象到堆中。


String类的对象内容不可改变,所以每当进行字符串拼接时,总是会在内存中创建一个新的对象,所以经常改变内容的字符串最好不要用String,因为每次生成对象都会对系统性能产生影响。

StringBuilder又称为可变字符序列,是JDK5.0中新增加的一个类,它是一个类似于String的字符串缓冲区,通过某些方法调用可以改变该序列的长度和内容。即它是一个容器,容器中可以装很多字符串,并且能够对其中的字符串进行各种操作。它的内部拥有一个数组用来存放字符串内容,进行字符串拼接时,直接在数组中加入新内容,StringBuilder会自动维护数组的扩容。


日期

public static void main(String[] args) throws ParseException {
    
    
        //获取当前的日期,并把这个日期转换为指定格式的字符串,如2088-08-08 08:08:08。
        Date d1 = new Date();
        SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String formatDate1 = sdf1.format(d1);
        System.out.println(formatDate1);

        //使用SimpleDateFormat类,把2018-03-04转换为2018年03月04日。
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd");
        Date d2 = sdf2.parse("2018-03-04");
        SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy年MM月dd日");
        String formatDate2 = sdf3.format(d2);
        System.out.println(formatDate2);

        //用程序判断2018年2月14日是星期几
        Calendar c = Calendar.getInstance();
        c.set(2018,2,14);
        int week = c.get(Calendar.DAY_OF_WEEK);
        System.out.println(week);
        System.out.println(c.get(Calendar.YEAR)+"年"+c.get(Calendar.MONTH)+"月"+c.get(Calendar.DATE)+"日是星期"+(week-1));
    }

集合

集合框架

  1. 集合包括单列集合Collection接口,以及双列集合Map。Collection接口,它有两个子接口List和Set。
  2. List的特点是有序,可重复。List接口的实现类有ArrayList、LinkedList、Vetor。ArrayList的底层是数组,查询快,增删慢。LinkedList的底层是链表,查询慢,增删快。
  3. Set的特点是无序,不可重复。Set接口的实现类的HashSet(LinkedHashSet)、TreeSet。HashSet的底层是数组+链表+红黑树(超过阈值8时)。TreeSet的底层是二叉树。
  4. Map是双列集合接口,可以同时存放键值。键唯一,值可以不唯一,键值对唯一。她的实现类包括HashMap和LinkedHashMap

list转为数组,可以调用toArray方法

public static void main(String[] args) {
    
    
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        Object[] o = list.toArray();
        System.out.println(Arrays.toString(o));
    }

迭代器的实现原理
Iterator it = list.iterator();
首先调用iterator方法获得一个迭代器对象。然后用hasNext方法判断是否还有下一个元素,用Next方法获取下一个元素。
迭代器在遍历集合的时候,内部采用指针来跟踪集合中的元素。在调用Next之前,指针指在第一个元素之前,不指向任何元素。每调用一个Next,指针后移一位,并将当前指向元素返回。直到遇到hasNext返回false结果时停止移动指针。

//求返回s在al里面第一次出现的索引,如果s没出现过返回-1。
public static int listTest(ArrayList<Integer> al, Integer s){
    
    
        Iterator<Integer> it = al.iterator();
        int index = 0;
        while(it.hasNext()){
    
    
            Integer num = it.next();
            if(num.equals(s)){
    
    
                return index;
            }
            index++;
        }
        return -1;
    }

Comparable和Comparator两个接口的区别

Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。

Comparator强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。


异常

异常的继承体系

  1. 异常的继承体系:java.lang.Throwable --> java.lang.Error & java.lang.Exception Exception又分为编译时期异常:checked异常,与运行时期异常:runtime异常。
  2. Error:表示不可修复的恶性的错误,只能通过修改代码规避错误的产生,通常是系统级别的,所以很严重。
  3. Exception:表示可修复的良性(相对于错误)的异常,异常产生后程序员可以并且应该通过代码的方式纠正,使程序继续运行,是必须要处理的。
  4. 运行时期异常:runtime异常。在运行时期,检查异常。在编译时期,运行异常编译器不会检测(不报错)。

throw与throws的区别

  1. throw写在方法体内,throw一个异常对象。执行到throw处即停止。
  2. throws写在方法声明处,用来指定可能抛出的异常。多个异常可以使用逗号隔开。当在主函数中调用该方法时,如果发生异常,就会将异常对象抛给方法调用处。

异常处理方式

  1. try-catch-finally:自己定义异常的处理逻辑,处理之后的程序可以继续运行。try是可能发生异常的部分,catch写明异常的处理逻辑,finally代码块是无论是否发生异常,都必须执行的代码,用于释放资源。

  2. throws:在方法声明处写上异常类,抛出给调用者进行处理,最后由JVM进行中断处理。

常见异常和出现原因

  • 空指针异常:当应用试图在要求使用对象的地方使用了null时,抛出该异常。
  • 数组越界异常:当对数组的索引值为负数或大于等于数组大小时抛出此异常。
  • 算数运算异常:除零操作
  • 数字格式异常:当试图将一个String转换为指定的数字类型,而该字符串并不满足数字类型要求的格式时,抛出该异常。

并发和并行的区别
并行:指两个或多个事件在同一时刻发生(同时发生)。
并发:指两个或多个事件在同一个时间段内发生。(不同时

进程和线程的区别
进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。

线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

一个程序运行后至少有一个进程,一个进程中可以包含多个线程,但一个进程中至少包含一个线程。比如使用迅雷软件下载网络文件时,同时下载多个文件,就使用到了多线程下载。

自定义异常

public class NoScoreExcpetion extends Exception {
    
    
    public NoScoreExcpetion() {
    
    
    }

    public NoScoreExcpetion(String message) {
    
    
        super(message);
    }
}
public class Student {
    
    
    private String name;
    private int id;
    private int score;
......
//其他方法省略
......
    public void setScore(int score) throws NoScoreExcpetion {
    
    
        if(score <= 0){
    
    
            throw new NoScoreExcpetion("成绩必须大0");
        }
        this.score = score;
    }
}

public class Test {
    
    
    public static void main(String[] args) throws NoScoreExcpetion {
    
    
        Student s1 = new Student();
        s1.setName("张三");
        s1.setId(1);
        s1.setScore(-100);
        System.out.println(s1);
    }
}

多线程
创建多线程对象,开启多线程。
在子线程中输出1-100之间的偶数,主线程输出1-100之间的奇数。

public class MyThread extends Thread {
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            if (i % 2 == 0) {
    
    
                System.out.println(i);
            }
        }
    }
}
public class Test{
    
    
    public static void main(String[] args) {
    
    
        /*创建多线程对象,开启多线程。在子线程中输出1-100之间的偶数,主线程输出1-100之间的奇数。*/
        MyThread t = new MyThread();
        t.start();
        for (int i = 0; i < 100; i++) {
    
    
            if(i%2 != 0){
    
    
                System.out.println(i);
            }
        }
    }
}

start()方法与run()方法的区别
start开启线程,让jvm调用run()方法在开启的线程中执行。
run不开启线程,只是对象调用方法

创建线程的两种方式
继承Thread:定义线程子类,继承Tread,重写run方法。创建线程子类对象,调用start方法开启线程。

public class MyThread extends Thread {
    
    
    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName());
    }
}
public class Test{
    
    
    public static void main(String[] args) {
    
    
        MyThread t = new MyThread();
        t.start();
        System.out.println(Thread.currentThread().getName());
    }
}

实现Runnable:实现Runnable类,并重写run方法。创建Runnable实现类对象,传入Thread对象的含参构造方法中,通过Thread对象调用start方法开启线程。

public class Test{
    
    
    public static void main(String[] args) {
    
    
        Runnable r = new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println(Thread.currentThread().getName());
            }
        };
        Thread t = new Thread(r);
        t.start();
        System.out.println(Thread.currentThread().getName());
    }
}

实现Runnable接口的优势

  • 接口可以多实现,而类只能单继承
  • 适合多个相同的程序代码的线程去共享同一个资源。
  • 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数据独立。
  • 线程池只能放入实现Runnable或callable类线程,不能直接放入继承Thread的类。

线程状态

  • new:新建状态
  • blocked:阻塞状态。当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
  • time-waiting:休眠状态。一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
  • waiting:无限等待 。同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
  • runnable:运行状态
  • terminate:终止状态

线程池:是一个容纳多个线程的容器,里面的线程可以反复拿出来使用,无需反复创建线程对象,而过多消耗系统资源。

线程池的优点

  • 降低资源消耗:减少创建和销毁线程的次数,可以反复利用线程。
  • 提高线程的可管理性:可以根据系统的承受能力,调整线程池中工作线程数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
  • 提高响应速度:不需要创建线程就可以执行任务。

重写和重载
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!

重载是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。

下面的代码编译会报错,因为没有重写run方法,run里有参数,不是重写!

public class Test07 implements Runnable {
    
    
	public static void main(String[] args) {
    
    
		Thread t = new Thread(new Test07());
		t.start();
	 }

	public void run(int num) {
    
    
		for (int i = 0; i < num; i++) {
    
    
	   		System.out.println(i);
	 	}
	}
}

Lambda

省略规则

  • ()内的参数类型可以省略
  • ()内如果只有一个参数,()可省略
  • {}内如果只有一个语句可以省略{}、return和分号

格式

  • ():参数列表
  • ->:指向动作
  • {}:方法体括号

使用前提

  • 必须有接口,且抽象方法唯一。
  • 必须可推断,方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
Runnable a = ()->{
    
    
            for(int i = start; i <= end; i++){
    
    
                System.out.println(i);
            }
        };

无参无返回值Lambda练习
给定一个导演 Director接口,内含唯一的抽象方法makeMovie,且无参数、无返回值,使用lambda表达式在Test中完成调用。

public interface Director {
    
    
    public abstract void makeMovie();
}
public class Test {
    
    
    public static void main(String[] args) {
    
    
        Director d = ()-> System.out.println("我在拍电影");
        invoke(d);
    }
    public static void invoke(Director d){
    
    
        d.makeMovie();
    }
}

有参有返回值Lambda练习
给定一个计算器 Calculator 接口,内含抽象方法 calc (减法),其功能是可以将两个数字进行相减,并返回差值。使用Lambda表达式在Test中完成调用。

public interface Calculator {
    
    
    public abstract int calc(int num1, int num2);
}
public class Test {
    
    
    public static void main(String[] args) {
    
    
        Calculator c = (num1, num2)-> num1-num2;
        int result = invoke(c, 5, 1);
        System.out.println(result);
    }
    public static int invoke(Calculator c, int num1, int num2){
    
    
        return num1-num2;
    }
}

猜你喜欢

转载自blog.csdn.net/xd963625627/article/details/105342915