21个基本的Java面试问题 *

最好的Java开发人员和工程师可以回答的全部来源的基本问题. 在我们社区的推动下,我们鼓励专家提交问题并提供反馈.

现在就聘请一名顶级Java开发人员
Toptal logo是顶级自由软件开发人员的专属网络吗, designers, finance experts, product managers, 和世界上的项目经理. 顶级公司雇佣Toptal自由职业者来完成他们最重要的项目.

Interview Questions

1.

描述和比较故障快速迭代器和故障安全迭代器. Give examples.

View answer

两者的主要区别 fail-fast and fail-safe 迭代器是指是否可以修改集合 while it is being iterated. Fail-safe iterators allow this; fail-fast iterators do not.

  • Fail-fast 迭代器直接作用于集合本身. During iteration, 快速失败迭代器一旦意识到集合被修改就会失败.e.(当意识到成员已被添加、修改或删除时),并抛出一个 并发修改异常。. Some examples include ArrayList, HashSet, and HashMap (most JDK1.4 .集合的实现是快速故障的).

  • Fail-safe iterates operate on a cloned copy 的集合,因此做 not 如果在迭代期间修改了集合,则抛出异常. 返回的迭代器 ConcurrentHashMap or CopyOnWriteArrayList.

2.

ArrayList, LinkedList, and Vector 的所有实现吗 List interface. 在列表中添加和删除元素时,哪一种方法最有效? 解释你的答案,包括你可能知道的任何其他选择.

View answer

Of the three, LinkedList 通常会给你最好的表现吗. Here’s why:

ArrayList and Vector 每个都使用一个数组来存储列表中的元素. As a result, 当一个元素被插入(或从)列表中间移除时, 接下来的元素都必须进行相应的移动. Vector 是同步的,所以如果线程安全的实现是 not 需要时,建议使用 ArrayList rather than Vector.

LinkedList另一方面,它是使用双重链表实现的. As a result, 插入或删除元素只需要更新被插入或删除元素前后的链接.

However, 值得注意的是,如果性能是如此关键, 最好使用数组并自己管理它, 或者使用一个高性能的第三方软件包,比如 Trove or HPPC.

3.

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

View answer

In Java, Strings are immutable 并存储在String池中. What this means is that, once a String is created, 它会一直留在内存池中,直到被垃圾收集. 因此,即使在处理完字符串值(e.g., the password), 此后,它在内存中可用一段不确定的时间, 直到你无法真正控制的垃圾收集. Therefore, 任何有权访问内存转储的人都有可能提取敏感数据并加以利用.

In contrast, 如果你使用可变对象,比如字符数组, for example, to store the value, 您可以将其设置为空白,一旦您完成它,它将不再保留在内存中.

申请加入Toptal的发展网络

并享受可靠、稳定、远程 自由Java开发人员职位

Apply as a Freelancer
4.

What is the ThreadLocal class? 你会怎么用,为什么用?

View answer

A single ThreadLocal 实例可以为每个线程独立存储不同的值. 的每个线程 get() or set() method of a ThreadLocal 实例正在访问它自己的、独立初始化的变量副本. ThreadLocal 实例通常是类中的私有静态字段,它们希望将状态与线程(例如线程)相关联.g.(用户ID或事务ID). 下面的例子,来自 ThreadLocal Javadoc,生成每个线程的本地唯一标识符. 线程的id是在它第一次调用时分配的 ThreadId.get() 在随后的呼叫中保持不变.

public class ThreadId {
    //下一个要分配的线程ID
    private static final AtomicInteger nextId = new AtomicInteger(0);

    //包含每个线程ID的线程局部变量
    private static final ThreadLocal threadId =
        new ThreadLocal() {
            @覆盖受保护的整数initialValue() {
                return nextId.getAndIncrement();
        }
    };

    //返回当前线程的唯一ID,必要时赋值
    public static int get() {
        return threadId.get();
    }
}

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, 它的线程本地实例的所有副本都服从于垃圾收集(除非存在对这些副本的其他引用)。.

