java中常见面试题整理

1.Java有几种数据类型?分别是什么?
 Java有8种数据类型:

   字符类型:char(16位)

   布尔类型:boolean(true/false)

   数值类型:

       整数类型:byte(8位),short(16位),int(32位),long(64位)

      浮点数类型:float(32位,直接赋值必须在数字后加f/F),double(64位赋值在数字 后加d/D/不加)
  简单数据类型对应的封装类:
    boolean    byte    char             short    int           long    float     double   void
    Boolean    Byte    Character   Short    Integer   Long   Flolat   Double   Void
  Java有5中引用类型(对象类型):
    类,接口,数组,枚举,标注
   Jvm的内存空间
   堆空间:分配对象:new Student()
   栈空间:临时变量:Student stu
   代码区:类的定义,静态资源 Student.class

2.String、StringBuilder、StringBuffer的区别?和各自应用场景?
   String str1=”hello”;

   String str2=”hello”;

   String str3=new String(“hello”);

   String str4=new String(“hello”);

   str1=str2?    true

   str2=str3?    false

   str3=str4?    false

   str4.equals(str1)?  True   //equals方法默认比较的是内存地址,String类重写了equals方法,比较内容是否一致

   String str=“hello”这种方式创建字符串的时候,jvm会首先检查字符串常量池中是否存在该字符串对象,如果已存在,那么就不会在字符串常量池中再创建了,直接返回给该字符串     在字符串常量池中内存地址。如果该字符串还不存在该常量池中。那么就会在字符串常量池中先创建该字符串的对象,然后返回。 new String(“hello”)这种方式创建字符串对象的

   时候,首先会检查字符串常量池中是否存在该字符串对象,如果已存在,那么就不会在字符串常量池中再创建了,如果还没存在,那么就会在字符串常量池中创建“hello”字符串对     象,然后还会到对内存中再创建一份字符串的对象,把字符串常量池中的“hello”字符串拷贝到内存中的字符串对象,然后返回堆内存中字符串对象的内存地址

   笔试题:new String(“abc”);创建了几个对象?答:两个对象,一个对象位于字符串常量池中,一个位于堆内存中

         使用字节数组或字符数组都可以构建一个字符串对象如

         byte[] buf={97,98,99};

         str= new String(buf); //输出abc char[] arr={‘明’,‘天’};

         str= new String(arr); //输出明天

        字符串特点:字符串值是常量,它的值在创建后不能修改,字符串的内容一旦发生改变,那么马上会创建一个新的对象。

       注意:字符串的内容不适宜频繁修改,因为一旦修改马上创建一个新的对象,如果需要频繁修改字符串的内容,建议使用字符串缓冲类(StringBuffer) StringBuffer实际上是一         个存储字符的容器,StringBuffer底层是依赖了一个字符数组才能存储数据的,该字符数组默认的初始容量长度是16,如果初始容量的长度不够,则自动增加1倍

   StringBuffer与StringBuilder的相同点与不同点:

   相同点:

   1. 两个类都是字符串缓冲类

   2. 两个类的方法都是一致的

   不同点:

   1. StringBuffer是线程安全的,操作效率低;StringBuilder是线程非安全的,操作效率 高

   2. StringBuffer是jdk1.0出现的,StringBuilder是jdk1.5出现的 推荐使用StringBuilder,因为操作效率高

3.Object类下有哪些方法?

    Object clone() 创建并返回此对象的一个副本

    boolean equals(Object obj)指示其他某个对象是否与此对象相等

    protected void finalize()垃圾回收器确定不存在对该对象的更多引用时。有对象的垃圾回收器调用此方法

    Class<?> getClass()返回此Object的运行时类

    int hashCode()返回该对象的哈希吗值

    void notify()唤醒在此对象监视器上等待的单个线程

    void notifyAll()唤醒在此对象监视器上等待的所有线程

    String toString()返回该对象的字符串表示

    void wait()在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待

    void wait(long timeout)在其他线程调用此对象的notify()或notifyAll()方法,或者 超过制定的时间前,导致当前线程等待

    void wait(long timeout,int nanos)在其他线程调用此对象的notify()或notifyAll() 方法,或者其他某个线程中断当前线程,后者已超出某个实际时间量前,导致当前线程等待。

   补充问题:wait和sleep的区别是什么?
   1)wait和notify在线程问题中实组合使用的
   2)wait方法执行前会释放锁,而sleep不会

