Java_常见问题(1——35)


1. String 和 StringBuilder、StringBuffer的区别


(1)运行速度

String速度最慢,举个例子如下图,虽然第二次输出abcde,但是并非str这个对象改变了,JVM的操作是,第二次进行+这个操作的时候,新建了一个str对象,将原来的abc和新的de拼接起来赋予了新的str,原来的str被GC回收掉了。因此Java对String的操作就是不断新建string类型对象的过程,所以速度很慢。

而StringBuilder和StringBuffer的对象都是变量,可以直接进行对象的修改,没有其他操作,所以速度快。

happysneaker.com


(toString 方法会返回一个“以文本方式表示”此对象的字符串)

happysneaker.com


(2)线程安全

StringBuffer是线程安全的,StringBuilder是线程不安全的。

因为如果一个StringBuffer的对象再字符串缓冲区被多个线程使用的时候,很多方法可以带有synchronized关键字,所以可以保证线城市安全的,但是StringBuilder没有该关键字,所以不安全。

但是在单线程的情况下,还是建议使用速度更快的StringBuilder。

happysneaker.com


2.synchronized与lock的区别

    首先synchronized使用示例:

   示例一: 多窗口售票

Ticket.java

public class Ticket implements Runnable{
    private int num; //票的总数
    private boolean flag = true; //true为有票
    public Ticket(int num){ // 票类构造方法
        this.num=num;
    }
    
    @override
    public void run(){
        while(flag){
            ticket();
        }
    }
    