5.

What is the volatile keyword? 你会怎么用,为什么用?

View answer

在Java中,每个线程都有自己的堆栈,包括它可以访问的变量的副本. 当创建线程时,它将所有可访问变量的值复制到自己的堆栈中. The volatile 关键字基本上对JVM说"警告,这个变量可能在另一个线程中被修改".

在所有Java版本中, volatile 关键字保证对变量进行读写时的全局排序. 这意味着每个访问volatile字段的线程都将读取变量的当前值,而不是(潜在地)使用缓存值.

In Java 5 or later, volatile 读和写建立一个 happens-before 关系,很像获取和释放互斥锁.

Using volatile 可能比锁快,但在某些情况下不起作用. The range of situations in which volatile is effective was expanded in Java 5; in particular, double-checked locking now works correctly.

volatile关键字对于64位类型(如long和double)也很有用,因为它们是在两个操作中写入的. 如果没有volatile关键字,您将面临过期或无效值的风险.

的一个常见示例 volatile 是否使用标志来终止线程. If you’ve started a thread, 您希望能够从另一个线程安全地中断它, 您可以让线程定期检查一个标志(例如.e.要阻止它,就把旗子设为 true). 通过使旗子易挥发, 您可以确保检查其值的线程将看到它已被设置为 true 甚至不需要使用同步块. For example:

类Foo扩展线程
    Private volatile Boolean close = false;
    public void run() {
        while(!close) {
            // do work
        }
    }
    public void close() {
        close = true;
        //如果需要,在这里中断
    }
}
6.

Compare the sleep() and wait() 方法,包括何时以及为什么使用一个方法与. the other.

View answer

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

wait(),另一方面,简单 pauses the thread until either (a)指定的毫秒数已经过去 or (b)它从另一个线程接收到所需的通知(以先到者为准); without 保持对共享对象的监视器/锁的持有.

sleep() 最常用于轮询,或定期检查某些结果. wait() 通常用于多线程应用程序,与 notify() / notifyAll(),实现同步,避免竞态条件.

7.

尾递归在功能上等同于迭代. 因为Java还不支持尾部调用优化, 描述如何将一个简单的尾递归函数转换为一个循环,以及为什么一个通常比另一个更受欢迎.

View answer

下面是一个典型递归函数的例子,计算等差数列1,2,3…N. 注意在函数调用之后是如何执行加法的. 对于每个递归步骤,我们向堆栈中添加另一帧.

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

  返回n + sumfrommoneton (n - 1);
}

当递归调用位于其封闭上下文中的尾部位置时,即在函数调用自身之后,就会发生尾递归, 它不执行任何额外的工作. 也就是说,一旦基本情况完成,解决方案就显而易见了. For example:

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

  返回sumfrommoneton (n - 1, a + n);
}

Here you can see that a 扮演累加器的角色——而不是在堆栈向下的过程中计算总和, we compute it on the way up, 有效地使回程不必要, 因为它不存储额外的状态,也不执行进一步的计算. 一旦我们达到基本情况,工作就完成了——下面是同一个函数,“展开”.

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

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

许多函数式语言本身支持尾部调用优化,但是JVM不支持. 为了在Java中实现递归函数,我们需要意识到要避免这种限制 StackOverflowErrors. 在Java中,迭代几乎普遍优于递归.

8.

如何交换两个数值变量的值 without using any other variables?

View answer

You can swap two values a and b 不使用任何其他变量,如下所示:

a = a + b;
b = a - b;
a = a - b;
9.

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

View answer

This can be done using Thread.UncaughtExceptionHandler.

Here’s a simple example:

//创建未捕获的异常处理程序
Thread.UncaughtExceptionHandler handler =新线程.UncaughtExceptionHandler () {
    公共void uncaughtException(线程th, Throwable ex) {
        System.out.println("未捕获的异常:" + ex);
    }
};

// create another 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(“抛出异常 ...");
        抛出新的RuntimeException();
    }
};

