21基本Java面试问题 *

寻找 自由职业者Java开发者工作?将您的生活方式设计为顶尖的Java开发人员。

提交面试问题

描述并比较失败和失败安全的迭代器。举例说明。

主要区别 失败失败安全 迭代器是可以修改集合的吗? 尽管 它正在被迭代。故障安全迭代器允许这个;失败快速迭代器没有。

  • 失败 迭代器直接在集合本身上运行。在迭代期间,一旦他们意识到已经修改了该集合,失败快速迭代器会失败(即,在意识到成员已添加,修改或删除)并且会抛出一个 ConcurrentModificationException。一些例子包括 ArrayList, HashSet, 和 HashMap (大多数JDK1.4集合被实施为FAIL-FAIL)。

  • 失败安全 迭代操作 克隆副本 收集,因此做 不是 如果在迭代期间修改集合,则抛出异常。例子将包括返回的迭代器 ConcurrentHashMap 或者 CopyOnWriteArrayList.

ArrayList, LinkedList, 和 Vector are all implementations of the List interface. Which of them is most efficient for adding and removing elements from the list? Explain your answer, including any other alternatives you may be aware of.

三, LinkedList 一般会给你最好的表现。这就是为什么:

ArrayListVector each use an array to store the elements of the list. As a result, when an element is inserted into (or removed from) the middle of the list, the elements that follow must all be shifted accordingly. Vector is synchronized, so if a thread-safe implementation is 不是 needed, it is recommended to use ArrayList rather than Vector.

LinkedList另一方面,使用双链接列表来实现。结果,插入或删除元素仅需要更新立即在插入或移除元素之前立即的链接。

但是,值得注意的是,如果性能是至关重要的,最好只使用阵列并自己管理它,或者使用其中一个高性能第三方包装,如 tr 或者 HPPC..

为什么将敏感数据(如密码,社会安全号码等)存储在字符数组中而不是字符串中更安全?

在Java中,字符串是 不可变 并存储在字符串池中。这意味着,一旦创建了一个字符串,它会在内存中留在池中,直到收集垃圾。因此,即使您完成处理字符串值(例如,密码)后,它还可以在内存中可用,此后不确定时间(再次,直到收集垃圾),您没有真正的控制。因此,任何访问内存转储的任何人都可能会提取敏感数据并利用它。

相比之下,如果使用像字符数组这样的可变对象,例如,要存储值,可以将其设置为空白,以确保它不再保留在内存中。

申请加入Toptal'S开发网络

并享受可靠,稳定,远程自由Java开发人员工作。

申请自由职业者

What is the ThreadLocal class? How and why would you use it?

一个单一的 ThreadLocal instance can store different values for each thread independently. Each thread that accesses the get() 或者 set() method of a ThreadLocal instance is accessing its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or transaction ID). The example below, from the ThreadLocal Javadoc, generates unique identifiers local to each thread. A thread’s id is assigned the first time it invokes ThreadId.get() 和 remains unchanged on subsequent calls.

public class ThreadId {
    // Next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // Thread local variable containing each thread's ID
    private static final ThreadLocal<Integer> threadId =
        new ThreadLocal<Integer>() {
            @Override protected Integer initialValue() {
                return nextId.getAndIncrement();
        }
    };

    // Returns the current thread's unique ID, assigning it if necessary
    public static int get() {
        return threadId.get();
    }
}

只要线程活动并且可访问Threadlocal实例,每个线程都会对其线程局部变量的副本保持隐式引用;一个线程消失后,其线程本地实例的所有副本都会受到垃圾收集(除非存在对这些副本的其他引用)。

What is the volatile keyword? How and why would you use it?

在Java中,每个线程都有自己的堆栈,包括它可以访问的自己的变量副本。 When the thread is created, it copies the value of all accessible variables into its own stack. The volatile keyword basically says to the JVM “Warning, this variable may be modified in another Thread”.

In all versions of Java, the volatile keyword guarantees global ordering on reads and writes to a variable. This implies that every thread accessing a volatile field will read the variable’s current value instead of (potentially) using a cached value.

In Java 5 or later, volatile reads and writes establish a 发生在之前 关系,很像获取和发布互斥锁。