4.面向对象的特点有哪些?重写和重载的区别是什么?
 面向对象的特地点:继承、封装、多态
 函数的重写:在继承中,子类可以定义和父类相同名称且参数列表一致的函数,将这种函数称之为函数的重写。当子类调用该函数,一定是调用重写后的函数,可以通过super关键字进行父类的重写函数的调用

   注意:

  1.    函数名必须相同
  2.    参数列表必须相同
  3.    子类重写父类的函数的时候,函数的访问权限必须大于父类的函数的访问权限
  4.    子类重写父类的函数的时候,返回值的类型必须是父类函数类型或返回值类型的子类。不能返回比父类更大的数据类型;


   函数的重载:

   在同一个类中,有一个以上的同名函数,只要函数的参数列表或参数类型不一样即可,与返回值无关。函数重载存在的原因:为了增强方法的阅读性,优化了程序设计。

5.接口和抽象类有什么区别?
    抽象类的特点:

  1.  有抽象函数的类一定是抽象类
  2.   抽象类中不一定有抽象函数
  3.  抽象类不能创建对象
  4.  抽象类主要为了提高代码的复用性,让子类继承去使用
  5.  编译器强制子类实现抽象类父类的未实现的方法(可以不实现,前提是子类也要声明为抽象的)
  6.  抽象类一定有构造函数。主要是为了初始化抽象类中的属性,通常由子类实现
  7.  抽象类可以继承普通类和抽象类
  8.  final与abstract不能共存
  9.  static与abstract不能共存
  10.  private不能修饰抽象类


   接口的特点:

  1.    类实现接口可以通过implements实现,实现接口的时候必须把接口中的所有方法实现,一个类可以实现多个接口
  2.    接口定义的所有的属性默认是public static final的,即静态常量既然是常量,那么定义的时候必须赋值
  3.    接口中定义的方法不能有方法体,接口中定义的方法默认添加public abstract
  4.    有抽象函数的不一定是抽象类,也可以是接口类
  5.    由于接口中的方法默认是抽象的,所以不能实例化
  6.    对于接口而言,可以使用子类来实现接口中未被实现的功能函数
  7.    如果实现类中要访问接口中的成员,不能使用super关键字,因为两者之间没有显示的继承关系,况且接口中的成员属性是静态的,可以使用接口名直接访问
  8.    接口没有构造方法
  9.    一个类可以实现多个接口,只能继承一个类。一个接口可继承多个接口


6.ArrayList和LinkedList的实现原理是什么?各有什么优缺点?
   ArrayList底层采用数组实现,默认10,每次增长60%,查询块,增删慢
   LinkList底层采用链表实现,增删块,查询慢

---|Collection: 单列集合

      ---|List: 有存储顺序, 可重复

              ---|ArrayList:   数组实现, 查找快, 增删慢    由于是数组实现, 在增和删的时候会牵扯到数组增容, 以及拷贝元素. 所以慢。数组是可以直接按索引查找, 所以查找时较快                           

       ---|LinkedList:  链表实现, 增删快, 查找慢   由于链表实现, 增加时只要让前一个元素记住自己就可以, 删除时让前一个元素记住后一个元素, 后一个元素记住前一个元素. 这样的增删效率较高但查询时需要一个一个的遍历, 所以效率较低             

              ---|Vector:   和ArrayList原理相同, 但线程安全, 效率略低   和ArrayList实现方式相同, 但考虑了线程安全问题, 所以效率略

       ---|Set: 无存储顺序, 不可重复

              ---|HashSet

              ---|TreeSet

              ---|LinkedHashSet

---| Map: 键值对

       ---|HashMap

       ---|TreeMap

       ---|HashTable

       ---|LinkedHashMap

   什么时候该使用什么样的集合
          Collection 我们需要保存若干个对象的时候使用集合。
          List   如果我们需要保留存储顺序, 并且保留重复元素, 使用List.如果查询较多, 那么使用ArrayList如果存取较多, 那么使用LinkedList如果需要线程安全, 那么使用Vector
          Set  如果我们不需要保留存储顺序, 并且需要去掉重复元素, 使用Set.如果我们需要将元素排序, 那么使用TreeSet如果我们不需要排序, 使用HashSet, HashSet比TreeSet效率高.如果我们需要保留存储顺序, 又要过滤重复元素, 那么使用LinkedHashSet

