Java 诞生距今已有 25 年,但它仍然长期占据着“天下第一”编程语言的宝座。只是其统治地位并非坚不可摧,反倒可以说是危机四伏。云原生时代,Java 技术体系的许多前提假设都受到了挑战,目前已经有可预见的、足以威胁动摇其根基的潜在可能性正在酝酿。同时,像 Golang、Rust 这样的新生语言,以及 C、C++、C#、Python 等老对手也都对 Java 的市场份额虎视眈眈。面对危机,Java 正在尝试哪些变革?未来,Java 是会继续向前、再攀高峰,还是由盛转衰? 在今天由极客邦科技举办的QCon全球软件开发大会2020(深圳站)上,远光软件研究院院长、《深入理解 Java 虚拟机》系列书籍作者周志明发表了主题演讲《云原生时代的 Java》,以下内容为演讲整理。 今天,25 岁的 Java 仍然是最具有统治力的编程语言,长期占据编程语言排行榜的首位,拥有一千二百万的庞大开发者群体,全世界有四百五十亿部物理设备使用着 Java 技术,同时,在云端数据中心的虚拟化环境里,还运行着超过两百五十亿个 Java 虚拟机的进程实例 (数据来自Oracle的WebCast)。 以上这些数据是 ....
1.Java泛型的实现方法:类型擦除 大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。 如在代码中定义 List<Object>和 List<String>等类型,在编译后都会变成 List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法在运行时刻出现的类型转换异常的情况,类型擦除也是Java的泛型与C++模板机制实现方式之间的重要区别。 1-2.通过两个例子证明Java类型的类型擦除 例1.原始类型相等 public class Test { public static void main(String[] args) { ArrayList<String> list1 = new ArrayList<Str....
题目描述 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 示例 1: 输入:[1,2,3,1] 输出:4 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。 示例 2: 输入:[2,7,9,3,1] 输出:12 解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 偷窃到的最高金额 = 2 + 9 + 1 = 12 。 解法 动态规划写法 public int rob(int[] nums) { int length = nums.length; if (length <=0) { return 0; } if (length < 2) {....
给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。 找到所有在 [1, n] 范围之间没有出现在数组中的数字。 您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。 示例: 输入: [4,3,2,7,8,2,3,1] 输出: [5,6] 解法 遍历每个元素,对索引进行标记 将对应索引位置的值变为负数; 遍历下索引,看看哪些索引位置上的数不是负数的。 位置上不是负数的索引,对应的元素就是不存在的。 public List<Integer> findDisappearedNumbers(int[] nums) { List<Integer> res = new ArrayList<>(); for(int num: nums) { int index = Math.abs(num) - 1; if (nums[index] > 0) { nums[index] = nums[index] ....
题目 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 示例 1: 输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2] 示例 2: 输入: nums = [1], k = 1 输出: [1] 解法 利用hash表记录每个元素出现的次数,维护一个k大的优先队列, 优先队列中维护每个元素及出现次数,根据出现次数排序,取前K个即可。 public int[] topKFrequent(int[] nums, int k) { Map<Integer, Integer> countMap = new HashMap<>(); for(int num : nums) { countMap.put(num, countMap.getOrDefault(num, 0) + 1); } PriorityQueue<int []> queue = new PriorityQueue<>(new Comparator<int []>() { public int co....
题目描述 在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 示例 1: 输入: [3,2,1,5,6,4] 和 k = 2 输出: 5 示例 2: 输入: [3,2,3,1,2,4,5,5,6] 和 k = 4 输出: 4 解题 排序后取第k大值 public int findKthLargest(int[] nums, int k) { Arrays.sort(nums); return nums[nums.length - k]; } 利用堆排序 public int findKthLargest(int[] nums, int k) { PriorityQueue<Integer> queue = new PriorityQueue<>(); for(int i=0; i<nums.length; i++) { queue.add(nums[i]); if (queue.size() > k) { queue.poll(); } } return queu....
题目描述: 给你一个链表和一个特定值 x ,请你对链表进行分隔,使得所有小于 x 的节点都出现在大于或等于 x 的节点之前。 你应当保留两个分区中每个节点的初始相对位置。 示例: 输入:head = 1->4->3->2->5->2, x = 3 输出:1->2->2->4->3->5 解法: 只需要遍历链表的所有节点,小于x的放到一个小的链表中,大于等于x的放到一个大的链表中,最后再把这两个链表串起来即可。 代码 public ListNode partition(ListNode head, int x) { //小链表的头 ListNode smallHead = new ListNode(0); //大链表的头 ListNode bigHead = new ListNode(0); //小链表的尾 ListNode smallTail = smallHead; //大链表的尾 ListNode bigTail = bigHead; //遍历head链表 while (head != null) { i....
题目描述 给定一个经过编码的字符串,返回它解码后的字符串。 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。 此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。 示例 1: 输入:s = "3[a]2[bc]" 输出:"aaabcbc" 示例 2: 输入:s = "3[a2[c]]" 输出:"accaccacc" 示例 3: 输入:s = "2[abc]3[cd]ef" 输出:"abcabccdcdcdef" 示例 4: 输入:s = "abc3[cd]xyz" 输出:"abccdcdcdxyz" 解法: 字符串有多层嵌套的情况,利用栈解决。使用两个栈,一个数字栈,一个字符串栈 public String decodeString(String s) { StringBuilde....
题目描述: 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 思路: 代码: public int trap(int[] height) { int ans = 0, current = 0; Deque<Integer> stack = new LinkedList<Integer>(); while (current < height.length) { while (!stack.isEmpty() && height[current] > height[stack.peek()]) { int top = stack.pop(); if (stack.isEmpty()) { break; } int distance = current - stack.peek() - 1; int bounded_height = Math.min(height[current], height[stack.peek()]) - height[top]; ans += distance *....
题目 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 push(x) —— 将元素 x 推入栈中。 pop() —— 删除栈顶的元素。 top() —— 获取栈顶元素。 getMin() —— 检索栈中的最小元素。 解法: 我们只需要设计一个数据结构,使得每个元素 a 与其相应的最小值 m 时刻保持一一对应。因此我们可以使用一个辅助栈,与元素栈同步插入与删除,用于存储与每个元素对应的最小值。 当一个元素要入栈时,我们取当前辅助栈的栈顶存储的最小值,与当前元素比较得出最小值,将这个最小值插入辅助栈中; 当一个元素要出栈时,我们把辅助栈的栈顶元素也一并弹出; 在任意一个时刻,栈内元素的最小值就存储在辅助栈的栈顶元素中。 class MinStack { Stack<Integer> stack; Stack<Integer> minStack; /** initialize your data structure here. */ public MinStack() { stack = new Stack....
题目 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 示例 2: 输入: "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 示例 3: 输入: "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。 解法-滑动窗口 什么是滑动窗口? 其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列! 如何移动? 我们只要把队列的左边的元素移出就行了,直到满足题目要求! 一直维持这样的队列,找出队列出现最长的长度时候,求出解! 时间复杂度:O(n) public int lengthOfLongestSubstrin....
题目描述 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。 说明:不允许修改给定的链表。 进阶: 你是否可以使用 O(1) 空间解决此题? 解法: 1.哈希表 遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环 2.双指针 代码: public ListNode detectCycle(ListNode head) { ListNode slow = head; ListNode fast = head; while(fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; if (slow == fast) { fast = head; while(fast != slow) ....
题目描述 给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。 示例: 给定一个链表: 1->2->3->4->5, 和 n = 2. 当删除了倒数第二个节点后,链表变为 1->2->3->5. 解法: 1. 递归解法: int cur = 0; public ListNode removeNthFromEnd(ListNode head, int n) { if (head == null) { return null; } head.next = removeNthFromEnd(head.next, n); cur++; if(n == cur) { return head.next; } return head; } 运行结果: 2. 快慢指针 利用快慢指针,快指针先走n不,然后快慢指针一起走,当快指针到链表尾部时,slow刚好到n-1的位置。删除slow.next 即可 public ListNode removeNthFromEnd(ListNode head, int n) { if (he....
题目描述: 给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。 如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。 您可以假设除了数字 0 之外,这两个数都不会以 0 开头。 示例: 输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) 输出:7 -> 0 -> 8 原因:342 + 465 = 807 解法: 两个链表顺序遍历,对应两个节点的值相加作为新链表的值,如果产生进位,需要加到下一位。 sumVal / 10 等于1就是产生了进位,为0则没有进位 sumVal % 10则是新节点的值 构造root空节点,作为新链表的头节点(最终返回root.next节点) 构造游标节点的作用 ? 为了标记处理新节点的当前位置 为什么不复用root节点新建了游标节点?这样做最终返回新节点时找不到头节点 Java代码如下: public ListNode addTwoNumbers(List....
题目描述: 给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。 进阶: 你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗? 解法: 利用归并排序,从中间节点断开,分别排序,排序时利用递归。再合并排序结果。 public ListNode sortList(ListNode head) { // 1、递归结束条件 if (head == null || head.next == null) { return head; } // 2、找到链表中间节点并断开链表 & 递归下探 ListNode midNode = middleNode(head); ListNode rightHead = midNode.next; midNode.next = null; ListNode left = sortList(head); ListNode right = sortList(rightHead); // 3、当前层业务操作(合并有序链表) return mergeTwoList....
题目描述: 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 示例: 输入: [0,1,0,3,12] 输出: [1,3,12,0,0] 说明: 必须在原数组上操作,不能拷贝额外的数组。 尽量减少操作次数。 解法: 先遍历数据,把不为空的数字移动到数组前面,记录下不为空的个数j,大于j小于nums.length 的位置都填充为零 public void moveZeroes(int[] nums) { int j = 0; int length = nums.length; for(int i = 0; i < length; i++) { if (nums[i] != 0) { nums[j++] = nums[i]; } } while(j < length) { nums[j++] = 0; } } 运行结果:
题目描述 请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。 解法 public void deleteNode(ListNode node) { node.val = node.next.val; node.next = node.next.next; } 运行结果:
文档 本工程采用DDD的架构风格,经过组内DDD实践的沉淀形成的工程规范 DDD实践的参考文档详见: DDD实践参考文档 工程结构: --client/ |-- src |----|-- main |----|----|-- java |----|----|----|-- com.sankuai.crayfish.xxxx |----|----|----|----|-- service -- thrift服务 |----|----|----|----|-- model -- thrift对象定义 --server/ |-- deploy |-- src |----|-- main |----|----|-- java |----|----|----|-- com.sankuai.crayfish.xxxx |----|----|----|-- application -- 应用层 |----|----|----|----|-- service -- 应用服务 |----|----|----|----|-- factory -- 对象转换工程(非必须) |----|----|----|....
背景 X项目组后端工程都是根据DDD的规范搭建的工程,整体结构上是统一的,但是在实现细节上,每个人习惯不同,代码风格有很大差异,导致工程之间的风格迥异,维护成本变高,因此需要梳理一套组内通用的项目工程规范,通过规范让编码风格统一 目标 建立一套组内通用的工程规范,组内成员达成一致,严格按照规范执行,以达到提供代码可读性、工程可维护性的目的。同时提高协作沟通效率。 调研 领域驱动设计调研 结论:需要根据自己的业务需求,建立适合业务场景的架构模式。没有统一的答案,每个团队在DDD实践上都有自己的理解,并没有统一的标准 1.规范组成 主要有两大部分组成: 开发规范 运维规范 名词解释 规范约束词: 强制: 必须遵守 推荐: 建议的做法,通常情况下是最合理的 参考: 提供一种综合考虑较优的方案 2.DDD工程实践概述 领域驱动-补充资料-分层架构的背景 分为四层: (1)用户界面层(或表示层) (2)应用层 (3)领域层 (4)基础设施层 各层的关联方式: 各层之间是松散连接的 层与层之间的依赖关系只能是单向的 上层可以直接使用或操作下层元素,方法是通过调用下层元素的公共接口,保持对下层元素的....
分层领域模型命名约定 DDD分层领域模型规约: DTO(Data Transfer Object ):Thrift/pigeon 接口定义对象命名方式,命名方式XxxDTO DO(Data Object):持久层对象命名方式。命名方式XxxDO Entity(实体):代表一个领域实体。命名方式XxxEntity ValueObject(值对象):代表一个领域值对象。命名方式XxxValue 应用层 1.【强制】禁止在应用层写任何领域逻辑,仅负责协调领域模型对象,通过它们的领域能力来组合完成一个完整的应用目标 2.【强制】与横切关注点相关的内容应该放在应用层 3.【强制】thrift接口参数校验属于横切关注点,应该放在应用服务来做 实体 1.【强制】实体对象的命名以Entity结尾。比如UserEntity、CompanyEntity 2.【推荐】Entity<->DO转换建议使用Mapstruct(参考本文【mapstruct对象转换示例】),除非有足够的理由不使用。 3.【推荐】实体尽量使用充血模型。属于实体的行为必须放在实体中,不能放在聚合根中,否则会造成实体贫血 4.....