    private void ticket(){
        if(num<=0){
            flag = false;
            return;
        }
        try{
            Thread.sleep(20); //模拟延时
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        
        System.out.println("Thread.currentThread().getName()+"售出票序列号:"+ num--);
    }
}

MainTest.java

public class MainTest{
    public static void main(String[] args){
        Ticket ticket = new Ticket(5);
        Thread window01 = new Thread(ticket,"窗口01");
        Thread window02 = new Thread(ticket,"窗口02");
        Thread window03 = new Thread(ticket,"窗口03");
        window01.start();
        window02.start();
        window03.start();
    }
}

程序运行结果如下,是错误的:

happysneaker.com

如果是单个窗口售票就不会出现这种问题,多线程的情况下会出现多个窗口争抢共享资源,出现紊乱。

两种解决方法:在ticket()方法前增加一个synchronized关键字,或者把ticket()方法体放入synchronized块。

happysneaker.com

结果是正确的


从这里可以看出在实例方法上面加上synchronized关键字的实现效果跟对整个方法体加上synchronized效果是一样的。 另外一点需要注意加锁的时机也非常重要 ,本示例中ticket()方法中有两处操作容易出现紊乱,一个是在if语句模块,一处是在num–,这两处操作本身都不是原子类型的操作,但是在使用运行的时候需要这两处当成一个整体操作,所以synchronized将整个方法体都包裹在了一起。如若不然,假设num当前值是1,但是窗口01执行到了num–,整个操作还没执行完成,只进行了赋值运算还没进行自减运算,但是窗口02已经进入到了if语句模块,此时num还是等于1,等到窗口02执行到了输出语句的时候,窗口01的num–也已经将自减运算执行完成,这时候窗口02就会输出序列号0的票。再者如果将synchronized关键字加在了run方法上面,这时候的操作不会出现紊乱或者错误,但是这种加锁方式无异于单窗口操作,当窗口01拿到锁进入run()方法之后,必须等到flag为false才会将语句执行完成跳出循环,这时候的num就已经为0了,也就是说票已经被售卖完了,这种方式摒弃了多线程操作,违背了最初的设计原则-多窗口售票。


示例二:懒汉式单例模式

那么根据上面多窗口售票的逻辑我们在getInstance()方法上面加上一个synchronized关键字,给该方法加上锁,加上锁之后可以避免多线程模式下生成多个不同对象,但是同样会带来一个效率问题,因为不管哪个线性进入getInstance()方法都会先获得锁,然后再次释放锁,这是一个方面,另一个方面就是只有在第一次调用getInstance()方法的时候,也就是在if语句块内才会出现多线程并发问题,而我们却索性将整个方法都上锁了。讨论到这里就引出了另外一个问题,究竟是synchronized方法好还是synchronized代码块好呢? 有一个原则就是锁的范围越小越好 ,加锁的目的就是将锁进去的代码作为原子性操作,因为非原子操作都不是线程安全的,因此synchronized代码块应该是在开发过程中优先考虑使用的加锁方式。


Lock:



3.关于Java的this关键字

https://www.cnblogs.com/livterjava/p/4709383.html


4.getName()和Thread.currentThread().getName()的区别:

https://blog.csdn.net/qq_33728573/article/details/60540549


5.为什么我们调用start()方法时会执行run()方法?为什么我们不能直接调用run()方法?他们之间有什么区别?



为什么我们调用start()方法时会执行run()方法?

因为类Thread中的start方法中,调用了Thread中的run方法。顺便说下,类A继承了Tread类,在A中写run方法,就会覆盖掉Thread中的run方法,所以此时调用start方法后,实现的是自己的run方法体里面的代码。

为什么我们不能直接调用run()方法?

如果我们直接调用子线程的run()方法,其方法还是运行在主线程中,代码在程序中是顺序执行的,所以不会有解决耗时操作的问题。所以不能直接调用线程的run()方法,只有子线程开始了,才会有异步的效果。当thread.start()方法执行了以后,子线程才会执行run()方法,这样的效果和在主线程中直接调用run()方法的效果是截然不同的。

start( )与run( )之间有什么区别?

run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用; 
start()方法:启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程;


6.Java创建现成的两种方法

 i 通过扩展Thread类来创建线程

ii 通过实现Runnable接口的run()方法来创建线程


i

 MultiThreadDemo.java

public class MultiThreadDemo{
	public static void main(String[] args){
		MultiThread m1 = new MultiThread("Window 1");
		MultiThread m2 = new MultiThread("Window 2");
		MultiThread m3 = new MultiThread("Window 3");
		m1.start();
		m2.start();
		m3.start();
	}
}

MultiThread.java

public class MultiThread extends Thread{
	private int ticket = 100; //每个线程有 100 票
	
	public MultiThread(){}
	public MultiThread(String name){
		super(name);
	}
	
	@Override
	public void run(){
		while(ticket>0){
			System.out.println(ticket-- + "is saled by " + Thread.currentThread().getName());
		}
	}
}

运行结果

happysneaker.com

可以看出三个线程同时执行,并无优先级,同时占用同一资源,互相独立,互不干扰。


ii

MultiThreadDemo.java

public class MultiThreadDemo{
	public static void main(String[] args){
		MultiThread m1 = new MultiThread("Window 1");
		MultiThread m2 = new MultiThread("Window 2");
		MultiThread m3 = new MultiThread("Window 3");
		Thread t1 = new Thread(m1);
		Thread t2 = new Thread(m2);
		Thread t3 = new Thread(m3);
		t1.start();
		t2.start();
		t3.start();
	}
}

MultiThread.java

public class MultiThread implements Runnable{
	private int ticket = 100; //每个线程有 100 票
	private String name;
	
	MultiThread(String name){
		this.name = name;
	}
	
	@Override
	public void run(){
		while(ticket>0){
			System.out.println(ticket-- + "is saled by " + name);
		}
	}
}

运行结果同上。也是线程之间互相独立,互不干扰。也是每个线程都拥有100票。


iii 只能通过实现Runnable接口的方式来实现线程之间的资源共享,因为Java的类是但继承的,而Thread类是一个类啊,哈哈哈哈哈哈。

MultiThreadDemo.java

 public class MultiThreadDemo{
	public static void main(String[] args){
		MultiThread m = new MultiThread();// 仅创建了一个资源

		Thread t1 = new Thread(m);
		Thread t2 = new Thread(m);
		Thread t3 = new Thread(m);
		t1.start();
		t2.start();
		t3.start();
	}
}


MultiThread.java

public class MultiThread implements Runnable{
	private int ticket = 100; //每个线程有 100 票
	
	@Override
	public void run(){
		while(ticket>0){
			System.out.println(ticket-- + "is saled by " + Thread.currentThread());
		}
	}
}

这个就实现了资源共享。


实现Runnable接口相对于扩展Thread类来说,具有无可比拟的优势。这种方式不仅有利于程序的健壮性,使代码能够被多个线程共享,而且代码和数据资源相对独立,从而特别适合多个具有相同代码的线程去处理同一资源的情况。这样一来,线程、代码和数据资源三者有效分离,很好地体现了面向对象程序设计的思想。因此,几乎所有的多线程程序都是通过实现Runnable接口的方式来完成的。



7.abc循环打印十次


  1. package com.multithread.wait;

  2. public class MyThreadPrinter2 implements Runnable {


  3. private String name;

  4. private Object prev;

  5. private Object self;


  6. private MyThreadPrinter2(String name, Object prev, Object self) {

  7. this.name = name;

  8. this.prev = prev;

  9. this.self = self;

  10. }


  11. @Override  

  12. public void run() {

  13. int count = 10;

  14. while (count > 0) {

  15. synchronized (prev) {

  16. synchronized (self) {

  17. System.out.print(name);

  18. count--;


  19. self.notify();

  20. }

  21. try {

  22. prev.wait();

  23. catch (InterruptedException e) {

  24. e.printStackTrace();

  25. }

  26. }


  27. }

  28. }


  29. public static void main(String[] args) throws Exception {

  30. Object a = new Object();

  31. Object b = new Object();

  32. Object c = new Object();

  33. MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);

  34. MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);

  35. MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);



  36. new Thread(pa).start();

  37. Thread.sleep(100);  //确保按顺序A、B、C执行

  38. new Thread(pb).start();

  39. Thread.sleep(100);

  40. new Thread(pc).start();

  41. Thread.sleep(100);

  42. }