7.HashMap 的数据结构,如何解决hash冲突问题?何时发生reHash操作?reHash过程是怎么样的?
HashMap的数据结构是数组+链表的形式,底层采用的是数组,数组中每一项采用的是链表。
若发生hash冲突问题,即在同一hash位置有一个以上元素,此时元素应以链表的方式依次往后排放,以此来解决hash冲突问题。

当数组中元素大于hashMap的极限容量(容量*负载因子,负载因子默认为0.75,初始容量默认为16),此时会发生rehash操作。
rehash操作的过程是开辟一个新的数组空间,然后将所有元素重新使用hash算法
解决hash冲突问题的方法是扩容,当数组中元素大于hashMap点的极限容量(容量*负载因子),负载因子默认为0.75,初始容量默认为16,此时会发生rehash操作。
rehash操作的方式是开辟一个新的数组空间,然后将所有元素进行重新使用hash算法算出位置后以新的排序方式放在新的数组中。

8.hashMap是线程安全的吗?hashMap和hashTable的区别是什么?

  1. hashMap是线程不安全的,而hashTable是线程安全的
  2. hashMap是不同步的,而hashTable是同步的
  3. hashMap的效率比hashTable高
  4. hashMap允许NULL键和NULL值,hashTable不允许
  5. 扩容方式不一样:hashMap:原始容量=16, 扩容后:2*原始容量
                  hashTable:原始容量=11, 扩容后:2*原始容量+1

9.ConcurrentHanshMap的特点是什么?它比hashTable效率高的原因是什么?
ConcurrentHanshMap的特点是:它的同步机制是基于lock操作的,保证同步的时候,锁住的不是整个对象,而是某一个segment;
hashtable是基于sychonized操作的,锁住的是整个对象,所以效率低。

10.volatile关键字的作用是什么?
volatile可以保证可见性和有序性。

volatile被设计用来修饰被不同线程访问和修改的变量。被volatile类型定义的变量,系统每次用到它都是直接从内存当中取,而不会利用缓存。在使用了volatile修饰成员变量后,所有线程在任何时候所看到的变量的值都是相同的。保证了可见性。但是volatile会阻止编译器对代码的优化,降低程序的执行效率,所以,除非迫不得已,尽量不使用volatile。

11.static关键字的作用是什么?
       static关键字的作用主要是为了实现对象之间重复属性的数据共享
       static的使用:
       主要用于修饰类的成员
          成员变量:①非静态成员变量:需要通过创建对象来访问
                    ②静态成员变量:使用类名直接调用,也可以通过对象访问
          成员方法:①静态函数中不能访问非静态成员变量,只能访问静态变量
                    ②静态方法不可以定义this,super关键字

          ③非静态函数:非静态函数中可以访问静态成员变量

12.final关键字的作用是什么
       final关键字主要用于修饰类、类成员、方法、以及方法的形参
  ①final修饰成员属性,说明该成员属性是常量,不能被修改。(常量名必须大写)
  ②基本数据类型,final使其值不变;对象引用:final使其引用恒定不变,无法让其
      指向一个新的对象,但是对象自身却可以被修改;该关键字一般和static组合使用
         (常量可优先加载,不必等到创建对象的时候再初始化);final和static可互换位
           置;常量一般final修饰
  ③final修饰类:该类是最终类,不能被继承
  ④final修饰方法:该方法是最终方法,不能被重写(如果一个类中的函数都被修饰为final时,可以将类定义为final)

13.synchronized关键字的作用是什么?synchronized有几种用法?
  synchronized是给同步代码块或同步函数加锁,有两种用法:
  方式1:同步代码块:
  Synchronized(“锁对象”){
    需要同步的代码块
  }
  方式2:同步函数:
  函数使用synchronized修饰