//将未捕获的异常处理程序设置为新线程运行时使用的异常处理程序
//抛出未捕获的异常
otherThread.setUncaughtExceptionHandler(处理器);

//启动另一个线程——我们未捕获的异常处理程序将被调用
//另一个线程抛出未捕获的异常
otherThread.start();
10.

什么是Java类加载器? 列出并解释这三种类型的类装入器的用途.

View answer

The Java Classloader 是Java运行时环境的一部分,它按需将类(惰性加载)加载到JVM (Java虚拟机)中。. 类可以从本地文件系统、远程文件系统甚至web加载.

当JVM启动时,使用三个类加载器: 1. Bootstrap Classloader: Loads core java API file rt.jar from folder. 2. Extension Classloader: 从文件夹加载jar文件. 3. 系统/应用程序类加载器: 从CLASSPATH环境变量中指定的路径加载jar文件.

11.

Is a finally 类抛出异常时执行的块 try block that does not have a catch block, and if so, when?

View answer

A finally 块执行,即使抛出异常或将异常传播到调用代码块.

Example:

公共类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
线程"main"中的异常.lang.arithmelceexception: / by 0
	at exceptions.FinallyExecution.divide(FinallyExecution.java:20)
	at exceptions.FinallyExecution.main(FinallyExecution.java:9)

…or…

线程"main"中的异常.lang.arithmelceexception: / by 0
	at exceptions.FinallyExecution.divide(FinallyExecution.java:20)
	at exceptions.FinallyExecution.main(FinallyExecution.java:9)
finally of divide
finally in main
12.

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

View answer

这是初始化顺序的问题. 子类构造函数还没有机会运行,也没有办法强制它在父类之前运行它. 考虑下面的示例类:

        公共抽象类Widget {
	        private final int cachedWidth;
	        private final int cachedHeight;
	
	        public Widget() {
	            this.cachedWidth = width();
	            this.cachedHeight = height();
	        }
	
	        受保护的抽象int宽度();
	        受保护抽象int height();
	    }

这似乎是一个抽象Widget的良好开端:它允许子类填充 width and height,并缓存它们的初始值. 然而,看看当你指定一个典型的子类实现,像这样:

        公共类SquareWidget扩展Widget {
	        private final int size;
	
	        公共SquareWidget(int size) {
	            this.size = size;
	        }
	
	        @Override
	        protected int width() {
	            return size;
	        }
	
	        @Override
	        protected int height() {
	            return size;
	        }
	    }

现在我们引入了一个微妙的bug: Widget.cachedWidth and Widget.cachedHeight will always be zero for SquareWidget instances! This is because the this.size = size assignment occurs after the Widget constructor runs.

避免在抽象类的构造函数中调用抽象方法, 因为它限制了这些抽象方法的实现方式.

13.

泛型类型参数的变化是多么大啊? Java在这方面给了你多大的控制权?

View answer

Java的泛型类型参数是 invariant. 这意味着对于任何不同的类型 A and B, G 不是子类型还是超类型 G. As a real world example, List 不是超类型还是子类型 List. So even though String extends (i.e. is a subtype of) Object,以下两个赋值将无法编译:

        List strings = Arrays.asList("hi there");
        List objects = Arrays.asList("hi there");


Java确实以形式提供了一些控制 use-site variance. 在个别方法上,我们可以使用 ? extends Type to create a covariant parameter. Here’s an example:

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


        List longs = Arrays.asList(42L, 128L, -10L);
        双sumOfLongs = sum(长);

Even though longs is a List and not List, it can be passed to sum.

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

        public void forEachNumber(Callback callback) {
            callback.call(50.0f);
            callback.call(123123);
            callback.call((short) 99);
        }

forEachNumber allows Callback to be a subtype of Callback ,这意味着任何处理超类型的回调 Number will do:

        forEachNumber(new Callback() {
            覆盖公共无效调用(对象值){
                System.out.println(value);
            }
        });