Using volatile may be faster than a lock, but it will not work in some situations. The range of situations in which volatile is effective was expanded in Java 5; in particular, 双重检查锁定 now works correctly.

Volatile关键字对64位类型非常有用,如很长而双倍,因为它们以两种操作写入。没有volatile关键字,您风险陈旧或无效值。

One common example for using volatile is for a flag to terminate a thread. If you’ve started a thread, and you want to be able to safely interrupt it from a different thread, you can have the thread periodically check a flag (i.e., to stop it, set the flag to true). By making the flag volatile, you can ensure that the thread that is checking its value will see that it has been set to true 没有 even having to use a synchronized block. For example:

public class Foo extends Thread {
    private volatile boolean close = false;
    public void run() {
        while(!close) {
            // do work
        }
    }
    public void close() {
        close = true;
        // interrupt here if needed
    }
}

Compare the sleep()wait() methods in Java, including when and why you would use one vs. the other.

sleep() 是一个阻塞操作,可在共享对象的监视器/锁上保持指定的毫秒数。

wait(),另一方面只是 暂停 线程直到 任何一个 (a)已经过指定的毫秒数 或者 (b)它从另一个线程接收所需的通知(以何种第一个), 没有 保留在共享对象的监视器/锁上。

sleep() is most commonly used for polling, or to check for certain results, at a regular interval. wait() is generally used in multithreaded applications, in conjunction with 不是ify() / 不是ifyAll(), to achieve synchronization and avoid race conditions.

尾递归在功能上相当于迭代。由于Java尚未支持尾呼叫优化,因此介绍如何将简单的尾部递归函数转换为循环,以及为什么通常优先于另一个。

这里是典型递归函数的示例,计算算术系列1,2,3 ... n。请注意,在函数调用后,如何执行此外的添加。对于每个递归步骤,我们将另一帧添加到堆栈中。

public int sumFromOneToN(int n) {
  if (n < 1) {
    return 0;
  }

  return n + sumFromOneToN(n - 1);
}

当递归调用处于封闭上下文中的尾部位置时,会发生尾部递归 - 在函数调用自身之后,它不会执行额外的工作。也就是说,一旦基本情况完成,就会显而易见。例如:

public int sumFromOneToN(int n, int a) {
  if (n < 1) {
    return a;
  }

  return sumFromOneToN(n - 1, a + n);
}

Here you can see that a plays the role of the accumulator - instead of computing the sum on the way down the stack, we compute it on the way up, effectively making the return trip unnecessary, since it stores no additional state and performs no further computation. Once we hit the base case, the work is done - below is that same function, “unrolled”.

public int sumFromOneToN(int n) {
  int a = 0;

  while(n > 0) {
    a += n--;
  }
  
  return a;
}

Many functional languages natively support tail call optimization, however the JVM does not. In order to implement recursive functions in Java, we need to be aware of this limitation to avoid StackOverflowErrors. In Java, iteration is almost universally preferred to recursion.

如何交换两个数字变量的值 没有 使用任何其他变量?

You can swap two values ab 没有 using any other variables as follows:

a = a + b;
b = a - b;
a = a - b;

如何在Java中捕获由另一个线程抛出的异常?

这可以使用 thread.unctaughtExceptionHandler..

这是一个简单的例子:

// create our uncaught exception handler
thread.unctaughtExceptionHandler. handler = new thread.unctaughtExceptionHandler.() {
    public void uncaughtException(Thread th, Throwable ex) {
        System.out.println("Uncaught exception: " + ex);
    }
};

// create another thread
Thread otherThread = new Thread() {
    public void run() {
        System.out.println("Sleeping ...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.println("Interrupted.");
        }
        System.out.println("Throwing exception ...");
        throw new RuntimeException();
    }
};

// set our uncaught exception handler as the one to be used when the new thread
// throws an uncaught exception
otherThread.setUncaughtExceptionHandler(handler);

// start the other thread - our uncaught exception handler will be invoked when
// the other thread throws an uncaught exception
otherThread.start();

什么是Java ClassLoader?列出并解释三种类型的类加载器的目的。