  43. }


  先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是prev,就是前一个线程所持有的对象锁。还有一个就是自身对象锁。主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行的顺序。


wait和sleep区别

共同点: 
1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。 
2. wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。 
   如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。 
   需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。 
不同点: 
1. Thread类的方法:sleep(),yield()等 
   Object的方法:wait()和notify()等 
2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。 
   sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。 
3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 
所以sleep()和wait()方法的最大区别是:
    sleep()睡眠时,保持对象锁,仍然占有该锁;
    而wait()睡眠时,释放对象锁。
  但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException(但不建议使用该方法)。

sleep()方法
sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
   sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。
  在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。 
wait()方法
wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;
  wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。

  wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。


8、主线程:JVM调用程序main()所产生的进程,名字就是main;

        后台线程:为其他线程提供服务的线程,也叫守护线程,比如GC(JAVA的垃圾回收机制);

        

9.关于Java定时器Timer

schedule(TimerTask task, longdelay)只执行一次,schedule(TimerTask task, long delay, longperiod)才是重复的执行。

Timer是一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。Timer实际上是个线程,定时调度所拥有的TimerTasks。 

happysneaker.com

happysneaker.com

如下一个简单的定时器:

import java.util.Timer;
import java.util.TimerTask;
import java.util.Date;

public class MultiThread{
	

	public static void main(String[] args){
		Timer timer = new Timer();
		Task task = new Task();
		timer.schedule(task, new Date(),1000);
	}
}
	
	class Task extends TimerTask{
		@Override
		public void run(){
			System.out.println("dododo...");
		}
	}
//运行结果是从当前时间开始,每隔一秒输出dododo...

分析Timer和TimerTask的源码,原来Timer是一个线程,TimerTask是一个Runnable实现类,那么只要提交TimerTask对象就可以运行任务了。

(要想进行运行线程的任务:需要把对象提交给线程,然后运行方法存在于对象的类中)


更详细的关于Java计时器Timer的在:https://www.cnblogs.com/luckygxf/p/7078662.html


最后总结就是:Timer定时任务原理基本理解,单线程 + 最小堆 + 不断轮询


10.Mysql事务管理

https://my.oschina.net/huangyong/blog/160012

另外:一个事务是一个连续的一组数据库操作。



11.Ioc控制反转

在采用面向对象方法设计的软件系统中,底层实现都是由N个对象组成的,所有对象通过彼此的合作,最终实现系统的业务逻辑。即软件系统中对象之间的耦合,这样的话,对象与对象之间有着复杂的联系。为了解耦,所以出现了IOC。


IoC 这个概念。简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。IoC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦,如下图所示:

happysneaker.com

即把各个对象类封装之后,通过IoC容器来关联这些对象类。这样对象与对象之间就通过IoC容器进行联系,但对象与对象之间并没有什么直接联系。

happysneaker.com

happysneaker.com

IOC(控制反转)的别名:依赖注入(DI)

所谓依赖注入,就是由IoC容器在运行期间,动态地将某种依赖关系注入到对象之中。

IOC的原理