但是,请注意,尝试提供只处理 Long (a subtype of Number) will rightly fail:

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

自由地应用使用-站点差异可以防止Java代码中经常出现的许多不安全强制转换,并且在设计由多个开发人员使用的接口时至关重要.

14.

什么是静态初始化器,什么时候使用它们?

View answer

静态初始化器使您有机会在类的初始加载期间运行代码,它保证这些代码只运行一次,并且在可以以任何方式访问类之前完成运行.

它们对于执行复杂静态对象的初始化或使用静态注册表注册类型非常有用, as JDBC drivers do.

假设您想要创建一个静态的、不可变的 Map 包含一些特性标志. Java没有很好的一行代码来初始化映射, 所以你可以使用静态初始化器:

        public static final Map FEATURE_FLAGS;
        static {
            Map flags = new HashMap<>();
            flags.把(“挫伤用户”,假);
            flags.把(“reticulate-splines”,真正的);
            flags.put(...);
            FEATURE_FLAGS =集合.unmodifiableMap(flags);
        }

Within the same class, 您可以重复声明静态字段并立即初始化它的这种模式, 因为允许使用多个静态初始化式.

15.

If one needs a Set,你如何选择 HashSet vs. TreeSet?

View answer

At first glance, HashSet 几乎在各方面都很优秀。 add, remove and contains, vs. O(log(N)) for TreeSet.

However, TreeSet 当您希望维护插入元素的顺序或查询集合内的一系列元素时,是必不可少的.

Consider a Set of timestamped Event objects. They could be stored in a HashSet, with equals and hashCode based on that timestamp. 这是一种高效的存储方式,允许根据特定的时间戳查找事件, 但是你如何得到在某一天发生的所有事件? 这需要O(n)遍历 HashSet,但它只是一个O(log(n))的运算 TreeSet using the tailSet method:

        public class Event implements Comparable {
            私有最终长时间戳;
    
            公共事件(长时间戳){
                this.timestamp = timestamp;
            }
    
            @Override public int compareTo(事件){
                return Long.compare(this.timestamp, that.timestamp);
            }
        }
       
        ...
	
        SortedSet events = new TreeSet<>();
        events.addAll(...); // events come in

        //今天发生的所有事件
        long midnightToday = ...;
        events.tailSet(新事件(midnightToday));

If Event 碰巧是一个我们不能扩展或者没有实现的类 Comparable, TreeSet 允许我们通过我们自己的 Comparator:

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

Generally speaking, TreeSet 当顺序很重要,当读取与增加的写入成本相平衡时,这是一个好的选择吗.

16.

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

View answer

Java 8中引入了方法引用,并允许将构造函数和方法(静态或其他)用作lambdas. 它们允许在方法引用与预期签名匹配时丢弃lambda的样板.

例如,假设我们有一个必须由shutdown钩子停止的服务. 在Java 8之前,我们会有这样的代码:

        最后SomeBusyService服务=新的SomeBusyService();
        service.start();

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

使用lambda,这可以大大减少:

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

However, stop matches the signature of Runnable.run (void 返回类型,没有参数),因此我们可以引入一个方法引用 stop method of that specific SomeBusyService instance:

        onShutdown(service::stop);

这是简洁的(与冗长的代码相反),并清楚地传达了正在发生的事情.

方法引用不需要绑定到特定的实例, either; one can also use a method reference to an arbitrary object, which is useful in Stream operations. 例如,假设我们有a Person 类,并且只需要一组人的小写名字:

        List people = ...

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

复杂的lambda也可以被推入静态或实例方法,然后通过方法引用使用. 这使得代码比被“困”在lambda中的代码更易于重用和测试.

因此,我们可以看到方法引用主要用于改进代码组织, clarity and terseness.

17.

Java枚举为何比整型常量更强大? 如何使用此功能?

View answer

枚举本质上是具有固定数量实例的最终类. 它们可以实现接口,但不能扩展另一个类.