java. ClassLoader. 是Java运行时环境的一部分,可将CASES(延迟加载)加载到JVM(Java虚拟机)中加载类。可以从本地文件系统,远程文件系统甚至网络加载类。

启动JVM后,使用三类加载器: 1. Bootstrap ClassLoader: 从文件夹加载核心Java API文件RT.jar。 2. 扩展ClassLoader: 从文件夹加载JAR文件。 3. 系统/应用程序ClassLoader: 从类路径环境变量中指定的路径加载JAR文件。

Is a finally block executed when an exception is thrown from a try block that does not have a catch block, and if so, when?

A finally block is executed even if an exception is thrown or propagated to the calling code block.

例子:

public class FinallyExecution {
	public static void main(String[] args) {	
		try{			
			FinallyExecution.divide(100, 0);}
		finally{
			System.out.println("finally in main");
		}
	}	
	public static void divide(int n, int div){
		try{
			int ans = n/div; }
		finally{
			System.out.println("finally of divide");
		}
	}
}

输出可以变化,是:

finally of divide
finally in main
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at exceptions.FinallyExecution.divide(FinallyExecution.java:20)
	at exceptions.FinallyExecution.main(FinallyExecution.java:9)

…或者…

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at exceptions.FinallyExecution.divide(FinallyExecution.java:20)
	at exceptions.FinallyExecution.main(FinallyExecution.java:9)
finally of divide
finally in main

设计抽象类时,为什么要避免在其构造函数内调用抽象方法?

这是初始化顺序的问题。子类构造函数尚未有机会运行,并且没有办法强制在父类之前运行它。考虑以下示例类:

        public abstract class Widget {
	        private final int cachedWidth;
	        private final int cachedHeight;
	
	        public Widget() {
	            this.cachedWidth = width();
	            this.cachedHeight = height();
	        }
	
	        protected abstract int width();
	        protected abstract int height();
	    }

This seems like a good start for an abstract Widget: it allows subclasses to fill in widthheight, 和 caches their initial values. However, look when you spec out a typical subclass implementation like so:

        public class SquareWidget extends Widget {
	        private final int size;
	
	        public SquareWidget(int size) {
	            this.size = size;
	        }
	
	        @Override
	        protected int width() {
	            return size;
	        }
	
	        @Override
	        protected int height() {
	            return size;
	        }
	    }

Now we’ve introduced a subtle bug: Widget.cachedWidthWidget.cachedHeight will always be zero for SquareWidget instances! This is because the this.size = size assignment occurs the Widget constructor runs.

避免在抽象类的构造函数中调用抽象方法,因为它限制了如何实现抽象方法。

在通用类型参数上施加了什么方案? Java给你这个的多少控制?

java.的通用类型参数是 不变. This means for any distinct types AB, G<A> is not a subtype or supertype of G<B>. As a real world example, List<String> is not a supertype or subtype of List<Object>. So even though String extends (i.e. is a subtype of) Object, both of the following assignments will fail to compile:

        List<String> strings = Arrays.<Object>asList("hi there");
        List<Object> objects = Arrays.<String>asList("hi there");

java.确实可以以某种方式为您提供一些控制 使用现场方差. On individual methods, we can use ? extends Type to create a 协商 范围。这是一个例子:

        public double sum(List<? extends Number> numbers) {
            double sum = 0;
            for (Number number : numbers) {
                sum += number.doubleValue();
            }
            return sum;
        }


        List<Long> longs = Arrays.asList(42L, 128L, -10L);
        double sumOfLongs = sum(longs);

Even though longs is a List<Long> 和 not List<Number>, it can be passed to sum.

Similarly, ? super Type lets a method parameter be 。考虑带回调参数的函数:

        public void forEachNumber(Callback<? super Number> callback) {
            callback.call(50.0f);
            callback.call(123123);
            callback.call((short) 99);
        }

forEachNumber allows Callback<Object> to be a subtype of Callback <Number>, which means any callback that handles a supertype of Number will do:

        forEachNumber(new Callback<Object>() {
            @Override public void call(Object value) {
                System.out.println(value);
            }
        });

Note, however, that attempting to provide a callback that handles only Long (a subtype of Number) will rightly fail:

        // fails to compile!
        forEachNumber(new Callback<Long>() { ... });