14.synchronized和lock的区别
  1)synchronized在成功完成功能或者抛出异常时,虚拟机会自动释放线程占有的锁; 而Lock对象在发生异常时,如果没有主动调用unLock()方法去释放锁,则锁对象会一直持有,因此使用Lock时需要在finally块中释放锁;

  2)lock接口锁可以通过多种方法来尝试获取锁包括立即返回是否成功的tryLock(),以及一直尝试获取的lock()方法和尝试等待指定时间长度获取的方法,比synchronized相对灵活了许多

  3)通过在读多,写少的高并发情况下,我们用ReentrantRead,WriteLock分别获取读锁和写锁来提高系统的性能,因为读锁是共享锁,即可以同时有多个线程读取共享资源,而写锁则保证了对共享资源的修改只能是单线程的。

       补充:
  关于线程:
  1.一个java应用程序至少有两个线程,一个是主线程,负责main方法代码的执行,一个是垃圾回收器线程,负责回收垃圾
  2.多线程的好处:①解决了一个进程能同时执行多个任务的问题②提高了资源利用率
  3.多线程弊端:①增加CPU的负担②降低了一个进程中线程的执行概率③引发了线程安全问题④出现了死锁现象
  4.创建线程的方式有2种
    方式一:①自定义一个类继承Thread类②重写Thread的run()方法,自定义线程就写在run()方法中③创建Thread的子类对象,并且调用start方法开启线程
    方式二:①自定义一个类实现Runable接口②实现Runable的run方法,把自定义线程的任务定义在run方法上③创建Runable实现类对象④创建Thread类的对象,并且把

        Runable实现类的对象作为实参传递⑤调用Thread类的start方法开启线程

    推荐使用第二种,原因:因为java是单继承多实现的
  5.出现线程安全问题的根本原因:①存在两个或两个以上线程对象,且线程之间共享了同一资源②有多个语句操作者同一资源
  6.线程安全问题的解决方式:java线程同步机制:
  方式1:同步代码块:
  Synchronized(“锁对象”){
    需要同步的代码块
  }
  同步代码块要注意的事项:
  ①任意一个对象都可以作为锁对象
  ②在同步代码块中调用了sleep方法不释放锁对象
  ③只有真正存在线程安全问题的时候才使用同步代码块,否则会降低相率
  ④多线程操作的锁对象必须是唯一共享的。否则无效
  方式二:同步函数:
  函数使用synchronized修饰
  注意:
  ①如果是一个非静态的同步函数的锁对象是this对象,如果是静态的同步函数的锁对象是当前函数所属类的字节码文件(class对象)
  ②同步函数的锁对象是固定的,不能由你来指定

  推荐使用同步代码块
  ①同步代码块的锁对象可以由我们随意指定,同步函数不行
  ②同步代码块可以很方便控制需要被同步代码的范围,同步函数必须是整个函数的代码块被同步了

15.死锁现象:
  死锁现象出现的根本原因:

  1. 出现两个或两个以上的线程
  2. 出现两个或两个以上的共享资源

死锁现象的解决方案:没有方案,只能尽量避免

16.wait与notify方法要注意的事项:

  1. wait方法与notify方法是属于Object对象的,因为任意对象都可作为锁对象
  2. wait方法与notify方法必须要在同步代码块或者是同步函数中才能使用
  3. wait方法与notify方法必须要由锁对象调用


17.线程的停止:

  1. 停止一个线程,我们一般都会通过一个变量去控制
  2. 如果需要停止一个等待状态下的线程,我们需要通过变量配合niotify()和interrupt()来使用

18.守护线程(后台线程):在一个进程中,如果只剩下守护线程,那么守护线程也会死亡。

  1. 一个线程默认都不是守护线程,使用d.setDaemon(true),设置是否为守护线程,true为是,false为非守护线程
  2. 一个线程如果执行join()语句,那么就有新的线程加入,执行该语句的线程必须要让步给新的线程完成任务,然后才能执行

19.MQ原理:
  消息队列,工作方式是不同步的

20.hashMap与ConcurrentHashMap的区别
  HashMap---hashTable
      首先hashMap的是线程不安全的,但是效率比较高,而hashTable是线程安全的,但效率有很低,若要在保证线程安全的前提下有较高的效率,可考虑使用

      concurrentHashMap。
  HashTable—ConCurrentHashMap
      concurrentHashMap和HashTable一样都是有加了锁操作的,都是线程安全的,然而它们的锁机制不一样,所以concurrentHashMap比HashTable的效率高。

           concurrentHashMap是基于lock机制的,有16把锁分别锁住concurrentHashMap的16的部分,而HashTable锁住的是整个HashTable对象。