这种灵活性在实现策略模式时非常有用, for example, 当策略的数量是固定的. 考虑一个记录多种欧博体育app下载的地址簿. 我们可以将这些方法表示为枚举并附加字段, 比如要在UI中显示的图标的文件名, 以及任何相应的行为, 比如如何通过这个方法发起联系:

        公共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(字符串图标){
                this.icon = icon;
            }
    
            private final String icon;
            
            public abstract void initiate(User User);
    
            public String getIcon() {
                return icon;
            }
        }

We can dispense with switch 语句完全通过简单地使用实例 ContactMethod:

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

这只是枚举功能的开始. Generally, 枚举的安全性和灵活性意味着它们应该用来代替整型常量, 并且可以通过自由使用抽象方法来消除switch语句.

18.

一个系列得到另一个系列的“支持”意味着什么? 举例说明此属性何时有用.

View answer

如果一个集合支持另一个集合, 这意味着一个方面的变化会反映在另一个方面,反之亦然.

例如,假设我们想创建一个 whitelist 中删除无效键的函数 Map. 使用 Map.keySet,它返回一组由原始映射支持的键. 当我们从键集中删除键时,它们也会从后端映射中删除:

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

retainAll 写入到后备映射, 并且允许我们轻松地实现一些东西,否则就需要迭代输入映射中的条目, comparing them against allowedKey, etcetera.

Note, 重要的是要查阅后备集合的文档,以了解哪些修改将成功写入. In the example above, map.keySet().add(value) 会失败,因为我们不能在没有值的情况下向后端映射添加键.

19.

What is reflection? 给出一个只能使用反射实现的功能示例.

View answer

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

给出的例子可能包括:

  • 基于注释的序列化库通常将类字段映射到JSON键或XML元素(使用注释). 这些库需要反射来检查这些字段及其注释,并在序列化期间访问值.
  • 模型-视图-控制器框架基于路由规则调用控制器方法. 这些框架必须使用反射来找到与动作名称对应的方法, 检查它的签名是否符合框架的要求.g. takes a Request object, returns a Response),最后调用该方法.
  • 依赖注入框架严重依赖于反射. 它们使用它来实例化任意bean以进行注入,检查注释字段,例如 @Inject 以发现它们是否需要注入bean,并设置这些值.
  • 对象关系映射器(如Hibernate)使用反射将数据库列映射到字段或类的getter/setter对, 甚至可以通过读取类名和getter名来推断表名和列名, respectively.

具体的代码示例可以是一些简单的东西,比如将对象的字段复制到map中:

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

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

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

这些技巧对于调试或实用程序方法(如 toString 方法,该方法对任何类都有效.

除了实现泛型库, 直接使用反射很少,但它仍然是一个方便的工具. 当这些机制失效时,了解反射也很有用.

However, 除非绝对必要,否则避免反思通常是谨慎的做法, 因为它可以将直接的编译器错误转换为运行时错误.

20.

嵌套类可以是静态的或非静态的(也称为内部类)。. 你如何决定使用哪一个? Does it matter?

View answer

两者的关键区别在于,内部类可以完全访问外围类的字段和方法. 这对于事件处理程序来说很方便, 但这是有代价的:内部类的每个实例都保留并需要对其封闭类的引用.

考虑到这个代价,在很多情况下我们应该选择静态嵌套类. 当嵌套类的实例将比封闭类的实例更长寿时, 嵌套类应该是静态的,以防止内存泄漏. 考虑工厂模式的实现:

        公共接口WidgetParser
            Widget parse(String str);
        }
        
        公共类WidgetParserFactory {
            公共WidgetParserFactory(ParseConfig config) {
                ...
            }
        
            WidgetParser创建(){
                new WidgetParserImpl(...);
            }
        
            私有类WidgetParserImpl实现WidgetParser {
                ...
                
                @Override public Widget parse(String str) {
                    ...
                }
            }
        }