自由应用程序的使用现场方差可以防止通常出现在Java代码中的许多不安全的演员,并且在设计多个开发人员使用的接口时是至关重要的。

什么是静态初始化者,你什么时候用它们?

静态初始化程序使您有机会在类的初始加载期间运行代码,并且保证此代码只运行一次,并且在您的课程可以以任何方式访问之前将完成运行。

它们对于执行复杂静态对象的初始化是有用的,或者用静态注册表注册类型,因为JDBC驱动程序执行。

Suppose you want to create a static, immutable Map containing some feature flags. Java doesn’t have a good one-liner for initializing maps, so you can use static initializers instead:

        public static final Map<String, Boolean> FEATURE_FLAGS;
        static {
            Map<String, Boolean> flags = new HashMap<>();
            flags.put("frustrate-users", false);
            flags.put("reticulate-splines", true);
            flags.put(...);
            FEATURE_FLAGS = Collections.unmodifiableMap(flags);
        }

在同一类中,您可以重复声明静态字段的此模式并立即初始化它,因为允许多个静态初始化器。

If one needs a Set, how do you choose between HashSet vs. TreeSet?

At first glance, HashSet is superior in almost every way: O(1) add, removecontains, vs. O(log(N)) for TreeSet.

However, TreeSet is indispensable when you wish to maintain order over the inserted elements or query for a range of elements within the set.

Consider a Set of timestamped Event objects. They could be stored in a HashSet, with equalshashCode based on that timestamp. This is efficient storage and permits looking up events by a specific timestamp, but how would you get all events that happened on any given day? That would require a O(n) traversal of the HashSet, but it’s only a O(log(n)) operation with TreeSet using the tailSet method:

        public class Event implements Comparable<Event> {
            private final long timestamp;
    
            public Event(long timestamp) {
                this.timestamp = timestamp;
            }
    
            @Override public int compareTo(Event that) {
                return Long.compare(this.timestamp, that.timestamp);
            }
        }
       
        ...
	
        SortedSet<Event> events = new TreeSet<>();
        events.addAll(...); // events come in

        // all events that happened today
        long midnightToday = ...;
        events.tailSet(new Event(midnightToday));

If Event happens to be a class that we cannot extend or that doesn’t implement Comparable, TreeSet allows us to pass in our own Comparator:

        SortedSet<Event> events = new TreeSet<>(
                (left, right) -> Long.compare(left.timestamp, right.timestamp));

Generally speaking, TreeSet is a good choice when order matters and when reads are balanced against the increased cost of writes.

什么是方法引用,它们如何有用?

方法引用在Java 8中引入,允许构造函数和方法(静态或其他)用作λ。当方法参考匹配预期的签名时,它们允许其中丢弃Lambda的样板。

例如,假设我们有一个必须由关闭挂钩停止的服务。在Java 8之前,我们将有这样的代码:

        final SomeBusyService service = new SomeBusyService();
        service.start();

        onShutdown(new Runnable() {
            @Override
            public void run() {
                service.stop();
            }
        });

使用Lambdas,这可以很大减少:

        onShutdown(() -> service.stop());

However, stop matches the signature of Runnable.run (void return type, no parameters), and so we can introduce a method reference to the stop method of that specific SomeBusyService instance:

        onShutdown(service::stop);

这是简洁的(而不是冗长的代码),并清楚地传达正在发生的事情。

Method references don’t need to be tied to a specific instance, either; one can also use a method reference to an arbitrary object, which is useful in Stream operations. For example, suppose we have a Person class and want just the lowercase names of a collection of people:

        List<Person> people = ...

        List<String> names = people.stream()
                .map(Person::getName)
                .map(String::toLowerCase)
                .collect(toList());

复杂的Lambda也可以被推到静态或实例方法中,然后通过方法参考使用。这使得代码比在Lambda中“被困”更可重复使用和可测试。

因此,我们可以看到方法引用主要用于改善代码组织,清晰度和特定。

java. enums如何比整数常量更强大?如何使用这种能力?

枚举基本上是具有固定数量的实例的最终类。它们可以实现接口但无法扩展另一个类。