控制反转是Spring框架的核心,其原理是基于面向对象OO设计原则的The Hollywood Principle:Don't call  us, we will call you.(别找我,我会来找你)。也就是说,所有的组建都是被动的,所有的组件初始化和调用都是由容器负责。组建处在一个容器中,由容器负责管理。简单来讲,就是由容器控制程序之间的关系,而非传统实现中,由程序代码直接操控,即在一个类中调用另外一个类。这就是控制反转的概念所在:控制权由代码转到了外部容器,控制权的转移,即所谓反转。


另外Spring的两大主要思想的另一个AOP:面向切面编程:

https://www.cnblogs.com/Wolfmanlq/p/6036019.html


12.Spring工作流程

先上工作流程图:

happysneaker.com


★然后是工作流程,分为8步:

1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获;

      2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;

      3. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(...)方法

       4.  提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

      HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息

      数据转换:对请求消息进行数据转换。如String转换成Integer、Double等

      数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等

      数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中

      5.  Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;

      6.  根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;

      7. ViewResolver 结合Model和View,来渲染视图(modelAndView中包含了视图逻辑名和模型数据信息),DispatcherServlet通过ViewResolver完成逻辑视图名到真实视图对象的解析工作。

      8. 将渲染结果返回给客户端(当得到真实对象的view,DispatcherServlet就使用这个view对象对modelAndView中的模型数据进行视图渲染,然后将渲染后的结果返回给客户端)。


(从宏观角度来讲:DispatcherServlet是整个web应用的控制器。从微观来看,controller是单个HTTP请求的控制器,,而modelAndView是HTTP请求过程中返回的模型和视图。)


13.

happysneaker.com


 Spring为什么要结合使用HandlerMapping以及HandlerAdapter来处理Handler?

    符合面向对象中的单一职责原则,代码架构清晰,便于维护,最重要的是代码可复用性高。如HandlerAdapter可能会被用于处理多种Handler。




14.HandlerMapping

处理器映射。

有什么用呢:通过处理器映射,你可以将web请求映射到正确的处理器handler上(handlermapping返回的就是一个handler)。

在基于springmvc的web应用程序中,我们可以为前端控制器DispatcherServlet提供多个handlermapping供其使用,DispatcherServlet将会选用一个优先级最高的handlermapping,若handlermapping返回的是一个可用的handler,那么DispatcherServlet将使用当前返回的handler进行web请求的处理,而不再询问其他的handlermapping,否则DispatcherServlet 将不断轮询其他的handlermapping,直到获得一个可用的handler。

happysneaker.com


那么什么又是DispatcherServlet呢,是SpringMVC提供的前置控制器,所有的请求都经过他来统一分发,在DispatcherServlet将请求发送给SpringController之前,需要借Handlermapping来定位到具体的Controller。


总之Handlermapping的作用一句话:

根据当前请求找到对应的handler,并将handler(执行程序)与一堆handlerInterceptor(拦截器)封装到HandlerExecutionChain对象中。


15.HandlerAdapter

HanlderAdapter:根据handler来找到支持他的HandlerAdapter,通过HandlerAdapter执行这个handler得到modelAndView对象。

springmvc通过handlerAdapter的handler方法来调用请求处理函数。

在DispatcherServlet根据URL利用handlermapping找到对应的handler之后,首先检查当前的IOC容器中所有的可用的HanderAdapter,再利用HandlerAdapter中的supports方法找到可以使用的HandlerAdapter,handlerAdapter是用来执行之前所得到的handler的,然后执行之后就是向DispatcherServlet返回一个modelAndView对象。




PS.拦截器、过滤器:https://zhidao.baidu.com/question/583953960492530045.html



16.Java垃圾回收管理机制(Garbage Collection)

垃圾回收机制的因入可以有效地防止内存的泄露、保证内存的有效使用,也大大解放了程序员的双手,使得我们在编写程序时候,不需要再去考虑内存管理。

内存泄漏:内存泄漏指的是一个内存空间在使用完毕之后未回收,在Java中的一般情况下,是指一个内存对象的生命周期超过了程序需要他的时间长度。


☆然鹅——有了GC也可能出现内存泄漏问题,分为如下三种情况:

i. 静态集合类HashMap、Vector等的使用最容易出现内存泄漏,这些静态变量的生命周期和应用程序一致,所有的Object对象也不能被释放,因为他们也一直被Vector等应用着。

ii. 各种连接,比如数据库连接、网络连接、IO连接等没有显式调用close关闭,不被GC回收导致内存泄漏。

iii. 监听器的使用,在释放对象的同时没有相应删除监听器。