21.Java注解的原理、作用是什么?

  原理:从java源码到class字节码是由编译器完成的,编译器会对java源码进行解析并生成class文件,而注解也是在编译时由编译器进行处理,编译器会对注解符号处理并附加到class结构中,根据jvm规范,class文件结构是严格有序的格式,唯一可以附加信息到class结构中的方式就是保存到class结构的attributes属性中。我们知道对于类、字段、方法,在class结构中都有自己特定的表结构,而且各自都有自己的属性,而对于注解,作用的范围也可以不同,可以作用在类上,也可以作用在字段或方法上,这时编译器会对应将注解信息存放到类、字段、方法自己的属性上。在我们的AnnotationTest类被编译后,在对应的AnnotationTest.class文件中会包含一个

RuntimeVisibleAnnotations属性,由于这个注解是作用在类上,所以此属性被添加到类的属性集上。即Test注解的键值对value=test会被记录起来。而当JVM加载

AnnotationTest.class文件字节码时,就会将RuntimeVisibleAnnotations属性值保存到AnnotationTest的Class对象中,于是就可以通过

AnnotationTest.class.getAnnotation(Test.class)获取到Test注解对象,进而再通过Test注解对象获取到Test里面的属性值。这里可能会有疑问,Test注解对象是什么?其实

注解被编译后的本质就是一个继承Annotation接口的接口,所以@Test其实就是“public interface Test extends Annotation”,当我们通过

AnnotationTest.class.getAnnotation(Test.class)调用时,JDK会通过动态代理生成一个实现了Test接口的对象,并把将RuntimeVisibleAnnotations属性值设置进此对象中,    此对象即为Test注解对象,通过它的value()方法就可以获取到注解值。Java注解实现机制的整个过程如上面所示,它的实现需要编译器和JVM一起配合

作用:·      

  1)生成文档。这是最常见的,也是java 最早提供的注解。常用的有@see @param @return 等· 

  2)跟踪代码依赖性,实现替代配置文件功能。比较常见的是spring 2.5 开始的基 于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配 置文件的数量。

  3)在编译时进行格式检查。如@Override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。

22.Java反射机制是什么?通过反射可以做什么?Class类下主要的方法有哪些
  类字节码文件是在硬盘上存储的,是一个个的.class文件。我们在new一个对象时,JVM会先把字节码文件的信息读出来放到内存中,第二次用时,就不用在加载了,而是直接使用之前缓存的这个字节码信息。字节码的信息包括:类名、声明的方法、声明的字段等信息。在Java中“万物皆对象”,这些信息当然也需要封装一个对象,这就是Class类、Method类、Field类。 通过Class类、Method类、Field类等等类可以得到这个类型的一些信息,甚至可以不用new关键字就创建一个实例,可以执行一个对象中的方法,设置或获取字段的值,这就是反射技术。