乍一看,这个设计看起来不错 WidgetParserFactory 用嵌套类隐藏解析器的实现细节 WidgetParserImpl. However, WidgetParserImpl is not static, and so if WidgetParserFactory 后立即丢弃 WidgetParser 创建时,工厂将泄漏,以及它保存的所有引用.

WidgetParserImpl 应该是静态的,如果它需要访问任何 WidgetParserFactory的内部,它们应该被传递到 WidgetParserImpl’s constructor instead. 这也使得提取更容易 WidgetParserImpl 如果它超出其封闭类,则将其放入单独的类中.

内部类也很难通过反射构造,因为它们对封闭类的“隐藏”引用, 在基于反射的序列化过程中,这个引用可能会被吸收, 这可能不是有意的.

因此,我们可以看到是否将嵌套类设置为静态的决定是很重要的, 并且,在实例将“逃避”封闭类或涉及对这些嵌套类的反射的情况下,应该将嵌套类设置为静态.

21.

两者的区别是什么 String s = "Test" and 字符串s = new String("Test")? Which is better and why?

View answer

In general, String s = "Test" 使用效率比 字符串s = new String("Test").

In the case of String s = "Test",将在String池中创建一个值为“Test”的String. 如果创建了另一个具有相同值的String (e.g., String s2 = "Test"),它将引用String池中的同一对象.

However, if you use 字符串s = new String("Test"), 除了在String池中创建值为“Test”的String之外, 然后将该String对象传递给String对象的构造函数(i.e., new String("Test")),并使用该值创建另一个String对象(不在String池中). 因此,每次这样的调用都将创建一个额外的String对象(例如.g., String s2 = new String("Test") 会创建一个附加字符串对象吗, 而不仅仅是从String池中重用同一个String对象).

面试不仅仅是棘手的技术问题, 所以这些只是作为一个指南. 并不是每一个值得雇佣的“A”候选人都能回答所有的问题, 回答所有问题也不能保证成为A级考生. At the end of the day, 招聘仍然是一门艺术,一门科学,需要大量的工作.

Why Toptal

厌倦了面试候选人? 不知道该问什么才能让你得到一份好工作?

让Toptal为你找到最合适的人.

现在就聘请一名顶级Java开发人员

我们的Java开发者专属网络

希望找到一份Java开发人员的工作?

让Toptal为你找到合适的工作.

Apply as a Java Developer

工作机会从我们的网络

提出面试问题

提交的问题和答案将被审查和编辑, 并可能会或可能不会选择张贴, 由Toptal全权决定, LLC.

* All fields are required

寻找Java开发人员?

Looking for Java Developers? 查看Toptal的Java开发人员.

Julie Wetherbee

Freelance Java Developer
United StatesToptal的自由Java开发人员 Since August 21, 2015

Julie在为各种规模的企业构建软件应用程序和领导工程团队方面拥有超过20年的经验. She has expertise in Java, JavaScript, C, C++, and Perl, 并且熟悉许多流行的框架. 最近,Julie为沃尔玛设计并实现了一个大规模的Oracle数据库分片解决方案.com.

Show More

Jean-François Savard

Freelance Java Developer
CanadaToptal的自由Java开发人员 Since March 24, 2016

jean - franois是一个充满激情的开发人员,他从14岁开始用Java编程,从那时起,他几乎没有一天不写代码. 尽管他在Java及其相关框架方面有独特的经验, 他对知识的渴望使他探索了计算机科学的几个方面, such as machine learning, data science, software architecture, 以及基于云的开发.

Show More

Claudio Aldana

Freelance Java Developer
United StatesToptal的自由Java开发人员 Since September 12, 2018

Claudio是一位专注于业务成果的经验丰富的IT专家, 还有扎实的工程背景. 他运用数据科学来优化客户满意度, product personalization, and customer churn. Claudio也是一名认证的SharePoint专家,曾与微软的知名客户合作过, 帮助他们最大限度地提高安全性, performance, and usability.

Show More

Toptal Connects the Top 3% 世界各地的自由职业人才.

Join the Toptal community.

Learn more