这种灵活性在实现策略模式方面非常有用,例如,当策略的数量是固定的时。考虑一本记录多种联系方式的地址簿。我们可以将这些方法作为枚举和附加字段,如在UI中显示图标的文件名,以及任何相应的行为,如如何通过该方法启动联系人:

        public enum ContactMethod {
            PHONE("telephone.png") {
                @Override public void initiate(User user) {
                    Telephone.dial(user.getPhoneNumber());
                }
            },
            EMAIL("envelope.png") {
                @Override public void initiate(User user) {
                    EmailClient.sendTo(user.getEmailAddress());
                }
            },
            SKYPE("skype.png") {
                ...
            };
    
            ContactMethod(String icon) {
                this.icon = icon;
            }
    
            private final String icon;
            
            public abstract void initiate(User user);
    
            public String getIcon() {
                return icon;
            }
        }

We can dispense with switch statements entirely by simply using instances of 接触Method:

        ContactMethod method = user.getPrimaryContactMethod();
        displayIcon(method.getIcon());
        method.initiate(user);

这只是可以使用枚举完成的开始。通常,枚举的安全性和灵活性意味着应该使用它们代替整数常数,并且可以通过自由利用抽象方法来消除交换机语句。

一个被另一个人“支持”的集合是什么意思?举例说明这个属性很有用。

如果一个集合返回另一个,则意味着一个人的变化反映在另一个中,反之亦然。

For example, suppose we wanted to create a whitelist function that removes invalid keys from a Map. This is made far easier with Map.keySet, which returns a set of keys that is backed by the original map. When we remove keys from the key set, they are also removed from the backing map:

        public static <K, V> Map<K, V> whitelist(Map<K, V> map, K... allowedKeys) {
            Map<K, V> copy = new HashMap<>(map);
            copy.keySet().retainAll(asList(allowedKeys));
            return copy;
        }

retainAll writes through to the backing map, and allows us to easily implement something that would otherwise require iterating over the entries in the input map, comparing them against allowedKey, etcetera.

Note, it is important to consult the documentation of the backing collection to see which modifications will successfully write through. In the example above, map.keySet().add(value) would fail, because we cannot add a key to the backing map without a value.

什么是反思?展示只能使用反射实现的功能的示例。

反射允许编程访问有关Java程序类型的信息。常用信息包括:类上可用的方法和字段,类的接口,以及类,字段和方法上的运行时保留的注释。

给出的例子可能包括:

  • 基于注释的序列化库通常将类字段映射到JSON键或XML元素(使用注释)。这些库需要反射来检查这些字段及其注释,并在序列化期间访问值。
  • Model-View-Controller frameworks call controller methods based on routing rules. These frameworks must use reflection to find a method corresponding to an action name, check that its signature conforms to what the framework expects (e.g. takes a Request object, returns a Response), and finally, invoke the method.
  • Dependency injection frameworks lean heavily on reflection. They use it to instantiate arbitrary beans for injection, check fields for annotations such as @Inject to discover if they require injection of a bean, and also to set those values.
  • 诸如Hibernate的对象关系映射器使用反射将数据库列映射到类的字段或getter / Setter对,并且可以通过读取类和吸收器名称来推断表和列名称。

具体代码示例可能是简单的,例如将对象的字段复制到地图中:

        Person person = new Person("Doug", "Sparling", 31);

        Map<String, Object> values = new HashMap<>();
        for (Field field : person.getClass().getDeclaredFields()) {
            values.put(field.getName(), field.get(person));
        }

        // prints {firstName=Doug, lastName=Sparling, age=31}
        System.out.println(values);

Such tricks can be useful for debugging, or for utility methods such as a toString method that works on any class.

除了实施通用图书馆,直接使用反射是罕见的,但它仍然是一个方便的工具。当这些机制失败时,反射知识也很有用。

但是,除非严格必要,否则通常是谨慎的,因为它可以将直接的编译器错误转化为运行时错误。

嵌套类可以是静态或非静态的(也称为内部类)。你如何决定使用哪个?有关系吗?

MINES类之间的关键差异可以完全访问封闭类的字段和方法。这对于事件处理程序来说,这可以方便,但是以成本:内部类的每个实例都保留并需要引用其封闭类。