Class类的方法有:
1)获取class类的三种方式:
①Class.forName();
// 方式一
Class clazz1 = Class.forName("cn.itcast.gz.reflect.Person");
②类名.class
// 方式二
Class clazz2 = Person.class;
③对象名.getClass();
// 方式三
Person p1 = new Person();
Class clazz3 = p1.getClass();
2)通过class类获取类型的一些信息
Class clazz1 = Class.forName("cn.itcast.gz.reflect.Person");
①getName()类的名称(全名,全限定名)
// 获取类的名称
String name = clazz1.getName();
②getSimpleName()类的的简单名称(不带包名)
// 获取类的简单名称
System.out.println(clazz1.getSimpleName());
③getModifiers(); 类的的修饰符
// 获取类的修饰符
int modifiers = clazz1.getModifiers();
④创建对象
无参数构造创建对象
newInstance()
// 构建对象(默认调用无参数构造.)
Object ins = clazz1.newInstance();
Person p = (Person) ins;
⑤获取指定参数的构造器对象,并可以使用Constructor对象创建一个实例
Constructor<T> getConstructor(Class<?>... parameterTypes)
// 获取指定参数的构造函数
Constructor<?> con = clazz1.getConstructor(String.class, int.class);
// 使用Constructor创建对象.
Object p1 = con.newInstance("jack", 28);
System.out.println(((Person) p1).getName());
3)通过class类获取类型中方法的信息
Class clazz1 = Class.forName("cn.itcast.gz.reflect.Person");
①获取公共方法包括继承的父类的方法
getMethods()返回一个数组,元素类型是Method
// 1.获取非私用方法(包括父类继承的方法)
Method[] methods = clazz1.getMethods();
System.out.println(methods.length);
for (Method m : methods) {
// System.out.println(m.getName());
if ("eat".equals(m.getName())) {
m.invoke(clazz1.newInstance(), null);
②获取指定参数的公共方法
getMethod("setName", String.class);
③获得所有的方法,包括私有
Method[] getDeclaredMethods()
④获得指定参数的方法,包括私有
Method getDeclaredMethod(String name, Class<?>... parameterTypes)

  /**    * 获取公有方法.    * @throws Exception    * */  

private static void test3() throws Exception {

      Class clazz1 = Class.forName("cn.itcast.gz.reflect.Person"); 

     // 1.获取非私用方法(包括父类继承的方法)

      Method[] methods = clazz1.getMethods(); 

     System.out.println(methods.length); 

     for (Method m : methods) {

         // System.out.println(m.getName()); 

        if ("eat".equals(m.getName())) { 

           m.invoke(clazz1.newInstance(), null); 

        } 

     }

   }

/**    * 获取指定方法签名的方法    *    * @throws Exception    * */  

private static void test4() throws Exception {

      Class clazz1 = Class.forName("cn.itcast.gz.reflect.Person");

      // 获取指定名称的函数

      Method method1 = clazz1.getMethod("eat", null);

      method1.invoke(new Person(), null);   }

/**    * 获取指定方法名且有参数的方法    *    * @throws Exception    * */ 

  private static void test5() throws Exception { 

     Class clazz1 = Class.forName("cn.itcast.gz.reflect.Person"); 

     Method method = clazz1.getMethod("eat", String.class);

     method.invoke(new Person(), "包子");

   }

    /**    * 获取指定方法名,参数列表为空的方法.    *    * @throws Exception    * */ 

  private static void test4() throws Exception {

      Class clazz1 = Class.forName("cn.itcast.gz.reflect.Person");

      // 获取指定名称的函数

      Method method1 = clazz1.getMethod("eat", null); 

     method1.invoke(new Person(), null);   }


/**    * 反射静态方法    * @throws Exception    * */

   private static void test7() throws Exception {

      Class clazz1 = Class.forName("cn.itcast.gz.reflect.Person");

      Method method = clazz1.getMethod("play", null);

      method.invoke(null, null);

   } 

   /**    * 访问私有方法 暴力反射    * @throws Exception    * */

   private static void test6() throws Exception {

      Class clazz1 = Class.forName("cn.itcast.gz.reflect.Person");

      Method method = clazz1.getDeclaredMethod("movie", String.class);

      method.setAccessible(true);

     method.invoke(new Person(), "小明");

   } 

4)通过Class类获取类型中的字段的信息
①获取公共字段
Field[] getFields()
②获取指定参数的公共字段
Field getField(String name)
③获取所有的字段
Field[] getDeclaredFields()
④获取指定参数的字段,包括私用
Field getDeclaredField(String name)

23.MySQL增删改查的基本命令是什么?熟记

http://www.cnblogs.com/mercuryji/p/mysql_operate.html
24.SPring中@Autowried、@Resource、@Component、@Service、@Controller、@PostConstruct注解的含义、作用是什么?
@Component 指定把一个对象加入IOC容器
@Repository 作用同@Component; 在持久层使用
@Service 作用同@Component; 在业务逻辑层使用
@Controller 作用同@Component; 在控制层使用,用于指示Spring类的实例是一个控制器
@Resource 属性注入(多例时,要确保同一类型只有同一变量)是J2EE自带的注解,可以按name和type匹配,默认按类型匹配
@Autowried 属性注入,默认按名臣匹配
@postConstruct 被该注解修饰的方法会在服务器加载servlet的时候运行,并且只会被服务器调用一次,类似于servlet的init()方法。被@PostContruct修饰的方法会在构造函数只会,init()之前运行。

原文地址:https://www.cnblogs.com/mercuryji/p/java.html