17.关于Java对象引用与对象的区别:

https://blog.csdn.net/qq_26805137/article/details/52945688


18.Ajax

Ajax就是 Asynchronous Javascript And Xml,

Ajax是一种不用刷新网页就能更新页面部分内容的一种动态网页制作技术,

通过在后台与服务器进行部分数据交换,实现网页的异步更新。


19.CDN相当于在终端和服务器之间加了个缓存。


20.

happysneaker.com

另外:当我们使用Ctrl+F5组合键刷新一个页面时,首先是在浏览器端,会直接向目标URL发送请求,而不会使用浏览器缓存的数据;其次即使请求发送到服务端,也有可能访问到的是缓存的数据。所以在HTTP的请求头中会增加一些请求头,它告诉服务端我们要获取最新的数据而非缓存。

happysneaker.com


happysneaker.com



21.java反射机制

Java反射机制就是在程序运行过程中,对于任意一个类,都能够知道这个类的所有属性和方法;

对于任意一个对象,都能够调用它的任意一个方法和属性;

这种动态的获取信息以及动态的调用方法的功能成为Java语言的反射机制。


22.instanceof 关键字

用来判断关键字左边的对象是否为右边类的实例,返回一个Boolean值:

system.out.println(a instanceof b);


23.Java 接口代码示例

interface USB{
	public void install();
	public void work();
}

class Computer{
	public void plugin(USB usb){
		usb.install();
		usb.work();
	}
	
}

class Phone implements USB{
	public void install(){
		System.out.println("手机驱动安装中。");
	}
	public void work(){
		System.out.println("手机开始工作咯。");
	}
}

class Printer implements USB{
	public void install(){
		System.out.println("打印机驱动安装中。");
	}
	public void work(){
		System.out.print("打印问紧啊啦。");
	}
}

class MP3 implements USB{
	public void install(){System.out.println("MP3驱动");};
	public void work(){System.out.println("mp3工作了。");};
	
}

public class MultiThread{
	public static void main(String[] args){
		Computer c = new Computer();
		c.plugin(new Phone());
		c.plugin(new Printer());
		c.plugin(new MP3());
	}

}

happysneaker.com


24.Java static的四种用法:

https://www.cnblogs.com/dotgua/p/6354151.html?utm_source=itdadao&utm_medium=referral



25.Java 的system.out.println();直接输入一个对象是几个意思:

Java中直接输出一个类的对象的时候,会调用这个类的toString()方法,自己写的类没有覆盖这个方法的话就是继承Object类的这个方法,Object中toString()方法的实现是这样的:

       getClass().getName() + "@" + Integer.toHexString(hashCode())。

  后面跟的是这个类的哈希码值,如果希望这个类打印出你希望的格式,你就要覆盖toString方法。

比如下面的代码:

public class Person {
    private static int count = 0;
    int id;
    String name;
    int age;
    
    public Person() {
        id = ++count;
    }
    
    public String toString() {    //就是这个方法
        return "Id:" + id + ", Name:" + name + ", Age:" + age;
    }
    
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.name = "zhangsan";
        p1.age = 10;
        Person p2 = new Person();
        p2.name = "lisi";
        p2.age = 12;
        System.out.println(p1);
        System.out.println(p2);
    }

    /**Output
     * Id:1, Name:zhangsan, Age:10
     * Id:2, Name:lisi, Age:12
     *///~
}



PS.要知道Java抽象类是不能被实例化的,下面的写法叫做匿名内部类,不会报错,但不是实例化了父类,而是实例化了一个无名子类:

抽象类:

public abstract class TestAbs{
    public void printMessage(String msg){
        printMessage2(msg);
    };
    public abstract void printMessage2(String msg);
}


main方法中使用:

TestAbs a = new TestAbs(){
    @Override
    public void printMessage2(String msg){
        System.out.println(msg);
    }
};

a.printMessage("aaa");


可以正常输出。


25.eclipse中获取程序执行所需要的空间:

  1. package test;

  2. public class Main {

  3. public static void main(String[] args) {

  4. long start = System.currentTimeMillis();

  5. //要测试的程序或方法

  6. long end = System.currentTimeMillis();

  7. System.out.println("程序运行时间:"+(end-start)+"ms");

  8. }

  9. }


26.如何把一段逗号分隔的字符串转换成一个数组?

正则表达式:

Java中的 split  函数是用于按指定字符(串)或正则去分割某个字符串,结果以字符串数组形式返回;

例如:

  1. String str="1234@abc";

  2. String[] a = str.split("@");

  3. System.out.println("处理结果: "+a[0]+","+a[1]);   //输出的是: 处理结果: 1234,abc


27.注意输出时候length()函数的使用方式,数组无括号,String有括号:

String x = "sdfasdfasdfdas";

String[] y = x;

System.out.println(x.length());

System.out.println(y.length);


28.

happysneaker.com


29.打印个杨辉三角:

(1)笛卡尔组合数:

// 编写程序,输入n,分别以直角三角形方式和等腰三角形模式输出n阶杨辉三角。(用二维数组实现)  

#include<stdio.h>
//我的版本 
int c(int m,int n);
main()
{
    int i,j,n;
    scanf("%d",&n);
    for(i=0;i<=n;i++)
    {
        for(j=0;j<=i;j++)
            printf("%5d",c(i,j));
        printf("\n");
    }
        system("pause");7
}
int c(int m,int n)
{
    int i,j,p=1,s=1;
    for(i=m,j=0;j<n;j++,i--)
        p*=i;
    for(i=n,j=0;j<n;j++,i--)
        s*=i;
    return p/s;

}


(2)二维数组方法(学习此类输出的格式语法)

// 直角三角形

#define M 20
#define N 20
#include<stdio.h>
main()
{
    int i,j,n;
    int a[M][N];
    scanf("%d",&n);
    // 初始化数组
    for(i=0;i<n;i++){
        for(j=0;j<=i;j++)
        {
            if(i==j||j==0)
            {
                a[i][j]=1;
            }
            else
            {
                a[i][j]=a[i-1][j]+a[i-1][j-1];
            }
        }
    }
    
    
    // 输出数组
    for(i=0;i<n;i++)
    {
        for(j=0;j<=i;j++)
        {
            printf("%5d",a[i][j]);
        }
        printf("\n");
    }
    
    system("pause");    
}


//等腰三角形/////////////////////////////////////////////////////////////////////////////

#define M 20
#define N 20
#include<stdio.h>
main()
{
    int i,j,n;
    int a[M][N];
    scanf("%d",&n);
    for(i=0;i<n;i++){
        for(j=0;j<=i;j++)
        {
            if(i==j||j==0) // 第一列的第一个元素永远是1,或者坐标i==j时的元素也是1
            {
                a[i][j]=1;
            }
            else
            {
                a[i][j]=a[i-1][j]+a[i-1][j-1]; // 其他元素就是上一层相邻俩元素之和,比如a[4][1]就是上一级的a[3][0]和a[3][1]的和
            }
        }
    }

    for(i=0;i<n;i++)
    {
		for(j=0;j<n-i;j++)
        {printf(" ");}
        for(j=0;j<=i;j++)
        {
            printf("%d ",a[i][j]);
        }
        printf("\n");
    }
system("pause");    
    
}


30.


31.C语言fgetc、fputc来复制文件:

// while(!feof(in)) fputc(fgetc(in),out) 如果in文件可以继续读取数据. 
// 那么 fputc(fgetc(in),out)  
// 读取in文件的一个字符然后文件指针in后移一个char长度, 
// 然后输出到out文件中out文件指针后移一个char长度.
    
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    FILE *in,*out;	
    char str[50]; // 存放字符串数组
  
	// 用户输入字符串内容
    printf("enter strings:\n");
    gets(str);
 
    if((out=fopen("d:\\被复制的文件的位置","w"))==NULL)
    {
        printf("cannot open file!\n");
        exit(0);
    }
 
	/*将字符串存到文件中*/
    fputs(str,out);
    fclose(out);
 
    printf("contents have been saved in file! \n\n");
 
	// 直接在下面的fopen中写路径
    if((out=fopen("d:\\被复制的文件的位置","r"))==NULL)
    {
        printf("cannot open the outputfile!\n");
        exit(0);
    }
    if((in=fopen("d:\\复制后的文件的位置","w"))==NULL)
    {
        printf("cannot open the inputfile!\n");
        exit(0);
    }
 
    while(!feof(out))/*一个字符一个字符的从第一个文件中读到第二个文件中*/
        fputc(fgetc(out),in);
 
    fclose(out);
    fclose(in);
    return 0;
}


32.



Unity那些事儿
请先登录后发表评论
  • 最新评论
  • 总共0条评论