考虑到这一成本,我们应该更喜欢静态嵌套类的情况很多。当嵌套类的实例将超过封闭类的实例时,嵌套类应该是静态的,以防止内存泄漏。考虑该工厂模式的实施:

        public interface WidgetParser {
            Widget parse(String str);
        }
        
        public class WidgetParserFactory {
            public WidgetParserFactory(ParseConfig config) {
                ...
            }
        
            public WidgetParser create() {
                new WidgetParserImpl(...);
            }
        
            private class WidgetParserImpl implements WidgetParser {
                ...
                
                @Override public Widget parse(String str) {
                    ...
                }
            }
        }

At a glance, this design looks good: the WidgetParserFactory hides the implementation details of the parser with the nested class WidgetParserImpl. However, WidgetParserImpl is not static, and so if WidgetParserFactory is discarded immediately after the WidgetParser is created, the factory will leak, along with all the references it holds.

WidgetParserImpl should be made static, and if it needs access to any of WidgetParserFactory’s internals, they should be passed into WidgetParserImpl’s constructor instead. This also makes it easier to extract WidgetParserImpl into a separate class should it outgrow its enclosing class.

由于它们对封闭类的“隐藏”参考而导致的内部类也难以通过反射构建,并且该参考文献可以在基于反射的序列化期间被吸入,这可能不是预期的。

因此,我们可以看到是如何使嵌套类静态的决定很重要,而且一个旨在使嵌套类静态在围栏类“转义”或涉及到这些嵌套类上的反射。

What is the difference between String s = "Test"String s = new String("Test")? Which is better and why?

In general, String s = "Test" is more efficient to use than String s = new String("Test").

In the case of String s = "Test", a String with the value “Test” will be created in the String pool. If another String with the same value is then created (e.g., String s2 = "Test"), it will reference this same object in the String pool.

However, if you use String s = new String("Test"), in addition to creating a String with the value “Test” in the String pool, that String object will then be passed to the constructor of the String Object (i.e., new String("Test")) and will create another String object (not in the String pool) with that value. Each such call will therefore create an additional String object (e.g., String s2 = new String("Test") would create an addition String object, rather than just reusing the same String object from the String pool).

有更多的采访,而不是棘手的技术问题,因此这些是仅作为指导。不是每个值得招聘的“A”候选人将能够回答他们所有人,也不回答他们所有保证“A”候选人。在一天结束时, 招聘仍然是艺术,科学 - 以及很多工作.

提交面试问题

提交的问题和答案可能需要审查和编辑,可能会或可能不会被选中以在Toptal,LLC的唯一自行决定酌情选择。

* 各个领域都需要

凯莉安马丁

自由替代java开发人员

美国自从2018年5月18日起的Toptal自由职业者Java开发商

Kelly拥有12年的经验,担任软件工程师,主要关注C ++,C和Java。她'除了并发系统外,还在桌面应用程序上工作,包括航空网中嵌入式系统的多线程系统,集群或网络。

展示更多

Radek Ostrowski.

自由替代java开发人员

澳大利亚自由职业者Java开发商自2014年7月25日起以来Toptal

Radek是一个认证的Toptal Blockchain工程师对Ethereum和智能合同特别感兴趣。在菲亚特世界中,他在大数据/机器学习项目中经验丰富。他是两个不同国际IBM Apache Spark比赛的三重赢家,Playstation的共同创造者4'在澳大利亚,波兰和塞尔维亚的会议的后端,一个成功的Hackathon竞争对手和演讲者。

展示更多

Rizwan Rizvi.

自由替代java开发人员

美国自从2018年5月21日起在Toptal的自由职业者Java开发人员

Rizwan通过明确的思维,创新方法,加强组织不同部分之间的沟通,克里兹万具有克服复杂挑战。在他的职业生涯中,他优化了IT专业人员多样化和分散团队的努力,一直在充满挑战环境中有利地交付项目。

展示更多

寻找Java开发人员?

寻找 java.开发人员?查看Toptal的Java开发人员。

toptal连接 最佳 3% 世界各地的自由人才。

加入Toptal社区。

学到更多